commit ef86554f9ab4da2611b62a266e4cfd2b5d5d1a07 Author: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Mon May 12 22:09:16 2025 +0200 Import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2216d9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +/.vs/ProjectEvaluation +/.vs/skyscraper8/DesignTimeBuild +/.vs/skyscraper8/FileContentIndex +/.vs/skyscraper8/v17 +*.exe +*.dll +*.pdb +/BlobStorages/skyscraper5.Data.Minio/bin/Debug/net5.0 +/BlobStorages/skyscraper5.Data.Minio/bin/Debug/net6.0 +/BlobStorages/skyscraper5.Data.Minio/bin/Debug/net8.0 +/BlobStorages/skyscraper5.Data.Minio/obj/Debug/net5.0 +/BlobStorages/skyscraper5.Data.Minio/obj/Debug/net6.0 +/BlobStorages/skyscraper5.Data.Minio/obj/Debug/net8.0 +/BlobStorages/skyscraper5.Data.Minio/obj +/DataTableStorages/skyscraper5.Data.MySql/bin/Debug/net5.0 +/DataTableStorages/skyscraper5.Data.MySql/bin/Debug/net8.0 +/DataTableStorages/skyscraper5.Data.MySql/obj/Debug/net5.0 +/DataTableStorages/skyscraper5.Data.MySql/obj/Debug/net6.0 +/DataTableStorages/skyscraper5.Data.MySql/obj/Debug/net8.0 +/DataTableStorages/skyscraper5.Data.MySql/obj +/DataTableStorages/skyscraper5.Data.PostgreSql/bin/Debug/net6.0 +/DataTableStorages/skyscraper5.Data.PostgreSql/bin/Debug/net8.0 +/DataTableStorages/skyscraper5.Data.PostgreSql/obj/Debug/net6.0 +/DataTableStorages/skyscraper5.Data.PostgreSql/obj/Debug/net8.0 +/DataTableStorages/skyscraper5.Data.PostgreSql/obj +/FactoryStorages/skyscraper5.Storage.MariaDbMinio/bin/Debug/net5.0 +/FactoryStorages/skyscraper5.Storage.MariaDbMinio/bin/Debug/net8.0 +/FactoryStorages/skyscraper5.Storage.MariaDbMinio/obj/Debug/net5.0 +/FactoryStorages/skyscraper5.Storage.MariaDbMinio/obj/Debug/net6.0 +/FactoryStorages/skyscraper5.Storage.MariaDbMinio/obj/Debug/net8.0 +/FactoryStorages/skyscraper5.Storage.MariaDbMinio/obj +/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/bin/Debug/net6.0 +/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/bin/Debug/net8.0 +/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/obj/Debug/net6.0 +/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/obj/Debug/net8.0 +/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/obj +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/SDL2-CS.dll.config +imgui.ini +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/runtimes/linux-x64/native +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/runtimes/osx-x64/native +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/runtimes/osx/native +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/skyscraper8.UI.ImGui.deps.json +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/skyscraper8.UI.ImGui.runtimeconfig.json +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/skyscraper8.deps.json +/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/skyscraper8.runtimeconfig.json +/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/bin/Debug/net6.0 +/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/bin/Debug/net8.0 +/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/obj/Debug/net6.0 +/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/obj/Debug/net8.0 +/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/obj +/Gimmicks/skyscraper5.Gimmicks.IptvCollector/bin/Debug/net6.0 +/Gimmicks/skyscraper5.Gimmicks.IptvCollector/bin/Debug/net8.0 +/Gimmicks/skyscraper5.Gimmicks.IptvCollector/obj/Debug/net6.0 +/Gimmicks/skyscraper5.Gimmicks.IptvCollector/obj/Debug/net8.0 +/Gimmicks/skyscraper5.Gimmicks.IptvCollector/obj +/Gimmicks/skyscraper5.LyngsatMapsScraper/bin/Debug/net6.0 +/Gimmicks/skyscraper5.LyngsatMapsScraper/bin/Debug/net8.0 +/Gimmicks/skyscraper5.LyngsatMapsScraper/obj/Debug/net6.0 +/Gimmicks/skyscraper5.LyngsatMapsScraper/obj/Debug/net8.0 +/Gimmicks/skyscraper5.LyngsatMapsScraper/obj +/Gimmicks/skyscraper5.RSR.IQTester/bin/Debug/net8.0 +/Gimmicks/skyscraper5.RSR.IQTester/obj/Debug/net6.0 +/Gimmicks/skyscraper5.RSR.IQTester/obj/Debug/net8.0 +/Gimmicks/skyscraper5.RSR.IQTester/obj +/GpsPlugins/skyscraper5.NmeaSharpWrapper/bin/Debug/net6.0 +/GpsPlugins/skyscraper5.NmeaSharpWrapper/bin/Debug/net8.0 +/GpsPlugins/skyscraper5.NmeaSharpWrapper/obj/Debug/net6.0 +/GpsPlugins/skyscraper5.NmeaSharpWrapper/obj/Debug/net8.0 +/GpsPlugins/skyscraper5.NmeaSharpWrapper/obj +/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/bin/Debug/net6.0 +/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/bin/Debug/net8.0 +/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/obj/Debug/net6.0 +/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/obj/Debug/net8.0 +/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/obj +/IoPlugins/skyscraper5.IO.StreamReader/bin/Debug/net6.0 +/IoPlugins/skyscraper5.IO.StreamReader/bin/Debug/net8.0 +/IoPlugins/skyscraper5.IO.StreamReader/obj/Debug/net6.0 +/IoPlugins/skyscraper5.IO.StreamReader/obj/Debug/net8.0 +/IoPlugins/skyscraper5.IO.StreamReader/obj +/MpePlugins/skyscraper5.Aprs/bin/Debug/net6.0 +/MpePlugins/skyscraper5.Aprs/bin/Debug/net8.0 +/MpePlugins/skyscraper5.Aprs/obj/Debug/net6.0 +/MpePlugins/skyscraper5.Aprs/obj/Debug/net8.0 +/MpePlugins/skyscraper5.Aprs/obj +/MpePlugins/skyscraper5.DNS/bin/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/bin/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/bin/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/obj/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/obj/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/obj +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/bin/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/bin/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/obj/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/obj/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/obj +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/bin/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/bin/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/obj/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/obj/Debug/net8.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/obj +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/bin/Debug/net6.0 +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/obj +/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/bin/Debug/net8.0 +/skyscraper8.Tests/bin/Debug/net8.0 +/skyscraper8.Tests/obj/Debug/net8.0 +/skyscraper8.Tests/obj +/skyscraper8/bin/Debug/net8.0 +/skyscraper8/obj diff --git a/BlobStorages/skyscraper5.Data.Minio/MinioObjectStorage.cs b/BlobStorages/skyscraper5.Data.Minio/MinioObjectStorage.cs new file mode 100644 index 0000000..fb6bc0c --- /dev/null +++ b/BlobStorages/skyscraper5.Data.Minio/MinioObjectStorage.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Minio; +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.Exceptions; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Skyscraper.Scraper.Storage.Split; + +namespace skyscraper5.Data +{ + public class MinioObjectStorage : ObjectStorage + { + private readonly IMinioClient _minioClient; + private readonly string _minioBucket; + private List _tasks; + private List droppedFiles; + + public MinioObjectStorage(IMinioClient minioClient, string minioBucket) + { + _minioClient = minioClient; + _minioBucket = minioBucket; + _tasks = new List(); + droppedFiles = new List(); + } + + private void WriteObject(string fullName, Stream buffer, string mime = "application/octet-stream", Dictionary optionalData = null) + { + PutObjectArgs putObjectArgs = new PutObjectArgs(); + putObjectArgs = putObjectArgs.WithBucket(_minioBucket); + putObjectArgs = putObjectArgs.WithObject(fullName); + putObjectArgs = putObjectArgs.WithStreamData(buffer); + putObjectArgs = putObjectArgs.WithObjectSize(buffer.Length); + putObjectArgs = putObjectArgs.WithContentType(mime); + putObjectArgs = putObjectArgs.WithHeaders(optionalData); + + lock (_tasks) + { + _tasks.Add(_minioClient.PutObjectAsync(putObjectArgs).ContinueWith(task => + { + droppedFiles.Add(fullName); + buffer.Close(); + buffer.Dispose(); + })); + } + + CleanTaskList(); + } + + private void CleanTaskList() + { + while (true) + { + bool done = true; + lock (_tasks) + { + foreach (Task task in _tasks) + { + if (task.IsCompleted) + { + _tasks.Remove(task); + Debug.WriteLine(String.Format("Removed completed Task: {0}", task.ToString())); + done = false; + break; + } + } + } + + if (done) + break; + } + } + + public bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId) + { + string combine = string.Join('/', "DSM-CC_Objects", networkId.ToString(), transportStreamId.ToString(), vfsFile.SourcePid.ToString(), vfsFile.ToString().Substring(1)); + combine = combine.Replace('\\', '/'); + if (FileExists(combine)) + return false; + + Dictionary extendedInfo = new Dictionary(); + extendedInfo.Add(nameof(vfsFile.ObjectLocation.CarouselId), vfsFile.ObjectLocation.CarouselId.ToString()); + extendedInfo.Add(nameof(vfsFile.ObjectLocation.ModuleId), vfsFile.ObjectLocation.ModuleId.ToString()); + extendedInfo.Add(nameof(vfsFile.ObjectLocation.Version), vfsFile.ObjectLocation.Version.ToString()); + extendedInfo.Add(nameof(vfsFile.ObjectLocation.ObjectKey), BitConverter.ToString(vfsFile.ObjectLocation.ObjectKey)); + + WriteObject(combine,new MemoryStream(vfsFile.FileContent),"application/octet-stream",extendedInfo); + return true; + } + + public void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result) + { + string combine = string.Join('/', "DSM-CC_Data", currentNetworkId.ToString(), currentTransportStreamId.ToString(), elementaryPid.ToString(), String.Format("{0}_V{1}.bin", moduleModuleId, moduleModuleVersion)); + if (FileExists(combine)) + return; + + WriteObject(combine, result); + + WantedModuleCoordinate coordinate = new WantedModuleCoordinate(currentNetworkId, currentTransportStreamId, elementaryPid, moduleModuleId, moduleModuleVersion); + knownModuleCoordinates.Add(coordinate); + wantedModuleCoordinates.Remove(coordinate); + } + + public bool FileExists(string combine) + { + if (definetlyKnownFiles == null) + definetlyKnownFiles = new HashSet(); + + if (definetlyKnownFiles.Contains(combine)) + return true; + + if (droppedFiles != null) + { + if (droppedFiles.Contains(combine)) + return true; + } + + StatObjectArgs soa = new StatObjectArgs().WithBucket(_minioBucket).WithObject(combine); + try + { + ObjectStat objectStat = _minioClient.StatObjectAsync(soa).Result; + definetlyKnownFiles.Add(combine); + return true; + } + catch (AggregateException e) + { + MinioException minioException = e.InnerExceptions[0] as MinioException; + switch (minioException.Response.Code) + { + case "NoSuchKey": + return false; + case "Forbidden": + return true; + default: + throw new NotImplementedException(minioException.Response.Code); + } + } + } + + public bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + if (wantedModuleCoordinates == null) + wantedModuleCoordinates = new HashSet(); + if (knownModuleCoordinates == null) + knownModuleCoordinates = new HashSet(); + + WantedModuleCoordinate coordinate = new WantedModuleCoordinate(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + if (wantedModuleCoordinates.Contains(coordinate)) + return true; + if (knownModuleCoordinates.Contains(coordinate)) + return false; + + string combine = String.Join('/', "DSM-CC_Data", currentNetworkId.ToString(), currentTransportStreamId.ToString(), elementaryPid.ToString(), String.Format("{0}_V{1}.bin", moduleId, moduleVersion)); + bool isWanted = !FileExists(combine); + if (isWanted) + { + wantedModuleCoordinates.Add(coordinate); + } + else + { + knownModuleCoordinates.Add(coordinate); + } + return isWanted; + } + + public bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid) + { + string combine = string.Join('/', "Framegrabs", currentNetworkId.ToString(), transportStreamId.ToString(), String.Format("{1}_{0}.jpg", mappingStreamElementaryPid, mappingProgramNumber)); + return FileExists(combine); + } + + public void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort pid, byte[] imageData) + { + string combine = string.Join('/', "Framegrabs", currentNetworkId.ToString(), transportStreamId.ToString(), String.Format("{1}_{0}.jpg", pid, mappingProgramNumber)); + WriteObject(combine, new MemoryStream(imageData), "image/jpeg"); + } + + public void WaitForCompletion() + { + while (_tasks.Count > 0) + { + _tasks[0].Wait(); + _tasks.RemoveAt(0); + } + wantedModuleCoordinates?.Clear(); + knownModuleCoordinates?.Clear(); + definetlyKnownFiles?.Clear(); + } + + private HashSet wantedModuleCoordinates; + private HashSet knownModuleCoordinates; + private HashSet definetlyKnownFiles; + struct WantedModuleCoordinate + { + private int currentNetworkId, currentTransportStreamId, elementaryPid; + private ushort moduleId; + private byte moduleVersion; + + public WantedModuleCoordinate(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + this.currentNetworkId = currentNetworkId; + this.currentTransportStreamId = currentTransportStreamId; + this.elementaryPid = elementaryPid; + this.moduleId = moduleId; + this.moduleVersion = moduleVersion; + } + + public bool Equals(WantedModuleCoordinate other) + { + return currentNetworkId == other.currentNetworkId && currentTransportStreamId == other.currentTransportStreamId && elementaryPid == other.elementaryPid && moduleId == other.moduleId && moduleVersion == other.moduleVersion; + } + + public override bool Equals(object obj) + { + return obj is WantedModuleCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + } + } + + private int uiVersion; + public void UiSetVersion(int version) + { + this.uiVersion = version; + } + + public object[] GetPluginConnector() + { + return new object[] { _minioClient, _minioBucket }; + } + + public void Ping() + { + GetVersioningArgs args = new GetVersioningArgs().WithBucket(_minioBucket); + VersioningConfiguration vc = _minioClient.GetVersioningAsync(args).Result; + } + } +} diff --git a/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj b/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj new file mode 100644 index 0000000..e66e7c2 --- /dev/null +++ b/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + + + + + + + + + + + diff --git a/DataTableStorages/skyscraper5.Data.MySql/Ait.cs b/DataTableStorages/skyscraper5.Data.MySql/Ait.cs new file mode 100644 index 0000000..5352746 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Ait.cs @@ -0,0 +1,279 @@ + +using MySqlConnector; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using System.Collections.Generic; +using System.Data; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mhp.Descriptors.InteractionTransportSelectors; +using System; +using skyscraper5.Teletext; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private HashSet _knownAits; + + private void InsertAitAppNames(MySqlTransaction transaction, AitApplication aitApplication) + { + if (aitApplication.ApplicationName == null) + return; + + if (aitApplication.ApplicationName.Count == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait_application_names (org_id, app_id, lang, name) VALUES (@org_id,@app_id,@lang,@name)"; + command.Parameters.AddWithValue("@org_id", aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@lang", MySqlDbType.VarChar); + command.Parameters.Add("@name", MySqlDbType.VarChar); + foreach (KeyValuePair keyValuePair in aitApplication.ApplicationName) + { + command.Parameters["@lang"].Value = keyValuePair.Key; + command.Parameters["@name"].Value = keyValuePair.Value; + command.ExecuteNonQuery(); + } + } + + private void InsertAitAppProfiles(MySqlTransaction transaction, AitApplication aitApplication) + { + if (aitApplication.ApplicationProfiles == null) + return; + + if (aitApplication.ApplicationProfiles.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait_application_profiles (org_id, app_id, app_profile, major, minor, micro) VALUES (@org_id, @app_id, @app_profile, @major, @minor, @micro)"; + command.Parameters.AddWithValue("@org_id", aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@app_profile", DbType.Int32); + command.Parameters.Add("@major", MySqlDbType.Int16); + command.Parameters.Add("@minor", MySqlDbType.Int16); + command.Parameters.Add("@micro", MySqlDbType.Int16); + foreach (ApplicationDescriptor.ApplicationProfileEncoding profile in aitApplication.ApplicationProfiles) + { + command.Parameters["@app_profile"].Value = profile.ApplicationProfile; + command.Parameters["@major"].Value = profile.Major; + command.Parameters["@minor"].Value = profile.Minor; + command.Parameters["@micro"].Value = profile.Micro; + command.ExecuteNonQuery(); + } + } + + private void InsertAitBoundaryExtensions(MySqlTransaction transaction, AitApplication aitApplication) + { + if (aitApplication.BoundaryExtensions == null) + return; + if (aitApplication.BoundaryExtensions.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait_boundary_extensions (org_id, app_id, extension) VALUES (@org_id, @app_id, @extension)"; + command.Parameters.AddWithValue("@org_id", aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@extension", MySqlDbType.VarChar); + foreach (string extension in aitApplication.BoundaryExtensions) + { + command.Parameters["@extension"].Value = extension; + command.ExecuteNonQuery(); + } + } + + private void InsertAitExternalAuthorizations(MySqlTransaction transaction, AitApplication aitApplication) + { + if (aitApplication.ExternalAuthorizations == null) + return; + if (aitApplication.ExternalAuthorizations.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait_external_authorization (org_id, app_id, ext_org_id, ext_app_id, app_priority) VALUES (@org_id, @app_id, @ext_org_id, @ext_app_id, @app_priority)"; + command.Parameters.AddWithValue("@org_id", aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@ext_org_id", MySqlDbType.Int32); + command.Parameters.Add("@ext_app_id", MySqlDbType.Int32); + command.Parameters.Add("@app_priority", MySqlDbType.Int16); + foreach (ExternalApplicationAuthorisationDescriptor.ExternalAuthorization externalAuthorization in + aitApplication.ExternalAuthorizations) + { + command.Parameters["@ext_org_id"].Value = externalAuthorization.OrganisationId; + command.Parameters["@ext_app_id"].Value = externalAuthorization.ApplicationId; + command.Parameters["@app_priority"].Value = externalAuthorization.ApplicationPriority; + command.ExecuteNonQuery(); + } + } + + private void InsertAitTransportProtocols(MySqlTransaction transaction, AitApplication aitApplication) + { + if (aitApplication.TransportProtocols == null) + return; + if (aitApplication.TransportProtocols.Count == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait_transport_protocols (org_id, app_id, protocol_id, transport_protocol_label, remote_connection, original_network_id, transport_stream_id, service_id, component_tag, url_base) " + + "VALUES " + + "(@org_id, @app_id, @protocol_id, @transport_protocol_label, @remote_connection, @original_network_id, @transport_stream_id, @service_id, @component_tag, @url_base)"; + command.Parameters.AddWithValue("@org_id", aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@protocol_id", MySqlDbType.Int32); + command.Parameters.Add("@transport_protocol_label", MySqlDbType.Int16); + command.Parameters.Add("@remote_connection", MySqlDbType.Bool); + command.Parameters.Add("@original_network_id", MySqlDbType.Int32); + command.Parameters.Add("@transport_stream_id", MySqlDbType.Int32); + command.Parameters.Add("@service_id", MySqlDbType.Int32); + command.Parameters.Add("@component_tag", MySqlDbType.Int16); + command.Parameters.Add("@url_base", MySqlDbType.VarChar); + foreach (TransportProtocolDescriptor protocol in aitApplication.TransportProtocols) + { + command.Parameters["@protocol_id"].Value = protocol.ProtocolId; + command.Parameters["@transport_protocol_label"].Value = protocol.TransportProtocolLabel; + switch (protocol.ProtocolId) + { + case 1: + ObjectCarouselTransportSelector octs = (ObjectCarouselTransportSelector)protocol.Selector; + command.Parameters["@remote_connection"].Value = octs.RemoteConnection; + command.Parameters["@original_network_id"].Value = octs.OriginalNetworkId; + command.Parameters["@transport_stream_id"].Value = octs.TransportStreamId; + command.Parameters["@service_id"].Value = octs.ServiceId; + command.Parameters["@component_tag"].Value = octs.ComponentTag; + command.Parameters["@url_base"].Value = DBNull.Value; + command.ExecuteNonQuery(); + break; + case 3: + InteractionTransportSelector its = (InteractionTransportSelector)protocol.Selector; + command.Parameters["@remote_connection"].Value = DBNull.Value; + command.Parameters["@original_network_id"].Value = DBNull.Value; + command.Parameters["@transport_stream_id"].Value = DBNull.Value; + command.Parameters["@service_id"].Value = DBNull.Value; + command.Parameters["@component_tag"].Value = DBNull.Value; + command.Parameters["@url_base"].Value = its.UrlBase; + command.ExecuteNonQuery(); + InsertAitTransportProtocolUrlExtensions(transaction, aitApplication.ApplicationIdentifier, its); + break; + default: + throw new NotImplementedException(String.Format(protocol.ProtocolId.ToString())); + } + } + } + + private void InsertAitTransportProtocolUrlExtensions(MySqlTransaction transaction, ApplicationIdentifier aitApplicationApplicationIdentifier, InteractionTransportSelector its) + { + if (its.UrlExtensions == null) + return; + if (its.UrlExtensions.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait_transport_protocols_url_extensions (org_id, app_id, ordinal, url_extension) VALUES (@org_id, @app_id, @ordinal, @url_extensiion)"; + command.Parameters.AddWithValue("@org_id", aitApplicationApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplicationApplicationIdentifier.ApplicationId); + command.Parameters.Add("@ordinal", MySqlDbType.Int32); + command.Parameters.Add("@url_extensiion", MySqlDbType.VarChar); + for (int i = 0; i < its.UrlExtensions.Length; i++) + { + command.Parameters["@ordinal"].Value = i; + command.Parameters["@url_extensiion"].Value = its.UrlExtensions[i]; + command.ExecuteNonQuery(); + } + } + + public bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier) + { + if (_knownAits == null) + _knownAits = new HashSet(); + + if (_knownAits.Contains(aitApplicationApplicationIdentifier)) + return true; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ait WHERE app_id = @app AND org_id = @org"; + command.Parameters.AddWithValue("@app", aitApplicationApplicationIdentifier.ApplicationId); + command.Parameters.AddWithValue("@org", aitApplicationApplicationIdentifier.OrganisationId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _knownAits.Add(aitApplicationApplicationIdentifier); + dataReader.Close(); + connection.Close(); + return result; + } + } + + public void StoreAitApplication(AitApplication aitApplication) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_ait (org_id, app_id, app_control_code, app_priority, service_bound_flag, transport_protocol_label, visibility, initial_path, app_usage_code, jvm_arguments, jvm_base_directory, jvm_class_path_extension, jvm_initial_class, version, is_launchable_from_older_version, launchable_completely_from_cache, not_launchable_from_broadcast, application_storage_priority, storage_property, auto_select, service_id, service_name, version_number, launch_order, storage_priority, ip_connection_required)" + + "VALUES " + + "(@org_id, @app_id, @app_control_code, @app_priority, @service_bound_flag, @transport_protocol_label, @visibility, @initial_path, @app_usage_code, @jvm_arguments, @jvm_base_directory, " + + " @jvm_class_path_extension, @jvm_initial_class, @version, @is_launchable_from_older_version, @launchable_completely_from_cache, @not_launchable_from_broadcast, " + + " @application_storage_priority, @storage_property, @auto_select, @service_id, @service_name, @version_number, @launch_order, @storage_priority, @ip_connection_required)"; + command.Parameters.AddWithValue("@org_id", aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.AddWithValue("@app_control_code", aitApplication.ApplicationControlCode); + command.Parameters.AddWithValue("@app_priority", aitApplication.ApplicationPriority); + command.Parameters.AddWithValue("@service_bound_flag", aitApplication.ServiceBoundFlag); + command.Parameters.AddWithValue("@transport_protocol_label", aitApplication.TransportProtocolLabel); + command.Parameters.AddWithValue("@visibility", aitApplication.Visibility); + command.Parameters.AddWithValue("@initial_path", aitApplication.InitialPath); + command.Parameters.AddWithValue("@app_usage_code", aitApplication.ApplicationUsageCode); + if (aitApplication.JvmArguments != null) + command.Parameters.AddWithValue("@jvm_arguments", String.Join(' ', aitApplication.JvmArguments)); + else + command.Parameters.AddWithValue("@jvm_arguments", DBNull.Value); + command.Parameters.AddWithValue("@jvm_base_directory", aitApplication.JvmBaseDirectory); + command.Parameters.AddWithValue("@jvm_class_path_extension", aitApplication.JvmClassPathExtension); + command.Parameters.AddWithValue("@jvm_initial_class", aitApplication.JvmInitialClass); + command.Parameters.AddWithValue("@version", aitApplication.Version); + command.Parameters.AddWithValue("@is_launchable_from_older_version", aitApplication.IsLaunchableFromOlderVersion); + command.Parameters.AddWithValue("@launchable_completely_from_cache", aitApplication.LaunchableCompletelyFromCache); + command.Parameters.AddWithValue("@not_launchable_from_broadcast", aitApplication.NotLaunchableFromBroadcast); + command.Parameters.AddWithValue("@application_storage_priority", aitApplication.ApplicationStoragePriority); + command.Parameters.AddWithValue("@storage_property", aitApplication.StoragePriority); + command.Parameters.AddWithValue("@auto_select", aitApplication.AutoSelect); + command.Parameters.AddWithValue("@service_id", aitApplication.ServiceId); + command.Parameters.AddWithValue("@service_name", aitApplication.ServiceName); + command.Parameters.AddWithValue("@version_number", aitApplication.VersionNumber); + command.Parameters.AddWithValue("@launch_order", aitApplication.LaunchOrder); + command.Parameters.AddWithValue("@storage_priority", aitApplication.StoragePriority); + command.Parameters.AddWithValue("@ip_connection_required", aitApplication.IpConnectionRequired); + command.ExecuteNonQuery(); + + InsertAitAppNames(transaction, aitApplication); + InsertAitAppProfiles(transaction, aitApplication); + InsertAitBoundaryExtensions(transaction, aitApplication); + InsertAitExternalAuthorizations(transaction, aitApplication); + InsertAitTransportProtocols(transaction, aitApplication); + transaction.Commit(); + connection.Close(); + _knownAits.Add(aitApplication.ApplicationIdentifier); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Bat.cs b/DataTableStorages/skyscraper5.Data.MySql/Bat.cs new file mode 100644 index 0000000..30021cf --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Bat.cs @@ -0,0 +1,337 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Teletext; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using System.IO; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using System.Net.NetworkInformation; +using System.Net; +using skyscraper5.src.Skyscraper.Scraper.Dns; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private void InsertBatCountryAvailability(MySqlConnection connection, MySqlTransaction transaction, BatBouquet batBouquet) + { + if (batBouquet.CountryAvailabilityDictionary == null) + return; + + if (batBouquet.CountryAvailabilityDictionary.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_bat_country_availability (id, country, availability) VALUES (@id, @country, @availability)"; + command.Parameters.AddWithValue("@id", batBouquet.BouquetId); + command.Parameters.Add("@country", MySqlDbType.VarChar); + command.Parameters.Add("@availability", MySqlDbType.Bool); + foreach (KeyValuePair keyValuePair in batBouquet.CountryAvailabilityDictionary) + { + command.Parameters["@country"].Value = keyValuePair.Key; + command.Parameters["@availability"].Value = keyValuePair.Value; + command.ExecuteNonQuery(); + } + } + + private void InsertBatMultilingualName(MySqlConnection connection, MySqlTransaction transaction, BatBouquet batBouquet) + { + if (batBouquet.MultilingualBouquetName == null) + return; + + if (batBouquet.MultilingualBouquetName.MultilingualBouquetName == null) + return; + + if (batBouquet.MultilingualBouquetName.MultilingualBouquetName.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_bat_multilingual_bouquet_name (id, lang, name) VALUES (@id, @lang, @name)"; + command.Parameters.AddWithValue("@id", batBouquet.BouquetId); + command.Parameters.Add("@lang", MySqlDbType.VarChar); + command.Parameters.Add("@name", MySqlDbType.VarChar); + foreach (KeyValuePair keyValuePair in batBouquet.MultilingualBouquetName + .MultilingualBouquetName) + { + command.Parameters["@lang"].Value = keyValuePair.Key; + command.Parameters["@name"].Value = keyValuePair.Value; + command.ExecuteNonQuery(); + } + } + + private BatBouquet GetBatBouquet(MySqlConnection connection, ushort newerBouquetId) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM dvb_bat WHERE id = @id"; + command.Parameters.AddWithValue("@id", newerBouquetId); + MySqlDataReader dataReader = command.ExecuteReader(); + BatBouquet result = null; + if (dataReader.Read()) + { + ushort id = dataReader.GetUInt16(0); + result = new BatBouquet(id); + DateTime dateAdded = dataReader.GetDateTime(1); + + if (!dataReader.IsDBNull(2)) + result.BouquetName = dataReader.GetString(2); + + if (!dataReader.IsDBNull(3)) + { + Guid guid = Guid.Parse(dataReader.GetString(3)); + } + + if (!dataReader.IsDBNull(4)) + result.PrivateDataSpecifier = dataReader.GetUInt32(4); + + if (!dataReader.IsDBNull(5)) + result.UriLinkageType = dataReader.GetByte(5); + + if (!dataReader.IsDBNull(6)) + result.Uri = dataReader.GetString(6); + + if (!dataReader.IsDBNull(7)) + result.MinPollingInterval = dataReader.GetUInt16(7); + + if (!dataReader.IsDBNull(8)) + result.ControlRemoteAccessOverInternet = dataReader.GetInt32(8); + + if (!dataReader.IsDBNull(9)) + result.DoNotApplyRevocation = dataReader.GetBoolean(9); + + if (!dataReader.IsDBNull(10)) + result.DoNotScramble = dataReader.GetBoolean(10); + } + dataReader.Close(); + return result; + } + + private struct BatTransportStreamCoordinate + { + public ushort BouquetId { get; } + public ushort NetworkId { get; } + public ushort TsId { get; } + + public BatTransportStreamCoordinate(ushort bouquetId, ushort networkId, ushort tsId) + { + BouquetId = bouquetId; + NetworkId = networkId; + TsId = tsId; + } + + public bool Equals(BatTransportStreamCoordinate other) + { + return BouquetId == other.BouquetId && NetworkId == other.NetworkId && TsId == other.TsId; + } + + public override bool Equals(object obj) + { + return obj is BatTransportStreamCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(BouquetId, NetworkId, TsId); + } + } + + private HashSet _batTransportStreamCoordinates; + + private void InsertBatTransportStreamCountryAvailability(MySqlTransaction transaction, ushort batBouquetBouquetId, BatTransportStream child) + { + if (child.CountryAvailability == null) + return; + + if (child.CountryAvailability.Count == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_bat_transport_stream_country_availability (bouquet_id, original_network_id, transport_stream_id, country, availability) " + + "VALUES (@bouquet_id, @original_network_id, @transport_stream_id, @country, @availability)"; + command.Parameters.AddWithValue("@bouquet_id", batBouquetBouquetId); + command.Parameters.AddWithValue("@original_network_id", child.OriginalNetworkId); + command.Parameters.AddWithValue("@transport_stream_id", child.TransportStreamId); + command.Parameters.Add("@country", MySqlDbType.VarChar); + command.Parameters.Add("@availability", MySqlDbType.Bool); + foreach (KeyValuePair valuePair in child.CountryAvailability) + { + command.Parameters["@country"].Value = valuePair.Key; + command.Parameters["@availability"].Value = valuePair.Value; + command.ExecuteNonQuery(); + } + } + + private void InsertBatTransportStreamServiceList(MySqlTransaction transaction, ushort batBouquetBouquetId, BatTransportStream child) + { + if (child.ServiceList == null) + return; + + if (child.ServiceList.Count == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_bat_transport_stream_service_list (bouquet_id, original_network_id, transport_stream_id, service_id, service_type) " + + "VALUES (@bouquet_id, @original_network_id, @transport_stream_id, @service_id, @service_type)"; + command.Parameters.AddWithValue("@bouquet_id", batBouquetBouquetId); + command.Parameters.AddWithValue("@original_network_id", child.OriginalNetworkId); + command.Parameters.AddWithValue("@transport_stream_id", child.TransportStreamId); + command.Parameters.Add("@service_id", MySqlDbType.Int32); + command.Parameters.Add("@service_type", MySqlDbType.Int32); + + foreach (ServiceListDescriptor.Service service in child.ServiceList) + { + command.Parameters["@service_id"].Value = service.ServiceId; + command.Parameters["@service_type"].Value = (int)service.ServiceType; + command.ExecuteNonQuery(); + } + } + + public bool TestForBatBouquet(BatBouquet batBouquet) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_bat WHERE id = @id"; + command.Parameters.AddWithValue("@id", batBouquet.BouquetId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + connection.Close(); + return result; + } + } + + public bool UpdateBatBouquet(BatBouquet newer) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + bool result = false; + BatBouquet older = GetBatBouquet(connection, newer.BouquetId); + if (older.NeedUpdate(newer)) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_bat SET name = @name, private_dat_specifier = @private_dat_specifier, uri_linkage_type = @uri_linkage_type, url = @url, min_polling_interval =@min_polling_interval, control_remote_access_over_internet = @control_remote_access_over_internet, do_not_apply_revocation = @do_not_apply_revocation, do_not_scramble = @do_not_scramble WHERE id = @id"; + command.Parameters.AddWithValue("@id", newer.BouquetId); + command.Parameters.AddWithValue("@name", newer.BouquetName); + command.Parameters.AddWithValue("@private_dat_specifier", newer.PrivateDataSpecifier); + command.Parameters.AddWithValue("@uri_linkage_type", newer.UriLinkageType); + command.Parameters.AddWithValue("@url", newer.Uri); + command.Parameters.AddWithValue("@min_polling_interval", newer.MinPollingInterval); + command.Parameters.AddWithValue("@control_remote_access_over_internet", newer.ControlRemoteAccessOverInternet); + command.Parameters.AddWithValue("@do_not_apply_revocation", newer.DoNotApplyRevocation); + command.Parameters.AddWithValue("@do_not_scramble", newer.DoNotScramble); + command.ExecuteNonQuery(); + result = true; + } + connection.Close(); + return result; + } + } + + public void StoreBatBouquet(BatBouquet batBouquet) + { + bool hasLinkages = HasLinkages(batBouquet.Linkages); + Guid uuid = hasLinkages ? Guid.NewGuid() : Guid.Empty; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_bat (id, name, uuid, private_dat_specifier, uri_linkage_type, url, min_polling_interval, control_remote_access_over_internet, do_not_apply_revocation, do_not_scramble) " + + "VALUES " + + "(@id, @name, @uuid, @private_dat_specifier, @uri_linkage_type, @url, @min_polling_interval, @control_remote_access_over_internet, @do_not_apply_revocation, @do_not_scramble)"; + command.Parameters.AddWithValue("@id", batBouquet.BouquetId); + command.Parameters.AddWithValue("@name", batBouquet.BouquetName); + if (hasLinkages) + command.Parameters.AddWithValue("@uuid", uuid.ToString()); + else + command.Parameters.AddWithValue("@uuid", DBNull.Value); + command.Parameters.AddWithValue("@private_dat_specifier", batBouquet.PrivateDataSpecifier); + command.Parameters.AddWithValue("@uri_linkage_type", batBouquet.UriLinkageType); + command.Parameters.AddWithValue("@url", batBouquet.Uri); + command.Parameters.AddWithValue("@min_polling_interval", batBouquet.MinPollingInterval); + command.Parameters.AddWithValue("@control_remote_access_over_internet", batBouquet.ControlRemoteAccessOverInternet); + command.Parameters.AddWithValue("@do_not_apply_revocation", batBouquet.DoNotApplyRevocation); + command.Parameters.AddWithValue("@do_not_scramble", batBouquet.DoNotScramble); + command.ExecuteNonQuery(); + + if (hasLinkages) + InsertSdtLinkages(connection, uuid, batBouquet.Linkages, transaction); + + InsertBatCountryAvailability(connection, transaction, batBouquet); + InsertBatMultilingualName(connection, transaction, batBouquet); + transaction.Commit(); + connection.Close(); + } + } + + public bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + if (_batTransportStreamCoordinates == null) + _batTransportStreamCoordinates = new HashSet(); + + BatTransportStreamCoordinate coordinate = new BatTransportStreamCoordinate(batBouquetBouquetId, child.OriginalNetworkId, child.TransportStreamId); + if (_batTransportStreamCoordinates.Contains(coordinate)) + return true; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dvb_bat_transport_stream WHERE bouquet_id = @bouquet_id AND original_network_id = @original_network_id AND transport_stream_id = @transport_stream_id"; + command.Parameters.AddWithValue("@bouquet_id", batBouquetBouquetId); + command.Parameters.AddWithValue("@original_network_id", child.OriginalNetworkId); + command.Parameters.AddWithValue("@transport_stream_id", child.TransportStreamId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _batTransportStreamCoordinates.Add(coordinate); + dataReader.Close(); + return result; + } + } + + public bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + //It's very unlikely that we'll need to Update BAT TS Info, as these don't contain much. + return false; + } + + public void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + BatTransportStreamCoordinate coordinate = new BatTransportStreamCoordinate(batBouquetBouquetId, child.OriginalNetworkId, child.TransportStreamId); + _batTransportStreamCoordinates.Add(coordinate); + EnqueueSpeedhack(SpeedhackType.InsertBatTs, batBouquetBouquetId, child); + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Cat.cs b/DataTableStorages/skyscraper5.Data.MySql/Cat.cs new file mode 100644 index 0000000..6645de1 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Cat.cs @@ -0,0 +1,83 @@ +using MySqlConnector; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private struct CatCoordinate + { + public int Nid { get; } + public int Tsid { get; } + public int Pid { get; } + + public CatCoordinate(int nid, int tsid, int pid) + { + Nid = nid; + Tsid = tsid; + Pid = pid; + } + + public bool Equals(CatCoordinate other) + { + return Nid == other.Nid && Tsid == other.Tsid && Pid == other.Pid; + } + + public override bool Equals(object obj) + { + return obj is CatCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Nid, Tsid, Pid); + } + } + + private HashSet _catCoordinates; + + public bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid) + { + CatCoordinate coordinate = new CatCoordinate(currentNetworkId, currentTransportStreamId, caDescriptorCaPid); + if (_catCoordinates == null) + _catCoordinates = new HashSet(); + if (_catCoordinates.Contains(coordinate)) + return true; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "SELECT dateadded FROM dvb_cat WHERE nid = @nid AND tsid = @tsid AND ca_pid = @ca_pid"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ca_pid", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = currentNetworkId; + mySqlCommand.Parameters["@tsid"].Value = currentTransportStreamId; + mySqlCommand.Parameters["@ca_pid"].Value = caDescriptorCaPid; + MySqlDataReader dataReader = mySqlCommand.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _catCoordinates.Add(coordinate); + dataReader.Close(); + connection.Close(); + return result; + } + } + + public void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + CatCoordinate coordinate = new CatCoordinate(currentNetworkId, currentTransportStreamId, caDescriptor.CaPid); + _catCoordinates.Add(coordinate); + EnqueueSpeedhack(SpeedhackType.InsertCat, currentNetworkId, currentTransportStreamId, caDescriptor); + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Docsis.cs b/DataTableStorages/skyscraper5.Data.MySql/Docsis.cs new file mode 100644 index 0000000..e33ef40 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Docsis.cs @@ -0,0 +1,40 @@ +using MySqlConnector; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private bool _askedForLocation; + private int? _cachedLocation; + + public int? GetCurrentLocationId() + { + if (_askedForLocation) + return _cachedLocation; + + + int? result = null; + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT id FROM skyscraper_locations WHERE current = TRUE"; + MySqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + result = dataReader.GetInt32(0); + dataReader.Close(); + connection.Close(); + } + + _askedForLocation = true; + _cachedLocation = result; + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/DsmCc.cs b/DataTableStorages/skyscraper5.Data.MySql/DsmCc.cs new file mode 100644 index 0000000..18b8fb4 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/DsmCc.cs @@ -0,0 +1,40 @@ +using MySqlConnector; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + public void DataCarouselModuleMarkComplete(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + } + + public bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dsmcc_blacklist WHERE network_id = @network_id AND transport_stream_id = @transport_stream_id AND pid = @pid AND module_id = @module_id " + + "AND module_version = @module_version"; + command.Parameters.AddWithValue("@network_id", currentNetworkId); + command.Parameters.AddWithValue("@transport_stream_id", currentTransportStreamId); + command.Parameters.AddWithValue("@pid", elementaryPid); + command.Parameters.AddWithValue("@module_id", moduleId); + command.Parameters.AddWithValue("@module_version", moduleVersion); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + connection.Close(); + return result; + } + } + + } + } diff --git a/DataTableStorages/skyscraper5.Data.MySql/DsmCcEvent.cs b/DataTableStorages/skyscraper5.Data.MySql/DsmCcEvent.cs new file mode 100644 index 0000000..c6bff1f --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/DsmCcEvent.cs @@ -0,0 +1,57 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.DsmCc.Descriptors; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private bool TestForDsmCcEvent(DateTime timestamp, int cnid, int ctsid, int pid, MySqlConnection connection) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dsmcc_events WHERE timestamp = @timestamp AND cnid = @cnid AND ctsid = @ctsid AND pid = @pid"; + command.Parameters.AddWithValue("@timestamp", timestamp); + command.Parameters.AddWithValue("@cnid", cnid); + command.Parameters.AddWithValue("@ctsid", ctsid); + command.Parameters.AddWithValue("@pid", pid); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + public void StoreDsmCcDoItNowEvent(DateTime value, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + if (!TestForDsmCcEvent(value, currentNetworkId, currentTransportStreamId, pid, connection)) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dsmcc_events (timestamp, cnid, ctsid, progno, pid, event_id, npt_event_seconds, npt_event_microseconds, private_data) " + + "VALUES (@timestamp, @cnid, @ctsid, @progno, @pid, @event_id, @npt_event_seconds, @npt_event_microseconds, @private_data)"; + command.Parameters.AddWithValue("@timestamp", value); + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@pid", pid); + command.Parameters.AddWithValue("@event_id", descriptorListStreamEventDescriptor.EventId); + command.Parameters.AddWithValue("@npt_event_seconds", descriptorListStreamEventDescriptor.NptEventSeconds); + command.Parameters.AddWithValue("@npt_event_microseconds", descriptorListStreamEventDescriptor.NptEventMicroseconds); + command.Parameters.Add("@private_data", MySqlDbType.TinyBlob); + command.Parameters["@private_data"].Value = descriptorListStreamEventDescriptor.PrivateData; + command.ExecuteNonQuery(); + } + connection.Close(); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Eit.cs b/DataTableStorages/skyscraper5.Data.MySql/Eit.cs new file mode 100644 index 0000000..8a81277 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Eit.cs @@ -0,0 +1,434 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private bool TestForEitEvent(MySqlConnection connector, EitEvent eitEvent, MySqlTransaction transaction) + { + MySqlCommand command = connector.CreateCommand(); + if (transaction != null) + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_eit WHERE onid = @onid AND tsid = @tsid AND service_id = @service_id AND start_time = @start_time"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertEitCaSystem(MySqlConnection connection, MySqlTransaction transaction, EitEvent eitEvent) + { + if (eitEvent.CaSystems == null) + return; + + if (eitEvent.CaSystems.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit_ca_systems (onid, tsid, service_id, start_time, ca_system) VALUES (@onid, @tsid, @service_id, @start_time, @ca_system)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.Add("@ca_system", MySqlDbType.Int32); + foreach (ushort caSystem in eitEvent.CaSystems) + { + command.Parameters["@ca_system"].Value = caSystem; + command.ExecuteNonQuery(); + } + } + + private void InsertEitComponent(MySqlConnection connection, MySqlTransaction transaction, EitEvent eitEvent) + { + if (eitEvent.Components == null) + return; + + if (eitEvent.Components.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit_component (onid, tsid, service_id, start_time, text, iso_639_langauge_code, component_tag, component_type, stream_content, stream_content_ext) " + + "VALUES (@onid, @tsid, @service_id, @start_time, @text, @iso_639_langauge_code, @component_tag, @component_type, @stream_content, @stream_content_ext)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.Add("@text", MySqlDbType.VarChar); + command.Parameters.Add("@iso_639_langauge_code", MySqlDbType.VarChar); + command.Parameters.Add("@component_tag", MySqlDbType.Int16); + command.Parameters.Add("@component_type", MySqlDbType.Int16); + command.Parameters.Add("@stream_content", MySqlDbType.Int32); + command.Parameters.Add("@stream_content_ext", MySqlDbType.Int32); + bool[] usedComponentTags = new bool[256]; + foreach (ComponentDescriptor component in eitEvent.Components) + { + if (usedComponentTags[component.ComponentTag]) + continue; + + usedComponentTags[component.ComponentTag] = true; + command.Parameters["@text"].Value = component.Text; + command.Parameters["@iso_639_langauge_code"].Value = component.Iso639LanguageCode; + command.Parameters["@component_tag"].Value = component.ComponentTag; + command.Parameters["@component_type"].Value = component.ComponentType; + command.Parameters["@stream_content"].Value = component.StreamContent; + command.Parameters["@stream_content_ext"].Value = component.StreamContentExt; + command.ExecuteNonQuery(); + } + } + + private void InsertEitContent(MySqlConnection connection, MySqlTransaction transaction, EitEvent eitEvent) + { + if (eitEvent.Content == null) + return; + + if (eitEvent.Content.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit_content (onid, tsid, service_id, start_time, l, m, r) VALUES (@onid, @tsid, @service_id, @start_time, @l, @m, @r)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.Add("@l", MySqlDbType.Int32); + command.Parameters.Add("@m", MySqlDbType.Int32); + command.Parameters.Add("@r", MySqlDbType.Int32); + foreach (Tuple tuple in eitEvent.Content) + { + command.Parameters["@l"].Value = tuple.Item1; + command.Parameters["@m"].Value = tuple.Item2; + command.Parameters["@r"].Value = tuple.Item3; + command.ExecuteNonQuery(); + } + } + + private void InsertEitCrids(MySqlConnection connection, MySqlTransaction transaction, EitEvent eitEvent) + { + if (eitEvent.Crids == null) + return; + + if (eitEvent.Crids.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit_crids (onid, tsid, service_id, start_time, ordinal, crid_type, crid_location, crid_bytes) " + + "VALUES (@onid, @tsid, @service_id, @start_time, @ordinal, @crid_type, @crid_location, @crid_bytes)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.Add("@ordinal", MySqlDbType.Int32); + command.Parameters.Add("@crid_type", MySqlDbType.Int32); + command.Parameters.Add("@crid_location", MySqlDbType.Int32); + command.Parameters.Add("@crid_bytes", MySqlDbType.TinyBlob); + for (int i = 0; i < eitEvent.Crids.Count; i++) + { + command.Parameters["@ordinal"].Value = i; + command.Parameters["@crid_type"].Value = (int)eitEvent.Crids[i].CridType; + command.Parameters["@crid_location"].Value = (int)eitEvent.Crids[i].CridLocation; + command.Parameters["@crid_bytes"].Value = eitEvent.Crids[i].CridBytes; + command.ExecuteNonQuery(); + } + } + + private void InsertEitEventItems(MySqlConnection connection, MySqlTransaction transaction, EitEvent eitEvent) + { + if (eitEvent.Items == null) + return; + + if (eitEvent.Items.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit_event_items (onid, tsid, service_id, start_time, ordinal, description, item) " + + "VALUES (@onid, @tsid, @service_id, @start_time, @ordinal, @description, @item)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.Add("@ordinal", MySqlDbType.Int32); + command.Parameters.Add("@description", MySqlDbType.VarChar); + command.Parameters.Add("@item", MySqlDbType.MediumText); + for (int i = 0; i < eitEvent.Items.Count; i++) + { + command.Parameters["@ordinal"].Value = i; + command.Parameters["@description"].Value = eitEvent.Items[i].Description; + if (eitEvent.Items[i].Description == null) + continue; + command.Parameters["@item"].Value = eitEvent.Items[i].Item; + if (eitEvent.Items[i].Item == null) + continue; + SetNulls(command); + command.ExecuteNonQuery(); + } + } + + private void InsertEitParentalRatings(MySqlConnection connection, MySqlTransaction transaction, EitEvent eitEvent) + { + if (eitEvent.ParentalRatings == null) + return; + + if (eitEvent.ParentalRatings.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit_parental_ratings (onid, tsid, service_id, start_time, country, age) " + + "VALUES (@onid, @tsid, @service_id, @start_time, @country, @age)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.Add("@country", MySqlDbType.VarChar); + command.Parameters.Add("@age", MySqlDbType.Int32); + foreach (Tuple parentalRating in eitEvent.ParentalRatings) + { + if (parentalRating == null) + continue; + + command.Parameters["@country"].Value = parentalRating.Item1; + command.Parameters["@age"].Value = parentalRating.Item2; + command.ExecuteNonQuery(); + } + } + + private Queue queuedEitEvents; + private HashSet eitCoordinatesCacheIndex; + private HashSet eitCoordinatesCache; + private Thread eitWriterThread; + + private bool IsEitWriterThreadRunning() + { + if (eitWriterThread == null) + return false; + + if (!eitWriterThread.IsAlive) + return false; + + return true; + } + private void EitWriterThreadFunction() + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + while (true) + { + EitEvent eitEvent = null; + lock (queuedEitEvents) + { + if (queuedEitEvents.Count == 0) + break; + eitEvent = queuedEitEvents.Dequeue(); + if (queuedEitEvents.Count % 100 == 0) + { + //Console.WriteLine("{0} EIT events left to write...", queuedEitEvents.Count); + } + } + + if (!TestForEitEvent(connection, eitEvent, transaction)) + { + bool hasLinkages = HasLinkages(eitEvent.Linkages); + Guid guid = hasLinkages ? Guid.NewGuid() : Guid.Empty; + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_eit " + + " (onid, tsid, service_id, event_id, start_time, duration, running_status, free_ca, iso_639_language_code, event_name, text, extended_text, pdc, private_data_specifier, vps, uuid, reference_service_id, reference_event_id, control_remove_access_over_internet, do_not_apply_revocation, do_not_scramble)" + + "VALUES" + + " (@onid, @tsid, @service_id, @event_id, @start_time, @duration, @running_status, @free_ca, @iso_639_language_code, @event_name, @text, @extended_text, @pdc, @private_data_specifier, " + + " @vps, @uuid, @reference_service_id, @reference_event_id, @control_remove_access_over_internet, @do_not_apply_revocation, @do_not_scramble)"; + command.Parameters.AddWithValue("@onid", eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@tsid", eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@service_id", eitEvent.ServiceId); + command.Parameters.AddWithValue("@event_id", eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", eitEvent.StartTime); + command.Parameters.AddWithValue("@duration", (int)eitEvent.Duration.TotalSeconds); + command.Parameters.AddWithValue("@running_status", (int)eitEvent.RunningStatus); + command.Parameters.AddWithValue("@free_ca", eitEvent.FreeCa); + command.Parameters.AddWithValue("@iso_639_language_code", eitEvent.Iso639LanguageCode); + command.Parameters.AddWithValue("@event_name", eitEvent.EventName); + command.Parameters.AddWithValue("@text", eitEvent.Text); + if (eitEvent.ExtendedText != null) + command.Parameters.AddWithValue("@extended_text", string.Join("", eitEvent.ExtendedText)); + else + command.Parameters.AddWithValue("@extended_text", DBNull.Value); + command.Parameters.AddWithValue("@pdc", eitEvent.Pdc); + command.Parameters.AddWithValue("@private_data_specifier", eitEvent.PrivateDataSpecifier); + command.Parameters.AddWithValue("@vps", eitEvent.VpsString); + if (hasLinkages) + command.Parameters.AddWithValue("@uuid", guid); + else + command.Parameters.AddWithValue("@uuid", DBNull.Value); + command.Parameters.AddWithValue("@reference_service_id", eitEvent.ReferenceServiceId); + command.Parameters.AddWithValue("@reference_event_id", eitEvent.ReferenceEventId); + command.Parameters.AddWithValue("@control_remove_access_over_internet", + eitEvent.ControlRemoteAccessOverInternet); + command.Parameters.AddWithValue("@do_not_apply_revocation", eitEvent.DoNotApplyRevocation); + command.Parameters.AddWithValue("@do_not_scramble", eitEvent.DoNotScramble); + SetNulls(command); + command.ExecuteNonQuery(); + + InsertEitCaSystem(connection, transaction, eitEvent); + InsertEitComponent(connection, transaction, eitEvent); + InsertEitContent(connection, transaction, eitEvent); + InsertEitCrids(connection, transaction, eitEvent); + InsertEitEventItems(connection, transaction, eitEvent); + InsertEitParentalRatings(connection, transaction, eitEvent); + if (hasLinkages) + InsertSdtLinkages(connection, guid, eitEvent.Linkages, transaction); + } + } + transaction.Commit(); + connection.Close(); + } + } + + private struct EitCoordinates + { + public ushort onid, tsid, sid; + public DateTime starttime; + + private EitCoordinates(ushort onid1, ushort tsid1, ushort sid1, int starttimeYear, int starttimeMonth) + { + onid = onid1; + tsid = tsid1; + sid = sid1; + starttime = new DateTime(starttimeYear, starttimeMonth, 1, 0, 0, 0); + } + + public EitCoordinates(ushort onid, ushort tsid, ushort sid, DateTime starttime) + { + this.onid = onid; + this.tsid = tsid; + this.sid = sid; + this.starttime = starttime; + } + + public EitCoordinates(EitEvent eitEvent) + { + onid = eitEvent.OriginalNetworkId; + tsid = eitEvent.TransportStreamId; + sid = eitEvent.ServiceId; + starttime = eitEvent.StartTime; + } + + public EitCoordinates(EitCoordinates coordinatesRounded, DateTime getDateTime) + { + onid = coordinatesRounded.onid; + tsid = coordinatesRounded.tsid; + sid = coordinatesRounded.sid; + starttime = getDateTime; + } + + public bool Equals(EitCoordinates other) + { + return onid == other.onid && tsid == other.tsid && sid == other.sid && starttime.Equals(other.starttime); + } + + public override bool Equals(object obj) + { + return obj is EitCoordinates other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(onid, tsid, sid, starttime); + } + + public EitCoordinates Round() + { + return new EitCoordinates(onid, tsid, sid, starttime.Year, starttime.Month); + } + } + + private void PrecacheEitEntries(EitCoordinates coordinatesRounded) + { + if (eitCoordinatesCache == null) + eitCoordinatesCache = new HashSet(); + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT start_time FROM dvb_eit WHERE onid = @onid AND tsid = @tsid AND service_id = @service_id AND YEAR(start_time) = @year AND MONTH(start_time) = @month"; + command.Parameters.AddWithValue("@onid", coordinatesRounded.onid); + command.Parameters.AddWithValue("@tsid", coordinatesRounded.tsid); + command.Parameters.AddWithValue("@service_id", coordinatesRounded.sid); + command.Parameters.AddWithValue("@year", coordinatesRounded.starttime.Year); + command.Parameters.AddWithValue("@month", coordinatesRounded.starttime.Month); + MySqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + eitCoordinatesCache.Add(new EitCoordinates(coordinatesRounded, dataReader.GetDateTime(0))); + } + dataReader.Close(); + connection.Close(); + } + + eitCoordinatesCacheIndex.Add(coordinatesRounded); + } + + public bool StoreEitEvent(EitEvent eitEvent) + { + EitCoordinates coordinates = new EitCoordinates(eitEvent); + EitCoordinates coordinatesRounded = coordinates.Round(); + if (eitCoordinatesCacheIndex == null) + eitCoordinatesCacheIndex = new HashSet(); + if (!eitCoordinatesCacheIndex.Contains(coordinatesRounded)) + { + PrecacheEitEntries(coordinatesRounded); + } + + if (eitCoordinatesCache.Contains(coordinates)) + return false; + + if (queuedEitEvents == null) + queuedEitEvents = new Queue(); + + lock (queuedEitEvents) + { + queuedEitEvents.Enqueue(eitEvent); + } + eitCoordinatesCache.Add(coordinates); + + if (!IsEitWriterThreadRunning()) + { + eitWriterThread = new Thread(EitWriterThreadFunction); + eitWriterThread.Name = "EIT Writer"; + eitWriterThread.Priority = ThreadPriority.Lowest; + eitWriterThread.Start(); + } + + return true; + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Int.cs b/DataTableStorages/skyscraper5.Data.MySql/Int.cs new file mode 100644 index 0000000..48ebf2d --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Int.cs @@ -0,0 +1,24 @@ +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + public bool TestForIpMacNotification(IpMacNotification notification) + { + throw new NotImplementedException(); + } + + public void StoreIpMacNotification(IpMacNotification notification) + { + throw new NotImplementedException(); + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Linkages.cs b/DataTableStorages/skyscraper5.Data.MySql/Linkages.cs new file mode 100644 index 0000000..6090ff4 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Linkages.cs @@ -0,0 +1,232 @@ +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static skyscraper5.Dvb.Descriptors.LinkageDescriptor; + +namespace skyscraper5.Data.MySql +{ + partial class MySqlDataStorage + { + private void InsertSdtLinkages(MySqlConnection connection, Guid source, ICollection Linkages, MySqlTransaction transaction) + { + if (Linkages == null) + return; + + if (Linkages.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = "INSERT INTO dvb_linkages " + + " (uuid, linkage_type, handover_type, handover_origin_type, handover_network_id, handover_initial_service_id, target_event_id, target_event_listed, target_event_simulcasted, table_type, private_data_bytes, bouquet_id, original_network_id, transport_stream_id, service_id) " + + "VALUES " + + " (@uuid, @linkage_type, @handover_type,@handover_origin_type,@handover_network_id,@handover_initial_service_id,@target_event_id,@target_event_listed,@target_event_simulcasted,@target_type,@private_data_bytes, @bouquet_id, @original_network_id, @transport_stream_id, @service_id)"; + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@linkage_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@handover_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@handover_origin_type", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@handover_network_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@handover_initial_service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@target_event_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@target_event_listed", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@target_event_simulcasted", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@target_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@private_data_bytes", MySqlDbType.TinyBlob); + mySqlCommand.Parameters.Add("@bouquet_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@original_network_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@transport_stream_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters["@uuid"].Value = source.ToString(); + foreach (LinkageDescriptor linkageDescriptor in Linkages) + { + if (TestForLinkage(transaction, source, linkageDescriptor.LinkageType, linkageDescriptor.OriginalNetworkId, linkageDescriptor.TransportStreamId, linkageDescriptor.ServiceId)) + continue; + + mySqlCommand.Parameters["@linkage_type"].Value = (int)linkageDescriptor.LinkageType; + mySqlCommand.Parameters["@handover_type"].Value = linkageDescriptor.HandoverType; + mySqlCommand.Parameters["@handover_origin_type"].Value = linkageDescriptor.HandoverOriginType; + mySqlCommand.Parameters["@handover_network_id"].Value = linkageDescriptor.HandoverNetworkId; + mySqlCommand.Parameters["@handover_initial_service_id"].Value = linkageDescriptor.HandoverInitialServiceId; + mySqlCommand.Parameters["@target_event_id"].Value = linkageDescriptor.TargetEventId; + mySqlCommand.Parameters["@target_event_listed"].Value = linkageDescriptor.TargetEventListed; + mySqlCommand.Parameters["@target_event_simulcasted"].Value = linkageDescriptor.TargetEventSimulcasted; + mySqlCommand.Parameters["@target_type"].Value = (int)linkageDescriptor.TableType; + mySqlCommand.Parameters["@private_data_bytes"].Value = linkageDescriptor.PrivateDataBytes; + mySqlCommand.Parameters["@bouquet_id"].Value = linkageDescriptor.BouquetId; + mySqlCommand.Parameters["@original_network_id"].Value = linkageDescriptor.OriginalNetworkId; + mySqlCommand.Parameters["@transport_stream_id"].Value = linkageDescriptor.TransportStreamId; + mySqlCommand.Parameters["@service_id"].Value = linkageDescriptor.ServiceId; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + + InsertSdtLinkageExtendedEvent(connection, source, linkageDescriptor); + InsertSdtLinkageIpMac(connection, source, linkageDescriptor); + InsertSdtLinkageSsu(transaction, source, linkageDescriptor); + } + } + + private bool TestForLinkage(MySqlTransaction transaction, Guid source, LinkageDescriptor.LinkageTypeEnum linkageDescriptorLinkageType, ushort linkageDescriptorOriginalNetworkId, ushort linkageDescriptorTransportStreamId, ushort linkageDescriptorServiceId) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_linkages WHERE uuid = @uuid AND linkage_type = @linkage_type AND original_network_id = @original_network_id " + + "AND transport_stream_id = @transport_stream_id AND service_id = @service_id"; + command.Parameters.AddWithValue("@uuid", source.ToString()); + command.Parameters.AddWithValue("@linkage_type", (int)linkageDescriptorLinkageType); + command.Parameters.AddWithValue("@original_network_id", linkageDescriptorOriginalNetworkId); + command.Parameters.AddWithValue("@transport_stream_id", linkageDescriptorTransportStreamId); + command.Parameters.AddWithValue("@service_id", linkageDescriptorServiceId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertSdtLinkageSsu(MySqlTransaction transaction, Guid source, LinkageDescriptor linkageDescriptor) + { + if (linkageDescriptor.SystemSoftwareUpdateLinkStructure == null) + return; + + if (linkageDescriptor.SystemSoftwareUpdateLinkStructure.Count == 0) + return; + + MySqlCommand mySqlCommand = transaction.Connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = + "INSERT INTO dvb_linkages_ssu (uuid, oui, selector) VALUES (@uuid, @oui, @selector)"; + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@oui", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@selector", MySqlDbType.TinyBlob); + mySqlCommand.Parameters["@uuid"].Value = source.ToString(); + foreach (LinkageDescriptor.OuiPrivateData ouiPrivateData in linkageDescriptor.SystemSoftwareUpdateLinkStructure) + { + if (!TestForLinkageSsu(transaction, source, ouiPrivateData.OUI)) + { + mySqlCommand.Parameters["@oui"].Value = BitConverter.ToString(ouiPrivateData.OUI); + mySqlCommand.Parameters["@selector"].Value = ouiPrivateData.Selector; + mySqlCommand.ExecuteNonQuery(); + } + } + } + + private bool TestForLinkageSsu(MySqlTransaction transaction, Guid uuid, byte[] oui) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = "SELECT dateadded FROM dvb_linkages_ssu WHERE uuid = @uuid AND oui = @oui"; + command.Parameters.AddWithValue("@uuid", uuid.ToString()); + command.Parameters.AddWithValue("@oui", BitConverter.ToString(oui)); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertSdtLinkageIpMac(MySqlConnection connection, Guid source, LinkageDescriptor linkageDescriptor) + { + if (linkageDescriptor.IpMacNotificationLinkages == null) + return; + + if (linkageDescriptor.IpMacNotificationLinkages.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_sdt_linkages_ipmac (uuid, id) VALUES (@uuid, @id)"; + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + + mySqlCommand.Parameters["@uuid"].Value = source.ToString(); + foreach (LinkageDescriptor.IpMacNotificationLinkage ipMacNotificationLinkage in linkageDescriptor.IpMacNotificationLinkages) + { + mySqlCommand.Parameters["@id"].Value = ipMacNotificationLinkage.Id; + mySqlCommand.ExecuteNonQuery(); + + InsertSdtLinkageIpMacNames(connection, source, ipMacNotificationLinkage); + } + } + + private void InsertSdtLinkageIpMacNames(MySqlConnection connection, Guid source, LinkageDescriptor.IpMacNotificationLinkage ipMacNotificationLinkage) + { + if (ipMacNotificationLinkage.Names == null) + return; + + if (ipMacNotificationLinkage.Names.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_sdt_linkages_ipmac_names (uuid, id, lang, name) VALUES (@uuid, @id, @lang, @name)"; + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@lang", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@name", MySqlDbType.VarChar); + mySqlCommand.Parameters["@uuid"].Value = source.ToString(); + mySqlCommand.Parameters["@id"].Value = ipMacNotificationLinkage.Id; + + foreach (Tuple tuple in ipMacNotificationLinkage.Names) + { + mySqlCommand.Parameters["@lang"].Value = tuple.Item1; + mySqlCommand.Parameters["@name"].Value = tuple.Item2; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertSdtLinkageExtendedEvent(MySqlConnection connection, Guid source, LinkageDescriptor linkageDescriptor) + { + if (linkageDescriptor.ExtendedEventLinkages == null) + return; + + if (linkageDescriptor.ExtendedEventLinkages.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_sdt_linkages_extended_event_linkages " + + " (uuid, target_event, target_listed, event_simulcast, link_type, user_defined_id, target_tsid, target_onid, target_service_id) " + + "VALUES " + + " (@uuid,@target_event,@target_listed,@event_simulcast,@link_type,@user_defined_id,@target_tsid,@target_onid,@target_service_id)"; + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@target_event", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@target_listed", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@event_simulcast", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@link_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@user_defined_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@target_tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@target_onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@target_service_id", MySqlDbType.Int32); + + mySqlCommand.Parameters["@uuid"].Value = source.ToString(); + foreach (LinkageDescriptor.ExtendedEventLinkageInfo eventLinkage in linkageDescriptor.ExtendedEventLinkages) + { + mySqlCommand.Parameters["@target_event"].Value = eventLinkage.TargetEventId; + mySqlCommand.Parameters["@target_listed"].Value = eventLinkage.TargetListed; + mySqlCommand.Parameters["@event_simulcast"].Value = eventLinkage.EventSimulcast; + mySqlCommand.Parameters["@link_type"].Value = eventLinkage.LinkType; + mySqlCommand.Parameters["@user_defined_id"].Value = eventLinkage.UserDefinedId; + mySqlCommand.Parameters["@target_tsid"].Value = eventLinkage.TargetTransportStreamId; + mySqlCommand.Parameters["@target_onid"].Value = eventLinkage.TargetOriginalNetworkId; + mySqlCommand.Parameters["@target_service_id"].Value = eventLinkage.TargetServiceId; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + } + } + + private bool HasLinkages(ICollection links) + { + if (links == null) + return false; + + if (links.Count == 0) + return false; + + return true; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/MySqlDataStorage.cs b/DataTableStorages/skyscraper5.Data.MySql/MySqlDataStorage.cs new file mode 100644 index 0000000..2666d76 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/MySqlDataStorage.cs @@ -0,0 +1,518 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.NetworkInformation; +using MySqlConnector; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds.Messages; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.Teletext; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private readonly MySqlConnectionStringBuilder _mcsb; + + public MySqlDataStorage(MySqlConnectionStringBuilder mcsb) + { + _mcsb = mcsb; + } + + public bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + throw new NotImplementedException(); + } + + public void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + throw new NotImplementedException(); + } + + public bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, + string programmeService2) + { + throw new NotImplementedException(); + } + + public bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text) + { + throw new NotImplementedException(); + } + + public bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, PTY.ProgrammeTypeCodes pty) + { + throw new NotImplementedException(); + } + + public bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + throw new NotImplementedException(); + } + + + + + + + public bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, + RunningStatus runningStatus, DateTime currentTime) + { + throw new NotImplementedException(); + } + + public bool AprsGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, + double? speed) + { + throw new NotImplementedException(); + } + + + public DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid) + { + throw new NotImplementedException(); + } + + public void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + throw new NotImplementedException(); + } + + public bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + throw new NotImplementedException(); + } + + public void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + throw new NotImplementedException(); + } + + public void UiSatellitesAdd(SatellitePosition newPosition) + { + throw new NotImplementedException(); + } + + public void UiSatellitesDelete(SatellitePosition satellitePosition) + { + throw new NotImplementedException(); + } + + public bool UiTunerTestFor(TunerMetadata tuner) + { + throw new NotImplementedException(); + } + + public void UiTunerUpdate(TunerMetadata tuner) + { + throw new NotImplementedException(); + } + + public void UiTunerInsert(TunerMetadata tuner) + { + throw new NotImplementedException(); + } + + public void UiTunerGetConfiguration(TunerMetadata foundTuner) + { + throw new NotImplementedException(); + } + + public bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int currentLocation) + { + throw new NotImplementedException(); + } + + public void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int currentLocation) + { + throw new NotImplementedException(); + } + + public bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, + int currentLocation) + { + throw new NotImplementedException(); + } + + public void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, + int currentLocation) + { + throw new NotImplementedException(); + } + + public bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress) + { + throw new NotImplementedException(); + } + + + + public void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation) + { + throw new NotImplementedException(); + } + + public void WaitForCompletion() + { + if (IsEitWriterThreadRunning()) + { + eitWriterThread.Join(); + } + eitCoordinatesCache?.Clear(); + eitCoordinatesCacheIndex?.Clear(); + knownPmts?.Clear(); + knownPats?.Clear(); + _catCoordinates?.Clear(); + + _untGroupCoordinates?.Clear(); + _compatibilityCoordinates?.Clear(); + _platformCoordinates?.Clear(); + _batTransportStreamCoordinates?.Clear(); + _sdtCoordinates?.Clear(); + _tdtCoordinates?.Clear(); + _totCoordinates?.Clear(); + _tsdtCoordinates?.Clear(); + + if (IsSpeedhackThreadAlive()) + { + _waitingForSpeedhacksToComplete = true; + _speedhackThread.Join(); + _speedhackQueue.Clear(); + _waitingForSpeedhacksToComplete = false; + } + + _sdtUpdateCoordinates?.Clear(); + } + + public void UiSetVersion(int version) + { + throw new NotImplementedException(); + } + + public bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + throw new NotImplementedException(); + } + + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan duration, int tstype) + { + throw new NotImplementedException(); + } + + public bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + throw new NotImplementedException(); + } + + public void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + throw new NotImplementedException(); + } + + public void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset) + { + throw new NotImplementedException(); + } + + public List UiLnbTypesListAll() + { + throw new NotImplementedException(); + } + + public void UiLnbTypesAdd(LnbType defaultLnbType) + { + throw new NotImplementedException(); + } + + public List UiDishTypesListAll() + { + throw new NotImplementedException(); + } + + public void UiDishTypesAdd(DishType defaultDishType) + { + throw new NotImplementedException(); + } + + public object[] GetPluginConnector() + { + throw new NotImplementedException(); + } + + public void Ping() + { + throw new NotImplementedException(); + } + + public IEnumerable> SelectAllPmt() + { + throw new NotImplementedException(); + } + + public SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber) + { + throw new NotImplementedException(); + } + + public IEnumerable> SelectAllSdt() + { + throw new NotImplementedException(); + } + + public void BeamsDisableAll() + { + throw new NotImplementedException(); + } + + public void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp) + { + throw new NotImplementedException(); + } + + public void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string getPolygonString, string id) + { + throw new NotImplementedException(); + } + + public bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string id) + { + throw new NotImplementedException(); + } + + public void BeamsDisableSpecific(int databasePointerId, float databasePointerSatpos, string databasePointerName, DateTime databasePointerBeamsProcessTimestamp) + { + throw new NotImplementedException(); + } + + public IEnumerable BeamsSelectEnabled() + { + throw new NotImplementedException(); + } + + public List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp) + { + throw new NotImplementedException(); + } + + public void InsertBlindscanJob(DbBlindscanJob jobInDb) + { + throw new NotImplementedException(); + } + + public void UpdateJobState(DbBlindscanJob jobInDb) + { + throw new NotImplementedException(); + } + + public void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, SearchResult2 searchResult2) + { + throw new NotImplementedException(); + } + + public void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, BlindscanResultState blindscanResultState, SearchResult2 searchResult2) + { + throw new NotImplementedException(); + } + + public void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1, SearchResult2 resultSr2, HumanReadableService humanReadableService) + { + throw new NotImplementedException(); + } + + public bool TestForIncompleteJob() + { + throw new NotImplementedException(); + } + + public DbBlindscanJob GetPastBlindscanJob(long offset) + { + throw new NotImplementedException(); + } + + public void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value) + { + throw new NotImplementedException(); + } + + public IReadOnlyList ListImportFileByTag1(int tag1) + { + throw new NotImplementedException(); + } + + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId) + { + throw new NotImplementedException(); + } + + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp) + { + throw new NotImplementedException(); + } + + public bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + throw new NotImplementedException(); + } + + public void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + throw new NotImplementedException(); + } + + public int GetRmtTransmissionStandard(ushort networkId) + { + throw new NotImplementedException(); + } + + public byte[] GetTmst(ushort interactiveNetworkId) + { + throw new NotImplementedException(); + } + + public void InsertTmst(ushort interactiveNetworkId, byte[] modes) + { + throw new NotImplementedException(); + } + + public void UpdateTmst(ushort interactiveNetworkId, byte[] modes) + { + throw new NotImplementedException(); + } + + public bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + throw new NotImplementedException(); + } + + public void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + throw new NotImplementedException(); + } + + public bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + throw new NotImplementedException(); + } + + public void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + throw new NotImplementedException(); + } + + public bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + throw new NotImplementedException(); + } + + public void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + throw new NotImplementedException(); + } + + public bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame) + { + throw new NotImplementedException(); + } + + public void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame) + { + throw new NotImplementedException(); + } + + public bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + throw new NotImplementedException(); + } + + public void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + throw new NotImplementedException(); + } + + public void CreateTim(PhysicalAddress mac) + { + throw new NotImplementedException(); + } + + public bool TestForTim(PhysicalAddress mac) + { + throw new NotImplementedException(); + } + + public bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + throw new NotImplementedException(); + } + + public bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew) + { + throw new NotImplementedException(); + } + + public bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor) + { + throw new NotImplementedException(); + } + + public bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamp) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDbBlindscanJobs() + { + throw new NotImplementedException(); + } + + public void DeleteBlindscanJob(Guid guid) + { + throw new NotImplementedException(); + } + + public void DeleteBlindscanResults(Guid jobGuid, int i) + { + throw new NotImplementedException(); + } + + public void MoveBlScanResultsToAnotherJob(Guid jobGuid1, Guid jobGuid2, int j) + { + throw new NotImplementedException(); + } + + public long DnsCountA() + { + throw new NotImplementedException(); + } + + public string DnsIpToName(IPAddress source) + { + throw new NotImplementedException(); + } + + public void RememberDnsRecord(DnsRecord record) + { + throw new NotImplementedException(); + } + + public bool TestForIp(IPAddress iP) + { + throw new NotImplementedException(); + } + + } +} \ No newline at end of file diff --git a/DataTableStorages/skyscraper5.Data.MySql/Nit.cs b/DataTableStorages/skyscraper5.Data.MySql/Nit.cs new file mode 100644 index 0000000..56d3d0c --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Nit.cs @@ -0,0 +1,999 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Split; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private void InsertNitCells(MySqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Cells == null) + return; + if (nitNetwork.Cells.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_cells" + + " (nid_id, cell_id, cell_longitude, cell_latitude, extend_of_latitude, extend_of_longitude) " + + "VALUES" + + " (@nid_id,@cell_id,@cell_longitude,@cell_laitutde,@extend_of_latitude,@extend_of_longitude)"; + mySqlCommand.Parameters.Add("@nid_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_longitude", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_laitutde", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@extend_of_latitude", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@extend_of_longitude", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid_id"].Value = nitNetwork.NetworkId; + foreach (CellListDescriptor.Cell cell in nitNetwork.Cells) + { + mySqlCommand.Parameters["@cell_id"].Value = cell.CellId; + mySqlCommand.Parameters["@cell_longitude"].Value = cell.CellLongitude; + mySqlCommand.Parameters["@cell_laitutde"].Value = cell.CellLatitude; + mySqlCommand.Parameters["@extend_of_latitude"].Value = cell.ExtentOfLatitude; + mySqlCommand.Parameters["@extend_of_longitude"].Value = cell.ExtentOfLongitude; + mySqlCommand.ExecuteNonQuery(); + InsertNitCellSubcells(connection, nitNetwork.NetworkId, cell); + } + } + + private void InsertNitCellSubcells(MySqlConnection connection, ushort nitNetworkNetworkId, CellListDescriptor.Cell cell) + { + if (cell.Subcells == null) + return; + if (cell.Subcells.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_cells_subcells " + + " (nid, cell_id, cell_id_extension, subcell_latitude, subcell_longitude, subcell_extent_of_latitude, subcell_extent_of_longitude) " + + "VALUES " + + " (@nid,@cell_id,@cell_id_extension,@subcell_latitude,@subcell_longitude,@subcell_extend_of_latitude,@subcell_extend_of_longitude"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id_extension", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@subcell_latitude", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_longitude", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_extend_of_latitude", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_extend_of_longitude", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = nitNetworkNetworkId; + mySqlCommand.Parameters["@cell_id"].Value = cell.CellId; + foreach (CellListDescriptor.Subcell subcell in cell.Subcells) + { + mySqlCommand.Parameters["@cell_id_extension"].Value = subcell.CellIdExtension; + mySqlCommand.Parameters["@subcell_latitude"].Value = subcell.SubcellLatitude; + mySqlCommand.Parameters["@subcell_longitude"].Value = subcell.SubcellLongitude; + mySqlCommand.Parameters["@subcell_extend_of_latitude"].Value = subcell.SubcellExtentOfLatitude; + mySqlCommand.Parameters["@subcell_extend_of_longitude"].Value = subcell.SubcellExtentOfLongitude; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitMessages(MySqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Messages == null) + return; + if (nitNetwork.Messages.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_messages (network_id, message_id, language_code, message) VALUES (@network_id,@message_id,@language_code,@message)"; + mySqlCommand.Parameters.Add("@network_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@message_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@language_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@message", MySqlDbType.VarChar); + mySqlCommand.Parameters["@network_id"].Value = nitNetwork.NetworkId; + foreach (MessageDescriptor message in nitNetwork.Messages) + { + mySqlCommand.Parameters["@message_id"].Value = message.MessageId; + mySqlCommand.Parameters["@language_code"].Value = message.Iso639LanguageCode; + mySqlCommand.Parameters["@message"].Value = message.Message; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitMultilingualName(MySqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.MultilingualNetworkName == null) + return; + if (nitNetwork.MultilingualNetworkName.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_multilingual_name (id, language, name) VALUES (@id,@language,@name)"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@language", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@name", MySqlDbType.VarChar); + mySqlCommand.Parameters["@id"].Value = nitNetwork.NetworkId; + foreach (KeyValuePair pair in nitNetwork.MultilingualNetworkName) + { + mySqlCommand.Parameters["@language"].Value = pair.Key; + mySqlCommand.Parameters["@name"].Value = pair.Value; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitRegionNames(MySqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.RegionNames == null) + return; + if (nitNetwork.RegionNames.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_region_names (id, name, primary_code, secondary_code, teritary_code) VALUES (@id,@name,@primary,@secondary,@teritary)"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@primary", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@secondary", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@teritary", MySqlDbType.Int32); + mySqlCommand.Parameters["@id"].Value = nitNetwork.NetworkId; + foreach (TargetRegionNameDescriptor.TargetRegionName regionName in nitNetwork.RegionNames) + { + mySqlCommand.Parameters["@name"].Value = regionName.RegionName; + mySqlCommand.Parameters["@primary"].Value = regionName.PrimaryRegionCode; + mySqlCommand.Parameters["@secondary"].Value = regionName.SecondaryRegionCode; + mySqlCommand.Parameters["@teritary"].Value = regionName.TertiaryRegionCode; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitRegions(MySqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Regions == null) + return; + if (nitNetwork.RegionNames.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_regions " + + "(id, country_code, primary_region_code, secondary_region_code, teritary_region_code) " + + "VALUES " + + "(@id,@country,@primary,@secondary,@teritary)"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@country", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@primary", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@secondary", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@teritary", MySqlDbType.Int32); + mySqlCommand.Parameters["@id"].Value = nitNetwork.NetworkId; + foreach (TargetRegionDescriptor.TargetRegion region in nitNetwork.Regions) + { + mySqlCommand.Parameters["@country"].Value = region.CountryCode; + mySqlCommand.Parameters["@primary"].Value = region.PrimaryRegionCode; + mySqlCommand.Parameters["@secondary"].Value = region.SecondaryRegionCode; + mySqlCommand.Parameters["@teritary"].Value = region.TertiaryRegionCode; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitServices(MySqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.ServiceList == null) + return; + if (nitNetwork.ServiceList.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_services (network_id, service_id, service_type) VALUES (@network_id,@service_id,@service_type)"; + mySqlCommand.Parameters.Add("@network_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_type", MySqlDbType.Int32); + mySqlCommand.Parameters["@network_id"].Value = nitNetwork.NetworkId; + foreach (ServiceListDescriptor.Service service in nitNetwork.ServiceList) + { + mySqlCommand.Parameters["@service_id"].Value = service.ServiceId; + mySqlCommand.Parameters["@service_type"].Value = (int)service.ServiceType; + mySqlCommand.ExecuteNonQuery(); + } + } + + private NitNetwork GetNitNetwork(ushort newerNetworkId) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "SELECT * FROM dvb_nit WHERE id = @id"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters["@id"].Value = newerNetworkId; + MySqlDataReader dataReader = mySqlCommand.ExecuteReader(); + NitNetwork result = null; + if (dataReader.Read()) + { + ushort id = dataReader.GetUInt16(0); + result = new NitNetwork(id); + if (!dataReader.IsDBNull(1)) + result.Name = dataReader.GetString(1); + if (!dataReader.IsDBNull(2)) + ; + if (!dataReader.IsDBNull(3)) + result.PrivateDataSpecifierId = dataReader.GetUInt32(3); + if (!dataReader.IsDBNull(4)) + result.XaitPid = dataReader.GetUInt16(4); + if (!dataReader.IsDBNull(5)) + result.RegionNameCountryCode = dataReader.GetString(5); + if (!dataReader.IsDBNull(6)) + result.RegionNameLanguageCode = dataReader.GetString(6); + if (!dataReader.IsDBNull(7)) + result.RegionCountryCode = dataReader.GetString(7); + if (!dataReader.IsDBNull(8)) + result.MinPollingInterval = dataReader.GetUInt16(8); + if (!dataReader.IsDBNull(9)) + result.Uri = dataReader.GetString(9); + if (!dataReader.IsDBNull(10)) + result.UriLinkageType = dataReader.GetByte(10); + dataReader.Close(); + } + connection.Close(); + return result; + } + } + + private void InsertNitTransportStreamCellFrequencies(MySqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.CellFrequencies == null) + return; + + if (transportStream.CellFrequencies.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_cell_frequencies (nid, tsid, cell_id, frequency) VALUES (@nid,@tsid,@cell_id, @frequency)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@frequency", MySqlDbType.Int64); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStream.TransportStreamId; + foreach (CellFrequencyLinkDescriptor.Cell cell in transportStream.CellFrequencies) + { + mySqlCommand.Parameters["@cell_id"].Value = cell.CellId; + mySqlCommand.Parameters["@frequency"].Value = cell.Frequency; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitTransportStreamCellInfos(MySqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.CellInfos == null) + return; + + if (transportStream.CellInfos.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_cell_infos (nid, tsid, cell_id) VALUES (@nid,@tsid,@cell_id)"; + mySqlCommand.Parameters.Add("@nid", DbType.Int32); + mySqlCommand.Parameters.Add("@tsid", DbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", DbType.Int32); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStream.TransportStreamId; + foreach (T2DeliverySystemDescriptor.CellInfo cellInfo in transportStream.CellInfos) + { + mySqlCommand.Parameters["@cell_id"].Value = cellInfo.CellId; + mySqlCommand.ExecuteNonQuery(); + + InsertNitTransportStreamCellInfosCenterFrequencies(connection, networkId, transportStream.TransportStreamId, cellInfo); + InsertNitTransportStreamCellInfosSubcellInfos(connection, networkId, transportStream.TransportStreamId, cellInfo); + } + } + + private void InsertNitTransportStreamCellInfosSubcellInfos(MySqlConnection connection, ushort networkId, ushort transportStreamTransportStreamId, T2DeliverySystemDescriptor.CellInfo cellInfo) + { + if (cellInfo.CentreFrequencies == null) + return; + + if (cellInfo.CentreFrequencies.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_cell_infos_subcell_infos (nid, tsid, cell_id, ordinal, cell_id_extension, transposer_frequency) " + + "VALUES (@nid,@tsid,@cell_id,@ordinal,@cell_id_extension,@transposeer_frequency)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ordinal", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id_extension", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@transposer_frequency", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStreamTransportStreamId; + mySqlCommand.Parameters["@cell_id"].Value = cellInfo.CellId; + for (int i = 0; i < cellInfo.SubcellInfos.Length; i++) + { + mySqlCommand.Parameters["@ordinal"].Value = i; + mySqlCommand.Parameters["@cell_id_extension"].Value = cellInfo.SubcellInfos[i].CellIdExtension; + mySqlCommand.Parameters["@transposer_frequency"].Value = cellInfo.SubcellInfos[i].TransposerFrequency; + mySqlCommand.ExecuteNonQuery(); + } + + } + + private void InsertNitTransportStreamCellInfosCenterFrequencies(MySqlConnection connection, ushort networkId, ushort transportStreamTransportStreamId, T2DeliverySystemDescriptor.CellInfo cellInfo) + { + if (cellInfo.CentreFrequencies == null) + return; + + if (cellInfo.CentreFrequencies.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_cell_infos_center_frequencies (nid, tsid, cell_id, ordinal, frequency) " + + "VALUES (@nid, @tsid, @cell_id, @ordinal, @frequency)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ordinal", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@frequency", MySqlDbType.Int64); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStreamTransportStreamId; + mySqlCommand.Parameters["@cell_id"].Value = cellInfo.CellId; + for (int i = 0; i < cellInfo.CentreFrequencies.Length; i++) + { + mySqlCommand.Parameters["@ordinal"].Value = i; + mySqlCommand.Parameters["@frequency"].Value = cellInfo.CentreFrequencies[i]; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitTransportStreamCells(MySqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.Cells == null) + return; + + if (transportStream.Cells.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_cells (nid, tsid, cell_id, cell_lat, cell_lon, extent_lat, extent_lon) " + + "VALUES (@nid, @tsid, @cell_id, @cell_lat, @cell_lon, @extent_lat, @extent_lon)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_lat", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_lon", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@extent_lat", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@extent_lon", MySqlDbType.Int64); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStream.TransportStreamId; + foreach (CellListDescriptor.Cell cell in transportStream.Cells) + { + mySqlCommand.Parameters["@cell_id"].Value = cell.CellId; + mySqlCommand.Parameters["@cell_lat"].Value = cell.CellLatitude; + mySqlCommand.Parameters["@cell_lon"].Value = cell.CellLongitude; + mySqlCommand.Parameters["@extent_lat"].Value = cell.ExtentOfLatitude; + mySqlCommand.Parameters["@extent_lon"].Value = cell.ExtentOfLongitude; + mySqlCommand.ExecuteNonQuery(); + + InsertDvbNitTransportStreamCellSubcells(connection, networkId, transportStream.TransportStreamId, cell); + } + } + + private void InsertDvbNitTransportStreamCellSubcells(MySqlConnection connection, ushort networkId, ushort transportStreamTransportStreamId, CellListDescriptor.Cell cell) + { + if (cell.Subcells == null) + return; + + if (cell.Subcells.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_cells_subcells (nid, tsid, cell_id, cell_id_extension, subcell_lat, subcell_lon, subcell_extent_lat, subcell_extent_lon) " + + "VALUES (@nid, @tsid, @cell_id, @cell_id_extension, @subcell_lat, @subcell_lon, @subcell_extent_lat, @subcell_extent_lon)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@cell_id_extension", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_lat", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_lon", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_extent_lat", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@subcell_extent_lon", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStreamTransportStreamId; + mySqlCommand.Parameters["@cell_id"].Value = cell.CellId; + foreach (CellListDescriptor.Subcell subcell in cell.Subcells) + { + mySqlCommand.Parameters["@cell_id_extension"].Value = subcell.CellIdExtension; + mySqlCommand.Parameters["@subcell_lat"].Value = subcell.SubcellLatitude; + mySqlCommand.Parameters["@subcell_lon"].Value = subcell.SubcellLongitude; + mySqlCommand.Parameters["@subcell_extent_lat"].Value = subcell.SubcellExtentOfLatitude; + mySqlCommand.Parameters["@subcell_extent_lon"].Value = subcell.SubcellExtentOfLongitude; + mySqlCommand.ExecuteNonQuery(); + } + } + + + private void InsertNitTransportStreamCentreFrequencies(MySqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.CentreFrequencies == null) + return; + + if (transportStream.CentreFrequencies.Length == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream_centre_frequencies (nid, tsid, ordinal, frequency) " + + "VALUES (@nid,@tsid,@ordinal,@frequency)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ordinal", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@frequency", MySqlDbType.Int64); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStream.TransportStreamId; + for (int i = 0; i < transportStream.CentreFrequencies.Length; i++) + { + mySqlCommand.Parameters["@ordinal"].Value = i; + mySqlCommand.Parameters["@frequency"].Value = transportStream.CentreFrequencies[i]; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertNitTransportStreamServices(MySqlConnection connection, ushort networkId, NitTransportStream transportStream, MySqlTransaction transaction) + { + if (transportStream.Services == null) + return; + + if (transportStream.Services.Services == null) + return; + + if (transportStream.Services.Services.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_nit_transport_stream_services (nid, tsid, service_id, service_type) " + + "VALUES (@nid, @tsid, @service_id, @service_type)"; + command.Parameters.Add("@nid", MySqlDbType.Int32); + command.Parameters.Add("@tsid", MySqlDbType.Int32); + command.Parameters.Add("@service_id", MySqlDbType.Int32); + command.Parameters.Add("@service_type", MySqlDbType.Int32); + command.Parameters["@nid"].Value = networkId; + command.Parameters["@tsid"].Value = transportStream.TransportStreamId; + foreach (ServiceListDescriptor.Service service in transportStream.Services.Services) + { + command.Parameters["@service_id"].Value = service.ServiceId; + command.Parameters["@service_type"].Value = (int)service.ServiceType; + command.ExecuteNonQuery(); + } + } + + private void InsertNitTransportStreamTargetRegions(MySqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.TargetRegions == null) + return; + + if (transportStream.TargetRegions.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_nit_transport_stream_target_regions (nid, tsid, country_code, primary_rc, secondary_rc, teritary_rc) " + + "VALUES (@nid,@tsid,@country_code,@primary_rc,@secondary_rc,@teritary_rc)"; + command.Parameters.Add("@nid", MySqlDbType.Int32); + command.Parameters.Add("@tsid", MySqlDbType.Int32); + command.Parameters.Add("@country_code", MySqlDbType.VarChar); + command.Parameters.Add("@primary_rc", MySqlDbType.Int16); + command.Parameters.Add("@secondary_rc", MySqlDbType.Int16); + command.Parameters.Add("@teritary_rc", MySqlDbType.Int32); + command.Parameters["@nid"].Value = networkId; + command.Parameters["@tsid"].Value = transportStream.TransportStreamId; + foreach (TargetRegionDescriptor.TargetRegion targetRegion in transportStream.TargetRegions) + { + command.Parameters["@country_code"].Value = targetRegion.CountryCode; + command.Parameters["@primary_rc"].Value = targetRegion.PrimaryRegionCode; + command.Parameters["@secondary_rc"].Value = targetRegion.SecondaryRegionCode; + command.Parameters["@teritary_rc"].Value = targetRegion.TertiaryRegionCode; + SetNulls(command); + command.ExecuteNonQuery(); + } + } + + private NitTransportStream GetNitTransportStream(MySqlConnection connection, ushort networkId, ushort olderTransportStreamId) + { + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "SELECT * FROM dvb_nit_transport_stream WHERE nid = @nid AND tsid = @tsid"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = olderTransportStreamId; + MySqlDataReader dataReader = mySqlCommand.ExecuteReader(); + if (!dataReader.Read()) + { + dataReader.Close(); + return null; + } + + ushort transportStreamId = dataReader.GetUInt16(1); + ushort originalNetworkId = dataReader.GetUInt16(3); + NitTransportStream.TransportMedium deliveryMethod = (NitTransportStream.TransportMedium)dataReader.GetInt32(4); + + NitTransportStream result = new NitTransportStream(originalNetworkId, transportStreamId); + result.DeliveryMethod = deliveryMethod; + + if (!dataReader.IsDBNull(5)) + result.East = dataReader.GetBoolean(5); + + if (!dataReader.IsDBNull(6)) + result.FecInner = (SatelliteDeliverySystemDescriptor.InnerFecScheme)dataReader.GetInt32(6); + + if (!dataReader.IsDBNull(7)) + result.Frequency = dataReader.GetInt64(7); + + if (!dataReader.IsDBNull(8)) + result.OrbitalPosition = dataReader.GetFloat(8); + + if (!dataReader.IsDBNull(9)) + result.Polarization = (SatelliteDeliverySystemDescriptor.PolarizationEnum)dataReader.GetInt32(9); + + if (!dataReader.IsDBNull(10)) + result.RollOff = dataReader.GetFloat(10); + + if (!dataReader.IsDBNull(11)) + result.S2 = dataReader.GetBoolean(11); + + if (!dataReader.IsDBNull(12)) + result.SymbolRate = dataReader.GetInt64(12); + + if (!dataReader.IsDBNull(13)) + result.ScramblingSequenceIndex = dataReader.GetInt32(13); + + if (!dataReader.IsDBNull(14)) + result.InputStreamIdentifier = dataReader.GetByte(14); + + if (!dataReader.IsDBNull(15)) + result.TimesliceNumber = dataReader.GetByte(15); + + if (!dataReader.IsDBNull(16)) + result.TsGsMode = (S2SatelliteDeliverySystemDescriptor.TsGsModeCoding)dataReader.GetInt32(16); + + if (!dataReader.IsDBNull(17)) + result.PrivateDataSpecifierId = dataReader.GetUInt32(17); + + if (!dataReader.IsDBNull(18)) + result.TfsFlag = dataReader.GetBoolean(18); + + if (!dataReader.IsDBNull(19)) + result.Bandwidth = dataReader.GetInt32(19); + + if (!dataReader.IsDBNull(20)) + result.GuardInterval = dataReader.GetInt32(20); + + if (!dataReader.IsDBNull(21)) + result.OtherFrequencyFlag = dataReader.GetBoolean(21); + + if (!dataReader.IsDBNull(22)) + result.PlpId = dataReader.GetByte(22); + + if (!dataReader.IsDBNull(23)) + result.SisoMiso = dataReader.GetInt32(23); + + if (!dataReader.IsDBNull(24)) + result.T2SystemId = dataReader.GetUInt16(24); + + if (!dataReader.IsDBNull(25)) + result.TransmissionMode = dataReader.GetInt32(25); + + if (!dataReader.IsDBNull(26)) + result.CodingType = (FrequencyListDescriptor.CodingTypeValue)dataReader.GetInt32(26); + + if (!dataReader.IsDBNull(27)) + result.ModulationType = dataReader.GetInt32(27); + + if (!dataReader.IsDBNull(28)) + result.FecOuter = (CableDeliverySystemDescriptor.OuterFecScheme)dataReader.GetInt32(28); + + if (!dataReader.IsDBNull(29)) + result.CodeRateHpStream = (TerristialDeliverySystemDescriptor.CodeRate)dataReader.GetInt32(29); + + if (!dataReader.IsDBNull(30)) + result.CodeRateLpStream = (TerristialDeliverySystemDescriptor.CodeRate)dataReader.GetInt32(30); + + if (!dataReader.IsDBNull(31)) + result.HierarchyInformation = (TerristialDeliverySystemDescriptor.HierarchySignallingFormat)dataReader.GetInt32(31); + + if (!dataReader.IsDBNull(32)) + result.MpeFecIndicator = dataReader.GetBoolean(32); + + if (!dataReader.IsDBNull(33)) + result.Priority = dataReader.GetBoolean(33); + + if (!dataReader.IsDBNull(34)) + result.TimeSlicingIndicator = dataReader.GetInt32(34); + + if (!dataReader.IsDBNull(35)) + result.NetworkName = dataReader.GetString(35); + + if (!dataReader.IsDBNull(36)) + result.TargetRegionCountryCode = dataReader.GetString(36); + + dataReader.Close(); + return result; + } + + struct NitTransportStreamCoordinate + { + private readonly ushort _networkId; + private readonly ushort _transportStreamTransportStreamId; + + public NitTransportStreamCoordinate(ushort networkId, ushort transportStreamTransportStreamId) + { + _networkId = networkId; + _transportStreamTransportStreamId = transportStreamTransportStreamId; + } + + public bool Equals(NitTransportStreamCoordinate other) + { + return _networkId == other._networkId && _transportStreamTransportStreamId == other._transportStreamTransportStreamId; + } + + public override bool Equals(object obj) + { + return obj is NitTransportStreamCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_networkId, _transportStreamTransportStreamId); + } + } + + private HashSet _nitTransportStreamCoordinates; + private HashSet _nitTransportStreamUpdateCoordinates; + + public bool TestForNitNetwork(NitNetwork nitNetwork) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "SELECT dateadded FROM dvb_nit WHERE id = @id"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters["@id"].Value = nitNetwork.NetworkId; + MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); + bool result = mySqlDataReader.Read(); + mySqlDataReader.Close(); + connection.Close(); + return result; + } + } + + public void StoreNitNetwork(NitNetwork nitNetwork) + { + bool hasLinkages = HasLinkages(nitNetwork.Linkages); + Guid linkageGuid = default(Guid); + if (hasLinkages) + linkageGuid = Guid.NewGuid(); + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = + "INSERT INTO dvb_nit" + + " (id, name, uuid, private_data_specifier_id, xait_pid, region_name_country_code, region_name_language_code, region_country_code, min_polling_interval, uri, uri_linkage_type) " + + "VALUES" + + " (@id,@name,@uuid,@private_data_specifier,@xait_pid,@region_name_country_code,@region_name_language_code,@region_country_code,@min_polling_interval,@uri,@uri_linkage_type)"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@private_data_specifier", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@xait_pid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@region_name_country_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@region_name_language_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@region_country_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@min_polling_interval", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@uri", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@uri_linkage_type", MySqlDbType.Int32); + mySqlCommand.Parameters["@id"].Value = nitNetwork.NetworkId; + mySqlCommand.Parameters["@name"].Value = nitNetwork.Name; + if (hasLinkages) + mySqlCommand.Parameters["@uuid"].Value = linkageGuid.ToString(); + mySqlCommand.Parameters["@private_data_specifier"].Value = nitNetwork.PrivateDataSpecifierId; + mySqlCommand.Parameters["@xait_pid"].Value = nitNetwork.XaitPid; + mySqlCommand.Parameters["@region_name_country_code"].Value = nitNetwork.RegionNameCountryCode; + mySqlCommand.Parameters["@region_name_language_code"].Value = nitNetwork.RegionNameLanguageCode; + mySqlCommand.Parameters["@region_country_code"].Value = nitNetwork.RegionCountryCode; + mySqlCommand.Parameters["@min_polling_interval"].Value = nitNetwork.MinPollingInterval; + mySqlCommand.Parameters["@uri"].Value = nitNetwork.Uri; + mySqlCommand.Parameters["@uri_linkage_type"].Value = nitNetwork.UriLinkageType; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + + if (hasLinkages) + InsertSdtLinkages(connection, linkageGuid, nitNetwork.Linkages, transaction); + + InsertNitCells(connection, nitNetwork); + InsertNitMessages(connection, nitNetwork); + InsertNitMultilingualName(connection, nitNetwork); + InsertNitRegionNames(connection, nitNetwork); + InsertNitRegions(connection, nitNetwork); + InsertNitServices(connection, nitNetwork); + transaction.Commit(); + connection.Close(); + } + } + + public bool UpdateNitNetwork(NitNetwork newer) + { + NitNetwork older = GetNitNetwork(newer.NetworkId); + if (older.NeedsUpdate(newer)) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "UPDATE dvb_nit " + + "SET name = @name, private_data_specifier_id = @private_data_specifier, xait_pid = @xait_pid, region_name_country_code = @region_name_country_code, region_name_language_code = @region_name_language_code, region_country_code = @region_country_code, min_polling_interval = @min_polling_interval, uri = @uri, uri_linkage_type = @uri_linkage_type, numupdates = numupdates + 1, dateupdated = CURRENT_TIMESTAMP " + + "WHERE id = @id"; + mySqlCommand.Parameters.Add("@id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@private_data_specifier", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@xait_pid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@region_name_country_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@region_name_language_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@region_country_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@min_polling_interval", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@uri", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@uri_linkage_type", MySqlDbType.Int32); + mySqlCommand.Parameters["@id"].Value = newer.NetworkId; + mySqlCommand.Parameters["@name"].Value = newer.Name; + mySqlCommand.Parameters["@private_data_specifier"].Value = newer.PrivateDataSpecifierId; + mySqlCommand.Parameters["@xait_pid"].Value = newer.XaitPid; + mySqlCommand.Parameters["@region_name_country_code"].Value = newer.RegionNameCountryCode; + mySqlCommand.Parameters["@region_name_language_code"].Value = newer.RegionNameLanguageCode; + mySqlCommand.Parameters["@region_country_code"].Value = newer.RegionCountryCode; + mySqlCommand.Parameters["@min_polling_interval"].Value = newer.MinPollingInterval; + mySqlCommand.Parameters["@uri"].Value = newer.Uri; + mySqlCommand.Parameters["@uri_linkage_type"].Value = newer.UriLinkageType; + mySqlCommand.ExecuteNonQuery(); + connection.Close(); + return true; + } + } + return false; + } + + public bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + if (_nitTransportStreamCoordinates == null) + _nitTransportStreamCoordinates = new HashSet(); + NitTransportStreamCoordinate coordinate = new NitTransportStreamCoordinate(networkId, transportStream.TransportStreamId); + if (_nitTransportStreamCoordinates.Contains(coordinate)) + return true; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "SELECT dateadded FROM dvb_nit_transport_stream WHERE nid=@nid AND tsid=@tsid"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStream.TransportStreamId; + MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); + bool result = mySqlDataReader.Read(); + if (result) + _nitTransportStreamCoordinates.Add(coordinate); + mySqlDataReader.Close(); + connection.Close(); + return result; + } + } + + public void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + EnqueueSpeedhack(SpeedhackType.InsertNitTransportStream, networkId, transportStream); + NitTransportStreamCoordinate coordinate = new NitTransportStreamCoordinate(networkId, transportStream.TransportStreamId); + _nitTransportStreamCoordinates.Add(coordinate); + + } + + public bool UpdateNitTransportStream(ushort networkId, NitTransportStream newer) + { + if (_nitTransportStreamUpdateCoordinates == null) + _nitTransportStreamUpdateCoordinates = new HashSet(); + + NitTransportStreamCoordinate coordinate = new NitTransportStreamCoordinate(networkId, newer.TransportStreamId); + if (_nitTransportStreamUpdateCoordinates.Contains(coordinate)) + return false; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + NitTransportStream older = GetNitTransportStream(connection, networkId, newer.TransportStreamId); + connection.Close(); + if (older == null) + { + return false; + } + + _nitTransportStreamUpdateCoordinates.Add(coordinate); + if (older.NeedUpdate(newer)) + { + EnqueueSpeedhack(SpeedhackType.UpdateNitTransportStream, networkId, newer); + return true; + } + return false; + } + } + + public int CountNitEntries() + { + using (MySqlConnection connectino = new MySqlConnection(_mcsb.ToString())) + { + connectino.Open(); + using (MySqlCommand command = connectino.CreateCommand()) + { + command.CommandText = "SELECT COUNT(*) FROM dvb_nit_transport_stream"; + MySqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + int result = dataReader.GetInt32(0); + dataReader.Close(); + connectino.Close(); + return result; + } + } + } + + public IEnumerable EnumerateNitTransportStreams() + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + using (MySqlCommand command = connection.CreateCommand()) + { + command.CommandText = "SELECT * FROM dvb_nit_transport_stream"; + MySqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + ushort nid = dataReader.GetUInt16(0); + ushort tsid = dataReader.GetUInt16(1); + DateTime dateAdded = dataReader.GetDateTime(2); + int onid = dataReader.GetUInt16(3); + NitTransportStream child = new NitTransportStream(nid, tsid); + child.DeliveryMethod = (NitTransportStream.TransportMedium)dataReader.GetInt32(4); + if (!dataReader.IsDBNull(5)) + child.East = dataReader.GetBoolean(5); + + if (!dataReader.IsDBNull(6)) + child.FecInner = (SatelliteDeliverySystemDescriptor.InnerFecScheme)dataReader.GetInt32(6); + + if (!dataReader.IsDBNull(7)) + child.Frequency = dataReader.GetInt64(7); + + if (!dataReader.IsDBNull(8)) + child.OrbitalPosition = dataReader.GetFloat(8); + + if (!dataReader.IsDBNull(9)) + child.Polarization = (SatelliteDeliverySystemDescriptor.PolarizationEnum)dataReader.GetInt32(9); + + if (!dataReader.IsDBNull(10)) + child.RollOff = dataReader.GetFloat(10); + + if (!dataReader.IsDBNull(11)) + child.S2 = dataReader.GetBoolean(11); + + if (!dataReader.IsDBNull(12)) + child.SymbolRate = dataReader.GetInt64(12); + + if (!dataReader.IsDBNull(13)) + child.ScramblingSequenceIndex = dataReader.GetInt32(13); + + if (!dataReader.IsDBNull(14)) + child.InputStreamIdentifier = dataReader.GetByte(14); + + if (!dataReader.IsDBNull(15)) + child.TimesliceNumber = dataReader.GetByte(15); + + if (!dataReader.IsDBNull(16)) + child.TsGsMode = (S2SatelliteDeliverySystemDescriptor.TsGsModeCoding)dataReader.GetInt32(16); + + if (!dataReader.IsDBNull(17)) + child.PrivateDataSpecifierId = (uint)dataReader.GetUInt32(17); + + if (!dataReader.IsDBNull(18)) + child.TfsFlag = dataReader.GetBoolean(18); + + if (!dataReader.IsDBNull(19)) + child.Bandwidth = dataReader.GetInt32(19); + + if (!dataReader.IsDBNull(20)) + child.GuardInterval = dataReader.GetInt32(20); + + if (!dataReader.IsDBNull(21)) + child.OtherFrequencyFlag = dataReader.GetBoolean(21); + + if (!dataReader.IsDBNull(22)) + child.PlpId = dataReader.GetByte(22); + + if (!dataReader.IsDBNull(23)) + child.SisoMiso = dataReader.GetInt32(23); + + if (!dataReader.IsDBNull(24)) + child.T2SystemId = dataReader.GetUInt16(24); + + if (!dataReader.IsDBNull(25)) + child.TransmissionMode = dataReader.GetInt32(25); + + if (!dataReader.IsDBNull(26)) + child.CodingType = (FrequencyListDescriptor.CodingTypeValue)dataReader.GetInt32(26); + + if (!dataReader.IsDBNull(27)) + child.ModulationType = dataReader.GetInt32(27); + + if (!dataReader.IsDBNull(28)) + child.FecOuter = (CableDeliverySystemDescriptor.OuterFecScheme)dataReader.GetInt32(28); + + if (!dataReader.IsDBNull(29)) + child.CodeRateHpStream = (TerristialDeliverySystemDescriptor.CodeRate)dataReader.GetInt32(29); + + if (!dataReader.IsDBNull(30)) + child.CodeRateLpStream = (TerristialDeliverySystemDescriptor.CodeRate)dataReader.GetInt32(30); + + if (!dataReader.IsDBNull(31)) + child.HierarchyInformation = (TerristialDeliverySystemDescriptor.HierarchySignallingFormat)dataReader.GetInt32(31); + + if (!dataReader.IsDBNull(32)) + child.MpeFecIndicator = dataReader.GetBoolean(32); + + if (!dataReader.IsDBNull(33)) + child.Priority = dataReader.GetBoolean(33); + + if (!dataReader.IsDBNull(34)) + child.TimeSlicingIndicator = dataReader.GetInt32(34); + + if (!dataReader.IsDBNull(35)) + child.NetworkName = dataReader.GetString(35); + + if (!dataReader.IsDBNull(36)) + child.TargetRegionCountryCode = dataReader.GetString(36); + + if (!dataReader.IsDBNull(37)) + Guid.Parse(dataReader.GetString(37)); + + if (!dataReader.IsDBNull(38)) + dataReader.GetInt32(38); + + if (!dataReader.IsDBNull(39)) + dataReader.GetDateTime(39); + + yield return child; + } + dataReader.Close(); + } + connection.Close(); + } + } + + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Pat.cs b/DataTableStorages/skyscraper5.Data.MySql/Pat.cs new file mode 100644 index 0000000..fa9cee3 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Pat.cs @@ -0,0 +1,58 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private bool TestForPatEntry(int currentNetworkId, int currentTransportStreamId, ushort programId, MySqlConnection connection) + { + PmtCoordinate coordinate = new PmtCoordinate(currentNetworkId, currentTransportStreamId, programId); + + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dvb_pat WHERE cnid = @cnid AND ctsid = @ctsid AND program_id = @program_id"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@program_id", programId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + { + knownPats.Add(coordinate); + } + dataReader.Close(); + return result; + } + + private HashSet knownPats; + + public bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId) + { + if (knownPats == null) + knownPats = new HashSet(); + PmtCoordinate coordinate = new PmtCoordinate(currentNetworkId, currentTransportStreamId, programId); + if (knownPats.Contains(coordinate)) + return false; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + int result = 0; + if (!TestForPatEntry(currentNetworkId, currentTransportStreamId, programId, connection)) + { + EnqueueSpeedhack(SpeedhackType.StorePat, currentNetworkId, currentTransportStreamId, pmtPid, programId); + knownPats.Add(coordinate); + } + connection.Close(); + return result > 0; + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Pmt.cs b/DataTableStorages/skyscraper5.Data.MySql/Pmt.cs new file mode 100644 index 0000000..7b233f3 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Pmt.cs @@ -0,0 +1,557 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using System.IO; +using static skyscraper5.Dvb.Descriptors.ApplicationSignallingDescriptor; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private bool TestForPmtStream(MySqlTransaction transaction, int onid, int tsid, int progno, int pid) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_pmt_streams WHERE onid = @onid AND tsid = @tsid AND program_number = @program_number AND elementary_pid = @elementary_pid"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@program_number", progno); + command.Parameters.AddOrSet("@elementary_pid", pid); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertPmtStreams(MySqlConnection connection, ProgramMapping mapping, int onid, int tsid, + MySqlTransaction mySqlTransaction) + { + if (mapping.Streams == null) + return; + + if (mapping.Streams.Count == 0) + return; + + + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_pmt_streams (onid, tsid, program_number, stream_type, elementary_pid, component_tag, audio_type, iso_639_language_code, carousel_format_id, carousel_id, data_broadcast_id, data_broadcast_selector, supplementary_independant_stream, editorial_classification, supplementary_language_code, asvc, bsid, component_type, main_id, frame_rate, mpeg1_only_flag, chroma_format, constrained_parameter_flag, frame_rate_extension_flag, multiple_framerate_flag, profile_and_level_indicator, still_picture_flag, alignment_type, maximum_bitrate, audio_stream_id, free_format, layer, variable_rate_audio, constraint_set0_flag, avc_24hour_picture_flag, avc_compatible_flags, avc_still_present, constraint_set1_flag, constraint_set2_flag, constraint_set3_flag, constraint_set4_flag, constraint_set5_flag, frame_packing_sei_not_present, level_idc, profile_rdc, ca_pid, ca_system_id, ca_private_data, private_data_specifier, format_identifier, additional_identification_info, association_tag_selector, association_tag_transaction_id, association_tag_use, association_tag_private_data, association_tag_timeout, aac_type, aac_additional_info, aac_profile_and_level, saoc_de_flag, ancillary_data_identifier, mix_info_exists, substream1, substream2, substream3, copied_44bits, frame_only_constraint_flag, hdr_wcg_idc, hevc_24hr_picture_present_flag, hevc_still_present_flag, interlaced_source_flag, non_packed_constraint_flag, profile_compatibility_indication, profile_idc, profile_space, progressive_source_flag, sub_pic_hrd_params_not_present_flag, temporal_id_max, tier_flag, temporal_id_min, ac4_channel_mode, ac4_dialog_enhancement, ac4_dsi, leak_valid_flag, sb_leak_rate, sb_size, private_data_indicator, _90khz_flag, picture_and_timing_info_present_flag, hdr_management_valid_flag, k, n, num_units_in_tick, target_schedule_idx, target_schedule_idx_not_present_flag, adaption_field_data_identifier, fixed_frame_rate_flag, picture_to_display_conversion_flag, temporal_poc_flag, mpeg4_audio_profile_and_level, aac_channel_configuration, aac_profile, aac_additional_information, scrambling_mode, related_content_descriptor_present, num_t2mi_streams, t2mi_stream_id, pcr_iscr_common_clock_flag) " + + "VALUES " + + "(@onid, @tsid, @program_number, " + + "@stream_type, @elementary_pid, @component_tag, @audio_type, @iso_639_language_code, @carousel_format_id, @carousel_id, @data_broadcast_id, @data_broadcast_selector, " + + "@supplementary_independant_stream, @editorial_classification, @supplementary_language_code, @asvc, @bsid, @component_type, @main_id, @frame_rate, @mpeg1_only_flag, " + + "@chroma_format, @constrained_parameter_flag, @frame_rate_extension_flag, @multiple_framerate_flag, @profile_and_level_indicator, @still_picture_flag, @alignment_type, " + + "@maximum_bitrate, @audio_stream_id, @free_format, @layer, @variable_rate_audio, @constraint_set0_flag, @avc_24hour_picture_flag, @avc_compatible_flags, @avc_still_present, @constraint_set1_flag, @constraint_set2_flag, @constraint_set3_flag, @constraint_set4_flag, @constraint_set5_flag, @frame_packing_sei_not_present, @level_idc, @profile_rdc, @ca_pid, @ca_system_id, @ca_private_data, @private_data_specifier, @format_identifier, @additional_identification_info, @association_tag_selector, @association_tag_transaction_id, @association_tag_use, @association_tag_private_data, @association_tag_timeout, @aac_type, @aac_additional_info, @aac_profile_and_level, @saoc_de_flag, @ancillary_data_identifier, @mix_info_exists, @substream1, @substream2, @substream3, @copied_44bits, @frame_only_constraint_flag, @hdr_wcg_idc, @hevc_24hr_picture_present_flag, @hevc_still_present_flag, @interlaced_source_flag, @non_packed_constraint_flag, @profile_compatibility_indication, @profile_idc, @profile_space, @progressive_source_flag, @sub_pic_hrd_params_not_present_flag, @temporal_id_max, @tier_flag, @temporal_id_min, @ac4_channel_mode, @ac4_dialog_enhancement, @ac4_dsi, @leak_valid_flag, @sb_leak_rate, @sb_size, @private_data_indicator, @_90khz_flag, @picture_and_timing_info_present_flag, @hdr_management_valid_flag, @k, @n, @num_units_in_tick, @target_schedule_idx, @target_schedule_idx_not_present_flag, @adaption_field_data_identifier, @fixed_frame_rate_flag, @picture_to_display_conversion_flag, @temporal_poc_flag, @mpeg4_audio_profile_and_level, @aac_channel_configuration, @aac_profile, @aac_additional_information, @scrambling_mode, @related_content_descriptor_present, @num_t2mi_streams, @t2mi_stream_id, @pcr_iscr_common_clock_flag)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@program_number", mapping.ProgramNumber); + foreach (ProgramMappingStream stream in mapping.Streams) + { + if (TestForPmtStream(mySqlTransaction, onid, tsid, mapping.ProgramNumber, stream.ElementaryPid)) + continue; + + command.Parameters.AddOrSet("@stream_type",(int)stream.StreamType); + command.Parameters.AddOrSet("@elementary_pid", stream.ElementaryPid); + command.Parameters.AddOrSet("@component_tag", stream.ComponentTag); + command.Parameters.AddOrSet("@audio_type", stream.AudioType); + command.Parameters.AddOrSet("@iso_639_language_code", stream.Iso639LanguageCode); + command.Parameters.AddOrSet("@carousel_format_id", stream.CarouselFormatId); + command.Parameters.AddOrSet("@carousel_id", stream.CarouselId); + command.Parameters.AddOrSet("@data_broadcast_id", stream.DataBroadcastId); + command.Parameters.AddOrSet("@data_broadcast_selector", stream.DataBroadcastSelector); + command.Parameters.AddOrSet("@supplementary_independant_stream", stream.SupplementaryIndependantStream); + command.Parameters.AddOrSet("@editorial_classification", stream.EditorialClassification); + command.Parameters.AddOrSet("@supplementary_language_code", stream.SupplementaryLanguageCode); + command.Parameters.AddOrSet("@asvc", stream.Asvc); + command.Parameters.AddOrSet("@bsid", stream.BSID); + command.Parameters.AddOrSet("@component_type", stream.ComponentType); + command.Parameters.AddOrSet("@main_id", stream.MainId); + command.Parameters.AddOrSet("@frame_rate", stream.FrameRate); + command.Parameters.AddOrSet("@mpeg1_only_flag", stream.Mpeg1OnlyFlag); + command.Parameters.AddOrSet("@chroma_format", stream.ChromaFormat); + command.Parameters.AddOrSet("@constrained_parameter_flag", stream.ConstrainedParameterFlag); + command.Parameters.AddOrSet("@frame_rate_extension_flag", stream.FrameRateExtensionFlag); + command.Parameters.AddOrSet("@multiple_framerate_flag", stream.MultipleFramerateFlag); + command.Parameters.AddOrSet("@profile_and_level_indicator", stream.ProfileAndLevelIndication); + command.Parameters.AddOrSet("@still_picture_flag", stream.StillPictureFlag); + command.Parameters.AddOrSet("@alignment_type", stream.AlignmentType); + command.Parameters.AddOrSet("@maximum_bitrate", stream.MaximumBitrate); + command.Parameters.AddOrSet("@audio_stream_id", stream.AudioStreamId); + command.Parameters.AddOrSet("@free_format", stream.FreeFormat); + command.Parameters.AddOrSet("@layer", stream.Layer); + command.Parameters.AddOrSet("@variable_rate_audio", stream.VariableRateAudio); + command.Parameters.AddOrSet("@constraint_set0_flag", stream.ConstraintSet0Flag); + command.Parameters.AddOrSet("@avc_24hour_picture_flag", stream.Avc24HourPictureFlag); + //@avc_compatible_flags, @avc_still_present, @constraint_set1_flag, @constraint_set2_flag, @constraint_set3_flag, @constraint_set4_flag, @constraint_set5_flag, + //@frame_packing_sei_not_present, @level_idc, @profile_rdc, @ca_pid, @ca_system_id, @ca_private_data, @private_data_specifier, @format_identifier, @additional_identification_info, + //@association_tag_selector, @association_tag_transaction_id, @association_tag_use, @association_tag_private_data, @association_tag_timeout, @aac_type, @aac_additional_info, + //@aac_profile_and_level, @saoc_de_flag, @ancillary_data_identifier, @mix_info_exists, @substream1, @substream2, + command.Parameters.AddOrSet("@avc_compatible_flags", stream.AvcCompatibleFlags); + command.Parameters.AddOrSet("@avc_still_present", stream.AvcStillPresent); + command.Parameters.AddOrSet("@constraint_set1_flag", stream.ConstraintSet1Flag); + command.Parameters.AddOrSet("@constraint_set2_flag", stream.ConstraintSet2Flag); + command.Parameters.AddOrSet("@constraint_set3_flag", stream.ConstraintSet3Flag); + command.Parameters.AddOrSet("@constraint_set4_flag", stream.ConstraintSet4Flag); + command.Parameters.AddOrSet("@constraint_set5_flag", stream.ConstraintSet5Flag); + command.Parameters.AddOrSet("@frame_packing_sei_not_present", stream.FramePackingSeiNotPresent); + command.Parameters.AddOrSet("@level_idc", stream.LevelIdc); + command.Parameters.AddOrSet("@profile_rdc", stream.ProfileIdc); + command.Parameters.AddOrSet("@ca_pid", stream.CaPid); + command.Parameters.AddOrSet("@ca_system_id", stream.CaSystemId); + command.Parameters.AddOrSet("@ca_private_data", stream.CaPrivateData); + command.Parameters.AddOrSet("@private_data_specifier", stream.PrivateDataSpecifier); + command.Parameters.AddOrSet("@format_identifier", stream.FormatIdentifier); + command.Parameters.AddOrSet("@additional_identification_info", stream.AdditionalIdentificationInfo); + command.Parameters.AddOrSet("@association_tag_selector", stream.AssociationTagSelector); + command.Parameters.AddOrSet("@association_tag_transaction_id", stream.AssociationTagTransactionId); + command.Parameters.AddOrSet("@association_tag_use", stream.AssociationTagUse); + command.Parameters.AddOrSet("@association_tag_private_data", stream.AssociationTagPrivateData); + command.Parameters.AddOrSet("@association_tag_timeout", stream.AssociationTagTimeOut); + command.Parameters.AddOrSet("@aac_type", stream.AacType); + command.Parameters.AddOrSet("@aac_additional_info", stream.AacAdditionalInfo); + command.Parameters.AddOrSet("@aac_profile_and_level", stream.AacProfileAndLevel); + command.Parameters.AddOrSet("@saoc_de_flag", stream.SaocDeFlag); + if (stream.AncillaryDataDescriptor != null) + { + command.Parameters.AddOrSet("@ancillary_data_identifier", stream.AncillaryDataDescriptor.AncillaryDataIdentifier); + } + else + { + command.Parameters.AddOrSet("@ancillary_data_identifier",(byte)0); + command.Parameters.AddOrSet("@ancillary_data_identifier",DBNull.Value); + } + command.Parameters.AddOrSet("@mix_info_exists", stream.MixInfoExists); + command.Parameters.AddOrSet("@substream1", stream.Substream1); + command.Parameters.AddOrSet("@substream2", stream.Substream2); + //@substream3, @copied_44bits, @frame_only_constraint_flag, @hdr_wcg_idc, @hevc_24hr_picture_present_flag, @hevc_still_present_flag, @interlaced_source_flag, + //@non_packed_constraint_flag, @profile_compatibility_indication, @profile_idc, @profile_space, @progressive_source_flag, @sub_pic_hrd_params_not_present_flag, + //@temporal_id_max, @tier_flag, @temporal_id_min, @ac4_channel_mode, @ac4_dialog_enhancement, @ac4_dsi, @leak_valid_flag, @sb_leak_rate, @sb_size, @private_data_indicator, + //@_90khz_flag, @picture_and_timing_info_present_flag, @hdr_management_valid_flag, @k, @n, @num_units_in_tick, @target_schedule_idx, @target_schedule_idx_not_present_flag, + //@adaption_field_data_identifier, @fixed_frame_rate_flag, @picture_to_display_conversion_flag, @temporal_poc_flag, @mpeg4_audio_profile_and_level + command.Parameters.AddOrSet("@substream3", stream.Substream3); + command.Parameters.AddOrSet("@copied_44bits", stream.Copied44bits); + command.Parameters.AddOrSet("@frame_only_constraint_flag", stream.FrameOnlyConstraintFlag); + command.Parameters.AddOrSet("@hdr_wcg_idc", stream.HdrWcgIdc); + command.Parameters.AddOrSet("@hevc_24hr_picture_present_flag", stream.Hevc24hrPicturePresentFlag); + command.Parameters.AddOrSet("@hevc_still_present_flag", stream.HevcStillPresentFlag); + command.Parameters.AddOrSet("@interlaced_source_flag", stream.InterlacedSourceFlag); + command.Parameters.AddOrSet("@non_packed_constraint_flag", stream.NonPackedConstraintFlag); + command.Parameters.AddOrSet("@profile_compatibility_indication", stream.ProfileCompatibilityIndication); + command.Parameters.AddOrSet("@profile_idc", stream.ProfileIdc); + command.Parameters.AddOrSet("@profile_space", stream.ProfileSpace); + command.Parameters.AddOrSet("@progressive_source_flag", stream.ProgressiveSourceFlag); + command.Parameters.AddOrSet("@sub_pic_hrd_params_not_present_flag", stream.SubPicHrdParamsNotPresentFlag); + command.Parameters.AddOrSet("@temporal_id_max", stream.TemporalIdMax); + command.Parameters.AddOrSet("@tier_flag", stream.TierFlag); + command.Parameters.AddOrSet("@temporal_id_min", stream.TemporalIdMin); + command.Parameters.AddOrSet("@ac4_channel_mode", stream.Ac4ChannelMode); + command.Parameters.AddOrSet("@ac4_dialog_enhancement", stream.Ac4DialogEnhancement); + command.Parameters.AddOrSet("@ac4_dsi", stream.Ac4Dsi); + command.Parameters.AddOrSet("@leak_valid_flag", stream.LeakValidFlag); + command.Parameters.AddOrSet("@sb_leak_rate", stream.SbLeakRate); + command.Parameters.AddOrSet("@sb_size", stream.SbSize); + command.Parameters.AddOrSet("@private_data_indicator", stream.PrivateDataIndicator); + command.Parameters.AddOrSet("@_90khz_flag", stream._90khzFlag); + command.Parameters.AddOrSet("@picture_and_timing_info_present_flag", stream.PictureAndTimingInfoPresentFlag); + command.Parameters.AddOrSet("@hdr_management_valid_flag", stream.HdrManagementValidFlag); + command.Parameters.AddOrSet("@k", stream.K); + command.Parameters.AddOrSet("@n", stream.N); + command.Parameters.AddOrSet("@num_units_in_tick", stream.NumUnitsInTick); + command.Parameters.AddOrSet("@target_schedule_idx", stream.TargetScheduleIdx); + command.Parameters.AddOrSet("@target_schedule_idx_not_present_flag", stream.TargetScheduleIdxNotPresentFlag); + command.Parameters.AddOrSet("@adaption_field_data_identifier", stream.AdaptionFieldDataIdentifier); + command.Parameters.AddOrSet("@fixed_frame_rate_flag", stream.FixedFrameRateFlag); + command.Parameters.AddOrSet("@picture_to_display_conversion_flag", stream.PictureToDisplayConversionFlag); + command.Parameters.AddOrSet("@temporal_poc_flag", stream.TemporalPocFlag); + command.Parameters.AddOrSet("@mpeg4_audio_profile_and_level", stream.Mpeg4AudioProfileAndLevel); + //@, @, @, @, @, @, @, @pcr_iscr_common_clock_flag)"; + command.Parameters.AddOrSet("@aac_channel_configuration", stream.AacChannelConfiguration); + command.Parameters.AddOrSet("@aac_profile", stream.AacProfile); + command.Parameters.AddOrSet("@aac_additional_information", stream.AacAdditionalInformation); + command.Parameters.AddOrSet("@scrambling_mode", stream.ScramblingMode); + command.Parameters.AddOrSet("@related_content_descriptor_present", stream.RelatedContentDescriptorPresent); + command.Parameters.AddOrSet("@num_t2mi_streams", stream.NumT2MiStreams); + command.Parameters.AddOrSet("@t2mi_stream_id", stream.T2MiStreamId); + command.Parameters.AddOrSet("@pcr_iscr_common_clock_flag", stream.PcrIscrCommonClockFlag); + command.Transaction = mySqlTransaction; + SetNulls(command); + command.ExecuteNonQuery(); + + InsertPmtStreamsApplications(onid, tsid, stream, connection, mySqlTransaction); + InsertPmtStreamsAudioPreselection(onid, tsid, stream, mySqlTransaction); + InsertPmtStreamsFlexmux(onid, tsid, stream, connection); + InsertPmtStreamsIp(onid, tsid, stream, connection); + InsertPmtStreamsSubtitle(onid, tsid, stream, connection, mySqlTransaction); + InsertPmtStreamsTeletext(onid, tsid, stream, connection, false, mySqlTransaction); + InsertPmtStreamsTeletext(onid, tsid, stream, connection, true, mySqlTransaction); + InsertPmtStreamsVbi(onid, tsid, stream, mySqlTransaction); + } + } + + private void InsertPmtStreamsVbi(int onid, int tsid, ProgramMappingStream stream, MySqlTransaction transaction) + { + if (stream.VbiData == null) + return; + if (stream.VbiData.Count == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_pmt_streams_vbi (onid, tsid, elementary_pid, field_parity, line_offset, data_service_id) " + + "VALUES (@onid, @tsid, @elementary_pid, @field_parity, @line_offset, @data_service_id)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + command.Parameters.Add("@field_parity", MySqlDbType.Bool); + command.Parameters.Add("@line_offset", MySqlDbType.Int32); + command.Parameters.Add("@data_service_id", MySqlDbType.Int16); + foreach (VbiDataDescriptor.DataService dataService in stream.VbiData) + { + command.Parameters["@data_service_id"].Value = dataService.DataServiceId; + for (int i = 0; i < dataService.FieldParity.Length; i++) + { + if (!TestPmtStreamVbi(transaction, onid, tsid, stream.ElementaryPid, dataService.FieldParity[i], dataService.LineOffset[i])) + { + command.Parameters["@field_parity"].Value = dataService.FieldParity[i]; + command.Parameters["@line_offset"].Value = dataService.LineOffset[i]; + command.ExecuteNonQuery(); + } + } + } + } + + private bool TestPmtStreamVbi(MySqlTransaction transaction, int onid, int tsid, int streamElementaryPid, bool fieldParity, int lineOffset) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_pmt_streams_vbi WHERE onid = @onid AND tsid = @tsid AND elementary_pid = @elementary_pid AND field_parity = @field_parity AND line_offset = @line_offset"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", streamElementaryPid); + command.Parameters.AddWithValue("@field_parity", fieldParity); + command.Parameters.AddWithValue("@line_offset", lineOffset); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertPmtStreamsTeletext(int onid, int tsid, ProgramMappingStream stream, MySqlConnection connection, bool vbi, MySqlTransaction transaction) + { + TeletextDescriptor.TeletextDescriptorTeletext[] teletexts = vbi ? stream.VbiTeletexts : stream.Teletexts; + if (teletexts == null) + return; + + if (teletexts.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_pmt_streams_teletext (onid, tsid, elementary_pid, ordinal, iso_639_language_code, teletext_type, teletext_magazine_number, teletext_page_number, vbi) " + + "VALUES " + + "(@onid, @tsid, @elementary_pid, @ordinal, @iso_639_language_code, @teletext_type, @teletext_magazine_number, @teletext_page_number, @vbi)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + command.Parameters.AddWithValue("@vbi", vbi); + command.Parameters.Add("@ordinal", MySqlDbType.Int32); + command.Parameters.Add("@iso_639_language_code", MySqlDbType.VarChar); + command.Parameters.Add("@teletext_type", MySqlDbType.Int32); + command.Parameters.Add("@teletext_magazine_number", MySqlDbType.Int32); + command.Parameters.Add("@teletext_page_number", MySqlDbType.Int32); + for (int i = 0; i < teletexts.Length; i++) + { + if (TestForPmtTeletext(onid, tsid, stream.ElementaryPid, i, transaction)) + { + continue; + } + else + { + TeletextDescriptor.TeletextDescriptorTeletext teletext = teletexts[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@iso_639_language_code"].Value = teletext.Iso639LanguageCode; + command.Parameters["@teletext_type"].Value = (int)teletext.TeletextType; + command.Parameters["@teletext_magazine_number"].Value = teletext.TeletextMagazineNumber; + command.Parameters["@teletext_page_number"].Value = teletext.TeletextPageNumber; + command.ExecuteNonQuery(); + } + } + } + + private bool TestForPmtTeletext(int onid, int tsid, int streamElementaryPid, int i, MySqlTransaction transaction) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_pmt_streams_teletext WHERE onid = @onid AND tsid = @tsid AND elementary_pid = @elementary_pid AND ordinal = @ordinal"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", streamElementaryPid); + command.Parameters.AddWithValue("@ordinal", i); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertPmtStreamsSubtitle(int onid, int tsid, ProgramMappingStream stream, MySqlConnection connection, MySqlTransaction mySqlTransaction) + { + if (stream.Subtitlings == null) + return; + + if (stream.Subtitlings.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = mySqlTransaction; + command.CommandText = "INSERT INTO dvb_pmt_streams_subtitle (onid, tsid, elementary_pid, ordinal, iso_639_language_type, subtitling_type, composition_page_id, ancillary_page_id) " + + "VALUES " + + "(@onid, @tsid, @elementary_pid, @ordinal, @iso_639_language_type, @subtitling_type, @composition_page_id, @ancillary_page_id)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + for (int i = 0; i < stream.Subtitlings.Length; i++) + { + if (TestForPmtStreamSubtitle(onid, tsid, stream.ElementaryPid, i, mySqlTransaction)) + continue; + command.Parameters.AddOrSet("@ordinal", i); + command.Parameters.AddOrSet("@iso_639_language_type", stream.Subtitlings[i].Iso639LanguageCode); + command.Parameters.AddOrSet("@subtitling_type", stream.Subtitlings[i].SubtitlingType); + command.Parameters.AddOrSet("@composition_page_id", stream.Subtitlings[i].CompositionPageId); + command.Parameters.AddOrSet("@ancillary_page_id", stream.Subtitlings[i].AncillaryPageId); + command.ExecuteNonQuery(); + } + } + + private bool TestForPmtStreamSubtitle(int onid, int tsid, int pid, int ordinal, MySqlTransaction transaction) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_pmt_streams_subtitle WHERE onid = @onid AND tsid = @tsid AND elementary_pid = @elementary_pid AND ordinal = @ordinal"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", pid); + command.Parameters.AddOrSet("@ordinal", ordinal); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void InsertPmtStreamsIp(int onid, int tsid, ProgramMappingStream stream, MySqlConnection connection) + { + if (stream.IpMacNotificationInfo == null) + return; + + if (stream.IpMacNotificationInfo.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_pmt_streams_ip (onid, tsid, elementary_pid, platform_id, action_type, int_versioning_flag, int_version) " + + "VALUES " + + "(@onid, @tsid, @elementary_pid, @platform_id, @action_type, @int_versioning_flag, @int_version)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + foreach (DataBroadcastIdDescriptor.IpMacPlatform platform in stream.IpMacNotificationInfo) + { + command.Parameters.AddWithValue("@platform_id", platform.PlatformId); + command.Parameters.AddWithValue("@action_type", platform.ActionType); + command.Parameters.AddWithValue("@int_versioning_flag", platform.IntVersioningFlag); + command.Parameters.AddWithValue("@int_version", platform.IntVersion); + command.ExecuteNonQuery(); + } + } + + private void InsertPmtStreamsFlexmux(int onid, int tsid, ProgramMappingStream stream, MySqlConnection connection) + { + if (stream.FlexMuxChannels == null) + return; + + if (stream.FlexMuxChannels.Length == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_pmt_streams_flexmux (nid, tsid, elementary_pid, es_id, flexmux_channel) VALUES (@nid,@tsid,@elementary_pid,@es_id,@flexmux_channel)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + command.Parameters.Add("@es_id", MySqlDbType.Int32); + command.Parameters.Add("@flexmux_channel", MySqlDbType.Int16); + foreach (FmcDescriptor.Fmc flexMuxChannel in stream.FlexMuxChannels) + { + command.Parameters["@es_id"].Value = flexMuxChannel.EsId; + command.Parameters["@flexmux_channel"].Value = flexMuxChannel.FlexMuxChannel; + command.ExecuteNonQuery(); + } + } + + private void InsertPmtStreamsAudioPreselection(int onid, int tsid, ProgramMappingStream stream, MySqlTransaction transaction) + { + if (stream.AudioPreselection == null) + return; + + if (stream.AudioPreselection.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_pmt_streams_audio_preselection (onid, tsid, elementary_pid, preselection_id, audio_rendering_indication, audio_description, spoken_subtitles, dialogue_enhancement, interactivity_enabled, iso_639_language_code, message_id, component_tags, future_extension) " + + "VALUES " + + "(@onid, @tsid, @elementary_pid, @preselection_id, @audio_rendering_indication, @audio_description, @spoken_subtitles, @dialogue_enhancement, @interactivity_enabled, " + + "@iso_639_language_code, @message_id, @component_tags, @future_extension)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + foreach (AudioPreselectionDescriptor.AudioPreselection audioPreselection in stream.AudioPreselection) + { + command.Parameters.AddOrSet("@preselection_id", audioPreselection.PreselectionId); + command.Parameters.AddOrSet("@audio_rendering_indication", audioPreselection.AudioRenderingIndication); + command.Parameters.AddOrSet("@audio_description", audioPreselection.AudioDescription); + command.Parameters.AddOrSet("@spoken_subtitles", audioPreselection.SpokenSubtitles); + command.Parameters.AddOrSet("@dialogue_enhancement", audioPreselection.DialogueEnhancement); + command.Parameters.AddOrSet("@interactivity_enabled", audioPreselection.InteractivityEnabled); + command.Parameters.AddOrSet("@iso_639_language_code", audioPreselection.Iso639LangaugeCode); + command.Parameters.AddOrSet("@message_id", audioPreselection.MessageId); + command.Parameters.AddOrSet("@component_tags", audioPreselection.ComponentTags); + command.Parameters.AddOrSet("@future_extension", audioPreselection.FutureExtension); + command.ExecuteNonQuery(); + } + } + + private void InsertPmtStreamsApplications(int onid, int tsid, ProgramMappingStream stream, MySqlConnection connection, MySqlTransaction transaction) + { + if (stream.Applications == null) + return; + if (stream.Applications.Count == 0) + return; + + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_pmt_streams_applications (onid, tsid, elementary_pid, application_type, ait_version_number) " + + "VALUES (@onid,@tsid,@elementary_pid,@application_type,@ait_version_number)"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", stream.ElementaryPid); + foreach (ApplicationSignallingDescriptor.ApplicationSignal applicationSignal in stream.Applications) + { + if (TestForPmtStreamApplication(onid, tsid, stream.ElementaryPid, transaction, applicationSignal)) + continue; + command.Parameters.AddOrSet("@application_type", applicationSignal.ApplicationType); + command.Parameters.AddOrSet("@ait_version_number", applicationSignal.AitVersionNumber); + command.ExecuteNonQuery(); + } + } + + private bool TestForPmtStreamApplication(int onid, int tsid, int pid, MySqlTransaction transaction, ApplicationSignallingDescriptor.ApplicationSignal signal) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_pmt_streams_applications WHERE onid = @onid AND tsid = @tsid AND elementary_pid = @elementary_pid " + + "AND application_type = @application_type AND ait_version_number = @ait_version_number"; + command.Parameters.AddWithValue("@onid", onid); + command.Parameters.AddWithValue("@tsid", tsid); + command.Parameters.AddWithValue("@elementary_pid", pid); + command.Parameters.AddOrSet("@application_type", signal.ApplicationType); + command.Parameters.AddOrSet("@ait_version_number", signal.AitVersionNumber); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private struct PmtCoordinate + { + public int nid, tsid; + public ushort programNumber; + + public PmtCoordinate(int nid, int tsid, ushort programNumber) + { + this.nid = nid; + this.tsid = tsid; + this.programNumber = programNumber; + } + + public bool Equals(PmtCoordinate other) + { + return nid == other.nid && tsid == other.tsid && programNumber == other.programNumber; + } + + public override bool Equals(object obj) + { + return obj is PmtCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(nid, tsid, programNumber); + } + } + + private HashSet knownPmts; + + public bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + if (knownPmts == null) + knownPmts = new HashSet(); + + PmtCoordinate coordinate = new PmtCoordinate(currentNetworkId, currentTransportStreamId, mapping.ProgramNumber); + if (knownPmts.Contains(coordinate)) + return true; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "SELECT dateadded FROM dvb_pmt WHERE nid = @nid AND tsid = @tsid AND program_number = @program_number"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@program_number", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = currentNetworkId; + mySqlCommand.Parameters["@tsid"].Value = currentTransportStreamId; + mySqlCommand.Parameters["@program_number"].Value = mapping.ProgramNumber; + MySqlDataReader dataReader = mySqlCommand.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + connection.Close(); + + if (result) + { + knownPmts.Add(coordinate); + } + + return result; + } + } + + public bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + if (knownPmts == null) + knownPmts = new HashSet(); + knownPmts.Add(new PmtCoordinate(currentNetworkId, currentTransportStreamId, mapping.ProgramNumber)); + + EnqueueSpeedhack(SpeedhackType.InsertPmt, currentNetworkId, currentTransportStreamId, mapping); + return true; + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Scte35.cs b/DataTableStorages/skyscraper5.Data.MySql/Scte35.cs new file mode 100644 index 0000000..95b58e6 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Scte35.cs @@ -0,0 +1,365 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Scte35; +using skyscraper5.Scte35.Descriptors; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private void InsertScte35SpliceComponents(MySqlTransaction transaction, int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + if (spliceInsert.Components == null) + return; + + if (spliceInsert.Components.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO scte35_splice_components (cnid, ctsid, progno, splice_event_id, k, v) VALUES (@cnid, @ctsid, @progno, @splice_event_id, @k, @v)"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@splice_event_id", spliceInsert.SpliceEventId); + command.Parameters.Add("@k", MySqlDbType.Int16); + command.Parameters.Add("@v", MySqlDbType.Int64); + foreach (Tuple component in spliceInsert.Components) + { + command.Parameters["@k"].Value = component.Item1; + command.Parameters["@v"].Value = component.Item2; + command.ExecuteNonQuery(); + } + } + + private void InsertScte35SpliceSegmentationComponents(MySqlTransaction transaction, int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + if (spliceInsert.Descriptors == null) + return; + + if (spliceInsert.Descriptors.segmentationDescriptor == null) + return; + + SegmentationDescriptor.Component[] components = spliceInsert.Descriptors.segmentationDescriptor.Components; + if (components == null) + return; + + if (components.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO scte35_splice_segmentation_components (cnid, ctsid, progno, splice_event_id, component_tag, pts_offset) " + + "VALUES (@cnid, @ctsid, @progno, @splice_event_id, @component_tag, @pts_offset)"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@splice_event_id", spliceInsert.SpliceEventId); + command.Parameters.Add("@component_tag", MySqlDbType.Int16); + command.Parameters.Add("@pts_offset", MySqlDbType.Int64); + foreach (SegmentationDescriptor.Component component in components) + { + command.Parameters["@component_tag"].Value = component.ComponentTag; + command.Parameters["@pts_offset"].Value = component.PtsOffset; + command.ExecuteNonQuery(); + } + } + + private DateTime? QueryScte35TimeSignal(MySqlConnection connection, int currentNetworkId, int currentTransportStreamId, ushort programNumber) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM scte35_time_signal WHERE cnid = @cnid AND ctsid = @ctsid AND progno = @progno"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + MySqlDataReader dataReader = command.ExecuteReader(); + DateTime? result = null; + if (dataReader.Read()) + result = dataReader.GetDateTime(0); + dataReader.Close(); + return result; + } + + public bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, + SpliceInsert spliceInsert) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM scte35_splice WHERE cnid = @cnid AND ctsid = @ctsid AND progno = @progno AND splice_event_id = @splice_event_id"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@splice_event_id", spliceInsert.SpliceEventId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + connection.Close(); + return result; + } + } + + public void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, + SpliceInsert spliceInsert) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO scte35_splice (cnid, ctsid, progno, splice_event_id, splice_time, splice_event_cancel_indicator, avails_expected, avail_num, unique_program_id, duration, duration_auto_return, splice_immediate_flag, duration_flag, program_splice_flag, out_of_network_indicator, provider_avail_id, identifier, sub_segments_expected, sub_segment_num, segments_expected, segment_num, segmentation_type_id, segmentation_upid, segmentation_upid_type, segmentation_duration, program_segmentation_flag, segmentation_event_cancel_indicator, device_restrictions, archive_allowed_flag, no_regional_blackout_flag, web_delivery_flag, segmentation_duration_flag, segmentation_event_id, segmentation_identifier, utc_offset, tai_ns, tai_seconds, tai_identififer) " + + "VALUES (@cnid, @ctsid, @progno, @splice_event_id, @splice_time, @splice_event_cancel_indicator, @avails_expected, @avail_num, @unique_program_id, @duration, @duration_auto_return, @splice_immediate_flag, " + + "@duration_flag, @program_splice_flag, @out_of_network_indicator, @provider_avail_id, @identifier, @sub_segments_expected, @sub_segment_num, @segments_expected, @segment_num," + + "@segmentation_type_id, @segmentation_upid, @segmentation_upid_type, @segmentation_duration, @program_segmentation_flag, @segmentation_event_cancel_indicator, @device_restrictions, " + + "@archive_allowed_flag, @no_regional_blackout_flag, @web_delivery_flag, @segmentation_duration_flag, @segmentation_event_id, @segmentation_identifier, @utc_offset, @tai_ns, @tai_seconds, @tai_identififer)"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@splice_event_id", spliceInsert.SpliceEventId); + command.Parameters.AddWithValue("@splice_time", spliceInsert.SpliceTime); + command.Parameters.AddWithValue("@splice_event_cancel_indicator", spliceInsert.SpliceEventCancelIndicator); + command.Parameters.AddWithValue("@avails_expected", spliceInsert.AvailsExpected); + command.Parameters.AddWithValue("@avail_num", spliceInsert.AvailNum); + command.Parameters.AddWithValue("@unique_program_id", spliceInsert.UniqueProgramId); + command.Parameters.AddWithValue("@duration", spliceInsert.Duration); + command.Parameters.AddWithValue("@duration_auto_return", spliceInsert.DurationAutoReturn); + command.Parameters.AddWithValue("@splice_immediate_flag", spliceInsert.SpliceImmediateFlag); + command.Parameters.AddWithValue("@duration_flag", spliceInsert.DurationFlag); + command.Parameters.AddWithValue("@program_splice_flag", spliceInsert.ProgramSpliceFlag); + command.Parameters.AddWithValue("@out_of_network_indicator", spliceInsert.OutOfNetworkIndicator); + if (spliceInsert.Descriptors != null) + { + if (spliceInsert.Descriptors.availDescriptor != null) + { + command.Parameters.AddWithValue("@provider_avail_id", spliceInsert.Descriptors.availDescriptor.ProviderAvailId); + command.Parameters.AddWithValue("@identifier", spliceInsert.Descriptors.availDescriptor.Identifier); + } + else + { + command.Parameters.AddWithValue("@provider_avail_id", DBNull.Value); + command.Parameters.AddWithValue("@identifier", DBNull.Value); + } + + if (spliceInsert.Descriptors.segmentationDescriptor != null) + { + command.Parameters.AddWithValue("@sub_segments_expected", spliceInsert.Descriptors.segmentationDescriptor.SubSegmentsExpected); + command.Parameters.AddWithValue("@sub_segment_num", spliceInsert.Descriptors.segmentationDescriptor.SubSegmentNum); + command.Parameters.AddWithValue("@segments_expected", spliceInsert.Descriptors.segmentationDescriptor.SegmentsExpected); + command.Parameters.AddWithValue("@segment_num", spliceInsert.Descriptors.segmentationDescriptor.SegmentNum); + command.Parameters.AddWithValue("@segmentation_type_id", spliceInsert.Descriptors.segmentationDescriptor.SegmentationTypeId); + command.Parameters.AddWithValue("@segmentation_upid", spliceInsert.Descriptors.segmentationDescriptor.SegmentationUpid); + command.Parameters.AddWithValue("@segmentation_upid_type", spliceInsert.Descriptors.segmentationDescriptor.SegmentationUpidType); + command.Parameters.AddWithValue("@segmentation_duration", spliceInsert.Descriptors.segmentationDescriptor.SegmentationDuration); + command.Parameters.AddWithValue("@program_segmentation_flag", spliceInsert.Descriptors.segmentationDescriptor.ProgramSegmentationFlag); + command.Parameters.AddWithValue("@segmentation_event_cancel_indicator", spliceInsert.Descriptors.segmentationDescriptor.SegmentationEventCancelIndicator); + command.Parameters.AddWithValue("@device_restrictions", spliceInsert.Descriptors.segmentationDescriptor.DeviceRestrictions); + command.Parameters.AddWithValue("@archive_allowed_flag", spliceInsert.Descriptors.segmentationDescriptor.ArchiveAllowedFlag); + command.Parameters.AddWithValue("@no_regional_blackout_flag", spliceInsert.Descriptors.segmentationDescriptor.NoRegionalBlackoutFlag); + command.Parameters.AddWithValue("@web_delivery_flag", spliceInsert.Descriptors.segmentationDescriptor.WebDeliveryFlag); + command.Parameters.AddWithValue("@segmentation_duration_flag", spliceInsert.Descriptors.segmentationDescriptor.SegmentationDurationFlag); + command.Parameters.AddWithValue("@segmentation_event_id", spliceInsert.Descriptors.segmentationDescriptor.SegmentationEventId); + command.Parameters.AddWithValue("@segmentation_identifier", spliceInsert.Descriptors.segmentationDescriptor.Identifier); + } + else + { + command.Parameters.AddWithValue("@sub_segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@sub_segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_type_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid_type", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration", DBNull.Value); + command.Parameters.AddWithValue("@program_segmentation_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_cancel_indicator", DBNull.Value); + command.Parameters.AddWithValue("@device_restrictions", DBNull.Value); + command.Parameters.AddWithValue("@archive_allowed_flag", DBNull.Value); + command.Parameters.AddWithValue("@no_regional_blackout_flag", DBNull.Value); + command.Parameters.AddWithValue("@web_delivery_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_identifier", DBNull.Value); + } + + if (spliceInsert.Descriptors.timeDescriptor != null) + { + command.Parameters.AddWithValue("@segmentation_identifier", spliceInsert.Descriptors.timeDescriptor.UtcOffset); + command.Parameters.AddWithValue("@tai_ns", spliceInsert.Descriptors.timeDescriptor.TaiNs); + command.Parameters.AddWithValue("@tai_seconds", spliceInsert.Descriptors.timeDescriptor.TaiSeconds); + command.Parameters.AddWithValue("@tai_identififer", spliceInsert.Descriptors.timeDescriptor.Identifier); + } + else + { + command.Parameters.AddWithValue("@utc_offset", DBNull.Value); + command.Parameters.AddWithValue("@tai_ns", DBNull.Value); + command.Parameters.AddWithValue("@tai_seconds", DBNull.Value); + command.Parameters.AddWithValue("@tai_identififer", DBNull.Value); + } + } + else + { + command.Parameters.AddWithValue("@provider_avail_id", DBNull.Value); + command.Parameters.AddWithValue("@identifier", DBNull.Value); + command.Parameters.AddWithValue("@sub_segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@sub_segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_type_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid_type", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration", DBNull.Value); + command.Parameters.AddWithValue("@program_segmentation_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_cancel_indicator", DBNull.Value); + command.Parameters.AddWithValue("@device_restrictions", DBNull.Value); + command.Parameters.AddWithValue("@archive_allowed_flag", DBNull.Value); + command.Parameters.AddWithValue("@no_regional_blackout_flag", DBNull.Value); + command.Parameters.AddWithValue("@web_delivery_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_identifier", DBNull.Value); + command.Parameters.AddWithValue("@utc_offset", DBNull.Value); + command.Parameters.AddWithValue("@tai_ns", DBNull.Value); + command.Parameters.AddWithValue("@tai_seconds", DBNull.Value); + command.Parameters.AddWithValue("@tai_identififer", DBNull.Value); + } + + command.ExecuteNonQuery(); + + InsertScte35SpliceComponents(transaction, currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + InsertScte35SpliceSegmentationComponents(transaction, currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + transaction.Commit(); + connection.Close(); + } + } + + public void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, TimeSignal spliceInsert) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO scte35_time_signal (cnid, ctsid, progno, current, value, provider_avail_id, identifier, sub_segments_expected, sub_segment_num, segments_expected, segment_num, segmentation_type_id, segmentation_upid, segmentation_upid_type, segmentation_duration, program_segmentation_flag, segmentation_event_cancel_indicator, device_restrictions, archive_allowed_flag, no_regional_blackout_flag, web_delivery_flag, segmentation_duration_flag, segmentation_event_id, segmentation_identifier, utc_offset, tai_ns, tai_seconds, tai_identififer) " + + "VALUES (@cnid, @ctsid, @progno, @current, @value, @provider_avail_id, @identifier, @sub_segments_expected, @sub_segment_num, @segments_expected, @segment_num, @segmentation_type_id, @segmentation_upid, @segmentation_upid_type, @segmentation_duration, @program_segmentation_flag, @segmentation_event_cancel_indicator, @device_restrictions, @archive_allowed_flag, @no_regional_blackout_flag, @web_delivery_flag, @segmentation_duration_flag, @segmentation_event_id, @segmentation_identifier, @utc_offset, @tai_ns, @tai_seconds, @tai_identififer)"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@current", currentTime); + if (spliceInsert.Descriptors != null) + { + if (spliceInsert.Descriptors.availDescriptor != null) + { + command.Parameters.AddWithValue("@provider_avail_id", spliceInsert.Descriptors.availDescriptor.ProviderAvailId); + command.Parameters.AddWithValue("@identifier", spliceInsert.Descriptors.availDescriptor.Identifier); + } + else + { + command.Parameters.AddWithValue("@provider_avail_id", DBNull.Value); + command.Parameters.AddWithValue("@identifier", DBNull.Value); + } + + if (spliceInsert.Descriptors.segmentationDescriptor != null) + { + command.Parameters.AddWithValue("@sub_segments_expected", spliceInsert.Descriptors.segmentationDescriptor.SubSegmentsExpected); + command.Parameters.AddWithValue("@sub_segment_num", spliceInsert.Descriptors.segmentationDescriptor.SubSegmentNum); + command.Parameters.AddWithValue("@segments_expected", spliceInsert.Descriptors.segmentationDescriptor.SegmentsExpected); + command.Parameters.AddWithValue("@segment_num", spliceInsert.Descriptors.segmentationDescriptor.SegmentNum); + command.Parameters.AddWithValue("@segmentation_type_id", spliceInsert.Descriptors.segmentationDescriptor.SegmentationTypeId); + command.Parameters.AddWithValue("@segmentation_upid", spliceInsert.Descriptors.segmentationDescriptor.SegmentationUpid); + command.Parameters.AddWithValue("@segmentation_upid_type", spliceInsert.Descriptors.segmentationDescriptor.SegmentationUpidType); + command.Parameters.AddWithValue("@segmentation_duration", spliceInsert.Descriptors.segmentationDescriptor.SegmentationDuration); + command.Parameters.AddWithValue("@program_segmentation_flag", spliceInsert.Descriptors.segmentationDescriptor.ProgramSegmentationFlag); + command.Parameters.AddWithValue("@segmentation_event_cancel_indicator", spliceInsert.Descriptors.segmentationDescriptor.SegmentationEventCancelIndicator); + command.Parameters.AddWithValue("@device_restrictions", spliceInsert.Descriptors.segmentationDescriptor.DeviceRestrictions); + command.Parameters.AddWithValue("@archive_allowed_flag", spliceInsert.Descriptors.segmentationDescriptor.ArchiveAllowedFlag); + command.Parameters.AddWithValue("@no_regional_blackout_flag", spliceInsert.Descriptors.segmentationDescriptor.NoRegionalBlackoutFlag); + command.Parameters.AddWithValue("@web_delivery_flag", spliceInsert.Descriptors.segmentationDescriptor.WebDeliveryFlag); + command.Parameters.AddWithValue("@segmentation_duration_flag", spliceInsert.Descriptors.segmentationDescriptor.SegmentationDurationFlag); + command.Parameters.AddWithValue("@segmentation_event_id", spliceInsert.Descriptors.segmentationDescriptor.SegmentationEventId); + command.Parameters.AddWithValue("@segmentation_identifier", spliceInsert.Descriptors.segmentationDescriptor.Identifier); + } + else + { + command.Parameters.AddWithValue("@sub_segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@sub_segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_type_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid_type", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration", DBNull.Value); + command.Parameters.AddWithValue("@program_segmentation_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_cancel_indicator", DBNull.Value); + command.Parameters.AddWithValue("@device_restrictions", DBNull.Value); + command.Parameters.AddWithValue("@archive_allowed_flag", DBNull.Value); + command.Parameters.AddWithValue("@no_regional_blackout_flag", DBNull.Value); + command.Parameters.AddWithValue("@web_delivery_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_identifier", DBNull.Value); + } + + if (spliceInsert.Descriptors.timeDescriptor != null) + { + command.Parameters.AddWithValue("@utc_offset", spliceInsert.Descriptors.timeDescriptor.UtcOffset); + command.Parameters.AddWithValue("@tai_ns", spliceInsert.Descriptors.timeDescriptor.TaiNs); + command.Parameters.AddWithValue("@tai_seconds", spliceInsert.Descriptors.timeDescriptor.TaiSeconds); + command.Parameters.AddWithValue("@tai_identififer", spliceInsert.Descriptors.timeDescriptor.Identifier); + } + else + { + command.Parameters.AddWithValue("@utc_offset", DBNull.Value); + command.Parameters.AddWithValue("@tai_ns", DBNull.Value); + command.Parameters.AddWithValue("@tai_seconds", DBNull.Value); + command.Parameters.AddWithValue("@tai_identififer", DBNull.Value); + } + } + else + { + command.Parameters.AddWithValue("@provider_avail_id", DBNull.Value); + command.Parameters.AddWithValue("@identifier", DBNull.Value); + command.Parameters.AddWithValue("@sub_segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@sub_segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segments_expected", DBNull.Value); + command.Parameters.AddWithValue("@segment_num", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_type_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_upid_type", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration", DBNull.Value); + command.Parameters.AddWithValue("@program_segmentation_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_cancel_indicator", DBNull.Value); + command.Parameters.AddWithValue("@device_restrictions", DBNull.Value); + command.Parameters.AddWithValue("@archive_allowed_flag", DBNull.Value); + command.Parameters.AddWithValue("@no_regional_blackout_flag", DBNull.Value); + command.Parameters.AddWithValue("@web_delivery_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_duration_flag", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_event_id", DBNull.Value); + command.Parameters.AddWithValue("@segmentation_identifier", DBNull.Value); + command.Parameters.AddWithValue("@utc_offset", DBNull.Value); + command.Parameters.AddWithValue("@tai_ns", DBNull.Value); + command.Parameters.AddWithValue("@tai_seconds", DBNull.Value); + command.Parameters.AddWithValue("@tai_identififer", DBNull.Value); + } + + command.ExecuteNonQueryAsync().ContinueWith( + x => + { + connection.Close(); + } + ); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Sdt.cs b/DataTableStorages/skyscraper5.Data.MySql/Sdt.cs new file mode 100644 index 0000000..d68addb --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Sdt.cs @@ -0,0 +1,362 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using static skyscraper5.Dvb.Descriptors.ServiceListDescriptor; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private void InsertSdtCaIdentifiers(MySqlTransaction transaction, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtService.CaIdentifiers == null) + return; + + if (sdtService.CaIdentifiers.Length == 0) + return; + + MySqlCommand mySqlCommand = transaction.Connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = "INSERT INTO dvb_sdt_ca_identifiers" + + " (tsid, onid, service_id, ca_identifier) " + + "VALUES" + + " (@tsid,@onid,@service_id,@ca_identifier)"; + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ca_identifier", MySqlDbType.Int32); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = sdtService.ServiceId; + foreach (ushort sdtServiceCaIdentifier in sdtService.CaIdentifiers) + { + mySqlCommand.Parameters["@ca_identifier"].Value = sdtServiceCaIdentifier; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertSdtCountryAvailability(MySqlTransaction transaction, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtService.CountryAvailability == null) + return; + + if (sdtService.CountryAvailability.Count == 0) + return; + + MySqlCommand mySqlCommand = transaction.Connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = "INSERT INTO dvb_sdt_country_availability" + + " (tsid, onid, service_id, country, availability) " + + "VALUES " + + " (@tsid,@onid,@service_id,@country,@availability)"; + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@country", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@availability", MySqlDbType.Bool); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = sdtService.ServiceId; + foreach (KeyValuePair keyValuePair in sdtService.CountryAvailability) + { + mySqlCommand.Parameters["@country"].Value = keyValuePair.Key; + mySqlCommand.Parameters["@availability"].Value = keyValuePair.Value; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertSdtMultilingualServiceName(MySqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtService.MultilingualServiceName == null) + return; + + if (sdtService.MultilingualServiceName.Count == 0) + return; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_sdt_multilingual_service_name" + + " (tsid, onid, service_id, language_code, service_provider_name, service_name)" + + "VALUES" + + " (@tsid, @onid, @service_id, @language_code, @service_provider_name, @service_name)"; + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@language_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@service_provider_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@service_name", MySqlDbType.VarChar); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = sdtService.ServiceId; + + foreach (MultilingualServiceNameDescriptor.MultilingualServiceName multilingualServiceName in sdtService + .MultilingualServiceName) + { + mySqlCommand.Parameters["@language_code"].Value = multilingualServiceName.Iso639LanguageCode; + mySqlCommand.Parameters["@service_provider_name"].Value = multilingualServiceName.ServiceProviderName; + mySqlCommand.Parameters["@service_name"].Value = multilingualServiceName.ServiceName; + mySqlCommand.ExecuteNonQuery(); + } + } + + private void InsertSdtNvodReferences(MySqlTransaction transaction, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtService.NvodReferences == null) + return; + + if (sdtService.NvodReferences.Length == 0) + return; + + MySqlCommand mySqlCommand = transaction.Connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = + "INSERT INTO dvb_sdt_nvod_references " + + " (tsid, onid, service_id, other_tsid, other_onid, other_service_id) " + + "VALUES " + + " (@tsid, @onid, @service_id, @other_tsid, @other_onid, @other_service_id)"; + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@other_tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@other_onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@other_service_id", MySqlDbType.Int32); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = sdtService.ServiceId; + + foreach (NvodReferenceDescriptor.NvodReference nvodReference in sdtService.NvodReferences) + { + mySqlCommand.Parameters["@other_tsid"].Value = nvodReference.TransportStreamId; + mySqlCommand.Parameters["@other_onid"].Value = nvodReference.OriginalNetworkId; + mySqlCommand.Parameters["@other_service_id"].Value = nvodReference.ServiceId; + mySqlCommand.ExecuteNonQuery(); + } + } + + private static SdtService GetSdt(ushort transportStreamId, ushort originalNetworkId, ushort oldServiceId, MySqlConnection connection) + { + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "SELECT * FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND service_id = @service_id"; + mySqlCommand.Parameters.Add("@tsid", DbType.Int32); + mySqlCommand.Parameters.Add("@onid", DbType.Int32); + mySqlCommand.Parameters.Add("@service_id", DbType.Int32); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = oldServiceId; + MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); + SdtService result = null; + if (mySqlDataReader.Read()) + { + ushort tsid = mySqlDataReader.GetUInt16(0); + ushort onid = mySqlDataReader.GetUInt16(1); + ushort serviceId = mySqlDataReader.GetUInt16(2); + bool eitSchedule = mySqlDataReader.GetBoolean(3); + bool eitScheduleFollowing = mySqlDataReader.GetBoolean(4); + RunningStatus runningStatus = (RunningStatus)mySqlDataReader.GetInt16(5); + bool freeCaMode = mySqlDataReader.GetBoolean(6); + result = new SdtService(serviceId, eitSchedule, eitScheduleFollowing, runningStatus, freeCaMode); + if (!mySqlDataReader.IsDBNull(7)) + result.ServiceName = mySqlDataReader.GetString(7); + if (!mySqlDataReader.IsDBNull(8)) + result.ServiceProviderName = mySqlDataReader.GetString(8); + if (!mySqlDataReader.IsDBNull(9)) + result.ServiceType = (ServiceDescriptor.ServiceTypeCoding)mySqlDataReader.GetInt32(9); + if (!mySqlDataReader.IsDBNull(10)) + result.PrivateDataSpecifier = mySqlDataReader.GetUInt32(10); + if (!mySqlDataReader.IsDBNull(11)) + result.DataBroadcastId = mySqlDataReader.GetUInt16(11); + if (!mySqlDataReader.IsDBNull(12)) + result.Selector = mySqlDataReader.GetByteArray(12); + if (!mySqlDataReader.IsDBNull(13)) + result.ReferenceServiceId = mySqlDataReader.GetUInt16(13); + if (!mySqlDataReader.IsDBNull(14)) + result.DefaultAuthority = mySqlDataReader.GetString(14); + if (!mySqlDataReader.IsDBNull(15)) + result.ControlRemoteAccessOverInternet = mySqlDataReader.GetInt32(15); + if (!mySqlDataReader.IsDBNull(16)) + result.DoNotApplyRevocation = mySqlDataReader.GetBoolean(16); + if (!mySqlDataReader.IsDBNull(17)) + result.DoNotScramble = mySqlDataReader.GetBoolean(17); + if (!mySqlDataReader.IsDBNull(18)) + result.OldOriginalNetworkId = mySqlDataReader.GetUInt16(18); + if (!mySqlDataReader.IsDBNull(19)) + result.OldServiceId = mySqlDataReader.GetUInt16(19); + if (!mySqlDataReader.IsDBNull(20)) + result.OldTransportStreamId = mySqlDataReader.GetUInt16(20); + if (!mySqlDataReader.IsDBNull(21)) + Guid.Parse(mySqlDataReader.GetString(21)); + DateTime dateAdded = mySqlDataReader.GetDateTime(22); + long numUpdates = mySqlDataReader.GetInt64(23); + if (!mySqlDataReader.IsDBNull(24)) + mySqlDataReader.GetDateTime(24); //DateUpdated + if (!mySqlDataReader.IsDBNull(25)) + result.ComponentTag = mySqlDataReader.GetByte(25); + if (!mySqlDataReader.IsDBNull(26)) + result.Iso639LanguageCode = mySqlDataReader.GetString(26); + if (!mySqlDataReader.IsDBNull(27)) + result.Text = mySqlDataReader.GetString(27); + } + + mySqlDataReader.Close(); + return result; + } + + private struct SdtCoordinate + { + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public ushort SdtServiceServiceId { get; } + + public SdtCoordinate(ushort transportStreamId, ushort originalNetworkId, ushort sdtServiceServiceId) + { + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + SdtServiceServiceId = sdtServiceServiceId; + } + + public bool Equals(SdtCoordinate other) + { + return TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId && SdtServiceServiceId == other.SdtServiceServiceId; + } + + public override bool Equals(object obj) + { + return obj is SdtCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(TransportStreamId, OriginalNetworkId, SdtServiceServiceId); + } + } + + private HashSet _sdtCoordinates; + private HashSet _sdtUpdateCoordinates; + + private void InsertSdtComponents(MySqlTransaction transaction, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtService.Components == null) + return; + + if (sdtService.Components.Count == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_sdt_components (tsid, nid, service_id, component_tag, stream_content_ext, stream_content, component_type, iso_639_language_code, text) " + + "VALUES (@tsid, @nid, @service_id, @component_tag, @stream_content_ext, @stream_content, @component_type, @iso_639_language_code, @text)"; + command.Parameters.AddWithValue("@tsid", transportStreamId); + command.Parameters.AddWithValue("@nid", originalNetworkId); + command.Parameters.AddWithValue("@service_id", sdtService.ServiceId); + command.Parameters.Add("@component_tag", MySqlDbType.Int16); + command.Parameters.Add("@stream_content_ext", MySqlDbType.Int16); + command.Parameters.Add("@stream_content", MySqlDbType.Int16); + command.Parameters.Add("@component_type", MySqlDbType.Int16); + command.Parameters.Add("@iso_639_language_code", MySqlDbType.VarChar); + command.Parameters.Add("@text", MySqlDbType.Text); + bool[] componentTags = new bool[256]; + foreach (ComponentDescriptor component in sdtService.Components) + { + if (componentTags[component.ComponentTag]) + continue; + + componentTags[component.ComponentTag] = true; + command.Parameters["@component_tag"].Value = component.ComponentTag; + command.Parameters["@stream_content_ext"].Value = component.StreamContentExt; + command.Parameters["@stream_content"].Value = component.StreamContent; + command.Parameters["@component_type"].Value = component.ComponentType; + command.Parameters["@iso_639_language_code"].Value = component.Iso639LanguageCode; + command.Parameters["@text"].Value = component.Text; + command.ExecuteNonQuery(); + } + } + + + public bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (_sdtCoordinates == null) + _sdtCoordinates = new HashSet(); + + SdtCoordinate coordinate = new SdtCoordinate(transportStreamId, originalNetworkId, sdtService.ServiceId); + if (_sdtCoordinates.Contains(coordinate)) + return true; + + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + bool read; + connection.Open(); + using (MySqlCommand mySqlCommand = connection.CreateCommand()) + { + mySqlCommand.CommandText = "SELECT dateadded FROM dvb_sdt WHERE tsid=@tsid AND onid=@onid AND service_id=@sid"; + mySqlCommand.Parameters.Add("@tsid", DbType.Int32); + mySqlCommand.Parameters.Add("@onid", DbType.Int32); + mySqlCommand.Parameters.Add("@sid", DbType.Int32); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@sid"].Value = sdtService.ServiceId; + MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); + read = mySqlDataReader.Read(); + if (read) + _sdtCoordinates.Add(coordinate); + mySqlDataReader.Close(); + } + connection.Close(); + return read; + } + } + + + public bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService newer) + { + if (_sdtUpdateCoordinates == null) + _sdtUpdateCoordinates = new HashSet(); + + SdtCoordinate sdtCoordinate = new SdtCoordinate(transportStreamId, originalNetworkId, newer.ServiceId); + + if (_sdtUpdateCoordinates.Contains(sdtCoordinate)) + return false; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + SdtService older = GetSdt(transportStreamId, originalNetworkId, newer.ServiceId, connection); + if (older == null) + { + connection.Close(); + return false; + } + + if (!older.NeedsUpdate(newer)) + { + connection.Close(); + return false; + } + + EnqueueSpeedhack(SpeedhackType.UpdateSdt, transportStreamId, originalNetworkId, newer); + _sdtUpdateCoordinates.Add(sdtCoordinate); + connection.Close(); + return true; + } + } + + + + public void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + SdtCoordinate coordinate = new SdtCoordinate(transportStreamId, originalNetworkId, sdtService.ServiceId); + _sdtCoordinates.Add(coordinate); + EnqueueSpeedhack(SpeedhackType.InsertSdt, transportStreamId, originalNetworkId, sdtService); + } + + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Skyscraper.cs b/DataTableStorages/skyscraper5.Data.MySql/Skyscraper.cs new file mode 100644 index 0000000..4eaa3bf --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Skyscraper.cs @@ -0,0 +1,122 @@ +using MySqlConnector; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + public bool ImportFileKnown(FileInfo fi) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + using (MySqlCommand mySqlCommand = connection.CreateCommand()) + { + mySqlCommand.CommandText = "SELECT dateadded FROM skyscraper_imported_streams WHERE filename = @filename"; + mySqlCommand.Parameters.Add("filename", MySqlDbType.VarChar); + mySqlCommand.Parameters["filename"].Value = fi.Name.ToLowerInvariant(); + MySqlDataReader dataReader = mySqlCommand.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + connection.Close(); + return result; + } + } + } + + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan duration) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO skyscraper_imported_streams (filename, debugger, time_taken) VALUES (@filename, @debugger, @time_taken)"; + command.Parameters.AddWithValue("@filename", fi.Name.ToLowerInvariant()); + command.Parameters.AddWithValue("@debugger", Debugger.IsAttached); + command.Parameters.AddWithValue("@time_taken", (int)duration.TotalSeconds); + command.ExecuteNonQuery(); + connection.Close(); + } + } + + + public List UiSatellitesListAll() + { + List result = new List(); + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + using (MySqlCommand command = connection.CreateCommand()) + { + command.CommandText = "SELECT * FROM skyscraper_satellites"; + MySqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + SatellitePosition satellitePosition = new SatellitePosition(); + satellitePosition.angle = dataReader.GetFloat(2); + satellitePosition.cardinalDirection = dataReader.GetBoolean(3) ? 0 : 1; + satellitePosition.name = dataReader.GetString(4); + result.Add(satellitePosition); + } + } + connection.Close(); + } + + return result; + } + + public HeadlessJob GetQueuedJob() + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + using (MySqlCommand command = connection.CreateCommand()) + { + command.CommandText = "SELECT * FROM skyscraper_queued_jobs WHERE done = FALSE"; + MySqlDataReader mySqlDataReader = command.ExecuteReader(); + HeadlessJob result = null; + if (mySqlDataReader.Read()) + { + result = new HeadlessJob(); + result.uuid = Guid.Parse(mySqlDataReader.GetString(0)); + result.jobType = (HeadlessJobType)mySqlDataReader.GetInt32(2); + if (!mySqlDataReader.IsDBNull(3)) + result.iArg1 = mySqlDataReader.GetInt32(3); + if (!mySqlDataReader.IsDBNull(4)) + result.sArg1 = mySqlDataReader.GetString(4); + result.isSynthetic = false; + } + + connection.Close(); + return result; + } + } + } + + public void SetQueuedJobComplete(HeadlessJob headlessJob) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + using (MySqlCommand command = connection.CreateCommand()) + { + command.CommandText = "UPDATE skyscraper_queued_jobs SET done = TRUE WHERE uuid = @uuid"; + command.Parameters.Add("uuid", MySqlDbType.VarChar); + command.Parameters["@uuid"].Value = headlessJob.uuid.ToString(); + command.ExecuteNonQuery(); + connection.Close(); + } + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Speedhacks.cs b/DataTableStorages/skyscraper5.Data.MySql/Speedhacks.cs new file mode 100644 index 0000000..5da1520 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Speedhacks.cs @@ -0,0 +1,693 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using static skyscraper5.Dvb.Descriptors.ContentIdentifierDescriptor; +using System.Diagnostics.Metrics; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private enum SpeedhackType + { + UpdateTdt, + InsertTdt, + UpdateTimeOffset, + InsertTimeOffset, + StorePat, + InsertPmt, + InsertTsdt, + UpdateTsdtStationIdentification, + InsertSdt, + InsertCat, + InsertBatTs, + InsertNitTransportStream, + UpdateSdt, + UpdateNitTransportStream + } + + private Queue> _speedhackQueue; + private Thread _speedhackThread; + private bool _waitingForSpeedhacksToComplete; + + private bool IsSpeedhackThreadAlive() + { + if (_speedhackThread == null) + return false; + + if (!_speedhackThread.IsAlive) + return false; + + return true; + } + + private void EnqueueSpeedhack(SpeedhackType hackType, params object[] args) + { + if (_speedhackQueue == null) + _speedhackQueue = new Queue>(); + + lock (_speedhackQueue) + { + _speedhackQueue.Enqueue(new KeyValuePair(hackType, args)); + } + + if (!IsSpeedhackThreadAlive()) + { + _speedhackThread = new Thread(RunSpeedhackThread); + _speedhackThread.Name = "PSI Writer"; + _speedhackThread.Priority = ThreadPriority.Lowest; + _speedhackThread.Start(); + } + } + + private void RunSpeedhackThread() + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + while (_speedhackQueue.Count > 0) + { + KeyValuePair speedhackJob; + lock (_speedhackQueue) + { + if (_waitingForSpeedhacksToComplete) + { + if (_speedhackQueue.Count > 0 && _speedhackQueue.Count % 100 == 0) + { + Console.WriteLine("{0} SQLs in queue...", _speedhackQueue.Count); + } + } + speedhackJob = _speedhackQueue.Dequeue(); + } + + object[] args = speedhackJob.Value; + MySqlTransaction transaction; + switch (speedhackJob.Key) + { + case SpeedhackType.UpdateTdt: + UpdateTdt(connection, (int)args[0], (int)args[1], (DateTime)args[2]); + break; + case SpeedhackType.UpdateTimeOffset: + transaction = connection.BeginTransaction(); + UpdateTimeOffset(transaction, (int)args[0], (int)args[1], (DateTime)args[2], (LocalTimeOffsetDescriptor.LocalTime)args[3]); + transaction.Commit(); + break; + case SpeedhackType.InsertTdt: + InsertTdt(connection, (int)args[0], (int)args[1], (DateTime)args[2]); + break; + case SpeedhackType.InsertTimeOffset: + transaction = connection.BeginTransaction(); + UpdateTimeOffset(transaction, (int)args[0], (int)args[1], (DateTime)args[2], (LocalTimeOffsetDescriptor.LocalTime)args[3]); + transaction.Commit(); + break; + case SpeedhackType.StorePat: + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_pat (cnid, ctsid, pmt_pid, program_id) VALUES (@cnid, @ctsid, @pmt_pid, @program_id)"; + command.Parameters.AddWithValue("@cnid", (int)args[0]); + command.Parameters.AddWithValue("@ctsid", (int)args[1]); + command.Parameters.AddWithValue("@pmt_pid", (int)args[2]); + command.Parameters.AddWithValue("@program_id", (ushort)args[3]); + command.ExecuteNonQuery(); + break; + case SpeedhackType.InsertPmt: + InsertPmt(connection, (int)args[0], (int)args[1], (ProgramMapping)args[2]); + break; + case SpeedhackType.InsertTsdt: + InsertTsdt(connection, (TsdtCoordinate)args[0]); + break; + case SpeedhackType.UpdateTsdtStationIdentification: + UpdateTsdtStationIdentification(connection, (int)args[0], (int)args[1], (string)args[2]); + break; + case SpeedhackType.InsertSdt: + InsertSdt(connection, (ushort)args[0], (ushort)args[1], (SdtService)args[2]); + break; + case SpeedhackType.InsertCat: + InsertCat(connection, (int)args[0], (int)args[1], (CaDescriptor)args[2]); + break; + case SpeedhackType.InsertBatTs: + InsertBatTs(connection, (ushort)args[0], (BatTransportStream)args[1]); + break; + case SpeedhackType.InsertNitTransportStream: + InsertNitTransportStream(connection, (ushort)args[0], (NitTransportStream)args[1]); + break; + case SpeedhackType.UpdateSdt: + UpdateSdt(connection, (ushort)args[0], (ushort)args[1], (SdtService)args[2]); + break; + case SpeedhackType.UpdateNitTransportStream: + SqlUpdateNitTransportStream(connection, (ushort)args[0], (NitTransportStream)args[1]); + break; + default: + throw new NotImplementedException(speedhackJob.Key.ToString()); + } + } + connection.Close(); + } + } + + private void SqlUpdateNitTransportStream(MySqlConnection connection, ushort networkId, NitTransportStream newer) + { + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "UPDATE dvb_nit_transport_stream " + + "SET onid = @onid, delivery_method = @delivery_method, east = @east, fec_inner = @fec_inner, frequency = @frequency, orbital_position = @orbital_position, polarization = @polarization, roll_off = @roll_off, s2 = @s2, symbol_rate = @symbol_rate, scrambling_sequence_index = @scrambling_sequence_index, input_stream_identifier = @input_stream_identifier, timeslice_number = @timeslice_number, ts_gs_mode = @ts_gs_mode, private_data_specifier_id = @private_data_specifier_id, tfs_flag = @tfs_flag, bandwidth = @bandwidth, guard_interval = @guard_interval, other_frequency_flag = @other_frequency_flag, plp_id = @plp_id, siso_miso = @siso_miso, t2_system_id = @t2_system_id, transmission_mode = @transmission_mode, coding_type = @coding_type, modulation_type = @modulation_type, fec_outer = @fec_outer, code_rate_hp_stream = @code_rate_hp_stream, code_rate_lp_stream = @code_rate_lp_stream, hierarchy_information = @hierarchy_information, mpe_fec_indicator = @mpe_fec_indicator, priority = @priority, time_slicing_indicator = @time_slicing_indicator, network_name = @network_name, target_region_country_code = @target_region_country_code, numupdates = numupdates + 1, dateupdated = CURRENT_TIMESTAMP " + + "WHERE nid = @nid " + + "AND tsid = @tsid"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@delivery_method", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@east", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@fec_inner", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@frequency", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@orbital_position", MySqlDbType.Float); + mySqlCommand.Parameters.Add("@polarization", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@roll_off", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@s2", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@symbol_rate", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@scrambling_sequence_index", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@input_stream_identifier", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@timeslice_number", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@ts_gs_mode", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@private_data_specifier_id", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@tfs_flag", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@bandwidth", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@guard_interval", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@other_frequency_flag", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@plp_id", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@siso_miso", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@t2_system_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@transmission_mode", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@coding_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@modulation_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@fec_outer", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@code_rate_hp_stream", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@code_rate_lp_stream", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@hierarchy_information", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@mpe_fec_indicator", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@priority", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@time_slicing_indicator", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@network_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@target_region_country_code", MySqlDbType.VarChar); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = newer.TransportStreamId; + mySqlCommand.Parameters["@onid"].Value = newer.OriginalNetworkId; + mySqlCommand.Parameters["@delivery_method"].Value = (int?)newer.DeliveryMethod; + mySqlCommand.Parameters["@east"].Value = newer.East; + mySqlCommand.Parameters["@fec_inner"].Value = (int?)newer.FecInner; + mySqlCommand.Parameters["@frequency"].Value = newer.Frequency; + mySqlCommand.Parameters["@orbital_position"].Value = newer.OrbitalPosition; + mySqlCommand.Parameters["@polarization"].Value = (int?)newer.Polarization; + mySqlCommand.Parameters["@roll_off"].Value = newer.RollOff; + mySqlCommand.Parameters["@s2"].Value = newer.S2; + mySqlCommand.Parameters["@symbol_rate"].Value = newer.SymbolRate; + mySqlCommand.Parameters["@scrambling_sequence_index"].Value = newer.ScramblingSequenceIndex; + mySqlCommand.Parameters["@input_stream_identifier"].Value = newer.InputStreamIdentifier; + mySqlCommand.Parameters["@timeslice_number"].Value = newer.TimesliceNumber; + mySqlCommand.Parameters["@ts_gs_mode"].Value = (int?)newer.TsGsMode; + mySqlCommand.Parameters["@private_data_specifier_id"].Value = newer.PrivateDataSpecifierId; + mySqlCommand.Parameters["@tfs_flag"].Value = newer.TfsFlag; + mySqlCommand.Parameters["@bandwidth"].Value = newer.Bandwidth; + mySqlCommand.Parameters["@guard_interval"].Value = newer.GuardInterval; + mySqlCommand.Parameters["@other_frequency_flag"].Value = newer.OtherFrequencyFlag; + mySqlCommand.Parameters["@plp_id"].Value = newer.PlpId; + mySqlCommand.Parameters["@siso_miso"].Value = newer.SisoMiso; + mySqlCommand.Parameters["@t2_system_id"].Value = newer.T2SystemId; + mySqlCommand.Parameters["@transmission_mode"].Value = newer.TransmissionMode; + mySqlCommand.Parameters["@coding_type"].Value = (int?)newer.CodingType; + mySqlCommand.Parameters["@modulation_type"].Value = newer.ModulationType; + mySqlCommand.Parameters["@fec_outer"].Value = (int?)newer.FecOuter; + mySqlCommand.Parameters["@code_rate_hp_stream"].Value = (int?)newer.CodeRateHpStream; + mySqlCommand.Parameters["@code_rate_lp_stream"].Value = (int?)newer.CodeRateLpStream; + mySqlCommand.Parameters["@hierarchy_information"].Value = (int?)newer.HierarchyInformation; + mySqlCommand.Parameters["@mpe_fec_indicator"].Value = newer.MpeFecIndicator; + mySqlCommand.Parameters["@priority"].Value = newer.Priority; + mySqlCommand.Parameters["@time_slicing_indicator"].Value = newer.TimeSlicingIndicator; + mySqlCommand.Parameters["@network_name"].Value = newer.NetworkName; + mySqlCommand.Parameters["@target_region_country_code"].Value = newer.TargetRegionCountryCode; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + + } + + private void UpdateSdt(MySqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService newer) + { + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "UPDATE dvb_sdt " + + "SET eit_schedule = @eit_schedule, eit_schedule_following = @eit_schedule_following, running_status = @running_status, free_ca_mode = @free_ca_mode, service_name = @service_name, service_provider_name = @service_provider_name, service_type = @service_type, private_data_specifier = @private_data_specifier, component_tag = @component_tag, iso_639_language_code = @iso_639_language_code, text = @text, data_broadcast_id = @data_broadcast_id, selector = @selector, reference_service_id = @reference_service_id, default_authority = @default_authority, control_remote_access_over_internet = @control_remote_access_over_internet, do_not_apply_revocation = @do_not_apply_revocation, do_not_scramble = @do_not_scramble, old_onid = @old_onid, old_service_id = @old_service_id, old_tsid = @old_tsid, num_updates = num_updates + 1, dateupdated = CURRENT_TIMESTAMP " + + "WHERE tsid = @tsid " + + "AND onid = @onid " + + "AND service_id = @service_id"; + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@eit_schedule", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@eit_schedule_following", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@running_status", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@free_ca_mode", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@service_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@service_provider_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@service_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@private_data_specifier", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@component_tag", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@iso_639_language_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@text", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@data_broadcast_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@selector", MySqlDbType.TinyBlob); + mySqlCommand.Parameters.Add("@reference_service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@control_remote_access_over_internet", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@do_not_apply_revocation", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@do_not_scramble", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@old_onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@old_service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@old_tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@default_authority", MySqlDbType.VarChar); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = newer.ServiceId; + mySqlCommand.Parameters["@eit_schedule"].Value = newer.EitScheduleFlag; + mySqlCommand.Parameters["@eit_schedule_following"].Value = newer.EitPresentFollowingFlag; + mySqlCommand.Parameters["@running_status"].Value = (int)newer.RunningStatus; + mySqlCommand.Parameters["@free_ca_mode"].Value = newer.FreeCaMode; + mySqlCommand.Parameters["@service_name"].Value = newer.ServiceName; + mySqlCommand.Parameters["@service_provider_name"].Value = newer.ServiceProviderName; + mySqlCommand.Parameters["@service_type"].Value = (int?)newer.ServiceType; + mySqlCommand.Parameters["@private_data_specifier"].Value = newer.PrivateDataSpecifier; + mySqlCommand.Parameters["@component_tag"].Value = newer.ComponentTag; + mySqlCommand.Parameters["@iso_639_language_code"].Value = newer.Iso639LanguageCode; + mySqlCommand.Parameters["@text"].Value = newer.Text; + mySqlCommand.Parameters["@data_broadcast_id"].Value = newer.DataBroadcastId; + mySqlCommand.Parameters["@selector"].Value = newer.Selector; + mySqlCommand.Parameters["@reference_service_id"].Value = newer.ReferenceServiceId; + mySqlCommand.Parameters["@control_remote_access_over_internet"].Value = newer.ControlRemoteAccessOverInternet; + mySqlCommand.Parameters["@do_not_apply_revocation"].Value = newer.DoNotApplyRevocation; + mySqlCommand.Parameters["@do_not_scramble"].Value = newer.DoNotScramble; + mySqlCommand.Parameters["@old_onid"].Value = newer.OldOriginalNetworkId; + mySqlCommand.Parameters["@old_service_id"].Value = newer.OldServiceId; + mySqlCommand.Parameters["@old_tsid"].Value = newer.OldTransportStreamId; + mySqlCommand.Parameters["@default_authority"].Value = newer.DefaultAuthority; + + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + + } + + private void InsertNitTransportStream(MySqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + Guid uuid = Guid.Empty; + bool hasLinkages = HasLinkages(transportStream.Linkages); + if (hasLinkages) + uuid = Guid.NewGuid(); + { + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = + "INSERT INTO dvb_nit_transport_stream " + + " (nid, tsid, onid, delivery_method, east, fec_inner, frequency, orbital_position, polarization, roll_off, s2, symbol_rate, scrambling_sequence_index, input_stream_identifier, timeslice_number, ts_gs_mode, private_data_specifier_id, tfs_flag, bandwidth, guard_interval, other_frequency_flag, plp_id, siso_miso, t2_system_id, transmission_mode, coding_type, modulation_type, fec_outer, code_rate_hp_stream, code_rate_lp_stream, hierarchy_information, mpe_fec_indicator, priority, time_slicing_indicator, network_name, target_region_country_code, uuid) " + + "VALUES" + + " (@nid, @tsid, @onid, @delivery_method, @east, @fec_inner, @frequency, @orbital_position, @polarization, @roll_off, @s2, @symbol_rate, " + + " @scrambling_sequence_index, @input_stream_identifier, @timeslice_number, @ts_gs_mode, @private_data_specifier_id, @tfs_flag, @bandwidth, " + + " @guard_interval, @other_frequency_flag, @plp_id, @siso_miso, @t2_system_id, @transmission_mode, @coding_type, @modulation_type, @fec_outer, " + + " @code_rate_hp_stream, @code_rate_lp_stream, @hierarchy_information, @mpe_fec_indicator, @priority, @time_slicing_indicator, @network_name, " + + " @target_region_country_code, @uuid)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@delivery_method", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@east", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@fec_inner", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@frequency", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@orbital_position", MySqlDbType.Float); + mySqlCommand.Parameters.Add("@polarization", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@roll_off", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@s2", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@symbol_rate", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@scrambling_sequence_index", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@input_stream_identifier", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@timeslice_number", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@ts_gs_mode", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@private_data_specifier_id", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@tfs_flag", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@bandwidth", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@guard_interval", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@other_frequency_flag", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@plp_id", MySqlDbType.Int16); + mySqlCommand.Parameters.Add("@siso_miso", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@t2_system_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@transmission_mode", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@coding_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@modulation_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@fec_outer", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@code_rate_hp_stream", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@code_rate_lp_stream", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@hierarchy_information", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@mpe_fec_indicator", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@priority", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@time_slicing_indicator", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@network_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@target_region_country_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters["@nid"].Value = networkId; + mySqlCommand.Parameters["@tsid"].Value = transportStream.TransportStreamId; + mySqlCommand.Parameters["@onid"].Value = transportStream.OriginalNetworkId; + mySqlCommand.Parameters["@delivery_method"].Value = (int?)transportStream.DeliveryMethod; + mySqlCommand.Parameters["@east"].Value = transportStream.East; + mySqlCommand.Parameters["@fec_inner"].Value = (int?)transportStream.FecInner; + mySqlCommand.Parameters["@frequency"].Value = transportStream.Frequency; + mySqlCommand.Parameters["@orbital_position"].Value = transportStream.OrbitalPosition; + mySqlCommand.Parameters["@polarization"].Value = (int?)transportStream.Polarization; + mySqlCommand.Parameters["@roll_off"].Value = transportStream.RollOff; + mySqlCommand.Parameters["@s2"].Value = transportStream.S2; + mySqlCommand.Parameters["@symbol_rate"].Value = transportStream.SymbolRate; + mySqlCommand.Parameters["@scrambling_sequence_index"].Value = transportStream.ScramblingSequenceIndex; + mySqlCommand.Parameters["@input_stream_identifier"].Value = transportStream.InputStreamIdentifier; + mySqlCommand.Parameters["@timeslice_number"].Value = transportStream.TimesliceNumber; + mySqlCommand.Parameters["@ts_gs_mode"].Value = (int?)transportStream.TsGsMode; + mySqlCommand.Parameters["@private_data_specifier_id"].Value = transportStream.PrivateDataSpecifierId; + mySqlCommand.Parameters["@tfs_flag"].Value = transportStream.TfsFlag; + mySqlCommand.Parameters["@bandwidth"].Value = transportStream.Bandwidth; + mySqlCommand.Parameters["@guard_interval"].Value = transportStream.GuardInterval; + mySqlCommand.Parameters["@other_frequency_flag"].Value = transportStream.OtherFrequencyFlag; + mySqlCommand.Parameters["@plp_id"].Value = transportStream.PlpId; + mySqlCommand.Parameters["@siso_miso"].Value = transportStream.SisoMiso; + mySqlCommand.Parameters["@t2_system_id"].Value = transportStream.T2SystemId; + mySqlCommand.Parameters["@transmission_mode"].Value = transportStream.TransmissionMode; + mySqlCommand.Parameters["@coding_type"].Value = (int?)transportStream.CodingType; + mySqlCommand.Parameters["@modulation_type"].Value = transportStream.ModulationType; + mySqlCommand.Parameters["@fec_outer"].Value = (int?)transportStream.FecOuter; + mySqlCommand.Parameters["@code_rate_hp_stream"].Value = (int?)transportStream.CodeRateHpStream; + mySqlCommand.Parameters["@code_rate_lp_stream"].Value = (int?)transportStream.CodeRateLpStream; + mySqlCommand.Parameters["@hierarchy_information"].Value = (int?)transportStream.HierarchyInformation; + mySqlCommand.Parameters["@mpe_fec_indicator"].Value = transportStream.MpeFecIndicator; + mySqlCommand.Parameters["@priority"].Value = transportStream.Priority; + mySqlCommand.Parameters["@time_slicing_indicator"].Value = transportStream.TimeSlicingIndicator; + mySqlCommand.Parameters["@network_name"].Value = transportStream.NetworkName; + mySqlCommand.Parameters["@target_region_country_code"].Value = transportStream.TargetRegionCountryCode; + if (hasLinkages) + mySqlCommand.Parameters["@uuid"].Value = uuid.ToString(); + else + mySqlCommand.Parameters["@uuid"].Value = DBNull.Value; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + + InsertNitTransportStreamCellFrequencies(connection, networkId, transportStream); + InsertNitTransportStreamCellInfos(connection, networkId, transportStream); + InsertNitTransportStreamCells(connection, networkId, transportStream); + InsertNitTransportStreamCentreFrequencies(connection, networkId, transportStream); + InsertNitTransportStreamServices(connection, networkId, transportStream, transaction); + InsertNitTransportStreamTargetRegions(connection, networkId, transportStream); + transaction.Commit(); + } + + } + + private void InsertBatTs(MySqlConnection connection, ushort batBouquetBouquetId, BatTransportStream child) + { + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_bat_transport_stream (bouquet_id, original_network_id, transport_stream_id, private_data_specifier, default_authority) " + + "VALUES (@bouquet_id, @original_network_id, @transport_stream_id, @private_data_specifier, @default_authority)"; + command.Parameters.AddWithValue("@bouquet_id", batBouquetBouquetId); + command.Parameters.AddWithValue("@original_network_id", child.OriginalNetworkId); + command.Parameters.AddWithValue("@transport_stream_id", child.TransportStreamId); + command.Parameters.AddWithValue("@private_data_specifier", child.PrivateDataSpecifier); + command.Parameters.AddWithValue("@default_authority", child.DefaultAuthority); + command.ExecuteNonQuery(); + + InsertBatTransportStreamCountryAvailability(transaction, batBouquetBouquetId, child); + InsertBatTransportStreamServiceList(transaction, batBouquetBouquetId, child); + transaction.Commit(); + } + + private void InsertCat(MySqlConnection connection, int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "INSERT INTO dvb_cat (nid, tsid, ca_pid, ca_system_id, private_data) VALUES (@nid,@tsid,@ca_pid,@ca_system_id,@private_data)"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ca_pid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@ca_system_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@private_data", MySqlDbType.TinyBlob); + mySqlCommand.Parameters["@nid"].Value = currentNetworkId; + mySqlCommand.Parameters["@tsid"].Value = currentTransportStreamId; + mySqlCommand.Parameters["@ca_pid"].Value = caDescriptor.CaPid; + mySqlCommand.Parameters["@ca_system_id"].Value = caDescriptor.CaSystemId; + if (caDescriptor.PrivateData == null) + mySqlCommand.Parameters["@private_data"].Value = DBNull.Value; + else if (caDescriptor.PrivateData.Length == 0) + mySqlCommand.Parameters["@private_data"].Value = DBNull.Value; + else + mySqlCommand.Parameters["@private_data"].Value = caDescriptor.PrivateData; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + } + + private void InsertSdt(MySqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + bool hasLinkages = HasLinkages(sdtService.Linkages); + Guid linkageGuid = Guid.Empty; + if (hasLinkages) + linkageGuid = Guid.NewGuid(); + + using (MySqlTransaction transaction = connection.BeginTransaction()) + { + using (MySqlCommand mySqlCommand = connection.CreateCommand()) + { + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = + "INSERT INTO dvb_sdt" + + " (tsid, onid, service_id, eit_schedule, eit_schedule_following, running_status, free_ca_mode, service_name, service_provider_name, service_type, private_data_specifier, iso_639_language_code, text, data_broadcast_id, selector, reference_service_id, control_remote_access_over_internet, do_not_apply_revocation, do_not_scramble, uuid)" + + "VALUES" + + " (@tsid,@onid,@service_id,@eit_schedule,@eit_schedule_following, @running_status, @free_ca_mode,@service_name,@service_provider_name,@service_type,@private_data_specifier,@iso_639_language_code,@text,@data_broadcat_id,@selector, @reference_service_id, @control_remote_access_over_internet, @do_not_apply_revocation, @do_not_scramble, @uuid)"; + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@eit_schedule", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@eit_schedule_following", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@running_status", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@free_ca_mode", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@service_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@service_provider_name", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@service_type", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@private_data_specifier", MySqlDbType.Int64); + mySqlCommand.Parameters.Add("@component_tag", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@iso_639_language_code", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@text", MySqlDbType.VarChar); + mySqlCommand.Parameters.Add("@data_broadcat_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@selector", MySqlDbType.TinyBlob); + mySqlCommand.Parameters.Add("@reference_service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@control_remote_access_over_internet", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@do_not_apply_revocation", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@do_not_scramble", MySqlDbType.Bool); + mySqlCommand.Parameters.Add("@old_onid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@old_service_id", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@old_tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@uuid", MySqlDbType.VarChar); + mySqlCommand.Parameters["@tsid"].Value = transportStreamId; + mySqlCommand.Parameters["@onid"].Value = originalNetworkId; + mySqlCommand.Parameters["@service_id"].Value = sdtService.ServiceId; + mySqlCommand.Parameters["@eit_schedule"].Value = sdtService.EitScheduleFlag; + mySqlCommand.Parameters["@eit_schedule_following"].Value = sdtService.EitPresentFollowingFlag; + mySqlCommand.Parameters["@running_status"].Value = (int)sdtService.RunningStatus; + mySqlCommand.Parameters["@free_ca_mode"].Value = sdtService.FreeCaMode; + mySqlCommand.Parameters["@service_name"].Value = sdtService.ServiceName; + mySqlCommand.Parameters["@service_provider_name"].Value = sdtService.ServiceProviderName; + mySqlCommand.Parameters["@service_type"].Value = (int?)sdtService.ServiceType; + mySqlCommand.Parameters["@private_data_specifier"].Value = sdtService.PrivateDataSpecifier; + mySqlCommand.Parameters["@component_tag"].Value = sdtService.ComponentTag; + mySqlCommand.Parameters["@iso_639_language_code"].Value = sdtService.Iso639LanguageCode; + mySqlCommand.Parameters["@text"].Value = sdtService.Text; + mySqlCommand.Parameters["@data_broadcat_id"].Value = sdtService.DataBroadcastId; + mySqlCommand.Parameters["@selector"].Value = sdtService.Selector; + mySqlCommand.Parameters["@reference_service_id"].Value = sdtService.ReferenceServiceId; + mySqlCommand.Parameters["@control_remote_access_over_internet"].Value = sdtService.ControlRemoteAccessOverInternet; + mySqlCommand.Parameters["@do_not_apply_revocation"].Value = sdtService.DoNotApplyRevocation; + mySqlCommand.Parameters["@do_not_scramble"].Value = sdtService.DoNotScramble; + mySqlCommand.Parameters["@old_onid"].Value = sdtService.OldOriginalNetworkId; + mySqlCommand.Parameters["@old_service_id"].Value = sdtService.OldServiceId; + mySqlCommand.Parameters["@old_tsid"].Value = sdtService.OldTransportStreamId; + mySqlCommand.Parameters["@uuid"].Value = hasLinkages ? linkageGuid : null; + SetNulls(mySqlCommand); + mySqlCommand.ExecuteNonQuery(); + } + + InsertSdtCaIdentifiers(transaction, transportStreamId, originalNetworkId, sdtService); + InsertSdtCountryAvailability(transaction, transportStreamId, originalNetworkId, sdtService); + if (hasLinkages) + InsertSdtLinkages(connection, linkageGuid, sdtService.Linkages, transaction); + InsertSdtMultilingualServiceName(connection, transportStreamId, originalNetworkId, sdtService); + InsertSdtNvodReferences(transaction, transportStreamId, originalNetworkId, sdtService); + InsertSdtComponents(transaction, transportStreamId, originalNetworkId, sdtService); + transaction.Commit(); + } + + } + + private void UpdateTsdtStationIdentification(MySqlConnection connection, int cnid, int ctsid, string stationIdentification) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_tsdt SET station_identification = @stationIdent WHERE cnid = @cnid AND @ctsid = @ctsid"; + command.Parameters.AddWithValue("@stationIdent", stationIdentification); + command.Parameters.AddWithValue("@cnid", cnid); + command.Parameters.AddWithValue("@ctsid", ctsid); + command.ExecuteNonQuery(); + } + + private void InsertTsdt(MySqlConnection connection, TsdtCoordinate tsdtCoordinate) + { + MySqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_tsdt (cnid, ctsid, compliance) VALUES (@cnid,@ctsid,@compliance)"; + command.Parameters.AddWithValue("@cnid", tsdtCoordinate.CurrentNetworkId); + command.Parameters.AddWithValue("@ctsid", tsdtCoordinate.CurrentTransportStreamId); + command.Parameters.AddWithValue("@compliance", tsdtCoordinate.Compliance); + command.ExecuteNonQuery(); + } + + private void InsertPmt(MySqlConnection connection, int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + /*MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "SELECT dateadded FROM dvb_pmt WHERE nid = @nid AND tsid = @tsid AND program_number = @program_number"; + mySqlCommand.Parameters.Add("@nid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@tsid", MySqlDbType.Int32); + mySqlCommand.Parameters.Add("@program_number", MySqlDbType.Int32); + mySqlCommand.Parameters["@nid"].Value = currentNetworkId; + mySqlCommand.Parameters["@tsid"].Value = currentTransportStreamId; + mySqlCommand.Parameters["@program_number"].Value = mapping.ProgramNumber; + MySqlDataReader dataReader = mySqlCommand.ExecuteReader(); + bool alreadyKnown = dataReader.Read(); + dataReader.Close(); + if (alreadyKnown) + return;*/ + + MySqlTransaction transaction = connection.BeginTransaction(); + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.Transaction = transaction; + mySqlCommand.CommandText = + "INSERT INTO dvb_pmt " + + "(nid, tsid, program_number, pcr_pid, maximum_bitrate, multiplex_buffer_utilization_bound_valid_flag, multiplex_lfw_offset_lower_bound, multiplex_ltw_offset_upper_bound, clock_accuracy_exponent, clock_accuracy_integer, external_clock_reference_indicator, sb_leak_rate, sb_size, ca_pid, ca_system_id, ca_private_data, private_data_specifier, registration_additional_identification_info, registration_format_identifier, new_original_network_id, new_service_id, new_transport_stream_id, component_tag, metadata_input_leak_rate, metadata_buffer_size, metadata_output_leak_rate, scrambling_mode, es_id, audio_type, iso_639_language_code, iod_graphics_profile_level_indication, iod_visual_profile_level_indication, iod_audio_profile_level_indication, iod_scene_profile_level_indication, iod_od_profile_level_indication, iod_url_string, iod_include_inline_profile_level_flag, iod_url_flag, iod_object_descriptor_id, scope_of_iod_label, iod_label, private_data_indicator, frame_Rate, chroma_format, constrained_parameter_flag, frame_rate_extension_flag, mpeg1_only_flag, multiple_framerate_flag, profile_and_level_indication, still_picture_flag, metadata_program_number, metadata_application_format, metadata_application_format_identifier, metadata_format, metadata_format_identifier, metadata_locator_record, metadata_locator_record_flag, metadata_service_id, mpeg_carriage_flag, metadata_private_data, transport_stream_id, transport_stream_location, free_format, audio_stream_id, layer, variable_rate_audio, copy_control_private_data, alignment_type, hierarchy_type, hierarchy_layer_type, tref_present_flag, hierarchy_channel, no_view_scalability_flag, hierarchy_embedded_layer_index, no_quality_scalability_flag, no_spatial_scalability_flag, no_temporal_scalability_flag) " + + "VALUES " + + "(@nid, @tsid, @program_number, @pcr_pid, @maximum_bitrate, @multiplex_buffer_utilization_bound_valid_flag, @multiplex_lfw_offset_lower_bound, @multiplex_ltw_offset_upper_bound, " + + "@clock_accuracy_exponent, @clock_accuracy_integer, @external_clock_reference_indicator, @sb_leak_rate, @sb_size, @ca_pid, @ca_system_id, @ca_private_data, @private_data_specifier, " + + "@registration_additional_identification_info, @registration_format_identifier, @new_original_network_id, @new_service_id, @new_transport_stream_id, @component_tag, " + + "@metadata_input_leak_rate, @metadata_buffer_size, @metadata_output_leak_rate, @scrambling_mode, @es_id, @audio_type, @iso_639_language_code, @iod_graphics_profile_level_indication, " + + "@iod_visual_profile_level_indication, @iod_audio_profile_level_indication, @iod_scene_profile_level_indication, @iod_od_profile_level_indication, @iod_url_string, " + + "@iod_include_inline_profile_level_flag, @iod_url_flag, @iod_object_descriptor_id, @scope_of_iod_label, @iod_label, @private_data_indicator, @frame_Rate, @chroma_format, @constrained_parameter_flag, @frame_rate_extension_flag, @mpeg1_only_flag, @multiple_framerate_flag, @profile_and_level_indication, @still_picture_flag, @metadata_program_number, @metadata_application_format, @metadata_application_format_identifier, @metadata_format, @metadata_format_identifier, @metadata_locator_record, @metadata_locator_record_flag, @metadata_service_id, @mpeg_carriage_flag, @metadata_private_data, @transport_stream_id, @transport_stream_location, @free_format, @audio_stream_id, @layer, @variable_rate_audio, @copy_control_private_data, @alignment_type, @hierarchy_type, @hierarchy_layer_type, @tref_present_flag, @hierarchy_channel, @no_view_scalability_flag, @hierarchy_embedded_layer_index, @no_quality_scalability_flag, @no_spatial_scalability_flag, @no_temporal_scalability_flag)\r\n"; + mySqlCommand.Parameters.AddWithValue("@nid", currentNetworkId); + mySqlCommand.Parameters.AddWithValue("@tsid", currentTransportStreamId); + mySqlCommand.Parameters.AddWithValue("@program_number", mapping.ProgramNumber); + mySqlCommand.Parameters.AddWithValue("@pcr_pid", mapping.PcrPid); + mySqlCommand.Parameters.AddWithValue("@maximum_bitrate", mapping.MaximumBitrate); + mySqlCommand.Parameters.AddWithValue("@multiplex_buffer_utilization_bound_valid_flag", mapping.MultiplexBufferUtilizationBoundValidFlag); + mySqlCommand.Parameters.AddWithValue("@multiplex_lfw_offset_lower_bound", mapping.MultiplexLtwOffsetLowerBound); + mySqlCommand.Parameters.AddWithValue("@multiplex_ltw_offset_upper_bound", mapping.MultiplexLtwOffsetUpperBound); + mySqlCommand.Parameters.AddWithValue("@clock_accuracy_exponent", mapping.ClockAccuracyExponent); + mySqlCommand.Parameters.AddWithValue("@clock_accuracy_integer", mapping.ClockAccuracyInteger); + mySqlCommand.Parameters.AddWithValue("@external_clock_reference_indicator", mapping.ExternalClockReferenceIndicator); + mySqlCommand.Parameters.AddWithValue("@sb_leak_rate", mapping.SbLeakRate); + mySqlCommand.Parameters.AddWithValue("@sb_size", mapping.SbSize); + mySqlCommand.Parameters.AddWithValue("@ca_pid", mapping.CaPid); + mySqlCommand.Parameters.AddWithValue("@ca_system_id", mapping.CaSystemId); + mySqlCommand.Parameters.AddWithValue("@ca_private_data", mapping.CaPrivateData); + mySqlCommand.Parameters.AddWithValue("@private_data_specifier", mapping.PrivateDataSpecifier); + mySqlCommand.Parameters.AddWithValue("@registration_additional_identification_info", mapping.RegistrationAdditionalIdentificationInfo); + mySqlCommand.Parameters.AddWithValue("@registration_format_identifier", mapping.RegistrationFormatIdentifier); + mySqlCommand.Parameters.AddWithValue("@new_original_network_id", mapping.NewOriginalNetworkId); + mySqlCommand.Parameters.AddWithValue("@new_service_id", mapping.NewServiceId); + mySqlCommand.Parameters.AddWithValue("@new_transport_stream_id", mapping.NewTransportStreamId); + mySqlCommand.Parameters.AddWithValue("@component_tag", mapping.ComponentTag); + mySqlCommand.Parameters.AddWithValue("@metadata_input_leak_rate", mapping.MetadataInputLeakRate); + mySqlCommand.Parameters.AddWithValue("@metadata_buffer_size", mapping.MetadataBufferSize); + mySqlCommand.Parameters.AddWithValue("@metadata_output_leak_rate", mapping.MetadataOutputLeakRate); + mySqlCommand.Parameters.AddWithValue("@scrambling_mode", mapping.ScramblingMode); + mySqlCommand.Parameters.AddWithValue("@es_id", mapping.EsId); + mySqlCommand.Parameters.AddWithValue("@audio_type", mapping.AudioType); + mySqlCommand.Parameters.AddWithValue("@iso_639_language_code", mapping.Iso639LanguageCode); + if (mapping.InitialObjectDescriptor != null) + { + mySqlCommand.Parameters.AddWithValue("@iod_graphics_profile_level_indication", mapping.InitialObjectDescriptor.GraphicsProfileLevelIndication); + mySqlCommand.Parameters.AddWithValue("@iod_visual_profile_level_indication", mapping.InitialObjectDescriptor.VisualProfileLevelIndication); + mySqlCommand.Parameters.AddWithValue("@iod_audio_profile_level_indication", mapping.InitialObjectDescriptor.AudioProfileLevelIndication); + mySqlCommand.Parameters.AddWithValue("@iod_scene_profile_level_indication", mapping.InitialObjectDescriptor.SceneProfileLevelIndication); + mySqlCommand.Parameters.AddWithValue("@iod_od_profile_level_indication", mapping.InitialObjectDescriptor.OdProfileLevelIndication); + mySqlCommand.Parameters.AddWithValue("@iod_url_string", mapping.InitialObjectDescriptor.UrlString); + mySqlCommand.Parameters.AddWithValue("@iod_include_inline_profile_level_flag", mapping.InitialObjectDescriptor.IncludeInlineProfileLevelFlag); + mySqlCommand.Parameters.AddWithValue("@iod_url_flag", mapping.InitialObjectDescriptor.UrlFlag); + mySqlCommand.Parameters.AddWithValue("@iod_object_descriptor_id", mapping.InitialObjectDescriptor.ObjectDescriptorId); + } + else + { + mySqlCommand.Parameters.AddWithValue("@iod_graphics_profile_level_indication", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_visual_profile_level_indication", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_audio_profile_level_indication", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_scene_profile_level_indication", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_od_profile_level_indication", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_url_string", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_include_inline_profile_level_flag", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_url_flag", DBNull.Value); + mySqlCommand.Parameters.AddWithValue("@iod_object_descriptor_id", DBNull.Value); + } + //@, @, @, @, @)\r\n"; + mySqlCommand.Parameters.AddWithValue("@scope_of_iod_label", mapping.ScopeOfIodLabel); + mySqlCommand.Parameters.AddWithValue("@iod_label", mapping.IodLabel); + mySqlCommand.Parameters.AddWithValue("@private_data_indicator", mapping.PrivateDataIndicator); + mySqlCommand.Parameters.AddWithValue("@frame_Rate", mapping.FrameRate); + mySqlCommand.Parameters.AddWithValue("@chroma_format", mapping.ChromaFormat); + mySqlCommand.Parameters.AddWithValue("@constrained_parameter_flag", mapping.ConstrainedParameterFlag); + mySqlCommand.Parameters.AddWithValue("@frame_rate_extension_flag", mapping.FrameRateExtensionFlag); + mySqlCommand.Parameters.AddWithValue("@mpeg1_only_flag", mapping.Mpeg1OnlyFlag); + mySqlCommand.Parameters.AddWithValue("@multiple_framerate_flag", mapping.MultipleFramerateFlag); + mySqlCommand.Parameters.AddWithValue("@profile_and_level_indication", mapping.ProfileAndLevelIndication); + mySqlCommand.Parameters.AddWithValue("@still_picture_flag", mapping.StillPictureFlag); + mySqlCommand.Parameters.AddWithValue("@metadata_program_number", mapping.MetadataProgramNumber); + mySqlCommand.Parameters.AddWithValue("@metadata_application_format", mapping.MetadataApplicationFormat); + mySqlCommand.Parameters.AddWithValue("@metadata_application_format_identifier", mapping.MetadataApplicationFormatIdentifier); + mySqlCommand.Parameters.AddWithValue("@metadata_format", mapping.MetadataFormat); + mySqlCommand.Parameters.AddWithValue("@metadata_format_identifier", mapping.MetadataFormatIdentifier); + mySqlCommand.Parameters.AddWithValue("@metadata_locator_record", mapping.MetadataLocatorRecord); + mySqlCommand.Parameters.AddWithValue("@metadata_locator_record_flag", mapping.MetadataLocatorRecordFlag); + mySqlCommand.Parameters.AddWithValue("@metadata_service_id", mapping.MetadataServiceId); + mySqlCommand.Parameters.AddWithValue("@mpeg_carriage_flag", mapping.MpegCarriageFlag); + mySqlCommand.Parameters.AddWithValue("@metadata_private_data", mapping.MetadataPrivateData); + mySqlCommand.Parameters.AddWithValue("@transport_stream_id", mapping.TransportStreamId); + mySqlCommand.Parameters.AddWithValue("@transport_stream_location", mapping.TransportStreamLocation); + mySqlCommand.Parameters.AddWithValue("@free_format", mapping.FreeFormat); + mySqlCommand.Parameters.AddWithValue("@audio_stream_id", mapping.AudioStreamId); + mySqlCommand.Parameters.AddWithValue("@layer", mapping.Layer); + mySqlCommand.Parameters.AddWithValue("@variable_rate_audio", mapping.VariableRateAudio); + mySqlCommand.Parameters.AddWithValue("@copy_control_private_data", mapping.CopyControlPrivateData); + mySqlCommand.Parameters.AddWithValue("@alignment_type", mapping.AlignmentType); + mySqlCommand.Parameters.AddWithValue("@hierarchy_type", mapping.HierarchyType); + mySqlCommand.Parameters.AddWithValue("@hierarchy_layer_type", mapping.HierarchyLayerType); + mySqlCommand.Parameters.AddWithValue("@tref_present_flag", mapping.TrefPresentFlag); + mySqlCommand.Parameters.AddWithValue("@hierarchy_channel", mapping.HierarchyChannel); + mySqlCommand.Parameters.AddWithValue("@no_view_scalability_flag", mapping.NoViewScalabilityFlag); + mySqlCommand.Parameters.AddWithValue("@hierarchy_embedded_layer_index", mapping.HierarchyEmbeddedLayerIndex); + mySqlCommand.Parameters.AddWithValue("@no_quality_scalability_flag", mapping.NoQualityScalabilityFlag); + mySqlCommand.Parameters.AddWithValue("@no_spatial_scalability_flag", mapping.NoSpatialScalabilityFlag); + mySqlCommand.Parameters.AddWithValue("@no_temporal_scalability_flag", mapping.NoTemporalScalabilityFlag); + SetNulls(mySqlCommand); + int executeNonQuery = mySqlCommand.ExecuteNonQuery(); + + if (executeNonQuery > 0) + { + InsertPmtStreams(connection, mapping, currentNetworkId, currentTransportStreamId, transaction); + } + + transaction.Commit(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Tdt.cs b/DataTableStorages/skyscraper5.Data.MySql/Tdt.cs new file mode 100644 index 0000000..7971554 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Tdt.cs @@ -0,0 +1,124 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private struct TdtCoordinate + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + + public TdtCoordinate(int currentNetworkId, int currentTransportStreamId) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + CoordinateTimestamp = DateTime.MinValue; + } + + public bool Equals(TdtCoordinate other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId; + } + + public override bool Equals(object obj) + { + return obj is TdtCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId); + } + + public DateTime CoordinateTimestamp { get; set; } + } + + private HashSet _tdtCoordinates; + private DateTime? TestForTdt(MySqlConnection connection, int currentNetworkId, int currentTransportStreamId) + { + if (_tdtCoordinates == null) + _tdtCoordinates = new HashSet(); + TdtCoordinate coordinate = new TdtCoordinate(currentNetworkId, currentTransportStreamId); + TdtCoordinate storedCoordinate; + bool hasStoredCoordinate = _tdtCoordinates.TryGetValue(coordinate, out storedCoordinate); + if (hasStoredCoordinate) + return storedCoordinate.CoordinateTimestamp; + + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "SELECT utc FROM dvb_tdt WHERE cnid = @cnid AND ctid = @ctid"; + mySqlCommand.Parameters.AddWithValue("@cnid", currentNetworkId); + mySqlCommand.Parameters.AddWithValue("@ctid", currentTransportStreamId); + MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); + DateTime? result = null; + if (mySqlDataReader.Read()) + { + result = mySqlDataReader.GetDateTime(0); + coordinate.CoordinateTimestamp = result.Value; + _tdtCoordinates.Add(coordinate); + } + mySqlDataReader.Close(); + return result; + } + + private void UpdateTdt(MySqlConnection connection, int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + if (_speedhackQueue.Any(x => x.Key == SpeedhackType.UpdateTdt && (int)x.Value[0] == currentNetworkId && (int)x.Value[1] == currentTransportStreamId && (DateTime)x.Value[2] > utcTime)) + { + return; + } + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = + "UPDATE dvb_tdt SET utc = @utc, dateupdated = CURRENT_TIMESTAMP, num_updates = num_updates + 1 WHERE cnid = @cnid AND ctid = @ctid"; + mySqlCommand.Parameters.AddWithValue("@cnid", currentNetworkId); + mySqlCommand.Parameters.AddWithValue("@ctid", currentTransportStreamId); + mySqlCommand.Parameters.AddWithValue("@utc", utcTime); + mySqlCommand.ExecuteNonQuery(); + } + + private void InsertTdt(MySqlConnection connection, int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + MySqlCommand mySqlCommand = connection.CreateCommand(); + mySqlCommand.CommandText = "INSERT INTO dvb_tdt (cnid, ctid, utc) VALUES (@cnid, @ctid, @utc)"; + mySqlCommand.Parameters.AddWithValue("@cnid", currentNetworkId); + mySqlCommand.Parameters.AddWithValue("@ctid", currentTransportStreamId); + mySqlCommand.Parameters.AddWithValue("@utc", utcTime); + mySqlCommand.ExecuteNonQuery(); + } + + public bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + bool result = false; + connection.Open(); + DateTime? older = TestForTdt(connection, currentNetworkId, currentTransportStreamId); + connection.Close(); + if (older.HasValue) + { + if (utcTime > older.Value) + { + //UpdateTdt(connection, currentNetworkId, currentTransportStreamId, utcTime); + EnqueueSpeedhack(SpeedhackType.UpdateTdt, currentNetworkId, currentTransportStreamId, utcTime); + result = true; + } + } + else + { + //InsertTdt(connection, currentNetworkId, currentTransportStreamId, utcTime); + EnqueueSpeedhack(SpeedhackType.InsertTdt, currentNetworkId, currentTransportStreamId, utcTime); + _tdtCoordinates.Add(new TdtCoordinate(currentNetworkId, currentTransportStreamId)); + result = true; + } + return result; + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Teletext.cs b/DataTableStorages/skyscraper5.Data.MySql/Teletext.cs new file mode 100644 index 0000000..812c76c --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Teletext.cs @@ -0,0 +1,60 @@ +using MySqlConnector; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using skyscraper5.Teletext; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + + + public bool TestForTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, + DateTime timestamp) + { + ushort pageNo = magazine.HumanReadablePageNumber; + if (pageNo > 999) + return false; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM teletext_page_cache WHERE nid = @nid AND tsid = @tsid AND progno = @progno AND magazine = @magazine AND timestamp = @timestamp"; + command.Parameters.AddWithValue("@nid", networkId); + command.Parameters.AddWithValue("@tsid", transportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@magazine", magazine.HumanReadablePageNumber); + command.Parameters.AddWithValue("@timestamp", timestamp); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + } + + public void MarkTeletextPageAsKnown(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, + DateTime timestamp) + { + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO teletext_page_cache (nid, tsid, progno, magazine, timestamp) VALUES (@nid, @tsid, @progno, @magazine, @timestamp)"; + command.Parameters.AddWithValue("@nid", networkId); + command.Parameters.AddWithValue("@tsid", transportStreamId); + command.Parameters.AddWithValue("@progno", programNumber); + command.Parameters.AddWithValue("@magazine", magazine.HumanReadablePageNumber); + command.Parameters.AddWithValue("@timestamp", timestamp); + command.ExecuteNonQuery(); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Tot.cs b/DataTableStorages/skyscraper5.Data.MySql/Tot.cs new file mode 100644 index 0000000..9d356c9 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Tot.cs @@ -0,0 +1,157 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Versioning; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.Descriptors; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private DateTime? TestForTimeOffset(MySqlTransaction transaction, int currentNetworkId, int currentTransportStreamId, string timeOffsetCountryCode) + { + DateTime? result = null; + if (_totCoordinates == null) + _totCoordinates = new HashSet(); + TotCoordinate coordinate = new TotCoordinate(currentNetworkId, currentTransportStreamId, timeOffsetCountryCode); + TotCoordinate cachedCoordinate; + bool wasCached = _totCoordinates.TryGetValue(coordinate, out cachedCoordinate); + if (wasCached) + return cachedCoordinate.Timestamp; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = "SELECT utc FROM dvb_tot WHERE nid = @nid AND tsid = @tsid AND countrycode = @country_code"; + command.Parameters.AddWithValue("@nid", currentNetworkId); + command.Parameters.AddWithValue("@tsid", currentTransportStreamId); + command.Parameters.AddWithValue("@country_code", timeOffsetCountryCode); + MySqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + result = dataReader.GetDateTime(0); + coordinate.Timestamp = result.Value; + } + + dataReader.Close(); + return result; + } + + private void UpdateTimeOffset(MySqlTransaction transaction, int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor.LocalTime timeOffset) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "UPDATE dvb_tot SET utc = @utc, local_time_offset = @local_time_offset, time_of_change = @time_of_change, next_time_offset = @next_time_offset " + + "WHERE nid = @nid AND tsid = @tsid AND countrycode = @country_code"; + command.Parameters.AddWithValue("@nid", currentNetworkId); + command.Parameters.AddWithValue("@tsid", currentTransportStreamId); + command.Parameters.AddWithValue("@country_code", timeOffset.CountryCode); + command.Parameters.AddWithValue("@utc", utcTime); + command.Parameters.AddWithValue("@local_time_offset", (int)timeOffset.LocalTimeOffset.TotalSeconds); + command.Parameters.AddWithValue("@time_of_change", timeOffset.TimeOfChange); + command.Parameters.AddWithValue("@next_time_offset", timeOffset.NextTimeOffset); + command.ExecuteNonQuery(); + } + + private void InsertTimeOffset(MySqlTransaction transaction, int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor.LocalTime timeOffset) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT dvb_tot (nid, tsid, utc, countrycode, country_region_id, local_time_offset_polarity, local_time_offset, time_of_change, next_time_offset) " + + "VALUES (@nid, @tsid, @utc, @countrycode, @country_region_id, @local_time_offset_polarity, @local_time_offset, @time_of_change, @next_time_offset)"; + command.Parameters.AddWithValue("@nid", currentNetworkId); + command.Parameters.AddWithValue("@tsid", currentTransportStreamId); + command.Parameters.AddWithValue("@utc", utcTime); + command.Parameters.AddWithValue("@countrycode", timeOffset.CountryCode); + command.Parameters.AddWithValue("@country_region_id", timeOffset.CountryRegionId); + command.Parameters.AddWithValue("@local_time_offset_polarity", timeOffset.LocalTimeOffsetPolarity); + command.Parameters.AddWithValue("@local_time_offset", (int)timeOffset.LocalTimeOffset.TotalSeconds); + command.Parameters.AddWithValue("@time_of_change", timeOffset.TimeOfChange); + command.Parameters.AddWithValue("@next_time_offset", timeOffset.NextTimeOffset); + command.ExecuteNonQuery(); + + TdtCoordinate tdtCoordinate = new TdtCoordinate(currentNetworkId, currentTransportStreamId); + tdtCoordinate.CoordinateTimestamp = utcTime; + _tdtCoordinates.Add(tdtCoordinate); + } + + struct TotCoordinate + { + private readonly int _currentNetworkId; + private readonly int _currentTransportStreamId; + private readonly string _timeOffsetCountryCode; + + public TotCoordinate(int currentNetworkId, int currentTransportStreamId, string timeOffsetCountryCode) + { + _currentNetworkId = currentNetworkId; + _currentTransportStreamId = currentTransportStreamId; + _timeOffsetCountryCode = timeOffsetCountryCode; + Timestamp = DateTime.MinValue; + } + + public bool Equals(TotCoordinate other) + { + return _currentNetworkId == other._currentNetworkId && _currentTransportStreamId == other._currentTransportStreamId && _timeOffsetCountryCode == other._timeOffsetCountryCode; + } + + public override bool Equals(object obj) + { + return obj is TotCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_currentNetworkId, _currentTransportStreamId, _timeOffsetCountryCode); + } + + public DateTime Timestamp { get; set; } + } + + private HashSet _totCoordinates; + + public bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + if (ltod.LocalTimeOffsets == null) + return false; + + if (ltod.LocalTimeOffsets.Length == 0) + return false; + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + bool result = false; + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + foreach (LocalTimeOffsetDescriptor.LocalTime timeOffset in ltod.LocalTimeOffsets) + { + DateTime? older = TestForTimeOffset(transaction, currentNetworkId, currentTransportStreamId, timeOffset.CountryCode); + if (older.HasValue) + { + if (utcTime > older.Value) + { + //UpdateTimeOffset(transaction, currentNetworkId, currentTransportStreamId, utcTime, timeOffset); + EnqueueSpeedhack(SpeedhackType.UpdateTimeOffset, currentNetworkId, currentTransportStreamId, utcTime, timeOffset); + result = true; + } + } + else + { + //InsertTimeOffset(transaction, currentNetworkId, currentTransportStreamId, utcTime, timeOffset); + EnqueueSpeedhack(SpeedhackType.InsertTimeOffset, currentNetworkId, currentTransportStreamId, utcTime, timeOffset); + result = true; + } + } + transaction.Commit(); + connection.Close(); + return result; + } + } + + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Tsdt.cs b/DataTableStorages/skyscraper5.Data.MySql/Tsdt.cs new file mode 100644 index 0000000..9af695c --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Tsdt.cs @@ -0,0 +1,102 @@ +using MySqlConnector; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private class TsdtCoordinate + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public string Compliance { get; } + public string StationIdentification { get; set; } + + public TsdtCoordinate(int currentNetworkId, int currentTransportStreamId, string compliance) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + Compliance = compliance; + StationIdentification = null; + } + + public bool Equals(TsdtCoordinate other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && Compliance == other.Compliance; + } + + public override bool Equals(object obj) + { + return obj is TsdtCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, Compliance); + } + } + + private HashSet _tsdtCoordinates; + + public bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + if (_tsdtCoordinates == null) + _tsdtCoordinates = new HashSet(); + + TsdtCoordinate coordinate = new TsdtCoordinate(currentNetworkId, currentTransportStreamId, compliance); + if (_tsdtCoordinates.Contains(coordinate)) + return true; + + using (MySqlConnection conn = new MySqlConnection(_mcsb.ToString())) + { + conn.Open(); + MySqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_tsdt WHERE cnid = @cnid AND ctsid = @ctsid AND compliance = @compliance"; + command.Parameters.AddWithValue("@cnid", currentNetworkId); + command.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + command.Parameters.AddWithValue("@compliance", compliance); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _tsdtCoordinates.Add(coordinate); + dataReader.Close(); + conn.Close(); + return result; + } + } + + public void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + TsdtCoordinate coordinate = new TsdtCoordinate(currentNetworkId, currentTransportStreamId, compliance); + EnqueueSpeedhack(SpeedhackType.InsertTsdt, coordinate); + _tsdtCoordinates.Add(coordinate); + } + + public bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string stationIdentification) + { + if (_tsdtCoordinates == null) + return false; + + TsdtCoordinate tsdtCoordinate = _tsdtCoordinates.Where(x => + x.CurrentNetworkId == currentNetworkId && x.CurrentTransportStreamId == currentTransportStreamId) + .First(); + if (tsdtCoordinate.CurrentNetworkId == 0 && tsdtCoordinate.CurrentTransportStreamId == 0) + return false; + + if (!stationIdentification.Equals(tsdtCoordinate.StationIdentification)) + { + EnqueueSpeedhack(SpeedhackType.UpdateTsdtStationIdentification, currentNetworkId, currentTransportStreamId, stationIdentification); + tsdtCoordinate.StationIdentification = stationIdentification; + return true; + } + + return false; + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Unt.cs b/DataTableStorages/skyscraper5.Data.MySql/Unt.cs new file mode 100644 index 0000000..77bd70a --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Unt.cs @@ -0,0 +1,341 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + private struct GroupCoordinate + { + public byte CommonActionType { get; } + public byte CommonOuiHash { get; } + public string CommonOui { get; } + public byte CommonProcessingOrder { get; } + + public GroupCoordinate(byte commonActionType, byte commonOuiHash, string commonOui, byte commonProcessingOrder) + { + CommonActionType = commonActionType; + CommonOuiHash = commonOuiHash; + CommonOui = commonOui; + CommonProcessingOrder = commonProcessingOrder; + } + + public bool Equals(GroupCoordinate other) + { + return CommonActionType == other.CommonActionType && CommonOuiHash == other.CommonOuiHash && CommonOui == other.CommonOui && CommonProcessingOrder == other.CommonProcessingOrder; + } + + public override bool Equals(object obj) + { + return obj is GroupCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(CommonActionType, CommonOuiHash, CommonOui, CommonProcessingOrder); + } + } + + private HashSet _untGroupCoordinates; + private bool TestForUnt(MySqlTransaction transaction, UpdateNotificationGroup common) + { + GroupCoordinate groupCoordinate = new GroupCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder); + if (_untGroupCoordinates.Contains(groupCoordinate)) + return true; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_unt WHERE action_type = @action_type AND oui_hash = @oui_hash AND oui = @oui AND processing_order = @processing_order"; + command.Parameters.AddWithValue("@action_type", common.ActionType); + command.Parameters.AddWithValue("@oui_hash", common.OuiHash); + command.Parameters.AddWithValue("@oui", common.Oui); + command.Parameters.AddWithValue("@processing_order", common.ProcessingOrder); + MySqlDataReader mySqlDataReader = command.ExecuteReader(); + bool read = mySqlDataReader.Read(); + if (read) + _untGroupCoordinates.Add(groupCoordinate); + mySqlDataReader.Close(); + return read; + } + + private void InsertUnt(MySqlTransaction transaction, UpdateNotificationGroup common) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt (action_type, oui_hash, oui, processing_order, data_broadcast_id, association_tag, private_data, update_flag, update_method, update_priority) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @data_broadcast_id, @association_tag, @private_data, @update_flag, @update_method, @update_priority)"; + command.Parameters.AddWithValue("@action_type", common.ActionType); + command.Parameters.AddWithValue("@oui_hash", common.OuiHash); + command.Parameters.AddWithValue("@oui", common.Oui); + command.Parameters.AddWithValue("@processing_order", common.ProcessingOrder); + command.Parameters.AddWithValue("@data_broadcast_id", common.DataBroadcastId); + command.Parameters.AddWithValue("@association_tag", common.AssociationTag); + command.Parameters.AddWithValue("@private_data", common.PrivateData); + command.Parameters.AddWithValue("@update_flag", common.UpdateFlag); + command.Parameters.AddWithValue("@update_method", common.UpdateMethod); + command.Parameters.AddWithValue("@update_priority", common.UpdatePriority); + command.ExecuteNonQuery(); + + GroupCoordinate groupCoordinate = new GroupCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder); + _untGroupCoordinates.Add(groupCoordinate); + } + + private struct CompatibilityCoordinate + { + public byte CommonActionType { get; } + public byte CommonOuiHash { get; } + public string CommonOui { get; } + public byte CommonProcessingOrder { get; } + public byte CompatibilityDescriptorType { get; } + + public CompatibilityCoordinate(byte commonActionType, byte commonOuiHash, string commonOui, byte commonProcessingOrder, byte compatibilityDescriptorType) + { + CommonActionType = commonActionType; + CommonOuiHash = commonOuiHash; + CommonOui = commonOui; + CommonProcessingOrder = commonProcessingOrder; + CompatibilityDescriptorType = compatibilityDescriptorType; + } + + public bool Equals(CompatibilityCoordinate other) + { + return CommonActionType == other.CommonActionType && CommonOuiHash == other.CommonOuiHash && CommonOui == other.CommonOui && CommonProcessingOrder == other.CommonProcessingOrder && CompatibilityDescriptorType == other.CompatibilityDescriptorType; + } + + public override bool Equals(object obj) + { + return obj is CompatibilityCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(CommonActionType, CommonOuiHash, CommonOui, CommonProcessingOrder, CompatibilityDescriptorType); + } + } + + private HashSet _compatibilityCoordinates; + private bool TestForUntCompat(MySqlTransaction transaction, UpdateNotificationGroup common, Compatibility compatibility) + { + CompatibilityCoordinate compatibilityCoordinate = new CompatibilityCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType); + if (_compatibilityCoordinates.Contains(compatibilityCoordinate)) + return true; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_unt_compatibility WHERE " + + "action_type = @action_type AND oui_hash = @oui_hash AND oui = @oui AND processing_order = @processing_order AND descriptor_type = @descriptor_type"; + command.Parameters.AddWithValue("@action_type", common.ActionType); + command.Parameters.AddWithValue("@oui_hash", common.OuiHash); + command.Parameters.AddWithValue("@oui", common.Oui); + command.Parameters.AddWithValue("@processing_order", common.ProcessingOrder); + command.Parameters.AddWithValue("@descriptor_type", compatibility.DescriptorType); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _compatibilityCoordinates.Add(compatibilityCoordinate); + dataReader.Close(); + return result; + } + + private void InsertUntCompat(MySqlTransaction transaction, UpdateNotificationGroup common, Compatibility compatibility) + { + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type, private_data, specifier_type, specifier_data, model, version) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @descriptor_type, @private_data, @specifier_type, @specifier_data, @model, @version)"; + command.Parameters.AddWithValue("@action_type", common.ActionType); + command.Parameters.AddWithValue("@oui_hash", common.OuiHash); + command.Parameters.AddWithValue("@oui", common.Oui); + command.Parameters.AddWithValue("@processing_order", common.ProcessingOrder); + command.Parameters.AddWithValue("@descriptor_type", compatibility.DescriptorType); + command.Parameters.AddWithValue("@private_data", compatibility.PrivateData); + command.Parameters.AddWithValue("@specifier_type", compatibility.SpecifierType); + command.Parameters.AddWithValue("@specifier_data", compatibility.SpecifierData); + command.Parameters.AddWithValue("@model", compatibility.Model); + command.Parameters.AddWithValue("@version", compatibility.Version); + command.ExecuteNonQuery(); + _compatibilityCoordinates.Add(new CompatibilityCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType)); + } + + private int GetUntPlatformSqlKey(Platform platform) + { + int result = 0; + if (platform.UpdateFlag.HasValue) + result |= 0x02; + if (platform.DataBroadcastId.HasValue) + result |= 0x04; + if (platform.SuperCaSystemId.HasValue) + result |= 0x20; + if (platform.SerialData != null) + result |= 0x80; + if (platform.MacAddressMatches != null) + result |= 0x40; + if (platform.SubgroupTag != null) + result |= 0x400; + return result; + } + + private struct GroupCompatabilityPlatformCoordinate + { + public byte CommonActionType { get; } + public byte CommonOuiHash { get; } + public string CommonOui { get; } + public byte CommonProcessingOrder { get; } + public byte CompatibilityDescriptorType { get; } + public int PlatformId { get; } + + public GroupCompatabilityPlatformCoordinate(byte commonActionType, byte commonOuiHash, string commonOui, byte commonProcessingOrder, byte compatibilityDescriptorType, int platformId) + { + CommonActionType = commonActionType; + CommonOuiHash = commonOuiHash; + CommonOui = commonOui; + CommonProcessingOrder = commonProcessingOrder; + CompatibilityDescriptorType = compatibilityDescriptorType; + PlatformId = platformId; + } + } + + private HashSet _platformCoordinates; + + private bool TestForUntCompatPlatform(MySqlTransaction transaction, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + GroupCompatabilityPlatformCoordinate platformCoordinate = new GroupCompatabilityPlatformCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType, GetUntPlatformSqlKey(platform)); + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_unt_compatibility_platform WHERE action_type = @action_type AND oui_hash = @oui_hash AND oui = @oui AND processing_order = @processing_order " + + "AND descriptor_type = @descriptor_type AND platform_type_bitmask = @platform_type_bitmask"; + command.Parameters.AddWithValue("@action_type", common.ActionType); + command.Parameters.AddWithValue("@oui_hash", common.OuiHash); + command.Parameters.AddWithValue("@oui", common.Oui); + command.Parameters.AddWithValue("@processing_order", common.ProcessingOrder); + command.Parameters.AddWithValue("@descriptor_type", compatibility.DescriptorType); + command.Parameters.AddWithValue("@platform_type_bitmask", platformCoordinate.PlatformId); + MySqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _platformCoordinates.Add(platformCoordinate); + dataReader.Close(); + return result; + } + + private void InsertUntCompatPlatform(MySqlTransaction transaction, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + GroupCompatabilityPlatformCoordinate platformCoordinate = new GroupCompatabilityPlatformCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType, GetUntPlatformSqlKey(platform)); + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt_compatibility_platform (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, update_flag, update_method, update_priority, data_broadcast_id, association_tag, private_data, smart_card_number, super_ca_system_id, serial_data, mac_address_mask, subgroup_tag) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @descriptor_type, @platform_type_bitmask, @update_flag, @update_method, @update_priority, @data_broadcast_id, " + + " @association_tag, @private_data, @smart_card_number, @super_ca_system_id, @serial_data, @mac_address_mask, @subgroup_tag)"; + command.Parameters.AddWithValue("@action_type", common.ActionType); + command.Parameters.AddWithValue("@oui_hash", common.OuiHash); + command.Parameters.AddWithValue("@oui", common.Oui); + command.Parameters.AddWithValue("@processing_order", common.ProcessingOrder); + command.Parameters.AddWithValue("@descriptor_type", compatibility.DescriptorType); + command.Parameters.AddWithValue("@platform_type_bitmask", platformCoordinate.PlatformId); + command.Parameters.AddWithValue("@update_flag", platform.UpdateFlag); + command.Parameters.AddWithValue("@update_method", platform.UpdateMethod); + command.Parameters.AddWithValue("@update_priority", platform.UpdatePriority); + command.Parameters.AddWithValue("@data_broadcast_id", platform.DataBroadcastId); + command.Parameters.AddWithValue("@association_tag", platform.AssociationTag); + command.Parameters.AddWithValue("@private_data", platform.PrivateData); + command.Parameters.AddWithValue("@smart_card_number", platform.SmartCardNumber); + command.Parameters.AddWithValue("@super_ca_system_id", platform.SuperCaSystemId); + command.Parameters.AddWithValue("@serial_data", platform.SerialData); + command.Parameters.AddWithValue("@mac_address_mask", platform.MacAddressMask); + command.Parameters.AddWithValue("@subgroup_tag", platform.SubgroupTag); + command.ExecuteNonQuery(); + + InsertUntCompatPlatformMacAddressMatches(transaction, platformCoordinate, platform); + _platformCoordinates.Add(platformCoordinate); + } + + private void InsertUntCompatPlatformMacAddressMatches(MySqlTransaction transaction, GroupCompatabilityPlatformCoordinate platformCoordinate, Platform platform) + { + if (platform.MacAddressMatches == null) + return; + + if (platform.MacAddressMatches.Length == 0) + return; + + MySqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt_compatibility_platform_mac_address_matches (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @descriptor_type, @platform_type_bitmask, @mac_match) "; + command.Parameters.AddWithValue("@action_type", platformCoordinate.CommonActionType); + command.Parameters.AddWithValue("@oui_hash", platformCoordinate.CommonOuiHash); + command.Parameters.AddWithValue("@oui", platformCoordinate.CommonOui); + command.Parameters.AddWithValue("@processing_order", platformCoordinate.CommonProcessingOrder); + command.Parameters.AddWithValue("@descriptor_type", platformCoordinate.CompatibilityDescriptorType); + command.Parameters.AddWithValue("@platform_type_bitmask", platformCoordinate.PlatformId); + command.Parameters.Add("@mac_match", MySqlDbType.VarChar); + + foreach (string match in platform.MacAddressMatches) + { + command.Parameters["@mac_match"].Value = match; + command.ExecuteNonQuery(); + } + } + + public bool TestForUpdateNotification(int hashCode, UpdateNotificationGroup common) + { + throw new NotImplementedException(); + } + + public void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + if (_untGroupCoordinates == null) + _untGroupCoordinates = new HashSet(); + if (_compatibilityCoordinates == null) + _compatibilityCoordinates = new HashSet(); + if (_platformCoordinates == null) + _platformCoordinates = new HashSet(); + + GroupCoordinate groupCoordinate = new GroupCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder); + if (_untGroupCoordinates.Contains(groupCoordinate)) + { + CompatibilityCoordinate compatibilityCoordinate = new CompatibilityCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType); + if (_compatibilityCoordinates.Contains(compatibilityCoordinate)) + { + GroupCompatabilityPlatformCoordinate platformCoordinate = new GroupCompatabilityPlatformCoordinate(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType, GetUntPlatformSqlKey(platform)); + if (_platformCoordinates.Contains(platformCoordinate)) + { + return; + } + } + } + + using (MySqlConnection connection = new MySqlConnection(_mcsb.ToString())) + { + connection.Open(); + MySqlTransaction transaction = connection.BeginTransaction(); + + if (!TestForUnt(transaction, common)) + InsertUnt(transaction, common); + + if (!TestForUntCompat(transaction, common, compatibility)) + InsertUntCompat(transaction, common, compatibility); + + if (!TestForUntCompatPlatform(transaction, common, compatibility, platform)) + InsertUntCompatPlatform(transaction, common, compatibility, platform); + + transaction.Commit(); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/Utilities.cs b/DataTableStorages/skyscraper5.Data.MySql/Utilities.cs new file mode 100644 index 0000000..5d12489 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/Utilities.cs @@ -0,0 +1,51 @@ +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MySqlConnector; + +namespace skyscraper5.Data.MySql +{ + public partial class MySqlDataStorage : DataStorage + { + public void SetNulls(DbCommand command) + { + foreach (DbParameter commandParameter in command.Parameters) + { + if (commandParameter.Value == null) + commandParameter.Value = DBNull.Value; + } + } + } + + public static class MySqlDataReaderExtensions + { + public static byte[] GetByteArray(this MySqlDataReader reader, int column) + { + if (reader.IsDBNull(column)) + return null; + + Stream stream = reader.GetStream(column); + int len32 = (int)stream.Length; + byte[] buffer = new byte[len32]; + stream.Read(buffer, 0, len32); + stream.Close(); + return buffer; + } + } + + public static class MySqlParameterCollectionExtensions + { + public static void AddOrSet(this MySqlParameterCollection collection, string name, T value) + { + if (collection.Contains(name)) + collection[name].Value = value; + else + collection.AddWithValue(name, value); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.MySql/setup.sql b/DataTableStorages/skyscraper5.Data.MySql/setup.sql new file mode 100644 index 0000000..be27159 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/setup.sql @@ -0,0 +1,3395 @@ +create schema skyscraper5 collate utf8mb3_general_ci; + +create table skyscraper_satellites +( + checksum int not null + primary key + unique, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + angle float not null, + east bool not null, + name VARCHAR(32) not null, + lof1 int not null, + lof2 int not null, + lofsw int not null, + minfreq int not null, + maxfreq int not null +); + +create table skyscraper_queued_jobs +( + uuid varchar(36) default UUID() not null + primary key + unique, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + type int not null, + i_arg1 int null, + s_arg1 VARCHAR(255) null +); + +alter table skyscraper_queued_jobs + add done bool default false not null; + +create table skyscraper_imported_streams +( + filename mediumtext not null + primary key + unique, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null +); + +create table dvb_sdt +( + tsid int not null, + onid int not null, + service_id int not null, + eit_schedule bool not null, + eit_schedule_following bool not null, + free_ca_mode bool not null, + service_name varchar(128) null, + service_provider_name varchar(128) null, + service_type int null, + private_data_specifier bigint null, + constraint dvb_sdt_pk + primary key (tsid, onid, service_id) +); + +create table dvb_sdt_ca_identifiers +( + tsid int not null, + onid int not null, + service_id int not null, + ordinal int not null, + ca_identifier int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null +); + +alter table dvb_sdt + add component_tag int null; + +alter table dvb_sdt + add iso_639_language_code varchar(3) null; + +alter table dvb_sdt + add component_type int null; + +alter table dvb_sdt + add stream_context int null; + +alter table dvb_sdt + add stream_context_ext int null; + +alter table dvb_sdt + add text varchar(100) null; + +alter table dvb_sdt + add data_broadcast_id int null; + +alter table dvb_sdt + add selector tinyblob null; + +alter table dvb_sdt_ca_identifiers + drop column ordinal; + +alter table dvb_sdt_ca_identifiers + add constraint dvb_sdt_ca_identifiers_pk + primary key (tsid, onid, service_id, ca_identifier); + +alter table dvb_sdt_ca_identifiers + add constraint dvb_sdt_ca_identifiers_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id); + +create table dvb_sdt_country_availability +( + tsid int not null, + onid int not null, + service_id int not null, + country VARCHAR(3) not null, + availability bool not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_country_availability_pk + primary key (tsid, onid, service_id, country), + constraint dvb_sdt_country_availability_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +create table dvb_sdt_linkages +( + tsid int not null, + onid int not null, + service_id int not null, + linkage_type int not null, + handover_type int null, + handover_origin_type bool null, + handover_network_id int null, + handover_initial_service_id int null, + target_event_id int null, + target_event_listed bool null, + target_event_simulcasted bool null, + constraint dvb_sdt_linkages_pk + primary key (tsid, onid, service_id, linkage_type), + constraint dvb_sdt_linkages_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +create table dvb_sdt_linkages_extended_event_linkages +( + tsid int not null, + onid int not null, + service_id int not null, + target_event int not null, + target_listed bool not null, + event_simulcast bool not null, + link_type int not null, + user_defined_id int null, + target_tsid int null, + target_onid int null, + target_service_id int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_linkages_extended_event_linkages_pk + primary key (tsid, onid, service_id, target_event), + constraint dvb_sdt_linkages_extended_event_linkages_dvb_sdt_tsid_onid_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +create table dvb_sdt_ssu +( + tsid int not null, + onid int not null, + service_id int not null, + oui varchar(8) not null, + selector tinyblob not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_ssu_pk + primary key (tsid, onid, service_id, oui), + constraint dvb_sdt_ssu_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +alter table dvb_sdt + add table_type smallint null; + +alter table dvb_sdt + add bouquet_id int null; + +alter table dvb_sdt + add private_data_bytes tinyblob null; + +alter table dvb_sdt + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +alter table dvb_sdt_linkages + add table_type int null; + +alter table dvb_sdt_linkages + add private_data_bytes TINYBLOB null; + +create table dvb_sdt_ipmac +( + tsid int not null, + onid int not null, + service_id int not null, + id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_ipmac_pk + primary key (tsid, onid, service_id, id), + constraint dvb_sdt_ipmac_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +create table dvb_sdt_ipmac_names +( + tsid int not null, + onid int not null, + service_id int not null, + id int not null, + lang VARCHAR(3) not null, + name VARCHAR(128) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_ipmac_names_pk + primary key (tsid, onid, service_id, id, lang), + constraint dvb_sdt_ipmac_names_dvb_sdt_ipmac_tsid_onid_service_id_id_fk + foreign key (tsid, onid, service_id, id) references dvb_sdt_ipmac (tsid, onid, service_id, id) +); + +rename table dvb_sdt_ssu to dvb_sdt_linkages_ssu + +rename table dvb_sdt_ipmac_names to dvb_sdt_ssu_ipmac_names; + +rename table dvb_sdt_ipmac to dvb_sdt_ssu_ipmac; + +rename table dvb_sdt_ssu_ipmac to dvb_sdt_linkages_ipmac; + +rename table dvb_sdt_ssu_ipmac_names to dvb_sdt_linkages_ipmac_names; + +alter table dvb_sdt + add reference_service_id int null after private_data_bytes; + +create table dvb_sdt_nvod_references +( + tsid int not null, + onid int not null, + service_id int not null, + other_tsid int not null, + other_onid int not null, + other_service_id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_nvod_references_pk + primary key (tsid, onid, service_id, other_tsid, other_onid, other_service_id), + constraint dvb_sdt_nvod_references_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +alter table dvb_sdt + add default_authority VARCHAR(100) null after reference_service_id; + +create table dvb_sdt_multilingual_service_name +( + tsid int not null, + onid int not null, + service_id int not null, + language_code VARCHAR(3) not null, + service_provider_name VARCHAR(100) not null, + service_name VARCHAR(100) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_sdt_multilingual_service_name_pk + primary key (tsid, onid, service_id, language_code), + constraint dvb_sdt_multilingual_service_name_dvb_sdt_tsid_onid_service_fk + foreign key (tsid, onid, service_id) references dvb_sdt (tsid, onid, service_id) +); + +alter table dvb_sdt + add control_remote_access_over_internet int null after default_authority; + +alter table dvb_sdt + add do_not_apply_revocation bool null after control_remote_access_over_internet; + +alter table dvb_sdt + add do_not_scramble bool null after do_not_apply_revocation; + +alter table dvb_sdt + add old_onid int null after do_not_scramble; + +alter table dvb_sdt + add old_service_id int null after old_onid; + +alter table dvb_sdt + add old_tsid int null after old_service_id; + +alter table dvb_sdt + drop column table_type; + +alter table dvb_sdt + drop column bouquet_id; + +alter table dvb_sdt + drop column private_data_bytes; + +alter table dvb_sdt + add running_status int not null after eit_schedule_following; + +alter table dvb_sdt + add num_updates bigint default 0 not null; + +alter table dvb_sdt + add dateupdated TIMESTAMP null; + +rename table dvb_sdt_linkages to dvb_linkages; + +rename table dvb_sdt_linkages_extended_event_linkages to dvb_linkages_extended_event_linkages; + +rename table dvb_sdt_linkages_ipmac to dvb_linkages_ipmac; + +rename table dvb_sdt_linkages_ipmac_names to dvb_linkages_ipmac_names; + +rename table dvb_sdt_linkages_ssu to dvb_linkages_ssu; + +alter table dvb_linkages + add uuid varchar(37) not null first; + +alter table dvb_linkages + drop foreign key dvb_sdt_linkages_dvb_sdt_tsid_onid_service_id_fk + +alter table dvb_linkages + drop primary key; + +alter table dvb_linkages + drop column tsid; + +alter table dvb_linkages + drop column onid; + +alter table dvb_linkages + drop column service_id; + +alter table dvb_linkages + add constraint dvb_linkages_pk + primary key (uuid, linkage_type); + +alter table dvb_linkages_extended_event_linkages + drop foreign key dvb_sdt_linkages_extended_event_linkages_dvb_sdt_tsid_onid_fk; + +alter table dvb_linkages_extended_event_linkages + add uuid varchar(37) not null first; + +alter table dvb_linkages_extended_event_linkages + drop primary key; + +alter table dvb_linkages_extended_event_linkages + drop column tsid; + +alter table dvb_linkages_extended_event_linkages + drop column onid; + +alter table dvb_linkages_extended_event_linkages + drop column service_id; + +alter table dvb_linkages_extended_event_linkages + add constraint dvb_linkages_extended_event_linkages_pk + primary key (uuid, target_event); + +alter table dvb_linkages_ipmac + drop foreign key dvb_sdt_ipmac_dvb_sdt_tsid_onid_service_id_fk; + +alter table dvb_linkages_ipmac + add uuid varchar(37) not null first; + +alter table dvb_linkages_ipmac + drop primary key; + +alter table dvb_linkages_ipmac_names + drop foreign key dvb_sdt_ipmac_names_dvb_sdt_ipmac_tsid_onid_service_id_id_fk; + +alter table dvb_linkages_ipmac + drop primary key; + +alter table dvb_linkages_ipmac + drop column tsid; + +alter table dvb_linkages_ipmac + drop column onid; + +alter table dvb_linkages_ipmac + drop column service_id; + +alter table dvb_linkages_ipmac + add primary key (uuid, id); + +alter table dvb_linkages_ipmac + add constraint dvb_linkages_ipmac_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +alter table dvb_linkages_extended_event_linkages + add constraint dvb_linkages_extended_event_linkages_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +alter table dvb_linkages_ipmac_names + drop primary key; + +alter table dvb_linkages_ipmac_names + drop column tsid; + +alter table dvb_linkages_ipmac_names + add uuid varchar(37) not null first; + +alter table dvb_linkages_ipmac_names + drop column onid; + +alter table dvb_linkages_ipmac_names + drop column service_id; + +alter table dvb_linkages_ipmac_names + add constraint dvb_linkages_ipmac_names_pk + primary key (uuid, id); + +alter table dvb_linkages_ipmac_names + add constraint dvb_linkages_ipmac_names_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +alter table dvb_linkages_ssu + drop foreign key dvb_sdt_ssu_dvb_sdt_tsid_onid_service_id_fk; + +alter table dvb_linkages_ssu + add uuid varchar(37) not null first; + +alter table dvb_linkages_ssu + drop primary key; + +alter table dvb_linkages_ssu + drop column tsid; + +alter table dvb_linkages_ssu + drop column onid; + +alter table dvb_linkages_ssu + drop column service_id; + +alter table dvb_linkages_ssu + add constraint dvb_linkages_ssu_pk + primary key (uuid, oui); + +alter table dvb_linkages_ssu + add constraint dvb_linkages_ssu_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +alter table dvb_sdt + add uuid varchar(37) null after old_tsid; + +create table dvb_nit +( + id int not null + primary key +); + +alter table dvb_nit + add name VARCHAR(100) null; + +alter table dvb_nit + add uuid varchar(37) null; + +alter table dvb_nit + add private_data_specifier_id bigint null; + +create table dvb_nit_cells +( + nid_id int not null, + cell_id int not null, + cell_longitude int not null, + cell_latitude int not null, + extend_of_latitude int not null, + extend_of_longitude int not null, + constraint dvb_nit_cells_pk + primary key (nid_id, cell_id), + constraint dvb_nit_cells_dvb_nit_id_fk + foreign key (nid_id) references dvb_nit (id) +); + +alter table dvb_nit_cells + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_nit_cells_subcells +( + nid int not null, + cell_id int not null, + cell_id_extension smallint not null, + subcell_latitude int not null, + subcell_longitude int not null, + subcell_extent_of_latitude int not null, + subcell_extent_of_longitude int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_cells_subcells_pk + primary key (nid, cell_id, cell_id_extension), + constraint dvb_nit_cells_subcells_dvb_nit_cells_nid_id_cell_id_fk + foreign key (nid, cell_id) references dvb_nit_cells (nid_id, cell_id) +); + +alter table dvb_nit + add xait_pid int null; + +create table dvb_nit_multilingual_name +( + id int not null, + language varchar(3) not null, + name varchar(100) not null, + dateadded timestamp default current_timestamp not null, + constraint dvb_nit_multilingual_name_pk + primary key (id, language), + constraint dvb_nit_multilingual_name_dvb_nit_id_fk + foreign key (id) references dvb_nit (id) +); + +create table dvb_nit_region_names +( + id int not null, + name VARCHAR(100) not null, + primary_code smallint not null, + secondary_code smallint null, + teritary_code int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_region_names_pk + primary key (id, name), + constraint dvb_nit_region_names_dvb_nit_id_fk + foreign key (id) references dvb_nit (id) +); + +alter table dvb_nit + add region_name_country_code varchar(3) null; + +alter table dvb_nit + add region_name_language_code varchar(3) null; + +create table dvb_nit_regions +( + id int not null, + country_code varchar(3) not null, + primary_region_code smallint null, + secondary_region_code smallint null, + teritary_region_code int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_regions_pk + primary key (id, country_code), + constraint dvb_nit_regions_dvb_nit_id_fk + foreign key (id) references dvb_nit (id) +); + +alter table dvb_nit + add region_country_code VARCHAR(3) null; + +create table dvb_nit_messages +( + network_id int not null, + message_id int not null, + language_code VARCHAR(3) not null, + message VARCHAR(100) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_messages_pk + primary key (network_id, message_id), + constraint dvb_nit_messages_dvb_nit_id_fk + foreign key (network_id) references dvb_nit (id) +); + +alter table dvb_nit + add min_polling_interval int null; + +alter table dvb_nit + add uri varchar(100) null; + +alter table dvb_nit + add uri_linkage_type int null; + +create table dvb_nit_services +( + network_id int not null, + service_id int not null, + service_type int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_services_pk + primary key (network_id, service_id), + constraint dvb_nit_services_dvb_nit_id_fk + foreign key (network_id) references dvb_nit (id) +); + +alter table dvb_nit + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_nit_transport_stream +( + nid int not null, + tsid int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_pk + primary key (nid, tsid), + constraint dvb_nit_transport_stream_dvb_nit_id_fk + foreign key (nid) references dvb_nit (id) +); + +alter table dvb_nit_transport_stream + add onid int not null; + +alter table dvb_nit_transport_stream + add delivery_method int not null; + +alter table dvb_nit_transport_stream + add east bool null; + +alter table dvb_nit_transport_stream + add fec_inner int null; + +alter table dvb_nit_transport_stream + add frequency bigint null; + +alter table dvb_nit_transport_stream + add orbital_position float null; + +alter table dvb_nit_transport_stream + add polarization int null; + +alter table dvb_nit_transport_stream + add roll_off int null; + +alter table dvb_nit_transport_stream + add s2 bool null; + +alter table dvb_nit_transport_stream + add symbol_rate bigint null; + +alter table dvb_nit_transport_stream + add scrambling_sequence_index int null; + +alter table dvb_nit_transport_stream + add input_stream_identifier smallint null; + +alter table dvb_nit_transport_stream + add timeslice_number smallint null; + +alter table dvb_nit_transport_stream + add ts_gs_mode int null; + +alter table dvb_nit_transport_stream + add private_data_specifier_id bigint null; + +alter table dvb_nit_transport_stream + add tfs_flag bool null; + +alter table dvb_nit_transport_stream + add bandwidth int null; + +alter table dvb_nit_transport_stream + add guard_interval int null; + +alter table dvb_nit_transport_stream + add other_frequency_flag bool null; + +alter table dvb_nit_transport_stream + add plp_id smallint null; + +alter table dvb_nit_transport_stream + add siso_miso int null; + +alter table dvb_nit_transport_stream + add t2_system_id int null; + +alter table dvb_nit_transport_stream + add transmission_mode int null; + +alter table dvb_nit_transport_stream + add coding_type int null; + +alter table dvb_nit_transport_stream + add modulation_type int null; + +alter table dvb_nit_transport_stream + add fec_outer int null; + +alter table dvb_nit_transport_stream + add code_rate_hp_stream int null; + +alter table dvb_nit_transport_stream + add code_rate_lp_stream int null; + +alter table dvb_nit_transport_stream + add hierarchy_information int null; + +alter table dvb_nit_transport_stream + add mpe_fec_indicator bool null; + +alter table dvb_nit_transport_stream + add priority bool null; + +alter table dvb_nit_transport_stream + add time_slicing_indicator bool null; + +alter table dvb_nit_transport_stream + add network_name varchar(100) null; + +alter table dvb_nit_transport_stream + add target_region_country_code varchar(3) null; + +create table dvb_nit_transport_stream_services +( + nid int null, + tsid int null, + service_id int null, + service_type int null, + date_added TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_services_pk + primary key (nid, tsid, service_id), + constraint dvb_nit_transport_stream_services_dvb_nit_transport_stream_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +alter table dvb_nit_transport_stream + add uuid varchar(37) null; + +create table dvb_nit_transport_stream_lcn +( + nid int not null, + tsid int not null, + service_id int not null, + lcn int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_lcn_pk + primary key (nid, tsid, service_id), + constraint dvb_nit_transport_stream_lcn_dvb_nit_transport_stream_nid_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +create table dvb_nit_transport_stream_cell_infos +( + nid int not null, + tsid int not null, + cell_id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_cell_infos_pk + primary key (nid, tsid, cell_id), + constraint dvb_nit_transport_stream_cell_infos_dvb_nit_transport_stream_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +create table dvb_nit_transport_stream_cell_infos_center_frequencies +( + nid int not null, + tsid int not null, + cell_id int not null, + ordinal int not null, + frequency bigint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_cell_infos_center_frequencies_pk + primary key (tsid, nid, cell_id, ordinal), + constraint dvb_nit_transport_stream_cell_infos_center_frequencies_dvb_fk + foreign key (nid, tsid, cell_id) references dvb_nit_transport_stream_cell_infos (nid, tsid, cell_id) +); + +drop table dvb_nit_transport_stream_lcn; + +create table dvb_nit_transport_stream_cell_infos_subcell_infos +( + nid int not null, + tsid int not null, + cell_id int not null, + ordinal int not null, + cell_id_extension smallint not null, + transposer_frequency bigint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_cell_infos_subcell_infos_pk + primary key (tsid, nid, cell_id, ordinal), + constraint dvb_nit_transport_stream_cell_infos_subcell_infos_dvb_nit_fk + foreign key (nid, tsid, cell_id) references dvb_nit_transport_stream_cell_infos (nid, tsid, cell_id) +); + +create table dvb_nit_transport_stream_centre_frequencies +( + nid int not null, + tsid int not null, + ordinal int not null, + frequency bigint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_centre_frequencies_pk + primary key (tsid, nid, ordinal), + constraint dvb_nit_transport_stream_centre_frequencies_dvb_nit_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +create table dvb_nit_transport_stream_cells +( + nid int not null, + tsid int not null, + cell_id int not null, + cell_lat int not null, + cell_lon int not null, + extent_lat bigint not null, + extent_lon bigint not null, + date_added TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_cells_pk + primary key (nid, tsid, cell_id), + constraint dvb_nit_transport_stream_cells_dvb_nit_transport_stream_nid_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +create table dvb_nit_transport_streams_cells_subcells +( + nid int not null, + tsid int not null, + cell_id int not null, + cell_id_extension int not null, + subcell_lat int not null, + subcell_lon int not null, + subcell_extent_lat int not null, + subcell_extent_lon int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_streams_cells_subcells_pk + primary key (nid, tsid, cell_id, cell_id_extension), + constraint dvb_nit_transport_streams_cells_subcells_dvb_nit_transport_fk + foreign key (nid, tsid, cell_id) references dvb_nit_transport_stream_cells (nid, tsid, cell_id) +); + +create table dvb_nit_transport_stream_target_regions +( + nid int not null, + tsid int not null, + country_code varchar(3) not null, + primary_rc smallint null, + secondary_rc smallint null, + teritary_rc int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_target_regions_pk + primary key (nid, tsid, country_code), + constraint dvb_nit_transport_stream_target_regions_dvb_nit_transport_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +create table dvb_nit_transport_stream_cell_frequencies +( + nid int not null, + tsid int not null, + cell_id int not null, + frequency bigint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_nit_transport_stream_cell_frequencies_pk + primary key (nid, tsid, cell_id), + constraint dvb_nit_transport_stream_cell_frequencies_dvb_nit_transport_fk + foreign key (nid, tsid) references dvb_nit_transport_stream (nid, tsid) +); + +alter table dvb_nit_transport_stream + modify roll_off float null; + +rename table dvb_nit_transport_streams_cells_subcells to dvb_nit_transport_stream_cells_subcells; + +alter table dvb_nit + add numupdates int default 0 not null; + +alter table dvb_nit + add dateupdated TIMESTAMP null; + +alter table dvb_nit_transport_stream + add numupdates int default 0 not null; + +alter table dvb_nit_transport_stream + add dateupdated TIMESTAMP null; + +create table dvb_cat +( + nid int not null, + tsid int not null, + ca_pid int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_cat_pk + primary key (nid, tsid, ca_pid) +) + +alter table dvb_cat + add ca_system_id int not null after ca_pid; + +alter table dvb_cat + add private_data tinyblob null after ca_system_id; + +create table dvb_pmt +( + nid int not null, + tsid int not null, + program_number int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pmt_pk + primary key (nid, tsid, program_number) +); + +alter table dvb_pmt + add pcr_pid int not null after program_number; + +create table dvb_pmt_streams +( + onid int not null, + tsid int not null, + program_number int not null, + stream_type int not null, + elementary_pid int not null, + component_tag smallint null, + audio_type int null, + iso_639_language_code VARCHAR(3) null, + constraint dvb_pmt_streams_pk + primary key (onid, tsid, program_number, elementary_pid), + constraint dvb_pmt_streams_dvb_pmt_nid_tsid_program_number_fk + foreign key (onid, tsid, program_number) references dvb_pmt (nid, tsid, program_number) +); + +create table dvb_pmt_streams_teletext +( + onid int not null, + tsid int not null, + elementary_pid int not null, + iso_639_language_code VARCHAR(3) not null, + teletext_type int not null, + teletext_magazine_number int not null, + teletext_page_number int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pmt_streams_teletext_pk + primary key (onid, tsid, elementary_pid, teletext_page_number) +); + +create index dvb_pmt_streams_onid_tsid_elementary_pid_index + on dvb_pmt_streams (onid, tsid, elementary_pid); + +alter table dvb_pmt_streams_teletext + add constraint dvb_pmt_streams_teletext_dvb_pmt_streams_onid_tsid_elementary_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid); + +create table dvb_pmt_streams_applications +( + onid int not null, + tsid int not null, + elementary_pid int not null, + application_type int not null, + ait_version_number int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pmt_streams_applications_pk + primary key (onid, tsid, elementary_pid), + constraint dvb_pmt_streams_applications_dvb_pmt_streams_onid_tsid_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid) +); + +alter table dvb_pmt_streams + add carousel_format_id smallint null; + +alter table dvb_pmt_streams + add carousel_id bigint null; + +alter table dvb_pmt_streams + add data_broadcast_id int null; + +alter table dvb_pmt_streams + add data_broadcast_selector tinyblob null; + +create table dvb_pmt_streams_ip +( + onid int not null, + tsid int not null, + elementary_pid int not null, + platform_id bigint not null, + action_type tinyint not null, + int_versioning_flag bool not null, + int_version int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pmt_streams_ip_pk + primary key (onid, tsid, elementary_pid, platform_id), + constraint dvb_pmt_streams_ip_dvb_pmt_streams_onid_tsid_elementary_pid_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid) +); + +alter table dvb_pmt_streams + add supplementary_independant_stream bool null; + +alter table dvb_pmt_streams + add editorial_classification int null; + +alter table dvb_pmt_streams + add supplementary_language_code varchar(3) null; + +alter table dvb_pmt_streams + add asvc tinyint null; + +alter table dvb_pmt_streams + add bsid int null; + +alter table dvb_pmt_streams + add component_type int null; + +alter table dvb_pmt_streams + add main_id tinyint null; + +alter table dvb_pmt_streams + add frame_rate double null; + +alter table dvb_pmt_streams + add mpeg1_only_flag bool null; + +alter table dvb_pmt_streams + add chroma_format int null; + +alter table dvb_pmt_streams + add constrained_parameter_flag bool null; + +alter table dvb_pmt_streams + add frame_rate_extension_flag int null; + +alter table dvb_pmt_streams + add multiple_framerate_flag bool null; + +alter table dvb_pmt_streams + add profile_and_level_indicator tinyint null; + +alter table dvb_pmt_streams + add still_picture_flag bool null; + +alter table dvb_pmt_streams + add alignment_type tinyint null; + +alter table dvb_pmt_streams + add maximum_bitrate bigint null; + +alter table dvb_pmt_streams + add audio_stream_id bool null; + +alter table dvb_pmt_streams + add free_format bool null; + +alter table dvb_pmt_streams + add layer int null; + +alter table dvb_pmt_streams + add variable_rate_audio bool null; + +alter table dvb_pmt_streams + add constraint_set0_flag bool null; + +alter table dvb_pmt_streams + add avc_24hour_picture_flag bool null; + +alter table dvb_pmt_streams + add avc_compatible_flags int null; + +alter table dvb_pmt_streams + add avc_still_present bool null; + +alter table dvb_pmt_streams + add constraint_set1_flag bool null; + +alter table dvb_pmt_streams + add constraint_set2_flag bool null; + +alter table dvb_pmt_streams + add constraint_set3_flag bool null; + +alter table dvb_pmt_streams + add constraint_set4_flag bool null; + +alter table dvb_pmt_streams + add constraint_set5_flag bool null; + +alter table dvb_pmt_streams + add frame_packing_sei_not_present bool not null; + +alter table dvb_pmt_streams + add level_idc tinyint null; + +alter table dvb_pmt_streams + add profile_rdc tinyint null; + +alter table dvb_pmt_streams + add ca_pid int null; + +alter table dvb_pmt_streams + add ca_system_id int null; + +alter table dvb_pmt_streams + add ca_private_data tinyblob null; + +alter table dvb_pmt_streams + add private_data_specifier bigint null; + +create table dvb_pmt_streams_subtitle +( + onid int not null, + tsid int not null, + elementary_pid int not null, + ordinal int not null, + iso_639_language_type varchar(3) not null, + subtitling_type tinyint not null, + composition_page_id int not null, + ancillary_page_id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP null, + constraint dvb_pmt_streams_subtitle_pk + primary key (onid, tsid, elementary_pid, ordinal), + constraint dvb_pmt_streams_subtitle_dvb_pmt_streams_onid_tsid_elementary_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid) +); + +alter table dvb_pmt_streams + add format_identifier bigint null; + +alter table dvb_pmt_streams + add additional_identification_info tinyblob null; + +alter table dvb_pmt_streams + add association_tag_selector tinyblob null; + +alter table dvb_pmt_streams + add association_tag_transaction_id bigint null; + +alter table dvb_pmt_streams + add association_tag_use int null; + +alter table dvb_pmt_streams + add association_tag_private_data tinyblob null; + +alter table dvb_pmt_streams + add association_tag_timeout bigint null; + +create table dvb_pmt_streams_vbi +( + onid int not null, + tsid int not null, + elementary_pid int not null, + field_parity bool not null, + line_offset int not null, + data_service_id tinyint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP null, + constraint dvb_pmt_streams_vbi_pk + primary key (onid, tsid, elementary_pid, field_parity, line_offset), + constraint dvb_pmt_streams_vbi_dvb_pmt_streams_onid_tsid_elementary_pid_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid) +); + +alter table dvb_pmt_streams + add aac_type tinyint null; + +alter table dvb_pmt_streams + add aac_additional_info tinyblob null; + +alter table dvb_pmt_streams + add aac_profile_and_level tinyint null; + +alter table dvb_pmt_streams + add saoc_de_flag bool null; + +alter table dvb_pmt_streams + add ancillary_data_identifier smallint null; + +alter table dvb_pmt_streams + add mix_info_exists bool null; + +alter table dvb_pmt_streams + add substream1 tinyint null; + +alter table dvb_pmt_streams + add substream2 tinyint null; + +alter table dvb_pmt_streams + add substream3 tinyint null; + +alter table dvb_pmt_streams + add copied_44bits bigint null; + +alter table dvb_pmt_streams + add frame_only_constraint_flag bool null; + +alter table dvb_pmt_streams + add hdr_wcg_idc int null; + +alter table dvb_pmt_streams + add hevc_24hr_picture_present_flag bool null; + +alter table dvb_pmt_streams + add hevc_still_present_flag bool null; + +alter table dvb_pmt_streams + add interlaced_source_flag bool null; + +alter table dvb_pmt_streams + add non_packed_constraint_flag bool null; + +alter table dvb_pmt_streams + add profile_compatibility_indication bigint null; + +alter table dvb_pmt_streams + add profile_idc int null; + +alter table dvb_pmt_streams + add profile_space int null; + +alter table dvb_pmt_streams + add progressive_source_flag bool null; + +alter table dvb_pmt_streams + add sub_pic_hrd_params_not_present_flag bool null; + +alter table dvb_pmt_streams + add temporal_id_max int null; + +alter table dvb_pmt_streams + add tier_flag bool null; + +alter table dvb_pmt_streams + add temporal_id_min int null; + +alter table dvb_pmt_streams + add ac4_channel_mode int null; + +alter table dvb_pmt_streams + add ac4_dialog_enhancement bool null; + +alter table dvb_pmt_streams + add ac4_dsi tinyblob null; + +create table dvb_pmt_streams_audio_preselection +( + onid int not null, + tsid int not null, + elementary_pid int not null, + preselection_id int not null, + audio_rendering_indication int not null, + audio_description bool not null, + spoken_subtitles bool not null, + dialogue_enhancement int not null, + interactivity_enabled bool not null, + iso_639_language_code varchar(3) not null, + message_id tinyint not null, + component_tags tinyblob not null, + future_extension tinyblob not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pmt_streams_audio_preselection_pk + primary key (onid, tsid, elementary_pid, preselection_id), + constraint dvb_pmt_streams_audio_preselection_dvb_pmt_streams_onid_tsid_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid) +); + +alter table dvb_pmt_streams + add leak_valid_flag bool null + +create table dvb_pmt_streams_flexmux +( + nid int not null, + tsid int not null, + elementary_pid int not null, + es_id int not null, + flexmux_channel tinyint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pmt_streams_flexmux_pk + primary key (nid, tsid, elementary_pid, es_id), + constraint dvb_pmt_streams_flexmux_dvb_pmt_streams_onid_tsid_elementary_fk + foreign key (nid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid) +); + +alter table dvb_pmt_streams + add sb_leak_rate bigint null; + +alter table dvb_pmt_streams + add sb_size bigint not null; + +alter table dvb_pmt_streams + add private_data_indicator bigint null; + +alter table dvb_pmt_streams + add _90khz_flag bool null; + +alter table dvb_pmt_streams + add picture_and_timing_info_present_flag bool null; + +alter table dvb_pmt_streams + add hdr_management_valid_flag bool null; + +alter table dvb_pmt_streams + add k bigint null; + +alter table dvb_pmt_streams + add n bigint null; + +alter table dvb_pmt_streams + add num_units_in_tick bigint null; + +alter table dvb_pmt_streams + add target_schedule_idx int null; + +alter table dvb_pmt_streams + add target_schedule_idx_not_present_flag bool null; + +alter table dvb_pmt_streams + add adaption_field_data_identifier tinyint null; + +alter table dvb_pmt_streams + add fixed_frame_rate_flag bool null; + +alter table dvb_pmt_streams + add picture_to_display_conversion_flag bool null; + +alter table dvb_pmt_streams + add temporal_poc_flag bool null; + +alter table dvb_pmt_streams + add mpeg4_audio_profile_and_level tinyint null; + +alter table dvb_pmt_streams + add aac_channel_configuration tinyint null; + +alter table dvb_pmt_streams + add aac_profile tinyint null; + +alter table dvb_pmt_streams + add aac_additional_information tinyint null; + +alter table dvb_pmt_streams + add scrambling_mode tinyint null; + +alter table dvb_pmt_streams + add related_content_descriptor_present bool null; + +alter table dvb_pmt_streams_teletext + add vbi boolean default false not null; + +alter table dvb_pmt_streams + add num_t2mi_streams int null; + +alter table dvb_pmt_streams + add t2mi_stream_id int null; + +alter table dvb_pmt_streams + add pcr_iscr_common_clock_flag bool null; + +alter table dvb_pmt + add maximum_bitrate bigint null; + +alter table dvb_pmt + add multiplex_buffer_utilization_bound_valid_flag bool null; + +alter table dvb_pmt + add multiplex_lfw_offset_lower_bound int null; + +alter table dvb_pmt + add multiplex_ltw_offset_upper_bound int null; + +alter table dvb_pmt + add clock_accuracy_exponent int null; + +alter table dvb_pmt + add clock_accuracy_integer int null; + +alter table dvb_pmt + add external_clock_reference_indicator bool null; + +alter table dvb_pmt + add sb_leak_rate bigint null; + +alter table dvb_pmt + add sb_size bigint null; + +alter table dvb_pmt + add ca_pid int null; + +alter table dvb_pmt + add ca_system_id int null; + +alter table dvb_pmt + add ca_private_data tinyblob null; + +alter table dvb_pmt + add private_data_specifier bigint null; + +alter table dvb_pmt + add registration_additional_identification_info tinyblob null; + +alter table dvb_pmt + add registration_format_identifier bigint null; + +alter table dvb_pmt + add new_original_network_id int null; + +alter table dvb_pmt + add new_service_id int null; + +alter table dvb_pmt + add new_transport_stream_id int null; + +alter table dvb_pmt + add component_tag tinyint null; + +alter table dvb_pmt + add metadata_input_leak_rate int null; + +alter table dvb_pmt + add metadata_buffer_size int null; + +alter table dvb_pmt + add metadata_output_leak_rate int null; + +alter table dvb_pmt + add scrambling_mode tinyint null; + +alter table dvb_pmt + add es_id int null; + +alter table dvb_pmt + add audio_type int null; + +alter table dvb_pmt + add iso_639_language_code varchar(3) null; + +alter table dvb_pmt + add iod_graphics_profile_level_indication tinyint null; + +alter table dvb_pmt + add iod_visual_profile_level_indication tinyint null; + +alter table dvb_pmt + add iod_audio_profile_level_indication tinyint null; + +alter table dvb_pmt + add iod_scene_profile_level_indication tinyint null; + +alter table dvb_pmt + add iod_od_profile_level_indication tinyint null; + +alter table dvb_pmt + add iod_url_string varchar(200) null; + +alter table dvb_pmt + add iod_include_inline_profile_level_flag bool null; + +alter table dvb_pmt + add iod_url_flag bool null; + +alter table dvb_pmt + add iod_object_descriptor_id int null; + +alter table dvb_pmt + add scope_of_iod_label tinyint null; + +alter table dvb_pmt + add iod_label tinyint null; + +alter table dvb_pmt + add private_data_indicator bigint null; + +alter table dvb_pmt + add frame_Rate double null; + +alter table dvb_pmt + add chroma_format int null; + +alter table dvb_pmt + add constrained_parameter_flag bool null; + +alter table dvb_pmt + add frame_rate_extension_flag int null; + +alter table dvb_pmt + add mpeg1_only_flag bool null; + +alter table dvb_pmt + add multiple_framerate_flag bool null; + +alter table dvb_pmt + add profile_and_level_indication tinyint null; + +alter table dvb_pmt + add still_picture_flag bool null; + +alter table dvb_pmt + add metadata_program_number int null; + +alter table dvb_pmt + add metadata_application_format int null; + +alter table dvb_pmt + add metadata_application_format_identifier bigint null; + +alter table dvb_pmt + add metadata_format int null; + +alter table dvb_pmt + add metadata_format_identifier bigint null; + +alter table dvb_pmt + add metadata_locator_record tinyblob null; + +alter table dvb_pmt + add metadata_locator_record_flag bool null; + +alter table dvb_pmt + add metadata_service_id tinyblob null; + +alter table dvb_pmt + add mpeg_carriage_flag int null; + +alter table dvb_pmt + add metadata_private_data tinyblob null; + +alter table dvb_pmt + add transport_stream_id int null; + +alter table dvb_pmt + add transport_stream_location int null; + +alter table dvb_pmt + add free_format bool null; + +alter table dvb_pmt + add audio_stream_id bool null; + +alter table dvb_pmt + add layer int null; + +alter table dvb_pmt + add variable_rate_audio bool null; + +alter table dvb_pmt + add copy_control_private_data tinyblob null; + +alter table dvb_pmt + add alignment_type tinyint null; + +alter table dvb_pmt + add hierarchy_type int null; + +alter table dvb_pmt + add hierarchy_layer_type int null; + +alter table dvb_pmt + add tref_present_flag bool null; + +alter table dvb_pmt + add hierarchy_channel int null; + +alter table dvb_pmt + add no_view_scalability_flag bool null; + +alter table dvb_pmt + add hierarchy_embedded_layer_index int null; + +alter table dvb_pmt + add no_quality_scalability_flag bool null; + +alter table dvb_pmt + add no_spatial_scalability_flag bool null; + +alter table dvb_pmt + add no_temporal_scalability_flag bool null; + +alter table dvb_pmt_streams + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +alter table dvb_pmt_streams + modify frame_packing_sei_not_present tinyint(1) null; + +alter table dvb_pmt_streams + modify level_idc smallint null; + +alter table dvb_pmt_streams + modify sb_size bigint null; + +create table dvb_pat +( + cnid int not null, + ctsid int not null, + pmt_pid int not null, + program_id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_pat_pk + primary key (cnid, ctsid, program_id) +); + +create table dvb_tdt +( + cnid int not null, + ctid int not null, + utc TIMESTAMP not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + dateupdated TIMESTAMP null, + num_updates bigint default 0 not null, + constraint dvb_tdt_pk + primary key (cnid, ctid) +); + +alter table dvb_tdt + modify utc timestamp null on update current_timestamp() + +create table dvb_eit +( + onid int not null, + tsid int not null, + service_id int not null, + event_id int not null, + start_time TIMESTAMP not null, + duration int not null, + running_status int not null, + free_ca bool not null, + constraint dvb_eit_pk + primary key (onid, tsid, service_id, start_time) +); + +alter table dvb_eit + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +alter table dvb_eit + modify start_time timestamp default '0000-00-00 00:00:00' null on update current_timestamp(); + +alter table dvb_eit + add iso_639_language_code varchar(3) null; + +alter table dvb_eit + add event_name varchar(128) null; + +alter table dvb_eit + add text mediumtext null; + +create table dvb_eit_event_items +( + onid int not null, + tsid int not null, + service_id int not null, + start_time TIMESTAMP default '0000-00-00 00:00:00' not null, + ordinal int not null, + description varchar(128) not null, + item mediumtext not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_eit_event_items_pk + primary key (onid, tsid, service_id, start_time, ordinal), + constraint dvb_eit_event_items_dvb_eit_onid_tsid_service_id_start_time_fk + foreign key (onid, tsid, service_id, start_time) references dvb_eit (onid, tsid, service_id, start_time) +); + +alter table dvb_eit + add extended_text mediumtext null; + +alter table dvb_eit + add pdc timestamp null; + +create table dvb_eit_component +( + onid int not null, + tsid int not null, + service_id int not null, + start_time timestamp default '0000-00-00 00:00:00' not null, + text varchar(127) not null, + iso_639_langauge_code varchar(3) not null, + component_tag smallint not null, + component_type smallint not null, + stream_content int not null, + stream_content_ext int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP null, + constraint dvb_eit_pk + primary key (onid, tsid, service_id, start_time, component_tag), + constraint dvb_eit_component_dvb_eit_onid_tsid_service_id_start_time_fk + foreign key (onid, tsid, service_id, start_time) references dvb_eit (onid, tsid, service_id, start_time) +); + +create table dvb_eit_content +( + onid int not null, + tsid int not null, + service_id int not null, + start_time timestamp default '0000-00-00 00:00:00' not null, + l int not null, + m int not null, + r tinyint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP null, + constraint dvb_eit_content_pk + primary key (onid, tsid, service_id, start_time, l, m, r), + constraint dvb_eit_content_dvb_eit_onid_tsid_service_id_start_time_fk + foreign key (onid, tsid, service_id, start_time) references dvb_eit (onid, tsid, service_id, start_time) +); + +alter table dvb_eit + add private_data_specifier bigint null; + +alter table dvb_eit + add vps varchar(6) null; + +create table dvb_eit_parental_ratings +( + onid int not null, + tsid int not null, + service_id int not null, + start_time timestamp default '0000-00-00 00:00:00' not null, + country varchar(3) not null, + age int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_eit_parental_ratings_pk + primary key (onid, tsid, service_id, start_time, country), + constraint dvb_eit_parental_ratings_dvb_eit_onid_tsid_service_id_fk + foreign key (onid, tsid, service_id) references dvb_eit (onid, tsid, service_id) +); + +alter table dvb_eit + add uuid varchar(37) null; + +create table dvb_eit_ca_systems +( + onid int not null, + tsid int not null, + service_id int not null, + start_time timestamp default '0000-00-00 00:00:00' not null, + ca_system int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_eit_ca_systems_pk + primary key (onid, tsid, service_id, start_time, ca_system), + constraint dvb_eit_ca_systems_dvb_eit_onid_tsid_service_id_start_time_fk + foreign key (onid, tsid, service_id, start_time) references dvb_eit (onid, tsid, service_id, start_time) +); + +alter table dvb_eit + add reference_service_id int null; + +alter table dvb_eit + add reference_event_id int null; + +create table dvb_eit_crids +( + onid int not null, + tsid int not null, + service_id int not null, + start_time timestamp default '0000-00-00 00:00:00' not null, + ordinal int not null, + crid_type int not null, + crid_location int not null, + crid_bytes tinyblob not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_eit_crids_pk + primary key (onid, tsid, service_id, start_time, ordinal), + constraint dvb_eit_crids_dvb_eit_onid_tsid_service_id_start_time_fk + foreign key (onid, tsid, service_id, start_time) references dvb_eit (onid, tsid, service_id, start_time) +); + +alter table dvb_eit + add control_remove_access_over_internet int null; + +alter table dvb_eit + add do_not_apply_revocation bool null; + +alter table dvb_eit + add do_not_scramble bool null; + +create table dvb_bat +( + id int not null + primary key, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null +); + +alter table dvb_bat + add name VARCHAR(127) null; + +alter table dvb_bat + add uuid varchar(37) null; + +create table dvb_bat_country_availability +( + id int not null, + country varchar(3) not null, + availability bool not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_bat_country_availability_pk + primary key (id, country), + constraint dvb_bat_country_availability_dvb_bat_id_fk + foreign key (id) references dvb_bat (id) +); + +alter table dvb_bat + add private_dat_specifier bigint null; + +alter table dvb_bat + add uri_linkage_type tinyint null; + +alter table dvb_bat + add url varchar(127) null; + +alter table dvb_bat + add min_polling_interval int null; + +create table dvb_bat_multilingual_bouquet_name +( + id int not null + primary key, + lang varchar(3) not null, + name varchar(127) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_bat_multilingual_bouquet_name_dvb_bat_id_fk + foreign key (id) references dvb_bat (id) +); +alter table dvb_bat + add control_remote_access_over_internet int null; + +alter table dvb_bat + add do_not_apply_revocation bool null; + +alter table dvb_bat + add do_not_scramble bool null; + +create table teletext_page_cache +( + nid int not null, + tsid int not null, + progno int not null, + magazine int not null, + timestamp TIMESTAMP default '0000-00-00 00:00:00' not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint teletext_page_cache_pk + primary key (nid, tsid, progno, magazine, timestamp) +); + +create table dsmcc_blacklist +( + network_id int not null, + transport_stream_id int not null, + pid int not null, + module_id int not null, + module_version smallint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dsmcc_blacklist_pk + primary key (network_id, transport_stream_id, pid, module_id, module_version) +); + +create table dvb_tot +( + nid int not null, + tsid int not null, + utc datetime default '0000-00-00 00:00:00' not null, + countrycode varchar(3) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_tot_pk + primary key (nid, tsid, countrycode) +); + +alter table dvb_tot + add local_time_offset int null; + +alter table dvb_tot + add time_of_change datetime null; + +alter table dvb_tot + add next_time_offset int null; + +alter table dvb_tot + add country_region_id int not null after countrycode; + +alter table dvb_tot + modify dateadded timestamp default current_timestamp() not null after next_time_offset; + +alter table dvb_tot + add local_time_offset_polarity bool not null after country_region_id; + +alter table dvb_tot + modify local_time_offset int not null; + +alter table dvb_tot + modify time_of_change datetime not null; + +alter table dvb_tot + modify next_time_offset int not null; + +create index dvb_eit_onid_tsid_service_id_index + on dvb_eit (onid, tsid, service_id); + +alter table dvb_pmt_streams_teletext + add ordinal int default 0 not null after elementary_pid; + +alter table dvb_pmt_streams_teletext + drop foreign key dvb_pmt_streams_teletext_dvb_pmt_streams_onid_tsid_elementary_fk; + +alter table dvb_pmt_streams_teletext + drop primary key; + +alter table dvb_pmt_streams_teletext + add constraint dvb_pmt_streams_teletext_pk + primary key (onid, tsid, elementary_pid, ordinal); + +alter table dvb_pmt_streams_teletext + add constraint dvb_pmt_streams_teletext_dvb_pmt_streams_onid_tsid_elementary_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid); + +create table dvb_ait +( + org_id int not null, + app_id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_ait_pk + primary key (org_id, app_id) +); + +INSERT INTO dsmcc_blacklist (network_id, transport_stream_id, pid, module_id, module_version) +VALUES +(318,1300,3018,9729,138), +(318,1300,3025,4660,16), +(318,1300,3026,4660,32), +(318,1300,7891,4660,85), +(318,1300,7893,4660,74), +(318,12400,8001,0,1), +(318,12400,8001,1,1), +(318,12400,8001,2,1), +(318,12400,8001,3,1), +(318,12400,8001,4,1), +(318,12400,8001,5,1), +(318,12400,8001,6,1), +(318,12400,8001,7,1), +(318,12400,8001,8,1), +(318,12400,8001,9,1), +(318,12400,8001,10,1), +(318,12400,8001,11,1), +(318,12400,8001,12,1), +(318,12400,8001,13,1), +(318,12400,8001,14,1), +(318,12400,8001,15,1), +(318,12400,8001,16,1), +(318,12400,8001,17,1), +(318,12400,8001,18,1), +(318,12400,8001,19,1), +(318,12400,8001,20,1), +(318,12400,8001,21,1), +(318,12400,8001,22,1), +(318,12400,8001,23,1), +(318,12400,8001,24,1), +(318,12400,8001,25,1), +(318,12400,8001,26,1), +(318,12400,8001,27,1), +(318,12400,8001,28,1), +(318,12400,8001,29,1), +(318,12400,8001,30,1), +(318,12400,8001,31,1), +(318,12400,8001,32,1), +(318,12400,8001,33,1), +(318,12400,8001,34,1), +(318,12400,8001,35,1), +(318,12400,8001,36,1), +(318,12400,8001,37,1), +(318,12400,8001,38,1), +(318,12400,8002,0,1), +(318,12400,8002,1,1), +(318,12400,8002,2,1), +(318,12400,8002,3,1), +(318,12400,8002,4,1), +(318,12400,8002,5,1), +(318,12400,8002,6,1), +(318,12400,8002,7,1), +(318,12400,8002,8,1), +(318,12400,8002,9,1), +(318,12400,8002,10,1), +(318,12400,8002,11,1), +(318,12400,8002,12,1), +(318,12400,8002,13,1), +(318,12400,8002,14,1), +(318,12400,8002,15,1), +(318,12400,8002,16,1), +(318,12400,8002,17,1), +(318,12400,8002,18,1), +(318,12400,8002,19,1), +(318,12400,8002,20,1), +(318,12400,8002,21,1), +(318,12400,8002,22,1), +(318,12400,8002,23,1), +(318,12400,8002,24,1), +(318,12400,8002,25,1), +(318,12400,8002,26,1), +(318,12400,8002,27,1), +(318,12400,8002,28,1), +(318,12400,8002,29,1), +(318,12400,8002,30,1), +(318,12400,8002,31,1), +(318,12400,8002,32,1), +(318,12400,8002,33,1), +(318,12400,8002,34,1), +(318,12400,8002,35,1), +(318,12400,8002,36,1), +(318,12400,8002,37,1), +(318,12400,8002,38,1), +(113,7400,8170,512,0), +(113,7400,8171,0,1), +(113,7400,8172,0,1), +(113,7400,8173,0,1), +(318,11000,3019,4660,121) + +alter table dvb_ait + add app_control_code tinyint not null; + +create table dvb_ait_application_profiles +( + org_id int not null, + app_id int not null, + app_profile int not null, + major tinyint not null, + minor tinyint not null, + micro tinyint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_ait_application_profile_pk + primary key (org_id, app_id, app_profile), + constraint dvb_ait_application_profile_dvb_ait_org_id_app_id_fk + foreign key (org_id, app_id) references dvb_ait (org_id, app_id) +); + +alter table dvb_ait + add app_priority tinyint null; + +alter table dvb_ait + add service_bound_flag bool null; + +alter table dvb_ait + add transport_protocol_label tinyblob null; + +alter table dvb_ait + add visibility int null; + +create table dvb_ait_application_names +( + org_id int not null, + app_id int not null, + lang varchar(3) not null, + name varchar(128) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_ait_application_names_pk + primary key (org_id, app_id, lang), + constraint dvb_ait_application_names_dvb_ait_org_id_app_id_fk + foreign key (org_id, app_id) references dvb_ait (org_id, app_id) +); + +alter table dvb_ait + add initial_path varchar(127) null; + +create table dvb_ait_boundary_extensions +( + org_id int not null, + app_id int not null, + extension varchar(128) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_ait_boundary_extensions_pk + primary key (org_id, app_id, extension), + constraint dvb_ait_boundary_extensions_dvb_ait_org_id_app_id_fk + foreign key (org_id, app_id) references dvb_ait (org_id, app_id) +); + +alter table dvb_ait + add app_usage_code tinyint null; + +alter table dvb_ait + add jvm_arguments mediumtext null; + +alter table dvb_ait + add jvm_base_directory varchar(127) null; + +alter table dvb_ait + add jvm_class_path_extension varchar(127) null; + +alter table dvb_ait + add jvm_initial_class varchar(127) null; + +alter table dvb_ait + add version bigint null; + +alter table dvb_ait + add is_launchable_from_older_version bool null; + +alter table dvb_ait + add launchable_completely_from_cache bool null; + +alter table dvb_ait + add not_launchable_from_broadcast bool null; + +alter table dvb_ait + add application_storage_priority tinyint null; + +alter table dvb_ait + add storage_property tinyint null; + +create table dvb_ait_external_authorization +( + org_id int not null, + app_id int not null, + ext_org_id int not null, + ext_app_id int not null, + app_priority smallint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_ait_external_authorization_pk + primary key (org_id, app_id, ext_org_id, ext_app_id), + constraint dvb_ait_external_authorization_dvb_ait_org_id_app_id_fk + foreign key (org_id, app_id) references dvb_ait (org_id, app_id) +); + +alter table dvb_ait + add auto_select bool null; + +alter table dvb_ait + add service_id bigint null; + +alter table dvb_ait + add service_name varchar(127) null; + +alter table dvb_ait + add version_number bigint null; + +alter table dvb_ait + add launch_order tinyint null; + +alter table dvb_ait + add storage_priority int null; + +alter table dvb_ait + add ip_connection_required bool null; + +create table dsmcc_events +( + timestamp datetime not null, + cnid int not null, + ctsid int not null, + progno int not null, + pid int not null, + constraint dsmcc_events_pk + primary key (timestamp, cnid, ctsid, pid) +); + +alter table dsmcc_events + add event_id int not null; + +alter table dsmcc_events + add npt_event_seconds bigint not null; + +alter table dsmcc_events + add npt_event_microseconds int not null; + +alter table dsmcc_events + add private_data tinyint null; + +alter table dsmcc_events + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +alter table dsmcc_events + modify private_data tinyblob null; + +alter table dvb_eit + modify event_name varchar(255) null; + +alter table teletext_page_cache + add constraint teletext_page_cache_pk + primary key (nid, tsid, progno, magazine, timestamp); + +alter table dvb_pmt_streams_applications + drop foreign key dvb_pmt_streams_applications_dvb_pmt_streams_onid_tsid_fk; + +alter table dvb_pmt_streams_applications + drop primary key; + +alter table dvb_pmt_streams_applications + add primary key (onid, tsid, elementary_pid, application_type, ait_version_number); + +alter table dvb_pmt_streams_applications + add constraint dvb_pmt_streams_applications_dvb_pmt_streams_onid_tsid_fk + foreign key (onid, tsid, elementary_pid) references dvb_pmt_streams (onid, tsid, elementary_pid); + +alter table dvb_eit_content + modify r smallint not null; + +create table dvb_unt +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + data_broadcast_id int null, + association_tag int null, + private_data tinyint null, + update_flag int null, + update_method int null, + update_priority int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_pk + primary key (action_type, oui_hash, oui, processing_order) +); + +create table dvb_unt_compatibility +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type tinyint not null, + private_data tinyblob null, + specifier_type tinyint null, + specifier_data varchar(8) null, + model int null, + version int null, + constraint dvb_unt_compatibility_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type), + constraint dvb_unt_compatibility_dvb_unt_action_type_oui_hash_oui_fk + foreign key (action_type, oui_hash, oui, processing_order) references dvb_unt (action_type, oui_hash, oui, processing_order) +); + +alter table dvb_unt_compatibility + add dateadded TIMESTAMP default CURRENT_TIMESTAMP null; + +create table dvb_unt_compatibility_platform +( + action_type tinyint null, + oui_hash smallint null, + oui varchar(8) null, + processing_order tinyint null, + descriptor_type int null, + platform_type_bitmask int null comment 'Generated by Skyscraper on-the-fly, in order to differentiate between platforms.', + update_flag int null, + update_method int null, + update_priority int null, + data_broadcast_id int null, + association_tag int null, + private_data tinyblob null, + smart_card_number varchar(128) null, + super_ca_system_id bigint null, + serial_data tinyblob null, + mac_address_mask int null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform + add constraint dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type) references dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type); + +create table dvb_unt_compatibility_platform_mac_address_matches +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type int not null, + platform_type_bitmask int not null, + mac_match varchar(17) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop primary key; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match); + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type) references dvb_unt_compatibility_platform (action_type); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash) references dvb_unt_compatibility_platform (action_type, oui_hash); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui) references dvb_unt_compatibility_platform (action_type, oui_hash, oui); + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type, + platform_type_bitmask) references dvb_unt_compatibility_platform (action_type, oui_hash, oui, + processing_order, + descriptor_type, + platform_type_bitmask); + +alter table dvb_unt_compatibility_platform + add subgroup_tag tinyblob null; + +alter table dvb_unt_compatibility_platform + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_unt +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + data_broadcast_id int null, + association_tag int null, + private_data tinyint null, + update_flag int null, + update_method int null, + update_priority int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_pk + primary key (action_type, oui_hash, oui, processing_order) +); + +create table dvb_unt_compatibility +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type tinyint not null, + private_data tinyblob null, + specifier_type tinyint null, + specifier_data varchar(8) null, + model int null, + version int null, + constraint dvb_unt_compatibility_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type), + constraint dvb_unt_compatibility_dvb_unt_action_type_oui_hash_oui_fk + foreign key (action_type, oui_hash, oui, processing_order) references dvb_unt (action_type, oui_hash, oui, processing_order) +); + +alter table dvb_unt_compatibility + add dateadded TIMESTAMP default CURRENT_TIMESTAMP null; + +create table dvb_unt_compatibility_platform +( + action_type tinyint null, + oui_hash smallint null, + oui varchar(8) null, + processing_order tinyint null, + descriptor_type int null, + platform_type_bitmask int null comment 'Generated by Skyscraper on-the-fly, in order to differentiate between platforms.', + update_flag int null, + update_method int null, + update_priority int null, + data_broadcast_id int null, + association_tag int null, + private_data tinyblob null, + smart_card_number varchar(128) null, + super_ca_system_id bigint null, + serial_data tinyblob null, + mac_address_mask int null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform + add constraint dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type) references dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type); + +create table dvb_unt_compatibility_platform_mac_address_matches +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type int not null, + platform_type_bitmask int not null, + mac_match varchar(17) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop primary key; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match); + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type) references dvb_unt_compatibility_platform (action_type); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash) references dvb_unt_compatibility_platform (action_type, oui_hash); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui) references dvb_unt_compatibility_platform (action_type, oui_hash, oui); + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type, + platform_type_bitmask) references dvb_unt_compatibility_platform (action_type, oui_hash, oui, + processing_order, + descriptor_type, + platform_type_bitmask); + +alter table dvb_unt_compatibility_platform + add subgroup_tag tinyblob null; + +alter table dvb_unt_compatibility_platform + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_unt +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + data_broadcast_id int null, + association_tag int null, + private_data tinyint null, + update_flag int null, + update_method int null, + update_priority int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_pk + primary key (action_type, oui_hash, oui, processing_order) +); + +create table dvb_unt_compatibility +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type tinyint not null, + private_data tinyblob null, + specifier_type tinyint null, + specifier_data varchar(8) null, + model int null, + version int null, + constraint dvb_unt_compatibility_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type), + constraint dvb_unt_compatibility_dvb_unt_action_type_oui_hash_oui_fk + foreign key (action_type, oui_hash, oui, processing_order) references dvb_unt (action_type, oui_hash, oui, processing_order) +); + +alter table dvb_unt_compatibility + add dateadded TIMESTAMP default CURRENT_TIMESTAMP null; + +create table dvb_unt_compatibility_platform +( + action_type tinyint null, + oui_hash smallint null, + oui varchar(8) null, + processing_order tinyint null, + descriptor_type int null, + platform_type_bitmask int null comment 'Generated by Skyscraper on-the-fly, in order to differentiate between platforms.', + update_flag int null, + update_method int null, + update_priority int null, + data_broadcast_id int null, + association_tag int null, + private_data tinyblob null, + smart_card_number varchar(128) null, + super_ca_system_id bigint null, + serial_data tinyblob null, + mac_address_mask int null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform + add constraint dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type) references dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type); + +create table dvb_unt_compatibility_platform_mac_address_matches +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type int not null, + platform_type_bitmask int not null, + mac_match varchar(17) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop primary key; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match); + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type) references dvb_unt_compatibility_platform (action_type); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash) references dvb_unt_compatibility_platform (action_type, oui_hash); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui) references dvb_unt_compatibility_platform (action_type, oui_hash, oui); + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type, + platform_type_bitmask) references dvb_unt_compatibility_platform (action_type, oui_hash, oui, + processing_order, + descriptor_type, + platform_type_bitmask); + +alter table dvb_unt_compatibility_platform + add subgroup_tag tinyblob null; + +alter table dvb_unt_compatibility_platform + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_unt +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + data_broadcast_id int null, + association_tag int null, + private_data tinyint null, + update_flag int null, + update_method int null, + update_priority int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_pk + primary key (action_type, oui_hash, oui, processing_order) +); + +create table dvb_unt_compatibility +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type tinyint not null, + private_data tinyblob null, + specifier_type tinyint null, + specifier_data varchar(8) null, + model int null, + version int null, + constraint dvb_unt_compatibility_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type), + constraint dvb_unt_compatibility_dvb_unt_action_type_oui_hash_oui_fk + foreign key (action_type, oui_hash, oui, processing_order) references dvb_unt (action_type, oui_hash, oui, processing_order) +); + +alter table dvb_unt_compatibility + add dateadded TIMESTAMP default CURRENT_TIMESTAMP null; + +create table dvb_unt_compatibility_platform +( + action_type tinyint null, + oui_hash smallint null, + oui varchar(8) null, + processing_order tinyint null, + descriptor_type int null, + platform_type_bitmask int null comment 'Generated by Skyscraper on-the-fly, in order to differentiate between platforms.', + update_flag int null, + update_method int null, + update_priority int null, + data_broadcast_id int null, + association_tag int null, + private_data tinyblob null, + smart_card_number varchar(128) null, + super_ca_system_id bigint null, + serial_data tinyblob null, + mac_address_mask int null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform + add constraint dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type) references dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type); + +create table dvb_unt_compatibility_platform_mac_address_matches +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type int not null, + platform_type_bitmask int not null, + mac_match varchar(17) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop primary key; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match); + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type) references dvb_unt_compatibility_platform (action_type); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash) references dvb_unt_compatibility_platform (action_type, oui_hash); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui) references dvb_unt_compatibility_platform (action_type, oui_hash, oui); + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type, + platform_type_bitmask) references dvb_unt_compatibility_platform (action_type, oui_hash, oui, + processing_order, + descriptor_type, + platform_type_bitmask); + +alter table dvb_unt_compatibility_platform + add subgroup_tag tinyblob null; + +alter table dvb_unt_compatibility_platform + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_unt +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + data_broadcast_id int null, + association_tag int null, + private_data tinyint null, + update_flag int null, + update_method int null, + update_priority int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_pk + primary key (action_type, oui_hash, oui, processing_order) +); + +create table dvb_unt_compatibility +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type tinyint not null, + private_data tinyblob null, + specifier_type tinyint null, + specifier_data varchar(8) null, + model int null, + version int null, + constraint dvb_unt_compatibility_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type), + constraint dvb_unt_compatibility_dvb_unt_action_type_oui_hash_oui_fk + foreign key (action_type, oui_hash, oui, processing_order) references dvb_unt (action_type, oui_hash, oui, processing_order) +); + +alter table dvb_unt_compatibility + add dateadded TIMESTAMP default CURRENT_TIMESTAMP null; + +create table dvb_unt_compatibility_platform +( + action_type tinyint null, + oui_hash smallint null, + oui varchar(8) null, + processing_order tinyint null, + descriptor_type int null, + platform_type_bitmask int null comment 'Generated by Skyscraper on-the-fly, in order to differentiate between platforms.', + update_flag int null, + update_method int null, + update_priority int null, + data_broadcast_id int null, + association_tag int null, + private_data tinyblob null, + smart_card_number varchar(128) null, + super_ca_system_id bigint null, + serial_data tinyblob null, + mac_address_mask int null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform + add constraint dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type) references dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type); + +create table dvb_unt_compatibility_platform_mac_address_matches +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type int not null, + platform_type_bitmask int not null, + mac_match varchar(17) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop primary key; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match); + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type) references dvb_unt_compatibility_platform (action_type); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash) references dvb_unt_compatibility_platform (action_type, oui_hash); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui) references dvb_unt_compatibility_platform (action_type, oui_hash, oui); + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type, + platform_type_bitmask) references dvb_unt_compatibility_platform (action_type, oui_hash, oui, + processing_order, + descriptor_type, + platform_type_bitmask); + +alter table dvb_unt_compatibility_platform + add subgroup_tag tinyblob null; + +alter table dvb_unt_compatibility_platform + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table dvb_unt +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + data_broadcast_id int null, + association_tag int null, + private_data tinyint null, + update_flag int null, + update_method int null, + update_priority int null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_pk + primary key (action_type, oui_hash, oui, processing_order) +); + +create table dvb_unt_compatibility +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type tinyint not null, + private_data tinyblob null, + specifier_type tinyint null, + specifier_data varchar(8) null, + model int null, + version int null, + constraint dvb_unt_compatibility_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type), + constraint dvb_unt_compatibility_dvb_unt_action_type_oui_hash_oui_fk + foreign key (action_type, oui_hash, oui, processing_order) references dvb_unt (action_type, oui_hash, oui, processing_order) +); + +alter table dvb_unt_compatibility + add dateadded TIMESTAMP default CURRENT_TIMESTAMP null; + +create table dvb_unt_compatibility_platform +( + action_type tinyint null, + oui_hash smallint null, + oui varchar(8) null, + processing_order tinyint null, + descriptor_type int null, + platform_type_bitmask int null comment 'Generated by Skyscraper on-the-fly, in order to differentiate between platforms.', + update_flag int null, + update_method int null, + update_priority int null, + data_broadcast_id int null, + association_tag int null, + private_data tinyblob null, + smart_card_number varchar(128) null, + super_ca_system_id bigint null, + serial_data tinyblob null, + mac_address_mask int null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform + add constraint dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type) references dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type); + +create table dvb_unt_compatibility_platform_mac_address_matches +( + action_type tinyint not null, + oui_hash smallint not null, + oui varchar(8) not null, + processing_order tinyint not null, + descriptor_type int not null, + platform_type_bitmask int not null, + mac_match varchar(17) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_unt_compatibility_platform_pk + primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask) +); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop primary key; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add primary key (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match); + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type) references dvb_unt_compatibility_platform (action_type); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash) references dvb_unt_compatibility_platform (action_type, oui_hash); + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui) references dvb_unt_compatibility_platform (action_type, oui_hash, oui); + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify descriptor_type tinyint not null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + add constraint dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk + foreign key (action_type, oui_hash, oui, processing_order, descriptor_type, + platform_type_bitmask) references dvb_unt_compatibility_platform (action_type, oui_hash, oui, + processing_order, + descriptor_type, + platform_type_bitmask); + +alter table dvb_unt_compatibility_platform + add subgroup_tag tinyblob null; + +alter table dvb_unt_compatibility_platform + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +alter table dvb_pmt + modify iod_graphics_profile_level_indication smallint null; + +alter table dvb_pmt + modify iod_visual_profile_level_indication smallint null; + +alter table dvb_pmt + modify iod_audio_profile_level_indication smallint null; + +alter table dvb_pmt + modify iod_scene_profile_level_indication smallint null; + +alter table dvb_pmt + modify iod_od_profile_level_indication smallint null; + +alter table dvb_eit_component + modify text varchar(127) null; + +alter table dvb_linkages + add bouquet_id int null; + +alter table dvb_linkages + modify handover_origin_type tinyint(1) null after bouquet_id; + +alter table dvb_linkages + add original_network_id int null after handover_type; + +alter table dvb_linkages + add transport_stream_id int null after original_network_id; + +alter table dvb_linkages + modify handover_initial_service_id int null after handover_origin_type; + +alter table dvb_linkages + modify handover_network_id int null after handover_initial_service_id; + +alter table dvb_linkages + add service_id int null after transport_stream_id; + +alter table dvb_linkages + modify original_network_id int not null; + +alter table dvb_linkages + modify transport_stream_id int not null; + +alter table dvb_linkages + modify service_id int not null; + +alter table dvb_linkages + modify handover_type int null; + +alter table dvb_linkages_ssu + drop foreign key dvb_linkages_ssu_dvb_linkages_uuid_fk; + +alter table dvb_linkages_ipmac_names + drop foreign key dvb_linkages_ipmac_names_dvb_linkages_uuid_fk; + +alter table dvb_linkages_ipmac + drop foreign key dvb_linkages_ipmac_dvb_linkages_uuid_fk; + +alter table dvb_linkages_extended_event_linkages + drop foreign key dvb_linkages_extended_event_linkages_dvb_linkages_uuid_fk; + +alter table dvb_linkages + drop primary key; + +alter table dvb_linkages + add primary key (uuid, linkage_type, original_network_id, transport_stream_id, service_id); + +alter table dvb_linkages_extended_event_linkages + add constraint dvb_linkages_extended_event_linkages_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +alter table dvb_linkages_ipmac + add constraint dvb_linkages_ipmac_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +alter table dvb_linkages_ipmac_names + add constraint dvb_linkages_ipmac_names_dvb_linkages_ipmac_uuid_id_fk + foreign key (uuid, id) references dvb_linkages_ipmac (uuid, id); + +alter table dvb_linkages_ssu + add constraint dvb_linkages_ssu_dvb_linkages_uuid_fk + foreign key (uuid) references dvb_linkages (uuid); + +create table dvb_bat_transport_stream +( + bouquet_id int not null, + original_network_id int null, + transport_stream_id int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null +); + +create table dvb_bat_transport_stream_services +( + bouquet_id int not null, + original_network_id int not null, + transport_stream_id int not null, + service_id int not null, + service_type int not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_bat_transport_stream_services_pk + primary key (bouquet_id, original_network_id, transport_stream_id, service_id) +); + +alter table dvb_bat_transport_stream + add constraint dvb_bat_transport_stream_pk + primary key (bouquet_id, original_network_id, transport_stream_id); + +alter table dvb_bat_transport_stream_services + add constraint dvb_bat_transport_stream_services_dvb_bat_transport_stream_fk + foreign key (bouquet_id, original_network_id, transport_stream_id) references dvb_bat_transport_stream (bouquet_id, original_network_id, transport_stream_id); + +rename table dvb_bat_transport_stream_services to dvb_bat_transport_stream_service_list; + +alter table dvb_bat_transport_stream + add private_data_specifier bigint null; + +create table dvb_bat_transport_stream_country_availability +( + bouquet_id int not null, + original_network_id int not null, + transport_stream_id int not null, + country varchar(3) not null, + availability bool not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_bat_transport_stream_country_availability_pk + primary key (bouquet_id, original_network_id, transport_stream_id, country), + constraint dvb_bat_transport_stream_country_availability_dvb_bat_fk + foreign key (bouquet_id, original_network_id, transport_stream_id) references dvb_bat_transport_stream (bouquet_id, original_network_id, transport_stream_id) +); + +alter table dvb_bat_transport_stream + add default_authority varchar(128) null; + +alter table skyscraper_imported_streams + add debugger bool not null; + +alter table skyscraper_imported_streams + add time_taken int not null; + + +alter table dvb_ait + modify app_priority smallint null; + +alter table dvb_ait_transport_protocols + modify component_tag smallint null; + +alter table dvb_unt_compatibility_platform + drop foreign key dvb_unt_compatibility_platform_dvb_unt_compatibility_action_fk; + +alter table dvb_unt_compatibility_platform_mac_address_matches + drop foreign key dvb_unt_compatibility_platform_mac_address_matches_dvb_unt_fk; + +alter table dvb_unt_compatibility + modify descriptor_type smallint not null; + +alter table dvb_unt_compatibility_platform + modify descriptor_type smallint not null; + +alter table dvb_linkages + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +create table scte35_splice +( + cnid int not null, + ctsid int not null, + progno int not null, + splice_event_id bigint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint sct35_splice_pk + primary key (cnid, ctsid, progno, splice_event_id) +); + +alter table scte35_splice + add splice_event_cancel_indicator bool null; + +alter table scte35_splice + add avails_expected smallint null; + +alter table scte35_splice + add avail_num smallint null; + +alter table scte35_splice + add unique_program_id int null; + +alter table scte35_splice + add duration bigint null; + +alter table scte35_splice + add duration_auto_return bool null; + +create table scte35_splice_components +( + cnid int not null, + ctsid int not null, + progno int not null, + splice_event_id bigint not null, + k smallint not null, + v bigint null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint table_name_pk + primary key (cnid, ctsid, progno, splice_event_id, k), + constraint scte35_splice_components_scte35_splice_cnid_ctsid_progno_fk + foreign key (cnid, ctsid, progno, splice_event_id) references scte35_splice (cnid, ctsid, progno, splice_event_id) +); + +alter table scte35_splice + add splice_time bigint null after splice_event_id; + +alter table scte35_splice + add splice_immediate_flag bool null; + +alter table scte35_splice + add duration_flag bool null; + +alter table scte35_splice + add program_splice_flag bool null; + +alter table scte35_splice + add out_of_network_indicator bool null; + +alter table scte35_splice + add provider_avail_id bigint null; + +alter table scte35_splice + add identifier bigint null; + +alter table scte35_splice + add sub_segments_expected smallint null; + +alter table scte35_splice + add sub_segment_num smallint null; + +alter table scte35_splice + add segments_expected smallint null; + +alter table scte35_splice + add segment_num smallint null; + +alter table scte35_splice + add segmentation_type_id smallint null; + +alter table scte35_splice + add segmentation_upid varchar(16) null; + +alter table scte35_splice + add segmentation_upid_type smallint null; + +alter table scte35_splice + add segmentation_duration bigint null; + +create table scte35_splice_segmentation_components +( + cnid int not null, + ctsid int not null, + progno int not null, + splice_event_id bigint not null, + component_tag smallint not null, + pts_offset bigint not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint scte35_splice_segmentation_components_pk + primary key (cnid, ctsid, progno, splice_event_id, component_tag), + constraint scte35_splice_segmentation_components_scte35_splice_cnid_fk + foreign key (cnid, ctsid, progno, splice_event_id) references scte35_splice (cnid, ctsid, progno, splice_event_id) +); + +alter table scte35_splice + add program_segmentation_flag bool null; + +alter table scte35_splice + add segmentation_event_cancel_indicator bool null; + +alter table scte35_splice + add device_restrictions int null; + +alter table scte35_splice + add archive_allowed_flag bool null; + +alter table scte35_splice + add no_regional_blackout_flag bool null; + +alter table scte35_splice + add web_delivery_flag bool null; + +alter table scte35_splice + add segmentation_duration_flag bool null; + +alter table scte35_splice + add segmentation_event_id bigint null; + +alter table scte35_splice + add identifier bigint null; + +alter table scte35_splice + add utc_offset int null; + +alter table scte35_splice + add tai_ns bigint null; + +alter table scte35_splice + add tai_seconds bigint null; + +alter table scte35_splice + add tai_identififer bigint null; + +alter table scte35_splice + add program_segmentation_flag bool null; + +alter table scte35_splice + add segmentation_event_cancel_indicator bool null; + +alter table scte35_splice + add device_restrictions int null; + +alter table scte35_splice + add archive_allowed_flag bool null; + +alter table scte35_splice + add no_regional_blackout_flag bool null; + +alter table scte35_splice + add web_delivery_flag bool null; + +alter table scte35_splice + add segmentation_duration_flag bool null; + +alter table scte35_splice + add segmentation_event_id bigint null; + +alter table scte35_splice + add segmentation_identifier bigint null; + +alter table scte35_splice + add utc_offset int null; + +alter table scte35_splice + add tai_ns bigint null; + +alter table scte35_splice + add tai_seconds bigint null; + +alter table scte35_splice + add tai_identififer bigint null; + +create table scte35_time_signal + +( + cnid int not null, + ctsid int not null, + progno int not null, + current int not null, + value bigint null, + provider_avail_id bigint null, + identifier bigint null, + sub_segments_expected smallint null, + sub_segment_num smallint null, + segments_expected smallint null, + segment_num smallint null, + segmentation_type_id smallint null, + segmentation_upid varchar(16) null, + segmentation_upid_type smallint null, + segmentation_duration bigint null, + program_segmentation_flag tinyint(1) null, + segmentation_event_cancel_indicator tinyint(1) null, + device_restrictions int null, + archive_allowed_flag tinyint(1) null, + no_regional_blackout_flag tinyint(1) null, + web_delivery_flag tinyint(1) null, + segmentation_duration_flag tinyint(1) null, + segmentation_event_id bigint null, + segmentation_identifier bigint null, + utc_offset int null, + tai_ns bigint null, + tai_seconds bigint null, + tai_identififer bigint null, + constraint scte35_time_signal_pk + primary key (cnid, ctsid, progno) +); + +alter table scte35_time_signal + modify current timestamp not null; + +alter table scte35_time_signal + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null after value; + +alter table scte35_time_signal + drop primary key; + +alter table scte35_time_signal + add primary key (cnid, ctsid, progno, current); + +alter table dvb_pmt_streams_audio_preselection + modify component_tags tinyblob null; + +alter table dvb_pmt_streams_audio_preselection + modify future_extension tinyblob null; + +create table dvb_tsid +( + cnid int not null, + ctsid int not null, + compliance varchar(4) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null, + constraint dvb_tsid_pk + primary key (cnid, ctsid, compliance) +); + +rename table dvb_tsid to dvb_tsdt; + +alter table dvb_tsdt + add station_identification varchar(4) null; + +alter table dvb_sdt + drop column component_tag; + +alter table dvb_sdt + drop column iso_639_language_code; + +alter table dvb_sdt + drop column component_type; + +alter table dvb_sdt + drop column stream_context; + +alter table dvb_sdt + drop column stream_context_ext; + +alter table dvb_sdt + drop column text; + +alter table dvb_sdt + add component_tag tinyint null; + +alter table dvb_sdt + add iso_639_language_code varchar(3) null; + +alter table dvb_sdt + add text tinytext null; + +create table dvb_sdt_components +( + tsid int not null, + nid int not null, + service_id int not null, + component_tag smallint not null, + stream_content_ext tinyint not null, + stream_content tinyint not null, + component_type tinyint not null, + iso_639_language_code varchar(3) not null, + text mediumtext not null, + constraint table_name_pk + primary key (nid, tsid, service_id, component_tag) +); + +alter table dvb_sdt_components + add constraint dvb_sdt_components_dvb_sdt_tsid_onid_service_id_fk + foreign key (tsid, nid, service_id) references dvb_sdt (tsid, onid, service_id); + +alter table dvb_sdt_components + add dateadded TIMESTAMP default CURRENT_TIMESTAMP not null; + +alter table dvb_sdt_components + modify text mediumtext null; + +alter table dvb_unt + modify processing_order smallint not null; + +alter table dvb_unt + modify private_data tinyblob null; + +alter table dvb_unt_compatibility + modify processing_order smallint not null; + +alter table dvb_unt_compatibility_platform + modify processing_order smallint not null; + +alter table dvb_unt_compatibility_platform + modify mac_address_mask varchar(17) null; + +alter table dvb_unt_compatibility_platform_mac_address_matches + modify processing_order smallint not null; + +create table skyscraper_locations +( + id int not null + primary key, + lon double not null, + lat double not null, + name VARCHAR(100) not null, + dateadded TIMESTAMP default CURRENT_TIMESTAMP not null +); + +alter table skyscraper_locations + add current bit default false not null; + +create unique index skyscraper_locations_current_uindex + on skyscraper_locations (current); + diff --git a/DataTableStorages/skyscraper5.Data.MySql/skyscraper8.Data.MySql.csproj b/DataTableStorages/skyscraper5.Data.MySql/skyscraper8.Data.MySql.csproj new file mode 100644 index 0000000..d3e38f8 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.MySql/skyscraper8.Data.MySql.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + + + + + + + + + + + diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Ait.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Ait.cs new file mode 100644 index 0000000..fc429a4 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Ait.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Mhp; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mhp.Descriptors.InteractionTransportSelectors; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _knownAitApplications; + public bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier) + { + if (_knownAitApplications == null) + _knownAitApplications = new HashSet(); + if (_knownAitApplications.Contains(aitApplicationApplicationIdentifier)) + return true; + + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ait WHERE org_id = @org AND app_id = @app"; + command.Parameters.AddWithValue("@org",NpgsqlDbType.Bigint, (long)aitApplicationApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app",NpgsqlDbType.Integer,(int)aitApplicationApplicationIdentifier.ApplicationId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + conn.Close(); + } + + if (result) + _knownAitApplications.Add(aitApplicationApplicationIdentifier); + + return result; + } + + public void StoreAitApplication(AitApplication aitApplication) + { + EnqueueTask(x => WriteAitApplication(x, aitApplication)); + _knownAitApplications.Add(aitApplication.ApplicationIdentifier); + } + + private void WriteAitApplication(NpgsqlConnection connection, AitApplication aitApplication) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_ait (org_id, app_id, app_control_code, app_priority, service_bound_flag,\r\n transport_protocol_label, visibility, initial_path, app_usage_code, jvm_arguments,\r\n jvm_base_directory, jvm_class_path_extension, jvm_initial_class, version,\r\n is_launchable_from_older_version, launchable_completely_from_cache, not_launchable_from_broadcast,\r\n application_storage_priority, storage_property, auto_select, service_id, service_name,\r\n version_number, launch_order, storage_priorityy, ip_connection_required)" + + "values " + + "(@org_id,@app_id,@app_control_code,@app_priority,@service_bound_flag,@transport_protocol_label,@visibility,@initial_path,@app_usage_code,@jvm_arguments,@jvm_base_directory,@jvm_class_path_extension,@jvm_initial_class, @version,\r\n @is_launchable_from_older_version, @launchable_completely_from_cache, @not_launchable_from_broadcast,\r\n @application_storage_priority, @storage_property, @auto_select, @service_id, @service_name,\r\n @version_number, @launch_order, @storage_priorityy, @ip_connection_required);"; + command.Parameters.AddParameter("@org_id", NpgsqlDbType.Bigint, aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddParameter("@app_id", NpgsqlDbType.Integer, aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.AddParameter("@app_control_code", NpgsqlDbType.Integer, aitApplication.ApplicationControlCode); + command.Parameters.AddParameter("@app_priority", NpgsqlDbType.Integer, aitApplication.ApplicationPriority); + command.Parameters.AddParameter("@service_bound_flag", NpgsqlDbType.Boolean, aitApplication.ServiceBoundFlag); + command.Parameters.AddParameter("@transport_protocol_label", NpgsqlDbType.Bytea, aitApplication.TransportProtocolLabel); + command.Parameters.AddParameter("@visibility", NpgsqlDbType.Integer, aitApplication.Visibility); + command.Parameters.AddParameter("@initial_path", NpgsqlDbType.Text, aitApplication.InitialPath); + command.Parameters.AddParameter("@app_usage_code", NpgsqlDbType.Integer, aitApplication.ApplicationUsageCode); + command.Parameters.AddParameter("@jvm_arguments", NpgsqlDbType.Text, aitApplication.ConcatJvmArguments()); + command.Parameters.AddParameter("@jvm_base_directory", NpgsqlDbType.Text, aitApplication.JvmBaseDirectory); + command.Parameters.AddParameter("@jvm_class_path_extension", NpgsqlDbType.Text, aitApplication.JvmClassPathExtension); + //@jvm_initial_class,@version,@is_launchable_from_older_version,@launchable_completely_from_cache,@not_launchable_from_broadcast,@application_storage_priority,@storage_property,@auto_select,@service_id, + command.Parameters.AddParameter("@jvm_initial_class", NpgsqlDbType.Text, aitApplication.JvmInitialClass); + command.Parameters.AddParameter("@version", NpgsqlDbType.Bigint, (long?)aitApplication.Version); + command.Parameters.AddParameter("@is_launchable_from_older_version", NpgsqlDbType.Boolean, aitApplication.IsLaunchableFromOlderVersion); + command.Parameters.AddParameter("@launchable_completely_from_cache", NpgsqlDbType.Boolean, aitApplication.LaunchableCompletelyFromCache); + command.Parameters.AddParameter("@not_launchable_from_broadcast", NpgsqlDbType.Boolean, aitApplication.NotLaunchableFromBroadcast); + command.Parameters.AddParameter("@application_storage_priority", NpgsqlDbType.Integer, (int?)aitApplication.ApplicationStoragePriority); + command.Parameters.AddParameter("@storage_property", NpgsqlDbType.Integer, (int?)aitApplication.StorageProperty); + command.Parameters.AddParameter("@auto_select", NpgsqlDbType.Boolean, aitApplication.AutoSelect); + command.Parameters.AddParameter("@service_id", NpgsqlDbType.Bigint, (long?)aitApplication.ServiceId); + //@service_name,@version_number,@launch_order,@storage_priorityy,@ip_connection_required);"; + command.Parameters.AddParameter("@service_name", NpgsqlDbType.Text, aitApplication.ServiceName); + command.Parameters.AddParameter("@version_number", NpgsqlDbType.Bigint, (long?)aitApplication.VersionNumber); + command.Parameters.AddParameter("@launch_order", NpgsqlDbType.Integer, (int?)aitApplication.LaunchOrder); + command.Parameters.AddParameter("@storage_priorityy", NpgsqlDbType.Integer, (int?)aitApplication.StoragePriority); + command.Parameters.AddParameter("@ip_connection_required", NpgsqlDbType.Boolean, aitApplication.IpConnectionRequired); + command.ExecuteNonQuery(); + + WriteAitApplicationNames(connection, aitApplication); + WriteAitApplicationProfiles(connection, aitApplication); + WriteAitBoundaryExtension(connection, aitApplication); + WriteAitExtensionAuthorization(connection, aitApplication); + WriteAitTransportProtocols(connection, aitApplication); + } + + private void WriteAitTransportProtocols(NpgsqlConnection connection, AitApplication aitApplication) + { + if (aitApplication.TransportProtocols == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_ait_transport_protocols (org_id, app_id, protocol_id, transport_protocol_label,\r\n oc_remote_connection, oc_onid, oc_tsid, oc_sid, oc_component_tag,\r\n inter_url_base) " + + "values (@org_id, @app_id, @protocol_id, @transport_protocol_label,@oc_remote_connection, @oc_onid, @oc_tsid, @oc_sid, @oc_component_tag,@inter_url_base)"; + command.Parameters.AddWithValue("@org_id", NpgsqlDbType.Bigint, (long)aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", NpgsqlDbType.Integer, (int)aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@protocol_id", NpgsqlDbType.Integer); + command.Parameters.Add("@transport_protocol_label", NpgsqlDbType.Integer); + command.Parameters.Add("@oc_remote_connection", NpgsqlDbType.Boolean); + command.Parameters.Add("@oc_onid", NpgsqlDbType.Integer); + command.Parameters.Add("@oc_tsid", NpgsqlDbType.Integer); + command.Parameters.Add("@oc_sid", NpgsqlDbType.Integer); + command.Parameters.Add("@oc_component_tag", NpgsqlDbType.Integer); + command.Parameters.Add("@inter_url_base", NpgsqlDbType.Text); + + foreach (TransportProtocolDescriptor protocol in aitApplication.TransportProtocols) + { + command.Parameters["@protocol_id"].Value = (int)protocol.ProtocolId; + command.Parameters["@transport_protocol_label"].Value = protocol.TransportProtocolLabel; + switch (protocol.ProtocolId) + { + case 1: + ObjectCarouselTransportSelector objectCarouselTransportSelector = (ObjectCarouselTransportSelector)protocol.Selector; + command.Parameters["@oc_remote_connection"].Value = objectCarouselTransportSelector.RemoteConnection; + command.Parameters["@oc_onid"].Value = (int?)objectCarouselTransportSelector.OriginalNetworkId; + command.Parameters["@oc_tsid"].Value = (int?)objectCarouselTransportSelector.TransportStreamId; + command.Parameters["@oc_sid"].Value = (int?)objectCarouselTransportSelector.ServiceId; + command.Parameters["@oc_component_tag"].Value = (int)objectCarouselTransportSelector.ComponentTag; + command.Parameters["@inter_url_base"].Value = DBNull.Value; + break; + case 3: + InteractionTransportSelector interactionTransportSelector = (InteractionTransportSelector)protocol.Selector; + command.Parameters["@oc_remote_connection"].Value = DBNull.Value; + command.Parameters["@oc_onid"].Value = DBNull.Value; + command.Parameters["@oc_tsid"].Value = DBNull.Value; + command.Parameters["@oc_sid"].Value = DBNull.Value; + command.Parameters["@oc_component_tag"].Value = DBNull.Value; + command.Parameters["@inter_url_base"].Value = interactionTransportSelector.UrlBase; + break; + default: + throw new NotImplementedException(String.Format("{0} {1}", nameof(protocol.ProtocolId), protocol.ProtocolId)); + } + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + if (protocol.ProtocolId == 3) + { + WriteAitTransportProtocolsUrlExtensions(connection, aitApplication, (InteractionTransportSelector)protocol.Selector); + } + } + } + + private void WriteAitTransportProtocolsUrlExtensions(NpgsqlConnection connection, AitApplication aitApplication, InteractionTransportSelector protocolSelector) + { + if (protocolSelector.UrlExtensions == null) + return; + if (protocolSelector.UrlExtensions.Length == 0) + return; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into dvb_ait_transport_protocols_url_extensions (org_id, app_id, protocol_id, extension) values (@orgId,@appId,@protocolId,@extension)"; + cmd.Parameters.AddWithValue("@orgId", NpgsqlDbType.Bigint, (long)aitApplication.ApplicationIdentifier.OrganisationId); + cmd.Parameters.AddWithValue("@appId", NpgsqlDbType.Integer, (int)aitApplication.ApplicationIdentifier.ApplicationId); + cmd.Parameters.AddWithValue("@protocolId", NpgsqlDbType.Integer, (int)protocolSelector.ProtocolId); + cmd.Parameters.Add("@extension", NpgsqlDbType.Text); + foreach (string extension in protocolSelector.UrlExtensions) + { + if (string.IsNullOrEmpty(extension)) + continue; + cmd.Parameters["@extension"].Value = extension; + cmd.ExecuteNonQuery(); + } + } + + private void WriteAitExtensionAuthorization(NpgsqlConnection connection, AitApplication aitApplication) + { + if (aitApplication.ExternalAuthorizations == null) + return; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into dvb_ait_external_authorization (src_org_id, src_app_id, tgt_org_id, tgt_app_id, app_priority)\r\n" + + "values (@src_org_id, @src_app_id, @tgt_org_id, @tgt_app_id, @app_priority)"; + cmd.Parameters.AddWithValue("@src_org_id", NpgsqlDbType.Bigint, (long)aitApplication.ApplicationIdentifier.OrganisationId); + cmd.Parameters.AddWithValue("@src_app_id", NpgsqlDbType.Integer, (int)aitApplication.ApplicationIdentifier.ApplicationId); + cmd.Parameters.Add("@tgt_org_id", NpgsqlDbType.Bigint); + cmd.Parameters.Add("@tgt_app_id", NpgsqlDbType.Integer); + cmd.Parameters.Add("@app_priority", NpgsqlDbType.Integer); + + for (int i = 0; i < aitApplication.ExternalAuthorizations.Length; i++) + { + ExternalApplicationAuthorisationDescriptor.ExternalAuthorization externalAuthorization = aitApplication.ExternalAuthorizations[i]; + cmd.Parameters["@tgt_org_id"].Value = (long)externalAuthorization.OrganisationId; + cmd.Parameters["@tgt_app_id"].Value = (int)externalAuthorization.ApplicationId; + cmd.Parameters["@app_priority"].Value = (int)externalAuthorization.ApplicationPriority; + cmd.ExecuteNonQuery(); + } + } + + private void WriteAitBoundaryExtension(NpgsqlConnection connection, AitApplication aitApplication) + { + if (aitApplication.BoundaryExtensions == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "insert into dvb_ait_boundary_extensions (org_id, app_id, extension)" + + "values (@org_id, @app_id, @extension)"; + command.Parameters.AddWithValue("@org_id", NpgsqlDbType.Bigint, (long)aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", NpgsqlDbType.Integer, (int)aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@extension", NpgsqlDbType.Text); + for (int i = 0; i < aitApplication.BoundaryExtensions.Length; i++) + { + command.Parameters["@extension"].Value = aitApplication.BoundaryExtensions[i]; + command.ExecuteNonQuery(); + } + } + + private void WriteAitApplicationProfiles(NpgsqlConnection connection, AitApplication aitApplication) + { + if (aitApplication.ApplicationProfiles == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_ait_application_profiles (org_id, app_id, ordinal, app_profile, major, minor, micro) " + + "values (@org_id, @app_id, @ordinal, @app_profile, @major, @minor, @micro)"; + command.Parameters.AddWithValue("@org_id", NpgsqlDbType.Bigint, (long)aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", NpgsqlDbType.Integer, (int)aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@app_profile", NpgsqlDbType.Integer); + command.Parameters.Add("@major", NpgsqlDbType.Integer); + command.Parameters.Add("@minor", NpgsqlDbType.Integer); + command.Parameters.Add("@micro", NpgsqlDbType.Integer); + for (int i = 0; i < aitApplication.ApplicationProfiles.Length; i++) + { + ApplicationDescriptor.ApplicationProfileEncoding applicationProfile = aitApplication.ApplicationProfiles[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@app_profile"].Value = (int)applicationProfile.ApplicationProfile; + command.Parameters["@major"].Value = applicationProfile.Major; + command.Parameters["@minor"].Value = applicationProfile.Minor; + command.Parameters["@micro"].Value = applicationProfile.Micro; + command.ExecuteNonQuery(); + } + } + + private void WriteAitApplicationNames(NpgsqlConnection connection, AitApplication aitApplication) + { + if (aitApplication.ApplicationName == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ait_application_names (org_id, app_id, k, v)" + + "VALUES (@org_id, @app_id, @k, @v)"; + command.Parameters.AddWithValue("@org_id", NpgsqlDbType.Bigint, (long)aitApplication.ApplicationIdentifier.OrganisationId); + command.Parameters.AddWithValue("@app_id", NpgsqlDbType.Integer, (int)aitApplication.ApplicationIdentifier.ApplicationId); + command.Parameters.Add("@k", NpgsqlDbType.Varchar); + command.Parameters.Add("@v", NpgsqlDbType.Text); + foreach (KeyValuePair keyValuePair in aitApplication.ApplicationName) + { + command.Parameters["@k"].Value = keyValuePair.Key.Replace("\0", ""); + command.Parameters["@v"].Value = keyValuePair.Value; + command.ExecuteNonQuery(); + } + } + + + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Bat.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Bat.cs new file mode 100644 index 0000000..af0d21c --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Bat.cs @@ -0,0 +1,454 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using static skyscraper5.Dvb.Descriptors.ServiceListDescriptor; +using static skyscraper5.src.InteractionChannel.Model.Rmt; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _knownBatBouquets; + public bool TestForBatBouquet(BatBouquet batBouquet) + { + if (_knownBatBouquets == null) + _knownBatBouquets = new HashSet(); + DatabaseKeyBatBouquet key = new DatabaseKeyBatBouquet(batBouquet.BouquetId); + if (_knownBatBouquets.Contains(key)) + return true; + + bool result = false; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_bat WHERE id = @id"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)batBouquet.BouquetId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + if (result) + _knownBatBouquets.Add(key); + + return result; + } + + public void StoreBatBouquet(BatBouquet batBouquet) + { + DatabaseKeyBatBouquet key = new DatabaseKeyBatBouquet(batBouquet.BouquetId); + EnqueueTask(x => WriteBatBouquet(x, batBouquet)); + _knownBatBouquets.Add(key); + if (_updatedBats == null) + _updatedBats = new HashSet(); + _updatedBats.Add(key); + } + + private void WriteBatBouquet(NpgsqlConnection conn, BatBouquet bouquet) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_bat (id, bouquet_name, private_data_specifier, uri_linkage_type, uri, min_polling_interval, control_remote_access_over_internet, do_not_apply_revocation, do_not_scramble) " + + "values " + + "(@id, @bouquet_name, @private_data_specifier, @uri_linkage_type, @uri, @min_polling_interval, @control_remote_access_over_internet, @do_not_apply_revocation, @do_not_scramble)"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer, (int)bouquet.BouquetId); + command.Parameters.AddParameter("@bouquet_name", NpgsqlDbType.Text, bouquet.BouquetName); + command.Parameters.AddParameter("@private_data_specifier", NpgsqlDbType.Bigint, bouquet.PrivateDataSpecifier); + command.Parameters.AddParameter("@uri_linkage_type", NpgsqlDbType.Integer, bouquet.UriLinkageType); + command.Parameters.AddParameter("@uri", NpgsqlDbType.Text, bouquet.Uri); + command.Parameters.AddParameter("@min_polling_interval", NpgsqlDbType.Integer, bouquet.MinPollingInterval); + command.Parameters.AddParameter("@control_remote_access_over_internet", NpgsqlDbType.Integer, bouquet.ControlRemoteAccessOverInternet); + command.Parameters.AddParameter("@do_not_apply_revocation", NpgsqlDbType.Boolean, bouquet.DoNotApplyRevocation); + command.Parameters.AddParameter("@do_not_scramble", NpgsqlDbType.Boolean, bouquet.DoNotScramble); + command.ExecuteNonQuery(); + command.Dispose(); + + WriteBatCountryAvailability(conn, bouquet); + WriteBatLinkage(conn, bouquet); + WriteBatMultilingualBouquetNames(conn, bouquet); + } + + private void WriteBatMultilingualBouquetNames(NpgsqlConnection conn, BatBouquet bouquet) + { + if (bouquet.MultilingualBouquetName == null) + return; + if (bouquet.MultilingualBouquetName.MultilingualBouquetName == null) + return; + + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "insert into dvb_bat_multilingual_bouquet_names (id, k, v)\r\nvalues (@id,@k,@v)"; + cmd.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)bouquet.BouquetId); + cmd.Parameters.Add("@k", NpgsqlDbType.Varchar); + cmd.Parameters.Add("@v", NpgsqlDbType.Text); + + foreach (KeyValuePair keyValuePair in bouquet.MultilingualBouquetName.MultilingualBouquetName) + { + cmd.Parameters["@k"].Value = keyValuePair.Key; + cmd.Parameters["@v"].Value = keyValuePair.Value; + cmd.ExecuteNonQuery(); + } + } + + private void WriteBatLinkage(NpgsqlConnection conn, BatBouquet bouquet) + { + if (bouquet.Linkages == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_bat_linkages (id, ordinal, l_tsid, l_onid, l_sid, linkage_type, handover_type, handover_origin_type, handover_network_id, handover_initial_service_id, target_event_id, target_event_listed, target_event_simulcasted, table_type, private_data_bytes, bouquet_id) " + + "values " + + "(@id,@ordinal,@l_tsid,@l_onid,@l_sid,@linkage_type,@handover_type,@handover_origin_type,@handover_network_id,@handover_initial_service_id,@target_event_id,@target_event_listed,@target_event_simulcasted,@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer,bouquet.BouquetId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@l_tsid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_onid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_sid", NpgsqlDbType.Integer); + command.Parameters.Add("@linkage_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_origin_type", NpgsqlDbType.Boolean); + command.Parameters.Add("@handover_network_id", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_initial_service_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_listed", NpgsqlDbType.Boolean); + command.Parameters.Add("@target_event_simulcasted", NpgsqlDbType.Boolean); + //@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.Add("@table_type", NpgsqlDbType.Integer); + command.Parameters.Add("@private_data_bytes", NpgsqlDbType.Bytea); + command.Parameters.Add("@bouquet_id", NpgsqlDbType.Integer); + + for (int i = 0; i < bouquet.Linkages.Count; i++) + { + LinkageDescriptor linkage = bouquet.Linkages[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@l_tsid"].Value = (int)linkage.TransportStreamId; + command.Parameters["@l_onid"].Value = (int)linkage.OriginalNetworkId; + command.Parameters["@l_sid"].Value = (int)linkage.ServiceId; + command.Parameters["@linkage_type"].Value = (int)linkage.LinkageType; + command.Parameters["@handover_type"].Value = linkage.HandoverType.HasValue ? linkage.HandoverType.Value : DBNull.Value; + command.Parameters["@handover_origin_type"].Value = linkage.HandoverOriginType.HasValue ? linkage.HandoverOriginType.Value : DBNull.Value; + command.Parameters["@handover_network_id"].Value = linkage.HandoverNetworkId.HasValue ? linkage.HandoverNetworkId.Value : DBNull.Value; + command.Parameters["@handover_initial_service_id"].Value = linkage.HandoverInitialServiceId.HasValue ? linkage.HandoverInitialServiceId.Value : DBNull.Value; + command.Parameters["@target_event_id"].Value = linkage.TargetEventId.HasValue ? linkage.TargetEventId.Value : DBNull.Value; + command.Parameters["@target_event_listed"].Value = linkage.TargetEventListed.HasValue ? linkage.TargetEventListed.Value : DBNull.Value; + command.Parameters["@target_event_simulcasted"].Value = linkage.TargetEventSimulcasted.HasValue ? linkage.TargetEventSimulcasted.Value : DBNull.Value; + command.Parameters["@table_type"].Value = linkage.TableType.HasValue ? (int)linkage.TableType.Value : DBNull.Value; + command.Parameters["@private_data_bytes"].Value = linkage.PrivateDataBytes != null ? linkage.PrivateDataBytes : DBNull.Value; + command.Parameters["@bouquet_id"].Value = linkage.BouquetId.HasValue ? linkage.BouquetId : DBNull.Value; + command.ExecuteNonQuery(); + + WriteBatLinkageExtendedLinkages(conn, bouquet, i); + WriteBatLinkageIpmacLinkage(conn, bouquet, i); + WriteBatLinkageSsuLinkStructure(conn, bouquet, i); + } + } + + private void WriteBatLinkageSsuLinkStructure(NpgsqlConnection conn, BatBouquet bouquet, int ordinal) + { + if (bouquet.Linkages[ordinal].SystemSoftwareUpdateLinkStructure == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_bat_linkages_ssu_link_structure (id, ordinal, subordinal, oui, selector) " + + "VALUES (@id, @ordinal, @subordinal, @oui, @selector)"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)bouquet.BouquetId); + command.Parameters.AddWithValue("@ordinal", NpgsqlDbType.Integer, ordinal); + command.Parameters.Add("@subordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@oui", NpgsqlDbType.Varchar); + command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea); + for (int i = 0; i < bouquet.Linkages[ordinal].SystemSoftwareUpdateLinkStructure.Count; i++) + { + command.Parameters["@subordinal"].Value = i; + command.Parameters["@oui"].Value = BitConverter.ToString(bouquet.Linkages[ordinal].SystemSoftwareUpdateLinkStructure[i].OUI); + command.Parameters["@selector"].Value = bouquet.Linkages[ordinal].SystemSoftwareUpdateLinkStructure[i].Selector; + command.ExecuteNonQuery(); + } + } + + private void WriteBatLinkageIpmacLinkage(NpgsqlConnection conn, BatBouquet bouquet, int ordinal) + { + if (bouquet.Linkages[ordinal].IpMacNotificationLinkages == null) + return; + + for (int i = 0; i < bouquet.Linkages[ordinal].IpMacNotificationLinkages.Count; i++) + { + throw new NotImplementedException(); + } + } + + private void WriteBatLinkageExtendedLinkages(NpgsqlConnection conn, BatBouquet bouquet, int ordinal) + { + if (bouquet.Linkages[ordinal].ExtendedEventLinkages == null) + return; + + for (int i = 0; i < bouquet.Linkages[ordinal].ExtendedEventLinkages.Length; i++) + { + throw new NotImplementedException(); + } + } + + private void WriteBatCountryAvailability(NpgsqlConnection conn, BatBouquet bouquet) + { + if (bouquet.CountryAvailabilityDictionary == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_bat_country_availability (id,k,v) " + + "VALUES (@id,@k,@v)"; + command.Parameters.AddWithValue("@id", (int)bouquet.BouquetId); + command.Parameters.Add("@k", NpgsqlDbType.Varchar); + command.Parameters.Add("@v", NpgsqlDbType.Boolean); + foreach (KeyValuePair keyValuePair in bouquet.CountryAvailabilityDictionary) + { + command.Parameters["@k"].Value = keyValuePair.Key; + command.Parameters["@v"].Value = keyValuePair.Value; + command.ExecuteNonQuery(); + } + } + + private HashSet _updatedBats; + public bool UpdateBatBouquet(BatBouquet batBouquet) + { + if (_updatedBats == null) + _updatedBats = new HashSet(); + DatabaseKeyBatBouquet key = new DatabaseKeyBatBouquet(batBouquet.BouquetId); + if (_updatedBats.Contains(key)) + return false; + EnqueueTask(x => WriteBatBouquetUpdate(x, batBouquet)); + _updatedBats.Add(key); + return true; + } + + private void WriteBatBouquetUpdate(NpgsqlConnection connection, BatBouquet newer) + { + BatBouquet older = SelectBat(connection, newer.BouquetId); + if (!older.NeedUpdate(newer)) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_bat\r\nSET bouquet_name = @bouquet_name,\r\n private_data_specifier = @private_data_specifier,\r\n uri_linkage_type = @uri_linkage_type,\r\n uri = @uri,\r\n min_polling_interval = @min_polling_interval,\r\n control_remote_access_over_internet = @control_remote_access_over_internet,\r\n do_not_apply_revocation = @do_not_apply_revocation,\r\n do_not_scramble = @do_not_scramble,\r\n updated_counter = updated_counter + 1,\r\n updated_timestamp = CURRENT_TIMESTAMP\r\nWHERE id = @id"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer, (int)newer.BouquetId); + command.Parameters.AddParameter("@bouquet_name", NpgsqlDbType.Text, newer.BouquetName); + command.Parameters.AddParameter("@private_data_specifier", NpgsqlDbType.Bigint, newer.PrivateDataSpecifier); + command.Parameters.AddParameter("@uri_linkage_type", NpgsqlDbType.Integer, newer.UriLinkageType); + command.Parameters.AddParameter("@uri", NpgsqlDbType.Text, newer.Uri); + command.Parameters.AddParameter("@min_polling_interval", NpgsqlDbType.Integer, newer.MinPollingInterval); + command.Parameters.AddParameter("@control_remote_access_over_internet", NpgsqlDbType.Integer, newer.ControlRemoteAccessOverInternet); + command.Parameters.AddParameter("@do_not_apply_revocation", NpgsqlDbType.Boolean, newer.DoNotApplyRevocation); + command.Parameters.AddParameter("@do_not_scramble", NpgsqlDbType.Boolean, newer.DoNotScramble); + command.ExecuteNonQuery(); + } + + private BatBouquet SelectBat(NpgsqlConnection connection, int id) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM dvb_bat WHERE id = @id"; + command.Parameters.AddWithValue("@id", id); + NpgsqlDataReader dataReader = command.ExecuteReader(); + BatBouquet result = null; + if (dataReader.Read()) + { + ushort id2 = (ushort)dataReader.GetInt32(0); + result = new BatBouquet(id2); + DateTime dateadded = dataReader.GetDateTime(1); + result.BouquetName = dataReader.IsDBNull(2) ? null : dataReader.GetString(2); + result.PrivateDataSpecifier = dataReader.IsDBNull(3) ? null : (ushort)dataReader.GetInt64(3); + result.UriLinkageType = dataReader.IsDBNull(4) ? null : (byte)dataReader.GetInt32(4); + result.Uri = dataReader.IsDBNull(5) ? null : dataReader.GetString(5); + result.MinPollingInterval = dataReader.IsDBNull(6) ? null : (ushort)dataReader.GetInt32(6); + result.ControlRemoteAccessOverInternet = dataReader.IsDBNull(7) ? null : dataReader.GetInt32(7); + result.DoNotApplyRevocation = dataReader.IsDBNull(8) ? null : dataReader.GetBoolean(8); + result.DoNotScramble = dataReader.IsDBNull(9) ? null : dataReader.GetBoolean(9); + int updateCounter = dataReader.GetInt32(10); + DateTime? updatedTimestamp = dataReader.IsDBNull(11) ? null : dataReader.GetDateTime(11); + } + dataReader.Close(); + command.Dispose(); + return result; + } + + private HashSet _knownBatTs; + public bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + if (_knownBatTs == null) + _knownBatTs = new HashSet(); + + DatabaseKeyBatTs ts = new DatabaseKeyBatTs(batBouquetBouquetId, child.TransportStreamId, child.OriginalNetworkId); + if (_knownBatTs.Contains(ts)) + return true; + + bool result = false; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateAdded FROM dvb_bat_transport_stream WHERE bid = @bid AND tsid = @tsid AND onid = @onid"; + command.Parameters.AddWithValue("@bid", NpgsqlDbType.Integer, (int)batBouquetBouquetId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)child.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)child.OriginalNetworkId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + conn.Close(); + } + + if (result) + _knownBatTs.Add(ts); + return result; + } + + public void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + EnqueueTask(x => WriteBatTransportStream(x, batBouquetBouquetId, child)); + DatabaseKeyBatTs key = new DatabaseKeyBatTs(batBouquetBouquetId, child.TransportStreamId, child.OriginalNetworkId); + _knownBatTs.Add(key); + if (_updatedBatTs == null) + _updatedBatTs = new HashSet(); + _updatedBatTs.Add(key); + } + + private void WriteBatTransportStream(NpgsqlConnection connection, ushort bouquetId, BatTransportStream transportStream) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_bat_transport_stream (bid, tsid, onid, private_data_specifier, default_authority) " + + "VALUES (@bid, @tsid, @onid, @private_data_specifier, @default_authority)"; + command.Parameters.AddWithValue("@bid", NpgsqlDbType.Integer, (int)bouquetId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)transportStream.OriginalNetworkId); + command.Parameters.AddParameter("@private_data_specifier", NpgsqlDbType.Bigint, transportStream.PrivateDataSpecifier); + command.Parameters.AddParameter("@default_authority", NpgsqlDbType.Text, transportStream.DefaultAuthority); + command.ExecuteNonQuery(); + + WriteBatTransportStreamCountryAvailability(connection, bouquetId, transportStream); + WriteBatTransportStreamServiceList(connection, bouquetId, transportStream); + } + + private bool TestForBatTransportStreamServiceList(NpgsqlConnection connection, int bouquetId, int transportStreamId, int originalNetworkId, int serviceId) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_bat_transport_stream_service_list WHERE bid = @bid AND tsid = @tsid AND onid = @onid AND sid = @sid"; + command.Parameters.AddWithValue("@bid", NpgsqlDbType.Integer, bouquetId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, originalNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, serviceId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + + private void WriteBatTransportStreamServiceList(NpgsqlConnection connection, ushort bouquetId, BatTransportStream transportStream) + { + if (transportStream.ServiceList == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_bat_transport_stream_service_list (bid,tsid,onid,sid,service_type)" + + "VALUES (@bid,@tsid,@onid,@sid,@service_type)"; + command.Parameters.AddWithValue("@bid", NpgsqlDbType.Integer, (int)bouquetId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)transportStream.OriginalNetworkId); + command.Parameters.Add("@sid", NpgsqlDbType.Integer); + command.Parameters.Add("@service_type", NpgsqlDbType.Integer); + + foreach (ServiceListDescriptor.Service service in transportStream.ServiceList) + { + if (TestForBatTransportStreamServiceList(connection,(int)bouquetId,(int)transportStream.TransportStreamId,(int)transportStream.OriginalNetworkId,service.ServiceId)) + { + continue; + } + command.Parameters["@sid"].Value = (int)service.ServiceId; + command.Parameters["@service_type"].Value = (int)service.ServiceType; + command.ExecuteNonQuery(); + } + } + + private void WriteBatTransportStreamCountryAvailability(NpgsqlConnection connection, ushort bouquetId, BatTransportStream transportStream) + { + if (transportStream.CountryAvailability == null) + return; + + foreach (KeyValuePair keyValuePair in transportStream.CountryAvailability) + { + throw new NotImplementedException(); + } + } + + private HashSet _updatedBatTs; + public bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + if (_updatedBatTs == null) + _updatedBatTs = new HashSet(); + + DatabaseKeyBatTs key = new DatabaseKeyBatTs(batBouquetBouquetId, child.TransportStreamId, child.OriginalNetworkId); + if (_updatedBatTs.Contains(key)) + return false; + + EnqueueTask(x => WriteUpdateBatTs(x, batBouquetBouquetId, child)); + _updatedBatTs.Add(key); + return true; + } + + private void WriteUpdateBatTs(NpgsqlConnection connection, ushort batBouquetBouquetId, BatTransportStream transportStream) + { + DatabaseKeyBatTs key = new DatabaseKeyBatTs(batBouquetBouquetId, transportStream.TransportStreamId, transportStream.OriginalNetworkId); + BatTransportStream older = SelectBatTs(connection, batBouquetBouquetId, transportStream.TransportStreamId, transportStream.OriginalNetworkId); + if (!older.NeedsUpdate(transportStream)) + { + return; + } + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_bat_transport_stream " + + "SET private_data_specifier = @private_data_specifier,\r\n default_authority = @default_authority " + + "WHERE bid = @bid AND tsid = @tsid AND onid = @onid\r\n"; + command.Parameters.AddWithValue("@bid", NpgsqlDbType.Integer, (int)batBouquetBouquetId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)transportStream.OriginalNetworkId); + command.Parameters.AddParameter("@private_data_specifier", NpgsqlDbType.Bigint, transportStream.PrivateDataSpecifier); + command.Parameters.AddParameter("@default_authority", NpgsqlDbType.Text, transportStream.DefaultAuthority); + command.ExecuteNonQuery(); + } + + private BatTransportStream SelectBatTs(NpgsqlConnection conn, ushort bouquetId, ushort transportStreamId, ushort originalNetworkId) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT * FROM dvb_bat_transport_stream WHERE bid = @bid AND tsid = @tsid AND onid = @onid"; + command.Parameters.AddParameter("@bid", NpgsqlDbType.Integer, (int)bouquetId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, (int)originalNetworkId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + BatTransportStream result = null; + if (dataReader.Read()) + { + ushort bid = (ushort)dataReader.GetInt32(0); + ushort tsid = (ushort)dataReader.GetInt32(1); + ushort onid = (ushort)dataReader.GetInt32(2); + DateTime dateadded = dataReader.GetDateTime(3); + result = new BatTransportStream(tsid, onid); + result.PrivateDataSpecifier = dataReader.IsDBNull(4) ? null : (uint)dataReader.GetInt64(4); + result.DefaultAuthority = dataReader.IsDBNull(5) ? null : dataReader.GetString(5); + } + dataReader.Close(); + command.Dispose(); + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Beams.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Beams.cs new file mode 100644 index 0000000..289b987 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Beams.cs @@ -0,0 +1,173 @@ +using Npgsql; +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Gps; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public void BeamsDisableAll() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "UPDATE skyscraper5_beams SET enabled = FALSE"; + command.ExecuteNonQuery(); + connection.Close(); + } + } + + public void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM skyscraper5_beams WHERE id = @id AND process_timestamp = @process_timestamp"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, id); + command.Parameters.AddWithValue("@process_timestamp", NpgsqlDbType.Timestamp, processTimestamp); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool isKnown = dataReader.Read(); + dataReader.Close(); + if (isKnown) + { + command.CommandText = "UPDATE skyscraper5_beams SET enabled = TRUE WHERE id = @id AND process_timestamp = @process_timestamp"; + command.ExecuteNonQuery(); + } + else + { + command.CommandText = "INSERT INTO skyscraper5_beams (id,satpos,name,process_timestamp) VALUES (@id,@satpos,@name,@process_timestamp)"; + command.Parameters.AddWithValue("@satpos", NpgsqlDbType.Double, (double)satpos); + command.Parameters.AddWithValue("@name", NpgsqlDbType.Text, name); + command.ExecuteNonQuery(); + } + connection.Close(); + } + } + + public void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, + string name, + string getPolygonString, string id) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into skyscraper5_beams_footprints (beam_id, beam_process_timestamp, name, polygon,polygonid)\r\n" + + "values (@beam_id,@beam_process_timestamp,@name,@polygon,@polygonid)"; + command.Parameters.AddWithValue("@beam_id", NpgsqlDbType.Integer, databasePointerId); + command.Parameters.AddWithValue("@beam_process_timestamp", NpgsqlDbType.Timestamp, databasePointerBeamsProcessTimestamp); + command.Parameters.AddWithValue("@name", NpgsqlDbType.Varchar, name); + command.Parameters.AddWithValue("@polygon", NpgsqlDbType.Text, getPolygonString); + command.Parameters.AddWithValue("@polygonid", NpgsqlDbType.Text, id); + command.ExecuteNonQuery(); + conn.Close(); + } + } + + public bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string polygonid) + { + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded " + + "FROM skyscraper5_beams_footprints " + + "WHERE beam_id = @beam_id " + + "AND beam_process_timestamp = @beam_process_timestamp " + + "AND name = @name " + + "AND polygonid = @polygonid "; + command.Parameters.AddWithValue("@beam_id", NpgsqlDbType.Integer, databasePointerId); + command.Parameters.AddWithValue("@beam_process_timestamp", NpgsqlDbType.Timestamp, databasePointerBeamsProcessTimestamp); + command.Parameters.AddWithValue("@name", NpgsqlDbType.Text, name); + command.Parameters.AddWithValue("@polygonid", NpgsqlDbType.Text, polygonid); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + conn.Close(); + } + + return result; + } + + public void BeamsDisableSpecific(int id, float databasePointerSatpos, string databasePointerName, + DateTime processTimestamp) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM skyscraper5_beams WHERE id = @id AND process_timestamp = @process_timestamp"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, id); + command.Parameters.AddWithValue("@process_timestamp", NpgsqlDbType.Timestamp, processTimestamp); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool isKnown = dataReader.Read(); + dataReader.Close(); + if (isKnown) + { + command.CommandText = "UPDATE skyscraper5_beams SET enabled = FALSE WHERE id = @id AND process_timestamp = @process_timestamp"; + command.ExecuteNonQuery(); + } + else + { + //Not yet known, so we won't need to update. Okay, keep going. + } + connection.Close(); + } + } + + public IEnumerable BeamsSelectEnabled() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT id, satpos, name, process_timestamp FROM skyscraper5_beams"; + NpgsqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + int id = dataReader.GetInt32(0); + float satpos = dataReader.GetFloat(1); + string name = dataReader.GetString(2); + DateTime processTimestamp = dataReader.GetDateTime(3); + yield return new SatelliteBeam(id, satpos, name, processTimestamp); + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + } + + public List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp) + { + List result = new List(); + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT name, polygon, dateadded, polygonid FROM skyscraper5_beams_footprints WHERE beam_id = @id AND beam_process_timestamp = @process_timestamp"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, satelliteBeamId); + command.Parameters.AddWithValue("@process_timestamp", NpgsqlDbType.Timestamp, satelliteBeamProcessTimestamp); + NpgsqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + string name = dataReader.GetString(0); + string polygon = dataReader.GetString(1); + DateTime dateadded = dataReader.GetDateTime(2); + string polygonId = dataReader.GetString(3); + result.Add(new SatelliteBeamFootprint(satelliteBeamId, satelliteBeamProcessTimestamp, name, polygon, dateadded, polygonId)); + } + } + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/BlindscanJobs.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/BlindscanJobs.cs new file mode 100644 index 0000000..8854a5e --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/BlindscanJobs.cs @@ -0,0 +1,572 @@ +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Gps; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public void InsertBlindscanJob(DbBlindscanJob jobInDb) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "insert into skyscraper5_blindscan_jobs (uuid, tuner_mac, tuner_std, diseqc_index, horizontal_low, horizontal_high,\r\n" + + " vertical_low, vertical_high, gps_lon, gps_lat, sat_position)\r\n" + + "values (@uuid,@tuner_mac,@tuner_std,@diseqc_index,@hl,@hh,@vl,@vh,@gps_lon,@gps_lat,@sat_position);"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, jobInDb.JobGuid); + command.Parameters.AddWithValue("@tuner_mac", NpgsqlDbType.MacAddr, jobInDb.TunerMAC); + command.Parameters.AddWithValue("@tuner_std", NpgsqlDbType.Integer, (int)jobInDb.TunerStandard); + command.Parameters.AddWithValue("@diseqc_index", NpgsqlDbType.Integer, jobInDb.DiseqCIndex); + command.Parameters.AddWithValue("@hl", NpgsqlDbType.Integer, (int)jobInDb.HorizontalLowState); + command.Parameters.AddWithValue("@hh", NpgsqlDbType.Integer, (int)jobInDb.HorizontalHighState); + command.Parameters.AddWithValue("@vl", NpgsqlDbType.Integer, (int)jobInDb.VerticalLowState); + command.Parameters.AddWithValue("@vh", NpgsqlDbType.Integer, (int)jobInDb.VerticalHighState); + if (jobInDb.GpsCoordinate.HasValue) + { + command.Parameters.AddWithValue("@gps_lon", NpgsqlDbType.Double, (double)jobInDb.GpsCoordinate.Value.Longitude); + command.Parameters.AddWithValue("@gps_lat", NpgsqlDbType.Double, (double)jobInDb.GpsCoordinate.Value.Latitude); + } + else + { + command.Parameters.AddWithValue("@gps_lon", NpgsqlDbType.Double, DBNull.Value); + command.Parameters.AddWithValue("@gps_lat", NpgsqlDbType.Double, DBNull.Value); + } + + if (jobInDb.SatPosition != null) + { + command.Parameters.AddWithValue("@sat_position", NpgsqlDbType.Integer, jobInDb.SatPosition.Checksum); + } + else + { + command.Parameters.AddWithValue("@sat_position", NpgsqlDbType.Integer, DBNull.Value); + } + + command.ExecuteNonQuery(); + command.Dispose(); + connection.Close(); + } + } + + public void UpdateJobState(DbBlindscanJob jobInDb) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE skyscraper5_blindscan_jobs SET horizontal_low = @hl, horizontal_high = @hh, vertical_low = @vl, vertical_high = @vh, dateupdated = CURRENT_TIMESTAMP, version = version + 1 WHERE uuid = @uuid"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, jobInDb.JobGuid); + command.Parameters.AddWithValue("@hl", NpgsqlDbType.Integer, (int)jobInDb.HorizontalLowState); + command.Parameters.AddWithValue("@hh", NpgsqlDbType.Integer, (int)jobInDb.HorizontalHighState); + command.Parameters.AddWithValue("@vl", NpgsqlDbType.Integer, (int)jobInDb.VerticalLowState); + command.Parameters.AddWithValue("@vh", NpgsqlDbType.Integer, (int)jobInDb.VerticalHighState); + int result = command.ExecuteNonQuery(); + if (result != 1) + throw new InvalidDataException("db update failed"); + command.Dispose(); + connection.Close(); + } + } + + public void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, SearchResult2 searchResult2) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into skyscraper5_blindscan_search_results (related_job, satellite, polarity_index,\r\n" + + " lock, freq, pol, sr, std_type, mod_type, fec,\r\n" + + " inversion, roll_off, pilot, frame, coding_type, stream_type, mis,\r\n" + + " input_stream_ids, issyi, npd, pls, cw, bitrate, rf_level, snr, ber,\r\n" + + " pre_ber, bw, feclp, transmode, guard, hierarchy, plp, lid, s1type,\r\n" + + " payload, bw_ext, mode_t2, ver_t2, cell_id, net_id)\r\n" + + "values (@related_job, @satellite, @polarity_index, @lock, @freq, @pol, @sr, @std_type, @mod_type, @fec, @inversion,\r\n" + + " @roll_off, @pilot, @frame, @coding_type, @stream_type, @mis, @input_stream_ids, @issyi, @npd, @pls, @cw,\r\n" + + " @bitrate, @rf_level, @snr, @ber, @pre_ber, @bw, @feclp, @transmode, @guard, @hierarchy, @plp, @lid, @s1type,\r\n" + + " @payload, @bw_ext, @mode_t2, @ver_t2, @cell_id, @net_id)"; + command.Parameters.Add("@related_job", NpgsqlDbType.Uuid); + command.Parameters.Add("@satellite", NpgsqlDbType.Boolean); + command.Parameters.Add("@polarity_index", NpgsqlDbType.Integer); + command.Parameters.Add("@lock", NpgsqlDbType.Boolean); + command.Parameters.Add("@freq", NpgsqlDbType.Integer); + command.Parameters.Add("@pol", NpgsqlDbType.Integer); + command.Parameters.Add("@sr", NpgsqlDbType.Integer); + command.Parameters.Add("@std_type", NpgsqlDbType.Integer); + command.Parameters.Add("@mod_type", NpgsqlDbType.Integer); + command.Parameters.Add("@fec", NpgsqlDbType.Integer); + command.Parameters.Add("@inversion", NpgsqlDbType.Integer); + command.Parameters.Add("@roll_off", NpgsqlDbType.Integer); + command.Parameters.Add("@pilot", NpgsqlDbType.Integer); + command.Parameters.Add("@frame", NpgsqlDbType.Integer); + command.Parameters.Add("@coding_type", NpgsqlDbType.Integer); + command.Parameters.Add("@stream_type", NpgsqlDbType.Integer); + command.Parameters.Add("@mis", NpgsqlDbType.Integer); + command.Parameters.Add("@input_stream_ids", NpgsqlDbType.Bytea); + command.Parameters.Add("@issyi", NpgsqlDbType.Smallint); + command.Parameters.Add("@npd", NpgsqlDbType.Smallint); + command.Parameters.Add("@pls", NpgsqlDbType.Integer); + command.Parameters.Add("@cw", NpgsqlDbType.Integer); + command.Parameters.Add("@bitrate", NpgsqlDbType.Integer); + command.Parameters.Add("@rf_level", NpgsqlDbType.Integer); + command.Parameters.Add("@snr", NpgsqlDbType.Double); + command.Parameters.Add("@ber", NpgsqlDbType.Double); + command.Parameters.Add("@pre_ber", NpgsqlDbType.Double); + //SR2 + command.Parameters.Add("@bw", NpgsqlDbType.Integer); + command.Parameters.Add("@feclp", NpgsqlDbType.Integer); + command.Parameters.Add("@transmode", NpgsqlDbType.Integer); + command.Parameters.Add("@guard", NpgsqlDbType.Integer); + command.Parameters.Add("@hierarchy", NpgsqlDbType.Integer); + command.Parameters.Add("@plp", NpgsqlDbType.Integer); + command.Parameters.Add("@lid", NpgsqlDbType.Bytea); + command.Parameters.Add("@s1type", NpgsqlDbType.Integer); + command.Parameters.Add("@payload", NpgsqlDbType.Integer); + command.Parameters.Add("@bw_ext", NpgsqlDbType.Integer); + command.Parameters.Add("@mode_t2", NpgsqlDbType.Integer); + command.Parameters.Add("@ver_t2", NpgsqlDbType.Integer); + command.Parameters.Add("@cell_id", NpgsqlDbType.Integer); + command.Parameters.Add("@net_id", NpgsqlDbType.Integer); + + foreach (NpgsqlParameter parameter in command.Parameters) + parameter.Value = DBNull.Value; + + command.Parameters["@related_job"].Value = jobInDb.JobGuid; + command.Parameters["@satellite"].Value = satellite; + command.Parameters["@polarity_index"].Value = polarityIndex; + + if (satellite) + { + command.Parameters["@lock"].Value = searchResult.Lock; + command.Parameters["@freq"].Value = searchResult.Freq; + command.Parameters["@pol"].Value = searchResult.Pol; + command.Parameters["@sr"].Value = searchResult.SR; + command.Parameters["@std_type"].Value = searchResult.StdType; + command.Parameters["@mod_type"].Value = searchResult.ModType; + command.Parameters["@fec"].Value = (int)searchResult.FEC; + command.Parameters["@inversion"].Value = searchResult.Inversion; + command.Parameters["@roll_off"].Value = searchResult.RollOff; + command.Parameters["@pilot"].Value = searchResult.Pilot; + command.Parameters["@frame"].Value = searchResult.Frame; + command.Parameters["@coding_type"].Value = searchResult.CodingType; + command.Parameters["@stream_type"].Value = searchResult.StreamType; + command.Parameters["@mis"].Value = searchResult.MIS; + command.Parameters["@input_stream_ids"].Value = searchResult.IS; + command.Parameters["@issyi"].Value = searchResult.ISSYI; + command.Parameters["@npd"].Value = searchResult.NPD; + command.Parameters["@pls"].Value = searchResult.PLS; + command.Parameters["@cw"].Value = searchResult.CW; + command.Parameters["@bitrate"].Value = DBNull.Value; + command.Parameters["@rf_level"].Value = searchResult.RFLevel; + command.Parameters["@snr"].Value = searchResult.SNR; + command.Parameters["@ber"].Value = searchResult.BER; + command.Parameters["@pre_ber"].Value = searchResult.preBER; + } + else + { + command.Parameters["@lock"].Value = searchResult2.Lock; + command.Parameters["@pol"].Value = 0; + command.Parameters["@freq"].Value = searchResult2.Freq; + command.Parameters["@sr"].Value = searchResult2.SR; + command.Parameters["@std_type"].Value = searchResult2.StdType; + command.Parameters["@mod_type"].Value = searchResult2.ModType; + command.Parameters["@fec"].Value = searchResult2.FEC; + command.Parameters["@bw"].Value = searchResult2.BW; + command.Parameters["@feclp"].Value = searchResult2.FECLP; + command.Parameters["@transmode"].Value = searchResult2.TransMode; + command.Parameters["@guard"].Value = searchResult2.Guard; + command.Parameters["@hierarchy"].Value = searchResult2.Hierarchy; + command.Parameters["@plp"].Value = searchResult2.PLP; + command.Parameters["@lid"].Value = searchResult2.LID; + command.Parameters["@s1type"].Value = searchResult2.S1Type; + command.Parameters["@payload"].Value = searchResult2.Payload; + command.Parameters["@bw_ext"].Value = searchResult2.BwExt; + command.Parameters["@mode_t2"].Value = searchResult2.ModeT2; + command.Parameters["@ver_t2"].Value = searchResult2.VerT2; + command.Parameters["@cell_id"].Value = searchResult2.CellID; + command.Parameters["@net_id"].Value = searchResult2.NetID; + } + + command.ExecuteNonQuery(); + command.Dispose(); + connection.Close(); + } + } + + public void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult,BlindscanResultState blindscanResultState, SearchResult2 searchResult2) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + if (satellite) + { + command.CommandText = + "UPDATE skyscraper5_blindscan_search_results\r\n" + + "SET state = @state, dateupdated = CURRENT_TIMESTAMP, version = version + 1\r\n" + + "WHERE related_job = @related_job AND satellite = TRUE AND freq = @freq AND pol = @pol"; + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, searchResult.Freq); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, searchResult.Pol); + } + else + { + command.CommandText = + "UPDATE skyscraper5_blindscan_search_results\r\n" + + "SET state = @state, dateupdated = CURRENT_TIMESTAMP, version = version + 1\r\n" + + "WHERE related_job = @related_job AND satellite = FALSE AND freq = @freq AND pol = 0"; + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, searchResult2.Freq); + } + + command.Parameters.AddWithValue("@related_job", NpgsqlDbType.Uuid, jobInDb.JobGuid); + command.Parameters.AddWithValue("@state", NpgsqlDbType.Integer, (int)blindscanResultState); + int i = command.ExecuteNonQuery(); + if (i != 1) + throw new DBConcurrencyException(String.Format("Somehow I got {0} matches when I expected one.", i)); + command.Dispose(); + connection.Close(); + } + } + + public void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1,SearchResult2 resultSr2, HumanReadableService humanReadableService) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into skyscraper5_blindscan_services (related_job, freq, pol, sid, provider_name, service_name, ca_id,service_type)\r\n" + + "values (@related_job,@freq,@pol,@sid,@provider_name,@service_name,@ca_id,@service_type)"; + command.Parameters.AddWithValue("@related_job", NpgsqlDbType.Uuid, jobInDb.JobGuid); + if (resultSatellite) + { + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, resultSr1.Freq); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, resultSr1.Pol); + } + else + { + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, resultSr2.Freq); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, 0); + } + + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)humanReadableService.ServiceId); + command.Parameters.AddWithValue("@provider_name", NpgsqlDbType.Text, humanReadableService.ProviderName.Trim((char)0)); + string serviceName = humanReadableService.ServiceName.Trim((char)0).Trim(); + command.Parameters.AddWithValue("@service_name", NpgsqlDbType.Text, serviceName); + + if (humanReadableService.CaId.HasValue) + command.Parameters.AddWithValue("@ca_id", NpgsqlDbType.Integer, (int)humanReadableService.CaId.Value); + else + command.Parameters.AddWithValue("@ca_id", NpgsqlDbType.Integer, DBNull.Value); + + command.Parameters.AddWithValue("@service_type", NpgsqlDbType.Integer, (int)humanReadableService.ServiceType); + + command.ExecuteNonQuery(); + command.Dispose(); + connection.Close(); + } + } + + public bool TestForIncompleteJob() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT uuid FROM skyscraper5_blindscan_jobs WHERE horizontal_low < 100 OR horizontal_high < 100 OR vertical_low < 100 OR vertical_high < 100"; + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + return result; + } + } + + public DbBlindscanJob GetPastBlindscanJob(long offset) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM skyscraper5_blindscan_jobs ORDER BY dateadded DESC LIMIT 1 OFFSET @pastindex"; + command.Parameters.AddWithValue("@pastindex", NpgsqlDbType.Integer, (int)offset); + NpgsqlDataReader dataReader = command.ExecuteReader(); + DbBlindscanJob result = null; + if (dataReader.Read()) + { + Guid uuid = dataReader.GetGuid(0); + PhysicalAddress tuner_mac = (PhysicalAddress)dataReader.GetValue(1); + int tuner_std = dataReader.GetInt32(2); + int diseqc_index = dataReader.GetInt32(3); + int horizontal_low = dataReader.GetInt32(4); + int horizontal_high = dataReader.GetInt32(5); + int vertical_low = dataReader.GetInt32(6); + int vertical_high = dataReader.GetInt32(7); + DateTime date_added = dataReader.GetDateTime(8); + DateTime? date_updated = null; + if (!dataReader.IsDBNull(9)) + date_updated = dataReader.GetDateTime(9); + int version = dataReader.GetInt32(10); + double gps_lon = dataReader.GetDouble(11); + double gps_lat = dataReader.GetDouble(12); + int sat_position = dataReader.GetInt32(13); + int serial = dataReader.GetInt32(14); + string note = null; + if (!dataReader.IsDBNull(15)) + note = dataReader.GetString(15); + result = new DbBlindscanJob(uuid, tuner_mac, (STD_TYPE)tuner_std, diseqc_index, new DummyGpsReceiver(true,(float)gps_lat,(float)gps_lon), SatellitePosition.FromChecksum(sat_position)); + result.DateAdded = date_added; + result.HorizontalHighState = (DbBlindscanJobPolarizationStatus)horizontal_high; + result.HorizontalLowState = (DbBlindscanJobPolarizationStatus)horizontal_low; + result.VerticalHighState = (DbBlindscanJobPolarizationStatus)vertical_high; + result.VerticalLowState = (DbBlindscanJobPolarizationStatus)vertical_low; + } + dataReader.Close(); + command.Dispose(); + return result; + } + } + + public void DeleteBlindscanJob(Guid guid) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlTransaction transaction = connection.BeginTransaction(); + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "DELETE FROM skyscraper5_blindscan_services WHERE related_job = @uuid"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, guid); + int servicesRemoved = command.ExecuteNonQuery(); + command.Dispose(); + + command = connection.CreateCommand(); + command.CommandText = "DELETE FROM skyscraper5_blindscan_search_results WHERE related_job = @uuid"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, guid); + int searchResultRemoves = command.ExecuteNonQuery(); + command.Dispose(); + + command = connection.CreateCommand(); + command.CommandText = "DELETE FROM skyscraper5_blindscan_jobs WHERE uuid = @uuid"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, guid); + int searchResultJobs = command.ExecuteNonQuery(); + command.Dispose(); + + if (searchResultJobs != 1) + throw new DataException(String.Format("Expected to delete 1 row, but removed {0}", searchResultJobs)); + + transaction.Commit(); + transaction.Dispose(); + connection.Close(); + } + } + + public void DeleteBlindscanResults(Guid uuid, int pol) + { + pol++; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT freq, pol, state FROM skyscraper5_blindscan_search_results WHERE related_job = @uuid AND polarity_index = @pol"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, uuid); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, pol); + NpgsqlDataReader reader = command.ExecuteReader(); + List> toBeDeleted = new List>(); + while (reader.Read()) + { + int left = reader.GetInt32(0); + int mid = reader.GetInt32(1); + BlindscanResultState right = (BlindscanResultState)reader.GetInt32(2); + toBeDeleted.Add(new Tuple(left, mid, right)); + } + reader.Close(); + command.Dispose(); + + NpgsqlTransaction transaction = connection.BeginTransaction(); + foreach (Tuple deleteMe in toBeDeleted) + { + switch (deleteMe.Item3) + { + case BlindscanResultState.Found: + //not yet touched, no services to be deleted. + break; + case BlindscanResultState.Tuning: + //failed to touch, no serives to be deleted + break; + case BlindscanResultState.Done: + DeleteBlindscanServices(connection, uuid, deleteMe.Item1, deleteMe.Item2); + break; + default: + throw new NotImplementedException(deleteMe.Item3.ToString()); + } + } + + command = connection.CreateCommand(); + command.CommandText = "DELETE FROM skyscraper5_blindscan_search_results WHERE related_job = @uuid AND polarity_index = @pol"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, uuid); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, pol); + command.ExecuteNonQuery(); + command.Dispose(); + + SetBlindscanPolarityState(connection, uuid, pol, DbBlindscanJobPolarizationStatus.SELECTED_WAITING); + + transaction.Commit(); + transaction.Dispose(); + connection.Close(); + } + } + + private void SetBlindscanPolarityState(NpgsqlConnection connection, Guid uuid, int polarityIndex, DbBlindscanJobPolarizationStatus newStatus) + { + string updateColumn = null; + switch (polarityIndex) + { + case 1: updateColumn = "horizontal_low"; break; + case 2: updateColumn = "horizontal_high"; break; + case 3: updateColumn = "vertical_low"; break; + case 4: updateColumn = "vertical_high"; break; + default: throw new ArgumentOutOfRangeException(nameof(polarityIndex)); + } + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = String.Format("UPDATE skyscraper5_blindscan_jobs SET {0} = @value, dateupdated = CURRENT_TIMESTAMP, version = version + 1 WHERE uuid = @uuid", updateColumn); + command.Parameters.AddWithValue("@value", NpgsqlDbType.Integer, (int)newStatus); + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, uuid); + int result = command.ExecuteNonQuery(); + if (result != 1) + throw new DataException(String.Format("Expected to update 1 row, but got {0}", result)); + command.Dispose(); + } + + private void DeleteBlindscanServices(NpgsqlConnection connection, Guid uuid, int freq, int pol) + { + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "DELETE FROM skyscraper5_blindscan_services WHERE related_job = @uuid AND freq = @freq AND pol = @pol"; + cmd.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, uuid); + cmd.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, freq); + cmd.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, pol); + cmd.ExecuteNonQuery(); + } + + public void MoveBlScanResultsToAnotherJob(Guid moveFrom, Guid moveTo, int polarity) + { + polarity++; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + //Services, die bewegt werden müssen finden + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT freq, pol FROM skyscraper5_blindscan_search_results WHERE related_job = @uuid AND polarity_index = @pol"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, moveFrom); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, polarity); + NpgsqlDataReader reader = command.ExecuteReader(); + List> toBeMoved = new List>(); + while (reader.Read()) + { + int left = reader.GetInt32(0); + int mid = reader.GetInt32(1); + toBeMoved.Add(new Tuple(left, mid)); + } + reader.Close(); + command.Dispose(); + + NpgsqlTransaction transaction = connection.BeginTransaction(); + + //Services bewegen + int totalServices = 0; + command = connection.CreateCommand(); + command.CommandText = "UPDATE skyscraper5_blindscan_services SET related_job = @moveTo WHERE freq = @freq AND pol = @pol AND related_job = @moveFrom"; + foreach (Tuple moveMe in toBeMoved) + { + command.Parameters.Clear(); + command.Parameters.AddWithValue("@moveFrom", NpgsqlDbType.Uuid, moveFrom); + command.Parameters.AddWithValue("@moveTo", NpgsqlDbType.Uuid, moveTo); + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, moveMe.Item1); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, moveMe.Item2); + totalServices += command.ExecuteNonQuery(); + } + command.Dispose(); + + //Transponder bewegen + command = connection.CreateCommand(); + command.CommandText = "UPDATE skyscraper5_blindscan_search_results SET related_job = @moveTo WHERE related_job = @moveFrom AND polarity_index = @polarity_index"; + command.Parameters.AddWithValue("@moveTo", NpgsqlDbType.Uuid, moveTo); + command.Parameters.AddWithValue("@moveFrom", NpgsqlDbType.Uuid, moveFrom); + command.Parameters.AddWithValue("@pol", NpgsqlDbType.Integer, polarity); + command.ExecuteNonQuery(); + command.Dispose(); + + SetBlindscanPolarityState(connection, moveFrom, polarity, DbBlindscanJobPolarizationStatus.SELECTED_WAITING); + SetBlindscanPolarityState(connection, moveTo, polarity, DbBlindscanJobPolarizationStatus.SELECTED_DONE); + + transaction.Commit(); + transaction.Dispose(); + connection.Close(); + } + } + + public IEnumerable GetDbBlindscanJobs() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + //Services, die bewegt werden müssen finden + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM skyscraper5_blindscan_jobs ORDER BY dateadded DESC"; + NpgsqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + Guid uuid = dataReader.GetGuid("uuid"); + DateTime dateAdded = dataReader.GetDateTime("dateadded"); + DbBlindscanJobPolarizationStatus horizontalLow = (DbBlindscanJobPolarizationStatus)dataReader.GetInt32("horizontal_low"); + DbBlindscanJobPolarizationStatus horizontalHigh = (DbBlindscanJobPolarizationStatus)dataReader.GetInt32("horizontal_high"); + DbBlindscanJobPolarizationStatus verticalLow = (DbBlindscanJobPolarizationStatus)dataReader.GetInt32("vertical_low"); + DbBlindscanJobPolarizationStatus verticalHigh = (DbBlindscanJobPolarizationStatus)dataReader.GetInt32("vertical_high"); + PhysicalAddress tunerMac = (PhysicalAddress)dataReader.GetValue("tuner_mac"); + STD_TYPE tunerStd = (STD_TYPE)dataReader.GetInt32("tuner_std"); + DummyGpsReceiver dgr = null; + if (!dataReader.IsDBNull("gps_lon")) + { + float gpsLon = (float)dataReader.GetDouble("gps_lon"); + float gpsLat = (float)dataReader.GetDouble("gps_lat"); + dgr = new DummyGpsReceiver(true, gpsLat, gpsLon); + } + + SatellitePosition satPositionObj = null; + if (!dataReader.IsDBNull("sat_position")) + { + int satPosition = dataReader.GetInt32("sat_position"); + satPositionObj = SatellitePosition.FromChecksum(satPosition); + } + int diseqcIndex = dataReader.GetInt32("diseqc_index"); + int version = dataReader.GetInt32("version"); + + + DbBlindscanJob child = new DbBlindscanJob(uuid, tunerMac, tunerStd, diseqcIndex, dgr, satPositionObj); + child.DateAdded = dateAdded; + child.HorizontalLowState = horizontalLow; + child.HorizontalHighState = horizontalHigh; + child.VerticalLowState = verticalLow; + child.VerticalHighState = verticalHigh; + yield return child; + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Cat.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Cat.cs new file mode 100644 index 0000000..04f5ae9 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Cat.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _knownCats; + public bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid) + { + if (_knownCats == null) + _knownCats = new HashSet(); + + DatabaseKeyCat key = new DatabaseKeyCat(currentNetworkId, currentTransportStreamId, caDescriptorCaPid); + if (_knownCats.Contains(key)) + return true; + + bool result = false; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + result = TestForCaSystemEx(currentNetworkId, currentTransportStreamId, caDescriptorCaPid, conn); + conn.Close(); + } + + if (result) + _knownCats.Add(key); + + return result; + + } + + private static bool TestForCaSystemEx(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid, NpgsqlConnection conn) + { + bool result; + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_cat WHERE nid = @nid AND tsid = @tsid AND pid = @pid"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@pid", NpgsqlDbType.Integer, caDescriptorCaPid); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + + public void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + EnqueueTask(x => WriteCaSystems(x, currentNetworkId, currentTransportStreamId, caDescriptor)); + DatabaseKeyCat key = new DatabaseKeyCat(currentNetworkId, currentTransportStreamId, caDescriptor.CaPid); + _knownCats.Add(key); + } + + private void WriteCaSystems(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + if (TestForCaSystemEx(currentNetworkId,currentTransportStreamId,caDescriptor.CaPid,connection)) + { + return; + } + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_cat (nid,tsid,pid,ca_system_id,private_data) " + + "VALUES (@nid,@tsid,@pid,@ca_system_id,@private_data)"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@pid", NpgsqlDbType.Integer, caDescriptor.CaPid); + command.Parameters.AddWithValue("@ca_system_id", NpgsqlDbType.Integer, (int)caDescriptor.CaSystemId); + + object privateData = DBNull.Value; + if (caDescriptor.PrivateData != null && caDescriptor.PrivateData.Length > 0) + privateData = caDescriptor.PrivateData; + + command.Parameters.AddWithValue("@private_data", NpgsqlDbType.Bytea, privateData); + command.ExecuteNonQuery(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Dns.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Dns.cs new file mode 100644 index 0000000..b4280cd --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Dns.cs @@ -0,0 +1,286 @@ +using Npgsql; +using NpgsqlTypes; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public long DnsCountA() + { + throw new NotImplementedException(); + } + + private uint dnsIpMisses; + public string DnsIpToName(IPAddress source) + { + string result = source.ToString(); + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT domain_name\r\n" + + "FROM dns_ips ips\r\n" + + "LEFT JOIN dns_records records on ips.ip_id = records.ip_id\r\n" + + "LEFT JOIN dns_domains domains on records.domain_id = domains.domain_id\r\n" + + "WHERE ips.ip_address = @ia\r\n" + + "AND (records.record_type = 1 OR records.record_type = 28)"; + command.Parameters.AddWithValue("@ia", source); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + result = dataReader.GetString(0); + else + dnsIpMisses++; + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + return result; + } + + + public bool TestForIp(IPAddress iP) + { + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + result = TestForIp(connection, iP); + connection.Close(); + } + return result; + } + + public void RememberDnsRecord(DnsRecord record) + { + if (record.ToString().Contains("\0")) + return; + + EnqueueTask(x => { + RememberDnsRecordEx(x, record); + } + ); + } + + private uint deduplicated; + private void RememberDnsRecordEx(NpgsqlConnection x, DnsRecord record) + { + if (record.Domain.ToString().Contains("\0")) + { + return; + } + + long domainId; + if (TestForDomainName(x,record.Domain)) + domainId = SelectDomainId(x,record); + else + domainId = InsertDomainName(x,record); + + bool isIp = record.RecordType.Item2.Equals("A") || record.RecordType.Item2.Equals("AAAA"); + long? ipId = null; + if (isIp) + { + if (TestForIp(x,record.IP)) + ipId = SelectIp(x,record.IP); + else + ipId = InsertIp(x,record.IP); + } + + if (!TestForDnsRecord(x,record, domainId, ipId)) + InsertDnsRecord(x,record, domainId, ipId); + else + deduplicated++; + + if (isIp) + { + long vhostId; + if (TestForVhost(x, record.Domain)) + vhostId = SelectVhostId(x, record.Domain); + else + vhostId = InsertVhost(x, record.Domain); + + if (!TestForIpVHostMapping(x, ipId.Value, vhostId)) + InsertIpVHostMapping(x, ipId.Value, vhostId); + } + } + + private void InsertIpVHostMapping(NpgsqlConnection x, long ipId, long vhostId) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dns_ip_vhosts (ip_id, vhost_id) VALUES (@iid,@vid)"; + command.Parameters.AddWithValue("@iid", ipId); + command.Parameters.AddWithValue("@vid", vhostId); + command.ExecuteNonQuery(); + } + + private bool TestForIpVHostMapping(NpgsqlConnection x, long ipId, long vhostId) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dns_ip_vhosts WHERE ip_id = @iid AND vhost_id = @vid"; + command.Parameters.AddWithValue("@iid", ipId); + command.Parameters.AddWithValue("@vid", vhostId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + + private long InsertVhost(NpgsqlConnection x, string domain) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dns_vhosts (vhost_name) VALUES (@vn) RETURNING vhost_id"; + command.Parameters.AddWithValue("@vn", domain); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + long result = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + return result; + } + + private long SelectVhostId(NpgsqlConnection x, string domain) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT vhost_id FROM dns_vhosts WHERE vhost_name = @vn"; + command.Parameters.AddWithValue("@vn", domain); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + long result = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + return result; + } + + private bool TestForVhost(NpgsqlConnection x, string domain) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dns_vhosts WHERE vhost_name = @vn"; + command.Parameters.AddWithValue("@vn", domain); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + + private void InsertDnsRecord(NpgsqlConnection x, DnsRecord record, long domainId, long? ipId) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dns_records (domain_id, record_type, ttl, resource_data, ip_id) VALUES (@did,@rt,@ttl,@rd,@iid)"; + command.Parameters.AddWithValue("@did", domainId); + command.Parameters.AddWithValue("@rt", record.RecordType.Item1); + command.Parameters.AddWithValue("@ttl", (int)record.TTL.TotalSeconds); + command.Parameters.AddWithValue("@rd", record.ResourceData); + if (ipId.HasValue) + command.Parameters.AddWithValue("@iid", NpgsqlDbType.Bigint, ipId); + else + command.Parameters.AddWithValue("@iid", NpgsqlDbType.Bigint, DBNull.Value); + command.ExecuteNonQuery(); + } + + private bool TestForDnsRecord(NpgsqlConnection x, DnsRecord record, long domainId, long? ipId) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dns_records WHERE domain_id = @did AND record_type = @rt AND resource_data = @rd"; + command.Parameters.AddWithValue("@did", domainId); + command.Parameters.AddWithValue("@rt", record.RecordType.Item1); + command.Parameters.AddWithValue("@rd", record.ResourceData); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + + private long? InsertIp(NpgsqlConnection x, IPAddress iP) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dns_ips (ip_address) VALUES (@ia) RETURNING ip_id"; + command.Parameters.AddWithValue("@ia", iP); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + long result = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + return result; + } + + private long? SelectIp(NpgsqlConnection x, IPAddress iP) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT ip_id FROM dns_ips WHERE ip_address = @ia"; + command.Parameters.AddWithValue("@ia", iP); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + long result = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + return result; + } + + private bool TestForIp(NpgsqlConnection x, IPAddress iP) + { + bool result; + using (NpgsqlCommand cmd = x.CreateCommand()) + { + cmd.CommandText = "SELECT dateadded FROM dns_ips WHERE ip_address = @ip"; + cmd.Parameters.AddWithValue("@ip", iP); + using (NpgsqlDataReader reader = cmd.ExecuteReader()) + { + result = reader.Read(); + reader.Close(); + } + } + return result; + } + + private long InsertDomainName(NpgsqlConnection x, DnsRecord record) + { + long result; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dns_domains (domain_name) VALUES (@name) RETURNING domain_id"; + command.Parameters.AddWithValue("@name", record.Domain); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + result = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + return result; + } + + private long SelectDomainId(NpgsqlConnection x, DnsRecord record) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT domain_id FROM dns_domains WHERE domain_name = @name"; + command.Parameters.AddWithValue("@name", record.Domain); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + long result = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + return result; + } + + private bool TestForDomainName(NpgsqlConnection x, string domain) + { + bool result; + using (NpgsqlCommand cmd = x.CreateCommand()) + { + cmd.CommandText = "SELECT dateadded FROM dns_domains WHERE domain_name = @name"; + cmd.Parameters.AddWithValue("@name", domain); + NpgsqlDataReader npgsqlDataReader = cmd.ExecuteReader(); + result = npgsqlDataReader.Read(); + npgsqlDataReader.Close(); + } + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Docsis.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Docsis.cs new file mode 100644 index 0000000..d1821a8 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Docsis.cs @@ -0,0 +1,283 @@ +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Docsis.MacManagement; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + partial class PostgresqlDataStore + { + private HashSet> knownDocsisUpstreams; + public bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int currentLocation) + { + if (knownDocsisUpstreams == null) + knownDocsisUpstreams = new HashSet>(); + + Tuple tuple = new Tuple(currentLocation, mmmFrequency); + if (knownDocsisUpstreams.Contains(tuple)) + return true; + + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM docsis_upstream_channel WHERE location = @loc AND frequency=@freq"; + command.Parameters.AddWithValue("@loc", NpgsqlDbType.Integer, currentLocation); + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, (int)mmmFrequency); + NpgsqlDataReader reader = command.ExecuteReader(); + bool result = reader.Read(); + if (result) + { + knownDocsisUpstreams.Add(tuple); + } + reader.Close(); + connection.Close(); + return result; + } + } + + public void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int currentLocation) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlTransaction transaction = connection.BeginTransaction(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO docsis_upstream_channel " + + "VALUES (DEFAULT, DEFAULT, @loc, @freq, @upstreamChannelId, @configChangeCount, @minislotSize, @downstreamChannelId, @randomSeed, @symbolsInOfdmaFrame, " + + "@centerFrequencyOfSubcarrier, @subcarrierSpacing, @ofdmaRolloffPeriodSize, @ofdmaCyclicPrefixSize, @ofdmaTimesnapSnapshotDivide, @ofdmaTimestampSnapshot, " + + "@ucdChangeIndicator, @sCdmaMode, @preamblePattern, @modulationRate) RETURNING ss5_serial"; + command.Parameters.AddWithValue("@loc", NpgsqlDbType.Integer, currentLocation); + command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, (int)mmm.Frequency); + command.Parameters.AddWithValue("@upstreamChannelId", NpgsqlDbType.Integer, mmm.UpstreamChannelID); + command.Parameters.AddWithValue("@configChangeCount", NpgsqlDbType.Integer, mmm.ConfigurationChangeCount); + command.Parameters.AddWithValue("@minislotSize", NpgsqlDbType.Integer, mmm.MinislotSize); + command.Parameters.AddWithValue("@downstreamChannelId", NpgsqlDbType.Integer, mmm.DownstreamChannelId); + command.Parameters.AddWithValue("@randomSeed", NpgsqlDbType.Integer, (int)mmm.RandomizationSeed); + command.Parameters.AddWithValue("@symbolsInOfdmaFrame", NpgsqlDbType.Integer, mmm.SymbolsInOfdmaFrame); + command.Parameters.AddWithValue("@centerFrequencyOfSubcarrier", NpgsqlDbType.Bigint, mmm.CenterFrequencyOfSubcarrier0); + command.Parameters.AddWithValue("@subcarrierSpacing", NpgsqlDbType.Integer, mmm.SubcarrierSpacing); + command.Parameters.AddWithValue("@ofdmaRolloffPeriodSize", NpgsqlDbType.Integer, mmm.OfdmaRolloffPeriodSize); + command.Parameters.AddWithValue("@ofdmaCyclicPrefixSize", NpgsqlDbType.Integer, mmm.OfdmaCyclicPrefixSize); + command.Parameters.AddWithValue("@ofdmaTimesnapSnapshotDivide", NpgsqlDbType.Integer, mmm.OfdmaTimestampSnapshotDivideBy20); + command.Parameters.AddWithValue("@ofdmaTimestampSnapshot", NpgsqlDbType.Integer, mmm.OfdmaTimestampSnapshot); + command.Parameters.AddWithValue("@ucdChangeIndicator", NpgsqlDbType.Integer, mmm.UcdChangeIndicator?.GetRawValue()); + command.Parameters.AddWithValue("@sCdmaMode", NpgsqlDbType.Boolean, mmm.S_CDMAMode); + command.Parameters.AddWithValue("@preamblePattern", NpgsqlDbType.Bytea, mmm.PreamblePattern); + command.Parameters.AddWithValue("@modulationRate", NpgsqlDbType.Integer, mmm.ModulationRate); + SetNulls(command); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + int ss5serial = dataReader.GetInt32(0); + dataReader.Close(); + command.Dispose(); + if (mmm.BurstDescriptors != null && mmm.BurstDescriptors.Count > 0) + { + InsertDocsisUpstreamChannelBurstDescriptors(connection, ss5serial, mmm.BurstDescriptors); + } + if (mmm.SubcarrierExclusionBand != null && mmm.SubcarrierExclusionBand.Length > 0) + { + throw new NotImplementedException(); + } + transaction.Commit(); + connection.Close(); + } + } + + private void InsertDocsisUpstreamChannelBurstDescriptors(NpgsqlConnection connection, int channelSs5serial, List burstDescriptors) + { + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into docsis_upstream_channel_burst_descriptors VALUES (DEFAULT, @related_channel, DEFAULT, @subcarriers_fine_ranging, " + + "@ofdma_broadcast_ir_starting_power_level_increase, @ofdma_broadcast_ir_starting_power_level, @subcarriers_initial_ranging, @preamble_type, " + + "@rs_interleaver_block_size, @rs_interleaver_depth, @scrambler, @last_codeword_length, @guard_time_size, @maximum_burst_size, @scrambler_seed, " + + "@fec_codeword_information_bytes, @fec_error_correction, @preamble_value_offset, @preamble_length, @differential_encoding, @modulation_type, @interval_usage_code) RETURNING ss5_serial"; + cmd.Parameters.AddWithValue("@related_channel", NpgsqlDbType.Integer, channelSs5serial); + cmd.Parameters.Add("@subcarriers_fine_ranging", NpgsqlDbType.Integer); + cmd.Parameters.Add("@ofdma_broadcast_ir_starting_power_level_increase", NpgsqlDbType.Double); + cmd.Parameters.Add("@ofdma_broadcast_ir_starting_power_level", NpgsqlDbType.Double); + cmd.Parameters.Add("@subcarriers_initial_ranging", NpgsqlDbType.Integer); + cmd.Parameters.Add("@preamble_type", NpgsqlDbType.Integer); + cmd.Parameters.Add("@rs_interleaver_block_size", NpgsqlDbType.Integer); + cmd.Parameters.Add("@rs_interleaver_depth", NpgsqlDbType.Integer); + cmd.Parameters.Add("@scrambler", NpgsqlDbType.Boolean); + cmd.Parameters.Add("@last_codeword_length", NpgsqlDbType.Integer); + cmd.Parameters.Add("@guard_time_size", NpgsqlDbType.Integer); + cmd.Parameters.Add("@maximum_burst_size", NpgsqlDbType.Integer); + cmd.Parameters.Add("@scrambler_seed", NpgsqlDbType.Integer); + cmd.Parameters.Add("@fec_codeword_information_bytes", NpgsqlDbType.Integer); + cmd.Parameters.Add("@fec_error_correction", NpgsqlDbType.Integer); + cmd.Parameters.Add("@preamble_value_offset", NpgsqlDbType.Integer); + cmd.Parameters.Add("@preamble_length", NpgsqlDbType.Integer); + cmd.Parameters.Add("@differential_encoding", NpgsqlDbType.Boolean); + cmd.Parameters.Add("@modulation_type", NpgsqlDbType.Integer); + cmd.Parameters.Add("@interval_usage_code", NpgsqlDbType.Integer); + foreach (UpstreamChannelDescriptor.BurstDescriptor bd in burstDescriptors) + { + cmd.Parameters["@subcarriers_fine_ranging"].Value = bd.SubcarriersFineRanging; + cmd.Parameters["@ofdma_broadcast_ir_starting_power_level_increase"].Value = bd.OfdmaBroadcastIrStartingPowerLevelIncrease; + cmd.Parameters["@ofdma_broadcast_ir_starting_power_level"].Value = bd.OfdmaBroadcastIrStartingPowerLevel; + cmd.Parameters["@subcarriers_initial_ranging"].Value = bd.SubcarriersInitialRanging; + cmd.Parameters["@preamble_type"].Value = (int)bd.PreambleType; + cmd.Parameters["@rs_interleaver_block_size"].Value = (int)bd.RsInterleaverBlockSize; + cmd.Parameters["@rs_interleaver_depth"].Value = bd.RsInterleaverDepth; + cmd.Parameters["@scrambler"].Value = bd.Scrambler; + cmd.Parameters["@last_codeword_length"].Value = (int)bd.LastCodewordLength; + cmd.Parameters["@guard_time_size"].Value = bd.GuardTimeSize; + cmd.Parameters["@maximum_burst_size"].Value = bd.MaximumBurstSize; + cmd.Parameters["@scrambler_seed"].Value = bd.ScramblerSeed; + cmd.Parameters["@fec_codeword_information_bytes"].Value = bd.FecCodewordInformationBytes; + cmd.Parameters["@fec_error_correction"].Value = bd.FecErrorCorrection; + cmd.Parameters["@preamble_value_offset"].Value = (int)bd.PreambleValueOffset; + cmd.Parameters["@preamble_length"].Value = (int)bd.PreambleLength; + cmd.Parameters["@differential_encoding"].Value = bd.DifferentialEncoding; + cmd.Parameters["@modulation_type"].Value = (int)bd.ModulationType; + cmd.Parameters["@interval_usage_code"].Value = bd.IntervalUsageCode; + SetNulls(cmd); + NpgsqlDataReader dataReader = cmd.ExecuteReader(); + dataReader.Read(); + int ourSerial = dataReader.GetInt32(0); + dataReader.Close(); + if (bd.OfdmaDataProfiles != null && bd.OfdmaDataProfiles.Length > 0) + { + throw new NotImplementedException(); + } + } + cmd.Dispose(); + } + + private HashSet _knownDocsisParticipants; + public void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation) + { + if (_knownDocsisParticipants == null) + _knownDocsisParticipants = new HashSet(); + + if (_knownDocsisParticipants.Contains(pa)) + return; + + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM docsis_participants WHERE physicalAddr=@mac"; + command.Parameters.AddWithValue("@mac", NpgsqlDbType.MacAddr, pa); + NpgsqlDataReader reader = command.ExecuteReader(); + if (reader.Read()) + { + _knownDocsisParticipants.Add(pa); + connection.Close(); + return; + } + reader.Close(); + command.Dispose(); + + command = connection.CreateCommand(); + command.CommandText = "INSERT INTO docsis_participants (location,physicalAddr) VALUES (@loc,@mac)"; + command.Parameters.AddWithValue("@loc", NpgsqlDbType.Integer, currentLocation); + command.Parameters.AddWithValue("@mac", NpgsqlDbType.MacAddr, pa); + command.ExecuteNonQuery(); + _knownDocsisParticipants.Add(pa); + command.Dispose(); + connection.Close(); + } + } + + private HashSet> _knownDownstreamChannels; + public bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation) + { + if (_knownDownstreamChannels == null) + _knownDownstreamChannels = new HashSet>(); + + Tuple currentTuple = new Tuple(currentLocation, downstreamActiveChannel.Frequency.Value); + if (_knownDownstreamChannels.Contains(currentTuple)) + return true; + + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM docsis_downstream_channel WHERE location = @location AND frequency = @frequency"; + command.Parameters.AddWithValue("@location", NpgsqlDbType.Integer, currentLocation); + command.Parameters.AddWithValue("@frequency", NpgsqlDbType.Bigint, (long)downstreamActiveChannel.Frequency); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _knownDownstreamChannels.Add(currentTuple); + dataReader.Close(); + connection.Close(); + return result; + } + } + + public void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "insert into docsis_downstream_channel VALUES (DEFAULT, DEFAULT, @physical_address, @location, @frequency, @tukey_raised_cosine_window, " + + "@cyclic_prefix, @khz_subcarrier_spacing, @qam_fec_lock_recovery, @mdd_recovery, @qam_fec_lock_failure, @mdd_timeout, @primary_capable, @modulation_order, " + + "@annex, @channel_id)"; + command.Parameters.AddWithValue("@physical_address", NpgsqlDbType.MacAddr, physicalAddress); + command.Parameters.AddWithValue("@location", NpgsqlDbType.Integer, currentLocation); + command.Parameters.AddWithValue("@frequency", NpgsqlDbType.Bigint, (long)downstreamActiveChannel.Frequency); + command.Parameters.AddWithValue("@tukey_raised_cosine_window", NpgsqlDbType.Integer, downstreamActiveChannel.TukeyRaisedCosineWindow); + command.Parameters.AddWithValue("@cyclic_prefix", NpgsqlDbType.Integer, downstreamActiveChannel.CyclicPrefix); + command.Parameters.AddWithValue("@khz_subcarrier_spacing", NpgsqlDbType.Boolean, downstreamActiveChannel._50khzSubcarrierSpacing); + command.Parameters.AddWithValue("@qam_fec_lock_recovery", NpgsqlDbType.Boolean, downstreamActiveChannel.QamFecLockRecovery); + command.Parameters.AddWithValue("@mdd_recovery", NpgsqlDbType.Boolean, downstreamActiveChannel.MddRecovery); + command.Parameters.AddWithValue("@qam_fec_lock_failure", NpgsqlDbType.Boolean, downstreamActiveChannel.QamFecLockFailure); + command.Parameters.AddWithValue("@mdd_timeout", NpgsqlDbType.Boolean, downstreamActiveChannel.MddTimeout); + command.Parameters.AddWithValue("@primary_capable", NpgsqlDbType.Integer, (int)downstreamActiveChannel.PrimaryCapable); + + if (downstreamActiveChannel.ModulationOrder.HasValue) + command.Parameters.AddWithValue("@modulation_order", NpgsqlDbType.Integer, (int)downstreamActiveChannel.ModulationOrder); + else + command.Parameters.AddWithValue("@modulation_order", NpgsqlDbType.Integer, DBNull.Value); + + if (downstreamActiveChannel.Annex.HasValue) + command.Parameters.AddWithValue("@annex", NpgsqlDbType.Integer, (int)downstreamActiveChannel.Annex); + else + command.Parameters.AddWithValue("@annex", NpgsqlDbType.Integer, DBNull.Value); + + command.Parameters.AddWithValue("@channel_id", NpgsqlDbType.Integer, downstreamActiveChannel.ChannelId); + SetNulls(command); + command.ExecuteNonQuery(); + command.Dispose(); + connection.Close(); + } + } + + private HashSet> cmts_ips; + public bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress) + { + if (cmts_ips == null) + cmts_ips = new HashSet>(); + + Tuple currentTuple = new Tuple(arpHeaderSenderHardwareAddress, arpHeaderSenderProtocolAddress); + if (cmts_ips.Contains(currentTuple)) + return false; + + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "UPDATE docsis_participants SET ip = @ip WHERE physicaladdr = @mac"; + command.Parameters.AddWithValue("@ip", NpgsqlDbType.Inet, arpHeaderSenderProtocolAddress); + command.Parameters.AddWithValue("@mac", NpgsqlDbType.MacAddr, arpHeaderSenderHardwareAddress); + int rows = command.ExecuteNonQuery(); + bool result = rows > 0; + if (result) + cmts_ips.Add(currentTuple); + command.Dispose(); + connection.Close(); + return result; + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/DsmCcBlacklisting.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/DsmCcBlacklisting.cs new file mode 100644 index 0000000..d09f36e --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/DsmCcBlacklisting.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _blacklistedDsmCcModules; + public bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT MAX(progress) FROM dsmcc_modules_blacklist WHERE cnid = @cnid AND ctsid = @ctsid AND elementary_pid = @pid AND module_id = @moduleId AND module_version = @moduleVersion"; + command.Parameters.AddWithValue("@cnid",NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, elementaryPid); + command.Parameters.AddWithValue("@moduleId", NpgsqlTypes.NpgsqlDbType.Integer, (int)moduleId); + command.Parameters.AddWithValue("@moduleVersion", NpgsqlTypes.NpgsqlDbType.Integer, (int)moduleVersion); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (!dataReader.Read()) + { + dataReader.Close(); + command.Dispose(); + return false; + } + if (dataReader.IsDBNull(0)) + { + dataReader.Close(); + command.Dispose(); + return false; + } + double bestProgress = dataReader.GetDouble(0); + dataReader.Close(); + command.Dispose(); + + command = connection.CreateCommand(); + command.CommandText = "SELECT COUNT(*) FROM dsmcc_modules_blacklist WHERE cnid = @cnid AND ctsid = @ctsid AND elementary_pid = @pid AND module_id = @moduleId AND module_version = @moduleVersion AND progress = @progress"; + command.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, elementaryPid); + command.Parameters.AddWithValue("@moduleId", NpgsqlTypes.NpgsqlDbType.Integer, (int)moduleId); + command.Parameters.AddWithValue("@moduleVersion", NpgsqlTypes.NpgsqlDbType.Integer, (int)moduleVersion); + command.Parameters.AddWithValue("@progress", NpgsqlTypes.NpgsqlDbType.Double, bestProgress); + dataReader = command.ExecuteReader(); + dataReader.Read(); + long numFailures = dataReader.GetInt64(0); + dataReader.Close(); + command.Dispose(); + connection.Close(); + return numFailures >= 3; + } + } + + public void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dsmcc_modules_blacklist (cnid, ctsid, elementary_pid, module_id, module_version, progress) VALUES (@cnid,@ctsid,@pid,@moduleId,@moduleVersion,@progress)"; + command.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, key.CurrentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, key.CurrentTransportStreamId); + command.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, key.ElementaryPid); + command.Parameters.AddWithValue("@moduleId", NpgsqlTypes.NpgsqlDbType.Integer, (int)key.ModuleId); + command.Parameters.AddWithValue("@moduleVersion", NpgsqlTypes.NpgsqlDbType.Integer, (int)key.ModuleVersion); + command.Parameters.AddWithValue("@progress", NpgsqlTypes.NpgsqlDbType.Double, value); + int result = command.ExecuteNonQuery(); + if (result != 1) + throw new NpgsqlException(String.Format("Excepted to write 1 line, but wrote {0}", result)); + command.Dispose(); + connection.Close(); + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/DsmCcDoItNow.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/DsmCcDoItNow.cs new file mode 100644 index 0000000..5a1023e --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/DsmCcDoItNow.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.DsmCc.Descriptors; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public void StoreDsmCcDoItNowEvent(DateTime value, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + EnqueueTask(x => WriteDsmCcDoItNowEvent(x, value, currentNetworkId, currentTransportStreamId, programNumber, descriptorListStreamEventDescriptor, pid)); + } + + private void WriteDsmCcDoItNowEvent(NpgsqlConnection conn, DateTime value, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + if (TestForDoItNowEvent(conn, value, currentNetworkId, currentTransportStreamId, programNumber, descriptorListStreamEventDescriptor, pid)) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dsmcc_do_it_now_events (cnid, ctsid, program_number, pid, utc, event_id, private_data, npt_event_microsecond, npt_event_seconds)" + + "values (@cnid, @ctsid, @program_number, @pid, @utc, @event_id, @private_data, @npt_event_microsecond, @npt_event_seconds);"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddWithValue("@pid", NpgsqlDbType.Integer, pid); + command.Parameters.AddWithValue("@utc", NpgsqlDbType.Timestamp, value); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)descriptorListStreamEventDescriptor.EventId); + command.Parameters.AddWithValue("@private_data", NpgsqlDbType.Bytea, descriptorListStreamEventDescriptor.PrivateData); + command.Parameters.AddWithValue("@npt_event_microsecond", NpgsqlDbType.Bigint, (long)descriptorListStreamEventDescriptor.NptEventMicroseconds); + command.Parameters.AddWithValue("@npt_event_seconds", NpgsqlDbType.Bigint, (long)descriptorListStreamEventDescriptor.NptEventSeconds); + command.ExecuteNonQuery(); + } + + private bool TestForDoItNowEvent(NpgsqlConnection conn, DateTime value, int currentNetworkId, + int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, + int pid) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "SELECT dateadded " + + "FROM dsmcc_do_it_now_events " + + "WHERE cnid = @cnid " + + "AND ctsid = @ctsid " + + "AND program_number = @program_number " + + "AND pid = @pid " + + "AND utc = @utc " + + "AND event_id = @event_id"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddWithValue("@pid", NpgsqlDbType.Integer, pid); + command.Parameters.AddWithValue("@utc", NpgsqlDbType.Timestamp, value); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)descriptorListStreamEventDescriptor.EventId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Eit.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Eit.cs new file mode 100644 index 0000000..44b4dfc --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Eit.cs @@ -0,0 +1,479 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public bool StoreEitEvent(EitEvent eitEvent) + { + if (knownEitEvents == null) + knownEitEvents = new HashSet(); + DatabaseKeyEitEvent key = new DatabaseKeyEitEvent(eitEvent.ServiceId, eitEvent.TransportStreamId, eitEvent.OriginalNetworkId, eitEvent.EventId, eitEvent.StartTime); + if (knownEitEvents.Contains(key)) + return false; + if (TestForEitEvent(key)) + { + knownEitEvents.Add(key); + return false; + } + + EnqueueTask(x => WriteEitEvent(x, eitEvent)); + knownEitEvents.Add(key); + return true; + } + + public void Ping() + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT relname FROM pg_catalog.pg_class"; + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool hasDsmCcTables = false; + bool hasDvbTables = false; + bool hasScte35Tables = false; + bool hasSkyscraper5Tables = false; + while (dataReader.Read()) + { + string tableName = dataReader.GetString(0); + if (tableName.StartsWith("dsmcc_")) + hasDsmCcTables = true; + if (tableName.StartsWith("dvb_")) + hasDvbTables = true; + if (tableName.StartsWith("scte35_")) + hasScte35Tables = true; + if (tableName.StartsWith("skyscraper5_")) + hasSkyscraper5Tables = true; + } + dataReader.Close(); + command.Dispose(); + conn.Close(); + if (!hasDsmCcTables || !hasDvbTables || !hasScte35Tables || !hasSkyscraper5Tables) + { + throw new SchemaMissingException("The PostgreSQL schema does not seem to be installed."); + } + } + } + + public IEnumerable> SelectAllPmt() + { + throw new NotImplementedException(); + } + + public SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber) + { + throw new NotImplementedException(); + } + + public IEnumerable> SelectAllSdt() + { + throw new NotImplementedException(); + } + + + private HashSet knownEitEvents; + + private bool TestForEitEvent(DatabaseKeyEitEvent eitEvent) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + return TestForEitEventEx(eitEvent, conn); + } + } + + private static bool TestForEitEventEx(DatabaseKeyEitEvent eitEvent, NpgsqlConnection conn) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dvb_eit WHERE sid = @sid AND tsid = @tsid AND onid = @onid AND event_id = @event_id AND start_time = @start_time"; + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private void WriteEitEvent(NpgsqlConnection conn, EitEvent eitEvent) + { + DatabaseKeyEitEvent key = new DatabaseKeyEitEvent(eitEvent.ServiceId, eitEvent.TransportStreamId, eitEvent.OriginalNetworkId, eitEvent.EventId, eitEvent.StartTime); + if (TestForEitEventEx(key, conn)) + { + return; + } + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_eit " + + "(sid, tsid, onid, event_id, start_time, running_status, free_ca, iso_639_language_code, event_name, text, extended_text, pdc, private_data_specifier, vps_string, reference_service_id, reference_event_id, control_remote_access_over_internet, do_not_apply_revocation, do_not_scramble) " + + "VALUES " + + "(@sid, @tsid, @onid, @event_id, @start_time, @running_status, @free_ca, @iso_639_language_code, @event_name, @text, @extended_text, @pdc, @private_data_specifier, @vps_string, @reference_service_id, @reference_event_id, @control_remote_access_over_internet, @do_not_apply_revocation, @do_not_scramble)"; + + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.AddWithValue("@running_status", NpgsqlDbType.Integer, (int)eitEvent.RunningStatus); + command.Parameters.AddWithValue("@free_ca", NpgsqlDbType.Boolean, eitEvent.FreeCa); + command.Parameters.AddWithValue("@iso_639_language_code", NpgsqlDbType.Varchar, eitEvent.Iso639LanguageCode); + command.Parameters.AddWithValue("@event_name", NpgsqlDbType.Text, eitEvent.EventName.TrimJunk()); + command.Parameters.AddWithValue("@text", NpgsqlDbType.Text, eitEvent.Text.TrimJunk()); + + + if (eitEvent.ExtendedText != null) + { + StringBuilder sb = new StringBuilder(); + foreach (string s in eitEvent.ExtendedText) + if (s != null) + sb.Append(s.Trim('\0')); + + + command.Parameters.AddWithValue("@extended_text", NpgsqlDbType.Text, sb.ToString().TrimJunk()); + } + else + { + command.Parameters.AddWithValue("@extended_text", NpgsqlDbType.Text, DBNull.Value); + } + + command.Parameters.AddWithValue("@pdc", NpgsqlDbType.Timestamp, eitEvent.Pdc); + + if (eitEvent.PrivateDataSpecifier.HasValue) + command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, (long)eitEvent.PrivateDataSpecifier); + else + command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, DBNull.Value); + + command.Parameters.AddWithValue("@vps_string", NpgsqlDbType.Text, eitEvent.VpsString); + + if (eitEvent.ReferenceServiceId.HasValue) + command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, eitEvent.ReferenceServiceId); + else + command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, DBNull.Value); + + if (eitEvent.ReferenceEventId.HasValue) + command.Parameters.AddWithValue("@reference_event_id", NpgsqlDbType.Integer, eitEvent.ReferenceEventId); + else + command.Parameters.AddWithValue("@reference_event_id", NpgsqlDbType.Integer, DBNull.Value); + + if (eitEvent.ControlRemoteAccessOverInternet.HasValue) + command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, eitEvent.ControlRemoteAccessOverInternet); + else + command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, DBNull.Value); + + if (eitEvent.DoNotApplyRevocation.HasValue) + command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, eitEvent.DoNotApplyRevocation); + else + command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, DBNull.Value); + + if (eitEvent.DoNotScramble.HasValue) + command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, eitEvent.DoNotScramble); + else + command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, DBNull.Value); + + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + WriteEitCaSystems(conn, eitEvent); + WriteEitComponents(conn, eitEvent); + WriteEitContent(conn, eitEvent); + WriteEitCrids(conn, eitEvent); + WriteEitItems(conn, eitEvent); + WriteEitLinkages(conn, eitEvent); + WriteEitParentalRatings(conn, eitEvent); + } + + private void WriteEitParentalRatings(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.ParentalRatings == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_eit_parental_ratings " + + "(sid,tsid,onid,event_id,start_time,ordinal,k,v) " + + "VALUES " + + "(@sid,@tsid,@onid,@event_id,@start_time,@ordinal,@k,@v)"; + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@k", NpgsqlDbType.Varchar); + command.Parameters.Add("@v", NpgsqlDbType.Integer); + for (int i = 0; i < eitEvent.ParentalRatings.Length; i++) + { + if (eitEvent.ParentalRatings[i] == null) + continue; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@k"].Value = eitEvent.ParentalRatings[i].Item1; + command.Parameters["@v"].Value = eitEvent.ParentalRatings[i].Item2; + command.ExecuteNonQuery(); + } + } + + private void WriteEitLinkages(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.Linkages == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_eit_linkages (sid, tsid, onid, event_id, start_time, ordinal, l_tsid, l_onid, l_sid, linkage_type, handover_type, handover_origin_type, handover_network_id, handover_initial_service_id, target_event_id, target_event_listed, target_event_simulcasted, table_type, private_data_bytes, bouquet_id) " + + "values " + + "(@sid,@tsid,@onid,@event_id,@start_time,@ordinal,@l_tsid,@l_onid,@l_sid,@linkage_type,@handover_type,@handover_origin_type,@handover_network_id,@handover_initial_service_id,@target_event_id,@target_event_listed,@target_event_simulcasted,@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddParameter("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddParameter("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@l_tsid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_onid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_sid", NpgsqlDbType.Integer); + command.Parameters.Add("@linkage_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_origin_type", NpgsqlDbType.Boolean); + command.Parameters.Add("@handover_network_id", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_initial_service_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_listed", NpgsqlDbType.Boolean); + command.Parameters.Add("@target_event_simulcasted", NpgsqlDbType.Boolean); + //@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.Add("@table_type", NpgsqlDbType.Integer); + command.Parameters.Add("@private_data_bytes", NpgsqlDbType.Bytea); + command.Parameters.Add("@bouquet_id", NpgsqlDbType.Integer); + + for (int i = 0; i < eitEvent.Linkages.Count; i++) + { + LinkageDescriptor linkage = eitEvent.Linkages[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@l_tsid"].Value = (int)linkage.TransportStreamId; + command.Parameters["@l_onid"].Value = (int)linkage.OriginalNetworkId; + command.Parameters["@l_sid"].Value = (int)linkage.ServiceId; + command.Parameters["@linkage_type"].Value = (int)linkage.LinkageType; + command.Parameters["@handover_type"].Value = linkage.HandoverType.HasValue ? (int)linkage.HandoverType.Value : DBNull.Value; + command.Parameters["@handover_origin_type"].Value = linkage.HandoverOriginType.HasValue ? linkage.HandoverOriginType.Value : DBNull.Value; + command.Parameters["@handover_network_id"].Value = linkage.HandoverNetworkId.HasValue ? (int)linkage.HandoverNetworkId.Value : DBNull.Value; + command.Parameters["@handover_initial_service_id"].Value = linkage.HandoverInitialServiceId.HasValue ? (int)linkage.HandoverInitialServiceId.Value : DBNull.Value; + command.Parameters["@target_event_id"].Value = linkage.TargetEventId.HasValue ? (int)linkage.TargetEventId.Value : DBNull.Value; + command.Parameters["@target_event_listed"].Value = linkage.TargetEventListed.HasValue ? linkage.TargetEventListed.Value : DBNull.Value; + command.Parameters["@target_event_simulcasted"].Value = linkage.TargetEventSimulcasted.HasValue ? linkage.TargetEventSimulcasted.Value : DBNull.Value; + command.Parameters["@table_type"].Value = linkage.TableType.HasValue ? (int)linkage.TableType.Value : DBNull.Value; + command.Parameters["@private_data_bytes"].Value = linkage.PrivateDataBytes != null ? linkage.PrivateDataBytes : DBNull.Value; + command.Parameters["@bouquet_id"].Value = linkage.BouquetId.HasValue ? (int)linkage.BouquetId.Value : DBNull.Value; + command.ExecuteNonQuery(); + + WriteEitLinkageExtendedEventLinkages(conn, eitEvent, i); + WriteEitLinkageIpmacLinkages(conn, eitEvent, i); + WriteEitLinkageSsuLinkStructure(conn, eitEvent, i); + } + } + + private void WriteEitLinkageSsuLinkStructure(NpgsqlConnection conn, EitEvent eitEvent, int i) + { + if (eitEvent.Linkages[i].SystemSoftwareUpdateLinkStructure == null) + return; + + for (int j = 0; j < eitEvent.Linkages[i].SystemSoftwareUpdateLinkStructure.Count; j++) + { + LinkageDescriptor.OuiPrivateData ssuLinkStruct = eitEvent.Linkages[i].SystemSoftwareUpdateLinkStructure[j]; + throw new NotImplementedException(); + } + } + + private void WriteEitLinkageIpmacLinkages(NpgsqlConnection conn, EitEvent eitEvent, int i) + { + if (eitEvent.Linkages[i].IpMacNotificationLinkages == null) + return; + + for (int j = 0; i < eitEvent.Linkages[i].IpMacNotificationLinkages.Count; j++) + { + LinkageDescriptor.IpMacNotificationLinkage ipMacNotificationLinkage = eitEvent.Linkages[i].IpMacNotificationLinkages[j]; + throw new NotImplementedException(); + } + } + + private void WriteEitLinkageExtendedEventLinkages(NpgsqlConnection conn, EitEvent eitEvent, int i) + { + if (eitEvent.Linkages[i].ExtendedEventLinkages == null) + return; + + for (int j = 0; i < eitEvent.Linkages[i].ExtendedEventLinkages.Length; j++) + { + LinkageDescriptor.ExtendedEventLinkageInfo extendedEventLinkage = eitEvent.Linkages[i].ExtendedEventLinkages[j]; + throw new NotImplementedException(); + } + } + + private void WriteEitItems(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.Items == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_eit_items (sid, tsid, onid, event_id, start_time, description, item, ordinal) " + + "VALUES (@sid, @tsid, @onid, @event_id, @start_time, @description, @item, @ordinal)"; + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, eitEvent.ServiceId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, eitEvent.TransportStreamId); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, eitEvent.OriginalNetworkId); + command.Parameters.AddParameter("@event_id", NpgsqlDbType.Integer, eitEvent.EventId); + command.Parameters.AddParameter("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.Add("@description", NpgsqlDbType.Text); + command.Parameters.Add("@item", NpgsqlDbType.Text); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + for (int i = 0; i < eitEvent.Items.Count; i++) + { + EitEvent.EitEventItem item = eitEvent.Items[i]; + command.Parameters["@description"].Value = item.Description; + command.Parameters["@item"].Value = item.Item; + command.Parameters["@ordinal"].Value = i; + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + } + command.Dispose(); + } + + private void WriteEitCrids(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.Crids == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_eit_crids " + + "(sid, tsid, onid, event_id, start_time, ordinal, crid_type, crid_location, crid_bytes, crid_ref) " + + "VALUES " + + "(@sid, @tsid, @onid, @event_id, @start_time, @ordinal, @crid_type, @crid_location, @crid_bytes, @crid_ref)"; + + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddParameter("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddParameter("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@crid_type", NpgsqlDbType.Integer); + command.Parameters.Add("@crid_location", NpgsqlDbType.Integer); + command.Parameters.Add("@crid_bytes", NpgsqlDbType.Bytea); + command.Parameters.Add("@crid_ref", NpgsqlDbType.Integer); + int ordinal = 0; + foreach (ContentIdentifierDescriptor.Crid eitEventCrid in eitEvent.Crids) + { + command.Parameters["@ordinal"].Value = ordinal++; + command.Parameters["@crid_type"].Value = (int)eitEventCrid.CridType; + command.Parameters["@crid_location"].Value = (int)eitEventCrid.CridLocation; + command.Parameters["@crid_bytes"].Value = eitEventCrid.CridBytes; + command.Parameters["@crid_ref"].Value = (int?)eitEventCrid.CridRef; + command.Parameters.CheckNulls(); + } + } + + private void WriteEitContent(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.Content == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_eit_content " + + "(sid,tsid,onid,event_id,start_time,ordinal,l,m,r) " + + "VALUES " + + "(@sid,@tsid,@onid,@event_id,@start_time,@ordinal,@l,@m,@r)"; + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@l", NpgsqlDbType.Integer); + command.Parameters.Add("@m", NpgsqlDbType.Integer); + command.Parameters.Add("@r", NpgsqlDbType.Integer); + for (int i = 0; i < eitEvent.Content.Length; i++) + { + command.Parameters["@ordinal"].Value = i; + command.Parameters["@l"].Value = eitEvent.Content[i].Item1; + command.Parameters["@m"].Value = eitEvent.Content[i].Item2; + command.Parameters["@r"].Value = eitEvent.Content[i].Item3; + command.ExecuteNonQuery(); + } + } + + private void WriteEitComponents(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.Components == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_eit_components " + + "(sid, tsid, onid, event_id, start_time, ordinal, stream_content_ext, stream_content, component_type, component_tag, iso_639_language_code, text) " + + "VALUES " + + "(@sid, @tsid, @onid, @event_id, @start_time, @ordinal, @stream_content_ext, @stream_content, @component_type, @component_tag, @iso_639_language_code, @text)"; + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@stream_content_ext", NpgsqlDbType.Integer); + command.Parameters.Add("@stream_content", NpgsqlDbType.Integer); + command.Parameters.Add("@component_type", NpgsqlDbType.Integer); + command.Parameters.Add("@component_tag", NpgsqlDbType.Integer); + command.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar); + command.Parameters.Add("@text", NpgsqlDbType.Text); + int ordinal = 0; + foreach (ComponentDescriptor component in eitEvent.Components) + { + command.Parameters["@ordinal"].Value = ordinal++; + command.Parameters["@stream_content_ext"].Value = component.StreamContentExt; + command.Parameters["@stream_content"].Value = component.StreamContent; + command.Parameters["@component_type"].Value = component.ComponentType; + command.Parameters["@component_tag"].Value = component.ComponentTag; + command.Parameters["@iso_639_language_code"].Value = component.Iso639LanguageCode; + string text = component.Text; + if (text != null) + text = component.Text.Trim('\0'); + command.Parameters["@text"].Value = text; + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + } + } + + private void WriteEitCaSystems(NpgsqlConnection conn, EitEvent eitEvent) + { + if (eitEvent.CaSystems == null) + return; + + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "insert into dvb_eit_ca_systems (sid, tsid, onid, event_id, start_time, ordinal, caid)\r\nvalues (@sid, @tsid, @onid, @event_id, @start_time, @ordinal, @caid)"; + cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + cmd.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + cmd.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + cmd.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + cmd.Parameters.Add("@caid", NpgsqlDbType.Integer); + for (int i = 0; i < eitEvent.CaSystems.Length; i++) + { + cmd.Parameters["@ordinal"].Value = i; + cmd.Parameters["@caid"].Value = (int)eitEvent.CaSystems[i]; + cmd.ExecuteNonQuery(); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/ImportFiles.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/ImportFiles.cs new file mode 100644 index 0000000..aacffbe --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/ImportFiles.cs @@ -0,0 +1,71 @@ +using Npgsql; +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public bool ImportFileKnown(FileInfo fi) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM skyscraper5_jobs_files_imported WHERE path = @path"; + command.Parameters.AddWithValue("@path", NpgsqlDbType.Text, fi.ToString()); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + return result; + } + } + + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan duration, int tstype) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO skyscraper5_jobs_files_imported (path,duration,tstype) VALUES (@path,@duration,@tstype)"; + command.Parameters.AddWithValue("@path", NpgsqlDbType.Text, fi.ToString()); + command.Parameters.AddWithValue("@duration", NpgsqlDbType.Double, duration.TotalSeconds); + command.Parameters.AddWithValue("@tstype", NpgsqlDbType.Integer, tstype); + int executeNonQuery = command.ExecuteNonQuery(); + if (executeNonQuery != 1) + throw new DataException(String.Format("Expect to insert 1 line, but did {0}", executeNonQuery)); + command.Dispose(); + connection.Close(); + } + } + + public IReadOnlyList ListImportFileByTag1(int tag1) + { + List result = new List(); + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT path FROM skyscraper5_jobs_files_imported WHERE tag1 = @tag"; + command.Parameters.AddWithValue("@tag", NpgsqlDbType.Integer, tag1); + NpgsqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + result.Add(dataReader.GetString(0)); + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Int.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Int.cs new file mode 100644 index 0000000..d2a178c --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Int.cs @@ -0,0 +1,82 @@ +using Npgsql; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _knownInts; + + public bool TestForIpMacNotification(IpMacNotification notification) + { + if (_knownInts == null) + _knownInts = new HashSet(); + + DatabaseKeyInt key = new DatabaseKeyInt(notification.PlatformId); + if (_knownInts.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT dateadded FROM dvb_int WHERE platform_id = @id"; + cmd.Parameters.AddWithValue("@id", NpgsqlTypes.NpgsqlDbType.Bigint, (long)notification.PlatformId); + NpgsqlDataReader reader = cmd.ExecuteReader(); + result = reader.Read(); + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + if (result) + _knownInts.Add(key); + return result; + } + + public void StoreIpMacNotification(IpMacNotification notification) + { + EnqueueTask(x => WriteIpMacNotification(x, notification)); + DatabaseKeyInt key = new DatabaseKeyInt(notification.PlatformId); + _knownInts.Add(key); + } + + private void WriteIpMacNotification(NpgsqlConnection conn, IpMacNotification notification) + { + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "insert into dvb_int (platform_id, platform_name, platform_provider_name,\r\n platform_provider_name_language_code, platform_action_type, platform_name_language_code,\r\n platform_processing_order, target_type, target_name, operational_network_id,\r\n operational_mpe_fec_algorithm, operational_component_tag, operational_transport_stream_id,\r\n operational_time_slice_fec_id, operational_time_slicing, operational_onid, operational_sid,\r\n operational_time_slice_frame_size, operational_time_slice_max_average_rate,\r\n operational_time_slice_max_bust_duration) " + + "values " + + "(@platform_id,@platform_name,@platform_provider_name,@platform_provider_name_language_code,@platform_action_type,@platform_name_language_code,@platform_processing_order,@target_type,@target_name,@operational_network_id,@operational_mpe_fec_algorithm, @operational_component_tag, @operational_transport_stream_id,\r\n @operational_time_slice_fec_id, @operational_time_slicing, @operational_onid, @operational_sid,\r\n @operational_time_slice_frame_size, @operational_time_slice_max_average_rate,\r\n @operational_time_slice_max_bust_duration\r\n\r\n )"; + cmd.Parameters.AddWithValue("@platform_id", NpgsqlTypes.NpgsqlDbType.Bigint, (long)notification.PlatformId); + cmd.Parameters.AddWithValue("@platform_name", NpgsqlTypes.NpgsqlDbType.Text, notification.PlatformName); + cmd.Parameters.AddWithValue("@platform_provider_name", NpgsqlTypes.NpgsqlDbType.Text, notification.PlatformProviderName); + cmd.Parameters.AddWithValue("@platform_provider_name_language_code", NpgsqlTypes.NpgsqlDbType.Varchar, notification.PlatformProviderNameLanguageCode); + cmd.Parameters.AddWithValue("@platform_action_type", NpgsqlTypes.NpgsqlDbType.Integer, notification.PlatformActionType); + cmd.Parameters.AddWithValue("@platform_name_language_code", NpgsqlTypes.NpgsqlDbType.Varchar, notification.PlatformNameLanguageCode); + cmd.Parameters.AddWithValue("@platform_processing_order", NpgsqlTypes.NpgsqlDbType.Integer, notification.PlatformProcessingOrder); + cmd.Parameters.AddWithValue("@target_type", NpgsqlTypes.NpgsqlDbType.Integer, notification.TargetType); + cmd.Parameters.AddWithValue("@target_name", NpgsqlTypes.NpgsqlDbType.Text, notification.TargetName); + //@operational_network_id,@operational_mpe_fec_algorithm,@operational_component_tag,@operational_transport_stream_id,@operational_time_slice_fec_id,@operational_time_slicing,@operational_onid,@operational_sid,@operational_time_slice_frame_size, @operational_time_slice_max_average_rate,\r\n @operational_time_slice_max_bust_duration\r\n\r\n )"; + cmd.Parameters.AddWithValue("@operational_network_id", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalNetworkId); + cmd.Parameters.AddWithValue("@operational_mpe_fec_algorithm", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalMpeFecAlgorithm); + cmd.Parameters.AddWithValue("@operational_component_tag", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalComponentTag); + cmd.Parameters.AddWithValue("@operational_transport_stream_id", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalTransportStreamId); + cmd.Parameters.AddWithValue("@operational_time_slice_fec_id", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalTimeSliceFecId); + cmd.Parameters.AddWithValue("@operational_time_slicing", NpgsqlTypes.NpgsqlDbType.Boolean, (bool?)notification.OperationalTimeSlicing); + cmd.Parameters.AddWithValue("@operational_onid", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalOriginalNetworkId); + cmd.Parameters.AddWithValue("@operational_sid", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalServiceId); + //@operational_time_slice_frame_size,@operational_time_slice_max_average_rate,@operational_time_slice_max_bust_duration + cmd.Parameters.AddWithValue("@operational_time_slice_frame_size", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalTimeSliceFrameSize); + cmd.Parameters.AddWithValue("@operational_time_slice_max_average_rate", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalTimeSliceMaxAverageRate); + cmd.Parameters.AddWithValue("@operational_time_slice_max_bust_duration", NpgsqlTypes.NpgsqlDbType.Integer, (int?)notification.OperationalTimeSliceMaxBurstDuration); + cmd.Parameters.CheckNulls(); + cmd.ExecuteNonQuery(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs new file mode 100644 index 0000000..c890798 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs @@ -0,0 +1,962 @@ +using Ionic.Crc; +using Npgsql; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.NetworkInformation; +using System.Security.Cryptography; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private List> _tbtpCache; + private ushort? timNid; + + + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId) + { + timNid = interactiveNetworkId; + if (_tbtpCache == null) + _tbtpCache = new List>(); + Tuple coords = new Tuple(interactiveNetworkId, groupId, logonId); + if (_tbtpCache.Contains(coords)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand npgsqlCommand = connection.CreateCommand(); + npgsqlCommand.CommandText = "SELECT dateadded FROM dvb_ic_tbtp WHERE network_id = @nid AND group_id = @gid AND @logon_id = @lid"; + npgsqlCommand.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + npgsqlCommand.Parameters.AddWithValue("@gid", (long)groupId); + npgsqlCommand.Parameters.AddWithValue("@lid", (long)logonId); + NpgsqlDataReader dataReader = npgsqlCommand.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + npgsqlCommand.Dispose(); + connection.Close(); + } + if (result) + _tbtpCache.Add(coords); + return result; + } + + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp) + { + timNid = interactiveNetworkId; + if (_tbtpCache == null) + _tbtpCache = new List>(); + Tuple coords = new Tuple(interactiveNetworkId, gtoupId, btp.LogonId); + _tbtpCache.Add(coords); + EnqueueTask(x => WriteTbtp(x, interactiveNetworkId, gtoupId, superframeCount, frameNumber, btp)); + + } + + private void WriteTbtp(NpgsqlConnection x, ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp) + { + timNid = interactiveNetworkId; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "insert into dvb_ic_tbtp (network_id, group_id, superframe_count, frame_number, logon_id, assignment_type,\r\n vbdc_queue_empty, start_slot, channel_id, assignment_count)\r\n" + + "values (@nid,@gid,@scount,@fnumber,@lid,@atype,@vqemtpy,@sslot,@cid,@acount);"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + command.Parameters.AddWithValue("@gid", (long)gtoupId); + command.Parameters.AddWithValue("@scount", (long)superframeCount); + command.Parameters.AddWithValue("@fnumber", (long)frameNumber); + command.Parameters.AddWithValue("@lid", (long)btp.LogonId); + command.Parameters.AddWithValue("@atype", (int)btp.AssignmentType); + command.Parameters.AddWithValue("@vqempty", btp.VbdcQueueEmptyFlag); + command.Parameters.AddWithValue("@sslot", (long)btp.StartSlot); + command.Parameters.AddWithValue("@cid", (long?)btp.ChannelId); + command.Parameters.AddWithValue("@acount", (long)btp.AssignmentCount); + command.ExecuteNonQuery(); + } + + private List> _cmtCache; + public bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + timNid = interactiveNetworkId; + if (_cmtCache == null) + _cmtCache = new List>(); + Tuple tuple = new Tuple(interactiveNetworkId, entry.GroupId, entry.LoginId); + if (_cmtCache.Contains(tuple)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_cmt WHERE network_id = @nid AND group_id = @gid AND login_id = @lid"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + command.Parameters.AddWithValue("@gid", (short)entry.GroupId); + command.Parameters.AddWithValue("@lid", (int)entry.LoginId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + if (result) + _cmtCache.Add(tuple); + return result; + } + + public void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + timNid = interactiveNetworkId; + if (_cmtCache == null) + _cmtCache = new List>(); + Tuple tuple = new Tuple(interactiveNetworkId, entry.GroupId, entry.LoginId); + _cmtCache.Add(tuple); + EnqueueTask(x => WriteCmt(x, interactiveNetworkId, entry)); + } + + private void WriteCmt(NpgsqlConnection x, ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + timNid = interactiveNetworkId; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "insert into dvb_ic_cmt (network_id, group_id, login_id, slot_type, burst_time_scaling, burst_time_correction,\r\n power_correction, esn0, frequency_correction)\r\nvalues " + + "(@nid,@gid,@lid,@stype,@btscaling,@btcorrection,@pcorrection,@esn,@fcorrection)"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + command.Parameters.AddWithValue("@gid", entry.GroupId); + command.Parameters.AddWithValue("@lid", (int)entry.LoginId); + command.Parameters.AddWithValue("@stype", (int)entry.SlotType); + command.Parameters.AddWithValue("@btscaling", entry.BurstTimeScaling); + command.Parameters.AddWithValue("@btcorrection", (long?)entry.BurstTimeCorrection); + command.Parameters.AddWithValue("@pcorrection", NpgsqlTypes.NpgsqlDbType.Integer, entry.PowerCorrection); + command.Parameters.AddWithValue("@esn", entry.EsN0); + command.Parameters.AddWithValue("@fcorrection", (int)entry.FrequencyCorrection); + SetNulls(command); + command.ExecuteNonQuery(); + } + + private Dictionary _rmtTransmissionStdCache; + public int GetRmtTransmissionStandard(ushort networkId) + { + timNid = networkId; + if (_rmtTransmissionStdCache == null) + _rmtTransmissionStdCache = new Dictionary(); + if (_rmtTransmissionStdCache.ContainsKey(networkId)) + return _rmtTransmissionStdCache[networkId]; + + int result = 0; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT MAX(f_transmission_standard) FROM dvb_ic_rmt_transport_stream WHERE nid = @nid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)networkId); + NpgsqlDataReader npgsqlDataReader = command.ExecuteReader(); + if (npgsqlDataReader.Read()) + { + if (!npgsqlDataReader.IsDBNull(0)) + { + result = npgsqlDataReader.GetInt32(0); + } + } + npgsqlDataReader.Close(); + command.Dispose(); + connection.Close(); + } + if (result != 0) + _rmtTransmissionStdCache.Add(networkId, result); + return result; + } + + private Dictionary _tmstCache; + public byte[] GetTmst(ushort interactiveNetworkId) + { + timNid = interactiveNetworkId; + if (_tmstCache == null) + _tmstCache = new Dictionary(); + if (_tmstCache.ContainsKey(interactiveNetworkId)) + return _tmstCache[interactiveNetworkId]; + + byte[] result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT supported FROM dvb_ic_tmst WHERE network_id = @nid"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + NpgsqlDataReader npgsqlDataReader = command.ExecuteReader(); + if (npgsqlDataReader.Read()) + result = npgsqlDataReader.GetByteArray(0); + else + result = null; + npgsqlDataReader.Close(); + command.Dispose(); + connection.Close(); + } + _tmstCache.Add(interactiveNetworkId, result); + return result; + } + + public void InsertTmst(ushort interactiveNetworkId, byte[] modes) + { + timNid = interactiveNetworkId; + if (_tmstCache == null) + _tmstCache = new Dictionary(); + if (_tmstCache.ContainsKey(interactiveNetworkId)) + if (_tmstCache[interactiveNetworkId] == null) + _tmstCache.Remove(interactiveNetworkId); + _tmstCache.Add(interactiveNetworkId, modes); + EnqueueTask(x => InsertTmstEx(x, interactiveNetworkId, modes)); + } + + private void InsertTmstEx(NpgsqlConnection x, ushort interactiveNetworkId, byte[] modes) + { + timNid = interactiveNetworkId; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "insert into dvb_ic_tmst (network_id, supported ) values (@nid,@data)"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + command.Parameters.AddWithValue("@data", modes); + int a = command.ExecuteNonQuery(); + command.Dispose(); + } + + public void UpdateTmst(ushort interactiveNetworkId, byte[] modes) + { + timNid = interactiveNetworkId; + if (_tmstCache == null) + _tmstCache = new Dictionary(); + _tmstCache.Remove(interactiveNetworkId); + _tmstCache.Add(interactiveNetworkId, modes); + EnqueueTask(x => UpdateTmstEx(x, interactiveNetworkId, modes)); + } + + private void UpdateTmstEx(NpgsqlConnection x, ushort interactiveNetworkId, byte[] modes) + { + timNid = interactiveNetworkId; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "UPDATE dvb_ic_tmst\r\nSET supported = @data, version = version + 1, dateupdated = CURRENT_TIMESTAMP\r\nWHERE network_id = @nid"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + command.Parameters.AddWithValue("@data", modes); + int a = command.ExecuteNonQuery(); + command.Dispose(); + } + + + private HashSet _rmtLinkageCache; + public bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + if (_rmtLinkageCache == null) + _rmtLinkageCache = new HashSet(); + DatabaseKeyRmtLinkage key = linkage.ToKey(); + if (_rmtLinkageCache.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_rmt_linkage WHERE tsid=@tsid AND onid=@onid AND sid=@sid AND inid=@inid"; + command.Parameters.AddWithValue("@tsid", (int)linkage.TransportStreamId); + command.Parameters.AddWithValue("@onid", (int)linkage.OriginalNetworkId); + command.Parameters.AddWithValue("@sid", (int)linkage.ServiceId); + command.Parameters.AddWithValue("@inid", (int)linkage.InteractiveNetworkId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + if (result) + _rmtLinkageCache.Add(key); + return result; + } + + public void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + if (_rmtLinkageCache == null) + _rmtLinkageCache = new HashSet(); + DatabaseKeyRmtLinkage key = linkage.ToKey(); + if (_rmtLinkageCache.Contains(key)) + return; + _rmtLinkageCache.Add(key); + EnqueueTask(x => InsertRmtLinkageEx(x, linkage)); + } + + private void InsertRmtLinkageEx(NpgsqlConnection x, _0x4a_LinkageDescriptor linkage) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "insert into dvb_ic_rmt_linkage (tsid, onid, sid, linkage_type, inid, private_data) values (@tsid,@onid,@sid,@ltype,@inid,@pdata) returning uuid"; + command.Parameters.AddWithValue("@tsid", (int)linkage.TransportStreamId); + command.Parameters.AddWithValue("@onid", (int)linkage.OriginalNetworkId); + command.Parameters.AddWithValue("@sid", (int)linkage.ServiceId); + command.Parameters.AddWithValue("@ltype", (int)linkage.LinkageType); + command.Parameters.AddWithValue("@inid", (int)linkage.InteractiveNetworkId); + command.Parameters.AddWithValue("@pdata", linkage.PrivateData); + if (linkage.PopulationIds.Length > 0) + { + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + object o = dataReader.GetValue(0); + dataReader.Close(); + command.Dispose(); + Guid parent = (Guid)o; + foreach (_0x4a_LinkageDescriptor.Population population in linkage.PopulationIds) + { + InsertRmtLinkagePopulation(x, parent, population); + } + } + else + { + command.ExecuteNonQuery(); + command.Dispose(); + } + } + + private void InsertRmtLinkagePopulation(NpgsqlConnection x, Guid parent, _0x4a_LinkageDescriptor.Population population) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "insert into dvb_ic_rmt_linkage_populations (parent, base, mask) values (@parent,@base,@mask)"; + command.Parameters.AddWithValue("@parent", NpgsqlTypes.NpgsqlDbType.Uuid, parent); + command.Parameters.AddWithValue("@base", NpgsqlTypes.NpgsqlDbType.Integer, (int)population.Base); + command.Parameters.AddWithValue("@mask", NpgsqlTypes.NpgsqlDbType.Integer, (int)population.Mask); + command.ExecuteNonQuery(); + } + + private HashSet _rmtTransportStreamCache; + public bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + timNid = networkId; + if (_rmtTransportStreamCache == null) + _rmtTransportStreamCache = new HashSet(); + DatabaseKeyRmtTransportStream key = transportStream.ToKey(networkId); + if (_rmtTransportStreamCache.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_rmt_transport_stream WHERE nid=@nid AND tsid=@tsid AND onid=@onid"; + command.Parameters.AddWithValue("@nid", (int)networkId); + command.Parameters.AddWithValue("@tsid", (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@onid", (int)transportStream.OriginalNetworkId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + if (result) + _rmtTransportStreamCache.Add(key); + return result; + } + + public void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + timNid = networkId; + if (_rmtTransportStreamCache == null) + _rmtTransportStreamCache = new HashSet(); + DatabaseKeyRmtTransportStream key = transportStream.ToKey(networkId); + if (_rmtTransportStreamCache.Contains(key)) + return; + _rmtTransportStreamCache.Add(key); + EnqueueTask(x => InsertRmtTransportStreamEx(x, networkId, transportStream)); + } + + private void InsertRmtTransportStreamEx(NpgsqlConnection x, ushort networkId, Rmt.TransportStream transportStream) + { + timNid = networkId; + _0xa9_SatelliteReturnLinkDescriptor r = transportStream.SatelliteReturnLink; + _0xa8_SatelliteForwardLinkDescriptor f = transportStream.SatelliteForwardLink; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ic_rmt_transport_stream (nid, tsid, onid, r_satellite_id, r_beam_id, r_gateway_id, r_orbital_position, r_cardinal_direction, r_superframe_id," + + "r_tx_frequency_offset, r_private_data, f_satellite_id, f_beam_id, f_ncc_id, f_link_usage, f_local_link_id, f_frequency, f_orbital_position, f_cardinal_direction, f_polarization, " + + "f_transmission_standard, f_scrambling_sequence_selector, f_roll_off, f_symbol_rate, f_fec_inner, f_input_stream_identifier, f_spreading_code_selector, f_scrambling_sequence_index, " + + "f_private_data_bytes) VALUES " + + "(@nid,@tsid,@onid,@rsid,@rbid,@rgid,@roposition,@rcdirection,@rsfid,@rtxfoffset,@rpdata,@fsid,@fbid,@fnccid,@flusage,@fllid,@ffrequency,@foposition,@fcdirection,@fpolarization,@ftstandard," + + "@fssselector,@froff,@fsrate,@ffecinner,@fisidentifier,@fscselector,@fssindex,@fprbytes)"; + command.Parameters.AddWithValue("@nid", (int)networkId); + command.Parameters.AddWithValue("@tsid", (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@onid", (int)transportStream.OriginalNetworkId); + command.Parameters.Add("@rsid", NpgsqlTypes.NpgsqlDbType.Integer); + command.Parameters.Add("@rbid", NpgsqlTypes.NpgsqlDbType.Integer); + command.Parameters.Add("@rgid", NpgsqlTypes.NpgsqlDbType.Integer); + command.Parameters.Add("@roposition", NpgsqlTypes.NpgsqlDbType.Integer); + command.Parameters.Add("@rcdirection", NpgsqlTypes.NpgsqlDbType.Boolean); + command.Parameters.Add("@rsfid", NpgsqlTypes.NpgsqlDbType.Integer); + command.Parameters.Add("@rtxfoffset", NpgsqlTypes.NpgsqlDbType.Bigint); + command.Parameters.Add("@rpdata", NpgsqlTypes.NpgsqlDbType.Bytea); + if (r != null) + { + command.Parameters["@rsid"].Value = (int)r.SatelliteId; + command.Parameters["@rbid"].Value = (int)r.BeamId; + command.Parameters["@rgid"].Value = (int)r.GatewayId; + command.Parameters["@roposition"].Value = (int)r.OrbitalPosition; + command.Parameters["@rcdirection"].Value = r.Eastern; + command.Parameters["@rsfid"].Value = (int)r.SuperframeId; + command.Parameters["@rtxfoffset"].Value = (long)r.TxFrequencyOffset; + command.Parameters["@rpdata"].Value = r.PrivateDataBytes; + } + command.Parameters.AddWithValue("@fsid", (int)f.SatelliteId); + command.Parameters.AddWithValue("@fbid", (int)f.BeamId); + command.Parameters.AddWithValue("@fnccid", (int)f.NccId); + command.Parameters.AddWithValue("@flusage", (int)f.LinkUsage); + command.Parameters.AddWithValue("@fllid", (int)f.LocalLinkId); + command.Parameters.AddWithValue("@ffrequency", (long)f.Frequency); + command.Parameters.AddWithValue("@foposition", (int)f.OrbitalPosition); + command.Parameters.AddWithValue("@fcdirection", f.East); + command.Parameters.AddWithValue("@fpolarization", (int)f.Polarization); + command.Parameters.AddWithValue("@ftstandard", (int)f.TransmissionStandard); + command.Parameters.AddWithValue("@fssselector", NpgsqlTypes.NpgsqlDbType.Boolean, f.ScramblingSequenceSelector); + command.Parameters.AddWithValue("@froff", NpgsqlTypes.NpgsqlDbType.Double, f.RollOff); + command.Parameters.AddWithValue("@fsrate", (int)f.SymbolRate); + command.Parameters.AddWithValue("@ffecinner", NpgsqlTypes.NpgsqlDbType.Integer, (int?)f.FecInner); + command.Parameters.AddWithValue("@fisidentifier", NpgsqlTypes.NpgsqlDbType.Integer, (int?)f.InputStreamIndentifier); + command.Parameters.AddWithValue("@fscselector", NpgsqlTypes.NpgsqlDbType.Integer, f.SpreadingCodeSelector); + command.Parameters.AddWithValue("@fssindex", f.ScramblingSequenceIndex); + command.Parameters.AddWithValue("@fprbytes", f.PrivateDataBytes); + SetNulls(command); + command.ExecuteNonQuery(); + } + + private HashSet> _sctCache; + public bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + timNid = interactiveNetworkId; + if (_sctCache == null) + _sctCache = new HashSet>(); + Tuple key = new Tuple(interactiveNetworkId, superframe.SuperframeId); + if (_sctCache.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_sct WHERE nid = @nid AND superframe_id = @sframeid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)interactiveNetworkId); + command.Parameters.AddWithValue("@sframeid", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + if (result) + _sctCache.Add(key); + return result; + } + + public void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + timNid = interactiveNetworkId; + if (_sctCache == null) + _sctCache = new HashSet>(); + Tuple key = new Tuple(interactiveNetworkId, superframe.SuperframeId); + if (_sctCache.Contains(key)) + return; + EnqueueTask(x => StoreSuperframeCompositionEx(x, interactiveNetworkId, superframe)); + } + + private void StoreSuperframeCompositionEx(NpgsqlConnection x, ushort interactiveNetworkId, Sct.Superframe superframe) + { + timNid = interactiveNetworkId; + + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_sct WHERE nid = @nid AND superframe_id = @sframeid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)interactiveNetworkId); + command.Parameters.AddWithValue("@sframeid", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool alreadyExists = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + if (alreadyExists) + return; + + command = x.CreateCommand(); + command.CommandText = "insert into dvb_ic_sct (nid, superframe_id, large_timing_uncertaintiy_flag, uplink_polarization, superframe_start_time_base, superframe_start_time_ext, " + + "superframe_duration, superframe_centre_frequency, superframe_counter) values (@nid,@sid,@ltuflag,@upolarization,@sstbase,@sstext,@sduration,@scfrequency,@scounter) RETURNING uuid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)interactiveNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeId); + command.Parameters.AddWithValue("@ltuflag", NpgsqlTypes.NpgsqlDbType.Boolean, superframe.LargeTimingUncertainityFlag); + command.Parameters.AddWithValue("@upolarization", NpgsqlTypes.NpgsqlDbType.Integer, (int)superframe.UplinkPolarization); + command.Parameters.AddWithValue("@sstbase", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeStartTimeBase); + command.Parameters.AddWithValue("@sstext", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeStartTimeExt); + command.Parameters.AddWithValue("@sduration", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeDuration); + command.Parameters.AddWithValue("@scfrequency", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeCentreFrequency); + command.Parameters.AddWithValue("@scounter", NpgsqlTypes.NpgsqlDbType.Bigint, (long)superframe.SuperframeCounter); + dataReader = command.ExecuteReader(); + dataReader.Read(); + Guid parent = dataReader.GetGuid(0); + dataReader.Close(); + command.Dispose(); + for (int i = 0; i < superframe.Frames.Length; i++) + { + StoreSuperframeFrame(x, parent, superframe.Frames[i], i); + } + } + + private void StoreSuperframeFrame(NpgsqlConnection x, Guid parent, Sct.Superframe.Frame frame, int ordinal) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ic_sct_frame (parent, frame_id, frame_start_time, frame_centre_frequency_offset, ordinal) " + + "VALUES (@parent,@fid,@fstime,@fcfoffset,@ordinal)"; + command.Parameters.AddWithValue("@parent", NpgsqlTypes.NpgsqlDbType.Uuid, parent); + command.Parameters.AddWithValue("@fid", NpgsqlTypes.NpgsqlDbType.Bigint, (long)frame.FrameId); + command.Parameters.AddWithValue("@fstime", NpgsqlTypes.NpgsqlDbType.Bigint, (long)frame.FrameStartTime); + command.Parameters.AddWithValue("@fcfoffset", NpgsqlTypes.NpgsqlDbType.Bigint, (long)frame.FrameCentreFrequencyOffset); + command.Parameters.AddWithValue("@ordinal", NpgsqlTypes.NpgsqlDbType.Integer, ordinal); + command.ExecuteNonQuery(); + } + + private HashSet> _fctCache; + public bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame) + { + timNid = interactiveNetworkId; + if (_fctCache == null) + _fctCache = new HashSet>(); + Tuple key = new Tuple((int)interactiveNetworkId, (int)frame.FrameId); + if (_fctCache.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_fct WHERE nid = @nid AND frame_id = @fid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)interactiveNetworkId); + command.Parameters.AddWithValue("@fid", NpgsqlTypes.NpgsqlDbType.Integer, (int)frame.FrameId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + if (result) + _fctCache.Add(key); + return result; + } + + public void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame) + { + timNid = interactiveNetworkId; + if (_fctCache == null) + _fctCache = new HashSet>(); + Tuple key = new Tuple((int)interactiveNetworkId, (int)frame.FrameId); + if (_fctCache.Contains(key)) + return; + _fctCache.Add(key); + EnqueueTask(x => InsertFctFrameEx(x, interactiveNetworkId, frame)); + } + + private void InsertFctFrameEx(NpgsqlConnection x, ushort interactiveNetworkId, Fct.Frame frame) + { + timNid = interactiveNetworkId; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ic_fct (nid, frame_id, frame_duration, total_timeslot_count, start_timeslot_number)" + + "VALUES (@nid,@fid,@fduration,@ttcount,@stnumber)RETURNING uuid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)interactiveNetworkId); + command.Parameters.AddWithValue("@fid", NpgsqlTypes.NpgsqlDbType.Integer, (int)frame.FrameId); + command.Parameters.AddWithValue("@fduration", NpgsqlTypes.NpgsqlDbType.Bigint, (long)frame.FrameDuration); + command.Parameters.AddWithValue("@ttcount", NpgsqlTypes.NpgsqlDbType.Integer, (int)frame.TotalTimeslotCount); + command.Parameters.AddWithValue("@stnumber", NpgsqlTypes.NpgsqlDbType.Integer, (int)frame.StartTimeslotNumber); + NpgsqlDataReader dataReader = command.ExecuteReader(); + dataReader.Read(); + Guid guid = dataReader.GetGuid(0); + dataReader.Close(); + command.Dispose(); + for (int i = 0; i < frame.Timeslots.Length; i++) + { + InsertFctFrameTimeslot(x, guid, frame.Timeslots[i], i); + } + } + + private void InsertFctFrameTimeslot(NpgsqlConnection x, Guid guid, Fct.Frame.Timeslot timeslot, int ordinal) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ic_fct_timeslot (parent,timeslot_id,timeslot_frequency_offset,timeslot_time_offset, repeat_count, ordinal) " + + "VALUES (@parent,@tid,@tfoffset,@ttoffset,@rcount,@ordinal)"; + command.Parameters.AddWithValue("@parent", NpgsqlTypes.NpgsqlDbType.Uuid, guid); + command.Parameters.AddWithValue("@tid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timeslot.TimeslotId); + command.Parameters.AddWithValue("@tfoffset", NpgsqlTypes.NpgsqlDbType.Bigint, (long)timeslot.TimeslotFrequencyOffset); + command.Parameters.AddWithValue("@ttoffset", NpgsqlTypes.NpgsqlDbType.Bigint, (long)timeslot.TimeslotTimeOffset); + command.Parameters.AddWithValue("@rcount", NpgsqlTypes.NpgsqlDbType.Integer, (int)timeslot.RepeatCount); + command.Parameters.AddWithValue("@ordinal", NpgsqlTypes.NpgsqlDbType.Integer, ordinal); + command.ExecuteNonQuery(); + } + + private HashSet> _sptCache; + public bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + timNid = interactiveNetworkId; + if (_sptCache == null) + _sptCache = new HashSet>(); + Tuple key = new Tuple(interactiveNetworkId, satellite.Id); + if (_sptCache.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_spt WHERE network_id = @nid AND satellite_id = @sid"; + command.Parameters.AddWithValue("@nid", (int)interactiveNetworkId); + command.Parameters.AddWithValue("@sid", (int)satellite.Id); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + _sptCache.Add(key); + return result; + } + + public void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + timNid = interactiveNetworkId; + if (_sptCache == null) + _sptCache = new HashSet>(); + Tuple key = new Tuple(interactiveNetworkId, satellite.Id); + if (_sptCache.Contains(key)) + return; + _sptCache.Add(key); + EnqueueTask(x => StoreSatellitePositionEx(x, interactiveNetworkId, satellite)); + } + + private void StoreSatellitePositionEx(NpgsqlConnection x, ushort interactiveNetworkId, Spt.Satellite satellite) + { + timNid = interactiveNetworkId; + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ic_spt (network_id, satellite_id, x, y, z) VALUES (@nid,@sid,@x,@y,@z)"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)interactiveNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlTypes.NpgsqlDbType.Integer, (int)satellite.Id); + command.Parameters.AddWithValue("@x", NpgsqlTypes.NpgsqlDbType.Bigint, (long)satellite.X); + command.Parameters.AddWithValue("@y", NpgsqlTypes.NpgsqlDbType.Bigint, (long)satellite.Y); + command.Parameters.AddWithValue("@z", NpgsqlTypes.NpgsqlDbType.Bigint, (long)satellite.Z); + command.ExecuteNonQuery(); + } + + public void CreateTim(PhysicalAddress mac) + { + if (timNid == null) + return; + if (_tims == null) + _tims = new List(); + if (_tims.Contains(mac)) + return; + + _tims.Add(mac); + EnqueueTask(x => CreateTimEx(x, mac, timNid.Value)); + } + + private void CreateTimEx(NpgsqlConnection x, PhysicalAddress mac, ushort nid) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "INSERT INTO dvb_ic_tim (mac,nid) VALUES (@mac,@nid)"; + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)nid); + command.ExecuteNonQuery(); + } + + private List _tims; + public bool TestForTim(PhysicalAddress mac) + { + if (timNid == null) + return true; + if (_tims == null) + _tims = new List(); + if (_tims.Contains(mac)) + return true; + + bool result; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_ic_tim WHERE mac = @mac AND nid = @nid"; + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + if (result) + _tims.Add(mac); + return result; + } + + private List _correctedTims; + public bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + if (timNid == null) + return false; + if (_correctedTims == null) + _correctedTims = new List(); + if (_correctedTims.Contains(mac)) + return false; + + _correctedTims.Add(mac); + EnqueueTask(x => CorrectTimEx(x, mac, cmd,timNid.Value)); + return true; + } + + private void CorrectTimEx(NpgsqlConnection x, PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmdKnown, ushort nid) + { + _0xa1_CorrectionMessageDescriptor cmdNew = new _0xa1_CorrectionMessageDescriptor(CorrectionMessageSlotType.TRF, 0, null, null, null, 0); + NpgsqlCommand selectCommand = x.CreateCommand(); + selectCommand.CommandText = "SELECT cmd_slot_type, cmd_burst_time_scaling, cmd_burst_time_correction, cmd_power_correction, cmd_esn0, cmd_frequency_correction " + + "FROM dvb_ic_tim " + + "WHERE mac = @mac AND nid = @nid"; + selectCommand.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + selectCommand.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + NpgsqlDataReader dataReader = selectCommand.ExecuteReader(); + if (dataReader.Read()) + { + if (!dataReader.IsDBNull(0) && !dataReader.IsDBNull(1) && !dataReader.IsDBNull(5)) + { + CorrectionMessageSlotType slotType = (CorrectionMessageSlotType)dataReader.GetInt32(0); + int burstTimeScaling = dataReader.GetInt32(1); + uint? burstTimeCorrection = null; + if (!dataReader.IsDBNull(2)) + burstTimeCorrection = (uint)dataReader.GetInt64(2); + int? powerCorrection = null; + if (!dataReader.IsDBNull(3)) + powerCorrection = dataReader.GetInt32(3); + int? esn0 = null; + if (!dataReader.IsDBNull(4)) + esn0 = dataReader.GetInt32(4); + ushort frequencyCorrection = (ushort)dataReader.GetInt32(5); + cmdNew = new _0xa1_CorrectionMessageDescriptor(slotType, burstTimeScaling, burstTimeCorrection, powerCorrection, esn0, frequencyCorrection); + } + } + dataReader.Close(); + selectCommand.Dispose(); + + if (cmdKnown.Equals(cmdNew)) + return; + + NpgsqlCommand updateCommand = x.CreateCommand(); + updateCommand.CommandText = "UPDATE dvb_ic_tim " + + "SET cmd_slot_type = @stype, cmd_burst_time_scaling = @btscaling, cmd_burst_time_correction = @btcorrection, cmd_power_correction = @pcorrection, cmd_esn0 = @cesn, " + + "cmd_frequency_correction = @fcorrection, version = version + 1, dateupdated = CURRENT_TIMESTAMP " + + "WHERE mac = @mac AND nid = @nid"; + updateCommand.Parameters.Add("@stype", NpgsqlTypes.NpgsqlDbType.Integer); + updateCommand.Parameters.Add("@btscaling", NpgsqlTypes.NpgsqlDbType.Integer); + updateCommand.Parameters.Add("@btcorrection", NpgsqlTypes.NpgsqlDbType.Bigint); + updateCommand.Parameters.Add("@pcorrection", NpgsqlTypes.NpgsqlDbType.Integer); + updateCommand.Parameters.Add("@esn", NpgsqlTypes.NpgsqlDbType.Integer); + updateCommand.Parameters.Add("@fcorrection", NpgsqlTypes.NpgsqlDbType.Integer); + updateCommand.Parameters.Add("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr); + updateCommand.Parameters["@stype"].Value = cmdNew.SlotType; + updateCommand.Parameters["@btscaling"].Value = cmdNew.BurstTimeScaling; + updateCommand.Parameters["@btcorrection"].Value = cmdNew.BurstTimeCorrection; + updateCommand.Parameters["@pcorrection"].Value = cmdNew.PowerCorrection; + updateCommand.Parameters["@esn"].Value = cmdNew.EsN0; + updateCommand.Parameters["@fcorrection"].Value = cmdNew.FrequencyCorrection; + updateCommand.Parameters["@mac"].Value = mac; + updateCommand.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + SetNulls(updateCommand); + int a = updateCommand.ExecuteNonQuery(); + Debug.Assert(a != 0); + } + + private List contentedTims; + public bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew) + { + if (timNid == null) + return false; + if (contentedTims == null) + contentedTims = new List(); + if (contentedTims.Contains(mac)) + return false; + + bool needUpdate = true; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT ccd_superframe_id, ccd_csc_response_timeout, ccd_csc_max_losses, ccd_max_time_before_retry " + + "FROM dvb_ic_tim " + + "WHERE mac = @mac AND nid = @nid"; + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + if (!dataReader.IsDBNull(0)) + { + int ccdSuperframeId = dataReader.GetInt32(0); + long ccdCscResponseTimeout = dataReader.GetInt64(1); + int ccdCscMaxLosses = dataReader.GetInt32(2); + long ccdMaxTimeBeforeRetry = dataReader.GetInt64(3); + _0xab_ContentionControlDescriptor ccdInDb = new _0xab_ContentionControlDescriptor((byte)ccdSuperframeId, (uint)ccdCscResponseTimeout, (byte)ccdCscMaxLosses, (uint)ccdMaxTimeBeforeRetry); + needUpdate = !ccdNew.Equals(ccdInDb); + } + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + contentedTims.Add(mac); + if (needUpdate) + { + EnqueueTask(x => ContentionTimEx(x, mac, timNid.Value, ccdNew)); + } + return needUpdate; + } + + private void ContentionTimEx(NpgsqlConnection x, PhysicalAddress mac, ushort value, _0xab_ContentionControlDescriptor ccdNew) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "UPDATE dvb_ic_tim " + + "SET ccd_superframe_id = @sid, ccd_csc_response_timeout = @crtimeout, ccd_csc_max_losses = @cmlosses, ccd_max_time_before_retry = @mtbretry, " + + " version = version + 1, dateupdated = CURRENT_TIMESTAMP " + + "WHERE mac = @mac AND nid = @nid"; + command.Parameters.AddWithValue("@sid", ccdNew.SuperframeId); + command.Parameters.AddWithValue("@crtimeout", (long)ccdNew.CscResponseTimeout); + command.Parameters.AddWithValue("@cmlosses", ccdNew.CscMaxLosses); + command.Parameters.AddWithValue("@mtbretry", (long)ccdNew.MaxTimeBeforeRetry); + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + command.ExecuteNonQuery(); + } + + + private List correctionControlledTims; + public bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor newCcd) + { + if (timNid == null) + return false; + if (correctionControlledTims == null) + correctionControlledTims = new List(); + if (correctionControlledTims.Contains(mac)) + return false; + + bool needUpdate = true; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT ccd_acq_response_timeout, ccd_sync_response_timeout, ccd_acq_max_losses, ccd_sync_max_losses " + + "FROM dvb_ic_tim " + + "WHERE mac = @mac AND nid = @nid"; + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + if (!dataReader.IsDBNull(0)) + { + long acqResonseTimeout = dataReader.GetInt64(0); + long syncResponseTimeout = dataReader.GetInt64(1); + int acqMaxLosses = dataReader.GetInt32(2); + int syncMaxLosses = dataReader.GetInt32(3); + + _0xac_CorrectionControlDescriptor ccdInDb = new _0xac_CorrectionControlDescriptor((uint)acqResonseTimeout,(uint)syncResponseTimeout, (byte)acqMaxLosses, (byte)syncMaxLosses); + needUpdate = !newCcd.Equals(ccdInDb); + } + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + if (needUpdate) + EnqueueTask(x => CorrectionControlTimEx(x, timNid.Value, mac, newCcd)); + return needUpdate; + } + + private void CorrectionControlTimEx(NpgsqlConnection x, ushort value, PhysicalAddress mac, _0xac_CorrectionControlDescriptor newCcd) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "UPDATE dvb_ic_tim " + + "SET ccd_acq_response_timeout = @artimeout, ccd_sync_response_timeout = @srtimeout, ccd_acq_max_losses = @amlosses, ccd_sync_max_losses = @smlosses, version = version + 1, dateupdated = CURRENT_TIMESTAMP " + + "WHERE mac = @mac AND nid = @nid"; + command.Parameters.AddWithValue("@artimeout", (long)newCcd.AcqResponseTimeout); + command.Parameters.AddWithValue("@srtimeout", (long)newCcd.SyncResponseTimeout); + command.Parameters.AddWithValue("@amlosses", newCcd.AcqMaxLosses); + command.Parameters.AddWithValue("@smlosses", newCcd.SyncMaxLosses); + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + command.ExecuteNonQuery(); + } + + private Dictionary, byte[]> _networkLayerInfoTims; + public bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamp) + { + if (timNid == null) + return false; + if (_networkLayerInfoTims == null) + _networkLayerInfoTims = new Dictionary, byte[]>(); + Tuple coordinate = new Tuple(timNid.Value, mac); + if (!_networkLayerInfoTims.ContainsKey(coordinate)) + { + byte[] newValue = new byte[0]; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT nli_message \r\nFROM dvb_ic_tim \r\nWHERE mac = @mac AND nid = @nid\r\n"; + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, mac); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)timNid.Value); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + if (!dataReader.IsDBNull(0)) + { + newValue = dataReader.GetByteArray(0); + } + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + _networkLayerInfoTims.Add(coordinate, newValue); + } + + byte[] known = _networkLayerInfoTims[coordinate]; + byte[] candi = nlid.MessageBuffer; + if (!AreArraysEqual(known,candi)) + { + EnqueueTask(x => NetworkLayerInfoTimEx(x, coordinate, nlid)); + return true; + } + return false; + } + + private bool AreArraysEqual(byte[] l, byte[] r) + { + if (l.Length != r.Length) + return false; + + for (int i = 0; i < l.Length; i++) + { + if (l[i] != r[i]) + return false; + } + return true; + } + + private void NetworkLayerInfoTimEx(NpgsqlConnection x, Tuple coordinate, _0xa0_NetworkLayerInfoDescriptor nlid) + { + NpgsqlCommand command = x.CreateCommand(); + command.CommandText = "UPDATE dvb_ic_tim\r\nSET nli_message = @message, version = version + 1, dateupdated = CURRENT_TIMESTAMP\r\nWHERE mac = @mac AND nid = @nid"; + command.Parameters.AddWithValue("@message", NpgsqlTypes.NpgsqlDbType.Bytea, nlid.MessageBuffer); + command.Parameters.AddWithValue("@mac", NpgsqlTypes.NpgsqlDbType.MacAddr, coordinate.Item2); + command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)coordinate.Item1); + command.ExecuteNonQuery(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Nit.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Nit.cs new file mode 100644 index 0000000..3f0c9a9 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Nit.cs @@ -0,0 +1,724 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet knownNitNetworks; + public bool TestForNitNetwork(NitNetwork nitNetwork) + { + if (knownNitNetworks == null) + knownNitNetworks = new HashSet(); + + DatabaseKeyNitNetwork key = new DatabaseKeyNitNetwork(nitNetwork.NetworkId); + if (knownNitNetworks.Contains(key)) + return true; + + bool result = false; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_nit WHERE id = @id"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer, nitNetwork.NetworkId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + dataReader.Dispose(); + command.Dispose(); + conn.Close(); + } + + if (result) + knownNitNetworks.Add(key); + return result; + } + + public void StoreNitNetwork(NitNetwork nitNetwork) + { + if (_knownUpdatedNitNetworks == null) + _knownUpdatedNitNetworks = new HashSet(); + + DatabaseKeyNitNetwork key = new DatabaseKeyNitNetwork(nitNetwork.NetworkId); + EnqueueTask(x => WriteNit(x, nitNetwork)); + knownNitNetworks.Add(key); + _knownUpdatedNitNetworks.Add(key); + } + + private void WriteNit(NpgsqlConnection connection, NitNetwork nitNetwork) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_nit (id, name, private_data_specifier_id, xait_pid, region_name_country_code, region_name_language_code, region_country_code, min_polling_interval, uri, uri_linkage_type) " + + "VALUES (@id, @name, @private_data_specifier_id, @xait_pid, @region_name_country_code, @region_name_language_code, @region_country_code, @min_polling_interval, @uri, @uri_linkage_type)"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer, nitNetwork.NetworkId); + string name = nitNetwork.Name; + if (name != null) + name = name.Replace("\0", ""); + command.Parameters.AddParameter("@name", NpgsqlDbType.Text, name); + command.Parameters.AddParameter("@private_data_specifier_id", NpgsqlDbType.Bigint, (long?)nitNetwork.PrivateDataSpecifierId); + command.Parameters.AddParameter("@xait_pid", NpgsqlDbType.Integer, (int?)nitNetwork.XaitPid); + command.Parameters.AddParameter("@region_name_country_code", NpgsqlDbType.Varchar, nitNetwork.RegionCountryCode); + command.Parameters.AddParameter("@region_name_language_code", NpgsqlDbType.Varchar, nitNetwork.RegionNameLanguageCode); + command.Parameters.AddParameter("@region_country_code", NpgsqlDbType.Varchar, nitNetwork.RegionCountryCode); + command.Parameters.AddParameter("@min_polling_interval", NpgsqlDbType.Integer, (int?)nitNetwork.MinPollingInterval); + command.Parameters.AddParameter("@uri", NpgsqlDbType.Text, nitNetwork.Uri); + command.Parameters.AddParameter("@uri_linkage_type", NpgsqlDbType.Integer, (int?)nitNetwork.UriLinkageType); + command.ExecuteNonQuery(); + + WriteNitCells(connection, nitNetwork); + WriteNitLinkages(connection, nitNetwork); + WriteNitMessage(connection, nitNetwork); + WriteNitMultilingualNetworkNames(connection, nitNetwork); + WriteNitRegionNames(connection, nitNetwork); + WriteNitRegions(connection, nitNetwork); + WriteNitServiceList(connection, nitNetwork); + } + + private void WriteNitServiceList(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.ServiceList == null) + return; + + foreach (ServiceListDescriptor.Service service in nitNetwork.ServiceList) + { + throw new NotImplementedException(); + } + } + + private void WriteNitRegions(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Regions == null) + return; + + foreach (TargetRegionDescriptor.TargetRegion region in nitNetwork.Regions) + { + throw new NotImplementedException(); + } + } + + private void WriteNitRegionNames(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.RegionNames == null) + return; + + foreach (TargetRegionNameDescriptor.TargetRegionName name in nitNetwork.RegionNames) + { + throw new NotImplementedException(); + } + } + + private void WriteNitMultilingualNetworkNames(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.MultilingualNetworkName == null) + return; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into dvb_nit_multilingual_network_names (id, k, v) values (@id,@k,@v)"; + cmd.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)nitNetwork.NetworkId); + cmd.Parameters.Add("@k", NpgsqlDbType.Varchar); + cmd.Parameters.Add("@v", NpgsqlDbType.Text); + + foreach (KeyValuePair keyValuePair in nitNetwork.MultilingualNetworkName) + { + cmd.Parameters["@k"].Value = keyValuePair.Key; + cmd.Parameters["@v"].Value = keyValuePair.Value; + cmd.ExecuteNonQuery(); + } + } + + private void WriteNitMessage(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Messages == null) + return; + + foreach (MessageDescriptor message in nitNetwork.Messages) + { + throw new NotImplementedException(); + } + } + + private void WriteNitLinkages(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Linkages == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_nit_linkages (id, ordinal, l_tsid, l_onid, l_sid, linkage_type, handover_type, handover_origin_type, handover_network_id, handover_initial_service_id, target_event_id, target_event_listed, target_event_simulcasted, table_type, private_data_bytes, bouquet_id) " + + "values " + + "(@id,@ordinal,@l_tsid,@l_onid,@l_sid,@linkage_type,@handover_type,@handover_origin_type,@handover_network_id,@handover_initial_service_id,@target_event_id,@target_event_listed,@target_event_simulcasted,@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer, nitNetwork.NetworkId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@l_tsid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_onid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_sid", NpgsqlDbType.Integer); + command.Parameters.Add("@linkage_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_origin_type", NpgsqlDbType.Boolean); + command.Parameters.Add("@handover_network_id", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_initial_service_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_listed", NpgsqlDbType.Boolean); + command.Parameters.Add("@target_event_simulcasted", NpgsqlDbType.Boolean); + //@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.Add("@table_type", NpgsqlDbType.Integer); + command.Parameters.Add("@private_data_bytes", NpgsqlDbType.Bytea); + command.Parameters.Add("@bouquet_id", NpgsqlDbType.Integer); + + for (int i = 0; i < nitNetwork.Linkages.Count; i++) + { + LinkageDescriptor linkage = nitNetwork.Linkages[i]; + command.Parameters["@id"].Value = (int)nitNetwork.NetworkId; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@l_tsid"].Value = (int)linkage.TransportStreamId; + command.Parameters["@l_onid"].Value = (int)linkage.OriginalNetworkId; + command.Parameters["@l_sid"].Value = (int)linkage.ServiceId; + command.Parameters["@linkage_type"].Value = (int)linkage.LinkageType; + command.Parameters["@handover_type"].Value = linkage.HandoverType.HasValue ? linkage.HandoverType.Value : DBNull.Value; + command.Parameters["@handover_origin_type"].Value = linkage.HandoverOriginType.HasValue ? linkage.HandoverOriginType.Value : DBNull.Value; + command.Parameters["@handover_network_id"].Value = linkage.HandoverNetworkId.HasValue ? linkage.HandoverNetworkId.Value : DBNull.Value; + command.Parameters["@handover_initial_service_id"].Value = linkage.HandoverInitialServiceId.HasValue ? linkage.HandoverInitialServiceId.Value : DBNull.Value; + command.Parameters["@target_event_id"].Value = linkage.TargetEventId.HasValue ? linkage.TargetEventId.Value : DBNull.Value; + command.Parameters["@target_event_listed"].Value = linkage.TargetEventListed.HasValue ? linkage.TargetEventListed.Value : DBNull.Value; + command.Parameters["@target_event_simulcasted"].Value = linkage.TargetEventSimulcasted.HasValue ? linkage.TargetEventSimulcasted.Value : DBNull.Value; + command.Parameters["@table_type"].Value = linkage.TableType.HasValue ? (int)linkage.TableType.Value : DBNull.Value; + command.Parameters["@private_data_bytes"].Value = linkage.PrivateDataBytes != null ? linkage.PrivateDataBytes : DBNull.Value; + command.Parameters["@bouquet_id"].Value = linkage.BouquetId.HasValue ? linkage.BouquetId : DBNull.Value; + command.ExecuteNonQuery(); + + WriteNitLinkageExtendedEventLinkages(connection, nitNetwork, linkage); + WriteNitLinkageIpmacLinkages(connection, nitNetwork, i); + WriteNitLinkageSsuLinkStructure(connection, nitNetwork, i); + } + } + + private void WriteNitLinkageSsuLinkStructure(NpgsqlConnection connection, NitNetwork nitNetwork, int ordinal) + { + LinkageDescriptor linkage = nitNetwork.Linkages[ordinal]; + if (linkage.SystemSoftwareUpdateLinkStructure == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO dvb_nit_linkages_ssu_link_structure (id, ordinal, subordinal, oui, selector) " + + "VALUES (@id, @ordinal, @subordinal, @oui, @selector)"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)nitNetwork.NetworkId); + command.Parameters.AddWithValue("@ordinal", NpgsqlDbType.Integer, ordinal); + command.Parameters.Add("@subordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@oui", NpgsqlDbType.Varchar); + command.Parameters.Add("@selector", NpgsqlDbType.Bytea); + for (int i = 0; i < linkage.SystemSoftwareUpdateLinkStructure.Count; i++) + { + LinkageDescriptor.OuiPrivateData ssuLinkStructure = linkage.SystemSoftwareUpdateLinkStructure[i]; + command.Parameters["@subordinal"].Value = i; + command.Parameters["@oui"].Value = BitConverter.ToString(ssuLinkStructure.OUI); + command.Parameters["@selector"].Value = ssuLinkStructure.Selector; + command.ExecuteNonQuery(); + } + } + + private void WriteNitLinkageIpmacLinkages(NpgsqlConnection connection, NitNetwork nitNetwork, int ordinal) + { + LinkageDescriptor linkage = nitNetwork.Linkages[ordinal]; + if (linkage.IpMacNotificationLinkages == null) + return; + + foreach (LinkageDescriptor.IpMacNotificationLinkage ipMacNotificationLinkage in linkage.IpMacNotificationLinkages) + { + throw new NotImplementedException(); + } + } + + private void WriteNitLinkageExtendedEventLinkages(NpgsqlConnection connection, NitNetwork nitNetwork, LinkageDescriptor linkage) + { + if (linkage.ExtendedEventLinkages == null) + return; + + for (int i = 0; i < linkage.ExtendedEventLinkages.Length; i++) + { + LinkageDescriptor.ExtendedEventLinkageInfo extendedEventLinkage = linkage.ExtendedEventLinkages[i]; + throw new NotImplementedException(); + } + } + + private void WriteNitCells(NpgsqlConnection connection, NitNetwork nitNetwork) + { + if (nitNetwork.Cells == null) + return; + + NpgsqlCommand npgsqlCommand = connection.CreateCommand(); + npgsqlCommand.CommandText = "insert into dvb_nit_cells (id, cell_id, cell_lat, cell_lon, extent_lat, extent_lon) values (@id,@cellid,@cellat,@celllon,@extlat,@extlon)"; + npgsqlCommand.Parameters.AddWithValue("@id", (int)nitNetwork.NetworkId); + npgsqlCommand.Parameters.Add("@cellid", NpgsqlDbType.Integer); + npgsqlCommand.Parameters.Add("@cellat", NpgsqlDbType.Integer); + npgsqlCommand.Parameters.Add("@celllon", NpgsqlDbType.Integer); + npgsqlCommand.Parameters.Add("@extlat", NpgsqlDbType.Bigint); + npgsqlCommand.Parameters.Add("@extlon", NpgsqlDbType.Bigint); + foreach (CellListDescriptor.Cell cell in nitNetwork.Cells) + { + npgsqlCommand.Parameters["@cellid"].Value = (int)cell.CellId; + npgsqlCommand.Parameters["@cellat"].Value = (int)cell.CellLatitude; + npgsqlCommand.Parameters["@celllon"].Value = (int)cell.CellLongitude; + npgsqlCommand.Parameters["@extlat"].Value = (long)cell.ExtentOfLatitude; + npgsqlCommand.Parameters["@extlon"].Value = (long)cell.ExtentOfLongitude; + npgsqlCommand.ExecuteNonQuery(); + + if (cell.Subcells != null) + { + if (cell.Subcells.Length > 0) + { + WriteNitSubcells(connection, nitNetwork.NetworkId, cell.CellId, cell.Subcells); + } + } + } + } + + private void WriteNitSubcells(NpgsqlConnection connection, int networkId, int cellId, CellListDescriptor.Subcell[] subcells) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "insert into dvb_nit_cells_subcells (id, cell_id, cell_id_extension, subcell_lat, subcell_lon, subcell_extent_lat, subcell_extent_lon) " + + "values (@nid,@cellid,@cellex,@subcelllat,@subcellon,@sbucellextlat,@subcell_extent_lon)"; + command.Parameters.AddWithValue("@nid", networkId); + command.Parameters.AddWithValue("@cellid", cellId); + command.Parameters.Add("@cellex", NpgsqlDbType.Integer); + command.Parameters.Add("@subcelllat", NpgsqlDbType.Integer); + command.Parameters.Add("@subcellon", NpgsqlDbType.Integer); + command.Parameters.Add("@sbucellextlat", NpgsqlDbType.Integer); + command.Parameters.Add("@subcell_extent_lon", NpgsqlDbType.Integer); + foreach(CellListDescriptor.Subcell subcell in subcells) + { + command.Parameters["@cellex"].Value = (int)subcell.CellIdExtension; + command.Parameters["@subcelllat"].Value = (int)subcell.SubcellLatitude; + command.Parameters["@subcellon"].Value = (int)subcell.SubcellLongitude; + command.Parameters["@sbucellextlat"].Value = (int)subcell.CellIdExtension; + command.Parameters["@subcell_extent_lon"].Value = (int)subcell.CellIdExtension; + command.ExecuteNonQuery(); + } + } + + private HashSet _knownNitTs; + public bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + if (_knownNitTs == null) + _knownNitTs = new HashSet(); + + DatabaseKeyNitTs key = new DatabaseKeyNitTs(networkId, transportStream.TransportStreamId); + if (_knownNitTs.Contains(key)) + return true; + + bool result = false; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dvb_nit_transport_stream WHERE nid = @nid AND tsid = @tsid"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + } + + if (result) + _knownNitTs.Add(key); + + return result; + } + + public void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + EnqueueTask(x => WriteNitTransportStream(x, networkId, transportStream)); + DatabaseKeyNitTs ts = new DatabaseKeyNitTs(networkId, transportStream.TransportStreamId); + _knownNitTs.Add(ts); + if (_knownUpdatedNitTransportStream == null) + return; + _knownUpdatedNitTransportStream.Add(ts); + } + + private void WriteNitTransportStream(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (TestForNitTransportStream(networkId,transportStream)) + { + return; + } + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_nit_transport_stream (nid, tsid, onid, east, fec_inner, frequency, orbital_position, polarization, roll_off, s2, symbol_rate, scrambling_sequence_index, input_stream_identifier, timeslice_number, ts_gs_mode, private_data_specifier_id, tfs_flag, bandwidth, guard_interval, other_frequency_flag, plp_id, siso_miso, t2_system_id, transmission_mode, coding_type, modulation_type, fec_outer, code_rate_hp_stream, code_rate_lp_stream, hierarchy_information, mpe_fec_indicator, priority, time_slicing_indicator, network_name, target_region_country_code) " + + "values " + + "(@nid,@tsid,@onid,@east,@fec_inner,@frequency,@orbital_position,@polarization,@roll_off,@s2,@symbol_rate,@scrambling_sequence_index,@input_stream_identifier,@timeslice_number,@ts_gs_mode,@private_data_specifier_id,\r\n @tfs_flag, @bandwidth, @guard_interval, @other_frequency_flag, @plp_id, @siso_miso,\r\n @t2_system_id, @transmission_mode, @coding_type, @modulation_type, @fec_outer,\r\n @code_rate_hp_stream, @code_rate_lp_stream, @hierarchy_information,\r\n @mpe_fec_indicator, @priority, @time_slicing_indicator, @network_name,\r\n @target_region_country_code);"; + command.Parameters.AddParameter("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, transportStream.TransportStreamId); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, transportStream.OriginalNetworkId); + command.Parameters.AddParameter("@east", NpgsqlDbType.Boolean, transportStream.East); + command.Parameters.AddParameter("@fec_inner", NpgsqlDbType.Integer, (int?)transportStream.FecInner); + command.Parameters.AddParameter("@frequency", NpgsqlDbType.Bigint, transportStream.Frequency); + command.Parameters.AddParameter("@orbital_position", NpgsqlDbType.Real, transportStream.OrbitalPosition); + command.Parameters.AddParameter("@polarization", NpgsqlDbType.Integer, (int?)transportStream.Polarization); + command.Parameters.AddParameter("@roll_off", NpgsqlDbType.Real, transportStream.RollOff); + command.Parameters.AddParameter("@s2", NpgsqlDbType.Boolean, transportStream.S2); + command.Parameters.AddParameter("@symbol_rate", NpgsqlDbType.Bigint, transportStream.SymbolRate); + command.Parameters.AddParameter("@scrambling_sequence_index", NpgsqlDbType.Integer, transportStream.ScramblingSequenceIndex); + command.Parameters.AddParameter("@input_stream_identifier", NpgsqlDbType.Integer, transportStream.InputStreamIdentifier); + command.Parameters.AddParameter("@timeslice_number", NpgsqlDbType.Integer, transportStream.TimesliceNumber); + command.Parameters.AddParameter("@ts_gs_mode", NpgsqlDbType.Integer, (int?)transportStream.TsGsMode); + //@private_data_specifier_id,@tfs_flag,@bandwidth,@guard_interval,@other_frequency_flag,@plp_id,@siso_miso,@t2_system_id,@transmission_mode,@coding_type,@modulation_type,@fec_outer,@code_rate_hp_stream, + command.Parameters.AddParameter("@private_data_specifier_id", NpgsqlDbType.Bigint, (long?)transportStream.PrivateDataSpecifierId); + command.Parameters.AddParameter("@tfs_flag", NpgsqlDbType.Boolean, transportStream.TfsFlag); + command.Parameters.AddParameter("@bandwidth", NpgsqlDbType.Integer, transportStream.Bandwidth); + command.Parameters.AddParameter("@guard_interval", NpgsqlDbType.Integer, transportStream.GuardInterval); + command.Parameters.AddParameter("@other_frequency_flag", NpgsqlDbType.Boolean, transportStream.OtherFrequencyFlag); + command.Parameters.AddParameter("@plp_id", NpgsqlDbType.Integer, (int?)transportStream.PlpId); + command.Parameters.AddParameter("@siso_miso", NpgsqlDbType.Integer, transportStream.SisoMiso); + command.Parameters.AddParameter("@t2_system_id", NpgsqlDbType.Integer, (int?)transportStream.T2SystemId); + command.Parameters.AddParameter("@transmission_mode", NpgsqlDbType.Integer, transportStream.TransmissionMode); + command.Parameters.AddParameter("@coding_type", NpgsqlDbType.Integer, (int?)transportStream.CodingType); + command.Parameters.AddParameter("@modulation_type", NpgsqlDbType.Integer, transportStream.ModulationType); + command.Parameters.AddParameter("@fec_outer", NpgsqlDbType.Integer, (int?)transportStream.FecOuter); + command.Parameters.AddParameter("@code_rate_hp_stream", NpgsqlDbType.Integer, (int?)transportStream.CodeRateHpStream); + //@code_rate_lp_stream,@hierarchy_information,@mpe_fec_indicator,@priority,@time_slicing_indicator,@network_name,@target_region_country_code)"; + command.Parameters.AddParameter("@code_rate_lp_stream", NpgsqlDbType.Integer, (int?)transportStream.CodeRateLpStream); + command.Parameters.AddParameter("@hierarchy_information", NpgsqlDbType.Integer, (int?)transportStream.HierarchyInformation); + command.Parameters.AddParameter("@mpe_fec_indicator", NpgsqlDbType.Boolean, transportStream.MpeFecIndicator); + command.Parameters.AddParameter("@priority", NpgsqlDbType.Boolean, transportStream.Priority); + command.Parameters.AddParameter("@time_slicing_indicator", NpgsqlDbType.Integer, (int?)transportStream.TimesliceNumber); + command.Parameters.AddParameter("@network_name", NpgsqlDbType.Text, transportStream.NetworkName); + command.Parameters.AddParameter("@target_region_country_code", NpgsqlDbType.Varchar, transportStream.TargetRegionCountryCode); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + InsertNitTransportStreamCellFrequencies(conn, networkId, transportStream); + InsertNitTransportStreamCellInfos(conn, networkId, transportStream); + InsertNitTransportStreamCells(conn, networkId, transportStream); + InsertNitTransportStreamCentreFrequencies(conn, networkId, transportStream); + InsertNitTransportStreamLinkages(conn, networkId, transportStream); + InsertNitTransportStreamServiceList(conn, networkId, transportStream); + InsertNitTransportStreamTargetRegions(conn, networkId, transportStream); + } + + private void InsertNitTransportStreamTargetRegions(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.TargetRegions == null) + return; + + foreach (TargetRegionDescriptor.TargetRegion targetRegion in transportStream.TargetRegions) + { + throw new NotImplementedException(); + } + } + + private void InsertNitTransportStreamServiceList(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.Services == null) + return; + if (transportStream.Services.Services == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_nit_transport_stream_service_list (tsid, nid, sid, service_type)" + + "VALUES (@tsid, @nid, @sid, @service_type)"; + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.Add("@sid", NpgsqlDbType.Integer); + command.Parameters.Add("@service_type", NpgsqlDbType.Integer); + foreach (ServiceListDescriptor.Service service in transportStream.Services.Services) + { + command.Parameters["@sid"].Value = (int)service.ServiceId; + command.Parameters["@service_type"].Value = (int)service.ServiceType; + command.ExecuteNonQuery(); + } + } + + private void InsertNitTransportStreamLinkages(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.Linkages == null) + return; + + for (int i = 0; i < transportStream.Linkages.Count; i++) + { + LinkageDescriptor linkage = transportStream.Linkages[i]; + throw new NotImplementedException(); + } + } + + private void InsertNitTransportStreamCentreFrequencies(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.CentreFrequencies == null) + return; + + foreach (uint transportStreamCentreFrequency in transportStream.CentreFrequencies) + { + throw new NotImplementedException(); + } + } + + private void InsertNitTransportStreamCells(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.Cells == null) + return; + + foreach (CellListDescriptor.Cell cell in transportStream.Cells) + { + throw new NotImplementedException(); + } + } + + private void InsertNitTransportStreamCellInfos(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.CellInfos == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "insert into dvb_nit_transport_stream_cell_infos (onid, tsid, cell_id)\r\nvalues (@onid, @tsid, @cell_id)\r\n"; + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + command.Parameters.Add("@cell_id", NpgsqlDbType.Integer); + foreach (T2DeliverySystemDescriptor.CellInfo cellInfo in transportStream.CellInfos) + { + command.Parameters["@cell_id"].Value = (int)cellInfo.CellId; + command.ExecuteNonQuery(); + + InsertNitTransportStreamCellInfoSubcellInfos(conn, networkId, transportStream.TransportStreamId, cellInfo); + InsertNitTransportStreamCellInfoCentreFrequencies(conn, networkId, transportStream.TransportStreamId, cellInfo); + } + } + + private void InsertNitTransportStreamCellInfoCentreFrequencies(NpgsqlConnection conn, ushort networkId, ushort transportStreamId, T2DeliverySystemDescriptor.CellInfo cellInfo) + { + if (cellInfo.CentreFrequencies == null) + return; + + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "insert into dvb_nit_transport_stream_cell_frequencies (nid, tsid, cell_id, frequency, ordinal)\r\nvalues (@nid,@tsid,@cell_id,@frequency,@ordinal)"; + cmd.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, (int)networkId); + cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + cmd.Parameters.AddWithValue("@cell_id", NpgsqlDbType.Integer, (int)cellInfo.CellId); + cmd.Parameters.Add("@frequency", NpgsqlDbType.Bigint); + cmd.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + for (int i = 0; i < cellInfo.CentreFrequencies.Length; i++) + { + cmd.Parameters["@frequency"].Value = (long)cellInfo.CentreFrequencies[i]; + cmd.Parameters["@ordinal"].Value = i; + cmd.ExecuteNonQuery(); + } + } + + private void InsertNitTransportStreamCellInfoSubcellInfos(NpgsqlConnection conn, ushort networkId, ushort transportStreamId, T2DeliverySystemDescriptor.CellInfo cellInfo) + { + if (cellInfo.SubcellInfos == null) + return; + + foreach (T2DeliverySystemDescriptor.SubcellInfo m in cellInfo.SubcellInfos) + { + throw new NotImplementedException(); + } + } + + private void InsertNitTransportStreamCellFrequencies(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + if (transportStream.CellFrequencies == null) + return; + + foreach (CellFrequencyLinkDescriptor.Cell cell in transportStream.CellFrequencies) + { + throw new NotImplementedException(); + } + } + + private HashSet _knownUpdatedNitNetworks; + public bool UpdateNitNetwork(NitNetwork nitNetwork) + { + if (_knownUpdatedNitNetworks == null) + _knownUpdatedNitNetworks = new HashSet(); + nitNetwork.Sanitize(); + DatabaseKeyNitNetwork key = new DatabaseKeyNitNetwork(nitNetwork.NetworkId); + if (_knownUpdatedNitNetworks.Contains(key)) + return false; + EnqueueTask(x => WriteUpdateNitNetwork(x, nitNetwork)); + _knownUpdatedNitNetworks.Add(key); + return true; + } + + private void WriteUpdateNitNetwork(NpgsqlConnection connection, NitNetwork newer) + { + NitNetwork older = SelectNitNetwork(connection, newer.NetworkId); + if (!older.NeedsUpdate(newer)) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "update dvb_nit\r\nset name = @name,\r\n private_data_specifier_id = @private_data_specifier_id,\r\n xait_pid = @xait_pid,\r\n region_name_country_code = @region_name_country_code,\r\n region_name_language_code = @region_name_language_code,\r\n region_country_code = @region_country_code,\r\n min_polling_interval = @min_polling_interval,\r\n uri = @uri,\r\n uri_linkage_type = @uri_linkage_type,\r\n updated_counter = updated_counter + 1,\r\n updated_timestamp = CURRENT_TIMESTAMP\r\nwhere id = @id"; + command.Parameters.AddParameter("@id", NpgsqlDbType.Integer, newer.NetworkId); + command.Parameters.AddParameter("@name", NpgsqlDbType.Text, newer.Name); + command.Parameters.AddParameter("@private_data_specifier_id", NpgsqlDbType.Bigint, (long?)newer.PrivateDataSpecifierId); + command.Parameters.AddParameter("@xait_pid", NpgsqlDbType.Integer, (int?)newer.XaitPid); + command.Parameters.AddParameter("@region_name_country_code", NpgsqlDbType.Varchar, newer.RegionCountryCode); + command.Parameters.AddParameter("@region_name_language_code", NpgsqlDbType.Varchar, newer.RegionNameLanguageCode); + command.Parameters.AddParameter("@region_country_code", NpgsqlDbType.Varchar, newer.RegionCountryCode); + command.Parameters.AddParameter("@min_polling_interval", NpgsqlDbType.Integer, (int?)newer.MinPollingInterval); + command.Parameters.AddParameter("@uri", NpgsqlDbType.Text, newer.Uri); + command.Parameters.AddParameter("@uri_linkage_type", NpgsqlDbType.Integer, (int?)newer.UriLinkageType); + command.ExecuteNonQuery(); + } + + private NitNetwork SelectNitNetwork(NpgsqlConnection connection, int targetId) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM dvb_nit WHERE id = @targetId"; + command.Parameters.AddWithValue("@targetId", NpgsqlDbType.Integer, targetId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + NitNetwork result = null; + if (dataReader.Read()) + { + ushort id = (ushort)dataReader.GetInt32(0); + DateTime dateadded = dataReader.GetDateTime(1); + result = new NitNetwork(id); + result.Name = dataReader.IsDBNull(2) ? null : dataReader.GetString(2); + result.PrivateDataSpecifierId = dataReader.IsDBNull(3) ? null : (uint)dataReader.GetInt64(3); + result.XaitPid = dataReader.IsDBNull(4) ? null : (ushort)dataReader.GetInt32(4); + result.RegionNameCountryCode = dataReader.IsDBNull(5) ? null : dataReader.GetString(5); + result.RegionNameLanguageCode = dataReader.IsDBNull(6) ? null : dataReader.GetString(6); + result.RegionCountryCode = dataReader.IsDBNull(7) ? null : dataReader.GetString(7); + result.MinPollingInterval = dataReader.IsDBNull(8) ? null : (ushort)dataReader.GetInt32(8); + result.Uri = dataReader.IsDBNull(9) ? null : dataReader.GetString(9); + result.UriLinkageType = dataReader.IsDBNull(10) ? null : (byte)dataReader.GetInt32(10); + } + dataReader.Close(); + command.Dispose(); + return result; + } + + private HashSet _knownUpdatedNitTransportStream; + public bool UpdateNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + if (_knownUpdatedNitTransportStream == null) + _knownUpdatedNitTransportStream = new HashSet(); + + DatabaseKeyNitTs key = new DatabaseKeyNitTs(networkId, transportStream.TransportStreamId); + if (_knownUpdatedNitTransportStream.Contains(key)) + return false; + + EnqueueTask(x => WriteNitUpdateTransportStream(x, networkId, transportStream)); + _knownUpdatedNitTransportStream.Add(key); + return true; + } + + private void WriteNitUpdateTransportStream(NpgsqlConnection connection, ushort networkId, NitTransportStream transportStream) + { + NitTransportStream older = SelectNitTransportStream(connection, networkId, transportStream.TransportStreamId); + if (older == null) + return; + if (!older.NeedUpdate(transportStream)) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "update dvb_nit_transport_stream\r\nset onid = @onid,\r\n east = @east,\r\n fec_inner = @fec_inner,\r\n frequency = @frequency,\r\n orbital_position = @orbital_position,\r\n polarization = @polarization,\r\n roll_off = @roll_off,\r\n s2 = @s2,\r\n symbol_rate = @symbol_rate,\r\n scrambling_sequence_index = @scrambling_sequence_index,\r\n input_stream_identifier = @input_stream_identifier,\r\n timeslice_number = @timeslice_number,\r\n ts_gs_mode = @ts_gs_mode,\r\n private_data_specifier_id = @private_data_specifier_id,\r\n tfs_flag = @tfs_flag,\r\n bandwidth = @bandwidth,\r\n guard_interval = @guard_interval,\r\n other_frequency_flag = @other_frequency_flag,\r\n plp_id = @plp_id,\r\n siso_miso = @siso_miso,\r\n t2_system_id = @t2_system_id,\r\n transmission_mode = @transmission_mode,\r\n coding_type = @coding_type,\r\n modulation_type = @modulation_type,\r\n fec_outer = @fec_outer,\r\n code_rate_hp_stream = @code_rate_hp_stream,\r\n code_rate_lp_stream = @code_rate_lp_stream,\r\n hierarchy_information = @hierarchy_information,\r\n mpe_fec_indicator = @mpe_fec_indicator,\r\n priority = @priority,\r\n time_slicing_indicator = @time_slicing_indicator,\r\n network_name = @network_name,\r\n target_region_country_code = @target_region_country_code\r\nwhere nid = @nid \r\nand tsid = @tsid"; + command.Parameters.AddParameter("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, transportStream.TransportStreamId); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, transportStream.OriginalNetworkId); + command.Parameters.AddParameter("@east", NpgsqlDbType.Boolean, transportStream.East); + command.Parameters.AddParameter("@fec_inner", NpgsqlDbType.Integer, (int?)transportStream.FecInner); + command.Parameters.AddParameter("@frequency", NpgsqlDbType.Bigint, transportStream.Frequency); + command.Parameters.AddParameter("@orbital_position", NpgsqlDbType.Real, transportStream.OrbitalPosition); + command.Parameters.AddParameter("@polarization", NpgsqlDbType.Integer, (int?)transportStream.Polarization); + command.Parameters.AddParameter("@roll_off", NpgsqlDbType.Real, transportStream.RollOff); + command.Parameters.AddParameter("@s2", NpgsqlDbType.Boolean, transportStream.S2); + command.Parameters.AddParameter("@symbol_rate", NpgsqlDbType.Bigint, transportStream.SymbolRate); + command.Parameters.AddParameter("@scrambling_sequence_index", NpgsqlDbType.Integer, transportStream.ScramblingSequenceIndex); + command.Parameters.AddParameter("@input_stream_identifier", NpgsqlDbType.Integer, transportStream.InputStreamIdentifier); + command.Parameters.AddParameter("@timeslice_number", NpgsqlDbType.Integer, transportStream.TimesliceNumber); + command.Parameters.AddParameter("@ts_gs_mode", NpgsqlDbType.Integer, (int?)transportStream.TsGsMode); + //@private_data_specifier_id,@tfs_flag,@bandwidth,@guard_interval,@other_frequency_flag,@plp_id,@siso_miso,@t2_system_id,@transmission_mode,@coding_type,@modulation_type,@fec_outer,@code_rate_hp_stream, + command.Parameters.AddParameter("@private_data_specifier_id", NpgsqlDbType.Bigint, (long?)transportStream.PrivateDataSpecifierId); + command.Parameters.AddParameter("@tfs_flag", NpgsqlDbType.Boolean, transportStream.TfsFlag); + command.Parameters.AddParameter("@bandwidth", NpgsqlDbType.Integer, transportStream.Bandwidth); + command.Parameters.AddParameter("@guard_interval", NpgsqlDbType.Integer, transportStream.GuardInterval); + command.Parameters.AddParameter("@other_frequency_flag", NpgsqlDbType.Boolean, transportStream.OtherFrequencyFlag); + command.Parameters.AddParameter("@plp_id", NpgsqlDbType.Integer, (int?)transportStream.PlpId); + command.Parameters.AddParameter("@siso_miso", NpgsqlDbType.Integer, transportStream.SisoMiso); + command.Parameters.AddParameter("@t2_system_id", NpgsqlDbType.Integer, (int?)transportStream.T2SystemId); + command.Parameters.AddParameter("@transmission_mode", NpgsqlDbType.Integer, transportStream.TransmissionMode); + command.Parameters.AddParameter("@coding_type", NpgsqlDbType.Integer, (int?)transportStream.CodingType); + command.Parameters.AddParameter("@modulation_type", NpgsqlDbType.Integer, transportStream.ModulationType); + command.Parameters.AddParameter("@fec_outer", NpgsqlDbType.Integer, (int?)transportStream.FecOuter); + command.Parameters.AddParameter("@code_rate_hp_stream", NpgsqlDbType.Integer, (int?)transportStream.CodeRateHpStream); + //@code_rate_lp_stream,@hierarchy_information,@mpe_fec_indicator,@priority,@time_slicing_indicator,@network_name,@target_region_country_code)"; + command.Parameters.AddParameter("@code_rate_lp_stream", NpgsqlDbType.Integer, (int?)transportStream.CodeRateLpStream); + command.Parameters.AddParameter("@hierarchy_information", NpgsqlDbType.Integer, (int?)transportStream.HierarchyInformation); + command.Parameters.AddParameter("@mpe_fec_indicator", NpgsqlDbType.Boolean, transportStream.MpeFecIndicator); + command.Parameters.AddParameter("@priority", NpgsqlDbType.Boolean, transportStream.Priority); + command.Parameters.AddParameter("@time_slicing_indicator", NpgsqlDbType.Integer, (int?)transportStream.TimesliceNumber); + command.Parameters.AddParameter("@network_name", NpgsqlDbType.Text, transportStream.NetworkName); + command.Parameters.AddParameter("@target_region_country_code", NpgsqlDbType.Varchar, transportStream.TargetRegionCountryCode); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + } + + private NitTransportStream SelectNitTransportStream(NpgsqlConnection connection, ushort networkId, ushort transportStreamId) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM dvb_nit_transport_stream WHERE nid = @nid AND tsid = @tsid"; + command.Parameters.AddParameter("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + NitTransportStream result = null; + if (dataReader.Read()) + { + ushort nid = (ushort)dataReader.GetInt32(0); + ushort tsid = (ushort)dataReader.GetInt32(1); + DateTime dateadded = (DateTime)dataReader.GetDateTime(2); + ushort onid = (ushort)dataReader.GetInt32(3); + result = new NitTransportStream(onid, tsid); + result.East = dataReader.IsDBNull(4) ? null : dataReader.GetBoolean(4); + result.FecInner = dataReader.IsDBNull(5) ? null : (SatelliteDeliverySystemDescriptor.InnerFecScheme)dataReader.GetInt32(5); + result.Frequency = dataReader.IsDBNull(6) ? null : dataReader.GetInt64(6); + result.OrbitalPosition = dataReader.IsDBNull(7) ? null : dataReader.GetFloat(7); + result.Polarization = dataReader.IsDBNull(8) ? null : (SatelliteDeliverySystemDescriptor.PolarizationEnum)dataReader.GetInt32(8); + result.RollOff = dataReader.IsDBNull(9) ? null : dataReader.GetFloat(9); + result.S2 = dataReader.IsDBNull(10) ? null : dataReader.GetBoolean(10); + result.SymbolRate = dataReader.IsDBNull(11) ? null : dataReader.GetInt64(11); + result.ScramblingSequenceIndex = dataReader.IsDBNull(12) ? null : dataReader.GetInt32(12); + result.InputStreamIdentifier = dataReader.IsDBNull(13) ? null : (byte)dataReader.GetInt32(13); + result.TimesliceNumber = dataReader.IsDBNull(14) ? null : (byte)dataReader.GetInt32(14); + result.TsGsMode = dataReader.IsDBNull(15) ? null : (S2SatelliteDeliverySystemDescriptor.TsGsModeCoding)dataReader.GetInt32(15); + result.PrivateDataSpecifierId = dataReader.IsDBNull(16) ? null : (uint)dataReader.GetInt64(16); + result.TfsFlag = dataReader.IsDBNull(17) ? null : dataReader.GetBoolean(17); + result.Bandwidth = dataReader.IsDBNull(18) ? null : dataReader.GetInt32(18); + result.GuardInterval = dataReader.IsDBNull(19) ? null : dataReader.GetInt32(19); + result.OtherFrequencyFlag = dataReader.IsDBNull(20) ? null : dataReader.GetBoolean(20); + result.PlpId = dataReader.IsDBNull(21) ? null : dataReader.GetByte(21); + result.SisoMiso = dataReader.IsDBNull(22) ? null : dataReader.GetInt32(22); + result.T2SystemId = dataReader.IsDBNull(23) ? null : (ushort)dataReader.GetInt32(23); + result.TransmissionMode = dataReader.IsDBNull(24) ? null : dataReader.GetInt32(24); + result.CodingType = dataReader.IsDBNull(25) ? null : (FrequencyListDescriptor.CodingTypeValue)dataReader.GetInt32(25); + result.ModulationType = dataReader.IsDBNull(26) ? null : dataReader.GetInt32(26); + result.FecOuter = dataReader.IsDBNull(27) ? null : (CableDeliverySystemDescriptor.OuterFecScheme)dataReader.GetInt32(27); + result.CodeRateHpStream = dataReader.IsDBNull(28) ? null : (TerristialDeliverySystemDescriptor.CodeRate)dataReader.GetInt32(28); + result.CodeRateLpStream = dataReader.IsDBNull(29) ? null : (TerristialDeliverySystemDescriptor.CodeRate)dataReader.GetInt32(29); + result.HierarchyInformation = dataReader.IsDBNull(30) ? null : (TerristialDeliverySystemDescriptor.HierarchySignallingFormat)dataReader.GetInt32(30); + result.MpeFecIndicator = dataReader.IsDBNull(31) ? null : dataReader.GetBoolean(31); + result.Priority = dataReader.IsDBNull(32) ? null : dataReader.GetBoolean(32); + result.TimeSlicingIndicator = dataReader.IsDBNull(33) ? null : dataReader.GetInt32(33); + result.NetworkName = dataReader.IsDBNull(34) ? null : dataReader.GetString(34); + result.TargetRegionCountryCode = dataReader.IsDBNull(35) ? null : dataReader.GetString(35); + } + dataReader.Close(); + command.Dispose(); + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/NpgsqlParameterCollectionExtensions.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/NpgsqlParameterCollectionExtensions.cs new file mode 100644 index 0000000..fbfc811 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/NpgsqlParameterCollectionExtensions.cs @@ -0,0 +1,86 @@ +using Npgsql; +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + internal static class NpgsqlParameterCollectionExtensions + { + public static void CheckNulls(this NpgsqlParameterCollection collection) + { + foreach (NpgsqlParameter o in collection) + { + if (o.Value == null) + o.Value = DBNull.Value; + else if (o.Value == DBNull.Value) + continue; + else + { + if (o.NpgsqlDbType == NpgsqlDbType.Text || o.NpgsqlDbType == NpgsqlDbType.Varchar) + { + string oValue = (string)o.Value; + oValue = oValue.Trim('\0'); + o.Value = oValue; + } + } + } + } + + public static void AddParameter(this NpgsqlParameterCollection collection, string parameterName, NpgsqlDbType columnType, object value) + { + if (value == null) + { + collection.AddWithValue(parameterName, columnType, DBNull.Value); + } + else + { + if (value is ushort && columnType == NpgsqlDbType.Integer) + { + collection.AddWithValue(parameterName, columnType, Convert.ToInt32((ushort)value)); + } + else if (value is uint && columnType == NpgsqlDbType.Bigint) + { + collection.AddWithValue(parameterName, columnType, Convert.ToInt64((uint)value)); + } + else + { + collection.AddWithValue(parameterName, columnType, value); + } + + if (value is string) + { + string s = (string)value; + if (s.Contains("\0")) + s = s.Replace("\0", ""); + } + } + } + + public static byte[] GetByteArray(this NpgsqlDataReader dataReader, int ordinal) + { + if (dataReader.IsDBNull(ordinal)) + return null; + + Stream stream = dataReader.GetStream(ordinal); + byte[] buffer = new byte[stream.Length]; + if (stream.Read(buffer, 0, buffer.Length) != buffer.Length) + throw new IOException("failed to read stream"); + stream.Close(); + return buffer; + } + + public static void SetAllNull(this NpgsqlParameterCollection collection) + { + foreach (NpgsqlParameter o in collection) + { + o.Value = DBNull.Value; + } + } + } + +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Pat.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Pat.cs new file mode 100644 index 0000000..4023f02 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Pat.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet knownPats; + public bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId) + { + if (knownPats == null) + knownPats = new HashSet(); + + DatabaseKeyPatEntry key = new DatabaseKeyPatEntry(currentNetworkId, currentTransportStreamId, programId); + if (knownPats.Contains(key)) + { + return true; + } + + bool alreadyKnownInDb = false; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + alreadyKnownInDb = TestPat(currentNetworkId, currentTransportStreamId, programId, key, alreadyKnownInDb, conn); + conn.Close(); + } + + if (alreadyKnownInDb) + return true; + + EnqueueTask(x => WritePat(x, currentNetworkId, currentTransportStreamId, pmtPid, programId)); + knownPats.Add(key); + return false; + } + + private bool TestPat(int currentNetworkId, int currentTransportStreamId, ushort programId, DatabaseKeyPatEntry key, bool alreadyKnownInDb, NpgsqlConnection conn) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_pat WHERE cnid = @cnid AND ctsid = @ctsid AND program_id = @program_id"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@program_id", NpgsqlDbType.Integer, (int)programId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + alreadyKnownInDb = true; + knownPats.Add(key); + } + dataReader.Close(); + return alreadyKnownInDb; + } + + private void WritePat(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId) + { + DatabaseKeyPatEntry key = new DatabaseKeyPatEntry(currentNetworkId, currentTransportStreamId, programId); + if (!TestPat(currentNetworkId, currentTransportStreamId, programId, key, false, conn)) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_pat " + + "(cnid, ctsid, pmt_pid, program_id) " + + "VALUES " + + "(@cnid, @ctsid, @pmt_pid, @program_id)"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@pmt_pid", NpgsqlDbType.Integer, pmtPid); + command.Parameters.AddWithValue("@program_id", NpgsqlDbType.Integer, (int)programId); + command.ExecuteNonQuery(); + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Pmt.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Pmt.cs new file mode 100644 index 0000000..5da1f3b --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Pmt.cs @@ -0,0 +1,587 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Iso14496_1; +using skyscraper5.Iso14496_1.Descriptors; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet knownPmts; + public bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + if (knownPmts == null) + knownPmts = new HashSet(); + DatabaseKeyPmtEntry entry = new DatabaseKeyPmtEntry(currentNetworkId, currentTransportStreamId, mapping.ProgramNumber); + if (knownPmts.Contains(entry)) + return true; + + bool result = false; + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_pmt WHERE cnid = @cnid AND ctsid = @ctsid AND program_number = @program_number"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)mapping.ProgramNumber); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + connection.Close(); + } + + if (result) + knownPmts.Add(entry); + return result; + } + + public bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + DatabaseKeyPmtEntry entry = new DatabaseKeyPmtEntry(currentNetworkId, currentTransportStreamId, mapping.ProgramNumber); + EnqueueTask(x => WritePmtEntry(x, currentNetworkId, currentTransportStreamId, mapping)); + knownPmts.Add(entry); + return true; + } + + private void WritePmtEntry(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_pmt WHERE cnid = @cnid AND ctsid = @ctsid AND program_number = @program_number"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, mapping.ProgramNumber); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool alreadyKnown = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + + if (alreadyKnown) + return; + + command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_pmt (cnid, ctsid, program_number, pcr_pid, maximum_bitrate,multiplex_buffer_utilization_bound_valid_flag, multiplex_ltw_offset_lower_bound,multiplex_ltw_offset_upper_bound, clock_accuracy_exponent, clock_accuracy_integer,\r\n external_clock_reference_indicator, sb_leak_rate, sb_size, ca_pid, ca_system_id, ca_private_data,\r\n private_data_specifiger, registration_additional_identification_info,\r\n registration_format_identifier, new_onid, new_sid, new_tsid, component_tag,\r\n metadata_input_leak_rate, metadata_buffer_size, metadata_output_leak_rate, scrambling_mode, es_id,\r\n audio_type, iso_639_language_code, scope_of_iod_label, iod_label, private_data_indicator,\r\n frame_rate, chroma_format, \"constrained_parameter_flag\", frame_rate_extension_flag, mpeg1_only_flag,\r\n multiple_framerate_flag, profile_and_level_indication, still_picture_flag, metadata_program_number,\r\n metadata_application_format, metadata_application_format_identifier, metadata_format,\r\n metadata_format_identifier, metadata_locator_record, metadata_locator_record_flag,\r\n metadata_service_id, mpeg_carriage_flag, metadata_private_data, transport_stream_id,\r\n transport_stream_location, free_format, audio_stream_id, layer, variable_rate_audio,\r\n copy_control_private_data, alignment_type, hierarchy_type, hierarchy_layer_type, tref_present_flag,hierarchy_channel, no_view_scalability_flag, hierarchy_embedded_layer_index,no_quality_scalability_flag, no_spatial_scalability_flag, no_temporal_scalability_flag)" + + "values " + + "(@cnid,@ctsid,@program_number,@pcr_pid,@maximum_bitrate,@multiplex_buffer_utilization_bound_valid_flag,@multiplex_ltw_offset_lower_bound,@multiplex_ltw_offset_upper_bound,@clock_accuracy_exponent, @clock_accuracy_integer,@external_clock_reference_indicator, @sb_leak_rate, @sb_size, @ca_pid, @ca_system_id, @ca_private_data,\r\n @private_data_specifiger, @registration_additional_identification_info,\r\n @registration_format_identifier, @new_onid, @new_sid, @new_tsid, @component_tag,\r\n @metadata_input_leak_rate, @metadata_buffer_size, @metadata_output_leak_rate, @scrambling_mode, @es_id,\r\n @audio_type, @iso_639_language_code, @scope_of_iod_label, @iod_label, @private_data_indicator,\r\n @frame_rate, @chroma_format, @constrained_parameter_flag, @frame_rate_extension_flag, @mpeg1_only_flag,\r\n @multiple_framerate_flag, @profile_and_level_indication, @still_picture_flag, @metadata_program_number,\r\n @metadata_application_format, @metadata_application_format_identifier, @metadata_format,\r\n @metadata_format_identifier, @metadata_locator_record, @metadata_locator_record_flag,\r\n @metadata_service_id, @mpeg_carriage_flag, @metadata_private_data, @transport_stream_id,\r\n @transport_stream_location, @free_format, @audio_stream_id, @layer, @variable_rate_audio,\r\n @copy_control_private_data, @alignment_type, @hierarchy_type, @hierarchy_layer_type, @tref_present_flag,\r\n @hierarchy_channel, @no_view_scalability_flag, @hierarchy_embedded_layer_index,@no_quality_scalability_flag, @no_spatial_scalability_flag, @no_temporal_scalability_flag)"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, mapping.ProgramNumber); + command.Parameters.AddParameter("@pcr_pid", NpgsqlDbType.Integer, mapping.PcrPid); + command.Parameters.AddParameter("@maximum_bitrate", NpgsqlDbType.Bigint, mapping.MaximumBitrate); + command.Parameters.AddParameter("@multiplex_buffer_utilization_bound_valid_flag", NpgsqlDbType.Boolean, mapping.MultiplexBufferUtilizationBoundValidFlag); + command.Parameters.AddParameter("@multiplex_ltw_offset_lower_bound", NpgsqlDbType.Integer, mapping.MultiplexLtwOffsetLowerBound); + command.Parameters.AddParameter("@multiplex_ltw_offset_upper_bound", NpgsqlDbType.Integer, mapping.MultiplexLtwOffsetUpperBound); + command.Parameters.AddParameter("@clock_accuracy_exponent", NpgsqlDbType.Integer, mapping.ClockAccuracyExponent); + command.Parameters.AddParameter("@clock_accuracy_integer", NpgsqlDbType.Integer, mapping.ClockAccuracyInteger); + command.Parameters.AddParameter("@external_clock_reference_indicator", NpgsqlDbType.Boolean, mapping.ExternalClockReferenceIndicator); + //@sb_leak_rate,@sb_size,@ca_pid,@ca_system_id,@ca_private_data,@private_data_specifiger,@registration_additional_identification_info,@registration_format_identifier,@new_onid,@new_sid,@new_tsid,@component_tag, + command.Parameters.AddParameter("@sb_leak_rate", NpgsqlDbType.Bigint, mapping.SbLeakRate); + command.Parameters.AddParameter("@sb_size", NpgsqlDbType.Bigint, mapping.SbSize); + command.Parameters.AddParameter("@ca_pid", NpgsqlDbType.Integer, mapping.CaPid); + command.Parameters.AddParameter("@ca_system_id", NpgsqlDbType.Integer, mapping.CaSystemId); + command.Parameters.AddParameter("@ca_private_data", NpgsqlDbType.Bytea, mapping.CaPrivateData); + command.Parameters.AddParameter("@private_data_specifiger", NpgsqlDbType.Bigint, mapping.PrivateDataIndicator); + command.Parameters.AddParameter("@registration_additional_identification_info", NpgsqlDbType.Bytea, mapping.RegistrationAdditionalIdentificationInfo); + command.Parameters.AddParameter("@registration_format_identifier", NpgsqlDbType.Bigint, mapping.RegistrationFormatIdentifier); + command.Parameters.AddParameter("@new_onid", NpgsqlDbType.Integer, mapping.NewOriginalNetworkId); + command.Parameters.AddParameter("@new_sid", NpgsqlDbType.Integer, mapping.NewServiceId); + command.Parameters.AddParameter("@new_tsid", NpgsqlDbType.Integer, mapping.NewTransportStreamId); + command.Parameters.AddParameter("@component_tag", NpgsqlDbType.Integer, mapping.ComponentTag); + //@metadata_input_leak_rate,@metadata_buffer_size,@metadata_output_leak_rate,@scrambling_mode,@es_id,@audio_type,@iso_639_language_code,@scope_of_iod_label,@iod_label,@private_data_indicator, + command.Parameters.AddParameter("@metadata_input_leak_rate", NpgsqlDbType.Integer, mapping.MetadataInputLeakRate); + command.Parameters.AddParameter("@metadata_buffer_size", NpgsqlDbType.Integer, mapping.MetadataBufferSize); + command.Parameters.AddParameter("@metadata_output_leak_rate", NpgsqlDbType.Integer, mapping.MetadataOutputLeakRate); + command.Parameters.AddParameter("@scrambling_mode", NpgsqlDbType.Integer, mapping.ScramblingMode); + command.Parameters.AddParameter("@es_id", NpgsqlDbType.Integer, mapping.EsId); + command.Parameters.AddParameter("@audio_type", NpgsqlDbType.Integer, (int?)mapping.AudioType); + command.Parameters.AddParameter("@iso_639_language_code", NpgsqlDbType.Varchar, mapping.Iso639LanguageCode); + command.Parameters.AddParameter("@scope_of_iod_label", NpgsqlDbType.Integer, mapping.ScopeOfIodLabel); + command.Parameters.AddParameter("@iod_label", NpgsqlDbType.Integer, mapping.IodLabel); + command.Parameters.AddParameter("@private_data_indicator", NpgsqlDbType.Bigint, mapping.PrivateDataIndicator); + //@frame_rate,@chroma_format,@constrained_parameter_flag,@frame_rate_extension_flag,@mpeg1_only_flag,@multiple_framerate_flag,@profile_and_level_indication,@still_picture_flag,@metadata_program_number, + command.Parameters.AddParameter("@frame_rate", NpgsqlDbType.Double, mapping.FrameRate); + command.Parameters.AddParameter("@chroma_format", NpgsqlDbType.Integer, mapping.ChromaFormat); + command.Parameters.AddParameter("@constrained_parameter_flag", NpgsqlDbType.Boolean, mapping.ConstrainedParameterFlag); + command.Parameters.AddParameter("@frame_rate_extension_flag", NpgsqlDbType.Integer, mapping.FrameRateExtensionFlag); + command.Parameters.AddParameter("@mpeg1_only_flag", NpgsqlDbType.Boolean, mapping.Mpeg1OnlyFlag); + command.Parameters.AddParameter("@multiple_framerate_flag", NpgsqlDbType.Boolean, mapping.MultipleFramerateFlag); + command.Parameters.AddParameter("@profile_and_level_indication", NpgsqlDbType.Integer, mapping.ProfileAndLevelIndication); + command.Parameters.AddParameter("@still_picture_flag", NpgsqlDbType.Boolean, mapping.StillPictureFlag); + command.Parameters.AddParameter("@metadata_program_number", NpgsqlDbType.Integer, mapping.MetadataProgramNumber); + //@metadata_application_format,@metadata_application_format_identifier,@metadata_format,@metadata_format_identifier,@metadata_locator_record,@metadata_locator_record_flag,@metadata_service_id, + command.Parameters.AddParameter("@metadata_application_format", NpgsqlDbType.Integer, mapping.MetadataApplicationFormat); + command.Parameters.AddParameter("@metadata_application_format_identifier", NpgsqlDbType.Bigint, mapping.MetadataApplicationFormatIdentifier); + command.Parameters.AddParameter("@metadata_format", NpgsqlDbType.Integer, mapping.MetadataFormat); + command.Parameters.AddParameter("@metadata_format_identifier", NpgsqlDbType.Bigint, mapping.MetadataFormatIdentifier); + command.Parameters.AddParameter("@metadata_locator_record", NpgsqlDbType.Bytea, mapping.MetadataLocatorRecord); + command.Parameters.AddParameter("@metadata_locator_record_flag", NpgsqlDbType.Boolean, mapping.MetadataLocatorRecordFlag); + command.Parameters.AddParameter("@metadata_service_id", NpgsqlDbType.Integer, mapping.MetadataServiceId); + //@mpeg_carriage_flag,@metadata_private_data,@transport_stream_id,@transport_stream_location,@free_format,@audio_stream_id,@layer,@variable_rate_audio,@copy_control_private_data,@alignment_type, + command.Parameters.AddParameter("@mpeg_carriage_flag", NpgsqlDbType.Integer, mapping.MpegCarriageFlag); + command.Parameters.AddParameter("@metadata_private_data", NpgsqlDbType.Bytea, mapping.MetadataPrivateData); + command.Parameters.AddParameter("@transport_stream_id", NpgsqlDbType.Integer, mapping.TransportStreamId); + command.Parameters.AddParameter("@transport_stream_location", NpgsqlDbType.Integer, mapping.TransportStreamLocation); + command.Parameters.AddParameter("@free_format", NpgsqlDbType.Boolean, mapping.FreeFormat); + command.Parameters.AddParameter("@audio_stream_id", NpgsqlDbType.Boolean, mapping.AudioStreamId); + command.Parameters.AddParameter("@layer", NpgsqlDbType.Integer, mapping.Layer); + command.Parameters.AddParameter("@variable_rate_audio", NpgsqlDbType.Boolean, mapping.VariableRateAudio); + command.Parameters.AddParameter("@copy_control_private_data", NpgsqlDbType.Bytea, mapping.CopyControlPrivateData); + command.Parameters.AddParameter("@alignment_type", NpgsqlDbType.Integer, mapping.AlignmentType); + //@hierarchy_type,@hierarchy_layer_type,@tref_present_flag,@hierarchy_channel,@no_view_scalability_flag,@hierarchy_embedded_layer_index,@no_quality_scalability_flag,@no_spatial_scalability_flag, + command.Parameters.AddParameter("@hierarchy_type", NpgsqlDbType.Integer, mapping.HierarchyType); + command.Parameters.AddParameter("@hierarchy_layer_type", NpgsqlDbType.Integer, mapping.HierarchyLayerType); + command.Parameters.AddParameter("@tref_present_flag", NpgsqlDbType.Boolean, mapping.TrefPresentFlag); + command.Parameters.AddParameter("@hierarchy_channel", NpgsqlDbType.Integer, mapping.HierarchyChannel); + command.Parameters.AddParameter("@no_view_scalability_flag", NpgsqlDbType.Boolean, mapping.NoViewScalabilityFlag); + command.Parameters.AddParameter("@hierarchy_embedded_layer_index", NpgsqlDbType.Integer, mapping.HierarchyEmbeddedLayerIndex); + command.Parameters.AddParameter("@no_quality_scalability_flag", NpgsqlDbType.Boolean, mapping.NoQualityScalabilityFlag); + command.Parameters.AddParameter("@no_spatial_scalability_flag", NpgsqlDbType.Boolean, mapping.NoSpatialScalabilityFlag); + //@no_temporal_scalability_flag)"; + command.Parameters.AddParameter("@no_temporal_scalability_flag", NpgsqlDbType.Boolean, mapping.NoTemporalScalabilityFlag); + + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + WritePmtInitialObjectDescriptor(conn, currentNetworkId, currentTransportStreamId, mapping); + WritePmtStreams(conn, currentNetworkId, currentTransportStreamId, mapping); + } + + private void WritePmtStreams(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + if (mapping.Streams == null) + return; + + bool[] streamsDone = new bool[0x1fff]; + foreach (ProgramMappingStream mappingStream in mapping.Streams) + { + if (streamsDone[mappingStream.ElementaryPid]) + continue; + + WritePmtStream(conn, currentNetworkId, currentTransportStreamId, mapping.ProgramNumber, mappingStream); + streamsDone[mappingStream.ElementaryPid] = true; + } + } + + private void WritePmtStream(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_pmt_streams (cnid, ctsid, program_number, stream_type, elementary_pid, component_tag, audio_type, iso_639_language_code, carousel_format_id, carousel_id, data_broadcast_id," + + " data_broadcast_selector, supplementary_independant_stream, editorial_classification, supplementary_language_code, asvc, bsid, component_type, main_id, frame_rate, mpeg1_only_flag, chroma_format, " + + "the_constrained_parameter_flag, frame_rate_extension_flag, multiple_framerate_flag, profile_and_level_indication, still_picture_flag, alignment_type, maximum_bitrate, audio_stream_id, free_format, " + + "layer, variable_rate_audio, constraint_set0_flag, avc_24hour_picture_flag, avc_compatible_flag, avc_still_present, constraint_set1_flag, constraint_set2_flag, constraint_set3_flag, " + + "constraint_set4_flag, constraint_set5_flag, frame_packing_sei_not_present, level_idc, profile_rdc, ca_pid, ca_system_id, ca_private_data, private_data_specifier, format_identifier, " + + "additional_identification_info, association_tag_selector, association_tag_transaction_id, association_tag_use, association_tag_private_data, association_tag_timeout, aac_type, " + + "aac_additional_info, aac_profile_and_level, saoc_de_flag, ancillary_data_identifier, mix_info_exists, substream1, substream2, substream3, copied_44bits, frame_only_constraint_flag, " + + "hdr_wcg_idc, hevc_24hr_picture_present_flag, hevc_still_present_flag, interlaced_source_flag, non_packed_constraint_flag, profile_compatibility_indication, profile_idc, " + + "profile_space, progressive_source_flag, sub_pic_hrd_params_not_present_flag, temporal_id_max, tier_flag, temporal_id_min, ac4_channel_mode, ac4_dialog_enhancement, ac4_dsi, " + + "leak_valid_flag, sb_leak_rate, sb_size, private_data_indicator, \"_90khz_flag\", picture_and_timing_info_present_flag, hdr_management_valid_flag, k, n, num_units_in_tick, " + + "target_schedule_idx, target_schedule_idx_not_present_flag, adaption_field_data_identifier, fixed_frame_rate_flag, picture_to_display_conversion_flag, temporal_poc_flag, " + + "mpeg4_audio_profile_and_level, aac_channel_configuration, aac_profile, aac_additional_information, scrambling_mode, related_content_descriptor_present, num_t2mi_streams, " + + "t2mi_stream_id, pcr_iscr_common_clock_flag, audio_specific_config, audio_profile_level_indication)\r\n " + + "values\r\n " + + "(@cnid,@ctsid,@program_number,@stream_type,@elementary_pid,@component_tag,@audio_type,@iso_639_language_code,@carousel_format_id,@carousel_id,@data_broadcast_id,@data_broadcast_selector,@supplementary_independant_stream, @editorial_classification,\r\n @supplementary_language_code, @asvc, @bsid, @component_type, @main_id, @frame_rate,\r\n @mpeg1_only_flag, @chroma_format, @constrained_parameter_flag, @frame_rate_extension_flag,\r\n @multiple_framerate_flag, @profile_and_level_indication, @still_picture_flag, @alignment_type,\r\n @maximum_bitrate, @audio_stream_id, @free_format, @layer, @variable_rate_audio,\r\n @constraint_set0_flag, @avc_24hour_picture_flag, @avc_compatible_flag, @avc_still_present,\r\n @constraint_set1_flag, @constraint_set2_flag, @constraint_set3_flag, @constraint_set4_flag,\r\n @constraint_set5_flag, @frame_packing_sei_not_present, @level_idc, @profile_rdc, @ca_pid,\r\n @ca_system_id, @ca_private_data, @private_data_specifier, @format_identifier,\r\n @additional_identification_info, @association_tag_selector, @association_tag_transaction_id,\r\n @association_tag_use, @association_tag_private_data, @association_tag_timeout, @aac_type,\r\n @aac_additional_info, @aac_profile_and_level, @saoc_de_flag, @ancillary_data_identifier,\r\n @mix_info_exists, @substream1, @substream2, @substream3, @copied_44bits,\r\n @frame_only_constraint_flag, @hdr_wcg_idc, @hevc_24hr_picture_present_flag,\r\n @hevc_still_present_flag, @interlaced_source_flag, @non_packed_constraint_flag,\r\n @profile_compatibility_indication, @profile_idc, @profile_space, @progressive_source_flag,\r\n @sub_pic_hrd_params_not_present_flag, @temporal_id_max, @tier_flag, @temporal_id_min,\r\n @ac4_channel_mode, @ac4_dialog_enhancement, @ac4_dsi, @leak_valid_flag, @sb_leak_rate, @sb_size,\r\n @private_data_indicator, @_90khz_flag, @picture_and_timing_info_present_flag,\r\n @hdr_management_valid_flag, @k, @n, @num_units_in_tick, @target_schedule_idx,\r\n @target_schedule_idx_not_present_flag, @adaption_field_data_identifier,\r\n @fixed_frame_rate_flag, @picture_to_display_conversion_flag, @temporal_poc_flag,\r\n @mpeg4_audio_profile_and_level, @aac_channel_configuration, @aac_profile,\r\n @aac_additional_information, @scrambling_mode, @related_content_descriptor_present,\r\n @num_t2mi_streams, @t2mi_stream_id, @pcr_iscr_common_clock_flag, @audio_specific_config, @audio_profile_level_indication);"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, mappingProgramNumber); + command.Parameters.AddParameter("@stream_type", NpgsqlDbType.Integer, (int)stream.StreamType); + command.Parameters.AddParameter("@elementary_pid", NpgsqlDbType.Integer, stream.ElementaryPid); + command.Parameters.AddParameter("@component_tag", NpgsqlDbType.Integer, stream.ComponentTag); + command.Parameters.AddParameter("@audio_type", NpgsqlDbType.Integer, (int?)stream.AudioType); + command.Parameters.AddParameter("@iso_639_language_code", NpgsqlDbType.Varchar, stream.Iso639LanguageCode); + command.Parameters.AddParameter("@carousel_format_id", NpgsqlDbType.Integer, stream.CarouselFormatId); + command.Parameters.AddParameter("@carousel_id", NpgsqlDbType.Bigint, stream.CarouselId); + command.Parameters.AddParameter("@data_broadcast_id", NpgsqlDbType.Integer, stream.DataBroadcastId); + command.Parameters.AddParameter("@data_broadcast_selector", NpgsqlDbType.Bytea, stream.DataBroadcastSelector); + //@supplementary_independant_stream,@editorial_classification,@supplementary_language_code,@asvc,@bsid,@component_type,@main_id,@frame_rate,@mpeg1_only_flag,@chroma_format,@constrained_parameter_flag, + command.Parameters.AddParameter("@supplementary_independant_stream", NpgsqlDbType.Boolean, stream.SupplementaryIndependantStream); + command.Parameters.AddParameter("@editorial_classification", NpgsqlDbType.Integer, (int?)stream.EditorialClassification); + command.Parameters.AddParameter("@supplementary_language_code", NpgsqlDbType.Varchar, stream.SupplementaryLanguageCode); + command.Parameters.AddParameter("@asvc", NpgsqlDbType.Integer, stream.Asvc); + command.Parameters.AddParameter("@bsid", NpgsqlDbType.Integer, stream.BSID); + command.Parameters.AddParameter("@component_type", NpgsqlDbType.Integer, (int?)stream.ComponentType); + command.Parameters.AddParameter("@main_id", NpgsqlDbType.Integer, stream.MainId); + command.Parameters.AddParameter("@frame_rate", NpgsqlDbType.Double, stream.FrameRate); + command.Parameters.AddParameter("@mpeg1_only_flag", NpgsqlDbType.Boolean, stream.Mpeg1OnlyFlag); + command.Parameters.AddParameter("@chroma_format", NpgsqlDbType.Integer, stream.ChromaFormat); + command.Parameters.AddParameter("@constrained_parameter_flag", NpgsqlDbType.Boolean, stream.ConstrainedParameterFlag); + //@frame_rate_extension_flag,@multiple_framerate_flag,@profile_and_level_indication,@still_picture_flag,@alignment_type,@maximum_bitrate,@audio_stream_id,@free_format,@layer,@variable_rate_audio, + command.Parameters.AddParameter("@frame_rate_extension_flag", NpgsqlDbType.Integer, stream.FrameRateExtensionFlag); + command.Parameters.AddParameter("@multiple_framerate_flag", NpgsqlDbType.Boolean, stream.MultipleFramerateFlag); + command.Parameters.AddParameter("@profile_and_level_indication", NpgsqlDbType.Integer, stream.ProfileAndLevelIndication); + command.Parameters.AddParameter("@still_picture_flag", NpgsqlDbType.Boolean, stream.StillPictureFlag); + command.Parameters.AddParameter("@alignment_type", NpgsqlDbType.Integer, stream.AlignmentType); + command.Parameters.AddParameter("@maximum_bitrate", NpgsqlDbType.Bigint, stream.MaximumBitrate); + command.Parameters.AddParameter("@audio_stream_id", NpgsqlDbType.Boolean, stream.AudioStreamId); + command.Parameters.AddParameter("@free_format", NpgsqlDbType.Boolean, stream.FreeFormat); + command.Parameters.AddParameter("@layer", NpgsqlDbType.Integer, stream.Layer); + command.Parameters.AddParameter("@variable_rate_audio", NpgsqlDbType.Boolean, stream.VariableRateAudio); + //@constraint_set0_flag,@avc_24hour_picture_flag,@avc_compatible_flag,@avc_still_present,@constraint_set1_flag,@constraint_set2_flag,@constraint_set3_flag,@constraint_set4_flag,@constraint_set5_flag, + command.Parameters.AddParameter("@constraint_set0_flag", NpgsqlDbType.Boolean, stream.ConstraintSet0Flag); + command.Parameters.AddParameter("@constraint_set0_flag", NpgsqlDbType.Boolean, stream.ConstraintSet0Flag); + command.Parameters.AddParameter("@avc_24hour_picture_flag", NpgsqlDbType.Boolean, stream.Avc24HourPictureFlag); + command.Parameters.AddParameter("@avc_compatible_flag", NpgsqlDbType.Integer, stream.AvcCompatibleFlags); + command.Parameters.AddParameter("@avc_still_present", NpgsqlDbType.Boolean, stream.AvcStillPresent); + command.Parameters.AddParameter("@constraint_set1_flag", NpgsqlDbType.Boolean, stream.ConstraintSet1Flag); + command.Parameters.AddParameter("@constraint_set2_flag", NpgsqlDbType.Boolean, stream.ConstraintSet2Flag); + command.Parameters.AddParameter("@constraint_set3_flag", NpgsqlDbType.Boolean, stream.ConstraintSet3Flag); + command.Parameters.AddParameter("@constraint_set4_flag", NpgsqlDbType.Boolean, stream.ConstraintSet4Flag); + command.Parameters.AddParameter("@constraint_set5_flag", NpgsqlDbType.Boolean, stream.ConstraintSet5Flag); + //@frame_packing_sei_not_present,@level_idc,@profile_rdc,@ca_pid,@ca_system_id,@ca_private_data,@private_data_specifier,@format_identifier,@additional_identification_info,@association_tag_selector, + command.Parameters.AddParameter("@frame_packing_sei_not_present", NpgsqlDbType.Boolean, stream.FramePackingSeiNotPresent); + command.Parameters.AddParameter("@level_idc", NpgsqlDbType.Integer, stream.LevelIdc); + command.Parameters.AddParameter("@profile_rdc", NpgsqlDbType.Integer, stream.ProfileRdc); + command.Parameters.AddParameter("@ca_pid", NpgsqlDbType.Integer, stream.CaPid); + command.Parameters.AddParameter("@ca_system_id", NpgsqlDbType.Integer, stream.CaSystemId); + command.Parameters.AddParameter("@ca_private_data", NpgsqlDbType.Bytea, stream.CaPrivateData); + command.Parameters.AddParameter("@private_data_specifier", NpgsqlDbType.Bigint, stream.PrivateDataSpecifier); + command.Parameters.AddParameter("@format_identifier", NpgsqlDbType.Bigint, stream.FormatIdentifier); + command.Parameters.AddParameter("@additional_identification_info", NpgsqlDbType.Bytea, stream.AdditionalIdentificationInfo); + command.Parameters.AddParameter("@association_tag_selector", NpgsqlDbType.Bytea, stream.AssociationTagSelector); + //@association_tag_transaction_id,@association_tag_use,@association_tag_private_data,@association_tag_timeout,@aac_type,@aac_additional_info,@aac_profile_and_level,@saoc_de_flag, + command.Parameters.AddParameter("@association_tag_transaction_id", NpgsqlDbType.Bigint, stream.AssociationTagTransactionId); + command.Parameters.AddParameter("@association_tag_use", NpgsqlDbType.Integer, stream.AssociationTagUse); + command.Parameters.AddParameter("@association_tag_private_data", NpgsqlDbType.Bytea, stream.AssociationTagPrivateData); + command.Parameters.AddParameter("@association_tag_timeout", NpgsqlDbType.Bigint, stream.AssociationTagTimeOut); + command.Parameters.AddParameter("@aac_type", NpgsqlDbType.Integer, stream.AacType); + command.Parameters.AddParameter("@aac_additional_info", NpgsqlDbType.Bytea, stream.AacAdditionalInfo); + command.Parameters.AddParameter("@aac_profile_and_level", NpgsqlDbType.Integer, stream.AacProfileAndLevel); + command.Parameters.AddParameter("@saoc_de_flag", NpgsqlDbType.Boolean, stream.SaocDeFlag); + //@ancillary_data_identifier,@mix_info_exists,@substream1,@substream2,@substream3,@copied_44bits,@frame_only_constraint_flag,@hdr_wcg_idc,@hevc_24hr_picture_present_flag,@hevc_still_present_flag, + if (stream.AncillaryDataDescriptor != null) + command.Parameters.AddParameter("@ancillary_data_identifier", NpgsqlDbType.Integer, stream.AncillaryDataDescriptor.AncillaryDataIdentifier); + else + command.Parameters.AddWithValue("@ancillary_data_identifier", NpgsqlDbType.Integer, DBNull.Value); + command.Parameters.AddParameter("@mix_info_exists", NpgsqlDbType.Boolean, stream.MixInfoExists); + command.Parameters.AddParameter("@substream1", NpgsqlDbType.Integer, stream.Substream1); + command.Parameters.AddParameter("@substream2", NpgsqlDbType.Integer, stream.Substream2); + command.Parameters.AddParameter("@substream3", NpgsqlDbType.Integer, stream.Substream3); + command.Parameters.AddParameter("@copied_44bits", NpgsqlDbType.Bigint, stream.Copied44bits); + command.Parameters.AddParameter("@frame_only_constraint_flag", NpgsqlDbType.Boolean, stream.FrameOnlyConstraintFlag); + command.Parameters.AddParameter("@hdr_wcg_idc", NpgsqlDbType.Integer, stream.HdrWcgIdc); + command.Parameters.AddParameter("@hevc_24hr_picture_present_flag", NpgsqlDbType.Boolean, stream.Hevc24hrPicturePresentFlag); + command.Parameters.AddParameter("@hevc_still_present_flag", NpgsqlDbType.Boolean, stream.HevcStillPresentFlag); + //@interlaced_source_flag,@non_packed_constraint_flag,@profile_compatibility_indication,@profile_idc,@profile_space,@progressive_source_flag,@sub_pic_hrd_params_not_present_flag,@temporal_id_max, + command.Parameters.AddParameter("@interlaced_source_flag", NpgsqlDbType.Boolean, stream.InterlacedSourceFlag); + command.Parameters.AddParameter("@non_packed_constraint_flag", NpgsqlDbType.Boolean, stream.NonPackedConstraintFlag); + command.Parameters.AddParameter("@profile_compatibility_indication", NpgsqlDbType.Bigint, stream.ProfileCompatibilityIndication); + command.Parameters.AddParameter("@profile_idc", NpgsqlDbType.Bigint, stream.ProfileIdc); + command.Parameters.AddParameter("@profile_space", NpgsqlDbType.Integer, stream.ProfileSpace); + command.Parameters.AddParameter("@progressive_source_flag", NpgsqlDbType.Boolean, stream.ProgressiveSourceFlag); + command.Parameters.AddParameter("@sub_pic_hrd_params_not_present_flag", NpgsqlDbType.Boolean, stream.SubPicHrdParamsNotPresentFlag); + command.Parameters.AddParameter("@temporal_id_max", NpgsqlDbType.Integer, stream.TemporalIdMax); + //@tier_flag,@temporal_id_min,@ac4_channel_mode,@ac4_dialog_enhancement,@ac4_dsi,@leak_valid_flag,@sb_leak_rate,@sb_size,@private_data_indicator,@_90khz_flag,@picture_and_timing_info_present_flag, + command.Parameters.AddParameter("@tier_flag", NpgsqlDbType.Boolean, stream.TierFlag); + command.Parameters.AddParameter("@temporal_id_min", NpgsqlDbType.Integer, stream.TemporalIdMin); + command.Parameters.AddParameter("@ac4_channel_mode", NpgsqlDbType.Integer, stream.Ac4ChannelMode); + command.Parameters.AddParameter("@ac4_dialog_enhancement", NpgsqlDbType.Boolean, stream.Ac4DialogEnhancement); + command.Parameters.AddParameter("@ac4_dsi", NpgsqlDbType.Bytea, stream.Ac4Dsi); + command.Parameters.AddParameter("@ac4_dsi", NpgsqlDbType.Bytea, stream.Ac4Dsi); + command.Parameters.AddParameter("@leak_valid_flag", NpgsqlDbType.Boolean, stream.LeakValidFlag); + command.Parameters.AddParameter("@sb_leak_rate", NpgsqlDbType.Bigint, stream.SbLeakRate); + command.Parameters.AddParameter("@sb_size", NpgsqlDbType.Bigint, stream.SbSize); + command.Parameters.AddParameter("@private_data_indicator", NpgsqlDbType.Bigint, stream.PrivateDataIndicator); + command.Parameters.AddParameter("@_90khz_flag", NpgsqlDbType.Boolean, stream._90khzFlag); + command.Parameters.AddParameter("@picture_and_timing_info_present_flag", NpgsqlDbType.Boolean, stream.PictureAndTimingInfoPresentFlag); + //@hdr_management_valid_flag,@k,@n,@num_units_in_tick,@target_schedule_idx,@target_schedule_idx_not_present_flag,@adaption_field_data_identifier,@fixed_frame_rate_flag, + command.Parameters.AddParameter("@hdr_management_valid_flag", NpgsqlDbType.Boolean, stream.HdrManagementValidFlag); + command.Parameters.AddParameter("@k", NpgsqlDbType.Bigint, stream.K); + command.Parameters.AddParameter("@n", NpgsqlDbType.Bigint, stream.N); + command.Parameters.AddParameter("@num_units_in_tick", NpgsqlDbType.Bigint, stream.NumUnitsInTick); + command.Parameters.AddParameter("@target_schedule_idx", NpgsqlDbType.Integer, stream.TargetScheduleIdx); + command.Parameters.AddParameter("@target_schedule_idx_not_present_flag", NpgsqlDbType.Boolean, stream.TargetScheduleIdxNotPresentFlag); + command.Parameters.AddParameter("@adaption_field_data_identifier", NpgsqlDbType.Integer, stream.AdaptionFieldDataIdentifier); + command.Parameters.AddParameter("@fixed_frame_rate_flag", NpgsqlDbType.Boolean, stream.FixedFrameRateFlag); + //@picture_to_display_conversion_flag,@temporal_poc_flag,@mpeg4_audio_profile_and_level,@aac_channel_configuration,@aac_profile,@aac_additional_information,@scrambling_mode, + command.Parameters.AddParameter("@picture_to_display_conversion_flag", NpgsqlDbType.Boolean, stream.PictureToDisplayConversionFlag); + command.Parameters.AddParameter("@temporal_poc_flag", NpgsqlDbType.Boolean, stream.TemporalPocFlag); + command.Parameters.AddParameter("@mpeg4_audio_profile_and_level", NpgsqlDbType.Integer, stream.Mpeg4AudioProfileAndLevel); + command.Parameters.AddParameter("@aac_channel_configuration", NpgsqlDbType.Integer, stream.AacChannelConfiguration); + command.Parameters.AddParameter("@aac_profile", NpgsqlDbType.Integer, stream.AacProfile); + command.Parameters.AddParameter("@aac_additional_information", NpgsqlDbType.Bytea, stream.AacAdditionalInfo); + command.Parameters.AddParameter("@scrambling_mode", NpgsqlDbType.Integer, stream.ScramblingMode); + //@related_content_descriptor_present,@num_t2mi_streams,@t2mi_stream_id,@pcr_iscr_common_clock_flag); "; + command.Parameters.AddParameter("@related_content_descriptor_present",NpgsqlDbType.Boolean,stream.RelatedContentDescriptorPresent); + command.Parameters.AddParameter("@num_t2mi_streams", NpgsqlDbType.Integer,stream.NumT2MiStreams); + command.Parameters.AddParameter("@t2mi_stream_id", NpgsqlDbType.Integer,stream.T2MiStreamId); + command.Parameters.AddParameter("@pcr_iscr_common_clock_flag", NpgsqlDbType.Boolean,stream.PcrIscrCommonClockFlag); + //audio_specific_config, audio_profile_level_indication + command.Parameters.AddParameter("@audio_specific_config", NpgsqlDbType.Bytea, stream.AudioSpecifConfig); + command.Parameters.AddParameter("@audio_profile_level_indication", NpgsqlDbType.Bytea, stream.AudioProfileLevelIndication); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + WritePmtStreamApplications(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber,stream); + WritePmtStreamAudioPreselections(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream); + WritePmtStreamFluxmuxChannels(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream); + WritePmtStreamIpMacNotificationInfo(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream); + WritePmtStreamSubtitlings(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream); + WritePmtStreamTeletexts(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream, false); + WritePmtStreamVbiData(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream); + WritePmtStreamTeletexts(conn, currentNetworkId, currentTransportStreamId, mappingProgramNumber, stream, true); + } + + private void WritePmtStreamVbiData(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + if (stream.VbiData == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_pmt_streams_vbi_data (cnid, ctsid, program_number, elementary_pid, ordinal, data_service_id, field_parity, line_offset) " + + "values (@cnid, @ctsid, @program_number, @elementary_pid, @ordinal, @data_service_id, @field_parity, @line_offset)"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)mappingProgramNumber); + command.Parameters.AddWithValue("@elementary_pid", NpgsqlDbType.Integer, stream.ElementaryPid); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@data_service_id", NpgsqlDbType.Integer); + command.Parameters.Add("@field_parity", NpgsqlDbType.Boolean); + command.Parameters.Add("@line_offset", NpgsqlDbType.Integer); + + int ordinalIterator = 0; + foreach (VbiDataDescriptor.DataService dataService in stream.VbiData) + { + for (int i = 0; i < dataService.FieldParity.Length; i++) + { + int ordinal = ordinalIterator++; + bool parity = dataService.FieldParity[i]; + int lineOffset = dataService.LineOffset[i]; + command.Parameters["@ordinal"].Value = ordinalIterator; + command.Parameters["@data_service_id"].Value = dataService.DataServiceId; + command.Parameters["@field_parity"].Value = parity; + command.Parameters["@line_offset"].Value = lineOffset; + command.ExecuteNonQuery(); + } + } + } + + private void WritePmtStreamTeletexts(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream, bool vbi) + { + TeletextDescriptor.TeletextDescriptorTeletext[] teletext; + string tableName; + + if (vbi) + { + teletext = stream.VbiTeletexts; + tableName = "dvb_pmt_streams_vbi_teletexts"; + } + else + { + teletext = stream.Teletexts; + tableName = "dvb_pmt_streams_teletexts"; + } + + if (teletext == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + String.Format( + "insert into {0} (cnid, ctsid, program_number, elementary_pid, ordinal, iso_639_language_code, teletext_type, teletext_magazine_number, teletext_page_number) " + + "values " + + "(@cnid, @ctsid, @program_number, @elementary_pid, @ordinal, @iso_639_language_code, @teletext_type, @teletext_magazine_number, @teletext_page_number)", + tableName); + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, (int)mappingProgramNumber); + command.Parameters.AddParameter("@elementary_pid", NpgsqlDbType.Integer, (int)stream.ElementaryPid); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar); + command.Parameters.Add("@teletext_type", NpgsqlDbType.Integer); + command.Parameters.Add("@teletext_magazine_number", NpgsqlDbType.Integer); + command.Parameters.Add("@teletext_page_number", NpgsqlDbType.Integer); + + for (int i = 0; i < teletext.Length; i++) + { + command.Parameters["@ordinal"].Value = i; + command.Parameters["@iso_639_language_code"].Value = teletext[i].Iso639LanguageCode.Replace("\0", ""); ; + command.Parameters["@teletext_type"].Value = (int)teletext[i].TeletextType; + command.Parameters["@teletext_magazine_number"].Value = (int)teletext[i].TeletextMagazineNumber; + command.Parameters["@teletext_page_number"].Value = (int)teletext[i].TeletextPageNumber; + command.ExecuteNonQuery(); + } + } + + private void WritePmtStreamSubtitlings(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + if (stream.Subtitlings == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_pmt_streams_subtitlings (cnid, ctsid, program_number, elementary_pid, ordinal, iso_639_language_code, subtitling_type, composition_page_id, ancillary_page_id) " + + "values " + + "(@cnid, @ctsid, @program_number, @elementary_pid, @ordinal, @iso_639_language_code, @subtitling_type, @composition_page_id, @ancillary_page_id)"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, (int)mappingProgramNumber); + command.Parameters.AddParameter("@elementary_pid", NpgsqlDbType.Integer, (int)stream.ElementaryPid); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar); + command.Parameters.Add("@subtitling_type", NpgsqlDbType.Integer); + command.Parameters.Add("@composition_page_id", NpgsqlDbType.Integer); + command.Parameters.Add("@ancillary_page_id", NpgsqlDbType.Integer); + + for (int i = 0; i < stream.Subtitlings.Length; i++) + { + SubtitlingDescriptor.Subtitling subtitling = stream.Subtitlings[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@iso_639_language_code"].Value = subtitling.Iso639LanguageCode; + command.Parameters["@subtitling_type"].Value = subtitling.SubtitlingType; + command.Parameters["@composition_page_id"].Value = (int)subtitling.CompositionPageId; + command.Parameters["@ancillary_page_id"].Value = (int)subtitling.AncillaryPageId; + command.ExecuteNonQuery(); + } + } + + private void WritePmtStreamIpMacNotificationInfo(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + if (stream.IpMacNotificationInfo == null) + return; + + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "insert into dvb_pmt_streams_ip_mac_notification_info (cnid, ctsid, program_number, elementary_pid, platform_id,action_type, int_versioning_flag, int_version) " + + "values (@cnid, @ctsid, @program_number, @elementary_pid, @platform_id, @action_type, @int_versioning_flag, @int_version)"; + cmd.Parameters.AddWithValue("@cnid",NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)mappingProgramNumber); + cmd.Parameters.AddWithValue("@elementary_pid", NpgsqlDbType.Integer, stream.ElementaryPid); + cmd.Parameters.Add("@platform_id", NpgsqlDbType.Integer); + cmd.Parameters.Add("@action_type", NpgsqlDbType.Integer); + cmd.Parameters.Add("@int_versioning_flag", NpgsqlDbType.Boolean); + cmd.Parameters.Add("@int_version", NpgsqlDbType.Integer); + + foreach (DataBroadcastIdDescriptor.IpMacPlatform ipMacPlatform in stream.IpMacNotificationInfo) + { + cmd.Parameters["@platform_id"].Value = (int)ipMacPlatform.PlatformId; + cmd.Parameters["@action_type"].Value = (int)ipMacPlatform.ActionType; + cmd.Parameters["@int_versioning_flag"].Value = ipMacPlatform.IntVersioningFlag; + cmd.Parameters["@int_version"].Value = (int)ipMacPlatform.IntVersion; + cmd.ExecuteNonQuery(); + } + } + + private void WritePmtStreamFluxmuxChannels(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + if (stream.FlexMuxChannels == null) + return; + + foreach (FmcDescriptor.Fmc flexMuxChannel in stream.FlexMuxChannels) + { + throw new NotImplementedException(); + } + } + + private void WritePmtStreamAudioPreselections(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + if (stream.AudioPreselection == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_pmt_streams_audio_preselections (cnid, ctsid, program_number, elementary_pid, preselection_id, audio_rendering_indication, audio_description, spoken_subtitles, dialogue_enhancement, interactivity_enabled, iso_639_language_code,\r\n message_id, component_tags, future_extension) " + + "values " + + "(@cnid,@ctsid,@program_number,@elementary_pid,@preselection_id,@audio_rendering_indication,@audio_description,@spoken_subtitles,@dialogue_enhancement,@interactivity_enabled,@iso_639_language_code,@message_id, @component_tags, @future_extension)"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)mappingProgramNumber); + command.Parameters.AddWithValue("@elementary_pid", NpgsqlDbType.Integer, stream.ElementaryPid); + command.Parameters.Add("@preselection_id", NpgsqlDbType.Integer); + command.Parameters.Add("@audio_rendering_indication", NpgsqlDbType.Integer); + command.Parameters.Add("@audio_description", NpgsqlDbType.Boolean); + command.Parameters.Add("@spoken_subtitles", NpgsqlDbType.Boolean); + command.Parameters.Add("@dialogue_enhancement", NpgsqlDbType.Boolean); + command.Parameters.Add("@interactivity_enabled", NpgsqlDbType.Boolean); + command.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar); + //@message_id, @component_tags, @future_extension)"; + command.Parameters.Add("@message_id", NpgsqlDbType.Integer); + command.Parameters.Add("@component_tags", NpgsqlDbType.Bytea); + command.Parameters.Add("@future_extension", NpgsqlDbType.Bytea); + + foreach (AudioPreselectionDescriptor.AudioPreselection audioPreselection in stream.AudioPreselection) + { + command.Parameters["@preselection_id"].Value = audioPreselection.PreselectionId; + command.Parameters["@audio_rendering_indication"].Value = (int)audioPreselection.AudioRenderingIndication; + command.Parameters["@audio_description"].Value = audioPreselection.AudioDescription; + command.Parameters["@spoken_subtitles"].Value = audioPreselection.SpokenSubtitles; + command.Parameters["@dialogue_enhancement"].Value = audioPreselection.DialogueEnhancement; + command.Parameters["@interactivity_enabled"].Value = audioPreselection.InteractivityEnabled; + command.Parameters["@iso_639_language_code"].Value = audioPreselection.Iso639LangaugeCode; + command.Parameters["@message_id"].Value = audioPreselection.MessageId; + command.Parameters["@component_tags"].Value = audioPreselection.ComponentTags; + command.Parameters["@future_extension"].Value = audioPreselection.FutureExtension; + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + } + } + + private void WritePmtStreamApplications(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ushort mappingProgramNumber, ProgramMappingStream stream) + { + if (stream.Applications == null) + return; + + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into dvb_pmt_streams_applications (cnid, ctsid, program_number, elementary_pid, ordinal, application_type, ait_version_number) " + + "values " + + "(@cnid, @ctsid, @program_number, @elementary_pid, @ordinal, @application_type, @ait_version_number);"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, (int)mappingProgramNumber); + command.Parameters.AddParameter("@elementary_pid", NpgsqlDbType.Integer, (int)stream.ElementaryPid); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@application_type", NpgsqlDbType.Integer); + command.Parameters.Add("@ait_version_number", NpgsqlDbType.Integer); + for (int i = 0; i < stream.Applications.Count; i++) + { + ApplicationSignallingDescriptor.ApplicationSignal applicationSignal = stream.Applications[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@application_type"].Value = applicationSignal.ApplicationType; + command.Parameters["@ait_version_number"].Value = applicationSignal.AitVersionNumber; + command.ExecuteNonQuery(); + } + } + + + private void WritePmtInitialObjectDescriptor(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + if (mapping.InitialObjectDescriptor == null) + return; + + InitialObjectDescriptor iod = mapping.InitialObjectDescriptor; + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "insert into dvb_pmt_initial_object_descriptors (cnid, ctsid, program_number,\r\n graphics_profile_level_indication, visual_profile_level_indication,\r\n audio_profile_level_indication, scene_profile_level_indication,\r\n od_profile_level_indication, url_string,\r\n include_inline_profile_level_flag, url_flag, object_descriptor_id) " + + "values (@cnid, @ctsid, @program_number, @graphics_profile_level_indication, @visual_profile_level_indication, @audio_profile_level_indication, @scene_profile_level_indication, @od_profile_level_indication, @url_string, @include_inline_profile_level_flag, @url_flag, @object_descriptor_id)"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)mapping.ProgramNumber); + cmd.Parameters.AddWithValue("@graphics_profile_level_indication", NpgsqlDbType.Integer, (int?)iod.GraphicsProfileLevelIndication); + cmd.Parameters.AddWithValue("@visual_profile_level_indication", NpgsqlDbType.Integer, (int?)iod.VisualProfileLevelIndication); + cmd.Parameters.AddWithValue("@audio_profile_level_indication", NpgsqlDbType.Integer, (int?)iod.AudioProfileLevelIndication); + cmd.Parameters.AddWithValue("@scene_profile_level_indication", NpgsqlDbType.Integer, (int?)iod.SceneProfileLevelIndication); + cmd.Parameters.AddWithValue("@od_profile_level_indication", NpgsqlDbType.Integer, (int?)iod.OdProfileLevelIndication); + //@url_string, @include_inline_profile_level_flag, @url_flag, @object_descriptor_id)"; + cmd.Parameters.AddWithValue("@url_string", NpgsqlDbType.Text, iod.UrlString); + cmd.Parameters.AddWithValue("@include_inline_profile_level_flag", NpgsqlDbType.Boolean, iod.IncludeInlineProfileLevelFlag); + cmd.Parameters.AddWithValue("@url_flag", NpgsqlDbType.Boolean, iod.UrlFlag); + cmd.Parameters.AddWithValue("@object_descriptor_id", NpgsqlDbType.Integer, iod.ObjectDescriptorId); + cmd.Parameters.CheckNulls(); + cmd.ExecuteNonQuery(); + } + + + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs new file mode 100644 index 0000000..de0e731 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Net; +using System.Net.NetworkInformation; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Rds.Messages; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore : DataStorage + { + public PostgresqlDataStore(NpgsqlConnectionStringBuilder stringBuilder) + { + connectionStringBuilder = stringBuilder; + } + + private NpgsqlConnectionStringBuilder connectionStringBuilder; + + public bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + throw new NotImplementedException(); + } + + public void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + throw new NotImplementedException(); + } + + public bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, + string programmeService2) + { + throw new NotImplementedException(); + } + + public bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, + string text) + { + throw new NotImplementedException(); + } + + public bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, + PTY.ProgrammeTypeCodes pty) + { + throw new NotImplementedException(); + } + + public bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, + int programNumber) + { + throw new NotImplementedException(); + } + + public bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + throw new NotImplementedException(); + } + + public void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + throw new NotImplementedException(); + } + + + + + + + + private static void SetNulls(NpgsqlCommand command) + { + foreach (NpgsqlParameter param in command.Parameters) + { + if (param.Value == null) + { + param.Value = DBNull.Value; + } + } + } + + + + + private int? detectedLocation; + public int? GetCurrentLocationId() + { + if (detectedLocation.HasValue) + return detectedLocation; + + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + Guid? locationUuid = null; + double? lon = null; + double? lat = null; + + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT uuid, ROUND(CAST(gps_lon as numeric),5), ROUND(CAST(gps_lat as numeric),5) FROM skyscraper5_blindscan_jobs\r\nWHERE tuner_std != 1\r\nORDER BY dateupdated DESC"; + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + locationUuid = dataReader.GetGuid(0); + lon = dataReader.GetDouble(1); + lat = dataReader.GetDouble(2); + } + dataReader.Close(); + command.Dispose(); + + if (!locationUuid.HasValue) + { + command = connection.CreateCommand(); + command.CommandText = "SELECT lonround, latround FROM docsis_locations WHERE guess_default = TRUE"; + dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + locationUuid = Guid.NewGuid(); + lon = dataReader.GetDouble(0); + lat = dataReader.GetDouble(1); + } + dataReader.Close(); + command.Dispose(); + } + + if (locationUuid.HasValue) + { + command = connection.CreateCommand(); + command.CommandText = "SELECT id FROM docsis_locations WHERE lonround = @lon AND latround = @lat"; + command.Parameters.AddWithValue("@lon", NpgsqlDbType.Double, lon.Value); + command.Parameters.AddWithValue("@lat", NpgsqlDbType.Double, lat.Value); + dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + detectedLocation = dataReader.GetInt32(0); + dataReader.Close(); + command.Dispose(); + connection.Close(); + return detectedLocation.Value; + } + dataReader.Close(); + + command = connection.CreateCommand(); + command.CommandText = "INSERT INTO docsis_locations (lonround,latround) VALUES (@lon,@lat) RETURNING id"; + command.Parameters.AddWithValue("@lon", NpgsqlDbType.Double, lon.Value); + command.Parameters.AddWithValue("@lat", NpgsqlDbType.Double, lat.Value); + dataReader = command.ExecuteReader(); + dataReader.Read(); + detectedLocation = dataReader.GetInt32(0); + dataReader.Close(); + connection.Close(); + return detectedLocation.Value; + } + connection.Close(); + } + + throw new NotImplementedException(); + } + + + + public HeadlessJob GetQueuedJob() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT uuid, jobtype, iarg1, sarg1 FROM skyscraper5_job_queue WHERE completed = FALSE ORDER BY dateadded"; + NpgsqlDataReader dataReader = command.ExecuteReader(); + HeadlessJob job = null; + if (dataReader.Read()) + { + job = new HeadlessJob(); + job.uuid = dataReader.GetGuid(0); + job.jobType = (HeadlessJobType)dataReader.GetInt32(1); + if (!dataReader.IsDBNull(2)) + job.iArg1 = dataReader.GetInt32(2); + if (!dataReader.IsDBNull(3)) + job.sArg1 = dataReader.GetString(3); + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + return job; + } + } + + public void SetQueuedJobComplete(HeadlessJob headlessJob) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "UPDATE skyscraper5_job_queue SET completed = TRUE, dateupdated = CURRENT_TIMESTAMP, version = version + 1 WHERE uuid = @uuid"; + command.Parameters.AddWithValue("@uuid", NpgsqlDbType.Uuid, headlessJob.uuid); + int executeNonQuery = command.ExecuteNonQuery(); + if (executeNonQuery != 1) + throw new DataException(String.Format("Didn't expect to update {0} rows.", executeNonQuery)); + command.Dispose(); + connection.Close(); + } + } + + public object[] GetPluginConnector() + { + return new object[] { connectionStringBuilder }; + } + } + +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Rst.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Rst.cs new file mode 100644 index 0000000..6acb817 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Rst.cs @@ -0,0 +1,73 @@ +using Npgsql; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _knownRsts; + + public bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime) + { + if (_knownRsts == null) + _knownRsts = new HashSet(); + + DatabaseKeyRst key = new DatabaseKeyRst(transportStreamId, originalNetworkId, serviceId, eventId, runningStatus, currentTime); + if (_knownRsts.Contains(key)) + return false; + + bool alreadyKnown; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT dateadded " + + "FROM dvb_rst " + + "WHERE tsid = @tsid " + + "AND onid = @onid " + + "AND sid = @sid " + + "AND eid = @eid " + + "AND running_status = @running_status " + + "AND dvb_time = @dvb_time"; + cmd.Parameters.AddWithValue("@tsid", NpgsqlTypes.NpgsqlDbType.Integer, (int)transportStreamId); + cmd.Parameters.AddWithValue("@onid", NpgsqlTypes.NpgsqlDbType.Integer, (int)originalNetworkId); + cmd.Parameters.AddWithValue("@sid", NpgsqlTypes.NpgsqlDbType.Integer, (int)serviceId); + cmd.Parameters.AddWithValue("@eid", NpgsqlTypes.NpgsqlDbType.Integer, (int)eventId); + cmd.Parameters.AddWithValue("@running_status", NpgsqlTypes.NpgsqlDbType.Integer, (int)runningStatus); + cmd.Parameters.AddWithValue("@dvb_time", NpgsqlTypes.NpgsqlDbType.Timestamp, currentTime); + NpgsqlDataReader dataReader = cmd.ExecuteReader(); + alreadyKnown = dataReader.Read(); + dataReader.Close(); + cmd.Dispose(); + conn.Close(); + } + + if (!alreadyKnown) + { + EnqueueTask(x => InsertRst(x, transportStreamId, originalNetworkId, serviceId, eventId, runningStatus, currentTime)); + } + _knownRsts.Add(key); + return !alreadyKnown; + } + + private void InsertRst(NpgsqlConnection conn, uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime) + { + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "INSERT INTO dvb_rst (tsid, onid, sid, eid, running_status, dvb_time) VALUES (@tsid, @onid, @sid, @eid, @running_status, @dvb_time)"; + cmd.Parameters.AddWithValue("@tsid", NpgsqlTypes.NpgsqlDbType.Integer, (int)transportStreamId); + cmd.Parameters.AddWithValue("@onid", NpgsqlTypes.NpgsqlDbType.Integer, (int)originalNetworkId); + cmd.Parameters.AddWithValue("@sid", NpgsqlTypes.NpgsqlDbType.Integer, (int)serviceId); + cmd.Parameters.AddWithValue("@eid", NpgsqlTypes.NpgsqlDbType.Integer, (int)eventId); + cmd.Parameters.AddWithValue("@running_status", NpgsqlTypes.NpgsqlDbType.Integer, (int)runningStatus); + cmd.Parameters.AddWithValue("@dvb_time", NpgsqlTypes.NpgsqlDbType.Timestamp, currentTime); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/SchemaMissingException.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/SchemaMissingException.cs new file mode 100644 index 0000000..683f825 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/SchemaMissingException.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + [Serializable] + internal class SchemaMissingException : ApplicationException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public SchemaMissingException() + { + } + + public SchemaMissingException(string message) : base(message) + { + } + + public SchemaMissingException(string message, Exception inner) : base(message, inner) + { + } + + protected SchemaMissingException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Scte35.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Scte35.cs new file mode 100644 index 0000000..5597a38 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Scte35.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded " + + "FROM scte35_splices " + + "WHERE cnid = @cnid " + + "AND ctsid = @ctsid " + + "AND program_number = @program_number " + + "AND splice_event_id = @splice_event_id"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddParameter("@splice_event_id", NpgsqlDbType.Bigint, (long)spliceInsert.SpliceEventId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + } + + return result; + } + + public void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, + SpliceInsert spliceInsert) + { + EnqueueTask(x => WriteScte35SpliceInsert(x, currentNetworkId, currentTransportStreamId, programNumber, spliceInsert)); + } + + private void WriteScte35SpliceInsert(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + if (TestForScte35SpliceInsert(currentNetworkId,currentTransportStreamId,programNumber,spliceInsert)) + { + return; + } + transaction.Commit(); + transaction = null; + transaction = connection.BeginTransaction(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into scte35_splices (cnid, ctsid, program_number, splice_event_id, splice_event_cancel_indicator, out_of_network_indicator, program_splice_flag, duration_flag, splice_immediate_flag, splice_time, duration_auto_return, duration, unique_program_id, avail_num, avails_expected) " + + "values " + + "(@cnid,@ctsid,@program_number,@splice_event_id,@splice_event_cancel_indicator,@out_of_network_indicator,@program_splice_flag,@duration_flag,@splice_immediate_flag,@splice_time,@duration_auto_return, @duration, @unique_program_id, @avail_num, @avails_expected)"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddParameter("@splice_event_id", NpgsqlDbType.Bigint, (long)spliceInsert.SpliceEventId); + command.Parameters.AddParameter("@splice_event_cancel_indicator", NpgsqlDbType.Boolean, spliceInsert.SpliceEventCancelIndicator); + command.Parameters.AddParameter("@out_of_network_indicator", NpgsqlDbType.Boolean, spliceInsert.OutOfNetworkIndicator); + command.Parameters.AddParameter("@program_splice_flag", NpgsqlDbType.Boolean, spliceInsert.ProgramSpliceFlag); + command.Parameters.AddParameter("@duration_flag", NpgsqlDbType.Boolean, spliceInsert.DurationFlag); + command.Parameters.AddParameter("@splice_immediate_flag", NpgsqlDbType.Boolean, spliceInsert.SpliceImmediateFlag); + command.Parameters.AddParameter("@splice_time", NpgsqlDbType.Bigint, (long?)spliceInsert.SpliceTime); + command.Parameters.AddParameter("@duration_auto_return", NpgsqlDbType.Boolean, spliceInsert.DurationAutoReturn); + //@duration, @unique_program_id, @avail_num, @avails_expected\r\n );\r\n"; + command.Parameters.AddParameter("@duration", NpgsqlDbType.Bigint, (long?)spliceInsert.Duration); + command.Parameters.AddParameter("@unique_program_id", NpgsqlDbType.Integer, spliceInsert.UniqueProgramId); + command.Parameters.AddParameter("@avail_num", NpgsqlDbType.Integer, spliceInsert.AvailNum); + command.Parameters.AddParameter("@avails_expected", NpgsqlDbType.Integer, spliceInsert.AvailsExpected); + try + { + command.ExecuteNonQuery(); + } + catch (PostgresException pe) + { + transaction.Rollback(); + transaction = null; + transaction = connection.BeginTransaction(); + Console.WriteLine("Warning: Network {0}, TSID {1}, Program {2}, Splice {3} not unique.", currentNetworkId, currentTransportStreamId, programNumber, spliceInsert.SpliceEventId); + return; + } + + WriteScte35SpliceComponents(connection, currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + + if (spliceInsert.Descriptors == null) + return; + + if (spliceInsert.Descriptors.availDescriptor != null) + { + WriteScte35SpliceAvailability(connection, currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + } + + if (spliceInsert.Descriptors.segmentationDescriptor != null) + { + WriteScte35SpliceSegmentation(connection, currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + } + + if (spliceInsert.Descriptors.timeDescriptor != null) + { + throw new NotImplementedException(); + } + } + + private void WriteScte35SpliceAvailability(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + Scte35.Descriptors.AvailDescriptor descriptor = spliceInsert.Descriptors.availDescriptor; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "update scte35_splices\r\n" + + "set provider_avail_id = @provider_avail_id, identifier = @identifier\r\n" + + "where cnid = @cnid " + + "and ctsid = @ctsid " + + "and program_number = @program_number " + + "and splice_event_id = @splice_event_id\r\n"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddParameter("@splice_event_id", NpgsqlDbType.Bigint, (long)spliceInsert.SpliceEventId); + command.Parameters.AddParameter("@provider_avail_id", NpgsqlDbType.Bigint, (long)descriptor.ProviderAvailId); + command.Parameters.AddParameter("@identifier", NpgsqlDbType.Bigint, (long)descriptor.Identifier); + command.ExecuteNonQuery(); + } + + private void WriteScte35SpliceSegmentation(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + Scte35.Descriptors.SegmentationDescriptor segment = spliceInsert.Descriptors.segmentationDescriptor; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into scte35_splices_segmentations (cnid, ctsid, program_number, splice_event_id, splice_time,\r\n identifier, segmentation_event_id, segmentation_event_cancel_indicator,\r\n program_segmentation_flag, segmentation_duration_flag, web_delivery_flag,\r\n no_regional_blackout_flag, archive_allowed_flag, device_restrictions,\r\n segmentation_duration, segmentation_upid_type, segmentation_upid,\r\n segmentation_type_id, segment_num, segments_expected, sub_segment_num,\r\n sub_segments_expected) " + + "values " + + "(@cnid,@ctsid,@program_number,@splice_event_id,@splice_time,@identifier,@segmentation_event_id,@segmentation_event_cancel_indicator,@program_segmentation_flag,@segmentation_duration_flag,@web_delivery_flag,\r\n @no_regional_blackout_flag, @archive_allowed_flag, @device_restrictions,\r\n @segmentation_duration, @segmentation_upid_type, @segmentation_upid,\r\n @segmentation_type_id, @segment_num, @segments_expected, @sub_segment_num,\r\n @sub_segments_expected\r\n )"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)programNumber); + cmd.Parameters.AddWithValue("@splice_event_id", NpgsqlDbType.Bigint, (long)spliceInsert.SpliceEventId); + cmd.Parameters.AddWithValue("@splice_time", NpgsqlDbType.Bigint, (long?)spliceInsert.SpliceTime); + cmd.Parameters.AddWithValue("@identifier", NpgsqlDbType.Bigint, (long?)segment.Identifier); + cmd.Parameters.AddWithValue("@segmentation_event_id", NpgsqlDbType.Bigint, (long?)segment.SegmentationEventId); + cmd.Parameters.AddWithValue("@segmentation_event_cancel_indicator", NpgsqlDbType.Boolean, segment.SegmentationEventCancelIndicator); + cmd.Parameters.AddWithValue("@program_segmentation_flag", NpgsqlDbType.Boolean, segment.ProgramSegmentationFlag); + cmd.Parameters.AddWithValue("@segmentation_duration_flag", NpgsqlDbType.Boolean, segment.SegmentationDurationFlag); + //@web_delivery_flag,@no_regional_blackout_flag,@archive_allowed_flag,@device_restrictions,@segmentation_duration,@segmentation_upid_type,@segmentation_upid,@segmentation_type_id,@segment_num,@segments_expected, @sub_segment_num,\r\n @sub_segments_expected\r\n )"; + cmd.Parameters.AddWithValue("@web_delivery_flag", NpgsqlDbType.Boolean, segment.WebDeliveryFlag); + cmd.Parameters.AddWithValue("@no_regional_blackout_flag", NpgsqlDbType.Boolean, segment.NoRegionalBlackoutFlag); + cmd.Parameters.AddWithValue("@archive_allowed_flag", NpgsqlDbType.Boolean, segment.ArchiveAllowedFlag); + cmd.Parameters.AddWithValue("@device_restrictions", NpgsqlDbType.Integer, segment.DeviceRestrictions); + cmd.Parameters.AddWithValue("@segmentation_duration", NpgsqlDbType.Bigint, segment.SegmentationDuration); + cmd.Parameters.AddWithValue("@segmentation_upid_type", NpgsqlDbType.Integer, segment.SegmentationUpidType); + cmd.Parameters.AddWithValue("@segmentation_upid", NpgsqlDbType.Text, segment.SegmentationUpid); + cmd.Parameters.AddWithValue("@segmentation_type_id", NpgsqlDbType.Integer, (int?)segment.SegmentationTypeId); + cmd.Parameters.AddWithValue("@segment_num", NpgsqlDbType.Integer, (int?)segment.SegmentNum); + //@segments_expected,@sub_segment_num,@sub_segments_expected + cmd.Parameters.AddWithValue("@segments_expected", NpgsqlDbType.Integer, (int?)segment.SegmentsExpected); + cmd.Parameters.AddWithValue("@sub_segment_num", NpgsqlDbType.Integer, (int?)segment.SubSegmentNum); + cmd.Parameters.AddWithValue("@sub_segments_expected", NpgsqlDbType.Integer, (int?)segment.SubSegmentsExpected); + cmd.Parameters.CheckNulls(); + cmd.ExecuteNonQuery(); + } + + private void WriteScte35SpliceComponents(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + if (spliceInsert.Components == null) + return; + + for (int i = 0; i < spliceInsert.Components.Length; i++) + { + throw new NotImplementedException(); + } + } + + private Dictionary knownScte35TimeSignals; + public void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, + TimeSignal timeSignal) + { + EnqueueTask(x => WriteScte35TimeSignal(x, currentNetworkId, currentTransportStreamId, currentTime, programNumber, timeSignal)); + EnqueueTask(CommitTransaction); + } + + private void WriteScte35TimeSignal(NpgsqlConnection connection, int cnid, int ctsid, DateTime timeSignalled, ushort programNumber, + TimeSignal timeSignal) + { + if (knownScte35TimeSignals == null) + knownScte35TimeSignals = new Dictionary(); + + DatabaseKeyScte35TimeSignal key = new DatabaseKeyScte35TimeSignal(cnid, ctsid, programNumber); + + if (knownScte35TimeSignals.ContainsKey(key)) + { + //already cached + DateTime timeInDb = knownScte35TimeSignals[key]; + if (timeSignalled > timeInDb) + { + //Need to update + UpdateScte35TimeSignal(connection, cnid, ctsid, timeSignalled, programNumber, timeSignal); + knownScte35TimeSignals[key] = timeSignalled; + } + else + { + //Nothing to do + return; + } + } + else + { + //not yet cached + DateTime? timeInDb = SelectScte35TimeSignal(connection, cnid, ctsid, programNumber); + if (timeInDb.HasValue) + { + if (timeSignalled > timeInDb.Value) + { + //Need to update and cache + UpdateScte35TimeSignal(connection, cnid, ctsid, timeSignalled, programNumber, timeSignal); + knownScte35TimeSignals[key] = timeInDb.Value; + } + else + { + //Cache past value and do nothing + knownScte35TimeSignals[key] = timeInDb.Value; + return; + } + } + else + { + //Need to insert and cache + InsertScte35TimeSignal(connection, cnid, ctsid, timeSignalled, programNumber, timeSignal); + knownScte35TimeSignals[key] = timeSignalled; + } + } + } + + private void UpdateScte35TimeSignal(NpgsqlConnection connection, int cnid, int ctsid, DateTime timeSignalled, ushort programNumber, TimeSignal timeSignal) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "update scte35_time_signal\r\nset ctime = @ctime,\r\n scte35_value = @scte35_value,\r\n provider_avail_id = @provider_avail_id,\r\n identifier = @identifier,\r\n sub_segments_expected = @sub_segments_expected,\r\n sub_segment_num = @sub_segment_num,\r\n segments_expected = @segments_expected,\r\n segment_num = @segment_num,\r\n segmentation_type_id = @segmentation_type_id,\r\n segmentation_upid = @segmentation_upid,\r\n segmentation_upid_type = @segmentation_upid_type,\r\n segmentation_duration = @segmentation_duration,\r\n program_segmentation_flag = @program_segmentation_flag,\r\n segmentation_event_cancel_indicator = @segmentation_event_cancel_indicator,\r\n device_restrictions = @device_restrictions,\r\n archive_allowed_flag = @archive_allowed_flag,\r\n no_regional_blackout_flag = @no_regional_blackout_flag,\r\n web_delivery_flag = @web_delivery_flag,\r\n segmentation_duration_flag = @segmentation_duration_flag,\r\n segmentation_event_id = @segmentation_event_id,\r\n seg_identifier = @seg_identifier,\r\n utc_offset = @utc_offset,\r\n tai_ns = @tai_ns,\r\n tai_seconds = @tai_seconds,\r\n time_identifier = @time_identifier,\r\n updated_counter = updated_counter + 1,\r\n updated_timestamp = CURRENT_TIMESTAMP\r\nwhere cnid = @cnid\r\nand tsid = @tsid\r\nand program_number = @program_number"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, cnid); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, ctsid); + command.Parameters.AddParameter("@ctime", NpgsqlDbType.Timestamp, timeSignalled); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddParameter("@scte35_value", NpgsqlDbType.Bigint, (long?)timeSignal.Value); + command.Parameters.AddParameter("@provider_avail_id", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.availDescriptor?.ProviderAvailId); + command.Parameters.AddParameter("@identifier", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.availDescriptor?.Identifier); + command.Parameters.AddParameter("@sub_segments_expected", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SubSegmentsExpected); + command.Parameters.AddParameter("@sub_segment_num", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SubSegmentNum); + command.Parameters.AddParameter("@segments_expected", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentsExpected); + command.Parameters.AddParameter("@segment_num", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentNum); + command.Parameters.AddParameter("@segmentation_type_id", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationTypeId); + if (timeSignal.Descriptors.segmentationDescriptor != null) + { + if (timeSignal.Descriptors.segmentationDescriptor.SegmentationUpid.Contains("\0")) + { + timeSignal.Descriptors.segmentationDescriptor.SegmentationUpid = ""; + } + } + command.Parameters.AddParameter("@segmentation_upid", NpgsqlDbType.Text, timeSignal.Descriptors.segmentationDescriptor?.SegmentationUpid); + //@segmentation_upid_type,@segmentation_duration,@program_segmentation_flag,@segmentation_event_cancel_indicator,@device_restrictions,@archive_allowed_flag,@no_regional_blackout_flag,@web_delivery_flag,@segmentation_duration_flag, @segmentation_event_id, @seg_identifier, @utc_offset, @tai_ns, @tai_seconds, @time_identifier)"; + command.Parameters.AddParameter("@segmentation_upid_type", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationUpidType); + command.Parameters.AddParameter("@segmentation_duration", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationDuration); + command.Parameters.AddParameter("@program_segmentation_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.ProgramSegmentationFlag); + command.Parameters.AddParameter("@segmentation_event_cancel_indicator", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationEventCancelIndicator); + command.Parameters.AddParameter("@device_restrictions", NpgsqlDbType.Integer, (int?)timeSignal.Descriptors.segmentationDescriptor?.DeviceRestrictions); + command.Parameters.AddParameter("@archive_allowed_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.ArchiveAllowedFlag); + command.Parameters.AddParameter("@no_regional_blackout_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.NoRegionalBlackoutFlag); + command.Parameters.AddParameter("@web_delivery_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.WebDeliveryFlag); + //@segmentation_duration_flag, @segmentation_event_id, @seg_identifier, @utc_offset, @tai_ns, @tai_seconds, @time_identifier)"; + command.Parameters.AddParameter("@segmentation_duration_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationDurationFlag); + command.Parameters.AddParameter("@segmentation_event_id", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationEventId); + command.Parameters.AddParameter("@seg_identifier", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.segmentationDescriptor?.Identifier); + if (timeSignal.Descriptors.timeDescriptor != null) + { + command.Parameters.AddParameter("@utc_offset", NpgsqlDbType.Integer, (int?)timeSignal.Descriptors.timeDescriptor.UtcOffset); + command.Parameters.AddParameter("@tai_ns", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.timeDescriptor.TaiNs); + command.Parameters.AddParameter("@tai_seconds", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.timeDescriptor.TaiSeconds); + command.Parameters.AddParameter("@time_identifier", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.timeDescriptor.Identifier); + } + else + { + command.Parameters.AddParameter("@utc_offset", NpgsqlDbType.Integer, DBNull.Value); + command.Parameters.AddParameter("@tai_ns", NpgsqlDbType.Bigint, DBNull.Value); + command.Parameters.AddParameter("@tai_seconds", NpgsqlDbType.Bigint, DBNull.Value); + command.Parameters.AddParameter("@time_identifier", NpgsqlDbType.Bigint, DBNull.Value); + } + command.ExecuteNonQuery(); + } + + private void InsertScte35TimeSignal(NpgsqlConnection connection, int cnid, int ctsid, DateTime timeSignalled, ushort programNumber, TimeSignal timeSignal) + { + if (timeSignal.Descriptors != null) + if (timeSignal.Descriptors.segmentationDescriptor != null) + if (timeSignal.Descriptors.segmentationDescriptor.SegmentationUpid != null) + if (timeSignal.Descriptors.segmentationDescriptor.SegmentationUpid.Contains("\0")) + timeSignal.Descriptors.segmentationDescriptor.SegmentationUpid = ""; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into scte35_time_signal (cnid, tsid, ctime, program_number, scte35_value, provider_avail_id, identifier, sub_segments_expected, sub_segment_num, segments_expected, segment_num," + + " segmentation_type_id, segmentation_upid, segmentation_upid_type, segmentation_duration, program_segmentation_flag, segmentation_event_cancel_indicator, device_restrictions, archive_allowed_flag,\r\n no_regional_blackout_flag, web_delivery_flag, segmentation_duration_flag,\r\n segmentation_event_id, seg_identifier, utc_offset, tai_ns, tai_seconds, time_identifier) " + + "values " + + "(@cnid,@tsid,@ctime,@program_number,@scte35_value,@provider_avail_id,@identifier,@sub_segments_expected,@sub_segment_num,@segments_expected,@segment_num,@segmentation_type_id,@segmentation_upid,@segmentation_upid_type, @segmentation_duration, @program_segmentation_flag, @segmentation_event_cancel_indicator, @device_restrictions, @archive_allowed_flag, @no_regional_blackout_flag, @web_delivery_flag, @segmentation_duration_flag, @segmentation_event_id, @seg_identifier, @utc_offset, @tai_ns, @tai_seconds, @time_identifier)"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, cnid); + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, ctsid); + command.Parameters.AddParameter("@ctime", NpgsqlDbType.Timestamp, timeSignalled); + command.Parameters.AddParameter("@program_number", NpgsqlDbType.Integer, programNumber); + command.Parameters.AddParameter("@scte35_value", NpgsqlDbType.Bigint, (long?)timeSignal.Value); + command.Parameters.AddParameter("@provider_avail_id", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.availDescriptor?.ProviderAvailId); + command.Parameters.AddParameter("@identifier", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.availDescriptor?.Identifier); + command.Parameters.AddParameter("@sub_segments_expected", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SubSegmentsExpected); + command.Parameters.AddParameter("@sub_segment_num", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SubSegmentNum); + command.Parameters.AddParameter("@segments_expected", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentsExpected); + command.Parameters.AddParameter("@segment_num", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentNum); + command.Parameters.AddParameter("@segmentation_type_id", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationTypeId); + command.Parameters.AddParameter("@segmentation_upid", NpgsqlDbType.Text, timeSignal.Descriptors.segmentationDescriptor?.SegmentationUpid); + //@segmentation_upid_type,@segmentation_duration,@program_segmentation_flag,@segmentation_event_cancel_indicator,@device_restrictions,@archive_allowed_flag,@no_regional_blackout_flag,@web_delivery_flag,@segmentation_duration_flag, @segmentation_event_id, @seg_identifier, @utc_offset, @tai_ns, @tai_seconds, @time_identifier)"; + command.Parameters.AddParameter("@segmentation_upid_type", NpgsqlDbType.Integer, (byte?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationUpidType); + command.Parameters.AddParameter("@segmentation_duration", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationDuration); + command.Parameters.AddParameter("@program_segmentation_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.ProgramSegmentationFlag); + command.Parameters.AddParameter("@segmentation_event_cancel_indicator", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationEventCancelIndicator); + command.Parameters.AddParameter("@device_restrictions", NpgsqlDbType.Integer, (int?)timeSignal.Descriptors.segmentationDescriptor?.DeviceRestrictions); + command.Parameters.AddParameter("@archive_allowed_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.ArchiveAllowedFlag); + command.Parameters.AddParameter("@no_regional_blackout_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.NoRegionalBlackoutFlag); + command.Parameters.AddParameter("@web_delivery_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.WebDeliveryFlag); + //@segmentation_duration_flag, @segmentation_event_id, @seg_identifier, @utc_offset, @tai_ns, @tai_seconds, @time_identifier)"; + command.Parameters.AddParameter("@segmentation_duration_flag", NpgsqlDbType.Boolean, (bool?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationDurationFlag); + command.Parameters.AddParameter("@segmentation_event_id", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.segmentationDescriptor?.SegmentationEventId); + command.Parameters.AddParameter("@seg_identifier", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.segmentationDescriptor?.Identifier); + command.Parameters.AddParameter("@utc_offset", NpgsqlDbType.Integer, (int?)timeSignal.Descriptors.timeDescriptor?.UtcOffset); + command.Parameters.AddParameter("@tai_ns", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.timeDescriptor?.TaiNs); + command.Parameters.AddParameter("@tai_seconds", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.timeDescriptor?.TaiSeconds); + command.Parameters.AddParameter("@time_identifier", NpgsqlDbType.Bigint, (long?)timeSignal.Descriptors.timeDescriptor?.Identifier); + command.ExecuteNonQuery(); + } + + private DateTime? SelectScte35TimeSignal(NpgsqlConnection connection, int cnid, int ctsid, ushort programNumber) + { + DateTime? result = null; + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT ctime FROM scte35_time_signal WHERE cnid = @cnid AND tsid = @tsid AND program_number = @program_number"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, cnid); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, ctsid); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)programNumber); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + result = dataReader.GetDateTime(0); + dataReader.Close(); + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Sdt.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Sdt.cs new file mode 100644 index 0000000..4265d36 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Sdt.cs @@ -0,0 +1,575 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using Npgsql.Internal.Postgres; +using NpgsqlTypes; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using static skyscraper5.Dvb.Descriptors.NvodReferenceDescriptor; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (knownSdtServices == null) + knownSdtServices = new HashSet(); + DatabaseKeySdtService key = new DatabaseKeySdtService(transportStreamId, originalNetworkId, sdtService.ServiceId); + if (knownSdtServices.Contains(key)) + return true; + + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND sid = @sid"; + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = false; + if (dataReader.Read()) + { + result = true; + knownSdtServices.Add(key); + } + dataReader.Close(); + connection.Close(); + return result; + } + } + + private HashSet knownSdtServices; + + public void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (updatedSdtServices == null) + updatedSdtServices = new HashSet(); + + DatabaseKeySdtService key = new DatabaseKeySdtService(transportStreamId, originalNetworkId, sdtService.ServiceId); + EnqueueTask(x => WriteSdtService(x, transportStreamId, originalNetworkId, sdtService)); + knownSdtServices.Add(key); + updatedSdtServices.Add(key); + } + + private void WriteSdtService(NpgsqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND sid = @sid"; + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + NpgsqlDataReader reader = command.ExecuteReader(); + bool alreadyKnown = reader.Read(); + reader.Close(); + command.Dispose(); + if (alreadyKnown) + return; + + command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_sdt " + + "( tsid, onid, sid, eit_schedule_flag, eit_present_following_flag, running_status, free_ca_mode, service_name, service_provider_name, service_type_coding, private_data_specifier, data_broadcast_id, selector, default_authority, control_remote_access_over_internet, reference_service_id, do_not_apply_revocation, do_not_scramble, old_onid, old_sid, old_tsid, component_tag, iso_639_language_code, text) " + + "VALUES " + + "( @tsid, @onid, @sid, @eit_schedule_flag, @eit_present_following_flag, @running_status, @free_ca_mode, @service_name, @service_provider_name, @service_type_coding, " + + " @private_data_specifier, @data_broadcast_id, @selector, @default_authority, @control_remote_access_over_internet, @reference_service_id, @do_not_apply_revocation, " + + " @do_not_scramble, @old_onid, @old_sid, @old_tsid, @component_tag, @iso_639_language_code, @text)"; + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + command.Parameters.AddWithValue("@eit_schedule_flag", NpgsqlDbType.Boolean, sdtService.EitScheduleFlag); + command.Parameters.AddWithValue("@eit_present_following_flag", NpgsqlDbType.Boolean, sdtService.EitPresentFollowingFlag); + command.Parameters.AddWithValue("@running_status", NpgsqlDbType.Integer, (int)sdtService.RunningStatus); + command.Parameters.AddWithValue("@free_ca_mode", NpgsqlDbType.Boolean, sdtService.FreeCaMode); + command.Parameters.AddWithValue("@service_name", NpgsqlDbType.Text, sdtService.ServiceName); + command.Parameters.AddWithValue("@service_provider_name", NpgsqlDbType.Text, sdtService.ServiceProviderName); + + if (sdtService.ServiceType.HasValue) + command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, (int)sdtService.ServiceType); + else + command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.PrivateDataSpecifier.HasValue) + command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, (long)sdtService.PrivateDataSpecifier); + else + command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, DBNull.Value); + + if (sdtService.DataBroadcastId.HasValue) + command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, (int)sdtService.DataBroadcastId); + else + command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.Selector != null) + command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, sdtService.Selector); + else + command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, DBNull.Value); + + command.Parameters.AddWithValue("@default_authority", NpgsqlDbType.Varchar, sdtService.DefaultAuthority); + + if (sdtService.ControlRemoteAccessOverInternet.HasValue) + command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, sdtService.ControlRemoteAccessOverInternet); + else + command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, DBNull.Value); + + //@do_not_scramble, @old_onid, @old_sid, @old_tsid, @component_tag, @iso_639_language_code, @text + if (sdtService.ReferenceServiceId.HasValue) + command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, (int)sdtService.ReferenceServiceId); + else + command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.DoNotApplyRevocation.HasValue) + command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, sdtService.DoNotApplyRevocation); + else + command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, DBNull.Value); + + if (sdtService.DoNotScramble.HasValue) + command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, sdtService.DoNotScramble); + else + command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, DBNull.Value); + + if (sdtService.OldOriginalNetworkId.HasValue) + command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, (int)sdtService.OldOriginalNetworkId); + else + command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.OldServiceId.HasValue) + command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, (int)sdtService.OldServiceId); + else + command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.OldTransportStreamId.HasValue) + command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, (int)sdtService.OldTransportStreamId); + else + command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.ComponentTag.HasValue) + command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, sdtService.ComponentTag); + else + command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, DBNull.Value); + + command.Parameters.AddWithValue("@iso_639_language_code", NpgsqlDbType.Varchar, sdtService.Iso639LanguageCode); + command.Parameters.AddWithValue("@text", NpgsqlDbType.Text, sdtService.Text); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + WriteSdtCaIdentifiers(connection, sdtService, transportStreamId, originalNetworkId); + WriteSdtComponents(connection, sdtService, transportStreamId, originalNetworkId); + WriteSdtCountryAvailability(connection, sdtService, transportStreamId, originalNetworkId); + WriteSdtLinkages(connection, sdtService, transportStreamId, originalNetworkId); + WriteSdtMultilingualServiceName(connection, sdtService, transportStreamId, originalNetworkId); + WriteSdtNvodReferences(connection, sdtService, transportStreamId, originalNetworkId); + WriteSdtMessages(connection, sdtService, transportStreamId, originalNetworkId); + } + + private void WriteSdtMessages(NpgsqlConnection connection, SdtService sdtService, ushort transportStreamId, ushort originalNetworkId) + { + if (sdtService.Messages == null) + return; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into dvb_sdt_messages (tsid, onid, sid, msgid, language_code, message, dateadded)\r\nvalues (@tsid,@onid,@sid,@msgid,@language,@message,DEFAULT)"; + cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, originalNetworkId); + cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + cmd.Parameters.Add("@msgid", NpgsqlDbType.Integer); + cmd.Parameters.Add("@language", NpgsqlDbType.Varchar); + cmd.Parameters.Add("@message", NpgsqlDbType.Text); + + foreach (MessageDescriptor message in sdtService.Messages) + { + cmd.Parameters["@msgid"].Value = message.MessageId; + cmd.Parameters["@language"].Value = message.Iso639LanguageCode; + cmd.Parameters["@message"].Value = message.Message; + cmd.ExecuteNonQuery(); + } + } + + private void WriteSdtNvodReferences(NpgsqlConnection connection, SdtService sdtService, int tsid, int onid) + { + if (sdtService.NvodReferences == null) + return; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into dvb_sdt_nvod_references (tsid, onid, sid, ordinal, nvod_tsid, nvod_onid, nvod_sid)\r\n" + + "values (@tsid, @onid, @sid, @ordinal, @nvod_tsid, @nvod_onid, @nvod_sid)"; + cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, tsid); + cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, onid); + cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + cmd.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + cmd.Parameters.Add("@nvod_tsid", NpgsqlDbType.Integer); + cmd.Parameters.Add("@nvod_onid", NpgsqlDbType.Integer); + cmd.Parameters.Add("@nvod_sid", NpgsqlDbType.Integer); + + for (int i = 0; i < sdtService.NvodReferences.Length; i++) + { + NvodReference nvod = sdtService.NvodReferences[i]; + if (nvod == null) + continue; + + cmd.Parameters["@ordinal"].Value = i; + cmd.Parameters["@nvod_tsid"].Value = (int)nvod.TransportStreamId; + cmd.Parameters["@nvod_onid"].Value = (int)nvod.OriginalNetworkId; + cmd.Parameters["@nvod_sid"].Value = (int)nvod.ServiceId; + cmd.ExecuteNonQuery(); + } + } + + private void WriteSdtMultilingualServiceName(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid) + { + if (sdtService.MultilingualServiceName == null) + return; + + NpgsqlCommand cmd = connection.CreateCommand(); + cmd.CommandText = "insert into dvb_sdt_multilingual_service_name (tsid, onid, sid, ordinal, iso_639_language_code, service_provider_name, service_name) " + + "values " + + "(@tsid, @onid, @sid, @ordinal, @iso_639_language_code, @service_provider_name, @service_name)"; + cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)tsid); + cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)onid); + cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + cmd.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + cmd.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar); + cmd.Parameters.Add("@service_provider_name", NpgsqlDbType.Text); + cmd.Parameters.Add("@service_name", NpgsqlDbType.Text); + for (int i = 0; i < sdtService.MultilingualServiceName.Count; i++) + { + MultilingualServiceNameDescriptor.MultilingualServiceName multilingualServiceName = sdtService.MultilingualServiceName[i]; + cmd.Parameters["@ordinal"].Value = i; + cmd.Parameters["@iso_639_language_code"].Value = multilingualServiceName.Iso639LanguageCode; + cmd.Parameters["@service_provider_name"].Value = multilingualServiceName.ServiceProviderName; + cmd.Parameters["@service_name"].Value = multilingualServiceName.ServiceName; + cmd.Parameters.CheckNulls(); + cmd.ExecuteNonQuery(); + } + } + + private void WriteSdtLinkages(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid) + { + if (sdtService.Linkages == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_sdt_linkages (tsid, onid, sid, ordinal, l_tsid, l_onid, l_sid, linkage_type, handover_type, handover_origin_type, handover_network_id, handover_initial_service_id, target_event_id, target_event_listed, target_event_simulcasted, table_type, private_data_bytes, bouquet_id) " + + "values " + + "(@tsid,@onid,@sid,@ordinal,@l_tsid,@l_onid,@l_sid,@linkage_type,@handover_type,@handover_origin_type,@handover_network_id,@handover_initial_service_id,@target_event_id,@target_event_listed,@target_event_simulcasted,@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, tsid); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid); + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@l_tsid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_onid", NpgsqlDbType.Integer); + command.Parameters.Add("@l_sid", NpgsqlDbType.Integer); + command.Parameters.Add("@linkage_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_type", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_origin_type", NpgsqlDbType.Boolean); + command.Parameters.Add("@handover_network_id", NpgsqlDbType.Integer); + command.Parameters.Add("@handover_initial_service_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_id", NpgsqlDbType.Integer); + command.Parameters.Add("@target_event_listed", NpgsqlDbType.Boolean); + command.Parameters.Add("@target_event_simulcasted", NpgsqlDbType.Boolean); + //@table_type,@private_data_bytes,@bouquet_id)"; + command.Parameters.Add("@table_type", NpgsqlDbType.Integer); + command.Parameters.Add("@private_data_bytes", NpgsqlDbType.Bytea); + command.Parameters.Add("@bouquet_id", NpgsqlDbType.Integer); + + int i = 0; + foreach (LinkageDescriptor linkage in sdtService.Linkages) + { + command.Parameters["@ordinal"].Value = i++; + command.Parameters["@l_tsid"].Value = (int)linkage.TransportStreamId; + command.Parameters["@l_onid"].Value = (int)linkage.OriginalNetworkId; + command.Parameters["@l_sid"].Value = (int)linkage.ServiceId; + command.Parameters["@linkage_type"].Value = (int)linkage.LinkageType; + command.Parameters["@handover_type"].Value = linkage.HandoverType.HasValue ? (int)linkage.HandoverType.Value : DBNull.Value; + command.Parameters["@handover_origin_type"].Value = linkage.HandoverOriginType.HasValue ? linkage.HandoverOriginType.Value : DBNull.Value; + command.Parameters["@handover_network_id"].Value = linkage.HandoverNetworkId.HasValue ? (int)linkage.HandoverNetworkId.Value : DBNull.Value; + command.Parameters["@handover_initial_service_id"].Value = linkage.HandoverInitialServiceId.HasValue ? linkage.HandoverInitialServiceId.Value : DBNull.Value; + command.Parameters["@target_event_id"].Value = linkage.TargetEventId.HasValue ? linkage.TargetEventId.Value : DBNull.Value; + command.Parameters["@target_event_listed"].Value = linkage.TargetEventListed.HasValue ? linkage.TargetEventListed.Value : DBNull.Value; + command.Parameters["@target_event_simulcasted"].Value = linkage.TargetEventSimulcasted.HasValue ? linkage.TargetEventSimulcasted.Value : DBNull.Value; + command.Parameters["@table_type"].Value = linkage.TableType.HasValue ? (int)linkage.TableType.Value : DBNull.Value; + command.Parameters["@private_data_bytes"].Value = linkage.PrivateDataBytes != null ? linkage.PrivateDataBytes : DBNull.Value; + command.Parameters["@bouquet_id"].Value = linkage.BouquetId.HasValue ? linkage.BouquetId : DBNull.Value; + command.ExecuteNonQuery(); + + WriteSdtLinkageExtendedEventLinkages(connection, sdtService, tsid, onid, linkage, i); + WriteSdtLinkageIpmacLinkages(connection, sdtService, tsid, onid, linkage, i); + WriteSdtLinkageSsuLinkStructure(connection, sdtService, tsid, onid, linkage, i); + } + } + + private void WriteSdtLinkageSsuLinkStructure(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid, LinkageDescriptor linkage, int i) + { + if (linkage.SystemSoftwareUpdateLinkStructure == null) + return; + + foreach (LinkageDescriptor.OuiPrivateData ouiPrivateData in linkage.SystemSoftwareUpdateLinkStructure) + { + throw new NotImplementedException(); + } + } + + private void WriteSdtLinkageIpmacLinkages(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid, LinkageDescriptor linkage, int i) + { + if (linkage.IpMacNotificationLinkages == null) + return; + + foreach (LinkageDescriptor.IpMacNotificationLinkage notificationLinkage in + linkage.IpMacNotificationLinkages) + { + throw new NotImplementedException(); + } + } + + private void WriteSdtLinkageExtendedEventLinkages(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid, LinkageDescriptor linkage, int i) + { + if (linkage.ExtendedEventLinkages == null) + return; + + foreach (LinkageDescriptor.ExtendedEventLinkageInfo eventLinkage in linkage.ExtendedEventLinkages) + { + throw new NotImplementedException(); + } + } + + private void WriteSdtCountryAvailability(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid) + { + if (sdtService.CountryAvailability == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_sdt_country_availability (tsid, onid, sid, ordinal, k, v) " + + "VALUES (@tsid, @onid, @sid, @ordinal, @k, @v)"; + command.Parameters.AddParameter("@tsid",NpgsqlDbType.Integer,tsid); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid); + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@k", NpgsqlDbType.Varchar); + command.Parameters.Add("@v", NpgsqlDbType.Boolean); + int ordinal = 0; + foreach (var (key, value) in sdtService.CountryAvailability) + { + command.Parameters["@ordinal"].Value = ordinal++; + command.Parameters["@k"].Value = key; + command.Parameters["@v"].Value = value; + command.ExecuteNonQuery(); + } + } + + private void WriteSdtComponents(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid) + { + if (sdtService.Components == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_sdt_components (tsid, onid, sid, ordinal, stream_content_ext, stream_content, component_type,component_tag, iso_639_language_code, text)" + + "values ( @tsid, @onid, @sid, @ordinal, @stream_content_ext, @stream_content, @component_type,@component_tag, @iso_639_language_code, @text)"; + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, tsid); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid); + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@stream_content_ext", NpgsqlDbType.Integer); + command.Parameters.Add("@stream_content", NpgsqlDbType.Integer); + command.Parameters.Add("@component_type", NpgsqlDbType.Integer); + command.Parameters.Add("@component_tag", NpgsqlDbType.Integer); + command.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar); + command.Parameters.Add("@text", NpgsqlDbType.Text); + for (int i = 0; i < sdtService.Components.Count; i++) + { + ComponentDescriptor component = sdtService.Components[i]; + command.Parameters["@ordinal"].Value = i; + command.Parameters["@stream_content_ext"].Value = component.StreamContentExt; + command.Parameters["@stream_content"].Value = component.StreamContent; + command.Parameters["@component_type"].Value = component.ComponentType; + command.Parameters["@component_tag"].Value = component.ComponentTag; + command.Parameters["@iso_639_language_code"].Value = component.Iso639LanguageCode; + command.Parameters["@text"].Value = component.Text; + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + } + } + + private void WriteSdtCaIdentifiers(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid) + { + if (sdtService.CaIdentifiers == null) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO dvb_sdt_ca_identifiers (tsid, onid, sid, ordinal, caid) " + + "VALUES (@tsid, @onid, @sid, @ordinal, @caid)"; + command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, tsid); + command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid); + command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId); + command.Parameters.Add("@ordinal", NpgsqlDbType.Integer); + command.Parameters.Add("@caid", NpgsqlDbType.Integer); + for (int i = 0; i < sdtService.CaIdentifiers.Length; i++) + { + command.Parameters["@ordinal"].Value = i; + command.Parameters["@caid"].Value = (int)sdtService.CaIdentifiers[i]; + command.ExecuteNonQuery(); + } + } + + private HashSet updatedSdtServices; + public bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService newerSdtService) + { + if (updatedSdtServices == null) + updatedSdtServices = new HashSet(); + + DatabaseKeySdtService key = new DatabaseKeySdtService(transportStreamId, originalNetworkId, newerSdtService.ServiceId); + if (updatedSdtServices.Contains(key)) + return false; + + SdtService olderSdtService = ReadSdtService(transportStreamId, originalNetworkId, newerSdtService.ServiceId, false); + bool result = olderSdtService.NeedsUpdate(newerSdtService); + if (result) + { + EnqueueTask(x => WriteSdtServiceUpdate(x, transportStreamId, originalNetworkId, newerSdtService)); + } + updatedSdtServices.Add(key); + return result; + } + + private void WriteSdtServiceUpdate(NpgsqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_sdt\r\nSET eit_schedule_flag = @eit_schedule_flag,\r\n eit_present_following_flag = @eit_present_following_flag,\r\n running_status = @running_status,\r\n free_ca_mode = @free_ca_mode,\r\n service_name = @service_name,\r\n service_provider_name = @service_provider_name,\r\n service_type_coding = @service_type_coding,\r\n private_data_specifier = @private_data_specifier,\r\n data_broadcast_id = @data_broadcast_id,\r\n selector = @selector,\r\n default_authority = @default_authority,\r\n control_remote_access_over_internet = @control_remote_access_over_internet,\r\n reference_service_id = @reference_service_id,\r\n do_not_apply_revocation = @do_not_apply_revocation,\r\n do_not_scramble = @do_not_scramble,\r\n old_onid = @old_onid,\r\n old_sid = @old_sid,\r\n old_tsid = @old_tsid,\r\n component_tag = @component_tag,\r\n iso_639_language_code = @iso_639_language_code,\r\n text = @text,\r\n updated_counter = updated_counter + 1,\r\n updated_timestamp = CURRENT_TIMESTAMP\r\nWHERE tsid = @tsid\r\nAND onid = @onid\r\nAND sid = @sid"; + + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId); + command.Parameters.AddWithValue("@eit_schedule_flag", NpgsqlDbType.Boolean, sdtService.EitScheduleFlag); + command.Parameters.AddWithValue("@eit_present_following_flag", NpgsqlDbType.Boolean, sdtService.EitPresentFollowingFlag); + command.Parameters.AddWithValue("@running_status", NpgsqlDbType.Integer, (int)sdtService.RunningStatus); + command.Parameters.AddWithValue("@free_ca_mode", NpgsqlDbType.Boolean, sdtService.FreeCaMode); + command.Parameters.AddWithValue("@service_name", NpgsqlDbType.Text, sdtService.ServiceName); + command.Parameters.AddWithValue("@service_provider_name", NpgsqlDbType.Text, sdtService.ServiceProviderName); + + if (sdtService.ServiceType.HasValue) + command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, (int)sdtService.ServiceType); + else + command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.PrivateDataSpecifier.HasValue) + command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, (long)sdtService.PrivateDataSpecifier); + else + command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, DBNull.Value); + + if (sdtService.DataBroadcastId.HasValue) + command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, (int)sdtService.DataBroadcastId); + else + command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.Selector != null) + command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, sdtService.Selector); + else + command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, DBNull.Value); + + command.Parameters.AddWithValue("@default_authority", NpgsqlDbType.Varchar, sdtService.DefaultAuthority); + + if (sdtService.ControlRemoteAccessOverInternet.HasValue) + command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, sdtService.ControlRemoteAccessOverInternet); + else + command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, DBNull.Value); + + //@do_not_scramble, @old_onid, @old_sid, @old_tsid, @component_tag, @iso_639_language_code, @text + if (sdtService.ReferenceServiceId.HasValue) + command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, (int)sdtService.ReferenceServiceId); + else + command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.DoNotApplyRevocation.HasValue) + command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, sdtService.DoNotApplyRevocation); + else + command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, DBNull.Value); + + if (sdtService.DoNotScramble.HasValue) + command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, sdtService.DoNotScramble); + else + command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, DBNull.Value); + + if (sdtService.OldOriginalNetworkId.HasValue) + command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, sdtService.OldOriginalNetworkId); + else + command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.OldServiceId.HasValue) + command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, sdtService.OldServiceId); + else + command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.OldTransportStreamId.HasValue) + command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, sdtService.OldTransportStreamId); + else + command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, DBNull.Value); + + if (sdtService.ComponentTag.HasValue) + command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, sdtService.ComponentTag); + else + command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, DBNull.Value); + + command.Parameters.AddWithValue("@iso_639_language_code", NpgsqlDbType.Varchar, sdtService.Iso639LanguageCode); + command.Parameters.AddWithValue("@text", NpgsqlDbType.Text, sdtService.Text); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + command.Dispose(); + } + + private SdtService ReadSdtService(ushort transportStreamId, ushort originalNetworkId, ushort serviceId, bool recursive) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT * FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND sid = @sid"; + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)serviceId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + SdtService result = null; + if (dataReader.Read()) + { + transportStreamId = (ushort)dataReader.GetInt32(0); + originalNetworkId = (ushort)dataReader.GetInt32(1); + serviceId = (ushort)dataReader.GetInt32(2); + DateTime dateAdded = dataReader.GetDateTime(3); + bool eitScheduleFlag = dataReader.GetBoolean(4); + bool eitPresentFollowingFlag = dataReader.GetBoolean(5); + RunningStatus runningStatus = (RunningStatus)dataReader.GetInt32(6); + bool freeCaMode = dataReader.GetBoolean(7); + result = new SdtService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode); + result.ServiceName = dataReader.IsDBNull(8) ? null : dataReader.GetString(8); + result.ServiceProviderName = dataReader.IsDBNull(9) ? null : dataReader.GetString(9); + result.ServiceType = dataReader.IsDBNull(10) ? null : (ServiceDescriptor.ServiceTypeCoding)dataReader.GetInt32(10); + result.PrivateDataSpecifier = dataReader.IsDBNull(11) ? null : (uint)dataReader.GetInt64(11); + result.DataBroadcastId = dataReader.IsDBNull(12) ? null : (ushort)dataReader.GetInt32(12); + result.Selector = dataReader.IsDBNull(13) ? null : dataReader.GetByteArray(13); + result.DefaultAuthority = dataReader.IsDBNull(14) ? null : dataReader.GetString(14); + result.ControlRemoteAccessOverInternet = dataReader.IsDBNull(15) ? null : (int)dataReader.GetInt32(15); + result.ReferenceServiceId = dataReader.IsDBNull(16) ? null : (ushort)dataReader.GetInt32(16); + result.DoNotApplyRevocation = dataReader.IsDBNull(17) ? null : (bool)dataReader.GetBoolean(17); + result.DoNotScramble = dataReader.IsDBNull(18) ? null : (bool)dataReader.GetBoolean(18); + result.OldOriginalNetworkId = dataReader.IsDBNull(19) ? null : (ushort)dataReader.GetInt32(19); + result.OldServiceId = dataReader.IsDBNull(20) ? null : (ushort)dataReader.GetInt32(20); + result.OldTransportStreamId = dataReader.IsDBNull(21) ? null : (ushort)dataReader.GetInt32(21); + result.ComponentTag = dataReader.IsDBNull(22) ? null : (byte)dataReader.GetInt32(22); + result.Iso639LanguageCode = dataReader.IsDBNull(23) ? null : dataReader.GetString(23); + result.Text = dataReader.IsDBNull(24) ? null : dataReader.GetString(24); + } + dataReader.Close(); + command.Dispose(); + conn.Close(); + return result; + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/StringExtensions.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/StringExtensions.cs new file mode 100644 index 0000000..18fe6a4 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/StringExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + internal static class StringExtensions + { + public static string TrimJunk(this string str) + { + if (str == null) + return null; + + str = str.Trim('\0'); + + if (!str.Contains("\0")) + return str; + + string iris = ""; + foreach(char c in str) + { + if (c == 0) + break; + iris += c; + } + return iris; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/T2MI.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/T2MI.cs new file mode 100644 index 0000000..f838bcf --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/T2MI.cs @@ -0,0 +1,177 @@ +using Npgsql; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + public Dictionary _knownT2MiTimestamps; + + public DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid) + { + if (_knownT2MiTimestamps == null) + _knownT2MiTimestamps = new Dictionary(); + + DatabaseKeyT2MI key = new DatabaseKeyT2MI(currentNetworkId, currentTransportStreamId, pid); + if (_knownT2MiTimestamps.ContainsKey(key)) + return _knownT2MiTimestamps[key]; + + DateTime result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "select t2mi_time\r\nfrom dvb_t2mi_timestamps\r\nWHERE cnid = @cnid\r\nAND ctsid = @ctsid\r\nAND pid = @pid"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, pid); + NpgsqlDataReader reader = cmd.ExecuteReader(); + if (reader.Read()) + { + result = reader.GetDateTime(0); + _knownT2MiTimestamps[key] = result; + } + else + { + result = DateTime.MinValue; + } + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + return result; + } + + public void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + DatabaseKeyT2MI key = new DatabaseKeyT2MI(currentNetworkId, currentTransportStreamId, pid); + if (_knownT2MiTimestamps.ContainsKey(key)) + { + //already in db, so perform update + EnqueueTask(x => UpdateT2MiTimestamp(x, currentNetworkId, currentTransportStreamId, pid, resolveTime)); + } + else + { + //not yet in db, so perform insert + EnqueueTask(x => InsertT2MiTimestamp(x, currentNetworkId, currentTransportStreamId, pid, resolveTime)); + } + _knownT2MiTimestamps[key] = resolveTime; + EnqueueTask(CommitTransaction); + } + + private void UpdateT2MiTimestamp(NpgsqlConnection x, int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + NpgsqlCommand cmd = x.CreateCommand(); + cmd.CommandText = "UPDATE dvb_t2mi_timestamps " + + "SET t2mi_time = @resolveTime, updated_counter = updated_counter + 1, updated_timestamp = current_timestamp " + + "WHERE cnid = @cnid " + + "AND ctsid = @ctsid " + + "AND pid = @pid"; + cmd.Parameters.AddWithValue("@resolveTime", NpgsqlTypes.NpgsqlDbType.Timestamp, resolveTime); + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, pid); + cmd.ExecuteNonQuery(); + } + + private void InsertT2MiTimestamp(NpgsqlConnection x, int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + NpgsqlCommand cmd = x.CreateCommand(); + cmd.CommandText = "insert into dvb_t2mi_timestamps (cnid, ctsid, pid, t2mi_time)\r\nvalues (@cnid,@ctsid,@pid,@t2mi_time)"; + cmd.Parameters.AddWithValue("@cnid", currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", currentTransportStreamId); + cmd.Parameters.AddWithValue("@pid", pid); + cmd.Parameters.AddWithValue("@t2mi_time", resolveTime); + cmd.ExecuteNonQuery(); + } + + private HashSet _knownT2MiTransmitters; + + public bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + if (_knownT2MiTransmitters == null) + _knownT2MiTransmitters = new HashSet(); + + DatabaseKeyT2MiTransmitter key = new DatabaseKeyT2MiTransmitter(currentNetworkId.Value, currentTransportStreamId.Value, relatedPid, txIdentifier); + if (_knownT2MiTransmitters.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT dateadded \r\nFROM dvb_t2mi_transmitters\r\nWHERE cnid = @cnid\r\nAND ctsid = @ctsid\r\nAND pid = @pid\r\nAND tx = @tx"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId.Value); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId.Value); + cmd.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, relatedPid); + cmd.Parameters.AddWithValue("@tx", NpgsqlTypes.NpgsqlDbType.Integer, (int)txIdentifier); + NpgsqlDataReader dataReader = cmd.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + cmd.Dispose(); + conn.Close(); + } + if (result) + _knownT2MiTransmitters.Add(key); + return result; + } + + public void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + EnqueueTask(x => InsertT2MiTransmitter(x, currentNetworkId.Value, currentTransportStreamId.Value, relatedPid, (int)txIdentifier)); + DatabaseKeyT2MiTransmitter key = new DatabaseKeyT2MiTransmitter(currentNetworkId.Value, currentTransportStreamId.Value, relatedPid, txIdentifier); + _knownT2MiTransmitters.Add(key); + } + + private void InsertT2MiTransmitter(NpgsqlConnection x, int currentNetworkId, int currentTransportStreamId, int relatedPid, int txIdentifier) + { + NpgsqlCommand cmd = x.CreateCommand(); + cmd.CommandText = "INSERT INTO dvb_t2mi_transmitters (cnid, ctsid, pid, tx) VALUES (@cnid, @ctsid, @pid, @tx)"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, relatedPid); + cmd.Parameters.AddWithValue("@tx", NpgsqlTypes.NpgsqlDbType.Integer, (int)txIdentifier); + cmd.ExecuteNonQuery(); + } + + private Dictionary _knownT2MiTimeOffsets; + public void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset) + { + if (_knownT2MiTimeOffsets == null) + _knownT2MiTimeOffsets = new Dictionary(); + + DatabaseKeyT2MiTransmitter key = new DatabaseKeyT2MiTransmitter(currentNetworkId.Value, currentTransportStreamId.Value, relatedPid, txIdentifier); + if (_knownT2MiTimeOffsets.ContainsKey(key)) + { + if (_knownT2MiTimeOffsets[key] == timeOffset) + return; + } + + EnqueueTask(x => UpdateT2MiTransmitterTimeOffset(x, currentNetworkId.Value, currentTransportStreamId.Value, relatedPid, txIdentifier, timeOffset)); + _knownT2MiTimeOffsets[key] = timeOffset; + } + + private void UpdateT2MiTransmitterTimeOffset(NpgsqlConnection x, int currentNetworkId, int currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset) + { + NpgsqlCommand cmd = x.CreateCommand(); + cmd.CommandText = "UPDATE dvb_t2mi_transmitters\r\n" + + "SET timeoffset = @timeoffset, updated_counter = updated_counter + 1, updated_timestamp = CURRENT_TIMESTAMP\r\n" + + "WHERE cnid = @cnid\r\n" + + "AND ctsid = @ctsid\r\n" + + "AND pid = @pid\r\n" + + "AND tx = @tx"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@pid", NpgsqlTypes.NpgsqlDbType.Integer, relatedPid); + cmd.Parameters.AddWithValue("@tx", NpgsqlTypes.NpgsqlDbType.Integer, (int)txIdentifier); + cmd.Parameters.AddWithValue("@timeoffset", NpgsqlTypes.NpgsqlDbType.Integer, (int)timeOffset); + cmd.ExecuteNonQuery(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Tdt.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Tdt.cs new file mode 100644 index 0000000..101b41b --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Tdt.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private Dictionary _knownTdt; + + public bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + if (_knownTdt == null) + _knownTdt = new Dictionary(); + DatabaseKeyTdt key = new DatabaseKeyTdt(currentNetworkId, currentTransportStreamId); + if (!_knownTdt.ContainsKey(key)) + { + DateTime? inDb = SelectTdt(currentNetworkId, currentTransportStreamId); + if (inDb.HasValue) + { + //already in db + _knownTdt.Add(key, inDb.Value); + } + else + { + //not in db, need insert + EnqueueTask(x => InsertTdt(x, currentNetworkId, currentTransportStreamId, utcTime)); + EnqueueTask(CommitTransaction); + _knownTdt.Add(key, utcTime); + return true; + } + } + + DateTime lastKnownTime = _knownTdt[key]; + if (lastKnownTime < utcTime) + { + EnqueueTask(x => UpdateTdt(x, currentNetworkId, currentTransportStreamId, utcTime)); + EnqueueTask(CommitTransaction); + _knownTdt[key] = utcTime; + return true; + } + return false; + } + + private void UpdateTdt(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_tdt " + + "SET utc_time = @utc, updated_counter = updated_counter + 1, updated_timestamp = CURRENT_TIMESTAMP " + + "WHERE cnid = @cnid " + + "AND ctsid = @ctsid"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@utc", NpgsqlDbType.Timestamp, utcTime); + command.ExecuteNonQuery(); + } + + private void InsertTdt(NpgsqlConnection conn, int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "INSERT INTO dvb_tdt (cnid, ctsid, utc_time) VALUES (@cnid, @ctsid, @utc)"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddWithValue("@utc", NpgsqlDbType.Timestamp, utcTime); + command.ExecuteNonQuery(); + } + + private DateTime? SelectTdt(int currentNetworkId, int currentTransportStreamId) + { + DateTime? result = null; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT utc_time FROM dvb_tdt WHERE cnid = @cnid AND ctsid = @ctsid"; + command.Parameters.AddWithValue("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddWithValue("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + result = dataReader.GetDateTime(0); + } + dataReader.Close(); + command.Dispose(); + conn.Close(); + } + return result; + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Teletext.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Teletext.cs new file mode 100644 index 0000000..237cc50 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Teletext.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.Teletext; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet knownTeletextPages; + public bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + if (knownTeletextPages == null) + knownTeletextPages = new HashSet(); + + timestamp = new DateTime(timestamp.Year, timestamp.Month, timestamp.Day, timestamp.Hour, timestamp.Minute, 0); + DatabaseKeyTeletextPage key = new DatabaseKeyTeletextPage(networkId, transportStreamId, programNumber, magazine.HumanReadablePageNumber, timestamp); + if (knownTeletextPages.Contains(key)) + return false; + + EnqueueTask(x => WriteTeletextPage(x, networkId, transportStreamId, programNumber, magazine, timestamp)); + knownTeletextPages.Add(key); + return true; + } + + private void WriteTeletextPage(NpgsqlConnection connection, int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + if (TestForTeletextPage(connection, networkId, transportStreamId, programNumber, magazine.HumanReadablePageNumber, timestamp)) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO teletext (nid, tsid, program_number, page, tdt_time, page_content) " + + "VALUES (@nid, @tsid, @program_number, @page, @tdt_time, @page_content)"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)programNumber); + command.Parameters.AddWithValue("@page", NpgsqlDbType.Integer, (int)magazine.HumanReadablePageNumber); + command.Parameters.AddWithValue("@tdt_time", NpgsqlDbType.Timestamp, timestamp); + command.Parameters.AddWithValue("@page_content", NpgsqlDbType.Bytea, magazine.FormatAsByteArray()); + command.ExecuteNonQuery(); + } + + private bool TestForTeletextPage(NpgsqlConnection connection, int networkId, int transportStreamId, ushort programNumber, int pageNumber, DateTime timestamp) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM teletext WHERE nid = @nid AND tsid = @tsid AND program_number = @program_number AND page = @page AND tdt_time = @tdt_time"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@program_number", NpgsqlDbType.Integer, (int)programNumber); + command.Parameters.AddWithValue("@page", NpgsqlDbType.Integer, pageNumber); + command.Parameters.AddWithValue("@tdt_time", NpgsqlDbType.Timestamp, timestamp); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Tot.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Tot.cs new file mode 100644 index 0000000..69c1769 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Tot.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private Dictionary _knownTots; + public bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + if (_knownTots == null) + _knownTots = new Dictionary(); + if (ltod == null) + return false; + + bool result = false; + foreach (LocalTimeOffsetDescriptor.LocalTime offset in ltod.LocalTimeOffsets) + { + DatabaseKeyTot key = new DatabaseKeyTot(currentNetworkId, currentTransportStreamId, offset.CountryCode, (int)offset.LocalTimeOffset.TotalSeconds, offset.LocalTimeOffsetPolarity); + if (_knownTots.ContainsKey(key)) + { + //Already cached + DateTime inCache = _knownTots[key]; + if (inCache < utcTime) + { + //We are in the future + EnqueueTask(x => WriteUpdateTot(x,currentNetworkId,currentTransportStreamId,utcTime, offset)); + _knownTots[key] = utcTime; + result = true; + } + else + { + //We are in the past, nothing to do. + continue; + } + } + else + { + //not yet cached + DateTime? inDb = SelectTot(currentNetworkId, currentTransportStreamId, offset.CountryCode, (int)offset.LocalTimeOffset.TotalSeconds, offset.LocalTimeOffsetPolarity); + if (inDb.HasValue) + { + if (inDb < utcTime) + { + //Database time in the past, need to update + EnqueueTask(x => WriteUpdateTot(x, currentNetworkId, currentTransportStreamId, utcTime, offset)); + _knownTots[key] = utcTime; + result = true; + } + else + { + //Database time in the future, no update needed + _knownTots[key] = inDb.Value; + continue; + } + } + else + { + //not yet in database, need to insert + EnqueueTask(x => WriteTot(x, currentNetworkId, currentTransportStreamId, utcTime, offset)); + _knownTots[key] = utcTime; + result = true; + } + } + } + + EnqueueTask(CommitTransaction); + return result; + } + + private void WriteTot(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor.LocalTime offset) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into dvb_tot (cnid, ctsid, country_code, utc, country_region_id, local_time_offset_polarity,\r\n local_time_offset, time_of_change, next_time_offset) " + + "values (@cnid, @ctsid, @country_code, @utc, @country_region_id, @local_time_offset_polarity,\r\n @local_time_offset, @time_of_change, @next_time_offset)"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, (ushort)currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, (ushort)currentTransportStreamId); + command.Parameters.AddParameter("@country_code", NpgsqlDbType.Varchar, offset.CountryCode.Trim('\0')); + command.Parameters.AddParameter("@utc", NpgsqlDbType.Timestamp, utcTime); + command.Parameters.AddParameter("@country_region_id", NpgsqlDbType.Integer, offset.CountryRegionId); + command.Parameters.AddParameter("@local_time_offset_polarity", NpgsqlDbType.Boolean, offset.LocalTimeOffsetPolarity); + command.Parameters.AddParameter("@local_time_offset", NpgsqlDbType.Integer, (int)offset.LocalTimeOffset.TotalSeconds); + command.Parameters.AddParameter("@time_of_change", NpgsqlDbType.Timestamp, offset.TimeOfChange); + command.Parameters.AddParameter("@next_time_offset", NpgsqlDbType.Integer, offset.NextTimeOffset.TotalSeconds); + command.ExecuteNonQuery(); + } + + private void WriteUpdateTot(NpgsqlConnection connection, int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor.LocalTime offset) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "UPDATE dvb_tot\r\n" + + "SET utc = @utc," + + " country_region_id = @country_region_id," + + " time_of_change = @time_of_change," + + " next_time_offset = @next_time_offset," + + " updated_counter = updated_counter + 1," + + " updated_timestamp = CURRENT_TIMESTAMP " + + "WHERE cnid = @cnid " + + "AND ctsid = @ctsid " + + "AND country_region_id = @country_region_id " + + "AND local_time_offset_polarity = @local_time_offset_polarity " + + "AND local_time_offset = @local_time_offset"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, (ushort)currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, (ushort)currentTransportStreamId); + command.Parameters.AddParameter("@country_code", NpgsqlDbType.Varchar, offset.CountryCode); + command.Parameters.AddParameter("@utc", NpgsqlDbType.Timestamp, utcTime); + command.Parameters.AddParameter("@country_region_id", NpgsqlDbType.Integer, offset.CountryRegionId); + command.Parameters.AddParameter("@local_time_offset_polarity", NpgsqlDbType.Boolean, offset.LocalTimeOffsetPolarity); + command.Parameters.AddParameter("@local_time_offset", NpgsqlDbType.Integer, (int)offset.LocalTimeOffset.TotalSeconds); + command.Parameters.AddParameter("@time_of_change", NpgsqlDbType.Timestamp, offset.TimeOfChange); + command.Parameters.AddParameter("@next_time_offset", NpgsqlDbType.Integer, offset.NextTimeOffset.TotalSeconds); + command.ExecuteNonQuery(); + } + + private DateTime? SelectTot(int currentNetworkId, int currentTransportStreamId, string countyCode, int totalSeconds, bool offsetLocalTimeOffsetPolarity) + { + DateTime? result = null; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "SELECT utc FROM dvb_tot WHERE cnid = @cnid AND ctsid = @ctsid AND country_code = @country_code AND local_time_offset = @local_time_offset AND local_time_offset_polarity = @local_time_offset_polarity"; + command.Parameters.AddParameter("@cnid", NpgsqlDbType.Integer, currentNetworkId); + command.Parameters.AddParameter("@ctsid", NpgsqlDbType.Integer, currentTransportStreamId); + command.Parameters.AddParameter("@country_code", NpgsqlDbType.Varchar, countyCode.Trim('\0')); + command.Parameters.AddParameter("@local_time_offset", NpgsqlDbType.Integer, totalSeconds); + command.Parameters.AddParameter("@local_time_offset_polarity", NpgsqlDbType.Boolean, offsetLocalTimeOffsetPolarity); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + result = dataReader.GetDateTime(0); + dataReader.Close(); + command.Dispose(); + conn.Close(); + } + + return result; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Tsdt.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Tsdt.cs new file mode 100644 index 0000000..abf1893 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Tsdt.cs @@ -0,0 +1,123 @@ +using Npgsql; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet> _knownCompliances; + public bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + if (_knownCompliances == null) + _knownCompliances = new HashSet>(); + + Tuple key = new Tuple(currentNetworkId, currentTransportStreamId, compliance); + if (_knownCompliances.Contains(key)) + return true; + + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT dateadded FROM dvb_tsdt WHERE cnid = @cnid AND ctsid = @ctsid AND compliance = @compliance"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@compliance", NpgsqlTypes.NpgsqlDbType.Text, compliance); + NpgsqlDataReader reader = cmd.ExecuteReader(); + result = reader.Read(); + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + if (result) + _knownCompliances.Add(key); + return result; + } + + public void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + EnqueueTask(x => InsertTsdt(x, currentNetworkId, currentTransportStreamId, compliance)); + Tuple key = new Tuple(currentNetworkId, currentTransportStreamId, compliance); + _knownCompliances.Add(key); + } + + private void InsertTsdt(NpgsqlConnection x, int currentNetworkId, int currentTransportStreamId, string compliance) + { + NpgsqlCommand cmd = x.CreateCommand(); + cmd.CommandText = "INSERT INTO dvb_tsdt (cnid,ctsid,compliance) VALUES (@cnid,@ctsid,@compliance)"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@compliance", NpgsqlTypes.NpgsqlDbType.Text, compliance); + cmd.ExecuteNonQuery(); + } + + private HashSet> knownStationIdentifications; + public bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string stationIdentification) + { + if (stationIdentification == null) + return false; + + if (knownStationIdentifications == null) + knownStationIdentifications = new HashSet>(); + + Tuple key = new Tuple(currentNetworkId, currentTransportStreamId, stationIdentification); + if (knownStationIdentifications.Contains(key)) + return false; + + string currentStationIdentification = null; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT station_identification FROM dvb_tsdt WHERE cnid = @cnid AND ctsid = @ctsid"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + NpgsqlDataReader reader = cmd.ExecuteReader(); + if (!reader.Read()) + { + //station identification without compliance? weird, let's wait a bit and try again. + reader.Close(); + conn.Close(); + return false; + } + if (!reader.IsDBNull(0)) + currentStationIdentification = reader.GetString(0); + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + + if (stationIdentification.Equals(currentStationIdentification)) + { + //schon bekannt, alles gut. + knownStationIdentifications.Add(key); + return false; + } + else + { + //irgendwie anders + EnqueueTask(x => UpdateStationIdentification(x, currentNetworkId, currentTransportStreamId, stationIdentification)); + knownStationIdentifications.Add(key); + return true; + } + } + + private void UpdateStationIdentification(NpgsqlConnection x, int currentNetworkId, int currentTransportStreamId, string stationIdentification) + { + NpgsqlCommand cmd = x.CreateCommand(); + cmd.CommandText = "update dvb_tsdt\r\n" + + "set station_identification = @station_identification\r\n" + + "where cnid = @cnid\r\n" + + "and ctsid = @ctsid"; + cmd.Parameters.AddWithValue("@cnid", NpgsqlTypes.NpgsqlDbType.Integer, currentNetworkId); + cmd.Parameters.AddWithValue("@ctsid", NpgsqlTypes.NpgsqlDbType.Integer, currentTransportStreamId); + cmd.Parameters.AddWithValue("@station_identification", NpgsqlTypes.NpgsqlDbType.Text, stationIdentification); + cmd.ExecuteNonQuery(); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/UiEntities.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/UiEntities.cs new file mode 100644 index 0000000..21f460d --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/UiEntities.cs @@ -0,0 +1,297 @@ +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Equipment; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private int uiVersion; + + public void UiSetVersion(int version) + { + this.uiVersion = version; + } + + public List UiLnbTypesListAll() + { + List lnbTypes = new List(); + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT * FROM skyscraper5_lnbs"; + NpgsqlDataReader dataReader = cmd.ExecuteReader(); + while (dataReader.Read()) + { + int id = dataReader.GetInt32(0); + DateTime added = dataReader.GetDateTime(1); + string name = dataReader.GetString(2); + int lof1 = dataReader.GetInt32(3); + int lof2 = dataReader.GetInt32(4); + int lofSw = dataReader.GetInt32(5); + int minFreq = dataReader.GetInt32(6); + int maxFreq = dataReader.GetInt32(7); + lnbTypes.Add(new LnbType(name, lof1, lof2, lofSw, minFreq, maxFreq) { DateAdded = added, Id = id }); + } + + dataReader.Close(); + cmd.Dispose(); + conn.Close(); + } + + return lnbTypes; + } + + public void UiLnbTypesAdd(LnbType defaultLnbType) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = + "INSERT INTO skyscraper5_lnbs (name, lof1, lof2, lof_sw, min_freq, max_freq) VALUES (@name,@lof1,@lof2,@lof_sw,@min_freq,@max_freq)"; + cmd.Parameters.AddWithValue("@name", NpgsqlDbType.Text, defaultLnbType.Name); + cmd.Parameters.AddWithValue("@lof1", NpgsqlDbType.Integer, defaultLnbType.Lof1); + cmd.Parameters.AddWithValue("@lof2", NpgsqlDbType.Integer, defaultLnbType.Lof2); + cmd.Parameters.AddWithValue("@lof_sw", NpgsqlDbType.Integer, defaultLnbType.LofSw); + cmd.Parameters.AddWithValue("@min_freq", NpgsqlDbType.Integer, defaultLnbType.MinimumFrequency); + cmd.Parameters.AddWithValue("@max_freq", NpgsqlDbType.Integer, defaultLnbType.MaximumFrequency); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + conn.Close(); + } + } + + public List UiDishTypesListAll() + { + List result = new List(); + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT * FROM skyscraper5_dishes"; + NpgsqlDataReader dataReader = cmd.ExecuteReader(); + while (dataReader.Read()) + { + int id = dataReader.GetInt32(0); + DateTime added = dataReader.GetDateTime(1); + string name = dataReader.GetString(2); + int diameter = dataReader.GetInt32(3); + DishShape shape = (DishShape)dataReader.GetInt32(4); + result.Add(new DishType(name, diameter, shape) { DateAdded = added, Id = id }); + } + } + + return result; + } + + public void UiDishTypesAdd(DishType defaultDishType) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "INSERT INTO skyscraper5_dishes (name, diameter, shape) VALUES (@name,@diameter,@shape)"; + cmd.Parameters.AddWithValue("@name", NpgsqlDbType.Text, defaultDishType.Name); + cmd.Parameters.AddWithValue("@diameter", NpgsqlDbType.Integer, defaultDishType.Diameter); + cmd.Parameters.AddWithValue("@shape", NpgsqlDbType.Integer, (int)defaultDishType.Shape); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + conn.Close(); + } + } + + + public List UiSatellitesListAll() + { + List positions = new List(); + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT * FROM skyscraper5_satellite_positions"; + NpgsqlDataReader reader = cmd.ExecuteReader(); + while (reader.Read()) + { + float angle = (float)reader.GetDouble(0); + int cardinal = reader.GetInt32(1); + string name = reader.GetString(2); + SatellitePosition child = new SatellitePosition(angle, cardinal, name); + positions.Add(child); + } + + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + + return positions; + } + + public void UiSatellitesAdd(SatellitePosition newPosition) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = + "insert into skyscraper5_satellite_positions (angle, cardinal, name, keyversion, key) " + + "values (@angle, @cardinal, @name, @keyversion, @key)"; + cmd.Parameters.AddWithValue("@angle", NpgsqlDbType.Real, newPosition.angle); + cmd.Parameters.AddWithValue("@cardinal", NpgsqlDbType.Integer, newPosition.cardinalDirection); + cmd.Parameters.AddWithValue("@name", NpgsqlDbType.Text, newPosition.name); + cmd.Parameters.AddWithValue("@keyversion", NpgsqlDbType.Integer, uiVersion); + cmd.Parameters.AddWithValue("@key", NpgsqlDbType.Text, newPosition.Checksum.ToString()); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + conn.Close(); + } + } + + public void UiSatellitesDelete(SatellitePosition satellitePosition) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = + "DELETE FROM skyscraper5_satellite_positions WHERE keyversion = @keyversion AND key = @key"; + cmd.Parameters.AddWithValue("@keyversion", NpgsqlDbType.Integer, uiVersion); + cmd.Parameters.AddWithValue("@key", NpgsqlDbType.Text, satellitePosition.Checksum.ToString()); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + conn.Close(); + } + } + + public bool UiTunerTestFor(TunerMetadata tuner) + { + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = + "SELECT dateadded FROM skyscraper5_tuners WHERE mac = @mac AND satkeyversion = @satkeyversion"; + cmd.Parameters.AddWithValue("@mac", NpgsqlDbType.Varchar, tuner.MacAddress.ToString()); + cmd.Parameters.AddWithValue("@satkeyversion", NpgsqlDbType.Integer, uiVersion); + NpgsqlDataReader reader = cmd.ExecuteReader(); + result = reader.Read(); + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + + return result; + } + + public void UiTunerUpdate(TunerMetadata tuner) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "UPDATE skyscraper5_tuners " + + "SET diseqc_type = @diseqc_type, sat1 = @sat1, sat2 = @sat2, sat3 = @sat3, sat4 = @sat4," + + " lnb1 = @lnb1, lnb2 = @lnb2, lnb3 = @lnb3, lnb4 = @lnb4," + + " dish1 =@dish1,dish2 =@dish2,dish3 =@dish3,dish4 =@dish4 " + + "WHERE mac = @mac AND satkeyversion = @satkeyversion"; + cmd.Parameters.AddWithValue("@diseqc_type", NpgsqlDbType.Integer, (int)tuner.DiseqcType); + cmd.Parameters.AddWithValue("@sat1", NpgsqlDbType.Integer, tuner.Satellites[0]); + cmd.Parameters.AddWithValue("@sat2", NpgsqlDbType.Integer, tuner.Satellites[1]); + cmd.Parameters.AddWithValue("@sat3", NpgsqlDbType.Integer, tuner.Satellites[2]); + cmd.Parameters.AddWithValue("@sat4", NpgsqlDbType.Integer, tuner.Satellites[3]); + cmd.Parameters.AddWithValue("@mac", NpgsqlDbType.Varchar, tuner.MacAddress.ToString()); + cmd.Parameters.AddWithValue("@satkeyversion", NpgsqlDbType.Integer, uiVersion); + cmd.Parameters.AddWithValue("@lnb1", NpgsqlDbType.Integer, tuner.Lnbs[0]); + cmd.Parameters.AddWithValue("@lnb2", NpgsqlDbType.Integer, tuner.Lnbs[1]); + cmd.Parameters.AddWithValue("@lnb3", NpgsqlDbType.Integer, tuner.Lnbs[2]); + cmd.Parameters.AddWithValue("@lnb4", NpgsqlDbType.Integer, tuner.Lnbs[3]); + cmd.Parameters.AddWithValue("@dish1", NpgsqlDbType.Integer, tuner.Dishes[0]); + cmd.Parameters.AddWithValue("@dish2", NpgsqlDbType.Integer, tuner.Dishes[1]); + cmd.Parameters.AddWithValue("@dish3", NpgsqlDbType.Integer, tuner.Dishes[2]); + cmd.Parameters.AddWithValue("@dish4", NpgsqlDbType.Integer, tuner.Dishes[3]); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + conn.Close(); + } + } + + public void UiTunerInsert(TunerMetadata tuner) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = + "insert into skyscraper5_tuners (mac, vindex, vname, v_std_type, caps, diseqc_type, sat1, sat2, sat3, sat4, satkeyversion, lnb1, lnb2, lnb3, lnb4, dish1, dish2, dish3, dish4) " + + "values " + + "(@mac, @vindex, @vname, @v_std_type, @caps, @diseqc_type, @sat1, @sat2, @sat3, @sat4, @satkeyversion, @lnb1, @lnb2, @lnb3, @lnb4, @dish1, @dish2, @dish3, @dish4)"; + cmd.Parameters.AddWithValue("@mac", NpgsqlDbType.Varchar, tuner.MacAddress.ToString()); + cmd.Parameters.AddWithValue("@vindex", NpgsqlDbType.Integer, tuner.Index); + cmd.Parameters.AddWithValue("@vname", NpgsqlDbType.Text, tuner.Name); + cmd.Parameters.AddWithValue("@v_std_type", NpgsqlDbType.Integer, (int)tuner.Type); + cmd.Parameters.AddWithValue("@caps", NpgsqlDbType.Integer, (int)tuner.Caps); + cmd.Parameters.AddWithValue("@diseqc_type", NpgsqlDbType.Integer, (int)tuner.DiseqcType); + cmd.Parameters.AddWithValue("@sat1", NpgsqlDbType.Integer, tuner.Satellites[0]); + cmd.Parameters.AddWithValue("@sat2", NpgsqlDbType.Integer, tuner.Satellites[1]); + cmd.Parameters.AddWithValue("@sat3", NpgsqlDbType.Integer, tuner.Satellites[2]); + cmd.Parameters.AddWithValue("@sat4", NpgsqlDbType.Integer, tuner.Satellites[3]); + cmd.Parameters.AddWithValue("@satkeyversion", NpgsqlDbType.Integer, uiVersion); + cmd.Parameters.AddWithValue("@lnb1", NpgsqlDbType.Integer, tuner.Lnbs[0]); + cmd.Parameters.AddWithValue("@lnb2", NpgsqlDbType.Integer, tuner.Lnbs[1]); + cmd.Parameters.AddWithValue("@lnb3", NpgsqlDbType.Integer, tuner.Lnbs[2]); + cmd.Parameters.AddWithValue("@lnb4", NpgsqlDbType.Integer, tuner.Lnbs[3]); + cmd.Parameters.AddWithValue("@dish1", NpgsqlDbType.Integer, tuner.Dishes[0]); + cmd.Parameters.AddWithValue("@dish2", NpgsqlDbType.Integer, tuner.Dishes[1]); + cmd.Parameters.AddWithValue("@dish3", NpgsqlDbType.Integer, tuner.Dishes[2]); + cmd.Parameters.AddWithValue("@dish4", NpgsqlDbType.Integer, tuner.Dishes[3]); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + conn.Close(); + } + } + + public void UiTunerGetConfiguration(TunerMetadata foundTuner) + { + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = + "SELECT diseqc_type, sat1, sat2, sat3, sat4, lnb1, lnb2, lnb3, lnb4, dish1, dish2, dish3, dish4 FROM skyscraper5_tuners WHERE mac = @mac AND satkeyversion = @satkeyversion"; + cmd.Parameters.AddWithValue("@mac", NpgsqlDbType.Varchar, foundTuner.MacAddress.ToString()); + cmd.Parameters.AddWithValue("@satkeyversion", NpgsqlDbType.Integer, uiVersion); + NpgsqlDataReader reader = cmd.ExecuteReader(); + if (reader.Read()) + { + foundTuner.DiseqcType = reader.GetInt32(0); + foundTuner.Satellites[0] = reader.GetInt32(1); + foundTuner.Satellites[1] = reader.GetInt32(2); + foundTuner.Satellites[2] = reader.GetInt32(3); + foundTuner.Satellites[3] = reader.GetInt32(4); + foundTuner.Lnbs[0] = reader.GetInt32(5); + foundTuner.Lnbs[1] = reader.GetInt32(6); + foundTuner.Lnbs[2] = reader.GetInt32(7); + foundTuner.Lnbs[3] = reader.GetInt32(8); + foundTuner.Dishes[0] = reader.GetInt32(9); + foundTuner.Dishes[1] = reader.GetInt32(10); + foundTuner.Dishes[2] = reader.GetInt32(11); + foundTuner.Dishes[3] = reader.GetInt32(12); + } + + reader.Close(); + cmd.Dispose(); + conn.Close(); + } + } + + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Unt.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Unt.cs new file mode 100644 index 0000000..34f943f --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Unt.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private HashSet _knownUntGroups; + private HashSet _knownUntCompatibility; + private HashSet _knownUntPlatforms; + + public void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + if (_knownUntGroups == null) + _knownUntGroups = new HashSet(); + if (_knownUntCompatibility == null) + _knownUntCompatibility = new HashSet(); + if (_knownUntPlatforms == null) + _knownUntPlatforms = new HashSet(); + + DatabaseKeyUntPlatform platformCoordinate = new DatabaseKeyUntPlatform(common.ActionType, common.OuiHash, + common.Oui, common.ProcessingOrder, compatibility.DescriptorType, platform.GetFlagsForSqlKey()); + if (_knownUntPlatforms.Contains(platformCoordinate)) + { + return; + } + + EnqueueTask(x => WriteUnt(x, common, compatibility, platform)); + + _knownUntPlatforms.Add(platformCoordinate); + } + + private void WriteUnt(NpgsqlConnection connection, UpdateNotificationGroup common, Compatibility compatibility, + Platform platform) + { + if (!TestForUnt(connection, common)) + InsertUnt(connection, common); + + if (!TestForUntCompat(connection, common, compatibility)) + InsertUntCompat(connection, common, compatibility); + + if (!TestForUntCompatPlatform(connection, common, compatibility, platform)) + InsertUntCompatPlatform(connection, common, compatibility, platform); + } + + private bool TestForUnt(NpgsqlConnection connection, UpdateNotificationGroup common) + { + DatabaseKeyUntGroup groupCoordinate = + new DatabaseKeyUntGroup(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder); + if (_knownUntGroups.Contains(groupCoordinate)) + return true; + + NpgsqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_unt WHERE action_type = @action_type AND oui_hash = @oui_hash AND oui = @oui AND processing_order = @processing_order"; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer, common.ActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, common.OuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, common.ProcessingOrder); + NpgsqlDataReader mySqlDataReader = command.ExecuteReader(); + bool read = mySqlDataReader.Read(); + if (read) + _knownUntGroups.Add(groupCoordinate); + mySqlDataReader.Close(); + return read; + } + + private void InsertUnt(NpgsqlConnection connection, UpdateNotificationGroup common) + { + NpgsqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt (action_type, oui_hash, oui, processing_order, data_broadcast_id, association_tag, private_data, update_flag, update_method, update_priority) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @data_broadcast_id, @association_tag, @private_data, @update_flag, @update_method, @update_priority)"; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer, common.ActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, common.OuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, common.ProcessingOrder); + command.Parameters.AddParameter("@data_broadcast_id", NpgsqlDbType.Integer, common.DataBroadcastId); + command.Parameters.AddParameter("@association_tag", NpgsqlDbType.Integer, common.AssociationTag); + command.Parameters.AddParameter("@private_data", NpgsqlDbType.Bytea, common.PrivateData); + command.Parameters.AddParameter("@update_flag", NpgsqlDbType.Integer, common.UpdateFlag); + command.Parameters.AddParameter("@update_method", NpgsqlDbType.Integer, common.UpdateMethod); + command.Parameters.AddParameter("@update_priority", NpgsqlDbType.Integer, common.UpdatePriority); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + + DatabaseKeyUntGroup groupCoordinate = + new DatabaseKeyUntGroup(common.ActionType, common.OuiHash, common.Oui, common.ProcessingOrder); + _knownUntGroups.Add(groupCoordinate); + } + + private bool TestForUntCompat(NpgsqlConnection connection, UpdateNotificationGroup common, + Compatibility compatibility) + { + DatabaseKeyUntCompatibility compatibilityCoordinate = new DatabaseKeyUntCompatibility(common.ActionType, + common.OuiHash, common.Oui, common.ProcessingOrder, compatibility.DescriptorType); + if (_knownUntCompatibility.Contains(compatibilityCoordinate)) + return true; + + NpgsqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_unt_compatibility WHERE " + + "action_type = @action_type AND oui_hash = @oui_hash AND oui = @oui AND processing_order = @processing_order AND descriptor_type = @descriptor_type"; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer,common.ActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, common.OuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, common.ProcessingOrder); + command.Parameters.AddParameter("@descriptor_type", NpgsqlDbType.Integer, compatibility.DescriptorType); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _knownUntCompatibility.Add(compatibilityCoordinate); + dataReader.Close(); + return result; + } + + private void InsertUntCompat(NpgsqlConnection connection, UpdateNotificationGroup common, + Compatibility compatibility) + { + NpgsqlCommand preCommand = connection.CreateCommand(); + preCommand.CommandText = "SELECT dateadded FROM dvb_unt_compatibility WHERE action_type = @atype AND oui_hash = @ohash AND oui = @oui AND processing_order = @porder AND descriptor_type = @dtype"; + preCommand.Parameters.AddParameter("@atype", NpgsqlDbType.Integer, common.ActionType); + preCommand.Parameters.AddParameter("@ohash", NpgsqlDbType.Integer, common.OuiHash); + preCommand.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + preCommand.Parameters.AddParameter("@porder", NpgsqlDbType.Integer, common.ProcessingOrder); + preCommand.Parameters.AddParameter("@dtype", NpgsqlDbType.Integer, compatibility.DescriptorType); + NpgsqlDataReader dataReader = preCommand.ExecuteReader(); + bool alreadyKnown = dataReader.Read(); + dataReader.Close(); + preCommand.Dispose(); + if (alreadyKnown) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt_compatibility (action_type, oui_hash, oui, processing_order, descriptor_type, private_data, specifier_type, specifier_data, model, version) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @descriptor_type, @private_data, @specifier_type, @specifier_data, @model, @version)"; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer, common.ActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, common.OuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, common.ProcessingOrder); + command.Parameters.AddParameter("@descriptor_type", NpgsqlDbType.Integer, compatibility.DescriptorType); + command.Parameters.AddParameter("@private_data", NpgsqlDbType.Bytea, compatibility.PrivateData); + command.Parameters.AddParameter("@specifier_type", NpgsqlDbType.Integer, compatibility.SpecifierType); + command.Parameters.AddParameter("@specifier_data", NpgsqlDbType.Text, compatibility.SpecifierData); + command.Parameters.AddParameter("@model",NpgsqlDbType.Integer, compatibility.Model); + command.Parameters.AddParameter("@version", NpgsqlDbType.Integer, compatibility.Version); + command.Parameters.CheckNulls(); + command.ExecuteNonQuery(); + _knownUntCompatibility.Add(new DatabaseKeyUntCompatibility(common.ActionType, common.OuiHash, common.Oui, + common.ProcessingOrder, compatibility.DescriptorType)); + } + + private bool TestForUntCompatPlatform(NpgsqlConnection connection, UpdateNotificationGroup common, + Compatibility compatibility, Platform platform) + { + int sqlKey = platform.GetFlagsForSqlKey(); + DatabaseKeyUntPlatform platformCoordinate = new DatabaseKeyUntPlatform(common.ActionType, common.OuiHash, + common.Oui, common.ProcessingOrder, compatibility.DescriptorType, sqlKey); + + NpgsqlCommand command = transaction.Connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "SELECT dateadded FROM dvb_unt_compatibility_platform WHERE action_type = @action_type AND oui_hash = @oui_hash AND oui = @oui AND processing_order = @processing_order " + + "AND descriptor_type = @descriptor_type AND platform_type_bitmask = @platform_type_bitmask"; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer, common.ActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, common.OuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, common.ProcessingOrder); + command.Parameters.AddParameter("@descriptor_type", NpgsqlDbType.Integer, compatibility.DescriptorType); + command.Parameters.AddParameter("@platform_type_bitmask", NpgsqlDbType.Integer, sqlKey); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + _knownUntPlatforms.Add(platformCoordinate); + dataReader.Close(); + return result; + } + + private void InsertUntCompatPlatform(NpgsqlConnection connection, UpdateNotificationGroup common, + Compatibility compatibility, Platform platform) + { + int sqlKey = platform.GetFlagsForSqlKey(); + DatabaseKeyUntPlatform platformCoordinate = new DatabaseKeyUntPlatform(common.ActionType, common.OuiHash, + common.Oui, common.ProcessingOrder, compatibility.DescriptorType, sqlKey); + + NpgsqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt_compatibility_platform (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, update_flag, update_method, update_priority, data_broadcast_id, association_tag, private_data, smart_card_number, super_ca_system_id, serial_data, mac_address_mask, subgroup_tag) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @descriptor_type, @platform_type_bitmask, @update_flag, @update_method, @update_priority, @data_broadcast_id, " + + " @association_tag, @private_data, @smart_card_number, @super_ca_system_id, @serial_data, @mac_address_mask, @subgroup_tag)"; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer, common.ActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, common.OuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, common.Oui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, common.ProcessingOrder); + command.Parameters.AddParameter("@descriptor_type", NpgsqlDbType.Integer, compatibility.DescriptorType); + command.Parameters.AddParameter("@platform_type_bitmask", NpgsqlDbType.Integer, sqlKey); + command.Parameters.AddParameter("@update_flag", NpgsqlDbType.Integer, platform.UpdateFlag); + command.Parameters.AddParameter("@update_method", NpgsqlDbType.Integer, platform.UpdateMethod); + command.Parameters.AddParameter("@update_priority", NpgsqlDbType.Integer, platform.UpdatePriority); + command.Parameters.AddParameter("@data_broadcast_id", NpgsqlDbType.Integer, platform.DataBroadcastId); + command.Parameters.AddParameter("@association_tag", NpgsqlDbType.Integer, platform.AssociationTag); + command.Parameters.AddParameter("@private_data", NpgsqlDbType.Bytea, platform.PrivateData); + command.Parameters.AddParameter("@smart_card_number", NpgsqlDbType.Text, platform.SmartCardNumber); + command.Parameters.AddParameter("@super_ca_system_id", NpgsqlDbType.Bigint, platform.SuperCaSystemId); + command.Parameters.AddParameter("@serial_data", NpgsqlDbType.Bytea, platform.SerialData); + command.Parameters.AddParameter("@mac_address_mask", NpgsqlDbType.Varchar, platform.MacAddressMask); + command.Parameters.AddParameter("@subgroup_tag", NpgsqlDbType.Bytea, platform.SubgroupTag); + command.ExecuteNonQuery(); + + InsertUntCompatPlatformMacAddressMatches(connection, platformCoordinate, platform); + _knownUntPlatforms.Add(platformCoordinate); + } + + private void InsertUntCompatPlatformMacAddressMatches(NpgsqlConnection connection, + DatabaseKeyUntPlatform platformCoordinate, Platform platform) + { + if (platform.MacAddressMatches == null) + return; + + if (platform.MacAddressMatches.Length == 0) + return; + + NpgsqlCommand command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + "INSERT INTO dvb_unt_compatibility_platform_mac_address_matches (action_type, oui_hash, oui, processing_order, descriptor_type, platform_type_bitmask, mac_match) " + + "VALUES (@action_type, @oui_hash, @oui, @processing_order, @descriptor_type, @platform_type_bitmask, @mac_match) "; + command.Parameters.AddParameter("@action_type", NpgsqlDbType.Integer, platformCoordinate.CommonActionType); + command.Parameters.AddParameter("@oui_hash", NpgsqlDbType.Integer, platformCoordinate.CommonOuiHash); + command.Parameters.AddParameter("@oui", NpgsqlDbType.Varchar, platformCoordinate.CommonOui); + command.Parameters.AddParameter("@processing_order", NpgsqlDbType.Integer, platformCoordinate.CommonProcessingOrder); + command.Parameters.AddParameter("@descriptor_type", NpgsqlDbType.Integer, platformCoordinate.CompatibilityDescriptorType); + command.Parameters.AddParameter("@platform_type_bitmask", NpgsqlDbType.Integer, platformCoordinate.PlatformKey); + command.Parameters.Add("@mac_match", NpgsqlDbType.Varchar); + + foreach (string match in platform.MacAddressMatches) + { + command.Parameters["@mac_match"].Value = match; + command.ExecuteNonQuery(); + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/WaitForCompletion.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/WaitForCompletion.cs new file mode 100644 index 0000000..8164dde --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/WaitForCompletion.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private bool _isWaitingForCompletion; + public void WaitForCompletion() + { + if (_writerThread != null) + { + if (_writerThread.IsAlive) + { + Console.WriteLine("database still busy"); + if (_writerThread == null) + return; + _isWaitingForCompletion = true; + _writerThread.Join(); + _isWaitingForCompletion = false; + _writerThread = null; + _writerTasks = null; + } + } + + knownEitEvents = null; + knownSdtServices = null; + knownPats = null; + knownPmts = null; + _knownBatBouquets = null; + knownNitNetworks = null; + updatedSdtServices = null; + _blacklistedDsmCcModules = null; + _knownTdt = null; + _knownNitTs = null; + knownTeletextPages = null; + _updatedBats = null; + _knownUpdatedNitNetworks = null; + _knownBatTs = null; + _knownCats = null; + knownScte35TimeSignals = null; + _knownInts = null; + _knownT2MiTimestamps = null; + _knownT2MiTransmitters = null; + _knownT2MiTimeOffsets = null; + _knownCompliances = null; + _tbtpCache = null; + _cmtCache = null; + _rmtTransmissionStdCache = null; + _tmstCache = null; + _rmtLinkageCache = null; + _rmtTransportStreamCache = null; + _sctCache = null; + _fctCache = null; + _sptCache = null; + timNid = null; + contentedTims = null; + correctionControlledTims = null; + _networkLayerInfoTims = null; + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs new file mode 100644 index 0000000..9846b49 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql; + +namespace skyscraper5.Data.PostgreSql +{ + public partial class PostgresqlDataStore + { + private Thread _writerThread; + + private delegate void WriterTask(NpgsqlConnection connection); + + private Queue _writerTasks; + + private void EnqueueTask(WriterTask task) + { + if (_writerTasks == null) + { + _writerTasks = new Queue(); + } + + lock (_writerTasks) + { + _writerTasks.Enqueue(task); + } + + if (_writerThread == null) + { + _writerThread = new Thread(WriterThreadFunction); + _writerThread.Priority = ThreadPriority.Highest; + _writerThread.Name = "PostgreSQL Writer Thread"; + _writerThread.Start(); + } + } + + private NpgsqlTransaction transaction; + private void WriterThreadFunction() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + transaction = connection.BeginTransaction(); + while (_writerTasks.Count > 0) + { + WriterTask task; + lock (_writerTasks) + { + task = _writerTasks.Dequeue(); + } + task(connection); + } + transaction.Commit(); + transaction.Dispose(); + connection.Close(); + } + + _writerThread = null; + } + + private void CommitTransaction(NpgsqlConnection conn) + { + transaction.Commit(); + transaction.Dispose(); + transaction = conn.BeginTransaction(); + if (_isWaitingForCompletion) + { + Console.WriteLine("-> DB: {0} tasks remain.", _writerTasks.Count); + } + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/skyscraper5.Data.PostgreSql.csproj b/DataTableStorages/skyscraper5.Data.PostgreSql/skyscraper5.Data.PostgreSql.csproj new file mode 100644 index 0000000..d02a6b5 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/skyscraper5.Data.PostgreSql.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + true + + + + + + + + + + + diff --git a/Documentation/16660841.cs b/Documentation/16660841.cs new file mode 100644 index 0000000..f26e3c1 --- /dev/null +++ b/Documentation/16660841.cs @@ -0,0 +1,40 @@ +public static void ToLatLon(double utmX, double utmY, string utmZone, out double latitude, out double longitude) + { + bool isNorthHemisphere = utmZone.Last() >= 'N'; + + var diflat = -0.00066286966871111111111111111111111111; + var diflon = -0.0003868060578; + + var zone = int.Parse(utmZone.Remove(utmZone.Length - 1)); + var c_sa = 6378137.000000; + var c_sb = 6356752.314245; + var e2 = Math.Pow((Math.Pow(c_sa,2) - Math.Pow(c_sb,2)),0.5)/c_sb; + var e2cuadrada = Math.Pow(e2,2); + var c = Math.Pow(c_sa,2) / c_sb; + var x = utmX - 500000; + var y = isNorthHemisphere ? utmY : utmY - 10000000; + + var s = ((zone * 6.0) - 183.0); + var lat = y / (c_sa * 0.9996); + var v = (c / Math.Pow(1 + (e2cuadrada * Math.Pow(Math.Cos(lat), 2)), 0.5)) * 0.9996; + var a = x / v; + var a1 = Math.Sin(2 * lat); + var a2 = a1 * Math.Pow((Math.Cos(lat)), 2); + var j2 = lat + (a1 / 2.0); + var j4 = ((3 * j2) + a2) / 4.0; + var j6 = ((5 * j4) + Math.Pow(a2 * (Math.Cos(lat)), 2)) / 3.0; + var alfa = (3.0 / 4.0) * e2cuadrada; + var beta = (5.0 / 3.0) * Math.Pow(alfa, 2); + var gama = (35.0 / 27.0) * Math.Pow(alfa, 3); + var bm = 0.9996 * c * (lat - alfa * j2 + beta * j4 - gama * j6); + var b = (y - bm) / v; + var epsi = ((e2cuadrada * Math.Pow(a, 2)) / 2.0) * Math.Pow((Math.Cos(lat)), 2); + var eps = a * (1 - (epsi / 3.0)); + var nab = (b * (1 - epsi)) + lat; + var senoheps = (Math.Exp(eps) - Math.Exp(-eps)) / 2.0; + var delt = Math.Atan(senoheps/(Math.Cos(nab) ) ); + var tao = Math.Atan(Math.Cos(delt) * Math.Tan(nab)); + + longitude = ((delt * (180.0 / Math.PI)) + s) + diflon; + latitude = ((lat + (1 + e2cuadrada * Math.Pow(Math.Cos(lat), 2) - (3.0 / 2.0) * e2cuadrada * Math.Sin(lat) * Math.Cos(lat) * (tao - lat)) * (tao - lat)) * (180.0 / Math.PI)) + diflat; + } \ No newline at end of file diff --git a/Documentation/68725547.r b/Documentation/68725547.r new file mode 100644 index 0000000..8b755a3 --- /dev/null +++ b/Documentation/68725547.r @@ -0,0 +1,52 @@ +require(tidyverse) +require(purrr) +require(testthat) + +find_one_utm_zone <- function(longitude, latitude) { + + # Special zones for Svalbard + if (latitude >= 72.0 && latitude <= 84.0 ) { + if (longitude >= 0.0 && longitude < 9.0) + return("31X"); + if (longitude >= 9.0 && longitude < 21.0) + return("33X") + if (longitude >= 21.0 && longitude < 33.0) + return("35X") + if (longitude >= 33.0 && longitude < 42.0) + return("37X") + } + # Special zones for Norway + if (latitude >= 56.0 && latitude < 64.0 ) { + if (longitude >= 0.0 && longitude < 3.0) + return("31V"); + if (longitude >= 3.0 && longitude < 12.0) + return("32V") + } + + # North + South Poles + + if (latitude > 84.0){ + if ((longitude+180)%%360-180 < 0) {return("Y")} + if ((longitude+180)%%360-180 > 0) {return("Z")} + } else if (latitude < -80.0){ + if ((longitude+180)%%360-180 < 0) {return("A")} + if ((longitude+180)%%360-180 > 0) {return("B")} + } + + # Everything in the middle + + if ( (latitude>-80.0) && (latitude<=84.0) ){ + + mid_zones <- LETTERS[c(3:8,10:14,16:24)] # C to X, skip I and O + utm_letter <- mid_zones[ min(floor( (latitude + 80) / 8 )+1 , 20) ] + utm_number <- (floor( (longitude + 180) / 6 ) %% 60) + 1 # modulo in case longitude is 0 to 360 instead of -180 to 180 + utm_zone <- paste0(utm_number, utm_letter) + return(utm_zone) + + } else { + stop("lat long not valid (or something else broke)") + } +} +find_utm_zone <- function(lon, lat){ + purrr::map2_chr(.x = lon, .y = lat, .f = find_one_utm_zone) +} \ No newline at end of file diff --git a/Documentation/TODO.md b/Documentation/TODO.md new file mode 100644 index 0000000..a2a8ffe --- /dev/null +++ b/Documentation/TODO.md @@ -0,0 +1,140 @@ +# skyscraper5 TODO + +* [DONE] LocalStreamReader (und rsr-server) ausgliedern, damit Main Assembly wieder in MSIL kompiliert werden kann. +* [DONE] LocalStreamReader als eigene Assembly (Library) +* [DONE] rsr-server als eigene eigene Assembly (Console Application) +* [DONE] dvbtmagic als eigene Assembly, hat ja nicht wirklich was mit skyscraping zu tun. +* AC3 Parser (kein Decoder) fur Ancillary Data +* MP2 Parser (kein Decoder) fur Ancillary Data +* AAC Parser (kein Decoder) fur Ancillary Data +* MPEG-2 Parser (kein Decoder) fur Auxillary Data +* H.264 Parser (kein Decoder) fur Auxillary Data +* H.265 Parser (kein Decoder) fur Auxillary Data +* [DONE] LNB Typen definieren, und in der UI einstellbar machen +* [DONE] LNB Typen sollten pro Tuner-Anschluss, nicht pro Satellit definiert werden. +* [DONE] Antennen Typen definieren, und in der UI einstellbar machen +* [DONE] GPS Position sollte ermittelt werden konnen. + * [DONE] manuell + * [DONE] direkt angeschlossenes Gerat per SerialPort + * [DONE] via TCP aus gpsd + * [DONE] via UDP Broadcast/Multicast +* [DONE] Blindscan-Jobs in Datenbank schreiben. (inkl. Tuner-ID/MAC, LNB-Typ, Antennen-Typ und GPS Daten) +* [DONE] Blindscan-Ergebnisse in Datenbank schreiben +* rudimanteren Java-Port + * (inkl. Disketten-Image for the lulz - keine Abhangigkeiten außer vielleicht Apache Commons ;) ) +* [DONE] uberlegen, ob sich satbeams.com scrapen lasst. + * [DONE] Dabei einzelne Satelliten, Beams und Footprints dieser Beams in DB schreiben, + * und dann in der UI Moglichkeit bieten, anhand von GPS-Daten, Antennen-Typ und LNB-Typ zu ermitteln welche Beams empfangbar sind. + * [DONE] satbeams.com geht definitiv nicht. + * [DONE] Aber das da: https://genmap.lyngsat.org/server2/kml + * [DONE] Das ist ein JSON, welches eine Liste an KML-Files zuruckgibt. + * [DONE] KML File parsen trivial - ist XML. + * [DONE] mithilfe von https://gis.stackexchange.com/questions/42879/check-if-lat-long-point-is-within-a-set-of-polygons-using-google-maps/46720#46720 testen ob in shape +* Blockstream parsen. Dazu Blog-Post "Anatomy of Blockstream Satellite" zur Hilfe nehmen. ( https://destevez.net/2022/10/anatomy-of-blockstream-satellite/ ) +* Client/Server-Architektur fur ScraperStorage +* [DONE] Architektur aufbauen, um Handler fur eingehende MPE/IP Pakete auszugliedern + * [DONE] APRS in eben jenes ausgliedern +* [DONE] Plugin-System so ausbauen, dass Assembly-Scanning nur noch an einer Stelle notwendig ist. + * [DONE] T2MI Packet Types + * [DONE] StreamTypeAutodetection + * [DONE] Behandlung des Gewinners nicht im SkyscraperContext sondern im Contestant selber, sonst ist's witzlos. + * [DONE] ScraperStorageFactoryConnectionManager + * [DONE] TunerFactoryConnectionManager + * [DONE] TsDescriptorUnpacker + * [DONE] Mpeg2ExtensionDescriptorConverter + * [DONE] UserDefinedDescriptorUnpacker + * [DONE] bereits vorhandene PrivateDataSpecifiers in eigene Assemblys auslagern + * [DONE] DvbExtensionDescriptorConverter + * [DONE] DsmCcMessageUnpacker + * [DONE] PushMacManagementMessage +* [DONE] GPS in der UI auswählbar machen + * [DONE] Optional Fenster mit GPS Daten einblenden lassen +* [DONE] GPS via GeoIP simulieren +* [DONE] Kabel-TV Frequenzlistengenerator +* [DONE] sauberen Exception-Tree aufbauen, mit Vererbung und allem drum und dran. +* Tests mit Code-Coverage +* [DONE] TS-Generator als System.IO.Stream + * [DONE] PAT-Generator + * [DONE] PMT-Generator + * [DONE] SDT-Generator + * [DONE] CAT-Generator + * [DONE] TDT-Generator + * [DONE] TOT-Generator +* [DONE] Namespaces aufräumen +* [DONE] StreamReader Versionen prüfen. Eventuell eine KnownIssues Funktion zum Server hinzufügen? (Beispiel: StreamReader 1.2.4.101 sehr gut, StreamReader 1.2.5.176 doof) + * [DONE] Dafür PE-Parser hinzufügen (um DLLs und EXEn zu parsen) + * [DONE] DLL-Versionsinfo aus PE lesen. + * [DONE] 1.2.5.194 -> bad + * [DONE] 1.2.5.176 -> bad + * [DONE] 1.2.4.101 -> good +* Inhalte von einer Datenbank in eine andere kopieren. +* [DONE] Im SDT Parser sollte der Message Descriptor geparst werden. + * [DONE] Die Messages sollten natürlich auch in eine Datenbank geschrieben werden. +* [DONE] In TestDrid: Kooperativer Blindscan. Scannen mit einer Karte, zappen mit einer anderen. + * Tuning Retries +* ExternalEsIdDescriptor implementieren. + * Natürlich auch in die Datenbank schreiben. +* Unbekanntes Text Encoding auf Eutelsat 7, 12648 H, MIS3 +* [DONE] "Unknown Protocol" 50 +* [DONE] "Unknown Protocal" 47 +* In MinioObjectStorage.cs bei Zeile 132 - behandeln dass nicht immer unbedingt eine MinioException rauskommt! +* skyscraper5.Ietf.Rfc971.InternetHeader sollte Validatable implementieren. +* In Testdrid: Einen Notizblock +* Wenn ein Blindscan false zurückgibt, aber dennoch Ergebnisse geliefert hat, diese trotzdem abklopfen. +* [DONE] Paketfiltersystem hinzufügen, statt Pakete manuell zu droppen. + * [DONE] Aufzeichnungssystem in den Paketfilter verlagern. + * [DONE] Scramble Paket Dropper + * Tester ob zwischen Scramble und Non-Scramble geswitcht wird. + * Den Screenshotter da dran hängen! + * [DONE] TEI Paket Dropper + * [DONE] Prioritätensystem da dran hängen. (Schließlich sollen die Pakete immer zuerst aufgezeichnet werden) + * [DONE] Skip x (512) Packets Filter + * PCR Monitor dorthin verlagern + * TCP Proxy dorthin verlagern +* Descriptor 0x2E (46) bei PMT (TsDescriptorUnpacker.cs, Zeile 81) +* Netzwerkschnittstelle einbauen + * PCAP Writer + * UDPDump + * TAP Adapter (siehe https://github.com/HBSnail/UniversalTunTapDriver/blob/master/README.md ) + * /dev/null + * IP-in-IP Encapsulation, new Socket(AddressFamily.InterNetwork, Socket.Raw, ProtocolType.IP) +* [DONE] Intelsat Internet reverse engineeren. Gutes Beispiel auf Intelsat 68,5°E 12625/H. TS Header ist nicht 4 sondern 9 Bytes + * [DONE] Oder auf Intelsat 72,1°E 12718/H + * [DONE] Hängt möglicherweise mit Digital Devices BBFRAMES zusammen? + * Ja, tut es, deshalb auch BBFRAME Header parsen +* SeaweedFS als File-Storage implementieren +* In GSE und MPE Paketen: + * [DONE] DNS Responses parsen und daraus "lernen" + * [DONE] DNS Type 39 + * UDP L2TP & L2F Pakete parsen & reingucken. (z.B. in skyscraper_20240830_1254_0235E_11620_H_5931.ts) + * NTP parsen (z.B. in 12551_h_intelsat_like_internet.ts) + * [DONE] Von hier klauen: https://github.com/kapetan/dns + * In IP-in-IP Pakete reingucken. + * In IPv6 Encapsulation Pakete reingucken. + * In GRE Pakete reingucken. + * In RFC3652 Pakete (Type 253) reingucken. Ist in der Payload das Offset 6 = 0x45, so ist ein getunneltes IPv4 Paket. :) +* [DONE] ServiceListDescriptor im BatParser +* [DONE] ExtensionDescriptor 32, in DvbExtensionDescriptorConverter, Zeile 35 +* [DONE] CAID 0x0649 in CaSystemNames +* [DONE] DNS Record Type 48 [ {Z:\Freebies\Datasets\SkyscraperLibrarian\DVB-S\E0008_Ku_Thor\skyscraper_20230404_2125_0010W_11876_V_19999.ts}, {Z:\Freebies\Datasets\SkyscraperLibrarian\DVB-S August 2024\Telstar15\skyscraper_20240826_1530_0150W_12596_V_44999.ts} ] +* [DONE] RCS Maps parsen +* [DONE] RCS Maps in DB schreiben +* [DONE] NuGet Caching Proxy +* [DONE] DNS Record Type 49213 +* HLS Stream + * benötigt vorher einen PCR delayten Stream + * benötigt vorher einen Rate-Limited Stream +* ScraperStorage ablösen, und stattdessen DataStorage und FileStorage einzeln setzen. +* .tar/.tar.gz File mit FilesystemStorage Inhalt importieren +* WGS84 zu Lat/Lon konvertieren - für RCS-Map. + * Beispielcode in C# hier: https://stackoverflow.com/a/16660841 + * Benötigt vorher eine Ermittelung der UTM-Zone: + * Beispielcode zur Ermittelung der UTM-Zone (in R) hier: https://stackoverflow.com/a/68725547 +* [DONE] IP-Protokoll 139 (skyscraper_20230408_1727_0390E_12739_H_3926_gse.ts) +* [DONE] DNS Record Type 20366 (skyscraper_20230410_1450_0660E_12626_V_38329.ts) +* [DONE] DNS IN ( {Z:\Freebies\Datasets\SkyscraperLibrarian\DVB-S August 2024\Telstar15\skyscraper_20240826_1530_0150W_12596_V_44999.ts} ) +* ScraperStorage: Big File +* ScraperStorage: Big File mit Index +* [DONE] Für BigFiles: Gesplitteter Stream, für FAT32 und Freunde +* [DONE] Einen "Forget Blindscan Job" Button in der UI +* TransportProtocolDescriptor - unbekannte ProtocolIDs ohne Exception behandeln. (Vielleicht eine Warning?) \ No newline at end of file diff --git a/Documentation/TSDuck-Samples/sophnet-test/pat.xml b/Documentation/TSDuck-Samples/sophnet-test/pat.xml new file mode 100644 index 0000000..60fa14b --- /dev/null +++ b/Documentation/TSDuck-Samples/sophnet-test/pat.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Documentation/TSDuck-Samples/sophnet-test/pmt.xml b/Documentation/TSDuck-Samples/sophnet-test/pmt.xml new file mode 100644 index 0000000..f369404 --- /dev/null +++ b/Documentation/TSDuck-Samples/sophnet-test/pmt.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + 457574656C736174375F31323536375F765F53444142 + + + + + + + + + + 4173747261335F31323136385F765F53444142 + + + + diff --git a/Documentation/TSDuck-Samples/sophnet-test/sdt.xml b/Documentation/TSDuck-Samples/sophnet-test/sdt.xml new file mode 100644 index 0000000..6b029f0 --- /dev/null +++ b/Documentation/TSDuck-Samples/sophnet-test/sdt.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Documentation/TSDuck-Samples/sophnet-test/sophnet.bat b/Documentation/TSDuck-Samples/sophnet-test/sophnet.bat new file mode 100644 index 0000000..e8a61cd --- /dev/null +++ b/Documentation/TSDuck-Samples/sophnet-test/sophnet.bat @@ -0,0 +1,23 @@ +set BITRATE=224000000 +set PCR_PER_SEC=5 +set PCR_DISTANCE=2000 +set PCR_PID=5004 + +tsp -v --bitrate %BITRATE% --max-flushed-packets 70 ^ + -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 inject pat.xml --pid 0 --bitrate 2000 ^ + -P cat -c ^ + -P inject pmt.xml --pid 5000 --bitrate 2000 ^ + -P inject sdt.xml --pid 17 --bitrate 2000 ^ + -P inject tdt.xml --pid 0x14 --bitrate 2000 --stuffing ^ + -P timeref --system-synchronous ^ + -P mpeinject 6969 --pid 5003 --max-queue 8192 ^ + -P mpeinject 6970 --pid 5005 --max-queue 8192 ^ + -O file --max-size 1000000000 test.ts + +pause \ No newline at end of file diff --git a/Documentation/TSDuck-Samples/sophnet-test/tdt.xml b/Documentation/TSDuck-Samples/sophnet-test/tdt.xml new file mode 100644 index 0000000..7f831e4 --- /dev/null +++ b/Documentation/TSDuck-Samples/sophnet-test/tdt.xml @@ -0,0 +1,4 @@ + + + + diff --git a/Documentation/code_sample_cable_receive.txt b/Documentation/code_sample_cable_receive.txt new file mode 100644 index 0000000..15c8b6e --- /dev/null +++ b/Documentation/code_sample_cable_receive.txt @@ -0,0 +1,58 @@ +LibDvbV5Device device = new LibDvbV5Device(); +device.SetLog(8, null); +device.Find(); + +LibDvbV5DeviceList findDeviceByDeliverySystem = device.FindDeviceByDeliverySystem(LibDvbV5FrontendDeliverySystem.SYS_DVBC_ANNEX_A); +if (findDeviceByDeliverySystem == null) + return; + +LibDvbV5DeviceList demuxerName = device.GetSisterDevice(findDeviceByDeliverySystem, DvbDeviceType.Demux); +LibDvbV5DeviceList dvrName = device.GetSisterDevice(findDeviceByDeliverySystem, DvbDeviceType.Dvr); + +LibDvb5FrontendParameters frontend = findDeviceByDeliverySystem.ToFrontendParameters(); +frontend.SetDeliverySystem(LibDvbV5FrontendDeliverySystem.SYS_DVBC_ANNEX_A); +frontend.StoreParameter(17, (uint)LibDvbV5FrontendDeliverySystem.SYS_DVBC_ANNEX_A); +frontend.StoreParameter(3, 610000000); //freq +frontend.StoreParameter(8, 6900000); //symbol rate +frontend.SetParameters(); +Thread.Sleep(400); +frontend.GetStatistics(); +Thread.Sleep(400); +uint retrieveStats = frontend.RetrieveStats(512); +if ((retrieveStats & 0x10) != 0) +{ + Console.WriteLine("got lock!"); + LibDvbV5OpenDescriptor dvrFd = device.DeviceOpen(dvrName); + if (dvrFd == null) + throw new IOException("open failed"); + dvrFd.SetBufferSize(1024 * 1024); + + LibDvbV5OpenDescriptor demuxFd = device.DeviceOpen(demuxerName); + if (demuxFd == null) + throw new IOException("open failed"); + + demuxFd.DemuxSetPesFilter(0x2000, DemuxerPesType.DMX_PES_OTHER); + + FileStream fileStream = File.OpenWrite(String.Format("{0}.ts", DateTime.Now.Ticks)); + byte[] packBuffer = new byte[188]; + int readLen = 0; + for (int i = 0; i < 9001; i++) + { + readLen = dvrFd.Read(packBuffer, 188); + if (readLen > 0) + { + fileStream.Write(packBuffer, 0, 188); + } + } + + fileStream.Flush(true); + fileStream.Close(); + demuxFd.Dispose(); + dvrFd.Dispose(); +} +else +{ + Console.WriteLine("no lock :("); +} +frontend.Dispose(); +device.Dispose(); \ No newline at end of file diff --git a/Documentation/code_sample_sat_receive.txt b/Documentation/code_sample_sat_receive.txt new file mode 100644 index 0000000..2723f99 --- /dev/null +++ b/Documentation/code_sample_sat_receive.txt @@ -0,0 +1,58 @@ +int dvbSatSearchLnb = LibDvbV5.DvbSatSearchLnb("EXTENDED"); +LibDvbV5SatLnb lnb = new LibDvbV5SatLnb(dvbSatSearchLnb); + +LibDvbV5Device device = new LibDvbV5Device(); +device.SetLog(8, null); +device.Find(); +LibDvbV5DeviceList demuxerName = device.SeekBySystemName(0, 0, DvbDeviceType.Demux); +LibDvbV5DeviceList dvrName = device.SeekBySystemName(0, 0, DvbDeviceType.Dvr); +LibDvbV5DeviceList frontendName = device.SeekBySystemName(0, 0, DvbDeviceType.Frontend); + +LibDvb5FrontendParameters frontend = new LibDvb5FrontendParameters(0, 0); +frontend.SetLnb(lnb); +frontend.StoreParameter(3, 11362000); //freq +frontend.StoreParameter(8, 22000000); //symbol rate +frontend.StoreParameter(17, (uint)LibDvbV5FrontendDeliverySystem.SYS_DVBS2); +frontend.StoreParameter(256, (uint)LibDvbV5SatPolarization.Horizontal); +frontend.SetParameters(); +Thread.Sleep(400); +frontend.GetStatistics(); +Thread.Sleep(400); +uint retrieveStats = frontend.RetrieveStats(512); +if ((retrieveStats & 0x10) != 0) +{ + Console.WriteLine("got lock!"); + LibDvbV5OpenDescriptor dvrFd = device.DeviceOpen(dvrName); + if (dvrFd == null) + throw new IOException("open failed"); + dvrFd.SetBufferSize(1024 * 1024); + + LibDvbV5OpenDescriptor demuxFd = device.DeviceOpen(demuxerName); + if (demuxFd == null) + throw new IOException("open failed"); + + demuxFd.DemuxSetPesFilter(0x2000, DemuxerPesType.DMX_PES_OTHER); + + FileStream fileStream = File.OpenWrite(String.Format("{0}.ts", DateTime.Now.Ticks)); + byte[] packBuffer = new byte[188]; + int readLen = 0; + for (int i = 0; i < 9001; i++) + { + readLen = dvrFd.Read(packBuffer, 188); + if (readLen > 0) + { + fileStream.Write(packBuffer, 0, 188); + } + } + + fileStream.Flush(true); + fileStream.Close(); + demuxFd.Dispose(); + dvrFd.Dispose(); +} +else +{ + Console.WriteLine("no lock :("); +} +frontend.Dispose(); +device.Dispose(); \ No newline at end of file diff --git a/Documentation/sophia-net-signalling.md b/Documentation/sophia-net-signalling.md new file mode 100644 index 0000000..8377648 --- /dev/null +++ b/Documentation/sophia-net-signalling.md @@ -0,0 +1,35 @@ +# Sophia.net Signalling + +When either a Private Data Descriptor or a Registration Descriptor with the value "0x534f5048" is found in any table +the following Signalling applies: + +## Stream Types in PMT: + +|Stream Type|Interpretation | +|0x80 |TBS 6903-X GSE Packets | +|0x81 |AC-3 Audio | +|0x82 |PID only used for PCR | + +## Descriptors: + +Sophia.net employs the following descriptors: + +|Descriptor |Interpretation | +|0x80 |Encapsulated Transport Stream over MPE | + +### 0x80: Encapsulated Transport Stream over MPE +When the following conditions all apply: + - This Descriptor is encountered in the inner loop of a PMT + - The Stream Type is 0x0D (data broadcast) + - A data_broadcast_id_descriptor is present + - data_broadcast_id is 5 +THEN: This PID will contain an Encapsulated Transport Stream carried within MPE Packets. + +Data within this descriptor should be a human readable description of what the Encapsulated Transport Stream is, but can be left empty. + + +## Other Rules: + +Aside from that, the following rules shall apply: + +* The IRD MUST process any TDT/TOT tables, regardless of whether the Transport Stream ID and Network ID is known or not. \ No newline at end of file diff --git a/FactoryStorages/skyscraper5.Storage.MariaDbMinio/MariaDbMinioStorageFactory.cs b/FactoryStorages/skyscraper5.Storage.MariaDbMinio/MariaDbMinioStorageFactory.cs new file mode 100644 index 0000000..6ed5a3f --- /dev/null +++ b/FactoryStorages/skyscraper5.Storage.MariaDbMinio/MariaDbMinioStorageFactory.cs @@ -0,0 +1,56 @@ +using Minio; +using Minio.DataModel.Args; +using MySqlConnector; +using skyscraper5.Data; +using skyscraper5.Data.MySql; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.Storage.Split; + +namespace skyscraper5.Storage.MariaDbMinio +{ + [ScrapeStorageFactoryId(2,"MariaDB & MinIO",false)] + public class MariaDbMinioStorageFactory : IScraperStorageFactory + { + public IScraperStroage CreateScraperStroage() + { + MySqlConnectionStringBuilder mcsb = new MySqlConnectionStringBuilder(); + mcsb.Database = MariaDbDatabase; + mcsb.Password = MariaDbPassword; + mcsb.Port = MariaDbPort; + mcsb.Server = MariaDbHost; + mcsb.UserID = MariaDbUsername; + mcsb.ApplicationName = "skyscraper5"; + mcsb.Pooling = true; + + IMinioClient mc = new MinioClient() + .WithEndpoint(MinioEndpoint) + .WithCredentials(MinioAccessKey, MinioSecretKey) + .WithSSL(MinioSecure).Build(); + BucketExistsArgs bucketExistsArgs = new BucketExistsArgs().WithBucket(MinioBucket); + bool bucketExists = mc.BucketExistsAsync(bucketExistsArgs).Result; + if (!bucketExists) + { + MakeBucketArgs makeBucketArgs = new MakeBucketArgs().WithBucket(MinioBucket); + mc.MakeBucketAsync(makeBucketArgs).Wait(); + } + + MySqlDataStorage myds = new MySqlDataStorage(mcsb); + + MinioObjectStorage mos = new MinioObjectStorage(mc, MinioBucket); + + return new SplitScraperStorage(myds, mos); + } + + + public string MariaDbHost { get; set; } + public ushort MariaDbPort { get; set; } + public string MariaDbUsername { get; set; } + public string MariaDbPassword { get; set; } + public string MariaDbDatabase { get; set; } + public string MinioEndpoint { get; set; } + public string MinioAccessKey { get; set; } + public string MinioSecretKey { get; set; } + public bool MinioSecure { get; set; } + public string MinioBucket { get; set; } + } +} diff --git a/FactoryStorages/skyscraper5.Storage.MariaDbMinio/skyscraper5.Storage.MariaDbMinio.csproj b/FactoryStorages/skyscraper5.Storage.MariaDbMinio/skyscraper5.Storage.MariaDbMinio.csproj new file mode 100644 index 0000000..81002cb --- /dev/null +++ b/FactoryStorages/skyscraper5.Storage.MariaDbMinio/skyscraper5.Storage.MariaDbMinio.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + + + + + + + + diff --git a/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/PostgresqlMinioStorageFactory.cs b/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/PostgresqlMinioStorageFactory.cs new file mode 100644 index 0000000..e3462d2 --- /dev/null +++ b/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/PostgresqlMinioStorageFactory.cs @@ -0,0 +1,70 @@ +using System.Diagnostics; +using Minio; +using Minio.DataModel.Args; +using Npgsql; +using skyscraper5.Data; +using skyscraper5.Data.PostgreSql; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.Storage.Split; + +namespace skyscraper5.Storage.PostgresqlMinio +{ + [SkyscraperPlugin] + [ScrapeStorageFactoryId(3,"PostgreSQL & MinIO",false)] + public class PostgresqlMinioStorageFactory : IScraperStorageFactory + { + public IScraperStroage CreateScraperStroage() + { + NpgsqlConnectionStringBuilder ncsb = new NpgsqlConnectionStringBuilder(); + ncsb.Database = PostgreSqlDatabase; + ncsb.ApplicationName = "skyscraper5"; + ncsb.Host = PostgreSqlHost; + ncsb.Password = PostgreSqlPassword; + ncsb.Pooling = true; + ncsb.Port = PostgreSqlPort; + ncsb.Username = PostgreSqlUsername; + + if (Debugger.IsAttached) + ncsb.Timeout = 1024; + + IMinioClient mc = new MinioClient() + .WithEndpoint(MinioEndpoint) + .WithCredentials(MinioAccessKey, MinioSecretKey) + .WithSSL(MinioSecure).Build(); + BucketExistsArgs bucketExistsArgs = new BucketExistsArgs().WithBucket(MinioBucket); + bool bucketExists = mc.BucketExistsAsync(bucketExistsArgs).Result; + if (!bucketExists) + { + MakeBucketArgs makeBucketArgs = new MakeBucketArgs().WithBucket(MinioBucket); + mc.MakeBucketAsync(makeBucketArgs).Wait(); + } + + PostgresqlDataStore postgresDs = new PostgresqlDataStore(ncsb); + MinioObjectStorage minioOs = new MinioObjectStorage(mc, MinioBucket); + + + return new SplitScraperStorage(postgresDs, minioOs); + } + + public string MinioBucket { get; set; } + + public bool MinioSecure { get; set; } + + public string MinioSecretKey { get; set; } + + public string MinioAccessKey { get; set; } + + public string MinioEndpoint { get; set; } + + public string PostgreSqlUsername { get; set; } + + public ushort PostgreSqlPort { get; set; } + + public string PostgreSqlPassword { get; set; } + + public string PostgreSqlHost { get; set; } + + public string PostgreSqlDatabase { get; set; } + } +} diff --git a/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/skyscraper5.Storage.PostgresqlMinio.csproj b/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/skyscraper5.Storage.PostgresqlMinio.csproj new file mode 100644 index 0000000..99b349f --- /dev/null +++ b/FactoryStorages/skyscraper5.Storage.PostgresqlMinio/skyscraper5.Storage.PostgresqlMinio.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/GUIs/skyscraper5.UI/.gitignore b/GUIs/skyscraper5.UI/.gitignore new file mode 100644 index 0000000..fc7a1db --- /dev/null +++ b/GUIs/skyscraper5.UI/.gitignore @@ -0,0 +1,7 @@ +/.vs/ProjectEvaluation +/.vs/skyscraper5.UI/DesignTimeBuild +/.vs/skyscraper5.UI/FileContentIndex +/.vs/skyscraper5.UI/v17 +/bin/Debug/net8.0-windows +/obj/Debug/net8.0-windows +/obj diff --git a/GUIs/skyscraper5.UI/Form1.Designer.cs b/GUIs/skyscraper5.UI/Form1.Designer.cs new file mode 100644 index 0000000..c6cc8ac --- /dev/null +++ b/GUIs/skyscraper5.UI/Form1.Designer.cs @@ -0,0 +1,352 @@ +namespace skyscraper5.UI +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); + ListViewItem listViewItem1 = new ListViewItem(new string[] { "Source", "???" }, -1, Color.Empty, Color.FromArgb(192, 255, 192), null); + ListViewItem listViewItem2 = new ListViewItem(new string[] { "Run Time", "???" }, -1, Color.Empty, Color.FromArgb(128, 255, 128), null); + ListViewItem listViewItem3 = new ListViewItem(new string[] { "Source Status", "???" }, -1, Color.Empty, Color.FromArgb(192, 255, 192), null); + menuStrip1 = new MenuStrip(); + statusStrip1 = new StatusStrip(); + tabControl1 = new TabControl(); + tabPage1 = new TabPage(); + splitContainer1 = new SplitContainer(); + splitContainer2 = new SplitContainer(); + treeView1 = new TreeView(); + imageList1 = new ImageList(components); + bottomLeft = new ListView(); + columnHeader4 = new ColumnHeader(); + columnHeader5 = new ColumnHeader(); + splitContainer3 = new SplitContainer(); + textBox1 = new TextBox(); + tableLayoutPanel1 = new TableLayoutPanel(); + pidListView = new ListView(); + columnHeader1 = new ColumnHeader(); + columnHeader2 = new ColumnHeader(); + columnHeader3 = new ColumnHeader(); + columnHeader6 = new ColumnHeader(); + columnHeader7 = new ColumnHeader(); + bottomCenter = new TextBox(); + timer1 = new System.Windows.Forms.Timer(components); + tabControl1.SuspendLayout(); + tabPage1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); + splitContainer1.Panel1.SuspendLayout(); + splitContainer1.Panel2.SuspendLayout(); + splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer2).BeginInit(); + splitContainer2.Panel1.SuspendLayout(); + splitContainer2.Panel2.SuspendLayout(); + splitContainer2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer3).BeginInit(); + splitContainer3.Panel1.SuspendLayout(); + splitContainer3.Panel2.SuspendLayout(); + splitContainer3.SuspendLayout(); + tableLayoutPanel1.SuspendLayout(); + SuspendLayout(); + // + // menuStrip1 + // + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(832, 24); + menuStrip1.TabIndex = 0; + menuStrip1.Text = "menuStrip1"; + // + // statusStrip1 + // + statusStrip1.Location = new Point(0, 457); + statusStrip1.Name = "statusStrip1"; + statusStrip1.Size = new Size(832, 22); + statusStrip1.TabIndex = 1; + statusStrip1.Text = "statusStrip1"; + // + // tabControl1 + // + tabControl1.Controls.Add(tabPage1); + tabControl1.Dock = DockStyle.Fill; + tabControl1.Location = new Point(0, 24); + tabControl1.Name = "tabControl1"; + tabControl1.SelectedIndex = 0; + tabControl1.Size = new Size(832, 433); + tabControl1.TabIndex = 2; + // + // tabPage1 + // + tabPage1.Controls.Add(splitContainer1); + tabPage1.Location = new Point(4, 24); + tabPage1.Name = "tabPage1"; + tabPage1.Padding = new Padding(3); + tabPage1.Size = new Size(824, 405); + tabPage1.TabIndex = 0; + tabPage1.Text = "TS Structure"; + tabPage1.UseVisualStyleBackColor = true; + // + // splitContainer1 + // + splitContainer1.Dock = DockStyle.Fill; + splitContainer1.Location = new Point(3, 3); + splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + splitContainer1.Panel1.Controls.Add(splitContainer2); + // + // splitContainer1.Panel2 + // + splitContainer1.Panel2.Controls.Add(splitContainer3); + splitContainer1.Size = new Size(818, 399); + splitContainer1.SplitterDistance = 272; + splitContainer1.TabIndex = 0; + // + // splitContainer2 + // + splitContainer2.Dock = DockStyle.Fill; + splitContainer2.Location = new Point(0, 0); + splitContainer2.Name = "splitContainer2"; + splitContainer2.Orientation = Orientation.Horizontal; + // + // splitContainer2.Panel1 + // + splitContainer2.Panel1.Controls.Add(treeView1); + // + // splitContainer2.Panel2 + // + splitContainer2.Panel2.Controls.Add(bottomLeft); + splitContainer2.Size = new Size(272, 399); + splitContainer2.SplitterDistance = 265; + splitContainer2.TabIndex = 0; + // + // treeView1 + // + treeView1.Dock = DockStyle.Fill; + treeView1.ImageIndex = 0; + treeView1.ImageList = imageList1; + treeView1.Location = new Point(0, 0); + treeView1.Name = "treeView1"; + treeView1.SelectedImageIndex = 0; + treeView1.Size = new Size(272, 265); + treeView1.TabIndex = 0; + treeView1.AfterSelect += treeView1_AfterSelect_1; + // + // imageList1 + // + imageList1.ColorDepth = ColorDepth.Depth32Bit; + imageList1.ImageStream = (ImageListStreamer)resources.GetObject("imageList1.ImageStream"); + imageList1.TransparentColor = Color.Transparent; + imageList1.Images.SetKeyName(0, "???"); + imageList1.Images.SetKeyName(1, "Table"); + imageList1.Images.SetKeyName(2, "services.ico"); + imageList1.Images.SetKeyName(3, "Control_Timer.bmp"); + imageList1.Images.SetKeyName(4, "database.bmp"); + imageList1.Images.SetKeyName(5, "Calendar_scheduleHS.png"); + imageList1.Images.SetKeyName(6, "PrimaryKeyHS.png"); + imageList1.Images.SetKeyName(7, "InsertHyperlinkHS.png"); + imageList1.Images.SetKeyName(8, "gear_32.bmp"); + // + // bottomLeft + // + bottomLeft.Columns.AddRange(new ColumnHeader[] { columnHeader4, columnHeader5 }); + bottomLeft.Dock = DockStyle.Fill; + bottomLeft.Items.AddRange(new ListViewItem[] { listViewItem1, listViewItem2, listViewItem3 }); + bottomLeft.Location = new Point(0, 0); + bottomLeft.Name = "bottomLeft"; + bottomLeft.Size = new Size(272, 130); + bottomLeft.TabIndex = 0; + bottomLeft.UseCompatibleStateImageBehavior = false; + bottomLeft.View = View.Details; + // + // columnHeader4 + // + columnHeader4.Text = "K"; + columnHeader4.Width = 80; + // + // columnHeader5 + // + columnHeader5.Text = "V"; + // + // splitContainer3 + // + splitContainer3.Dock = DockStyle.Fill; + splitContainer3.Location = new Point(0, 0); + splitContainer3.Name = "splitContainer3"; + splitContainer3.Orientation = Orientation.Horizontal; + // + // splitContainer3.Panel1 + // + splitContainer3.Panel1.Controls.Add(textBox1); + // + // splitContainer3.Panel2 + // + splitContainer3.Panel2.Controls.Add(tableLayoutPanel1); + splitContainer3.Size = new Size(542, 399); + splitContainer3.SplitterDistance = 118; + splitContainer3.TabIndex = 0; + // + // textBox1 + // + textBox1.Dock = DockStyle.Fill; + textBox1.Location = new Point(0, 0); + textBox1.Multiline = true; + textBox1.Name = "textBox1"; + textBox1.ReadOnly = true; + textBox1.ScrollBars = ScrollBars.Vertical; + textBox1.Size = new Size(542, 118); + textBox1.TabIndex = 0; + // + // tableLayoutPanel1 + // + tableLayoutPanel1.ColumnCount = 1; + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.Controls.Add(pidListView, 0, 0); + tableLayoutPanel1.Controls.Add(bottomCenter, 0, 1); + tableLayoutPanel1.Dock = DockStyle.Fill; + tableLayoutPanel1.Location = new Point(0, 0); + tableLayoutPanel1.Name = "tableLayoutPanel1"; + tableLayoutPanel1.RowCount = 2; + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 120F)); + tableLayoutPanel1.Size = new Size(542, 277); + tableLayoutPanel1.TabIndex = 1; + // + // pidListView + // + pidListView.Columns.AddRange(new ColumnHeader[] { columnHeader1, columnHeader2, columnHeader3, columnHeader6, columnHeader7 }); + pidListView.Dock = DockStyle.Fill; + pidListView.Location = new Point(3, 3); + pidListView.Name = "pidListView"; + pidListView.Size = new Size(536, 151); + pidListView.SmallImageList = imageList1; + pidListView.TabIndex = 0; + pidListView.UseCompatibleStateImageBehavior = false; + pidListView.View = View.Details; + pidListView.ColumnClick += pidListView_ColumnClick; + // + // columnHeader1 + // + columnHeader1.Text = "PID"; + // + // columnHeader2 + // + columnHeader2.Text = "Packets"; + // + // columnHeader3 + // + columnHeader3.Text = "Usage"; + // + // columnHeader6 + // + columnHeader6.Text = "Program"; + columnHeader6.Width = 100; + // + // columnHeader7 + // + columnHeader7.Text = "Usage"; + // + // bottomCenter + // + bottomCenter.BackColor = Color.Black; + bottomCenter.Dock = DockStyle.Fill; + bottomCenter.ForeColor = Color.Lime; + bottomCenter.Location = new Point(3, 160); + bottomCenter.Multiline = true; + bottomCenter.Name = "bottomCenter"; + bottomCenter.ReadOnly = true; + bottomCenter.Size = new Size(536, 114); + bottomCenter.TabIndex = 1; + bottomCenter.Tag = ""; + bottomCenter.Text = resources.GetString("bottomCenter.Text"); + // + // timer1 + // + timer1.Enabled = true; + timer1.Interval = 1000; + timer1.Tick += timer1_Tick; + // + // Form1 + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(832, 479); + Controls.Add(tabControl1); + Controls.Add(statusStrip1); + Controls.Add(menuStrip1); + MainMenuStrip = menuStrip1; + Name = "Form1"; + Text = "Form1"; + FormClosed += Form1_FormClosed; + Load += Form1_Load; + tabControl1.ResumeLayout(false); + tabPage1.ResumeLayout(false); + splitContainer1.Panel1.ResumeLayout(false); + splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); + splitContainer1.ResumeLayout(false); + splitContainer2.Panel1.ResumeLayout(false); + splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer2).EndInit(); + splitContainer2.ResumeLayout(false); + splitContainer3.Panel1.ResumeLayout(false); + splitContainer3.Panel1.PerformLayout(); + splitContainer3.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer3).EndInit(); + splitContainer3.ResumeLayout(false); + tableLayoutPanel1.ResumeLayout(false); + tableLayoutPanel1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private MenuStrip menuStrip1; + private StatusStrip statusStrip1; + private TabControl tabControl1; + private TabPage tabPage1; + private SplitContainer splitContainer1; + private SplitContainer splitContainer2; + private TreeView treeView1; + private SplitContainer splitContainer3; + private TextBox textBox1; + private ListView pidListView; + private ColumnHeader columnHeader1; + private ColumnHeader columnHeader2; + private ColumnHeader columnHeader3; + private ListView bottomLeft; + private ColumnHeader columnHeader4; + private ColumnHeader columnHeader5; + private System.Windows.Forms.Timer timer1; + private TableLayoutPanel tableLayoutPanel1; + private TextBox bottomCenter; + private ColumnHeader columnHeader6; + private ColumnHeader columnHeader7; + private ImageList imageList1; + } +} diff --git a/GUIs/skyscraper5.UI/Form1.cs b/GUIs/skyscraper5.UI/Form1.cs new file mode 100644 index 0000000..15ed172 --- /dev/null +++ b/GUIs/skyscraper5.UI/Form1.cs @@ -0,0 +1,392 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.UI.Overrides; +using skyscraper5.UI.StreamAcquisition; + +namespace skyscraper5.UI +{ + public partial class Form1 : Form, INodeEngine + { + public Form1() + { + InitializeComponent(); + } + + public Form1(StreamSource acquiredStream) + : this() + { + AcquiredStream = acquiredStream; + tsContext = new TsContext(); + tsContext.OnPacketLoss += TsContext_OnPacketLoss; + skyscraperContext = new SkyscraperContext(tsContext, eventLogger, storage); + skyscraperContext.UiJunction = uiJunction; + } + + private void TsContext_OnPacketLoss(uint pid, uint oldContinuity, uint newContinuity, long packetLossEvents) + { + continuityErrors = packetLossEvents; + } + + public StreamSource AcquiredStream { get; } + private TsContext tsContext; + private SkyscraperContext skyscraperContext; + private ISkyscraperEventLogger eventLogger; + private IScraperStroage storage; + private Form1UiJunction uiJunction; + private DateTime windowOpenedTimestamp; + private TimeSpan windowOpenDuration; + private long continuityErrors; + private TeiCounter teiCounterFilter; + private PmtCounter pmtCounter; + private PacketsPerSecondCounter packetsPerSecondCounter; + private ScrambleCounter scrambleCounter; + + + private void Form1_Load(object sender, EventArgs e) + { + scrambleCounter = new ScrambleCounter(); + packetsPerSecondCounter = new PacketsPerSecondCounter(); + teiCounterFilter = new TeiCounter(); + skyscraperContext.InitalizeFilterChain(teiCounterFilter, packetsPerSecondCounter, scrambleCounter); + + uiJunction = new Form1UiJunction(this); + skyscraperContext.UiJunction = uiJunction; + + + windowOpenedTimestamp = DateTime.Now; + windowOpenDuration = new TimeSpan(0, 0, 0); + bottomLeft.Items[0].SubItems[1].Text = AcquiredStream.GetSourceName(); + + skyscraperContext.PatDecoder.OnValidSection += PsiDecoder_OnValidSection; + skyscraperContext.PatDecoder.OnCrcError += PsiDecoder_OnCrcError; + pmtCounter = new PmtCounter(); + skyscraperContext.PmtDecoderTransformer = pmtCounter; + skyscraperContext.CatDecoder.OnValidSection += PsiDecoder_OnValidSection; + skyscraperContext.CatDecoder.OnCrcError += PsiDecoder_OnCrcError; + skyscraperContext.NitDecoder.OnValidSection += PsiDecoder_OnValidSection; + skyscraperContext.NitDecoder.OnCrcError += PsiDecoder_OnCrcError; + skyscraperContext.Pid0x11Decoder.OnValidSection += PsiDecoder_OnValidSection; + skyscraperContext.Pid0x11Decoder.OnCrcError += PsiDecoder_OnCrcError; + skyscraperContext.Pid0x12Decoder.OnValidSection += PsiDecoder_OnValidSection; + skyscraperContext.Pid0x12Decoder.OnCrcError += PsiDecoder_OnCrcError; + DrawBottomCenter(); + + pidListView_ColumnClick(this, new ColumnClickEventArgs(0)); + + AcquiredStream.StartSource(skyscraperContext); + } + + private long patErrors, catErrors, nitErrors, sdtErrors, batErrors, eitErrors; + private void PsiDecoder_OnCrcError(PsiSection section, int pid, long available) + { + switch (section.TableId) + { + case 0: patErrors++; break; + case 1: catErrors++; break; + case 0x40: nitErrors++; break; + case 0x41: nitErrors++; break; + case 0x42: sdtErrors++; break; + case 0x46: sdtErrors++; break; + case 0x4a: batErrors++; break; + default: + if (section.TableId >= 0x4e && section.TableId <= 0x6f) + { + eitErrors++; + break; + } + throw new NotImplementedException(); + } + } + + private long patValids, catValids, nitValids, sdtValids, batValids, eitValids; + private void PsiDecoder_OnValidSection(PsiSection section, int pid, long available) + { + switch (section.TableId) + { + case 0: patValids++; break; + case 1: catValids++; break; + case 0x40: nitValids++; break; + case 0x41: nitValids++; break; + case 0x42: sdtValids++; break; + case 0x46: sdtValids++; break; + case 0x4a: batValids++; break; + default: + if (section.TableId >= 0x4e && section.TableId <= 0x6f) + { + eitValids++; + break; + } + throw new NotImplementedException(); + } + } + + private void Form1_FormClosed(object sender, FormClosedEventArgs e) + { + AcquiredStream.StopSource(); + } + + private void timer1_Tick(object sender, EventArgs e) + { + windowOpenDuration = windowOpenDuration.Add(new TimeSpan(0, 0, 1)); + bottomLeft.Items[1].SubItems[1].Text = windowOpenDuration.ToString(); + DrawBottomCenter(); + RenderPidList(); + bottomLeft.Items[2].SubItems[1].Text = AcquiredStream.State.ToString(); + } + + private string bottomCenterTemplate; + private void DrawBottomCenter() + { + if (string.IsNullOrWhiteSpace(bottomCenterTemplate)) + { + bottomCenterTemplate = bottomCenter.Text; + } + + long patSections = patValids; + long pmtSections = pmtCounter.ValidSection; ; + long catSections = catValids; + long nitSections = nitValids; + long sdtSections = sdtValids; + long eitSections = eitValids; + long patCrcErrors = patErrors; + long pmtCrcErrors = pmtCounter.CrcErrors; + long catCrcErrors = catErrors; + long nitCrcErrors = nitErrors; + long sdtCrcErrors = sdtErrors; + long eitCrcErrors = eitErrors; + long continuityErrors = this.continuityErrors; + string muxBitrate = "???"; + long teiErrors = this.teiCounterFilter.TransportErrors; + string lastSec = RenderPacketsPerSecond(); + long syncLosses = 0; + long inBuffer = 0; + long outBuffer = 0; + + bottomCenter.Text = String.Format(bottomCenterTemplate, patSections, pmtSections, catSections, nitSections, sdtSections, eitSections, patCrcErrors, + pmtCrcErrors, catCrcErrors, nitCrcErrors, sdtCrcErrors, eitCrcErrors, continuityErrors, muxBitrate, teiErrors, lastSec, syncLosses, inBuffer, outBuffer); + } + + private char[] dataUnits = { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' }; + private string RenderPacketsPerSecond() + { + if (!packetsPerSecondCounter.Ready) + { + return "???"; + } + + long packetsPerSecond = packetsPerSecondCounter.PacketsPerSecond; + long bytesPerSecond = packetsPerSecond * 188; + long bitsPerSecond = bytesPerSecond * 8; + if (bitsPerSecond < 1000) + { + return String.Format("{0} bits/s", bytesPerSecond); + } + + int unitOffset = -1; + while (bitsPerSecond > 1000) + { + bitsPerSecond /= 1000; + unitOffset++; + } + return String.Format("{0} {1}bits/s", bitsPerSecond, dataUnits[unitOffset]); + } + + private PidListViewItem[] listViewItems; + private Color scrambledColor = Color.FromArgb(255, 255, 128, 128); + private Color unscrambledColor = Color.FromArgb(255, 128, 255, 128); + private void RenderPidList() + { + if (listViewItems == null) + listViewItems = new PidListViewItem[0x2000]; + + ulong[] ulongs = tsContext.GetPidStatistics(); + for (int i = 0; i < 0x2000; i++) + { + if (ulongs[i] != 0) + { + if (listViewItems[i] == null) + { + listViewItems[i] = new PidListViewItem(i); + pidListView.Items.Add(listViewItems[i]); + } + listViewItems[i].SetPackageStats(ulongs[i], packetsPerSecondCounter.TotalPacketsOverall); + listViewItems[i].BackColor = scrambleCounter.IsScrambled(i) ? scrambledColor : unscrambledColor; + } + + if (uiJunction.PidToPrograms != null && uiJunction.SdtNames != null) + { + if (uiJunction.PidToPrograms.ContainsKey(i)) + { + uint program = uiJunction.PidToPrograms[i]; + if (uiJunction.SdtNames.ContainsKey(program)) + { + listViewItems[i].ProgramName = uiJunction.SdtNames[program]; + } + } + } + + if (uiJunction.UsageLabels != null) + { + if (uiJunction.UsageLabels.ContainsKey(i)) + { + if (listViewItems[i] != null) + { + listViewItems[i].UsageLabel = uiJunction.UsageLabels[i]; + } + } + } + } + } + + private PidListViewItemSorter lvwColumnSorter; + private void pidListView_ColumnClick(object sender, ColumnClickEventArgs e) + { + if (lvwColumnSorter == null) + { + lvwColumnSorter = new PidListViewItemSorter(); + pidListView.ListViewItemSorter = lvwColumnSorter; + } + + if (e.Column == lvwColumnSorter.SortColumn) + { + if (lvwColumnSorter.Order == SortOrder.Ascending) + { + lvwColumnSorter.Order = SortOrder.Descending; + } + else + { + lvwColumnSorter.Order = SortOrder.Ascending; + } + } + else + { + // Set the column number that is to be sorted; default to ascending. + lvwColumnSorter.SortColumn = e.Column; + lvwColumnSorter.Order = SortOrder.Ascending; + } + + pidListView.Sort(); + } + + SkyscraperUiNode INodeEngine.EnsureNodeExists(params string[] path) + { + SkyscraperUiNode result = null; + if (IsDisposed) + return null; + Invoke(new Action(() => { result = EnsureNodeExistsEx(path); })); + return result; + } + + private SkyscraperUiNode EnsureNodeExistsEx(params string[] path) + { + TreeNodeCollection rootNodes = treeView1.Nodes; + + SkyscraperUiNode parent = null; + for (int i = 0; i < path.Length; i++) + { + SkyscraperUiNode foundNode = null; + if (i == 0) + { + foreach (TreeNode rootNode in rootNodes) + { + if (rootNode.Text.Equals(path[i])) + { + foundNode = (SkyscraperUiNode)rootNode; + break; + } + } + if (foundNode == null) + { + foundNode = new SkyscraperUiNode(); + foundNode.Text = path[i]; + rootNodes.Add(foundNode); + } + } + else + { + foreach (TreeNode childNode in parent.Nodes) + { + if (childNode.Text.Equals(path[i])) + { + foundNode = (SkyscraperUiNode)childNode; + break; + } + } + if (foundNode == null) + { + foundNode = new SkyscraperUiNode(); + foundNode.Text = path[i]; + parent.Nodes.Add(foundNode); + } + } + parent = foundNode; + } + return parent; + } + + public void RunOnUiThread(Action action) + { + Invoke(action); + } + + private void treeView1_AfterSelect_1(object sender, TreeViewEventArgs e) + { + SkyscraperUiNode node = e.Node as SkyscraperUiNode; + if (node == null) + return; + textBox1.Text = node.DetailedText; + } + + public bool TestForNode(params string[] path) + { + TreeNodeCollection rootNodes = treeView1.Nodes; + + SkyscraperUiNode parent = null; + for (int i = 0; i < path.Length; i++) + { + SkyscraperUiNode foundNode = null; + if (i == 0) + { + foreach (TreeNode rootNode in rootNodes) + { + if (rootNode.Text.Equals(path[i])) + { + foundNode = (SkyscraperUiNode)rootNode; + break; + } + } + if (foundNode == null) + { + return false; + } + } + else + { + foreach (TreeNode childNode in parent.Nodes) + { + if (childNode.Text.Equals(path[i])) + { + foundNode = (SkyscraperUiNode)childNode; + break; + } + } + if (foundNode == null) + { + return false; + } + } + parent = foundNode; + } + return true; + } + + void INodeEngine.SetIcon(SkyscraperUiNode node, int index) + { + RunOnUiThread(() => { + node.ImageIndex = index; + node.SelectedImageIndex = index; + }); + } + } +} diff --git a/GUIs/skyscraper5.UI/Form1.resx b/GUIs/skyscraper5.UI/Form1.resx new file mode 100644 index 0000000..a1c4ea2 --- /dev/null +++ b/GUIs/skyscraper5.UI/Form1.resx @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 132, 17 + + + 335, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs + LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu + SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAZDIAAAJNU0Z0AUkBTAIBAQkB + AAFIAQABSAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAATADAAEBAQABIAYAATAS + AAH/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8B + AAP/AQAC/8AAAf8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAAv8BtAKiAf8BcwI4Af8BdgE7ATwB/wFgAS4B + LwL/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAL/wAAB/wEAA/8BAAP/AQAC/wGMAWMBZAH/AXoCRAH/AY8B + QgFDAf8BjgJ6Af8BjgJLAf8BxAFhAWAB/wFgAi0C/wEAAv8BtQJgAf8BbgE0ATUB/wHLAqsC/wEAA/8B + AAL/wAAB/wEAAv8B7AHaAdsB/wGdAWcBaAH/AcEBdgF3Af8ByQJ7Af8BZAIuAf8BbQE1ATYB/wG7AWYB + ZwH/AcgBZwFmAf8BkAJFAf8BVgErASwB/wFzATgBOQH/AagCUQH/AXYBOAE5Af8BugKVAv8BAAL/wAAB + /wEAAv8B4AG/AcAB/wGmAl4B/wHkApMB/wHgAowB/wHLAnwB/wHFAXUBdAH/AdECdQH/AcwCbgH/AcgC + ZwH/AaMCUwH/AbQBWAFXAf8BwQFdAVwB/wG6AVoBWQH/AXABOAE5Av8BAAL/wAAB/wEAAv8B5gHLAcwB + /wHKAo0B/wG8AnUB/wHlApMB/wHgAowB/wHcAoUB/wHWAn0B/wHRAXYBdQH/AcwBbgFtAf8ByAFnAWYB + /wHDAmEB/wG+AVwBWwH/AXUBOgE7Af8B3ALDAv8BAAL/wAAB/wEAA/8BAAL/AaoBjgGPAf8BswFqAWwB + /wHlAZcBmAH/AeUCkwH/AcIBdAF1Af8BpAFgAWEB/wGgAVwBXQH/AbkCaAH/AcwBbgFtAf8BxwFnAWYB + /wGlAlMB/wFkATABMQL/AQAD/wEAAv/AAAHYAckBygH/AbcBWgFbAf8BrAFZAVoB/wG0AVwBXQH/Ae8B + ogGjAf8BwQF4AXkB/wGHAlEB/wHoAs0B/wHbAbcBuAH/Ab4BhQGGAf8BuAJnAf8BzAFuAW0B/wHIAWcB + ZgH/AY0COwH/AXABOAE5Af8BbgE2ATcB/8AAAcYCsQH/Ad0BhQGGAf8B/AG2AbgB/wH5AbEBsgH/AfQB + qgGrAf8BfQI9Af8BsQFxAXAB/wHNAa0BrwH/AeYB1AHVAf8BsQF2AXUB/wGbAVcBWAH/AdECdQH/AcwB + bgFtAf8BxwFnAWYB/wHEAmEB/wF9AT0BPgH/wAABxQGwAbEB/wHeAoYB/wH9AcABwQH/AfsBtQG2Af8B + +AGxAbIB/wGQAkkB/wHaAZMBlAH/AYIBRAFFAf8BjgFVAVYB/wHCAX4BfwH/AZkBVwFYAf8B1gF+AX0B + /wHRAXYBdQH/AcUBaQFqAf8BngFRAVIB/wGAAUABQQH/wAAB/wEAAv8B5QGZAZoB/wHiAaMBpAH/AeAB + lQGWAf8B+gG0AbYB/wHbAZIBkwH/AakBbgFvAf8B5wGkAaUB/wHkAp8B/wGwAnkB/wHAAXMBdAH/AdsB + hQGEAf8BzgJ4Af8BowJdAf8BrgJ4Af8BpgJyAf/AAAH/AQAD/wEAA/8BAAL/AbsBYAFhAf8B+wG2AbcB + /wH9AbcBuAH/AdcBjgGPAf8BiwFIAUkB/wGIAUcBSAH/AdEBhgGHAf8B5AKTAf8B4AKMAf8BwwJ1Af8B + ZQEzATQC/wEAA/8BAAL/wAAB/wEAA/8BAAL/AbwCUQH/AfgBsgGzAv8BuwG8Af8B/QG5AboB/wH8AbcB + uAH/AfkBsQGyAf8B9AGqAasB/wHuAqMB/wHoApkB/wHlAZMBlAH/AeACjAH/Ab4BcQFyAf8BegJIAv8B + AAL/wAAB/wEAA/8BAAL/AeUBlAGTAf8B+gG1AbYB/wH+AbkBugH/AcoCegH/AcMBdAF1Af8B/AG2AbgB + /wH5AbEBsgH/AcABegF7Af8BogFiAWMB/wG4AXMBdAH/AeUCkwH/AcMCeAH/AaUBZQFmAv8BAAL/wAAB + /wEAA/8BAAP/AQAC/wHoAqIB/wHeAY8BkAL/AQAC/wHPAYoBjQL/AbsBvAH/AfwBtgG3Af8BoAFXAVoC + /wEAAv8B2gGvAbAB/wGpAWkBaAH/AaoBaQFqAv8BAAP/AQAC/8AAAf8BAAP/AQAD/wEAA/8BAAP/AQAD + /wEAAv8BzgGIAYkB/wHXAZABkQH/Ac0BgQGCAf8BzQF7AXwC/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAC + /8AAAf8BAAP/AQAD/wEAAv8B4gHvAfQB/wGoAckB1wH/AXwBpAG2Af8BXwGGAZwB/wFKAXEBigH/AUUB + bAGHAf8BRQF2AZUB/wFYAY4BqwH/AY8BtwHNAf8B3QHqAfEC/wEAA/8BAAP/AQAC/4gAATICMQFNAVkB + VAFQAaEBagFYAUkB4wFhAU8BPgHjAVIBTgFKAaEDMQFNBAABMgIxAU0BWQFUAVABoQFqAVgBSQHjAWEB + TwE+AeMBUgFOAUoBoQMxAU0EAAH/AQAD/wEAAv8BrQHPAd8B/wFXAZEBrQH/AVwBowG/Af8BeAHAAdQB + /wGVAdkB5gH/AYEB2wHwAf8BUAHGAewB/wEkAaoB3QH/ARYBkgHHAf8BIgGAAawB/wE5AXEBkgH/AZ0B + vwHRAv8BAAP/AQAC/wG3AaIBkwH/AWMBSQE1Af8BYwFJATUB/wFjAUkBNQH/AWUBSwE4Af8BYwFJATUB + /wFjAUkBNQH/AWMBSQE1Af8BYwFJATUB/wFjAUkBNQH/AWMBSQE1Af8BYwFJATUB/wFjAUkBNQH/AWMB + SQE1Af8BYwFJATUB/wFjAUkBNQH/RAADNgFVAZMBggF3AecByAGyAaQB/wHjAc8BwgH/Ad0BxAG1Af8B + rQGRAX0B/wFuAVYBSAHnAVEBTgFMAY4BkwGCAXcB5wHIAbIBpAH/AeMBzwHCAf8B3QHEAbUB/wGtAZEB + fQH/AW4BVgFIAecCNQE0AVUB/wEAA/8BAAL/AVoBhAGbAf8BRAG9AecB/wFnAdMB8QH/AYwB4wH0Af8B + ogHrAfYB/wGDAd8B9AH/AVABygHwAf8BHgG2Ae0B/wECAacB6AH/AQEBoQHhAf8BCQGWAdIB/wE9AV4B + egL/AQAD/wEAAv8BvAGnAZgB/wHXAbcBnwH/AcQBngGEAf8BsgGLAW8B/wHaAboBowH/AckBqAGPAf8B + tAGPAXQB/wHZAbkBoQH/AcQBnwGEAf8BsgGLAXAB/wHXAbUBnQH/AcMBngGDAf8BsgGLAW8B/wHRAaoB + jwH/Ab8BmAF7Af8BYwFJATUB/xgAAysBQQEqAUUBVQHtAy8BSSAAAWsBZQFgAbIB3gHPAcQB/wFvAWYB + YQHCAXQBZQFbAdQBhgF5AW4B1AFOASwBFwH7AUcBJgEMAf8BRwEmAQwB/wFHASYBDAH/AWIBTwFBAeYB + dAFlAVsB1AGGAXkBbgHUAXwBcgFtAcIBtAGYAYQB/wFXAVABSQGyAf8BAAP/AQAC/wFdAYcBngH/AUMB + xAHwAf8BaQHUAfEB/wGMAeMB9AH/AaIB6wH2Af8BgwHfAfQB/wFQAcoB8AH/AR4BtgHtAf8BAgGoAegB + /wEBAaEB4QH/AQMBnAHaAf8BPgFfAXwC/wEAA/8BAAL/Ab4BqAGaAf8D/gH/A/4B/wHAAZkBfAH/Af0B + +gH4Af8B/QH4AfUB/wHBAZsBfQH/AfsB8gHsAf8B+wHwAekB/wG/AZcBeQH/AfoB6QHdAf8B+wHqAeAB + /wHBAZwBfwH/AfkB3wHPAf8B1AG9Aa4B/wFjAUkBNQH/GAABDQFEAWQB/gE0Aa8B1QH3ATgBRAFLAd8D + MwFSHAABpgGQAX0B9AL/Af0B/wFSAT4BMAHfAx4BKgGLAX8BdwHNAv8B/gL/AfUB7wH/AfkB6AHeAf8B + 8QHbAc0B/wHrAdABwQH/AUsBSAFGAY0DHgEqAY8BfwFzAd8B5wHPAcEB/wFkAUkBNQH0Af8BAAP/AQAC + /wFfAYkBoAH/AUMBxAHvAf8BaAHUAfEB/wGNAeMB9AH/AaIB6wH2Af8BggHfAfMB/wFRAcoB8AH/AR4B + tgHtAf8BAgGoAegB/wECAaEB4QH/AQMBnAHaAf8BQAFiAX4C/wEAA/8BAAL/AcABqwGcAf8D/gH/A/4B + /wHOAaUBiAH/Af4C/AH/Af4B+gH4Af8BzQGjAYUB/wH9AfQB8AH/AfsB8QHqAf8BzQGjAYUB/wH7AeoB + 4AH/AfoB6AHdAf8BzQGjAYUB/wH5AeEB0QH/AdMBvAGvAf8BYwFJATUB/xgAARIBUQFxAf8BWwHbAfcB + /QE5AY8BpQHxATcBPwFGAeAcAAFsAWYBYQGyAeoB3gHWAf8BfQFwAWYB3QFVAToBJgHzAWQBSgExAfsB + tAGZAYYB/QHMAbABngH/AcEBpAGQAfwB0wG6AaoB/wGzAZ0BjQHzAVkBSwE9AdYBZAFUAUgB1AF0AWsB + ZQHCAcQBrAGdAf8BXAFWAVABsgH/AQAD/wEAAv8BYQGMAaIB/wFFAcYB8AH/AWoB1AHxAf8BjAHjAfQB + /wGiAesB9gH/AYIB3wH0Af8BUQHKAfAB/wEeAbYB7QH/AQIBqAHoAf8BAQGiAeIB/wEEAZsB2wH/AUIB + ZQGAAv8BAAP/AQAC/wHCAa4BngH/AdQBrwGVAf8BwQGaAX4B/wGyAYsBbwH/Ac0BowGFAf8BvwGXAXoB + /wGyAYsBbwH/Ac0BowGFAf8BvwGXAXoB/wEMAUAB5wH/AQoBOwHaAf8BCAE0AccB/wEFAS0BtAH/Ac0B + owGFAf8BvwGXAXoB/wFjAUkBNQH/GAABFwFeAXwB/wFXAeAC/wEOATEBSAH8AygBOxwAAUoBSAFHAXsB + twGgAZUB+AHkAdkB0AH/Af4B9gH0Af8B+QHvAegB/wHRAb4BsQH/AZ0BfwFsAf8BfwFnAUwB/wG/AacB + lwH/AeQB2QHQAf8B/gH2AfQB/wH5Ae8B6AH/AdEBvgGxAf8BiwF4AW4B5wE2AjUBVQH/AQAD/wEAAv8B + ZAGPAaUB/wFGAcYB8AH/AWoB1AHyAf8BjQHjAfQB/wGiAesB9gH/AYMB3wH0Af8BUAHKAfAB/wEeAbYB + 7QH/AQEBpwHoAf8BAQGhAeIB/wEDAZwB2wH/AUQBaAGDAv8BAAP/AQAC/wHFAa8BoAH/A/4B/wP+Af8B + vwGXAXkB/wP+Af8D/gH/Ab8BlwF5Af8B/QH6AfgB/wH9AfcB9AH/AS8BYAH2Af8B/QH3AfQB/wH9AfQB + 8AH/AQgBNAHHAf8B+gHmAdsB/wHUAcEBtQH/AWMBSQE1Af8YAAEeAWsBiwH/AW4B5wL/AUIBpQG9AfQB + RgFNAVMBzBgAAxYBHQFjAV8BWwGwAdYBiwFkAf8BuQGNAWsB/wGnAZYBeQH/AZ4BjwF1Af8BjgF6AV0B + /wE3AYgBIgH/AWkBaAE3Af8BjQFmAU0B/wGGAXQBaQHiAZcBhQF5AeYBkQF+AXMB4wFfAVoBVwGhAjIB + MQFNBAAB/wEAA/8BAAL/AWUBkgGoAf8BRgHFAfAB/wFrAdQB8gH/AY0B4wH0Af8BogHrAfYB/wGCAd8B + 9AH/AVABygHwAf8BHgG2Ae0B/wEBAacB6AH/AQEBoQHhAf8BAwGcAdsB/wFGAWsBhgL/AQAD/wEAAv8B + xwGxAaIB/wP+Af8D/gH/Ac0BowGFAf8D/gH/A/4B/wHNAaMBhQH/Af4C/AH/Af0B+gH4Af8BWwGCAfsB + /wH+AvwB/wH9AfcB9AH/AQoBOwHaAf8B+gHqAeAB/wHUAcQBugH/AWMBSQE1Af8UAAMuAUgBHgFqAYsB + /wF3AekB/gH/AS0BRgFNAesDLQFFGAABRAFDAUIBcAHBAY0BbwH/AfkBlgFlAf8B3AGNAWEB/wFnAXQB + SwH/AVABoQExAf8BSgGjASkB/wF8AZIBJwH/AcwBdgFHAf8BagFpAToB/wFYAVUBPAH/AkEBQAFwEAAB + /wEAA/8BAAL/AWgBlQGrAf8BRgHFAfAB/wFrAdQB8gH/AY0B4wH0Af8BogHrAfYB/wGDAd8B9AH/AVEB + ywHwAf8BHgG1Ae0B/wECAacB6AH/AQEBoQHiAf8BBAGcAdoB/wFJAW4BiAL/AQAD/wEAAv8ByQGzAaQB + /wHUAa8BlQH/AcEBmgF+Af8BsgGLAW8B/wHNAaMBhQH/Ab8BlwF6Af8BsgGLAW8B/wHNAaMBhQH/Ab8B + lwF6Af8BegGbAf4B/wFbAYIB+wH/AS8BYAH2Af8BDAFAAecB/wHNAaMBhQH/AcABmQF8Af8BYwFJATUB + /xAAAxsBJQEcAVsBewH/AXwB6wL/AYoB7wL/AWAB0gHlAf8BDQEzAUMB/wI6ATsBYhQAAW4BaAFkAbcB + 7QGYAWwB/wHoAZcBaQH/AVwBcAFWAf8BXAGWAUQB/wFaAawBOAH/AZMBoAE5Af8B3gGFAVkB/wHTAX0B + UAH/AUwBaQFDAf8BPwF3ASwB/wFbAVQBTwG3EAAB/wEAA/8BAAL/AWoBmAGuAf8BRgHFAfAB/wFrAdQB + 8QH/AY0B4wH0Af8BogHrAfYB/wGDAd8B9AH/AVEBygHwAf8BHgG2Ae0B/wECAagB6QH/AQEBoQHhAf8B + AwGcAdoB/wFLAXABigL/AQAD/wEAAv8BywG1AaUB/wP+Af8D/gH/Ab8BlwF5Af8D/gH/A/4B/wG/AZcB + eQH/A/4B/wP+Af8BvwGXAXkB/wH9AfoB+AH/Af0B9wH0Af8BvwGXAXkB/wH7AfIB6wH/AeAB2AHSAf8B + ZgFNATkB/xAAASMBaAGJAf8BhwHsAfkB/wGbAfMC/wGDAe8C/wGiAfYC/wFtAc0B3gH/AQYBKgFAAf8U + AAGYAYgBfQHqAfsBnQFvAf8BfwF+AVoB/wE6AaEBQQH/AVIBswFCAf8BjQGRAVgB/wHkAZQBagH/AeYB + jgFiAf8B2wGFAVcB/wFfAZ4BJQH/AToBlwEeAf8BZwFTAUUB6hAAAf8BAAP/AQAC/wFtAZsBsAH/AUUB + xgHwAf8BawHUAfIB/wGNAeMB9AH/AaIB6wH2Af8BgwHfAfQB/wFRAcoB8AH/AR4BtgHtAf8BAgGoAegB + /wECAaEB4QH/AQMBnAHaAf8BTgF0AY4C/wEAA/8BAAL/AcwBtgGnAf8D/gH/A/4B/wHNAaMBhQH/A/4B + /wP+Af8BzQGjAYUB/wP+Af8D/gH/Ac0BowGFAf8B/gH8AfsB/wH9AfoB+AH/Ac0BowGFAf8B/QH0AfAB + /wH7AfEB7AH/AWMBSQE1Af8QAAEwAXUBkgH/Aa4B9wL/AYcB4wHvAf8BLgFhAXsB/wGNAeUB8QH/Aa4B + +AL/AQUBQgFfAf8UAAGaAY0BggHqAd4BkwFoAf8BYwGYAVIB/wFAAcUBXwH/AUQByQFqAf8BcwGdAW0B + /wG2AZ8BaQH/AdEBlQFjAf8B5AGMAWAB/wGjAZMBNgH/AUYBoAEjAf8BaAFVAUgB6hAAAf8BAAP/AQAC + /wFwAZ8BswH/AUUBxgHwAf8BaQHUAfEB/wGMAeMB9AH/AaIB6wH2Af8BgwHfAfQB/wFRAcoB8AH/AR4B + tgHtAf8BAgGnAegB/wEBAaEB4QH/AQMBmwHaAf8BUAF3AZAC/wEAA/8BAAL/AeoBqgGLAf8B6gGqAYsB + /wHqAagBhwH/AekBpAGCAf8B6AGgAX0B/wHoAZwBdwH/AeYBlwFwAf8B5QGSAWkB/wHkAY0BYQH/AeMB + iAFaAf8B4gGCAVIB/wHgAX4BSwH/AeABeQFFAf8B3wF0AT8B/wHfAXIBOgH/AaUBYgFBAfIQAAFHAZAB + qQH/AZ8B9QL/Aa4B+gL/AZIB5wHwAf8BsQH6Av8BigHpAfUB/wEEAUoBaAH/FAABcQFsAWgBtwGlAY4B + XAH/AVIBuAFmAf8BWgHWAYEB/wGNAeEBmwH/AYIB4wGeAf8BngHQAZMB/wGoAaMBZgH/Ae0BlQFkAf8B + 3wGJAVYB/wFOAZoBKQH/AV8BWAFUAbcQAAH/AQAD/wEAAv8BcQGhAbUB/wFrAdUB9AH/AZoB6AH3Af8B + tQHzAfoB/wHCAfgB/AH/AcMB+AH9Af8BwQH3Af0B/wGvAfAB+wH/AY8B4gH3Af8BagHQAfAB/wE+AbgB + 5QH/AVMBegGTAv8BAAP/AQAC/wHqAaoBiwH/Af4B6wHiAf8B/gHrAeEB/wH+AeoB4AH/Af4B6QHeAf8B + /gHoAd0B/wH9AecB3AH/AfkBrwGIAf8B+QGrAYMB/wHzAacBfwH/AfMBpwF/Af8B9AGjAX0B/wH1AaEB + eQH/AfYBowF4Af8B8gGeAXEB/wGqAWYBQQHyEAADIQEwAWkBtgHKAf8BUgGcAbMB/wE5AYABmgH/ASUB + bwGQAf8BGQFqAY4B/wMfASwUAAFFAUQBQwFwAZ0BogF5Af8BNQHHAWIB/wF3Ad8BlgH/AcQB7AG3Af8B + 0wHzAcoB/wHrAfcB1QH/AbAB5AGmAf8BggGUAWAB/wHVAZQBQAH/AYABhQE7Af8BQgJBAXAQAAH/AQAD + /wEAAv8BdAGjAbgB/wHFAfkB/QH/AcUB+QH9Af8BxQH5Af0B/wHFAfkB/QH/AcUB+QH9Af8BxQH5Af0B + /wHFAfkB/QH/AcUB+QH9Af8BxQH5Af0B/wHFAfkB/QH/AVUBfQGVAv8BAAP/AQAC/wHqAaoBiwH/AekB + pwGIAf8B6AGkAYMB/wHmAaABfgH/AeQBnAF4Af8B4gGXAXIB/wHgAZIBawH/Ad4BjAFlAf8B2wGHAV0B + /wHZAYEBVwH/AdcBfAFQAf8B1QF3AUoB/wHTAXMBRAH/AdEBbwE/Af8B0AFsAToB/wHPAWkBNwH/QAAD + FgEdAWsBZwFjAbABjgG1AXsB/wF5AdkBhwH/Ab8B7AG2Af8B3gH2AcwB/wF8AakBaAH/AXUB2AGJAf8B + QwGtAWEB/wGRAYMBRAH/AWEBWwFXAbADFgEdEAAB/wEAA/8BAAL/AbcB1wHhAf8BjAG9AcwB/wGdAc8B + 2wH/Aa0B3wHoAf8BuQHsAfMB/wHCAfUB+gH/AcIB9QH6Af8BuAHrAfEB/wGpAdoB4wH/AZQBxAHRAf8B + ewGoAbgB/wGpAcUB0QL/AQAD/wEAAv8DZAHNBP8BjwGMAYoF/wNtAeEE/wOABf8DgAX/A3AF/wNjAf8D + /QH/AVgBWQFYAf8D1wH/RAADJwE5AWsBZwFjAbABrQG0AYwB/wGpAc0BkwH/AZoB1gGSAf8BgQHcAZEB + /wFQAb8BbgH/AY0BjwFhAf8BYwFfAVsBsAMnATkUAAH/AQAD/wEAAv8C/gL/AeMB8QH0Af8BuQHXAeEB + /wGVAb4BzAH/AXsBqAG7Af8BbgGbAbAB/wFqAZgBrQH/AXMBoQGzAf8BjAGyAcIB/wGxAc0B1wH/Ad8B + 6wHvAf8D/gL/AQAD/wEAAv8EAAEgASUBIAH/AzIBTwEgASUBIAH/Ay4BSAEgASUBIAH/AysBQQEgASUB + IAH/A0QBeAEgASUBIAH/AzwBZAEgASUBIAH/AxwBJwEgASUBIAH/AzgBXANAAXBIAAMWAR0BRQFEAUMB + cAFxAWwBaAG3AZoBjQGCAeoBmAGIAX0B6gFuAWgBZAG3AUQBQwFCAXADFgEdWAAB/wEAA/8BAAP/AQAD + /wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAAv9AAAH/AQAD + /wEAA/8BAAP/AQAC/wHkAdwB2QH/AbABlAGQAf8BlAFpAWYB/wGBAUwBSwH/AYwBYQFeAf8BpQGHAYIB + /wG4AaQBngL/AQAD/wEAA/8BAAP/AQAD/wEAAv8SAAEDAf8BCgEMAQ0B/wEKAQ0BDgH/AQcBCQEKAf8B + IgEqAS4B/wE+AUwBUAH/AS8BOAE7Af8BKAEzATgB/wEdASUBKQH/ATUBPwFEAf8BAgEDAQQB/wQAAf8B + AAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8B + AAL/CAABaQJdAeABcgJaAe0EAANQAZoBlQFSAVMB/yQAAf8BAAP/AQAC/wHkAdwB2QH/AbYBmgGVAf8B + lwFlAWQB/wGdAmsB/wGlAXYBdwH/Aa4BgQGAAf8BrAF9AXwB/wGhAXEBbwH/AY4BWwFaAf8BjgFbAVoB + /wHkAdwB2QL/AQAD/wEAA/8BAAL/DAABHwEkASUB/wF3AZEBmgH/AWUBiQGWAf8BdQGXAaEB/wFzAZcB + oQH/AXMBlwGjAf8BcgGWAaMB/wFpAYwBmgH/AV0BfQGNAf8BXgF8AYwB/wE/AUwBUQH/CAAB/wEAAv8B + twGiAZMB/wFjAUkBNQH/AWMBSQE1Af8BYwFJATUB/wFjAUkBNQH/AWMBSQE1Af8BYwFJATUB/wFjAUkB + NQH/AWMBSQE1Af8BYwFJATUB/wFjAUkBNQH/AWMBSQE1Af8BYwFJATUB/wFjAUkBNQL/AQAC/wgAAXgB + VgFVAfIBsQFiAWYB/wFwAjwB/wGVAVIBUwH/AXACPAH/JAAB/wEAAv8B5wHfAdwB/wGxAY4BiwH/AZoB + aAFnAf8BrQGCAYMB/wHTAbYBtwH/Ae0B2AHZAf8B3wHLAc4B/wHtAcsBzgH/AdgBrwGxAf8BvgGQAZIB + /wGeAmwB/wGVAWYBZQH/AdgBzwHMAv8BAAP/AQAC/wwAATUBPQFAAf8BkQGwAboB/wGkAcMBywH/AZgB + vQHGAf8BsAHOAdYB/wG/AdgB3wH/AcUB2wHiAf8BwQHYAd8B/wG4AdEB1wH/AWABfwGOAf8BIgEtATEB + /wgAAf8BAAL/AbcBogGTAf8B2QHLAcIB/wHOAb0BsQH/AcsBuQGtAf8ByAG1AakB/wHFAbIBpQH/AcIB + rgGhAf8BvwGsAZ4B/wG9AagBmgH/AbsBpgGXAf8BuQGkAZUB/wG3AaIBkwH/AbcBogGTAf8BYwFJATUC + /wEAAv8BmAFXAVUB/wFxAkMB+QHAAW4BagH/AbsBYQFeAf8BewFdAVwB6gHaAXABbwH/AcQCYwH/AV8C + XAHIAW0BNQE2Af8cAAH/AQAC/wHEAakBpgH/AZoCZwH/AbMCkAH/AdwB1gHVAf8B+QL1Af8B/gL0Af8D + AAH/AfMB3QHeAf8B+gHdAeAB/wGuAZABkQH/AbkBkAGRAf8BngJsAf8BngF2AXMC/wEAA/8BAAL/DAAB + OAFDAUYB/wGMAa8BuQH/AawBzAHSAf8BogHEAcsB/wGjAccByAH/AaEBxAG+Af8BrAHKAcMB/wGzAdIB + 1wH/AbgB1AHaAf8BZwGIAZcB/wEhASsBMAH/CAAB/wEAAv8BtwGiAZMB/wP+Af8D/gH/Af0B+wH6Af8B + ywG5Aa0B/wH8AfIB7QH/AfsB7QHkAf8B+gHnAdwB/wG/AasBnQH/AfcB2wHKAf8B9gHVAcAB/wH1Ac8B + twH/AbcBogGTAf8BYwFJATUC/wEAAv8B3AGHAYYB/wHAAXUBdwH/AdwBhwGGAf8BdwJaAfIDRgF/AWMC + XwHQAcQCYwH/AbEBVgFXAf8BsQFWAVcB/xwAAeEB1QHSAf8BogJyAf8BoAF1AXYB/wHuAuUB/wMAAf8D + ogH/Af4C+QH/Af4C9AH/AfwB7QHuAf8B+wHlAecB/wMAAf8BvwGgAaMB/wG+AZABkQH/AY8BWwFcAf8B + yAG6AbUC/wEAAv8MAAFFAVoBYgH/AZABtQG/Af8BdQGjAYwB/wFLAaABNAH/AV4BuQE2Af8BhAHQAToB + /wF2AbYBZwH/AZ0BwwHKAf8BmwG/AccB/wFsAYsBlwH/AQwBDwEQAf8IAAH/AQAC/wG3AaIBkwH/A/4B + /wP+Af8D/gH/Ac4BvAGwAf8B/QH3AfMB/wH8AfIB7AH/AfsB7QHlAf8BwgGuAaEB/wH5AeEB0wH/AfcB + 2wHKAf8B9QHVAcAB/wG5AaQBlQH/AWMBSQE1Av8BAAL/AwwBEAHTAYcBhgH/AeABjwGNAf8BagJBAfkD + KAE8AzkBXgHNAm4B/wGxAVYBVwH/AwwBEBwAAdEBuQG2Af8BlgFjAWIB/wHHAq4R/wH+AvoB/wH+AvQB + /wH8AuwB/wH7AeUB5wH/AfsB3QHhAf8B2AGvAbEB/wGhAXIBcQH/AaYBiAGDAv8BAAL/DAABTwFnAXAB + /wGeAb4BxwH/AWABmQFlAf8BUQG3AQAB/wFuAc4BHwH/AYUB3gEiAf8BjQHIAYoB/wGtAc4B2AH/AZ8B + wQHIAf8BcQGSAZ0B/wELAQ4BDwH/CAAB/wEAAv8BtwGiAZMB/wHgAdMBygH/AdcBxgG8Af8B1AHDAbgB + /wHRAcABtQH/Ac4BvQGxAf8BywG5AawB/wHIAbYBqQH/AcUBsgGlAf8BwgGvAaEB/wG/AawBnQH/Ab0B + qQGaAf8BugGmAZcB/wFjAUkBNQL/AQAC/wHdAZABjgH/AeUBpAGmAf8B8AKoAf8BqgFrAW4B/wFiAl0B + 3AFtAl8B5QHCAWwBbQH/AbEBVgFXAf8BbQE1ATYB/wFpAl0B4AFyAloB7QQAA1ABmgGVAVIBUwH/CAAB + xgGkAaMB/wGHAVEBUAH/AegC3gH/A+gR/wH+AvoB/wH+AfUB9wH/AfwC7AH/Ae4B2AHaAf8B7QHLAc4B + /wGtAn4B/wGOAWIBYAL/AQAC/wwAAVEBaQFxAf8BrAHIAc8B/wFRAY8BRwH/AWkBvAEyAf8BcgHIATQB + /wFuAckBAAH/AZgByAGZAf8BtAHSAdwB/wGgAcEByQH/AVUBcAF6Af8MAAH/AQAC/wG6AaUBlgH/A/4B + /wP+Af8D/gH/AdUBxAG5Af8D/gH/Af0B+wH6Af8B/AH3AfQB/wHIAbYBqQH/AfsB7gHkAf8B+gHnAdwB + /wH5AeIB0wH/Ab0BqAGaAf8BYwFJATUC/wEAAv8BXgJdAcMBnwJ7AfMBiAJ3AecB7QGpAagB/wHdAYkB + hgH/AdYBfQF3Af8BwgFsAW0B/wHAAnIB/wG9AXIBcwH/AXgBVgFVAfIBsQFiAWYB/wFwAjwB/wGVAVIB + UwH/AXACPAH/CAABwwGaAZkB/wF6AUQBQwH/AfwC+wH/AwAB/wPVCf8DkAH/A8MB/wH+AvoB/wH+AfUB + 9gH/AwAB/wHeAcoBzAH/AbABgwGCAf8BgwFQAU8C/wEAAv8JAAEDAQQB/wFmAYIBjgH/Aa8ByAHMAf8B + VQGOAT4B/wFmAaoBOgH/AW0BsgFAAf8BZwG0AQAB/wGxAdMBvgH/AbcB1QHdAf8BmAG6AcMB/wFhAXUB + fQH/DAAB/wEAAv8BvgGpAZoB/wP+Af8D/gH/A/4B/wHXAccBuwH/A/4B/wP+Af8B/QH7AfoB/wHLAbkB + rAH/AfsB8wHtAf8B+gHtAeUB/wH6AecB3AH/Ab8BqwGdAf8BYwFJATUC/wEAAv8IAAGPAXcBeQHrAdsB + nwGeAf8B0AGCAX4B/wHOAX4BegH/AbkBbwFwAf8BmAFXAVUB/wFxAkMB+QHAAW4BagH/AbsBYQFeAf8B + ewFdAVwB6gHaAXABbwH/AcQCYwH/AV8CXAHIAW0BNQE2Af8BzQGrAaoB/wGCAUwBSwH/AeYB3QHcEf8D + xwH/AxMB/wPDAf8B/gL6Af8B/gH1AfYB/wHtAdgB2QH/AagCeAH/AZcBbAFrAv8BAAL/CAABAQEHAQkB + /wF0AZEBnQH/AasBwQHAAf8BFAFaAQAB/wE5AXQBAAH/AUQBgwEAAf8BSAGNAQAB/wG6AdIBygH/AcMB + 3gHlAf8BlQG3AcAB/wFSAWQBaQH/DAAB/wEAAv8BwwGuAZ4B/wHkAdgB0AH/Ad0BzgHFAf8B3AHMAcEB + /wHZAckBvwH/AdcBxwG8Af8B1AHEAbgB/wHRAcABtAH/Ac4BvQGxAf8BywG5AawB/wHIAbUBqQH/AcUB + sgGlAf8BwgGuAaAB/wFjAUkBNQL/AQAC/wgAAd0BnAGbAf8DWQG8AwwBEAHOAX4BegH/Ab0BcgFzAf8B + 3AGHAYYB/wHAAXUBdwH/AdwBhwGGAf8BdwJaAfIDRgF/AWMCXwHQAcQCYwH/AbEBVgFXAf8BsQFWAVcB + /wHbAcUBwgH/AZQBYQFgAf8BwAGpAacF/wPVDf8DxwH/AxMB/wPDAv8C+gH/AdMCuAH/AaACbgH/AbIB + mAGTAv8BAAL/CQABAwEGAf8BdQGQAZwB/wGzAc4B0gH/AZECsgH/AaABvQG8Af8BtAHMAccB/wG2Ac0B + yQH/AccB3gHhAf8B2AHsAfEB/wGeAbsBxAH/ATkBQQFEAf8MAAH/AQAC/wHIAbIBowH/A/4B/wP+Af8D + /gH/AdsBzAHBAf8D/gH/A/4B/wP+Af8B0QHAAbQB/wH9AfsB+QH/Af0B9wH0Af8B/AHzAe0B/wHFAbIB + pQH/AWMBSQE1Av8BAAL/HAADDAEQAdMBhwGGAf8B4AGPAY0B/wFqAkEB+QMoATwDOQFeAc0CbgH/AbEB + VgFXAf8DDAEQAeoB4AHcAf8BrAF+AX8B/wGRAWUBYwH/AeoC4QH/AwAB/wPsBf8D2gX/A8cB/wMcAf8B + 5gHfAeAB/wGuAoUB/wGbAmgB/wHTAcYBwQL/AQAC/wwAAVIBZgFtAf8BjQGyAbsB/wGeAcAByQH/AbsB + 1QHcAf8BxwHeAeUB/wHLAeAB5wH/AbkB0wHaAf8BpAHCAcoB/wGeAboBwwH/AToBQwFGAf8MAAH/AQAC + /wHMAbYBpwH/A/4B/wP+Af8D/gH/Ad0BzgHFAf8D/gH/A/4B/wP+Af8B1AHEAbgB/wP+Af8B/QH7AfoB + /wH9AfcB8wH/AcgBtQGoAf8BZAFKATYC/wEAAv8cAAHdAZABjgH/AeUBpAGmAf8B8AKoAf8BqgFrAW4B + /wFiAl0B3AFtAl8B5QHCAWwBbQH/AbEBVgFXAf8BbQE1ATYC/wEAAv8B3AHEAcIB/wGZAmcB/wGiAXwB + ewH/AeoC4Qn/AwAB/wPVBf8B6AHiAeEB/wG1ApIB/wGeAmwB/wG5AZ0BmgL/AQAD/wEAAv8IAAEaAR8B + IAH/AUsBWQFfAf8BOAFNAVkB/wFlAYUBkwH/AXABkAGcAf8BeQGZAaUB/wF/AZ0BqQH/AWcBgwGOAf8B + cQGNAZkB/wGeAbEBtwH/ARkBHAEdAf8MAAH/AQAC/wHqAaoBiwH/AeoBqgGLAf8B6gGqAYsB/wHpAaUB + hAH/AekBnwF6Af8B5wGXAW4B/wHmAY4BYgH/AeUBhgFWAf8B4wF9AUoB/wHjAXYBQAH/AeIBcgE5Af8B + 4gFyATkB/wHiAXIBOQH/AcgBYgEvAv8BAAL/HAABXgJdAcMBnwJ7AfMBiAJ3AecB7QGpAagB/wHdAYkB + hgH/AdYBfQF3Af8BwgFsAW0B/wHAAnIB/wG9AXIBcwL/AQAC/wGaAmgB/wFpAS4BLQH/AZoCaAH/AZEB + ZQFjAf8BwAGpAagB/wHnAtwB/wH8AvsB/wHoAd8B3gH/AcgCrwH/AaEBeAF3Af8BnQJrAf8BaQEuAS0B + /wFpAS4BLQL/AQAD/wEAAv8IAAEYARsBHAH/AUIBTAFQAf8BDwESARUB/wENARABEQH/AQYBCAEKAf8B + DwESARQB/wEMAQ8BEQH/AQQCBQH/AQgCCQH/AQABAgEEAf8QAAH/AQAC/wHqAaoBiwL/AcIBogH/Af4B + wAGfAf8B/QG9AZoB/wH8AbkBlgH/AfsBtQGQAf8B+gGwAYsB/wH5AasBhAH/AfgBpwF9Af8B9gGiAXcB + /wH1AZ0BcQH/AfUBmQFqAf8B8wGVAWUB/wHNAWUBMQL/AQAC/yQAAY8BdwF5AesB2wGfAZ4B/wHQAYIB + fgH/Ac4BfgF6Af8BuQFvAXAB/wgAAf8BAAL/AZUBaQFoAf8BzwKnAf8BaQEuAS0B/wGtAX4BfwH/AZQC + YQH/AYMBTQFMAf8BfAJFAf8BigJTAf8BmQJmAf8BpgJ2Af8BaQEuAS0B/wHPAqcB/wFpAS4BLQL/AQAD + /wEAAv9AAAH/AQAC/wHqAaoBiwH/AeoBqgGLAf8B6gGqAYsB/wHqAaoBiwH/AeoBpgGGAf8B6QGhAX8B + /wHoAZsBdgH/AecBlAFsAf8B5gGOAWIB/wHlAYcBWAH/AeQBgQFOAf8B5AF7AUYB/wHjAXYBPgH/AeIB + cgE5Av8BAAL/JAAB3QGcAZsB/wNZAbwDDAEQAc4BfgF6Af8BvQFyAXMB/wgAAf8BAAL/Ae4B5QHiAf8B + lQFpAWgB/wGaAmgC/wEAAv8B3AHFAcIB/wHPAa0BrAH/AWkBLgEtAf8ByAGmAaUB/wHSAbwBuAL/AQAC + /wGoAX0BfgH/AacBfwF9Af8B6wHnAeQC/wEAA/8BAAL/QAAB/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD + /wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAAv9AAAH/AQAD/wEAA/8BAAP/AQAD + /wEAA/8BAAL/AYABTgFMAf8BaQEuAS0B/wFpAS4BLQL/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAC + /wFCAU0BPgcAAT4DAAEoAwABQAMAATADAAEBAQABAQUAAYABARYAA/+DAAT/AcABgQQAAv8BgAUAAfwB + fwGABQAB/AE/AYAFAAH8AT8BgAUAAfwBPwGABQAB/AE/AQABAQQAAfgBPwEAAQ8EAAHwAR8BAAEPBAAB + 8AEfAQABDwQAAfABHwEAAQ8EAAHwAR8BAAEPBAAB8AEfAQABDwQAAv8BAAEPBAAC/wGAAR8CAAGAAQAC + /wHAAT8C/wIAAv8CAAHwAQECAAHJAf8CAAHgAQMCAAHBAf8CAAHgAQMDAAF/AgAB4AEDAwABfwIAAeAB + AwMAAX8CAAHgAQMDAAETAgAB4AEHAwABAwIAAcABBwIAAcADAAHAAQcCAAHAAwABwAEHAgAB/gMAAeAB + BwIAAf4DAAHAAQcCAAH+AwABwAEPAgAB/wGDAgAC/wIAAf8BgwIAAv8CAAL/AgAL + + + + PAT PMT CAT NIT SDT EIT +Sections {0} {1} {2} {3} {4} {5} +CRC Errors {6} {7} {8} {9} {10} {11} +Continuity Errors: {12} Mux. bitrate: {13} +TEI Errors: {14} Last sec.: {15} +Sync losses: {16} In buffer: {17} + Out buffer: {18} + + + 247, 17 + + \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/INodeEngine.cs b/GUIs/skyscraper5.UI/INodeEngine.cs new file mode 100644 index 0000000..85e9eaf --- /dev/null +++ b/GUIs/skyscraper5.UI/INodeEngine.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI +{ + internal interface INodeEngine + { + public SkyscraperUiNode EnsureNodeExists(params string[] path); + public void RunOnUiThread(Action action); + public bool TestForNode(params string[] path); + public void SetIcon(SkyscraperUiNode node, int index); + } +} diff --git a/GUIs/skyscraper5.UI/NuGet.Config b/GUIs/skyscraper5.UI/NuGet.Config new file mode 100644 index 0000000..a2740cb --- /dev/null +++ b/GUIs/skyscraper5.UI/NuGet.Config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/Overrides/Form1UiJunction.cs b/GUIs/skyscraper5.UI/Overrides/Form1UiJunction.cs new file mode 100644 index 0000000..38acacb --- /dev/null +++ b/GUIs/skyscraper5.UI/Overrides/Form1UiJunction.cs @@ -0,0 +1,456 @@ +using Newtonsoft.Json; +using skyscraper5.Docsis; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Net; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.Teletext.Wss; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace skyscraper5.UI.Overrides +{ + internal class Form1UiJunction : ISkyscraperUiJunction + { + private INodeEngine form1; + public Dictionary SdtNames { get; private set; } + public Dictionary PidToPrograms { get; private set; } + private JsonSerializerSettings jsonSerializerSettings; + public Dictionary UsageLabels { get; private set; } + + public Form1UiJunction(INodeEngine form1) + { + this.form1 = form1; + this.jsonSerializerSettings = new JsonSerializerSettings(); + this.jsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore; + this.jsonSerializerSettings.Formatting = Formatting.Indented; + SetPidUsage(0x0000, "PAT"); + SetPidUsage(0x0001, "CAT"); + SetPidUsage(0x0002, "TSDT"); + SetPidUsage(0x0010, "NIT"); + SetPidUsage(0x0011, "SDT/BAT"); + SetPidUsage(0x0012, "EIT/CIT"); + SetPidUsage(0x0013, "RST"); + SetPidUsage(0x0014, "TDT/TOT"); + SetPidUsage(0x0015, "network synchronization"); + SetPidUsage(0x0016, "RNT"); + SetPidUsage(0x001c, "link-local inband signalling"); + SetPidUsage(0x001d, "measurement"); + SetPidUsage(0x001e, "DIT"); + SetPidUsage(0x001f, "SIT"); + } + + private void SetPidUsage(int pid, string name) + { + if (UsageLabels == null) + UsageLabels = new Dictionary(); + UsageLabels[pid] = name; + } + + public void DsmCcModuleAdd(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion) + { + throw new NotImplementedException(); + } + + public void DsmCcModuleComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + throw new NotImplementedException(); + } + + public void DsmCcModuleProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, double moduleInfoDownloadProgress) + { + throw new NotImplementedException(); + } + + public void DsmCcVfs(VfsFile vfsFile) + { + SkyscraperUiNode dsmCcNode = form1.EnsureNodeExists("DSM-CC"); + form1.SetIcon(dsmCcNode, 1); + + string outName = String.Format("DSM-CC\\Object Carousels\\PID 0x{0:X4}\\{1}", vfsFile.SourcePid, vfsFile.Name); + string[] args = outName.Split("\\"); + form1.EnsureNodeExists(args); + } + + public IEnumerable GetServices() + { + throw new NotImplementedException(); + } + + public void NotifyAit(AitApplication aitApplication) + { + SkyscraperUiNode applicationNode = form1.EnsureNodeExists("AIT", aitApplication.TryGetName()); + form1.SetIcon((SkyscraperUiNode)applicationNode.Parent, 1); + } + + private Dictionary bats; + public void NotifyBat(BatBouquet batBouquet) + { + if (bats == null) + bats = new Dictionary(); + + string name = String.Format("{0} ({1})", batBouquet.BouquetId, batBouquet.BouquetName); + if (!bats.ContainsKey(batBouquet.BouquetId)) + bats.Add(batBouquet.BouquetId, name); + + SkyscraperUiNode bouquetNode = form1.EnsureNodeExists("BAT", name); + form1.SetIcon((SkyscraperUiNode)bouquetNode.Parent, 1); + form1.SetIcon(bouquetNode, 7); + } + + public void NotifyBatTs(ushort batBouquetBouquetId, BatTransportStream child) + { + string name = bats[batBouquetBouquetId]; + string name2 = String.Format("TS #{0}", child.TransportStreamId); + SkyscraperUiNode batTsNode = form1.EnsureNodeExists("BAT", name, name2); + form1.SetIcon(batTsNode, 4); + } + + public void NotifyBlockstreamCarrier() + { + throw new NotImplementedException(); + } + + public void NotifyCat(CaDescriptor caDescriptor) + { + string systemName = CaSystemNames.GetHumanReadableName(caDescriptor.CaSystemId); + string caName = String.Format("0x{0:X4} ({1})",caDescriptor.CaSystemId,systemName); + SkyscraperUiNode catNode = form1.EnsureNodeExists("CAT", caName); + if (!catNode.FillOutComplete) + { + form1.SetIcon((SkyscraperUiNode)catNode.Parent, 1); + form1.SetIcon(catNode, 6); + catNode.DetailedText = JsonConvert.SerializeObject(caDescriptor, jsonSerializerSettings); + SetPidUsage(caDescriptor.CaPid, String.Format("{0} ECM/EMM", systemName)); + catNode.FillOutComplete = true; + } + } + + public void NotifyDocsisCarrier(DocsisEnvironment docsisEnvironment) + { + throw new NotImplementedException(); + } + + public void NotifyDocsisFrequency(uint? frequency, bool isUpstream, object mmm) + { + throw new NotImplementedException(); + } + + public void NotifyEvent(EitEvent eitEvent) + { + SkyscraperUiNode serviceNode = form1.EnsureNodeExists("EIT", eitEvent.StartTime.ToShortDateString(), eitEvent.ServiceId.ToString()); + form1.SetIcon((SkyscraperUiNode)serviceNode.Parent.Parent, 1); + form1.SetIcon((SkyscraperUiNode)serviceNode.Parent, 5); + form1.SetIcon(serviceNode, 8); + } + + public void NotifyMpeTraffic(IpTrafficInfo iti, int ipv4PacketLength) + { + throw new NotImplementedException(); + } + + public void NotifyNit(NitTransportStream transportStream) + { + SkyscraperUiNode nitNode = form1.EnsureNodeExists("NIT", transportStream.RenderForUi()); + if (!nitNode.FillOutComplete) + { + nitNode.DetailedText = JsonConvert.SerializeObject(nitNode, jsonSerializerSettings); + nitNode.FillOutComplete = true; + form1.SetIcon((SkyscraperUiNode)nitNode.Parent, 1); + form1.SetIcon(nitNode, 4); + } + } + + public void NotifyPatProgram(int pmtPid, ushort programId) + { + string name = String.Format("PMT PID 0x{0:X4} - Program {0}", programId, pmtPid); + SkyscraperUiNode patNode = form1.EnsureNodeExists("PAT", name); + form1.SetIcon((SkyscraperUiNode)patNode.Parent, 1); + } + + public void NotifyPmtProgram(ProgramMapping result, int pmtPid) + { + string name = String.Format("PMT PID 0x{0:X4} - Program {0}", result.ProgramNumber,pmtPid); + SkyscraperUiNode programNode = form1.EnsureNodeExists("PAT",name); + if (!programNode.FillOutComplete) + { + form1.RunOnUiThread(() => form1.EnsureNodeExists("PAT").Expand()); + + if (!programNode.FillOutComplete) + { + programNode.DetailedText = JsonConvert.SerializeObject(result, jsonSerializerSettings); + programNode.FillOutComplete = true; + } + + string sdtName = null; + form1.RunOnUiThread(() => sdtName = TrySdtFromPmt(result.ProgramNumber)); + if (sdtName != null) + { + form1.EnsureNodeExists("PAT", name, String.Format("SDT: {0}", sdtName)); + form1.RunOnUiThread(() => programNode.Expand()); + } + + foreach (ProgramMappingStream pmtEntry in result.Streams) + { + string name2 = String.Format("PID 0x{0:X4}", pmtEntry.ElementaryPid); + SkyscraperUiNode pidNode = form1.EnsureNodeExists("PAT", name, name2); + form1.SetIcon((SkyscraperUiNode)pidNode.Parent, 8); + pidNode.DetailedText = JsonConvert.SerializeObject(pmtEntry, jsonSerializerSettings); + + if (PidToPrograms == null) + PidToPrograms = new Dictionary(); + PidToPrograms.Add(pmtEntry.ElementaryPid, result.ProgramNumber); + SetPidUsage(pmtEntry.ElementaryPid, StreamTypeAsString(pmtEntry,result.PrivateDataSpecifier)); + } + + PidToPrograms.Add(pmtPid, result.ProgramNumber); + SetPidUsage(pmtPid, "PMT"); + + programNode.FillOutComplete = true; + } + } + + private string TrySdtFromPmt(ushort programNumber) + { + if (!form1.TestForNode("SDT")) + return null; + + SkyscraperUiNode sdtNode = form1.EnsureNodeExists("SDT"); + foreach(TreeNode node in sdtNode.Nodes) + { + string prefix = String.Format("{0} ", programNumber); + if (node.Text.StartsWith(prefix)) + { + string result = node.Text.Substring(node.Text.Length); + return result; + } + } + return null; + } + + public void NotifyScte35(ushort programNumber, SpliceInsert spliceInsert) + { + throw new NotImplementedException(); + } + + public void NotifyScte35(ushort programNumber, TimeSignal spliceInsert) + { + throw new NotImplementedException(); + } + + public void NotifySdtService(SdtService sdtService) + { + string name = String.Format("{0} {1}", sdtService.ServiceId, sdtService.ServiceName); + SkyscraperUiNode node = form1.EnsureNodeExists("SDT", name); + if (!node.FillOutComplete) + { + if (SdtNames == null) + SdtNames = new Dictionary(); + SdtNames.Add(sdtService.ServiceId, sdtService.ServiceName); + + node.DetailedText = JsonConvert.SerializeObject(sdtService, jsonSerializerSettings); + form1.RunOnUiThread(new Action(() => { TrySdtToPmt(sdtService.ServiceId, sdtService.ServiceName); })); + form1.SetIcon((SkyscraperUiNode)node.Parent, 1); + node.FillOutComplete = true; + } + } + + private void TrySdtToPmt(uint programNo, string serviceName) + { + if (!form1.TestForNode("PAT")) + return; + + string query = String.Format(" {0}", programNo); + SkyscraperUiNode patNode = form1.EnsureNodeExists("PAT"); + foreach(TreeNode node in patNode.Nodes) + { + if (node.Text.EndsWith(query)) + { + SkyscraperUiNode childNode = new SkyscraperUiNode(); + childNode.Text = String.Format("SDT: {0}", serviceName); + node.Nodes.Add(childNode); + break; + } + } + } + + public void NotifyStreamTypeDetection(string contestantTag, int pid) + { + throw new NotImplementedException(); + } + + public void NotifyTdt(DateTime utcTime) + { + SkyscraperUiNode skyscraperUiNode = form1.EnsureNodeExists("TDT"); + form1.SetIcon(skyscraperUiNode, 1); + if (skyscraperUiNode.Nodes.Count == 0) + { + SkyscraperUiNode child = new SkyscraperUiNode(); + child.Text = utcTime.ToString(); + form1.RunOnUiThread(() => + { + skyscraperUiNode.Nodes.Add(child); + skyscraperUiNode.Expand(); + } + ); + form1.SetIcon(child, 3); + } + else + { + SkyscraperUiNode oldChild = (SkyscraperUiNode)skyscraperUiNode.Nodes[0]; + form1.RunOnUiThread(() => oldChild.Text = utcTime.ToString()); + } + } + + public void NotifyTot(DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + + SkyscraperUiNode skyscraperUiNode = form1.EnsureNodeExists("TOT"); + form1.SetIcon(skyscraperUiNode, 1); + if (skyscraperUiNode.Nodes.Count == 0) + { + SkyscraperUiNode child = new SkyscraperUiNode(); + child.Text = utcTime.ToString(); + form1.RunOnUiThread(() => + { + skyscraperUiNode.Nodes.Add(child); + skyscraperUiNode.Expand(); + } + ); + if (!child.FillOutComplete) + { + form1.SetIcon(child, 3); + child.DetailedText = JsonConvert.SerializeObject(ltod, jsonSerializerSettings); + } + } + else + { + SkyscraperUiNode oldChild = (SkyscraperUiNode)skyscraperUiNode.Nodes[0]; + form1.RunOnUiThread(() => oldChild.Text = utcTime.ToString()); + } + } + + public void NotifyWss(ushort programNumber, WssDataBlock wssDataBlock) + { + throw new NotImplementedException(); + } + + public void SetGseMode() + { + throw new NotImplementedException(); + } + + public void SetMemorySaverMode(bool saveMemory) + { + throw new NotImplementedException(); + } + + public void ShowFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid, byte[] imageData) + { + throw new NotImplementedException(); + } + + private string StreamTypeAsString(ProgramMappingStream stream, uint? parentRegistrationFormatIdentifier) + { + if (stream.StreamType == PmtStreamType.H262) + return "MPEG-2 Video"; + if (stream.AvcStillPresent.HasValue) + return "MPEG-4 Video"; + if (stream.AudioType.HasValue) + { + switch (stream.AudioType) + { + case AudioType.CleanEffects: return "Audio"; + case AudioType.HearingImpaired: return "Audio (for hearing impaired)"; + case AudioType.VisualImpairedCommentary: return "Audio (for visually impaired)"; + } + } + + if (stream.StreamType == PmtStreamType.AvcVideoStream) + { + return "MPEG-4 Video"; + } + + if (stream.StreamType == PmtStreamType.Iso11172Audio) + { + return "Audio"; + } + + if (stream.StreamType == PmtStreamType.Iso13818_3Audio) + return "MPEG-2 Audio"; + if (stream.StreamType == PmtStreamType.Iso13818_7AudioADTS) + return "AAC Audio"; + if (stream.StreamType == PmtStreamType.HevcVideoStream) + return "H.265 Video"; + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Ac4ChannelMode.HasValue) + return "Dolby AC-4 Audio"; + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.BSID.HasValue) + return "Dolby AC-3 Audio"; + if (stream.StreamType == PmtStreamType.Iso14496_3Audio && stream.AacProfileAndLevel.HasValue) + return "AAC Audio"; + if ((int)stream.StreamType == 0x81 && stream.ComponentType.HasValue) + return "Dolby AC-3 Audio"; + if (stream.Teletexts != null) + return "Teletext"; + + if (stream.Applications != null) + return "Red-button Application"; + + if (stream.DataBroadcastId == 0x0123) + return "Red-button Application"; + + if (stream.DataBroadcastId == 0x000a) + return "System Software Update"; + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Subtitlings != null && stream.Subtitlings.Length > 0) + return "Subtitle"; + + if (stream.DataBroadcastId == 0xf0) + return "MHP Application"; + + + if (stream.FormatIdentifier.HasValue && stream.FormatIdentifier == 0x43554549 && (int)stream.StreamType == 0x86) + return "Advertising Metadata"; + + if ((int)stream.StreamType == 0x86 && parentRegistrationFormatIdentifier.HasValue && parentRegistrationFormatIdentifier.Value == 0x43554549) + return "Advertising Metadata"; + + if (stream.DataBroadcastId == 0x0007) + return "Object Carousel"; + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.VbiData != null) + return "Teletext"; + + if ((byte)stream.StreamType == 0x89 && stream.AncillaryDataDescriptor != null && stream.AncillaryDataDescriptor.RdsOnly) + return "Radio Data Service"; + + if (stream.DataBroadcastId.HasValue && stream.DataBroadcastId.Value == 0x0005 /*&& stream.StreamType == PmtStreamType.Iso13818_6TypeD*/) + return "Multiprotocol Encapsulation"; + + if (stream.DataBroadcastId == 0x0106) + return "MHEG-5"; + + if (stream.DataBroadcastId == 0x000b) + return "IP/MAC Notification"; + + if (stream.RelatedContentDescriptorPresent.HasValue) + if (stream.RelatedContentDescriptorPresent.Value && stream.StreamType == PmtStreamType.Iso13818_1PrivateSections) + return "Related Content Table Information"; + + if (stream.NumT2MiStreams.HasValue && stream.StreamType == PmtStreamType.Iso13818_1PesPackets) + return "T2-MI"; + + return String.Format("??? (Type 0x{0:X2})", (int)stream.StreamType); + } + } +} diff --git a/GUIs/skyscraper5.UI/Overrides/PacketsPerSecondCounter.cs b/GUIs/skyscraper5.UI/Overrides/PacketsPerSecondCounter.cs new file mode 100644 index 0000000..c82c043 --- /dev/null +++ b/GUIs/skyscraper5.UI/Overrides/PacketsPerSecondCounter.cs @@ -0,0 +1,51 @@ +using skyscraper5.Mpeg2; +using skyscraper5.src.Mpeg2.PacketFilter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.Overrides +{ + internal class PacketsPerSecondCounter : IPacketFilter + { + public bool PassPacket(TsPacket packet) + { + DateTime now = DateTime.Now; + if (lastPacket.Second != now.Second) + { + countedPacketLastSecond = countedPacketThisSecond; + countedPacketThisSecond = 0; + Ready = true; + } + lastPacket = now; + countedPacketThisSecond++; + totalPacketsOverall++; + return true; + } + + private DateTime lastPacket; + private long countedPacketThisSecond; + private long countedPacketLastSecond; + private ulong totalPacketsOverall; + + public long PacketsPerSecond + { + get + { + return countedPacketThisSecond; + } + } + + public ulong TotalPacketsOverall + { + get + { + return totalPacketsOverall; + } + } + + public bool Ready { get; private set; } + } +} diff --git a/GUIs/skyscraper5.UI/Overrides/PmtCounter.cs b/GUIs/skyscraper5.UI/Overrides/PmtCounter.cs new file mode 100644 index 0000000..4cfe743 --- /dev/null +++ b/GUIs/skyscraper5.UI/Overrides/PmtCounter.cs @@ -0,0 +1,32 @@ +using skyscraper5.Mpeg2; +using skyscraper5.src.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.Overrides +{ + internal class PmtCounter : IPsiDecoderTransformer + { + public void Transform(PsiDecoder psiDecoder) + { + psiDecoder.OnCrcError += PsiDecoder_OnCrcError; + psiDecoder.OnValidSection += PsiDecoder_OnValidSection; + } + + private void PsiDecoder_OnValidSection(PsiSection section, int pid, long available) + { + ValidSection++; + } + + private void PsiDecoder_OnCrcError(PsiSection section, int pid, long available) + { + CrcErrors++; + } + + public long CrcErrors { get; private set; } + public long ValidSection { get; private set; } + } +} diff --git a/GUIs/skyscraper5.UI/Overrides/ScrambleCounter.cs b/GUIs/skyscraper5.UI/Overrides/ScrambleCounter.cs new file mode 100644 index 0000000..4c0815f --- /dev/null +++ b/GUIs/skyscraper5.UI/Overrides/ScrambleCounter.cs @@ -0,0 +1,34 @@ +using skyscraper5.Mpeg2; +using skyscraper5.src.Mpeg2.PacketFilter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.Overrides +{ + internal class ScrambleCounter : IPacketFilter + { + public bool PassPacket(TsPacket packet) + { + if (scrambled == null) + scrambled = new byte[0x2000]; + + if (packet.TSC != 0) + { + if (scrambled[packet.PID] != 255) + scrambled[packet.PID]++; + } + + return true; + } + + private byte[] scrambled; + + public bool IsScrambled(int pid) + { + return scrambled[pid] > 0; + } + } +} diff --git a/GUIs/skyscraper5.UI/Overrides/TeiCounter.cs b/GUIs/skyscraper5.UI/Overrides/TeiCounter.cs new file mode 100644 index 0000000..2d19ce4 --- /dev/null +++ b/GUIs/skyscraper5.UI/Overrides/TeiCounter.cs @@ -0,0 +1,23 @@ +using skyscraper5.Mpeg2; +using skyscraper5.src.Mpeg2.PacketFilter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.Overrides +{ + internal class TeiCounter : IPacketFilter + { + public bool PassPacket(TsPacket packet) + { + if (packet.TEI) + TransportErrors++; + + return true; + } + + public long TransportErrors { get; private set; } + } +} diff --git a/GUIs/skyscraper5.UI/PidListViewItem.cs b/GUIs/skyscraper5.UI/PidListViewItem.cs new file mode 100644 index 0000000..0f59ca0 --- /dev/null +++ b/GUIs/skyscraper5.UI/PidListViewItem.cs @@ -0,0 +1,74 @@ +using skyscraper5.UI.Overrides; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI +{ + internal class PidListViewItem : ListViewItem + { + public PidListViewItem(int pid) + { + SubItems.Add(new ListViewSubItem()); + SubItems.Add(new ListViewSubItem()); + SubItems.Add(new ListViewSubItem()); + SubItems.Add(new ListViewSubItem()); + + this.PID = pid; + } + + private int _pid; + public int PID + { + get + { + return _pid; + } + set + { + _pid = value; + Text = String.Format("0x{0:X4}", value); + } + } + + public ulong PackagesInPid { get; private set; } + public double Percentage { get; private set; } + + public string ProgramName + { + get + { + return SubItems[3].Text; + } + set + { + SubItems[3].Text = value; + } + } + + public string UsageLabel + { + get + { + return SubItems[4].Text; + } + set + { + SubItems[4].Text = value; + } + } + + public void SetPackageStats(ulong packagesInPid, ulong totalPackagesOverall) + { + SubItems[1].Text = packagesInPid.ToString(); + + this.Percentage = (double)packagesInPid / (double)totalPackagesOverall; + SubItems[2].Text = Math.Round(this.Percentage * 100.0, 2) + "%"; + + this.PackagesInPid = packagesInPid; + } + + } +} diff --git a/GUIs/skyscraper5.UI/PidListViewItemSorter.cs b/GUIs/skyscraper5.UI/PidListViewItemSorter.cs new file mode 100644 index 0000000..c34ed50 --- /dev/null +++ b/GUIs/skyscraper5.UI/PidListViewItemSorter.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI +{ + internal class PidListViewItemSorter : IComparer + { + public PidListViewItemSorter() + { + SorterInstanceGuid = Guid.NewGuid(); + } + + public Guid SorterInstanceGuid { get; private set; } + + public int SortColumn { get; internal set; } + + public SortOrder Order { get; internal set; } + + public int Compare(object? x, object? y) + { + PidListViewItem x2 = x as PidListViewItem; + PidListViewItem y2 = y as PidListViewItem; + + int value; + + switch(SortColumn) + { + case 0: value = x2.PID.CompareTo(y2.PID); break; + case 1: value = x2.PackagesInPid.CompareTo(y2.PackagesInPid); break; + case 2: value = x2.PackagesInPid.CompareTo(y2.PackagesInPid); break; + case 3: value = CompareProgramName(x2.ProgramName,y2.ProgramName,Order); break; + case 4: value = CompareProgramName(x2.UsageLabel, y2.UsageLabel, Order); break; + default: + throw new NotImplementedException(String.Format("{0}, {1}", SortColumn, Order)); + } + + if (Order == SortOrder.Descending) + value /= -1; + + return value; + } + + private int CompareProgramName(string x3, string y3, SortOrder sortOrder) + { + if (string.IsNullOrEmpty(x3) && string.IsNullOrEmpty(y3)) + return 0; + + if (string.IsNullOrEmpty(x3)) + return sortOrder == SortOrder.Descending ? -1 : 1; + + if (string.IsNullOrEmpty(y3)) + return sortOrder == SortOrder.Descending ? 1 : -1; + + return x3.CompareTo(y3); + } + } +} diff --git a/GUIs/skyscraper5.UI/Program.cs b/GUIs/skyscraper5.UI/Program.cs new file mode 100644 index 0000000..bfbe3c1 --- /dev/null +++ b/GUIs/skyscraper5.UI/Program.cs @@ -0,0 +1,56 @@ +using skyscraper5.UI.StreamAcquisition; +using System.Reflection; + +namespace skyscraper5.UI +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + List streamAcquirers; + Assembly assembly = Assembly.GetExecutingAssembly(); + streamAcquirers = assembly.GetTypes() + .Where(t => typeof(IStreamAcquirer).IsAssignableFrom(t)) + .Where(t => !t.IsInterface) + .Where(t => !t.IsAbstract) + .Select(x => x.GetConstructor(Type.EmptyTypes)) + .Select(x => x.Invoke(new object[0])) + .Select(x => new StreamAcquirerListItem((IStreamAcquirer)x)) + .ToList(); + + StreamSource acquiredStream = null; + string[] args = Environment.GetCommandLineArgs(); + if (args.Length == 2) + { + foreach (StreamAcquirerListItem possibleStreamer in streamAcquirers) + { + object handle = possibleStreamer.Wrapped.BaseGetHandle(args[1]); + if (handle == null) + continue; + StreamSource stream = possibleStreamer.Wrapped.BaseGetStream(handle); + if (stream == null) + continue; + acquiredStream = stream; + break; + } + } + + + ApplicationConfiguration.Initialize(); + if (acquiredStream == null) + { + StreamAcquirerPickingForm pickingForm = new StreamAcquirerPickingForm(streamAcquirers,args); + pickingForm.ShowDialog(); + if (pickingForm.StreamSource == null) + return; + acquiredStream = pickingForm.StreamSource; + } + + Application.Run(new Form1(acquiredStream)); + } + } +} \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/Properties/launchSettings.json b/GUIs/skyscraper5.UI/Properties/launchSettings.json new file mode 100644 index 0000000..15e252d --- /dev/null +++ b/GUIs/skyscraper5.UI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "skyscraper5.UI": { + "commandName": "Project", + "commandLineArgs": "\"Z:\\Freebies\\Datasets\\SkyscraperLibrarian\\DVB-C\\Bochum\\114.ts\"" + } + } +} \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/SkyscraperUiNode.cs b/GUIs/skyscraper5.UI/SkyscraperUiNode.cs new file mode 100644 index 0000000..e2bcd59 --- /dev/null +++ b/GUIs/skyscraper5.UI/SkyscraperUiNode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI +{ + internal class SkyscraperUiNode : TreeNode + { + public bool FillOutComplete { get; set; } + public string DetailedText { get; internal set; } + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquirers/FileStreamAcquirer.cs b/GUIs/skyscraper5.UI/StreamAcquirers/FileStreamAcquirer.cs new file mode 100644 index 0000000..ed6d2db --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquirers/FileStreamAcquirer.cs @@ -0,0 +1,46 @@ +using skyscraper5.UI.StreamAcquisition; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquirers +{ + [StreamAcquirer("File","Transport Stream File")] + internal class FileStreamAcquirer : StreamAcquisition.AbstractStreamAcquirer + { + public override FileInfo AskUserForHandle(IWin32Window parent) + { + OpenFileDialog openFileDialog = new OpenFileDialog(); + openFileDialog.Filter = "MPEG-2 Transport Stream (*.ts)|*.ts"; + openFileDialog.Multiselect = false; + openFileDialog.CheckFileExists = true; + openFileDialog.CheckPathExists = true; + DialogResult dialogResult = openFileDialog.ShowDialog(parent); + switch(dialogResult) + { + case DialogResult.OK: + return new FileInfo(openFileDialog.FileName); + case DialogResult.Cancel: + return null; + default: + throw new NotImplementedException(dialogResult.ToString()); + } + } + + public override FileInfo GetHandle(string arg) + { + if (File.Exists(arg)) + { + return new FileInfo(arg); + } + return null; + } + + public override StreamSource GetStream(FileInfo inputHandle) + { + return new StreamSourceIoStreamWrapper(inputHandle.OpenRead(), inputHandle.Name); + } + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquirers/NullStreamAcquirer.cs b/GUIs/skyscraper5.UI/StreamAcquirers/NullStreamAcquirer.cs new file mode 100644 index 0000000..a2d7f0a --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquirers/NullStreamAcquirer.cs @@ -0,0 +1,99 @@ +using skyscraper5.UI.StreamAcquisition; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquirers +{ + [StreamAcquirer("NULL","Returns only empty frames")] + internal class NullStreamAcquirer : AbstractStreamAcquirer + { + public override object AskUserForHandle(IWin32Window window) + { + return new object(); + } + + public override object GetHandle(string arg) + { + return null; + } + + private BlankTransportStream ts; + public override StreamSource GetStream(object inputHandle) + { + if (ts == null) + ts = new BlankTransportStream(); + + return new StreamSourceIoStreamWrapper(ts, null); + } + + class BlankTransportStream : Stream + { + public int _continuityCounter; + private long _internalPosition; + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotImplementedException(); + + public override long Position + { + get => _internalPosition; + set => throw new NotImplementedException(); + } + + public override void Flush() + { + throw new NotImplementedException(); + } + + private byte[] buffer; + public override int Read(byte[] outBuffer, int offset, int count) + { + if (count != 188) + throw new ArgumentOutOfRangeException(nameof(count)); + + if (buffer == null) + buffer = new byte[188]; + + buffer[0] = (byte)'G'; + + buffer[1] = 0x1f; + buffer[2] = 0xff; + + buffer[3] = 0; + buffer[3] <<= 2; + buffer[3] += 1; + buffer[3] <<= 2; + buffer[3] += (byte)_continuityCounter; + _continuityCounter++; + if (_continuityCounter > 0xf) + _continuityCounter = 0; + + Array.Copy(buffer, 0, outBuffer, offset, count); + return count; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/IStreamAcquirer.cs b/GUIs/skyscraper5.UI/StreamAcquisition/IStreamAcquirer.cs new file mode 100644 index 0000000..7eef59e --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/IStreamAcquirer.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquisition +{ + internal interface IStreamAcquirer + { + object BaseGetHandle(string arg); + StreamSource BaseGetStream(object inputHandle); + object BaseAskUserForHandle(IWin32Window parent); + + Type HandleType { get; } + } + + internal abstract class AbstractStreamAcquirer : IStreamAcquirer + { + public abstract T GetHandle(string arg); + public abstract StreamSource GetStream(T inputHandle); + public abstract T AskUserForHandle(IWin32Window parent); + + public object BaseGetHandle(string arg) + { + return (T)GetHandle(arg); + } + + public StreamSource BaseGetStream(object inputHandle) + { + return GetStream((T)inputHandle); + } + + public object BaseAskUserForHandle(IWin32Window parent) + { + return AskUserForHandle(parent); + } + + public Type HandleType { get => typeof(T); } + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerAttribute.cs b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerAttribute.cs new file mode 100644 index 0000000..a086a62 --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerAttribute.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquisition +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class StreamAcquirerAttribute : Attribute + { + public StreamAcquirerAttribute(string name, string description) + { + Name = name; + Description = description; + Hidden = false; + } + + public StreamAcquirerAttribute(string name, string description, bool hidden) + { + Name = name; + Description = description; + Hidden = hidden; + } + + public string Name { get; } + public string Description { get; } + public bool Hidden { get; } + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerListItem.cs b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerListItem.cs new file mode 100644 index 0000000..c8da956 --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerListItem.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquisition +{ + internal class StreamAcquirerListItem : ListViewItem + { + public StreamAcquirerListItem(IStreamAcquirer wrapped) + { + Wrapped = wrapped; + WrappedAttribute = (StreamAcquirerAttribute)Wrapped.GetType().GetCustomAttribute(typeof(StreamAcquirerAttribute)); + + Text = GetName(); + SubItems.Add(GetDescription()); + } + + public IStreamAcquirer Wrapped { get; } + public StreamAcquirerAttribute WrappedAttribute { get; } + + public bool Hidden + { + get + { + if (WrappedAttribute == null) + return true; + + return WrappedAttribute.Hidden; + } + } + + public string GetName() + { + if (WrappedAttribute == null) + return Wrapped.GetType().Name; + + return WrappedAttribute.Name; + } + + public string GetDescription() + { + if (WrappedAttribute == null) + return "???"; + + return WrappedAttribute.Description; + } + + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.Designer.cs b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.Designer.cs new file mode 100644 index 0000000..1aacb0d --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.Designer.cs @@ -0,0 +1,144 @@ +namespace skyscraper5.UI.StreamAcquisition +{ + partial class StreamAcquirerPickingForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + tableLayoutPanel1 = new TableLayoutPanel(); + listView1 = new ListView(); + columnHeader1 = new ColumnHeader(); + columnHeader2 = new ColumnHeader(); + tableLayoutPanel2 = new TableLayoutPanel(); + button1 = new Button(); + button2 = new Button(); + tableLayoutPanel1.SuspendLayout(); + tableLayoutPanel2.SuspendLayout(); + SuspendLayout(); + // + // tableLayoutPanel1 + // + tableLayoutPanel1.ColumnCount = 1; + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.Controls.Add(listView1, 0, 0); + tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 1); + tableLayoutPanel1.Dock = DockStyle.Fill; + tableLayoutPanel1.Location = new Point(0, 0); + tableLayoutPanel1.Name = "tableLayoutPanel1"; + tableLayoutPanel1.RowCount = 2; + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 90F)); + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 10F)); + tableLayoutPanel1.Size = new Size(514, 392); + tableLayoutPanel1.TabIndex = 0; + // + // listView1 + // + listView1.Columns.AddRange(new ColumnHeader[] { columnHeader1, columnHeader2 }); + listView1.Dock = DockStyle.Fill; + listView1.FullRowSelect = true; + listView1.Location = new Point(3, 3); + listView1.Name = "listView1"; + listView1.Size = new Size(508, 346); + listView1.TabIndex = 0; + listView1.UseCompatibleStateImageBehavior = false; + listView1.View = View.Details; + listView1.SelectedIndexChanged += listView1_SelectedIndexChanged; + listView1.DoubleClick += listView1_DoubleClick; + // + // columnHeader1 + // + columnHeader1.Text = "Name"; + columnHeader1.Width = 100; + // + // columnHeader2 + // + columnHeader2.Text = "Description"; + columnHeader2.Width = 400; + // + // tableLayoutPanel2 + // + tableLayoutPanel2.ColumnCount = 3; + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 65.7894745F)); + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 17.1052647F)); + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 17.1052647F)); + tableLayoutPanel2.Controls.Add(button1, 1, 0); + tableLayoutPanel2.Controls.Add(button2, 2, 0); + tableLayoutPanel2.Dock = DockStyle.Fill; + tableLayoutPanel2.Location = new Point(3, 355); + tableLayoutPanel2.Name = "tableLayoutPanel2"; + tableLayoutPanel2.RowCount = 1; + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel2.Size = new Size(508, 34); + tableLayoutPanel2.TabIndex = 1; + // + // button1 + // + button1.Dock = DockStyle.Fill; + button1.Enabled = false; + button1.Location = new Point(337, 3); + button1.Name = "button1"; + button1.Size = new Size(80, 28); + button1.TabIndex = 0; + button1.Text = "OK"; + button1.UseVisualStyleBackColor = true; + button1.Click += button1_Click; + // + // button2 + // + button2.Dock = DockStyle.Fill; + button2.Location = new Point(423, 3); + button2.Name = "button2"; + button2.Size = new Size(82, 28); + button2.TabIndex = 1; + button2.Text = "Cancel"; + button2.UseVisualStyleBackColor = true; + button2.Click += button2_Click; + // + // StreamAcquirerPickingForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(514, 392); + Controls.Add(tableLayoutPanel1); + FormBorderStyle = FormBorderStyle.FixedToolWindow; + Name = "StreamAcquirerPickingForm"; + Text = "StreamAcquirerPickingForm"; + tableLayoutPanel1.ResumeLayout(false); + tableLayoutPanel2.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private TableLayoutPanel tableLayoutPanel1; + private ListView listView1; + private ColumnHeader columnHeader1; + private ColumnHeader columnHeader2; + private TableLayoutPanel tableLayoutPanel2; + private Button button1; + private Button button2; + } +} \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.cs b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.cs new file mode 100644 index 0000000..884fc7d --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace skyscraper5.UI.StreamAcquisition +{ + partial class StreamAcquirerPickingForm : Form + { + public StreamAcquirerPickingForm(List streamAcquirers, string[] args) + { + InitializeComponent(); + Args = args; + + foreach (StreamAcquirerListItem streamAcquirer in streamAcquirers) + { + if (streamAcquirer.Hidden) + continue; + + listView1.Items.Add(streamAcquirer); + } + } + + public string[] Args { get; } + public StreamSource StreamSource { get; private set; } + + + private void button1_Click(object sender, EventArgs e) + { + ListViewItem lvi = listView1.SelectedItems[0]; + StreamAcquirerListItem sali = (StreamAcquirerListItem)lvi; + object o = sali.Wrapped.BaseAskUserForHandle(this); + if (o != null) + { + StreamSource resultStream = sali.Wrapped.BaseGetStream(o); + if (resultStream != null) + { + DialogResult = DialogResult.OK; + StreamSource = resultStream; + Close(); + } + } + } + + private void listView1_SelectedIndexChanged(object sender, EventArgs e) + { + button1.Enabled = listView1.SelectedItems.Count > 0; + } + + private void button2_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void listView1_DoubleClick(object sender, EventArgs e) + { + if (listView1.SelectedItems.Count != 1) + return; + + button1_Click(sender, e); + } + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.resx b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamAcquirerPickingForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamSource.cs b/GUIs/skyscraper5.UI/StreamAcquisition/StreamSource.cs new file mode 100644 index 0000000..b395a79 --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamSource.cs @@ -0,0 +1,55 @@ +using skyscraper5.Skyscraper.Scraper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquisition +{ + public abstract class StreamSource + { + protected abstract void StartSourceEx(); + + private bool started; + private Thread thread; + protected SkyscraperContext context; + protected bool stopRequested; + + public virtual string GetSourceName() + { + return this.GetType().Name; + } + + public StreamSourceState State { get; protected set; } + + public Thread StartSource(SkyscraperContext context) + { + if (started) + throw new InvalidOperationException("already started"); + + this.context = context; + this.thread = new Thread(StartSourceEx); + this.thread.Priority = ThreadPriority.Lowest; + this.thread.Name = String.Format("Skyscraper5.UI Source: {0}", GetSourceName()); + started = true; + this.thread.Start(); + this.State = StreamSourceState.Running; + return this.thread; + } + + public void StopSource() + { + stopRequested = true; + } + } + + public enum StreamSourceState + { + Ready, + Running, + Crashed, + Finished, + StoppedByUser + } +} diff --git a/GUIs/skyscraper5.UI/StreamAcquisition/StreamSourceIoStreamWrapper.cs b/GUIs/skyscraper5.UI/StreamAcquisition/StreamSourceIoStreamWrapper.cs new file mode 100644 index 0000000..62e4695 --- /dev/null +++ b/GUIs/skyscraper5.UI/StreamAcquisition/StreamSourceIoStreamWrapper.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.UI.StreamAcquisition +{ + internal class StreamSourceIoStreamWrapper : StreamSource + { + private Stream wrapped; + + public StreamSourceIoStreamWrapper(Stream wrapped, string name) + { + this.wrapped = wrapped; + this.StreamName = name; + } + + public string StreamName { get; private set; } + + public override string GetSourceName() + { + if (string.IsNullOrEmpty(StreamName)) + return base.GetSourceName(); + + return StreamName; + } + + protected override void StartSourceEx() + { + byte[] buffer = new byte[188]; + int result = 0; + do + { + result = wrapped.Read(buffer, 0, buffer.Length); + if (result == buffer.Length) + { + context.IngestSinglePacket(buffer); + } + if (stopRequested) + { + base.State = StreamSourceState.StoppedByUser; + return; + } + } while (result == 188); + base.State = StreamSourceState.Finished; + } + } +} diff --git a/GUIs/skyscraper5.UI/skyscraper5.UI.WindowsForms.csproj b/GUIs/skyscraper5.UI/skyscraper5.UI.WindowsForms.csproj new file mode 100644 index 0000000..90879e7 --- /dev/null +++ b/GUIs/skyscraper5.UI/skyscraper5.UI.WindowsForms.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + + + + + \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/skyscraper5.UI.WindowsForms.csproj.user b/GUIs/skyscraper5.UI/skyscraper5.UI.WindowsForms.csproj.user new file mode 100644 index 0000000..bdbf003 --- /dev/null +++ b/GUIs/skyscraper5.UI/skyscraper5.UI.WindowsForms.csproj.user @@ -0,0 +1,11 @@ + + + + + Form + + + Form + + + \ No newline at end of file diff --git a/GUIs/skyscraper5.UI/skyscraper5.UI.sln b/GUIs/skyscraper5.UI/skyscraper5.UI.sln new file mode 100644 index 0000000..32cd019 --- /dev/null +++ b/GUIs/skyscraper5.UI/skyscraper5.UI.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35931.197 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.UI", "skyscraper5.UI.csproj", "{F90C7577-C32B-415C-97B8-36D11EDD7419}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5", "..\skyscraper5\skyscraper5.csproj", "{FE8BF881-F953-BD10-FAC7-260EDE6119DB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F90C7577-C32B-415C-97B8-36D11EDD7419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F90C7577-C32B-415C-97B8-36D11EDD7419}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F90C7577-C32B-415C-97B8-36D11EDD7419}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F90C7577-C32B-415C-97B8-36D11EDD7419}.Release|Any CPU.Build.0 = Release|Any CPU + {FE8BF881-F953-BD10-FAC7-260EDE6119DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE8BF881-F953-BD10-FAC7-260EDE6119DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE8BF881-F953-BD10-FAC7-260EDE6119DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE8BF881-F953-BD10-FAC7-260EDE6119DB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8BC9D274-FFA4-43EB-9F06-1941C90EE5BD} + EndGlobalSection +EndGlobal diff --git a/GUIs/skyscraper8.UI.ImGui/.gitignore b/GUIs/skyscraper8.UI.ImGui/.gitignore new file mode 100644 index 0000000..0bbe9a7 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/.gitignore @@ -0,0 +1,21 @@ +/bin/Debug/net6.0/*.dll +/bin/Debug/net6.0/SDL2Demo.deps.json +/bin/Debug/net6.0/SDL2Demo.dll.config +/bin/Debug/net6.0/*.exe +/bin/Debug/net6.0/*.pdb +/bin/Debug/net6.0/SDL2Demo.runtimeconfig.json +/bin/Debug/net6.0/runtimes/linux-x64/native/*.so +/bin/Debug/net6.0/runtimes/linux-x86/native/*.so +/bin/Debug/net6.0/runtimes/osx-x64/native/*.dylib +/bin/Debug/net6.0/runtimes/win-x64/native/*.dll +/bin/Debug/net6.0/runtimes/win-x86/native/*.dll +/bin/Debug/net6.0/testdrid.deps.json +/bin/Debug/net6.0/testdrid.dll.config +/bin/Debug/net6.0/testdrid.runtimeconfig.json +/obj/Debug/net6.0 +/obj +/bin/Debug/net6.0/runtimes/osx-universal/native/*.dylib +/bin/Debug/net6.0/imgui.ini +/bin/Debug/net6.0/SDL2-CS.dll.config +/bin/Debug/net6.0/runtimes/osx/native/*.dylib +/bin/Debug/net6.0 diff --git a/GUIs/skyscraper8.UI.ImGui/ByteArrayExtensions.cs b/GUIs/skyscraper8.UI.ImGui/ByteArrayExtensions.cs new file mode 100644 index 0000000..711b6b5 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/ByteArrayExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo +{ + internal static class ByteArrayExtensions + { + private static byte[] buffer = new byte[4]; + [DebuggerStepThrough] + public static uint ReadUInt32BE(this byte[] stream, int offset) + { + buffer[0] = stream[offset + 0]; + buffer[1] = stream[offset + 1]; + buffer[2] = stream[offset + 2]; + buffer[3] = stream[offset + 3]; + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToUInt32(buffer, 0); + } + + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/CharSet.cs b/GUIs/skyscraper8.UI.ImGui/CharSet.cs new file mode 100644 index 0000000..d70289d --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/CharSet.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic.CompilerServices; +using SDL2; +using testdrid.SdlWrapper; + +namespace SkyscraperUI +{ + internal class CharSet + { + public CharSet(Texture texture) + { + _texture = texture; + sourceRect.w = 24; + sourceRect.h = 32; + targetRect.w = 24; + targetRect.h = 32; + FacingDirection = CharSetFacing.Down; + Stepping = CharSetStepping.Stand; + } + + private Texture _texture; + private byte _charNo; + + public byte CharacterNumber + { + get => _charNo; + set + { + if (value > 7) + throw new ArgumentOutOfRangeException(); + _charNo = value; + } + } + + public bool IsMoving { get; set; } + + public CharSetFacing FacingDirection { get; set; } + + public CharSetStepping Stepping { get; set; } + + public int X { get; set; } + + public int Y { get; set; } + + private SDL.SDL_Rect sourceRect; + private SDL.SDL_Rect targetRect; + + private static Point[] charNumberOffsets = new Point[] + { + new Point(0, 0), + new Point(72, 0), + new Point(144, 0), + new Point(216, 0), + new Point(0, 128), + new Point(72, 128), + new Point(144, 128), + new Point(216, 128) + }; + + private static int[] facingOffsets = new int[] { 0, 32, 64, 96 }; + private static int[] steppingOffstes = new[] { 0,24, 48 }; + private static int steppingDelay = 10; + private int steppingRemain; + + private Point home; + + #region AutoMove + + private bool autoMoveUsed; + private int targetX; + private int targetY; + public void AutoMoveTo(Point p) + { + autoMoveUsed = true; + targetX = p.X; + targetY = p.Y; + IsMoving = true; + } + + public bool AutoMoveTargetReached => (targetX == X) && (targetY == Y); + + public bool AutoMoving + { + get => autoMoveUsed; + set => autoMoveUsed = value; + } + #endregion + + #region Home + + public void SetHome(Point p) + { + home = p; + } + + public void AutoMoveToHome() + { + AutoMoveTo(home); + } + + public int GetHomeX() + { + return home.X; + } + + public int GetHomeY() + { + return home.Y; + } + + #endregion + public void Render(Renderer renderer) + { + if (autoMoveUsed) + { + if (targetY > Y) + { + FacingDirection = CharSetFacing.Down; + Y++; + } + else if (targetY < Y) + { + FacingDirection = CharSetFacing.Up; + Y--; + } + else if (targetX > X) + { + FacingDirection = CharSetFacing.Right; + X++; + } + else if (targetX < X) + { + FacingDirection = CharSetFacing.Left; + X--; + } + else + { + FacingDirection = CharSetFacing.Down; + Stepping = CharSetStepping.Stand; + IsMoving = false; + } + } + sourceRect.x = 0; + sourceRect.y = 0; + + sourceRect.x += charNumberOffsets[_charNo].X; + sourceRect.y += charNumberOffsets[_charNo].Y; + sourceRect.x += steppingOffstes[(byte)Stepping]; + sourceRect.y += facingOffsets[(byte)FacingDirection]; + + if (IsMoving) + { + steppingRemain++; + if (steppingRemain == steppingDelay) + { + steppingRemain = 0; + switch (Stepping) + { + case CharSetStepping.Stand: + Stepping = CharSetStepping.RightFootForward; + break; + case CharSetStepping.RightFootForward: + Stepping = CharSetStepping.LeftFootForward; + break; + case CharSetStepping.LeftFootForward: + Stepping = CharSetStepping.Stand; + break; + default: + Stepping = CharSetStepping.Stand; + break; + } + } + } + + targetRect.x = X; + targetRect.y = Y; + renderer.Copy(_texture, ref sourceRect, ref targetRect); + } + } + + enum CharSetFacing : byte + { + Up = 0, + Right = 1, + Down = 2, + Left = 3 + } + + enum CharSetStepping : byte + { + LeftFootForward = 0, + Stand = 1, + RightFootForward = 2 + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Echo/Core/Common/Packed/Float4.cs b/GUIs/skyscraper8.UI.ImGui/Echo/Core/Common/Packed/Float4.cs new file mode 100644 index 0000000..6215f47 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Echo/Core/Common/Packed/Float4.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; + +namespace Echo.Core.Common.Packed +{ + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct Float4 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Float4(in Vector128 v) => this.v = v; + + public readonly Vector128 v; + + public float X + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => v.GetElement(0); + } + + public float Y + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => v.GetElement(1); + } + + public float Z + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => v.GetElement(2); + } + + public float W + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => v.GetElement(3); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + Float4 Shuffle(byte shuffle) => new(Sse.Shuffle(v, v, shuffle)); + + [EditorBrowsableAttribute(EditorBrowsableState.Never), DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)] public Float4 XYXY => Shuffle((0 << 0) | (1 << 2) | (0 << 4) | (1 << 6)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Float4 operator -(in Float4 first, in Float4 second) => new(Sse.Subtract(first.v, second.v)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Float4 Max(in Float4 other) => new(Sse.Max(v, other.v)); + + public static Float4 Zero => (Float4)0f; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator Float4(float value) => new(Vector128.Create(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Float4 Min(in Float4 other) => new(Sse.Min(v, other.v)); + + [EditorBrowsableAttribute(EditorBrowsableState.Never), DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)] public Float4 __ZW => Blend(0b0011); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + Float4 Blend(byte blend) => new(Sse41.Blend(v, Vector128.Zero, blend)); //OPTIMIZE: we can use _mm_insert_ps here to save one instruction + + public Float4 Absoluted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Vector128 mask = Vector128.Create(~0u >> 1); + return new Float4(Sse.And(v, mask.AsSingle())); + } + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Echo/Core/Common/Packed/Int2.cs b/GUIs/skyscraper8.UI.ImGui/Echo/Core/Common/Packed/Int2.cs new file mode 100644 index 0000000..edc3092 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Echo/Core/Common/Packed/Int2.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Echo.Core.Common.Packed +{ + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct Int2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Int2(int x, int y) + { + X = x; + Y = y; + } + public int X { get; } + public int Y { get; } + } +} \ No newline at end of file diff --git a/GUIs/skyscraper8.UI.ImGui/Echo/UserInterface/Backend/ImGuiDevice.cs b/GUIs/skyscraper8.UI.ImGui/Echo/UserInterface/Backend/ImGuiDevice.cs new file mode 100644 index 0000000..c97cb29 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Echo/UserInterface/Backend/ImGuiDevice.cs @@ -0,0 +1,560 @@ +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using Echo.Core.Common.Packed; +using ImGuiNET; +using SDL2; +using SDL2Demo.SdlWrapper; +using testdrid.SdlWrapper; + +namespace Echo.UserInterface.Backend; + +using static SDL; + +/// +/// Implementation reference: +/// https://github.com/ocornut/imgui/blob/e23c5edd5fdef85ea0f5418b1368adb94bf86230/backends/imgui_impl_sdl.cpp +/// https://github.com/ocornut/imgui/blob/e23c5edd5fdef85ea0f5418b1368adb94bf86230/backends/imgui_impl_sdlrenderer.cpp +/// +public sealed unsafe class ImGuiDevice : IDisposable, IEventConsumer +{ + public ImGuiDevice(IntPtr window, IntPtr renderer) + { + this.window = window; + this.renderer = renderer; + io = ImGui.GetIO(); + + //Assign global names and flags + AssignBackendNames(); + io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors; + io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos; + io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; + + //Link viewport data + SDL_SysWMinfo info = default; + SDL_VERSION(out info.version); + + if (SDL_GetWindowWMInfo(window, ref info) == SDL_bool.SDL_TRUE) + { + ImGuiViewportPtr viewport = ImGui.GetMainViewport(); + viewport.PlatformHandleRaw = info.info.win.window; + } + + //Setup SDL hint + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); //Can be nearest, linear, or best + } + + public void Initialize() + { + //Create resources + CreateMouseCursors(); + CreateFontTexture(); + CreateClipboardSetup(); + } + + readonly IntPtr window; + readonly IntPtr renderer; + ImGuiIOPtr io; + + IntPtr[] mouseCursors; + IntPtr fontTexture; + + int mouseButtonDownCount; + int pendingMouseLeaveFrame; + + bool disposed; + + static readonly SetClipboardTextFn SetClipboardText = (_, text) => SDL_SetClipboardText(text); + static readonly GetClipboardTextFn GetClipboardText = _ => SDL_GetClipboardText(); + + public void ProcessEvent(in SDL_Event sdlEvent) + { + switch (sdlEvent.type) + { + case SDL_EventType.SDL_MOUSEMOTION: + { + io.AddMousePosEvent(sdlEvent.motion.x, sdlEvent.motion.y); + break; + } + case SDL_EventType.SDL_MOUSEWHEEL: + { + io.AddMouseWheelEvent(sdlEvent.wheel.x, sdlEvent.wheel.y); + break; + } + case SDL_EventType.SDL_MOUSEBUTTONDOWN: + case SDL_EventType.SDL_MOUSEBUTTONUP: + { + ProcessMouseButtonEvent(sdlEvent.button); + break; + } + case SDL_EventType.SDL_TEXTINPUT: + { + fixed (byte* ptr = sdlEvent.text.text) ImGuiNative.ImGuiIO_AddInputCharactersUTF8(io.NativePtr, ptr); + break; + } + case SDL_EventType.SDL_KEYDOWN: + case SDL_EventType.SDL_KEYUP: + { + ProcessKeyboardEvent(sdlEvent.key); + break; + } + case SDL_EventType.SDL_WINDOWEVENT: + { + ProcessWindowEvent(sdlEvent.window); + break; + } + } + } + + public void NewFrame(in TimeSpan deltaTime) + { + io.DeltaTime = (float)deltaTime.TotalSeconds; + + RefreshDisplaySize(); + UpdateMouseData(); + UpdateMouseCursor(); + } + + public void Render(ImDrawDataPtr data) + { + + //Setup clip information + Vector2 scale = data.FramebufferScale; + Float4 clipSize = Widen(scale * data.DisplaySize); + if (clipSize.X <= 0f || clipSize.Y <= 0f) return; + + SDL_Rect zerocopy = new SDL_Rect(); + //Render + if (SDL_RenderSetScale(renderer, scale.X, scale.Y) != 0) + throw SdlException.GenerateException(); + + if (SDL_RenderSetClipRect(renderer, ref zerocopy) != 0) + throw SdlException.GenerateException(); + + Float4 clipOffset = Widen(data.DisplayPos); + var lists = data.CmdListsRange; + + for (int i = 0; i < lists.Count; i++) + { + ExecuteCommandList(lists[i], clipOffset, clipSize); + } + + // SDL_RenderPresent(renderer); + + static Float4 Widen(Vector2 vector) => new Float4(vector.AsVector128()).XYXY; + } + + public IntPtr CreateTexture(Int2 size, bool streaming, bool bigEndian = false) + { + int access = streaming ? + (int)SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING : + (int)SDL_TextureAccess.SDL_TEXTUREACCESS_STATIC; + uint format = bigEndian ? SDL_PIXELFORMAT_RGBA8888 : SDL_PIXELFORMAT_ABGR8888; + + IntPtr texture = SDL_CreateTexture(renderer, format, access, size.X, size.Y); + if (texture == IntPtr.Zero) + throw SdlException.GenerateException(); + + if (SDL_SetTextureBlendMode(texture, SDL_BlendMode.SDL_BLENDMODE_BLEND) != 0) + throw SdlException.GenerateException(); + + return texture; + } + + public void DestroyTexture(ref IntPtr texture) + { + if (texture == IntPtr.Zero) return; + + SDL_DestroyTexture(texture); + texture = IntPtr.Zero; + } + + public void Dispose() + { + if (disposed) return; + disposed = true; + + DestroyMouseCursors(); + DestroyFontTexture(); + DestroyClipboardSetup(); + io = null; + } + + void AssignBackendNames() + { + if (SDL_GetRendererInfo(renderer, out SDL_RendererInfo info) != 0) + throw SdlException.GenerateException(); + + var name = (Marshal.PtrToStringAnsi(info.name) ?? "unknown").ToUpper(); + var size = new Int2(info.max_texture_width, info.max_texture_height); + + io.NativePtr->BackendPlatformName = (byte*)Marshal.StringToHGlobalAnsi("SDL2 & Dear ImGui for C#"); + io.NativePtr->BackendRendererName = (byte*)Marshal.StringToHGlobalAnsi($"{name} {size.X}x{size.Y}"); + } + + void CreateMouseCursors() + { + mouseCursors = new IntPtr[(int)ImGuiMouseCursor.COUNT]; + + mouseCursors[(int)ImGuiMouseCursor.Arrow] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_ARROW); + mouseCursors[(int)ImGuiMouseCursor.TextInput] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_IBEAM); + mouseCursors[(int)ImGuiMouseCursor.ResizeAll] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZEALL); + mouseCursors[(int)ImGuiMouseCursor.ResizeNS] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENS); + mouseCursors[(int)ImGuiMouseCursor.ResizeEW] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZEWE); + mouseCursors[(int)ImGuiMouseCursor.ResizeNESW] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENESW); + mouseCursors[(int)ImGuiMouseCursor.ResizeNWSE] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENWSE); + mouseCursors[(int)ImGuiMouseCursor.Hand] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_HAND); + mouseCursors[(int)ImGuiMouseCursor.NotAllowed] = SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_NO); + } + + void DestroyMouseCursors() + { + if (mouseCursors == null) return; + + foreach (IntPtr cursor in mouseCursors) + { + if (cursor == IntPtr.Zero) continue; + SDL_FreeCursor(cursor); + } + + mouseCursors = null; + } + + void CreateFontTexture() + { + io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height); + fontTexture = CreateTexture(new Int2(width, height), false); + + if (SDL_UpdateTexture(fontTexture, IntPtr.Zero, pixels, width * sizeof(uint)) != 0) + throw SdlException.GenerateException(); + if (SDL_SetTextureBlendMode(fontTexture, SDL_BlendMode.SDL_BLENDMODE_BLEND) != 0) + throw SdlException.GenerateException(); + if (SDL_SetTextureScaleMode(fontTexture, SDL_ScaleMode.SDL_ScaleModeLinear) != 0) + throw SdlException.GenerateException(); + + io.Fonts.SetTexID(fontTexture); + } + + void DestroyFontTexture() + { + DestroyTexture(ref fontTexture); + io.Fonts.SetTexID(IntPtr.Zero); + } + + void CreateClipboardSetup() + { + io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(SetClipboardText); + io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(GetClipboardText); + } + + void DestroyClipboardSetup() + { + io.SetClipboardTextFn = IntPtr.Zero; + io.GetClipboardTextFn = IntPtr.Zero; + } + + void ProcessMouseButtonEvent(SDL_MouseButtonEvent mouseButtonEvent) + { + int mouseButton = (uint)mouseButtonEvent.button switch + { + SDL_BUTTON_LEFT => 0, + SDL_BUTTON_RIGHT => 1, + SDL_BUTTON_MIDDLE => 2, + SDL_BUTTON_X1 => 3, + SDL_BUTTON_X2 => 4, + _ => -1 + }; + + if (mouseButton < 0) return; + + bool down = mouseButtonEvent.type == SDL_EventType.SDL_MOUSEBUTTONDOWN; + io.AddMouseButtonEvent(mouseButton, down); + mouseButtonDownCount += down ? 1 : -1; + } + + void ProcessKeyboardEvent(in SDL_KeyboardEvent keyboardEvent) + { + ref readonly SDL_Keysym key = ref keyboardEvent.keysym; + bool down = keyboardEvent.type == SDL_EventType.SDL_KEYDOWN; + + io.AddKeyEvent(ImGuiKey.ModCtrl, (key.mod & SDL_Keymod.KMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiKey.ModShift, (key.mod & SDL_Keymod.KMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiKey.ModAlt, (key.mod & SDL_Keymod.KMOD_ALT) != 0); + io.AddKeyEvent(ImGuiKey.ModSuper, (key.mod & SDL_Keymod.KMOD_GUI) != 0); + + io.AddKeyEvent(SDL_KeycodeToImGuiKey(key.sym), down); + } + + public static ImGuiKey SDL_KeycodeToImGuiKey(SDL_Keycode key) => key switch + { + SDL_Keycode.SDLK_TAB => ImGuiKey.Tab, + SDL_Keycode.SDLK_LEFT => ImGuiKey.LeftArrow, + SDL_Keycode.SDLK_RIGHT => ImGuiKey.RightArrow, + SDL_Keycode.SDLK_UP => ImGuiKey.UpArrow, + SDL_Keycode.SDLK_DOWN => ImGuiKey.DownArrow, + SDL_Keycode.SDLK_PAGEUP => ImGuiKey.PageUp, + SDL_Keycode.SDLK_PAGEDOWN => ImGuiKey.PageDown, + SDL_Keycode.SDLK_HOME => ImGuiKey.Home, + SDL_Keycode.SDLK_END => ImGuiKey.End, + SDL_Keycode.SDLK_INSERT => ImGuiKey.Insert, + SDL_Keycode.SDLK_DELETE => ImGuiKey.Delete, + SDL_Keycode.SDLK_BACKSPACE => ImGuiKey.Backspace, + SDL_Keycode.SDLK_SPACE => ImGuiKey.Space, + SDL_Keycode.SDLK_RETURN => ImGuiKey.Enter, + SDL_Keycode.SDLK_ESCAPE => ImGuiKey.Escape, + SDL_Keycode.SDLK_QUOTE => ImGuiKey.Apostrophe, + SDL_Keycode.SDLK_COMMA => ImGuiKey.Comma, + SDL_Keycode.SDLK_MINUS => ImGuiKey.Minus, + SDL_Keycode.SDLK_PERIOD => ImGuiKey.Period, + SDL_Keycode.SDLK_SLASH => ImGuiKey.Slash, + SDL_Keycode.SDLK_SEMICOLON => ImGuiKey.Semicolon, + SDL_Keycode.SDLK_EQUALS => ImGuiKey.Equal, + SDL_Keycode.SDLK_LEFTBRACKET => ImGuiKey.LeftBracket, + SDL_Keycode.SDLK_BACKSLASH => ImGuiKey.Backslash, + SDL_Keycode.SDLK_RIGHTBRACKET => ImGuiKey.RightBracket, + SDL_Keycode.SDLK_BACKQUOTE => ImGuiKey.GraveAccent, + SDL_Keycode.SDLK_CAPSLOCK => ImGuiKey.CapsLock, + SDL_Keycode.SDLK_SCROLLLOCK => ImGuiKey.ScrollLock, + SDL_Keycode.SDLK_NUMLOCKCLEAR => ImGuiKey.NumLock, + SDL_Keycode.SDLK_PRINTSCREEN => ImGuiKey.PrintScreen, + SDL_Keycode.SDLK_PAUSE => ImGuiKey.Pause, + SDL_Keycode.SDLK_KP_0 => ImGuiKey.Keypad0, + SDL_Keycode.SDLK_KP_1 => ImGuiKey.Keypad1, + SDL_Keycode.SDLK_KP_2 => ImGuiKey.Keypad2, + SDL_Keycode.SDLK_KP_3 => ImGuiKey.Keypad3, + SDL_Keycode.SDLK_KP_4 => ImGuiKey.Keypad4, + SDL_Keycode.SDLK_KP_5 => ImGuiKey.Keypad5, + SDL_Keycode.SDLK_KP_6 => ImGuiKey.Keypad6, + SDL_Keycode.SDLK_KP_7 => ImGuiKey.Keypad7, + SDL_Keycode.SDLK_KP_8 => ImGuiKey.Keypad8, + SDL_Keycode.SDLK_KP_9 => ImGuiKey.Keypad9, + SDL_Keycode.SDLK_KP_PERIOD => ImGuiKey.KeypadDecimal, + SDL_Keycode.SDLK_KP_DIVIDE => ImGuiKey.KeypadDivide, + SDL_Keycode.SDLK_KP_MULTIPLY => ImGuiKey.KeypadMultiply, + SDL_Keycode.SDLK_KP_MINUS => ImGuiKey.KeypadSubtract, + SDL_Keycode.SDLK_KP_PLUS => ImGuiKey.KeypadAdd, + SDL_Keycode.SDLK_KP_ENTER => ImGuiKey.KeypadEnter, + SDL_Keycode.SDLK_KP_EQUALS => ImGuiKey.KeypadEqual, + SDL_Keycode.SDLK_LCTRL => ImGuiKey.LeftCtrl, + SDL_Keycode.SDLK_LSHIFT => ImGuiKey.LeftShift, + SDL_Keycode.SDLK_LALT => ImGuiKey.LeftAlt, + SDL_Keycode.SDLK_LGUI => ImGuiKey.LeftSuper, + SDL_Keycode.SDLK_RCTRL => ImGuiKey.RightCtrl, + SDL_Keycode.SDLK_RSHIFT => ImGuiKey.RightShift, + SDL_Keycode.SDLK_RALT => ImGuiKey.RightAlt, + SDL_Keycode.SDLK_RGUI => ImGuiKey.RightSuper, + SDL_Keycode.SDLK_APPLICATION => ImGuiKey.Menu, + SDL_Keycode.SDLK_0 => ImGuiKey._0, + SDL_Keycode.SDLK_1 => ImGuiKey._1, + SDL_Keycode.SDLK_2 => ImGuiKey._2, + SDL_Keycode.SDLK_3 => ImGuiKey._3, + SDL_Keycode.SDLK_4 => ImGuiKey._4, + SDL_Keycode.SDLK_5 => ImGuiKey._5, + SDL_Keycode.SDLK_6 => ImGuiKey._6, + SDL_Keycode.SDLK_7 => ImGuiKey._7, + SDL_Keycode.SDLK_8 => ImGuiKey._8, + SDL_Keycode.SDLK_9 => ImGuiKey._9, + SDL_Keycode.SDLK_a => ImGuiKey.A, + SDL_Keycode.SDLK_b => ImGuiKey.B, + SDL_Keycode.SDLK_c => ImGuiKey.C, + SDL_Keycode.SDLK_d => ImGuiKey.D, + SDL_Keycode.SDLK_e => ImGuiKey.E, + SDL_Keycode.SDLK_f => ImGuiKey.F, + SDL_Keycode.SDLK_g => ImGuiKey.G, + SDL_Keycode.SDLK_h => ImGuiKey.H, + SDL_Keycode.SDLK_i => ImGuiKey.I, + SDL_Keycode.SDLK_j => ImGuiKey.J, + SDL_Keycode.SDLK_k => ImGuiKey.K, + SDL_Keycode.SDLK_l => ImGuiKey.L, + SDL_Keycode.SDLK_m => ImGuiKey.M, + SDL_Keycode.SDLK_n => ImGuiKey.N, + SDL_Keycode.SDLK_o => ImGuiKey.O, + SDL_Keycode.SDLK_p => ImGuiKey.P, + SDL_Keycode.SDLK_q => ImGuiKey.Q, + SDL_Keycode.SDLK_r => ImGuiKey.R, + SDL_Keycode.SDLK_s => ImGuiKey.S, + SDL_Keycode.SDLK_t => ImGuiKey.T, + SDL_Keycode.SDLK_u => ImGuiKey.U, + SDL_Keycode.SDLK_v => ImGuiKey.V, + SDL_Keycode.SDLK_w => ImGuiKey.W, + SDL_Keycode.SDLK_x => ImGuiKey.X, + SDL_Keycode.SDLK_y => ImGuiKey.Y, + SDL_Keycode.SDLK_z => ImGuiKey.Z, + SDL_Keycode.SDLK_F1 => ImGuiKey.F1, + SDL_Keycode.SDLK_F2 => ImGuiKey.F2, + SDL_Keycode.SDLK_F3 => ImGuiKey.F3, + SDL_Keycode.SDLK_F4 => ImGuiKey.F4, + SDL_Keycode.SDLK_F5 => ImGuiKey.F5, + SDL_Keycode.SDLK_F6 => ImGuiKey.F6, + SDL_Keycode.SDLK_F7 => ImGuiKey.F7, + SDL_Keycode.SDLK_F8 => ImGuiKey.F8, + SDL_Keycode.SDLK_F9 => ImGuiKey.F9, + SDL_Keycode.SDLK_F10 => ImGuiKey.F10, + SDL_Keycode.SDLK_F11 => ImGuiKey.F11, + SDL_Keycode.SDLK_F12 => ImGuiKey.F12, + _ => ImGuiKey.None + }; + + void ProcessWindowEvent(in SDL_WindowEvent windowEvent) + { + switch (windowEvent.windowEvent) + { + case SDL_WindowEventID.SDL_WINDOWEVENT_ENTER: + { + pendingMouseLeaveFrame = 0; + break; + } + case SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE: + { + pendingMouseLeaveFrame = ImGui.GetFrameCount() + 1; + break; + } + case SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED: + { + io.AddFocusEvent(true); + break; + } + case SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST: + { + io.AddFocusEvent(false); + break; + } + } + } + + void RefreshDisplaySize() + { + SDL_GetWindowSize(window, out int width, out int height); + var size = io.DisplaySize = new Vector2(width, height); + + if (SDL_GetRendererOutputSize(renderer, out int displayWidth, out int displayHeight) != 0) + throw SdlException.GenerateException(); + + if ((SDL_GetWindowFlags(window) & (uint)SDL_WindowFlags.SDL_WINDOW_MINIMIZED) != 0) size = Vector2.Zero; + if (size.X > 0f && size.Y > 0f) io.DisplayFramebufferScale = new Vector2(displayWidth, displayHeight) / size; + } + + private long nandate = 0; + void UpdateMouseData() + { + if (pendingMouseLeaveFrame >= ImGui.GetFrameCount() && mouseButtonDownCount == 0) + { + io.AddMousePosEvent(float.MinValue, float.MinValue); + pendingMouseLeaveFrame = 0; + } + + if (SDL_CaptureMouse(mouseButtonDownCount != 0 ? SDL_bool.SDL_TRUE : SDL_bool.SDL_FALSE) != 0) + { + nandate++; + //throw SdlException.GenerateException(); + } + + if (window != SDL_GetKeyboardFocus()) return; + if (io.WantSetMousePos) SDL_WarpMouseInWindow(window, (int)io.MousePos.X, (int)io.MousePos.Y); + + if (mouseButtonDownCount == 0) + { + _ = SDL_GetGlobalMouseState(out int globalX, out int globalY); + SDL_GetWindowPosition(window, out int windowX, out int windowY); + + io.AddMousePosEvent(globalX - windowX, globalY - windowY); + } + } + + void UpdateMouseCursor() + { + if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0) return; + + ImGuiMouseCursor cursor = ImGui.GetMouseCursor(); + + if (!io.MouseDrawCursor && cursor != ImGuiMouseCursor.None) + { + IntPtr mouseCursor = mouseCursors[(int)cursor]; + const int Fallback = (int)ImGuiMouseCursor.Arrow; + + if (mouseCursor == IntPtr.Zero) mouseCursor = mouseCursors[Fallback]; + + SDL_SetCursor(mouseCursor); + _ = SDL_ShowCursor((int)SDL_bool.SDL_TRUE); + } + else _ = SDL_ShowCursor((int)SDL_bool.SDL_FALSE); + } + + void ExecuteCommandList(ImDrawListPtr list, in Float4 clipOffset, in Float4 clipSize) + { + ImPtrVector buffer = list.CmdBuffer; + var vertices = (ImDrawVert*)list.VtxBuffer.Data; + var indices = (ushort*)list.IdxBuffer.Data; + + for (int i = 0; i < buffer.Size; i++) + { + ImDrawCmdPtr command = buffer[i]; + + if (command.UserCallback == IntPtr.Zero) + { + var clipRect = new Float4(command.ClipRect.AsVector128()) - clipOffset; + + Float4 clipMin = clipRect.Max(Float4.Zero); //(minX, minY, ____, ____).Max(zero) + Float4 clipMax = clipRect.Min(clipSize); //(____, ____, maxX, maxY).Min(size) + + if (clipMax.Z <= clipMin.X || clipMax.W <= clipMin.Y) continue; + + clipMin = clipMin.XYXY; //(minX, minY, minX, minY) + clipMax = clipMax.__ZW; //(0000, 0000, maxX, maxY) + + Float4 clip = (clipMax - clipMin).Absoluted; //(-minX, -minY, maxX - minX, maxY - minY).Absoluted + var rect = Sse2.ConvertToVector128Int32(clip.v); //( X, Y, width, height) + ImDrawVert* vertex = vertices + command.VtxOffset; + int stride = sizeof(ImDrawVert); + + lock (Renderer.Lockable) + { + if (SDL_RenderGeometryRaw + ( + renderer, command.TextureId, + (float*)&vertex->pos, stride, + (int*)&vertex->col, stride, + (float*)&vertex->uv, stride, + list.VtxBuffer.Size - (int)command.VtxOffset, + (IntPtr)(indices + command.IdxOffset), + (int)command.ElemCount, sizeof(ushort) + ) != 0) + { + throw SdlException.GenerateException(); + } + } + } + else + { + var callback = Marshal.GetDelegateForFunctionPointer(command.UserCallback); + callback(new IntPtr(list), new IntPtr(command)); //Perform user callback, not really used + } + } + } + + [DllImport("SDL2", CallingConvention = CallingConvention.Cdecl)] + public static extern unsafe int SDL_RenderGeometryRaw(IntPtr renderer, + IntPtr texture, + float* xy, + int xy_stride, + int* color, + int color_stride, + float* uv, + int uv_stride, + int num_vertices, + IntPtr indices, + int num_indices, + int size_indices); + + delegate void SetClipboardTextFn(IntPtr _, string text); + delegate string GetClipboardTextFn(IntPtr _); + delegate void ImDrawCallback(IntPtr list, IntPtr cmd); + + public void ProcessEvent(SDL_Event evt) + { + ProcessEvent(in evt); + } +} \ No newline at end of file diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/AboutWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/AboutWindow.cs new file mode 100644 index 0000000..ebb70a5 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/AboutWindow.cs @@ -0,0 +1,50 @@ +using System.Reflection; +using ImGuiNET; +using SDL2; + +namespace SDL2Demo.Forms +{ + internal class AboutWindow : IRenderable + { + private SDL.SDL_version sdlVersion; + + private PortableExecutableKinds peKind; + private ImageFileMachine machine; + + public AboutWindow() + { + typeof(AboutWindow).Module.GetPEKind(out peKind, out machine); + SDL2.SDL.SDL_GetVersion(out sdlVersion); + } + + public void Render() + { + bool open = true; + ImGui.Begin("About skyscraper5", ref open, ImGuiWindowFlags.AlwaysVerticalScrollbar); + ImGui.Text("skyscraper5, written by Feyris-Tan"); + ImGui.Spacing(); + + ImGui.Text(String.Format("Using SDL {0}.{1}.{2}", sdlVersion.major, sdlVersion.minor, sdlVersion.patch)); + ImGui.Text(String.Format("Using Dear ImGUI {0}", ImGui.GetVersion())); + ImGui.Text(String.Format("Using StreamReader.dll by crazycat69")); + ImGui.Spacing(); + + ImGui.Text(String.Format("The currently executing image file is for {0} processors.", machine.ToString())); + ImGui.Text(String.Format("You are running an {0}-bit operating system.", Environment.Is64BitOperatingSystem ? 64 : 32)); + ImGui.Text(String.Format("This is a {0}-bit process.", Environment.Is64BitProcess ? 64 : 32)); + if (Environment.Is64BitProcess) + { + ImGui.Text(String.Format("Please remember that StreamReader.dll requires a 32-bit context.")); + ImGui.Text("Consider using Remote Stream Reader"); + } + + if (ImGui.Button("OK")) + Closed = true; + ImGui.End(); + if (!open) + Closed = true; + } + + public bool Closed { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/BlindscanJobDeleter.cs b/GUIs/skyscraper8.UI.ImGui/Forms/BlindscanJobDeleter.cs new file mode 100644 index 0000000..ccc31d1 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/BlindscanJobDeleter.cs @@ -0,0 +1,102 @@ +using ImGuiNET; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Forms +{ + class BlindscanJobDeleter : IRenderable + { + private readonly IScraperStroage storage; + private List blindscanJobs; + private Guid selectedGuid; + private string tableGuid; + public bool Closed { get; private set; } + + public BlindscanJobDeleter(IScraperStroage storage) + { + this.storage = storage; + this.LoadPastBlindscans(); + this.tableGuid = Guid.NewGuid().ToString(); + } + + private void LoadPastBlindscans() + { + blindscanJobs = storage.GetDbBlindscanJobs().ToList(); + selectedGuid = Guid.Empty; + } + + private void DeleteSelectedJob() + { + storage.DeleteBlindscanJob(selectedGuid); + LoadPastBlindscans(); + } + + + public void Render() + { + bool pOpen = true; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.None; + if (ImGui.Begin("Configure Dish Types", ref pOpen, windowFlags)) + { + ImGui.Text(String.Format("{0} past jobs in DB.", blindscanJobs.Count)); + ImGui.Text(String.Format("Selected for deletion: {0}", selectedGuid.ToString())); + ImGui.SameLine(); + + bool nothingSelected = selectedGuid == Guid.Empty; + + ImGui.BeginDisabled(nothingSelected); + if (ImGui.Button("Perform Deletion")) + DeleteSelectedJob(); + ImGui.EndDisabled(); + + ImGui.Separator(); + + ImGui.BeginTable(tableGuid, 4); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Date"); + + ImGui.TableSetColumnIndex(1); + ImGui.Text("Job"); + + ImGui.TableSetColumnIndex(2); + ImGui.Text("State"); + + foreach(DbBlindscanJob blindscanJob in blindscanJobs) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(blindscanJob.DateAdded.ToString("dd.MM.yyyy hh:mm")); + + ImGui.TableSetColumnIndex(1); + ImGui.Text(blindscanJob.ToString(1)); + + ImGui.TableSetColumnIndex(2); + ImGui.Text(blindscanJob.ToString(2)); + + ImGui.TableSetColumnIndex(3); + ImGui.PushID(blindscanJob.JobGuid.ToString()); + if (ImGui.Button("Delete")) + { + this.selectedGuid = blindscanJob.JobGuid; + } + ImGui.PopID(); + } + + ImGui.EndTable(); + + } + ImGui.End(); + + if (!pOpen) + Closed = true; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/BlindscanProgressWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/BlindscanProgressWindow.cs new file mode 100644 index 0000000..8715bf0 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/BlindscanProgressWindow.cs @@ -0,0 +1,50 @@ +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Forms +{ + + internal class BlindscanProgressWindow : IRenderable + { + public BlindscanProgressWindow() + { + prevFram = DateTime.Now; + currFram = DateTime.Now; + } + private DateTime prevFram, currFram; + public int Start, End, Progress; + + + public void Render() + { + if (ImGui.Begin("Blindscan Progress", ImGuiWindowFlags.AlwaysAutoResize)) + { + prevFram = currFram; + currFram = DateTime.Now; + if (prevFram.Second != currFram.Second) + Progress += 1000; + + int secsRemain = (End / 1000) - (Progress / 1000); + TimeSpan remainTime = new TimeSpan(0, 0, secsRemain + 20); + + double frac = ((double)(Progress - Start) * 100.0) / (double)(End - Start); + ImGui.Text("Progress: "); + ImGui.SameLine(); + ImGui.SetNextItemWidth(100); + ImGui.ProgressBar(Convert.ToSingle(frac) / 100.0f, Vector2.Zero, String.Format("{0:0.##}%", frac)); + + ImGui.Text(String.Format("Estimated Time Remaining: {0}", remainTime.ToString())); + ImGui.Text(String.Format("Starting Frequency: {0} kHz", Start)); + ImGui.Text(String.Format("Scanning Frequency: {0} kHz", Progress)); + ImGui.Text(String.Format("Ending Frequency: {0} kHz", End)); + ImGui.End(); + } + } + } + +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureDishTypes.cs b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureDishTypes.cs new file mode 100644 index 0000000..7bf63f3 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureDishTypes.cs @@ -0,0 +1,135 @@ +using ImGuiNET; +using skyscraper5.Skyscraper.Scraper.Storage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using skyscraper5.Skyscraper.Equipment; + +namespace SDL2Demo.Forms +{ + internal class ConfigureDishTypes : IRenderable + { + private readonly IScraperStroage _scraperStroage; + private List dishTypes; + private string tableUuid; + + private string name; + private int diameter; + private DishShape shape; + public ConfigureDishTypes(IScraperStroage scraperStroage, List dishTypes) + { + _scraperStroage = scraperStroage; + this.dishTypes = dishTypes; + this.tableUuid = Guid.NewGuid().ToString(); + this.name = ""; + } + + + public void Render() + { + bool pOpen = true; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.AlwaysAutoResize; + ImGui.Begin("Configure Dish Types", ref pOpen, windowFlags); + if (justSaved) + { + name = ""; + diameter = 0; + shape = DishShape.Offset; + justSaved = false; + } + if (ImGui.TreeNode("Add Dish Type")) + { + ImGui.InputText("Name", ref name, 255); + ImGui.InputInt("Diameter", ref diameter, 5, 10); + + if (ImGui.BeginCombo("Shape", shape.ToString())) + { + DishShape[] dishShapes = Enum.GetValues(); + foreach (DishShape dishShape in dishShapes) + { + bool isSelected = dishShape == shape; + if (ImGui.Selectable(dishShape.ToString(), isSelected)) + { + shape = dishShape; + } + } + ImGui.EndCombo(); + } + + ImGui.BeginDisabled(!SaveButtonEnabled()); + if (ImGui.Button("Add")) + { + DishType newDish = new DishType(name, diameter, shape); + dishTypes.Add(newDish); + _scraperStroage.UiDishTypesAdd(newDish); + justSaved = true; + } + ImGui.EndDisabled(); + } + + ImGui.Separator(); + + ImGui.BeginTable(tableUuid, 3); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Name"); + + ImGui.TableSetColumnIndex(1); + ImGui.Text("Size"); + + ImGui.TableSetColumnIndex(2); + ImGui.Text("Type"); + + foreach (DishType dishType in dishTypes) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(dishType.Name); + + ImGui.TableSetColumnIndex(1); + ImGui.Text(String.Format("{0} cm", dishType.Diameter)); + + ImGui.TableSetColumnIndex(2); + ImGui.Text(dishType.Shape.ToString()); + } + ImGui.EndTable(); + ImGui.End(); + + if (!pOpen) + Closed = true; + } + + private bool SaveButtonEnabled() + { + if (string.IsNullOrEmpty(name)) + return false; + + if (string.IsNullOrWhiteSpace(name)) + return false; + + if (diameter == 0) + return false; + + if (diameter < 0) + { + diameter = 0; + return false; + } + + foreach (DishType dishType in dishTypes) + { + if (dishType.Name.Equals(name)) + return false; + } + + return true; + } + + public bool Closed { get; private set; } + + private bool justSaved; + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureGpsWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureGpsWindow.cs new file mode 100644 index 0000000..e00f8e4 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureGpsWindow.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Scraper.Storage; +using static SDL2Demo.Forms.ConfigureStorageWindow; + +namespace SDL2Demo.Forms +{ + internal class ConfigureGpsWindow : IRenderable + { + public Ini Ini { get; } + public bool IsOpen; + public bool RequireRestart { get; private set; } + + private int selectedGpsReceiver; + private List settings; + private ReadOnlyDictionary gpsReceivers; + private bool HasChanges; + + public ConfigureGpsWindow(Ini ini) + { + Ini = ini; + IsOpen = true; + selectedGpsReceiver = ini.ReadValue("startup", "gps", 0); + settings = new List(); + gpsReceivers = GpsManager.GetGpsTunerFactories(); + + foreach (KeyValuePair keyValuePair in gpsReceivers) + { + PropertyInfo[] propertyInfos = keyValuePair.Value.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in propertyInfos) + { + Setting s = new Setting(); + s.IniKey = keyValuePair.Key; + s.SettingName = propertyInfo.Name; + s.SettingControlUuid = Guid.NewGuid().ToString(); + switch (propertyInfo.PropertyType.Name) + { + case nameof(String): + s.SettingMode = 1; + s.StringValue = ini.ReadValue(String.Format("gps{0}", s.IniKey), s.SettingName, ""); + break; + case nameof(Boolean): + s.SettingMode = 2; + s.BooleanValue = ini.ReadValue(String.Format("gps{0}", s.IniKey), s.SettingName, false); + break; + case nameof(UInt16): + s.SettingMode = 3; + s.UshortValue = (ushort)ini.ReadValue(String.Format("gps{0}", s.IniKey), s.SettingName, ushort.MinValue); + break; + case nameof(Single): + s.SettingMode = 4; + s.FloatValue = (float)ini.ReadValue(String.Format("gps{0}", s.IniKey), s.SettingName, 0.0f); + break; + case nameof(Int32): + s.SettingMode = 5; + s.IntegerValue = (int)ini.ReadValue(String.Format("gps{0}", s.IniKey), s.SettingName, 0); + break; + default: + throw new NotImplementedException(propertyInfo.PropertyType.Name); + } + + settings.Add(s); + } + } + + } + + private void Save() + { + Ini.WriteValue("startup", "gps", selectedGpsReceiver); + foreach (Setting setting in settings) + { + switch (setting.SettingMode) + { + case 1: + Ini.WriteValue(String.Format("gps{0}", setting.IniKey), setting.SettingName, setting.StringValue); + break; + case 2: + Ini.WriteValue(String.Format("gps{0}", setting.IniKey), setting.SettingName, setting.BooleanValue); + break; + case 3: + Ini.WriteValue(String.Format("gps{0}", setting.IniKey), setting.SettingName, setting.UshortValue); + break; + case 4: + Ini.WriteValue(String.Format("gps{0}", setting.IniKey), setting.SettingName, setting.FloatValue); + break; + case 5: + Ini.WriteValue(String.Format("gps{0}", setting.IniKey), setting.SettingName, setting.IntegerValue); + break; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", setting.SettingMode)); + } + } + + Ini.Export(new FileInfo("skyscraper5.ini")); + + if (HasChanges) + RequireRestart = true; + HasChanges = false; + + } + + private Dictionary keyUuids; + public void Render() + { + float itemWidth = 100.0f; + ImGuiWindowFlags flags = ImGuiWindowFlags.None; + if (HasChanges) + flags |= ImGuiWindowFlags.UnsavedDocument; + + ImGui.Begin("Configure GPS Receiver", ref IsOpen, flags); + + foreach (KeyValuePair keyValuePair in gpsReceivers) + { + if (ImGui.RadioButton(keyValuePair.Value.GetType().Name, ref selectedGpsReceiver, keyValuePair.Key)) + { + HasChanges = true; + } + + if (keyUuids == null) + keyUuids = new Dictionary(); + if (!keyUuids.ContainsKey(keyValuePair.Key)) + keyUuids.Add(keyValuePair.Key, Guid.NewGuid().ToString()); + + ImGui.BeginTable(keyUuids[keyValuePair.Key], 2); + + foreach (Setting setting in settings.Where(x => x.IniKey == keyValuePair.Key)) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(setting.SettingName); + ImGui.TableSetColumnIndex(1); + ImGui.PushID(setting.SettingControlUuid); + switch (setting.SettingMode) + { + case 1: + ImGui.SetNextItemWidth(itemWidth); + if (ImGui.InputText("", ref setting.StringValue, 255)) + HasChanges = true; + break; + case 2: + if (ImGui.Checkbox("", ref setting.BooleanValue)) + HasChanges = true; + break; + case 3: + if (setting.UshortValue < ushort.MinValue) + setting.UshortValue = ushort.MinValue; + if (setting.UshortValue > ushort.MaxValue) + setting.UshortValue = ushort.MaxValue; + ImGui.SetNextItemWidth(itemWidth); + if (ImGui.InputInt("", ref setting.UshortValue)) + HasChanges = true; + break; + case 4: + ImGui.SetNextItemWidth(itemWidth); + if (ImGui.InputFloat("", ref setting.FloatValue, 0.01f, 0.1f)) + HasChanges = true; + break; + case 5: + ImGui.SetNextItemWidth(itemWidth); + if (ImGui.InputInt("", ref setting.IntegerValue, 1, 10)) + HasChanges = true; + break; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", setting.SettingMode)); + } + } + ImGui.PopID(); + ImGui.EndTable(); + } + + bool canApply = CanApply(); + ImGui.BeginDisabled(!canApply); + if (ImGui.Button("OK")) + { + Save(); + IsOpen = false; + } + ImGui.EndDisabled(); + + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + IsOpen = false; + } + + ImGui.SameLine(); + ImGui.BeginDisabled(!canApply); + if (ImGui.Button("Apply")) + { + Save(); + } + ImGui.EndDisabled(); + ImGui.End(); + } + + private bool CanApply() + { + foreach (Setting setting in settings.Where(x => x.IniKey == selectedGpsReceiver)) + { + if (!setting.Validate()) + { + return false; + } + } + + return true; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureLnbTypes.cs b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureLnbTypes.cs new file mode 100644 index 0000000..08df4a5 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureLnbTypes.cs @@ -0,0 +1,156 @@ +using ImGuiNET; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Scraper.Storage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Data; +using System.Xml.Linq; +using skyscraper5.Skyscraper.Equipment; + +namespace SDL2Demo.Forms +{ + internal class ConfigureLnbTypes : IRenderable + { + private readonly IScraperStroage _scraperStroage; + private List lnbTypes; + private string tableUuid; + private string lnbPopupUuid; + + private string name; + private int lof1, lof2, lofSw, minFreq, maxFreq; + + public ConfigureLnbTypes(IScraperStroage scraperStroage, List lnbTypes) + { + _scraperStroage = scraperStroage; + this.lnbTypes = lnbTypes; + this.tableUuid = Guid.NewGuid().ToString(); + this.name = ""; + this.lnbPopupUuid = Guid.NewGuid().ToString(); + } + + public void Render() + { + bool pOpen = true; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.AlwaysAutoResize; + ImGui.Begin("Configure LNB Types", ref pOpen, windowFlags); + if (ImGui.TreeNode("Add LNB Type")) + { + ImGui.InputText("Name", ref name, 255); + + ImGui.InputInt("LOF1", ref lof1, 50, 100); + ImGui.InputInt("LOF2", ref lof2, 50, 100); + ImGui.InputInt("LOF-SW", ref lofSw, 50, 100); + ImGui.InputInt("Min. Freq", ref minFreq, 50, 100); + ImGui.InputInt("Max. Freq", ref maxFreq, 50, 100); + + if (ImGui.Button("Pick Template")) + { + ImGui.OpenPopup(lnbPopupUuid); + } + ImGui.SameLine(); + ImGui.BeginDisabled(!SaveButtonEnabled()); + if (ImGui.Button("Add")) + { + LnbType newLnb = new LnbType(name, lof1, lof2, lofSw, minFreq, maxFreq); + lnbTypes.Add(newLnb); + _scraperStroage.UiLnbTypesAdd(newLnb); + } + ImGui.EndDisabled(); + + if (ImGui.BeginPopup(lnbPopupUuid)) + { + if (ImGui.Selectable("Ku-Band")) + { + lof1 = 9750; + lof2 = 10600; + lofSw = 11700; + minFreq = 10700; + maxFreq = 12750; + } + + if (ImGui.Selectable("C-Band")) + { + lof1 = 5150; + lof2 = 0; + lofSw = 0; + minFreq = 3000; + maxFreq = 4200; + } + + ImGui.EndPopup(); + } + } + + ImGui.Separator(); + + + ImGui.BeginTable(tableUuid, 3); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Name"); + + ImGui.TableSetColumnIndex(1); + ImGui.Text("LOF1/LOF2/LOFSW"); + + ImGui.TableSetColumnIndex(2); + ImGui.Text("Frequency Range"); + foreach (LnbType lnbType in lnbTypes) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(lnbType.Name); + + ImGui.TableSetColumnIndex(1); + ImGui.Text(String.Format("{0}/{1}/{2}", lnbType.Lof1, lnbType.Lof2, lnbType.LofSw)); + + ImGui.TableSetColumnIndex(2); + ImGui.Text(String.Format("{0} -> {1} MHz", lnbType.MinimumFrequency, lnbType.MinimumFrequency)); + } + ImGui.EndTable(); + + + ImGui.End(); + + if (!pOpen) + Closed = true; + } + + private bool SaveButtonEnabled() + { + if (string.IsNullOrEmpty(name)) + return false; + + if (minFreq == 0) + return false; + + if (maxFreq == 0) + return false; + + if (minFreq > maxFreq) + return false; + + if (lof1 == 0) + return false; + + foreach (LnbType lnbType in lnbTypes) + { + if (lnbType.MinimumFrequency == minFreq && + lnbType.MaximumFrequency == maxFreq && + lnbType.Lof1 == lof1 && + lnbType.Lof2 == lof2 && + lnbType.LofSw == lofSw) + return false; + + if (lnbType.Name.Equals(name)) + return false; + } + + return true; + } + + public bool Closed { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureStorage.cs b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureStorage.cs new file mode 100644 index 0000000..ae1be51 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureStorage.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace SDL2Demo.Forms +{ + internal class ConfigureStorageWindow : IRenderable + { + public Ini Ini { get; } + public ReadOnlyCollection> StorageFactories { get; } + + public ConfigureStorageWindow(Ini ini, ReadOnlyCollection> storageFactories) + { + Ini = ini; + StorageFactories = storageFactories; + IsOpen = true; + storageId = ini.ReadValue("startup", "storage", 0); + settings = new List(); + + foreach (KeyValuePair keyValuePair in storageFactories) + { + PropertyInfo[] propertyInfos = keyValuePair.Value.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in propertyInfos) + { + Setting s = new Setting(); + s.IniKey = keyValuePair.Key.Id; + s.SettingName = propertyInfo.Name; + s.SettingControlUuid = Guid.NewGuid().ToString(); + switch (propertyInfo.PropertyType.Name) + { + case nameof(String): + s.SettingMode = 1; + s.StringValue = ini.ReadValue(String.Format("storage{0}", s.IniKey), s.SettingName, ""); + break; + case nameof(Boolean): + s.SettingMode = 2; + s.BooleanValue = ini.ReadValue(String.Format("storage{0}", s.IniKey), s.SettingName, false); + break; + case nameof(UInt16): + s.SettingMode = 3; + s.UshortValue = (ushort)ini.ReadValue(String.Format("storage{0}", s.IniKey), s.SettingName, ushort.MinValue); + break; + default: + throw new NotImplementedException(propertyInfo.PropertyType.Name); + } + + settings.Add(s); + } + } + } + + public class Setting + { + public long IniKey; + public string SettingName; + public int SettingMode; + public string StringValue; + public bool BooleanValue; + public int UshortValue; + public string SettingControlUuid; + public float FloatValue; + public int IntegerValue; + + public bool Validate() + { + switch (SettingMode) + { + case 1: + return !string.IsNullOrEmpty(StringValue) && !string.IsNullOrWhiteSpace(StringValue); + case 2: + return true; + case 3: + return UshortValue >= ushort.MinValue && UshortValue <= ushort.MaxValue; + case 4: + return float.IsFinite(FloatValue); + default: + throw new NotImplementedException(String.Format("Setting mode {0}", SettingMode)); + } + } + } + + public bool IsOpen; + private bool HasChanges; + private int storageId; + private List settings; + public bool RequireRestart; + + private void Save() + { + Ini.WriteValue("startup", "storage", storageId); + foreach (Setting setting in settings) + { + switch (setting.SettingMode) + { + case 1: + Ini.WriteValue(String.Format("storage{0}", setting.IniKey), setting.SettingName, setting.StringValue); + break; + case 2: + Ini.WriteValue(String.Format("storage{0}", setting.IniKey), setting.SettingName, setting.BooleanValue); + break; + case 3: + Ini.WriteValue(String.Format("storage{0}", setting.IniKey), setting.SettingName, setting.UshortValue); + break; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", setting.SettingMode)); + } + } + + Ini.Export(new FileInfo("skyscraper5.ini")); + + if (HasChanges) + RequireRestart = true; + HasChanges = false; + + } + + private bool CanApply() + { + foreach (Setting setting in settings.Where(x => x.IniKey == storageId)) + { + if (!setting.Validate()) + { + return false; + } + } + + return true; + } + + public void Render() + { + ImGuiWindowFlags flags = ImGuiWindowFlags.None; + if (HasChanges) + flags |= ImGuiWindowFlags.UnsavedDocument; + + ImGui.Begin("Configure ScraperStorage", ref IsOpen, flags); + + foreach (KeyValuePair keyValuePair in StorageFactories) + { + if (ImGui.RadioButton(keyValuePair.Key.DisplayName, ref storageId, keyValuePair.Key.Id)) + { + HasChanges = true; + } + + ImGui.BeginTable(keyValuePair.Key.TemporaryUUID, 2); + + foreach (Setting setting in settings.Where(x => x.IniKey == keyValuePair.Key.Id)) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(setting.SettingName); + ImGui.TableSetColumnIndex(1); + ImGui.PushID(setting.SettingControlUuid); + switch (setting.SettingMode) + { + case 1: + if (ImGui.InputText("", ref setting.StringValue, 255)) + HasChanges = true; + break; + case 2: + if (ImGui.Checkbox("", ref setting.BooleanValue)) + HasChanges = true; + break; + case 3: + if (setting.UshortValue < ushort.MinValue) + setting.UshortValue = ushort.MinValue; + if (setting.UshortValue > ushort.MaxValue) + setting.UshortValue = ushort.MaxValue; + if (ImGui.InputInt("", ref setting.UshortValue)) + HasChanges = true; + break; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", setting.SettingMode)); + } + } + ImGui.PopID(); + ImGui.EndTable(); + } + + bool canApply = CanApply(); + ImGui.BeginDisabled(!canApply); + if (ImGui.Button("OK")) + { + Save(); + IsOpen = false; + } + ImGui.EndDisabled(); + + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + IsOpen = false; + } + + ImGui.SameLine(); + ImGui.BeginDisabled(!canApply); + if (ImGui.Button("Apply")) + { + Save(); + } + ImGui.EndDisabled(); + ImGui.End(); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureTunerFactoryWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureTunerFactoryWindow.cs new file mode 100644 index 0000000..a261bb6 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureTunerFactoryWindow.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO.TunerInterface; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace SDL2Demo.Forms +{ + internal class ConfigureTunerFactoryWindow : IRenderable + { + private int tunerFactoryId; + private readonly List settings; + + public Ini Ini { get; } + public ReadOnlyCollection> TunerFactories { get; } + + public ConfigureTunerFactoryWindow(Ini ini, ReadOnlyCollection> tunerFactories) + { + Ini = ini; + TunerFactories = tunerFactories; + Closed = false; + tunerFactoryId = ini.ReadValue("startup", "tunerFactory", 0); + settings = new List(); + + foreach (KeyValuePair keyValuePair in TunerFactories) + { + PropertyInfo[] propertyInfos = keyValuePair.Value.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in propertyInfos) + { + Setting s = new Setting(); + s.IniKey = keyValuePair.Key.Id; + s.SettingName = propertyInfo.Name; + switch (propertyInfo.PropertyType.Name) + { + case nameof(String): + s.SettingMode = 1; + s.StringValue = ini.ReadValue(String.Format("tunerFactory{0}", s.IniKey), s.SettingName, ""); + break; + case nameof(Int32): + s.SettingMode = 2; + s.IntValue = ini.ReadValue(String.Format("tunerFactory{0}", s.IniKey), s.SettingName, 0); + break; + default: + throw new NotImplementedException(propertyInfo.PropertyType.Name); + } + + settings.Add(s); + } + } + } + + internal class Setting + { + public Setting() + { + UiGuid = Guid.NewGuid().ToString(); + } + + public string UiGuid { get; set; } + + public long IniKey { get; set; } + public string SettingName { get; set; } + public int SettingMode { get; set; } + public int IntValue; + + public string StringValue; + + public bool Validate() + { + switch (SettingMode) + { + case 1: + return !string.IsNullOrEmpty(StringValue) && !string.IsNullOrWhiteSpace(StringValue); + case 2: + return true; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", SettingMode)); + } + } + } + + private void Save() + { + Ini.WriteValue("startup", "tunerFactory", tunerFactoryId); + foreach (Setting setting in settings) + { + switch (setting.SettingMode) + { + case 1: + Ini.WriteValue(String.Format("tunerFactory{0}", setting.IniKey), setting.SettingName, setting.StringValue); + break; + case 2: + Ini.WriteValue(String.Format("tunerFactory{0}", setting.IniKey), setting.SettingName, setting.IntValue); + break; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", setting.SettingMode)); + } + } + + Ini.Export(new FileInfo("skyscraper5.ini")); + + if (HasChanges) + RequireRestart = true; + HasChanges = false; + + } + + public bool RequireRestart { get; set; } + + public bool HasChanges { get; set; } + + private bool CanApply() + { + foreach (Setting setting in settings.Where(x => x.IniKey == tunerFactoryId)) + { + if (!setting.Validate()) + { + return false; + } + } + return true; + } + + public void Render() + { + ImGuiWindowFlags flags = ImGuiWindowFlags.None; + if (HasChanges) + flags |= ImGuiWindowFlags.UnsavedDocument; + + bool pOpen = true; + ImGui.Begin("Configure Tuner Factory", ref pOpen, flags); + + foreach (KeyValuePair keyValuePair in TunerFactories) + { + if (ImGui.RadioButton(keyValuePair.Key.DisplayName, ref tunerFactoryId, keyValuePair.Key.Id)) + { + HasChanges = true; + } + + ImGui.BeginTable(keyValuePair.Key.TemporaryUUID, 2); + + foreach (Setting setting in settings.Where(x => x.IniKey == keyValuePair.Key.Id)) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(setting.SettingName); + ImGui.TableSetColumnIndex(1); + ImGui.PushID(setting.UiGuid); + switch (setting.SettingMode) + { + case 1: + if (ImGui.InputText("", ref setting.StringValue, 255)) + HasChanges = true; + break; + case 2: + if (ImGui.InputInt("", ref setting.IntValue,1,10)) + HasChanges = true; + break; + default: + throw new NotImplementedException(String.Format("Setting mode {0}", setting.SettingMode)); + } + ImGui.PopID(); + } + + ImGui.EndTable(); + } + + bool canApply = CanApply(); + ImGui.BeginDisabled(!canApply); + if (ImGui.Button("OK")) + { + Save(); + Closed = true; + } + ImGui.EndDisabled(); + + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + Closed = true; + } + + ImGui.SameLine(); + ImGui.BeginDisabled(!canApply); + if (ImGui.Button("Apply")) + { + Save(); + } + ImGui.EndDisabled(); + + + ImGui.End(); + if (!pOpen) + Closed = true; + } + + public bool Closed { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureTunersWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureTunersWindow.cs new file mode 100644 index 0000000..649ecc7 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/ConfigureTunersWindow.cs @@ -0,0 +1,238 @@ +using ImGuiNET; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace SDL2Demo.Forms +{ + internal class ConfigureTunersWindow : IRenderable + { + private readonly IScraperStroage _scraperStroage; + private List satPositions; + private List lnbTypes; + private List dishTypes; + + public ConfigureTunersWindow(List tuners, IScraperStroage scraperStroage) + { + _scraperStroage = scraperStroage; + Tuners = tuners; + TableGuid = Guid.NewGuid().ToString(); + TableGuid2 = Guid.NewGuid().ToString(); + bool needSats = Tuners.Any(x => x.Type == STD_TYPE.STD_DVBS); + if (needSats) + { + satPositions = _scraperStroage.UiSatellitesListAll(); + lnbTypes = _scraperStroage.UiLnbTypesListAll(); + dishTypes = _scraperStroage.UiDishTypesListAll(); + selectedSatListItems = new int[Tuners.Count][]; + selectedLnbListItems = new int[Tuners.Count][]; + selectedDishListItems = new int[Tuners.Count][]; + selectedDiseqcOptions = new int[Tuners.Count]; + for (int x = 0; x < Tuners.Count; x++) + { + selectedDiseqcOptions[x] = Tuners[x].DiseqcType; + if (Tuners[x].Type == STD_TYPE.STD_DVBS) + { + selectedSatListItems[x] = new int[4]; + selectedLnbListItems[x] = new int[4]; + selectedDishListItems[x] = new int[4]; + for (int y = 0; y < 4; y++) + { + int satelliteKey = Tuners[x].Satellites[y]; + int lnbKey = Tuners[x].Lnbs[y]; + int dishKey = Tuners[x].Dishes[y]; + selectedSatListItems[x][y] = satPositions.FindIndex(x => x.Checksum == satelliteKey); + selectedLnbListItems[x][y] = lnbTypes.FindIndex(x => x.Id == lnbKey); + selectedDishListItems[x][y] = dishTypes.FindIndex(x => x.Id == dishKey); + if (selectedSatListItems[x][y] == -1) + selectedSatListItems[x][y] = 0; + if (selectedLnbListItems[x][y] == -1) + selectedLnbListItems[x][y] = 0; + if (selectedDishListItems[x][y] == -1) + selectedDishListItems[x][y] = 0; + } + } + } + } + } + + + private int[] selectedDiseqcOptions; + private int[][] selectedSatListItems; + private int[][] selectedLnbListItems; + private int[][] selectedDishListItems; + public List Tuners { get; } + + private string TableGuid; + private string TableGuid2; + private bool HasChanges; + + private void Save() + { + for (int x = 0; x < Tuners.Count; x++) + { + if (Tuners[x].Type == STD_TYPE.STD_DVBS) + { + Tuners[x].DiseqcType = selectedDiseqcOptions[x]; + for (int y = 0; y < 4; y++) + { + Tuners[x].Satellites[y] = satPositions[selectedSatListItems[x][y]].Checksum; + Tuners[x].Lnbs[y] = lnbTypes[selectedLnbListItems[x][y]].Id; + Tuners[x].Dishes[y] = dishTypes[selectedDishListItems[x][y]].Id; + } + } + + if (_scraperStroage.UiTunerTestFor(Tuners[x])) + { + _scraperStroage.UiTunerUpdate(Tuners[x]); + } + else + { + _scraperStroage.UiTunerInsert(Tuners[x]); + } + } + HasChanges = false; + } + + public void Render() + { + bool pOpen = true; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.AlwaysAutoResize; + if (HasChanges) + windowFlags |= ImGuiWindowFlags.UnsavedDocument; + ImGui.Begin("Configure Tuners", ref pOpen, windowFlags); + + ImGui.BeginTable(TableGuid, 3, ImGuiTableFlags.Borders); + for (int x = 0; x < Tuners.Count; x++) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(Tuners[x].Name); + ImGui.TableSetColumnIndex(1); + ImGui.Text(Tuners[x].Type.ToString()); + ImGui.TableSetColumnIndex(2); + + if (Tuners[x].Type == STD_TYPE.STD_DVBS) + { + ImGui.PushID(String.Format("{0}_single", x)); + if (ImGui.RadioButton("Single LNB", ref selectedDiseqcOptions[x], 0)) + { + HasChanges = true; + } + ImGui.PopID(); + ImGui.SameLine(); + + ImGui.PushID(String.Format("{0}_tone", x)); + if (ImGui.RadioButton("Tone Burst", ref selectedDiseqcOptions[x], 1)) + { + HasChanges = true; + } + ImGui.PopID(); + ImGui.SameLine(); + + ImGui.PushID(String.Format("{0}_diseqcSwitch", x)); + if (ImGui.RadioButton("DiSEqC 1.0", ref selectedDiseqcOptions[x], 2)) + { + HasChanges = true; + } + ImGui.PopID(); + + int numSats = 0; + switch (selectedDiseqcOptions[x]) + { + case 0: numSats = 1; break; + case 1: numSats = 2; break; + case 2: numSats = 4; break; + default: throw new NotImplementedException(String.Format("{0} {1}", nameof(TunerMetadata.DiseqcType), selectedDiseqcOptions[x])); + } + + for (int y = 0; y < numSats; y++) + { + ImGui.PushID(String.Format("{0}_combo{1}sat", x, y)); + if (ImGui.BeginCombo(String.Format("Port {0}", y + 1), satPositions[selectedSatListItems[x][y]].ToString())) + { + for (int z = 0; z < satPositions.Count; z++) + { + bool isSelected = selectedSatListItems[x][y] == z; + if (ImGui.Selectable(satPositions[z].ToString(), isSelected)) + { + selectedSatListItems[x][y] = z; + HasChanges = true; + } + } + ImGui.EndCombo(); + } + ImGui.PopID(); + + ImGui.PushID(String.Format("{0}_combo{1}lnb", x, y)); + if (ImGui.BeginCombo(String.Format("LNB {0}",y + 1), lnbTypes[selectedLnbListItems[x][y]].ToString())) + { + for (int z = 0; z < lnbTypes.Count; z++) + { + bool isSelected = selectedLnbListItems[x][y] == z; + if (ImGui.Selectable(lnbTypes[z].ToString(), isSelected)) + { + selectedLnbListItems[x][y] = z; + HasChanges = true; + } + } + ImGui.EndCombo(); + } + ImGui.PopID(); + + ImGui.PushID(String.Format("{0}_combo{1}dish", x, y)); + if (ImGui.BeginCombo(String.Format("Dish {0}", y + 1), dishTypes[selectedDishListItems[x][y]].ToString())) + { + for (int z = 0; z < dishTypes.Count; z++) + { + bool isSelected = selectedDishListItems[x][y] == z; + if (ImGui.Selectable(dishTypes[z].ToString(), isSelected)) + { + selectedDishListItems[x][y] = z; + HasChanges = true; + } + } + ImGui.EndCombo(); + } + ImGui.PopID(); + } + } + } + ImGui.EndTable(); + + ImGui.Separator(); + ImGui.BeginTable(TableGuid2, 7); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(3); + if (ImGui.Button("OK")) + { + if (HasChanges) + Save(); + Closed = true; + } + ImGui.TableSetColumnIndex(4); + if (ImGui.Button("Cancel")) + { + Closed = true; + } + ImGui.TableSetColumnIndex(5); + ImGui.BeginDisabled(!HasChanges); + if (ImGui.Button("Apply")) + { + Save(); + } + ImGui.EndDisabled(); + ImGui.EndTable(); + + ImGui.End(); + if (!pOpen) + { + Closed = true; + } + } + + public bool Closed { get; set; } + + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/FoundFrequenciesWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/FoundFrequenciesWindow.cs new file mode 100644 index 0000000..0c4e159 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/FoundFrequenciesWindow.cs @@ -0,0 +1,103 @@ +using ImGuiNET; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static SDL2Demo.Jobs.Blindscan; + +namespace SDL2Demo.Forms +{ + + class FoundFrequenciesWindow : IRenderable + { + private readonly List _blindscanResults; + private readonly STD_TYPE _standard; + private string tableUuid; + private JobContext jobContext; + + public bool zapNowRequested; + public bool allowZapNow; + public bool doNotAutoZap; + internal ulong statusPacketsInTotal; + internal int statusPacketsInqueue; + + public FoundFrequenciesWindow(List blindscanResults, STD_TYPE standard, + JobContext jobContext) + { + _blindscanResults = blindscanResults; + _standard = standard; + tableUuid = Guid.NewGuid().ToString(); + this.jobContext = jobContext; + } + + public void Render() + { + if (this.jobContext.MemorySaverMode) + return; + + if (_blindscanResults.Count == 0) + return; + + if (ImGui.Begin("Blind-Scan Results")) + { + ImGui.Checkbox("Do not Auto-Zap", ref doNotAutoZap); + ImGui.SameLine(); + ImGui.BeginDisabled(!allowZapNow); + if (ImGui.Button("Zap now")) + { + zapNowRequested = true; + } + ImGui.EndDisabled(); + + if (allowZapNow) + { + ImGui.Text(String.Format("{0} packets received in total, {1} queued.", statusPacketsInTotal, statusPacketsInqueue)); + } + + if (ImGui.BeginTable(tableUuid, 5, + ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + for (int i = 0; i < _blindscanResults.Count; i++) + { + BlindscanResult blindscanResult = _blindscanResults[i]; + ImGui.TableNextRow(); + if (_standard == STD_TYPE.STD_DVBS) + { + ImGui.TableSetColumnIndex(0); + ImGui.Text(String.Format("{0} {1} ", blindscanResult.sr1.Freq / 1000, + blindscanResult.sr1.Pol == 0 ? "H" : "V")); + + ImGui.TableSetColumnIndex(1); + ImGui.Text((blindscanResult.sr1.SR / 1000).ToString()); + + ImGui.TableSetColumnIndex(2); + ImGui.Text(((MOD_TYPE)blindscanResult.sr1.ModType).ToString()); + + ImGui.TableSetColumnIndex(3); + ImGui.Text((blindscanResult.sr1.MIS + 1).ToString()); + } + else + { + ImGui.TableSetColumnIndex(0); + ImGui.Text((blindscanResult.sr2.Freq / 1000).ToString()); + + ImGui.TableSetColumnIndex(1); + ImGui.Text((blindscanResult.sr2.SR / 10).ToString()); + + ImGui.TableSetColumnIndex(2); + ImGui.Text(((MOD_TYPE)blindscanResult.sr2.ModType).ToString()); + } + + ImGui.TableSetColumnIndex(4); + ImGui.Text(blindscanResult.State.ToString()); + } + ImGui.EndTable(); + } + ImGui.End(); + } + } + } + +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/GpsDisplay.cs b/GUIs/skyscraper8.UI.ImGui/Forms/GpsDisplay.cs new file mode 100644 index 0000000..1efb33f --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/GpsDisplay.cs @@ -0,0 +1,107 @@ +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Gps; + +namespace SDL2Demo.Forms +{ + internal class GpsDisplay : IRenderable + { + private readonly IGpsReceiver gps; + + public GpsDisplay(IGpsReceiver gps) + { + this.gps = gps; + } + + private string tableUuid; + private PropertyInfo[] nmeaProps; + private Vector2? setWindowSize; + + public bool RenderNmea { get; set; } + public bool HasNmea { get; set; } + + public void Render() + { + if (this.tableUuid == null) + tableUuid = Guid.NewGuid().ToString(); + + if (setWindowSize.HasValue) + { + ImGui.SetNextWindowSize(setWindowSize.Value); + setWindowSize = null; + } + + if (ImGui.Begin("GPS", ImGuiWindowFlags.AlwaysVerticalScrollbar)) + { + float f = ImGui.GetWindowWidth(); + if (f < 310) + { + setWindowSize = new Vector2(310, ImGui.GetWindowHeight()); + } + if (gps.HasLock) + { + ImGui.BeginTable(tableUuid, 2); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Location"); + + ImGui.TableSetColumnIndex(1); + GpsCoordinate gpsCoordinate = gps.Coordinate; + ImGui.Text(String.Format("{0}, {1}", gpsCoordinate.Latitude, gpsCoordinate.Longitude)); + + ImGui.Spacing(); + ImGui.Spacing(); + ImGui.Spacing(); + + HasNmea = gps.ProvidesAdditionalNmeaData; + if (RenderNmea) + { + AdditionalNmeaData nmeaData = gps.AdditionalNmeaData; + if (nmeaData != null) + { + if (nmeaProps == null) + nmeaProps = nmeaData.GetType().GetProperties(); + + foreach (PropertyInfo propertyInfo in nmeaProps) + { + if (propertyInfo.PropertyType.IsArray) + continue; + + if (propertyInfo.Name.Equals(nameof(nmeaData.Satellites))) + continue; + + object? value = propertyInfo.GetValue(nmeaData); + if (value != null) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(propertyInfo.Name); + + ImGui.TableSetColumnIndex(1); + ImGui.Text(value.ToString()); + } + } + } + } + + ImGui.EndTable(); + } + else + { + ImGui.Text("Waiting for lock..."); + } + + + ImGui.End(); + } + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/IqWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/IqWindow.cs new file mode 100644 index 0000000..ef7d107 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/IqWindow.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Echo.Core.Common.Packed; +using Echo.UserInterface.Backend; +using ImGuiNET; +using SDL2; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Forms +{ + internal class IqWindow : IRenderable + { + private readonly Renderer _renderer; + private readonly Texture _texture; + private readonly bool imgui; + private readonly Vector2 _imguiVector; + private bool noDraw; + public void ResetIqGraphic() + { + noDraw = true; + iqData = new byte[256][]; + for (int i = 0; i < iqData.Length; i++) + { + iqData[i] = new byte[256]; + } + noDraw = false; + } + + public IqWindow(Renderer renderer) + { + ResetIqGraphic(); + _renderer = renderer; + _texture = Texture.Create(_renderer, SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, 256, 256); + _texture.SetDrawBlendMode(SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); + imgui = false; + } + + public IqWindow(ImGuiDevice imGuiDevice) + { + ResetIqGraphic(); + IntPtr textureIntPtr = imGuiDevice.CreateTexture(new Int2(256, 256), true, !BitConverter.IsLittleEndian); + _texture = Texture.FromIntPointer(textureIntPtr); + _renderer = null; + _imguiVector = new Vector2(256, 256); + imgui = true; + } + + private byte[][] iqData; + private ulong iqErrors; + + public Point RenderOffset { get; set; } + + public void PushIqSample(byte i, byte q) + { + if (iqData[i][q] == byte.MaxValue) + return; + + iqData[i][q]++; + } + + private void RenderInternal() + { + if (noDraw) + return; + byte b = 0; + try + { + for (int y = 0; y < iqData.Length; y++) + { + if (noDraw) + return; + for (int x = 0; x < iqData[y].Length; x++) + { + if (noDraw) + return; + b = iqData[y][x]; + if (b != 0) + _texture.SetPixel(x, y, 255, (byte)(255 - b), 0, 0); + else + _texture.SetPixel(x, y, 0, 0, 0, 0); + } + } + } + catch (NullReferenceException e) + { + iqErrors++; + } + } + + public void Render() + { + _texture.Lock(); + RenderInternal(); + _texture.Unlock(); + + if (imgui) + { + ImGui.PushStyleVar(ImGuiStyleVar.WindowMinSize, 256); + ImGui.Begin("IQ", ImGuiWindowFlags.NoResize); + ImGui.Image(_texture.Pointer, _imguiVector); + ImGui.End(); + } + else + { + _renderer.Copy(_texture, RenderOffset); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PushIqSamples(sbyte[] iqBuffer) + { + byte i, q; + for (int idx = 0; idx < iqBuffer.Length; idx += 2) + { + i = (byte)(int)(iqBuffer[idx + 0] + sbyte.MaxValue); + q = (byte)(int)(iqBuffer[idx + 1] + sbyte.MaxValue); + PushIqSample(i, q); + } + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/JobDisplay.cs b/GUIs/skyscraper8.UI.ImGui/Forms/JobDisplay.cs new file mode 100644 index 0000000..16a23be --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/JobDisplay.cs @@ -0,0 +1,2089 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Numerics; +using System.Reflection.Metadata.Ecma335; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using SDL2Demo.Net; +using skyscraper5.Docsis; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mhp; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Net; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.Teletext.Wss; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Forms +{ + internal class JobDisplay : IRenderable, ISkyscraperUiJunction + { + + public void Render() + { + if (!memorySaverMode) + { + RenderTableIndex(); + RenderEit(); + RenderSdt(); + RenderPat(); + RenderPmt(); + RenderNit(); + RenderMpe(); + RenderAit(); + RenderDataCarousels(); + RenderWss(); + RenderBat(); + RenderObjectCarousels(); + RenderTot(); + RenderTdt(); + RenderCat(); + RenderScte35(); + RenderDocsis(); + RenderFramegrabs(); + } + } + +#region Table-Index + + private int TsType; + private bool HasPat; + private bool HasPmt; + private bool HasSdt; + private bool HasNit; + private bool HasEit; + private bool HasMpe; + private bool HasAit; + private bool HasDsmCc; + private bool HasWss; + private bool HasBat; + private bool HasTot; + private bool HasTdt; + private bool HasCat; + private bool HasScte35; + private bool ipTrafficContainsDab; + private Process myProcess; + + private void RenderTableIndex() + { + if (ImGui.Begin("Table Overview")) + { + if (myProcess == null) + myProcess = Process.GetCurrentProcess(); + + myProcess.Refresh(); + SafeText(String.Format("Used memory: {0} MB",myProcess.PrivateMemorySize64 / 1000 / 1000)); + + ImGui.BeginDisabled(true); + ImGui.RadioButton("TS", ref TsType, 1); + ImGui.SameLine(); + ImGui.RadioButton("GS", ref TsType, 2); + ImGui.SameLine(); + ImGui.RadioButton("DOCSIS", ref TsType, 3); + ImGui.SameLine(); + ImGui.RadioButton("Blockstream", ref TsType, 4); + + ImGuiStylePtr style = ImGui.GetStyle(); + float window_visible_x2 = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; + float el_width = 100; + if (TsType == 1) + { + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("PAT ", ref HasPat); + float lastButtonX2 = ImGui.GetItemRectMax().X; + float nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("PMT ", ref HasPmt); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("SDT ", ref HasSdt); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("NIT ", ref HasNit); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("EIT ", ref HasEit); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("MPE ", ref HasMpe); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("AIT ", ref HasAit); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("DSM-CC ", ref HasDsmCc); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("WSS ", ref HasWss); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("BAT ", ref HasBat); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("TOT ", ref HasTot); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("TDT ", ref HasTdt); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("CAT ", ref HasCat); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + + ImGui.SetNextItemWidth(el_width); + ImGui.Checkbox("SCTE-35", ref HasScte35); + lastButtonX2 = ImGui.GetItemRectMax().X; + nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width; + if (nextButtonX2 < window_visible_x2) + ImGui.SameLine(); + } + + if (TsType == 2) + { + ImGui.Checkbox("IP", ref HasMpe); + ImGui.Checkbox("SDAB", ref ipTrafficContainsDab); + } + + if (TsType == 3) + { + ImGui.Checkbox("Packets", ref _docsisEnvironment.DocsisEnvironmentStatistics.PacketPduPresent); + ImGui.Checkbox("Special Use", ref _docsisEnvironment.DocsisEnvironmentStatistics.SpecialUseMacPresent); + ImGui.Checkbox("Isolation Packets", ref _docsisEnvironment.DocsisEnvironmentStatistics.IsolationPacketPduPresent); + ImGui.Checkbox("MAC Specific", ref _docsisEnvironment.DocsisEnvironmentStatistics.MacSpecificHeadersPresent); + } + ImGui.EndDisabled(); + ImGui.End(); + } + } + + private void SafeText(string s) + { + if (string.IsNullOrEmpty(s)) + { + ImGui.Text(""); + return; + } + + if (string.IsNullOrWhiteSpace(s)) + { + ImGui.Text("[null]"); + return; + } + + if (s.Contains("% ")) + s = s.Replace("% ", "%% "); + + ImGui.Text(s); + } +#endregion Table-Index + +#region EIT + private struct EitEventCoordinate + { + public DateTime StartTime; + public ushort NetworkId; + public ushort ServiceId; + } + + private struct EitDay + { + public DateTime Date; + public bool Visible; + public Guid tableGuid; + } + + private class EitDayComparer : IComparer + { + public int Compare(EitDay x, EitDay y) + { + return x.Date.CompareTo(y.Date); + } + } + + private class EitEventCoordinateComparer : IComparer + { + public int Compare(EitEventCoordinate x, EitEventCoordinate y) + { + return x.StartTime.Ticks.CompareTo(y.StartTime.Ticks); + } + } + + private SortedList> eitDisplay; + private void RenderEit() + { + if (eitDisplay == null) + return; + + if (ImGui.Begin("Event Information Table")) + { + lock (eitDisplay) + { + foreach (KeyValuePair> keyValuePair in eitDisplay) + { + EitDay day = keyValuePair.Key; + if (ImGui.CollapsingHeader(keyValuePair.Key.Date.ToLongDateString(), ref day.Visible)) + { + if (day.tableGuid == Guid.Empty) + day.tableGuid = Guid.NewGuid(); + ImGui.BeginTable(day.ToString(), 3, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings); + foreach (KeyValuePair valuePair in keyValuePair.Value) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(valuePair.Key.StartTime.ToShortTimeString()); + ImGui.TableSetColumnIndex(1); + string sName = ResolveServiceDisplayName(valuePair.Key.ServiceId); + SafeText(sName); + ImGui.TableSetColumnIndex(2); + SafeText(valuePair.Value); + } + + ImGui.EndTable(); + } + } + } + + ImGui.End(); + } + } + + public void NotifyEvent(EitEvent eitEvent) + { + if (eitDisplay == null) + eitDisplay = new SortedList>(new EitDayComparer()); + + TsType = 1; + HasEit = true; + + EitDay date = new EitDay(); + date.Date = eitEvent.StartTime.Date; + date.Visible = true; + lock (eitDisplay) + { + if (!eitDisplay.ContainsKey(date)) + { + eitDisplay[date] = new SortedList(new EitEventCoordinateComparer()); + } + + SortedList sortedList = eitDisplay[date]; + + EitEventCoordinate coordinate = new EitEventCoordinate(); + coordinate.StartTime = eitEvent.StartTime; + coordinate.NetworkId = eitEvent.OriginalNetworkId; + coordinate.ServiceId = eitEvent.ServiceId; + if (!sortedList.ContainsKey(coordinate)) + { + sortedList.Add(coordinate, eitEvent.EventName); + } + } + } + + #endregion EIT + + #region SDT + + private string ResolveServiceDisplayName(ushort serviceId) + { + if (sdtDisplay == null) + return String.Format("0x{0:X4}", serviceId); + + lock (sdtDisplay) + { + if (sdtDisplay.ContainsKey(serviceId)) + { + string v = sdtDisplay[serviceId].ServiceName; + if (string.IsNullOrEmpty(v)) + return "???"; + else + return v; + } + } + + return String.Format("0x{0:X4}", serviceId); + } + + private SdtCoordinate ResolveService(ushort serviceId) + { + if (sdtDisplay == null) + return new SdtCoordinate(); + + lock (sdtDisplay) + { + if (sdtDisplay.ContainsKey(serviceId)) + { + return sdtDisplay[serviceId]; + } + } + + return new SdtCoordinate(); + } + + private struct SdtCoordinate + { + public string ProviderName; + public string ServiceName; + public bool FreeCaMode; + public ushort[] CaIdentifiers; + public ServiceDescriptor.ServiceTypeCoding? ServiceType; + } + + private SortedList sdtDisplay; + private string sdtTableUuid; + private void RenderSdt() + { + if (sdtDisplay == null) + return; + + if (string.IsNullOrEmpty(sdtTableUuid)) + sdtTableUuid = Guid.NewGuid().ToString(); + + + ImGui.Begin("Service Description Table"); + if (ImGui.BeginTable(sdtTableUuid, 4, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("Service ID"); + ImGui.TableSetupColumn("Name"); + ImGui.TableSetupColumn("Provider"); + ImGui.TableSetupColumn("CA"); + ImGui.TableHeadersRow(); + + lock (sdtDisplay) + { + foreach (KeyValuePair keyValuePair in sdtDisplay) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(String.Format("{0:X4}", keyValuePair.Key)); + + ImGui.TableSetColumnIndex(1); + SafeText(keyValuePair.Value.ServiceName); + + ImGui.TableSetColumnIndex(2); + SafeText(keyValuePair.Value.ProviderName); + + ImGui.TableSetColumnIndex(3); + bool ca = keyValuePair.Value.FreeCaMode; + ImGui.BeginDisabled(true); + ImGui.Checkbox("", ref ca); + ImGui.EndDisabled(); + } + } + + ImGui.EndTable(); + ImGui.End(); + } + } + + public void NotifySdtService(SdtService sdtService) + { + TsType = 1; + HasSdt = true; + if (sdtDisplay == null) + sdtDisplay = new SortedList(); + + if (sdtDisplay.ContainsKey(sdtService.ServiceId)) + return; + + SdtCoordinate child = new SdtCoordinate(); + child.ProviderName = sdtService.ServiceProviderName; + child.ServiceName = sdtService.ServiceName; + child.FreeCaMode = sdtService.FreeCaMode; + child.CaIdentifiers = sdtService.CaIdentifiers; + child.ServiceType = sdtService.ServiceType; + + lock (sdtDisplay) + { + sdtDisplay.Add(sdtService.ServiceId, child); + } + } + + #endregion + + #region PAT + private class PatEntry + { + public ushort programId; + public ProgramMapping pmt; + public string PmtViewUuid; + } + + private SortedList patDisplay; + public void NotifyPatProgram(int pmtPid, ushort programId) + { + HasPat = true; + TsType = 1; + + if (patDisplay == null) + patDisplay = new SortedList(); + + if (patDisplay.ContainsKey(pmtPid)) + return; + + PatEntry patEntry = new PatEntry(); + patEntry.programId = programId; + lock (patDisplay) + { + patDisplay.Add(pmtPid, patEntry); + } + } + + private string patTableUuid; + public void RenderPat() + { + if (patDisplay == null) + return; + + if (string.IsNullOrEmpty(patTableUuid)) + patTableUuid = Guid.NewGuid().ToString(); + if (ImGui.Begin("Program Allocation Table")) + { + if (ImGui.BeginTable(patTableUuid, 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("PMT PID"); + ImGui.TableSetupColumn("Service"); + ImGui.TableHeadersRow(); + + lock (patDisplay) + { + foreach (KeyValuePair keyValuePair in patDisplay) + { + string k = String.Format("0x{0:X4}", keyValuePair.Key); + string v = ResolveServiceDisplayName(keyValuePair.Value.programId); + if (v == null) + v = "???"; + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(k); + + ImGui.TableSetColumnIndex(1); + SafeText(v); + } + } + ImGui.EndTable(); + } + ImGui.End(); + } + } + #endregion + + #region PMT + + public void NotifyPmtProgram(ProgramMapping result, int pmtPid) + { + HasPmt = true; + TsType = 1; + + if (!patDisplay.ContainsKey(pmtPid)) + return; + + PatEntry patEntry = patDisplay[pmtPid]; + if (patEntry.pmt != null) + return; + + patEntry.pmt = result; + } + + private string pmtTabBarUuid; + public void RenderPmt() + { + if (!HasPmt) + return; + + if (string.IsNullOrEmpty(pmtTabBarUuid)) + pmtTabBarUuid = Guid.NewGuid().ToString(); + + if (ImGui.Begin("Program Map Table")) + { + if (ImGui.BeginTabBar(pmtTabBarUuid, ImGuiTabBarFlags.FittingPolicyScroll | ImGuiTabBarFlags.TabListPopupButton)) + { + lock (patDisplay) + { + foreach (KeyValuePair keyValuePair in patDisplay) + { + if (ImGui.BeginTabItem(ResolveServiceDisplayName(keyValuePair.Value.programId))) + { + if (string.IsNullOrEmpty(keyValuePair.Value.PmtViewUuid)) + keyValuePair.Value.PmtViewUuid = Guid.NewGuid().ToString(); + + if (keyValuePair.Value.pmt == null) + continue; + + ProgramMapping pmt = keyValuePair.Value.pmt; + + SafeText(String.Format("Service ID: 0x{0:X4}", pmt.ProgramNumber)); + + ImGui.BeginTable(keyValuePair.Value.PmtViewUuid, 2, ImGuiTableFlags.NoSavedSettings); + ImGui.TableSetupColumn("PID"); + ImGui.TableSetupColumn("Type"); + ImGui.TableHeadersRow(); + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(String.Format("0x{0:X4}", keyValuePair.Key)); + + ImGui.TableSetColumnIndex(1); + SafeText("PMT"); + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(String.Format("0x{0:X4}", keyValuePair.Value.pmt.PcrPid)); + + ImGui.TableSetColumnIndex(1); + SafeText("PCR"); + + foreach (ProgramMappingStream programMappingStream in pmt.Streams) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(String.Format("0x{0:X4}", programMappingStream.ElementaryPid)); + + ImGui.TableSetColumnIndex(1); + SafeText(StreamTypeAsString(programMappingStream,pmt.RegistrationFormatIdentifier)); + } + + ImGui.EndTable(); + ImGui.EndTabItem(); + } + } + } + ImGui.EndTabBar(); + } + ImGui.End(); + } + } + + private string StreamTypeAsString(ProgramMappingStream stream, uint? parentRegistrationFormatIdentifier) + { + if (streamTypes != null) + { + if (streamTypes.ContainsKey(stream.ElementaryPid)) + { + return streamTypes[stream.ElementaryPid]; + } + } + if (stream.StreamType == PmtStreamType.H262) + return "MPEG-2 Video"; + if (stream.AvcStillPresent.HasValue) + return "MPEG-4 Video (still frame)"; + if (stream.AudioType.HasValue) + { + switch (stream.AudioType) + { + case AudioType.CleanEffects: return "Audio (no effects)"; + case AudioType.HearingImpaired: return "Audio (for hearing impaired)"; + case AudioType.VisualImpairedCommentary: return "Audio (for visually impaired)"; + } + } + + if (stream.StreamType == PmtStreamType.AvcVideoStream) + { + return "MPEG-4 Video"; + } + + if (stream.StreamType == PmtStreamType.Iso11172Audio) + { + return "Audio"; + } + + if (stream.StreamType == PmtStreamType.Iso13818_3Audio) + return "MPEG-2 Audio"; + if (stream.StreamType == PmtStreamType.Iso13818_7AudioADTS) + return "AAC Audio"; + if (stream.StreamType == PmtStreamType.HevcVideoStream) + return "H.265 Video"; + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Ac4ChannelMode.HasValue) + return "Dolby AC-4 Audio"; + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.BSID.HasValue) + return "Dolby AC-3 Audio"; + if (stream.StreamType == PmtStreamType.Iso14496_3Audio && stream.AacProfileAndLevel.HasValue) + return "AAC Audio"; + if ((int)stream.StreamType == 0x81 && stream.ComponentType.HasValue) + return "Dolby AC-3 Audio"; + if (stream.Teletexts != null) + return "Teletext"; + + if (stream.Applications != null) + return "Red-button Application"; + + if (stream.DataBroadcastId == 0x0123) + return "Red-button Application"; + + if (stream.DataBroadcastId == 0x000a) + return "System Software Update"; + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Subtitlings != null && stream.Subtitlings.Length > 0) + return "Subtitle"; + + if (stream.DataBroadcastId == 0xf0) + return "MHP Application"; + + + if (stream.FormatIdentifier.HasValue && stream.FormatIdentifier == 0x43554549 && (int)stream.StreamType == 0x86) + return "Advertising Metadata"; + + if ((int)stream.StreamType == 0x86 && parentRegistrationFormatIdentifier.HasValue && parentRegistrationFormatIdentifier.Value == 0x43554549) + return "Advertising Metadata"; + + if (stream.DataBroadcastId == 0x0007) + return "Object Carousel"; + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.VbiData != null) + return "Teletext"; + + if ((byte)stream.StreamType == 0x89 && stream.AncillaryDataDescriptor != null && stream.AncillaryDataDescriptor.RdsOnly) + return "Radio Data Service"; + + if (stream.DataBroadcastId.HasValue && stream.DataBroadcastId.Value == 0x0005 /*&& stream.StreamType == PmtStreamType.Iso13818_6TypeD*/) + return "Multiprotocol Encapsulation"; + + if (stream.DataBroadcastId == 0x0106) + return "MHEG-5"; + + if (stream.DataBroadcastId == 0x000b) + return "IP/MAC Notification"; + + if (stream.RelatedContentDescriptorPresent.HasValue) + if (stream.RelatedContentDescriptorPresent.Value && stream.StreamType == PmtStreamType.Iso13818_1PrivateSections) + return "Related Content Table Information"; + + if (stream.NumT2MiStreams.HasValue && stream.StreamType == PmtStreamType.Iso13818_1PesPackets) + return "T2-MI"; + + return String.Format("??? (Type 0x{0:X2})", (int)stream.StreamType); + } + #endregion + + #region NIT + + private class NitNetworkMeta + { + public ushort ONID; + public string DisplayUUID; + + public override string ToString() + { + return String.Format("Network 0x{0:X4}", ONID); + } + + public bool Equals(NitNetworkMeta other) + { + return ONID == other.ONID; + } + + public override bool Equals(object? obj) + { + return obj is NitNetworkMeta other && Equals(other); + } + + public override int GetHashCode() + { + return ONID.GetHashCode(); + } + } + + private class NitCoordinateComparer : IComparer + { + public int Compare(NitNetworkMeta x, NitNetworkMeta y) + { + return x.ONID.CompareTo(y.ONID); + } + } + + private SortedList> nitDisplay; + private string nitTableUuid; + + public void NotifyNit(NitTransportStream transportStream) + { + TsType = 1; + HasNit = true; + + if (nitDisplay == null) + nitDisplay = new SortedList>(new NitCoordinateComparer()); + + NitNetworkMeta nnm = new NitNetworkMeta(); + nnm.ONID = transportStream.OriginalNetworkId; + if (!nitDisplay.ContainsKey(nnm)) + { + lock (nitDisplay) + { + nitDisplay.Add(nnm, new SortedList()); + } + } + + SortedList nitTransportStreams = nitDisplay[nnm]; + if (nitTransportStreams.ContainsKey(transportStream.TransportStreamId)) + return; + + lock (nitTransportStreams) + { + nitTransportStreams.Add(transportStream.TransportStreamId, transportStream); + } + } + + private string GetFrequencyString(NitTransportStream nts) + { + if (nts.Frequency.HasValue && nts.Polarization.HasValue) + return String.Format("{0} Mhz, {1}", nts.Frequency.Value / 100, nts.Polarization.ToString().Substring(0, 1)); + + return "???"; + } + + private void RenderNit() + { + if (nitDisplay == null) + return; + + if (string.IsNullOrWhiteSpace(nitTableUuid)) + nitTableUuid = Guid.NewGuid().ToString(); + + if (ImGui.Begin("Network Information Table")) + { + lock (nitDisplay) + { + foreach (KeyValuePair> keyValuePair in nitDisplay) + { + if (ImGui.CollapsingHeader(keyValuePair.Key.ToString())) + { + if (string.IsNullOrEmpty(keyValuePair.Key.DisplayUUID)) + keyValuePair.Key.DisplayUUID = Guid.NewGuid().ToString(); + if (ImGui.BeginTable(keyValuePair.Key.DisplayUUID, 4, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + lock (keyValuePair.Value) + { + foreach (KeyValuePair nitTransportStream in keyValuePair + .Value) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(nitTransportStream.Value.DeliveryMethod.ToString().Replace('_', '_')); + + ImGui.TableSetColumnIndex(1); + if (nitTransportStream.Value.OrbitalPosition.HasValue && + nitTransportStream.Value.East.HasValue) + { + SafeText(String.Format("{0:F1} °{1}", + nitTransportStream.Value.OrbitalPosition.Value, + nitTransportStream.Value.East.Value ? "E" : "W")); + } + + ImGui.TableSetColumnIndex(2); + SafeText(GetFrequencyString(nitTransportStream.Value)); + + ImGui.TableSetColumnIndex(3); + if (nitTransportStream.Value.SymbolRate.HasValue) + SafeText(String.Format("{0} ksym/s", + nitTransportStream.Value.SymbolRate / 10)); + } + } + } + ImGui.EndTable(); + } + } + } + ImGui.End(); + } + } + + #endregion + + #region MPE + + private IpTrafficInfoComparer trafficInfoComparer; + private string mpeTableUuid; + private List> mpeDisplays; + public void NotifyMpeTraffic(IpTrafficInfo iti, int ipv4PacketLength) + { + if (trafficInfoComparer == null) + trafficInfoComparer = new IpTrafficInfoComparer(); + HasMpe = true; + if (mpeDisplays == null) + mpeDisplays = new List>(); + + lock (mpeDisplays) + { + foreach (KeyValuePair line in mpeDisplays) + { + if (line.Key.Equals(iti)) + { + line.Value.CountPacket(ipv4PacketLength); + return; + } + } + + KeyValuePair newChild = new KeyValuePair(iti, new IpPerformanceInfo()); + newChild.Value.CountPacket(ipv4PacketLength); + mpeDisplays.Add(newChild); + } + } + + private DateTime prev, next; + private void RenderMpe() + { + if (mpeDisplays == null) + return; + + prev = next; + next = DateTime.Now; + if (prev.Second != next.Second) + { + lock (mpeDisplays) + { + foreach (KeyValuePair line in mpeDisplays) + { + line.Value.UpdatePerSeconds(); + } + } + } + + string windowName = String.Format("{0} Encapsulation", TsType == 1 ? "Multiprotocol" : "Generic Stream"); + if (ImGui.Begin(windowName)) + { + if (mpeDisplays != null) + { + if (mpeDisplays.Count > 0) + { + SafeText(String.Format("Communication parties: {0}", mpeDisplays.Count)); + } + } + if (string.IsNullOrEmpty(mpeTableUuid)) + mpeTableUuid = Guid.NewGuid().ToString(); + if (ImGui.BeginTable(mpeTableUuid, 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings)) + { + ImGui.TableSetupColumn("Protocol"); + ImGui.TableSetupColumn("Source IP"); + ImGui.TableSetupColumn("Destination IP"); + ImGui.TableSetupColumn("Packets"); + ImGui.TableSetupColumn("Traffic"); + ImGui.TableHeadersRow(); + + lock (mpeDisplays) + { + mpeDisplays.Sort(trafficInfoComparer); + foreach (KeyValuePair ipPerformanceInfo in mpeDisplays) + { + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(IpTrafficInfo.GetProtocolName(ipPerformanceInfo.Key.Protocol)); + + ImGui.TableSetColumnIndex(1); + SafeText(RenderIp(ipPerformanceInfo, false)); + + ImGui.TableSetColumnIndex(2); + SafeText(RenderIp(ipPerformanceInfo, true)); + + ImGui.TableSetColumnIndex(3); + SafeText(ipPerformanceInfo.Value.TotalPackets.ToString()); + + ImGui.TableSetColumnIndex(4); + SafeText(String.Format("{0:F1} kb/s", + (double)ipPerformanceInfo.Value.BytesPerSecond / 1000.0)); + } + } + + ImGui.EndTable(); + } + ImGui.End(); + } + } + + private string RenderIp(KeyValuePair ipPerformanceInfo, bool destination) + { + if (!destination) + { + if (string.IsNullOrEmpty(ipPerformanceInfo.Key.SourceName)) + return ipPerformanceInfo.Key.Source.ToString(); + else + return ipPerformanceInfo.Key.SourceName; + } + else + { + if (string.IsNullOrEmpty(ipPerformanceInfo.Key.TargetName)) + return ipPerformanceInfo.Key.Target.ToString(); + else + return ipPerformanceInfo.Key.TargetName; + } + /*if (destination) + { + if (!string.IsNullOrEmpty(ipPerformanceInfo.Value.DestinationResolution)) + { + return ipPerformanceInfo.Value.DestinationResolution; + } + else + { + return ipPerformanceInfo.Key.Target.ToString(); + } + } + else + { + if (!string.IsNullOrEmpty(ipPerformanceInfo.Value.SourceResolution)) + { + return ipPerformanceInfo.Value.SourceResolution; + } + else + { + return ipPerformanceInfo.Key.Source.ToString(); + } + }*/ + } + + public int GseCommunicationParties + { + get + { + if (TsType != 2) + return 0; + + if (mpeDisplays == null) + return 0; + + return mpeDisplays.Count; + } + } + #endregion + + #region AIT + + private SortedList aitDisplay; + private string aitTableUuid; + public void NotifyAit(AitApplication aitApplication) + { + TsType = 1; + HasAit = true; + + if (aitDisplay == null) + aitDisplay = new SortedList(new ApplicationIdentifierComparer()); + + lock (aitDisplay) + { + if (!aitDisplay.ContainsKey(aitApplication.ApplicationIdentifier)) + { + aitDisplay.Add(aitApplication.ApplicationIdentifier, aitApplication); + } + } + } + + private void RenderAit() + { + if (aitDisplay == null) + return; + + if (ImGui.Begin("Application Identification Table")) + { + if (string.IsNullOrEmpty(aitTableUuid)) + aitTableUuid = Guid.NewGuid().ToString(); + + if (ImGui.BeginTable(aitTableUuid, 2,ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("Application Name"); + ImGui.TableSetupColumn("Transport"); + ImGui.TableHeadersRow(); + + lock (aitDisplay) + { + foreach (KeyValuePair keyValuePair in aitDisplay) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(keyValuePair.Value.TryGetName()); + + ImGui.TableSetColumnIndex(1); + foreach (TransportProtocolDescriptor transportProtocolDescriptor in keyValuePair.Value.TransportProtocols) + { + SafeText(transportProtocolDescriptor.Selector.ToString()); + } + } + } + + ImGui.EndTable(); + } + ImGui.End(); + } + } + #endregion + + #region Data Carousel + + private struct DsmCcModuleIdentifier + { + public int pid; + public ushort moduleId; + public byte moduleVersion; + + } + + private sealed class DsmCcModuleIdentifierComparer : IComparer + { + public int Compare(DsmCcModuleIdentifier x, DsmCcModuleIdentifier y) + { + var pidComparison = x.pid.CompareTo(y.pid); + if (pidComparison != 0) return pidComparison; + var moduleIdComparison = x.moduleId.CompareTo(y.moduleId); + if (moduleIdComparison != 0) return moduleIdComparison; + return x.moduleVersion.CompareTo(y.moduleVersion); + } + } + + + private SortedList dsmCcDisplay; + private string objectCarouselTableUuid; + + public void DsmCcModuleAdd(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion) + { + TsType = 1; + HasDsmCc = true; + + if (dsmCcDisplay == null) + dsmCcDisplay = new SortedList(new DsmCcModuleIdentifierComparer()); + + DsmCcModuleIdentifier id = new DsmCcModuleIdentifier(); + id.pid = elementaryPid; + id.moduleId = moduleInfoModuleId; + id.moduleVersion = moduleInfoModuleVersion; + lock (dsmCcDisplay) + { + dsmCcDisplay.Add(id, 0); + } + } + + public void DsmCcModuleProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, double progress) + { + TsType = 1; + HasDsmCc = true; + + if (dsmCcDisplay == null) + return; + + DsmCcModuleIdentifier id = new DsmCcModuleIdentifier(); + id.pid = elementaryPid; + id.moduleId = moduleInfoModuleId; + id.moduleVersion = moduleInfoModuleVersion; + if (!dsmCcDisplay.ContainsKey(id)) + return; + lock (dsmCcDisplay) + { + dsmCcDisplay[id] = progress; + } + } + + public void DsmCcModuleComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + TsType = 1; + HasDsmCc = true; + + if (dsmCcDisplay == null) + return; + + DsmCcModuleIdentifier id = new DsmCcModuleIdentifier(); + id.pid = elementaryPid; + id.moduleId = moduleModuleId; + id.moduleVersion = moduleModuleVersion; + lock (dsmCcDisplay) + { + dsmCcDisplay.Remove(id); + } + } + + private void RenderDataCarousels() + { + if (dsmCcDisplay == null) + return; + if (dsmCcDisplay.Count == 0) + return; + + if (ImGui.Begin("Digital Storage Media Command & Control, Data Carousels")) + { + if (string.IsNullOrEmpty(objectCarouselTableUuid)) + { + objectCarouselTableUuid = Guid.NewGuid().ToString(); + } + + if (ImGui.BeginTable(objectCarouselTableUuid, 4, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("PID"); + ImGui.TableSetupColumn("Module ID"); + ImGui.TableSetupColumn("Version"); + ImGui.TableSetupColumn("Progress"); + ImGui.TableHeadersRow(); + + lock (dsmCcDisplay) + { + foreach (KeyValuePair keyValuePair in dsmCcDisplay) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(String.Format("{0}", keyValuePair.Key.pid)); + + ImGui.TableSetColumnIndex(1); + SafeText(String.Format("{0}", keyValuePair.Key.moduleId)); + + ImGui.TableSetColumnIndex(2); + SafeText(String.Format("{0}", keyValuePair.Key.moduleVersion)); + + ImGui.TableSetColumnIndex(3); + ImGui.SetNextItemWidth(100); + ImGui.ProgressBar(Convert.ToSingle(keyValuePair.Value) / 100.0f, Vector2.Zero, String.Format("{0}%", keyValuePair.Value)); + } + } + ImGui.EndTable(); + } + ImGui.End(); + } + + } + + #endregion + + #region WSS + + private string wssTableUuid; + private SortedList wssDisplay; + public void NotifyWss(ushort programNumber, WssDataBlock wssDataBlock) + { + HasWss = true; + TsType = 1; + + if (wssDisplay == null) + wssDisplay = new SortedList(); + + if (!wssDisplay.ContainsKey(programNumber)) + { + lock (wssDisplay) + { + wssDisplay.Add(programNumber, wssDataBlock); + } + } + } + + private void RenderWss() + { + if (wssDisplay == null) + return; + + if (ImGui.Begin("Wide Screen Signalling")) + { + if (String.IsNullOrEmpty(wssTableUuid)) + wssTableUuid = Guid.NewGuid().ToString(); + + if (ImGui.BeginTable(wssTableUuid, 7, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings)) + { + ImGui.TableSetupColumn("Service"); + ImGui.TableSetupColumn("Aspect Ratio"); + ImGui.TableSetupColumn("Film Mode"); + ImGui.TableSetupColumn("Motion Adaptive Color"); + ImGui.TableSetupColumn("Subtitle"); + ImGui.TableSetupColumn("Surround Sound"); + ImGui.TableSetupColumn("Copy restricted"); + ImGui.TableHeadersRow(); + + lock (wssDisplay) + { + foreach (KeyValuePair wssDataBlock in wssDisplay) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(ResolveServiceDisplayName(wssDataBlock.Key)); + + ImGui.TableSetColumnIndex(1); + SafeText(wssDataBlock.Value.AspectRatioToString()); + + ImGui.TableSetColumnIndex(2); + ImGui.BeginDisabled(true); + bool b = wssDataBlock.Value.FilmMode; + ImGui.Checkbox("", ref b); + + ImGui.TableSetColumnIndex(3); + b = wssDataBlock.Value.MotionAdaptiveColorPlus; + ImGui.Checkbox("", ref b); + + ImGui.TableSetColumnIndex(4); + b = wssDataBlock.Value.SubtitlesWithinTeletext; + ImGui.Checkbox("", ref b); + + ImGui.TableSetColumnIndex(5); + b = wssDataBlock.Value.SurroundSound; + ImGui.Checkbox("", ref b); + + ImGui.TableSetColumnIndex(6); + b = wssDataBlock.Value.CopyingRestricted; + ImGui.Checkbox("", ref b); + ImGui.EndDisabled(); + } + } + + ImGui.EndTable(); + } + ImGui.End(); + } + } + #endregion + + #region Stream Type Autodetection + + private Dictionary streamTypes; + + public void NotifyStreamTypeDetection(string contestantTag, int pid) + { + if (streamTypes == null) + streamTypes = new Dictionary(); + streamTypes[pid] = contestantTag; + } + + + + #endregion + + #region BAT + + private struct DisplayableBat + { + public string Name; + public List Linkages; + public string TableUuid; + public HashSet TransportStreams; + } + + private SortedList batDisplay; + + public void NotifyBat(BatBouquet batBouquet) + { + TsType = 1; + HasBat = true; + + if (batDisplay == null) + batDisplay = new SortedList(); + + if (batDisplay.ContainsKey(batBouquet.BouquetId)) + return; + + DisplayableBat child = new DisplayableBat(); + child.Name = batBouquet.TryGetName(); + child.Linkages = batBouquet.Linkages; + child.TableUuid = Guid.NewGuid().ToString(); + child.TransportStreams = new HashSet(); + + lock (batDisplay) + { + batDisplay[batBouquet.BouquetId] = child; + } + } + + public void NotifyBatTs(ushort batBouquetBouquetId, BatTransportStream child) + { + TsType = 1; + HasBat = true; + + if (batDisplay == null) + return; + if (!batDisplay.ContainsKey(batBouquetBouquetId)) + return; + + batDisplay[batBouquetBouquetId].TransportStreams.Add(child); + } + + private void RenderBat() + { + if (batDisplay == null) + return; + + if (batDisplay.Count == 0) + return; + + if (ImGui.Begin("Bouquet Association Table")) + { + lock (batDisplay) + { + foreach (KeyValuePair keyValuePair in batDisplay) + { + if (ImGui.CollapsingHeader(String.Format("Bouquet #{0} - {1}", keyValuePair.Key, keyValuePair.Value.Name))) + { + if (ImGui.BeginTable(keyValuePair.Value.TableUuid, 5, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("L/TS"); + ImGui.TableSetupColumn("ONID"); + ImGui.TableSetupColumn("TSID"); + ImGui.TableSetupColumn("Service"); + ImGui.TableSetupColumn(""); + ImGui.TableHeadersRow(); + + lock (keyValuePair.Value.Linkages) + { + foreach (LinkageDescriptor linkageDescriptor in keyValuePair.Value.Linkages) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText("Link"); + + ImGui.TableSetColumnIndex(1); + SafeText(String.Format("{0:X4}",linkageDescriptor.OriginalNetworkId)); + + ImGui.TableSetColumnIndex(2); + SafeText(String.Format("{0:X4}", linkageDescriptor.TransportStreamId)); + + ImGui.TableSetColumnIndex(3); + SafeText(ResolveServiceDisplayName(linkageDescriptor.ServiceId)); + + ImGui.TableSetColumnIndex(4); + SafeText(linkageDescriptor.LinkageType.ToString()); + } + } + + lock (keyValuePair.Value.TransportStreams) + { + foreach (BatTransportStream valueTransportStream in keyValuePair.Value.TransportStreams) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText("TS"); + + ImGui.TableSetColumnIndex(1); + SafeText(String.Format("{0:X4}", valueTransportStream.OriginalNetworkId)); + + ImGui.TableSetColumnIndex(2); + SafeText(String.Format("{0:X4}", valueTransportStream.TransportStreamId)); + + if (valueTransportStream.ServiceList != null) + { + foreach (ServiceListDescriptor.Service service in valueTransportStream.ServiceList) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText("Service"); + + ImGui.TableSetColumnIndex(3); + SafeText(ResolveServiceDisplayName(service.ServiceId)); + + ImGui.TableSetColumnIndex(4); + SafeText(service.ServiceType.ToString()); + } + } + } + } + ImGui.EndTable(); + } + } + } + } + ImGui.End(); + } + } + + #endregion + + #region Object Carousel + + private SortedList displayObjectCarouselRoots; + + private string GetServiceNameFromPid(int pid) + { + lock (patDisplay) + { + foreach (KeyValuePair keyValuePair in patDisplay) + { + PatEntry patEntry = keyValuePair.Value; + if (patEntry.pmt == null) + continue; + + foreach (ProgramMappingStream programMappingStream in patEntry.pmt.Streams) + { + if (programMappingStream.ElementaryPid == pid) + { + return ResolveServiceDisplayName(patEntry.programId); + } + } + } + } + + return "???"; + } + + public void DsmCcVfs(VfsFile vfsFile) + { + TsType = 1; + HasDsmCc = true; + if (displayObjectCarouselRoots == null) + displayObjectCarouselRoots = new SortedList(); + if (displayObjectCarouselRoots.ContainsKey(vfsFile.SourcePid)) + return; + + VfsDirectory masterDirectory = vfsFile.ParentDirectory; + while (masterDirectory.ParentDirectory != null) + masterDirectory = masterDirectory.ParentDirectory; + + lock (displayObjectCarouselRoots) + { + displayObjectCarouselRoots.Add(vfsFile.SourcePid, masterDirectory); + } + } + + private void RenderObjectCarousels() + { + if (displayObjectCarouselRoots == null) + return; + + if (ImGui.Begin("Digital Storage Media - Command & Control, Object Carousels")) + { + lock (displayObjectCarouselRoots) + { + foreach (KeyValuePair displayObjectCarouselRoot in displayObjectCarouselRoots) + { + string name = String.Format("Object Carousel in PID 0x{1:X4} (Service: {0})", GetServiceNameFromPid(displayObjectCarouselRoot.Key), displayObjectCarouselRoot.Key); + if (ImGui.CollapsingHeader(name)) + { + RenderDirectoryNode(displayObjectCarouselRoot.Value); + } + } + } + } + } + + private void RenderDirectoryNode(VfsDirectory directory) + { + if (!directory.SkyscrpaerUiData.ContainsKey("UUID")) + directory.SkyscrpaerUiData.Add("UUID", Guid.NewGuid().ToString()); + + string dname = directory.Name; + if (string.IsNullOrEmpty(dname) && directory.ParentDirectory == null) + dname = ""; + + if (ImGui.TreeNode(directory.SkyscrpaerUiData["UUID"].ToString(), dname)) + { + foreach (VfsDirectory subdirectory in directory.subdirectories) + { + RenderDirectoryNode(subdirectory); + } + + foreach (VfsFile directoryFile in directory.files) + { + SafeText(directoryFile.Name); + } + + foreach (VfsEvent directoryEvent in directory.events) + { + SafeText(directoryEvent.Name); + } + ImGui.TreePop(); + } + } + + #endregion + + #region TOT + + private DateTime? totTime; + private LocalTimeOffsetDescriptor totDescriptor; + private string totDisplayUuid; + + public void NotifyTot(DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + TsType = 1; + HasTot = true; + + totTime = utcTime; + totDescriptor = ltod; + } + + private void RenderTot() + { + if (!totTime.HasValue) + return; + + if (ImGui.Begin("Time Offset Table")) + { + SafeText(totTime.ToString()); + + if (totDescriptor != null) + { + if (totDescriptor.Valid) + { + if (string.IsNullOrEmpty(totDisplayUuid)) + totDisplayUuid = Guid.NewGuid().ToString(); + + if (ImGui.BeginTable(totDisplayUuid, 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings)) + { + ImGui.TableSetupColumn("Region"); + ImGui.TableSetupColumn("Polarity"); + ImGui.TableSetupColumn("Offset"); + ImGui.TableHeadersRow(); + + foreach (LocalTimeOffsetDescriptor.LocalTime totDescriptorLocalTimeOffset in totDescriptor.LocalTimeOffsets) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(totDescriptorLocalTimeOffset.CountryCode); + + ImGui.TableSetColumnIndex(1); + bool b = totDescriptorLocalTimeOffset.LocalTimeOffsetPolarity; + ImGui.BeginDisabled(true); + ImGui.Checkbox("", ref b); + ImGui.EndDisabled(); + + ImGui.TableSetColumnIndex(2); + SafeText(totDescriptorLocalTimeOffset.LocalTimeOffset.ToString()); + } + ImGui.EndTable(); + } + } + } + ImGui.End(); + } + } + + #endregion + + #region TDT + + private DateTime? tdtDisplay; + public void NotifyTdt(DateTime utcTime) + { + TsType = 1; + HasTdt = true; + tdtDisplay = utcTime; + } + + + + private void RenderTdt() + { + if (tdtDisplay.HasValue) + { + if (ImGui.Begin("Time and Date Table")) + { + SafeText(tdtDisplay.Value.ToString()); + ImGui.End(); + } + } + } + + #endregion + + #region CAT + private string caTableUuid; + private HashSet caDisplay; + public void NotifyCat(CaDescriptor caDescriptor) + { + TsType = 1; + HasCat = true; + + if (caDisplay == null) + caDisplay = new HashSet(); + + lock (caDisplay) + { + caDisplay.Add(caDescriptor); + } + } + private void RenderCat() + { + if (caDisplay == null) + return; + + if (ImGui.Begin("Conditional Access Table")) + { + if (string.IsNullOrEmpty(caTableUuid)) + caTableUuid = Guid.NewGuid().ToString(); + + if (ImGui.BeginTable(caTableUuid, 3, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("CA System"); + ImGui.TableSetupColumn("ECM/EMM PID"); + ImGui.TableSetupColumn("Private Data"); + ImGui.TableHeadersRow(); + lock (caDisplay) + { + foreach (CaDescriptor caDescriptor in caDisplay) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(CaSystemNames.GetHumanReadableName(caDescriptor.CaSystemId)); + + ImGui.TableSetColumnIndex(1); + SafeText(String.Format("{0:X4}", caDescriptor.CaPid)); + + ImGui.TableSetColumnIndex(2); + if (caDescriptor.PrivateData != null) + { + if (caDescriptor.PrivateData.Length != 0) + SafeText(BitConverter.ToString(caDescriptor.PrivateData)); + } + } + } + ImGui.EndTable(); + } + ImGui.End(); + } + } + #endregion CAT + + #region SCTE-35 + class Scte35DisplayData + { + private const string NONE = ""; + + public SpliceInsert LastSplice { get; set; } + public TimeSignal TimeSignal { get; set; } + + public string LastSpliceToString() + { + if (LastSplice == null) + return NONE; + + if (!LastSplice.SpliceTime.HasValue) + return NONE; + + return String.Format("0x{0:X12}", LastSplice.SpliceTime.Value); + } + + public string TimeSignalToString() + { + if (TimeSignal == null) + return NONE; + + if (!TimeSignal.Value.HasValue) + return NONE; + + return String.Format("0x{0:X12}", TimeSignal.Value.Value); + } + } + + private SortedList _scte35DisplayDatas; + private string scte35DisplayTableId; + + private Scte35DisplayData GetScte35DisplayData(ushort program) + { + if (_scte35DisplayDatas == null) + _scte35DisplayDatas = new SortedList(); + + scte35DisplayTableId = Guid.NewGuid().ToString(); + + if (_scte35DisplayDatas.ContainsKey(program)) + { + return _scte35DisplayDatas[program]; + } + else + { + Scte35DisplayData result = new Scte35DisplayData(); + lock (_scte35DisplayDatas) + { + _scte35DisplayDatas.Add(program, result); + } + return result; + } + } + + public void NotifyScte35(ushort programNumber, SpliceInsert spliceInsert) + { + TsType = 1; + HasScte35 = true; + Scte35DisplayData mod = GetScte35DisplayData(programNumber); + mod.LastSplice = spliceInsert; + } + + public void NotifyScte35(ushort programNumber, TimeSignal spliceInsert) + { + TsType = 1; + HasScte35 = true; + Scte35DisplayData mod = GetScte35DisplayData(programNumber); + mod.TimeSignal = spliceInsert; + } + + private bool memorySaverMode; + private readonly JobContext jobContext; + + public JobDisplay(JobContext jobContext) + { + this.jobContext = jobContext; + } + + public void SetMemorySaverMode(bool saveMemory) + { + this.memorySaverMode = saveMemory; + this.jobContext.MemorySaverMode = saveMemory; + } + + + public void RenderScte35() + { + if (_scte35DisplayDatas == null) + return; + + if (ImGui.Begin("SCTE-35 Cue Messages")) + { + if (ImGui.BeginTable(scte35DisplayTableId, 3, + ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("Service"); + ImGui.TableSetupColumn("Time Signal"); + ImGui.TableSetupColumn("Last Splice"); + ImGui.TableHeadersRow(); + + lock (_scte35DisplayDatas) + { + foreach (KeyValuePair scte35DisplayData in _scte35DisplayDatas) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(ResolveServiceDisplayName(scte35DisplayData.Key)); + + ImGui.TableSetColumnIndex(1); + SafeText(scte35DisplayData.Value.TimeSignalToString()); + + ImGui.TableSetColumnIndex(2); + SafeText(scte35DisplayData.Value.LastSpliceToString()); + } + } + ImGui.EndTable(); + } + ImGui.End(); + } + } + #endregion + + #region DOCSIS + private DocsisEnvironment _docsisEnvironment; + public void NotifyDocsisCarrier(DocsisEnvironment docsisEnvironment) + { + _docsisEnvironment = docsisEnvironment; + TsType = 3; + } + + private void RenderDocsis() + { + if (_docsisEnvironment == null) + return; + + RenderDocsisParticipants(); + RenderDocsisFrequencies(); + } + + private DateTime prevStamp, currStamp; + private void RenderDocsisParticipants() + { + prevStamp = currStamp; + currStamp = DateTime.Now; + if (currStamp.Second != prevStamp.Second) + { + _docsisEnvironment.DocsisEnvironmentStatistics.ParticipantStatistics.Sort((x, y) => y.Throughput.CompareTo(x.Throughput)); + _docsisEnvironment.DocsisEnvironmentStatistics.CaptureSecond(); + } + + ImGui.Begin("DOCSIS Participant infos"); + SafeText(String.Format("Total scrambled packets: {0} ({1} p/s)", _docsisEnvironment.DocsisEnvironmentStatistics.ScrambledPacketsTotal,_docsisEnvironment.DocsisEnvironmentStatistics.ScrambledPacketsPerSecond)); + SafeText(String.Format("Total clear packets: {0} ({1} p/s)", _docsisEnvironment.DocsisEnvironmentStatistics.UnscrambledPacketsTotal,_docsisEnvironment.DocsisEnvironmentStatistics.UnscrambledPacketsPerSecond)); + SafeText(String.Format("Current bandwidth: {0:F1} mb/s", (float)_docsisEnvironment.DocsisEnvironmentStatistics.ThroughputPerSecond / 1000000.0f)); + + ImGui.BeginTable("DOCSIS Participant Index", 6, ImGuiTableFlags.Resizable); + + ImGui.TableSetupColumn("MAC"); + ImGui.TableSetupColumn("IP"); + ImGui.TableSetupColumn("Manufacturer"); + ImGui.TableSetupColumn("Packet/s"); + ImGui.TableSetupColumn("Bandwidth used"); + ImGui.TableSetupColumn("Cumulative usage"); + ImGui.TableHeadersRow(); + + lock (_docsisEnvironment.DocsisEnvironmentStatistics.ParticipantStatistics) + { + foreach (DocsisEnvironment.Statistics.ParticipantStatistic participantStatistic in _docsisEnvironment.DocsisEnvironmentStatistics.ParticipantStatistics) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(participantStatistic.Address.ToString()); + + ImGui.TableSetColumnIndex(1); + SafeText(participantStatistic.Ip?.ToString()); + + ImGui.TableSetColumnIndex(2); + SafeText(GetParticipantManufacturer(participantStatistic)); + + ImGui.TableSetColumnIndex(3); + SafeText(participantStatistic.PacketsPerSecond.ToString()); + + ImGui.TableSetColumnIndex(4); + SafeText(String.Format("{0:F1} kb/s", (double)participantStatistic.ThroughputPerSecond / 1000.0)); + + ImGui.TableSetColumnIndex(5); + double percent = ((double)participantStatistic.ThroughputTotal * 100.0) / + (double)_docsisEnvironment.DocsisEnvironmentStatistics.ThroughputTotal; + SafeText(String.Format("{0:F1} %%", percent)); + } + } + ImGui.EndTable(); + ImGui.End(); + } + + private bool dontBotherWithOuis; + private Dictionary ouiLookupTable; + private string GetParticipantManufacturer(DocsisEnvironment.Statistics.ParticipantStatistic stat) + { + if (dontBotherWithOuis) + return "???"; + + if (ouiLookupTable == null) + { + ouiLookupTable = new Dictionary(); + FileInfo dbFile = new FileInfo("standards-oui.ieee.org.txt"); + if (!dbFile.Exists) + { + dontBotherWithOuis = true; + return "???"; + } + + StreamReader streamReader = dbFile.OpenText(); + while (!streamReader.EndOfStream) + { + string? readLine = streamReader.ReadLine(); + if (readLine.Contains(" (base 16)")) + { + string[] strings = readLine.Split("\t"); + string k = strings[0].Split(" ")[0]; + if (ouiLookupTable.ContainsKey(k)) + continue; + string v = strings[strings.Length - 1]; + ouiLookupTable.Add(k, v); + } + } + streamReader.Dispose(); + } + + string victim = stat.Address.ToString().Substring(0, 6); + if (ouiLookupTable.ContainsKey(victim)) + return ouiLookupTable[victim]; + return "???"; + } + + private SortedList> docsisFreqs; + public void NotifyDocsisFrequency(uint? frequency, bool isUpstream, object mmm) + { + if (!frequency.HasValue) + return; + + if (docsisFreqs == null) + docsisFreqs = new SortedList>(); + + if (docsisFreqs.ContainsKey(frequency.Value)) + return; + + lock (docsisFreqs) + { + docsisFreqs.Add(frequency.Value, new Tuple(isUpstream, mmm)); + } + } + + public void SetGseMode() + { + TsType = 2; + } + + + private void RenderDocsisFrequencies() + { + if (docsisFreqs == null) + return; + + ImGui.Begin("DOCSIS Frequency Mapping"); + + ImGui.BeginTable("DOCSIS Participant Index", 3); + + ImGui.TableSetupColumn("Frequency"); + ImGui.TableSetupColumn("Usage"); + ImGui.TableHeadersRow(); + + lock (docsisFreqs) + { + foreach (KeyValuePair> docsisFreq in docsisFreqs) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + SafeText(String.Format("{0:F1} Mhz", (float)docsisFreq.Key / 1000000.0f)); + + ImGui.TableSetColumnIndex(1); + SafeText(docsisFreq.Value.Item1 ? "Upstream" : "Downstream"); + } + } + + ImGui.EndTable(); + + ImGui.End(); + } + #endregion + + #region Framegrabs + private List _framegrabWindows; + private Queue> _framegrabQueue; + public void ShowFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid, byte[] imageData) + { + string resolveServiceDisplayName = ResolveServiceDisplayName(mappingProgramNumber); + if (string.IsNullOrEmpty(resolveServiceDisplayName)) + resolveServiceDisplayName = String.Format("Framegrab of Service #{0:X4}"); + else + resolveServiceDisplayName = String.Format("Framegrab of \"{0}\"", resolveServiceDisplayName); + + if (PictureWindow.Renderer == null) + return; + + if (_framegrabQueue == null) + _framegrabQueue = new Queue>(); + + lock (_framegrabQueue) + { + _framegrabQueue.Enqueue(new Tuple(PictureWindow.Renderer, resolveServiceDisplayName, imageData)); + } + } + + private void RenderFramegrabs() + { + if (_framegrabQueue != null) + { + if (_framegrabQueue.Count > 0) + { + Tuple candidate = null; + lock (_framegrabQueue) + { + candidate = _framegrabQueue.Dequeue(); + } + + PictureWindow child = new PictureWindow(candidate.Item1, candidate.Item2, candidate.Item3); + if (_framegrabWindows == null) + _framegrabWindows = new List(); + lock (_framegrabWindows) + { + int pos = _framegrabWindows.Count + 1; + pos *= 20; + pos += 100; + child.SetPosition(pos, pos); + _framegrabWindows.Add(child); + } + } + } + + + if (_framegrabWindows != null) + { + lock (_framegrabWindows) + { + _framegrabWindows.ForEach(x => x.Render()); + } + } + } + #endregion + + #region Blockstream + + public void NotifyBlockstreamCarrier() + { + TsType = 4; + } + + #endregion + + #region DB-Schnittstelle + + public IEnumerable GetServices() + { + if (HasPmt) + { + foreach (var (pmtPid, patValue) in patDisplay) + { + ushort serviceId = patValue.programId; + SdtCoordinate sdt = ResolveService(serviceId); + string providerName = sdt.ProviderName; + string serviceName = sdt.ServiceName; + ushort? caId = ResolveCaId(patValue.pmt, sdt); + ServiceDescriptor.ServiceTypeCoding? serviceType = sdt.ServiceType; + yield return new HumanReadableService(serviceId, providerName, serviceName, caId, serviceType.GetValueOrDefault()); + } + } + } + + private ushort? ResolveCaId(ProgramMapping patValuePmt, SdtCoordinate sdt) + { + if (patValuePmt != null) + { + if (patValuePmt.CaSystemId.HasValue) + return patValuePmt.CaSystemId.Value; + + foreach (ProgramMappingStream stream in patValuePmt.Streams) + { + if (stream.CaSystemId.HasValue) + return stream.CaSystemId; + } + } + + if (sdt.CaIdentifiers != null) + { + if (sdt.CaIdentifiers.Length > 0) + return sdt.CaIdentifiers[0]; + } + + return null; + } + + #endregion + + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/LogWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/LogWindow.cs new file mode 100644 index 0000000..578586a --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/LogWindow.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using skyscraper5.Skyscraper.Scraper; + +namespace SDL2Demo.Forms +{ + internal class LogWindow : IRenderable, ISkyscraperEventLogger + { + public LogWindow() + { + logEntries = new LinkedList(); + clipperMemory = Marshal.AllocHGlobal(1024); + flags = Int32.MaxValue; + } + + private string GetFullLogAsString() + { + StringWriter sw = new StringWriter(); + LinkedListNode logEntriesFirst = logEntries.First; + while (true) + { + if (logEntriesFirst.Next == null) + break; + sw.WriteLine(logEntriesFirst.Value); + logEntriesFirst = logEntriesFirst.Next; + } + return sw.ToString(); + } + + private IntPtr clipperMemory; + private Vector2 zero; + private LinkedList logEntries; + private int flags; + private Vector2 windowSize; + private bool isOpen; + public bool IsOpen + { + get => isOpen; + set + { + logEntries = new LinkedList(); + isOpen = value; + } + } + public void Render() + { + if (!IsOpen) + return; + + if (windowSize == Vector2.Zero) + windowSize = new Vector2(0.0f, ImGui.GetFontSize() * 12.0f); + + ImGui.SetNextWindowSize(windowSize, ImGuiCond.FirstUseEver); + + if (!ImGui.Begin("skyscraper5 Debug Log", ref isOpen)) + { + ImGui.End(); + return; + } + + ImGui.AlignTextToFramePadding(); + ImGui.Text("Log events:"); + ImGui.SameLine(); + ImGui.CheckboxFlags("All", ref flags, Int32.MaxValue); + ImGui.SameLine(); ImGui.CheckboxFlags("UI", ref flags, 1); + ImGui.SameLine(); ImGui.CheckboxFlags("Scraper", ref flags, 2); + ImGui.SameLine(); ImGui.CheckboxFlags("Seconds", ref flags, 4); + ImGui.SameLine(); ImGui.CheckboxFlags("Job", ref flags, 8); + //ImGui.SameLine(); ImGui.CheckboxFlags("???16", ref flags, 16); + //ImGui.SameLine(); ImGui.CheckboxFlags("???32", ref flags, 32); + //ImGui.SameLine(); ImGui.CheckboxFlags("???32", ref flags, 64); + //ImGui.SameLine(); ImGui.CheckboxFlags("???32", ref flags, 128); + //ImGui.SameLine(); ImGui.CheckboxFlags("???32", ref flags, 256); + + if (ImGui.SmallButton("Clear")) + { + logEntries = new LinkedList(); + } + ImGui.SameLine(); + if (ImGui.SmallButton("Copy")) + ImGui.SetClipboardText(GetFullLogAsString()); + ImGui.BeginChild("##log", zero, true, ImGuiWindowFlags.AlwaysVerticalScrollbar | ImGuiWindowFlags.AlwaysHorizontalScrollbar); + + ImGuiListClipperPtr clipper = new ImGuiListClipperPtr(clipperMemory); + clipper.Begin(logEntries.Count); + while (clipper.Step()) + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + string line = GetLine(line_no); + ImGui.TextUnformatted(line); + } + if (ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) + ImGui.SetScrollHereY(1.0f); + ImGui.EndChild(); + + ImGui.End(); + + } + + private string GetLine(int lineNo) + { + LinkedListNode logEntriesFirst = logEntries.First; + if (lineNo == 0) + return logEntriesFirst.Value; + try + { + for (int i = 1; i < lineNo; i++) + { + if (logEntriesFirst == null) + return ""; + if (logEntriesFirst.Next == null) + return ""; + logEntriesFirst = logEntriesFirst.Next; + } + } + catch (NullReferenceException e) + { + return ""; + } + return logEntriesFirst.Value; + } + + private const bool LOG_TO_CONSOLE = true; + private const bool LOG_TO_DEBUG = false; + private const bool LOG_TO_TRACE = false; + public void Log(string toString, int flags = 1) + { + lock (logEntries) + { + while (logEntries.Count > 9000) + { + logEntries.RemoveFirst(); + } + if (isOpen) + { + if (((this.flags) & (flags)) != 0) + { + logEntries.AddLast(toString); + if (LOG_TO_CONSOLE) + Console.WriteLine(toString); + if (LOG_TO_DEBUG) + Debug.WriteLine(toString); + if (LOG_TO_TRACE) + Trace.WriteLine(toString); + } + } + } + } + + public void Log(TimeSpan duration, DateTime eventTimestamp, SkyscraperContextEvent eventType, string name = null) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0} {1}] ", eventTimestamp.ToShortDateString(), eventTimestamp.ToShortTimeString()); + sb.AppendFormat("{0}", eventType.ToString()); + + if (!string.IsNullOrEmpty(name) && !string.IsNullOrWhiteSpace(name)) + { + sb.AppendFormat(" ({0}) ", name); + } + + + if (eventType != SkyscraperContextEvent.StartPacketProcessing && eventType != SkyscraperContextEvent.TdtTime && eventType != SkyscraperContextEvent.TotTime) + { + sb.AppendFormat(" ({0} ms)", duration.TotalMilliseconds); + } + + Log(sb.ToString(),2); + } + + public ulong NumEvents => (uint)logEntries.Count; + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/MessageWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/MessageWindow.cs new file mode 100644 index 0000000..a1f232f --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/MessageWindow.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; + +namespace SDL2Demo.Forms +{ + internal class MessageWindow : IRenderable + { + public string Message { get; } + private string WindowUuid; + + public MessageWindow(string message) + { + Message = message; + WindowUuid = Guid.NewGuid().ToString(); + } + + private bool sizeSet; + public void Render() + { + bool closeMe = true; + if (!sizeSet) + { + ImGui.SetNextWindowSize(new Vector2(300, 160)); + sizeSet = true; + } + + ImGui.Begin(String.Format("Information ##{0}",WindowUuid), ref closeMe); + ImGui.TextWrapped(Message); + if (ImGui.Button("OK")) + closeMe = false; + ImGui.End(); + if (!closeMe) + { + Closed = true; + if (OnClose != null) + OnClose(); + } + } + + public bool Closed { get; private set; } + + public Action OnClose { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/PictureWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/PictureWindow.cs new file mode 100644 index 0000000..9edaae1 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/PictureWindow.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using Echo.UserInterface.Backend; +using ImGuiNET; +using SDL2; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Forms +{ + internal class PictureWindow : IRenderable, IDisposable + { + private readonly string _title; + private Texture _texture; + private Surface _surface; + private Vector2 _sizeVector; + public bool isOpen; + public Vector2 position; + + public PictureWindow(Renderer renderer, string title, byte[] buffer) + { + if (string.IsNullOrEmpty(title)) + throw new ArgumentNullException(nameof(title)); + + _title = title; + + lock (Renderer.Lockable) + { + _surface = Surface.ImageLoadFromPtr(buffer); + _texture = Texture.FromSurface(renderer, _surface); + SDL.SDL_Surface data = _surface.GetSurfaceData(); + _sizeVector = new Vector2(data.w, data.h); + isOpen = true; + position = new Vector2(100, 100); + } + } + + ~PictureWindow() + { + Dispose(); + } + + public void Render() + { + if (!isOpen) + { + Dispose(); + return; + } + + ImGui.PushStyleVar(ImGuiStyleVar.WindowMinSize, _sizeVector); + ImGui.SetNextWindowPos(position, ImGuiCond.FirstUseEver | ImGuiCond.Once); + if (ImGui.Begin(_title, ref isOpen, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) + { + ImGui.Image(_texture.Pointer, _sizeVector); + } + ImGui.End(); + ImGui.PopStyleVar(); + } + + + public void Dispose() + { + _texture.Dispose(); + _surface.Dispose(); + } + + internal static Renderer Renderer { get; set; } + + public void SetPosition(int x, int y) + { + position.X = x; + position.Y = y; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/SatellitesConfigurationWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/SatellitesConfigurationWindow.cs new file mode 100644 index 0000000..c150417 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/SatellitesConfigurationWindow.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace SDL2Demo.Forms +{ + internal class SatellitesConfigurationWindow : IRenderable + { + public SatellitesConfigurationWindow(IScraperStroage storage) + { + _storage = storage; + lnbPopupUuid = Guid.NewGuid().ToString(); + name = ""; + memoryTableUuid = Guid.NewGuid().ToString(); + allPositions = storage.UiSatellitesListAll(); + + } + + private string lnbPopupUuid; + private string memoryTableUuid; + private List allPositions; + private readonly IScraperStroage _storage; + + + private string name; + private float degrees; + private int cardinalDirection; + + private void CheckBounds() + { + if (degrees < 0.0) + degrees = 0.0f; + if (degrees > 180.0) + degrees = 180.0f; + } + private bool SaveButtonEnabled() + { + if (string.IsNullOrEmpty(name)) + return false; + + if (string.IsNullOrWhiteSpace(name)) + return false; + + if (allPositions.Count == 0) + return true; + + int currentChecksum = SatellitePosition.GetChecksum(degrees, cardinalDirection); + foreach (SatellitePosition satellitePosition in allPositions) + { + if (currentChecksum == satellitePosition.Checksum) + return false; + } + + return true; + } + public void Render() + { + bool pOpen = true; + ImGui.Begin("Configure satellite positions", ref pOpen); + + if (ImGui.TreeNode("Add satellite position")) + { + ImGui.InputText("Name", ref name, 255); + ImGui.InputFloat("Position", ref degrees, 0.1f); + ImGui.RadioButton("East", ref cardinalDirection, 0); + ImGui.SameLine(); + ImGui.RadioButton("West", ref cardinalDirection, 1); + + CheckBounds(); + + ImGui.SameLine(); + ImGui.BeginDisabled(!SaveButtonEnabled()); + if (ImGui.Button("Add")) + { + SatellitePosition newPosition = new SatellitePosition(degrees, cardinalDirection, name); + allPositions.Add(newPosition); + _storage.UiSatellitesAdd(newPosition); + } + + ImGui.EndDisabled(); + } + + ImGui.Separator(); + + ImGui.BeginTable(memoryTableUuid, 4); + foreach (SatellitePosition satellitePosition in allPositions) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(satellitePosition.name); + ImGui.TableSetColumnIndex(1); + ImGui.Text(String.Format("{0:0.0}° {1}", satellitePosition.angle, satellitePosition.cardinalDirection == 0 ? "E" : "W")); + ImGui.TableSetColumnIndex(2); + ImGui.Text(""); + + ImGui.TableSetColumnIndex(3); + ImGui.PushID(String.Format("RemoveSat {0}", satellitePosition.Checksum)); + if (ImGui.Button("Remove")) + { + _storage.UiSatellitesDelete(satellitePosition); + allPositions.Remove(satellitePosition); + break; + } + ImGui.PopID(); + } + ImGui.EndTable(); + + ImGui.End(); + + if (!pOpen) + Closed = true; + } + + public bool Closed { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Forms/UiBlockingWindow.cs b/GUIs/skyscraper8.UI.ImGui/Forms/UiBlockingWindow.cs new file mode 100644 index 0000000..b082da7 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Forms/UiBlockingWindow.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; + +namespace SDL2Demo.Forms +{ + internal class UiBlockingWindow : IRenderable + { + public UiBlockingWindow() + { + Title = "Please wait..."; + Text = "skyscraper5 is busy..."; + } + + public string Title { get; set; } + public string Text { get; set; } + + public string Line2 { get; set; } + + public void Render() + { + ImGui.Begin(Title, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize); + ImGui.Text(Text); + if (!string.IsNullOrEmpty(Line2)) + ImGui.Text(Line2); + ImGui.End(); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/IJob.cs b/GUIs/skyscraper8.UI.ImGui/IJob.cs new file mode 100644 index 0000000..fc2f86f --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/IJob.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo +{ + internal interface IJob + { + void Run(); + void Cancel(); + + public JobContext JobContext { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/IPressurePlate.cs b/GUIs/skyscraper8.UI.ImGui/IPressurePlate.cs new file mode 100644 index 0000000..ddd657f --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/IPressurePlate.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo +{ + internal interface IPressurePlate + { + public bool Visible { get; set; } + public Point Position { get; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/IRenderable.cs b/GUIs/skyscraper8.UI.ImGui/IRenderable.cs new file mode 100644 index 0000000..ba0af77 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/IRenderable.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo +{ + internal interface IRenderable + { + void Render(); + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/JobContext.cs b/GUIs/skyscraper8.UI.ImGui/JobContext.cs new file mode 100644 index 0000000..5ed2baa --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/JobContext.cs @@ -0,0 +1,34 @@ +using Echo.UserInterface.Backend; +using SDL2Demo.Forms; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage; +using SkyscraperUI; + +namespace SDL2Demo +{ + internal class JobContext + { + public Random RNG { get; set; } + public Thread Thread { get; set; } + public bool ReadyForNextJob { get; set; } + public IJob Job { get; set; } + + public Queue MessageQueue { get; set; } + public CharSet[] Puppets { get; set; } + public ISkyscraperEventLogger ScraperEventLogger { get; set; } + public IScraperStroage ScraperStorage { get; set; } + public bool CanCancel { get; set; } + public List Renderables { get; set; } + + public List PressurePlates { get; set; } + public LogWindow LogWindow { get; set; } + public bool MemorySaverMode { get; set; } + public IStreamReader StreamReader { get; set; } + public Ini Ini { get; set; } + public ImGuiDevice ImgUiDevice { get; internal set; } + public IGpsReceiver Gps { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Jobs/Blindscan.cs b/GUIs/skyscraper8.UI.ImGui/Jobs/Blindscan.cs new file mode 100644 index 0000000..a8a4278 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Jobs/Blindscan.cs @@ -0,0 +1,1391 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Drawing; +using System.Net.NetworkInformation; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using ImGuiNET; +using Newtonsoft.Json; +using SDL2Demo.Forms; +using SDL2Demo.SdlWrapper; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Mpeg2.PacketFilter; + +namespace SDL2Demo.Jobs +{ + internal class Blindscan : IRenderable, IJob + { + #region Driver + + public bool WindowOpen; + public BlindscanTarget SelectedBlindscanTarget; + + public class BlindscanTarget + { + public BlindscanTarget(string name, int tunerIndex, STD_TYPE tunerStandard, int diseqcType, + SatellitePosition satPosition, int satIndex, LnbType lnbType, PhysicalAddress tunerMetadataMacAddress) + { + this.name = name; + this.tunerIndex = tunerIndex; + this.tunerStandard = tunerStandard; + this.diseqcType = diseqcType; + this.satPosition = satPosition; + this.satIndex = satIndex; + this.buttonUuid = Guid.NewGuid().ToString(); + this.lnbType = lnbType; + this.macAddress = tunerMetadataMacAddress; + } + + public string name; + public int tunerIndex; + public STD_TYPE tunerStandard; + public int diseqcType; + public SatellitePosition satPosition; + public int satIndex; + public string buttonUuid; + public LnbType lnbType; + public readonly PhysicalAddress macAddress; + + public bool IsTorC() + { + switch (tunerStandard) + { + case STD_TYPE.STD_DVBC: return true; + case STD_TYPE.STD_DVBC2: return true; + case STD_TYPE.STD_DVBT: return true; + case STD_TYPE.STD_DVBT2: return true; + case STD_TYPE.STD_DVBS: return false; + case STD_TYPE.STD_DVBS2: return false; + default: throw new NotImplementedException(tunerStandard.ToString()); + } + } + } + + private List possibleBlindscanTargets; + private string tableUuid; + private bool willCaptureFiles; + private IStreamReader streamReader; + private bool hasSwitchingLnb; + private bool willScanHorizontalLow, willScanHorizontalHigh, willScanVerticalLow, willScanVerticalHigh; + private bool continuationDataExists; + public bool ContinuationMode; + private FoundFrequenciesWindow ourFoundFrequenciesWindow; + private bool continueOldJob; + private DbBlindscanJob jobInDb; + private IDbBlindscanJobStorage jobStorage; + + public Blindscan(List tuners, List satellitePositions, List lnbTypes, IDbBlindscanJobStorage jobStorage) + { + this.jobStorage = jobStorage; + continuationDataExists = jobStorage.TestForIncompleteJob(); + + possibleBlindscanTargets = new List(); + foreach (TunerMetadata tunerMetadata in tuners) + { + switch (tunerMetadata.Type) + { + case STD_TYPE.STD_DVBS: + int numSatsFromDiseqcType = tunerMetadata.GetNumSatsFromDiseqcType(); + for (int i = 0; i < numSatsFromDiseqcType; i++) + { + SatellitePosition satellitePosition = satellitePositions.Find(x => x.Checksum == tunerMetadata.Satellites[i]); + LnbType lnbType = lnbTypes.Find(x => x.Id == tunerMetadata.Lnbs[i]); + if (satellitePosition == null) + continue; + if (lnbType == null) + continue; + if (lnbType.LofSw != 0) + { + hasSwitchingLnb = true; + willScanHorizontalHigh = true; + willScanHorizontalLow = true; + willScanVerticalHigh = true; + willScanVerticalLow = true; + } + + possibleBlindscanTargets.Add(new BlindscanTarget(tunerMetadata.Name, tunerMetadata.Index, + tunerMetadata.Type, tunerMetadata.DiseqcType, satellitePosition, i, lnbType, tunerMetadata.MacAddress)); + } + + break; + case STD_TYPE.STD_DVBC: + possibleBlindscanTargets.Add(new BlindscanTarget(tunerMetadata.Name, tunerMetadata.Index, tunerMetadata.Type, 0, null, 0, null, tunerMetadata.MacAddress)); + break; + case STD_TYPE.STD_DVBT: + possibleBlindscanTargets.Add(new BlindscanTarget(tunerMetadata.Name, tunerMetadata.Index, tunerMetadata.Type, 0, null, 0, null, tunerMetadata.MacAddress)); + break; + default: + throw new NotImplementedException(tunerMetadata.Type.ToString()); + } + } + + WindowOpen = true; + tableUuid = Guid.NewGuid().ToString(); + } + + public void Render() + { + if (ImGui.Begin("Blindscan", ref WindowOpen, ImGuiWindowFlags.AlwaysAutoResize)) + { + if (possibleBlindscanTargets.Count == 0) + { + ImGui.Text("No possible blindscan targets!"); + ImGui.Text("Did you configure the tuners and satellite positions?"); + ImGui.End(); + return; + } + + ImGui.BeginTable(tableUuid, 4, + ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit); + foreach (BlindscanTarget possibleBlindscanTarget in possibleBlindscanTargets) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text(possibleBlindscanTarget.name); + + ImGui.TableSetColumnIndex(1); + if (possibleBlindscanTarget.tunerStandard == STD_TYPE.STD_DVBS) + { + ImGui.Text(String.Format("DiSEqC Position {0}", possibleBlindscanTarget.satIndex)); + } + else if (possibleBlindscanTarget.tunerStandard == STD_TYPE.STD_DVBC) + { + ImGui.Text("DVB-C"); + } + + ImGui.TableSetColumnIndex(2); + if (possibleBlindscanTarget.tunerStandard == STD_TYPE.STD_DVBS) + { + ImGui.Text(String.Format("{0} ({1})", possibleBlindscanTarget.satPosition.name, possibleBlindscanTarget.lnbType.Name)); + } + else + { + ImGui.TextWrapped(possibleBlindscanTarget.tunerIndex.ToString()); + } + + ImGui.TableSetColumnIndex(3); + ImGui.PushID(possibleBlindscanTarget.buttonUuid); + if (ImGui.Button("Run scan")) + { + WindowOpen = false; + SelectedBlindscanTarget = possibleBlindscanTarget; + ContinuationMode = false; + } + + ImGui.PopID(); + } + + ImGui.EndTable(); + + ImGui.Checkbox("Capture Packets to files", ref willCaptureFiles); + + if (hasSwitchingLnb) + { + ImGui.Checkbox("Scan Low Horizontal Region", ref willScanHorizontalLow); + ImGui.Checkbox("Scan High Horizontal Region", ref willScanHorizontalHigh); + ImGui.Checkbox("Scan Low Vertical Region", ref willScanVerticalLow); + ImGui.Checkbox("Scan High Vertical Region", ref willScanVerticalHigh); + } + + if (continuationDataExists) + { + if (ImGui.Button("Continue with previous run")) + { + WindowOpen = false; + ContinuationMode = true; + + } + } + + ImGui.End(); + } + } + + public void Run() + { + if (JobContext == null) + throw new NullReferenceException("Job Context was not enrolled."); + if (JobContext.StreamReader == null) + throw new NullReferenceException("No StreamReader available"); + if (streamReader == null) + streamReader = JobContext.StreamReader; + + if (continueOldJob) + { + throw new NotImplementedException("continue old blindscan job"); + } + else + { + + jobInDb = new DbBlindscanJob(Guid.NewGuid(), SelectedBlindscanTarget.macAddress, SelectedBlindscanTarget.tunerStandard, SelectedBlindscanTarget.satIndex, JobContext.Gps, SelectedBlindscanTarget.satPosition); + if (SelectedBlindscanTarget.IsTorC()) + { + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_WAITING; + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.NOT_SELECTED_NON_S_HINT; + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.NOT_SELECTED_NON_S_HINT; + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.NOT_SELECTED_NON_S_HINT; + } + else + { + jobInDb.HorizontalHighState = willScanHorizontalHigh ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobInDb.HorizontalLowState = willScanHorizontalLow ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobInDb.VerticalLowState = willScanVerticalLow ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobInDb.VerticalHighState = willScanVerticalHigh ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + } + jobStorage.InsertBlindscanJob(jobInDb); + } + + JobContext.LogWindow.Log("Attempting to start the tuner...", 8); + if (!streamReader.StartDvbEx(SelectedBlindscanTarget.tunerIndex)) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Failed to start tuner.")); + JobContext.ReadyForNextJob = true; + return; + } + + JobContext.LogWindow.Log("Tuner started", 8); + + foundFrequencies = new List(); + ourFoundFrequenciesWindow = new FoundFrequenciesWindow(foundFrequencies, SelectedBlindscanTarget.tunerStandard, JobContext); + JobContext.Renderables.Add(ourFoundFrequenciesWindow); + if (!RunBlindscan()) + { + streamReader.StopDVB(); + JobContext.ReadyForNextJob = true; + return; + } + + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + JobContext.Puppets[2].AutoMoveTo(new Point(0, 720 / 2)); + JobContext.Puppets[3].AutoMoveTo(new Point(1280 / 2, 0)); + + //RunScrape(); + streamReader.StopDVB(); + JobContext.ReadyForNextJob = true; + Console.WriteLine("Blindscan Job done!"); + return; + } + + public void Cancel() + { + throw new NotImplementedException(); + } + + public JobContext JobContext { get; set; } + + #endregion Driver + + #region Scanner + + public class BlindscanResult : IPressurePlate + { + internal BlindscanResult() + { + + } + + public BlindscanResult(SearchResult2 dvbcResult, int minFreq, int maxFreq) + { + Visible = true; + State = BlindscanResultState.Found; + + int w = (dvbcResult.Freq / 1000) - minFreq; + int hundert = 1280; + int g = (maxFreq - minFreq); + + int x = w * hundert / g; + Position = new Point(x, 600); + + sr2 = dvbcResult; + Satellite = false; + } + + public BlindscanResult(SearchResult searchResult, int satPositionMinFreq, int satPositionMaxFreq) + { + int x = 0, y = 0; + + if (searchResult.Pol == 0) + { + //Horizontal + int w = (searchResult.Freq / 1000) - satPositionMinFreq; + int hundert = 1280; + int g = (satPositionMaxFreq - satPositionMinFreq); + + x = w * hundert / g; + y = 720 / 2; + } + else + { + //Vertical + int w = (searchResult.Freq / 1000) - satPositionMinFreq; + int hundert = 720; + int g = (satPositionMaxFreq - satPositionMinFreq); + + x = 1280 / 2; + y = w * hundert / g; + } + + Position = new Point(x, y); + Visible = true; + sr1 = searchResult; + Satellite = true; + } + + + public bool Visible { get; set; } + public Point Position { get; private set; } + + public BlindscanResultState State { get; set; } + + public SearchResult sr1; + + public SearchResult2 sr2; + + public bool Satellite { get; private set; } + } + + public bool RunBlindscan() + { + + + Caps caps = streamReader.GetCaps(); + + switch (SelectedBlindscanTarget.tunerStandard) + { + case STD_TYPE.STD_DVBC: + if (caps.HasFlag(Caps.SR_AIRSCAN)) + { + int startFreq = 48000; + int endFreq = 1000000; + uint step = 3000; + uint bandwidth = 8000; + STD_TYPE stdType = STD_TYPE.STD_DVBC; + SearchResult2 sr2 = default; + int tpNum = default; + + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + + IntPtr pSearchResult = Marshal.AllocHGlobal(UInt16.MaxValue); + bool airScan = streamReader.AirScan(startFreq, endFreq, step, bandwidth, (int)stdType, + pSearchResult, ref tpNum, + ((ref SearchResult2 searchResult) => SearchResult2Callback(searchResult))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + Marshal.FreeHGlobal(pSearchResult); + if (!airScan) + { + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + JobContext.MessageQueue.Enqueue(new MessageWindow("AirScan failed.")); + return false; + } + + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + return true; + } + else + { + throw new NotImplementedException("Don't know how to blindscan with this tuner."); + } + case STD_TYPE.STD_DVBS: + if (caps.HasFlag(Caps.SR_BLSCAN2)) + { + SearchResult sr1 = default; + int tpNum = default; + + int lof1 = SelectedBlindscanTarget.lnbType.Lof1 * 1000; + int lof2 = SelectedBlindscanTarget.lnbType.Lof2 * 1000; + int lofSw = SelectedBlindscanTarget.lnbType.LofSw * 1000; + int diseqc = SelectedBlindscanTarget.diseqcType; + int startFreq = SelectedBlindscanTarget.lnbType.MinimumFrequency * 1000; + int endFreq = SelectedBlindscanTarget.lnbType.MaximumFrequency * 1000; + bool anythingSuceeded = false; + IntPtr allocHGlobal = Marshal.AllocHGlobal(UInt16.MaxValue); + + if (lofSw != 0) + { + if (willScanHorizontalLow) + { + if (SendDiseqcCommand(diseqc, false, true)) + { + JobContext.LogWindow.Log(String.Format("Scanning low horizontal band...")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool hLower = BlScan2Wrap(startFreq, lofSw, 0, lof1, lof2, lofSw,allocHGlobal, ref tpNum,((ref SearchResult searchResult) => SearchResult1Callback(searchResult,1))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!hLower) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning low horizontal area failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to low horizontal area failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + + if (willScanHorizontalHigh) + { + if (SendDiseqcCommand(diseqc, true, true)) + { + SendDiseqcCommand(diseqc, true, true); + JobContext.LogWindow.Log(String.Format("Scanning high horizontal band...")); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool hUpper = BlScan2Wrap(lofSw, endFreq, 0, lof1, lof2, lofSw,allocHGlobal, ref tpNum,((ref SearchResult searchResult) => SearchResult1Callback(searchResult,2))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!hUpper) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning high horizontal area failed.")); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to high horizontal area failed.")); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + + if (willScanVerticalLow) + { + if (SendDiseqcCommand(diseqc, false, false)) + { + JobContext.LogWindow.Log(String.Format("Scanning low vertical band...")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool vLower = BlScan2Wrap(startFreq, lofSw, 1, lof1, lof2, lofSw,allocHGlobal, ref tpNum,((ref SearchResult searchResult) => SearchResult1Callback(searchResult,3))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!vLower) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning low vertical area failed.")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to low vertical area failed.")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + + if (willScanVerticalHigh) + { + if (SendDiseqcCommand(diseqc, true, false)) + { + JobContext.LogWindow.Log(String.Format("Scanning high vertical band...")); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool vUpper = BlScan2Wrap(lofSw, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult,4))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!vUpper) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning high vertical area failed.")); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to high vertical area failed.")); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + } + else + { + if (SendDiseqcCommand(diseqc, false, true)) + { + JobContext.LogWindow.Log(String.Format("Scanning left circular band...")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool lCirc = BlScan2Wrap(startFreq, endFreq, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult,1))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!lCirc) + { + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning left circular area failed.")); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to left circulation failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + if (SendDiseqcCommand(diseqc, false, false)) + { + JobContext.LogWindow.Log(String.Format("Scanning right circular band...")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool rCirc = BlScan2Wrap(startFreq, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult,3))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!rCirc) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning right circular area failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + RunScrape(); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to right circulation failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + } + } + + Marshal.FreeHGlobal(allocHGlobal); + return anythingSuceeded; + } + else + { + throw new NotImplementedException("Don't know how to blindscan with this tuner."); + } + default: + throw new NotImplementedException(SelectedBlindscanTarget.tunerStandard.ToString()); + } + } + + private BlindscanProgressWindow _blindscanProgressWindow; + private bool BlScan2Wrap(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofSw, IntPtr pSearchResult, ref int pTpNum, BlScanCallback lpFunc) + { + _blindscanProgressWindow = new BlindscanProgressWindow(); + _blindscanProgressWindow.Start = freq_start; + _blindscanProgressWindow.Progress = freq_start; + _blindscanProgressWindow.End = freq_stop; + lock (JobContext.Renderables) + { + JobContext.Renderables.Add(_blindscanProgressWindow); + } + + bool result = streamReader.BLScan2(freq_start, freq_stop, pol, lof1, lof2, lofSw, pSearchResult, ref pTpNum, lpFunc); + + lock (JobContext.Renderables) + { + JobContext.Renderables.Remove(_blindscanProgressWindow); + } + + return result; + } + + private void SearchResult1Callback(SearchResult searchResult, int polarityIndex) + { + BlindscanResult blindscanResult = new BlindscanResult(searchResult, SelectedBlindscanTarget.lnbType.MinimumFrequency, SelectedBlindscanTarget.lnbType.MaximumFrequency); + JobContext.Puppets[blindscanResult.sr1.Pol].AutoMoveTo(blindscanResult.Position); + lock (JobContext.PressurePlates) + { + JobContext.PressurePlates.Add(blindscanResult); + } + + lock (foundFrequencies) + { + foundFrequencies.Add(blindscanResult); + } + + JobContext.LogWindow.Log(String.Format("Found frequency: {0}, {1}", searchResult.Freq / 1000,searchResult.Pol == 0 ? "H" : "V")); + SoundPlayer.PlaySoundFile("lock.wav"); + + _blindscanProgressWindow.Progress = searchResult.Freq; + + jobStorage.InsertSearchResult(jobInDb, true, searchResult, polarityIndex, new SearchResult2()); + } + + private void SearchResult2Callback(SearchResult2 searchResult) + { + BlindscanResult blindscanResult = new BlindscanResult(searchResult, 48, 1000); + JobContext.Puppets[0].AutoMoveTo(new Point(blindscanResult.Position.X, blindscanResult.Position.Y)); + lock (JobContext.PressurePlates) + { + JobContext.PressurePlates.Add(blindscanResult); + } + + lock (foundFrequencies) + { + foundFrequencies.Add(blindscanResult); + } + + JobContext.LogWindow.Log(String.Format("Found frequency: {0}", searchResult.Freq / 1000)); + SoundPlayer.PlaySoundFile("lock.wav"); + + jobStorage.InsertSearchResult(jobInDb, false, new SearchResult(), 7, searchResult); + } + + private bool SendDiseqcCommand(int diseqcType, bool highBand, bool horizontal) + { + DiSEqC_Opcode myOpcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE; + if (highBand) + myOpcode |= DiSEqC_Opcode.DISEQC_HIGH_BAND; + else + myOpcode |= DiSEqC_Opcode.DISEQC_LOW_BAND; + + if (horizontal) + myOpcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL; + else + myOpcode |= DiSEqC_Opcode.DISEQC_VERTICAL; + + if (diseqcType == 2) + { + int parameter = SelectedBlindscanTarget.satIndex + 1; + switch (parameter) + { + case 1: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A; + break; + case 2: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B; + break; + case 3: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A; + break; + case 4: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B; + break; + default: + throw new ArgumentOutOfRangeException("DiSEqC Switch Position"); + } + } + + JobContext.LogWindow.Log(String.Format("Send DiSEqC Command {0:X2}", (byte)myOpcode), 8); + + DateTime started = DateTime.Now; + bool result = streamReader.SendDiSEqC((uint)diseqcType, myOpcode); + TimeSpan timeTaken = DateTime.Now - started; + JobContext.LogWindow.Log(String.Format("DiSEqC Comannd sent in {0:F1} seconds.", timeTaken.TotalSeconds)); + if (timeTaken.TotalSeconds > 10) + { + JobContext.LogWindow.Log( + String.Format("Something went wrong while performing the DiSEqC operation, trying again..."), 8); + Thread.Sleep(1000); + return SendDiseqcCommand(diseqcType, highBand, horizontal); + } + + return result; + } + + private List foundFrequencies; + + #endregion Scanner + + #region Scraper + + public void RunScrape() + { + int lof1 = 0; + int lof2 = 0; + int lofSw = 0; + + if (SelectedBlindscanTarget.tunerStandard == STD_TYPE.STD_DVBS) + { + lof1 = SelectedBlindscanTarget.lnbType.Lof1 * 1000; + lof2 = SelectedBlindscanTarget.lnbType.Lof2 * 1000; + lofSw = SelectedBlindscanTarget.lnbType.LofSw * 1000; + } + + foreach (BlindscanResult blindscanResult in foundFrequencies) + { + recordingFilename = null; + DateTime now = DateTime.Now; + switch (SelectedBlindscanTarget.tunerStandard) + { + case STD_TYPE.STD_DVBS: + JobContext.Puppets[2 + blindscanResult.sr1.Pol].AutoMoveTo(blindscanResult.Position); + blindscanResult.State = BlindscanResultState.Tuning; + jobStorage.UpdateTransponderState(jobInDb, blindscanResult.Satellite, blindscanResult.sr1, blindscanResult.State, blindscanResult.sr2); + bool channel = streamReader.SetChannel(blindscanResult.sr1.Freq, blindscanResult.sr1.SR, + blindscanResult.sr1.Pol, blindscanResult.sr1.FEC, lof1, lof2, lofSw); + if (!channel) + { + blindscanResult.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, blindscanResult.Satellite, blindscanResult.sr1, blindscanResult.State, blindscanResult.sr2); + continue; + } + + recordingFilename = String.Format( + "skyscraper_{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}_{8:D4}{9}_{5}_{6}_{7}.ts", + now.Year, now.Month, now.Day, now.Hour, now.Minute, blindscanResult.sr1.Freq / 1000, + blindscanResult.sr1.Pol == 0 ? "H" : "V", blindscanResult.sr1.SR / 1000, + (int)(SelectedBlindscanTarget.satPosition.angle * 10), + SelectedBlindscanTarget.satPosition.cardinalDirection == 0 ? "E" : "W"); + break; + case STD_TYPE.STD_DVBC: + JobContext.Puppets[1].AutoMoveTo(blindscanResult.Position); + blindscanResult.State = BlindscanResultState.Tuning; + jobStorage.UpdateTransponderState(jobInDb, blindscanResult.Satellite, blindscanResult.sr1, blindscanResult.State, blindscanResult.sr2); + bool channel2 = streamReader.SetChannel2((uint)blindscanResult.sr2.Freq, + (uint)blindscanResult.sr2.BW); + if (!channel2) + { + blindscanResult.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, blindscanResult.Satellite, blindscanResult.sr1, blindscanResult.State, blindscanResult.sr2); + continue; + } + + recordingFilename = String.Format("skyscraper_{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}_C_{5}_{6}.ts", + now.Year, now.Month, now.Day, now.Hour, now.Minute, blindscanResult.sr2.Freq / 1000, + blindscanResult.sr2.SR); + break; + default: + throw new NotImplementedException(SelectedBlindscanTarget.tunerStandard.ToString()); + } + + RunSkyscraper(blindscanResult); + blindscanResult.Visible = false; + } + + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + JobContext.Puppets[2].AutoMoveToHome(); + JobContext.Puppets[3].AutoMoveToHome(); + } + + + private JobDisplay jobDisplay; + private ISkyscraperContext skyscraperContext; + private string recordingFilename; + + + + + private bool HasLock(bool cableTv) + { + if (cableTv) + { + SearchResult2 sr2 = new SearchResult2(); + if (!streamReader.SignalInfo2(ref sr2)) + return false; + return sr2.Lock; + } + else + { + SearchResult sr = new SearchResult(); + if (!streamReader.SignalInfo(ref sr)) + return false; + return sr.Lock; + } + } + + private ulong droppedPackets; + private ulong packetsReceivedInTotal; + private Queue packetsQueue; + private int packetsToDrop; + + private void DvbCallback(IntPtr data, int length) + { + if (length % 188 == 0) + { + try + { + byte[] buffer = new byte[length]; + Marshal.Copy(data, buffer, 0, length); + + if (packetsToDrop <= 0) + { + lock (packetsQueue) + { + packetsQueue.Enqueue(buffer); + } + } + + packetsToDrop -= (buffer.Length / 188); + packetsReceivedInTotal += (uint)(buffer.Length / 188); + ourFoundFrequenciesWindow.statusPacketsInTotal = packetsReceivedInTotal; + ourFoundFrequenciesWindow.statusPacketsInqueue = packetsQueue.Count; + } + catch (OutOfMemoryException e) + { + droppedPackets++; + } + } + else + { + JobContext.LogWindow.Log(String.Format("odd packet size!"), 8); + } + } + + private bool HasPackets() + { + lock (packetsQueue) + { + return packetsQueue.Count > 0; + } + } + + private DateTime startedAt; + + private sbyte[] iqBuffer; + private DateTime prevIqTimestamp; + private IqWindow iqWindow; + + private bool StopConditionMet() + { + if (startedAt == DateTime.MinValue) + startedAt = DateTime.Now; + + if (ourFoundFrequenciesWindow.zapNowRequested) + { + ourFoundFrequenciesWindow.zapNowRequested = false; + return true; + } + + if (ourFoundFrequenciesWindow.doNotAutoZap) + return false; + + if (packetsReceivedInTotal == 0 && (DateTime.Now - startedAt).TotalSeconds > 5) + return true; + + if (jobDisplay != null) + { + if (jobDisplay.GseCommunicationParties > 1000) + return true; + } + + if (!skyscraperContext.EnableTimeout) + { + skyscraperContext.TimeoutSeconds = 10; + skyscraperContext.EnableTimeout = true; + } + + if (packetsReceivedInTotal > 0 && skyscraperContext.IsAbortConditionMet()) + return true; + + return false; + } + + private void DrainPackets() + { + if (packetsQueue.Count == 0) + return; + + JobContext.LogWindow.Log( + String.Format("{0} packets left after switching off the filter.", packetsQueue.Count), 8); + + DateTime startedAt = DateTime.Now; + byte[] singlePacketBuffer = new byte[188]; + byte[] packetBuffer = null; + DateTime drainTickerPrevious = DateTime.Now, drainTickerNow = DateTime.Now; + while (packetsQueue.Count > 0) + { + packetBuffer = packetsQueue.Dequeue(); + if (packetBuffer == null) + continue; + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + } + + drainTickerPrevious = drainTickerNow; + drainTickerNow = DateTime.Now; + if (drainTickerNow.Second != drainTickerPrevious.Second) + { + JobContext.LogWindow.Log( + String.Format("{0} packets left ({1}).", packetsQueue.Count, drainTickerNow.ToLongTimeString()), + 8); + } + } + + JobContext.LogWindow.Log( + String.Format("Packets drained in {0} seconds.", (DateTime.Now - startedAt).TotalSeconds), 8); + } + + private void RunSkyscraper(BlindscanResult result) + { + int misCounter = 1; + bool misMode = false; + bool cableTvMode = false; + Caps caps = streamReader.GetCaps(); + SearchResult satelliteSr = new SearchResult(); + SearchResult2 cableSr = new SearchResult2(); + + if (caps.HasFlag(Caps.SR_SIGINFO)) + { + /*if (caps.HasFlag(Caps.SR_MISSEL)) + if (!streamReader.MISSel(false, 0, 0)) + { + result.State = BlindscanResultState.MisFailure; + return; + } + */ + + /*S2Mode m = new S2Mode(); + if (caps.HasFlag(Caps.SR_MODSEL)) + if (!streamReader.ModSel(ref m, 0)) + { + result.State = BlindscanResultState.ModFailure; + return; + }*/ + + /* + if (caps.HasFlag(Caps.SR_PLSSEL)) + if (!streamReader.PLSSel(0, 0)) + { + result.State = BlindscanResultState.PlsFailure; + return; + } + */ + + + JobContext.LogWindow.Log(String.Format("Trying to BLScanEx..."), 8); + if (!streamReader.BLScanEx(result.sr1.Freq, 5000, result.sr1.Pol, + SelectedBlindscanTarget.lnbType.Lof1 * 1000, SelectedBlindscanTarget.lnbType.Lof2 * 1000, + SelectedBlindscanTarget.lnbType.LofSw * 1000, 1000000, (STD_TYPE)result.sr1.StdType, + ref satelliteSr)) + { + //No blindscan? No problem! Try anyway! + satelliteSr = result.sr1; + satelliteSr.Lock = false; + result.State = BlindscanResultState.BlScanFailure; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + } + + if (!satelliteSr.Lock) + { + JobContext.LogWindow.Log(String.Format("Trying to SetChannel..."), 8); + bool channel = streamReader.SetChannel(result.sr1.Freq, result.sr1.SR, result.sr1.Freq, result.sr1.FEC, SelectedBlindscanTarget.lnbType.Lof1 * 1000, SelectedBlindscanTarget.lnbType.Lof2 * 1000, SelectedBlindscanTarget.lnbType.LofSw * 1000); + if (!channel) + { + result.State = BlindscanResultState.TuningFailed; + SoundPlayer.PlaySoundFile("fail.wav"); + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + return; + } + else + { + JobContext.LogWindow.Log(String.Format("Trying to get SignalInfo..."), 8); + bool signalInfo = streamReader.SignalInfo(ref satelliteSr); + if (!signalInfo) + { + result.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + + if (!satelliteSr.Lock) + { + result.State = BlindscanResultState.NoLock; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + } + } + + if (caps.HasFlag(Caps.SR_MISSEL)) + { + if (satelliteSr.MIS > 16) + satelliteSr.MIS = 1; + + if (satelliteSr.MIS == 0) + satelliteSr.MIS = 1; + + if (satelliteSr.MIS != 1) + misMode = true; + + misCounter = satelliteSr.MIS; + } + } + else if (caps.HasFlag(Caps.SR_SIGINFO2)) + { + JobContext.LogWindow.Log(String.Format("Trying to SetChannel2..."), 8); + bool channel2 = streamReader.SetChannel2((uint)result.sr2.Freq, (uint)result.sr2.BW); + if (!channel2) + { + result.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + + JobContext.LogWindow.Log(String.Format("Trying to get SignalInfo2..."), 8); + bool signalInfo2 = streamReader.SignalInfo2(ref cableSr); + if (!signalInfo2) + { + result.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + + if (!cableSr.Lock) + { + result.State = BlindscanResultState.NoLock; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + } + else + { + throw new NotImplementedException("Couldn't figure out what signal info to use."); + } + + for (int mis = 0; mis < misCounter; mis++) + { + if (misMode) + { + JobContext.LogWindow.Log(String.Format("Selecting MIS IS {0}", satelliteSr.IS[mis]), 8); + bool misSel = streamReader.MISSel(misMode, satelliteSr.IS[mis], 0xff); + if (!misSel) + { + result.State = BlindscanResultState.MisFailure; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + } + + //Start Filter + TsRecorder tsRecorder = null; + if (willCaptureFiles) + { + tsRecorder = new TsRecorder(); + tsRecorder.Recording = true; + string outputDirName = JobContext.Ini.ReadValue("recording", "output_dir", "recording_output"); + tsRecorder.RecordingOutputDirectory = new DirectoryInfo(outputDirName); + tsRecorder.RecordingOutputDirectory.EnsureExists(); + if (tsRecorder.PrepareRecording()) + { + tsRecorder.SetNextFilename(recordingFilename); + tsRecorder.CreateBufferedStream(); + } + } + + IntPtr filterReference = IntPtr.MaxValue; + JobContext.LogWindow.Log(String.Format("Set-Up filter..."), 8); + packetsToDrop = 1024; + packetsQueue = new Queue(); + ourFoundFrequenciesWindow.statusPacketsInqueue = 0; + ourFoundFrequenciesWindow.statusPacketsInTotal = 0; + bool filter = streamReader.SetFilter(8192, DvbCallback, 0x02, 2, ref filterReference); + if (!filter) + { + result.State = BlindscanResultState.FilterFailure; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + JobContext.LogWindow.Log(String.Format("Filter set-up complete!"), 8); + + //Use the Filter + result.State = BlindscanResultState.Scraping; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + jobDisplay = new JobDisplay(JobContext); + lock (JobContext.Renderables) + { + JobContext.Renderables.Add(jobDisplay); + } + + SoundPlayer.PlaySoundFile("Success1.wav"); + skyscraperContext = SkyscraperContextFactory.CreateSkyscraper(JobContext.ScraperEventLogger, JobContext.ScraperStorage); + skyscraperContext.TcpProxyEnabled = true; + skyscraperContext.UiJunction = jobDisplay; + IPacketFilter[] packetFilters = new IPacketFilter[0]; + if (tsRecorder != null) + packetFilters = new IPacketFilter[] { tsRecorder }; + skyscraperContext.InitalizeFilterChain(tsRecorder); + + + byte[] singlePacketBuffer = new byte[188]; + + startedAt = DateTime.MinValue; + packetsReceivedInTotal = 0; + ourFoundFrequenciesWindow.allowZapNow = true; + + //The actual scraping happens in this loop + while (!StopConditionMet()) + { + if (!HasPackets()) + { + Thread.Sleep(100); + continue; + } + + byte[] packetBuffer = null; + lock (packetsQueue) + { + packetBuffer = packetsQueue.Dequeue(); + } + + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + } + } + ourFoundFrequenciesWindow.allowZapNow = false; + //Stop Filter + JobContext.LogWindow.Log(String.Format("Deleting Filter..."), 8); + bool stopped = streamReader.DelFilter(filterReference); + if (!stopped) + { + result.State = BlindscanResultState.DelFilterFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + return; + } + JobContext.LogWindow.Log(String.Format("Deleted filter!"), 8); + DrainPackets(); + skyscraperContext.Dispose(); + + if (tsRecorder != null) + tsRecorder.Dispose(); + + + lock (JobContext.Renderables) + { + JobContext.Renderables.Remove(jobDisplay); + } + + result.State = BlindscanResultState.DataSaving; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + + foreach (HumanReadableService humanReadableService in jobDisplay.GetServices()) + { + jobStorage.InsertTransponderService(jobInDb, result.Satellite, result.sr1, result.sr2, humanReadableService); + } + + jobDisplay = null; + result.State = BlindscanResultState.Done; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + + + } + /*bool cableTvMode = false; + Caps caps = streamReader.GetCaps(); + if (caps.HasFlag(Caps.SR_SIGINFO)) + { + cableTvMode = false; + } + else if (caps.HasFlag(Caps.SR_SIGINFO2)) + { + cableTvMode = true; + } + else + { + throw new NotImplementedException("Couldn't figure out what signal info to use."); + } + + if (!HasLock(cableTvMode)) + { + result.State = BlindscanResultState.NoLock; + return; + } + + int misCounter = 1; + int misMode = 0; + byte[] s2_is = null; + if (!cableTvMode) + { + SearchResult sr = default; + if (!streamReader.SignalInfo(ref sr)) + { + result.State = BlindscanResultState.NoLock; + return; + } + + if (sr.MIS > 16) + sr.MIS = 0; + + if (!sr.Lock) + return; + + misMode = sr.MIS; + s2_is = sr.IS; + } + + for (int mis = 0; mis < misCounter; mis++) + { + if (misMode != 0) + { + JobContext.LogWindow.Log(String.Format("Selecting MIS IS {0}", s2_is[mis]),8); + bool misSel = streamReader.MISSel(misMode != 0, s2_is[mis], 0xff); + if (!misSel) + { + result.State = BlindscanResultState.MisFailure; + return; + } + } + + //Start Filter + if (PrepareRecording()) + { + FileInfo recordingFileInfo = new FileInfo(Path.Combine(RecordingOutputDirectory.FullName,recordingFilename)); + recordingFileInfo.Directory.EnsureExists(); + recordingFileStream = recordingFileInfo.OpenWrite(); + recordingBufferedStream = new BufferedStream(recordingFileStream); + } + + IntPtr filterReference = IntPtr.MaxValue; + JobContext.LogWindow.Log(String.Format("before set filter"), 8); + packetsQueue = new Queue(); + bool filter = streamReader.SetFilter(8192, DvbCallback, 0x02, 2, ref filterReference); + if (!filter) + { + result.State = BlindscanResultState.FilterFailure; + return; + } + + //Use the Filter + result.State = BlindscanResultState.Scraping; + jobDisplay = new JobDisplay(JobContext); + lock (JobContext.Renderables) + { + JobContext.Renderables.Add(jobDisplay); + } + + skyscraperContext = SkyscraperContextFactory.CreateSkyscraper(JobContext.ScraperEventLogger, JobContext.ScraperStorage); + skyscraperContext.TcpProxyEnabled = true; + skyscraperContext.UiJunction = jobDisplay; + byte[] singlePacketBuffer = new byte[188]; + while (!StopConditionMet()) + { + if (!HasPackets()) + { + Thread.Sleep(100); + continue; + } + + byte[] packetBuffer = null; + lock (packetsQueue) + { + packetBuffer = packetsQueue.Dequeue(); + } + + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + } + } + skyscraperContext.Dispose(); + + //Stop Filter + bool stopped = streamReader.DelFilter(filterReference); + if (!stopped) + { + result.State = BlindscanResultState.DelFilterFailed; + return; + } + + DrainPackets(); + if (recordingBufferedStream != null) + { + recordingBufferedStream.Flush(); + recordingBufferedStream.Close(); + recordingBufferedStream.Dispose(); + recordingBufferedStream = null; + recordingFileStream.Close(); + recordingFileStream.Dispose(); + recordingFileStream = null; + } + + lock (JobContext.Renderables) + { + JobContext.Renderables.Remove(jobDisplay); + } + + jobDisplay = null; + result.State = BlindscanResultState.Done;*/ + } + } + + + #endregion +} \ No newline at end of file diff --git a/GUIs/skyscraper8.UI.ImGui/Jobs/CoopBlindscan.cs b/GUIs/skyscraper8.UI.ImGui/Jobs/CoopBlindscan.cs new file mode 100644 index 0000000..0ac3a2e --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Jobs/CoopBlindscan.cs @@ -0,0 +1,1039 @@ +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using System.IO; +using ImGuiNET; +using SDL2Demo.Forms; +using skyscraper5.Skyscraper.IO; +using static SDL2Demo.Jobs.Blindscan; +using System.Drawing; +using System.Runtime.InteropServices; +using SDL2Demo.SdlWrapper; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.src.Mpeg2.PacketFilter; + +namespace SDL2Demo.Jobs +{ + class CoopBlindscan : IRenderable, IJob + { + private IDbBlindscanJobStorage jobStorage; + private List tunerMetadataList; + private JobContext jobContext; + private List satellites; + private List lnbs; + + public CoopBlindscan(List tuners, List satellitePositions, List lnbTypes, IDbBlindscanJobStorage jobStorage) + { + this.jobStorage = jobStorage; + this.tunerMetadataList = tuners.Where(x => x.Type == STD_TYPE.STD_DVBS || x.Type == STD_TYPE.STD_DVBS2).ToList(); + this.settingsWindowOpen = true; + this.satellites = satellitePositions; + this.lnbs = lnbTypes; + this.lastTuner = -2; + this.somethingStarted = false; + this.settingsWindowScanHorizontalLow = true; + this.settingsWindowScanHorizontalHigh = true; + this.settingsWindowScanVerticalLow = true; + this.settingsWindowScanVerticalHigh = true; + this.settingsWindowDiseqc = 1; + if (tuners.Count > 2) + { + this.settingsWindowTunerAselection = 0; + this.settingsWindowTunerBselection = 1; + } + } + + + public void Cancel() + { + throw new NotImplementedException(); + } + + #region Configuration Window + public JobContext JobContext { get { return jobContext; } set { jobContext = value; } } + public bool WindowOpen => settingsWindowOpen; + public CoopBlindscanConfiguration Configuration { get; private set; } + + private bool settingsWindowOpen; + private int settingsWindowTunerAselection; + private int settingsWindowTunerBselection; + private bool settingsWindowScanHorizontalLow, settingsWindowScanHorizontalHigh, settingsWindowScanVerticalLow, settingsWindowScanVerticalHigh; + private bool settingsWindowCaptureFile; + private int settingsWindowDiseqc; + private int settingsWindowSatellite; + public void Render() + { + if (ImGui.Begin("Blindscan", ref settingsWindowOpen, ImGuiWindowFlags.AlwaysAutoResize)) + { + if (tunerMetadataList.Count < 2) + { + ImGui.Text("You need at least to DVB-S Tuners for the cooperative blindscan."); + ImGui.End(); + return; + } + + ImGui.Text("This assumes that both tuners are connected to the same antenna set-up, using e.g. a splitter."); + + ImGui.PushID("tunerA"); + if (ImGui.BeginCombo("Tuner for BLScanEx", tunerMetadataList[settingsWindowTunerAselection].Name)) + { + for (int i = 0; i < tunerMetadataList.Count; i++) + { + bool isSelected = settingsWindowTunerAselection == i; + if (ImGui.Selectable(tunerMetadataList[i].Name,isSelected)) + { + settingsWindowTunerAselection = i; + } + } + ImGui.EndCombo(); + } + ImGui.PopID(); + + ImGui.PushID("tunerB"); + if (ImGui.BeginCombo("Tuner for SetFilter", tunerMetadataList[settingsWindowTunerBselection].Name)) + { + for (int i = 0; i < tunerMetadataList.Count; i++) + { + bool isSelected = settingsWindowTunerBselection == i; + if (ImGui.Selectable(tunerMetadataList[i].Name, isSelected)) + { + settingsWindowTunerBselection = i; + } + } + ImGui.EndCombo(); + } + ImGui.PopID(); + + ImGui.PushID("satellite"); + if (ImGui.BeginCombo("Target Satellite", satellites[settingsWindowSatellite].name)) + { + for (int i = 0; i < satellites.Count; i++) + { + bool isSelected = settingsWindowSatellite == i; + if (ImGui.Selectable(satellites[i].name,isSelected)) + { + settingsWindowSatellite = i; + } + } + ImGui.EndCombo(); + } + ImGui.PopID(); + + ImGui.Checkbox("Capture Packets to files", ref settingsWindowCaptureFile); + ImGui.Checkbox("Scan Low Horizontal Region", ref settingsWindowScanHorizontalLow); + ImGui.Checkbox("Scan High Horizontal Region", ref settingsWindowScanHorizontalHigh); + ImGui.Checkbox("Scan Low Vertical Region", ref settingsWindowScanVerticalLow); + ImGui.Checkbox("Scan High Vertical Region", ref settingsWindowScanVerticalHigh); + + ImGui.PushID("diseqc"); + ImGui.Text("DiSEqC"); + ImGui.SameLine(); + ImGui.RadioButton("A", ref settingsWindowDiseqc, 1); + ImGui.SameLine(); + ImGui.RadioButton("B", ref settingsWindowDiseqc, 2); + ImGui.SameLine(); + ImGui.RadioButton("C", ref settingsWindowDiseqc, 3); + ImGui.SameLine(); + ImGui.RadioButton("D", ref settingsWindowDiseqc, 4); + ImGui.PopID(); + + bool sameTunerSelected = settingsWindowTunerAselection == settingsWindowTunerBselection; + + if (sameTunerSelected) + { + ImGui.Text("Please select different tuners for the operations."); + ImGui.BeginDisabled(); + } + + if (ImGui.Button("Start")) + { + settingsWindowOpen = false; + int lnbIndex = tunerMetadataList[settingsWindowTunerAselection].Lnbs[settingsWindowDiseqc - 1]; + LnbType lnb = lnbs.Find(x => x.Id == lnbIndex); + Configuration = new CoopBlindscanConfiguration(tunerMetadataList[settingsWindowTunerAselection], tunerMetadataList[settingsWindowTunerBselection], settingsWindowScanHorizontalLow, settingsWindowScanHorizontalHigh, settingsWindowScanVerticalLow, settingsWindowScanVerticalHigh, settingsWindowDiseqc, settingsWindowSatellite, SatellitePosition.FromChecksum(satellites[settingsWindowSatellite].Checksum), lnb, settingsWindowCaptureFile); + } + ImGui.SameLine(); + + if (sameTunerSelected) + ImGui.EndDisabled(); + + if (ImGui.Button("Cancel")) + { + settingsWindowOpen = false; + } + + ImGui.End(); + } + } + + public class CoopBlindscanConfiguration + { + public CoopBlindscanConfiguration(TunerMetadata tunerA, TunerMetadata tunerB, bool doHorizontalLow, bool doHorizontalHigh, bool doVerticalLow, bool doVerticalHigh, int diseqc, int satelliteId, SatellitePosition direction, LnbType lnbType, bool recordingEnabled) + { + TunerA = tunerA; + TunerB = tunerB; + DoHorizontalLow = doHorizontalLow; + DoHorizontalHigh = doHorizontalHigh; + DoVerticalLow = doVerticalLow; + DoVerticalHigh = doVerticalHigh; + Diseqc = diseqc; + SatelliteId = satelliteId; + Direction = direction; + LnbType = lnbType; + RecordingEnabled = recordingEnabled; + } + + public TunerMetadata TunerA { get; } + public TunerMetadata TunerB { get; } + public bool DoHorizontalLow { get; } + public bool DoHorizontalHigh { get; } + public bool DoVerticalLow { get; } + public bool DoVerticalHigh { get; } + public int Diseqc { get; } + public int SatelliteId { get; } + public SatellitePosition Direction { get; } + public LnbType LnbType { get; } + public bool RecordingEnabled { get; } + } + #endregion + + #region Blindscanning Driver + private IStreamReader streamReader; + private DbBlindscanJob jobInDb; + private List foundFrequencies; + private FoundFrequenciesWindow ourFoundFrequenciesWindow; + private BlindscanProgressWindow _blindscanProgressWindow; + private string recordingFilename; + private int packetsToDrop; + private Queue packetsQueue; + private JobDisplay jobDisplay; + private ISkyscraperContext skyscraperContext; + private DateTime startedAt; + private ulong packetsReceivedInTotal; + private ulong droppedPackets; + public void Run() + { + if (JobContext == null) + throw new NullReferenceException("Job Context was not enrolled."); + if (JobContext.StreamReader == null) + throw new NullReferenceException("No StreamReader available"); + if (streamReader == null) + streamReader = JobContext.StreamReader; + + + jobInDb = new DbBlindscanJob(Guid.NewGuid(), Configuration.TunerA.MacAddress, Configuration.TunerA.Type, Configuration.SatelliteId, JobContext.Gps, Configuration.Direction); + jobInDb.HorizontalHighState = Configuration.DoHorizontalHigh ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobInDb.HorizontalLowState = Configuration.DoHorizontalLow ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobInDb.VerticalLowState = Configuration.DoVerticalLow ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobInDb.VerticalHighState = Configuration.DoVerticalHigh ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED; + jobStorage.InsertBlindscanJob(jobInDb); + + foundFrequencies = new List(); + ourFoundFrequenciesWindow = new FoundFrequenciesWindow(foundFrequencies, Configuration.TunerA.Type, JobContext); + JobContext.Renderables.Add(ourFoundFrequenciesWindow); + if (!RunBlindscan()) + { + SetTuner(-1); + JobContext.ReadyForNextJob = true; + return; + } + + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + JobContext.Puppets[2].AutoMoveTo(new Point(0, 720 / 2)); + JobContext.Puppets[3].AutoMoveTo(new Point(1280 / 2, 0)); + + JobContext.ReadyForNextJob = true; + Console.WriteLine("Blindscan Job done!"); + return; + } + + public bool RunBlindscan() + { + Caps caps = Configuration.TunerA.Caps; + + if (caps.HasFlag(Caps.SR_BLSCAN2)) + { + SearchResult sr1 = default; + int tpNum = default; + + int lof1 = Configuration.LnbType.Lof1 * 1000; + int lof2 = Configuration.LnbType.Lof2 * 1000; + int lofSw = Configuration.LnbType.LofSw * 1000; + int diseqc = Configuration.TunerA.DiseqcType; + int startFreq = Configuration.LnbType.MinimumFrequency * 1000; + int endFreq = Configuration.LnbType.MaximumFrequency * 1000; + bool anythingSuceeded = false; + IntPtr allocHGlobal = Marshal.AllocHGlobal(UInt16.MaxValue); + + if (lofSw != 0) + { + if (Configuration.DoHorizontalLow) + { + SetTuner(1); + if (SendDiseqcCommand(false, true)) + { + JobContext.LogWindow.Log(String.Format("Scanning low horizontal band...")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool hLower = BlScan2Wrap(startFreq, lofSw, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult, 1))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!hLower) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning low horizontal area failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + SetTuner(2); + SendDiseqcCommand(false, true); + RunScrape(); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to low horizontal area failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + + if (Configuration.DoHorizontalHigh) + { + SetTuner(1); + if (SendDiseqcCommand(true, true)) + { + JobContext.LogWindow.Log(String.Format("Scanning high horizontal band...")); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool hUpper = BlScan2Wrap(lofSw, endFreq, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult, 2))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!hUpper) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning high horizontal area failed.")); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + SetTuner(2); + SendDiseqcCommand(true, true); + RunScrape(); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to high horizontal area failed.")); + jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + + if (Configuration.DoVerticalLow) + { + SetTuner(1); + if (SendDiseqcCommand(false, false)) + { + JobContext.LogWindow.Log(String.Format("Scanning low vertical band...")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool vLower = BlScan2Wrap(startFreq, lofSw, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult, 3))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!vLower) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning low vertical area failed.")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + SetTuner(2); + SendDiseqcCommand(false, false); + RunScrape(); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to low vertical area failed.")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + + if (Configuration.DoVerticalHigh) + { + SetTuner(1); + if (SendDiseqcCommand(true, false)) + { + JobContext.LogWindow.Log(String.Format("Scanning high vertical band...")); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool vUpper = BlScan2Wrap(lofSw, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult, 4))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!vUpper) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning high vertical area failed.")); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + SetTuner(2); + SendDiseqcCommand(true, false); + RunScrape(); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to high vertical area failed.")); + jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + } + } + else + { + SetTuner(1); + if (SendDiseqcCommand(false, true)) + { + JobContext.LogWindow.Log(String.Format("Scanning left circular band...")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool lCirc = BlScan2Wrap(startFreq, endFreq, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult, 1))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!lCirc) + { + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning left circular area failed.")); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + SetTuner(2); + SendDiseqcCommand(false, true); + RunScrape(); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to left circulation failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC; + jobStorage.UpdateJobState(jobInDb); + } + SetTuner(1); + if (SendDiseqcCommand(false, false)) + { + JobContext.LogWindow.Log(String.Format("Scanning right circular band...")); + jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING; + jobStorage.UpdateJobState(jobInDb); + bool rCirc = BlScan2Wrap(startFreq, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, ((ref SearchResult searchResult) => SearchResult1Callback(searchResult, 3))); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + if (!rCirc) + { + JobContext.MessageQueue.Enqueue(new MessageWindow("Blindscanning right circular area failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED; + jobStorage.UpdateJobState(jobInDb); + } + else + { + anythingSuceeded = true; + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING; + jobStorage.UpdateJobState(jobInDb); + SetTuner(2); + SendDiseqcCommand(false, false); + RunScrape(); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + foundFrequencies.Clear(); + } + } + else + { + JobContext.MessageQueue.Enqueue(new MessageWindow("DiSEqC Switch to right circulation failed.")); + jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE; + jobStorage.UpdateJobState(jobInDb); + } + } + + Marshal.FreeHGlobal(allocHGlobal); + SetTuner(-1); + return anythingSuceeded; + } + else + { + throw new NotImplementedException("Don't know how to blindscan with this tuner."); + } + } + + private bool somethingStarted; + private int lastTuner; + private bool SetTuner(int mode) + { + if (lastTuner == mode) + return true; + + if (somethingStarted) + { + streamReader.StopDVB(); + somethingStarted = false; + } + + bool result; + switch(mode) + { + case 1: + result = streamReader.StartDvbEx(Configuration.TunerA.Index); + break; + case 2: + result = streamReader.StartDvbEx(Configuration.TunerB.Index); + break; + case -1: + result = true; + break; + default: + throw new NotImplementedException(String.Format("Tuner mode {0}", mode)); + } + if (mode != -1 && result) + somethingStarted = true; + + if (!result) + throw new Exception("Starting tuner failed."); + lastTuner = mode; + return true; + } + + private bool BlScan2Wrap(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofSw, IntPtr pSearchResult, ref int pTpNum, BlScanCallback lpFunc) + { + _blindscanProgressWindow = new BlindscanProgressWindow(); + _blindscanProgressWindow.Start = freq_start; + _blindscanProgressWindow.Progress = freq_start; + _blindscanProgressWindow.End = freq_stop; + lock (JobContext.Renderables) + { + JobContext.Renderables.Add(_blindscanProgressWindow); + } + + bool result = streamReader.BLScan2(freq_start, freq_stop, pol, lof1, lof2, lofSw, pSearchResult, ref pTpNum, lpFunc); + + lock (JobContext.Renderables) + { + JobContext.Renderables.Remove(_blindscanProgressWindow); + } + + return result; + } + + private bool SendDiseqcCommand( bool highBand, bool horizontal) + { + DiSEqC_Opcode myOpcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE; + if (highBand) + myOpcode |= DiSEqC_Opcode.DISEQC_HIGH_BAND; + else + myOpcode |= DiSEqC_Opcode.DISEQC_LOW_BAND; + + if (horizontal) + myOpcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL; + else + myOpcode |= DiSEqC_Opcode.DISEQC_VERTICAL; + + int parameter = Configuration.Diseqc; + switch (parameter) + { + case 1: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A; + break; + case 2: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B; + break; + case 3: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A; + break; + case 4: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B; + break; + default: + throw new ArgumentOutOfRangeException("DiSEqC Switch Position"); + } + + JobContext.LogWindow.Log(String.Format("Send DiSEqC Command {0:X2}", (byte)myOpcode), 8); + + DateTime started = DateTime.Now; + bool result = streamReader.SendDiSEqC((uint)2, myOpcode); + TimeSpan timeTaken = DateTime.Now - started; + JobContext.LogWindow.Log(String.Format("DiSEqC Comannd sent in {0:F1} seconds.", timeTaken.TotalSeconds)); + if (timeTaken.TotalSeconds > 10) + { + JobContext.LogWindow.Log( + String.Format("Something went wrong while performing the DiSEqC operation, trying again..."), 8); + Thread.Sleep(1000); + return SendDiseqcCommand(highBand, horizontal); + } + + return result; + } + + private void SearchResult1Callback(SearchResult searchResult, int polarityIndex) + { + BlindscanResult blindscanResult = new BlindscanResult(searchResult, Configuration.LnbType.MinimumFrequency, Configuration.LnbType.MaximumFrequency); + JobContext.Puppets[blindscanResult.sr1.Pol].AutoMoveTo(blindscanResult.Position); + lock (JobContext.PressurePlates) + { + JobContext.PressurePlates.Add(blindscanResult); + } + + lock (foundFrequencies) + { + foundFrequencies.Add(blindscanResult); + } + + JobContext.LogWindow.Log(String.Format("Found frequency: {0}, {1}", searchResult.Freq / 1000, searchResult.Pol == 0 ? "H" : "V")); + SoundPlayer.PlaySoundFile("lock.wav"); + + _blindscanProgressWindow.Progress = searchResult.Freq; + + jobStorage.InsertSearchResult(jobInDb, true, searchResult, polarityIndex, new SearchResult2()); + } + + + public void RunScrape() + { + int lof1 = Configuration.LnbType.Lof1 * 1000; + int lof2 = Configuration.LnbType.Lof2 * 1000; + int lofSw = Configuration.LnbType.LofSw * 1000; + + foreach (BlindscanResult blindscanResult in foundFrequencies) + { + recordingFilename = null; + DateTime now = DateTime.Now; + + JobContext.Puppets[2 + blindscanResult.sr1.Pol].AutoMoveTo(blindscanResult.Position); + blindscanResult.State = BlindscanResultState.Tuning; + jobStorage.UpdateTransponderState(jobInDb, blindscanResult.Satellite, blindscanResult.sr1, blindscanResult.State, blindscanResult.sr2); + bool channel = streamReader.SetChannel(blindscanResult.sr1.Freq, blindscanResult.sr1.SR, + blindscanResult.sr1.Pol, blindscanResult.sr1.FEC, lof1, lof2, lofSw); + if (!channel) + { + blindscanResult.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, blindscanResult.Satellite, blindscanResult.sr1, blindscanResult.State, blindscanResult.sr2); + continue; + } + + recordingFilename = String.Format( + "skyscraper_{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}_{8:D4}{9}_{5}_{6}_{7}.ts", + now.Year, now.Month, now.Day, now.Hour, now.Minute, blindscanResult.sr1.Freq / 1000, + blindscanResult.sr1.Pol == 0 ? "H" : "V", blindscanResult.sr1.SR / 1000, + (int)(Configuration.Direction.angle * 10), + Configuration.Direction.cardinalDirection == 0 ? "E" : "W"); + + + RunSkyscraper(blindscanResult); + blindscanResult.Visible = false; + } + + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.Puppets[1].AutoMoveToHome(); + JobContext.Puppets[2].AutoMoveToHome(); + JobContext.Puppets[3].AutoMoveToHome(); + } + + private void RunSkyscraper(BlindscanResult result) + { + int misCounter = 1; + bool misMode = false; + bool cableTvMode = false; + Caps caps = streamReader.GetCaps(); + SearchResult satelliteSr = new SearchResult(); + SearchResult2 cableSr = new SearchResult2(); + + if (caps.HasFlag(Caps.SR_SIGINFO)) + { + JobContext.LogWindow.Log(String.Format("Trying to BLScanEx..."), 8); + if (!streamReader.BLScanEx(result.sr1.Freq, 5000, result.sr1.Pol, + Configuration.LnbType.Lof1 * 1000, Configuration.LnbType.Lof2 * 1000, + Configuration.LnbType.LofSw * 1000, 1000000, (STD_TYPE)result.sr1.StdType, + ref satelliteSr)) + { + //No blindscan? No problem! Try anyway! + satelliteSr = result.sr1; + satelliteSr.Lock = false; + result.State = BlindscanResultState.BlScanFailure; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + } + + if (!satelliteSr.Lock) + { + JobContext.LogWindow.Log(String.Format("Trying to SetChannel..."), 8); + bool channel = streamReader.SetChannel(result.sr1.Freq, result.sr1.SR, result.sr1.Freq, result.sr1.FEC, Configuration.LnbType.Lof1 * 1000, Configuration.LnbType.Lof2 * 1000, Configuration.LnbType.LofSw * 1000); + if (!channel) + { + result.State = BlindscanResultState.TuningFailed; + SoundPlayer.PlaySoundFile("fail.wav"); + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + return; + } + else + { + JobContext.LogWindow.Log(String.Format("Trying to get SignalInfo..."), 8); + bool signalInfo = streamReader.SignalInfo(ref satelliteSr); + if (!signalInfo) + { + result.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + + if (!satelliteSr.Lock) + { + result.State = BlindscanResultState.NoLock; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + } + } + + if (caps.HasFlag(Caps.SR_MISSEL)) + { + if (satelliteSr.MIS > 16) + satelliteSr.MIS = 1; + + if (satelliteSr.MIS == 0) + satelliteSr.MIS = 1; + + if (satelliteSr.MIS != 1) + misMode = true; + + misCounter = satelliteSr.MIS; + } + } + else if (caps.HasFlag(Caps.SR_SIGINFO2)) + { + JobContext.LogWindow.Log(String.Format("Trying to SetChannel2..."), 8); + bool channel2 = streamReader.SetChannel2((uint)result.sr2.Freq, (uint)result.sr2.BW); + if (!channel2) + { + result.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + + JobContext.LogWindow.Log(String.Format("Trying to get SignalInfo2..."), 8); + bool signalInfo2 = streamReader.SignalInfo2(ref cableSr); + if (!signalInfo2) + { + result.State = BlindscanResultState.TuningFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + + if (!cableSr.Lock) + { + result.State = BlindscanResultState.NoLock; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + } + else + { + throw new NotImplementedException("Couldn't figure out what signal info to use."); + } + + for (int mis = 0; mis < misCounter; mis++) + { + if (misMode) + { + JobContext.LogWindow.Log(String.Format("Selecting MIS IS {0}", satelliteSr.IS[mis]), 8); + bool misSel = streamReader.MISSel(misMode, satelliteSr.IS[mis], 0xff); + if (!misSel) + { + result.State = BlindscanResultState.MisFailure; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + } + + //Start Filter + TsRecorder tsRecorder = null; + if (Configuration.RecordingEnabled) + { + tsRecorder = new TsRecorder(); + tsRecorder.Recording = true; + string outputDirName = JobContext.Ini.ReadValue("recording", "output_dir", "recording_output"); + tsRecorder.RecordingOutputDirectory = new DirectoryInfo(outputDirName); + tsRecorder.RecordingOutputDirectory.EnsureExists(); + if (tsRecorder.PrepareRecording()) + { + tsRecorder.SetNextFilename(recordingFilename); + tsRecorder.CreateBufferedStream(); + } + } + + IntPtr filterReference = IntPtr.MaxValue; + JobContext.LogWindow.Log(String.Format("Set-Up filter..."), 8); + packetsToDrop = 1024; + packetsQueue = new Queue(); + ourFoundFrequenciesWindow.statusPacketsInqueue = 0; + ourFoundFrequenciesWindow.statusPacketsInTotal = 0; + bool filter = streamReader.SetFilter(8192, DvbCallback, 0x02, 2, ref filterReference); + if (!filter) + { + result.State = BlindscanResultState.FilterFailure; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + SoundPlayer.PlaySoundFile("fail.wav"); + return; + } + JobContext.LogWindow.Log(String.Format("Filter set-up complete!"), 8); + + //Use the Filter + result.State = BlindscanResultState.Scraping; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + jobDisplay = new JobDisplay(JobContext); + lock (JobContext.Renderables) + { + JobContext.Renderables.Add(jobDisplay); + } + + SoundPlayer.PlaySoundFile("Success1.wav"); + SkipFilter skipper = new SkipFilter(SkipFilter.CRAZYSCAN_BUFFER * 7); + skyscraperContext = SkyscraperContextFactory.CreateSkyscraper(JobContext.ScraperEventLogger, JobContext.ScraperStorage); + skyscraperContext.TcpProxyEnabled = true; + skyscraperContext.UiJunction = jobDisplay; + IPacketFilter[] packetFilters = new IPacketFilter[0]; + if (tsRecorder != null) + packetFilters = new IPacketFilter[] { tsRecorder, skipper }; + skyscraperContext.InitalizeFilterChain(packetFilters); + + + byte[] singlePacketBuffer = new byte[188]; + + startedAt = DateTime.MinValue; + packetsReceivedInTotal = 0; + ourFoundFrequenciesWindow.allowZapNow = true; + + //The actual scraping happens in this loop + while (!StopConditionMet()) + { + if (!HasPackets()) + { + Thread.Sleep(100); + continue; + } + + byte[] packetBuffer = null; + lock (packetsQueue) + { + packetBuffer = packetsQueue.Dequeue(); + } + + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + } + } + ourFoundFrequenciesWindow.allowZapNow = false; + //Stop Filter + JobContext.LogWindow.Log(String.Format("Deleting Filter..."), 8); + bool stopped = streamReader.DelFilter(filterReference); + if (!stopped) + { + result.State = BlindscanResultState.DelFilterFailed; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + return; + } + JobContext.LogWindow.Log(String.Format("Deleted filter!"), 8); + DrainPackets(); + skyscraperContext.Dispose(); + + if (tsRecorder != null) + tsRecorder.Dispose(); + + + lock (JobContext.Renderables) + { + JobContext.Renderables.Remove(jobDisplay); + } + + result.State = BlindscanResultState.DataSaving; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + + foreach (HumanReadableService humanReadableService in jobDisplay.GetServices()) + { + jobStorage.InsertTransponderService(jobInDb, result.Satellite, result.sr1, result.sr2, humanReadableService); + } + + jobDisplay = null; + result.State = BlindscanResultState.Done; + jobStorage.UpdateTransponderState(jobInDb, result.Satellite, result.sr1, result.State, result.sr2); + } + } + + private void DvbCallback(IntPtr data, int length) + { + if (length % 188 == 0) + { + try + { + byte[] buffer = new byte[length]; + Marshal.Copy(data, buffer, 0, length); + + if (packetsToDrop <= 0) + { + lock (packetsQueue) + { + packetsQueue.Enqueue(buffer); + } + } + + packetsToDrop -= (buffer.Length / 188); + packetsReceivedInTotal += (uint)(buffer.Length / 188); + ourFoundFrequenciesWindow.statusPacketsInTotal = packetsReceivedInTotal; + ourFoundFrequenciesWindow.statusPacketsInqueue = packetsQueue.Count; + } + catch (OutOfMemoryException e) + { + droppedPackets++; + } + } + else + { + JobContext.LogWindow.Log(String.Format("odd packet size!"), 8); + } + } + + private bool StopConditionMet() + { + if (startedAt == DateTime.MinValue) + startedAt = DateTime.Now; + + if (ourFoundFrequenciesWindow.zapNowRequested) + { + ourFoundFrequenciesWindow.zapNowRequested = false; + return true; + } + + if (ourFoundFrequenciesWindow.doNotAutoZap) + return false; + + if (packetsReceivedInTotal == 0 && (DateTime.Now - startedAt).TotalSeconds > 5) + return true; + + if (jobDisplay != null) + { + if (jobDisplay.GseCommunicationParties > 1000) + return true; + } + + if (!skyscraperContext.EnableTimeout) + { + skyscraperContext.TimeoutSeconds = 10; + skyscraperContext.EnableTimeout = true; + } + + if (packetsReceivedInTotal > 0 && skyscraperContext.IsAbortConditionMet()) + return true; + + return false; + } + + + private bool HasPackets() + { + lock (packetsQueue) + { + return packetsQueue.Count > 0; + } + } + + private void DrainPackets() + { + if (packetsQueue.Count == 0) + return; + + JobContext.LogWindow.Log( + String.Format("{0} packets left after switching off the filter.", packetsQueue.Count), 8); + + DateTime startedAt = DateTime.Now; + byte[] singlePacketBuffer = new byte[188]; + byte[] packetBuffer = null; + DateTime drainTickerPrevious = DateTime.Now, drainTickerNow = DateTime.Now; + while (packetsQueue.Count > 0) + { + packetBuffer = packetsQueue.Dequeue(); + if (packetBuffer == null) + continue; + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + } + + drainTickerPrevious = drainTickerNow; + drainTickerNow = DateTime.Now; + if (drainTickerNow.Second != drainTickerPrevious.Second) + { + JobContext.LogWindow.Log( + String.Format("{0} packets left ({1}).", packetsQueue.Count, drainTickerNow.ToLongTimeString()), + 8); + } + } + + JobContext.LogWindow.Log( + String.Format("Packets drained in {0} seconds.", (DateTime.Now - startedAt).TotalSeconds), 8); + } + + #endregion + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Jobs/ScrapeFromTcp.cs b/GUIs/skyscraper8.UI.ImGui/Jobs/ScrapeFromTcp.cs new file mode 100644 index 0000000..6f31b4a --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Jobs/ScrapeFromTcp.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using ImGuiNET; +using SDL2Demo.Forms; +using skyscraper5.Skyscraper.Scraper; + +namespace SDL2Demo.Jobs +{ + internal class ScrapeFromTcp : IRenderable, IJob + { + public ScrapeFromTcp() + { + isOpen = true; + ip = "127.0.0.1"; + port = 6969; + } + public bool IsOpen => isOpen; + public bool OkayButtonClicked { get; private set; } + + private bool isOpen; + private string ip; + private int port; + private bool wantsCancellation; + private bool allowTimeout; + public void Render() + { + ImGui.Begin("Connect to TCP Stream", ref isOpen, ImGuiWindowFlags.AlwaysAutoResize); + + ImGui.InputText("IP", ref ip, 100); + ImGui.InputInt("Port", ref port, 1, 10); + ImGui.Checkbox("Timeout", ref allowTimeout); + if (ImGui.Button("Connect")) + { + isOpen = false; + OkayButtonClicked = true; + } + + ImGui.End(); + } + + private JobDisplay jobDisplay; + private ISkyscraperContext skyscraperContext; + public void Run() + { + JobContext.Puppets[0].AutoMoveTo(new Point(JobContext.Puppets[0].GetHomeX(), 650)); + + TcpClient tc; + try + { + tc = new TcpClient(ip, port); + } + catch (Exception e) + { + JobContext.MessageQueue.Enqueue(new MessageWindow(e.Message)); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.ReadyForNextJob = true; + return; + } + + jobDisplay = new JobDisplay(JobContext); + JobContext.Puppets[0].AutoMoveTo(new Point(JobContext.Puppets[0].X, 600)); + skyscraperContext = SkyscraperContextFactory.CreateSkyscraper(JobContext.ScraperEventLogger, JobContext.ScraperStorage); + skyscraperContext.UiJunction = jobDisplay; + skyscraperContext.TcpProxyEnabled = true; + skyscraperContext.EnableTimeout = allowTimeout; + skyscraperContext.TimeoutSeconds = 60; + skyscraperContext.InitalizeFilterChain(); + BufferedStream bufferedStream = new BufferedStream(tc.GetStream(), 96256 * 2); + JobContext.Renderables.Add(jobDisplay); + JobContext.CanCancel = true; + skyscraperContext.IngestFromStream(bufferedStream); + skyscraperContext.Dispose(); + JobContext.CanCancel = false; + + tc.Close(); + tc.Dispose(); + JobContext.Puppets[0].AutoMoveToHome(); + JobContext.ReadyForNextJob = true; + } + + public void Cancel() + { + JobContext.CanCancel = false; + skyscraperContext.CancelOnNextPacket = true; + wantsCancellation = true; + } + + public JobContext JobContext { get; set; } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Net/IpPerformanceInfo.cs b/GUIs/skyscraper8.UI.ImGui/Net/IpPerformanceInfo.cs new file mode 100644 index 0000000..02392eb --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Net/IpPerformanceInfo.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Net +{ + public class IpPerformanceInfo + { + public long TotalPackets; + public long PacketsSinceSecond; + public long PacketsPerSecond; + public long TotalBytes; + public long BytesSinceSecond; + public long BytesPerSecond; + public string SourceResolution; + public string DestinationResolution; + + public void UpdatePerSeconds() + { + PacketsPerSecond = PacketsSinceSecond; + PacketsSinceSecond = 0; + BytesPerSecond = BytesSinceSecond; + BytesSinceSecond = 0; + } + + public void CountPacket(int len) + { + TotalPackets++; + PacketsSinceSecond++; + TotalBytes += len; + BytesSinceSecond += len; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Net/IpTrafficInfoComparer.cs b/GUIs/skyscraper8.UI.ImGui/Net/IpTrafficInfoComparer.cs new file mode 100644 index 0000000..03005c8 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Net/IpTrafficInfoComparer.cs @@ -0,0 +1,75 @@ +using skyscraper5.Skyscraper.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Net +{ + public sealed class IpTrafficInfoComparer : IComparer> + { + public IpTrafficInfoComparer(IpTrafficInfoComparerMode mode = IpTrafficInfoComparerMode.ByNumPackets) + { + ComparisionMode = mode; + } + + public IpTrafficInfoComparerMode ComparisionMode { get; set; } + public int Compare(KeyValuePair x, KeyValuePair y) + { + switch(ComparisionMode) + { + case IpTrafficInfoComparerMode.None: + return 0; + case IpTrafficInfoComparerMode.BySourceAddress: + return CompareSourceAddress(x.Key, y.Key); + case IpTrafficInfoComparerMode.ByNumPackets: + return CompareByNumPackets(x.Value, y.Value); + case IpTrafficInfoComparerMode.ByThroughput: + return x.Value.BytesPerSecond.CompareTo(y.Value.BytesPerSecond) / -1; + default: + throw new NotImplementedException(ComparisionMode.ToString()); + } + } + + private int CompareByNumPackets(IpPerformanceInfo x, IpPerformanceInfo y) + { + int realValue = x.TotalPackets.CompareTo(y.TotalPackets) / -1; + if (realValue != 0) + return realValue; + return x.TotalBytes.CompareTo(y.TotalBytes); + } + + private int CompareSourceAddress(IpTrafficInfo x, IpTrafficInfo y) + { + int cmp = 0; + byte[] l = x.Source.GetAddressBytes(); + byte[] r = y.Source.GetAddressBytes(); + for (int i = 0; i < l.Length; i++) + { + cmp = l[i].CompareTo(r[i]); + if (cmp != 0) + return cmp; + } + + l = x.Target.GetAddressBytes(); + r = y.Target.GetAddressBytes(); + for (int i = 0; i < l.Length; i++) + { + cmp = l[i].CompareTo(r[i]); + if (cmp != 0) + return cmp; + } + + return x.Protocol.CompareTo(y.Protocol); + } + } + + public enum IpTrafficInfoComparerMode + { + None = 0, + BySourceAddress = 1, + ByNumPackets = 2, + ByThroughput = 3 + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/NuGet.Config b/GUIs/skyscraper8.UI.ImGui/NuGet.Config new file mode 100644 index 0000000..70ba4a2 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/NuGet.Config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/GUIs/skyscraper8.UI.ImGui/Program.cs b/GUIs/skyscraper8.UI.ImGui/Program.cs new file mode 100644 index 0000000..b7f7363 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Program.cs @@ -0,0 +1,1018 @@ +using System.Collections.ObjectModel; +using System.Drawing; +using System.Net.NetworkInformation; +using System.Reflection; +using Echo.UserInterface.Backend; +using ImGuiNET; +using SDL2; +using SDL2Demo.SdlWrapper; +using testdrid.SdlWrapper; +using SDL2Demo.Forms; +using SDL2Demo; +using SDL2Demo.Jobs; +using SDL2Demo.Screenhacks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.IO.TunerInterface; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; +using skyscraper5.src.Skyscraper; + + +namespace SkyscraperUI +{ + public class Program + { + public static void Main(string[] args) + { + new Program().Run(); + } + + private bool quit; + + private void Run() + { + rng = new Random(); + + FileInfo iniFile = new FileInfo("skyscraper5.ini"); + if (iniFile.Exists) + ini = new Ini(iniFile.FullName); + else + ini = new Ini(); + + showPuppets = ini.ReadValue("ui", "enable_puppets", true); + + int sdlInit = SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING); + if (sdlInit != 0) + { + throw new Exception(SDL.SDL_GetError()); + } + + int imgInit = SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG); + if (imgInit != 2) + { + throw new Exception(SDL.SDL_GetError()); + } + + window = Window.Create("skyscraper5 GUI", 1280, 720); + renderer = Renderer.Create(window); + renderer.SetDrawBlendMode(SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); + PictureWindow.Renderer = renderer; + + IntPtr intPtr = ImGui.CreateContext(); + ImGui.SetCurrentContext(intPtr); + + ImGui.StyleColorsClassic(); + ImGui.GetStyle().FrameRounding = 2; + ImGui.GetStyle().FrameBorderSize = 1; + + imGuiDevice = new ImGuiDevice(window.Pointer, renderer.Pointer); + imGuiDevice.Initialize(); + EventPoller.GetInstance().Junction = imGuiDevice; + + Surface charsetSurface = Surface.LoadFromBMP("charset.bmp"); + charsetSurface.SetColorKey(1, 0x00000000); + Texture charsetTexture = Texture.FromSurface(renderer, charsetSurface); + charsetSurface.Dispose(); + + Surface object2Surface = Surface.ImageLoad("Object2.bmp"); + object2Surface.SetColorKey(1, 0x00000000); + Texture object2Texture = Texture.FromSurface(renderer, object2Surface); + object2Surface.Dispose(); + object2Charset = new CharSet(object2Texture) { CharacterNumber = 4 }; + + logWindow = new LogWindow(); + logWindow.IsOpen = ini.ReadValue("ui", "enable_log", true); ; + uiBlockingWindow = new UiBlockingWindow(); + messageWindows = new List(); + + LoadScreenHacks(); + + Thread theStartupThread = new Thread(StartupThread); + theStartupThread.Priority = ThreadPriority.Lowest; + theStartupThread.Start(); + + charsets = new CharSet[8]; + for (byte i = 0; i < 8; i++) + { + charsets[i] = new CharSet(charsetTexture) + { CharacterNumber = i, X = rng.Next(1280), Y = rng.Next(720) }; + charsets[i].SetHome(new Point(24 * i, 720 - 32)); + charsets[i].AutoMoveToHome(); + } + + EventPoller.GetInstance().Quit += TestdridClass_Quit; + + previous = DateTime.Now; + now = DateTime.Now; + while (!quit) + { + EventPoller.GetInstance().PollEvents(); + BeforeRenderFrame(); + + renderer.Clear(); + RenderFrame(); + imGuiDevice.Render(ImGui.GetDrawData()); + + renderer.Present(); + window.GlSwap(); + } + + if (CanCancelJob()) + jobContext.Job.Cancel(); + + if (gps != null) + gps.Stop(); + + streamReader.Dispose(); + } + + private void LoadScreenHacks() + { + Assembly assembly = GetType().Assembly; + Type[] types = assembly.GetTypes(); + types = types.Where(x => x.GetCustomAttribute(typeof(ScreenHackIdAttribute)) != null).ToArray(); + Array.Sort(types, new ScreenHackTypeComparer()); + allScreenHacks = new ScreenHack[types.Length]; + for (int i = 0; i < allScreenHacks.Length; i++) + { + allScreenHacks[i] = (ScreenHack)Activator.CreateInstance(types[i]); + } + + int readValue = ini.ReadValue("ui", "screenhack", -1); + if (readValue == -1) + { + readValue = 0; + } + + currentScreenHack = allScreenHacks[readValue]; + currentScreenHack.Setup(window.GetWindowRectangle(), renderer); + } + + private ImGuiDevice imGuiDevice; + private Random rng; + private Renderer renderer; + private Window window; + + private CharSet[] charsets; + private DateTime previous; + private DateTime now; + private UiBlockingWindow uiBlockingWindow; + private List tuners; + private ConfigureTunersWindow configureTunersWindow; + private Ini ini; + + private ReadOnlyCollection> scraperStorageFactories; + private ReadOnlyCollection> tunerFactories; + + private IStreamReader streamReader; + private IScraperStroage scraperStroage; + private IGpsReceiver gps; + + + private List messageWindows; + private AboutWindow aboutWindow; + private ConfigureStorageWindow configureStorageWindow; + private SatellitesConfigurationWindow satellitesConfigurationWindow; + private List satellitePositions; + private List lnbTypes; + private List dishTypes; + private LogWindow logWindow; + private ScrapeFromTcp scrapeFromTcpWindow; + private JobContext jobContext; + private CharSet object2Charset; + private Blindscan blindscanWindow; + private CoopBlindscan coopBlindscanWindow; + private ConfigureTunerFactoryWindow tunerFactoryWindow; + private ConfigureLnbTypes configureLnbTypesWindow; + private ConfigureDishTypes configureDishTypesWindow; + private ConfigureGpsWindow configureGpsWindow; + private GpsDisplay gpsDisplayWindow; + private BlindscanJobDeleter jobDeleter; + + private ScreenHack[] allScreenHacks; + private ScreenHack currentScreenHack; + + private bool showPuppets; + private bool showLogWindowOnJobStart; + private bool showConfigureTunersWindow; + private bool blockUi; + private bool showDemoWindow; + private bool showMainMenu; + private bool showIqWindow; + private bool showTunerFactoryConfiguration; + private bool showConfigureLnbTypesWindow; + private bool showConfigureDishTypesWindow; + + private string engineProductName; + private Version engineVersion; + + + private void BeforeRenderFrame() + { + if (jobContext != null) + { + if (jobContext.MessageQueue.Count != 0) + { + MessageWindow messageWindow = jobContext.MessageQueue.Dequeue(); + logWindow.Log(messageWindow.Message); + messageWindows.Add(messageWindow); + } + + if (jobContext.ReadyForNextJob) + { + foreach (CharSet charSet in charsets) + charSet.AutoMoveToHome(); + + logWindow.Log(String.Format("Job completed: {0}", jobContext.Job.ToString())); + SoundPlayer.PlaySoundFile("Teleport2.wav"); + jobContext = null; + } + } + } + + private bool CanOpenJobDeleter() + { + if (jobContext != null) + return false; + + if (jobDeleter != null) + { + if (!jobDeleter.Closed) + return false; + } + + return true; + } + + private bool CanOpenDishTypesConfiguration() + { + if (showConfigureDishTypesWindow) + return false; + + if (jobContext != null) + return false; + + return true; + } + + private bool CanOpenLnbTypesConfiguration() + { + if (showConfigureLnbTypesWindow) + return false; + + if (jobContext != null) + return false; + + return true; + } + + private bool CanOpenTunerFactoryConfiguration() + { + if (showTunerFactoryConfiguration) + return false; + + if (showConfigureTunersWindow) + return false; + + return true; + } + + private bool CanOpenTunerConfiguration() + { + if (showConfigureTunersWindow) + return false; + + if (tuners == null) + return false; + + if (tuners.Count == 0) + return false; + + if (satellitePositions.Count == 0) + return false; + + return true; + } + + private bool CanPerformBlindscan() + { + if (jobContext != null) + return false; + + if (tuners == null) + return false; + + if (tuners.Count == 0) + return false; + + if (showConfigureTunersWindow) + return false; + + if (blindscanWindow != null) + return false; + + if (coopBlindscanWindow != null) + return false; + + if (scrapeFromTcpWindow != null) + return false; + + return true; + } + + private bool CanScrapeFromTcpIp() + { + if (jobContext != null) + return false; + + if (scrapeFromTcpWindow != null) + return false; + + if (blindscanWindow != null) + return false; + + return true; + } + + private bool CanCancelJob() + { + if (jobContext == null) + return false; + + return jobContext.CanCancel; + } + + private bool _isDisplayingGpsLocation; + private bool IsDisplayingGpsLocation() + { + if (gpsDisplayWindow == null) + return false; + + return _isDisplayingGpsLocation; + } + + private bool CanShowGpsLocation() + { + return true; + } + + private bool IsDisplayingNmeaData() + { + if (gpsDisplayWindow == null) + return false; + + if (!_isDisplayingGpsLocation) + return false; + + if (!gpsDisplayWindow.HasNmea) + return false; + + return gpsDisplayWindow.RenderNmea; + } + + private bool CanDisplayNmeaData() + { + if (gpsDisplayWindow == null) + return false; + + if (!_isDisplayingGpsLocation) + return false; + + return gpsDisplayWindow.HasNmea; + } + + + private bool playedStartSound; + + private void RenderFrame() + { + if (!playedStartSound) + { + SoundPlayer.PlaySoundFile("Decision2.wav"); + playedStartSound = true; + } + currentScreenHack.Render(); + + if (showPuppets) + { + if (jobContext != null) + { + lock (jobContext.PressurePlates) + { + foreach (IPressurePlate jobContextPressurePlate in jobContext.PressurePlates) + { + if (jobContextPressurePlate.Visible) + { + object2Charset.X = jobContextPressurePlate.Position.X; + object2Charset.Y = jobContextPressurePlate.Position.Y; + object2Charset.Render(renderer); + } + } + } + } + + for (int i = 0; i < charsets.Length; i++) + { + //if (!charsets[i].AutoMoving || charsets[i].AutoMoveTargetReached) + //charsets[i].AutoMoveTo(new Point(rng.Next(1280), rng.Next(720))); + charsets[i].Render(renderer); + } + } + + previous = now; + now = DateTime.Now; + imGuiDevice.NewFrame(now - previous); + ImGui.NewFrame(); + + if (!blockUi) + { + + if (showMainMenu) + { + ImGui.BeginMainMenuBar(); + if (ImGui.BeginMenu("Jobs")) + { + if (ImGui.MenuItem("Connect to TCP/IP Stream", CanScrapeFromTcpIp())) + scrapeFromTcpWindow = new ScrapeFromTcp(); + if (ImGui.MenuItem("Perform Blindscan", CanPerformBlindscan())) + blindscanWindow = new Blindscan(tuners, satellitePositions, lnbTypes, scraperStroage); + if (ImGui.MenuItem("Perform Cooperative Blindscan", CanPerformBlindscan())) + coopBlindscanWindow = new CoopBlindscan(tuners, satellitePositions, lnbTypes, scraperStroage); + if (ImGui.MenuItem("Cancel current job", CanCancelJob())) + jobContext.Job.Cancel(); + if (ImGui.MenuItem("Quit")) + quit = true; + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("View")) + { + if (ImGui.MenuItem("Show Puppets", "", showPuppets)) + showPuppets = !showPuppets; + if (ImGui.MenuItem("Show ImGui Demo Window", "", showDemoWindow)) + showDemoWindow = !showDemoWindow; + if (ImGui.MenuItem("Show Log Window", "", logWindow.IsOpen)) + logWindow.IsOpen = !logWindow.IsOpen; + if (ImGui.MenuItem("Auto-Show Log Window when job starts", "", showLogWindowOnJobStart)) + showLogWindowOnJobStart = !showLogWindowOnJobStart; + if (ImGui.MenuItem("Show GPS Location", "", IsDisplayingGpsLocation(), CanShowGpsLocation())) + { + if (gpsDisplayWindow == null) + gpsDisplayWindow = new GpsDisplay(gps); + _isDisplayingGpsLocation = !_isDisplayingGpsLocation; + } + + if (ImGui.MenuItem("Show NMEA data", "", IsDisplayingNmeaData(), CanDisplayNmeaData())) + gpsDisplayWindow.RenderNmea = !gpsDisplayWindow.RenderNmea; + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Configuration")) + { + if (ImGui.MenuItem("Configure Tuner Factory", CanOpenTunerFactoryConfiguration())) + { + showTunerFactoryConfiguration = true; + tunerFactoryWindow = new ConfigureTunerFactoryWindow(ini, tunerFactories); + } + if (ImGui.MenuItem("Configure Tuners", CanOpenTunerConfiguration())) + { + showConfigureTunersWindow = true; + configureTunersWindow = new ConfigureTunersWindow(tuners, scraperStroage); + } + + if (ImGui.MenuItem("Configure LNB Types", CanOpenLnbTypesConfiguration())) + { + showConfigureLnbTypesWindow = true; + configureLnbTypesWindow = new ConfigureLnbTypes(scraperStroage, lnbTypes); + } + + if (ImGui.MenuItem("Configure Dish Types", CanOpenDishTypesConfiguration())) + { + showConfigureDishTypesWindow = true; + configureDishTypesWindow = new ConfigureDishTypes(scraperStroage, dishTypes); + } + + if (ImGui.MenuItem("Configure ScraperStorage", configureStorageWindow == null)) + { + configureStorageWindow = new ConfigureStorageWindow(ini, scraperStorageFactories); + } + + if (ImGui.MenuItem("Configure GPS Receiver", configureGpsWindow == null)) + { + configureGpsWindow = new ConfigureGpsWindow(ini); + } + + if (ImGui.MenuItem("Configure Satellite Positions", satellitesConfigurationWindow == null)) + { + satellitesConfigurationWindow = new SatellitesConfigurationWindow(scraperStroage); + } + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Data")) + { + if (ImGui.MenuItem("Delete Blindscan Jobs", CanOpenJobDeleter())) + { + jobDeleter = new BlindscanJobDeleter(scraperStroage); + } + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Help")) + { + if (ImGui.MenuItem("About skyscraper5", aboutWindow == null)) + { + aboutWindow = new AboutWindow(); + } + + ImGui.EndMenu(); + } + + ImGui.EndMainMenuBar(); + } + + if (jobDeleter != null) + { + jobDeleter.Render(); + if (jobDeleter.Closed) + { + jobDeleter = null; + } + } + + if (showConfigureDishTypesWindow) + { + configureDishTypesWindow.Render(); + if (configureDishTypesWindow.Closed) + { + showConfigureDishTypesWindow = false; + configureDishTypesWindow = null; + } + } + if (showConfigureLnbTypesWindow) + { + configureLnbTypesWindow.Render(); + if (configureLnbTypesWindow.Closed) + { + showConfigureLnbTypesWindow = false; + configureLnbTypesWindow = null; + } + } + if (showTunerFactoryConfiguration) + { + tunerFactoryWindow.Render(); + if (tunerFactoryWindow.Closed) + { + if (tunerFactoryWindow.RequireRestart) + { + MessageWindow mw = new MessageWindow( + "To apply the tuner factory configuration, skyscraper5 will need to restart. Press the okay button to exit the application."); + mw.OnClose = () => quit = true; + messageWindows.Add(mw); + } + + showTunerFactoryConfiguration = false; + tunerFactoryWindow = null; + } + } + + if (showConfigureTunersWindow) + { + configureTunersWindow.Render(); + if (configureTunersWindow.Closed) + { + showConfigureTunersWindow = false; + configureTunersWindow = null; + } + } + + if (showDemoWindow) + ImGui.ShowDemoWindow(); + + if (configureStorageWindow != null) + { + configureStorageWindow.Render(); + if (!configureStorageWindow.IsOpen) + { + if (configureStorageWindow.RequireRestart) + { + MessageWindow mw = new MessageWindow( + "To apply the storage configuration, skyscraper5 will need to restart. Press the okay button to exit the application."); + mw.OnClose = () => quit = true; + messageWindows.Add(mw); + } + + configureStorageWindow = null; + } + } + + if (configureGpsWindow != null) + { + configureGpsWindow.Render(); + if (!configureGpsWindow.IsOpen) + { + if (configureGpsWindow.RequireRestart) + { + MessageWindow mw = new MessageWindow( + "To apply the GPS receiver configuration, skyscraper5 will need to restart. Press the okay button to exit the application."); + mw.OnClose = () => quit = true; + messageWindows.Add(mw); + } + + configureGpsWindow = null; + } + } + + if (satellitesConfigurationWindow != null) + { + satellitesConfigurationWindow.Render(); + if (satellitesConfigurationWindow.Closed) + { + satellitesConfigurationWindow = null; + satellitePositions = scraperStroage.UiSatellitesListAll(); + } + } + + if (scrapeFromTcpWindow != null) + { + scrapeFromTcpWindow.Render(); + if (!scrapeFromTcpWindow.IsOpen) + { + if (scrapeFromTcpWindow.OkayButtonClicked) + { + EnrollJob(scrapeFromTcpWindow); + } + + scrapeFromTcpWindow = null; + } + } + + if (blindscanWindow != null) + { + blindscanWindow.Render(); + if (!blindscanWindow.WindowOpen) + { + if (blindscanWindow.SelectedBlindscanTarget != null || blindscanWindow.ContinuationMode) + { + EnrollJob(blindscanWindow); + } + + blindscanWindow = null; + } + } + + if (coopBlindscanWindow != null) + { + coopBlindscanWindow.Render(); + if (!coopBlindscanWindow.WindowOpen) + { + if (coopBlindscanWindow.Configuration != null) + { + EnrollJob(coopBlindscanWindow); + } + coopBlindscanWindow = null; + } + } + + if (aboutWindow != null) + { + aboutWindow.Render(); + if (aboutWindow.Closed) + aboutWindow = null; + } + + if (_isDisplayingGpsLocation) + { + gpsDisplayWindow.Render(); + } + + if (jobContext != null) + { + if (jobContext.Renderables.Count > 0) + { + lock (jobContext.Renderables) + { + foreach (IRenderable jobContextRenderable in jobContext.Renderables) + { + jobContextRenderable.Render(); + } + } + } + } + + logWindow.Render(); + + foreach (MessageWindow messageWindow in messageWindows) + { + messageWindow.Render(); + } + + messageWindows.RemoveAll(x => x.Closed); + } + else + { + uiBlockingWindow.Render(); + } + + ImGui.EndFrame(); + ImGui.Render(); + + } + + private void TestdridClass_Quit(SDL.SDL_QuitEvent qe) + { + quit = true; + gps.Stop(); + } + + private void EnrollJob(IJob job) + { + if (jobContext == null) + { + jobContext = new JobContext(); + } + + if (showLogWindowOnJobStart) + logWindow.IsOpen = true; + + job.JobContext = jobContext; + + jobContext.Gps = gps; + jobContext.ImgUiDevice = imGuiDevice; + jobContext.Ini = ini; + jobContext.StreamReader = streamReader; + jobContext.LogWindow = logWindow; + jobContext.PressurePlates = new List(); + jobContext.RNG = rng; + jobContext.MessageQueue = new Queue(); + jobContext.Renderables = new List(); + jobContext.Job = job; + jobContext.ReadyForNextJob = false; + jobContext.Puppets = charsets; + jobContext.ScraperStorage = scraperStroage; + jobContext.ScraperEventLogger = logWindow; + jobContext.Thread = new Thread(job.Run); + jobContext.Thread.Name = "Current Job"; + jobContext.Thread.Start(); + } + + private void StartupThread() + { + uiBlockingWindow.Title = "Starting up..."; + uiBlockingWindow.Text = "Please wait while I read the configuration file..."; + blockUi = true; + + //LOAD STORAGE ---------------------------------------------------------------------------------------------------------------------------------------------------------- + uiBlockingWindow.Text = "Please wait while I load the storage factory library..."; + ScraperStorageFactoryConnectionManager scraperStorageFactoryConnectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + + uiBlockingWindow.Text = "Please wait while I check the storage factory classes..."; + scraperStorageFactories = scraperStorageFactoryConnectionManager.GetKnownFactories(); + int storage = ini.ReadValue("startup", "storage", 0); + KeyValuePair bootingStorage = + scraperStorageFactories.First(x => x.Key.Id == storage); + if (bootingStorage.Key.VolatileStorage) + messageWindows.Add(new MessageWindow( + "Please note that Skyscraper is currently using a volatile storage class. This will work, but won't save any data. If you want to save the gathered data, please configure a non-volatile storage class.")); + + uiBlockingWindow.Text = "Please wait while I apply the storage factory class configuration..."; + string factoryCname = String.Format("storage{0}", bootingStorage.Key.Id); + ScraperStorageFactoryConnectionManager.ConfigureFactoryFromIni(bootingStorage, ini); + + uiBlockingWindow.Text = "Please wait while I start-up the storage..."; + try + { + scraperStroage = bootingStorage.Value.CreateScraperStroage(); + } + catch (Exception e) + { + scraperStroage = new InMemoryScraperStorageFactory().CreateScraperStroage(); + string rant = String.Format( + "{0} failed to start.\nThe following went wrong:\n\n{1}\n\nI've switched to volatile storage. This will work, but won't save any data. If you want to save the gathered data, please consider configuring {0} correctly.", + bootingStorage.Value.GetType().Name, e.Message); + messageWindows.Add(new MessageWindow(rant)); + } + scraperStroage.UiSetVersion(2); + + uiBlockingWindow.Text = "Please wait while I test whether the storage is responding..."; + try + { + scraperStroage.Ping(); + } + catch (Exception e) + { + scraperStroage = new InMemoryScraperStorageFactory().CreateScraperStroage(); + string rant = String.Format( + "{0} failed to respond.\nThe following went wrong:\n\n{1}\n\nI've switched to volatile storage. This will work, but won't save any data. If you want to save the gathered data, please consider configuring {0} correctly.", + bootingStorage.Value.GetType().Name, e.Message); + messageWindows.Add(new MessageWindow(rant)); + } + + //LOAD GPS ---------------------------------------------------------------------------------------------------------------------------------------------------------------- + uiBlockingWindow.Text = "Please wait while I load the GPS receiver library..."; + int gpsReceiverId = GpsManager.GetConfiguredGpsId(); + IGpsReceiverFactory gpsReceiverFactory = GpsManager.GetGpsReceiverFactoryById(gpsReceiverId); + GpsManager.AutoconfigureGpsReceiverFactory(gpsReceiverFactory); + + uiBlockingWindow.Text = "Please wait while I instantiate the GPS receiver..."; + gps = gpsReceiverFactory.CreateGpsReceiver(); + + uiBlockingWindow.Text = "Please wait while I start the GPS receiver..."; + gps.Start(); + + //LOAD BASE DATA -------------------------------------------------------------------------------------------------------------------------------------------------------------- + uiBlockingWindow.Text = "Please wait while I query the storage for known satellite positions..."; + satellitePositions = scraperStroage.UiSatellitesListAll(); + + uiBlockingWindow.Text = "Please wait while I check for the default LNB types..."; + EquipmentUtilities.InsertDefaultLnbTypes(scraperStroage); + + uiBlockingWindow.Text = "Please wait while I query the storage for known LNB types..."; + lnbTypes = scraperStroage.UiLnbTypesListAll(); + + uiBlockingWindow.Text = "Please wait while I check for the default dish types..."; + EquipmentUtilities.InsertDefaultDishTypes(scraperStroage); + + uiBlockingWindow.Text = "Please wait while I query the storage for known Dish types..."; + dishTypes = scraperStroage.UiDishTypesListAll(); + + //LOAD TUNER -------------------------------------------------------------------------------------------------------------------------------------------------------------------- + uiBlockingWindow.Text = "Please wait while I check the Tuner Factory classes..."; + TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance(); + tunerFactories = tunerFactoryConnectionManager.GetKnownFactories(); + int tunerFactory = ini.ReadValue("startup", "tunerFactory", 0); + KeyValuePair bootingTuner = tunerFactories.First(x => x.Key.Id == tunerFactory); + bool isNoTuner = bootingTuner.Key.DisplayName.Equals("No tuner"); + if (isNoTuner) + { + messageWindows.Add(new MessageWindow( + "Please not that Skyscraper is currently configured to not use a Tuner Factory Class. This will work, but functionality will be severely limited. Please configure a Tuner Factory Class in order to get the best experience.")); + } + + uiBlockingWindow.Text = "Please wait while I'll apply the tuner factory class configuration..:"; + TunerFactoryConnectionManager.ConfigureFactoryFromIni(bootingTuner, ini); + + uiBlockingWindow.Text = "Please wait while the Tuner Factory class code is being executed..."; + try + { + streamReader = bootingTuner.Value.CreateStreamReader(); + } + catch (Exception e) + { + uiBlockingWindow.Text = "Oh dear, it failed."; + messageWindows.Add(new MessageWindow( + "Please not that the Tuner Factory class code failed to execute. " + + "This won't stop the program from working, but functionality will be severely limited. " + + "Please make sure the Tuner Factory Class is properly configured in order to get the best experience.\n\n" + + "The following went wrong:\n" + e.Message)); + streamReader = new NullTunerFactory().CreateStreamReader(); + } + + + try + { + uiBlockingWindow.Text = "Please wait while I try to see whether the tuner factory works..."; + streamReader.CheckForDVB(); + } + catch (BadImageFormatException e) + { + uiBlockingWindow.Text = "Oh dear, it doesn't."; + messageWindows.Add(new MessageWindow( + "The configured tuner factory is not suitable for the current processor architecture. Tuning won't work, therefore functionality will be severely limited. Please configure a Tuner Factory Class suitable for this processor architecture in order to get the best experience. If you need to run crazycat69's StreamReader.dll on 64-bit machines, try RemoteStreamReader.")); + streamReader = new NullTunerFactory().CreateStreamReader(); + } + + uiBlockingWindow.Text = "Please wait while I check for your tuners..."; + List foundTuners = new List(); + bool checkForDvbExEx = streamReader.CheckForDVBExEx((index, name, type) => + { + TunerMetadata tuner = new TunerMetadata(index, name, type); + uiBlockingWindow.Line2 = String.Format("Found tuner {0}", name); + foundTuners.Add(tuner); + }); + if (!checkForDvbExEx) + { + if (!isNoTuner) + uiBlockingWindow.Line2 = String.Format("{0} has failed. Tuning won't be possible!", nameof(streamReader.CheckForDVBExEx)); + } + + foreach (TunerMetadata foundTuner in foundTuners) + { + streamReader.StopDVB(); + + uiBlockingWindow.Line2 = String.Format("Starting tuner {0}", foundTuner.Name); + bool startDvbEx = streamReader.StartDvbEx(foundTuner.Index); + if (!startDvbEx) + { + uiBlockingWindow.Line2 = String.Format("Failed to start {0}", foundTuner.Name); + Thread.Sleep(1000); + continue; + } + + uiBlockingWindow.Line2 = String.Format("Checking capabilities of {0}", foundTuner.Name); + foundTuner.Caps = streamReader.GetCaps(); + + byte[] macBuffer = new byte[6]; + uiBlockingWindow.Line2 = String.Format("Reading MAC Address of {0}", foundTuner.Name); + bool mac = streamReader.GetMAC(macBuffer); + if (!mac) + { + uiBlockingWindow.Line2 = String.Format("Failed to read MAC Address of {0}", foundTuner.Name); + Thread.Sleep(1000); + } + else + { + foundTuner.MacAddress = new PhysicalAddress(macBuffer); + } + + uiBlockingWindow.Line2 = String.Format("Stopping {0}", foundTuner.Name); + bool stopDvb = streamReader.StopDVB(); + if (!stopDvb) + { + uiBlockingWindow.Line2 = String.Format("Failed to stop {0}", foundTuner.Name); + Thread.Sleep(1000); + continue; + } + + uiBlockingWindow.Line2 = String.Format("Querying storage for configuration of {0}...", foundTuner.Name); + if (scraperStroage.UiTunerTestFor(foundTuner)) + { + scraperStroage.UiTunerGetConfiguration(foundTuner); + } + + if (tuners == null) + tuners = new List(); + tuners.Add(foundTuner); + } + + uiBlockingWindow.Text = "Please wait while I check the engine Version..."; + uiBlockingWindow.Line2 = "Reading ProductName..."; + this.engineProductName = streamReader.GetEngineName(); + + uiBlockingWindow.Line2 = "Reading ProductVersion..."; + this.engineVersion = streamReader.GetEngineVersion(); + + if (!this.engineProductName.Equals("NullStreamReader")) + { + bool finalCaps = streamReader.CheckForDVB(); + if (!finalCaps) + { + messageWindows.Add(new MessageWindow("Somehow CheckForDVB failed after a second call.")); + } + } + + uiBlockingWindow.Text = "Please wait while I try to qualify the engine..."; + uiBlockingWindow.Line2 = ""; + QualificationToolResultEnum qualification = QualificationTool.QualifyTunerFactory(this.engineProductName, this.engineVersion); + switch (qualification) + { + case QualificationToolResultEnum.Good: + break; + case QualificationToolResultEnum.Bad: + messageWindows.Add(new MessageWindow(String.Format("You are using {0}, Version {1}\nThis version is known to cause issues with skyscraper. You can continue using it, but if it causes issues, you're on your own.", this.engineProductName, this.engineVersion))); + break; + case QualificationToolResultEnum.Unknown: + messageWindows.Add(new MessageWindow(String.Format("You are using {0}, Version {1}\nThis version has not been tested with skyscraper, and might cause some issues. Consider submitting a copy of this version to the author.", this.engineProductName, this.engineVersion))); + break; + case QualificationToolResultEnum.PossibleIssues: + messageWindows.Add(new MessageWindow(String.Format("You are using {0}, Version {1}\nThis version will work, but might have minor issues in some edge cases.", this.engineProductName, this.engineVersion))); + break; + default: + messageWindows.Add(new MessageWindow(String.Format("You are using {0}, Version {1}\nThe Qualification said \"{2}\", but this status is not implemented.\n Possibly your skyscraper Version and your testdrid Version mismatch?", this.engineProductName, this.engineVersion,qualification.ToString()))); + break; + } + + //gseTest(); + + blockUi = false; + showMainMenu = true; + } + + private void gseTest() + { + SearchResult sr = new SearchResult(); + TunerMetadata tunerMetadata = tuners.Find(x => x.Name.Contains("5927")); + bool startDvbEx = streamReader.StartDvbEx(tunerMetadata.Index); + bool sendDiSEqC = streamReader.SendDiSEqC(2, DiSEqC_Opcode.DISEQC_HIGH_NIBBLE | DiSEqC_Opcode.DISEQC_VERTICAL | DiSEqC_Opcode.DISEQC_HIGH_BAND | DiSEqC_Opcode.DISEQC_OPTION_B | DiSEqC_Opcode.DISEQC_POSITION_A); + /*bool channel = streamReader.SetChannel(12611938, 21999618, 1, VITERBIRATE_TYPE.VR_3_4, 9750000, 10600000, 11700000); + while (true) + { + streamReader.SignalInfo(ref sr); + if (sr.Lock) + break; + else + Thread.Sleep(100); + }*/ + bool blScanEx = streamReader.BLScanEx(11817000, 5000, 1, 9750000, 10600000, 11700000, 1000000, STD_TYPE.STD_AUTO, ref sr); + + bool channel = streamReader.SetChannel(11817000, 29700000, 1, VITERBIRATE_TYPE.VR_AUTO, 9750000, 10600000, 11700000); + bool signalInfo = streamReader.SignalInfo(ref sr); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/RandomExtension.cs b/GUIs/skyscraper8.UI.ImGui/RandomExtension.cs new file mode 100644 index 0000000..4fb517e --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/RandomExtension.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace testdrid +{ + public static class RandomExtensions + { + /// + /// Generates normally distributed numbers. Each operation makes two Gaussians for the price of one, and apparently they can be cached or something for better performance, but who cares. + /// + /// + /// Mean of the distribution + /// Standard deviation + /// + public static double NextGaussian(this Random r, double mu = 0, double sigma = 1) + { + var u1 = r.NextDouble(); + var u2 = r.NextDouble(); + + var rand_std_normal = Math.Sqrt(-2.0 * Math.Log(u1)) * + Math.Sin(2.0 * Math.PI * u2); + + var rand_normal = mu + sigma * rand_std_normal; + + return rand_normal; + } + + /// + /// Generates values from a triangular distribution. + /// + /// + /// See http://en.wikipedia.org/wiki/Triangular_distribution for a description of the triangular probability distribution and the algorithm for generating one. + /// + /// + /// Minimum + /// Maximum + /// Mode (most frequent value) + /// + public static double NextTriangular(this Random r, double a, double b, double c) + { + var u = r.NextDouble(); + + return u < (c - a) / (b - a) + ? a + Math.Sqrt(u * (b - a) * (c - a)) + : b - Math.Sqrt((1 - u) * (b - a) * (b - c)); + } + + /// + /// Equally likely to return true or false. Uses . + /// + /// + public static bool NextBoolean(this Random r) + { + return r.Next(2) > 0; + } + + /// + /// Shuffles a list in O(n) time by using the Fisher-Yates/Knuth algorithm. + /// + /// + /// + public static void Shuffle(this Random r, IList list) + { + for (var i = 0; i < list.Count; i++) + { + var j = r.Next(0, i + 1); + + var temp = list[j]; + list[j] = list[i]; + list[i] = temp; + } + } + + /// + /// Returns n unique random numbers in the range [1, n], inclusive. + /// This is equivalent to getting the first n numbers of some random permutation of the sequential numbers from 1 to max. + /// Runs in O(k^2) time. + /// + /// + /// Maximum number possible. + /// How many numbers to return. + /// + public static int[] Permutation(this Random rand, int n, int k) + { + var result = new List(); + var sorted = new SortedSet(); + + for (var i = 0; i < k; i++) + { + var r = rand.Next(1, n + 1 - i); + + foreach (var q in sorted) + if (r >= q) + r++; + + result.Add(r); + sorted.Add(r); + } + + return result.ToArray(); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/BlankScreen.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/BlankScreen.cs new file mode 100644 index 0000000..05638c6 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/BlankScreen.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Screenhacks +{ + [ScreenHackId(0)] + internal class BlankScreen : ScreenHack + { + protected override void Setup() + { + + } + + public override void MousePressed() + { + } + + public override void Dispose() + { + } + + public override void Render() + { + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/Complexification/2_Substrate.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/Complexification/2_Substrate.cs new file mode 100644 index 0000000..937c1de --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/Complexification/2_Substrate.cs @@ -0,0 +1,338 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SDL2; + +namespace SDL2Demo.Screenhacks.Complexification +{ + [ScreenHackId(2)] + internal class Substrate : Processing + { + int dimx = 250; + int dimy = 250; + int num = 0; + int maxnum = 100; + + // grid of cracks + int[] cgrid; + Crack[] cracks; + + // color parameters + int maxpal = 512; + int numpal = 0; + private Color[] goodcolor; + + // sand painters + SandPainter[] sands; + + protected override void Init() + { + goodcolor = new Color[maxpal]; + //Background(Color.FromArgb(255, 255, 255, 255)); + TakeColor(); + + dimx = windowRectangle.Width; + dimy = windowRectangle.Height; + cgrid = new int[dimx * dimy]; + cracks = new Crack[maxnum]; + + Begin(); + } + + private void Begin() + { + // erase crack grid + for (int y = 0; y < dimy; y++) + { + for (int x = 0; x < dimx; x++) + { + cgrid[y * dimx + x] = 10001; + } + } + // make random crack seeds + for (int k = 0; k < 16; k++) + { + int i = (Random(dimx * dimy - 1)); + cgrid[i] = (Random(360)); + } + + // make just three cracks + num = 0; + for (int k = 0; k < 3; k++) + { + MakeCrack(); + } + //Background(Color.FromArgb(255, 255, 255, 255)); + } + + private void MakeCrack() + { + if (num < maxnum) + { + // make a new crack instance + cracks[num] = new Crack(this); + num++; + } + } + + private void TakeColor() + { + uint[] srcArrays = + { + 0x201F21, 0x262C2E, 0x352626, 0x372B27, + 0x302C2E, 0x392B2D, 0x323229, 0x3F3229, + 0x38322E, 0x2E333D, 0x333A3D, 0x473329, + 0x40392C, 0x40392E, 0x47402C, 0x47402E, + 0x4E402C, 0x4F402E, 0x4E4738, 0x584037, + 0x65472D, 0x6D5D3D, 0x745530, 0x755532, + 0x745D32, 0x746433, 0x7C6C36, 0x523152, + 0x444842, 0x4C5647, 0x655D45, 0x6D5D44, + 0x6C5D4E, 0x746C43, 0x7C6C42, 0x7C6C4B, + 0x6B734B, 0x73734B, 0x7B7B4A, 0x6B6C55, + 0x696D5E, 0x7B6C5D, 0x6B7353, 0x6A745D, + 0x727B52, 0x7B7B52, 0x57746E, 0x687466, + 0x9C542B, 0x9D5432, 0x9D5B35, 0x936B36, + 0xAA7330, 0xC45A27, 0xD95223, 0xD85A20, + 0xDB5A23, 0xE57037, 0x836C4B, 0x8C6B4B, + 0x82735C, 0x937352, 0x817B63, 0x817B6D, + 0x927B63, 0xD9893B, 0xE49832, 0xDFA133, + 0xE5A037, 0xF0AB3B, 0x8A8A59, 0xB29A58, + 0x89826B, 0x9A8262, 0x888B7C, 0x909A7A, + 0xA28262, 0xA18A69, 0xA99968, 0x99A160, + 0x99A168, 0xCA8148, 0xEB8D43, 0xC29160, + 0xC29168, 0xD1A977, 0xC9B97F, 0xF0E27B, + 0x9F928B, 0xC0B999, 0xE6B88F, 0xC8C187, + 0xE0C886, 0xF2CC85, 0xF5DA83, 0xECDE9D, + 0xF5D294, 0xF5DA94, 0xF4E784, 0xF4E18A, + 0xF4E193, 0xE7D8A7, 0xF1D4A5, 0xF1DCA5, + 0xF4DBAD, 0xF1DCAE, 0xF4DBB5, 0xF5DBBD, + 0xF4E2AD, 0xF5E9AD, 0xF4E3BE, 0xF5EABE, + 0xF7F0B6, 0xD9D1C1, 0xE0D0C0, 0xE7D8C0, + 0xF1DDC6, 0xE8E1C0, 0xF3EDC7, 0xF6ECCE, + 0xF8F2C7, 0xEFEFD0, 0 + }; + for (int i = 0; i < srcArrays.Length; i++) + { + srcArrays[i] |= 0xff000000; + goodcolor[i] = Color.FromArgb((int)srcArrays[i]); + } + + numpal = srcArrays.Length; + } + + public override void MousePressed() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + public override void Render() + { + for (int n = 0; n < num; n++) + { + cracks[n].Move(); + } + + SDL.SDL_Rect a = new SDL.SDL_Rect(); + a.h = 16; + a.w = 16; + SDL.SDL_Rect b = new SDL.SDL_Rect(); + b.h = 720; + b.w = 1280; + + + renderer.Copy(whiteBackground, ref a, ref b); + renderer.Copy(_texture, topLeft); + } + + class Crack + { + private readonly Substrate _parent; + private float x, y, t; + private SandPainter sp; + + public Crack(Substrate parent) + { + _parent = parent; + FindStart(); + sp = new SandPainter(_parent); + } + + private void FindStart() + { + // pick random point + int px = 0; + int py = 0; + + // shift until crack is found + bool found = false; + int timeout = 0; + while ((!found) || (timeout++ > 1000)) + { + px = (_parent.Random(_parent.dimx)); + py = (_parent.Random(_parent.dimy)); + if (_parent.cgrid[py * _parent.dimx + px] < 10000) + { + found = true; + } + } + + if (found) + { + // start crack + int a = _parent.cgrid[py * _parent.dimx + px]; + if (_parent.Random(100) < 50) + { + a -= 90 + (int)(_parent.Random(-2.0f, 2.1f)); + } + else + { + a += 90 + (int)(_parent.Random(-2.0f, 2.1f)); + } + StartCrack(px, py, a); + } + else + { + //println("timeout: "+timeout); + } + } + + private void StartCrack(int X, int Y, int T) + { + x = X; + y = Y; + t = T;//%360; + x += 0.61f * (float)Math.Cos(t * Math.PI / 180.0); + y += 0.61f * (float)Math.Sin(t * Math.PI / 180.0); + } + + public void Move() + { + // continue cracking + x += 0.42f * (float)Math.Cos(t * Math.PI / 180); + y += 0.42f * (float)Math.Sin(t * Math.PI / 180); + + // bound check + float z = 0.33f; + int cx = (int)(x + _parent.Random(-z, z)); // add fuzz + int cy = (int)(y + _parent.Random(-z, z)); + + // draw sand painter + RegionColor(); + + // draw black crack + _parent.Stroke(0.0f, 85.0f); + _parent.Point(x + _parent.Random(-z, z), y + _parent.Random(-z, z)); + + + if ((cx >= 0) && (cx < _parent.dimx) && (cy >= 0) && (cy < _parent.dimy)) + { + // safe to check + if ((_parent.cgrid[cy * _parent.dimx + cx] > 10000) || (Math.Abs(_parent.cgrid[cy * _parent.dimx + cx] - t) < 5)) + { + // continue cracking + _parent.cgrid[cy * _parent.dimx + cx] = (int)(t); + } + else if (Math.Abs(_parent.cgrid[cy * _parent.dimx + cx] - t) > 2) + { + // crack encountered (not self), stop cracking + FindStart(); + _parent.MakeCrack(); + } + } + else + { + // out of bounds, stop cracking + FindStart(); + _parent.MakeCrack(); + } + } + + private void RegionColor() + { + // start checking one step away + float rx = x; + float ry = y; + bool openspace = true; + + // find extents of open space + while (openspace) + { + // move perpendicular to crack + rx += 0.81f * (float)Math.Sin(t * Math.PI / 180.0); + ry -= 0.81f * (float)Math.Cos(t * Math.PI / 180.0); + int cx = (int)(rx); + int cy = (int)(ry); + if ((cx >= 0) && (cx < _parent.dimx) && (cy >= 0) && (cy < _parent.dimy)) + { + // safe to check + if (_parent.cgrid[cy * _parent.dimx + cx] > 10000) + { + // space is open + } + else + { + openspace = false; + } + } + else + { + openspace = false; + } + } + // draw sand painter + sp.Render(rx, ry, x, y); + } + } + + class SandPainter + { + private readonly Substrate _parent; + + public SandPainter(Substrate _parent) + { + this._parent = _parent; + c = _parent.SomeColor(); + g = _parent.Random(0.01f, 0.1f); + } + + private Color c; + private float g; + + public void Render(float x, float y, float ox, float oy) + { + // modulate gain + g += _parent.Random(-0.050f, 0.050f); + float maxg = 1.0f; + if (g < 0) g = 0; + if (g > maxg) g = maxg; + + // calculate grains by distance + //int grains = (int)(Math.Sqrt((ox-x)*(ox-x)+(oy-y)*(oy-y))); + int grains = 64; + + // lay down grains of sand (transparent pixels) + float w = g / (grains - 1); + for (int i = 0; i < grains; i++) + { + float a = 0.1f - i / (grains * 10.0f); + _parent.Stroke(c.R, c.G, c.B, a * 256); + _parent.Point(ox + (x - ox) * (float)Math.Sin(Math.Sin(i * w)), oy + (y - oy) * (float)Math.Sin(Math.Sin(i * w))); + } + } + } + + private Color SomeColor() + { + return goodcolor[rng.Next(numpal)]; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/MaiWaifu.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/MaiWaifu.cs new file mode 100644 index 0000000..d2ab022 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/MaiWaifu.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Screenhacks +{ + [ScreenHackId(UInt16.MaxValue)] + internal class MaiWaifu : ScreenHack + { + private Texture wallpaperTexture; + + private FileInfo GetWallpaperFilename() + { + if (Directory.Exists("alt-wallpapers")) + { + DirectoryInfo altWallpapersFolder = new DirectoryInfo("alt-wallpapers"); + FileInfo[] altWallpapers = altWallpapersFolder.GetFiles("*.png"); + if (altWallpapers.Length > 0) + { + return rng.Pick(altWallpapers); + } + } + + DirectoryInfo di = new DirectoryInfo("."); + FileInfo[] strings = di.GetFiles("*.png"); + return rng.Pick(strings); + } + + protected override void Setup() + { + FileInfo filename = GetWallpaperFilename(); + + if (filename != null) + { + Surface wallpaperSurface = Surface.ImageLoad(filename.FullName); + wallpaperTexture = Texture.FromSurface(renderer, wallpaperSurface); + wallpaperSurface.Dispose(); + } + } + + public override void MousePressed() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + wallpaperTexture?.Dispose(); + } + + public override void Render() + { + if (wallpaperTexture != null) + { + renderer.Copy(wallpaperTexture, windowRectangle); + } + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/PixelTest.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/PixelTest.cs new file mode 100644 index 0000000..22927a8 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/PixelTest.cs @@ -0,0 +1,46 @@ +using SDL2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Screenhacks +{ + [ScreenHackId(1)] + internal class PixelTest : ScreenHack + { + private Texture _texture; + private byte[] buffer; + + protected override void Setup() + { + _texture = Texture.Create(renderer, SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, windowRectangle.Width, windowRectangle.Height); + _texture.SetDrawBlendMode(SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); + buffer = new byte[3]; + } + + public override void MousePressed() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + _texture.Dispose(); + } + + public override void Render() + { + int x = rng.Next(windowRectangle.Width); + int y = rng.Next(windowRectangle.Height); + rng.NextBytes(buffer); + _texture.Lock(); + _texture.SetPixel(x, y, 255, buffer[0], buffer[1], buffer[2]); + _texture.Unlock(); + renderer.Copy(_texture, topLeft); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/Processing.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/Processing.cs new file mode 100644 index 0000000..9020dba --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/Processing.cs @@ -0,0 +1,254 @@ +using SDL2; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Screenhacks +{ + internal abstract class Processing : ScreenHack + { + protected Texture whiteBackground; + protected Texture _texture; + + protected override void Setup() + { + Surface surface = Surface.Create(16, 16); + surface.Fill(0xffffffff); + whiteBackground = Texture.FromSurface(renderer, surface); + + _texture = Texture.Create(renderer, SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, windowRectangle.Width, windowRectangle.Height); + _texture.SetDrawBlendMode(SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); + Init(); + } + + protected abstract void Init(); + + protected void Background(Color c) + { + Surface lockAsSurface = _texture.LockAsSurface(); + lockAsSurface.Fill((uint)c.ToArgb()); + _texture.Unlock(); + } + + protected int Random(int max) + { + return rng.Next(max); + } + + protected float Random(float min, float max) + { + if (min >= max) + return min; + float diff = max - min; + return Random(diff) + min; + } + + protected float Random(float max) + { + if (max == 0.0F) + return 0.0F; + + float f = 0.0F; + while (true) + { + f = rng.NextSingle() * max; + if (f != max) + return f; + } + } + + protected int Random(int min, int max) + { + return rng.Next(min, max); + } + + protected void Stroke(float paramFloat1, float paramFloat2) + { + ColorCalc(paramFloat1, paramFloat2); + ColorStroke(); + } + + private void ColorStroke() + { + this.stroke = true; + this.strokeR = this.calcR; + this.strokeG = this.calcG; + this.strokeB = this.calcB; + this.strokeA = this.calcA; + this.strokeRi = this.calcRi; + this.strokeGi = this.calcGi; + this.strokeBi = this.calcBi; + this.strokeAi = this.calcAi; + this.strokeColor = this.calcColor; + this.strokeAlpha = this.calcAlpha; + this.strokeColorObject = Color.FromArgb(strokeAi,strokeRi,strokeGi,strokeBi); + } + + private bool stroke; + private float strokeR, strokeG, strokeB, strokeA; + private int strokeRi, strokeGi, strokeBi, strokeAi; + private int strokeColor; + private bool strokeAlpha; + + protected void ColorCalc(float paramFloat1, float paramFloat2) + { + /*if (paramFloat1 > this.colorModeX) + paramFloat1 = this.colorModeX; + if (paramFloat2 > this.colorModeA) + paramFloat2 = this.colorModeA; + if (paramFloat1 < 0.0F) + paramFloat1 = 0.0F; + if (paramFloat2 < 0.0F) + paramFloat2 = 0.0F;*/ + this.calcR = this.colorScale ? (paramFloat1 / this.colorModeX) : paramFloat1; + this.calcG = this.calcR; + this.calcB = this.calcR; + this.calcA = this.colorScale ? (paramFloat2 / this.colorModeA) : paramFloat2; + this.calcRi = (int)(this.calcR); + this.calcGi = (int)(this.calcG); + this.calcBi = (int)(this.calcB); + this.calcAi = (int)(this.calcA); + this.calcColor = this.calcAi << 24 | this.calcRi << 16 | this.calcGi << 8 | this.calcBi; + if (this.calcAi != 255) + this.calcAlpha = false; + this.calcAlpha = true; + } + + private float colorModeX, colorModeA, calcR, calcG, calcB, calcA; + + private bool colorScale; + + private int calcRi, calcGi, calcBi, calcAi, calcColor; + + private bool calcAlpha; + private Color strokeColorObject; + + protected int colorMode = 1; + private float colorModeY, colorModeZ; + + protected void Point(float x, float y) + { + int ix = (int)x; + int iy = (int)y; + _texture.Lock(); + _texture.SetPixel(ix, iy, strokeColorObject.A, strokeColorObject.R, strokeColorObject.G, strokeColorObject.B); + _texture.Unlock(); + } + + protected void Stroke(float r, float g, float b, float a) + { + ColorCalc(r, g, b, a); + ColorStroke(); + } + + protected void ColorCalc(float paramFloat1, float paramFloat2, float paramFloat3, float paramFloat4) + { + float f1, f2, f3, f4, f5; + /* + if (paramFloat1 > this.colorModeX) + paramFloat1 = this.colorModeX; + if (paramFloat2 > this.colorModeY) + paramFloat2 = this.colorModeY; + if (paramFloat3 > this.colorModeZ) + paramFloat3 = this.colorModeZ; + if (paramFloat4 > this.colorModeA) + paramFloat4 = this.colorModeA; + if (paramFloat1 < 0.0F) + paramFloat1 = 0.0F; + if (paramFloat2 < 0.0F) + paramFloat2 = 0.0F; + if (paramFloat3 < 0.0F) + paramFloat3 = 0.0F; + if (paramFloat4 < 0.0F) + paramFloat4 = 0.0F;*/ + switch (this.colorMode) + { + case 1: + if (this.colorScale) + { + this.calcR = paramFloat1 / this.colorModeX; + this.calcG = paramFloat2 / this.colorModeY; + this.calcB = paramFloat3 / this.colorModeZ; + this.calcA = paramFloat4 / this.colorModeA; + break; + } + this.calcR = paramFloat1; + this.calcG = paramFloat2; + this.calcB = paramFloat3; + this.calcA = paramFloat4; + break; + case 3: + paramFloat1 /= this.colorModeX; + paramFloat2 /= this.colorModeY; + paramFloat3 /= this.colorModeZ; + this.calcA = this.colorScale ? (paramFloat4 / this.colorModeA) : paramFloat4; + if (paramFloat2 == 0.0F) + { + this.calcR = this.calcG = this.calcB = paramFloat3; + break; + } + f1 = (paramFloat1 - (int)paramFloat1) * 6.0F; + f2 = f1 - (int)f1; + f3 = paramFloat3 * (1.0F - paramFloat2); + f4 = paramFloat3 * (1.0F - paramFloat2 * f2); + f5 = paramFloat3 * (1.0F - paramFloat2 * (1.0F - f2)); + switch ((int)f1) + { + case 0: + this.calcR = paramFloat3; + this.calcG = f5; + this.calcB = f3; + break; + case 1: + this.calcR = f4; + this.calcG = paramFloat3; + this.calcB = f3; + break; + case 2: + this.calcR = f3; + this.calcG = paramFloat3; + this.calcB = f5; + break; + case 3: + this.calcR = f3; + this.calcG = f4; + this.calcB = paramFloat3; + break; + case 4: + this.calcR = f5; + this.calcG = f3; + this.calcB = paramFloat3; + break; + case 5: + this.calcR = paramFloat3; + this.calcG = f3; + this.calcB = f4; + break; + } + break; + } + this.calcRi = (int)(1.0f * this.calcR); + this.calcGi = (int)(1.0f * this.calcG); + this.calcBi = (int)(1.0f * this.calcB); + this.calcAi = (int)(1.0f * this.calcA); + this.calcColor = this.calcAi << 24 | this.calcRi << 16 | this.calcGi << 8 | this.calcBi; + if (this.calcAi != 255) + this.calcAlpha = false; + this.calcAlpha = true; + } + + protected float Red(int paramInt) + { + float f = (paramInt >> 16 & 0xFF); + if (this.colorRgb255) + return f; + return f / 255.0F * this.colorModeX; + } + + protected bool colorRgb255; + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/RandomExtensions.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/RandomExtensions.cs new file mode 100644 index 0000000..a76d9b7 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/RandomExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Screenhacks +{ + internal static class RandomExtensions + { + public static T? Pick(this Random rng, T[] collection) + { + if (collection == null) + return default(T); + + if (collection.Length == 0) + return default(T); + + if (collection.Length == 1) + return collection[0]; + + return collection[rng.Next(collection.Length)]; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHack.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHack.cs new file mode 100644 index 0000000..916df14 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHack.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using testdrid.SdlWrapper; + +namespace SDL2Demo.Screenhacks +{ + abstract class ScreenHack : IDisposable, IRenderable + { + protected Renderer renderer; + protected Random rng; + protected Rectangle windowRectangle; + protected static Point topLeft; + + public void Setup(Rectangle window, Renderer renderer) + { + this.windowRectangle = new Rectangle(0, 0, window.Width, window.Height); + this.renderer = renderer; + this.rng = new Random(); + topLeft = new Point(0, 0); + this.Setup(); + } + + protected abstract void Setup(); + public abstract void MousePressed(); + public abstract void Dispose(); + public abstract void Render(); + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHackId.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHackId.cs new file mode 100644 index 0000000..b4374a5 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHackId.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Screenhacks +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class ScreenHackIdAttribute : Attribute + { + public ushort Id { get; } + + public ScreenHackIdAttribute(ushort id) + { + Id = id; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHackTypeComparer.cs b/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHackTypeComparer.cs new file mode 100644 index 0000000..83488eb --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Screenhacks/ScreenHackTypeComparer.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace SDL2Demo.Screenhacks +{ + internal class ScreenHackTypeComparer : Comparer + { + public override int Compare(Type? x, Type? y) + { + Attribute customAttribute = x.GetCustomAttribute(typeof(ScreenHackIdAttribute)); + if (customAttribute == null) + return 0; + + Attribute attribute = y.GetCustomAttribute(typeof(ScreenHackIdAttribute)); + if (attribute == null) + return 0; + + ScreenHackIdAttribute l = (ScreenHackIdAttribute)customAttribute; + ScreenHackIdAttribute r = (ScreenHackIdAttribute)attribute; + + return l.Id.CompareTo(r.Id); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/EventPoller.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/EventPoller.cs new file mode 100644 index 0000000..b2b4d15 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/EventPoller.cs @@ -0,0 +1,244 @@ +using SDL2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SDL2Demo.SdlWrapper; +using static SDL2.SDL; + +namespace testdrid.SdlWrapper +{ + internal class EventPoller + { + public static EventPoller GetInstance() + { + if (singleton == null) + { + singleton = new EventPoller(); + } + + return singleton; + } + + private EventPoller() + { + } + + private static EventPoller singleton; + private SDL_Event sdlEvent; + public IEventConsumer Junction { get; set; } + public void PollEvents() + { + while (SDL.SDL_PollEvent(out sdlEvent) != 0) + { + Junction.ProcessEvent(sdlEvent); + switch (sdlEvent.type) + { + case SDL_EventType.SDL_AUDIODEVICEADDED: + SDL_AudioDeviceEvent sdlAudioDeviceEvent = sdlEvent.adevice; + AudioDeviceAdded?.Invoke(sdlAudioDeviceEvent); + break; + case SDL_EventType.SDL_WINDOWEVENT: + SDL_WindowEvent sdlWindowEvent = sdlEvent.window; + switch (sdlWindowEvent.windowEvent) + { + case SDL_WindowEventID.SDL_WINDOWEVENT_SHOWN: + WindowShown?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED: + WindowFocusGained?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_EXPOSED: + WindowExposed?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE: + WindowClose?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_ENTER: + WindowEnteredByMouseCursor?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE: + WindowLeftByMouseCursor?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST: + WindowFocusGained?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_MOVED: + WindowMoved?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_HIDDEN: + WindowHidden?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: + WindowSizeChanged?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_MINIMIZED: + WindowMinimized?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED: + WindowResized?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_RESTORED: + WindowResized?.Invoke(sdlWindowEvent); + break; + case SDL_WindowEventID.SDL_WINDOWEVENT_ICCPROF_CHANGED: + WindowIccProfileChanged?.Invoke(sdlWindowEvent); + break; + default: + Console.WriteLine(sdlWindowEvent.windowEvent.ToString()); + throw new NotImplementedException(sdlWindowEvent.windowEvent.ToString()); + } + break; + case SDL_EventType.SDL_TEXTEDITING: + SDL_TextEditingEvent sdlTextEditingEvent = sdlEvent.edit; + TextEditing?.Invoke(sdlTextEditingEvent); + break; + case SDL_EventType.SDL_MOUSEMOTION: + SDL_MouseMotionEvent sdlMouseMotionEvent = sdlEvent.motion; + MouseMotion?.Invoke(sdlMouseMotionEvent); + break; + case SDL_EventType.SDL_QUIT: + SDL_QuitEvent sdlQuitEvent = sdlEvent.quit; + Quit?.Invoke(sdlQuitEvent); + break; + case SDL_EventType.SDL_MOUSEBUTTONDOWN: + SDL_MouseButtonEvent sdlMouseButtonEvent = sdlEvent.button; + MouseButtonDown?.Invoke(sdlMouseButtonEvent); + break; + case SDL_EventType.SDL_MOUSEBUTTONUP: + SDL_MouseButtonEvent sdlMouseButtonEvent2 = sdlEvent.button; + MouseButtonUp?.Invoke(sdlMouseButtonEvent2); + break; + case SDL_EventType.SDL_MOUSEWHEEL: + SDL_MouseWheelEvent sdlMouseWheelEvent = sdlEvent.wheel; + MouseWheel?.Invoke(sdlMouseWheelEvent); + break; + case SDL_EventType.SDL_CLIPBOARDUPDATE: + ClipboardUpdate?.Invoke(); + break; + case SDL_EventType.SDL_KEYDOWN: + SDL_KeyboardEvent sdlKeyboardEvent = sdlEvent.key; + KeyDown?.Invoke(sdlKeyboardEvent); + break; + case SDL_EventType.SDL_TEXTINPUT: + SDL_TextInputEvent sdlTextInputEvent = sdlEvent.text; + TextInput?.Invoke(sdlTextInputEvent); + break; + case SDL_EventType.SDL_KEYUP: + SDL_KeyboardEvent sdlEventKey = sdlEvent.key; + KeyUp?.Invoke(sdlEventKey); + break; + case SDL_EventType.SDL_RENDER_TARGETS_RESET: + RenderTargetsReset?.Invoke(sdlEvent); + break; + case SDL_EventType.SDL_DISPLAYEVENT: + switch (sdlEvent.display.displayEvent) + { + case SDL_DisplayEventID.SDL_DISPLAYEVENT_CONNECTED: + DisplayConnected?.Invoke(sdlEvent.display); + break; + case SDL_DisplayEventID.SDL_DISPLAYEVENT_DISCONNECTED: + DisplayDisconnected?.Invoke(sdlEvent.display); + break; + default: + throw new NotImplementedException(sdlEvent.display.displayEvent.ToString()); + } + break; + case SDL_EventType.SDL_DROPBEGIN: + DropBegin?.Invoke(sdlEvent.drop); + break; + case SDL_EventType.SDL_JOYDEVICEADDED: + JoystickDeviceAdded?.Invoke(sdlEvent.jdevice); + break; + case SDL_EventType.SDL_CONTROLLERDEVICEADDED: + ControllerDeviceAdded?.Invoke(sdlEvent.cdevice); + break; + case SDL_EventType.SDL_AUDIODEVICEREMOVED: + AudioDeviceRemoved?.Invoke(sdlEvent.adevice); + break; + default: + Console.WriteLine(sdlEvent.type.ToString()); + throw new NotImplementedException(sdlEvent.type.ToString()); + } + } + } + + public delegate void ControllerDeviceDelegate(SDL_ControllerDeviceEvent cde); + public event ControllerDeviceDelegate ControllerDeviceAdded; + + public delegate void JoystickDeviceDelegate(SDL_JoyDeviceEvent jde); + public event JoystickDeviceDelegate JoystickDeviceAdded; + + public delegate void DropEventDelegate(SDL_DropEvent de); + public event DropEventDelegate DropBegin; + + public delegate void DisplayEventDelegate(SDL_DisplayEvent de); + public event DisplayEventDelegate DisplayConnected; + public event DisplayEventDelegate DisplayDisconnected; + + public delegate void AudioDeviceEventDelegate(SDL_AudioDeviceEvent ade); + public event AudioDeviceEventDelegate AudioDeviceAdded; + public event AudioDeviceEventDelegate AudioDeviceRemoved; + + public delegate void WindowEventDelegate(SDL_WindowEvent we); + public event WindowEventDelegate WindowShown; + + public event WindowEventDelegate WindowFocusGained; + + public delegate void TextEditingEventDelegate(SDL_TextEditingEvent tee); + public event TextEditingEventDelegate TextEditing; + + public event WindowEventDelegate WindowExposed; + + public event WindowEventDelegate WindowClose; + + public event WindowEventDelegate WindowEnteredByMouseCursor; + + public delegate void MouseMotionEventDelegate(SDL_MouseMotionEvent mme); + public event MouseMotionEventDelegate MouseMotion; + + public event WindowEventDelegate WindowLeftByMouseCursor; + + public delegate void QuitEventDelegate(SDL_QuitEvent qe); + public event QuitEventDelegate Quit; + + public event WindowEventDelegate WindowFocusLost; + + public event WindowEventDelegate WindowMoved; + + public event WindowEventDelegate WindowHidden; + + public event WindowEventDelegate WindowIccProfileChanged; + + public delegate void MouseButtonEventDelegate(SDL_MouseButtonEvent mbe); + public event MouseButtonEventDelegate MouseButtonDown; + + public event MouseButtonEventDelegate MouseButtonUp; + + public delegate void MouseWheelEventDelegate(SDL_MouseWheelEvent mwe); + public event MouseWheelEventDelegate MouseWheel; + + public delegate void ClipboardUpdateDelegate(); + public event ClipboardUpdateDelegate ClipboardUpdate; + + public delegate void KeyboardEventDelegate(SDL_KeyboardEvent ke); + public event KeyboardEventDelegate KeyDown; + + public delegate void TextInputEventDelegate(SDL_TextInputEvent tie); + public event TextInputEventDelegate TextInput; + + public event KeyboardEventDelegate KeyUp; + + public event WindowEventDelegate WindowSizeChanged; + + public delegate void InformalEventDelegate(SDL_Event sdlEvent); + public event InformalEventDelegate RenderTargetsReset; + + public event WindowEventDelegate WindowMinimized; + + public event WindowEventDelegate WindowResized; + + public event WindowEventDelegate WindowRestored; + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/GlContext.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/GlContext.cs new file mode 100644 index 0000000..4fec883 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/GlContext.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SDL2; +using testdrid.SdlWrapper; + +namespace SDL2Demo.SdlWrapper +{ + internal class GlContext : IDisposable + { + private GlContext() {} + + public static GlContext Create(Window window) + { + GlContext child = new GlContext(); + child.Pointer = SDL2.SDL.SDL_GL_CreateContext(window.Pointer); + if (child.Pointer == IntPtr.Zero) + throw SdlException.GenerateException(); + + SDL.SDL_GL_MakeCurrent(window.Pointer, child.Pointer); + SDL.SDL_GL_SetSwapInterval(1); + SDL.SDL_GL_SwapWindow(window.Pointer); + + return child; + } + + public IntPtr Pointer { get; private set; } + + public void Dispose() + { + SDL.SDL_GL_DeleteContext(Pointer); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/IEventConsumer.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/IEventConsumer.cs new file mode 100644 index 0000000..7a157e1 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/IEventConsumer.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static SDL2.SDL; + +namespace SDL2Demo.SdlWrapper +{ + internal interface IEventConsumer + { + public void ProcessEvent(SDL_Event evt); + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Renderer.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Renderer.cs new file mode 100644 index 0000000..23195f3 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Renderer.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SDL2; + +namespace testdrid.SdlWrapper +{ + internal class Renderer : IDisposable + { + private Renderer() { } + + internal IntPtr Pointer { get; set; } + + public static Renderer Create(Window targetWindow) + { + return Create(targetWindow, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED | SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC); + } + + public static Renderer Create(Window targetWindow, int index, SDL.SDL_RendererFlags flags) + { + Renderer child = new Renderer(); + child.Pointer = SDL.SDL_CreateRenderer(targetWindow.Pointer, index, flags); + if (child.Pointer == IntPtr.Zero) + { + throw SdlException.GenerateException(); + } + + return child; + } + + public void Dispose() + { + SDL.SDL_DestroyRenderer(Pointer); + Pointer = IntPtr.Zero; + } + + public void Clear() + { + SDL.SDL_RenderClear(Pointer); + } + + public void Copy(Texture texture) + { + int sdlRenderCopy = SDL.SDL_RenderCopy(this.Pointer, texture.Pointer, IntPtr.Zero, IntPtr.Zero); + if (sdlRenderCopy != 0) + throw SdlException.GenerateException(); + } + + public void Copy(Texture texture, ref SDL.SDL_Rect sourceRect, ref SDL.SDL_Rect targetRect) + { + int sdlRenderCopy = SDL.SDL_RenderCopy(this.Pointer, texture.Pointer, ref sourceRect, ref targetRect); + if (sdlRenderCopy != 0) + throw SdlException.GenerateException(); + } + + public void Copy(Texture texture, Point targetPoint) + { + SDL.SDL_Rect targetRect = new SDL.SDL_Rect(); + targetRect.x = targetPoint.X; + targetRect.y = targetPoint.Y; + targetRect.w = texture.GetWidth(); + targetRect.h = texture.GetHeight(); + + int sdlRenderCopy = SDL.SDL_RenderCopy(this.Pointer, texture.Pointer, IntPtr.Zero, ref targetRect); + if (sdlRenderCopy != 0) + throw SdlException.GenerateException(); + } + + public void Copy(Texture texture, Rectangle rectangle) + { + SDL.SDL_Rect targetRect = new SDL.SDL_Rect(); + targetRect.x = rectangle.X; + targetRect.y = rectangle.Y; + targetRect.w = rectangle.Width; + targetRect.h = rectangle.Height; + + int sdlRenderCopy = SDL.SDL_RenderCopy(this.Pointer, texture.Pointer, IntPtr.Zero, ref targetRect); + if (sdlRenderCopy != 0) + throw SdlException.GenerateException(); + } + + public void SetDrawBlendMode(SDL.SDL_BlendMode blendMode) + { + int result = SDL.SDL_SetRenderDrawBlendMode(Pointer, blendMode); + if (result != 0) + throw SdlException.GenerateException(); + } + + public void Present() + { + lock (Lockable) + { + SDL.SDL_RenderPresent(Pointer); + } + } + + private static object _lockable; + + public static object Lockable + { + get + { + if (_lockable == null) + _lockable = new object(); + + return _lockable; + } + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/SdlException.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/SdlException.cs new file mode 100644 index 0000000..41397f9 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/SdlException.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SDL2; + +namespace testdrid.SdlWrapper +{ + internal class SdlException : ApplicationException + { + private SdlException(string s) : base() + { + } + + public static SdlException GenerateException() + { + return new SdlException(SDL.SDL_GetError()); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/SoundPlayer.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/SoundPlayer.cs new file mode 100644 index 0000000..b0164a2 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/SoundPlayer.cs @@ -0,0 +1,52 @@ +using SDL2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using testdrid.SdlWrapper; + +namespace SDL2Demo.SdlWrapper +{ + internal class SoundPlayer + { + + private static uint audioDevice; + private static bool openedAudioDevice; + + public static void PlaySoundFile(string filename) + { + FileInfo fi = new FileInfo(filename); + if (!fi.Exists) + return; + + SDL2.SDL.SDL_AudioSpec audioSpec; + IntPtr audioBuffer; + uint audioLength; + + IntPtr result = SDL2.SDL.SDL_LoadWAV(fi.FullName, out audioSpec, out audioBuffer, out audioLength); + if (result == IntPtr.Zero) + throw SdlException.GenerateException(); + + if (!openedAudioDevice) + { + SDL.SDL_AudioSpec desired = new SDL.SDL_AudioSpec(); + SDL.SDL_AudioSpec obtained; + + uint deviceId = SDL.SDL_OpenAudioDevice(IntPtr.Zero, 0, ref audioSpec, out obtained, 0); + if (deviceId == 0) + throw SdlException.GenerateException(); + audioDevice = deviceId; + openedAudioDevice = true; + } + + int success = SDL2.SDL.SDL_QueueAudio(audioDevice, audioBuffer, audioLength); + if (success != 0) + throw SdlException.GenerateException(); + + SDL.SDL_PauseAudioDevice(audioDevice, 0); + + SDL.SDL_FreeWAV(audioBuffer); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Surface.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Surface.cs new file mode 100644 index 0000000..f048d6a --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Surface.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using SDL2; + +namespace testdrid.SdlWrapper +{ + internal class Surface : IDisposable + { + private Surface() {} + + internal Surface(IntPtr pointer) + { + IntPointer = pointer; + } + + public static Surface LoadFromBMP(string filename) + { + Surface result = new Surface(); + result.IntPointer = SDL.SDL_LoadBMP(filename); + if (result.IntPointer == IntPtr.Zero) + throw SdlException.GenerateException(); + return result; + } + + public static Surface ImageLoad(string filename) + { + Surface result = new Surface(); + result.IntPointer = SDL_image.IMG_Load(filename); + if (result.IntPointer == IntPtr.Zero) + throw SdlException.GenerateException(); + return result; + } + + public static Surface ImageLoadFromPtr(byte[] buffer) + { + IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length); + + IntPtr sdlRwFromMem = SDL.SDL_RWFromMem(unmanagedPointer, buffer.Length); + + //IMG_Load_RW + Surface result = new Surface(); + result.IntPointer = SDL_image.IMG_Load_RW(sdlRwFromMem, 1); + if (result.IntPointer == IntPtr.Zero) + throw SdlException.GenerateException(); + + Marshal.FreeHGlobal(unmanagedPointer); + return result; + } + + public static Surface Create(int width, int height) + { + uint rmask, gmask, bmask, amask; + if (!BitConverter.IsLittleEndian) + { + rmask = 0xff000000; + gmask = 0x00ff0000; + bmask = 0x0000ff00; + amask = 0x000000ff; + } + else + { + rmask = 0x000000ff; + gmask = 0x0000ff00; + bmask = 0x00ff0000; + amask = 0xff000000; + } + Surface result = new Surface(); + result.IntPointer = SDL.SDL_CreateRGBSurface(0, width, height, 32, rmask, gmask, bmask, amask); + if (result.IntPointer == IntPtr.Zero) + throw SdlException.GenerateException(); + return result; + } + + internal IntPtr IntPointer { get; set; } + + public void SetColorKey(int flag, uint key) + { + SDL.SDL_SetColorKey(IntPointer, flag, key); + } + + public void Dispose() + { + SDL.SDL_FreeSurface(IntPointer); + IntPointer = IntPtr.Zero; + } + + public void Fill(uint color) + { + int sdlFillRect = SDL.SDL_FillRect(IntPointer, IntPtr.Zero, color); + if (sdlFillRect != 0) + { + throw SdlException.GenerateException(); + } + } + + public void SetPixel(int x, int y, byte r, byte g, byte b) + { + SDL.SDL_Surface sdlSurface = Marshal.PtrToStructure(IntPointer); + SDL.SDL_PixelFormat sdlPixelFormat = Marshal.PtrToStructure(sdlSurface.format); + + int offset = (sdlPixelFormat.BytesPerPixel * sdlSurface.w) * y; + offset += (sdlPixelFormat.BytesPerPixel * x); + Marshal.WriteByte(sdlSurface.pixels, offset + 0, r); + Marshal.WriteByte(sdlSurface.pixels, offset + 1, g); + Marshal.WriteByte(sdlSurface.pixels, offset + 2, b); + Marshal.WriteByte(sdlSurface.pixels, offset + 3, 0xff); + } + + public SDL2.SDL.SDL_Surface GetSurfaceData() + { + SDL.SDL_Surface ptrToStructure = Marshal.PtrToStructure(IntPointer); + return ptrToStructure; + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Texture.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Texture.cs new file mode 100644 index 0000000..7901bb4 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Texture.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using SDL2; + +namespace testdrid.SdlWrapper +{ + internal class Texture : IDisposable + { + private Texture() {} + + internal IntPtr Pointer { get; set; } + public static Texture FromSurface(Renderer renderer, Surface surface) + { + Texture result = new Texture(); + result.Pointer = SDL.SDL_CreateTextureFromSurface(renderer.Pointer, surface.IntPointer); + if (result.Pointer == IntPtr.Zero) + { + throw SdlException.GenerateException(); + } + + return result; + } + + public static Texture Create(Renderer renderer, SDL.SDL_TextureAccess access, int w, int h) + { + Texture result = new Texture(); + result.Pointer = SDL.SDL_CreateTexture(renderer.Pointer, SDL.SDL_PIXELFORMAT_ARGB8888, (int)access, w, h); + if (result.Pointer == IntPtr.Zero) + { + throw SdlException.GenerateException(); + } + return result; + } + + internal static Texture FromIntPointer(IntPtr intPtr) + { + Texture result = new Texture(); + result.Pointer = intPtr; + return result; + } + + public void Dispose() + { + SDL.SDL_DestroyTexture(Pointer); + Pointer = IntPtr.Zero; + } + + private bool queryCached; + private uint queryFormat; + private int queryAccess, queryW, queryH; + private void Query() + { + if (!queryCached) + { + SDL.SDL_QueryTexture(Pointer, out queryFormat, out queryAccess, out queryW, out queryH); + queryCached = true; + } + } + + public int GetWidth() + { + Query(); + return queryW; + } + + public int GetHeight() + { + Query(); + return queryH; + } + + private IntPtr locked; + private int lockedPitch; + public void Lock() + { + if (locked != IntPtr.Zero) + throw new InvalidOperationException("Texture already locked"); + + int sdlLockTexture = SDL.SDL_LockTexture(Pointer, IntPtr.Zero, out locked, out lockedPitch); + if (sdlLockTexture != 0) + { + throw SdlException.GenerateException(); + } + } + + public void SetPixel(int x, int y, byte a, byte r, byte g, byte b) + { + if (y < 0) + return; + + if (x < 0) + return; + + if (x >= GetWidth()) + return; + + if (y >= GetHeight()) + return; + + if (locked == IntPtr.Zero) + throw new InvalidOperationException("Texture not locked"); + + int bytesPerPixel = lockedPitch / GetWidth(); + + int offset = lockedPitch * y; + offset += (x * bytesPerPixel); + + Marshal.WriteByte(locked, offset + 0, b); + Marshal.WriteByte(locked, offset + 1, g); + Marshal.WriteByte(locked, offset + 2, r); + Marshal.WriteByte(locked, offset + 3, a); + } + + public void Unlock() + { + if (locked == IntPtr.Zero) + throw new InvalidOperationException("Texture not locked"); + SDL.SDL_UnlockTexture(Pointer); + locked = IntPtr.Zero; + lockedPitch = 0; + } + + public void SetDrawBlendMode(SDL.SDL_BlendMode mode) + { + int i = SDL.SDL_SetTextureBlendMode(Pointer, mode); + if (i != 0) + throw SdlException.GenerateException(); + } + + public Surface LockAsSurface() + { + if (locked != IntPtr.Zero) + throw new InvalidOperationException("Texture already locked"); + + IntPtr surfacePtr; + if (SDL.SDL_LockTextureToSurface(Pointer, IntPtr.Zero, out surfacePtr) != 0) + throw SdlException.GenerateException(); + locked = surfacePtr; + return new Surface(surfacePtr); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Window.cs b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Window.cs new file mode 100644 index 0000000..54a093a --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/SdlWrapper/Window.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using SDL2; + +namespace testdrid.SdlWrapper +{ + internal class Window : IDisposable + { + private Window() {} + + internal IntPtr Pointer { get; set; } + + public static Window Create(string title, int width, int height) + { + return Create(title, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, width, height, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN | SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL); + } + + public static Window Create(string title, int x, int y, int width, int height, SDL.SDL_WindowFlags flags) + { + Window window = new Window(); + window._fullscreen = flags.HasFlag(SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP); + window.Pointer = SDL.SDL_CreateWindow(title, x, y, width, height, flags); + if (window.Pointer == IntPtr.Zero) + { + throw SdlException.GenerateException(); + } + return window; + } + + public void Dispose() + { + SDL.SDL_DestroyWindow(Pointer); + Pointer = IntPtr.Zero; + } + + public void GlSwap() + { + SDL.SDL_GL_SwapWindow(Pointer); + } + + private bool _fullscreen; + + public bool Fullscreen + { + get + { + return _fullscreen; + } + set + { + if (SDL.SDL_SetWindowFullscreen(Pointer, value ? (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0) != 0) + { + throw SdlException.GenerateException(); + } + } + } + + public Rectangle GetWindowRectangle() + { + int w = -1, h = -1, left = -1, top = -1; + SDL.SDL_GetWindowSize(this.Pointer, out w, out h); + SDL.SDL_GetWindowPosition(this.Pointer, out left, out top); + return new Rectangle(left, top, w, h); + } + } +} diff --git a/GUIs/skyscraper8.UI.ImGui/Tools/TestFixtureFromIntelsat/bl_continue.json b/GUIs/skyscraper8.UI.ImGui/Tools/TestFixtureFromIntelsat/bl_continue.json new file mode 100644 index 0000000..e80fc9f --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/Tools/TestFixtureFromIntelsat/bl_continue.json @@ -0,0 +1 @@ +{"target":{"name":"TBS 6903x DVBS/S2 Tuner 0","tunerIndex":4,"tunerStandard":1,"diseqcType":0,"satPosition":{"angle":34.5,"cardinalDirection":1,"name":"Intelsat 35e","lof1":9750,"lof2":10600,"lofSw":11700,"minFreq":10700,"maxFreq":12750,"Checksum":1107983411},"satIndex":0,"buttonUuid":"2dd66076-0417-4835-8311-7da0fb980021"},"willCaptureFiles":true,"hasSwitchingLnb":true,"willScanHorizontalLow":true,"willScanHorizontalHigh":true,"willScanVerticalLow":true,"willScanVerticalHigh":true,"results":[{"sr1":{"Lock":true,"Freq":11013599,"Pol":0,"SR":999988,"StdType":2,"ModType":5,"FEC":1,"Inversion":2,"RollOff":1,"Pilot":2,"Frame":1,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":1349,"CW":988011,"BitRate":-57,"RFLevel":-858993459,"SNR":0.0078,"BER":0.0078,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"195, 360","State":0},{"sr1":{"Lock":true,"Freq":11030252,"Pol":0,"SR":5618803,"StdType":2,"ModType":2,"FEC":3,"Inversion":2,"RollOff":1,"Pilot":2,"Frame":0,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":7585,"CW":12522522,"BitRate":-50,"RFLevel":-858993459,"SNR":0.008032,"BER":0.008032,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"206, 360","State":0},{"sr1":{"Lock":true,"Freq":11100003,"Pol":0,"SR":51427181,"StdType":2,"ModType":3,"FEC":5,"Inversion":2,"RollOff":6,"Pilot":2,"Frame":0,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":53998,"CW":170223370,"BitRate":-40,"RFLevel":-1717986918,"SNR":0.0,"BER":0.0,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"249, 360","State":0},{"sr1":{"Lock":true,"Freq":11468099,"Pol":0,"SR":14973745,"StdType":1,"ModType":1,"FEC":5,"Inversion":2,"RollOff":1,"Pilot":0,"Frame":0,"CodingType":0,"StreamType":0,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":20214,"CW":22998880,"BitRate":-48,"RFLevel":-1717986918,"SNR":0.0,"BER":0.0,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"479, 360","State":0},{"sr1":{"Lock":true,"Freq":11558991,"Pol":0,"SR":19999786,"StdType":2,"ModType":2,"FEC":3,"Inversion":2,"RollOff":3,"Pilot":2,"Frame":1,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":23999,"CW":44577771,"BitRate":-46,"RFLevel":1717986918,"SNR":0.249835,"BER":0.249835,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"535, 360","State":0},{"sr1":{"Lock":true,"Freq":11595491,"Pol":0,"SR":15454398,"StdType":2,"ModType":0,"FEC":0,"Inversion":2,"RollOff":1,"Pilot":1,"Frame":1,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":20863,"CW":0,"BitRate":-45,"RFLevel":858993459,"SNR":0.0,"BER":0.0,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"558, 360","State":0},{"sr1":{"Lock":true,"Freq":11634990,"Pol":0,"SR":32726484,"StdType":2,"ModType":3,"FEC":3,"Inversion":2,"RollOff":3,"Pilot":2,"Frame":1,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":39271,"CW":97523480,"BitRate":-43,"RFLevel":0,"SNR":0.34255599999999997,"BER":0.34255599999999997,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"583, 360","State":0},{"sr1":{"Lock":true,"Freq":11674992,"Pol":0,"SR":34284453,"StdType":2,"ModType":3,"FEC":15,"Inversion":2,"RollOff":6,"Pilot":2,"Frame":0,"CodingType":0,"StreamType":1,"MIS":0,"IS":"AAAAAAAAAAAAAAAAAAAAAA==","ISSYI":0,"NPD":0,"PLS":35998,"CW":0,"BitRate":-45,"RFLevel":858993459,"SNR":0.048361999999999995,"BER":0.048361999999999995,"preBER":0.0},"sr2":{"Lock":false,"Freq":0,"BW":0,"SR":0,"StdType":0,"ModType":0,"FEC":0,"FECLP":0,"Inversion":0,"TransMode":0,"Guard":0,"Hierarchy":0,"PLP":0,"LID":null,"Pilot":0,"S1Type":0,"Payload":0,"Frame":0,"BwExt":0,"ModeT2":0,"VerT2":0,"CellID":0,"NetID":0,"BitRate":0,"RFLevel":0,"SNR":0.0,"BER":0.0,"preBER":0.0},"Visible":true,"Position":"608, 360","State":0}]} \ No newline at end of file diff --git a/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/Object2.bmp b/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/Object2.bmp new file mode 100644 index 0000000..361f20d Binary files /dev/null and b/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/Object2.bmp differ diff --git a/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/charset.bmp b/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/charset.bmp new file mode 100644 index 0000000..65a563c Binary files /dev/null and b/GUIs/skyscraper8.UI.ImGui/bin/Debug/net8.0/charset.bmp differ diff --git a/GUIs/skyscraper8.UI.ImGui/skyscraper8.UI.ImGui.csproj b/GUIs/skyscraper8.UI.ImGui/skyscraper8.UI.ImGui.csproj new file mode 100644 index 0000000..9ae04c4 --- /dev/null +++ b/GUIs/skyscraper8.UI.ImGui/skyscraper8.UI.ImGui.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + x64 + true + + + + + + + + + + + + + diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/DvbTMagic.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/DvbTMagic.cs new file mode 100644 index 0000000..81867da --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/DvbTMagic.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using skyscraper2.TsDuckInterface; +using skyscraper2.TsDuckInterface.Inputs; +using skyscraper2.TsDuckInterface.Outputs; +using skyscraper2.TsDuckInterface.Processors; +using skyscraper2.TsDuckInterface.Tsscan; + +namespace skyscraper5.Skyscraper +{ + class DvbTMagic + { + private readonly XmlSerializer xs; + + public DvbTMagic() + { + xs = new XmlSerializer(typeof(tsduck)); + } + + public void Run() + { + if (File.Exists("uhf.xml")) + File.Delete("uhf.xml"); + Process tsscan = new Process(); + tsscan.StartInfo.FileName = "tsscan"; + tsscan.StartInfo.Arguments = "--debug=8 -a 1 --delivery-system=DVB-T2 --uhf-band --save-channels uhf.xml"; + tsscan.Start(); + tsscan.WaitForExit(); + if (File.Exists("uhf.xml")) + { + tsduck uhfOut = (tsduck)xs.Deserialize(new StringReader(File.ReadAllText("uhf.xml"))); + BatchScan(uhfOut); + } + + if (File.Exists("vhf.xml")) + File.Delete("vhf.xml"); + tsscan.StartInfo.Arguments = "--debug=8 -a 1 --delivery-system=DVB-T2 --vhf-band --save-channels vhf.xml"; + tsscan.Start(); + tsscan.WaitForExit(); + if (File.Exists("vhf.xml")) + { + tsduck uhfOut = (tsduck)xs.Deserialize(new StringReader(File.ReadAllText("vhf.xml"))); + BatchScan(uhfOut); + } + } + + private void BatchScan(tsduck tsscan) + { + if (tsscan.Items == null) + return; + if (tsscan.Items.Length == 0) + return; + + foreach (tsduckNetwork network in tsscan.Items) + { + foreach (tsduckNetworkTS ts in network.ts) + { + foreach (tsduckNetworkTSDvbt dvbt in ts.dvbt) + { + string freqString = dvbt.frequency.Replace(",", ""); + long freq = Int64.Parse(freqString); + + TspCommandBuilder cmdBuilder = new TspCommandBuilder(); + cmdBuilder.Verbose = true; + + DvbInput dvbInput = new DvbInput(); + dvbInput.AdapterId = 1; + dvbInput.Frequency = freq; + dvbInput.DeliverySystem = DeliverySystem.DVB_T2; + cmdBuilder.Input = dvbInput; + + cmdBuilder.Processors.Add(new HistoryProcessor()); + + UntilProcessor untilProcessor = new UntilProcessor(); + untilProcessor.Bytes = freq; + cmdBuilder.Processors.Add(untilProcessor); + + long mhz = freq / 1000 / 1000; + cmdBuilder.Output = new FileOutput(new FileInfo(String.Format("{0}.ts", mhz))); + + cmdBuilder.ExecuteAndWait(); + } + } + } + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/Program.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/Program.cs new file mode 100644 index 0000000..c7c8060 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/Program.cs @@ -0,0 +1,8 @@ +// See https://aka.ms/new-console-template for more information + +using skyscraper5.Skyscraper; + +Console.WriteLine("skyscraper5 DVB-T Batch Capture using TSDuck's tsscan and tsp!"); + +DvbTMagic magic = new DvbTMagic(); +magic.Run(); diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/DeliverySystem.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/DeliverySystem.cs new file mode 100644 index 0000000..5e98b8c --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/DeliverySystem.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace skyscraper2.TsDuckInterface +{ + class DeliverySystem + { + public string Name { get; } + + private DeliverySystem(string name) + { + Name = name; + } + + public override string ToString() + { + return Name; + } + + public static readonly DeliverySystem ATSC = new DeliverySystem("ATSC"); + public static readonly DeliverySystem ATSC_MH = new DeliverySystem("ATSC-MH"); + public static readonly DeliverySystem CMMB = new DeliverySystem("CMMB"); + public static readonly DeliverySystem DAB = new DeliverySystem("DAB"); + public static readonly DeliverySystem DSS = new DeliverySystem("DSS"); + public static readonly DeliverySystem DTMB = new DeliverySystem("DTMB"); + public static readonly DeliverySystem DVB_C = new DeliverySystem("DVB-C"); + public static readonly DeliverySystem DVB_C_AnnexA = new DeliverySystem("DVB-C/A"); + public static readonly DeliverySystem DVB_C_AnnexB = new DeliverySystem("DVB-C/B"); + public static readonly DeliverySystem DVB_C_AnnexC = new DeliverySystem("DVB-C/C"); + public static readonly DeliverySystem DVB_C2 = new DeliverySystem("DVB-C2"); + public static readonly DeliverySystem DVB_H = new DeliverySystem("DVB-H"); + public static readonly DeliverySystem DVB_S = new DeliverySystem("DVB-S"); + public static readonly DeliverySystem DVB_S_Turbo = new DeliverySystem("DVB-S-Turbo"); + public static readonly DeliverySystem DVB_S2 = new DeliverySystem("DVB-S2"); + public static readonly DeliverySystem DVB_T = new DeliverySystem("DVB-T"); + public static readonly DeliverySystem DVB_T2 = new DeliverySystem("DVB-T2"); + public static readonly DeliverySystem ISDB_C = new DeliverySystem("ISDB-C"); + public static readonly DeliverySystem ISDB_S = new DeliverySystem("ISDB-S"); + public static readonly DeliverySystem ISDB_T = new DeliverySystem("ISDB-T"); + + public static DeliverySystem FromString(string name) + { + if (name.Equals("S1")) + return DVB_S; + if (name.Equals("S2")) + return DVB_S2; + + name = name.Replace('_', '-'); + FieldInfo[] fieldInfos = typeof(DeliverySystem).GetFields(); + foreach (FieldInfo fieldInfo in fieldInfos) + { + DeliverySystem value = (DeliverySystem)fieldInfo.GetValue(null); + if (value.Name.ToLowerInvariant().Equals(name.ToLowerInvariant())) + return value; + } + + throw new ArgumentException("Unsupported delivery system"); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/Lnb.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/Lnb.cs new file mode 100644 index 0000000..f6c5041 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/Lnb.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface +{ + enum Lnb + { + Extended, + Universal, + Enhanced, + DBS, + Standard, + L10700, + L10750, + L11300, + QPH031, + DishPro, + Japan, + Amazonas3, + Amazonas2 + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/Polarity.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/Polarity.cs new file mode 100644 index 0000000..b86cf74 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/DvbModel/Polarity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface.DvbModel +{ + enum Polarity + { + Horizontal, + Vertical, + Left, + Right + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Inputs/DvbInput.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Inputs/DvbInput.cs new file mode 100644 index 0000000..35f9c32 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Inputs/DvbInput.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using skyscraper2.TsDuckInterface.DvbModel; + +namespace skyscraper2.TsDuckInterface.Inputs +{ + class DvbInput : TspInput + { + public byte? AdapterId { get; set; } + public DeliverySystem DeliverySystem { get; set; } + public byte? SatelliteNumber { get; set; } + public Lnb? Lnb { get; set; } + public long? Frequency { get; set; } + public long? SymbolRate { get; set; } + public Polarity? Polarity { get; set; } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("dvb "); + if (AdapterId.HasValue) + sb.AppendFormat("-a {0} ", AdapterId); + + if (Frequency != null) + sb.AppendFormat("--frequency {0} ", Frequency); + + if (DeliverySystem != null) + sb.AppendFormat("--delivery-system {0} ", DeliverySystem.ToString()); + + if (Lnb.HasValue) + sb.AppendFormat("--lnb {0} ", Lnb.ToString()); + + if (Polarity.HasValue) + sb.AppendFormat("--polarity {0} ", Polarity.ToString().ToLowerInvariant()); + + if (SatelliteNumber.HasValue) + sb.AppendFormat("--satellite-number {0} ", SatelliteNumber); + + if (SymbolRate.HasValue) + sb.AppendFormat("--symbol-rate {0} ", SymbolRate); + + return sb.ToString().Trim(); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Inputs/FileInput.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Inputs/FileInput.cs new file mode 100644 index 0000000..06e1e71 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Inputs/FileInput.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace skyscraper2.TsDuckInterface.Inputs +{ + class FileInput : TspInput + { + public FileInput(FileInfo sourceFile) + { + SourceFile = sourceFile; + } + + public FileInfo SourceFile { get; set; } + + public override string ToString() + { + return String.Format("file \"{0}\"", SourceFile.FullName); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Outputs/DropOutput.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Outputs/DropOutput.cs new file mode 100644 index 0000000..9048cd2 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Outputs/DropOutput.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface.Outputs +{ + class DropOutput : TspOutput + { + public override string ToString() + { + return "drop"; + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Outputs/FileOutput.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Outputs/FileOutput.cs new file mode 100644 index 0000000..f1852b2 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Outputs/FileOutput.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper2.TsDuckInterface.Outputs +{ + class FileOutput : TspOutput + { + public FileOutput(FileInfo fileName) + { + FileName = fileName; + } + + public FileInfo FileName { get; set; } + + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("file"); + sb.AppendFormat(" \"{0}\"", FileName.FullName); + return sb.ToString(); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/HistoryProcessor.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/HistoryProcessor.cs new file mode 100644 index 0000000..e504d01 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/HistoryProcessor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface.Processors +{ + class HistoryProcessor : TspProcessor + { + public override string ToString() + { + return "history"; + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/TablesProcessor.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/TablesProcessor.cs new file mode 100644 index 0000000..a12e0f9 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/TablesProcessor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace skyscraper2.TsDuckInterface.Processors +{ + class TablesProcessor : TspProcessor + { + public TablesProcessor(bool allOnce, bool stictXml, FileInfo xmlOutput, bool packAllSections) + { + AllOnce = allOnce; + StictXml = stictXml; + XmlOutput = xmlOutput; + PackAllSections = packAllSections; + } + + public bool AllOnce { get; set; } + public bool StictXml { get; set; } + public FileInfo XmlOutput { get; set; } + public bool PackAllSections { get; set; } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("tables "); + if (AllOnce) + sb.Append("--all-once "); + if (StictXml) + sb.Append("--strict-xml "); + if (XmlOutput != null) + sb.AppendFormat("--xml-output \"{0}\" ", XmlOutput.FullName); + if (PackAllSections) + sb.Append("--pack-all-sections "); + + return sb.ToString().Trim(); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/UntilProcessor.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/UntilProcessor.cs new file mode 100644 index 0000000..60938f9 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/Processors/UntilProcessor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface.Processors +{ + class UntilProcessor : TspProcessor + { + public int? Seconds { get; set; } + + public long? Bytes { get; set; } + + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("until"); + if (Seconds.HasValue) + sb.AppendFormat(" --seconds {0}", Seconds.Value); + if (Bytes.HasValue) + sb.AppendFormat(" --bytes {0}", Bytes.Value); + + return sb.ToString(); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspCommandBuilder.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspCommandBuilder.cs new file mode 100644 index 0000000..1784845 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspCommandBuilder.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using skyscraper2.TsDuckInterface.Inputs; +using skyscraper2.TsDuckInterface.Outputs; +using skyscraper2.TsDuckInterface.Processors; + +namespace skyscraper2.TsDuckInterface +{ + class TspCommandBuilder + { + public TspCommandBuilder() + { + Processors = new List(); + TspPath = "tsp"; + } + + public string TspPath { get; set; } + + public TspInput Input { get; set; } + public TspOutput Output { get; set; } + public List Processors { get; } + public bool Verbose { get; set; } + + public string GetArguments() + { + if (Input == null) + throw new NullReferenceException("No Input specified!"); + if (Output == null) + throw new NullReferenceException("No output specified!"); + + StringBuilder sb = new StringBuilder(); + if (Verbose) + sb.Append("-v "); + + sb.Append("-I "); + sb.Append(Input.ToString()); + sb.Append(" "); + + foreach (TspProcessor processor in Processors) + { + sb.Append("-P "); + sb.Append(processor.ToString()); + sb.Append(" "); + } + + sb.Append("-O "); + sb.Append(Output.ToString()); + sb.Append(" "); + return sb.ToString(); + } + + public int ExecuteAndWait() + { + + + Process tsp = new Process(); + tsp.StartInfo.FileName = TspPath; + tsp.StartInfo.Arguments = GetArguments(); + + tsp.Start(); + tsp.WaitForExit(); + + return tsp.ExitCode; + } + + public static FileInfo TsToXml(FileInfo ts) + { + string tempFileName = Path.GetTempFileName(); + FileInfo fi = new FileInfo(tempFileName); + if (fi.Exists) + fi.Delete(); + + TspCommandBuilder tcb = new TspCommandBuilder(); + tcb.Verbose = true; + tcb.Input = new FileInput(ts); + tcb.Output = new DropOutput(); + //tcb.Processors.Add(new HistoryProcessor()); + tcb.Processors.Add(new TablesProcessor(true, true, fi, true)); + int result = tcb.ExecuteAndWait(); + switch (result) + { + case 0: + break; + case 1: + Console.WriteLine("tsp encountered an error. Ignoring this stream."); + fi.Delete(); + return null; + case -1073741819: + Console.WriteLine("tsp encountered an access violation. Ignoring this stream."); + fi.Delete(); + return null; + default: + throw new NotImplementedException(result.ToString()); + } + return fi; + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspInput.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspInput.cs new file mode 100644 index 0000000..674e05d --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspInput.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface +{ + abstract class TspInput + { + public abstract override string ToString(); + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspOutput.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspOutput.cs new file mode 100644 index 0000000..5168034 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspOutput.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface +{ + abstract class TspOutput + { + public abstract override string ToString(); + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspProcessor.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspProcessor.cs new file mode 100644 index 0000000..e18395b --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/TspProcessor.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace skyscraper2.TsDuckInterface +{ + abstract class TspProcessor + { + public abstract override string ToString(); + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/uhf.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/uhf.cs new file mode 100644 index 0000000..23a3015 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TsDuckInterface/uhf.cs @@ -0,0 +1,230 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +using System.Xml.Serialization; + +// +// Dieser Quellcode wurde automatisch generiert von xsd, Version=4.8.3928.0. +// + +namespace skyscraper2.TsDuckInterface.Tsscan +{ + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)] + public partial class tsduck + { + + private tsduckNetwork[] itemsField; + + /// + [System.Xml.Serialization.XmlElementAttribute("network", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public tsduckNetwork[] Items + { + get { return this.itemsField; } + set { this.itemsField = value; } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class tsduckNetwork + { + + private tsduckNetworkTS[] tsField; + + private string idField; + + private string typeField; + + /// + [System.Xml.Serialization.XmlElementAttribute("ts", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public tsduckNetworkTS[] ts + { + get { return this.tsField; } + set { this.tsField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string id + { + get { return this.idField; } + set { this.idField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string type + { + get { return this.typeField; } + set { this.typeField = value; } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class tsduckNetworkTS + { + + private tsduckNetworkTSDvbt[] dvbtField; + + private tsduckNetworkTSService[] serviceField; + + private string idField; + + private string onidField; + + /// + [System.Xml.Serialization.XmlElementAttribute("dvbt", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public tsduckNetworkTSDvbt[] dvbt + { + get { return this.dvbtField; } + set { this.dvbtField = value; } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("service", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public tsduckNetworkTSService[] service + { + get { return this.serviceField; } + set { this.serviceField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string id + { + get { return this.idField; } + set { this.idField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string onid + { + get { return this.onidField; } + set { this.onidField = value; } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class tsduckNetworkTSDvbt + { + + private string frequencyField; + + private string modulationField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string frequency + { + get { return this.frequencyField; } + set { this.frequencyField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string modulation + { + get { return this.modulationField; } + set { this.modulationField = value; } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class tsduckNetworkTSService + { + + private string idField; + + private string nameField; + + private string providerField; + + private string pMTPIDField; + + private string typeField; + + private string casField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string id + { + get { return this.idField; } + set { this.idField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string name + { + get { return this.nameField; } + set { this.nameField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string provider + { + get { return this.providerField; } + set { this.providerField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string PMTPID + { + get { return this.pMTPIDField; } + set { this.pMTPIDField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string type + { + get { return this.typeField; } + set { this.typeField = value; } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string cas + { + get { return this.casField; } + set { this.casField = value; } + } + } +} \ No newline at end of file diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TspBatchCapture.cs b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TspBatchCapture.cs new file mode 100644 index 0000000..9f13c9c --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/TspBatchCapture.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Serialization; +using skyscraper2.TsDuckInterface; +using skyscraper2.TsDuckInterface.DvbModel; +using skyscraper2.TsDuckInterface.Inputs; +using skyscraper2.TsDuckInterface.Outputs; +using skyscraper2.TsDuckInterface.Processors; + +namespace skyscraper5.Skyscraper +{ + class TspBatchCapture + { + public TspBatchCapture() + { + xmlSerializer = new XmlSerializer(typeof(List)); + } + + private XmlSerializer xmlSerializer; + + private List GetDedupList() + { + if (!File.Exists("dedup.xml")) + return new List(); + + FileStream fileStream = File.OpenRead("dedup.xml"); + List result = (List)xmlSerializer.Deserialize(fileStream); + fileStream.Close(); + return result; + } + + private void SaveDedupList(List list) + { + FileStream fileStream = File.OpenWrite("dedup.xml"); + xmlSerializer.Serialize(fileStream, list); + fileStream.Close(); + } + + public void Run(FileInfo fi, int adapterNo, int satNo, string satName) + { + List dedupList = GetDedupList(); + StreamReader streamReader = fi.OpenText(); + string line = null; + while (!streamReader.EndOfStream) + { + string readLine = streamReader.ReadLine(); + if (readLine.StartsWith("#")) + continue; + string[] args = readLine.Split(' '); + if (args.Length != 8) + continue; + + string inputDeliverySystem = args[0]; + long frequency = Convert.ToInt64(args[1]); + char polarity = args[2][0]; + long symbolRate = Convert.ToInt64(args[3]); + string fec = args[4]; + double rollOff = Convert.ToDouble(String.Format("0,{0}", args[5])); + string modulation = args[6]; + long flags = Convert.ToInt64(args[7]); + + DvbInput dvbInput = new DvbInput(); + dvbInput.Frequency = frequency * 1000; + dvbInput.SymbolRate = symbolRate; + dvbInput.AdapterId = (byte)adapterNo; + dvbInput.DeliverySystem = DeliverySystem.FromString(inputDeliverySystem); + dvbInput.Lnb = Lnb.Extended; + dvbInput.Polarity = GetPolarity(polarity); + dvbInput.SatelliteNumber = (byte)satNo; + + DirectoryInfo di = new DirectoryInfo("E:\\tspBatchCapture"); + if (!di.Exists) + di.Create(); + string fileName = String.Format("{0}_{1}_{2}_{3}.ts", satName, frequency / 1000, polarity, symbolRate / 1000); + if (dedupList.Contains(fileName)) + continue; + FileInfo outputFileInfo = new FileInfo(Path.Combine(di.FullName, fileName)); + FileOutput fileOutput = new FileOutput(outputFileInfo); + + UntilProcessor untilProcessor = new UntilProcessor(); + untilProcessor.Bytes = 100000000L; //100 MB + + TspCommandBuilder tspCommandBuilder = new TspCommandBuilder(); + tspCommandBuilder.Verbose = true; + tspCommandBuilder.TspPath = "tsp"; + tspCommandBuilder.Input = dvbInput; + tspCommandBuilder.Output = fileOutput; + tspCommandBuilder.Processors.Add(untilProcessor); + int exitCode = tspCommandBuilder.ExecuteAndWait(); + switch (exitCode) + { + case 0: + dedupList.Add(fileName); + SaveDedupList(dedupList); + break; + case -1073741819: + dvbInput.DeliverySystem = FlipDeliverySystem(dvbInput.DeliverySystem); + tspCommandBuilder.ExecuteAndWait(); + dedupList.Add(fileName); + SaveDedupList(dedupList); + break; + default: + throw new NotImplementedException(exitCode.ToString()); + } + + Thread.Sleep(1000); + } + } + + private Polarity GetPolarity(char c) + { + if (c == 'H') + return Polarity.Horizontal; + else if (c == 'V') + return Polarity.Vertical; + else + throw new NotImplementedException(c.ToString()); + } + + private DeliverySystem FlipDeliverySystem(DeliverySystem ds) + { + if (ds.Equals(DeliverySystem.DVB_S)) + return DeliverySystem.DVB_S2; + if (ds.Equals(DeliverySystem.DVB_S2)) + return DeliverySystem.DVB_S; + throw new NotImplementedException(ds.ToString()); + } + } +} diff --git a/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/skyscraper5.Gimmicks.DvbTBatchCapture.csproj b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/skyscraper5.Gimmicks.DvbTBatchCapture.csproj new file mode 100644 index 0000000..91b464a --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.DvbTBatchCapture/skyscraper5.Gimmicks.DvbTBatchCapture.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/Gimmicks/skyscraper5.Gimmicks.IptvCollector/Program.cs b/Gimmicks/skyscraper5.Gimmicks.IptvCollector/Program.cs new file mode 100644 index 0000000..6cff731 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.IptvCollector/Program.cs @@ -0,0 +1,160 @@ +using System.Diagnostics; +using PlaylistsNET.Content; +using PlaylistsNET.Models; + +bool RunProgram(string progname, string args, string directory = null) +{ + Process process = new Process(); + process.StartInfo.FileName = progname; + process.StartInfo.Arguments = args; + process.StartInfo.UseShellExecute = false; + if (!string.IsNullOrEmpty(directory)) + process.StartInfo.WorkingDirectory = directory; + Console.WriteLine("Starting {0} at {1}", progname, DateTime.Now); + process.Start(); + process.WaitForExit(); +return process.ExitCode == 0; +} + +void EnsureFolderExists(DirectoryInfo directory) +{ + if (directory.Exists) + return; + + EnsureFolderExists(directory.Parent); + directory.Create(); + directory.Refresh(); +} + +string GetChannelName(M3uPlaylistEntry entry) +{ + if (entry.CustomProperties.ContainsKey("EXTINF")) + { + string extinf = entry.CustomProperties["EXTINF"]; + string[] extInfArgs = extinf.Split(','); + string channelName = extInfArgs[extInfArgs.Length - 1]; + channelName = CleanString(channelName); + return channelName; + } + foreach (KeyValuePair can in entry.CustomProperties) + { + if (can.Key.StartsWith("EXTINF:-1")) + { + string fullname = can.ToString(); + int offset = fullname.IndexOf("\","); + fullname = fullname.Substring(offset + 2); + fullname = fullname.Substring(0, fullname.Length - 1); + fullname = CleanString(fullname); + return fullname; + } + } + throw new NotImplementedException(); +} + +string CleanString(string channelName) +{ + char[] invalidChars = Path.GetInvalidFileNameChars(); + char[] buffer = channelName.ToCharArray(); + for (int i = 0; i < buffer.Length; i++) + { + if (invalidChars.Contains(buffer[i])) + { + buffer[i] = '_'; + } + if (buffer[i] > 126) + { + buffer[i] = '-'; + } + if (buffer[i] == ';') + { + buffer[i] = '+'; + } + } + return new string(buffer); +} + +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); + +DirectoryInfo streamDir = new DirectoryInfo(Path.Combine("iptv\\streams")); +if (!streamDir.Exists) +{ + if (!RunProgram("git", "clone https://github.com/iptv-org/iptv")) + { + Console.WriteLine("Failed to clone the git repository."); + return; + } +} +else +{ + RunProgram("git", "pull", "iptv"); +} + +streamDir.Refresh(); +FileInfo csvName = new FileInfo(String.Format("{0}.csv", DateTime.Now.Ticks)); +StringWriter csvBuilder = new StringWriter(); + +foreach (FileInfo m3uinfo in streamDir.GetFiles("*.m3u")) +{ + M3uContent m3u = new M3uContent(); + PlaylistsNET.Models.M3uPlaylist m3uPlaylist = m3u.GetFromString(File.ReadAllText(m3uinfo.FullName)); + List playlistEntries = m3uPlaylist.PlaylistEntries; + string countryName = Path.GetFileNameWithoutExtension(m3uinfo.Name); + foreach (M3uPlaylistEntry entry in playlistEntries) + { + string channelName = GetChannelName(entry); + string outputFileName = Path.Combine("output", countryName, String.Format("{0}.ts", channelName)); + string outputFlagName = Path.Combine("output", countryName, String.Format("{0}.fail", channelName)); + FileInfo outputFileInfo = new FileInfo(outputFileName); + if (!outputFileInfo.Exists) + { + if (File.Exists(outputFlagName)) + continue; + EnsureFolderExists(outputFileInfo.Directory); + string tspArgs = String.Format("-v -I hls \"{0}\" -P until -s 60 -O file \"{1}\"", entry.Path, outputFileInfo.FullName); + if (!RunProgram("tsp", tspArgs)) + { + File.WriteAllText(outputFlagName, ""); + } + } + outputFileInfo.Refresh(); + if (outputFileInfo.Exists) + { + uint[] pidStats = AnalyzeStream(outputFileInfo); + int activePids = pidStats.Count(x => x > 0); + string outputLine = String.Format("{0};{1};{2};", outputFileInfo.Directory.Name, channelName, activePids); + for (int i = 0; i < pidStats.Length; i++) + { + if (pidStats[i] > 0) + { + outputLine += String.Format("0x{0:X4};", i); + } + } + csvBuilder.WriteLine(outputLine); + Console.WriteLine(outputLine); + } + } +} +File.WriteAllText(csvName.FullName, csvBuilder.ToString()); + +uint[] AnalyzeStream(FileInfo outputFileInfo) +{ + FileStream fileStream = outputFileInfo.OpenRead(); + BufferedStream bufferedStream = new BufferedStream(fileStream, 192512); + byte[] packetBuffer = new byte[188]; + uint[] result = new uint[0x2000]; + while (true) + { + if (bufferedStream.Read(packetBuffer, 0, packetBuffer.Length) != packetBuffer.Length) + break; + if (packetBuffer[0] != 'G') + break; + int pid = packetBuffer[1] & 0x1f; + pid <<= 8; + pid += packetBuffer[2]; + result[pid]++; + } + bufferedStream.Close(); + fileStream.Close(); + return result; +} \ No newline at end of file diff --git a/Gimmicks/skyscraper5.Gimmicks.IptvCollector/Properties/launchSettings.json b/Gimmicks/skyscraper5.Gimmicks.IptvCollector/Properties/launchSettings.json new file mode 100644 index 0000000..6c183d4 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.IptvCollector/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "skyscraper5.Gimmicks.IptvCollector": { + "commandName": "Project", + "workingDirectory": "D:\\IPTV\\" + } + } +} \ No newline at end of file diff --git a/Gimmicks/skyscraper5.Gimmicks.IptvCollector/skyscraper5.Gimmicks.IptvCollector.csproj b/Gimmicks/skyscraper5.Gimmicks.IptvCollector/skyscraper5.Gimmicks.IptvCollector.csproj new file mode 100644 index 0000000..2b9ef68 --- /dev/null +++ b/Gimmicks/skyscraper5.Gimmicks.IptvCollector/skyscraper5.Gimmicks.IptvCollector.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Gimmicks/skyscraper5.LyngsatMapsScraper/Model/IndexJson.cs b/Gimmicks/skyscraper5.LyngsatMapsScraper/Model/IndexJson.cs new file mode 100644 index 0000000..9776541 --- /dev/null +++ b/Gimmicks/skyscraper5.LyngsatMapsScraper/Model/IndexJson.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.LyngsatMapsScraper.Model +{ + public class IndexJson + { + public IndexJsonKml[] kml_list; + } +} diff --git a/Gimmicks/skyscraper5.LyngsatMapsScraper/Model/IndexJsonKml.cs b/Gimmicks/skyscraper5.LyngsatMapsScraper/Model/IndexJsonKml.cs new file mode 100644 index 0000000..b815728 --- /dev/null +++ b/Gimmicks/skyscraper5.LyngsatMapsScraper/Model/IndexJsonKml.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.LyngsatMapsScraper.Model +{ + public class IndexJsonKml + { + public string id; + public string name; + public string satpos; + public string beams_process_timestamp; + public string url; + + public SaneSatellitePointer Sanitize() + { + return new SaneSatellitePointer(Int32.Parse(id), name, float.Parse(satpos,CultureInfo.InvariantCulture), UnixTimeStampToDateTime(Int64.Parse(beams_process_timestamp))); + } + + //stolen from https://stackoverflow.com/a/250400 + private static DateTime UnixTimeStampToDateTime(long unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(unixTimeStamp).ToLocalTime(); + return dateTime; + } + } + + public record SaneSatellitePointer(int id, string name, float satpos, DateTime beamsProcessTimestamp); +} diff --git a/Gimmicks/skyscraper5.LyngsatMapsScraper/Program.cs b/Gimmicks/skyscraper5.LyngsatMapsScraper/Program.cs new file mode 100644 index 0000000..8bf1241 --- /dev/null +++ b/Gimmicks/skyscraper5.LyngsatMapsScraper/Program.cs @@ -0,0 +1,168 @@ +using System.Globalization; +using System.Net; +using System.Text; +using System.Xml.Serialization; +using SharpKml.Base; +using SharpKml.Dom; +using SharpKml.Engine; +using skyscraper5.LyngsatMapsScraper.Model; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.LyngsatMapsScraper +{ + internal class Program + { + static void Main(string[] args) + { + new Program().Run(); + } + + private XmlSerializer xmlSerializer; + private bool gotHeaders; + private const string rootUrl = "https://genmap.lyngsat.org/server2/kml"; + private Random rng; + private void SafeThrottle() + { + if (rng == null) + rng = new Random(); + Thread.Sleep(rng.Next(500, 1500)); + } + + private void Run() + { + WebClient webClient = new WebClient(); + DirectoryInfo outputDirectoryInfo = new DirectoryInfo("temp"); + if (!outputDirectoryInfo.Exists) + outputDirectoryInfo.Create(); + FileInfo indexJsonFileInfo = outputDirectoryInfo.GetSubfile(String.Format("index_{0}_{1}_{2}.json", DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day)); + if (!indexJsonFileInfo.Exists) + { + Console.WriteLine("download index json"); + byte[] kmlByteArray = webClient.DownloadData(rootUrl); + indexJsonFileInfo.WriteAllBytes(kmlByteArray); + SafeThrottle(); + } + + + ScraperStorageFactoryConnectionManager connectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + IScraperStorageFactory storageFactory = connectionManager.AutoGetDefaultFactory(); + IScraperStroage storage = storageFactory.CreateScraperStroage(); + Console.WriteLine("mark all beams as disabled"); + storage.BeamsDisableAll(); + + + IndexJson indexJson = Newtonsoft.Json.JsonConvert.DeserializeObject(indexJsonFileInfo.ReadAllText()); + foreach (IndexJsonKml kml in indexJson.kml_list) + { + SaneSatellitePointer databasePointer = kml.Sanitize(); + Console.WriteLine("mark beam {0} enabled", kml.name); + storage.BeamsEnable(databasePointer.id, databasePointer.satpos, databasePointer.name, databasePointer.beamsProcessTimestamp); + + FileInfo kmlFileInfo = outputDirectoryInfo.GetSubfile(String.Format("{0}_{1}_{2}_{3}.kml", kml.satpos, kml.name, kml.id, kml.beams_process_timestamp)); + if (!kmlFileInfo.Exists) + { + if (!gotHeaders) + { + webClient.Headers.Add("Referer", rootUrl); + gotHeaders = true; + } + + Console.WriteLine("download {0}", kml.url); + try + { + byte[] kmlData = webClient.DownloadData(kml.url); + kmlFileInfo.WriteAllBytes(kmlData); + SafeThrottle(); + } + catch (WebException we) + { + HttpWebResponse httpWebResponse = we.Response as HttpWebResponse; + if (httpWebResponse.StatusCode == HttpStatusCode.NotFound) + { + storage.BeamsDisableSpecific(databasePointer.id, databasePointer.satpos, databasePointer.name, databasePointer.beamsProcessTimestamp); + SafeThrottle(); + continue; + } + else + { + Console.WriteLine(we); + throw; + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + FileStream kmlStream = kmlFileInfo.OpenRead(); + KmlFile kmlFile = KmlFile.Load(kmlStream); + Document rootFeature = (SharpKml.Dom.Document)((SharpKml.Dom.Kml)kmlFile.Root).Feature; + foreach (Feature feature in rootFeature.Features) + { + Placemark placemark = feature as Placemark; + if (placemark == null) + continue; + string name = placemark.Name; + + Polygon polygon = placemark.Geometry as Polygon; + if (polygon == null) + continue; + string id = polygon.Id; + + CoordinateCollection linearRingCoordinates = polygon.OuterBoundary.LinearRing.Coordinates; + StringBuilder sb = new StringBuilder(); + foreach (Vector vector in linearRingCoordinates) + { + sb.AppendFormat("{0},{1}\t", vector.Longitude.ToString(CultureInfo.InvariantCulture), vector.Latitude.ToString(CultureInfo.InvariantCulture)); + } + + string polygonString = sb.ToString().Trim('\t'); + if (string.IsNullOrEmpty(polygonString)) + continue; + Skyscraper.Drawing.Polygon testPolygon = new Skyscraper.Drawing.Polygon(polygonString); + string polygonString2 = testPolygon.GetPolygonString(); + if (!polygonString2.Equals(polygonString)) + { + throw new ApplicationException("Oh dear, I think we're encountering a bug."); + } + + if (!storage.TestForBeamFootprint(databasePointer.id, databasePointer.beamsProcessTimestamp, name, id)) + { + Console.WriteLine("write footprint {0}", name); + storage.BeamFootprintStore(databasePointer.id, databasePointer.beamsProcessTimestamp, name, polygonString2, id); + } + } + kmlStream.Close(); + } + } + } + + internal static class DirectoryInfoExtensions + { + public static FileInfo GetSubfile(this DirectoryInfo directory, string filename) + { + string path = Path.Combine(directory.FullName, filename); + return new FileInfo(path); + } + } + + internal static class FileInfoExtensionis + { + public static void WriteAllBytes(this FileInfo fileInfo, byte[] bytes) + { + File.WriteAllBytes(fileInfo.FullName, bytes); + } + + public static byte[] ReadAllBytes(this FileInfo fileInfo) + { + return File.ReadAllBytes(fileInfo.FullName); + } + + public static string ReadAllText(this FileInfo fileInfo) + { + return File.ReadAllText(fileInfo.FullName); + } + } +} \ No newline at end of file diff --git a/Gimmicks/skyscraper5.LyngsatMapsScraper/skyscraper5.LyngsatMapsScraper.csproj b/Gimmicks/skyscraper5.LyngsatMapsScraper/skyscraper5.LyngsatMapsScraper.csproj new file mode 100644 index 0000000..618c030 --- /dev/null +++ b/Gimmicks/skyscraper5.LyngsatMapsScraper/skyscraper5.LyngsatMapsScraper.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/Gimmicks/skyscraper5.RSR.IQTester/Program.cs b/Gimmicks/skyscraper5.RSR.IQTester/Program.cs new file mode 100644 index 0000000..cab1f31 --- /dev/null +++ b/Gimmicks/skyscraper5.RSR.IQTester/Program.cs @@ -0,0 +1,111 @@ +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.IO.RemoteStreamReader; +using System.Net; + +class Program +{ + public static void Main(string[] args) + { + new Program().Run(); + } + + private int? dvbDeviceToUse; + private void CheckForDvbCallback(int index, string name, STD_TYPE type) + { + if (type == STD_TYPE.STD_DVBS || type == STD_TYPE.STD_DVBS2) + { + dvbDeviceToUse = index; + } + } + + private void Run() + { + // See https://aka.ms/new-console-template for more information + RemoteStreamReaderClient client = new RemoteStreamReaderClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6970)); + + if (!client.CheckForDVBExEx(CheckForDvbCallback)) + { + Console.WriteLine("no devices"); + client.Dispose(); + return; + } + if (!client.StartDvbEx(dvbDeviceToUse.Value)) + { + Console.WriteLine("failed to start"); + client.Dispose(); + return; + } + + if (!client.SetChannel(11221000, 30000000, 1, VITERBIRATE_TYPE.VR_5_6, 9750000, 106000000, 117000000)) + { + Console.WriteLine("set channel failed"); + client.Dispose(); + return; + } + + SearchResult searchResult = new SearchResult(); + if (!client.SignalInfo(ref searchResult)) + { + Console.WriteLine("signal info failed"); + client.Dispose(); + return; + } + + bool pPresent = true, pLock = true; + int pRFLevel = Int32.MaxValue; + float pSNR = float.NaN; + float pBER = float.NaN; + sbyte[] iq = new sbyte[8]; + + char[][] buf = new char[Console.WindowWidth][]; + for (int i = 0; i < buf.Length; i++) + { + buf[i] = new char[Console.WindowHeight]; + Array.Fill(buf[i], (char)0x21); + } + + for (int t = 0; t < 9001; t++) + { + if (!client.GetSignalExEx(ref pPresent, ref pLock, ref pRFLevel, ref pSNR, ref pBER)) + { + Console.WriteLine("get signal ex ex failed"); + client.Dispose(); + return; + } + if (!client.IQScan(0, iq, 4)) + { + Console.WriteLine("iq scan failed"); + client.Dispose(); + return; + } + + Console.CursorLeft = 0; + Console.CursorTop = 0; + Console.Write("RF = {0}, SNR = {1}, BER = {2} ", pRFLevel, pSNR, pBER); + + for (int i = 0; i < iq.Length; i += 2) + { + double x = iq[i + 0] + sbyte.MaxValue; + double y = iq[i + 1] + sbyte.MaxValue; + double xScale = (double)255 / (double)Console.WindowWidth; + double yScale = (double)255 / (double)Console.WindowHeight; + + x /= xScale; + y /= yScale; + + int finalX = (int)x; + int finalY = (int)y; + + Console.CursorTop = (int)y; + Console.CursorLeft = (int)x; + Console.Write(buf[finalX][finalY]++); + } + Thread.Sleep(100); + } + + + client.StopDVB(); + client.Dispose(); + + } +} diff --git a/Gimmicks/skyscraper5.RSR.IQTester/skyscraper5.RSR.IQTester.csproj b/Gimmicks/skyscraper5.RSR.IQTester/skyscraper5.RSR.IQTester.csproj new file mode 100644 index 0000000..cfeb455 --- /dev/null +++ b/Gimmicks/skyscraper5.RSR.IQTester/skyscraper5.RSR.IQTester.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/GpsdClient.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/GpsdClient.cs new file mode 100644 index 0000000..469b20d --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/GpsdClient.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using NmeaParser; + +namespace skyscraper5.NmeaSharpWrapper +{ + internal class GpsdClient : NmeaSharpWrapper + { + public GpsdClient(string hostname, ushort port) + { + this.hostname = hostname; + this.port = port; + } + + private string hostname; + private ushort port; + protected override void ProcessorThread() + { + TcpClient tc = new TcpClient(); + tc.Connect(hostname, port); + NetworkStream networkStream = tc.GetStream(); + StreamWriter streamWriter = new StreamWriter(networkStream); + streamWriter.WriteLine("?WATCH={\"enable\":true,\"nmea\":true}"); + streamWriter.Flush(); + + StreamDevice streamDevice = new StreamDevice(networkStream); + streamDevice.MessageReceived += HandleNmeaMessage; + streamDevice.OpenAsync().Wait(); + CancelRequest = false; + while (!CancelRequest) + { + Thread.Sleep(1000); + } + + streamDevice.CloseAsync().Wait(); + streamWriter.Close(); + networkStream.Close(); + tc.Close(); + } + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/GpsdClientFactory.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/GpsdClientFactory.cs new file mode 100644 index 0000000..55cba5a --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/GpsdClientFactory.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.NmeaSharpWrapper +{ + [SkyscraperPlugin] + [PluginPriority(1)] + public class GpsdClientFactory : IGpsReceiverFactory + { + public string Hostname { get; set; } + public ushort Port { get; set; } + + public IGpsReceiver CreateGpsReceiver() + { + return new GpsdClient(Hostname, Port); + } + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaFromUdp.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaFromUdp.cs new file mode 100644 index 0000000..12211cf --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaFromUdp.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using NmeaParser; +using NmeaParser.Messages; + +namespace skyscraper5.NmeaSharpWrapper +{ + internal class NmeaFromUdp : NmeaSharpWrapper + { + public NmeaFromUdp(string ipAdress, int port) + { + this.ipAdress = ipAdress; + this.port = port; + } + + private string ipAdress; + private int port; + + protected override void ProcessorThread() + { + UdpClient client = new UdpClient(); + client.ExclusiveAddressUse = false; + client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + client.Client.Bind(new IPEndPoint(IPAddress.Parse(this.ipAdress),this.port)); + + CancelRequest = false; + IPEndPoint endPoint = null; + while (!CancelRequest) + { + byte[] receive = client.Receive(ref endPoint); + string s = Encoding.ASCII.GetString(receive); + HandleNmeaMessage(s); + } + client.Close(); + } + + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaFromUdpFactory.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaFromUdpFactory.cs new file mode 100644 index 0000000..2ac64d4 --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaFromUdpFactory.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.NmeaSharpWrapper +{ + [SkyscraperPlugin] + [PluginPriority(2)] + public class NmeaFromUdpFactory : IGpsReceiverFactory + { + public string BindIp { get; set; } + public ushort BindPort { get; set; } + public IGpsReceiver CreateGpsReceiver() + { + return new NmeaFromUdp(BindIp, BindPort); + } + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaSharpWrapper.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaSharpWrapper.cs new file mode 100644 index 0000000..572d756 --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/NmeaSharpWrapper.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NmeaParser; +using NmeaParser.Messages; +using skyscraper5.Skyscraper.Gps; + +namespace skyscraper5.NmeaSharpWrapper +{ + abstract class NmeaSharpWrapper : IGpsReceiver + { + public bool ProvidesAdditionalNmeaData => additionalNmeaData != null; + + + public bool HasLock => seenLock && seenLonLat; + + private bool seenLock, seenLonLat; + private GpsCoordinate receivedCoordinate; + public GpsCoordinate Coordinate => receivedCoordinate; + public AdditionalNmeaData AdditionalNmeaData => additionalNmeaData; + + + public void Stop() + { + CancelRequest = true; + } + + public void Start() + { + _thread = new Thread(ProcessorThread); + _thread.Priority = ThreadPriority.Lowest; + _thread.Name = String.Format("GPS Receiver: {0}", this.GetType().Name); + _thread.Start(); + } + + protected Thread _thread; + protected abstract void ProcessorThread(); + protected bool CancelRequest { get; set; } + private AdditionalNmeaData additionalNmeaData; + + protected void HandleNmeaMessage(object? sender, NmeaMessageReceivedEventArgs e) + { + HandleNmeaMessage(e.Message); + } + + protected void HandleNmeaMessage(string s) + { + NmeaMessage nmeaMessage = NmeaMessage.Parse(s); + HandleNmeaMessage(nmeaMessage); + } + + protected void HandleNmeaMessage(NmeaMessage message) + { + string messageMessageType = message.MessageType; + switch (messageMessageType) + { + case "GPGLL": + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + Gll gll = (Gll)message; + additionalNmeaData.ModeIndicator = gll.ModeIndicator.ToString(); + additionalNmeaData.DataActive = gll.DataActive; + additionalNmeaData.FixTime = gll.FixTime; + receivedCoordinate = new GpsCoordinate((float)gll.Latitude, (float)gll.Longitude); + seenLonLat = true; + break; + case "GPVTG": + Vtg vtg = (Vtg)message; + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + additionalNmeaData.CourseMagnetic = vtg.CourseMagnetic; + additionalNmeaData.CourseTrue = vtg.CourseTrue; + additionalNmeaData.SpeedKnots = vtg.SpeedKnots; + additionalNmeaData.SpeedKph = vtg.SpeedKph; + break; + case "GPZDA": + Zda zda = (Zda)message; + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + additionalNmeaData.FixDateTime = zda.FixDateTime; + break; + case "GPGGA": + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + Gga gga = (Gga)message; + additionalNmeaData.Quality = gga.Quality.ToString(); + additionalNmeaData.Altitude = gga.Altitude; + additionalNmeaData.AltitudeUnits = gga.AltitudeUnits; + additionalNmeaData.DpgsStationId = gga.DgpsStationId; + additionalNmeaData.FixTime = gga.FixTime; + additionalNmeaData.GeoidalSeparation = gga.GeoidalSeparation; + additionalNmeaData.GeoidalSeparationUnits = gga.GeoidalSeparationUnits; + additionalNmeaData.Hdop = gga.Hdop; + additionalNmeaData.NumberOfSatellites = gga.NumberOfSatellites; + additionalNmeaData.TimeSinceLastDgpsUpdate = gga.TimeSinceLastDgpsUpdate; + if (gga.Quality == Gga.FixQuality.GpsFix || gga.Quality == Gga.FixQuality.DgpsFix) + { + receivedCoordinate = new GpsCoordinate((float)gga.Latitude, (float)gga.Longitude); + seenLonLat = true; + seenLock = true; + } + break; + case "GPGSA": + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + Gsa gsa = (Gsa)message; + additionalNmeaData.Hdop = gsa.Hdop; + additionalNmeaData.Fix = gsa.Fix.ToString(); + additionalNmeaData.Mode = gsa.Mode.ToString(); + additionalNmeaData.Pdop = gsa.Pdop; + additionalNmeaData.SatelliteIDs = gsa.SatelliteIDs; + additionalNmeaData.Vdop = gsa.Vdop; + if (gsa.Fix == Gsa.FixType.Fix2D || gsa.Fix == Gsa.FixType.Fix3D) + seenLock = true; + break; + case "GPRMC": + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + Rmc rmc = (Rmc)message; + receivedCoordinate = new GpsCoordinate((float)rmc.Latitude, (float)rmc.Longitude); + seenLonLat = true; + additionalNmeaData.Active = rmc.Active; + additionalNmeaData.CourseTrue = rmc.Course; + additionalNmeaData.FixTime = rmc.FixTime.TimeOfDay; + additionalNmeaData.MagneticVariation = rmc.MagneticVariation; + additionalNmeaData.SpeedKnots = rmc.Speed; + break; + case "GPGSV": + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + Gsv gsv = (Gsv)message; + additionalNmeaData.GnssSignalId = gsv.GnssSignalId; + additionalNmeaData.SatellitesInView = gsv.SatellitesInView; + foreach (SatelliteVehicle vehicle in gsv.SVs) + additionalNmeaData.AddSatellite(vehicle.Id, vehicle.GnssSignalId, vehicle.Azimuth, + vehicle.Elevation, vehicle.SignalToNoiseRatio, vehicle.System.ToString(), + vehicle.TalkerId.ToString()); + break; + case "GPGBS": + if (additionalNmeaData == null) + additionalNmeaData = new AdditionalNmeaData(); + Gbs gbs = (Gbs)message; + additionalNmeaData.LatitudeError = gbs.LatitudeError; + additionalNmeaData.LongitudeError = gbs.LongitudeError; + additionalNmeaData.AltitudeError = gbs.AltitudeError; + additionalNmeaData.SatelliteId = gbs.SatelliteId; + additionalNmeaData.MissedDetectionProbability = gbs.MissedDetectionProbability; + additionalNmeaData.BiasEstimate = gbs.BiasEstimate; + additionalNmeaData.StandardDeviation = gbs.StandardDeviation; + break; + case "GSGPG": + case "GPG": + case "GPTXT": + //does not seem to contain anything? + break; + default: + throw new NotImplementedException(messageMessageType); + } + } + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/SerialDevice.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/SerialDevice.cs new file mode 100644 index 0000000..065508f --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/SerialDevice.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NmeaParser; + +namespace skyscraper5.NmeaSharpWrapper +{ + internal class SerialDevice : NmeaSharpWrapper + { + public SerialDevice(string portname, int baudrate) + { + this.portname = portname; + this.baudrate = baudrate; + } + + private string portname; + private int baudrate; + + protected override void ProcessorThread() + { + SerialPort sp = new SerialPort(this.portname, this.baudrate); + SerialPortDevice spd = new SerialPortDevice(sp); + spd.MessageReceived += HandleNmeaMessage; + spd.OpenAsync().Wait(); + CancelRequest = false; + while (!CancelRequest) + { + Thread.Sleep(1000); + } + + spd.CloseAsync().Wait(); + } + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/SerialDeviceFactory.cs b/GpsPlugins/skyscraper5.NmeaSharpWrapper/SerialDeviceFactory.cs new file mode 100644 index 0000000..260b62f --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/SerialDeviceFactory.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.NmeaSharpWrapper +{ + [SkyscraperPlugin] + [PluginPriority(3)] + public class SerialDeviceFactory : IGpsReceiverFactory + { + public string PortName { get; set; } + public int Baudrate { get; set; } + + public IGpsReceiver CreateGpsReceiver() + { + return new SerialDevice(PortName, Baudrate); + } + } +} diff --git a/GpsPlugins/skyscraper5.NmeaSharpWrapper/skyscraper5.NmeaSharpWrapper.csproj b/GpsPlugins/skyscraper5.NmeaSharpWrapper/skyscraper5.NmeaSharpWrapper.csproj new file mode 100644 index 0000000..7c6717b --- /dev/null +++ b/GpsPlugins/skyscraper5.NmeaSharpWrapper/skyscraper5.NmeaSharpWrapper.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + diff --git a/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Program.cs b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Program.cs new file mode 100644 index 0000000..e85e0ef --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Program.cs @@ -0,0 +1,21 @@ +// See https://aka.ms/new-console-template for more information +using skyscraper5.IO.StreamReader.RemoteStreamReaderServer; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using System.Net; + +Console.WriteLine("skyscraper5 RemoteStreamReader Server!"); + +if (args.Length != 0) +{ + if (args[0].Equals("/test")) + { + new Test().Run(); + return; + } +} + +RemoteStreamReaderServer rsrs = new RemoteStreamReaderServer(); +rsrs.ListenAddress = IPAddress.Any; +rsrs.ListenPort = 6970; +rsrs.LogRequests = true; +rsrs.Run(); \ No newline at end of file diff --git a/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Properties/launchSettings.json b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Properties/launchSettings.json new file mode 100644 index 0000000..407b631 --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "skyscraper5.IO.StreamReader.RemoteStreamReaderServer": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/RemoteStreamReaderServer.cs b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/RemoteStreamReaderServer.cs new file mode 100644 index 0000000..c18467d --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/RemoteStreamReaderServer.cs @@ -0,0 +1,663 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using skyscraper5.IO.StreamReader; +using skyscraper5.Skyscraper.IO.RemoteStreamReader; +using skyscraper5.Skyscraper.Net.Sockets; +using skyscraper5.src.Skyscraper.IO.PortableExecutables; + +namespace skyscraper5.Skyscraper.IO.CrazycatStreamReader +{ + + internal class RemoteStreamReaderServer + { + private bool _logRequests; + private int _listenPort; + private IPAddress _listenAddress; + private bool clientConnected; + private IStreamReader localStreamReader; + private Thread clientThread; + private Dictionary dllVersionInfo; + + public bool IsRunning { get; private set; } + + public int ListenPort + { + get => _listenPort; + set + { + if (IsRunning) + throw new InvalidOperationException(); + _listenPort = value; + } + } + + public IPAddress ListenAddress + { + get => _listenAddress; + set + { + if (IsRunning) + throw new InvalidOperationException(); + _listenAddress = value; + } + } + + public bool LogRequests + { + get => _logRequests; + set => _logRequests = value; + } + + public void Run() + { + if (localStreamReader == null) + { + localStreamReader = LocalStreamReader.GetInstance(); + if (!localStreamReader.CheckForDVB()) + { + Console.WriteLine("No tuners present, quitting."); + return; + } + } + + if (ListenAddress == null) + ListenAddress = IPAddress.Any; + + IPEndPoint listenEndPoint = new IPEndPoint(ListenAddress, ListenPort); + TcpListener tcpListener = new TcpListener(listenEndPoint); + tcpListener.Start(); + Console.WriteLine("Listening on {0}", tcpListener.LocalEndpoint); + + while (true) + { + TcpClient acceptTcpClient = tcpListener.AcceptTcpClient(); + if (clientConnected) + { + Console.WriteLine("Rejected connection from {0}. I'm busy.", acceptTcpClient.Client.RemoteEndPoint); + NetworkStream blockoffStream = acceptTcpClient.GetStream(); + blockoffStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.SERVER_BUSY_WITH_ANOTHER_CLIENT); + blockoffStream.Flush(); + acceptTcpClient.Close(); + } + else + { + Console.WriteLine("Accepted connection from {0}", acceptTcpClient.Client.RemoteEndPoint); + clientConnected = true; + clientThread = new Thread(ClientThreadMethod); + clientThread.Name = String.Format("RemoteStreamReader Client @ {0}", acceptTcpClient.Client.RemoteEndPoint); + clientThread.Start(acceptTcpClient); + } + } + } + + private void ClientThreadMethod(object o) + { + TcpClient tcpClient = (TcpClient)o; + NetworkStream tcpStream = tcpClient.GetStream(); + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.SERVER_READY); + tcpStream.Flush(); + bool breakout = false; + while (!breakout) + { + uint opcode; + try + { + opcode = tcpStream.ReadUInt32BE(); + if (LogRequests) + Console.WriteLine("Request from {0}: {1}", tcpClient.Client.RemoteEndPoint, (RemoteStreamReaderConstants)opcode); + } + catch (Exception e) + { + Console.WriteLine("Lost connection to {0}: {1}", tcpClient.Client.RemoteEndPoint, e); + clientConnected = false; + return; + } + switch (opcode) + { + case (uint)RemoteStreamReaderConstants.REQUEST_NOOP: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_HOSTNAME: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.WriteUTF8(Environment.MachineName); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_USERNAME: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.WriteUTF8(Environment.UserName); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_DATE_TIME: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.WriteInt64BE(DateTime.Now.Ticks); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_DISPOSE: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.Flush(); + breakout = true; + break; + case (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB_EX_EX: + bool checkForDvbExEx = localStreamReader.CheckForDVBExEx((index, name, type) => + { + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.ENUMERATE_CHECK_FOR_DVB_EX_EX); + tcpStream.WriteInt32BE(index); + tcpStream.WriteUTF8(name); + tcpStream.WriteInt32BE((int)type); + }); + if (checkForDvbExEx) + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + else + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_START_DVB_EX: + int readIndex = tcpStream.ReadInt32BE(); + bool startDvbExResult = localStreamReader.StartDvbEx(readIndex); + tcpStream.WriteUInt32BE(startDvbExResult + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_STOP_DVB: + bool stopDvbResult = localStreamReader.StopDVB(); + tcpStream.WriteUInt32BE(stopDvbResult + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_GET_TUNER_TYPE: + STD_TYPE m = (STD_TYPE)(-1); + bool getTunerTypeResult = localStreamReader.GetTunerType(ref m); + tcpStream.WriteUInt32BE(getTunerTypeResult + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if (getTunerTypeResult) + { + tcpStream.WriteInt32BE((int)m); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_GET_CAPS: + Caps caps = localStreamReader.GetCaps(); + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.WriteInt32BE((int)caps); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SEND_DISEQC: + uint diseqcType = tcpStream.ReadUInt32BE(); + byte recvMe = tcpStream.ReadUInt8(); + DiSEqC_Opcode diSEqCOpcode = (DiSEqC_Opcode)recvMe; + bool sendDiSEqC = localStreamReader.SendDiSEqC(diseqcType, diSEqCOpcode); + tcpStream.WriteUInt32BE(sendDiSEqC + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_RF_SCAN: + int rfScanFreq = tcpStream.ReadInt32BE(); + int rfScanPol = tcpStream.ReadInt32BE(); + int rfScanLof1 = tcpStream.ReadInt32BE(); + int rfScanLof2 = tcpStream.ReadInt32BE(); + int rfScanLofSw = tcpStream.ReadInt32BE(); + double rfScanRfLevel = Double.NaN; + bool rfScan = localStreamReader.RFScan(rfScanFreq, rfScanPol, rfScanLof1, rfScanLof2, rfScanLofSw, out rfScanRfLevel); + tcpStream.WriteUInt32BE(rfScan + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.WriteDouble(rfScanRfLevel); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL: + int setChannelFreq = tcpStream.ReadInt32BE(); + int setChannelSymbRate = tcpStream.ReadInt32BE(); + int setChannelPol = tcpStream.ReadInt32BE(); + VITERBIRATE_TYPE setChannelFec = (VITERBIRATE_TYPE)tcpStream.ReadInt32BE(); + int setChannelLof1 = tcpStream.ReadInt32BE(); + int setChannelLof2 = tcpStream.ReadInt32BE(); + int setChannelLofSw = tcpStream.ReadInt32BE(); + bool channel = localStreamReader.SetChannel(setChannelFreq, setChannelSymbRate, setChannelPol, setChannelFec, setChannelLof1, setChannelLof2, setChannelLofSw); + tcpStream.WriteUInt32BE(channel + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO: + SearchResult signalInfoSearchResult = default; + bool signalInfo = localStreamReader.SignalInfo(ref signalInfoSearchResult); + tcpStream.WriteUInt32BE(signalInfo + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if (signalInfo) + { + int sizeOf = Marshal.SizeOf(typeof(SearchResult)); + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + Marshal.StructureToPtr(signalInfoSearchResult, allocHGlobal, true); + byte[] bytes = new byte[sizeOf]; + Marshal.Copy(allocHGlobal, bytes, 0, sizeOf); + Marshal.FreeHGlobal(allocHGlobal); + tcpStream.Write(bytes, 0, sizeOf); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SET_FILTER: + int setFilterPid = tcpStream.ReadInt32BE(); + int setFilterSize = tcpStream.ReadInt32BE(); + FilterServer filterServer = new FilterServer(); + IntPtr setFilterNumber = default; + bool filter = localStreamReader.SetFilter(setFilterPid, filterServer.DvbCallback, 0x02, setFilterSize, ref setFilterNumber); + tcpStream.WriteUInt32BE(filter + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if (filter) + { + if (filterServers == null) + filterServers = new Dictionary(); + filterServers.Add(setFilterNumber, filterServer); + filterServer.ListenIp = ListenAddress; + filterServer.TcpListener = new TcpListener(filterServer.ListenIp, 0); + filterServer.TcpListener.Start(); + filterServer.Thread = new Thread(filterServer.Run); + string filterName = String.Format("Filter for PID {0} @{1}", setFilterPid, filterServer.TcpListener.Server.LocalEndPoint); + filterServer.Thread.Name = filterName; + filterServer.Thread.Start(); + tcpStream.WriteIntPtr(setFilterNumber); + tcpStream.WriteIPEndPoint((IPEndPoint)filterServer.TcpListener.LocalEndpoint); + Console.WriteLine(filterName); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_DEL_FILTER: + IntPtr delFilterNumber = tcpStream.ReadIntPtr(); + bool delFilter = localStreamReader.DelFilter(delFilterNumber); + tcpStream.WriteUInt32BE(delFilter + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if(delFilter) + { + FilterServer server = filterServers[delFilterNumber]; + lock (server.clients) + { + server.clients.ForEach(x => x.Close()); + } + server.Dispose(); + server.Thread.Interrupt(); + filterServers.Remove(delFilterNumber); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN2: + int blscan2FreqStart = tcpStream.ReadInt32BE(); + int blscan2FreqStop = tcpStream.ReadInt32BE(); + int blscan2Pol = tcpStream.ReadInt32BE(); + int blscan2Lof1 = tcpStream.ReadInt32BE(); + int blscan2Lof2 = tcpStream.ReadInt32BE(); + int blscan2LofSw = tcpStream.ReadInt32BE(); + IntPtr blscan2SearchResults = Marshal.AllocHGlobal(65536); + int blscan2TpNum = -1; + BlScanCallback blScanCallback = (ref SearchResult result) => + { + int sizeOf = Marshal.SizeOf(typeof(SearchResult)); + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + Marshal.StructureToPtr(result, allocHGlobal, true); + byte[] buffer = new byte[sizeOf]; + Marshal.Copy(allocHGlobal, buffer, 0, sizeOf); + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.ENUMERATE_BL_SCAN_2); + tcpStream.Write(buffer, 0, sizeOf); + Marshal.FreeHGlobal(allocHGlobal); + }; + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME); + bool blScan2 = localStreamReader.BLScan2(blscan2FreqStart, blscan2FreqStop, blscan2Pol, blscan2Lof1, blscan2Lof2, blscan2LofSw, blscan2SearchResults, ref blscan2TpNum, blScanCallback); + tcpStream.WriteUInt32BE(blScan2 + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if (blScan2) + { + tcpStream.WriteInt32BE(blscan2TpNum); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB: + bool checkForDvb = localStreamReader.CheckForDVB(); + if (checkForDvb) + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + else + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_GET_MAC: + byte[] buffer = new byte[6]; + bool mac = localStreamReader.GetMAC(buffer); + if (mac) + { + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.Write(buffer, 0, 6); + } + else + { + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_FAILED); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_AIR_SCAN: + int freqStart = tcpStream.ReadInt32BE(); + int freqEnd = tcpStream.ReadInt32BE(); + uint step = tcpStream.ReadUInt32BE(); + uint bandwidth = tcpStream.ReadUInt32BE(); + int std = tcpStream.ReadInt32BE(); + IntPtr airScanSearchResults = Marshal.AllocHGlobal(65536); + int airScanTpNum = -1; + AirScanCallback airScanCallback = (ref SearchResult2 result) => + { + int sizeOf = Marshal.SizeOf(typeof(SearchResult2)); + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + Marshal.StructureToPtr(result, allocHGlobal, true); + byte[] buffer = new byte[sizeOf]; + Marshal.Copy(allocHGlobal, buffer, 0, sizeOf); + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.ENUMERATE_AIR_SCAN); + tcpStream.Write(buffer, 0, sizeOf); + Marshal.FreeHGlobal(allocHGlobal); + }; + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME); + bool airscan = localStreamReader.AirScan(freqStart, freqEnd, step, bandwidth, std, airScanSearchResults, ref airScanTpNum, airScanCallback); + tcpStream.WriteUInt32BE(airscan + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if (airscan) + { + tcpStream.WriteInt32BE(airScanTpNum); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL_2: + uint freq = tcpStream.ReadUInt32BE(); + uint bandwidth2 = tcpStream.ReadUInt32BE(); + bool channel2 = localStreamReader.SetChannel2(freq, bandwidth2); + tcpStream.WriteUInt32BE(channel2 + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO_2: + SearchResult2 signalInfoSearchResult2 = default; + bool signalInfo2 = localStreamReader.SignalInfo2(ref signalInfoSearchResult2); + tcpStream.WriteUInt32BE(signalInfo2 + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + if (signalInfo2) + { + int sizeOf = Marshal.SizeOf(typeof(SearchResult2)); + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + Marshal.StructureToPtr(signalInfoSearchResult2, allocHGlobal, true); + byte[] bytes = new byte[sizeOf]; + Marshal.Copy(allocHGlobal, bytes, 0, sizeOf); + Marshal.FreeHGlobal(allocHGlobal); + tcpStream.Write(bytes, 0, sizeOf); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_MIS_SEL: + bool bEnable = tcpStream.ReadBoolean(); + byte misFilter = tcpStream.ReadUInt8(); + byte misFilterMask = tcpStream.ReadUInt8(); + bool misSel = localStreamReader.MISSel(bEnable, misFilter, misFilterMask); + tcpStream.WriteUInt32BE(misSel + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_PLS_SEL: + byte plsMode = tcpStream.ReadUInt8(); + uint code = tcpStream.ReadUInt32BE(); + bool plsSel = localStreamReader.PLSSel(plsMode, code); + tcpStream.WriteUInt32BE(plsSel + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN_EX: + freq = tcpStream.ReadUInt32BE(); + int freq_range = tcpStream.ReadInt32BE(); + int pol = tcpStream.ReadInt32BE(); + int lof1 = tcpStream.ReadInt32BE(); + int lof2 = tcpStream.ReadInt32BE(); + int lofsw = tcpStream.ReadInt32BE(); + int minsr = tcpStream.ReadInt32BE(); + std = tcpStream.ReadInt32BE(); + + SearchResult result = new SearchResult(); + bool blScanEx = localStreamReader.BLScanEx((int)freq, freq_range, pol, lof1, lof2, lofsw, minsr, (STD_TYPE)std, ref result); + tcpStream.WriteUInt32BE(blScanEx + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + + if (blScanEx) + { + int sizeOfEx = Marshal.SizeOf(typeof(SearchResult)); + IntPtr allocHGlobalEx = Marshal.AllocHGlobal(sizeOfEx); + Marshal.StructureToPtr(result, allocHGlobalEx, true); + byte[] bufferEx = new byte[sizeOfEx]; + Marshal.Copy(allocHGlobalEx, bufferEx, 0, sizeOfEx); + tcpStream.Write(bufferEx, 0, sizeOfEx); + Marshal.FreeHGlobal(allocHGlobalEx); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_MOD_SEL: + S2Mode s2mode = new S2Mode(); + s2mode.frameLen = tcpStream.ReadInt32BE(); + s2mode.modcode = (S2MODCODE)tcpStream.ReadInt32BE(); + s2mode.pilot = tcpStream.ReadBoolean(); + uint num = tcpStream.ReadUInt32BE(); + + bool modSel = localStreamReader.ModSel(ref s2mode, num); + tcpStream.WriteUInt32BE(modSel + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + + if (modSel) + { + tcpStream.WriteInt32BE(s2mode.frameLen); + tcpStream.WriteInt32BE((int)s2mode.modcode); + tcpStream.WriteBoolean(s2mode.pilot); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_IQ_SCAN: + uint input = tcpStream.ReadUInt32BE(); + sbyte[] pIQ; + num = tcpStream.ReadUInt32BE(); + pIQ = new sbyte[num * 2]; + + bool iqScan = localStreamReader.IQScan(input, pIQ, num); + tcpStream.WriteUInt32BE(iqScan + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + + if (iqScan) + { + byte[] pIQunsigned = new byte[num * 2]; + for (long i = 0; i < num * 2; i++) + { + pIQunsigned[i] = (byte)pIQ[i]; + } + tcpStream.WriteByteArray(pIQunsigned); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_GET_SIGNAL_EX_EX: + bool pPresent = false; + bool pLock = false; + int pRFLevel = int.MinValue; + float pSNR = float.NaN; + float pBER = float.NaN; + + bool getSignalExEx = localStreamReader.GetSignalExEx(ref pPresent, ref pLock, ref pRFLevel, ref pSNR, ref pBER); + tcpStream.WriteUInt32BE(getSignalExEx + ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL + : (uint)RemoteStreamReaderConstants.COMMAND_FAILED); + + if (getSignalExEx) + { + tcpStream.WriteBoolean(pPresent); + tcpStream.WriteBoolean(pPresent); + tcpStream.WriteInt32BE(pRFLevel); + tcpStream.WriteFloat(pSNR); + tcpStream.WriteFloat(pBER); + } + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_NAME: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.WriteUTF8(localStreamReader.GetEngineName()); + tcpStream.Flush(); + break; + case (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_VERSION: + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL); + tcpStream.WriteVersion(localStreamReader.GetEngineVersion()); + tcpStream.Flush(); + break; + default: + Console.WriteLine("Recieved unknown command {0:X8}", opcode); + tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_NOT_UNDERSTOOD); + tcpStream.Flush(); + break; + } + } + tcpStream.Close(); + tcpClient.Close(); + clientConnected = false; + } + + private Dictionary filterServers; + class FilterServer : IDisposable + { + public FilterServer() + { + clients = new List(); + } + + private byte[] buffer; + public void DvbCallback(IntPtr pointer, int length) + { + if (buffer == null) + buffer = new byte[188]; + + Marshal.Copy(pointer, buffer, 0, 188); + + lock (clients) + { + foreach (TcpClientWrapped tcpClient in clients) + { + try + { + tcpClient.DropPacket(buffer, 0, 188); + } + catch (Exception e) + { + if (tcpClient.wrapped.Client != null) + { + Console.WriteLine("Lost filter client: {0}", tcpClient.wrapped.Client.RemoteEndPoint); + } + + clients.Remove(tcpClient); + break; + } + } + } + } + + public IPAddress ListenIp { get; set; } + public Thread Thread { get; set; } + public TcpListener TcpListener { get; set; } + + public List clients; + + public class TcpClientWrapped : IDisposable + { + public TcpClientWrapped(TcpClient client) + { + this.wrapped = client; + this.bufferedStream = new BufferedStream(wrapped.GetStream(), 96256); + this.packetsRemainBeforeStart = 512; + } + internal TcpClient wrapped; + internal BufferedStream bufferedStream; + private int packetsRemainBeforeStart; + + public void Dispose() + { + wrapped?.Dispose(); + bufferedStream?.Dispose(); + } + + public void Close() + { + try + { + bufferedStream?.Close(); + wrapped?.Close(); + } + catch (IOException ioe) + { + Console.WriteLine("Failed to properly close TCPClient"); + } + } + + internal void DropPacket(byte[] buffer, int offset, int length) + { + if (packetsRemainBeforeStart > 0) + { + if (packetsRemainBeforeStart == 512 || packetsRemainBeforeStart == 256 || packetsRemainBeforeStart == 128 || packetsRemainBeforeStart == 64 || packetsRemainBeforeStart == 32 || packetsRemainBeforeStart == 16 || packetsRemainBeforeStart == 8 || packetsRemainBeforeStart == 4 || packetsRemainBeforeStart == 2 || packetsRemainBeforeStart == 1) + { + Console.WriteLine("Waiting {0} packets before starting to send to client {1}...", packetsRemainBeforeStart, wrapped.Client.RemoteEndPoint); + } + packetsRemainBeforeStart--; + return; + } + bufferedStream?.Write(buffer, offset, length); + } + } + + + public void Run() + { + while (!disposed) + { + try + { + TcpClient acceptTcpClient = TcpListener.AcceptTcpClient(); + Console.WriteLine("Accepted Filter client {0}", acceptTcpClient.Client.RemoteEndPoint); + lock (clients) + { + clients.Add(new TcpClientWrapped(acceptTcpClient)); + } + } + catch (Exception e) + { + break; + } + } + + if (!disposed) + Dispose(); + } + + private bool disposed; + public void Dispose() + { + TcpListener.Stop(); + disposed = true; + } + } + } +} diff --git a/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Test.cs b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Test.cs new file mode 100644 index 0000000..ded59f9 --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/Test.cs @@ -0,0 +1,65 @@ +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.IO.StreamReader.RemoteStreamReaderServer +{ + internal class Test + { + public void Run() + { + LocalStreamReader localStreamReader = LocalStreamReader.GetInstance(); + if (!localStreamReader.CheckForDVB()) + { + Console.WriteLine("no tuners!"); + } + + int? targetTuner = null; + localStreamReader.CheckForDVBExEx((x, y, z) => + { + if (y.Equals("TBS 6903x DVBS/S2 Tuner 0")) + { + targetTuner = x; + } + }); + + if (!targetTuner.HasValue) + { + Console.WriteLine("Didn't find the magic card."); + } + + if (!localStreamReader.StartDvbEx(targetTuner.Value)) + { + Console.WriteLine("could not start the card"); + } + + + IntPtr blscan2SearchResults = Marshal.AllocHGlobal(128 * 1024); + int tpNum = 0; + + BlScanCallback blScanCallback = (ref SearchResult result) => + { + Console.WriteLine(result.Freq); + }; + + if (!localStreamReader.SendDiSEqC(2, (DiSEqC_Opcode)0xf2)) + { + Console.WriteLine("diseqc failed"); + } + + if (!localStreamReader.BLScan2(10700000, 11700000, 0, 9750000, 10600000, 11700000, blscan2SearchResults, ref tpNum, blScanCallback)) + { + Console.WriteLine("Failed BLScan2"); + } + + if (!localStreamReader.StopDVB()) + { + Console.WriteLine("Stop DVB failed"); + } + } + } +} diff --git a/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/skyscraper5.IO.StreamReader.RemoteStreamReaderServer.csproj b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/skyscraper5.IO.StreamReader.RemoteStreamReaderServer.csproj new file mode 100644 index 0000000..81160e0 --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader.RemoteStreamReaderServer/skyscraper5.IO.StreamReader.RemoteStreamReaderServer.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + x86 + + + + + + + diff --git a/IoPlugins/skyscraper5.IO.StreamReader/LocalStreamReader.cs b/IoPlugins/skyscraper5.IO.StreamReader/LocalStreamReader.cs new file mode 100644 index 0000000..36a23de --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader/LocalStreamReader.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.src.Skyscraper.IO.PortableExecutables; + +namespace skyscraper5.IO.StreamReader +{ + public partial class LocalStreamReader : IStreamReader + { + private LocalStreamReader() + { + + } + + private static LocalStreamReader _instance; + + public static LocalStreamReader GetInstance() + { + if (_instance == null) + { + _instance = new LocalStreamReader(); + _instance.dllInfo = _instance.GetDllInfo(); + } + return _instance; + } + + public bool CheckForDVB() + { + return UnsafeStreamReaderMethods.CheckForDVB(); + } + + public bool CheckForDVBEx(DvbEnumCallback func) + { + return UnsafeStreamReaderMethods.CheckForDVBEx(func); + } + + public bool CheckForDVBExEx(DvbEnumCallbackEx func) + { + return UnsafeStreamReaderMethods.CheckForDVBExEx(func); + } + + public bool StartDVB() + { + return UnsafeStreamReaderMethods.StartDVB(); + } + + public bool StartDvbEx(int index) + { + return UnsafeStreamReaderMethods.StartDvbEx(index); + } + + public bool StopDVB() + { + return UnsafeStreamReaderMethods.StopDVB(); + } + + public bool GetTunerType(ref STD_TYPE type) + { + return UnsafeStreamReaderMethods.GetTunerType(ref type); + } + + public bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data) + { + return UnsafeStreamReaderMethods.SendDiSEqC(diseqcType, data); + } + + public bool SendDiseqCmd(byte[] buffer, int length) + { + return UnsafeStreamReaderMethods.SendDiseqCmd(buffer, length); + } + + public bool SendDiseqCmdEx(IntPtr pCmd, int length, IntPtr reply, int replyLength) + { + return UnsafeStreamReaderMethods.SendDiseqCmdEx(pCmd, length, reply, replyLength); + } + + public bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw) + { + return UnsafeStreamReaderMethods.SetChannel(freq, symbrate, pol, fec, lof1, lof2, lofsw); + } + + public bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod) + { + return UnsafeStreamReaderMethods.SetChannelEx(freq, symbrate, pol, fec, lof1, lof2, lofsw, mod); + } + + public bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod, int inv, int pilot, ROLLOFF_TYPE rolloff) + { + return UnsafeStreamReaderMethods.SetChannelExEx(freq, symbrate, pol, fec, lof1, lof2, lofsw, mod, inv, pilot, rolloff); + } + + public bool SetFilter(int pid, StdcallDvbCallback func, int callbackType, int size, ref IntPtr lpFilterNum) + { + return UnsafeStreamReaderMethods.SetFilter(pid, func, callbackType, size, ref lpFilterNum); + } + + public bool SetFilterEx(int pid, StdcallDvbCallbackEx lpFunc, int callbackType, int size, ref IntPtr lpFilterNum) + { + return UnsafeStreamReaderMethods.SetFilterEx(pid, lpFunc, callbackType, size, ref lpFilterNum); + } + + public bool SetBitFilter(int pid, IntPtr filterData, IntPtr filterMask, byte filterLength, StdcallDvbCallback lpFunc, ref IntPtr lpFilterNum) + { + return UnsafeStreamReaderMethods.SetBitFilter(pid, filterData, filterMask, filterLength, lpFunc, ref lpFilterNum); + } + + public bool SetRemoteControl(int irtype, short devAddr, StdcallDvbCallback func, ref IntPtr lpFilterNum) + { + return UnsafeStreamReaderMethods.SetRemoteControl(irtype, devAddr, func, ref lpFilterNum); + } + + public bool DelFilter(IntPtr filterNum) + { + return UnsafeStreamReaderMethods.DelFilter(filterNum); + } + + public bool GetSignal(ref int pStrength, ref int pQuality) + { + return UnsafeStreamReaderMethods.GetSignal(ref pStrength, ref pQuality); + } + + public bool GetSignalStrength(ref float pStrength, ref float pQuality) + { + return UnsafeStreamReaderMethods.GetSignalStrength(ref pStrength, ref pQuality); + } + + public bool GetSignalEx(ref float pSNR, ref float pBER) + { + return UnsafeStreamReaderMethods.GetSignalEx(ref pSNR, ref pBER); + } + + public bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, ref float pSNR, ref float pBER) + { + return UnsafeStreamReaderMethods.GetSignalExEx(ref pPresent, ref pLock, ref pRFLevel, ref pSNR, ref pBER); + } + + public bool Statistic(int[] pStat) + { + return UnsafeStreamReaderMethods.Statistic(pStat); + } + + public bool GetMAC(byte[] pMac) + { + return UnsafeStreamReaderMethods.GetMAC(pMac); + } + + public Caps GetCaps() + { + return UnsafeStreamReaderMethods.GetCaps(); + } + + public bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel) + { + return UnsafeStreamReaderMethods.RFScan(freq, pol, lof1, lof2, lofsw, out pRFLevel); + } + + public bool FFTInit() + { + return UnsafeStreamReaderMethods.FFTInit(); + } + + public bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, byte nb_acc, IntPtr pTab, IntPtr pBegin, IntPtr pNum) + { + return UnsafeStreamReaderMethods.FFTScan(freq, pol, lof1, lof2, lofsw, range, mode, nb_acc, pTab, pBegin, pNum); + } + + public bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, ref SearchResult pSearchResult) + { + return UnsafeStreamReaderMethods.BLScan(freq, freq_range, pol, lof1, lof2, lofsw, minsr, ref pSearchResult); + } + + public bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, STD_TYPE std, ref SearchResult pSearchResult) + { + return UnsafeStreamReaderMethods.BLScanEx(freq, freq_range, pol, lof1, lof2, lofsw, minsr, std, ref pSearchResult); + } + + public bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, IntPtr pSearchResult, ref int pTpNum, BlScanCallback lpFunc) + { + return UnsafeStreamReaderMethods.BLScan2(freq_start, freq_stop, pol, lof1, lof2, lofsw, pSearchResult, ref pTpNum, lpFunc); + } + + public bool SignalInfo(ref SearchResult pSearchResult) + { + return UnsafeStreamReaderMethods.SignalInfo(ref pSearchResult); + } + + public bool IQScan(uint input, sbyte[] pIQ, uint num) + { + return UnsafeStreamReaderMethods.IQScan(input, pIQ, num); + } + + public bool IQScan2(uint point, short[] pIQ, uint num) + { + return UnsafeStreamReaderMethods.IQScan2(point, pIQ, num); + } + + public bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint) + { + return UnsafeStreamReaderMethods.IQScan2Range(input, ref pMinPoint, ref pMaxPoint); + } + + public bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, ref int pMinDelayNs, ref int pMaxDelayNs) + { + return UnsafeStreamReaderMethods.CIRScanRange(bHiRes, ref pMinCnum, ref pMaxCnum, ref pMinDelayNs, ref pMaxDelayNs); + } + + public bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays) + { + return UnsafeStreamReaderMethods.CIRScan(bHiRes, pPowers, pDelays); + } + + public bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum) + { + return UnsafeStreamReaderMethods.CarRange(ref pMinCnum, ref pMaxCnum); + } + + public bool CarEsNo(ref ushort cnum, ref double pEsNo) + { + return UnsafeStreamReaderMethods.CarEsNo(ref cnum, ref pEsNo); + } + + public bool MISSel(bool bEnable, byte misFilter, byte misFilterMask) + { + return UnsafeStreamReaderMethods.MISSel(bEnable, misFilter, misFilterMask); + } + + public bool PLSSel(byte plsMode, uint code) + { + return UnsafeStreamReaderMethods.PLSSel(plsMode, code); + } + + public bool PLSGet(byte pMode, ref uint pCode) + { + return UnsafeStreamReaderMethods.PLSGet(pMode, ref pCode); + } + + public bool ModSel(ref S2Mode ps2Modes, uint num) + { + return UnsafeStreamReaderMethods.ModSel(ref ps2Modes, num); + } + + public bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum) + { + return UnsafeStreamReaderMethods.ModInv(WaitMs, ref pS2Modes, ref pNum); + } + + public bool SetChannel2(uint freq, uint bandwidth) + { + return UnsafeStreamReaderMethods.SetChannel2(freq, bandwidth); + } + + public bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream) + { + return UnsafeStreamReaderMethods.SetChannel2Ex(freq, bandwidth, std, stream); + } + + public bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, int stream) + { + return UnsafeStreamReaderMethods.SetChannel2ExEx(freq, bandwidth, symbrate, std, stream); + } + + public bool SignalInfo2(ref SearchResult2 si2) + { + return UnsafeStreamReaderMethods.SignalInfo2(ref si2); + } + + public bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel) + { + return UnsafeStreamReaderMethods.RFScan2(freq, std, ref pRFLevel); + } + + public bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, IntPtr pSearchResult, ref int pTpNum, AirScanCallback lpFunc) + { + return UnsafeStreamReaderMethods.AirScan(freq_start, freq_stop, step, bandwidth, std, pSearchResult, ref pTpNum, lpFunc); + } + + public bool GetEEPROM(byte[] buffer, int offset, int len) + { + return UnsafeStreamReaderMethods.GetEEPROM(buffer, offset, len); + } + + public bool SetEEPROM(byte[] buffer, int offset, int len) + { + return UnsafeStreamReaderMethods.SetEEPROM(buffer, offset, len); + } + + public void Dispose() + { + if (!StopDVB()) + { + throw new InvalidOperationException("failed to StopDVB"); + } + } + + private Dictionary dllInfo; + + private Dictionary GetDllInfo() + { + FileInfo streamReaderDll = new FileInfo("StreamReader.dll"); + if (!streamReaderDll.Exists) + { + Console.WriteLine("Could not find StreamReader.dll"); + return null; + } + + FileStream streamReaderDllStream = streamReaderDll.OpenRead(); + PeFile peFile = new PeFile(streamReaderDllStream); + PeSectionStream rsrcStream = peFile.GetSectionStream(".rsrc"); + ImageResourceDirectory imageResourceDirectory = RsrcParser.ParseRsrc(rsrcStream); + VsVersionInfo versionInfo = RsrcUtility.TryGetVersionInfo(imageResourceDirectory); + dllInfo = versionInfo.ToDictionary(); + peFile.Dispose(); + + if (!dllInfo.ContainsKey("ProductName")) + { + Console.WriteLine("Could not detect the ProductName in StreamReader.dll"); + return null; + } + if (!dllInfo.ContainsKey("ProductVersion")) + { + Console.WriteLine("Could not detect the Version of StreamReader.dll"); + return null; + } + return dllInfo; + } + public Version GetEngineVersion() + { + return Version.Parse(dllInfo["ProductVersion"]); + } + + public string GetEngineName() + { + return dllInfo["ProductName"]; + } + } +} diff --git a/IoPlugins/skyscraper5.IO.StreamReader/PInvokes.cs b/IoPlugins/skyscraper5.IO.StreamReader/PInvokes.cs new file mode 100644 index 0000000..65b98ff --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader/PInvokes.cs @@ -0,0 +1,512 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; + +namespace skyscraper5.IO.StreamReader +{ + public partial class LocalStreamReader + { + private static class UnsafeStreamReaderMethods + { + /// + /// Checks whether any sort of tuner is present in this system. + /// + /// Whether a tuner is present. + [DllImport("StreamReader.dll", EntryPoint = "CheckForDVB")] + internal static extern bool CheckForDVB(); + + /// + /// Checks whether one or more tuner is present in the system. + /// + /// A method which will be called for each tuner found. + /// Whether a tuner was found + [DllImport("StreamReader.dll", EntryPoint = "CheckForDVBEx")] + internal static extern bool CheckForDVBEx([MarshalAs(UnmanagedType.FunctionPtr)] DvbEnumCallback func); + + /// + /// Checks whether one or more tuner is present in the system. + /// + /// A method which will be called for each tuner found. + /// Whether a tuner was found + [DllImport("StreamReader.dll", EntryPoint = "CheckForDVBExEx")] + public static extern bool CheckForDVBExEx([MarshalAs(UnmanagedType.FunctionPtr)] DvbEnumCallbackEx func); + + /// + /// Boots the first system tuner and sets it as the currently active tuner. Note that + /// only one tuner can be active at the same time. + /// + /// Whether a tuner was booted sucessfully. + [DllImport("StreamReader.dll", EntryPoint = "StartDVB")] + internal static extern bool StartDVB(); + + /// + /// Boots a tuner and makes it the currently active tuner. Note that only one tuner + /// can be active at the same time. + /// + /// The ID of the tuner to boot + /// Whether the turner booted sucessfully. + [DllImport("StreamReader.dll", EntryPoint = "StartDVBEx")] + public static extern bool StartDvbEx(int index); + + /// + /// Shuts down the currently active tuner. + /// + /// Whether the tuner was shut down sucessfully + [DllImport("StreamReader.dll", EntryPoint = "StopDVB")] + public static extern bool StopDVB(); + + /// + /// Retrieves the tuner standard. + /// + /// A Pointer in which the tuner type will be put into. + /// Whether the tuner reports a standard + [DllImport("StreamReader.dll", EntryPoint = "GetTunerType")] + internal static extern bool GetTunerType(ref STD_TYPE type); + + /// + /// Sends a raw command to a DiSEqC switch with up to four positions. + /// + /// 0 for disabling DiSEqc, 1 for a tone-burst, 2 for enabling DiSEqC 1.0 + /// The opcode to send. + /// Whether the command was sent + [DllImport("StreamReader.dll", EntryPoint = "SendDiSEqC")] + public static extern bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data); + + /// + /// Send a raw command to a DiSEqC-Motor, ignoring any potential responses. + /// + /// The buffer containing the raw command + /// The length of the command + /// Whether the command was sent + [DllImport("StreamReader.dll", EntryPoint = "SendDiseqCmd")] + internal static extern bool SendDiseqCmd(byte[] buffer, int length); + + /// + /// Supposed to send a raw command to a DiSEqC-Motor and awaits a response. + /// Since I couldn't test this - (I don't have a motor on my dish!) this will merely + /// throw a NotImplementedException + /// + /// The command to send to the motor + /// The length of the command to send to the motor. + /// A byte array to store the motor's response in. + /// Expected length of the motor's response + /// Throws a NotImplementedException + internal static bool SendDiseqCmdEx(IntPtr pCmd, int length, IntPtr reply, int replyLength) + { + //I have no rotor to test this with. + //Also, I'm not sure whether this method head is correct. + throw new NotImplementedException(nameof(SendDiseqCmdEx)); + } + + /// + /// Set the tuner to a satellite frequency + /// + /// The frequency in kHz. + /// The symbol rate in Sym/s. (e.g. 27500 * 1000 = 27500000) + /// 0 for horizontal, 1 for vertical. + /// The error correction method used by the channel. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// Whether the tuner complied with the request + [DllImport("StreamReader.dll", EntryPoint = "SetChannel")] + public static extern bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, + int lof2, int lofsw); + + /// + /// Set the tuner to a satellite frequency + /// + /// The frequency in kHz. + /// The symbol rate in Sym/s. (e.g. 27500 * 1000 = 27500000) + /// 0 for horizontal, 1 for vertical. + /// The error correction method used by the channel. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The modulation type used by the channel. + /// Whether the tuner complied with the request. + [DllImport("StreamReader.dll", EntryPoint = "SetChannelEx")] + internal static extern bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, + int lof2, int lofsw, MOD_TYPE mod); + + /// + /// Set the tuner to a satellite frequency + /// + /// The frequency in kHz. + /// The symbol rate in Sym/s. (e.g. 27500 * 1000 = 27500000) + /// 0 for horizontal, 1 for vertical. + /// The error correction method used by the channel. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The modulation type used by the channel. + /// Spectral inversion: 0 for autodetection, 1 for no spectral inversion, 2 for spectral inversion. + /// S2 Pilot Symbols: 0 for autodetection, 1 for no pilot symbols, 2 for pilot symbols + /// + /// + [DllImport("StreamReader.dll", EntryPoint = "SetChannelExEx")] + internal static extern bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, + int lof2, int lofsw, MOD_TYPE mod, int inv, int pilot, ROLLOFF_TYPE rolloff); + + /// + /// Starts receiving data from the tuner. + /// + /// The PID you want to receive. Set this to 8192 if you want ALL pids. + /// The function to call when a packet was received. + /// Set this to 0x02 for all that is holy. Others might cause blue-screens. + /// Set this to 1 to receive single packets, or 2 to receive (a varying amount of) multiple packets. + /// The pointer you can use with DelFilter. + /// Whether the filter was started sucesfully. + [DllImport("StreamReader.dll", EntryPoint = "SetFilter")] + public static extern bool SetFilter(int pid, [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallback func, + int callbackType, int size, ref IntPtr lpFilterNum); + + /// + /// Starts receiving data from the tuner. + /// + /// The PID you want to receive. Set this to 8192 if you want ALL pids. + /// The function to call when a packet was received. + /// Set this to 0x02 for all that is holy. Others might cause blue-screens. + /// Set this to 1 to receive single packets, or 2 to receive (a varying amount of) multiple packets. + /// The pointer you can use with DelFilter. + /// Whether the filter was started sucesfully. + [DllImport("StreamReader.dll", EntryPoint = "SetFilterEx")] + internal static extern bool SetFilterEx(int pid, + [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallbackEx lpFunc, int callbackType, int size, + ref IntPtr lpFilterNum); + + [DllImport("StreamReader.dll", EntryPoint = "SetBitFilter")] + internal static extern bool SetBitFilter(int pid, IntPtr filterData, IntPtr filterMask, byte filterLength, + [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallback lpFunc, ref IntPtr lpFilterNum); + + /// + /// Creates a filter to read data from the virtual IR Channel of the tuner. + /// + /// The remote control type. 0x00 works for all remote types. + /// The adress of the remote control. Set to -1 for all remotes. + /// The function to call when an IR Command was received. + /// A pointer to the filter to use with DelFilter. + /// Whether the listening to a remote command is working. + [DllImport("StreamReader.dll", EntryPoint = "SetRemoteControl")] + internal static extern bool SetRemoteControl(int irtype, short devAddr, + [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallback func, ref IntPtr lpFilterNum); + + /// + /// Stops reading data. + /// + /// The pointer to the filter which should be stopped. + /// Whether the filter could be stopped. + [DllImport("StreamReader.dll", EntryPoint = "DelFilter")] + public static extern bool DelFilter(IntPtr filterNum); + + /// + /// Reports the signal strength and quality. For some tuners you'll need GetSignalStrength instead. + /// + /// A float pointer which will be set to the signal strength. + /// A float pointer which will be set to the signal quality. + /// Whether these information could be reported. + [DllImport("StreamReader.dll", EntryPoint = "GetSignal")] + internal static extern bool GetSignal(ref int pStrength, ref int pQuality); + + /// + /// An alternative method to read the signal strength and quality. + /// + /// A float pointer which will be set to the signal strength. + /// A float pointer which will be set to the signal quality. + /// Whether these information could be reported. + [DllImport("StreamReader.dll", EntryPoint = "GetSignalStrength")] + internal static extern bool GetSignalStrength(ref float pStrength, ref float pQuality); + + /// + /// Gets information about the current signal. + /// + /// Pointer to a float which will be set to the Signal-to-Noise ratio. + /// Pointer to a float which will be set to the biterror rate. + /// Whether the data could be reported. + [DllImport("StreamReader.dll", EntryPoint = "GetSignalEx")] + internal static extern bool GetSignalEx(ref float pSNR, ref float pBER); + + /// + /// Gets information about the current signal. + /// + /// Pointer to a boolean which will be set to true if a signal is present. + /// Pointer to a boolean which will be set to true if a signal lock is available. + /// Pointer to an integer which will be set to the RF Level. + /// Pointer to a float which will be set to the Signal-to-Noise ratio. + /// Pointer to a float which will be set to the biterror rate. + /// Whether the data could be reported. + [DllImport("StreamReader.dll", EntryPoint = "GetSignalExEx")] + internal static extern bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, + ref float pSNR, ref float pBER); + + /// + /// Read signal statistics into an integer array. + /// + /// Pointer to an array which will receive the following elements: Locked?, Signal Quality, Signal Present?, Signal Strength + /// Whether the statistics could be reported. + [DllImport("StreamReader.dll", EntryPoint = "Statistic")] + internal static extern bool Statistic(int[] pStat); + + /// + /// Reads the MAC Adress of a tuner. + /// + /// A byte array to put the MAC into. + /// Whether the MAC was sucessfully read. + [DllImport("StreamReader.dll", EntryPoint = "GetMAC")] + public static extern bool GetMAC(byte[] pMac); + + /// + /// Gets the TunerMetadata capabilities. + /// + /// A bitmask containing all the tuner capabilities. + [DllImport("StreamReader.dll", EntryPoint = "GetCaps")] + public static extern Caps GetCaps(); + + /// + /// Gets the RF Strength of a satellite frequency. + /// + /// An approximate channel frequency in kHz + /// 0 for horizontal, 1 for vertical + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The pointer to put the result in. + /// Whether the probe was sucessful. + [DllImport("StreamReader.dll", EntryPoint = "RFScan")] + internal static extern bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel); + + [DllImport("StreamReader.dll", EntryPoint = "FFTInit")] + internal static extern bool FFTInit(); + + internal static bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, + byte nb_acc, IntPtr pTab, IntPtr pBegin, IntPtr pNum) + { + //I don't have a tuner that supports this... + throw new NotImplementedException(); + } + + /// + /// Given an approximate satellite frequency, tune to the nearest channel. + /// + /// An approximate channel frequency in kHz + /// The range where to look for in kHz + /// 0 for horizontal, 1 for vertical + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The minimum acceptable symbol rate. Depending on your tuner you either want 200 or 1000. + /// The exact tuning data + /// Whether a channel could be found + [DllImport("StreamReader.dll", EntryPoint = "BLScan")] + internal static extern bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, + int minsr, ref SearchResult pSearchResult); + + /// + /// Given an approximate satellite frequency, tune to the nearest channel. + /// + /// An approximate channel frequency in kHz + /// The range where to look for in kHz + /// 0 for horizontal, 1 for vertical + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The minimum acceptable symbol rate. Depending on your tuner you either want 200 or 1000. + /// The network type + /// The exact tuning data + /// Whether a channel could be found + [DllImport("StreamReader.dll", EntryPoint = "BLScanEx")] + internal static extern bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, + int minsr, STD_TYPE std, ref SearchResult pSearchResult); + + /// + /// Performs a blind scan on a satellite network. + /// This method blocks until the scan is complete. + /// + /// The frequency to the start the scan on in kHz (10700000 for Ku-Band) + /// The frequency to end the scan on in kHz (12750000 for Ku-Band) + /// 0 for horizontal, 1 for vertical. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The struct to put the last found channel in. + /// Number of channels found. + /// The function to call when a channel was found. + /// Whether the blind scan was performed sucessfully. + [DllImport("StreamReader.dll", EntryPoint = "BLScan2")] + public static extern bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, + IntPtr pSearchResult, ref int pTpNum, [MarshalAs(UnmanagedType.FunctionPtr)] BlScanCallback lpFunc); + + /// + /// Retrieve extended carrier info after lock for a sattelite signal. + /// + /// Struct to output the data to + /// Whether the carrier info could be read. + [DllImport("StreamReader.dll", EntryPoint = "SignalInfo")] + public static extern bool SignalInfo(ref SearchResult pSearchResult); + + /// + /// Read constellation data for a satellite signal. + /// + /// Type of constellation data to read. You probably want 0. + /// Output Array + /// Number of Constellation points to read. Must be (pIQ.Length / 2) + /// Whether the constellation data was read + [DllImport("StreamReader.dll", EntryPoint = "IQScan")] + internal static extern bool IQScan(uint input, sbyte[] pIQ, uint num); + + /// + /// Read constellation data for a non-satellite signal. + /// + /// Type of constellation data to read. You probably want 0. + /// Output Array + /// Number of Constellation points to read. Must be (pIQ.Length / 2) + /// Whether the constellation data was read + [DllImport("StreamReader.dll", EntryPoint = "IQScan2")] + internal static extern bool IQScan2(uint point, short[] pIQ, uint num); + + [DllImport("StreamReader.dll", EntryPoint = "IQScan2Range")] + internal static extern bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint); + + [DllImport("StreamReader.dll", EntryPoint = "CIRScanRange")] + internal static extern bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, + ref int pMinDelayNs, ref int pMaxDelayNs); + + [DllImport("StreamReader.dll", EntryPoint = "CIRScan")] + internal static extern bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays); + + [DllImport("StreamReader.dll", EntryPoint = "CarRange")] + internal static extern bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum); + + [DllImport("StreamReader.dll", EntryPoint = "CarEsNo")] + internal static extern bool CarEsNo(ref ushort cnum, ref double pEsNo); + + [DllImport("StreamReader.dll", EntryPoint = "MISSel")] + public static extern bool MISSel(bool bEnable, byte misFilter, byte misFilterMask); + + /// + /// Sets the Physical Layer Scrambling credentials. + /// + /// The PLS mode. + /// The PLS key. + /// Whether the tuner complied. + [DllImport("StreamReader.dll", EntryPoint = "PLSSel")] + internal static extern bool PLSSel(byte plsMode, uint code); + + /// + /// Gets the current Physical Layer Scrambling key. + /// + /// The pointer to write the PLS Mode in. + /// The pointer to write the PLS Key in. + /// Whether the tuner sent valid PLS credentials. + [DllImport("StreamReader.dll", EntryPoint = "PLSGet")] + internal static extern bool PLSGet(byte pMode, ref uint pCode); + + [DllImport("StreamReader.dll", EntryPoint = "ModSel")] + internal static extern bool ModSel(ref S2Mode ps2Modes, uint num); + + [DllImport("StreamReader.dll", EntryPoint = "ModInv")] + internal static extern bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum); + + /// + /// Tune to DVB-T2 / DVB-C Channel + /// + /// Frequency in kHz + /// Channel Bandwidth in kHz (usually 8000) + /// Whether the tuner complied with the request. + [DllImport("StreamReader.dll", EntryPoint = "SetChannel2")] + public static extern bool SetChannel2(uint freq, uint bandwidth); + + /// + /// Tune to DVB-T2 / DVB-C channel. + /// + /// Frequency in kH + /// Channel Bandwidth in kHz (usually 8000) + /// Type of network + /// ??? (Setting this to 0 works.) + /// Whether the tuner complied with the request. + [DllImport("StreamReader.dll", EntryPoint = "SetChannel2Ex")] + internal static extern bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream); + + /// + /// Tune to DVB-T2 / DVB-C Channel. Beware, will freeze for a minute when SymbolRate is wrong. + /// + /// Frequency in kHz + /// Channel Bandwidth in kHz (usually 8000) + /// Sybol Rate in kiloSymbols (6900000 for Vodafone) + /// Type of network + /// ??? Setting this to 0 works. + /// Whether the tuner complied with the request. + [DllImport("StreamReader.dll", EntryPoint = "SetChannel2ExEx")] + internal static extern bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, + int stream); + + /// + /// Retrieve extended carrier info after lock for a non-sattelite signal. + /// + /// Outputs the Signal information + /// Whether the signal information was read sucessfully + [DllImport("StreamReader.dll", EntryPoint = "SignalInfo2")] + public static extern bool SignalInfo2(ref SearchResult2 si2); + + + /// + /// Get RF Level from FM / UHF / VHF Frequency + /// + /// Frequency in kHz + /// TunerMetadata standard + /// Output pointer + /// whether the operation completed sucessfully + [DllImport("StreamReader.dll", EntryPoint = "RFScan2")] + internal static extern bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel); + + /// + /// Performs a blind scan on non-satellite networks. + /// This method blocks until the scan is complete! + /// + /// Starting Frequency in kHz + /// End Frequency in kHz + /// Frequency increment in kHz (3000 is usually a safe choice here) + /// System bandwidth (usually 8000) + /// Type of Network + /// Outputs the final channel. Not really necessary. + /// Outputs the number of channels. + /// Function to call when a channel was found. + /// Whether the scan was performed sucessfully + [DllImport("StreamReader.dll", EntryPoint = "AirScan")] + public static extern bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, + IntPtr pSearchResult, ref int pTpNum, [MarshalAs(UnmanagedType.FunctionPtr)] AirScanCallback lpFunc); + + /// + /// Reads from the EEPROM of the tuner. + /// + /// Buffer to put the read data in. + /// Offset from where to start reading from the EEPROM + /// How many bytes to read + /// Wheter the reading operation was sucessful. + [DllImport("StreamReader.dll", EntryPoint = "GetEEPROM")] + internal static extern bool GetEEPROM(byte[] buffer, int offset, int len); + + /// + /// Supposed to write data to the device's EEPROM. + /// This method is not implemented on purpose because I neither have the balls to try + /// this, nor the money to replace my tuner in case I mess up. + /// This will throw a NotSupportedException when calling this. + /// + /// Buffer of data to write to the EEPROM. + /// Offset in the EEPROM to start writing. + /// How many bytes to write to the EEPROM + /// Throws NotSupportedException + internal static bool SetEEPROM(byte[] buffer, int offset, int len) + { + //I'm not willing to try this out. TBS Tuners are way too expensive for me than to + //mess with their firmware. + throw new NotSupportedException(); + } + } + } +} diff --git a/IoPlugins/skyscraper5.IO.StreamReader/StreamReaderTunerFactory.cs b/IoPlugins/skyscraper5.IO.StreamReader/StreamReaderTunerFactory.cs new file mode 100644 index 0000000..c058b60 --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader/StreamReaderTunerFactory.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.TunerInterface; + +namespace skyscraper5.IO.StreamReader +{ + [TunerFactoryId(1,"crazycat69's StreamReader.dll")] + internal class StreamReaderTunerFactory : ITunerFactory + { + public IStreamReader CreateStreamReader() + { + return LocalStreamReader.GetInstance(); + } + } +} diff --git a/IoPlugins/skyscraper5.IO.StreamReader/skyscraper5.IO.StreamReader.csproj b/IoPlugins/skyscraper5.IO.StreamReader/skyscraper5.IO.StreamReader.csproj new file mode 100644 index 0000000..2f0a5dd --- /dev/null +++ b/IoPlugins/skyscraper5.IO.StreamReader/skyscraper5.IO.StreamReader.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + x86 + + + + + + + diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsDecoder.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsDecoder.cs new file mode 100644 index 0000000..d1aae34 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsDecoder.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using AprsSharp.Parsers.Aprs; +using skyscraper5.Aprs.AprsSharp; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Ietf.Rfc768; +using skyscraper5.Ietf.Rfc971; + +namespace skyscraper5.Aprs +{ + class AprsDecoder + { + public AprsDecoder(IAprsMessageReciever receiver) + { + this.receiver = receiver; + } + + private IAprsMessageReciever receiver; + + public void OnIpv4PacketArrival(InternetHeader internetHeader, byte[] ipv4Packet) + { + UserDatagram userDatagram = new UserDatagram(ipv4Packet); + MemoryStream ms = new MemoryStream(userDatagram.Payload, false); + StreamReader sr = new StreamReader(ms, Encoding.ASCII); + while (!sr.EndOfStream) + { + string? readLine = sr.ReadLine(); + Packet packet = null; + try + { + packet = new Packet(readLine); + } + catch (ArgumentException e) + { + Debug.WriteLine("APRS Message decode failed: " + readLine); + } + + if (packet != null) + ForwardToReceiver(packet); + } + sr.Close(); + ms.Close(); + } + + public void OnIpDatagram(int sourcePid, byte[] payload) + { + throw new NotImplementedException(); + } + + private void ForwardToReceiver(Packet packet) + { + InfoField packetInfoField = packet.InfoField; + + string name = packetInfoField.GetType().Name; + switch (name) + { + case nameof(PositionInfo): + PositionInfo pi = (PositionInfo)packetInfoField; + receiver.OnAprsPosition(packet.Sender, pi.Position.Coordinates, pi.Comment); + break; + case nameof(UnimplementedInfoField): + break; + case nameof(WeatherInfo): + WeatherInfo wi = (WeatherInfo)packetInfoField; + receiver.OnAprsWeatherReport(packet.Sender, wi); + break; + case nameof(MessageField): + MessageField message = (MessageField)packetInfoField; + receiver.OnAprsChatMessage(packet.Sender, packet.Path[0], message.RawMessage); + break; + case nameof(StationCapabilities): + StationCapabilities stationCapabilities = (StationCapabilities)packetInfoField; + if (!stationCapabilities.IsIGate) + return; + receiver.OnAprsStationCapabilities(packet.Sender, stationCapabilities); + break; + case nameof(WeatherReportInfoField): + WeatherReportInfoField weatherReportInfoField = (WeatherReportInfoField)packetInfoField; + receiver.OnAprsWeatherReport(packet.Sender, weatherReportInfoField.Wrapped); + break; + case nameof(StatusInfo): + StatusInfo statusInfo = (StatusInfo)packetInfoField; + receiver.OnAprsPosition(packet.Sender, statusInfo.Position.Coordinates, statusInfo.Comment); + break; + case nameof(RawGpsInfoField): + RawGpsInfoField rawGpsInfoField = (RawGpsInfoField)packetInfoField; + if (rawGpsInfoField.Parsed != null) + receiver.OnAprsGpsSentence(packet.Sender, rawGpsInfoField.Parsed); + break; + default: + throw new NotImplementedException(name); + } + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/AprsSharp.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/AprsSharp.cs new file mode 100644 index 0000000..0aef6ab --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/AprsSharp.cs @@ -0,0 +1,2517 @@ +/* + * This file contains a modified version of the APRS Parser from APRSsharp: + * https://github.com/CBielstein/APRSsharp/tree/main/src/AprsParser + * The source of APRS Parser was retrieved and modified for use in skyscraper5 on 20.06.2022 + * + * The following changes were made to the APRS Parser + * - (back-)ported from .NET 6 to .NET 5 + * - Put everything into a single file. + * - Replaced Geocoordinate Portable with a custom implementation + * - Prevent Exception Throwing on some APRS Data Types. + * - Added support to raw APRS messages. (Data Type ":") + * + * Since APRS Parser is licensed under MIT, and skyscraper5 is as well, there should be no + * licensing issues. + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Aprs.AprsSharp; + +namespace AprsSharp.Parsers.Aprs +{ + /// + /// Specifies whether a function is referring to latitude or longitude during Maidenhead gridsquare encode/decode. + /// + public enum CoordinateSystem + { + /// + /// Latitute. + /// + Latitude, + + /// + /// Longitude. + /// + Longitude, + } + + /// + /// Used to specify format during decode of raw GPS data packets. + /// + public enum NmeaType + { + /// + /// Not yet decoded + /// + NotDecoded, + + /// + /// GGA: Global Position System Fix Data + /// + GGA, + + /// + /// GLL: Geographic Position, Latitude/Longitude Data + /// + GLL, + + /// + /// RMC: Recommended Minimum Specific GPS/Transit Data + /// + RMC, + + /// + /// VTG: Velocity and Track Data + /// + VTG, + + /// + /// WPT: Way Point Location (also WPL) + /// + WPT, + + /// + /// Not supported/known type + /// + Unknown, + } + + /// + /// The APRS packet type. + /// + public enum PacketType + { + /// + /// Current Mic-E Data (Rev 0 beta) + /// + CurrentMicEData, + + /// + /// Old Mic-E Data (Rev 0 beta) + /// + OldMicEData, + + /// + /// Position without timestamp (no APRS messaging), or Ultimeter 2000 WX Station + /// + PositionWithoutTimestampNoMessaging, + + /// + /// Peet Bros U-II Weather Station + /// + PeetBrosUIIWeatherStation, + + /// + /// Raw GPS data or Ultimeter 2000 + /// + RawGPSData, + + /// + /// Agrelo DFJr / MicroFinder + /// + AgreloDFJrMicroFinder, + + /// + /// [Reserved - Map Feature] + /// + MapFeature, + + /// + /// Old Mic-E Data (but Current data for TM-D700) + /// + OldMicEDataCurrentTMD700, + + /// + /// Item + /// + Item, + + /// + /// [Reserved - shelter data with time] + /// + ShelterDataWithTime, + + /// + /// Invalid data or test data + /// + InvalidOrTestData, + + /// + /// [Reserved - Space Weather] + /// + SpaceWeather, + + /// + /// Unused + /// + Unused, + + /// + /// Position with timestamp (no APRS messaging) + /// + PositionWithTimestampNoMessaging, + + /// + /// Message + /// + Message, + + /// + /// Object + /// + [field: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720", Justification = "APRS spec uses the word 'object', so it is appropriate here")] + Object, + + /// + /// Station Capabilities + /// + StationCapabilities, + + /// + /// Position without timestamp (with APRS messaging) + /// + PositionWithoutTimestampWithMessaging, + + /// + /// Status + /// + Status, + + /// + /// Query + /// + Query, + + /// + /// [Do not use] + /// + DoNotUse, + + /// + /// Positionwith timestamp (with APRS messaging) + /// + PositionWithTimestampWithMessaging, + + /// + /// Telemetry data + /// + TelemetryData, + + /// + /// Maidenhead grid locator beacon (obsolete) + /// + MaidenheadGridLocatorBeacon, + + /// + /// Weather Report (without position) + /// + WeatherReport, + + /// + /// Current Mic-E Data (not used in TM-D700) + /// + CurrentMicEDataNotTMD700, + + /// + /// User-Defined APRS packet format + /// + UserDefinedAPRSPacketFormat, + + /// + /// [Do not use - TNC stream switch character] + /// + DoNotUseTNSStreamSwitchCharacter, + + /// + /// Third-party traffic + /// + ThirdPartyTraffic, + + /// + /// Not a recognized symbol + /// + Unknown, + } + + /// + /// Represents the type of APRS timestamp. + /// + public enum TimestampType + { + /// + /// Days/Hours/Minutes zulu + /// + DHMz, + + /// + /// Days/Hours/Minutes local + /// + DHMl, + + /// + /// Hours/Minutes/Seconds (always zulu) + /// + HMS, + + /// + /// Hours/Minutes/Seconds (always zulu) + /// + MDHM, + + /// + /// Not a decoded timestamp + /// + NotDecoded, + } +} + +namespace AprsSharp.Parsers.Aprs.Extensions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Extension methods for conversion of enum types. + /// + public static class EnumConversionExtensions + { + /// + /// Maps a type-identifying character to a packet . + /// + private static readonly Dictionary DataTypeMap = new Dictionary() + { + { (char)0x1c, PacketType.CurrentMicEData }, + { (char)0x1d, PacketType.OldMicEData }, + { '!', PacketType.PositionWithoutTimestampNoMessaging }, + { '\"', PacketType.Unused }, + { '#', PacketType.PeetBrosUIIWeatherStation }, + { '$', PacketType.RawGPSData }, + { '%', PacketType.AgreloDFJrMicroFinder }, + { '&', PacketType.MapFeature }, + { '\'', PacketType.OldMicEDataCurrentTMD700 }, + { '(', PacketType.Unused }, + { ')', PacketType.Item }, + { '*', PacketType.PeetBrosUIIWeatherStation }, + { '+', PacketType.ShelterDataWithTime }, + { ',', PacketType.InvalidOrTestData }, + { '-', PacketType.Unused }, + { '.', PacketType.SpaceWeather }, + { '/', PacketType.PositionWithTimestampNoMessaging }, + { ':', PacketType.Message }, + { ';', PacketType.Object }, + { '<', PacketType.StationCapabilities }, + { '=', PacketType.PositionWithoutTimestampWithMessaging }, + { '>', PacketType.Status }, + { '?', PacketType.Query }, + { '@', PacketType.PositionWithTimestampWithMessaging }, + { 'T', PacketType.TelemetryData }, + { '[', PacketType.MaidenheadGridLocatorBeacon }, + { '\\', PacketType.Unused }, + { ']', PacketType.Unused }, + { '^', PacketType.Unused }, + { '_', PacketType.WeatherReport }, + { '`', PacketType.CurrentMicEDataNotTMD700 }, + { '{', PacketType.UserDefinedAPRSPacketFormat }, + { '}', PacketType.ThirdPartyTraffic }, + { 'A', PacketType.DoNotUse }, + { 'S', PacketType.DoNotUse }, + { 'U', PacketType.DoNotUse }, + { 'Z', PacketType.DoNotUse }, + { '0', PacketType.DoNotUse }, + { '9', PacketType.DoNotUse }, + }; + + /// + /// Converts a char to its corresponding . + /// + /// A char representation of . + /// of the info field, if not a valid mapping. + public static PacketType ToPacketType(this char typeIdentifier) => DataTypeMap.GetValueOrDefault(char.ToUpperInvariant(typeIdentifier), PacketType.Unknown); + + /// + /// Converts to the char representation of a given . + /// + /// to represent. + /// A char representing type. + public static char ToChar(this PacketType type) => DataTypeMap.First(pair => pair.Value == type).Key; + + /// + /// Determines the of a raw GPS packet determined by a string. + /// If the string is length 3, the three letters are taken as is. + /// If the string is length 6 or longer, the indentifier is expected in the place dictated by the NMEA formats. + /// + /// String of length 3 identifying a raw GPS type or an entire NMEA string. + /// The raw GPS represented by the argument. + public static NmeaType ToNmeaType(this string nmeaInput) + { + if (nmeaInput == null) + { + throw new ArgumentNullException(nameof(nmeaInput)); + } + else if (nmeaInput.Length != 3 && nmeaInput.Length < 6) + { + throw new ArgumentException("rawGpsIdentifier should be exactly 3 characters or at least 6. Given: " + nmeaInput.Length, nameof(nmeaInput)); + } + else if (nmeaInput.Length >= 6) + { + throw new NotImplementedException(); + } + + return nmeaInput.ToUpperInvariant() switch + { + "GGA" => NmeaType.GGA, + "GLL" => NmeaType.GLL, + "RMC" => NmeaType.RMC, + "VTG" => NmeaType.VTG, + "WPT" => NmeaType.WPT, + "WPL" => NmeaType.WPT, + _ => NmeaType.Unknown, + }; + } + } +} + +namespace AprsSharp.Parsers.Aprs.Extensions +{ + using System; + using System.Text.RegularExpressions; + + /// + /// Extension methods for . + /// + public static class MatchExtensions + { + /// + /// Asserts success of a Regex.Match call. + /// If the Match object is not successful, throws an ArgumentException. + /// + /// Match object to check for success. + /// Type that was being checked against the regex. + /// The name of the param that did not match the regex. + public static void AssertSuccess(this Match match, string type, string paramName) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + else if (!match.Success) + { + throw new ArgumentException($"{type} did not match regex", paramName); + } + } + + /// + /// Asserts success of a Regex.Match call. + /// If the Match object is not successful, throws an ArgumentException. + /// + /// Match object to check for success. + /// that was being checked against the regex. + /// The name of the param that did not match the regex. + public static void AssertSuccess(this Match match, PacketType type, string paramName) + { + match.AssertSuccess(type.ToString(), paramName); + } + } +} + +namespace AprsSharp.Parsers.Aprs.Extensions +{ + using System; + using System.Globalization; + + /// + /// Extension methods for handling weather packets. + /// + public static class WeatherExtensions + { + /// + /// Determines if a 's symbol is a weather station. + /// + /// A to check. + /// True if the 's symbol is a weather station, else false. + public static bool IsWeatherSymbol(this Position position) + { + if (position == null) + { + throw new ArgumentNullException(nameof(position)); + } + + return (position.SymbolTableIdentifier == '/' || position.SymbolTableIdentifier == '\\') && + position.SymbolCode == '_'; + } + + /// + /// Returns a string encoding of a single measurement for a complete weather packet. + /// + /// The measurement to convert to string. + /// The number of characters to use in this representation. + /// The integer as a string or all dots if null. + public static string ToWeatherEncoding(this int? measurement, int encodingLength = 3) + { + if (measurement == null) + { + return new string('.', encodingLength); + } + + string encoded = Math.Abs(measurement.Value).ToString(CultureInfo.InvariantCulture) + .PadLeft(encodingLength, '0'); + + // If negative, ensure the negative sign is at the front of the padding zeros instead of in the middle + // that's why we take the absolute value above, so that we can put the negative sign all the way at the front. + if (measurement < 0) + { + encoded = $"-{encoded.Substring(1)}"; + } + + return encoded; + } + } +} + +namespace AprsSharp.Parsers.Aprs + { + using System; + using AprsSharp.Parsers.Aprs.Extensions; + + /// + /// A representation of an info field on an APRS packet. + /// + public abstract class InfoField + { + /// + /// Initializes a new instance of the class. + /// + public InfoField() + { + Type = PacketType.Unknown; + } + + /// + /// Initializes a new instance of the class from an encoded string. + /// + /// An encoded InfoField from which to pull the Type. + public InfoField(string encodedInfoField) + { + if (encodedInfoField == null) + { + throw new ArgumentNullException(nameof(encodedInfoField)); + } + + Type = GetPacketType(encodedInfoField); + } + + /// + /// Initializes a new instance of the class from a . + /// + /// The of this . + public InfoField(PacketType type) + { + Type = type; + } + + /// + /// Initializes a new instance of the class from another . + /// This is the copy constructor. + /// + /// An to copy. + public InfoField(InfoField infoField) + { + if (infoField == null) + { + throw new ArgumentNullException(nameof(infoField)); + } + + Type = infoField.Type; + } + + /// + /// Gets or sets the of this packet. + /// + public PacketType Type { get; protected set; } + + /// + /// Instantiates a type of from the given string. + /// + /// String representation of the APRS info field. + /// A class extending . + public static InfoField FromString(string encodedInfoField) + { + PacketType type = GetPacketType(encodedInfoField); + + switch (type) + { + case PacketType.PositionWithoutTimestampNoMessaging: + case PacketType.PositionWithoutTimestampWithMessaging: + case PacketType.PositionWithTimestampNoMessaging: + case PacketType.PositionWithTimestampWithMessaging: + PositionInfo positionInfo = new PositionInfo(encodedInfoField); + return positionInfo.Position.IsWeatherSymbol() ? new WeatherInfo(positionInfo) : positionInfo; + + case PacketType.Status: + return new StatusInfo(encodedInfoField); + + case PacketType.MaidenheadGridLocatorBeacon: + return new MaidenheadBeaconInfo(encodedInfoField); + + case PacketType.CurrentMicEDataNotTMD700: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.Unknown: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.Object: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.Message: + return new MessageField(encodedInfoField); + + case PacketType.TelemetryData: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.StationCapabilities: + return new StationCapabilities(encodedInfoField); + + case PacketType.WeatherReport: + return new WeatherReportInfoField(encodedInfoField); + + case PacketType.OldMicEDataCurrentTMD700: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.DoNotUse: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.Item: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.AgreloDFJrMicroFinder: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.RawGPSData: + return new RawGpsInfoField(encodedInfoField); + + case PacketType.SpaceWeather: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.PeetBrosUIIWeatherStation: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.UserDefinedAPRSPacketFormat: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.Unused: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.Query: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.InvalidOrTestData: + return new UnimplementedInfoField(encodedInfoField); + + case PacketType.ThirdPartyTraffic: + return new UnimplementedInfoField(encodedInfoField); + default: + throw new NotImplementedException($"FromString not implemented for info field type {type}"); + } + } + + /// + /// Encodes an APRS info field to a string. + /// + /// String representation of the packet. + public abstract string Encode(); + + /// + /// Gets the of a string representation of an APRS info field. + /// + /// A string-encoded APRS info field. + /// of the info field. + private static PacketType GetPacketType(string encodedInfoField) + { + if (encodedInfoField == null) + { + throw new ArgumentNullException(nameof(encodedInfoField)); + } + + // TODO Issue #67: This isn't always true. + // '!' can come up to the 40th position. + char dataTypeIdentifier = char.ToUpperInvariant(encodedInfoField[0]); + + return dataTypeIdentifier.ToPacketType(); + } + } + } + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Text; + using System.Text.RegularExpressions; + using AprsSharp.Parsers.Aprs.Extensions; + + /// + /// Represents an info field for packet type . + /// + public class MaidenheadBeaconInfo : InfoField + { + /// + /// Initializes a new instance of the class. + /// NOTE: This packet type is considere obsolete and is included for decoding legacy devices. Use a status report instead. + /// + /// A string encoding of a . + public MaidenheadBeaconInfo(string encodedInfoField) + : base(encodedInfoField) + { + if (string.IsNullOrWhiteSpace(encodedInfoField)) + { + throw new ArgumentNullException(nameof(encodedInfoField)); + } + + if (Type != PacketType.MaidenheadGridLocatorBeacon) + { + throw new ArgumentException($"Packet encoding not of type {nameof(PacketType.MaidenheadGridLocatorBeacon)}. Type was {Type}", nameof(encodedInfoField)); + } + + Match match = Regex.Match(encodedInfoField, RegexStrings.MaidenheadGridLocatorBeacon); + match.AssertSuccess(PacketType.Status, nameof(encodedInfoField)); + + Position = new Position(); + Position.DecodeMaidenhead(match.Groups[1].Value); + + if (match.Groups[2].Success) + { + Comment = match.Groups[2].Value; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A position, which will be encoded as a maidenhead gridsquare locator. + /// An optional comment. + public MaidenheadBeaconInfo(Position position, string? comment) + : base(PacketType.MaidenheadGridLocatorBeacon) + { + Comment = comment; + Position = position ?? throw new ArgumentNullException(nameof(position)); + } + + /// + /// Gets the packet comment. + /// + public string? Comment { get; } + + /// + /// Gets the position from which the message was sent. + /// + public Position Position { get; } + + /// + public override string Encode() + { + StringBuilder encoded = new StringBuilder(); + + encoded.Append($"[{Position.EncodeGridsquare(6, false)}]"); + + if (!string.IsNullOrEmpty(Comment)) + { + encoded.Append(Comment); + } + + return encoded.ToString(); + } + } +} + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Text; + using System.Text.RegularExpressions; + using AprsSharp.Parsers.Aprs.Extensions; + + /// + /// Represents an info field for the following types of packet: + /// * + /// * + /// * + /// * . + /// + public class PositionInfo : InfoField + { + /// + /// Initializes a new instance of the class. + /// + /// A string encoding of a . + public PositionInfo(string encodedInfoField) + : base(encodedInfoField) + { + if (string.IsNullOrWhiteSpace(encodedInfoField)) + { + throw new ArgumentNullException(nameof(encodedInfoField)); + } + + if (Type == PacketType.PositionWithoutTimestampNoMessaging || Type == PacketType.PositionWithoutTimestampWithMessaging) + { + HasMessaging = Type == PacketType.PositionWithoutTimestampWithMessaging; + + Match match = Regex.Match(encodedInfoField, RegexStrings.PositionWithoutTimestamp); + match.AssertSuccess(PacketType.PositionWithoutTimestampNoMessaging, nameof(encodedInfoField)); + + Position = new Position(match.Groups[1].Value); + + if (match.Groups[6].Success) + { + Comment = match.Groups[6].Value; + } + } + else if (Type == PacketType.PositionWithTimestampNoMessaging || Type == PacketType.PositionWithTimestampWithMessaging) + { + HasMessaging = Type == PacketType.PositionWithTimestampWithMessaging; + + Match match = Regex.Match(encodedInfoField, RegexStrings.PositionWithTimestamp); + match.AssertSuccess( + HasMessaging ? + PacketType.PositionWithTimestampWithMessaging : + PacketType.PositionWithTimestampNoMessaging, + nameof(encodedInfoField)); + + Timestamp = new Timestamp(match.Groups[2].Value); + + Position = new Position(match.Groups[3].Value); + + if (match.Groups[8].Success) + { + Comment = match.Groups[8].Value; + } + } + else + { + throw new ArgumentException($"Packet encoding not one of the position types. Type was {Type}", nameof(encodedInfoField)); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// for this packet. + /// True if the sender supports messaging. + /// Optional for this packet. + /// Optional comment for this packet. + public PositionInfo(Position position, bool hasMessaging, Timestamp? timestamp, string? comment) + { + Position = position; + HasMessaging = hasMessaging; + Timestamp = timestamp; + Comment = comment; + + if (Timestamp == null) + { + Type = HasMessaging ? PacketType.PositionWithoutTimestampWithMessaging + : PacketType.PositionWithoutTimestampNoMessaging; + } + else + { + Type = HasMessaging ? PacketType.PositionWithTimestampWithMessaging + : PacketType.PositionWithTimestampNoMessaging; + } + } + + /// + /// Initializes a new instance of the class. + /// This is the copy constructor. + /// + /// A to copy. + public PositionInfo(PositionInfo positionInfo) + : base(positionInfo) + { + if (positionInfo == null) + { + throw new ArgumentNullException(nameof(positionInfo)); + } + + HasMessaging = positionInfo.HasMessaging; + Comment = positionInfo.Comment; + Timestamp = positionInfo.Timestamp; + Position = positionInfo.Position; + } + + /// + /// Gets a value indicating whether the sender of the packet supports messaging. + /// + public bool HasMessaging { get; } + + /// + /// Gets or sets the packet comment. + /// + public string? Comment { get; set; } + + /// + /// Gets the time at which the message was sent. + /// + public Timestamp? Timestamp { get; } + + /// + /// Gets the position from which the message was sent. + /// + public Position Position { get; } + + /// + public override string Encode() => Encode(TimestampType.DHMz); + + /// + /// Encodes an APRS info field to a string. + /// + /// The to use for timestamp encoding. + /// String representation of the info field. + public string Encode(TimestampType timeType = TimestampType.DHMz) + { + if (Position == null) + { + throw new ArgumentException($"Position cannot be null when encoding with type ${Type}"); + } + + StringBuilder encoded = new StringBuilder(); + + encoded.Append(Type.ToChar()); + + if (Type == PacketType.PositionWithTimestampWithMessaging || + Type == PacketType.PositionWithTimestampNoMessaging) + { + if (Timestamp == null) + { + throw new ArgumentException($"Timestamp cannot be null when encoding with type ${Type}"); + } + + encoded.Append(Timestamp.Encode(timeType)); + } + + encoded.Append(Position.Encode()); + encoded.Append(Comment); + + return encoded.ToString(); + } + } +} + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Text; + using System.Text.RegularExpressions; + using AprsSharp.Parsers.Aprs.Extensions; + + /// + /// Represents an info field for packet type . + /// + public class StatusInfo : InfoField + { + /// + /// The list of characters which may not be used in a comment on this type. + /// + private static readonly char[] CommentDisallowedChars = { '|', '~' }; + + /// + /// Initializes a new instance of the class. + /// + /// A string encoding of a . + public StatusInfo(string encodedInfoField) + : base(encodedInfoField) + { + if (string.IsNullOrWhiteSpace(encodedInfoField)) + { + throw new ArgumentNullException(nameof(encodedInfoField)); + } + + if (Type != PacketType.Status) + { + throw new ArgumentException($"Packet encoding not of type {nameof(PacketType.Status)}. Type was {Type}", nameof(encodedInfoField)); + } + + Match match = Regex.Match(encodedInfoField, RegexStrings.StatusWithMaidenheadAndComment); + if (match.Success) + { + match.AssertSuccess(PacketType.Status, nameof(encodedInfoField)); + + Position = new Position(); + Position.DecodeMaidenhead(match.Groups[1].Value); + + if (match.Groups[4].Success) + { + Comment = match.Groups[4].Value; + } + } + else + { + // TODO Issue #88 + throw new ArgumentException("Status report without maidenhead not yet implemented.Tracked by issue #88."); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A position, which will be encoded as a maidenhead gridsquare locator. + /// An optional comment. + public StatusInfo(Position position, string? comment) + : this(null, position, comment) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// An optional timestamp. + /// An optional comment. + public StatusInfo(Timestamp timestamp, string? comment) + : this(timestamp, null, comment) + { + } + + /// + /// Initializes a new instance of the class. + /// Consolidates logic from the other constructors. + /// + /// An optional timestamp. + /// A position, which will be encoded as a maidenhead gridsquare locator. + /// An optional comment. + private StatusInfo(Timestamp? timestamp, Position? position, string? comment) + : base(PacketType.Status) + { + if (position != null && timestamp != null) + { + throw new ArgumentException($"{nameof(timestamp)} may not be specified if a position is given."); + } + + if (comment != null) + { + // Validate lengths + if (position != null && comment.Length > 53) + { + // Delimiting space + 53 characters = 54. + // We will add the space during encode, so only 53 here. + throw new ArgumentException( + $"With a position specified, comment may be at most 53 characters. Given comment had length {comment.Length}", + nameof(comment)); + } + else if (timestamp != null && comment.Length > 55) + { + throw new ArgumentException( + $"With a timestamp, comment may be at most 55 characters. Given comment had length {comment.Length}", + nameof(comment)); + } + else if (comment.Length > 62) + { + throw new ArgumentException( + $"Without timestamp or or position, comment may be at most 62 characters. Given comment had length {comment.Length}", + nameof(comment)); + } + + // TODO Issue #90: Share this logic across all packet types with a comment. + // Validate no disallowed characters were used + if (comment.IndexOfAny(CommentDisallowedChars) != -1) + { + throw new ArgumentException($"Comment may not include `|` or `~` but was given: {comment}", nameof(comment)); + } + } + + Timestamp = timestamp; + Position = position; + Comment = comment; + } + + /// + /// Gets the packet comment. + /// + public string? Comment { get; } + + /// + /// Gets the time at which the message was sent. + /// + public Timestamp? Timestamp { get; } + + /// + /// Gets the position from which the message was sent. + /// + public Position? Position { get; } + + /// + public override string Encode() + { + StringBuilder encoded = new StringBuilder(); + + if (Position != null) + { + if (Timestamp != null) + { + throw new ArgumentException($"{nameof(Timestamp)} may not be specified if a position is given."); + } + + encoded.Append(Type.ToChar()); + encoded.Append(Position.EncodeGridsquare(6, true)); + + if (!string.IsNullOrEmpty(Comment)) + { + encoded.Append($" {Comment}"); + } + } + else + { + // TODO Issue #88 + throw new NotImplementedException("Status report without maidenhead not yet implemented.Tracked by issue #88."); + } + + return encoded.ToString(); + } + } +} + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + using AprsSharp.Parsers.Aprs.Extensions; + + /// + /// Represents an info field for position packets carrying weather information. + /// + public class WeatherInfo : PositionInfo + { + /// + /// Initializes a new instance of the class from an encoded . + /// + /// A string encoding of a with weather information. + public WeatherInfo(string encodedInfoField) + : this(new PositionInfo(encodedInfoField)) + { + } + + /// + /// Initializes a new instance of the class from a . + /// + /// A from which to decode a . + public WeatherInfo(PositionInfo positionInfo) + : base(positionInfo) + { + if (positionInfo == null) + { + throw new ArgumentNullException(nameof(positionInfo)); + } + + if (!positionInfo.Position.IsWeatherSymbol()) + { + throw new ArgumentException( + $@"Encoded packet must have weather symbol (`/_` or `\_`). Given: `{positionInfo.Position.SymbolTableIdentifier}{positionInfo.Position.SymbolCode}", + nameof(positionInfo)); + } + + WindDirection = GetWeatherMeasurement('^'); + WindSpeed = GetWeatherMeasurement('/'); + WindGust = GetWeatherMeasurement('g'); + Temperature = GetWeatherMeasurement('t'); + Rainfall1Hour = GetWeatherMeasurement('r'); + Rainfall24Hour = GetWeatherMeasurement('p'); + RainfallSinceMidnight = GetWeatherMeasurement('P'); + Humidity = GetWeatherMeasurement('h', 2); + BarometricPressure = GetWeatherMeasurement('b', 5); + Luminosity = GetWeatherMeasurement('L') ?? GetWeatherMeasurement('l') + 1000; + RainRaw = GetWeatherMeasurement('#'); + Snow = GetWeatherMeasurement('s'); + } + + /// + /// Initializes a new instance of the class. + /// + /// for this packet. + /// True if the sender supports messaging. + /// Optional for this packet. + /// Optional comment for this packet, will be appended after encoded weather information. + /// Wind direction in degrees. + /// Wind speed 1-minute sustained in miles per hour. + /// 5-minute max wind gust in miles per hour. + /// Temperature in degrees Fahrenheit. + /// 1-hour rainfall in 100ths of an inch. + /// 24-hour rainfall in 100ths of an inch. + /// Rainfall since midnight in 100ths of an inch. + /// Humidity in percentage. + /// Barometric pressure in 10ths of millibars/10ths of hPascal. + /// Luminosity in watts per square meter. + /// Raw rain. + /// Snowfall in inches in the last 24 hours. + public WeatherInfo( + Position position, + bool hasMessaging, + Timestamp? timestamp, + string? comment, + int? windDirection, + int? windSpeed, + int? windGust, + int? temperature, + int? rainfall1Hour, + int? rainfall24Hour, + int? rainfallSinceMidnight, + int? humidity, + int? barometricPressure, + int? luminosity, + int? rainRaw, + int? snow) + : base(position, hasMessaging, timestamp, comment) + { + WindDirection = windDirection; + WindSpeed = windSpeed; + WindGust = windGust; + Temperature = temperature; + Rainfall1Hour = rainfall1Hour; + Rainfall24Hour = rainfall24Hour; + RainfallSinceMidnight = rainfallSinceMidnight; + Humidity = humidity; + BarometricPressure = barometricPressure; + Luminosity = luminosity; + RainRaw = rainRaw; + Snow = snow; + + Comment = $"{EncodeWeatherInfo()}{comment}"; + } + + /// + /// Gets wind direction as degrees. + /// + public int? WindDirection { get; } + + /// + /// Gets wind speed 1-minute sustained in miles per hour. + /// + public int? WindSpeed { get; } + + /// + /// Gets 5-minute max wind gust in miles per hour. + /// + public int? WindGust { get; } + + /// + /// Gets temperature in degrees Fahrenheit. + /// + public int? Temperature { get; } + + /// + /// Gets 1-hour rainfall in 100ths of an inch. + /// + public int? Rainfall1Hour { get; } + + /// + /// Gets 24-hour rainfall in 100ths of an inch. + /// + public int? Rainfall24Hour { get; } + + /// + /// Gets rainfall since midnight in 100ths of an inch. + /// + public int? RainfallSinceMidnight { get; } + + /// + /// Gets humidity in percentage. + /// + public int? Humidity { get; } + + /// + /// Gets Barometric pressure in 10ths of millibars/10ths of hPascal. + /// + public int? BarometricPressure { get; } + + /// + /// Gets luminosity in watts per square meter. + /// + public int? Luminosity { get; } + + /// + /// Gets raw rain. + /// + public int? RainRaw { get; } + + /// + /// Gets snowfall in inches in the last 24 hours. + /// + public int? Snow { get; } + + /// + /// Retrieves an APRS weather measurement from the comment string. + /// + /// The weather element to fetch, as defined by the key the ARPS specification. + /// The expected number of digits in the measurement. + /// An int value, if found. Else, null. + private int? GetWeatherMeasurement(char measurementKey, int length = 3) + { + // Regex below looks for the measurement key followed by either `length` numbers + // or a negative number of `length - 1` digits (to allow for the negative sign) + var match = Regex.Match(Comment, $"{measurementKey}(([0-9]{{{length}}})|(-[0-9]{{{length - 1}}}))"); + return match.Success ? int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture) : null; + } + + /// + /// Encodes weather information to be placed in the comment field. + /// + /// An APRS encoding of weather information on this packet. + private string EncodeWeatherInfo() + { + StringBuilder sb = new StringBuilder(); + + sb.Append(WindDirection.ToWeatherEncoding()); + sb.Append($"/{WindSpeed.ToWeatherEncoding()}"); + sb.Append($"g{WindGust.ToWeatherEncoding()}"); + sb.Append($"t{Temperature.ToWeatherEncoding()}"); + sb.Append($"r{Rainfall1Hour.ToWeatherEncoding()}"); + sb.Append($"p{Rainfall24Hour.ToWeatherEncoding()}"); + sb.Append($"P{RainfallSinceMidnight.ToWeatherEncoding()}"); + sb.Append($"h{Humidity.ToWeatherEncoding(2)}"); + sb.Append($"b{BarometricPressure.ToWeatherEncoding(5)}"); + + // Only add less common measurements if provided + if (Luminosity != null) + { + char lum = Luminosity < 1000 ? 'L' : 'l'; + sb.Append($"{lum}{(Luminosity % 1000).ToWeatherEncoding()}"); + } + + if (RainRaw != null) + { + sb.Append($"#{RainRaw.ToWeatherEncoding()}"); + } + + if (Snow != null) + { + sb.Append($"s{Snow.ToWeatherEncoding()}"); + } + + return sb.ToString(); + } + } +} + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Collections.Generic; + using System.Text.RegularExpressions; + using AprsSharp.Parsers.Aprs.Extensions; + + /// + /// Represents a full APRS packet. + /// + public class Packet + { + /// + /// Initializes a new instance of the class. + /// + /// The string encoding of the APRS packet. + public Packet(string encodedPacket) + { + if (string.IsNullOrWhiteSpace(encodedPacket)) + { + throw new ArgumentNullException(nameof(encodedPacket)); + } + + Match match = Regex.Match(encodedPacket, RegexStrings.Tnc2Packet); + match.AssertSuccess("Full TNC2 Packet", nameof(encodedPacket)); + + Sender = match.Groups[1].Value; + Path = match.Groups[2].Value.Split(','); + ReceivedTime = DateTime.UtcNow; + InfoField = InfoField.FromString(match.Groups[3].Value); + } + + /// + /// Initializes a new instance of the class. + /// + /// The callsign of the sender. + /// The digipath for the packet. + /// An or extending class to send. + public Packet(string sender, IList path, InfoField infoField) + { + Sender = sender; + Path = path; + InfoField = infoField; + ReceivedTime = null; + } + + /// + /// Gets the sender's callsign. + /// + public string Sender { get; } + + /// + /// Gets the APRS digipath of the packet. + /// + public IList Path { get; } + + /// + /// Gets the time this packet was decoded. + /// Null if this packet was created instead of decoded. + /// + public DateTime? ReceivedTime { get; } + + /// + /// Gets the APRS information field for this packet. + /// + public InfoField InfoField { get; } + + /// + /// Encodes an APRS packet to a string. + /// + /// String representation of the packet. + public virtual string Encode() + { + return $"{Sender}>{string.Join(',', Path)}:{InfoField.Encode()}"; + } + } +} + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + using AprsSharp.Parsers.Aprs.Extensions; + using GeoCoordinatePortable; + + /// + /// Represents lat/long position in an APRS packet. + /// + public class Position + { + /// + /// Initializes a new instance of the class. + /// Decodes from LAT/LONG coordinates. + /// + /// String representing LAT/LONG coordinates. + public Position(string coords) + { + Decode(coords); + } + + /// + /// Initializes a new instance of the class. + /// + /// Coordinates to encode. + /// The primary or secondary symbole table. + /// The APRS symbol code from the table given. + /// Ambiguity to use on the coordinates. + public Position(GeoCoordinate? coords = null, char? table = null, char? symbol = null, int? amb = null) + { + Coordinates = coords ?? Coordinates; + SymbolTableIdentifier = table ?? SymbolTableIdentifier; + SymbolCode = symbol ?? SymbolCode; + Ambiguity = amb ?? Ambiguity; + } + + /// + /// Gets or sets lat/long coordinates. + /// + public GeoCoordinate Coordinates { get; set; } = new GeoCoordinate(0, 0); + + /// + /// Gets or sets the symbol table identifier. + /// + public char SymbolTableIdentifier { get; set; } = '\\'; + + /// + /// Gets or sets the symbol code. + /// + public char SymbolCode { get; set; } = '.'; + + /// + /// Gets or sets the position ambiguity. + /// + public int Ambiguity { get; set; } + + /// + /// Converts a string to have the given amount of ambiguity. + /// + /// String to convert. + /// Amount of ambiguity to emplace. + /// String representing ambiguity. + public static string EnforceAmbiguity(string coords, int enforceAmbiguity) + { + if (coords == null) + { + throw new ArgumentNullException(nameof(coords)); + } + else if (enforceAmbiguity > coords.Length - 2) + { + throw new ArgumentOutOfRangeException( + nameof(enforceAmbiguity), + $"Only {coords.Length - 2} numbers can be filtered, but {enforceAmbiguity} were requested for string {coords}"); + } + + StringBuilder sb = new StringBuilder(coords); + int remainingAmbiguity = enforceAmbiguity; + + for (int i = coords.Length - 2; i >= 0 && remainingAmbiguity > 0; --i) + { + if (i == coords.Length - 4) + { + continue; + } + + sb[i] = ' '; + --remainingAmbiguity; + } + + return sb.ToString(); + } + + /// + /// Counts the number of spaces from right to left and returns the number for ambiguity. + /// Also ensures there are no spaces left of a digit, which is an invalid APRS ambiguity syntax. + /// + /// An APRS latitude or longitude string. + /// Integer representing ambiguity. + public static int CountAmbiguity(string coords) + { + if (coords == null) + { + throw new ArgumentNullException(nameof(coords)); + } + + int ambiguity = 0; + bool foundDigit = false; + for (int i = coords.Length - 2; i >= 0; --i) + { + // skip the period + if (i == coords.Length - 4) + { + continue; + } + else if (coords[i] == ' ') + { + if (foundDigit) + { + throw new ArgumentException( + $"Coordinates shoud only have spaces growing from right. Error on string: {coords}", + nameof(coords)); + } + else + { + ++ambiguity; + } + } + else + { + foundDigit = true; + } + } + + return ambiguity; + } + + /// + /// Takes an encoded APRS coordinate string and uses it to initialize to . + /// + /// A string of APRS encoded coordinates. + public void Decode(string coords) + { + if (coords == null) + { + throw new ArgumentNullException(nameof(coords)); + } + + Match match = Regex.Match(coords, RegexStrings.PositionLatLongWithSymbols); + match.AssertSuccess("Coordinates", nameof(coords)); + + Ambiguity = 0; + double latitude = DecodeLatitude(match.Groups[1].Value); + double longitude = DecodeLongitude(match.Groups[3].Value); + + SymbolTableIdentifier = match.Groups[2].Value[0]; + SymbolCode = match.Groups[4].Value[0]; + Coordinates = new GeoCoordinate(latitude, longitude); + } + + /// + /// Decode from Maidenhead Location System gridsquare to a point in the middle of the gridsquare. + /// + /// 4 or 6 char Maidenhead gridsquare, optionally followed by symbol table and identifier. + public void DecodeMaidenhead(string gridsquare) + { + if (gridsquare == null) + { + throw new ArgumentNullException(nameof(gridsquare)); + } + + var match = Regex.Match(gridsquare, RegexStrings.MaidenheadGridFullLine); + match.AssertSuccess("Maidenhead", nameof(gridsquare)); + + string trimmedGridsquare = match.Groups[1].Value; + + // If a third group is matched, this group is the two symbol fields. + if (match.Groups[2].Success) + { + string symbols = match.Groups[2].Value; + SymbolTableIdentifier = symbols[0]; + SymbolCode = symbols[1]; + } + + double latitude = DecodeFromGridsquare(trimmedGridsquare, CoordinateSystem.Latitude); + double longitude = DecodeFromGridsquare(trimmedGridsquare, CoordinateSystem.Longitude); + Coordinates = new GeoCoordinate(latitude, longitude); + } + + /// + /// Encodes the location as a gridsquare from the coordinates on this object. + /// + /// Max number of characters to use in gridsquare encoding, must be {4, 6, 8}. Ambiguity will subtract from this. + /// If true, appends the symbol to the end of the string. + /// A string representation of the Maidenhead gridsquare. + public string EncodeGridsquare(int length, bool appendSymbol) + { + if (length != 4 && + length != 6 && + length != 8) + { + throw new ArgumentException($"Length should be 4, 6, or 8. Given value was {length}", nameof(length)); + } + else if (Coordinates.IsNull) + { + throw new NullReferenceException("Coordinates were null."); + } + else if (Ambiguity != 0 && + Ambiguity != 2 && + Ambiguity != 4 && + Ambiguity != 6) + { + throw new ArgumentException($"Ambiguity must be in {{0, 2, 4, 6}} to encode a gridsquare, but is {Ambiguity}"); + } + else if (Ambiguity > length - 4) + { + throw new ArgumentException($"Final length of the string must be at least 4. length - Ambiguity >= 4, but length is {length} and Ambiguity is {Ambiguity}"); + } + + string gridsquare = string.Empty; + + string longitude = EncodeGridsquareElement(Coordinates.Longitude, CoordinateSystem.Longitude, (length - Ambiguity) / 2); + string latitude = EncodeGridsquareElement(Coordinates.Latitude, CoordinateSystem.Latitude, (length - Ambiguity) / 2); + + for (int i = 0; i < length - Ambiguity; ++i) + { + if (i % 2 == 0) + { + gridsquare += longitude[i / 2]; + } + else + { + gridsquare += latitude[i / 2]; + } + } + + if (appendSymbol) + { + gridsquare += SymbolTableIdentifier; + gridsquare += SymbolCode; + } + + return gridsquare; + } + + /// + /// Decode the latitude section of the coordinate string. + /// + /// APRS encoded latitude coordinates. + /// Decimal latitude. + public double DecodeLatitude(string coords) + { + // Ensure latitude is well formatted + if (coords == null) + { + throw new ArgumentNullException(nameof(coords)); + } + + string upperCoords = coords.ToUpperInvariant(); + if (upperCoords.Length != 8) + { + throw new ArgumentException("Latitude coordinates should be 8 characters. Given string was " + upperCoords.Length); + } + + char direction = upperCoords[7]; + if (upperCoords[7] != 'N' && upperCoords[7] != 'S') + { + throw new ArgumentException("Coordinates should end in N or S. Given string ended in " + direction); + } + + if (upperCoords[4] != '.') + { + throw new ArgumentException("Coordinates should have '.' at index 4. Given string had " + upperCoords[4]); + } + + // Count ambiguity and ensure no out of place spaces + Ambiguity = CountAmbiguity(upperCoords); + + // replace spaces with zeros + string modifiedCoords = upperCoords.Replace(' ', '0'); + + // Break out strings and convert values + string degreesStr = modifiedCoords.Substring(0, 2); + string minutesStr = modifiedCoords.Substring(2, 2); + string hundMinutesStr = modifiedCoords.Substring(5, 2); + + double degrees = double.Parse(degreesStr, CultureInfo.InvariantCulture); + if (degrees < 00 || degrees > 90) + { + throw new ArgumentOutOfRangeException( + nameof(coords), + $"Degrees must be in range [00,90]. Found: {degrees} in coord string {modifiedCoords} from given coord string {coords}"); + } + + double minutes = double.Parse(minutesStr, CultureInfo.InvariantCulture); + double hundMinutes = double.Parse(hundMinutesStr, CultureInfo.InvariantCulture); + + double retval = degrees + ((minutes + (hundMinutes / 100)) / 60.0); + + if (direction == 'S') + { + retval *= -1; + } + + // limit significant figures + return Math.Round(retval, 4); + } + + /// + /// Decode the longitude section of the coordinate string. + /// + /// APRS encoded latitude coordinates. + /// Decimal longitude. + public double DecodeLongitude(string coords) + { + if (coords == null) + { + throw new ArgumentNullException(nameof(coords)); + } + else if (coords.Length != 9) + { + throw new ArgumentException( + $"Longitude format calls for 9 characters. Given string has length {coords.Length}", + nameof(coords)); + } + + string upperCoords = coords.ToUpperInvariant(); + + char direction = upperCoords[8]; + if (direction != 'W' && direction != 'E') + { + throw new ArgumentException( + $"Coordinates should end in E or W. Given string ended in {direction}", + nameof(coords)); + } + else if (upperCoords[5] != '.') + { + throw new ArgumentException( + $"Coordinates should have '.' at index 5. Given string had {upperCoords[5]}", + nameof(coords)); + } + + // This ensures no spaces are out of place + CountAmbiguity(upperCoords); + + // Enforce ambiguity from latitude and replace spaces + string modifiedCoords = EnforceAmbiguity(upperCoords, Ambiguity); + modifiedCoords = modifiedCoords.Replace(' ', '0'); + + // Break out strings and convert values + string degreesStr = modifiedCoords.Substring(0, 3); + string minutesStr = modifiedCoords.Substring(3, 2); + string hundMinuteStr = modifiedCoords.Substring(6, 2); + + double degrees = double.Parse(degreesStr, CultureInfo.InvariantCulture); + if (degrees < 000 || degrees > 180) + { + throw new ArgumentOutOfRangeException( + nameof(coords), + $"Degrees longitude must be in range [000, 180]. Found: {degrees} in coord string {modifiedCoords} from given coord string {coords}"); + } + + double minutes = double.Parse(minutesStr, CultureInfo.InvariantCulture); + double hundMinutes = double.Parse(hundMinuteStr, CultureInfo.InvariantCulture); + + double retval = degrees + ((minutes + (hundMinutes / 100)) / 60.0); + + if (direction == 'W') + { + retval *= -1; + } + + // limit significant figures + return Math.Round(retval, 4); + } + + /// + /// Encodes with the values set on the fields using lat/long. + /// + /// A string encoding of an APRS position (lat/long). + public string Encode() + { + return EncodeLatitude() + + SymbolTableIdentifier + + EncodeLongitude() + + SymbolCode; + } + + /// + /// Encodes latitude position, enforcing ambiguity + /// Really just calls through to EncodeCoordinates, but this is easier to test + /// with PrivateObject. + /// + /// Encoded APRS latitude position. + public string EncodeLatitude() + { + return EncodeCoordinates(CoordinateSystem.Latitude); + } + + /// + /// Encodes longitude position, enforcing ambiguity + /// Really just calls through to EncodeCoordinates, but this is easier to test + /// with PrivateObject. + /// + /// Encoded APRS longitude position. + public string EncodeLongitude() + { + return EncodeCoordinates(CoordinateSystem.Longitude); + } + + /// + /// Encode either the latitude or longitude part of a Maidenhead gridsquare. + /// + /// The coordinates to use. + /// Either latitude or longitude encoding. + /// The number of chars for this encoding (should be half the total length of the gridsquare as this is only lat or long). + /// A string of the lat or long for the gridsquare encoding. + private static string EncodeGridsquareElement(double coords, CoordinateSystem coordinateEncode, int length) + { + if (coordinateEncode == CoordinateSystem.Longitude && Math.Abs(coords) > 180) + { + throw new ArgumentOutOfRangeException(nameof(coords), "Longitude coordinates must be inside [-180, 180]"); + } + else if (coordinateEncode == CoordinateSystem.Latitude && Math.Abs(coords) > 90) + { + throw new ArgumentOutOfRangeException(nameof(coords), "Longitude coordinates must be inside [-90, 90]"); + } + + string encoded = string.Empty; + int charIndex; + double stepDivisor; + + // Longitude scales most values by 2 over latitude + int multiplier = (coordinateEncode == CoordinateSystem.Longitude) ? 2 : 1; + + // no negatives here + double shiftedCoords = coords + (multiplier * 90.0); + + if (length >= 1) + { + stepDivisor = 10.0 * multiplier; + charIndex = (int)(shiftedCoords / stepDivisor); + encoded += char.ConvertFromUtf32('A' + charIndex); + + shiftedCoords %= stepDivisor; + } + + if (length >= 2) + { + stepDivisor = 1.0 * multiplier; + charIndex = (int)(shiftedCoords / stepDivisor); + encoded += charIndex.ToString(CultureInfo.InvariantCulture); + + shiftedCoords %= stepDivisor; + } + + // moving in to minutes territory + shiftedCoords *= 60.0; + + if (length >= 3) + { + stepDivisor = 2.5 * multiplier; + charIndex = (int)(shiftedCoords / stepDivisor); + encoded += char.ConvertFromUtf32('A' + charIndex); + + shiftedCoords %= stepDivisor; + } + + if (length >= 4) + { + stepDivisor = 0.25 * multiplier; + charIndex = (int)(shiftedCoords / stepDivisor); + encoded += charIndex.ToString(CultureInfo.InvariantCulture); + + // shiftedCoords %= stepDivisor; + } + + return encoded; + } + + /// + /// Given a maidenhead gridsquare, converts to decimal latitude or longitude. + /// + /// A Maidenhead Location System gridsquare. + /// Latitude or longitude for decode. + /// A double representing latitude [-180.0, 180.0] or longitude [-90.0, 90.0]. + private static double DecodeFromGridsquare(string gridsquare, CoordinateSystem coordinateDecode) + { + if (gridsquare == null) + { + throw new ArgumentNullException(nameof(gridsquare)); + } + else if (gridsquare.Length != 4 && + gridsquare.Length != 6 && + gridsquare.Length != 8) + { + throw new ArgumentException( + $"The given Maidenhead Location System gridsquare was {gridsquare.Length} characters length when a length of 4 or 6 characters was expected.", + nameof(gridsquare)); + } + + double coord = 0; + double gridsquareSize = 0; + + // Chars alternatingly refer to longitude and latitude + int index = (coordinateDecode == CoordinateSystem.Latitude) ? 1 : 0; + + // Longitude scales most values by 2 over latitude + int multiplier = (coordinateDecode == CoordinateSystem.Longitude) ? 2 : 1; + + gridsquare = gridsquare.ToUpperInvariant(); + + if (gridsquare.Length >= 2) + { + if (gridsquare[index] < 'A' || gridsquare[index] > 'R') + { + throw new ArgumentException( + $"Gridsquare should use A-R for first pair of characters. The given string was {gridsquare}", + nameof(gridsquare)); + } + + gridsquareSize = 10 * multiplier; + coord += gridsquareSize * (gridsquare[index] - 'A'); + index += 2; + } + + if (gridsquare.Length >= 4) + { + if (!char.IsDigit(gridsquare[index])) + { + throw new ArgumentException( + $"Gridsquare should use 0-9 for second pair of characters. The given string was {gridsquare}", + nameof(gridsquare)); + } + + gridsquareSize = multiplier; + coord += gridsquareSize * char.GetNumericValue(gridsquare[index]); + index += 2; + } + + if (gridsquare.Length >= 6) + { + if (gridsquare[index] < 'A' || gridsquare[index] > 'X') + { + throw new ArgumentException( + $"Gridsquare should use A-X for third pair of characters. The given string was {gridsquare}", + nameof(gridsquare)); + } + + gridsquareSize = (2.5 * multiplier) / 60.0; + coord += ((2.5 * multiplier) * (gridsquare[index] - 'A')) / 60.0; + index += 2; + } + + if (gridsquare.Length >= 8) + { + if (!char.IsDigit(gridsquare[index])) + { + throw new ArgumentException( + $"Gridsquare should use 0-9 for fourth pair of characters. The given string was {gridsquare}", + nameof(gridsquare)); + } + + gridsquareSize = (0.25 * multiplier) / 60.0; + coord += ((0.25 * multiplier) * char.GetNumericValue(gridsquare[index])) / 60.0; + } + + // center the coordinate in the grid + coord += gridsquareSize / 2.0; + return coord - (90.0 * multiplier); + } + + /// + /// Encodes latitude or longitude position, enforcing ambiguity. + /// + /// to encode: Latitude or Longitude. + /// String of APRS latitude or longitude position. + private string EncodeCoordinates(CoordinateSystem type) + { + double coords; + char direction; + string decimalFormat; + + if (type == CoordinateSystem.Latitude) + { + coords = Coordinates.Latitude; + direction = coords < 0 ? 'S' : 'N'; + decimalFormat = "D2"; + } + else if (type == CoordinateSystem.Longitude) + { + coords = Coordinates.Longitude; + direction = coords > 0 ? 'E' : 'W'; + decimalFormat = "D3"; + } + else + { + throw new ArgumentException($"Invalid CoordinateSystem: {type}", nameof(type)); + } + + coords = Math.Abs(coords); + + int degrees = (int)Math.Floor(coords); + int minutes = (int)Math.Floor((coords - degrees) * 60); + int hundMinutes = (int)Math.Round((((coords - degrees) * 60) - minutes) * 100); + + string encoded = degrees.ToString(decimalFormat, CultureInfo.InvariantCulture) + + minutes.ToString("D2", CultureInfo.InvariantCulture) + + '.' + + hundMinutes.ToString("D2", CultureInfo.InvariantCulture) + + direction; + + encoded = EnforceAmbiguity(encoded, Ambiguity); + + return encoded; + } + } +} + +namespace AprsSharp.Parsers.Aprs +{ + /// + /// Holds strings for regex comparisons for reuse. + /// + internal static class RegexStrings + { + /// + /// Same as but symbols are optional. + /// Three groups: Full, alphanumeric grid, symbols. + /// + public static readonly string MaidenheadGridWithOptionalSymbols = $@"{MaidenheadGridWithSymbols}?"; + + /// + /// Matches Maidenhead grid with symbols. + /// Three groups: Full, alphanumeric grid, symbols. + /// + public const string MaidenheadGridWithSymbols = @"([a-zA-Z0-9]{4,8})(.{2})"; + + /// + /// Matches Maidenhead grid of 4-6 characters without symbols. + /// One group: Full match. + /// + public const string MaidenheadGrid4or6NoSymbols = @"([a-zA-Z0-9]{4,6})"; + + /// + /// Matchdes a lat/long position, including with ambiguity, including symbols. + /// Five matches: + /// Full + /// Latitute + /// Symbol table ID + /// Longitude + /// Symbol Code. + /// + public const string PositionLatLongWithSymbols = @"([0-9 \.NS]{8})(.)([0-9 \.EW]{9})(.)"; + + /// + /// Same as but forces full line match. + /// + /// + public static readonly string MaidenheadGridFullLine = $@"^{MaidenheadGridWithOptionalSymbols}$"; + + /// + /// Matches a Miadenhead Grid in square brackets [] with comment. + /// Four groups: Full, alphanumeric grid, comment. + /// + public static readonly string MaidenheadGridLocatorBeacon = $@"^\[{MaidenheadGrid4or6NoSymbols}\](.+)?$"; + + /// + /// Matches a Status info field with Maidenhead grid and optional comment (comment separated by a space) + /// Four matches: Full, full maidenhead, alphanumeric grid, symbols, comment. + /// + public static readonly string StatusWithMaidenheadAndComment = $@"^>({MaidenheadGridWithSymbols}) ?(.+)?$"; + + /// + /// Matches a PositionWithoutTimestamp info field. + /// Seven mathces: + /// Full + /// Full lat/long coords and symbols + /// Latitude + /// Symbol table + /// Longitude + /// Symbol code + /// Optional comment. + /// + public static readonly string PositionWithoutTimestamp = $@"^[!=]({PositionLatLongWithSymbols})(.+)?$"; + + /// + /// Matches a PositionWithTimestamp info field. + /// 9 mathces: + /// Full + /// Packet type symbol (/ or @) + /// Timestamp + /// Full position + /// Latitude + /// Symbol table + /// Longitude + /// Symbol code + /// Optional comment. + /// + public static readonly string PositionWithTimestamp = $@"^([/@])([0-9]{{6}}[/zh0-9])({PositionLatLongWithSymbols})(.+)?$"; + + /// + /// Matches a full TNC2-encoded packet. + /// 4 matches: + /// Full + /// Sender callsign + /// Path + /// Info field. + /// + public const string Tnc2Packet = @"^([^>]+)>([^:]+):(.+)$"; + } +} + +namespace AprsSharp.Parsers.Aprs +{ + using System; + using System.Globalization; + + /// + /// Represents, encodes, and decodes an APRS timestamp. + /// + public class Timestamp + { + /// + /// Initializes a new instance of the class + /// by decoding the given timestamp in to it. + /// + /// APRS timestamp. + public Timestamp(string timestamp) + { + Decode(timestamp); + } + + /// + /// Initializes a new instance of the class + /// given a object. + /// + /// DateTime object to use for this Timestamp. + public Timestamp(DateTime dt) + { + DateTime = dt; + } + + /// + /// Gets or sets a representing the APRS timestamp. + /// + public DateTime DateTime { get; set; } = DateTime.UtcNow; + + /// + /// Gets or sets a representing the APRS timestamp type. + /// + public TimestampType DecodedType { get; set; } = TimestampType.NotDecoded; + + /// + /// If we're given a time in DHM, we need to find the correct month and year to fill in for DateTime. + /// Assuming the day is in the past and the most recent occurance of that day in a month, this function finds the year and month. + /// + /// The day number to find. + /// A hint to the timeframe we're looking for. Generally, DateTime.Now. + /// The year in which the most recent occurance of that day number occured. + /// The month in which the most recent occurance of that day number occured. + public static void FindCorrectYearAndMonth( + int day, + DateTime hint, + out int year, + out int month) + { + if (day > 31 || day < 1) + { + throw new ArgumentOutOfRangeException("Day must be in range [1, 31], but the passed in day number was " + day); + } + + int currYear = hint.Year; + int currMonth = hint.Month; + int currDay = hint.Day; + + // If that day number has already happened this month, we're done! + if (day <= currDay) + { + year = currYear; + month = currMonth; + } + else + { + DateTime itrDate = hint; + while (itrDate.Day != day) + { + itrDate = itrDate.AddDays(-1); + } + + month = itrDate.Month; + year = itrDate.Year; + } + } + + /// + /// Given an hour, minute, and second, determines if this should be the same day as the hint (generally DateTime.Now) or the day before. + /// + /// Hour of packet. + /// Minute of packet. + /// Second of packet. + /// Usually DateTime.Now. + /// Found day. + /// Found month. + /// Found year. + public static void FindCorrectDayMonthAndYear( + int hour, + int minute, + int second, + DateTime hint, + out int day, + out int month, + out int year) + { + DateTime packetTime = new DateTime(hint.Year, hint.Month, hint.Day, hour, minute, second, hint.Kind); + TimeSpan diff = hint.Subtract(packetTime); + + // allow for a few minutes of clock drift between receiver and transmitter + if (diff.TotalMinutes < -5) + { + // assume it's from yesterday + packetTime = packetTime.AddDays(-1); + } + + day = packetTime.Day; + month = packetTime.Month; + year = packetTime.Year; + } + + /// + /// Given a month, day, hour, and minute, determines the appropriate year (in the past as determiend by hint). + /// + /// Packet month. + /// Packet day. + /// Packet hour. + /// Packet minute. + /// Usually DateTime.Now. + /// Found year. + public static void FindCorrectYear( + int month, + int day, + int hour, + int minute, + DateTime hint, + out int year) + { + DateTime packet; + int numRetries = 0; + + // Retries on four separate years to find a year + // that could match the day and month (incl. leap) + while (true) + { + try + { + packet = new DateTime( + hint.Year - numRetries, + month, + day, + hour, + minute, + 0, + hint.Kind); + + // again, use five minute clock drift + if (hint.Subtract(packet).TotalMinutes > -5) + { + break; + } + + ++numRetries; + } + catch (ArgumentOutOfRangeException) + { + ++numRetries; + if (numRetries >= 4) + { + throw; + } + } + } + + year = packet.Year; + } + + /// + /// Encodes the data from the stored datetime as a string with the requested type. + /// + /// The APRS . + /// String. 7-8 characters as defined by the APRS spec. + public string Encode(TimestampType type) + { + return type switch + { + TimestampType.DHMz => EncodeDHM(isZulu: true), + TimestampType.DHMl => EncodeDHM(isZulu: false), + TimestampType.HMS => EncodeHMS(), + TimestampType.MDHM => EncodeMDHM(), + TimestampType.NotDecoded => throw new ArgumentOutOfRangeException(nameof(type), $"Cannot encode to type: {TimestampType.NotDecoded}"), + _ => throw new NotSupportedException(), + }; + } + + /// + /// Takes the APRS Time Format string, detects the formatting, and decodes it in to this object. + /// + /// APRS timestamp. + public void Decode(string timestamp) + { + if (timestamp == null) + { + throw new ArgumentNullException(nameof(timestamp)); + } + else if (timestamp.Length != 7 && timestamp.Length != 8) + { + throw new ArgumentException("The given APRS timestamp is " + timestamp.Length + " characters long instead of the required 7-8."); + } + + char timeIndicator = timestamp[6]; + + if (timeIndicator == 'z' || timeIndicator == '/') + { + DecodeDHM(timestamp); + } + else if (timeIndicator == 'h') + { + DecodeHMS(timestamp); + } + else if (char.IsNumber(timeIndicator)) + { + DecodeMDHM(timestamp); + } + else + { + throw new ArgumentException("timestamp was not a valid APRS format (did not have a valid Time Indicator character)"); + } + } + + /// + /// Encodes to a Day/Hour/Minute (DHM) string in zulu time or local time. + /// + /// If true, encodes in DHM with zulu time, else local time. Zulu time should be used. + /// DHM encoded APRS timestamp string. + private string EncodeDHM(bool isZulu) + { + string encodedPacket = string.Empty; + DateTime convertedDateTime = isZulu ? DateTime.ToUniversalTime() : DateTime.ToLocalTime(); + + // Add day + encodedPacket += convertedDateTime.Day.ToString("D2", CultureInfo.InvariantCulture); + + // Add hour + encodedPacket += convertedDateTime.Hour.ToString("D2", CultureInfo.InvariantCulture); + + // Add minute + encodedPacket += convertedDateTime.Minute.ToString("D2", CultureInfo.InvariantCulture); + + // Add time indicator + encodedPacket += isZulu ? "z" : "/"; + + return encodedPacket; + } + + /// + /// Encodes to an Hour/Minute/Second (HMS) string in zulu time. + /// + /// HMS encoded APRS timestamp string. + private string EncodeHMS() + { + string encodedPacket = string.Empty; + DateTime convertedDateTime = DateTime.ToUniversalTime(); + + // Add hour + encodedPacket += convertedDateTime.Hour.ToString("D2", CultureInfo.InvariantCulture); + + // Add minute + encodedPacket += convertedDateTime.Minute.ToString("D2", CultureInfo.InvariantCulture); + + // Add second + encodedPacket += convertedDateTime.Second.ToString("D2", CultureInfo.InvariantCulture); + + // Add the time indicator + encodedPacket += "h"; + + return encodedPacket; + } + + /// + /// Encodes to an Month/Day/Hour/Minute (MDHM) string in zulu time. + /// + /// MDHM encoded APRS timestamp string. + private string EncodeMDHM() + { + string encodedPacket = string.Empty; + DateTime convertedDateTime = DateTime.ToUniversalTime(); + + // Add month + encodedPacket += convertedDateTime.Month.ToString("D2", CultureInfo.InvariantCulture); + + // Add day + encodedPacket += convertedDateTime.Day.ToString("D2", CultureInfo.InvariantCulture); + + // Add hour + encodedPacket += convertedDateTime.Hour.ToString("D2", CultureInfo.InvariantCulture); + + // Add minute + encodedPacket += convertedDateTime.Minute.ToString("D2", CultureInfo.InvariantCulture); + + return encodedPacket; + } + + /// + /// Takes a Day/Hours/Minutes formatted APRS timestamp and decodes it in to this object. + /// + /// Day/Hours/Minutes formatted APRS timestamp. + private void DecodeDHM(string timestamp) + { + if (timestamp == null) + { + throw new ArgumentNullException(nameof(timestamp)); + } + else if (timestamp.Length != 7) + { + throw new ArgumentException("timestamp is not in APRS DHM datetime format. Length is " + timestamp.Length + " when it should be 7"); + } + + char timeIndicator = timestamp[6]; + + bool wasZuluTime; + if (timeIndicator == 'z') + { + wasZuluTime = true; + } + else if (timeIndicator == '/') + { + wasZuluTime = false; + } + else + { + throw new ArgumentException("timestamp is not in DHM format as time indicator is " + timeIndicator + " when it should be z or /"); + } + + DecodedType = wasZuluTime ? TimestampType.DHMz : TimestampType.DHMl; + if (!int.TryParse(timestamp.Substring(0, 6), out _)) + { + throw new ArgumentException("timestamp contained non-numeric values in the first 6 spaces: " + timestamp); + } + + string dayStr = timestamp.Substring(0, 2); + string hourStr = timestamp.Substring(2, 2); + string minuteStr = timestamp.Substring(4, 2); + + int day = int.Parse(dayStr, CultureInfo.InvariantCulture); + int hour = int.Parse(hourStr, CultureInfo.InvariantCulture); + int minute = int.Parse(minuteStr, CultureInfo.InvariantCulture); + DateTime hint = wasZuluTime ? DateTime.UtcNow : DateTime.Now; + + FindCorrectYearAndMonth(day, hint, out int year, out int month); + DateTimeKind dtKind = wasZuluTime ? DateTimeKind.Utc : DateTimeKind.Local; + + DateTime = new DateTime(year, month, day, hour, minute, 0, dtKind); + } + + /// + /// Takes an Hours/Minutes/Seconds formatted APRS timestamp and decodes it in to this object. + /// + /// Hours/Minutes/Seconds formatted APRS timestamp. + private void DecodeHMS(string timestamp) + { + if (timestamp == null) + { + throw new ArgumentNullException(nameof(timestamp)); + } + else if (timestamp.Length != 7 || timestamp[6] != 'h') + { + throw new ArgumentException("timestamp is not in APRS HMS datetime format. Length is " + timestamp.Length + " when it should be 7 or format marker is " + timestamp[6] + " when it should be 'h'"); + } + + DecodedType = TimestampType.HMS; + + string hourStr = timestamp.Substring(0, 2); + string minuteStr = timestamp.Substring(2, 2); + string secondStr = timestamp.Substring(4, 2); + + int hour = int.Parse(hourStr, CultureInfo.InvariantCulture); + int minute = int.Parse(minuteStr, CultureInfo.InvariantCulture); + int second = int.Parse(secondStr, CultureInfo.InvariantCulture); + DateTime hint = DateTime.UtcNow; + + FindCorrectDayMonthAndYear( + hour, + minute, + second, + hint, + out int day, + out int month, + out int year); + + DateTimeKind dtKind = DateTimeKind.Utc; + DateTime = new DateTime(year, month, day, hour, minute, second, dtKind); + } + + /// + /// Takes a Month/Day/Hours/Minutes formmatted APRS timestamp and decodes it in to this object. + /// + /// Month/Day/Hours/Minutes forammted APRS timestamp. + private void DecodeMDHM(string timestamp) + { + if (timestamp == null) + { + throw new ArgumentNullException(nameof(timestamp)); + } + else if (timestamp.Length != 8) + { + throw new ArgumentException("timestamp is not in APRS MDHM datetime format. Length is " + timestamp.Length + " when it should be 8"); + } + + DecodedType = TimestampType.MDHM; + + string monthStr = timestamp.Substring(0, 2); + string dayStr = timestamp.Substring(2, 2); + string hourStr = timestamp.Substring(4, 2); + string minuteStr = timestamp.Substring(6, 2); + + int month = int.Parse(monthStr, CultureInfo.InvariantCulture); + int day = int.Parse(dayStr, CultureInfo.InvariantCulture); + int hour = int.Parse(hourStr, CultureInfo.InvariantCulture); + int minute = int.Parse(minuteStr, CultureInfo.InvariantCulture); + DateTime hint = DateTime.UtcNow; + + FindCorrectYear( + month, + day, + hour, + minute, + hint, + out int year); + + DateTimeKind dtKind = DateTimeKind.Utc; + DateTime = new DateTime(year, month, day, hour, minute, 0 /* second */, dtKind); + } + } +} \ No newline at end of file diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/GeoCoordinatePortable.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/GeoCoordinatePortable.cs new file mode 100644 index 0000000..cfee1b4 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/GeoCoordinatePortable.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GeoCoordinatePortable +{ + public struct GeoCoordinate + { + public double Latitude { get; } + public double Longitude { get; } + + public GeoCoordinate(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } + + public bool IsNull => Latitude == 0 && Longitude == 0; + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/MessageField.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/MessageField.cs new file mode 100644 index 0000000..d8f61a0 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/MessageField.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; + +namespace skyscraper5.Aprs.AprsSharp +{ + class MessageField : InfoField + { + public string RawMessage { get; } + + public MessageField(string encodedInfoField) + { + RawMessage = encodedInfoField; + } + + public override string Encode() + { + return RawMessage; + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/RawGpsInfoField.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/RawGpsInfoField.cs new file mode 100644 index 0000000..5db51ff --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/RawGpsInfoField.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; +using Microsoft.VisualBasic; +using NmeaParser.Messages; + +namespace skyscraper5.Aprs.AprsSharp +{ + internal class RawGpsInfoField : InfoField + { + public string RawMessage { get; private set; } + + public RawGpsInfoField(string msg) + : base(msg) + { + this.RawMessage = msg; + try + { + Parsed = NmeaMessage.Parse(msg); + } + catch (Exception e) + { + Parsed = null; + } + + } + + public NmeaMessage Parsed { get; private set; } + + public override string Encode() + { + return RawMessage; + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/StationCapabilities.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/StationCapabilities.cs new file mode 100644 index 0000000..0dac853 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/StationCapabilities.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; + +namespace skyscraper5.Aprs.AprsSharp +{ + public class StationCapabilities : InfoField + { + public StationCapabilities(string line) + { + if (line.StartsWith("<")) + line = line.Substring(1); + args = line.Split(','); + } + + private string[] args; + + public bool IsIGate => args[0].Equals("IGATE"); + + public int MessageCount + { + get + { + string first = args.FirstOrDefault(x => x.StartsWith("MSG_CNT")); + if (string.IsNullOrEmpty(first)) + return 0; + string[] strings = first.Split('='); + return int.Parse(strings[1]); + } + } + + public int LocalStations + { + get + { + string first = args.FirstOrDefault(x => x.StartsWith("LOC_CNT")); + if (string.IsNullOrEmpty(first)) + return 0; + string[] strings = first.Split('='); + return int.Parse(strings[1]); + } + } + + public override string Encode() + { + return String.Join(',', args); + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/UnimplementedInfoField.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/UnimplementedInfoField.cs new file mode 100644 index 0000000..f5ed572 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/UnimplementedInfoField.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; + +namespace skyscraper5.Aprs.AprsSharp +{ + class UnimplementedInfoField : InfoField + { + public string EncodedInfoField { get; } + + public UnimplementedInfoField(string encodedInfoField) + { + EncodedInfoField = encodedInfoField; + } + + public override string Encode() + { + return EncodedInfoField; + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/WeatherReportInfoField.cs b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/WeatherReportInfoField.cs new file mode 100644 index 0000000..0605688 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/AprsSharp/WeatherReportInfoField.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; + +namespace skyscraper5.Aprs.AprsSharp +{ + class WeatherReportInfoField : InfoField + { + public WeatherReportInfoField(string rawMsg) + { + Comment = rawMsg; + WindDirection = GetWeatherMeasurement('^'); + WindSpeed = GetWeatherMeasurement('/'); + WindGust = GetWeatherMeasurement('g'); + Temperature = GetWeatherMeasurement('t'); + Rainfall1Hour = GetWeatherMeasurement('r'); + Rainfall24Hour = GetWeatherMeasurement('p'); + RainfallSinceMidnight = GetWeatherMeasurement('P'); + Humidity = GetWeatherMeasurement('h', 2); + BarometricPressure = GetWeatherMeasurement('b', 5); + Luminosity = GetWeatherMeasurement('L') ?? GetWeatherMeasurement('l') + 1000; + RainRaw = GetWeatherMeasurement('#'); + Snow = GetWeatherMeasurement('s'); + + string ogMsg = rawMsg; + int indexOf = ogMsg.IndexOf(' ') - 1; + for (int i = indexOf; i > 0; i--) + { + char c = ogMsg[i]; + if (Char.IsDigit(c) || c == '.' || c == ' ') + { + indexOf = i; + break; + } + } + + indexOf++; + Comment = ogMsg.Substring(indexOf); + + Wrapped = new WeatherInfo(null, false, null, Comment, WindDirection, WindSpeed, WindGust, + Temperature, Rainfall1Hour, Rainfall24Hour, RainfallSinceMidnight, Humidity, BarometricPressure, + Luminosity, RainRaw, Snow); + Wrapped.Comment = Comment; + } + + private int? Snow { get; set; } + + private int? RainRaw { get; set; } + + private int? Luminosity { get; set; } + + private int? BarometricPressure { get; set; } + + private int? Humidity { get; set; } + + private int? RainfallSinceMidnight { get; set; } + + private int? Rainfall24Hour { get; set; } + + private int? Rainfall1Hour { get; set; } + + private int? Temperature { get; set; } + + private int? WindGust { get; set; } + + private int? WindSpeed { get; set; } + + private int? WindDirection { get; set; } + + private string Comment { get; set; } + + private int? GetWeatherMeasurement(char measurementKey, int length = 3) + { + // Regex below looks for the measurement key followed by either `length` numbers + // or a negative number of `length - 1` digits (to allow for the negative sign) + var match = Regex.Match(Comment, $"{measurementKey}(([0-9]{{{length}}})|(-[0-9]{{{length - 1}}}))"); + return match.Success ? int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture) : null; + } + + public WeatherInfo Wrapped { get; private set; } + public override string Encode() + { + throw new NotSupportedException(); + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/Aprs/IAprsMessageReciever.cs b/MpePlugins/skyscraper5.Aprs/Aprs/IAprsMessageReciever.cs new file mode 100644 index 0000000..186837f --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/Aprs/IAprsMessageReciever.cs @@ -0,0 +1,16 @@ +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using NmeaParser.Messages; +using skyscraper5.Aprs.AprsSharp; + +namespace skyscraper5.Aprs +{ + interface IAprsMessageReciever + { + void OnAprsPosition(string packetSender, GeoCoordinate positionCoordinates, string? piComment); + void OnAprsWeatherReport(string packetSender, WeatherInfo wi); + void OnAprsChatMessage(string sender, string receiver, string message); + void OnAprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities); + void OnAprsGpsSentence(string packetSender, NmeaMessage parsed); + } +} diff --git a/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsFilesystemStorage.cs b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsFilesystemStorage.cs new file mode 100644 index 0000000..9f0958c --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsFilesystemStorage.cs @@ -0,0 +1,140 @@ +using GeoCoordinatePortable; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model; +using AprsSharp.Parsers.Aprs; +using skyscraper5.Skyscraper; +using skyscraper5.Aprs.AprsSharp; +using System.IO; + +namespace skyscraper5.Aprs.AprsStorage +{ + internal class AprsFilesystemStorage : LX9SESStorage + { + public AprsFilesystemStorage(DirectoryInfo root) + { + this.rootDirectory = root; + this.jsonSerializerSettings = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.Indented + }; + } + + private readonly DirectoryInfo rootDirectory; + private readonly JsonSerializerSettings jsonSerializerSettings; + + private void EnsureDirectoryExists(DirectoryInfo di) + { + if (di.Exists) + return; + + EnsureDirectoryExists(di.Parent); + di.Create(); + } + + public bool AprsPosition(DateTime currentTime, string packetSender, GeoCoordinate positionCoordinates, string piComment) + { + string path = Path.Combine(rootDirectory.FullName, "APRS", String.Format("{0}.json", packetSender)); + FileInfo fi = new FileInfo(path); + + InMemoryAprsData aprsEntry; + if (fi.Exists) + aprsEntry = JsonConvert.DeserializeObject(File.ReadAllText(fi.FullName)); + else + { + aprsEntry = new InMemoryAprsData(packetSender); + EnsureDirectoryExists(fi.Directory); + } + + bool result = aprsEntry.LastSeen < currentTime; + if (!result) + return false; + aprsEntry.LastSeen = currentTime; + result = aprsEntry.Longitude != positionCoordinates.Longitude || aprsEntry.Latitude != positionCoordinates.Latitude; + if (result) + { + aprsEntry.Longitude = positionCoordinates.Longitude; + aprsEntry.Latitude = positionCoordinates.Latitude; + aprsEntry.Comment = piComment; + aprsEntry.TimesMoved++; + if (aprsEntry.TimesMoved > 2) + Console.WriteLine("{0} is actually moving", packetSender); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(aprsEntry, Formatting.Indented, jsonSerializerSettings)); + return true; + } + return false; + } + + public bool AprsWeatherReport(DateTime currentTime, string packetSender, WeatherInfo wi) + { + string path = Path.Combine(rootDirectory.FullName, "APRS", "Weather", packetSender, String.Format("{0}.json", currentTime.ToUnixTime())); + FileInfo fi = new FileInfo(path); + + if (fi.Exists) + return false; + + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(wi, Formatting.Indented, jsonSerializerSettings)); + return true; + } + + public bool AprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities) + { + string path = Path.Combine(rootDirectory.FullName, "APRS", "Capabilities", String.Format("{0}.json", packetSender)); + FileInfo fi = new FileInfo(path); + if (fi.Exists) + return false; + + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(stationCapabilities, Formatting.Indented, jsonSerializerSettings)); + return true; + } + + public bool AprsGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, double? speed) + { + string path = Path.Combine(rootDirectory.FullName, "APRS", String.Format("{0}.json", packetSender)); + FileInfo fi = new FileInfo(path); + + InMemoryAprsData aprsEntry; + if (fi.Exists) + aprsEntry = JsonConvert.DeserializeObject(File.ReadAllText(fi.FullName)); + else + { + return false; + } + + bool result = aprsEntry.LastSeen == currentTime; + if (!result) + return false; + result = aprsEntry.Course != course || aprsEntry.MagneticVariation != magneticVariation || aprsEntry.Speed != speed; + if (result) + { + aprsEntry.Course = course; + aprsEntry.MagneticVariation = magneticVariation; + aprsEntry.Speed = speed; + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(aprsEntry, Formatting.Indented, jsonSerializerSettings)); + return true; + } + return false; + } + + public void AprsMessage(string sender, DateTime currentTime, string receiver, string message) + { + int msgCrc32 = Hasher.HashObjects(sender, receiver, message); + string path = Path.Combine(rootDirectory.FullName, "APRS", "Messages", String.Format("{1}_{0}.json", currentTime.ToUnixTime(), msgCrc32)); + FileInfo fi = new FileInfo(path); + if (fi.Exists) + return; + + var foo = new { sender, currentTime, receiver, message }; + + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(foo, Formatting.Indented, jsonSerializerSettings)); + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsInMemoryStorage.cs b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsInMemoryStorage.cs new file mode 100644 index 0000000..37a8337 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsInMemoryStorage.cs @@ -0,0 +1,124 @@ +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using skyscraper5.Aprs.AprsSharp; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; + +namespace skyscraper5.Aprs.AprsStorage +{ + internal class AprsInMemoryStorage : LX9SESStorage + { + public AprsInMemoryStorage(InMemoryPluginToken inMemoryPluginToken) + { + this.aprsData = new HashSet(); + inMemoryPluginToken.Data["aprsData"] = this.aprsData; + } + + private HashSet aprsData; + + public bool AprsPosition(DateTime currentTime, string packetSender, GeoCoordinate positionCoordinates, string? piComment) + { + InMemoryAprsData aprsEntry = aprsData.FirstOrDefault(x => x.CallSign.Equals(packetSender)); + if (aprsEntry == null) + { + aprsEntry = new InMemoryAprsData(packetSender); + aprsData.Add(aprsEntry); + } + + bool result = aprsEntry.LastSeen < currentTime; + if (!result) + return false; + aprsEntry.LastSeen = currentTime; + result = aprsEntry.Longitude != positionCoordinates.Longitude || aprsEntry.Latitude != positionCoordinates.Latitude; + if (result) + { + aprsEntry.Longitude = positionCoordinates.Longitude; + aprsEntry.Latitude = positionCoordinates.Latitude; + aprsEntry.Comment = piComment; + aprsEntry.TimesMoved++; + if (aprsEntry.TimesMoved > 2) + Console.WriteLine("{0} is actually moving", packetSender); + return true; + } + return false; + } + + public bool AprsWeatherReport(DateTime currentTime, string packetSender, WeatherInfo wi) + { + InMemoryAprsData aprsEntry = aprsData.FirstOrDefault(x => x.CallSign.Equals(packetSender)); + if (aprsEntry == null) + { + return false; + } + + return aprsEntry.WeatherTimestamps.Add(currentTime); + } + + + public void AprsMessage(string sender, DateTime currentTime, string receiver, string message) + { + + } + + public bool AprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities) + { + InMemoryAprsData aprsEntry = aprsData.FirstOrDefault(x => x.CallSign.Equals(packetSender)); + if (aprsEntry == null) + return false; + + if (aprsEntry.LocalStations != stationCapabilities.LocalStations || aprsEntry.MessageCount != stationCapabilities.MessageCount) + { + aprsEntry.LocalStations = stationCapabilities.LocalStations; + aprsEntry.MessageCount = stationCapabilities.MessageCount; + return true; + } + + return false; + } + + public bool AprsGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, double? speed) + { + InMemoryAprsData aprsEntry = aprsData.FirstOrDefault(x => x.CallSign.Equals(packetSender)); + if (aprsEntry == null) + { + aprsEntry = new InMemoryAprsData(packetSender); + aprsData.Add(aprsEntry); + } + + bool result = false; + if (course.HasValue && !double.IsNaN(course.Value)) + { + if (!aprsEntry.Course.Equals(course)) + { + aprsEntry.Course = course; + result = true; + } + } + + if (magneticVariation.HasValue && !double.IsNaN(magneticVariation.Value)) + { + if (!aprsEntry.MagneticVariation.Equals(magneticVariation)) + { + aprsEntry.MagneticVariation = magneticVariation; + result = true; + } + } + + if (speed.HasValue && !double.IsNaN(speed.Value)) + { + if (!aprsEntry.Speed.Equals(speed)) + { + aprsEntry.Speed = speed; + result = true; + } + } + + return result; + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsPluginSystemTestStorage.cs b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsPluginSystemTestStorage.cs new file mode 100644 index 0000000..325b24e --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsPluginSystemTestStorage.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using skyscraper5.Aprs.AprsSharp; + +namespace skyscraper5.Aprs.AprsStorage +{ + /// + /// The APRS subsystem does not mess with descriptors, so it's not relevant for our plugin system testing. + /// + internal class AprsPluginSystemTestStorage : LX9SESStorage + { + public bool AprsPosition(DateTime currentTime, string packetSender, GeoCoordinate positionCoordinates, string piComment) + { + return false; + } + + public bool AprsWeatherReport(DateTime currentTime, string packetSender, WeatherInfo wi) + { + return false; + } + + public bool AprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities) + { + return false; + } + + public bool AprsGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, double? speed) + { + return false; + } + + public void AprsMessage(string sender, DateTime currentTime, string receiver, string message) + { + + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsPostgresqlStorage.cs b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsPostgresqlStorage.cs new file mode 100644 index 0000000..b83a44a --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/AprsStorage/AprsPostgresqlStorage.cs @@ -0,0 +1,425 @@ +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Aprs.AprsSharp; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Aprs.AprsStorage +{ + internal class AprsPostgresqlStorage : LX9SESStorage + { + private readonly NpgsqlConnectionStringBuilder connectionStringBuilder; + + public AprsPostgresqlStorage(NpgsqlConnectionStringBuilder npgsqlConnectionStringBuilder) + { + this.connectionStringBuilder = npgsqlConnectionStringBuilder; + } + + #region Background Worker + private Thread _writerThread; + private delegate void WriterTask(NpgsqlConnection connection); + private Queue _writerTasks; + private void EnqueueTask(WriterTask task) + { + if (_writerTasks == null) + { + _writerTasks = new Queue(); + } + + lock (_writerTasks) + { + _writerTasks.Enqueue(task); + } + + if (_writerThread == null) + { + _writerThread = new Thread(WriterThreadFunction); + _writerThread.Priority = ThreadPriority.Highest; + _writerThread.Name = "PostgreSQL APRS Writer Thread"; + _writerThread.Start(); + } + } + + private NpgsqlTransaction transaction; + private void WriterThreadFunction() + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + transaction = connection.BeginTransaction(); + while (_writerTasks.Count > 0) + { + WriterTask task; + lock (_writerTasks) + { + task = _writerTasks.Dequeue(); + } + task(connection); + } + transaction.Commit(); + transaction.Dispose(); + connection.Close(); + } + + _writerThread = null; + } + + private void CommitTransaction(NpgsqlConnection conn) + { + transaction.Commit(); + transaction.Dispose(); + transaction = conn.BeginTransaction(); + } + #endregion + + private HashSet loadedAprsPositionHistories; + private HashSet knownAprsPositions; + public bool AprsPosition(DateTime currentTime, string packetSender, GeoCoordinate positionCoordinates, string piComment) + { + if (positionCoordinates.IsNull) + return false; + if (loadedAprsPositionHistories == null) + loadedAprsPositionHistories = new HashSet(); + if (knownAprsPositions == null) + knownAprsPositions = new HashSet(); + if (!loadedAprsPositionHistories.Contains(packetSender)) + { + FetchAprsPositionHistory(packetSender); + } + + DatabaseKeyAprsPosition key = new DatabaseKeyAprsPosition(currentTime, packetSender, positionCoordinates.Longitude, positionCoordinates.Latitude); + if (knownAprsPositions.Contains(key)) + return false; + + EnqueueTask(x => WriteAprsPosition(x, currentTime, packetSender, positionCoordinates, piComment)); + knownAprsPositions.Add(key); + return true; + } + + private void WriteAprsPosition(NpgsqlConnection connection, DateTime currentTime, string packetSender, GeoCoordinate positionCoordinates, string piComment) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO aprs_positions (ctime, sender, lon, lat, comment) " + + "VALUES (@ctime, @sender, @lon, @lat, @comment)"; + command.Parameters.AddWithValue("@ctime", NpgsqlDbType.Timestamp, currentTime); + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, packetSender); + command.Parameters.AddWithValue("@lon", NpgsqlDbType.Double, positionCoordinates.Longitude); + command.Parameters.AddWithValue("@lat", NpgsqlDbType.Double, positionCoordinates.Latitude); + if (piComment != null) + piComment = piComment.Replace("\0", ""); + command.Parameters.AddWithValue("@comment", NpgsqlDbType.Text, piComment); + CheckNulls(command.Parameters); + command.ExecuteNonQuery(); + } + + public static void CheckNulls(NpgsqlParameterCollection collection) + { + foreach (NpgsqlParameter o in collection) + { + if (o.Value == null) + o.Value = DBNull.Value; + else if (o.Value == DBNull.Value) + continue; + else + { + if (o.NpgsqlDbType == NpgsqlDbType.Text || o.NpgsqlDbType == NpgsqlDbType.Varchar) + { + string oValue = (string)o.Value; + oValue = oValue.Trim('\0'); + o.Value = oValue; + } + } + } + } + + private void FetchAprsPositionHistory(string packetSender) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT ctime, lon, lat FROM aprs_positions WHERE sender = @sender"; + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, packetSender); + NpgsqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + DateTime ctime = dataReader.GetDateTime(0); + double lon = dataReader.GetDouble(1); + double lat = dataReader.GetDouble(2); + knownAprsPositions.Add(new DatabaseKeyAprsPosition(ctime, packetSender, lon, lat)); + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + loadedAprsPositionHistories.Add(packetSender); + } + + private HashSet knownAprsWeatherReporters; + private HashSet knownAprsWeatherReports; + public bool AprsWeatherReport(DateTime currentTime, string packetSender, WeatherInfo wi) + { + if (knownAprsWeatherReporters == null) + knownAprsWeatherReporters = new HashSet(); + if (knownAprsWeatherReports == null) + knownAprsWeatherReports = new HashSet(); + if (!knownAprsWeatherReporters.Contains(packetSender)) + SelectAprsWeatherReports(packetSender); + + DatabaseKeyAprsWeatherReport key = new DatabaseKeyAprsWeatherReport(packetSender, currentTime); + if (knownAprsWeatherReports.Contains(key)) + return false; + + EnqueueTask(x => WriteAprsWeatherReport(x, currentTime, packetSender, wi)); + knownAprsWeatherReports.Add(key); + return true; + } + + private void SelectAprsWeatherReports(string sender) + { + using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT rtime FROM aprs_weather_reports WHERE sender = @sender"; + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, sender); + NpgsqlDataReader dataReader = command.ExecuteReader(); + while (dataReader.Read()) + { + knownAprsWeatherReports.Add(new DatabaseKeyAprsWeatherReport(sender, dataReader.GetDateTime(0))); + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + } + + knownAprsWeatherReporters.Add(sender); + } + + private void WriteAprsWeatherReport(NpgsqlConnection connection, DateTime currentTime, string packetSender, WeatherInfo wi) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "insert into aprs_weather_reports (sender, rtime, lat, lon, has_messaging, ts, comment, wind_direction,\r\n wind_speed, wind_gust, temperature, rainfall_hour, rainfall_day,\r\n rainfall_since_midnight, humidity, barometric_pressure, luminosity, rain_raw, snow) " + + "values " + + "(@sender,@rtime,@lat,@lon,@has_messaging,@ts,@comment,@wind_direction,@wind_speed,@wind_gust,@temperature,@rainfall_hour,@rainfall_day,@rainfall_since_midnight,@humidity,@barometric_pressure,@luminosity,@rain_raw,@snow)"; + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, packetSender); + command.Parameters.AddWithValue("@rtime", NpgsqlDbType.Timestamp, currentTime); + + GeoCoordinate positionCoordinates = new GeoCoordinate(); + if (wi.Position != null) + { + if (!wi.Position.Coordinates.IsNull) + positionCoordinates = wi.Position.Coordinates; + } + command.Parameters.AddWithValue("@lat", NpgsqlDbType.Double, positionCoordinates.IsNull ? DBNull.Value : positionCoordinates.Latitude); + command.Parameters.AddWithValue("@lon", NpgsqlDbType.Double, positionCoordinates.IsNull ? DBNull.Value : positionCoordinates.Longitude); + command.Parameters.AddWithValue("@has_messaging", NpgsqlDbType.Boolean, wi.HasMessaging); + command.Parameters.AddWithValue("@ts", NpgsqlDbType.TimestampTz, wi.Timestamp?.DateTime.ToUniversalTime()); + command.Parameters.AddWithValue("@comment", NpgsqlDbType.Text, wi.Comment.Replace("\0", "")); + command.Parameters.AddWithValue("@wind_direction", NpgsqlDbType.Integer, wi.WindDirection); + command.Parameters.AddWithValue("@wind_speed", NpgsqlDbType.Integer, wi.WindSpeed); + command.Parameters.AddWithValue("@wind_gust", NpgsqlDbType.Integer, wi.WindGust); + command.Parameters.AddWithValue("@temperature", NpgsqlDbType.Integer, wi.Temperature); + command.Parameters.AddWithValue("@rainfall_hour", NpgsqlDbType.Integer, wi.Rainfall1Hour); + command.Parameters.AddWithValue("@rainfall_day", NpgsqlDbType.Integer, wi.Rainfall24Hour); + command.Parameters.AddWithValue("@rainfall_since_midnight", NpgsqlDbType.Integer, wi.RainfallSinceMidnight); + command.Parameters.AddWithValue("@humidity", NpgsqlDbType.Integer, wi.Humidity); + command.Parameters.AddWithValue("@barometric_pressure", NpgsqlDbType.Integer, wi.BarometricPressure); + command.Parameters.AddWithValue("@luminosity", NpgsqlDbType.Integer, wi.Luminosity); + command.Parameters.AddWithValue("@rain_raw", NpgsqlDbType.Integer, wi.RainRaw); + command.Parameters.AddWithValue("@snow", NpgsqlDbType.Integer, wi.Snow); + CheckNulls(command.Parameters); + command.ExecuteNonQuery(); + } + + private HashSet knownAprsStationCapabilities; + public bool AprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities) + { + if (knownAprsStationCapabilities == null) + knownAprsStationCapabilities = new HashSet(); + if (knownAprsStationCapabilities.Contains(packetSender)) + return false; + + bool inDb = TestForAprsStationCapabilities(packetSender); + if (inDb) + { + knownAprsStationCapabilities.Add(packetSender); + return false; + } + + EnqueueTask(x => WriteAprsStationCapabilities(x, packetSender, stationCapabilities)); + knownAprsStationCapabilities.Add(packetSender); + return true; + } + + private void WriteAprsStationCapabilities(NpgsqlConnection connection, string packetSender, StationCapabilities stationCapabilities) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO aprs_station_capabilities (sender, isl_gate, local_stations, message_count) " + + "VALUES (@sender, @isl_gate, @local_stations, @message_count)"; + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, packetSender); + command.Parameters.AddWithValue("@isl_gate", NpgsqlDbType.Boolean, stationCapabilities.IsIGate); + command.Parameters.AddWithValue("@local_stations", NpgsqlDbType.Integer, stationCapabilities.LocalStations); + command.Parameters.AddWithValue("@message_count", NpgsqlDbType.Integer, stationCapabilities.MessageCount); + command.ExecuteNonQuery(); + } + + private bool TestForAprsStationCapabilities(string sender) + { + bool result; + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM aprs_station_capabilities WHERE sender = @sender"; + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, sender); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + conn.Close(); + } + + return result; + } + + class CachedGpsSentence + { + public CachedGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, double? speed) + { + CurrentTime = currentTime; + PacketSender = packetSender; + Course = course; + MagneticVariation = magneticVariation; + Speed = speed; + } + + public DateTime CurrentTime { get; } + public string PacketSender { get; } + public double? Course { get; } + public double? MagneticVariation { get; } + public double? Speed { get; } + + public override bool Equals(object? obj) + { + return obj is CachedGpsSentence sentence && + CurrentTime == sentence.CurrentTime && + PacketSender == sentence.PacketSender; + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentTime, PacketSender); + } + } + + public bool AprsGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, + double? speed) + { + CachedGpsSentence cgs = new CachedGpsSentence(currentTime, packetSender, course, magneticVariation, speed); + if (TestForAprsGpsSentence(cgs)) + { + return false; + } + + EnqueueTask(x => WriteAprsGpsSentence(x,cgs)); + return true; + } + + private void WriteAprsGpsSentence(NpgsqlConnection conn, CachedGpsSentence cgs) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "insert into aprs_gps_sentences (callsign, calltime, course, magnetic_variation, speed) values (@sign,@time,@course,@magnetic_variation, @speed)"; + command.Parameters.AddWithValue("@sign", NpgsqlDbType.Varchar, cgs.PacketSender); + command.Parameters.AddWithValue("@time", NpgsqlDbType.Timestamp, cgs.CurrentTime); + command.Parameters.AddWithValue("@course", NpgsqlDbType.Double, cgs.Course); + command.Parameters.AddWithValue("@magnetic_variation", NpgsqlDbType.Double, cgs.MagneticVariation); + command.Parameters.AddWithValue("@speed", NpgsqlDbType.Double, cgs.Speed); + int v = command.ExecuteNonQuery(); + if (v >= 0) + { + _gpsSentences.Add(cgs); + } + } + + private HashSet _gpsSentences; + private bool TestForAprsGpsSentence(CachedGpsSentence cgs) + { + if (_gpsSentences == null) + _gpsSentences = new HashSet(); + if (_gpsSentences.Contains(cgs)) + return true; + + using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString())) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM aprs_gps_sentences WHERE callsign = @sign AND calltime = @time"; + command.Parameters.AddWithValue("@sign", NpgsqlDbType.Varchar, cgs.PacketSender); + command.Parameters.AddWithValue("@time", NpgsqlDbType.Timestamp, cgs.CurrentTime); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + conn.Close(); + + if (result) + { + _gpsSentences.Add(cgs); + } + + return result; + } + } + + public void AprsMessage(string sender, DateTime currentTime, string receiver, string message) + { + EnqueueTask(x => WriteAprsMessage(x, sender, currentTime, receiver, message)); + } + + private void WriteAprsMessage(NpgsqlConnection connection, string sender, DateTime currentTime, string receiver, string message) + { + int ctime_sort = SelectAprsMessageCtimeSort(connection, currentTime); + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO aprs_messages (ctime,ctime_sort,sender,receiver,message) " + + "VALUES (@ctime,@ctime_sort,@sender,@receiver,@message)"; + command.Parameters.AddWithValue("@ctime", NpgsqlDbType.Timestamp, currentTime); + command.Parameters.AddWithValue("@ctime_sort", NpgsqlDbType.Integer, ctime_sort); + command.Parameters.AddWithValue("@sender", NpgsqlDbType.Varchar, sender); + command.Parameters.AddWithValue("@receiver", NpgsqlDbType.Varchar, receiver); + command.Parameters.AddWithValue("@message", NpgsqlDbType.Text, message.Replace("\0", "")); + CheckNulls(command.Parameters); + command.ExecuteNonQuery(); + } + + private int SelectAprsMessageCtimeSort(NpgsqlConnection connection, DateTime currentTime) + { + int result = 0; + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT MAX(ctime_sort) FROM aprs_messages WHERE ctime = @ctime"; + command.Parameters.AddWithValue("@ctime", NpgsqlDbType.Timestamp, currentTime); + NpgsqlDataReader dataReader = command.ExecuteReader(); + if (dataReader.Read()) + { + if (!dataReader.IsDBNull(0)) + { + result = dataReader.GetInt32(0); + result++; + } + } + dataReader.Close(); + return result; + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/AprsStorage/InMemoryAprsData.cs b/MpePlugins/skyscraper5.Aprs/AprsStorage/InMemoryAprsData.cs new file mode 100644 index 0000000..4520e06 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/AprsStorage/InMemoryAprsData.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class InMemoryAprsData + { + public InMemoryAprsData(string callsign) + { + this.CallSign = callsign; + this.WeatherTimestamps = new HashSet(); + } + + public string CallSign { get; private set; } + public DateTime LastSeen { get; set; } + public double Longitude { get; set; } + public double Latitude { get; set; } + public string? Comment { get; set; } + public HashSet WeatherTimestamps { get; private set; } + public int? LocalStations { get; set; } + public int? MessageCount { get; set; } + public double? Course { get; set; } + public double? MagneticVariation { get; set; } + public double? Speed { get; set; } + public int TimesMoved { get; set; } + + protected bool Equals(InMemoryAprsData other) + { + return CallSign == other.CallSign; + } + + 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((InMemoryAprsData)obj); + } + + public override int GetHashCode() + { + return (CallSign != null ? CallSign.GetHashCode() : 0); + } + + public override string ToString() + { + return $"{nameof(CallSign)}: {CallSign}"; + } + } +} diff --git a/MpePlugins/skyscraper5.Aprs/LX9SESMessageReceiver.cs b/MpePlugins/skyscraper5.Aprs/LX9SESMessageReceiver.cs new file mode 100644 index 0000000..a24c0ed --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/LX9SESMessageReceiver.cs @@ -0,0 +1,105 @@ +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NmeaParser.Messages; +using skyscraper5.Aprs.AprsSharp; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Aprs +{ + internal class LX9SESMessageReceiver : IAprsMessageReciever + { + private readonly LX9SESStorage _storage; + public DateTime? currentTime; + private PluginLogMessage LogEvent; + + public LX9SESMessageReceiver(LX9SESStorage storage, PluginLogMessage pluginLogMessage) + { + this._storage = storage; + this.LogEvent = pluginLogMessage; + } + + public void OnAprsPosition(string packetSender, GeoCoordinate positionCoordinates, string? piComment) + { + if (!currentTime.HasValue) + return; + + if (positionCoordinates.Longitude == 0.0 || positionCoordinates.Latitude == 0.0) + return; + + if (_storage.AprsPosition(currentTime.Value, packetSender, positionCoordinates, piComment)) + { + LogEvent(String.Format("[{3}] {0} is at {1}, {2}", packetSender, positionCoordinates.Latitude, positionCoordinates.Longitude, "AprsPosition")); + } + } + + public void OnAprsWeatherReport(string packetSender, WeatherInfo wi) + { + if (!currentTime.HasValue) + return; + + string? wiComment = wi.Comment; + int indexOf = wi.Comment.IndexOf(' '); + wiComment = wiComment.Substring(indexOf + 1); + if (wi.Position != null) + { + if (_storage.AprsPosition(currentTime.Value, packetSender, wi.Position.Coordinates, wiComment)) + { + LogEvent(String.Format("[{3}] {0} is at {1}, {2}", packetSender, wi.Position.Coordinates.Latitude, wi.Position.Coordinates.Longitude,"AprsPosition")); + } + } + + if (_storage.AprsWeatherReport(currentTime.Value, packetSender, wi)) + { + LogEvent(String.Format("[{2}] {0}, {1}", packetSender, currentTime.Value, "AprsWeather")); + } + } + + public void OnAprsChatMessage(string sender, string receiver, string message) + { + if (!currentTime.HasValue) + return; + + _storage.AprsMessage(sender, currentTime.Value, receiver, message); + } + + public void OnAprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities) + { + if (!currentTime.HasValue) + return; + + if (_storage.AprsStationCapabilities(packetSender, stationCapabilities)) + { + LogEvent(String.Format("[{2}] {0} -> {1}", packetSender, stationCapabilities.Encode(), "AprsStationCapabilities")); + } + } + + public void OnAprsGpsSentence(string packetSender, NmeaMessage parsed) + { + if (!currentTime.HasValue) + return; + GeoCoordinate geoCoordinate; + + switch (parsed.MessageType) + { + case "GPRMC": + Rmc gprmc = (Rmc)parsed; + geoCoordinate = new GeoCoordinate(gprmc.Latitude, gprmc.Longitude); + bool conA = _storage.AprsPosition(currentTime.Value, packetSender, geoCoordinate, null); + bool conB = _storage.AprsGpsSentence(currentTime.Value, packetSender, gprmc.Course, gprmc.MagneticVariation, gprmc.Speed); + if (conA || conB) + { + LogEvent(String.Format("[{2}] {0}: {1}", packetSender, parsed.ToString(), "AprsGpsSentence")); + } + break; + default: + throw new NotImplementedException(parsed.MessageType); + } + } + + } +} diff --git a/MpePlugins/skyscraper5.Aprs/LX9SESPlugin.cs b/MpePlugins/skyscraper5.Aprs/LX9SESPlugin.cs new file mode 100644 index 0000000..efabbaa --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/LX9SESPlugin.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using Npgsql; +using skyscraper5.Aprs.AprsSharp; +using skyscraper5.Aprs.AprsStorage; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; + +namespace skyscraper5.Aprs +{ + [SkyscraperPlugin] + [PluginPriority(1)] + internal class Lx9SesPlugin : ISkyscraperMpePlugin + { + public void ConnectToStorage(object[] connector, PluginLogMessage logger) + { + bool skipNext = false; + foreach (object o in connector) + { + if (skipNext) + { + skipNext = false; + continue; + } + Type connectorType = o.GetType(); + if (connectorType == typeof(InMemoryPluginToken)) + { + _storage = new AprsInMemoryStorage((InMemoryPluginToken)o); + break; + } + else if (connectorType == typeof(NpgsqlConnectionStringBuilder)) + { + _storage = new AprsPostgresqlStorage((NpgsqlConnectionStringBuilder)o); + } + else + { + if (connectorType.Name.Equals("MinioClient")) + { + skipNext = true; + continue; + } + + if (connectorType.Name.Equals("DirectoryInfo")) + { + skipNext = true; + continue; + } + + throw new NotImplementedException(); + } + } + + messageReceiver = new LX9SESMessageReceiver(_storage, logger); + } + + public void SetContext(DateTime? currentDateTime) + { + messageReceiver.currentTime = currentDateTime; + } + + public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + if (internetHeader.Protocol == 0x11) + { + if (internetHeader.SourceAddress.Equals(_lx9sesSrc) && internetHeader.DestinationAddress.Equals(_lx9sesDst)) + { + return true; + } + } + + return false; + } + + public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + if (_aprsDecoder == null) + _aprsDecoder = new AprsDecoder(messageReceiver); + _aprsDecoder.OnIpv4PacketArrival(internetHeader, ipv4Packet); + } + + public bool StopProcessingAfterThis() + { + return true; + } + + private AprsDecoder _aprsDecoder; + private static readonly IPAddress _lx9sesDst = IPAddress.Parse("228.64.0.56"); + private static readonly IPAddress _lx9sesSrc = IPAddress.Parse("10.225.49.1"); + private LX9SESStorage _storage; + private LX9SESMessageReceiver messageReceiver; + + + } + + +} diff --git a/MpePlugins/skyscraper5.Aprs/LX9SESStorage.cs b/MpePlugins/skyscraper5.Aprs/LX9SESStorage.cs new file mode 100644 index 0000000..f5eee2b --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/LX9SESStorage.cs @@ -0,0 +1,21 @@ +using AprsSharp.Parsers.Aprs; +using GeoCoordinatePortable; +using skyscraper5.Aprs.AprsSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Aprs +{ + internal interface LX9SESStorage + { + bool AprsPosition(DateTime currentTime, string packetSender, GeoCoordinate positionCoordinates, string piComment); + bool AprsWeatherReport(DateTime currentTime, string packetSender, WeatherInfo wi); + bool AprsStationCapabilities(string packetSender, StationCapabilities stationCapabilities); + + bool AprsGpsSentence(DateTime currentTime, string packetSender, double? course, double? magneticVariation, double? speed); + void AprsMessage(string sender, DateTime currentTime, string receiver, string message); + } +} diff --git a/MpePlugins/skyscraper5.Aprs/skyscraper5.Aprs.csproj b/MpePlugins/skyscraper5.Aprs/skyscraper5.Aprs.csproj new file mode 100644 index 0000000..b83c3e9 --- /dev/null +++ b/MpePlugins/skyscraper5.Aprs/skyscraper5.Aprs.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + diff --git a/MpePlugins/skyscraper5.DNS/.gitignore b/MpePlugins/skyscraper5.DNS/.gitignore new file mode 100644 index 0000000..28b43be --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/.gitignore @@ -0,0 +1,3 @@ +/bin/Debug/net6.0 +/obj/Debug/net6.0 +/obj diff --git a/MpePlugins/skyscraper5.DNS/LongExtensions.cs b/MpePlugins/skyscraper5.DNS/LongExtensions.cs new file mode 100644 index 0000000..f7d53f3 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/LongExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS +{ + internal static class LongExtensions + { + public static DateTime ToUnixTime(this long unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(unixTimeStamp).ToLocalTime(); + return dateTime; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/NuGet.Config b/MpePlugins/skyscraper5.DNS/NuGet.Config new file mode 100644 index 0000000..29be6ac --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/NuGet.Config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MpePlugins/skyscraper5.DNS/PluginDns.cs b/MpePlugins/skyscraper5.DNS/PluginDns.cs new file mode 100644 index 0000000..5a20fe5 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/PluginDns.cs @@ -0,0 +1,87 @@ +using Newtonsoft.Json; +using skyscraper5.DNS.Protocol; +using skyscraper5.DNS.Protocol.ResourceRecords; +using skyscraper5.DNS.Protocol.ResourceRecords.Records; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS +{ + [SkyscraperPlugin] + internal class PluginDns : IDnsParser + { + public DnsParseResult Parse(byte[] data) + { + Header header = Header.FromArray(data); + if (!header.Response) + return new DnsParseResult(); + + Response response = Response.FromArray(data); + if (response.AnswerRecords.Count == 0) + return new DnsParseResult(); + + DnsParseResult result = new DnsParseResult(); + result.Records = new List(); + result.Records.AddRange(response.AnswerRecords.Where(x => x != null).Select(x => SanitizeDnsRecord(x))); + result.Records.AddRange(response.AdditionalRecords.Where(x => x != null).Select(x => SanitizeDnsRecord(x))); + + return result; + } + + private DnsRecord SanitizeDnsRecord(IResourceRecord record) + { + DnsRecord result = new DnsRecord(); + result.Domain = record.Name.ToString(); + result.RecordType = new Tuple((int)record.Type, record.Type.ToString()); + result.TTL = record.TimeToLive; + result.ResourceData = ResourceDataToString(record); + result.IP = ExtractIpAddress(record); + return result; + } + + private string ResourceDataToString(IResourceRecord record) + { + SimpleDnsRecord simpleDnsRecord = record as SimpleDnsRecord; + if (simpleDnsRecord != null) + { + return simpleDnsRecord.GetDataAsString(); + } + OptionRecord optionRecord = record as OptionRecord; + if (optionRecord != null) + { + string optionsJson = JsonConvert.SerializeObject(optionRecord.Options); + return optionsJson; + } + NamingAuthorityPointerResourceRecord namingAuthorityPointerResourceRecord = record as NamingAuthorityPointerResourceRecord; + if (namingAuthorityPointerResourceRecord != null) + { + string naprr = namingAuthorityPointerResourceRecord.Name.ToString(); + return naprr; + } + NameServerResourceRecord nameServerResourceRecord = record as NameServerResourceRecord; + if (nameServerResourceRecord != null) + { + string nsrr = nameServerResourceRecord.NSDomainName.ToString(); + return nsrr; + } + throw new NotImplementedException(record.GetType().Name.ToString()); + } + + private IPAddress ExtractIpAddress(IResourceRecord record) + { + IPAddressResourceRecord iparr = record as IPAddressResourceRecord; + if (iparr != null) + { + return iparr.IPAddress; + } + return null; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/PluginLoadHook.cs b/MpePlugins/skyscraper5.DNS/PluginLoadHook.cs new file mode 100644 index 0000000..71d1cee --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/PluginLoadHook.cs @@ -0,0 +1,18 @@ +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS +{ + [SkyscraperPlugin] + internal class PluginLoadHook : IPluginLoadHook + { + public void OnLoad() + { + Console.WriteLine("Load hook"); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/CharacterString.cs b/MpePlugins/skyscraper5.DNS/Protocol/CharacterString.cs new file mode 100644 index 0000000..eea7e74 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/CharacterString.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public class CharacterString + { + public static IList GetAllFromArray(byte[] message, int offset) + { + return GetAllFromArray(message, offset, out offset); + } + + public static IList GetAllFromArray(byte[] message, int offset, out int endOffset) + { + IList characterStrings = new List(); + + while (offset < message.Length) + { + characterStrings.Add(CharacterString.FromArray(message, offset, out offset)); + } + + endOffset = offset; + return characterStrings; + } + + public static CharacterString FromArray(byte[] message, int offset, out int endOffset) + { + if (message.Length < 1) + { + throw new ArgumentException("Empty message"); + } + + byte len = message[offset++]; + byte[] data = new byte[len]; + Buffer.BlockCopy(message, offset, data, 0, len); + endOffset = offset + len; + return new CharacterString(data); + } + + internal string ToUTF8() + { + return Encoding.UTF8.GetString(data); + } + + public CharacterString(byte[] data) + { + if (data.Length > MAX_SIZE) Array.Resize(ref data, MAX_SIZE); + this.data = data; + } + + private const int MAX_SIZE = byte.MaxValue; + private byte[] data; + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Domain.cs b/MpePlugins/skyscraper5.DNS/Protocol/Domain.cs new file mode 100644 index 0000000..1e73966 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Domain.cs @@ -0,0 +1,144 @@ +using skyscraper5.DNS.Protocol.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public class Domain : IComparable + { + public static Domain FromArray(byte[] message, int offset, out int endOffset) + { + IList labels = new List(); + bool endOffsetAssigned = false; + endOffset = 0; + byte lengthOrPointer; + HashSet visitedOffsetPointers = new HashSet(); + + while ((lengthOrPointer = message[offset++]) > 0) + { + // Two highest bits are set (pointer) + if (lengthOrPointer.GetBitValueAt(6, 2) == 3) + { + if (!endOffsetAssigned) + { + endOffsetAssigned = true; + endOffset = offset + 1; + } + + ushort pointer = lengthOrPointer.GetBitValueAt(0, 6); + offset = (pointer << 8) | message[offset]; + + if (visitedOffsetPointers.Contains(offset)) + { + throw new ArgumentException("Compression pointer loop detected"); + } + visitedOffsetPointers.Add(offset); + + continue; + } + + if (lengthOrPointer.GetBitValueAt(6, 2) != 0) + { + throw new IndexOutOfRangeException("Unexpected bit pattern in label length"); + } + + byte length = lengthOrPointer; + byte[] label = new byte[length]; + Array.Copy(message, offset, label, 0, length); + + labels.Add(label); + + offset += length; + } + + if (!endOffsetAssigned) + { + endOffset = offset; + } + + return new Domain(labels.ToArray()); + } + + public int CompareTo(Domain? other) + { + int length = Math.Min(labels.Length, other.labels.Length); + + for (int i = 0; i < length; i++) + { + int v = CompareTo(this.labels[i], other.labels[i]); + if (v != 0) return v; + } + + return this.labels.Length - other.labels.Length; + } + + private static int CompareTo(byte[] a, byte[] b) + { + int length = Math.Min(a.Length, b.Length); + + for (int i = 0; i < length; i++) + { + int v = CompareTo(a[i], b[i]); + if (v != 0) return v; + } + + return a.Length - b.Length; + } + + private static int CompareTo(byte a, byte b) + { + if (IsASCIIAlphabet(a) && IsASCIIAlphabet(b)) + { + a &= ASCII_UPPERCASE_MASK; + b &= ASCII_UPPERCASE_MASK; + } + + return a - b; + } + + private static bool IsASCIIAlphabet(byte b) + { + return (ASCII_UPPERCASE_FIRST <= b && b <= ASCII_UPPERCASE_LAST) || + (ASCII_LOWERCASE_FIRST <= b && b <= ASCII_LOWERCASE_LAST); + } + + private const byte ASCII_UPPERCASE_FIRST = 65; + private const byte ASCII_UPPERCASE_LAST = 90; + private const byte ASCII_LOWERCASE_FIRST = 97; + private const byte ASCII_LOWERCASE_LAST = 122; + private const byte ASCII_UPPERCASE_MASK = 223; + + public Domain(byte[][] labels) + { + this.labels = labels; + } + + private byte[][] labels; + + public int Size + { + get { return labels.Sum(l => l.Length) + labels.Length + 1; } + } + + public static Domain FromArray(byte[] message, int offset) + { + return FromArray(message, offset, out offset); + } + + [DebuggerStepThrough] + public string ToString(Encoding encoding) + { + return string.Join(".", labels.Select(label => encoding.GetString(label))); + } + + [DebuggerStepThrough] + public override string ToString() + { + return ToString(Encoding.ASCII); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Header.cs b/MpePlugins/skyscraper5.DNS/Protocol/Header.cs new file mode 100644 index 0000000..6bc3578 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Header.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DNS.Protocol.Utils; + +namespace skyscraper5.DNS.Protocol +{ + [Marshalling.Endian(Marshalling.Endianness.Big)] + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Header + { + public const int SIZE = 12; + + public static Header FromArray(byte[] header) + { + if (header.Length < SIZE) + { + Header dummy = new Header(); + dummy.Response = false; + return dummy; + } + + return Marshalling.Struct.GetStruct
(header, 0, SIZE); + } + + private ushort id; + + private byte flag0; + private byte flag1; + + // Question count: number of questions in the Question section + private ushort qdCount; + + // Answer record count: number of records in the Answer section + private ushort anCount; + + // Authority record count: number of records in the Authority section + private ushort nsCount; + + // Additional record count: number of records in the Additional section + private ushort arCount; + + public int Id + { + get { return id; } + set { id = (ushort)value; } + } + + public int QuestionCount + { + get { return qdCount; } + set { qdCount = (ushort)value; } + } + + public int AnswerRecordCount + { + get { return anCount; } + set { anCount = (ushort)value; } + } + + public int AuthorityRecordCount + { + get { return nsCount; } + set { nsCount = (ushort)value; } + } + + public int AdditionalRecordCount + { + get { return arCount; } + set { arCount = (ushort)value; } + } + + public bool Response + { + get { return Qr == 1; } + set { Qr = Convert.ToByte(value); } + } + + public OperationCode OperationCode + { + get { return (OperationCode)Opcode; } + set { Opcode = (byte)value; } + } + + public bool AuthorativeServer + { + get { return Aa == 1; } + set { Aa = Convert.ToByte(value); } + } + + public bool Truncated + { + get { return Tc == 1; } + set { Tc = Convert.ToByte(value); } + } + + public bool RecursionDesired + { + get { return Rd == 1; } + set { Rd = Convert.ToByte(value); } + } + + public bool RecursionAvailable + { + get { return Ra == 1; } + set { Ra = Convert.ToByte(value); } + } + + public bool AuthenticData + { + get { return Ad == 1; } + set { Ad = Convert.ToByte(value); } + } + + public bool CheckingDisabled + { + get { return Cd == 1; } + set { Cd = Convert.ToByte(value); } + } + + public ResponseCode ResponseCode + { + get { return (ResponseCode)RCode; } + set { RCode = (byte)value; } + } + + public int Size + { + get { return Header.SIZE; } + } + + public byte[] ToArray() + { + return Marshalling.Struct.GetBytes(this); + } + + public override string ToString() + { + return ObjectStringifier.New(this) + .AddAll() + .Remove(nameof(Size)) + .ToString(); + } + + // Query/Response Flag + private byte Qr + { + get { return Flag0.GetBitValueAt(7, 1); } + set { Flag0 = Flag0.SetBitValueAt(7, 1, value); } + } + + // Operation Code + private byte Opcode + { + get { return Flag0.GetBitValueAt(3, 4); } + set { Flag0 = Flag0.SetBitValueAt(3, 4, value); } + } + + // Authorative Answer Flag + private byte Aa + { + get { return Flag0.GetBitValueAt(2, 1); } + set { Flag0 = Flag0.SetBitValueAt(2, 1, value); } + } + + // Truncation Flag + private byte Tc + { + get { return Flag0.GetBitValueAt(1, 1); } + set { Flag0 = Flag0.SetBitValueAt(1, 1, value); } + } + + // Recursion Desired + private byte Rd + { + get { return Flag0.GetBitValueAt(0, 1); } + set { Flag0 = Flag0.SetBitValueAt(0, 1, value); } + } + + // Recursion Available + private byte Ra + { + get { return Flag1.GetBitValueAt(7, 1); } + set { Flag1 = Flag1.SetBitValueAt(7, 1, value); } + } + + // Zero (Reserved) + private byte Z + { + get { return Flag1.GetBitValueAt(6, 1); } + set { } + } + + // Authentic Data + private byte Ad + { + get { return Flag1.GetBitValueAt(5, 1); } + set { Flag1 = Flag1.SetBitValueAt(5, 1, value); } + } + + // Checking Disabled + private byte Cd + { + get { return Flag1.GetBitValueAt(4, 1); } + set { Flag1 = Flag1.SetBitValueAt(4, 1, value); } + } + + // Response Code + private byte RCode + { + get { return Flag1.GetBitValueAt(0, 4); } + set { Flag1 = Flag1.SetBitValueAt(0, 4, value); } + } + + private byte Flag0 + { + get { return flag0; } + set { flag0 = value; } + } + + private byte Flag1 + { + get { return flag1; } + set { flag1 = value; } + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/IMessageEntry.cs b/MpePlugins/skyscraper5.DNS/Protocol/IMessageEntry.cs new file mode 100644 index 0000000..2a1a478 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/IMessageEntry.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public interface IMessageEntry + { + Domain Name { get; } + RecordType Type { get; } + RecordClass Class { get; } + + int Size { get; } + byte[] ToArray(); + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/EndianAttribute.cs b/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/EndianAttribute.cs new file mode 100644 index 0000000..0446c96 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/EndianAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.Marshalling +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)] + public class EndianAttribute : Attribute + { + public EndianAttribute(Endianness endianness) + { + this.Endianness = endianness; + } + + public Endianness Endianness { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/Endianess.cs b/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/Endianess.cs new file mode 100644 index 0000000..24cbb99 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/Endianess.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.Marshalling +{ + public enum Endianness + { + Big, + Little, + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/Struct.cs b/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/Struct.cs new file mode 100644 index 0000000..a5f8938 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Marshalling/Struct.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.Marshalling +{ + internal class Struct + { + public static T GetStruct(byte[] data, int offset, int length) where T : struct + { + byte[] buffer = new byte[length]; + Array.Copy(data, offset, buffer, 0, buffer.Length); + + GCHandle handle = GCHandle.Alloc(ConvertEndian(buffer), GCHandleType.Pinned); + + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + } + finally + { + handle.Free(); + } + } + + private static byte[] ConvertEndian(byte[] data) + { + Type type = typeof(T); + FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + EndianAttribute endian = null; + + if (type.GetTypeInfo().IsDefined(typeof(EndianAttribute), false)) + { + endian = (EndianAttribute)type.GetTypeInfo().GetCustomAttributes(typeof(EndianAttribute), false).First(); + } + + foreach (FieldInfo field in fields) + { + if (endian == null && !field.IsDefined(typeof(EndianAttribute), false)) + { + continue; + } + + int offset = Marshal.OffsetOf(field.Name).ToInt32(); +#pragma warning disable 618 + int length = Marshal.SizeOf(field.FieldType); +#pragma warning restore 618 + endian = endian ?? (EndianAttribute)field.GetCustomAttributes(typeof(EndianAttribute), false).First(); + + if (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian || + endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian) + { + Array.Reverse(data, offset, length); + } + } + + return data; + } + + public static byte[] GetBytes(T obj) where T : struct + { + byte[] data = new byte[Marshal.SizeOf(obj)]; + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + + try + { + Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false); + return ConvertEndian(data); + } + finally + { + handle.Free(); + } + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/OperationCode.cs b/MpePlugins/skyscraper5.DNS/Protocol/OperationCode.cs new file mode 100644 index 0000000..d6d49f7 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/OperationCode.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public enum OperationCode + { + Query = 0, + IQuery, + Status, + // Reserved = 3 + Notify = 4, + Update, + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Question.cs b/MpePlugins/skyscraper5.DNS/Protocol/Question.cs new file mode 100644 index 0000000..3263f69 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Question.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public class Question + { + public static IList GetAllFromArray(byte[] message, int offset, int questionCount) + { + return GetAllFromArray(message, offset, questionCount, out offset); + } + + public static IList GetAllFromArray(byte[] message, int offset, int questionCount, out int endOffset) + { + IList questions = new List(questionCount); + + for (int i = 0; i < questionCount; i++) + { + try + { + questions.Add(FromArray(message, offset, out offset)); + } + catch (IndexOutOfRangeException ioore) + { + endOffset = offset; + return questions; + } + catch (ArgumentException ae) + { + endOffset = offset; + return questions; + } + } + + endOffset = offset; + return questions; + } + + public static Question FromArray(byte[] message, int offset, out int endOffset) + { + Domain domain = Domain.FromArray(message, offset, out offset); + Tail tail = Marshalling.Struct.GetStruct(message, offset, Tail.SIZE); + + endOffset = offset + Tail.SIZE; + + return new Question(domain, tail.Type, tail.Class); + } + + [Marshalling.Endian(Marshalling.Endianness.Big)] + [StructLayout(LayoutKind.Sequential, Pack = 2)] + private struct Tail + { + public const int SIZE = 4; + + private ushort type; + private ushort klass; + + public RecordType Type + { + get { return (RecordType)type; } + set { type = (ushort)value; } + } + + public RecordClass Class + { + get { return (RecordClass)klass; } + set { klass = (ushort)value; } + } + } + + private Domain domain; + private RecordType type; + private RecordClass klass; + + public Question(Domain domain, RecordType type = RecordType.A, RecordClass klass = RecordClass.IN) + { + this.domain = domain; + this.type = type; + this.klass = klass; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/RecordClass.cs b/MpePlugins/skyscraper5.DNS/Protocol/RecordClass.cs new file mode 100644 index 0000000..4b66615 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/RecordClass.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public enum RecordClass + { + IN = 1, + ANY = 255, + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/RecordType.cs b/MpePlugins/skyscraper5.DNS/Protocol/RecordType.cs new file mode 100644 index 0000000..cabc199 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/RecordType.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public enum RecordType + { + A = 1, + NS = 2, + CNAME = 5, + SOA = 6, + WKS = 11, + PTR = 12, + MX = 15, + TXT = 16, + AAAA = 28, + SRV = 33, + NAPTR = 35, + DNAME = 39, + OPT = 41, + DS = 43, + RRSIG = 46, + NSEC = 47, + NSEC3 = 50, + SVCB = 64, + HTTPS = 65, + ANY = 255, + DNSKEY = 48, + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/BaseResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/BaseResourceRecord.cs new file mode 100644 index 0000000..12d02af --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/BaseResourceRecord.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords +{ + public abstract class BaseResourceRecord : IResourceRecord + { + private IResourceRecord record; + + public BaseResourceRecord(IResourceRecord record) + { + this.record = record; + } + + public TimeSpan TimeToLive + { + get { return record.TimeToLive; } + } + + public int DataLength + { + get { return record.DataLength; } + } + + public byte[] Data + { + get { return record.Data; } + } + + public Domain Name + { + get { return record.Name; } + } + + + public int Size + { + get { return record.Size; } + } + + public RecordClass Class + { + get { return record.Class; } + } + + public RecordType Type + { + get { return record.Type; } + } + + public byte[] ToArray() + { + return record.ToArray(); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/IResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/IResourceRecord.cs new file mode 100644 index 0000000..7dae664 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/IResourceRecord.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords +{ + public interface IResourceRecord : IMessageEntry + { + TimeSpan TimeToLive { get; } + int DataLength { get; } + byte[] Data { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/CanonicalNameResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/CanonicalNameResourceRecord.cs new file mode 100644 index 0000000..c457518 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/CanonicalNameResourceRecord.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class CanonicalNameResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public CanonicalNameResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + CanonicalDomainName = Domain.FromArray(message, dataOffset); + } + + public Domain CanonicalDomainName { get; } + + public string GetDataAsString() + { + return CanonicalDomainName.ToString(); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DNameResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DNameResourceRecord.cs new file mode 100644 index 0000000..90921a7 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DNameResourceRecord.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + class DNameResourceRecord : BaseResourceRecord + { + public DNameResourceRecord(IResourceRecord record, byte[] message, int dataOffset) : base(record) + { + Dname = Domain.FromArray(Data, 0); + } + + public Domain Dname { get; private set; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DelegationSignerResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DelegationSignerResourceRecord.cs new file mode 100644 index 0000000..d8844cd --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DelegationSignerResourceRecord.cs @@ -0,0 +1,49 @@ +using Newtonsoft.Json; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class DelegationSignerResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public DelegationSignerResourceRecord(IResourceRecord record, byte[] message, int dataOffset) : base(record) + { + MemoryStream ms = new MemoryStream(Data, false); + KeyId = ms.ReadUInt16BE(); + Algorithm = (AlgorithmEnum)ms.ReadUInt8(); + DigestType = (DigestEnum)ms.ReadUInt8(); + Digest = ms.ReadBytes(ms.GetAvailableBytes()); + + } + + public ushort KeyId { get; } + public AlgorithmEnum Algorithm { get; } + public DigestEnum DigestType { get; } + public byte[] Digest { get; } + + public string GetDataAsString() + { + dynamic child = new + { + KeyId = KeyId, + Algorithm = Algorithm, + DigestType = DigestType, + Digest = Digest, + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + } + + public enum DigestEnum + { + SHA1 = 1, + SHA256 = 2, + GOST_R_34_11_94 = 3, + SHA384 = 4 + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DnsKeyResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DnsKeyResourceRecord.cs new file mode 100644 index 0000000..9f22dd7 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DnsKeyResourceRecord.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class DnsKeyResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public DnsKeyResourceRecord(IResourceRecord record, byte[] message, int dataOffset) : base(record) + { + MemoryStream ms = new MemoryStream(Data); + ushort flags = ms.ReadUInt16BE(); + KeySigningKey = (flags & 0x0001) != 0; + KeyRevoked = (flags & 0x0080) != 0; + ZoneKey = (flags & 0x0100) != 0; + Protocol = ms.ReadUInt8(); + Algorithm = ms.ReadUInt8(); + this.PublicKey = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public bool KeySigningKey { get; } + public bool KeyRevoked { get; } + public bool ZoneKey { get; } + public byte Protocol { get; } + public byte Algorithm { get; } + public byte[] PublicKey { get; } + + public string GetDataAsString() + { + dynamic child = new + { + KeySigningKey = KeySigningKey, + KeyRevoked = KeyRevoked, + ZoneKey = ZoneKey, + Protocol = Protocol, + Algorithm = Algorithm, + PublicKey = PublicKey + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DomainNamePointerRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DomainNamePointerRecord.cs new file mode 100644 index 0000000..851acec --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/DomainNamePointerRecord.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class DomainNamePointerRecord : BaseResourceRecord + { + public DomainNamePointerRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + DomainName = Domain.FromArray(Data, 0, out _); + } + + public Domain DomainName { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/GeneralPurposeServiceEndpointsRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/GeneralPurposeServiceEndpointsRecord.cs new file mode 100644 index 0000000..395470c --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/GeneralPurposeServiceEndpointsRecord.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using skyscraper5.DNS.Protocol.Marshalling; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class GeneralPurposeServiceEndpointsRecord : BaseResourceRecord, SimpleDnsRecord + { + + public GeneralPurposeServiceEndpointsRecord(ResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + if (BitConverter.IsLittleEndian) + (Data[0], Data[1]) = (Data[1], Data[0]); + SvcPriority = BitConverter.ToUInt16(Data, 0); + + int nextOffset = 2; + TargetName = Domain.FromArray(Data, 2, out nextOffset); + + MemoryStream ms = new MemoryStream(Data, false); + ms.Position = nextOffset; + while (ms.GetAvailableBytes() > 2) + { + ushort key = ms.ReadUInt16BE(); + int valueLength = ms.ReadUInt16BE(); + if (ms.GetAvailableBytes() < valueLength) + return; + byte[] value = ms.ReadBytes(valueLength); + if (SvcParams == null) + SvcParams = new List>(); + SvcParams.Add(new KeyValuePair(key, value)); + } + } + + public ushort SvcPriority { get; } + public Domain TargetName { get; } + + public List> SvcParams; + + public string GetDataAsString() + { + dynamic child = new + { + SvcPriority = SvcPriority, + TargetName = TargetName.ToString(), + SvcParams = SvcParams + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/HttpsSpecificServiceEndspointsRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/HttpsSpecificServiceEndspointsRecord.cs new file mode 100644 index 0000000..2e43c8f --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/HttpsSpecificServiceEndspointsRecord.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class HttpsSpecificServiceEndspointsRecord : BaseResourceRecord, SimpleDnsRecord + { + public HttpsSpecificServiceEndspointsRecord(ResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + if (BitConverter.IsLittleEndian) + (Data[0], Data[1]) = (Data[1], Data[0]); + SvcPriority = BitConverter.ToUInt16(Data, 0); + + int nextOffset = 2; + TargetName = Domain.FromArray(Data, 2, out nextOffset); + + MemoryStream ms = new MemoryStream(Data, false); + ms.Position = nextOffset; + while (ms.GetAvailableBytes() > 2) + { + ushort key = ms.ReadUInt16BE(); + int valueLength = ms.ReadUInt16BE(); + if (valueLength > ms.GetAvailableBytes()) + return; + byte[] value = ms.ReadBytes(valueLength); + if (SvcParams == null) + SvcParams = new List>(); + SvcParams.Add(new KeyValuePair(key, value)); + } + } + + public ushort SvcPriority { get; } + public Domain TargetName { get; } + public List> SvcParams { get; } + + public string GetDataAsString() + { + dynamic child = new + { + SvcPriority = SvcPriority, + TargetName = TargetName.ToString(), + SvcParams = SvcParams + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/IPAddressResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/IPAddressResourceRecord.cs new file mode 100644 index 0000000..84b7a45 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/IPAddressResourceRecord.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class IPAddressResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public IPAddressResourceRecord(IResourceRecord record) : base(record) + { + IPAddress = new IPAddress(Data); + } + + public IPAddress IPAddress { get; } + + public string GetDataAsString() + { + return IPAddress.ToString(); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/MailExchangeResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/MailExchangeResourceRecord.cs new file mode 100644 index 0000000..e0de395 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/MailExchangeResourceRecord.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class MailExchangeResourceRecord : BaseResourceRecord + { + private const int PREFERENCE_SIZE = 2; + + public MailExchangeResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + byte[] preference = new byte[MailExchangeResourceRecord.PREFERENCE_SIZE]; + Array.Copy(message, dataOffset, preference, 0, preference.Length); + + if (BitConverter.IsLittleEndian) + { + Array.Reverse(preference); + } + + dataOffset += MailExchangeResourceRecord.PREFERENCE_SIZE; + + Preference = BitConverter.ToUInt16(preference, 0); + ExchangeDomainName = Domain.FromArray(message, dataOffset); + } + + public int Preference { get; } + public Domain ExchangeDomainName { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NSec3Record.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NSec3Record.cs new file mode 100644 index 0000000..62498d8 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NSec3Record.cs @@ -0,0 +1,44 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class NSec3Record : BaseResourceRecord + { + public NSec3Record(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + MemoryStream ms = new MemoryStream(Data); + HashAlgorithm = (HashAlgorithmEnum)ms.ReadUInt8(); + NSec3Flags = ms.ReadUInt8(); + NSec3Iterations = ms.ReadUInt16BE(); + byte saltLength = ms.ReadUInt8(); + if (saltLength > 0) + Salt = ms.ReadBytes(8); + byte hashLength = ms.ReadUInt8(); + if (hashLength > 0) + if (ms.GetAvailableBytes() >= hashLength) + Hash = ms.ReadBytes(hashLength); + } + + public HashAlgorithmEnum HashAlgorithm { get; set; } + + private byte NSec3Flags; + + public ushort NSec3Iterations { get; } + + public bool AdditionalInsecureDelegationsAllowed => (NSec3Flags & 0x01) != 0; + + public byte[] Hash { get; } + public byte[] Salt { get; } + + public enum HashAlgorithmEnum : byte + { + SHA1 = 1 + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NSecResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NSecResourceRecord.cs new file mode 100644 index 0000000..53f95ed --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NSecResourceRecord.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class NSecResourceRecord : BaseResourceRecord + { + public NSecResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + NextDomainName = Domain.FromArray(Data, 0); + } + + public Domain NextDomainName { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NameServerResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NameServerResourceRecord.cs new file mode 100644 index 0000000..f0536e4 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NameServerResourceRecord.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class NameServerResourceRecord : BaseResourceRecord + { + public NameServerResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + NSDomainName = Domain.FromArray(message, dataOffset); + } + + public Domain NSDomainName { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NamingAuthorityPointerResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NamingAuthorityPointerResourceRecord.cs new file mode 100644 index 0000000..be55c2a --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/NamingAuthorityPointerResourceRecord.cs @@ -0,0 +1,34 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + class NamingAuthorityPointerResourceRecord : BaseResourceRecord + { + public NamingAuthorityPointerResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + MemoryStream ms = new MemoryStream(Data); + Order = ms.ReadUInt16BE(); + Preferences = ms.ReadUInt16BE(); + byte flagsLength = ms.ReadUInt8(); + Flags = ms.ReadBytes(flagsLength); + byte serviceLength = ms.ReadUInt8(); + Service = Encoding.UTF8.GetString(ms.ReadBytes(serviceLength)); + byte regexLength = ms.ReadUInt8(); + Regex = ms.ReadBytes(regexLength); + Replacement = Domain.FromArray(Data, (int)ms.Position); + } + + public ushort Order { get; } + public ushort Preferences { get; } + public byte[] Flags { get; } + public string Service { get; } + public byte[] Regex { get; } + public Domain Replacement { get; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/OptionRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/OptionRecord.cs new file mode 100644 index 0000000..154ec4c --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/OptionRecord.cs @@ -0,0 +1,29 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + internal class OptionRecord : BaseResourceRecord + { + public OptionRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + MemoryStream ms = new MemoryStream(Data, false); + while (ms.GetAvailableBytes() > 0) + { + ushort optionCode = ms.ReadUInt16BE(); + ushort optionLength = ms.ReadUInt16BE(); + byte[] optionData = ms.ReadBytes(optionLength); + if (Options == null) + Options = new List>(); + Options.Add(new KeyValuePair(optionCode, optionData)); + } + } + + public List> Options { get; private set; } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/PointerResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/PointerResourceRecord.cs new file mode 100644 index 0000000..fd8610b --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/PointerResourceRecord.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class PointerResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public PointerResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + PointerDomainName = Domain.FromArray(message, dataOffset); + } + + public Domain PointerDomainName { get; } + + public string GetDataAsString() + { + return PointerDomainName.ToString(); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/ResourceRecordSignatureRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/ResourceRecordSignatureRecord.cs new file mode 100644 index 0000000..ab2bc66 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/ResourceRecordSignatureRecord.cs @@ -0,0 +1,77 @@ +using Newtonsoft.Json; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class ResourceRecordSignatureRecord : BaseResourceRecord, SimpleDnsRecord + { + public ResourceRecordSignatureRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + MemoryStream ms = new MemoryStream(Data, false); + TypeCovered = ms.ReadUInt16BE(); + Algorithm = (AlgorithmEnum)ms.ReadUInt8(); + Labels = ms.ReadUInt8(); + OriginalTtl = new TimeSpan(0, 0, (int)ms.ReadUInt32BE()); + + SignatureExpiration = ((long)ms.ReadUInt32BE()).ToUnixTime(); + SignatureInception = ((long)ms.ReadUInt32BE()).ToUnixTime(); + KeyTag = ms.ReadUInt16BE(); + + int newPos = 0; + SignersName = Domain.FromArray(Data, (int)ms.Position, out newPos); + ms.Position = newPos; + + Signature = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public ushort TypeCovered { get; } + + public AlgorithmEnum Algorithm { get; } + public byte Labels { get; } + public TimeSpan OriginalTtl { get; } + public DateTime SignatureExpiration { get; } + public DateTime SignatureInception { get; } + public ushort KeyTag { get; } + public Domain SignersName { get; private set; } + public byte[] Signature { get; } + + public string GetDataAsString() + { + dynamic child = new + { + TypeCovered = TypeCovered, + Algorithm = Algorithm, + Labels = Labels, + OriginalTtl = OriginalTtl, + SignatureExpiration = SignatureExpiration, + SignatureInception = SignatureInception, + KeyTag = KeyTag, + SignersName = SignersName, + Signature = Signature + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + } + + public enum AlgorithmEnum : byte + { + DSA_SHA1 = 3, + RSA_SHA1 = 5, + DSA_SHA1_NSEC3 = 6, + RSA_SHA1_NSEC3 = 7, + RSA_SHA256 = 8, + RSA_SHA512 = 10, + GOST_R_34102001 = 12, + ECDSA_CURVE_P256_SHA256 = 13, + ECDSA_CURVE_P384_SHA384 = 14, + ED25519 = 15, + ED448 = 16 + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/ServiceResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/ServiceResourceRecord.cs new file mode 100644 index 0000000..c16754d --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/ServiceResourceRecord.cs @@ -0,0 +1,72 @@ +using Newtonsoft.Json; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class ServiceResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public ServiceResourceRecord(IResourceRecord record, byte[] message, int dataOffset) : base(record) + { + Head head = Marshalling.Struct.GetStruct(message, dataOffset, Head.SIZE); + + Priority = head.Priority; + Weight = head.Weight; + Port = head.Port; + Target = Domain.FromArray(message, dataOffset + Head.SIZE); + } + + [Marshalling.Endian(Marshalling.Endianness.Big)] + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct Head + { + public const int SIZE = 6; + + private ushort priority; + private ushort weight; + private ushort port; + + public ushort Priority + { + get { return priority; } + set { priority = value; } + } + + public ushort Weight + { + get { return weight; } + set { weight = value; } + } + + public ushort Port + { + get { return port; } + set { port = value; } + } + } + + public ushort Priority { get; } + public ushort Weight { get; } + public ushort Port { get; } + public Domain Target { get; } + + public string GetDataAsString() + { + dynamic child = new + { + Priority = Priority, + Weight = Weight, + Port = Port, + Target = Target.ToString() + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/StartOfAuthorityResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/StartOfAuthorityResourceRecord.cs new file mode 100644 index 0000000..5bb45fd --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/StartOfAuthorityResourceRecord.cs @@ -0,0 +1,97 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class StartOfAuthorityResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public StartOfAuthorityResourceRecord(IResourceRecord record, byte[] message, int dataOffset) + : base(record) + { + MasterDomainName = Domain.FromArray(message, dataOffset, out dataOffset); + ResponsibleDomainName = Domain.FromArray(message, dataOffset, out dataOffset); + + Options tail = Marshalling.Struct.GetStruct(message, dataOffset, Options.SIZE); + + SerialNumber = tail.SerialNumber; + RefreshInterval = tail.RefreshInterval; + RetryInterval = tail.RetryInterval; + ExpireInterval = tail.ExpireInterval; + MinimumTimeToLive = tail.MinimumTimeToLive; + } + + public Domain MasterDomainName { get; } + public Domain ResponsibleDomainName { get; } + public long SerialNumber { get; } + public TimeSpan RefreshInterval { get; } + public TimeSpan RetryInterval { get; } + public TimeSpan ExpireInterval { get; } + public TimeSpan MinimumTimeToLive { get; } + + public string GetDataAsString() + { + dynamic child = new + { + MasterDomainName = MasterDomainName.ToString(), + ResponsibleDomainName = ResponsibleDomainName.ToString(), + SerialNumber = SerialNumber, + RefreshInterval = RefreshInterval, + RetryInterval = RetryInterval, + ExpireInterval = ExpireInterval, + MinimumTimeToLive = MinimumTimeToLive + }; + string result = JsonConvert.SerializeObject(child); + return result; + } + + [Marshalling.Endian(Marshalling.Endianness.Big)] + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct Options + { + public const int SIZE = 20; + + private uint serialNumber; + private uint refreshInterval; + private uint retryInterval; + private uint expireInterval; + private uint ttl; + + public long SerialNumber + { + get { return serialNumber; } + set { serialNumber = (uint)value; } + } + + public TimeSpan RefreshInterval + { + get { return TimeSpan.FromSeconds(refreshInterval); } + set { refreshInterval = (uint)value.TotalSeconds; } + } + + public TimeSpan RetryInterval + { + get { return TimeSpan.FromSeconds(retryInterval); } + set { retryInterval = (uint)value.TotalSeconds; } + } + + public TimeSpan ExpireInterval + { + get { return TimeSpan.FromSeconds(expireInterval); } + set { expireInterval = (uint)value.TotalSeconds; } + } + + public TimeSpan MinimumTimeToLive + { + get { return TimeSpan.FromSeconds(ttl); } + set { ttl = (uint)value.TotalSeconds; } + } + } + + + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/TextResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/TextResourceRecord.cs new file mode 100644 index 0000000..e9b6956 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/Records/TextResourceRecord.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords.Records +{ + public class TextResourceRecord : BaseResourceRecord, SimpleDnsRecord + { + public TextResourceRecord(IResourceRecord record) : + base(record) + { + TextData = CharacterString.GetAllFromArray(Data, 0); + } + + public IList TextData { get; } + + public string GetDataAsString() + { + string[] strings = TextData.Select(x => x.ToUTF8()).ToArray(); + string value = JsonSerializer.Serialize(strings); + return value; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/ResourceRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/ResourceRecord.cs new file mode 100644 index 0000000..00fc048 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/ResourceRecord.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.ResourceRecords +{ + internal class ResourceRecord : IResourceRecord + { + public TimeSpan TimeToLive + { + get { return ttl; } + } + + public int DataLength + { + get { return data.Length; } + } + + public byte[] Data + { + get { return data; } + } + + public Domain Name + { + get { return domain; } + } + + public RecordType Type + { + get { return type; } + } + + + public RecordClass Class + { + get { return klass; } + } + + public int Size + { + get { return domain.Size + Tail.SIZE + data.Length; } + } + + + public byte[] ToArray() { + throw new NotImplementedException(); + } + + private Domain domain; + private RecordType type; + private RecordClass klass; + private TimeSpan ttl; + private byte[] data; + + [Marshalling.Endian(Marshalling.Endianness.Big)] + [StructLayout(LayoutKind.Sequential, Pack = 2)] + private struct Tail + { + public const int SIZE = 10; + + private ushort type; + private ushort klass; + private uint ttl; + private ushort dataLength; + + public RecordType Type + { + get { return (RecordType)type; } + set { type = (ushort)value; } + } + + public RecordClass Class + { + get { return (RecordClass)klass; } + set { klass = (ushort)value; } + } + + public TimeSpan TimeToLive + { + get { return TimeSpan.FromSeconds(ttl); } + set { ttl = (uint)value.TotalSeconds; } + } + + public int DataLength + { + get { return dataLength; } + set { dataLength = (ushort)value; } + } + } + + public static ResourceRecord FromArray(byte[] message, int offset, out int endOffset) + { + Domain domain = Domain.FromArray(message, offset, out offset); + Tail tail = Marshalling.Struct.GetStruct(message, offset, Tail.SIZE); + + byte[] data = new byte[tail.DataLength]; + + offset += Tail.SIZE; + Array.Copy(message, offset, data, 0, data.Length); + + endOffset = offset + data.Length; + + return new ResourceRecord(domain, data, tail.Type, tail.Class, tail.TimeToLive); + } + + public ResourceRecord(Domain domain, byte[] data, RecordType type, + RecordClass klass = RecordClass.IN, TimeSpan ttl = default(TimeSpan)) + { + this.domain = domain; + this.type = type; + this.klass = klass; + this.ttl = ttl; + this.data = data; + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/ResourceRecordFactory.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/ResourceRecordFactory.cs new file mode 100644 index 0000000..4389f58 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResourceRecords/ResourceRecordFactory.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DNS.Protocol.ResourceRecords.Records; + +namespace skyscraper5.DNS.Protocol.ResourceRecords +{ + internal class ResourceRecordFactory + { + public static IList GetAllFromArray(byte[] message, int offset, int count, out int endOffset) + { + IList result = new List(count); + + for (int i = 0; i < count; i++) + { + try + { + result.Add(FromArray(message, offset, out offset)); + } + catch (ArgumentException ae) + { + endOffset = offset; + return result; + } + catch (IndexOutOfRangeException ioore) + { + endOffset = offset; + return result; + } + } + + endOffset = offset; + return result; + } + + public static IResourceRecord FromArray(byte[] message, int offset, out int endOffest) + { + ResourceRecord record = ResourceRecord.FromArray(message, offset, out endOffest); + int dataOffset = endOffest - record.DataLength; + + if (record.Type == 0) + return null; + + if (record.DataLength == 0) + return null; + + switch (record.Type) + { + case RecordType.A: + case RecordType.AAAA: + return new IPAddressResourceRecord(record); + case RecordType.NS: + return new NameServerResourceRecord(record, message, dataOffset); + case RecordType.CNAME: + return new CanonicalNameResourceRecord(record, message, dataOffset); + case RecordType.SOA: + return new StartOfAuthorityResourceRecord(record, message, dataOffset); + case RecordType.PTR: + return new PointerResourceRecord(record, message, dataOffset); + case RecordType.MX: + return new MailExchangeResourceRecord(record, message, dataOffset); + case RecordType.TXT: + return new TextResourceRecord(record); + case RecordType.SRV: + return new ServiceResourceRecord(record, message, dataOffset); + case RecordType.SVCB: + return new GeneralPurposeServiceEndpointsRecord(record, message, dataOffset); + case RecordType.HTTPS: + return new HttpsSpecificServiceEndspointsRecord(record, message, dataOffset); + case RecordType.RRSIG: + return new ResourceRecordSignatureRecord(record, message, dataOffset); + case RecordType.OPT: + return new OptionRecord(record, message, dataOffset); + case RecordType.NSEC3: + return new NSec3Record(record, message, dataOffset); + case RecordType.NAPTR: + return new NamingAuthorityPointerResourceRecord(record, message, dataOffset); + case RecordType.NSEC: + return new NSecResourceRecord(record, message, dataOffset); + case RecordType.DNAME: + return new DNameResourceRecord(record, message, dataOffset); + case RecordType.DS: + return new DelegationSignerResourceRecord(record, message, dataOffset); + case RecordType.DNSKEY: + return new DnsKeyResourceRecord(record, message, dataOffset); + case(RecordType)32768: + throw new NotImplementedException(); + case (RecordType)32769: + throw new NotImplementedException(); + default: + int type = (int)record.Type; + if (type > 259) + return null; + else + throw new NotImplementedException(record.Type.ToString()); + } + } + + + + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Response.cs b/MpePlugins/skyscraper5.DNS/Protocol/Response.cs new file mode 100644 index 0000000..96cc4c3 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Response.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.PortableExecutable; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DNS.Protocol.ResourceRecords; + +namespace skyscraper5.DNS.Protocol +{ + public class Response + { + public static Response FromArray(byte[] message) + { + Header header = Header.FromArray(message); + int offset = header.Size; + + if (!header.Response) + { + throw new ArgumentException("Invalid response message"); + } + + if (header.Truncated) + { + return new Response(header, + Question.GetAllFromArray(message, offset, header.QuestionCount), + new List(), + new List(), + new List()); + } + + return new Response(header, + Question.GetAllFromArray(message, offset, header.QuestionCount, out offset), + ResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset), + ResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset), + ResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out offset)); + } + + public Response(Header header, IList questions, IList answers, + IList authority, IList additional) + { + this.header = header; + this.questions = questions; + this.answers = answers; + this.authority = authority; + this.additional = additional; + } + + private Header header; + private IList questions; + private IList answers; + private IList authority; + private IList additional; + + public IList Questions + { + get { return questions; } + } + + public IList AnswerRecords + { + get { return answers; } + } + + public IList AdditionalRecords + { + get { return additional; } + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/ResponseCode.cs b/MpePlugins/skyscraper5.DNS/Protocol/ResponseCode.cs new file mode 100644 index 0000000..5a922ec --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/ResponseCode.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + public enum ResponseCode + { + NoError = 0, + FormatError, + ServerFailure, + NameError, + NotImplemented, + Refused, + YXDomain, + YXRRSet, + NXRRSet, + NotAuth, + NotZone, + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/SimpleDnsRecord.cs b/MpePlugins/skyscraper5.DNS/Protocol/SimpleDnsRecord.cs new file mode 100644 index 0000000..d434244 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/SimpleDnsRecord.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol +{ + interface SimpleDnsRecord + { + public string GetDataAsString(); + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Utils/ByteExtensions.cs b/MpePlugins/skyscraper5.DNS/Protocol/Utils/ByteExtensions.cs new file mode 100644 index 0000000..6aac73e --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Utils/ByteExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.Utils +{ + internal static class ByteExtensions + { + public static byte GetBitValueAt(this byte b, byte offset, byte length) + { + return (byte)((b >> offset) & ~(0xff << length)); + } + + public static byte SetBitValueAt(this byte b, byte offset, byte length, byte value) + { + int mask = ~(0xff << length); + value = (byte)(value & mask); + + return (byte)((value << offset) | (b & ~(mask << offset))); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/Protocol/Utils/ObjectStringifier.cs b/MpePlugins/skyscraper5.DNS/Protocol/Utils/ObjectStringifier.cs new file mode 100644 index 0000000..104b06e --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/Protocol/Utils/ObjectStringifier.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DNS.Protocol.Utils +{ + internal class ObjectStringifier + { + public static ObjectStringifier New(object obj) + { + return new ObjectStringifier(obj); + } + + private object obj; + private Dictionary pairs; + + public ObjectStringifier(object obj) + { + this.obj = obj; + this.pairs = new Dictionary(); + } + + public ObjectStringifier AddAll() + { + PropertyInfo[] properties = obj.GetType().GetProperties( + BindingFlags.Public | BindingFlags.Instance); + + foreach (PropertyInfo property in properties) + { + object value = property.GetValue(obj, new object[] { }); + pairs.Add(property.Name, StringifyObject(value)); + } + + return this; + } + + private static string StringifyObject(object obj) + { + if (obj is string) + { + return (string)obj; + } + else if (obj is IDictionary) + { + return StringifyDictionary((IDictionary)obj); + } + else if (obj is IEnumerable) + { + return StringifyList((IEnumerable)obj); + } + else + { + return obj == null ? "null" : obj.ToString(); + } + } + + private static string StringifyDictionary(IDictionary dict) + { + StringBuilder result = new StringBuilder(); + + result.Append("{"); + + foreach (DictionaryEntry pair in dict) + { + result + .Append(pair.Key) + .Append("=") + .Append(StringifyObject(pair.Value)) + .Append(", "); + } + + if (result.Length > 1) + { + result.Remove(result.Length - 2, 2); + } + + return result.Append("}").ToString(); + } + + private static string StringifyList(IEnumerable enumerable) + { + return "[" + string.Join(", ", enumerable.Cast().Select(o => StringifyObject(o)).ToArray()) + "]"; + } + + public ObjectStringifier Remove(params string[] names) + { + foreach (string name in names) + { + pairs.Remove(name); + } + + return this; + } + + public override string ToString() + { + return StringifyDictionary(pairs); + } + } +} diff --git a/MpePlugins/skyscraper5.DNS/README.md b/MpePlugins/skyscraper5.DNS/README.md new file mode 100644 index 0000000..e066050 --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/README.md @@ -0,0 +1,9 @@ += skyscraper5.DNS = +This is a backport of kapetan's DNS Library to .NET 6.0 +The original source is found at https://github.com/kapetan/dns + +The following changes were made: +* Removed all dependencies to .NET Standard 2.0 +* Added a plugin infrastructure to interface with skyscraper5 +* Added SVCB Records +* Added HTTPS Records \ No newline at end of file diff --git a/MpePlugins/skyscraper5.DNS/skyscraper5.DNS.csproj b/MpePlugins/skyscraper5.DNS/skyscraper5.DNS.csproj new file mode 100644 index 0000000..959a41e --- /dev/null +++ b/MpePlugins/skyscraper5.DNS/skyscraper5.DNS.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..a2740cb --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/0x80_ArdVpsDescriptor.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/0x80_ArdVpsDescriptor.cs new file mode 100644 index 0000000..e7bfa51 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/0x80_ArdVpsDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.PrivateDataSpecifiers.ArdZdfOrf; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Ard +{ + [SkyscraperPlugin] + [UserDefinedDescriptor(5,0x82,"EIT")] + [DescriptorPluginEitHandler(typeof(EitHandler))] + class ArdVpsDescriptor : TsDescriptor + { + public ArdVpsDescriptor(byte[] buffer) + { + if (buffer.Length != 13) + return; + + VpsString = Encoding.UTF8.GetString(buffer); + } + + public string VpsString { get; private set; } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/EitHandler.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/EitHandler.cs new file mode 100644 index 0000000..545421b --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/EitHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Ard; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.PrivateDataSpecifiers.ArdZdfOrf +{ + internal class EitHandler : DescriptorPluginEitHandler + { + public void HandleEit(EitEvent outputEvent, TsDescriptor unpackedDescriptor) + { + byte descriptorId = unpackedDescriptor.GetDescriptorId(); + switch (descriptorId) + { + case 0x82: + ArdVpsDescriptor vpsDescriptor = (ArdVpsDescriptor)unpackedDescriptor; + outputEvent.VpsString = vpsDescriptor.VpsString; + break; + default: + throw new NotImplementedException(descriptorId.ToString()); + } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf.csproj b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf.csproj new file mode 100644 index 0000000..959a41e --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf/skyscraper5.PrivateDataSpecifiers.ArdZdfOrf.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/0x83_LogicalChannelDescriptor.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/0x83_LogicalChannelDescriptor.cs new file mode 100644 index 0000000..fab3135 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/0x83_LogicalChannelDescriptor.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.PrivateDataSpecifiers.Eacem; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Eacem.Descriptors +{ + //The official EICTA/EACEM specification is not available, so this is guesswork. + //Guesses taken from https://confindustriaradiotv.it/wp-content/uploads/2021/03/HD-Z-BOOK-Final-1.0.pdf + [SkyscraperPlugin] + [UserDefinedDescriptor(0x00000028, 0x83,"NIT","BAT")] + [BannedTable("SDT")] // <-- According to a document from ziggo. + [DescriptorPluginNitHandler(typeof(NitHook))] + [DescriptorPluginBatHandler(typeof(BatHook))] + class EacemLogicalChannelDescriptor : TsDescriptor + { + public EacemLogicalChannelDescriptor(byte[] buffer) + { + LogicalChannels = new LogicalChannelNumberEntry[buffer.Length / 4]; + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < LogicalChannels.Length; i++) + { + LogicalChannelNumberEntry lcn = new LogicalChannelNumberEntry(); + LogicalChannels[i] = lcn; + + lcn.ServiceId = ms.ReadUInt16BE(); + ushort readUInt16Be = ms.ReadUInt16BE(); + lcn.VisibleServiceFlag = (readUInt16Be & 0x8000) != 0; + lcn.LogicalChannelNumber = (readUInt16Be & 0x03ff); + } + } + + public LogicalChannelNumberEntry[] LogicalChannels { get; private set; } + + public class LogicalChannelNumberEntry : BaseLcn + { + public bool VisibleServiceFlag { get; set; } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/BatHook.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/BatHook.cs new file mode 100644 index 0000000..69fb73b --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/BatHook.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Eacem.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.PrivateDataSpecifiers.Eacem +{ + internal class BatHook : DescriptorPluginBatHandler + { + public void HandleBat(BatTransportStream outputTs, TsDescriptor unpackedDescriptor) + { + EacemLogicalChannelDescriptor elcn = (EacemLogicalChannelDescriptor)unpackedDescriptor; + if (outputTs.LCNs == null) + outputTs.LCNs = new List(); + outputTs.LCNs.AddRange(elcn.LogicalChannels); + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/NitHook.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/NitHook.cs new file mode 100644 index 0000000..5cf5f0e --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/NitHook.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Eacem.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.PrivateDataSpecifiers.Eacem +{ + internal class NitHook : DescriptorPluginNitHandler + { + public void HandleNit(NitTransportStream output, TsDescriptor unpacked) + { + EacemLogicalChannelDescriptor elcd = (EacemLogicalChannelDescriptor)unpacked; + output.LogicalChannels.AddRange(elcd.LogicalChannels); + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/skyscraper5.PrivateDataSpecifiers.Eacem.csproj b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/skyscraper5.PrivateDataSpecifiers.Eacem.csproj new file mode 100644 index 0000000..8839f64 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Eacem/skyscraper5.PrivateDataSpecifiers.Eacem.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/0x83_LogicalChannelDescriptor.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/0x83_LogicalChannelDescriptor.cs new file mode 100644 index 0000000..54d9237 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/0x83_LogicalChannelDescriptor.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.PrivateDataSpecifiers.Nordig; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Nordig.Descriptor +{ + [SkyscraperPlugin] + [UserDefinedDescriptor(0x00000029, 0x83, "NIT","BAT")] + [DescriptorPluginNitHandler(typeof(NordigNitHook))] + [DescriptorPluginBatHandler(typeof(NordigBatHook))] + class LogicalChannelDescriptor : TsDescriptor + { + public LogicalChannelDescriptor(byte[] buffer) + { + LogicalChannels = new LogicalChannelNumberEntry[buffer.Length / 4]; + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < LogicalChannels.Length; i++) + { + LogicalChannels[i] = new LogicalChannelNumberEntry(); + LogicalChannels[i].ServiceId = ms.ReadUInt16BE(); + LogicalChannels[i].LogicalChannelNumber = (ms.ReadUInt16BE() & 0x03ff); + } + } + + public LogicalChannelNumberEntry[] LogicalChannels { get; private set; } + + public class LogicalChannelNumberEntry : BaseLcn + { + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/0x87_LogicalChannelDescriptorVersion2.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/0x87_LogicalChannelDescriptorVersion2.cs new file mode 100644 index 0000000..5dcc81a --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/0x87_LogicalChannelDescriptorVersion2.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.PrivateDataSpecifiers.Nordig; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Nordig.Descriptor +{ + [SkyscraperPlugin] + [UserDefinedDescriptor(0x00000029, 0x87, "NIT")] + [DescriptorPluginNitHandler(typeof(NordigNitHook))] + class LogicalChannelDescriptorVersion2 : TsDescriptor + { + public LogicalChannelDescriptorVersion2(byte[] buffer) + { + LogicalChannels = new LogicalChannelNumberEntry[buffer.Length / 4]; + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < LogicalChannels.Length; i++) + { + LogicalChannels[i] = new LogicalChannelNumberEntry(); + LogicalChannels[i].ServiceId = ms.ReadUInt16BE(); + LogicalChannels[i].LogicalChannelNumber = (ms.ReadUInt16BE() & 0x03ff); + } + } + + public LogicalChannelNumberEntry[] LogicalChannels { get; private set; } + + public class LogicalChannelNumberEntry : BaseLcn + { + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/NordigBatHook.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/NordigBatHook.cs new file mode 100644 index 0000000..cb0fc1a --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/NordigBatHook.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Nordig.Descriptor; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.PrivateDataSpecifiers.Nordig +{ + internal class NordigBatHook : DescriptorPluginBatHandler + { + public void HandleBat(BatTransportStream outputTs, TsDescriptor unpackedDescriptor) + { + byte descId = unpackedDescriptor.GetDescriptorId(); + switch (descId) + { + case 0x83: + LogicalChannelDescriptor lcd = (LogicalChannelDescriptor)unpackedDescriptor; + if (outputTs.LCNs == null) + outputTs.LCNs = new List(); + outputTs.LCNs.AddRange(lcd.LogicalChannels); + break; + default: + throw new NotImplementedException(String.Format("{0:X2}", descId)); + } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/NordigNitHook.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/NordigNitHook.cs new file mode 100644 index 0000000..2cffd7f --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/NordigNitHook.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Nordig.Descriptor; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.PrivateDataSpecifiers.Nordig +{ + internal class NordigNitHook : DescriptorPluginNitHandler + { + public void HandleNit(NitTransportStream output, TsDescriptor unpacked) + { + byte descriptorId = unpacked.GetDescriptorId(); + switch (descriptorId) + { + case 0x83: + LogicalChannelDescriptor lcd = (LogicalChannelDescriptor)unpacked; + output.LogicalChannels.AddRange(lcd.LogicalChannels); + break; + case 0x87: + LogicalChannelDescriptorVersion2 lcdv2 = (LogicalChannelDescriptorVersion2)unpacked; + output.LogicalChannels.AddRange(lcdv2.LogicalChannels); + break; + default: + throw new NotImplementedException(String.Format("{0:X2}", descriptorId)); + } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/skyscraper5.PrivateDataSpecifiers.Nordig.csproj b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/skyscraper5.PrivateDataSpecifiers.Nordig.csproj new file mode 100644 index 0000000..959a41e --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.Nordig/skyscraper5.PrivateDataSpecifiers.Nordig.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/0x83_NorDigPrivateLogicalChannelDescriptor.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/0x83_NorDigPrivateLogicalChannelDescriptor.cs new file mode 100644 index 0000000..1de8091 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/0x83_NorDigPrivateLogicalChannelDescriptor.cs @@ -0,0 +1,38 @@ +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.PrivateDataSpecifiers.SimpliTV; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.SimpliTV.Descriptors +{ + [SkyscraperPlugin] + [UserDefinedDescriptor(0x1b0,0x83,"BAT")] + [DescriptorPluginBatHandler(typeof(BatHook))] + class NorDigPrivateLogicalChannelDescriptor : TsDescriptor + { + public NorDigPrivateLogicalChannelDescriptor(byte[] buffer) + { + LogicalChannels = new LogicalChannelNumberEntry[buffer.Length / 4]; + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < LogicalChannels.Length; i++) + { + LogicalChannelNumberEntry lcn = new LogicalChannelNumberEntry(); + LogicalChannels[i] = lcn; + + lcn.ServiceId = ms.ReadUInt16BE(); + ushort readUInt16Be = ms.ReadUInt16BE(); + lcn.VisibleServiceFlag = (readUInt16Be & 0x8000) != 0; + lcn.LogicalChannelNumber = (readUInt16Be & 0x3fff); + } + } + + public LogicalChannelNumberEntry[] LogicalChannels { get; private set; } + + public class LogicalChannelNumberEntry : BaseLcn + { + public bool VisibleServiceFlag { get; set; } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/BatHook.cs b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/BatHook.cs new file mode 100644 index 0000000..1b3cce2 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/BatHook.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.SimpliTV.Descriptors; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.PrivateDataSpecifiers.SimpliTV +{ + internal class BatHook : DescriptorPluginBatHandler + { + public void HandleBat(BatTransportStream outputTs, TsDescriptor unpackedDescriptor) + { + NorDigPrivateLogicalChannelDescriptor ndplcn = (NorDigPrivateLogicalChannelDescriptor)unpackedDescriptor; + if (outputTs.LCNs == null) + outputTs.LCNs = new List(); + outputTs.LCNs.AddRange(ndplcn.LogicalChannels); + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/skyscraper5.PrivateDataSpecifiers.SimpliTV.csproj b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/skyscraper5.PrivateDataSpecifiers.SimpliTV.csproj new file mode 100644 index 0000000..8839f64 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper5.PrivateDataSpecifiers.SimpliTV/skyscraper5.PrivateDataSpecifiers.SimpliTV.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/skyscraper8.Tests/Properties/Resources.Designer.cs b/skyscraper8.Tests/Properties/Resources.Designer.cs new file mode 100644 index 0000000..b7614cd --- /dev/null +++ b/skyscraper8.Tests/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace skyscraper8.Tests.Properties { + using System; + + + /// + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("skyscraper8.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Sucht eine lokalisierte Ressource vom Typ System.Byte[]. + /// + internal static byte[] ranging_response_test { + get { + object obj = ResourceManager.GetObject("ranging_response_test", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/skyscraper8.Tests/Properties/Resources.resx b/skyscraper8.Tests/Properties/Resources.resx new file mode 100644 index 0000000..29ae772 --- /dev/null +++ b/skyscraper8.Tests/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\ranging_response_test.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/skyscraper8.Tests/RangingResponseTest.cs b/skyscraper8.Tests/RangingResponseTest.cs new file mode 100644 index 0000000..4f94ac2 --- /dev/null +++ b/skyscraper8.Tests/RangingResponseTest.cs @@ -0,0 +1,28 @@ +using skyscraper5.Docsis.MacManagement; +using System.Net.NetworkInformation; +using skyscraper8.Tests.Properties; + +namespace skyscraper8.Tests +{ + public class RangingResponseTest + { + [Fact] + public void OffsetBreakerTest() + { + + byte[] blob = Resources.ranging_response_test; + Random rng = new Random(); + + byte[] sourceMacBuffer = new byte[6]; + rng.NextBytes(sourceMacBuffer); + PhysicalAddress sourceAddress = new PhysicalAddress(sourceMacBuffer); + + byte[] targetMacBuffer = new byte[6]; + rng.NextBytes(targetMacBuffer); + PhysicalAddress targetAddress = new PhysicalAddress(targetMacBuffer); + + RangingResponse rangingResponse = new RangingResponse(sourceAddress, targetAddress, blob); + Assert.True(rangingResponse.Valid); + } + } +} diff --git a/skyscraper8.Tests/Resources/ranging_response_test.bin b/skyscraper8.Tests/Resources/ranging_response_test.bin new file mode 100644 index 0000000..4b4b8b7 Binary files /dev/null and b/skyscraper8.Tests/Resources/ranging_response_test.bin differ diff --git a/skyscraper8.Tests/skyscraper8.Tests.csproj b/skyscraper8.Tests/skyscraper8.Tests.csproj new file mode 100644 index 0000000..d1bd80c --- /dev/null +++ b/skyscraper8.Tests/skyscraper8.Tests.csproj @@ -0,0 +1,48 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/skyscraper8.sln b/skyscraper8.sln new file mode 100644 index 0000000..a08fca2 --- /dev/null +++ b/skyscraper8.sln @@ -0,0 +1,191 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35931.197 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper8", "skyscraper8\skyscraper8.csproj", "{8DAAE3A2-72EA-4908-8F62-911D293043E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MpePlugins", "MpePlugins", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Aprs", "MpePlugins\skyscraper5.Aprs\skyscraper5.Aprs.csproj", "{E52A1723-7193-EC3C-7371-01A3BB58E0EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gimmicks", "Gimmicks", "{E00647B6-4509-4A1C-A7CB-D0C72325D23E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Gimmicks.DvbTBatchCapture", "Gimmicks\skyscraper5.Gimmicks.DvbTBatchCapture\skyscraper5.Gimmicks.DvbTBatchCapture.csproj", "{1015ACF1-E812-5410-66A5-E92DE682D2C6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IoPlugins", "IoPlugins", "{1A70B363-5274-4BDB-911E-4D688684C3A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.IO.StreamReader", "IoPlugins\skyscraper5.IO.StreamReader\skyscraper5.IO.StreamReader.csproj", "{C4262F19-0648-A1A1-3D62-748A45BC5329}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.IO.StreamReader.RemoteStreamReaderServer", "IoPlugins\skyscraper5.IO.StreamReader.RemoteStreamReaderServer\skyscraper5.IO.StreamReader.RemoteStreamReaderServer.csproj", "{1B8696DA-8A81-8F1F-9463-49C727959EDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.LyngsatMapsScraper", "Gimmicks\skyscraper5.LyngsatMapsScraper\skyscraper5.LyngsatMapsScraper.csproj", "{68EB7299-FC65-654E-B972-D79514B91BAD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GpsPlugins", "GpsPlugins", "{C1469289-C28B-49B6-9B4A-DDAA9EC3BA88}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.NmeaSharpWrapper", "GpsPlugins\skyscraper5.NmeaSharpWrapper\skyscraper5.NmeaSharpWrapper.csproj", "{60F519BA-49C0-7C7B-E26F-E4313A9B1697}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PrivateDataSpecifiers", "PrivateDataSpecifiers", "{56729C39-B90E-4DF3-A557-DB93436FB5FF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.PrivateDataSpecifiers.ArdZdfOrf", "PrivateDataSpecifiers\skyscraper5.PrivateDataSpecifiers.ArdZdfOrf\skyscraper5.PrivateDataSpecifiers.ArdZdfOrf.csproj", "{71CFF3DD-4F6C-3A21-B2C3-2F3A59D4FB4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.PrivateDataSpecifiers.Eacem", "PrivateDataSpecifiers\skyscraper5.PrivateDataSpecifiers.Eacem\skyscraper5.PrivateDataSpecifiers.Eacem.csproj", "{AD0F65A7-3257-BB3B-46F5-C16BFDF9DBCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.PrivateDataSpecifiers.Nordig", "PrivateDataSpecifiers\skyscraper5.PrivateDataSpecifiers.Nordig\skyscraper5.PrivateDataSpecifiers.Nordig.csproj", "{868A7453-E1B7-A619-F447-41E42420405C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.PrivateDataSpecifiers.SimpliTV", "PrivateDataSpecifiers\skyscraper5.PrivateDataSpecifiers.SimpliTV\skyscraper5.PrivateDataSpecifiers.SimpliTV.csproj", "{2296DA0C-AD28-DB06-1984-13B4AE5DE61D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BlobStorages", "BlobStorages", "{F3E36695-72EB-4E93-9B07-15AE40C8F9E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Data.Minio", "BlobStorages\skyscraper5.Data.Minio\skyscraper5.Data.Minio.csproj", "{96D43A77-C1DA-16EF-2084-BA939C0BEB4D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TabularStorages", "TabularStorages", "{CDAECB5B-E4A5-4287-85CA-6D65085A091E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper8.Data.MySql", "DataTableStorages\skyscraper5.Data.MySql\skyscraper8.Data.MySql.csproj", "{17C2E095-B952-8919-2C68-F2EAA7A601C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Data.PostgreSql", "DataTableStorages\skyscraper5.Data.PostgreSql\skyscraper5.Data.PostgreSql.csproj", "{07214423-32B2-DDE8-8D38-511D55ADC206}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.RSR.IQTester", "Gimmicks\skyscraper5.RSR.IQTester\skyscraper5.RSR.IQTester.csproj", "{0E4C7079-228A-B94C-379A-8A14B08BAB34}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StorageFactories", "StorageFactories", "{21CE40E6-0C04-4994-9353-F8A4208B6DA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Storage.MariaDbMinio", "FactoryStorages\skyscraper5.Storage.MariaDbMinio\skyscraper5.Storage.MariaDbMinio.csproj", "{11572C79-1D7F-5609-C7D6-8E32130E3901}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Storage.PostgresqlMinio", "FactoryStorages\skyscraper5.Storage.PostgresqlMinio\skyscraper5.Storage.PostgresqlMinio.csproj", "{B3930CC1-4877-FC74-8CD3-CFED82DB7474}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.DNS", "MpePlugins\skyscraper5.DNS\skyscraper5.DNS.csproj", "{3B52C24E-58E2-F982-F9B7-B9E7465B82A2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.Gimmicks.IptvCollector", "Gimmicks\skyscraper5.Gimmicks.IptvCollector\skyscraper5.Gimmicks.IptvCollector.csproj", "{8F17668C-623C-F9B3-EAD4-2922E5414B75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper8.Tests", "skyscraper8.Tests\skyscraper8.Tests.csproj", "{84EE9FCD-2C7F-DF84-C1BA-99D018CE9412}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UIs", "UIs", "{E23457C5-3A34-48EE-8107-C91E2C174B2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper5.UI.WindowsForms", "GUIs\skyscraper5.UI\skyscraper5.UI.WindowsForms.csproj", "{46CACA1C-F9B2-2FE0-2068-716F381325E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "skyscraper8.UI.ImGui", "GUIs\skyscraper8.UI.ImGui\skyscraper8.UI.ImGui.csproj", "{BDBDB7A9-D0A4-9B89-0801-2935B2066551}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8DAAE3A2-72EA-4908-8F62-911D293043E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DAAE3A2-72EA-4908-8F62-911D293043E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DAAE3A2-72EA-4908-8F62-911D293043E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DAAE3A2-72EA-4908-8F62-911D293043E8}.Release|Any CPU.Build.0 = Release|Any CPU + {E52A1723-7193-EC3C-7371-01A3BB58E0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E52A1723-7193-EC3C-7371-01A3BB58E0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E52A1723-7193-EC3C-7371-01A3BB58E0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E52A1723-7193-EC3C-7371-01A3BB58E0EA}.Release|Any CPU.Build.0 = Release|Any CPU + {1015ACF1-E812-5410-66A5-E92DE682D2C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1015ACF1-E812-5410-66A5-E92DE682D2C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1015ACF1-E812-5410-66A5-E92DE682D2C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1015ACF1-E812-5410-66A5-E92DE682D2C6}.Release|Any CPU.Build.0 = Release|Any CPU + {C4262F19-0648-A1A1-3D62-748A45BC5329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4262F19-0648-A1A1-3D62-748A45BC5329}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4262F19-0648-A1A1-3D62-748A45BC5329}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4262F19-0648-A1A1-3D62-748A45BC5329}.Release|Any CPU.Build.0 = Release|Any CPU + {1B8696DA-8A81-8F1F-9463-49C727959EDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B8696DA-8A81-8F1F-9463-49C727959EDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B8696DA-8A81-8F1F-9463-49C727959EDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B8696DA-8A81-8F1F-9463-49C727959EDA}.Release|Any CPU.Build.0 = Release|Any CPU + {68EB7299-FC65-654E-B972-D79514B91BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68EB7299-FC65-654E-B972-D79514B91BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68EB7299-FC65-654E-B972-D79514B91BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68EB7299-FC65-654E-B972-D79514B91BAD}.Release|Any CPU.Build.0 = Release|Any CPU + {60F519BA-49C0-7C7B-E26F-E4313A9B1697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60F519BA-49C0-7C7B-E26F-E4313A9B1697}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60F519BA-49C0-7C7B-E26F-E4313A9B1697}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60F519BA-49C0-7C7B-E26F-E4313A9B1697}.Release|Any CPU.Build.0 = Release|Any CPU + {71CFF3DD-4F6C-3A21-B2C3-2F3A59D4FB4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71CFF3DD-4F6C-3A21-B2C3-2F3A59D4FB4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71CFF3DD-4F6C-3A21-B2C3-2F3A59D4FB4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71CFF3DD-4F6C-3A21-B2C3-2F3A59D4FB4A}.Release|Any CPU.Build.0 = Release|Any CPU + {AD0F65A7-3257-BB3B-46F5-C16BFDF9DBCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD0F65A7-3257-BB3B-46F5-C16BFDF9DBCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD0F65A7-3257-BB3B-46F5-C16BFDF9DBCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD0F65A7-3257-BB3B-46F5-C16BFDF9DBCC}.Release|Any CPU.Build.0 = Release|Any CPU + {868A7453-E1B7-A619-F447-41E42420405C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {868A7453-E1B7-A619-F447-41E42420405C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {868A7453-E1B7-A619-F447-41E42420405C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {868A7453-E1B7-A619-F447-41E42420405C}.Release|Any CPU.Build.0 = Release|Any CPU + {2296DA0C-AD28-DB06-1984-13B4AE5DE61D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2296DA0C-AD28-DB06-1984-13B4AE5DE61D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2296DA0C-AD28-DB06-1984-13B4AE5DE61D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2296DA0C-AD28-DB06-1984-13B4AE5DE61D}.Release|Any CPU.Build.0 = Release|Any CPU + {96D43A77-C1DA-16EF-2084-BA939C0BEB4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96D43A77-C1DA-16EF-2084-BA939C0BEB4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96D43A77-C1DA-16EF-2084-BA939C0BEB4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96D43A77-C1DA-16EF-2084-BA939C0BEB4D}.Release|Any CPU.Build.0 = Release|Any CPU + {17C2E095-B952-8919-2C68-F2EAA7A601C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17C2E095-B952-8919-2C68-F2EAA7A601C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17C2E095-B952-8919-2C68-F2EAA7A601C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17C2E095-B952-8919-2C68-F2EAA7A601C3}.Release|Any CPU.Build.0 = Release|Any CPU + {07214423-32B2-DDE8-8D38-511D55ADC206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07214423-32B2-DDE8-8D38-511D55ADC206}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07214423-32B2-DDE8-8D38-511D55ADC206}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07214423-32B2-DDE8-8D38-511D55ADC206}.Release|Any CPU.Build.0 = Release|Any CPU + {0E4C7079-228A-B94C-379A-8A14B08BAB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E4C7079-228A-B94C-379A-8A14B08BAB34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E4C7079-228A-B94C-379A-8A14B08BAB34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E4C7079-228A-B94C-379A-8A14B08BAB34}.Release|Any CPU.Build.0 = Release|Any CPU + {11572C79-1D7F-5609-C7D6-8E32130E3901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11572C79-1D7F-5609-C7D6-8E32130E3901}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11572C79-1D7F-5609-C7D6-8E32130E3901}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11572C79-1D7F-5609-C7D6-8E32130E3901}.Release|Any CPU.Build.0 = Release|Any CPU + {B3930CC1-4877-FC74-8CD3-CFED82DB7474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3930CC1-4877-FC74-8CD3-CFED82DB7474}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3930CC1-4877-FC74-8CD3-CFED82DB7474}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3930CC1-4877-FC74-8CD3-CFED82DB7474}.Release|Any CPU.Build.0 = Release|Any CPU + {3B52C24E-58E2-F982-F9B7-B9E7465B82A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B52C24E-58E2-F982-F9B7-B9E7465B82A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B52C24E-58E2-F982-F9B7-B9E7465B82A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B52C24E-58E2-F982-F9B7-B9E7465B82A2}.Release|Any CPU.Build.0 = Release|Any CPU + {8F17668C-623C-F9B3-EAD4-2922E5414B75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F17668C-623C-F9B3-EAD4-2922E5414B75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F17668C-623C-F9B3-EAD4-2922E5414B75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F17668C-623C-F9B3-EAD4-2922E5414B75}.Release|Any CPU.Build.0 = Release|Any CPU + {84EE9FCD-2C7F-DF84-C1BA-99D018CE9412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84EE9FCD-2C7F-DF84-C1BA-99D018CE9412}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84EE9FCD-2C7F-DF84-C1BA-99D018CE9412}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84EE9FCD-2C7F-DF84-C1BA-99D018CE9412}.Release|Any CPU.Build.0 = Release|Any CPU + {46CACA1C-F9B2-2FE0-2068-716F381325E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46CACA1C-F9B2-2FE0-2068-716F381325E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46CACA1C-F9B2-2FE0-2068-716F381325E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46CACA1C-F9B2-2FE0-2068-716F381325E9}.Release|Any CPU.Build.0 = Release|Any CPU + {BDBDB7A9-D0A4-9B89-0801-2935B2066551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDBDB7A9-D0A4-9B89-0801-2935B2066551}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDBDB7A9-D0A4-9B89-0801-2935B2066551}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDBDB7A9-D0A4-9B89-0801-2935B2066551}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E52A1723-7193-EC3C-7371-01A3BB58E0EA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {1015ACF1-E812-5410-66A5-E92DE682D2C6} = {E00647B6-4509-4A1C-A7CB-D0C72325D23E} + {C4262F19-0648-A1A1-3D62-748A45BC5329} = {1A70B363-5274-4BDB-911E-4D688684C3A3} + {1B8696DA-8A81-8F1F-9463-49C727959EDA} = {1A70B363-5274-4BDB-911E-4D688684C3A3} + {68EB7299-FC65-654E-B972-D79514B91BAD} = {E00647B6-4509-4A1C-A7CB-D0C72325D23E} + {60F519BA-49C0-7C7B-E26F-E4313A9B1697} = {C1469289-C28B-49B6-9B4A-DDAA9EC3BA88} + {71CFF3DD-4F6C-3A21-B2C3-2F3A59D4FB4A} = {56729C39-B90E-4DF3-A557-DB93436FB5FF} + {AD0F65A7-3257-BB3B-46F5-C16BFDF9DBCC} = {56729C39-B90E-4DF3-A557-DB93436FB5FF} + {868A7453-E1B7-A619-F447-41E42420405C} = {56729C39-B90E-4DF3-A557-DB93436FB5FF} + {2296DA0C-AD28-DB06-1984-13B4AE5DE61D} = {56729C39-B90E-4DF3-A557-DB93436FB5FF} + {96D43A77-C1DA-16EF-2084-BA939C0BEB4D} = {F3E36695-72EB-4E93-9B07-15AE40C8F9E4} + {17C2E095-B952-8919-2C68-F2EAA7A601C3} = {CDAECB5B-E4A5-4287-85CA-6D65085A091E} + {07214423-32B2-DDE8-8D38-511D55ADC206} = {CDAECB5B-E4A5-4287-85CA-6D65085A091E} + {0E4C7079-228A-B94C-379A-8A14B08BAB34} = {E00647B6-4509-4A1C-A7CB-D0C72325D23E} + {11572C79-1D7F-5609-C7D6-8E32130E3901} = {21CE40E6-0C04-4994-9353-F8A4208B6DA2} + {B3930CC1-4877-FC74-8CD3-CFED82DB7474} = {21CE40E6-0C04-4994-9353-F8A4208B6DA2} + {3B52C24E-58E2-F982-F9B7-B9E7465B82A2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {8F17668C-623C-F9B3-EAD4-2922E5414B75} = {E00647B6-4509-4A1C-A7CB-D0C72325D23E} + {46CACA1C-F9B2-2FE0-2068-716F381325E9} = {E23457C5-3A34-48EE-8107-C91E2C174B2D} + {BDBDB7A9-D0A4-9B89-0801-2935B2066551} = {E23457C5-3A34-48EE-8107-C91E2C174B2D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5147EFA3-3D4E-4FDE-8A36-5840E8F1B80E} + EndGlobalSection +EndGlobal diff --git a/skyscraper8/Aac/AacTestProgram.cs b/skyscraper8/Aac/AacTestProgram.cs new file mode 100644 index 0000000..ec8faad --- /dev/null +++ b/skyscraper8/Aac/AacTestProgram.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper.Utils; + +namespace skyscraper5.src.Aac +{ + internal class AacTestProgram + { + public AacTestProgram() + { + FileInfo fi = new FileInfo(@"C:\Users\Sascha Schiemann\Documents\wdr_radio_test.ts"); + fileStream = fi.OpenRead(); + tsContext = new TsContext(); + } + + private FileStream fileStream; + private TsContext tsContext; + + public void Run() + { + PesDecoder pesDecoder = new PesDecoder(new PesDumper()); + tsContext.RegisterPacketProcessor(0x0277, pesDecoder); //0x0277 -> WDR 4 + byte[] buffer = new byte[188]; + while (fileStream.Read(buffer, 0, 188) == 188) + { + tsContext.PushPacket(buffer); + } + fileStream.Close(); + } + } +} diff --git a/skyscraper8/Abertis/AbertisDecoder.cs b/skyscraper8/Abertis/AbertisDecoder.cs new file mode 100644 index 0000000..d1128c8 --- /dev/null +++ b/skyscraper8/Abertis/AbertisDecoder.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Abertis +{ + internal class AbertisDecoder : ITsPacketProcessor, IPayloadUnitDecoder + { + public int Pid { get; } + public AbertisDecoderEventHandler EventHandler { get; } + + public AbertisDecoder(int pid, AbertisDecoderEventHandler eventHandler) + { + this.Pid = pid; + PacketLoss(); + this.EventHandler = eventHandler; + } + + private bool inSync; + private short bytesNeeded; + private byte[] packetBuffer; + + + + public void PushPacket(TsPacket packet) + { + byte[] packetPayload = packet.Payload; + + MemoryStream ms = new MemoryStream(packetPayload); + if (packet.AdaptionFieldControl == 3) + { + ms.ReadByte(); + } + + int syncLossCheck = 0; + while (ms.GetAvailableBytes() > 0) + { + if (!inSync) + { + byte syncByte = ms.ReadUInt8(); + if (syncByte == 'G') + { + ms.Position--; + inSync = true; + if (syncLossCheck != 0 && ms.Position != syncLossCheck) + { + EventHandler.OnAbertisSyncLoss(Pid, syncLossCheck, ms.Position); + } + } + } + else + { + long readChunkSize = Math.Min(bytesNeeded, ms.GetAvailableBytes()); + int readResult = ms.Read(packetBuffer, 188 - bytesNeeded, (int)readChunkSize); + bytesNeeded -= (short)readResult; + if (bytesNeeded == 0) + { + DeliverPacket(packetBuffer, 0, 188); + Array.Clear(packetBuffer, 0, 188); + bytesNeeded = 188; + inSync = false; + syncLossCheck = (int)ms.Position; + } + } + } + } + + public void PacketLoss() + { + packetBuffer = new byte[188]; + bytesNeeded = 188; + inSync = false; + } + + private Queue packetQueue; + private void DeliverPacket(byte[] buffer, int offset, int length) + { + if (length != 188) + throw new ArgumentOutOfRangeException(nameof(length), length, String.Format("{0} != {1}", nameof(length), 188)); + + byte[] deliverPacketBuffer = new byte[188]; + Array.Copy(buffer, offset, deliverPacketBuffer, 0, 188); + + if (deliverPacketBuffer[0] == 'G') + { + //We'll use a queue to ensure that the stream type autodetection has enough time to decide whether this is the right stream type. + if (packetQueue == null) + packetQueue = new Queue(); + packetQueue.Enqueue(deliverPacketBuffer); + if (packetQueue.Count >= 100) + EventHandler.OnAbertisPacket(Pid, packetQueue.Dequeue()); + } + else + { + EventHandler.OnAbertisSyncLoss(Pid); + PacketLoss(); + } + } + } +} diff --git a/skyscraper8/Abertis/AbertisDecoderEventHandler.cs b/skyscraper8/Abertis/AbertisDecoderEventHandler.cs new file mode 100644 index 0000000..9225b8a --- /dev/null +++ b/skyscraper8/Abertis/AbertisDecoderEventHandler.cs @@ -0,0 +1,8 @@ +namespace skyscraper5.Abertis +{ + internal interface AbertisDecoderEventHandler + { + void OnAbertisPacket(int pid, byte[] outBuffer); + void OnAbertisSyncLoss(int pid, int oldPosition = -1, long newPosition = -1); + } +} diff --git a/skyscraper8/Abertis/AbertisDemoProgram.cs b/skyscraper8/Abertis/AbertisDemoProgram.cs new file mode 100644 index 0000000..def6fd9 --- /dev/null +++ b/skyscraper8/Abertis/AbertisDemoProgram.cs @@ -0,0 +1,42 @@ +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; + +namespace skyscraper5.Abertis +{ + internal class AbertisDemoProgram : AbertisDecoderEventHandler + { + public AbertisDemoProgram(FileInfo fi) + { + this.fi = fi; + } + + private FileInfo fi; + + public void Run() + { + TsContext ts = new TsContext(); + AbertisDecoder decoder = new AbertisDecoder(0x02be,this); + ts.RegisterPacketProcessor(0x02be, decoder); + + ConsoleEventLogger logger = new ConsoleEventLogger(); + InMemoryScraperStorage scraper = new InMemoryScraperStorage(); + SkyscraperContext context = new SkyscraperContext(ts, logger, scraper); + FileStream fileStream = fi.OpenRead(); + context.IngestFromStream(fileStream); + } + + private FileStream outStream; + public void OnAbertisPacket(int pid, byte[] outBuffer) + { + if (outStream == null) + outStream = File.OpenWrite("abertis2.ts"); + outStream.Write(outBuffer, 0, 188); + } + + public void OnAbertisSyncLoss(int pid, int oldPosition = -1, long newPosition = -1) + { + } + } +} diff --git a/skyscraper8/Docsis/AnnexC/CommonTlvEncodings.cs b/skyscraper8/Docsis/AnnexC/CommonTlvEncodings.cs new file mode 100644 index 0000000..e2784fd --- /dev/null +++ b/skyscraper8/Docsis/AnnexC/CommonTlvEncodings.cs @@ -0,0 +1,135 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Intrinsics; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Docsis.AnnexC +{ + class CommonTlvEncodingObject + { + public CommonTlvEncodingObject(MemoryStream ms) + { + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte len = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(len); + switch (type) + { + case 5: + ModemCapabilitiesEncoding = new ModemCapabilitiesEncoding(v); + break; + case 22: + UpstreamDropPacketClassificationEncoding = new GeneralPacketClassifierEncoding(v); + break; + case 23: + DownstreamPacketClassificationEncoding = new GeneralPacketClassifierEncoding(v); + break; + case 24: + if (UpstreamServiceFlows == null) + UpstreamServiceFlows = new List(); + UpstreamServiceFlows.Add(new GeneralServiceFlowEncoding(v)); + break; + case 25: + if (DownstreamServiceFlows == null) + DownstreamServiceFlows = new List(); + DownstreamServiceFlows.Add(new GeneralServiceFlowEncoding(v)); + break; + case 27: + HmacDigest = v; + break; + case 31: + KeySequenceNumber = v[0]; + break; + case 46: + TransmitChannelConfiguration = new TransmitChannelConfigurationObject(v); + break; + case 47: + ServiceFlowSidClusterAssignment = new ServiceFlowSidClusterAssignmentObject(v); + break; + case 49: + RcpId = new RcpIdEncoding(v); + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf page 652 + throw new NotImplementedException(string.Format("Common TLV Encodings Type {0}", type)); + } + } + } + + public ModemCapabilitiesEncoding ModemCapabilitiesEncoding { get; private set; } + public ServiceFlowSidClusterAssignmentObject ServiceFlowSidClusterAssignment { get; private set; } + public GeneralPacketClassifierEncoding DownstreamPacketClassificationEncoding { get; set; } + + public GeneralPacketClassifierEncoding UpstreamDropPacketClassificationEncoding { get; set; } + + public TransmitChannelConfigurationObject TransmitChannelConfiguration { get; set; } + public class TransmitChannelConfigurationObject + { + public TransmitChannelConfigurationObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + TccReferenceId = v[0]; + break; + case 2: + UpstreamChannelAction = (UpstreamChannelActionEnum)v[0]; + break; + case 3: + UpstreamChannelId = v[0]; + break; + case 6: + (v[0], v[1]) = (v[1], v[0]); + RangingSid = BitConverter.ToUInt16(v, 0); + break; + case 11: + ListOfIucs = v; + break; + default: + //see CM-SP-MULPIv4.0-I01-190815.pdf page 713 + throw new NotImplementedException(string.Format("TCC TLV Type {0}", type)); + } + } + } + + public byte[] ListOfIucs { get; set; } + + public ushort? RangingSid { get; set; } + + public byte? UpstreamChannelId { get; set; } + + public UpstreamChannelActionEnum? UpstreamChannelAction { get; private set; } + public enum UpstreamChannelActionEnum : byte + { + NoAction = 0, + Add = 1, + Change = 2, + Delete = 3, + Replace = 4, + Rerange = 5 + } + + public byte? TccReferenceId { get; set; } + } + + public byte KeySequenceNumber { get; set; } + + public byte[] HmacDigest { get; set; } + + public List DownstreamServiceFlows { get; set; } + + public List UpstreamServiceFlows { get; } + public RcpIdEncoding RcpId { get; private set; } + } +} diff --git a/skyscraper8/Docsis/AnnexC/GeneralPacketClassifierEncoding.cs b/skyscraper8/Docsis/AnnexC/GeneralPacketClassifierEncoding.cs new file mode 100644 index 0000000..81f28e9 --- /dev/null +++ b/skyscraper8/Docsis/AnnexC/GeneralPacketClassifierEncoding.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis.AnnexC +{ + internal class GeneralPacketClassifierEncoding + { + public GeneralPacketClassifierEncoding(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + ClassifierReference = v[0]; + break; + case 2: + (v[0], v[1]) = (v[1], v[0]); + ClassifierIdentifier = BitConverter.ToUInt16(v, 0); + break; + case 3: + (v[0], v[1]) = (v[1], v[0]); + ServiceFlowReference = BitConverter.ToUInt16(v, 0); + break; + case 4: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + ServiceFlowIdentifier = BitConverter.ToUInt32(v, 0); + break; + case 5: + RulePriority = v[0]; + break; + case 6: + ClassifierActivationState = v[0] != 0; + break; + case 7: + DynamicServiceChangeAction = (DynamicServiceChangeActionEnum)v[0]; + break; + case 9: + Ipv4PacketClassification = new Ipv4PacketClassificationEncodings(v); + break; + default: + //see CM-SP-MULPIv4.0-I01-190815.pdf page 742->743 + throw new NotImplementedException(string.Format("{0} TLV Type {1}", nameof(GeneralPacketClassifierEncoding), type)); + } + } + } + + public DynamicServiceChangeActionEnum? DynamicServiceChangeAction { get; private set; } + public enum DynamicServiceChangeActionEnum : byte + { + Add, + Replace, + Delete + } + public bool? ClassifierActivationState { get; set; } + + public byte? RulePriority { get; set; } + + public Ipv4PacketClassificationEncodings Ipv4PacketClassification { get; private set; } + public class Ipv4PacketClassificationEncodings + { + public Ipv4PacketClassificationEncodings(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 2: + (v[0], v[1]) = (v[1], v[0]); + IpProtocol = BitConverter.ToUInt16(v, 0); + break; + case 3: + Ipv4SourceAddress = new IPAddress(v); + break; + case 5: + Ipv4DestinationAddress = new IPAddress(v); + break; + case 7: + (v[0], v[1]) = (v[1], v[0]); + SourcePortStart = BitConverter.ToUInt16(v, 0); + break; + case 8: + (v[0], v[1]) = (v[1], v[0]); + SourcePortEnd = BitConverter.ToUInt16(v, 0); + break; + case 9: + (v[0], v[1]) = (v[1], v[0]); + DestinationPortStart = BitConverter.ToUInt16(v, 0); + break; + case 10: + (v[0], v[1]) = (v[1], v[0]); + DestinationPortEnd = BitConverter.ToUInt16(v, 0); + break; + default: + //see CM-SP-MULPIv4.0-I01-190815.pdf page 746->747 + throw new NotImplementedException(string.Format("{0} TLV Type {1}", nameof(Ipv4PacketClassificationEncodings), type)); + } + } + } + + public ushort? DestinationPortStart { get; set; } + + public IPAddress Ipv4SourceAddress { get; set; } + + public IPAddress Ipv4DestinationAddress { get; set; } + + public ushort? DestinationPortEnd { get; set; } + + public ushort? IpProtocol { get; set; } + + public ushort? SourcePortStart { get; set; } + + public ushort? SourcePortEnd { get; set; } + } + public ushort? ServiceFlowReference { get; set; } + + public byte? ClassifierReference { get; set; } + + public ushort? ClassifierIdentifier { get; set; } + + public uint? ServiceFlowIdentifier { get; set; } + } +} diff --git a/skyscraper8/Docsis/AnnexC/GeneralServiceFlowEncoding.cs b/skyscraper8/Docsis/AnnexC/GeneralServiceFlowEncoding.cs new file mode 100644 index 0000000..255899e --- /dev/null +++ b/skyscraper8/Docsis/AnnexC/GeneralServiceFlowEncoding.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis.AnnexC +{ + internal class GeneralServiceFlowEncoding + { + public GeneralServiceFlowEncoding(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte len = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(len); + switch (type) + { + case 1: + (v[0], v[1]) = (v[1], v[0]); + ServiceFlowReference = BitConverter.ToUInt16(v, 0); + break; + case 2: + (v[0], v[1]) = (v[1], v[0]); + ServiceFlowIdentifier = BitConverter.ToUInt16(v, 0); + break; + case 6: + ProvisionedSet = (v[0] & 0x01) != 0; + AdmittedSet = (v[0] & 0x02) != 0; + ActiveSet = (v[0] & 0x04) != 0; + break; + case 7: + TrafficPriority = v[0]; + break; + case 8: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + UpstreamMaximumSustainedTrafficRate = BitConverter.ToUInt32(v, 0); + break; + case 9: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + MaximumTrafficBurst = BitConverter.ToUInt32(v, 0); + break; + case 10: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + MinimumReservedTrafficRate = BitConverter.ToUInt32(v, 0); + break; + case 11: + (v[0], v[1]) = (v[1], v[0]); + AssumedMinimumReservedRatePacketSize = BitConverter.ToUInt16(v, 0); + break; + case 12: + (v[0], v[1]) = (v[1], v[0]); + TimeoutForActiveQosParameters = BitConverter.ToUInt16(v, 0); + break; + case 15: + ServiceFlowSchedulingType = (ServiceFlowSchedulingTypeEnum)v[0]; + break; + case 16: + DoNotUseAllCms = (v[3] & 0x01) != 0; + DoNotUsePriorityRequest = (v[3] & 0x02) != 0; + DoNotUseRequest2ForRequests = (v[3] & 0x04) != 0; + DoNotUseRequest2ForData = (v[3] & 0x08) != 0; + DoNotPiggybackRequests = (v[3] & 0x10) != 0; + DoNotConcatenateData = (v[3] & 0x20) != 0; + DoNotFragmentData = (v[3] & 0x40) != 0; + DoNotSupressPayloadHeaders = (v[3] & 0x80) != 0; + DropPacketsTooBigForUnsolicitedGrant = (v[2] & 0x01) != 0; + DoNotUseSegmentHeaders = (v[2] & 0x02) != 0; + DoNotUseContentionRegions = (v[2] & 0x04) != 0; + break; + case 17: + if (len == 4) + { + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + NominalPollingInterval = BitConverter.ToUInt32(v, 0); + } + else if (len == 1) + { + DownstreamResequencing = v[0]; + } + break; + case 19: + (v[0], v[1]) = (v[1], v[0]); + UnsolicitedGrantSize = BitConverter.ToUInt16(v, 0); + break; + case 20: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + NominalGrantInterval = BitConverter.ToUInt32(v, 0); + break; + case 21: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + ToleratedGrantJitter = BitConverter.ToUInt32(v, 0); + break; + case 22: + GrantsPerInterval = v[0]; + break; + case 24: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + CmtsTimestamp = BitConverter.ToUInt32(v, 0); + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf page 758 + throw new NotImplementedException(string.Format("General Service Flow Encodings Type {0}", type)); + } + } + } + + public byte? DownstreamResequencing { get; set; } + + public uint? NominalPollingInterval { get; set; } + + public ushort? ServiceFlowReference { get; set; } + + public ushort? AssumedMinimumReservedRatePacketSize { get; set; } + + public byte? TrafficPriority { get; set; } + + public uint? MaximumTrafficBurst { get; set; } + + public uint? MinimumReservedTrafficRate { get; set; } + + public uint? UpstreamMaximumSustainedTrafficRate { get; set; } + + public uint? CmtsTimestamp { get; set; } + + public uint? NominalGrantInterval { get; set; } + + public byte? GrantsPerInterval { get; set; } + + public bool? DoNotUseContentionRegions { get; set; } + + public bool? DoNotUseSegmentHeaders { get; set; } + + public bool? DropPacketsTooBigForUnsolicitedGrant { get; set; } + + public bool? DoNotSupressPayloadHeaders { get; set; } + + public bool? DoNotFragmentData { get; set; } + + public bool? DoNotConcatenateData { get; set; } + + public bool? DoNotPiggybackRequests { get; set; } + + public bool? DoNotUseRequest2ForData { get; set; } + + public bool? DoNotUseRequest2ForRequests { get; set; } + + public bool? DoNotUsePriorityRequest { get; set; } + + public bool? DoNotUseAllCms { get; set; } + + public ServiceFlowSchedulingTypeEnum ServiceFlowSchedulingType { get; private set; } + public enum ServiceFlowSchedulingTypeEnum : byte + { + BestEffort = 2, + NonRealTimePollingService = 3, + RealTimePollingService = 4, + UnsolicitedGrantServiceWithActivityDeletion = 5, + UnsolicitedGrantService = 6, + ProactiveGrantService = 7 + } + + public uint? ToleratedGrantJitter { get; set; } + + public ushort? UnsolicitedGrantSize { get; set; } + + public ushort? TimeoutForActiveQosParameters { get; set; } + + public ushort? ServiceFlowIdentifier { get; set; } + + public bool? ActiveSet { get; set; } + + public bool? AdmittedSet { get; set; } + + public bool? ProvisionedSet { get; set; } + + public override string ToString() + { + return $"{nameof(ServiceFlowIdentifier)}: {ServiceFlowIdentifier}"; + } + } +} diff --git a/skyscraper8/Docsis/AnnexC/ModemCapabilitiesEncoding.cs b/skyscraper8/Docsis/AnnexC/ModemCapabilitiesEncoding.cs new file mode 100644 index 0000000..5378a21 --- /dev/null +++ b/skyscraper8/Docsis/AnnexC/ModemCapabilitiesEncoding.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis.AnnexC +{ + internal class ModemCapabilitiesEncoding + { + public ModemCapabilitiesEncoding(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, true); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte len = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(len); + switch (type) + { + case 1: + ConcatenationSupport = v[0] != 0 ? true : false; + break; + case 2: + DocsisVersion = DecodeVersion(v[0]); + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf, page 688 + throw new NotFiniteNumberException(String.Format("{0} {1}", nameof(ModemCapabilitiesEncoding), type)); + } + } + } + + public Version DocsisVersion { get; private set; } + + private Version DecodeVersion(byte value) + { + //see CM-SP-MULPIv4.0-I01-190815.pdf, page 689 + switch (value) + { + case 0: return new Version(1, 0); + case 1: return new Version(1, 1); + case 2: return new Version(2, 0); + case 3: return new Version(3, 0); + case 4: return new Version(3, 1); + case 5: return new Version(4, 0); + default: + return null; + } + } + + public bool ConcatenationSupport { get; private set; } + } +} diff --git a/skyscraper8/Docsis/AnnexC/RcpIdEncoding.cs b/skyscraper8/Docsis/AnnexC/RcpIdEncoding.cs new file mode 100644 index 0000000..4921e1c --- /dev/null +++ b/skyscraper8/Docsis/AnnexC/RcpIdEncoding.cs @@ -0,0 +1,71 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Channels; +using System.Threading.Tasks; +using static skyscraper5.src.InteractionChannel.Model2.Tbtp2; + +namespace skyscraper5.Docsis.AnnexC +{ + internal class RcpIdEncoding + { + public RcpIdEncoding(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 7: + SimplifiedReceiveChannelConfiguration = new SimplifiedReceiveChannelAssignmentEncoding(v); + break; + default: + throw new NotFiniteNumberException(String.Format("{0} {1}", nameof(ModemCapabilitiesEncoding), type)); + } + } + } + + public SimplifiedReceiveChannelAssignmentEncoding SimplifiedReceiveChannelConfiguration { get; private set; } + } + + class SimplifiedReceiveChannelAssignmentEncoding + { + public SimplifiedReceiveChannelAssignmentEncoding(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + PrimaryDownstreamChannelAssignment = v; + break; + case 2: + DownstreamChannelAssignment = v; + break; + case 3: + DownstreamProfileAssignment = v; + break; + default: + //page 728, CM-SP-MULPIv4.0-I01-190815.pdf + throw new NotFiniteNumberException(String.Format("{0} {1}", nameof(SimplifiedReceiveChannelAssignmentEncoding), type)); + } + } + } + + public byte[] PrimaryDownstreamChannelAssignment { get; private set; } + public byte[] DownstreamChannelAssignment { get; private set; } + public byte[] DownstreamProfileAssignment { get; } + } +} diff --git a/skyscraper8/Docsis/AnnexC/ServiceFlowSidClusterAssignmentObject.cs b/skyscraper8/Docsis/AnnexC/ServiceFlowSidClusterAssignmentObject.cs new file mode 100644 index 0000000..872da1c --- /dev/null +++ b/skyscraper8/Docsis/AnnexC/ServiceFlowSidClusterAssignmentObject.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis.AnnexC +{ + internal class ServiceFlowSidClusterAssignmentObject + { + public ServiceFlowSidClusterAssignmentObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + SFID = BitConverter.ToUInt32(v, 0); + break; + case 2: + SidClusterEncoding = new SidClusterEncodingObject(v); + break; + case 3: + SidClusterSwitchoverCriteria = new SidClusterSwitchoverCriteriaObject(v); + break; + default: + //See CM-SP-MULPIv4.0-I01-190815.pdf page 720->721 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(ServiceFlowSidClusterAssignmentObject), type)); + } + } + } + + public SidClusterSwitchoverCriteriaObject SidClusterSwitchoverCriteria { get; private set; } + public class SidClusterSwitchoverCriteriaObject + { + public SidClusterSwitchoverCriteriaObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + MaximumRequestPerSidCluster = v[0]; + break; + case 2: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + MaximumOutstandingBytesPerSidCluster = BitConverter.ToUInt32(v, 0); + break; + case 3: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + MaximumTotalBytesRequestedPerSidCluster = BitConverter.ToUInt32(v, 0); + break; + case 4: + (v[0], v[1]) = (v[1], v[0]); + MaximumTimeInTheSidCluster = BitConverter.ToUInt16(v, 0); + break; + default: + //See CM-SP-MULPIv4.0-I01-190815.pdf page 722 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(SidClusterSwitchoverCriteriaObject), type)); + } + } + } + + public ushort? MaximumTimeInTheSidCluster { get; set; } + + public uint? MaximumTotalBytesRequestedPerSidCluster { get; set; } + + public uint? MaximumOutstandingBytesPerSidCluster { get; set; } + + public byte? MaximumRequestPerSidCluster { get; set; } + } + public SidClusterEncodingObject SidClusterEncoding { get; private set; } + public class SidClusterEncodingObject + { + public SidClusterEncodingObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + SidClusterId = v[0]; + break; + case 2: + SidToChannelMapping = new SidToChannelMappingObject(v); + break; + // CM-SP-MULPIv4.0-I01-190815.pdf page 721 + default: + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(SidClusterEncodingObject), type)); + } + } + } + + public SidToChannelMappingObject SidToChannelMapping { get; private set; } + public class SidToChannelMappingObject + { + public SidToChannelMappingObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + UpstreamChannelId = v[0]; + break; + case 2: + (v[0], v[1]) = (v[1], v[0]); + SID = BitConverter.ToUInt16(v, 0); + break; + case 3: + Action = (ActionEnum)v[0]; + break; + default: + // CM-SP-MULPIv4.0-I01-190815.pdf page 721 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(SidToChannelMappingObject), type)); + } + } + } + + public ActionEnum? Action { get; private set; } + public enum ActionEnum : byte + { + Add = 1, + Delete = 2 + } + + public ushort? SID { get; set; } + + public byte? UpstreamChannelId { get; set; } + } + public byte? SidClusterId { get; set; } + } + public uint? SFID { get; set; } + } +} diff --git a/skyscraper8/Docsis/DocsisEnvironment.cs b/skyscraper8/Docsis/DocsisEnvironment.cs new file mode 100644 index 0000000..20d83bc --- /dev/null +++ b/skyscraper8/Docsis/DocsisEnvironment.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.XPath; +using Ionic.Crc; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.Ietf.Rfc2460; +using skyscraper5.Ietf.Rfc826; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Net.Pcap; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis +{ + public class DocsisEnvironment : IDisposable + { + private readonly IDocsisEventHandler eventHandler; + + internal DocsisEnvironment(IDocsisEventHandler eventHandler) + { + this.eventHandler = eventHandler; + this._stats = new Statistics(); + } + + public void PushPacket(MemoryStream ms) + { + _stats.Throughput += ms.Length; + + byte fc = ms.ReadUInt8(); + int fc_type = (fc & 0xc0) >> 6; + int fc_parm = (fc & 0x3e) >> 1; + bool ehdr_on = (fc & 0x01) != 0; + + bool isQueueDepthBasedRequest = false; + ushort mac_parm; + if (isQueueDepthBasedRequest) + mac_parm = ms.ReadUInt16BE(); + else + mac_parm = ms.ReadUInt8(); + + MacExtendedHeader eh = null; + ushort len = ms.ReadUInt16BE(); + if (ehdr_on) + { + len -= mac_parm; + byte[] ehdr = ms.ReadBytes(mac_parm); + eh = new MacExtendedHeader(ehdr); + } + + ushort hcs = ms.ReadUInt16BE(); + + switch (fc_type) + { + case 0b00: + _stats.PacketPduPresent = true; + _stats.PacketPdus++; + PushPacketPdu(false, len, ms, eh); + break; + case 0b11: + _stats.MacSpecificHeadersPresent = true; + _stats.MacSpecificHeaders++; + PushMacSpecificHeader(fc_parm, ms); + break; + case 0b10: + _stats.IsolationPacketPduPresent = true; + _stats.IsolationPacketPdus++; + PushPacketPdu(true, len, ms, eh); + break; + case 0b01: + _stats.SpecialUseMacPresent = true; + _stats.SpecialUseMacs++; + PushSpecialUseMac(ms); + break; + + default: + throw new NotImplementedException(String.Format("{0:X}", fc_type)); + } + } + + private void PushSpecialUseMac(MemoryStream ms) + { + if (ms.GetAvailableBytes() < 12) + return; + + PhysicalAddress da = new PhysicalAddress(ms.ReadBytes(6)); + PhysicalAddress sa = new PhysicalAddress(ms.ReadBytes(6)); + } + + private const bool ENABLE_DOCSIS_DUMPER = true; + private PcapWriter pcapWriter; + private void PushPacketPdu(bool isolation, ushort len, MemoryStream ms, MacExtendedHeader eh) + { + if (len < 14) + return; + + byte[] buffer = ms.ReadBytes(len); + ms = new MemoryStream(buffer); + + PhysicalAddress da = new PhysicalAddress(ms.ReadBytes(6)); + PhysicalAddress sa = new PhysicalAddress(ms.ReadBytes(6)); + ushort etherType = ms.ReadUInt16BE(); + byte[] userData = ms.ReadBytes(ms.GetAvailableBytes() - 4); + + AddParticipant(da); + AddParticipant(sa); + + Statistics.ParticipantStatistic participantStatistic; + lock (_stats.ParticipantStatistics) + { + participantStatistic = _stats.ParticipantStatistics.Find(x => x.Address.Equals(da)); + if (participantStatistic == null) + { + participantStatistic = new Statistics.ParticipantStatistic(); + participantStatistic.Address = da; + _stats.ParticipantStatistics.Add(participantStatistic); + } + + participantStatistic.Packets++; + participantStatistic.Throughput += userData.Length; + } + + if (eh != null && eh.Encrypted) + { + _stats.ScrambledPackets++; + //screw this packet, it's encrypted, we can't deal with it. Danke, Merkel. + return; + } + else + { + _stats.UnscrambledPackets++; + } + + + if (ENABLE_DOCSIS_DUMPER) + { + if (pcapWriter == null) + { + FileStream fileStream = File.OpenWrite(String.Format("{0}.pcap", DateTime.Now.ToUnixTime())); + pcapWriter = new PcapWriter(fileStream, TcpdumpNetworkType.Ethernet); + } + pcapWriter.WritePacket(buffer); + } + + if (etherType == 0x86dd) + { + Ipv6Header ipv6Header = new Ipv6Header(userData); + switch (ipv6Header.NextHeader) + { + case 0x3a: + //ICMP-v6. Boring + break; + default: + throw new NotImplementedException(String.Format("{0}.{1} 0x{2:X4}", nameof(Ipv6Header), nameof(ipv6Header.NextHeader), ipv6Header.NextHeader)); + } + } + else if (etherType == 0x0806) + { + ArpHeader arpHeader = new ArpHeader(userData); + eventHandler.OnLearnedIpFromMac(arpHeader.SenderHardwareAddress, arpHeader.SenderProtocolAddress); + if (arpHeader.Operation == ArpHeader.OperationEnum.Request) + { + participantStatistic.Ip = arpHeader.TargetProtocolAddress; + } + else if (arpHeader.Operation == ArpHeader.OperationEnum.Reply) + { + participantStatistic.Ip = arpHeader.SenderProtocolAddress; + } + else + { + throw new NotImplementedException(); + } + } + else if (etherType == 0x0800) + { + InternetHeader ipv4Header = new InternetHeader(userData); + switch (ipv4Header.Protocol) + { + case 0x02: //IGMP + break; + case 0x11: //UDP + break; + default: + throw new NotImplementedException(String.Format("{0} {1}", nameof(ipv4Header.Protocol), ipv4Header.Protocol)); + } + } + else + { + //throw new NotImplementedException(String.Format("{0} {1:X4}", nameof(etherType), etherType)); + } + } + + private void PushMacSpecificHeader(int fc_parm, MemoryStream remainingBytes) + { + CrcCalculatorStream crcStream = new CrcCalculatorStream(remainingBytes, true); + + PhysicalAddress da = new PhysicalAddress(crcStream.ReadBytes(6)); + PhysicalAddress sa = new PhysicalAddress(crcStream.ReadBytes(6)); + ushort msgLen = crcStream.ReadUInt16BE(); + byte dsap = crcStream.ReadUInt8(); + byte ssap = crcStream.ReadUInt8(); + byte control = crcStream.ReadUInt8(); + byte version = crcStream.ReadUInt8(); + byte type = crcStream.ReadUInt8(); + byte rsvd = crcStream.ReadUInt8(); + msgLen -= 6; + + if (msgLen > remainingBytes.GetAvailableBytes()) + return; + + byte[] packetBytes = crcStream.ReadBytes(msgLen); + + int crc = remainingBytes.ReadInt32LE(); + if (crcStream.Crc != crc) + return; + + AddParticipant(da); + AddParticipant(sa); + + switch (fc_parm) + { + case 0b00000: + PushTimingMessage(sa, packetBytes); + break; + case 0b00001: + PushMacManagementMessage(packetBytes, version, type, sa, da, packetBytes); + break; + case 0b00010: + //TODO: Request Frame + throw new NotImplementedException("Request Frame"); + case 0b00011: + throw new NotImplementedException("Fragmentation Header"); + case 0b00100: + throw new NotImplementedException("Queue Depth-based Request Frame"); + case 0b11100: + throw new NotImplementedException("Concatenation Header"); + default: + Debug.WriteLine(String.Format("Got MAC Specific Header with FC_PARM {0} - this wasn't valid in DOCSIS 4.0. Is it time to upgrade?", fc_parm)); + break; + } + } + + private Dictionary timingMessages; + private void PushTimingMessage(PhysicalAddress source, byte[] remainingBytes) + { + (remainingBytes[3], remainingBytes[2], remainingBytes[1], remainingBytes[0]) = (remainingBytes[0], remainingBytes[1], remainingBytes[2], remainingBytes[3]); + uint timing = BitConverter.ToUInt32(remainingBytes); + + if (timingMessages == null) + { + timingMessages = new Dictionary(); + } + + if (!timingMessages.ContainsKey(source)) + { + timingMessages[source] = 0; + } + + if (timingMessages[source] != timing) + { + timingMessages[source] = timing; + eventHandler.OnCmtsTimestamp(source, timing); + } + } + + private ReadOnlyDictionary _macManagementMessageTypes; + + private void PushMacManagementMessage(byte[] ms, byte version, byte type, PhysicalAddress sourceAddress, PhysicalAddress destinationAddress, byte[] readBytes) + { + + if (_macManagementMessageTypes == null) + { + _macManagementMessageTypes = PluginManager.GetInstance().GetDocsisMacManagementMessageTypes(); + + if (_macManagementMessageTypes.Count == 0) + { + throw new DocsisException("Didn't find any MAC Management Types."); + } + } + + if (_macManagementMessageTypes.Count == 0) + { + throw new DocsisException("No MAC Management Message Types"); + } + + + MacManagementMessageTypeAttribute neededType = new MacManagementMessageTypeAttribute(version, type); + if (!_macManagementMessageTypes.ContainsKey(neededType)) + { + //Found in CM-SP-MULPIv3.0-C0I-171207.pdf on page 94 + // or in CM-SP-MULPIv3.1-I03-1406101.pdf on page 100 + // or in CM-SP-MULPIv4.0-I01-190815.pdf on page 103 + throw new NotImplementedException(String.Format("{0} {1}", nameof(_macManagementMessageTypes), neededType.ToString())); + } + Type macManagementMessageType = _macManagementMessageTypes[neededType]; + MacManagementMessage mmm = (MacManagementMessage)Activator.CreateInstance(macManagementMessageType, new object[] { sourceAddress, destinationAddress, readBytes }); + if (!mmm.Valid) + return; + + string name = mmm.GetType().Name; + switch (name) + { + case nameof(UpstreamBandwidthAllocation): + //not particulary interesting + break; + case nameof(UpstreamBandwidthAllocationV5): + //not particulary interesting either + break; + case nameof(UpstreamChannelDescriptor): + eventHandler.OnUpstreamChannel((UpstreamChannelDescriptor)mmm); + break; + case nameof(RangingResponse): + //not interesting either + break; + case nameof(StatusReportAcknowledge): + //take a guess? + break; + case nameof(MacDomainDescriptor): + MacDomainDescriptor macDomainDescriptor = (MacDomainDescriptor)mmm; + if (macDomainDescriptor.DownstreamActiveChannels == null) + break; + if (macDomainDescriptor.DownstreamActiveChannels.Count == 0) + break; + foreach (MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel in macDomainDescriptor.DownstreamActiveChannels) + { + eventHandler.OnDownstreamChannel(macDomainDescriptor.Source, downstreamActiveChannel); + } + break; + case nameof(PrivacyKeyManagementResponse): + PrivacyKeyManagementResponse privacyKeyManagementResponse = (PrivacyKeyManagementResponse)mmm; + switch (privacyKeyManagementResponse.Code) + { + case 8: + //Since the key is encrypted as well, this isn't interesting. + break; + default: + //CM-SP-SECv4.0-I01-190815.pdf page 50 + throw new NotImplementedException(String.Format("{0}.{1} {2}", nameof(PrivacyKeyManagementResponse), nameof(privacyKeyManagementResponse.Code), privacyKeyManagementResponse.Code)); + } + break; + case nameof(DynamicServiceChangeResponse): + //Nothing of interesent in here either. + break; + case nameof(DynamicBondingChangeRequest): + //doesn't contain anything of interest. + break; + case nameof(DynamicBondingChangeAcknowledge): + //not interesting + break; + case nameof(DynamicServiceAdditionResponse): + //not interesting + break; + case nameof(DynamicServiceDeletionResponse): + //nothing in there + break; + default: + throw new NotImplementedException(); + } + } + + private HashSet participants; + private void AddParticipant(PhysicalAddress pa) + { + if (participants == null) + participants = new HashSet(); + if (participants.Add(pa)) + { + eventHandler.OnParticipantDetected(pa); + } + } + + public void Dispose() + { + pcapWriter?.Dispose(); + } + + public Statistics DocsisEnvironmentStatistics => _stats; + private Statistics _stats; + public class Statistics + { + public Statistics() + { + ParticipantStatistics = new List(); + } + + public bool PacketPduPresent; + public long PacketPdus { get; internal set; } + public long PacketPdusPerSecond { get; internal set; } + + + public bool MacSpecificHeadersPresent; + public long MacSpecificHeaders { get; internal set; } + public long MacSpecificHeadersPerSecond { get; internal set; } + + + public bool IsolationPacketPduPresent; + public long IsolationPacketPdus { get; internal set; } + public long IsolationPacketPdusPerSecond { get; internal set; } + + + public bool SpecialUseMacPresent; + public long SpecialUseMacs { get; internal set; } + public long SpecialUseMacsPerSecond { get; internal set; } + + public long Throughput { get; internal set; } + public long ThroughputPerSecond { get; internal set; } + public long ThroughputTotal { get; internal set; } + + public int ScrambledPackets { get; set; } + public int ScrambledPacketsPerSecond { get; private set; } + public long ScrambledPacketsTotal { get; private set; } + + public int UnscrambledPackets { get; set; } + public int UnscrambledPacketsPerSecond { get; private set; } + public long UnscrambledPacketsTotal { get; private set; } + + public class ParticipantStatistic + { + public PhysicalAddress Address { get; internal set; } + public int Packets { get; set; } + public int Throughput { get; set; } + public IPAddress Ip { get; set; } + + public int PacketsPerSecond { get; internal set; } + public int ThroughputPerSecond { get; internal set; } + public long ThroughputTotal { get; set; } + } + + public List ParticipantStatistics { get; internal set; } + + public void CaptureSecond() + { + PacketPdusPerSecond = PacketPdus; + PacketPdus = 0; + MacSpecificHeadersPerSecond = MacSpecificHeaders; + MacSpecificHeaders = 0; + IsolationPacketPdusPerSecond = IsolationPacketPdus; + IsolationPacketPdus = 0; + SpecialUseMacsPerSecond = SpecialUseMacs; + SpecialUseMacs = 0; + ThroughputTotal += Throughput; + ThroughputPerSecond = Throughput; + Throughput = 0; + ScrambledPacketsTotal += ScrambledPackets; + ScrambledPacketsPerSecond = ScrambledPackets; + ScrambledPackets = 0; + UnscrambledPacketsTotal += UnscrambledPackets; + UnscrambledPacketsPerSecond = UnscrambledPackets; + UnscrambledPackets = 0; + lock (ParticipantStatistics) + { + foreach (ParticipantStatistic participantStatistic in ParticipantStatistics) + { + participantStatistic.ThroughputTotal += participantStatistic.Throughput; + participantStatistic.ThroughputPerSecond = participantStatistic.Throughput; + participantStatistic.Throughput = 0; + participantStatistic.PacketsPerSecond = participantStatistic.Packets; + participantStatistic.Packets = 0; + } + } + } + } + + } +} diff --git a/skyscraper8/Docsis/DocsisException.cs b/skyscraper8/Docsis/DocsisException.cs new file mode 100644 index 0000000..013035c --- /dev/null +++ b/skyscraper8/Docsis/DocsisException.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Docsis +{ + [Serializable] + public class DocsisException : SkyscraperException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public DocsisException() + { + } + + public DocsisException(string message) : base(message) + { + } + + public DocsisException(string message, Exception inner) : base(message, inner) + { + } + + protected DocsisException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Docsis/DocsisPacketProcessor.cs b/skyscraper8/Docsis/DocsisPacketProcessor.cs new file mode 100644 index 0000000..97af930 --- /dev/null +++ b/skyscraper8/Docsis/DocsisPacketProcessor.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis +{ + internal class DocsisPacketProcessor : ITsPacketProcessor, IPayloadUnitDecoder, IDisposable + { + public DocsisPacketProcessor(IDocsisEventHandler eventHandler) + { + this.eventHandler = eventHandler; + this.docsisEnvironment = new DocsisEnvironment(eventHandler); + } + + + private IDocsisEventHandler eventHandler; + + private bool currentPayloadUnitPresent; + private MemoryStream currentPayloadBuffer; + private byte? FC; + + public int? FC_TYPE + { + get + { + if (!FC.HasValue) + return null; + + int i = (FC.Value & 0xc0) >> 6; + return i; + } + } + + public int? FC_PARM + { + get + { + if (!FC.HasValue) + return null; + int i = (FC.Value & 0x3e) >> 1; + return i; + } + } + + public bool? EHDR_ON + { + get + { + if (!FC.HasValue) + return null; + + bool i = (FC.Value & 0x01) != 0; + return i; + } + } + + private byte? MAC_PARM; + + private byte? LEN_L, LEN_R; + + private int? LEN + { + get + { + if (!LEN_L.HasValue || !LEN_R.HasValue) + return null; + + int result = LEN_L.Value; + result <<= 8; + result += LEN_R.Value; + return result; + } + } + + private byte[] EHDR; + private bool EHDR_done; + private int EHDR_ptr; + private int neededBytes; + + private byte? HCS_L, HCS_R; + + private bool packetDone; + + public void PushPacket(TsPacket packet) + { + if (packet.AdaptionFieldControl != 1) + { + PacketLoss(); + return; + } + + bool packetPayloadUnitStart = packet.PayloadUnitStart; + MemoryStream ms = new MemoryStream(packet.Payload, false); + + if (!currentPayloadUnitPresent && !packetPayloadUnitStart) + return; + + if (!currentPayloadUnitPresent && packetPayloadUnitStart) + { + ms.Position = packet.PayloadStartOffset; + currentPayloadUnitPresent = true; + currentPayloadBuffer = new MemoryStream(); + } + + while (ms.GetAvailableBytes() > 0) + { + if (!FC.HasValue) + { + FC = ms.ReadUInt8(); + currentPayloadBuffer.WriteByte(FC.Value); + continue; + } + + if (!MAC_PARM.HasValue) + { + MAC_PARM = ms.ReadUInt8(); + currentPayloadBuffer.WriteByte(MAC_PARM.Value); + continue; + } + + if (!LEN_L.HasValue) + { + LEN_L = ms.ReadUInt8(); + currentPayloadBuffer.WriteByte(LEN_L.Value); + continue; + } + + if (!LEN_R.HasValue) + { + LEN_R = ms.ReadUInt8(); + currentPayloadBuffer.WriteByte(LEN_R.Value); + if (EHDR_ON.Value) + { + EHDR = new byte[MAC_PARM.Value]; + neededBytes = MAC_PARM.Value; + EHDR_done = false; + } + continue; + } + + if (!EHDR_done && EHDR_ON.Value) + { + int ehdrAvailable = (int)Math.Min(neededBytes, ms.GetAvailableBytes()); + int ehdrWasRead = ms.Read(EHDR, EHDR_ptr, ehdrAvailable); + neededBytes -= ehdrWasRead; + EHDR_ptr += ehdrWasRead; + if (neededBytes == 0) + { + currentPayloadBuffer.Write(EHDR, 0, EHDR.Length); + EHDR_done = true; + } + continue; + } + + if (!HCS_L.HasValue) + { + HCS_L = ms.ReadUInt8(); + currentPayloadBuffer.WriteByte(HCS_L.Value); + continue; + } + + if (!HCS_R.HasValue) + { + HCS_R = ms.ReadUInt8(); + currentPayloadBuffer.WriteByte(HCS_R.Value); + packetDone = false; + neededBytes = LEN.Value; + if (EHDR_ON.Value) + neededBytes -= EHDR.Length; + continue; + } + + if (!packetDone) + { + int toRead = (int)Math.Min(neededBytes, ms.GetAvailableBytes()); + if (toRead < 0) + { + PacketLoss(); + return; + } + byte[] tmp = new byte[toRead]; + int readAmount = ms.Read(tmp, 0, toRead); + currentPayloadBuffer.Write(tmp, 0, readAmount); + neededBytes -= readAmount; + if (neededBytes == 0) + packetDone = true; + continue; + } + + HandlePacket(); + while (ms.GetAvailableBytes() > 0) + { + byte stuffing = ms.ReadUInt8(); + if (stuffing != 0xff) + { + ms.Position--; + currentPayloadUnitPresent = true; + currentPayloadBuffer = new MemoryStream(); + break; + } + } + } + } + + public void PacketLoss() + { + Reset(); + } + + private void Reset() + { + currentPayloadUnitPresent = false; + currentPayloadBuffer = null; + FC = null; + MAC_PARM = null; + LEN_L = null; + LEN_R = null; + EHDR = null; + EHDR_done = false; + EHDR_ptr = 0; + neededBytes = 0; + HCS_L = null; + HCS_R = null; + packetDone = false; + } + + private ulong processedPackets = 0; + private DocsisEnvironment docsisEnvironment; + + private void HandlePacket() + { + currentPayloadBuffer.Position = 0; + docsisEnvironment.PushPacket(currentPayloadBuffer); + Reset(); + } + + public void Dispose() + { + docsisEnvironment?.Dispose(); + } + + public DocsisEnvironment DocsisEnvironment => docsisEnvironment; + } +} diff --git a/skyscraper8/Docsis/IDocsisEventHandler.cs b/skyscraper8/Docsis/IDocsisEventHandler.cs new file mode 100644 index 0000000..19489a8 --- /dev/null +++ b/skyscraper8/Docsis/IDocsisEventHandler.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.MacManagement; + +namespace skyscraper5.Docsis +{ + internal interface IDocsisEventHandler + { + void OnParticipantDetected(PhysicalAddress pa); + void OnCmtsTimestamp(PhysicalAddress source, uint timing); + void OnUpstreamChannel(UpstreamChannelDescriptor mmm); + void OnDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel); + void OnLearnedIpFromMac(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress); + } +} diff --git a/skyscraper8/Docsis/MacExtendedHeader.cs b/skyscraper8/Docsis/MacExtendedHeader.cs new file mode 100644 index 0000000..c931058 --- /dev/null +++ b/skyscraper8/Docsis/MacExtendedHeader.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis +{ + internal class MacExtendedHeader + { + public MacExtendedHeader(byte[] ehdr) + { + MemoryStream ms = new MemoryStream(ehdr, false); + while (ms.GetAvailableBytes() > 0) + { + byte readUInt8 = ms.ReadUInt8(); + int EH_TYPE = readUInt8 & 0xf0 >> 4; + int EH_LEN = readUInt8 & 0x0f; + if (ms.GetAvailableBytes() < EH_LEN) + break; + byte[] tmpBuffer = ms.ReadBytes(EH_LEN); + switch (EH_TYPE) + { + case 0: //Null configuration settings. + break; + case 1: + if (tmpBuffer.Length == 1) + { + MinislotsRequested = tmpBuffer[0]; + } + else if (tmpBuffer.Length == 2) + { + (tmpBuffer[0], tmpBuffer[1]) = (tmpBuffer[1], tmpBuffer[0]); + SID = BitConverter.ToUInt16(tmpBuffer, 0); + } + break; + case 2: //Deprecated in DOCSIS 3.1 + //Acknowledgement Requested + (tmpBuffer[0], tmpBuffer[1]) = (tmpBuffer[1], tmpBuffer[0]); + SID = BitConverter.ToUInt16(tmpBuffer, 0); + break; + case 3: + UpstreamPrivacyEhElement = new UpstreamPrivacyElement(tmpBuffer); + break; + case 4: //Downstream Privacy EH Element + DownstreamPrivacyEhElement = new DownstreamPrivacyElement(tmpBuffer); + break; + case 5: //Payload Header Suppression Header + PayloadHeaderSupression = tmpBuffer[0]; + break; + case 6: //Service Flow EH Element + if (EH_LEN == 2) + { + UnsolicitedGrantSynchronizationHeader = tmpBuffer[1]; + } + break; + case 7: + UpstreamPrivacyEhElement = new UpstreamPrivacyElement(tmpBuffer); + break; + case 8: + if (EH_LEN == 1) + { + //CM-SP-MULPIv4.0-I01-190815.pdf, page 98 + throw new NotImplementedException(); + } + else if (EH_LEN == 3) + { + //CM-SP-MULPIv4.0-I01-190815.pdf, page 98 + throw new NotImplementedException(); + } + else if (EH_LEN == 5) + { + //CM-SP-MULPIv4.0-I01-190815.pdf, page 99 + throw new NotImplementedException(); + } + + break; + case 9: //DOCSIS Path Verify EH Element + DpvExtendedHeader = new DpvElement(tmpBuffer); + break; + case 10: //reserved + break; + case 11: //reserved + break; + case 12: //reserved + break; + case 13: //reserved + break; + case 14: //reserved + break; + case 15://Extended EH Element + //seems undocumented? + //see CM-SP-MULPIv4.0-I01-190815-1.pdf, page 96 + break; + default: + //see CM-SP-MULPIv4.0-I01-190815.pdf, page 96 + throw new NotImplementedException(String.Format("{0} {1}", nameof(EH_TYPE), EH_TYPE)); + } + } + } + + public byte UnsolicitedGrantSynchronizationHeader { get; set; } + + public DpvElement DpvExtendedHeader { get; private set; } + + internal class DpvElement + { + public DpvElement(byte[] tmpBuffer) + { + MemoryStream ms = new MemoryStream(tmpBuffer, false); + StartReferencePoint = ms.ReadUInt8(); + TimestampStart = ms.ReadUInt32BE(); + } + + public uint TimestampStart { get; set; } + + public byte StartReferencePoint { get; private set; } + } + + public ushort? SID { get; set; } + + public byte? MinislotsRequested { get; set; } + + public class UpstreamPrivacyElement : Validatable + { + public UpstreamPrivacyElement(byte[] buffer) + { + if (buffer.Length == 3) + { + KEY_SEQ = (buffer[0] & 0xf0) >> 4; + Version = (buffer[0] & 0x0f); + Encrypted = (buffer[1] & 0x80) != 0; + OddKey = (buffer[1] & 0x40) != 0; + Valid = true; + } + else if (buffer.Length == 4) + { + //see CM-SP-SECv4.0-I01-190815.pdf, page page 29 + throw new NotImplementedException(String.Format("BP_UP {0} {1}", nameof(buffer.Length), buffer.Length)); + } + else + { + Valid = false; + } + } + + public bool OddKey { get; set; } + public bool Encrypted { get; private set; } + public int KEY_SEQ { get; set; } + + public int Version { get; private set; } + } + + public UpstreamPrivacyElement UpstreamPrivacyEhElement { get; private set; } + + public byte PayloadHeaderSupression { get; set; } + + public class DownstreamPrivacyElement + { + public DownstreamPrivacyElement(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + KEY_SEQ = (readUInt8 & 0xf0) >> 4; + Version = (readUInt8 & 0x0f); + ushort readUInt16Be = ms.ReadUInt16BE(); + Encrypted = (readUInt16Be & 0x8000) != 0; + OddKey = (readUInt16Be & 0x4000) != 0; + SecurityAssociationId = (readUInt16Be & 0x3fff); + ms.ReadUInt8(); //reserved + + } + public int KEY_SEQ { get; set; } + public int Version { get; set; } + public bool Encrypted { get; set; } + public bool OddKey { get; set; } + public int SecurityAssociationId { get; set; } + } + + public DownstreamPrivacyElement DownstreamPrivacyEhElement { get; private set; } + + public bool Encrypted + { + get + { + if (DownstreamPrivacyEhElement == null) + return false; + return DownstreamPrivacyEhElement.Encrypted; + } + } + } +} diff --git a/skyscraper8/Docsis/MacManagement/BaselinePrivacyKeyManagementMessages/5_AuthReply.cs b/skyscraper8/Docsis/MacManagement/BaselinePrivacyKeyManagementMessages/5_AuthReply.cs new file mode 100644 index 0000000..2c4cef2 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/BaselinePrivacyKeyManagementMessages/5_AuthReply.cs @@ -0,0 +1,63 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.IO; + +namespace skyscraper5.Docsis.MacManagement.BaselinePrivacyKeyManagementMessages +{ + internal class AuthReply : PrivacyKeyManagementResponse.BpkmMessageObject + { + public AuthReply(byte[] buffer) : base(buffer) + { + } + + protected override void SetValue(byte type, byte[] v) + { + switch (type) + { + case 7: + AuthKey = v; + break; + case 9: + (v[3], v[2], v[1], v[0]) = (v[0], v[1], v[2], v[3]); + KeyLifetime = BitConverter.ToUInt32(v, 0); + break; + case 10: + KeySequenceNumber = v[0]; + break; + case 23: + SaDescriptor = new SaDescriptorObject(v); + break; + default: + //CM-SP-SECv4.0-I01-190815.pdf, page 52 + throw new NotImplementedException(); + } + } + + public byte KeySequenceNumber { get; private set; } + + public uint KeyLifetime { get; private set; } + public byte[] AuthKey { get; private set; } + public SaDescriptorObject SaDescriptor { get; private set; } + + internal class SaDescriptorObject + { + public SaDescriptorObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 3) + { + byte type = ms.ReadUInt8(); + ushort length = ms.ReadUInt16BE(); + byte[] value = ms.ReadBytes(length); + + switch(type) + { + default: + //CM-SP-SECv4.0-I01-190815.pdf, page 65 + throw new NotImplementedException(); + } + } + } + } + } +} diff --git a/skyscraper8/Docsis/MacManagement/BaselinePrivacyKeyManagementMessages/8_KeyReply.cs b/skyscraper8/Docsis/MacManagement/BaselinePrivacyKeyManagementMessages/8_KeyReply.cs new file mode 100644 index 0000000..e0beb24 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/BaselinePrivacyKeyManagementMessages/8_KeyReply.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Docsis.MacManagement.BaselinePrivacyKeyManagementMessages +{ + internal class KeyReply : PrivacyKeyManagementResponse.BpkmMessageObject + { + public KeyReply(byte[] buffer) : base(buffer) + { + } + + protected override void SetValue(byte type, byte[] v) + { + switch (type) + { + case 10: + KeySequenceNumber = v[0]; + break; + case 11: + HmacDigest = v; + break; + case 12: + (v[1], v[0]) = (v[0], v[1]); + SAID = BitConverter.ToUInt16(v, 0); + break; + case 13: + if (TekParameters == null) + TekParameters = new List(); + TekParameters.Add(new TekParameter(v)); + break; + default: + //CM-SP-SECv4.0-I01-190815.pdf page 53 + throw new NotImplementedException(); + } + } + + public byte[] HmacDigest { get; set; } + + public List TekParameters { get; private set; } + public class TekParameter + { + public TekParameter(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 3) + { + byte type = ms.ReadUInt8(); + ushort length = ms.ReadUInt16BE(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 8: + TEK = v; + break; + case 9: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + KeyLifetime = BitConverter.ToUInt32(v, 0); + break; + case 10: + TekKeySequenceNumber = v[0]; + break; + case 15: + CbcIv = v; + break; + default: + //CM-SP-SECv4.0-I01-190815.pdf page 60-61 + throw new NotImplementedException(); + } + } + } + + public byte[] CbcIv { get; set; } + + public byte? TekKeySequenceNumber { get; set; } + + public uint? KeyLifetime { get; set; } + + public byte[] TEK { get; set; } + + public override string ToString() + { + return $"{nameof(TekKeySequenceNumber)}: {TekKeySequenceNumber}, {nameof(TEK)}: {TEK}"; + } + } + + public ushort? SAID { get; set; } + + public byte? KeySequenceNumber { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T13_V1_PrivacyKeyManagementResponse.cs b/skyscraper8/Docsis/MacManagement/T13_V1_PrivacyKeyManagementResponse.cs new file mode 100644 index 0000000..b5bdc0d --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T13_V1_PrivacyKeyManagementResponse.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.MacManagement.BaselinePrivacyKeyManagementMessages; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(1,13)] + internal class PrivacyKeyManagementResponse : MacManagementMessage + { + public PrivacyKeyManagementResponse(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Code = ms.ReadUInt8(); + Identifier = ms.ReadUInt8(); + ushort length = ms.ReadUInt16BE(); + if (length > ms.GetAvailableBytes()) + return; + byte[] subbuffer = ms.ReadBytes(length); + + switch (Code) + { + case 5: BpkmMessage = new AuthReply(subbuffer); break; + case 8: BpkmMessage = new KeyReply(subbuffer); break; + default: + if (Code >= 16 || Code <= 3) + { + Valid = false; + return; + } + //CM-SP-SECv4.0-I01-190815.pdf page 50 + throw new NotImplementedException(String.Format("{0} {1}", nameof(Code), Code)); + } + Valid = true; + } + + public byte Identifier { get; private set; } + + public byte Code { get; private set; } + + public BpkmMessageObject BpkmMessage { get; private set; } + + public abstract class BpkmMessageObject + { + protected BpkmMessageObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 3) + { + byte type = ms.ReadUInt8(); + ushort length = ms.ReadUInt16BE(); + byte[] value = ms.ReadBytes(length); + SetValue(type, value); + } + } + + protected abstract void SetValue(byte type, byte[] v); + } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T16_V2_DynamicServiceAdditionResponse.cs b/skyscraper8/Docsis/MacManagement/T16_V2_DynamicServiceAdditionResponse.cs new file mode 100644 index 0000000..e37214a --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T16_V2_DynamicServiceAdditionResponse.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(2,16)] + internal class DynamicServiceAdditionResponse : MacManagementMessage + { + public DynamicServiceAdditionResponse(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + TransactionId = ms.ReadUInt16BE(); + ConfirmationCode = ms.ReadUInt8(); + TlvEncodedInformation = new CommonTlvEncodingObject(ms); + } + + public CommonTlvEncodingObject TlvEncodedInformation { get; set; } + + public byte ConfirmationCode { get; set; } + + public ushort TransactionId { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T18_V2_DynamicServiceChangeRequest.cs b/skyscraper8/Docsis/MacManagement/T18_V2_DynamicServiceChangeRequest.cs new file mode 100644 index 0000000..6451f2f --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T18_V2_DynamicServiceChangeRequest.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Net.NetworkInformation; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(2, 18)] + internal class T18_V2_DynamicServiceChangeRequest : MacManagementMessage + { + public T18_V2_DynamicServiceChangeRequest(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + TransactionId = ms.ReadUInt16BE(); + TlvEncodedInformation = new CommonTlvEncodingObject(ms); + } + + public CommonTlvEncodingObject TlvEncodedInformation { get; private set; } + + public ushort TransactionId { get; private set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T19_V2_DynamicServiceChangeResponse.cs b/skyscraper8/Docsis/MacManagement/T19_V2_DynamicServiceChangeResponse.cs new file mode 100644 index 0000000..76f507f --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T19_V2_DynamicServiceChangeResponse.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(2,19)] + internal class DynamicServiceChangeResponse : MacManagementMessage + { + public DynamicServiceChangeResponse(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + TransactionId = ms.ReadUInt16BE(); + ConfirmationCode = ms.ReadUInt8(); + + CommonEncodings = new CommonTlvEncodingObject(ms); + } + + public CommonTlvEncodingObject CommonEncodings { get; private set; } + + public byte ConfirmationCode { get; set; } + + public ushort TransactionId { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T22_V2_DynamicServiceDeletionResponse.cs b/skyscraper8/Docsis/MacManagement/T22_V2_DynamicServiceDeletionResponse.cs new file mode 100644 index 0000000..edfde32 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T22_V2_DynamicServiceDeletionResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(2,22)] + internal class DynamicServiceDeletionResponse : MacManagementMessage + { + public DynamicServiceDeletionResponse(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + (buffer[0], buffer[1]) = (buffer[1], buffer[0]); + TransactionId = BitConverter.ToUInt16(buffer, 0); + ConfirmationCode = buffer[2]; + } + + public byte ConfirmationCode { get; set; } + + public ushort TransactionId { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T29_V3_UpstreamChannelDescriptor.cs b/skyscraper8/Docsis/MacManagement/T29_V3_UpstreamChannelDescriptor.cs new file mode 100644 index 0000000..2bd6f71 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T29_V3_UpstreamChannelDescriptor.cs @@ -0,0 +1,379 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(3, 29)] + [MacManagementMessageType(5, 51)] + public class UpstreamChannelDescriptor : MacManagementMessage + { + public UpstreamChannelDescriptor(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + UpstreamChannelID = ms.ReadUInt8(); + ConfigurationChangeCount = ms.ReadUInt8(); + MinislotSize = ms.ReadUInt8(); + DownstreamChannelId = ms.ReadUInt8(); + + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + //Found in CM-SP-MULPIv3.1-I03-1406101.pdf on page 107 + case 1: + ModulationRate = v[0]; + break; + case 2: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + Frequency = BitConverter.ToUInt32(v); + break; + case 3: + PreamblePattern = v; + break; + case 5: + if (BurstDescriptors == null) + BurstDescriptors = new List(); + BurstDescriptors.Add(new BurstDescriptor(v)); + break; + case 6: + ExtendedPreamblePattern = v; + break; + case 7: + if (v[0] == 1) S_CDMAMode = true; + else if (v[0] == 2) S_CDMAMode = false; + else break; + break; + case 15: + //Undocumented as of DOCSIS 4.0 ? + break; + case 16: + RangingRequired = (RangingRequiredEnum)v[0]; + break; + case 23: + goto case 5; + case 24: + (v[0], v[1]) = (v[1], v[0]); + UcdChangeIndicator = new UcdChangeIndicatorBitmask(BitConverter.ToUInt16(v, 0)); + break; + case 25: + OfdmaTimestampSnapshot = v[0] & 0x0f; + OfdmaTimestampSnapshot <<= 8; //4 bits + OfdmaTimestampSnapshot += v[1]; + OfdmaTimestampSnapshot <<= 8; //12 bits + OfdmaTimestampSnapshot += v[2]; + OfdmaTimestampSnapshot <<= 8; //20 bits + OfdmaTimestampSnapshot += v[3]; + OfdmaTimestampSnapshot <<= 4; //28 bits + OfdmaTimestampSnapshot += ((v[4] & 0xf0) >> 4); + OfdmaTimestampSnapshotDivideBy20 = v[4] & 0x0f; + break; + case 26: + OfdmaCyclicPrefixSize = v[0]; + break; + case 27: + OfdmaRolloffPeriodSize = v[0]; + break; + case 28: + SubcarrierSpacing = v[0]; + break; + case 29: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + CenterFrequencyOfSubcarrier0 = BitConverter.ToUInt32(v); + break; + case 30: + SubcarrierExclusionBand = new Tuple[length / 4]; + for (int i = 0; i < length / 4; i++) + { + (v[(i * 4) + 0], v[(i * 4) + 1]) = (v[(i * 4) + 1], v[(i * 4) + 0]); + (v[(i * 4) + 2], v[(i * 4) + 3]) = (v[(i * 4) + 3], v[(i * 4) + 2]); + SubcarrierExclusionBand[i] = new Tuple(BitConverter.ToUInt16(v, (i * 4) + 0), BitConverter.ToUInt16(v, (i * 4) + 2)); + } + break; + case 32: + SymbolsInOfdmaFrame = v[0]; + break; + case 33: + byte[] v2 = new byte[4]; + (v2[0], v2[1], v2[2]) = (v[2], v[1], v[0]); + RandomizationSeed = BitConverter.ToUInt32(v2, 0); + break; + default: + throw new NotImplementedException(String.Format("{0} TLV type {1}", nameof(UpstreamChannelDescriptor), type)); + + } + } + Valid = true; + } + + public uint RandomizationSeed { get; set; } + + public byte? SymbolsInOfdmaFrame { get; set; } + + public Tuple[] SubcarrierExclusionBand { get; private set; } + + public uint? CenterFrequencyOfSubcarrier0 { get; set; } + + public byte? SubcarrierSpacing { get; set; } + + public byte? OfdmaRolloffPeriodSize { get; set; } + + public byte? OfdmaCyclicPrefixSize { get; set; } + + public int? OfdmaTimestampSnapshotDivideBy20 { get; set; } + + public int? OfdmaTimestampSnapshot { get; set; } + + public UcdChangeIndicatorBitmask UcdChangeIndicator { get; private set; } + + public List BurstDescriptors { get; private set; } + + public bool? S_CDMAMode { get; set; } + + public byte[] PreamblePattern { get; set; } + + public uint? Frequency { get; set; } + + public byte? ModulationRate { get; set; } + + public byte DownstreamChannelId { get; set; } + + public byte MinislotSize { get; set; } + + public byte ConfigurationChangeCount { get; set; } + + public byte UpstreamChannelID { get; set; } + + public byte[] ExtendedPreamblePattern { get; private set; } + + public RangingRequiredEnum RangingRequired { get; private set; } + + public enum RangingRequiredEnum : byte + { + NoRangingRequired = 0, + UnicastInitialRangingRequired = 1, + BroadcastInitialRangingRequired = 2, + ProbingRequired = 3 + } + + + public class BurstDescriptor + { + public BurstDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + IntervalUsageCode = ms.ReadUInt8(); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + //Found in CM-SP-MULPIv4.0-I01-190815.pdf, page 118 + case 1: + ModulationType = v[0]; + break; + case 2: + if (v[0] == 1) + DifferentialEncoding = true; + else if (v[0] == 2) + DifferentialEncoding = false; + else + break; + break; + case 3: + (v[1], v[0]) = (v[0], v[1]); + PreambleLength = BitConverter.ToUInt16(v, 0); + break; + case 4: + (v[1], v[0]) = (v[0], v[1]); + PreambleValueOffset = BitConverter.ToUInt16(v, 0); + break; + case 5: + FecErrorCorrection = v[0]; + break; + case 6: + FecCodewordInformationBytes = v[0]; + break; + case 7: + (v[1], v[0]) = (v[0], v[1]); + ScramblerSeed = BitConverter.ToUInt16(v, 0); + ScramblerSeed &= 0x7fff; + break; + case 8: + MaximumBurstSize = v[0]; + break; + case 9: + GuardTimeSize = v[0]; + break; + case 10: + LastCodewordLength = (CodewordLength)v[0]; + break; + case 11: + if (v[0] == 1) + Scrambler = true; + else if (v[0] == 2) + Scrambler = false; + else + break; + break; + case 12: + RsInterleaverDepth = v[0]; + break; + case 13: + (v[1], v[0]) = (v[0], v[1]); + RsInterleaverBlockSize = BitConverter.ToUInt16(v, 0); + break; + case 14: + PreambleType = (PreambleTypeEnum)v[0]; + break; + case 19: + (v[1], v[0]) = (v[0], v[1]); + SubcarriersInitialRanging = BitConverter.ToUInt16(v, 0); + break; + case 20: + (v[1], v[0]) = (v[0], v[1]); + SubcarriersFineRanging = BitConverter.ToUInt16(v, 0); + break; + case 21: + OfdmaDataProfiles = new OfdmaDataProfile[length / 2]; + for (int i = 0; i < length / 2; i++) + { + OfdmaDataProfiles[i] = new OfdmaDataProfile(v[(i * 2) + 0], v[(i * 2) + 1]); + } + break; + case 22: + OfdmaBroadcastIrStartingPowerLevel = (double)v[0] * 1.6; + OfdmaBroadcastIrStartingPowerLevelIncrease = (double)v[1] * 0.25; + break; + default: + throw new NotImplementedException(String.Format("{0} {1} {2}", nameof(BurstDescriptor), nameof(type), type)); + } + } + } + + public OfdmaDataProfile[] OfdmaDataProfiles { get; private set; } + + public ushort? SubcarriersFineRanging { get; set; } + + public double? OfdmaBroadcastIrStartingPowerLevelIncrease { get; set; } + public double? OfdmaBroadcastIrStartingPowerLevel { get; set; } + + public ushort? SubcarriersInitialRanging { get; set; } + + public PreambleTypeEnum? PreambleType { get; private set; } + public enum PreambleTypeEnum : byte + { + QPSK0 = 1, + QPSK1 = 2 + } + + public ushort? RsInterleaverBlockSize { get; set; } + + public byte? RsInterleaverDepth { get; set; } + + public bool? Scrambler { get; set; } + + public CodewordLength? LastCodewordLength { get; set; } + public enum CodewordLength : byte + { + Fixed = 1, + Shortened = 2 + } + + public byte? GuardTimeSize { get; set; } + + public byte? MaximumBurstSize { get; set; } + + public int? ScramblerSeed { get; set; } + + public byte? FecCodewordInformationBytes { get; set; } + + public byte? FecErrorCorrection { get; set; } + + public ushort? PreambleValueOffset { get; set; } + + public ushort? PreambleLength { get; set; } + + public bool? DifferentialEncoding { get; set; } + + public byte? ModulationType { get; set; } + + public byte IntervalUsageCode { get; set; } + } + + public class UcdChangeIndicatorBitmask + { + private readonly ushort rawValue; + + public UcdChangeIndicatorBitmask(ushort readUInt16Be) + { + this.rawValue = readUInt16Be; + } + + public bool ChangeIuc14 => (rawValue & 0x0020) != 0; + public bool ChangeIuc13 => (rawValue & 0x0040) != 0; + public bool ChangeIuc12 => (rawValue & 0x0080) != 0; + public bool ChangeIuc11 => (rawValue & 0x0100) != 0; + public bool ChangeIuc10 => (rawValue & 0x0200) != 0; + public bool ChangeIuc9 => (rawValue & 0x0400) != 0; + public bool ChangeIuc6 => (rawValue & 0x0800) != 0; + public bool ChangeIuc5 => (rawValue & 0x1000) != 0; + public bool ChangeOtherParameters => (rawValue & 0x2000) != 0; + public bool ChangeUnusedSubcarrierSpecification => (rawValue & 0x4000) != 0; + public bool ChangeSubcarrierExclusionBand => (rawValue & 0x8000) != 0; + + public ushort GetRawValue() + { + return rawValue; + } + } + + public class OfdmaDataProfile + { + private readonly byte first; + private readonly byte second; + + public OfdmaDataProfile(byte first, byte second) + { + this.first = first; + this.second = second; + } + + public ModulationOrderIndex Modulation => (ModulationOrderIndex)((first & 0xf0) >> 4); + + public int PilotPatternIndex => (first & 0x0f); + + public byte NumberOfMinisolts => second; + + public enum ModulationOrderIndex + { + NoBitLoading, + Reserved, + QPSK, + _8QAM, + _16QAM, + _32QAM, + _64QAM, + _128QAM, + _256QAM, + _512QAM, + _1024QAM, + _2048QAM, + _4096QAM + } + } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T33_V4_MacDomainDescriptor.cs b/skyscraper8/Docsis/MacManagement/T33_V4_MacDomainDescriptor.cs new file mode 100644 index 0000000..c9f8293 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T33_V4_MacDomainDescriptor.cs @@ -0,0 +1,513 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(4,33)] + public class MacDomainDescriptor : MacManagementMessage + { + public MacDomainDescriptor(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ConfigurationChangeCount = ms.ReadUInt8(); + NumberOfFragments = ms.ReadUInt8(); + FragmentSequenceNumber = ms.ReadUInt8(); + CurrentChannelDcid = ms.ReadUInt8(); + + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + DownstreamActiveChannel downstreamActiveChannel = new DownstreamActiveChannel(v); + if (DownstreamActiveChannels == null) + DownstreamActiveChannels = new List(); + DownstreamActiveChannels.Add(downstreamActiveChannel); + break; + case 2: + DownstreamServiceGroup = new DownstreamServiceGroupObject(v); + break; + case 3: + DownstreamAmbiguityResolutionFrequencyList = ParseDownDownstreamAmbiguityResolutionFrequencyList(v); + break; + case 4: + ReceiveChannelProfileReportingControl = new ReceiveChannelProfileReportingControlObject(v); + break; + case 5: + IpInitalizationParameters = new IpInitalizationParametersObject(v); + break; + case 6: + EarlyAuthentication = v[0]; + break; + case 7: + if (ActiveUpstreamChannelList == null) + { + ActiveUpstreamChannelList = new List(); + } + ActiveUpstreamChannelList.Add(new ActiveUpstreamChannelListObject(v)); + break; + case 8: + UpstreamChannelIds = v; + break; + case 9: + UpstreamFrequencyRange = v[0]; + break; + case 10: + SymbolClockLocking = v[0] != 0; + break; + case 11: + if (EventControlEncoding == null) + EventControlEncoding = new List(); + EventControlEncoding.Add(new EventControlEncodingObject(v)); + break; + case 12: + UpstreamTransmitPowerReporting = v[0]; + break; + case 15: + SequenceOutOfRange = (v[1] & 0x08) != 0; + CmOperatingOnBatteryBackup = (v[0] & 0x02) != 0; + CmReturnedToAcPower = (v[0] & 0x04) != 0; + CmMacAddressRemoval = (v[0] & 0x08) != 0; + break; + case 16: + ExtendedUpstreamTransmitPowerSupport = v[0]; + break; + case 17: + CmtsDocsisVersion = new CmtsDocsisVersionObject(v); + break; + case 18: + CmPeriodicMaintenanceTimeout = (CmPeriodicMaintenanceTimeoutEnum)v[0]; + break; + case 20: + DownstreamOfdmProfileFailure = (v[3] & 0x01) != 0; + PrimaryDownstreamChannelChange = (v[3] & 0x02) != 0; + DpdMismatch = (v[3] & 0x04) != 0; + bool Deprecated = (v[3] & 0x08) != 0; + NcpProfileFailure = (v[3] & 0x10) != 0; + PlcFailure = (v[3] & 0x20) != 0; + NcpProfileRecovery = (v[3] & 0x40) != 0; + PlcRecovery = (v[3] & 0x80) != 0; + OfdmProfileRecovery = (v[2] & 0x01) != 0; + OfdmaProfileFailure = (v[2] & 0x02) != 0; + MapStorageOverflowIndicator = (v[2] & 0x04) != 0; + MapStorageAlmostFullIndicator = (v[2] & 0x08) != 0; + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf see Page 176 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(MacDomainDescriptor), type)); + } + } + Valid = true; + } + + public CmPeriodicMaintenanceTimeoutEnum? CmPeriodicMaintenanceTimeout { get; private set; } + public enum CmPeriodicMaintenanceTimeoutEnum : byte + { + UseUnicastRanging = 0, + UseProbe = 1, + UseUnicastOrProbe = 2 + } + + public CmtsDocsisVersionObject CmtsDocsisVersion { get; private set; } + public class CmtsDocsisVersionObject + { + public CmtsDocsisVersionObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + MajorVersion = v[0]; + break; + case 2: + MinorVersion = v[0]; + break; + default: + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(CmtsDocsisVersionObject), type)); + } + } + } + + public byte? MinorVersion { get; set; } + + public byte? MajorVersion { get; set; } + } + + public byte? ExtendedUpstreamTransmitPowerSupport { get; set; } + + public bool? CmMacAddressRemoval { get; set; } + + public bool? CmReturnedToAcPower { get; set; } + + public bool? CmOperatingOnBatteryBackup { get; set; } + + public bool? SequenceOutOfRange { get; set; } + + public byte? UpstreamTransmitPowerReporting { get; set; } + + public bool? MapStorageAlmostFullIndicator { get; set; } + + public bool? MapStorageOverflowIndicator { get; set; } + + public bool? OfdmaProfileFailure { get; set; } + + public bool? OfdmProfileRecovery { get; set; } + + public bool? PlcRecovery { get; set; } + + public bool? NcpProfileRecovery { get; set; } + + public bool? PlcFailure { get; set; } + + public bool? NcpProfileFailure { get; set; } + + public bool? DpdMismatch { get; set; } + + public bool? PrimaryDownstreamChannelChange { get; set; } + + public bool? DownstreamOfdmProfileFailure { get; set; } + + public List EventControlEncoding { get; private set; } + public class EventControlEncodingObject + { + public EventControlEncodingObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + EventTypeCode = v[0]; //CM-SP-MULPIv4.0-I01-190815.pdf Page 482 + break; + case 2: + (v[0], v[1]) = (v[1], v[0]); + MaximumEventHoldoffTimer = new TimeSpan(0, 0, 0, 0, BitConverter.ToUInt16(v, 0) * 20); + break; + case 3: + MaximumNumberOfReports = v[0]; + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf page 184 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(EventControlEncodingObject), type)); + } + } + } + + public byte? MaximumNumberOfReports { get; set; } + + public TimeSpan? MaximumEventHoldoffTimer { get; set; } + + public byte? EventTypeCode { get; set; } + + public override string ToString() + { + return $"{nameof(EventTypeCode)}: {EventTypeCode}, {nameof(MaximumEventHoldoffTimer)}: {MaximumEventHoldoffTimer}, {nameof(MaximumNumberOfReports)}: {MaximumNumberOfReports}"; + } + } + + public bool? SymbolClockLocking { get; set; } + + public byte? UpstreamFrequencyRange { get; set; } + + public byte[] UpstreamChannelIds { get; set; } + + public List ActiveUpstreamChannelList { get; set; } + public class ActiveUpstreamChannelListObject + { + public ActiveUpstreamChannelListObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + UpstreamChannelId = v[0]; + break; + case 2: + T4Timeout = (v[0] & 0x02) != 0; + T3RetriesExceed = (v[0] & 0x01) != 0; + SucessfulRangingAfterT3RetriesExceeded = (v[1] & 0x80) != 0; + break; + case 3: + UpstreamChannelPriority = v[0]; + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf page 182 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(ActiveUpstreamChannelListObject), type)); + } + } + } + + public byte? UpstreamChannelPriority { get; set; } + + public bool? SucessfulRangingAfterT3RetriesExceeded { get; set; } + + public bool? T3RetriesExceed { get; set; } + + public bool? T4Timeout { get; set; } + + public byte? UpstreamChannelId { get; set; } + + public override string ToString() + { + return $"{nameof(UpstreamChannelId)}: {UpstreamChannelId}"; + } + } + + public byte? EarlyAuthentication { get; set; } + + public IpInitalizationParametersObject IpInitalizationParameters { get; private set; } + public class IpInitalizationParametersObject + { + public IpInitalizationParametersObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + IpProvisioningMode = (IpProvisioningModeEnum)v[0]; + break; + case 2: + byte[] tmp = new byte[4]; + (tmp[0], tmp[1], tmp[2]) = (v[2], v[1], v[0]); + DsidValue = BitConverter.ToUInt32(tmp, 0); + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf page 181 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(IpInitalizationParametersObject), type)); + } + } + } + + public uint? DsidValue { get; set; } + + public IpProvisioningModeEnum? IpProvisioningMode { get; private set; } + public enum IpProvisioningModeEnum : byte + { + IPv4 = 0, + IPv6 = 1, + Alternate = 2, + DualStack = 3 + } + } + + public ReceiveChannelProfileReportingControlObject ReceiveChannelProfileReportingControl { get; private set; } + public class ReceiveChannelProfileReportingControlObject + { + public ReceiveChannelProfileReportingControlObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, true); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + RcpScQamCenterFrequencySpacing = v[0]; + break; + case 2: + VerboseRcpReporting = v[0]; + break; + case 3: + FragmentRcpTransmission = v[0]; + break; + default: + // CM-SP-MULPIv4.0-I01-190815.pdf page 180 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(ReceiveChannelProfileReportingControlObject), type)); + } + } + } + + public byte? FragmentRcpTransmission { get; set; } + + public byte? VerboseRcpReporting { get; set; } + + public byte? RcpScQamCenterFrequencySpacing { get; set; } + } + + public DownstreamServiceGroupObject DownstreamServiceGroup { get; private set; } + public class DownstreamServiceGroupObject + { + public DownstreamServiceGroupObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + MD_DS_SG_ID = v[0]; + break; + case 2: + DownstreamChannelIds = v; + break; + default: + //CM-SP-MULPIv4.0-I01-190815.pdf, page 179 + throw new NotImplementedException(String.Format("{0} TLV {1}", nameof(DownstreamServiceGroupObject), type)); + } + } + } + + public byte[] DownstreamChannelIds { get; private set; } + + public byte MD_DS_SG_ID { get; set; } + } + + public List DownstreamActiveChannels { get; private set; } + + public class DownstreamActiveChannel + { + public DownstreamActiveChannel(byte[] bytes) + { + MemoryStream ms = new MemoryStream(bytes, false); + while (ms.GetAvailableBytes() >= 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + ChannelId = v[0]; + break; + case 2: + (v[3], v[2], v[1], v[0]) = (v[0], v[1], v[2], v[3]); + Frequency = BitConverter.ToUInt32(v, 0); + break; + case 3: + Annex = (AnnexEnum)((v[0] & 0xf0) >> 4); + ModulationOrder = (ModulationOrderEnum)(v[0] & 0x0f); + break; + case 4: + PrimaryCapable = (PrimaryCapableEnum)(v[0]); + break; + case 5: + (v[1], v[0]) = (v[0], v[1]); + ushort uInt16 = BitConverter.ToUInt16(v, 0); + bool reserved = (uInt16 & 0x0001) != 0; + MddTimeout = (uInt16 & 0x0002) != 0; + QamFecLockFailure = (uInt16 & 0x0004) != 0; + reserved = (uInt16 & 0x0008) != 0; + MddRecovery = (uInt16 & 0x0010) != 0; + QamFecLockRecovery = (uInt16 & 0x0020) != 0; + break; + case 7: + _50khzSubcarrierSpacing = (v[0] & 0x40) != 0; + CyclicPrefix = (v[0] & 0x38) >> 3; + TukeyRaisedCosineWindow = (v[0] & 0x07); + break; + default: + throw new NotImplementedException(String.Format("{0} Sub-TLV Type {1}", + nameof(MacDomainDescriptor), type)); + } + } + } + + public int? TukeyRaisedCosineWindow { get; set; } + + public int? CyclicPrefix { get; set; } + + public bool? _50khzSubcarrierSpacing { get; set; } + + public bool? QamFecLockRecovery { get; set; } + + public bool? MddRecovery { get; set; } + + public bool? QamFecLockFailure { get; set; } + + public bool? MddTimeout { get; set; } + + public PrimaryCapableEnum? PrimaryCapable { get; private set; } + + public enum PrimaryCapableEnum : byte + { + NotPrimaryCapable = 0, + PrimaryCapable = 1, + FdxDownstream = 2 + } + + public ModulationOrderEnum? ModulationOrder { get; private set; } + public AnnexEnum? Annex { get; private set; } + + public enum ModulationOrderEnum + { + _64QAM = 0, + _256QAM = 1, + C_DOCSIS = 2 + } + + public enum AnnexEnum + { + J83AnnexA = 0, + J83AnnexB = 1, + J83AnnexC = 2 + } + + public uint? Frequency { get; set; } + + public byte? ChannelId { get; set; } + + public override string ToString() + { + return $"{nameof(ChannelId)}: {ChannelId}, {nameof(Frequency)}: {Frequency}, {nameof(ModulationOrder)}: {ModulationOrder}"; + } + } + + public byte CurrentChannelDcid { get; set; } + + public byte FragmentSequenceNumber { get; set; } + + public byte NumberOfFragments { get; set; } + + public byte ConfigurationChangeCount { get; set; } + public uint[] DownstreamAmbiguityResolutionFrequencyList { get; } + + ///TODO: This is useful data! Gets all the frequencies that contain DOCSIS carriers. + public uint[] ParseDownDownstreamAmbiguityResolutionFrequencyList(byte[] buffer) + { + uint[] result = new uint[buffer.Length / 4]; + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < result.Length; i++) + { + result[i] = ms.ReadUInt32BE(); + } + ms.Dispose(); + return result; + } + + } +} diff --git a/skyscraper8/Docsis/MacManagement/T36_V4_DynamicBondingChangeRequest.cs b/skyscraper8/Docsis/MacManagement/T36_V4_DynamicBondingChangeRequest.cs new file mode 100644 index 0000000..e27577e --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T36_V4_DynamicBondingChangeRequest.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(4,36)] + internal class DynamicBondingChangeRequest : MacManagementMessage + { + public DynamicBondingChangeRequest(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + //CM-SP-MULPIv4.0-I01-190815.pdf page 191 is wrong! + //CM-SP-MULPIv3.1-I03-1406101.pdf page 177 is right instead! + MemoryStream ms = new MemoryStream(buffer, false); + TransactionId = ms.ReadUInt16BE(); + NumberOfFragments = ms.ReadUInt8(); + FragmentSequenceNumber = ms.ReadUInt8(); + TlvEncodedInformation = new CommonTlvEncodingObject(ms); + Valid = true; + } + + public byte FragmentSequenceNumber { get; set; } + + public byte NumberOfFragments { get; set; } + + public CommonTlvEncodingObject TlvEncodedInformation { get; set; } + + public ushort TransactionId { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T38_V4_DynamicBondingChangeAcknowledge.cs b/skyscraper8/Docsis/MacManagement/T38_V4_DynamicBondingChangeAcknowledge.cs new file mode 100644 index 0000000..48bdf4b --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T38_V4_DynamicBondingChangeAcknowledge.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(4,38)] + internal class DynamicBondingChangeAcknowledge : MacManagementMessage + { + public DynamicBondingChangeAcknowledge(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + TransactionId = ms.ReadUInt16BE(); + TlvEncodedInformation = new CommonTlvEncodingObject(ms); + Valid = true; + } + + public CommonTlvEncodingObject TlvEncodedInformation { get; set; } + + public ushort TransactionId { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T3_V1_UpstreamBandwidthAllocation.cs b/skyscraper8/Docsis/MacManagement/T3_V1_UpstreamBandwidthAllocation.cs new file mode 100644 index 0000000..bfb9993 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T3_V1_UpstreamBandwidthAllocation.cs @@ -0,0 +1,65 @@ +using System.IO; +using System.Net.NetworkInformation; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(1,3)] + internal class UpstreamBandwidthAllocation : MacManagementMessage + { + public UpstreamBandwidthAllocation(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + UpstreamChannelId = ms.ReadUInt8(); + UcdCount = ms.ReadUInt8(); + NumberOfElements = ms.ReadUInt8(); + byte reserved = ms.ReadUInt8(); + + AllocStartTime = ms.ReadUInt32BE(); + + AckTime = ms.ReadUInt32BE(); + + RangingBackoffStart = ms.ReadUInt8(); + RangingBackoffEnd = ms.ReadUInt8(); + DataBackoffStart = ms.ReadUInt8(); + DataBackoffEnd = ms.ReadUInt8(); + + long mapEntries = ms.GetAvailableBytes() / 4; + Map = new Triplet[mapEntries]; + + for (long i = 0; i < mapEntries; i++) + { + uint mapEntry = ms.ReadUInt32BE(); + uint sid = (mapEntry & 0xfffc0000) >> 18; + uint iuc = (mapEntry & 0x0003c000) >> 14; + uint off = (mapEntry & 0x00003fff); + Map[i] = new Triplet(sid, iuc, off); + } + Valid = true; + } + + public Triplet[] Map { get; private set; } + + public byte DataBackoffEnd { get; private set; } + + public byte DataBackoffStart { get; private set; } + + public byte RangingBackoffEnd { get; private set; } + + public byte RangingBackoffStart { get; private set; } + + public uint AckTime { get; private set; } + + public uint AllocStartTime { get; private set; } + + public byte NumberOfElements { get; private set; } + + public byte UcdCount { get; private set; } + + public byte UpstreamChannelId { get; private set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T3_V5_UpstreamBandwidthAllocation.cs b/skyscraper8/Docsis/MacManagement/T3_V5_UpstreamBandwidthAllocation.cs new file mode 100644 index 0000000..b68041f --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T3_V5_UpstreamBandwidthAllocation.cs @@ -0,0 +1,77 @@ +using System.IO; +using System.Net.NetworkInformation; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(5, 3)] + internal class UpstreamBandwidthAllocationV5 : MacManagementMessage + { + public UpstreamBandwidthAllocationV5(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + UpstreamChannelId = ms.ReadUInt8(); + UcdCount = ms.ReadUInt8(); + + ushort readUInt16Be = ms.ReadUInt16BE(); + int numberOfElements = (readUInt16Be & 0xff80) >> 7; + int rsvd = (readUInt16Be & 0x0070) >> 4; + CAT = (readUInt16Be & 0x000f); + + AllocStartTime = ms.ReadUInt32BE(); + ProbeInformationElements = new ProbeInformationElement[numberOfElements]; + + for (int i = 0; i < numberOfElements; i++) + { + ProbeInformationElements[i] = new ProbeInformationElement(ms.ReadUInt32BE()); + } + Valid = true; + } + + internal struct ProbeInformationElement + { + public ProbeInformationElement(uint value) + { + this.RawValue = value; + } + + public uint RawValue { get; private set; } + + public uint SID => (RawValue & 0xfffc0000) >> 18; + + public bool MER => (RawValue & 0x00020000) != 0; + + public bool Power => (RawValue & 0x00010000) != 0; + + public bool TxEqualization => (RawValue & 0x00008000) != 0; + + public bool Stagger => (RawValue & 0x00004000) != 0; + + public uint ProbeFrame => (RawValue & 0x00003000) >> 12; + + public uint SymbolInFrame => (RawValue & 0x00000fc0) >> 6; + + public uint StartSubcarrier => (RawValue & 0x00000038) >> 3; + + public uint SubcarrierSkipping => (RawValue & 0x00000007); + + public override string ToString() + { + return $"{nameof(SID)}: {SID}, {nameof(ProbeFrame)}: {ProbeFrame}, {nameof(SymbolInFrame)}: {SymbolInFrame}, {nameof(StartSubcarrier)}: {StartSubcarrier}, {nameof(SubcarrierSkipping)}: {SubcarrierSkipping}"; + } + } + + public ProbeInformationElement[] ProbeInformationElements { get; private set; } + + public uint AllocStartTime { get; private set; } + + public int CAT { get; private set; } + + public byte UcdCount { get; private set; } + + public byte UpstreamChannelId { get; private set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T45_V4_MultipartRegistrationResponse.cs b/skyscraper8/Docsis/MacManagement/T45_V4_MultipartRegistrationResponse.cs new file mode 100644 index 0000000..17db4a8 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T45_V4_MultipartRegistrationResponse.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Net.NetworkInformation; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(4,45)] + internal class T45_V4_MultipartRegistrationResponse : MacManagementMessage + { + public T45_V4_MultipartRegistrationResponse(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SID = ms.ReadUInt16BE(); + Response = ms.ReadUInt8(); + NumberOfFragments = ms.ReadUInt8(); + FragmentSequenceNumber = ms.ReadUInt8(); + TlvEncodedInformation = new CommonTlvEncodingObject(ms); + } + + public CommonTlvEncodingObject TlvEncodedInformation { get; set; } + + public byte FragmentSequenceNumber { get; set; } + + public byte NumberOfFragments { get; set; } + + public byte Response { get; set; } + + public ushort SID { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T48_V4_StatusReportAcknowledge.cs b/skyscraper8/Docsis/MacManagement/T48_V4_StatusReportAcknowledge.cs new file mode 100644 index 0000000..bd486d2 --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T48_V4_StatusReportAcknowledge.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(4,48)] + internal class StatusReportAcknowledge : MacManagementMessage + { + public StatusReportAcknowledge(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + (buffer[0], buffer[1]) = (buffer[1], buffer[0]); + TransactionId = BitConverter.ToUInt16(buffer, 0); + EventType = buffer[2]; + Valid = true; + } + + + public byte EventType { get; set; } + + public ushort TransactionId { get; set; } + } +} diff --git a/skyscraper8/Docsis/MacManagement/T5_V1_RangingResponse.cs b/skyscraper8/Docsis/MacManagement/T5_V1_RangingResponse.cs new file mode 100644 index 0000000..ccd14ee --- /dev/null +++ b/skyscraper8/Docsis/MacManagement/T5_V1_RangingResponse.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Numerics; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.AnnexC; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Docsis.MacManagement +{ + [SkyscraperPlugin] + [MacManagementMessageType(1,5)] + [MacManagementMessageType(5,5)] + public class RangingResponse : MacManagementMessage + { + public RangingResponse(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SID = ms.ReadUInt16BE(); + UpstreamChannelId = ms.ReadUInt8(); + long positionFor1516; + + while (ms.GetAvailableBytes() > 2) + { + positionFor1516 = ms.Position; + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 0: + //reserved + break; + case 1: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + TimingAdjust = BitConverter.ToUInt32(v, 0); + break; + case 2: + PowerLevelAdjust = v[0]; + break; + case 3: + (v[0], v[1]) = (v[1], v[0]); + OffsetFrequencyAdjust = BitConverter.ToUInt16(v, 0); + break; + case 4: + TxEqualizationData = v; + break; + case 5: + RangingStatus = (RangingStatusEnum)v[0]; + break; + case 6: + (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]); + DownstreamFrequencyOverride = BitConverter.ToUInt32(v, 0); + break; + case 7: + UpstreamChannelIdOverride = v[0]; + break; + case 8: + TimingAdjustFractionalPart = v[0]; + break; + case 9: + TransmitEqualizationSet = v; + break; + case 10: + SCdmaMaximumScheduledCodes = v[0]; + break; + case 11: + SCdmaPowerHeadroom = v[0]; + break; + case 12: + byte[] upstreamChannelAdjustmentsBuffer = ms.ReadBytes(length); + UpstreamChannelAdjustments = new UpstreamChannelAdjustmentsObject(upstreamChannelAdjustmentsBuffer); + break; + case 13: + T4TimeoutMultiplier = v[0]; + break; + case 14: + DynamicRangeWindowUpperEdge = ((double)v[0]) * 0.25; + break; + case 15: + case 16: + ms.Position = positionFor1516; + byte type2 = ms.ReadUInt8(); + ushort newLen = ms.ReadUInt16BE(); + if (ms.GetAvailableBytes() < newLen) + { + newLen = (ushort)ms.GetAvailableBytes(); + } + v = ms.ReadBytes(newLen); + TransmitEqualizationEncodingsForOfdmaChannels result1516 = Parse1516(v); + if (type == 15) + TransmitEqualizationAdjustForOfdmaChannels = Parse1516(v); + else if (type == 16) + TransmitEqualizationSetForOfdmaChannels = Parse1516(v); + else + throw new NotImplementedException(String.Format("2-byte TLV entry in Ranging Response with ID {0}", type)); + break; + case 17: + CommandPower = new CommandPowerObject(v); + if (!CommandPower.Valid) + { + CommandPower = null; + Valid = false; + return; + } + break; + case 18: + FdxCommandedPower = new CommandPowerObject(v); + if (!FdxCommandedPower.Valid) + { + FdxCommandedPower = null; + Valid = false; + return; + } + break; + default: + if (type > 18) + { + Valid = false; + return; + } + //see CM-SP-MULPIv4.0-I01-190815.pdf page 141 + throw new NotImplementedException(String.Format("{0} Type {1}", nameof(RangingResponse), type)); + } + } + Valid = true; + } + + public byte[] TransmitEqualizationSet { get; private set; } + + private TransmitEqualizationEncodingsForOfdmaChannels Parse1516(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + byte l = ms.ReadUInt8(); + byte m = ms.ReadUInt8(); + byte r = ms.ReadUInt8(); + TransmitEqualizationEncodingsForOfdmaChannels result = new TransmitEqualizationEncodingsForOfdmaChannels(); + result.LowestSubcarrier = (l << 4); + result.LowestSubcarrier += ((m & 0xf0) >> 4); + result.HighestSubcarrier = (m & 0x0f); + result.HighestSubcarrier <<= 4; + result.HighestSubcarrier += r; + + long numCoefficents = ms.GetAvailableBytes() / 4; + result.Coefficients = new Complex[numCoefficents]; + for (int i = 0; i < numCoefficents; i++) + { + ushort real = ms.ReadUInt16BE(); + ushort imaginary = ms.ReadUInt16BE(); + result.Coefficients[i] = new Complex(real, imaginary); + } + return result; + } + + public TransmitEqualizationEncodingsForOfdmaChannels TransmitEqualizationAdjustForOfdmaChannels { get; private set; } + private CommandPowerObject CommandPower { get; set; } + class CommandPowerObject : Validatable + { + public CommandPowerObject(byte[] buffer) + { + if (buffer.Length < 2) + { + Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + if (length > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + byte[] v = ms.ReadBytes(length); + switch (type) + { + case 1: + DynamicRangeWindow = v[0]; + break; + case 2: + TransmitPowerLevels = v; + break; + default: + Valid = false; + return; + } + } + Valid = true; + } + + public byte[] TransmitPowerLevels { get; set; } + + public byte? DynamicRangeWindow { get; set; } + } + + public byte? T4TimeoutMultiplier { get; set; } + + public byte[] TxEqualizationData { get; set; } + + public byte? PowerLevelAdjust { get; set; } + + public RangingStatusEnum? RangingStatus { get; private set; } + + public enum RangingStatusEnum : byte + { + Continue = 1, + Abort = 2, + Success = 3 + } + + public ushort? OffsetFrequencyAdjust { get; set; } + + public uint? TimingAdjust { get; set; } + + public ushort? UpstreamChannelId { get; set; } + + public ushort? SID { get; set; } + public TransmitEqualizationEncodingsForOfdmaChannels TransmitEqualizationSetForOfdmaChannels { get; private set; } + public double? DynamicRangeWindowUpperEdge { get; private set; } + public uint? DownstreamFrequencyOverride { get; } + public byte? UpstreamChannelIdOverride { get; private set; } + public byte? SCdmaPowerHeadroom { get; } + public UpstreamChannelAdjustmentsObject UpstreamChannelAdjustments { get; private set; } + private CommandPowerObject FdxCommandedPower { get; } + public byte? TimingAdjustFractionalPart { get; } + public byte? SCdmaMaximumScheduledCodes { get; } + + public class UpstreamChannelAdjustmentsObject : Validatable + { + public UpstreamChannelAdjustmentsObject(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + while (ms.GetAvailableBytes() > 2) + { + byte type = ms.ReadUInt8(); + byte length = ms.ReadUInt8(); + if (length > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + byte[] v = ms.ReadBytes(length); + switch (type) + { + default: + if (type > 6) + { + Valid = false; + return; + } + //See CM-SP-MULPIv4.0-I01-190815.pdf page 142 + throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(UpstreamChannelAdjustmentsObject), type)); + } + } + Valid = true; + } + } + + public class TransmitEqualizationEncodingsForOfdmaChannels + { + public int LowestSubcarrier { get; internal set; } + public int HighestSubcarrier { get; internal set; } + public Complex[] Coefficients { get; internal set; } + } + } +} diff --git a/skyscraper8/Docsis/MacManagementMessage.cs b/skyscraper8/Docsis/MacManagementMessage.cs new file mode 100644 index 0000000..6f1079d --- /dev/null +++ b/skyscraper8/Docsis/MacManagementMessage.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper5.Docsis +{ + public abstract class MacManagementMessage : Validatable + { + public PhysicalAddress Source { get; } + public PhysicalAddress Destination { get; } + + protected MacManagementMessage(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + Source = source; + Destination = destination; + } + + public MacManagementMessageTypeAttribute MessageType + { + get + { + Type type = this.GetType(); + object[] customAttributes = type.GetCustomAttributes(typeof(MacManagementMessageTypeAttribute), false); + if (customAttributes == null) + return null; + if (customAttributes.Length == 0) + return null; + MacManagementMessageTypeAttribute attribute = (MacManagementMessageTypeAttribute)customAttributes[0]; + return attribute; + } + } + } +} diff --git a/skyscraper8/Docsis/MacManagementMessageTypeAttribute.cs b/skyscraper8/Docsis/MacManagementMessageTypeAttribute.cs new file mode 100644 index 0000000..3962f97 --- /dev/null +++ b/skyscraper8/Docsis/MacManagementMessageTypeAttribute.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Docsis +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class MacManagementMessageTypeAttribute : Attribute + { + public byte Version { get; } + public byte Type { get; } + + public MacManagementMessageTypeAttribute(byte version, byte type) + { + Version = version; + Type = type; + } + + private bool Equals(MacManagementMessageTypeAttribute other) + { + return base.Equals(other) && Version == other.Version && Type == other.Type; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is MacManagementMessageTypeAttribute other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(base.GetHashCode(), Version, Type); + } + + public override string ToString() + { + return $"{nameof(Version)}: {Version}, {nameof(Type)}: {Type}"; + } + } +} diff --git a/skyscraper8/DsmCc/CompatibilityDescriptor.cs b/skyscraper8/DsmCc/CompatibilityDescriptor.cs new file mode 100644 index 0000000..966ff32 --- /dev/null +++ b/skyscraper8/DsmCc/CompatibilityDescriptor.cs @@ -0,0 +1,27 @@ +using System.IO; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.DsmCc +{ + class CompatibilityDescriptor + { + public CompatibilityDescriptor(MemoryStream ms) + { + ushort length = ms.ReadUInt16BE(); + if (length == 0) + return; + + ushort count = ms.ReadUInt16BE(); + Compatibilities = new Compatibility[count]; + for (ushort i = 0; i < count; i++) + { + if (ms.GetAvailableBytes() == 0) + return; + Compatibilities[i] = new Compatibility(ms); + } + } + + public Compatibility[] Compatibilities { get; } + } +} diff --git a/skyscraper8/DsmCc/Descriptors/0x13_CarouselIdentifierDescriptor.cs b/skyscraper8/DsmCc/Descriptors/0x13_CarouselIdentifierDescriptor.cs new file mode 100644 index 0000000..e7fa8b7 --- /dev/null +++ b/skyscraper8/DsmCc/Descriptors/0x13_CarouselIdentifierDescriptor.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x13,"PMT")] + [BannedTable("BAT","TSDT")] + class CarouselIdentifierDescriptor : TsDescriptor + { + public CarouselIdentifierDescriptor(byte[] buffer) + { + if (buffer.Length < 5) + return; + + MemoryStream ms = new MemoryStream(buffer, false); + CarouselId = ms.ReadUInt32BE(); + FormatId = ms.ReadUInt8(); + switch (FormatId) + { + case 0: + break; + case 1: + FormatSpecifier = new FormatSpecifier(ms); + break; + default: + throw new NotImplementedException(String.Format("Carousel format {0:X2}", FormatId)); + } + + Valid = true; + } + + public byte FormatId { get; private set; } + + public uint CarouselId { get; private set; } + public FormatSpecifier FormatSpecifier { get; } + } +} diff --git a/skyscraper8/DsmCc/Descriptors/0x14_AssociationTagDescriptor.cs b/skyscraper8/DsmCc/Descriptors/0x14_AssociationTagDescriptor.cs new file mode 100644 index 0000000..1616104 --- /dev/null +++ b/skyscraper8/DsmCc/Descriptors/0x14_AssociationTagDescriptor.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x14,"PMT")] + [BannedTable("TSDT")] + class AssociationTagDescriptor : TsDescriptor + { + public AssociationTagDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Use = ms.ReadUInt16BE(); + SelectorLength = ms.ReadUInt8(); + if (Use == 0x0000) + { + TransactionId = ms.ReadUInt32BE(); + TimeOut = ms.ReadUInt32BE(); + } + else if (Use == 0x0001) + { + if (SelectorLength != 0x00) + throw new DsmCcException("invalid {0}", nameof(AssociationTagDescriptor)); + } + else + { + Selector = ms.ReadBytes(SelectorLength); + } + + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] PrivateData { get; private set; } + + public byte[] Selector { get; private set; } + + public uint? TimeOut { get; private set; } + + public uint? TransactionId { get; private set; } + + public byte SelectorLength { get; private set; } + + public ushort Use { get; private set; } + } +} diff --git a/skyscraper8/DsmCc/Descriptors/0x15_DeferredAssociationTagsDescriptor.cs b/skyscraper8/DsmCc/Descriptors/0x15_DeferredAssociationTagsDescriptor.cs new file mode 100644 index 0000000..66762f2 --- /dev/null +++ b/skyscraper8/DsmCc/Descriptors/0x15_DeferredAssociationTagsDescriptor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x21,"PMT")] + [BannedTable("TSDT")] + class DeferredAssociationTagsDescriptor : TsDescriptor + { + public DeferredAssociationTagsDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte associationTagLoopLength = ms.ReadUInt8(); + if (associationTagLoopLength % 2 != 0) + return; + AssociationTag = new ushort[associationTagLoopLength / 2]; + for (int i = 0; i < associationTagLoopLength / 2; i++) + { + AssociationTag[i] = ms.ReadUInt16BE(); + } + + TransportStreamId = ms.ReadUInt16BE(); + ProgramNumber = ms.ReadUInt16BE(); + } + + public ushort ProgramNumber { get; private set; } + + public ushort TransportStreamId { get; private set; } + + public ushort[] AssociationTag { get; private set; } + } +} diff --git a/skyscraper8/DsmCc/Descriptors/0x1A_StreamEventDescriptor.cs b/skyscraper8/DsmCc/Descriptors/0x1A_StreamEventDescriptor.cs new file mode 100644 index 0000000..416cc1a --- /dev/null +++ b/skyscraper8/DsmCc/Descriptors/0x1A_StreamEventDescriptor.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x1a,"DSM-CC")] + [BannedTable("TSDT")] + public class StreamEventDescriptor : TsDescriptor + { + public StreamEventDescriptor(byte[] buffer) + { + if (buffer.Length < 10) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + EventId = ms.ReadUInt16BE(); + NptEventSeconds = ms.ReadUInt32BE(); + NptEventMicroseconds = ms.ReadUInt32BE(); + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] PrivateData { get; private set; } + + public uint NptEventMicroseconds { get; private set; } + + public uint NptEventSeconds { get; private set; } + + public ushort EventId { get; private set; } + + public override string ToString() + { + return Encoding.UTF8.GetString(PrivateData); + } + } +} diff --git a/skyscraper8/DsmCc/DsmCcAdaptionHeader.cs b/skyscraper8/DsmCc/DsmCcAdaptionHeader.cs new file mode 100644 index 0000000..0a4b2e2 --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcAdaptionHeader.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.DsmCc +{ + class DsmCcAdaptionHeader + { + public DsmCcAdaptionHeader(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + AdaptionType = ms.ReadUInt8(); + byte adaptionLength = ms.ReadUInt8(); + if (adaptionLength > ms.GetAvailableBytes()) + { + adaptionLength = 0; + return; + } + if (AdaptionType == 0x01) + { + ConditionalAccessType = ms.ReadUInt16BE(); + adaptionLength = ms.ReadUInt8(); + } + + if (adaptionLength > ms.GetAvailableBytes()) + { + adaptionLength = 0; + return; + } + AdationData = ms.ReadBytes(adaptionLength); + } + + public byte[] AdationData { get; private set; } + + public ushort? ConditionalAccessType { get; private set; } + + public byte AdaptionType { get; private set; } + } +} diff --git a/skyscraper8/DsmCc/DsmCcException.cs b/skyscraper8/DsmCc/DsmCcException.cs new file mode 100644 index 0000000..d1d9cbe --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; + +namespace skyscraper5.DsmCc +{ + class DsmCcException : Mpeg2Exception + { + public DsmCcMessage Message { get; } + + public DsmCcException(string s, params object[] args) : base(s, args) + { + } + + public DsmCcException(DsmCcMessage message, string s, params object[] args) : base(s, args) + { + Message = message; + } + } +} diff --git a/skyscraper8/DsmCc/DsmCcMessage.cs b/skyscraper8/DsmCc/DsmCcMessage.cs new file mode 100644 index 0000000..2036742 --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcMessage.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper5.DsmCc +{ + abstract class DsmCcMessage : Validatable + { + public DsmCcMessageHeader Header { get; internal set; } + } +} diff --git a/skyscraper8/DsmCc/DsmCcMessageAttribute.cs b/skyscraper8/DsmCc/DsmCcMessageAttribute.cs new file mode 100644 index 0000000..48d683b --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcMessageAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.DsmCc +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class DsmCcMessageAttribute : Attribute + { + public ushort MessageId { get; } + + public DsmCcMessageAttribute(ushort MessageId) + { + this.MessageId = MessageId; + } + } +} diff --git a/skyscraper8/DsmCc/DsmCcMessageHeader.cs b/skyscraper8/DsmCc/DsmCcMessageHeader.cs new file mode 100644 index 0000000..e0df87e --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcMessageHeader.cs @@ -0,0 +1,20 @@ +namespace skyscraper5.DsmCc +{ + class DsmCcMessageHeader + { + public byte ProtocolDiscriminator { get; } + public byte DsmccType { get; } + public ushort MessageId { get; } + public uint TransactionId { get; } + public ushort MessageLength { get; set; } + public DsmCcAdaptionHeader AdaptionHeader { get; internal set; } + + public DsmCcMessageHeader(byte protocolDiscriminator, byte dsmccType, ushort messageId, uint transactionId) + { + ProtocolDiscriminator = protocolDiscriminator; + DsmccType = dsmccType; + MessageId = messageId; + TransactionId = transactionId; + } + } +} diff --git a/skyscraper8/DsmCc/DsmCcMessageUnpacker.cs b/skyscraper8/DsmCc/DsmCcMessageUnpacker.cs new file mode 100644 index 0000000..ba4f45f --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcMessageUnpacker.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc +{ + class DsmCcMessageUnpacker + { + private static DsmCcMessageUnpacker _instance; + + public static DsmCcMessageUnpacker GetInstance() + { + if (_instance == null) + _instance = new DsmCcMessageUnpacker(); + return _instance; + } + + private ConstructorInfo[] constructors; + + private DsmCcMessageUnpacker() + { + constructors = PluginManager.GetInstance().GetDsmCcMessages(); + } + + public DsmCcMessage UnpackMessage(DsmCcMessageHeader childHeader, byte[] messageContent) + { + /** + * DSM-CC Types: + * 0x01 => User to Network Configuration Message + * 0x02 => User to Network Session Message + * 0x03 => Download Message + * 0x04 => SDB Channel Change Protocol + * 0x05 => User to Network Pass-Thru Message + */ + if (messageContent.Length == 0) + return null; + if (childHeader.DsmccType == 0x00) + return null; + if (childHeader.DsmccType >= 0x06 && childHeader.DsmccType <= 0x7f) + return null; //According to https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-mpeg-dsmcc.c, this is reserved. + if (childHeader.DsmccType >= 0x80 && childHeader.DsmccType <= 0xff) + return null; //At this time, we have no way of knowing who the "user" is which specifies "user defined" message types. + if (childHeader.MessageId == 0x472e) + return null; + ConstructorInfo constructorInfo = constructors[childHeader.MessageId]; + if (constructorInfo == null) + throw new NotImplementedException(String.Format("DSM-CC Message {0:X4}", childHeader.MessageId)); + + object invoke = constructorInfo.Invoke(new object[] { messageContent }); + DsmCcMessage result = (DsmCcMessage)invoke; + result.Header = childHeader; + return result; + } + } +} diff --git a/skyscraper8/DsmCc/DsmCcSection.cs b/skyscraper8/DsmCc/DsmCcSection.cs new file mode 100644 index 0000000..b800aa6 --- /dev/null +++ b/skyscraper8/DsmCc/DsmCcSection.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.DsmCc.Message; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.DsmCc +{ + class DsmCcSection + { + public DsmCcSection(MemoryStream ms, byte expectedTableId) + { + byte tableId = ms.ReadUInt8(); + if (tableId != expectedTableId) + throw new DsmCcException("{0} != {1}", nameof(tableId) != nameof(expectedTableId)); + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + bool privateIndicator = (readUInt16Be & 0x4000) != 0; + int dsmSectionLength = readUInt16Be & 0x0fff; + + if (ms.GetAvailableBytes() < 9) + return; + ushort tableIdExtension = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e) >> 1; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + long availableBytes = ms.GetAvailableBytes() - 4; + byte[] remainingBytes = ms.ReadBytes(availableBytes); + + switch (expectedTableId) + { + case 0x3a: + throw new NotImplementedException("DecodeLLCSNAP"); + case 0x3b: + DecodeUserNetworkMessage(remainingBytes); + break; + case 0x3c: + DecodeDownloadDataMessage(remainingBytes); + break; + case 0x3d: + DecodeDsmCcDescriptorList(remainingBytes); + break; + case 0x3e: + PrivateData = remainingBytes; + break; + default: + File.WriteAllBytes("dsmcc.bin", remainingBytes); + throw new NotImplementedException(); + } + + uint crc32 = ms.ReadUInt32BE(); + } + + public byte[] PrivateData { get; private set; } + + private void DecodeDsmCcDescriptorList(byte[] buffer) + { + DsmCcDescriptorList result = new DsmCcDescriptorList(); + + try + { + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(buffer, "DSM-CC"); + foreach (TsDescriptor tsDescriptor in unpackDescriptors) + { + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(StreamEventDescriptor): + result.StreamEventDescriptors.Add((StreamEventDescriptor)tsDescriptor); + break; + case nameof(UserDefinedDescriptor): + break; + default: + throw new NotImplementedException(name); + } + } + } + catch (Exception e) + { + result.Error = e; + } + + DsmCcMessage = result; + } + + private void DecodeDownloadDataMessage(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + DsmCcMessageHeader header = ReadDsmccHeader(ms); + if (header == null) + return; + uint downloadId = header.TransactionId; + + if (header.MessageLength > ms.GetAvailableBytes()) + return; + + byte[] messageContent = ms.ReadBytes(header.MessageLength); + this.DsmCcMessage = DsmCcMessageUnpacker.GetInstance().UnpackMessage(header, messageContent); + + } + + private void DecodeUserNetworkMessage(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + DsmCcMessageHeader childHeader = ReadDsmccHeader(ms); + if (childHeader == null) + { + Debug.WriteLine("DSM-CC Child Header not present..."); + return; + } + if (childHeader.MessageLength > ms.GetAvailableBytes()) + { + Debug.WriteLine("DSM-CC Message too long..."); + return; + } + byte[] messageContent = ms.ReadBytes(childHeader.MessageLength); + this.DsmCcMessage = DsmCcMessageUnpacker.GetInstance().UnpackMessage(childHeader, messageContent); + } + + private DsmCcMessageHeader ReadDsmccHeader(MemoryStream ms) + { + if (ms.GetAvailableBytes() < 12) + return null; + byte protocolDiscriminator = ms.ReadUInt8(); + byte dsmccType = ms.ReadUInt8(); + ushort messageId = ms.ReadUInt16BE(); + uint transactionId = ms.ReadUInt32BE(); + byte reserved = ms.ReadUInt8(); + byte adaptionLength = ms.ReadUInt8(); + ushort messageLength = ms.ReadUInt16BE(); + DsmCcMessageHeader childHeader = new DsmCcMessageHeader(protocolDiscriminator, dsmccType, messageId, transactionId); + if (adaptionLength > 2) + { + if (adaptionLength > ms.GetAvailableBytes()) + { + Debug.WriteLine("DSM-CC Adaption too long..."); + return childHeader; + } + childHeader.AdaptionHeader = new DsmCcAdaptionHeader(ms.ReadBytes(adaptionLength)); + } + messageLength -= adaptionLength; + childHeader.MessageLength = messageLength; + + return childHeader; + } + + public DsmCcMessage DsmCcMessage { get; private set; } + + } +} diff --git a/skyscraper8/DsmCc/FormatSpecifier.cs b/skyscraper8/DsmCc/FormatSpecifier.cs new file mode 100644 index 0000000..01e5584 --- /dev/null +++ b/skyscraper8/DsmCc/FormatSpecifier.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.DsmCc +{ + class FormatSpecifier + { + public FormatSpecifier(MemoryStream ms) + { + ModuleVersion = ms.ReadUInt8(); + ModuleId = ms.ReadUInt16BE(); + BlockSize = ms.ReadUInt16BE(); + ModuleSize = ms.ReadUInt32BE(); + CompressionMethod = ms.ReadUInt8(); + OriginalSize = ms.ReadUInt32BE(); + TimeOut = ms.ReadUInt8(); + + byte objectKeyLength = ms.ReadUInt8(); + ObjectKey = ms.ReadBytes(objectKeyLength); + } + + public byte[] ObjectKey { get; private set; } + + public byte TimeOut { get; private set; } + + public uint OriginalSize { get; private set; } + + public byte CompressionMethod { get; private set; } + + public uint ModuleSize { get; private set; } + + public ushort BlockSize { get; set; } + + public ushort ModuleId { get; private set; } + + public byte ModuleVersion { get; private set; } + } +} diff --git a/skyscraper8/DsmCc/Message/0x1002_DownloadInfoIndication.cs b/skyscraper8/DsmCc/Message/0x1002_DownloadInfoIndication.cs new file mode 100644 index 0000000..255f363 --- /dev/null +++ b/skyscraper8/DsmCc/Message/0x1002_DownloadInfoIndication.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Message +{ + [SkyscraperPlugin] + [DsmCcMessage(0x1002)] + class DownloadInfoIndication : DsmCcMessage + { + public DownloadInfoIndication(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + DownloadId = ms.ReadUInt32BE(); + BlockSize = ms.ReadUInt16BE(); + WindowSize = ms.ReadUInt8(); + AckPeriod = ms.ReadUInt8(); + TcDownloadWindow = ms.ReadUInt32BE(); + TcDownloadScenario = ms.ReadUInt32BE(); + + this.CompatibilityDescriptor = new CompatibilityDescriptor(ms); + + if (ms.GetAvailableBytes() < 2) + { + Valid = false; + return; + } + + ushort numberOfModules = ms.ReadUInt16BE(); + Modules = new Module[numberOfModules]; + for (int i = 0; i < numberOfModules; i++) + { + if (ms.GetAvailableBytes() < 8) + { + Valid = false; + return; + } + + Modules[i] = new Module(); + Modules[i].ID = ms.ReadUInt16BE(); + Modules[i].Size = ms.ReadUInt32BE(); + Modules[i].Version = ms.ReadUInt8(); + byte moduleInfoLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < moduleInfoLength) + { + Valid = false; + return; + } + + Modules[i].ModuleInfo = ms.ReadBytes(moduleInfoLength); + } + + if (ms.GetAvailableBytes() < 2) + { + Valid = false; + return; + } + + ushort privateDataLength = ms.ReadUInt16BE(); + if (privateDataLength < ms.GetAvailableBytes()) + { + PrivateData = ms.ReadBytes(privateDataLength); + } + + Valid = true; + } + + public byte[] PrivateData { get; private set; } + + public Module[] Modules { get; private set; } + + public class Module + { + public ushort ID { get; internal set; } + public uint Size { get; internal set; } + public byte Version { get; internal set; } + public byte[] ModuleInfo { get; internal set; } + } + + public CompatibilityDescriptor CompatibilityDescriptor { get; private set; } + + public uint TcDownloadScenario { get; private set; } + + public uint TcDownloadWindow { get; private set; } + + public byte AckPeriod { get; private set; } + + public byte WindowSize { get; private set; } + + public ushort BlockSize { get; private set; } + + public uint DownloadId { get; private set; } + } +} diff --git a/skyscraper8/DsmCc/Message/0x1003_DownloadDataBlock.cs b/skyscraper8/DsmCc/Message/0x1003_DownloadDataBlock.cs new file mode 100644 index 0000000..2c66219 --- /dev/null +++ b/skyscraper8/DsmCc/Message/0x1003_DownloadDataBlock.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Message +{ + [SkyscraperPlugin] + [DsmCcMessage(0x1003)] + class DownloadDataBlock : DsmCcMessage + { + public DownloadDataBlock(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + + ModuleId = ms.ReadUInt16BE(); + ModuleVersion = ms.ReadUInt8(); + byte reserved = ms.ReadUInt8(); + BlockNumber = ms.ReadUInt16BE(); + + BlockData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] BlockData { get; private set; } + + public ushort BlockNumber { get; private set; } + + public byte ModuleVersion { get; private set; } + + public ushort ModuleId { get; private set; } + + public uint DownloadId => base.Header.TransactionId; + + public void ForgetBlock() + { + BlockData = null; + } + } +} diff --git a/skyscraper8/DsmCc/Message/0x1006_DownloadServerInitiate.cs b/skyscraper8/DsmCc/Message/0x1006_DownloadServerInitiate.cs new file mode 100644 index 0000000..7b01706 --- /dev/null +++ b/skyscraper8/DsmCc/Message/0x1006_DownloadServerInitiate.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.DsmCc.Message +{ + [SkyscraperPlugin] + [DsmCcMessage(0x1006)] + class DownloadServerInitiate : DsmCcMessage + { + public DownloadServerInitiate(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ServerId = ms.ReadBytes(20); + + CompatibilityDescriptor = new CompatibilityDescriptor(ms); + + if (ms.GetAvailableBytes() < 2) + return; + ushort privateDataLength = ms.ReadUInt16BE(); + if (privateDataLength > ms.GetAvailableBytes()) + return; + PrivateData = ms.ReadBytes(privateDataLength); + } + + public byte[] PrivateData { get; private set; } + + public CompatibilityDescriptor CompatibilityDescriptor { get; private set; } + + public byte[] ServerId { get; private set; } + } +} diff --git a/skyscraper8/DsmCc/Message/DescriptorList.cs b/skyscraper8/DsmCc/Message/DescriptorList.cs new file mode 100644 index 0000000..7b17986 --- /dev/null +++ b/skyscraper8/DsmCc/Message/DescriptorList.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Descriptors; + +namespace skyscraper5.DsmCc.Message +{ + //The descriptor list is not an actual DSM-CC Message! + //I just use it as convenience to pass around the found descriptors. + class DsmCcDescriptorList : DsmCcMessage + { + public DsmCcDescriptorList() + { + StreamEventDescriptors = new List(); + } + public List StreamEventDescriptors; + public Exception Error { get; internal set; } + } +} diff --git a/skyscraper8/DsmCc/README.md b/skyscraper8/DsmCc/README.md new file mode 100644 index 0000000..245261a --- /dev/null +++ b/skyscraper8/DsmCc/README.md @@ -0,0 +1,3 @@ +Take this implementation of DSM-CC with a grain of salt! + +I didn't feel like paying 200 € (I'm just one little man, not a huge company! This would be about 10% of my monthly salary!) for a copy of the ISO 13818-6 standard, so most of the code in here is based off guesses and assumptions made after checking ETSI TR 101 202 V1.2.1 and various other random sources off the internet. Paywalls suck. \ No newline at end of file diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/LiteComponent.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/LiteComponent.cs new file mode 100644 index 0000000..eb25ee7 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/LiteComponent.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop +{ + class LiteComponent + { + public LiteComponent(MemoryStream ms) + { + ComponentId = ms.ReadUInt32BE(); + + byte componentDataLength = ms.ReadUInt8(); + ComponentData = ms.ReadBytes(componentDataLength); + } + + public byte[] ComponentData { get; set; } + + public uint ComponentId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/ModuleInfo.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/ModuleInfo.cs new file mode 100644 index 0000000..6691798 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/ModuleInfo.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Message; +using skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop +{ + class ModuleInfo + { + + public ModuleInfo(DownloadInfoIndication.Module module, DownloadInfoIndication section3BDsmCcMessage, ProgramMapping programMapping, byte componentTag) + { + this.ModuleId = module.ID; + this.ModuleVersion = module.Version; + this.ModuleSize = module.Size; + this.PmtComponentTag = componentTag; + this.PmtMapping = programMapping; + this.BlockSize = section3BDsmCcMessage.BlockSize; + + uint numBlocks = this.ModuleSize / section3BDsmCcMessage.BlockSize; + if (this.ModuleSize % section3BDsmCcMessage.BlockSize != 0) + numBlocks++; + Blocks = new DownloadDataBlock[numBlocks]; + + if (module.ModuleInfo.Length < 13) + return; + + MemoryStream ms = new MemoryStream(module.ModuleInfo, false); + + ModuleTimeout = ms.ReadUInt32BE(); + BlockTimeout = ms.ReadUInt32BE(); + MinBlockTime = ms.ReadUInt32BE(); + + byte tapsCount = ms.ReadUInt8(); + Taps = new Tap[tapsCount]; + for (int i = 0; i < tapsCount; i++) + { + if (ms.GetAvailableBytes() < 7) + return; + Taps[i] = new Tap(ms); + } + + if (ms.GetAvailableBytes() == 0) + return; + + byte userInfoLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < userInfoLength) + return; + byte[] userInfo = ms.ReadBytes(userInfoLength); + if (userInfoLength > 2) + { + UnpackDescriptors(userInfo); + } + + LooksLikeObjectCarousel = true; + } + + public bool LooksLikeObjectCarousel { get; private set; } + + public ushort BlockSize { get; private set; } + + public ProgramMapping PmtMapping { get; private set; } + + public byte PmtComponentTag { get; private set; } + + public uint ModuleSize { get; private set; } + + public byte ModuleVersion { get; private set; } + + public ushort ModuleId { get; private set; } + + private void UnpackDescriptors(byte[] userInfo) + { + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(userInfo, "DataCarousel"); + foreach (TsDescriptor tsDescriptor in descriptors) + { + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(CompressedModuleDescriptor): + this.CompressedModuleDescriptor = (CompressedModuleDescriptor)tsDescriptor; + break; + case nameof(CachingPriorityDescriptor): + this.CachingPriority = (CachingPriorityDescriptor)tsDescriptor; + break; + case nameof(Crc32Descriptor): + Crc32Descriptor crc32Descriptor = (Crc32Descriptor)tsDescriptor; + this.Crc32 = crc32Descriptor.CRC_32; + break; + case nameof(LabelDescriptor): + this.Label = ((LabelDescriptor)tsDescriptor).Label; + break; + default: + throw new NotImplementedException(name); + } + } + } + + public uint? Crc32 { get; set; } + + public string Label { get; set; } + + public CachingPriorityDescriptor CachingPriority { get; set; } + + public CompressedModuleDescriptor CompressedModuleDescriptor { get; private set; } + + public Tap[] Taps { get; private set; } + + public uint MinBlockTime { get; private set; } + + public uint BlockTimeout { get; private set; } + + public uint ModuleTimeout { get; private set; } + + public DownloadDataBlock[] Blocks { get; } + + public bool BlocksComplete + { + get + { + for (int i = 0; i < Blocks.Length; i++) + { + if (Blocks[i] == null) + return false; + } + + return true; + } + } + + protected bool Equals(ModuleInfo other) + { + return ModuleVersion == other.ModuleVersion && ModuleId == other.ModuleId; + } + + 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((ModuleInfo)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(ModuleVersion, ModuleId); + } + + public double DownloadProgress + { + get + { + double count = Blocks.Count(x => x != null); + double result = count * 100.0 / Blocks.Length; + return Math.Round(result, 1); + } + } + + public double TaggedProgress { get; set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/Name.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/Name.cs new file mode 100644 index 0000000..8968c97 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/Name.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop +{ + class Name + { + public Name(MemoryStream ms) + { + byte nameComponentsCount = ms.ReadUInt8(); + NameComponents = new Dictionary(); + for (byte i = 0; i < nameComponentsCount; i++) + { + byte idLength = ms.ReadUInt8(); + string id = Encoding.UTF8.GetString(ms.ReadBytes(idLength)); + id = id.TrimEnd((char)0); + byte kindLength = ms.ReadUInt8(); + string kind = Encoding.UTF8.GetString(ms.ReadBytes(kindLength)); + kind = kind.TrimEnd((char)0); + NameComponents.Add(id, kind); + } + } + + public Dictionary NameComponents { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/ObjectLocation.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/ObjectLocation.cs new file mode 100644 index 0000000..96d5ef5 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/ObjectLocation.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop +{ + public class ObjectLocation + { + public ObjectLocation(MemoryStream ms) + { + if (ms.ReadUInt32BE() != 0x49534f50) + throw new DvbException("invalid magic!"); + + byte componentDataLength = ms.ReadUInt8(); + + CarouselId = ms.ReadUInt32BE(); + ModuleId = ms.ReadUInt16BE(); + + byte version_major = ms.ReadUInt8(); + byte version_minor = ms.ReadUInt8(); + Version = new Version(version_major, version_minor); + + byte objectKeyLength = ms.ReadUInt8(); + ObjectKey = ms.ReadBytes(objectKeyLength); + } + + public byte[] ObjectKey { get; private set; } + + public Version Version { get; private set; } + + public ushort ModuleId { get; private set; } + + public uint CarouselId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/Tap.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/Tap.cs new file mode 100644 index 0000000..c655794 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/Tap.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop +{ + public class Tap + { + const bool CRASH_ON_PECULIAR_SELECTOR_TYPE = false; + + public Tap(MemoryStream ms) + { + ID = ms.ReadUInt16BE(); + Use = ms.ReadUInt16BE(); + AssociationTag = ms.ReadUInt16BE(); + + byte selectorLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < selectorLength) + return; + if (selectorLength >= 2) + { + ushort selectorType = ms.ReadUInt16BE(); + selectorLength -= 2; + byte[] selectorDataBytes = ms.ReadBytes(selectorLength); + + if (selectorType == 0x01) + Selector = new SelectorType_0x01(selectorDataBytes); + else + { + if (CRASH_ON_PECULIAR_SELECTOR_TYPE) + throw new NotImplementedException(String.Format("{1} {0:X4}", selectorType, nameof(selectorType))); + } + } + } + + public ushort AssociationTag { get; set; } + + public ushort Use { get; private set; } + + public ushort ID { get; private set; } + + public TapSelectors.SelectorType Selector { get; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/TapSelectors/SelectorType.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/TapSelectors/SelectorType.cs new file mode 100644 index 0000000..74999cd --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/TapSelectors/SelectorType.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors +{ + public abstract class SelectorType + { + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/TapSelectors/SelectorType_0x01.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/TapSelectors/SelectorType_0x01.cs new file mode 100644 index 0000000..586b5c1 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/TapSelectors/SelectorType_0x01.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors +{ + class SelectorType_0x01 : SelectorType + { + public SelectorType_0x01(byte[] buffer) + : this(new MemoryStream(buffer, true)) + { + + } + + public SelectorType_0x01(MemoryStream ms) + { + TransactionId = ms.ReadUInt32BE(); + Timeout = ms.ReadUInt32BE(); + } + + public uint Timeout { get; private set; } + + public uint TransactionId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Biop/TapUseDefinitions.cs b/skyscraper8/Dvb/DataBroadcasting/Biop/TapUseDefinitions.cs new file mode 100644 index 0000000..92e7d59 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Biop/TapUseDefinitions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.DataBroadcasting.Biop +{ + enum TapUseDefinitions : ushort + { + STR_NPT_USE = 0x000b, + STR_STATUS_AND_EVENT_USE = 0x000c, + STR_EVENT_USE = 0x000d, + STR_STATUS_USE = 0x000e, + BIOP_ES_USE = 0x0018, + BIOP_PROGRAM_USE = 0x0019 + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/BiopProfileBody.cs b/skyscraper8/Dvb/DataBroadcasting/BiopProfileBody.cs new file mode 100644 index 0000000..751518b --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/BiopProfileBody.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Dvb.DataBroadcasting.Dsm; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + class BiopProfileBody + { + public BiopProfileBody(byte[] buffer) + : this(new MemoryStream(buffer, false)) + { + + } + + public BiopProfileBody(MemoryStream ms) + { + if (ms.ReadUInt8() != 0x00) //0x00 = big endian + throw new NotImplementedException("little endian"); + + byte LiteComponentsCount = ms.ReadUInt8(); + + ObjectLocation = new ObjectLocation(ms); + ConnBinder = new ConnBinder(ms); + + LiteComponents = new LiteComponent[LiteComponentsCount - 2]; + for (int n = 0; n < LiteComponentsCount - 2; n++) + { + LiteComponents[n] = new LiteComponent(ms); + } + } + + public ObjectLocation ObjectLocation { get; private set; } + public ConnBinder ConnBinder { get; private set; } + + public LiteComponent[] LiteComponents { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/DataCarouselDecoder.cs b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDecoder.cs new file mode 100644 index 0000000..5351e84 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDecoder.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Ionic.Zlib; +using skyscraper5.DsmCc; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.DsmCc.Message; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.SystemSoftwareUpdate.CarouselIntention; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Utils; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + class DataCarouselDecoder : IPsiProcessor, IDisposable + { + private SiDumper siDumper; + private List waitingModuleInfos; + private ProgramMappingStream programMappingStream; + private ProgramMapping programMapping; + private DataCarouselIntention dataCarouselIntention; + private DataCarouselEventHandler eventHandler; + private const bool ENABLE_LOGGING = false; + + public DataCarouselDecoder(ProgramMappingStream _pms, ProgramMapping _pm, DataCarouselIntention _intention, DataCarouselEventHandler _eventHandler) + { + siDumper = new SiDumper(); + waitingModuleInfos = new List(); + programMappingStream = _pms; + programMapping = _pm; + dataCarouselIntention = _intention; + eventHandler = _eventHandler; + if (!programMappingStream.ComponentTag.HasValue) + throw new NotImplementedException("need a component tag!"); + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + //ETSI TR 101 202 V1.2.1 (2003-01) Page 57 + byte[] dataCopy = section.GetDataCopy(); + switch (dataCopy[0]) + { + case 0x3a: //LLCSNAP + //see tr_101202v010201p.pdf, Page 57 + ProcessLlcsnap(section, dataCopy); + break; + case 0x3b: //U-N Message + ProcessUNMessage(section, dataCopy); + break; + case 0x3c: + ProcessDDIMessage(section, dataCopy); + break; + case 0x3d: + ProcessDsmCcDescriptorLength(section, dataCopy, sourcePid); + break; + case 0x3e: //Private Data + //Undocumented, manufacterer specific data. + break; + default: + return; + } + } + + private void ProcessLlcsnap(PsiSection section, byte[] data) + { + MemoryStream ms = new MemoryStream(data, false); + uint readUInt24Be = ms.ReadUInt24BE(); + if (readUInt24Be == 0xfefe03) + throw new NotImplementedException(); + else if (readUInt24Be == 0xaaaa03) + throw new NotImplementedException(); + } + + private void ProcessDsmCcDescriptorLength(PsiSection section, byte[] dataCopy, int sourcePid) + { + if (!section.Valid) + return; + //see tr_101202v010201p.pdf, Page 57 + DsmCcSection descriptorList = new DsmCcSection(new MemoryStream(dataCopy, false), 0x3d); + DsmCcDescriptorList unpackedList = (DsmCcDescriptorList)descriptorList.DsmCcMessage; + if (unpackedList == null) + return; + + if (unpackedList.Error != null) + return; + + if (unpackedList.StreamEventDescriptors.Count == 0) + return; + + foreach (StreamEventDescriptor descriptorListStreamEventDescriptor in unpackedList.StreamEventDescriptors) + { + eventHandler.DsmCcDoItNowEvent(programMapping, descriptorListStreamEventDescriptor, sourcePid); + } + + } + + private void ProcessUNMessage(PsiSection section, byte[] dataCopy) + { + if (!section.Validate()) + return; + DsmCcSection section3b = new DsmCcSection(new MemoryStream(dataCopy, false), 0x3b); + if (section3b.DsmCcMessage == null) + return; + + switch (section3b.DsmCcMessage.Header.MessageId) + { + case 0x1002: + ProcessDownloadInfoIndication((DownloadInfoIndication)section3b.DsmCcMessage); + break; + case 0x1006: + ProcessDownloadServerInitiate((DownloadServerInitiate)section3b.DsmCcMessage); + break; + default: + throw new NotImplementedException(String.Format("UNM: {0} 0x{1:X4}", nameof(section3b.DsmCcMessage.Header.MessageId), section3b.DsmCcMessage.Header.MessageId)); + } + } + + private void ProcessDownloadInfoIndication(DownloadInfoIndication section3BDsmCcMessage) + { + if (section3BDsmCcMessage.Modules == null) + return; + if (!section3BDsmCcMessage.Valid) + return; + if (section3BDsmCcMessage.BlockSize == 0) + return; + + ModuleInfo[] moduleInfos = Array.ConvertAll(section3BDsmCcMessage.Modules, x => new ModuleInfo(x, section3BDsmCcMessage, programMapping, programMappingStream.ComponentTag.Value)); + Log("DSM-CC: {0:X8} Got Download Info Indication", section3BDsmCcMessage.Header.TransactionId); + foreach (ModuleInfo moduleInfo in moduleInfos) + { + if (waitingModuleInfos.Contains(moduleInfo)) + continue; + + if (!eventHandler.IsModuleWanted(programMappingStream.ElementaryPid, moduleInfo.ModuleId, moduleInfo.ModuleVersion)) + continue; + + waitingModuleInfos.Add(moduleInfo); + Log("DSM-CC: {0:X8} - Module {1:X4}, Version {2:X2}, Num Blocks: {3:X4}", section3BDsmCcMessage.Header.TransactionId, moduleInfo.ModuleId, moduleInfo.ModuleVersion, moduleInfo.ModuleSize / section3BDsmCcMessage.BlockSize); + if (!moduleInfo.LooksLikeObjectCarousel) + eventHandler.NotifyOfNewModule(programMappingStream.ElementaryPid, moduleInfo.ModuleId, moduleInfo.ModuleVersion); + } + } + + private void ProcessDDIMessage(PsiSection section, byte[] dataCopy) + { + if (!section.Validate()) + return; + DsmCcSection section3c = new DsmCcSection(new MemoryStream(dataCopy, false), 0x3c); + if (section3c.DsmCcMessage == null) + return; + switch (section3c.DsmCcMessage.Header.MessageId) + { + case 0x1003: + ProcessDownloadDataBlock((DownloadDataBlock)section3c.DsmCcMessage); + break; + default: + throw new NotImplementedException(String.Format("DDI: {0} 0x{1:X4}", nameof(section3c.DsmCcMessage.Header.MessageId), section3c.DsmCcMessage.Header.MessageId)); + } + } + + private void ProcessDownloadDataBlock(DownloadDataBlock block) + { + //We got a downloaded data block, now what? + Log("DSM-CC: {0:X8} - Download Data Block", block.DownloadId); + ModuleInfo moduleInfo = waitingModuleInfos.Find(x => x.ModuleId == block.ModuleId && x.ModuleVersion == block.ModuleVersion); + if (moduleInfo == null) + { + return; + } + + moduleInfo.Blocks[block.BlockNumber] = block; + if (moduleInfo.BlocksComplete) + { + try + { + AssembleModule(moduleInfo); + waitingModuleInfos.Remove(moduleInfo); + } + catch (ZlibException zlibE) + { + ForgetModule(moduleInfo, true, true); + Debug.WriteLine("A DSM-CC module was corrupted in transit."); + } + } + else if (moduleInfo.TaggedProgress < moduleInfo.DownloadProgress) + { + eventHandler.NotifyModuleDownloadProgress(programMappingStream.ElementaryPid, moduleInfo.ModuleId, moduleInfo.ModuleVersion, moduleInfo.DownloadProgress); + moduleInfo.TaggedProgress = moduleInfo.DownloadProgress; + } + } + + private ObjectCarouselMetadata vfs; + private void AssembleModule(ModuleInfo module) + { + ModuleInfoStream assembledStream = new ModuleInfoStream(module, true); + bool isObjectCarousel = ObjectCarouselUtilities.TestForObjectCarousel(assembledStream, module.CompressedModuleDescriptor); + assembledStream.Position = 0; + if (isObjectCarousel) + { + if (vfs == null) + vfs = new ObjectCarouselMetadata(eventHandler.GetObjectCarouselEventHandler(), programMappingStream.ElementaryPid, programMapping); + vfs.LastExtractions = 0; + vfs.DirectoriesCreated = 0; + ObjectCarouselUtilities.ExtractBiopModule(module, assembledStream, vfs); + if (vfs.DirectoriesCreated != 0) + eventHandler.PreventDsmCcModuleRepetition(programMappingStream.ElementaryPid, module.ModuleId, module.ModuleVersion); + return; + } + eventHandler.NotifyDownloadComplete(programMappingStream.ElementaryPid, module.ModuleId, module.ModuleVersion, assembledStream, isObjectCarousel); + //assembledStream.Close(); + + ForgetModule(module, true, false); + } + + private void ProcessDownloadServerInitiate(DownloadServerInitiate downloadServerInitiate) + { + if (downloadServerInitiate.PrivateData == null) + return; + + byte[] buffer = downloadServerInitiate.PrivateData; + switch (dataCarouselIntention) + { + case DataCarouselIntention.SoftwareUpdate: + GroupInfoIndication gii = new GroupInfoIndication(buffer); + break; + default: + /* + bool isObjectCarousel = ObjectCarouselUtilities.TestForObjectCarousel(downloadServerInitiate); + if (isObjectCarousel) + break; + File.WriteAllBytes("buffer.bin", buffer); + throw new NotImplementedException(dataCarouselIntention.ToString()); + */ + break; + } + } + + private void Log(string fmt, params object[] args) + { + if (ENABLE_LOGGING) + { + Console.WriteLine(fmt, args); + } + } + + private void ForgetModule(ModuleInfo mi, bool removeWaiting = true, bool clearBlocks = true) + { + if (removeWaiting) + { + waitingModuleInfos.Remove(mi); + } + + if (mi.Blocks == null) + return; + + if (clearBlocks) + { + for (int i = 0; i < mi.Blocks.Length; i++) + { + if (mi.Blocks[i] != null) + { + mi.Blocks[i].ForgetBlock(); + mi.Blocks[i] = null; + } + } + } + } + + public void Dispose() + { + while (waitingModuleInfos.Count > 0) + { + ForgetModule(waitingModuleInfos[0], true, false); + } + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x05_Crc32Descriptor.cs b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x05_Crc32Descriptor.cs new file mode 100644 index 0000000..197a778 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x05_Crc32Descriptor.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x05,"DataCarousel")] + class Crc32Descriptor : TsDescriptor + { + public Crc32Descriptor(byte[] buffer) + { + if (buffer.Length != 4) + { + Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + CRC_32 = ms.ReadUInt32BE(); + ms.Close(); + Valid = true; + } + + public uint CRC_32 { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x09_CompressedModuleDescriptor.cs b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x09_CompressedModuleDescriptor.cs new file mode 100644 index 0000000..5b53005 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x09_CompressedModuleDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x09,"DataCarousel")] + class CompressedModuleDescriptor : TsDescriptor + { + public CompressedModuleDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CompressionMethod = ms.ReadUInt8(); + OriginalSize = ms.ReadUInt32BE(); + } + + public uint OriginalSize { get; private set; } + + public byte CompressionMethod { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x71_CachingPriorityDescriptor.cs b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x71_CachingPriorityDescriptor.cs new file mode 100644 index 0000000..50ab3d4 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/DataCarouselDescriptors/0x71_CachingPriorityDescriptor.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x71,"DataCarousel")] + class CachingPriorityDescriptor : TsDescriptor + { + public CachingPriorityDescriptor(byte[] buffer) + { + PriorityValue = buffer[0]; + TransparencyLevel = (TransparencyLevelValue)buffer[1]; + } + + public TransparencyLevelValue TransparencyLevel { get; private set; } + + public byte PriorityValue { get; private set; } + + public enum TransparencyLevelValue : byte + { + TransparentCaching = 1, + SemiTransparentCaching = 2, + StaticCaching = 3 + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/DataCarouselEventHandler.cs b/skyscraper8/Dvb/DataBroadcasting/DataCarouselEventHandler.cs new file mode 100644 index 0000000..7a0dc53 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/DataCarouselEventHandler.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.DsmCc.Message; +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + interface DataCarouselEventHandler + { + bool IsModuleWanted(int elementaryPid, ushort moduleId, byte moduleVersion); + void NotifyOfNewModule(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion); + void NotifyModuleDownloadProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, double moduleInfoDownloadProgress); + ObjectCarouselEventHandler GetObjectCarouselEventHandler(); + void NotifyDownloadComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result, bool isObjectCarousel); + void DsmCcDoItNowEvent(ProgramMapping programMapping, StreamEventDescriptor descriptorListStreamEventDescriptor, int sourcePid); + void PreventDsmCcModuleRepetition(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion); + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/DataCarouselIntention.cs b/skyscraper8/Dvb/DataBroadcasting/DataCarouselIntention.cs new file mode 100644 index 0000000..960b099 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/DataCarouselIntention.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + enum DataCarouselIntention + { + NoIntention, + SoftwareUpdate + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Dsm/ConnBinder.cs b/skyscraper8/Dvb/DataBroadcasting/Dsm/ConnBinder.cs new file mode 100644 index 0000000..f878e1c --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Dsm/ConnBinder.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Dsm +{ + class ConnBinder + { + public ConnBinder(MemoryStream ms) + { + if (ms.ReadUInt32BE() != 0x49534F40) + { + throw new DvbException("invalid magic"); + } + + byte componentDataLength = ms.ReadUInt8(); + byte tapsCount = ms.ReadUInt8(); + OriginTap = new Tap(ms); + + BranchTaps = new Tap[tapsCount - 1]; + for (int i = 0; i < BranchTaps.Length; i++) + { + BranchTaps[i] = new Tap(ms); + } + + } + + public Tap OriginTap { get; private set; } + + public Tap[] BranchTaps { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Dsm/Event/EventList_T.cs b/skyscraper8/Dvb/DataBroadcasting/Dsm/Event/EventList_T.cs new file mode 100644 index 0000000..7dd6e15 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Dsm/Event/EventList_T.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Dsm.Event +{ + public class EventList_T + { + public EventList_T(MemoryStream ms) + { + N3 = ms.ReadUInt16BE(); + if (ms.GetAvailableBytes() < N3) + return; + EventNames = new string[N3]; + for (int i = 0; i < N3; i++) + { + byte eventNameLength = ms.ReadUInt8(); + N4 += eventNameLength; + EventNames[i] = Encoding.UTF8.GetString(ms.ReadBytes(i)).TrimEnd((char)0); + } + } + + public ushort N3 { get; private set; } + + public int N4 { get; private set; } + + public string[] EventNames { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/Dsm/Stream/Info_T.cs b/skyscraper8/Dvb/DataBroadcasting/Dsm/Stream/Info_T.cs new file mode 100644 index 0000000..823b5df --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/Dsm/Stream/Info_T.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting.Dsm.Stream +{ + public class Info_T + { + public Info_T(MemoryStream ms) + { + N2 = ms.ReadUInt8(); + ADescripton = ms.ReadBytes(N2); + uint aSeconds = ms.ReadUInt32BE(); + if (aSeconds > Int32.MaxValue) + throw new NotImplementedException(String.Format("{0} > {1}", nameof(aSeconds), Int32.MaxValue)); + ushort aMicroSeconds = ms.ReadUInt16BE(); + Duration = new TimeSpan(0, 0, 0, (int)aSeconds, aMicroSeconds); + Audio = ms.ReadUInt8(); + Video = ms.ReadUInt8(); + Data = ms.ReadUInt8(); + } + + public byte N2 { get; private set; } + + public byte Data { get; private set; } + + public byte Video { get; private set; } + + public byte Audio { get; private set; } + + public TimeSpan Duration { get; private set; } + + public byte[] ADescripton { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0C_IpMacPlatformNameDescriptor.cs b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0C_IpMacPlatformNameDescriptor.cs new file mode 100644 index 0000000..c54fb4d --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0C_IpMacPlatformNameDescriptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.DataBroadcasting.IntDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0c,"INT")] + internal class IpMacPlatformNameDescriptor : TsDescriptor + { + public IpMacPlatformNameDescriptor(byte[] buffer) + { + Iso639LanguageCode = Encoding.ASCII.GetString(buffer, 0, 3); + + Text = En300468AnnexATextDecoder.GetInstance().Decode(buffer, 3, buffer.Length - 3); + } + + public string Text { get; private set; } + + public string Iso639LanguageCode { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0D_IpMacPlatformProviderNameDescriptor.cs b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0D_IpMacPlatformProviderNameDescriptor.cs new file mode 100644 index 0000000..4366014 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0D_IpMacPlatformProviderNameDescriptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.DataBroadcasting.IntDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0D,"INT")] + internal class IpMacPlatformProviderNameDescriptor : TsDescriptor + { + public IpMacPlatformProviderNameDescriptor(byte[] buffer) + { + Iso639LanguageCode = Encoding.ASCII.GetString(buffer, 0, 3); + + Text = En300468AnnexATextDecoder.GetInstance().Decode(buffer, 3, buffer.Length - 3); + } + + public string Text { get; private set; } + + public string Iso639LanguageCode { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0F_TargetIpSlashDescriptor.cs b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0F_TargetIpSlashDescriptor.cs new file mode 100644 index 0000000..b02e45f --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x0F_TargetIpSlashDescriptor.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Net; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.DataBroadcasting.IntDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0f,"INT")] + internal class TargetIpSlashDescriptor : TsDescriptor + { + public TargetIpSlashDescriptor(byte[] buffer) + { + int length = buffer.Length / 5; + Targets = new CidrSubnet[length]; + for (int i = 0; i < length; i++) + { + byte[] ipBuffer = new byte[4]; + Array.Copy(buffer, i * 5, ipBuffer, 0, 4); + IPAddress address = new IPAddress(ipBuffer); + byte netmask = buffer[(i * 5) + 4]; + Targets[i] = new CidrSubnet(address, netmask); + } + } + + public CidrSubnet[] Targets { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x13_IpMacStreamLocationDescriptor.cs b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x13_IpMacStreamLocationDescriptor.cs new file mode 100644 index 0000000..afde365 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntDescriptors/0x13_IpMacStreamLocationDescriptor.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.DataBroadcasting.IntDescriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x13,"INT")] + internal class IpMacStreamLocationDescriptor : TsDescriptor + { + public IpMacStreamLocationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + NetworkId = ms.ReadUInt16BE(); + OriginalNetworkId = ms.ReadUInt16BE(); + TransportStreamId = ms.ReadUInt16BE(); + ServiceId = ms.ReadUInt16BE(); + ComponentTag = ms.ReadUInt8(); + } + + public byte ComponentTag { get; private set; } + + public ushort ServiceId { get; private set; } + + public ushort TransportStreamId { get; private set; } + + public ushort OriginalNetworkId { get; private set; } + + public ushort NetworkId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntEventHandler.cs b/skyscraper8/Dvb/DataBroadcasting/IntEventHandler.cs new file mode 100644 index 0000000..f370583 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntEventHandler.cs @@ -0,0 +1,14 @@ +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + internal interface IntEventHandler + { + void OnIpMacNotification(int sourcePid, Platform platform, Target target, Operational operational); + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntModel/IpMacNotification.cs b/skyscraper8/Dvb/DataBroadcasting/IntModel/IpMacNotification.cs new file mode 100644 index 0000000..f547913 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntModel/IpMacNotification.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Net; + +namespace skyscraper5.Dvb.DataBroadcasting.IntModel +{ + public class IpMacNotification + { + public IpMacNotification(Platform platform, int targetType, string targetName, Operational operational) + { + PlatformName = platform.Name; + PlatformProviderName = platform.ProviderName; + PlatformProviderNameLanguageCode = platform.ProviderNameLanguageCode; + PlatformActionType = platform.ActionType; + PlatformNameLanguageCode = platform.NameLanguageCode; + PlatformId = platform.PlatformId; + PlatformProcessingOrder = platform.ProcessingOrder; + + TargetType = targetType; + TargetName = targetName; + + OperationalNetworkId = operational.NetworkId; + OperationalMpeFecAlgorithm = operational.MpeFecAlgorithm; + OperationalComponentTag = operational.ComponentTag; + OperationalTransportStreamId = operational.TransportStreamId; + OperationalTimeSliceFecId = operational.TimeSliceFecId; + OperationalTimeSlicing = operational.TimeSlicing; + OperationalOriginalNetworkId = operational.OriginalNetworkId; + OperationalServiceId = operational.ServiceId; + OperationalTimeSliceFrameSize = operational.TimeSliceFrameSize; + OperationalTimeSliceMaxAverageRate = operational.TimeSliceMaxAverageRate; + OperationalTimeSliceMaxBurstDuration = operational.TimeSliceMaxBurstDuration; + + } + + public byte? OperationalTimeSliceMaxBurstDuration { get; set; } + + public int? OperationalTimeSliceMaxAverageRate { get; set; } + + public int? OperationalTimeSliceFrameSize { get; set; } + + public ushort? OperationalServiceId { get; set; } + + public ushort? OperationalOriginalNetworkId { get; set; } + + public bool? OperationalTimeSlicing { get; set; } + + public int? OperationalTimeSliceFecId { get; set; } + + public ushort? OperationalTransportStreamId { get; set; } + + public byte? OperationalComponentTag { get; set; } + + public int? OperationalMpeFecAlgorithm { get; set; } + + public ushort? OperationalNetworkId { get; set; } + + public string TargetName { get; set; } + + public int TargetType { get; set; } + + public byte PlatformProcessingOrder { get; set; } + + public uint PlatformId { get; set; } + + public string PlatformNameLanguageCode { get; set; } + + public byte PlatformActionType { get; set; } + + public string PlatformProviderNameLanguageCode { get; set; } + + public string PlatformProviderName { get; set; } + + public string PlatformName { get; set; } + + public static IEnumerable FlatMap(Platform platform, Target target, Operational operational) + { + int results = 0; + + if (target.AllReceivers) + { + yield return new IpMacNotification(platform, -1, null, operational); + yield break; + } + + if (target.TargetIpSlashes != null) + { + foreach (CidrSubnet targetTargetIpSlash in target.TargetIpSlashes) + { + results++; + yield return new IpMacNotification(platform, 1, targetTargetIpSlash.ToString(), operational); + } + } + + if (results == 0) + { + throw new NotImplementedException("Some Targets are not implemented properly."); + } + } + + protected bool Equals(IpMacNotification other) + { + return TargetName == other.TargetName && TargetType == other.TargetType && PlatformId == other.PlatformId; + } + + 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((IpMacNotification)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(TargetName, TargetType, PlatformId); + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntModel/Operational.cs b/skyscraper8/Dvb/DataBroadcasting/IntModel/Operational.cs new file mode 100644 index 0000000..199e9ea --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntModel/Operational.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.DataBroadcasting.IntModel +{ + public class Operational + { + //0x77 Time Slice and FEC identifier descriptor + public int? TimeSliceFrameSize { get; set; } + public int? TimeSliceMaxAverageRate { get; set; } + public byte? TimeSliceMaxBurstDuration { get; set; } + public int? MpeFecAlgorithm { get; set; } + public int? TimeSliceFecId { get; set; } + public bool? TimeSlicing { get; set; } + + //0x13 ip mac stream location descriptor + public byte? ComponentTag { get; set; } + public ushort? NetworkId { get; set; } + public ushort? OriginalNetworkId { get; set; } + public ushort? ServiceId { get; set; } + public ushort? TransportStreamId { get; set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntModel/Platform.cs b/skyscraper8/Dvb/DataBroadcasting/IntModel/Platform.cs new file mode 100644 index 0000000..fb2b525 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntModel/Platform.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Net; + +namespace skyscraper5.Dvb.DataBroadcasting.IntModel +{ + public class Platform + { + public uint PlatformId { get; } + public byte ProcessingOrder { get; } + public byte ActionType { get; } + + //0x0c Ip Mac Platform Name Descriptor + public string NameLanguageCode { get; set; } + public string Name { get; set; } + + //0x0d Ip Mac Platform Provider Name Descriptor + public string ProviderNameLanguageCode { get; set; } + public string ProviderName { get; set; } + + public Platform(byte actionType, uint platformId, byte processingOrder) + { + ActionType = actionType; + PlatformId = platformId; + ProcessingOrder = processingOrder; + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntModel/Target.cs b/skyscraper8/Dvb/DataBroadcasting/IntModel/Target.cs new file mode 100644 index 0000000..edd761a --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntModel/Target.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Net; + +namespace skyscraper5.Dvb.DataBroadcasting.IntModel +{ + public class Target + { + //0x0f Target Ip Slash Descriptor + public List TargetIpSlashes { get; set; } + + //Was Loop empty? + public bool AllReceivers { get; set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/IntParser.cs b/skyscraper8/Dvb/DataBroadcasting/IntParser.cs new file mode 100644 index 0000000..10dca83 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/IntParser.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.IntDescriptors; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Net; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + internal class IntParser : IPsiProcessor + { + public IntEventHandler EventHandler { get; } + + public IntParser(IntEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetData(), false); + byte tableId = ms.ReadUInt8(); + if (tableId != 0x4c) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + return; + int sectionLength = (readUInt16Be & 0x0fff); + ms = new MemoryStream(ms.ReadBytes(sectionLength), false); + + byte actionType = ms.ReadUInt8(); + byte platformIdHash = ms.ReadUInt8(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e) >> 1; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + + uint platformId = ms.ReadUInt24BE(); + byte processingOrder = ms.ReadUInt8(); + + ushort platformDescriptorLoopLength = ms.ReadUInt16BE(); + platformDescriptorLoopLength &= 0x0fff; + Platform output = new Platform(actionType, platformId, processingOrder); + ParsePlatformDescriptorLoop(ms.ReadBytes(platformDescriptorLoopLength), output); + + while (ms.GetAvailableBytes() > 4) + { + Target outputTarget = new Target(); + ushort targetDescriptorLoopLength = ms.ReadUInt16BE(); + targetDescriptorLoopLength &= 0x0fff; + ParseTargetDescriptorLoop(ms.ReadBytes(targetDescriptorLoopLength), outputTarget); + + Operational outputOperational = new Operational(); + ushort operationalDescriptorLoopLength = ms.ReadUInt16BE(); + operationalDescriptorLoopLength &= 0x0fff; + ParseOperationalDescriptorLoop(ms.ReadBytes(operationalDescriptorLoopLength), outputOperational); + + EventHandler.OnIpMacNotification(sourcePid, output, outputTarget, outputOperational); + } + + uint crc32 = ms.ReadUInt32BE(); + } + + private void ParsePlatformDescriptorLoop(byte[] input, Platform output) + { + foreach (TsDescriptor tsDescriptor in TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "INT")) + { + string cName = tsDescriptor.GetType().Name; + switch (cName) + { + case nameof(IpMacPlatformNameDescriptor): + if (!string.IsNullOrEmpty(output.Name)) + throw new NotImplementedException("platform name in multiple languages"); + + IpMacPlatformNameDescriptor impnd = (IpMacPlatformNameDescriptor)tsDescriptor; + output.NameLanguageCode = impnd.Iso639LanguageCode; + output.Name = impnd.Text; + break; + case nameof(IpMacPlatformProviderNameDescriptor): + if (!string.IsNullOrEmpty(output.ProviderName)) + throw new NotImplementedException("provider name in multiple languages"); + + IpMacPlatformProviderNameDescriptor ippnd = (IpMacPlatformProviderNameDescriptor)tsDescriptor; + output.ProviderNameLanguageCode = ippnd.Iso639LanguageCode; + output.ProviderName = ippnd.Text; + break; + default: + throw new NotImplementedException(cName); + } + } + } + + private void ParseTargetDescriptorLoop(byte[] input, Target output) + { + int numTargets = 0; + foreach (TsDescriptor tsDescriptor in TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "INT")) + { + string cName = tsDescriptor.GetType().Name; + switch (cName) + { + case nameof(TargetIpSlashDescriptor): + TargetIpSlashDescriptor tisd = (TargetIpSlashDescriptor)tsDescriptor; + if (output.TargetIpSlashes == null) + output.TargetIpSlashes = new List(); + output.TargetIpSlashes.AddRange(tisd.Targets); + numTargets++; + break; + default: + throw new NotImplementedException(cName); + } + } + + if (numTargets == 0) + output.AllReceivers = true; + } + + private void ParseOperationalDescriptorLoop(byte[] input, Operational output) + { + foreach (TsDescriptor tsDescriptor in TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "INT")) + { + string cName = tsDescriptor.GetType().Name; + switch (cName) + { + case nameof(TimeSliceFecIdentifierDescriptor): + TimeSliceFecIdentifierDescriptor tsfid = (TimeSliceFecIdentifierDescriptor)tsDescriptor; + output.TimeSliceFrameSize = tsfid.FrameSize; + output.TimeSliceMaxAverageRate = tsfid.MaxAverageRate; + output.TimeSliceMaxBurstDuration = tsfid.MaxBurstDuration; + output.MpeFecAlgorithm = tsfid.MpeFec; + output.TimeSliceFecId = tsfid.TimeSliceFecId; + output.TimeSlicing = tsfid.TimeSlicing; + break; + case nameof(IpMacStreamLocationDescriptor): + IpMacStreamLocationDescriptor ipmac = (IpMacStreamLocationDescriptor)tsDescriptor; + if (output.TransportStreamId.HasValue) + throw new NotImplementedException("multiple ip mac stram location descriptors"); + output.ComponentTag = ipmac.ComponentTag; + output.NetworkId = ipmac.NetworkId; + output.OriginalNetworkId = ipmac.OriginalNetworkId; + output.ServiceId = ipmac.ServiceId; + output.TransportStreamId = ipmac.TransportStreamId; + break; + default: + throw new NotImplementedException(cName); + } + } + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/InteroperableObjectReference.cs b/skyscraper8/Dvb/DataBroadcasting/InteroperableObjectReference.cs new file mode 100644 index 0000000..5c8fa00 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/InteroperableObjectReference.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + class InteroperableObjectReference + { + public InteroperableObjectReference(byte[] buffer) + : this(new MemoryStream(buffer,false)) + { + + } + public InteroperableObjectReference(MemoryStream ms) + { + + uint typeIdLength = ms.ReadUInt32BE(); + if (ms.GetAvailableBytes() < typeIdLength) + return; + TypeId = Encoding.ASCII.GetString(ms.ReadBytes(typeIdLength)); + TypeId = TypeId.Trim((char)0); + + if (typeIdLength % 4 != 0) + { + for (int i = 0; i < (4 - (typeIdLength % 4)); i++) + { + ms.ReadUInt8(); + } + } + + uint taggedProfilesCount = ms.ReadUInt32BE(); + TaggedProfiles = new TaggedProfile[taggedProfilesCount]; + for (uint i = 0; i < taggedProfilesCount; i++) + { + TaggedProfiles[i] = new TaggedProfile(); + TaggedProfiles[i].ProfileIdTag = ms.ReadUInt32BE(); + uint profileDataLength = ms.ReadUInt32BE(); + TaggedProfiles[i].ProfileData = ms.ReadBytes(profileDataLength); + } + } + + public string TypeId { get; private set; } + + public TaggedProfile[] TaggedProfiles { get; private set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/ModuleInfoStream.cs b/skyscraper8/Dvb/DataBroadcasting/ModuleInfoStream.cs new file mode 100644 index 0000000..d8bac37 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/ModuleInfoStream.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Message; +using skyscraper5.Dvb.DataBroadcasting.Biop; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + internal class ModuleInfoStream : Stream + { + public ModuleInfoStream(ModuleInfo mi, bool allowDispose) + { + this.myModule = mi; + this.myModuleLength = mi.Blocks.Select(x => x.BlockData.Length).Sum(); + this.allowDispose = allowDispose; + } + + private bool allowDispose; + private ModuleInfo myModule; + private long currentPosition; + private long myModuleLength; + + public override void Flush() + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetRequiredBlockId(long position) + { + for (int i = 0; i < myModule.Blocks.Length; i++) + { + if (myModule.Blocks[i].BlockData.Length > position) + { + return i; + } + + position -= myModule.Blocks[i].BlockData.Length; + } + throw new ArgumentOutOfRangeException(nameof(position)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetOffsetInBlock(long position) + { + for (int i = 0; i < myModule.Blocks.Length; i++) + { + if (myModule.Blocks[i].BlockData.Length > position) + { + return (int)position; + } + + position -= myModule.Blocks[i].BlockData.Length; + } + throw new ArgumentOutOfRangeException(nameof(position)); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (closed) + { + throw new ObjectDisposedException("This module is already disposed."); + } + + if (Position + count > Length) + { + count = (int)(Length - Position); + } + + if (Position == Length) + return 0; + + int requiredBlockId = GetRequiredBlockId(currentPosition); + int offsetInBlock = GetOffsetInBlock(currentPosition); + int remainingInBlock = myModule.Blocks[requiredBlockId].BlockData.Length - offsetInBlock; + int bytesToCopy = Math.Min(remainingInBlock, count); + int stillNeededToCopy = count - bytesToCopy; + + Array.Copy(myModule.Blocks[requiredBlockId].BlockData, offsetInBlock, buffer, offset, bytesToCopy); + Position += bytesToCopy; + int result = bytesToCopy; + offset += bytesToCopy; + count -= bytesToCopy; + + while (stillNeededToCopy > 0) + { + requiredBlockId++; + offsetInBlock = 0; + remainingInBlock = myModule.Blocks[requiredBlockId].BlockData.Length - offsetInBlock; + bytesToCopy = Math.Min(remainingInBlock, count); + stillNeededToCopy = count - bytesToCopy; + + Array.Copy(myModule.Blocks[requiredBlockId].BlockData, offsetInBlock, buffer, offset, bytesToCopy); + Position += bytesToCopy; + result += bytesToCopy; + offset += bytesToCopy; + count -= bytesToCopy; + } + return result; + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + currentPosition = offset; + break; + case SeekOrigin.Current: + currentPosition += offset; + break; + case SeekOrigin.End: + currentPosition = Length + offset; + break; + default: + throw new NotImplementedException(origin.ToString()); + } + return currentPosition; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Can't modify the length of a ModuleInfo"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Can't write to a ModuleInfo"); + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => myModuleLength; + + public override long Position + { + get => currentPosition; + set => Seek(value, SeekOrigin.Begin); + } + + public override bool CanTimeout => false; + + private bool closed; + public override void Close() + { + closed = true; + + for (int i = 0; i < myModule.Blocks.Length; i++) + { + if (myModule.Blocks[i] != null) + { + myModule.Blocks[i].ForgetBlock(); + myModule.Blocks[i] = null; + } + } + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/MpeFecSectionParser.cs b/skyscraper8/Dvb/DataBroadcasting/MpeFecSectionParser.cs new file mode 100644 index 0000000..8cda9a0 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/MpeFecSectionParser.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + class MpeFecSectionParser : IPsiProcessor + { + public void GatherPsi(PsiSection section, int sourcePid) + { + byte[] dataCopy = section.GetDataCopy(); + MemoryStream ms = new MemoryStream(dataCopy, false); + byte table_id = ms.ReadUInt8(); + if (table_id != 0x78) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + bool privateIndicator = (readUInt16Be & 0x4000) != 0; + int sectionLength = (readUInt16Be & 0x0fff); + + byte paddinggColumns = ms.ReadUInt8(); + + byte readUInt8 = ms.ReadUInt8(); //reserved for future use + readUInt8 = ms.ReadUInt8(); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + + RealTimeParameters realTimeParameters = new RealTimeParameters(ms.ReadUInt32BE()); + + long rsDataLength = ms.GetAvailableBytes() - 4; + byte[] rsData = ms.ReadBytes(rsDataLength); + + uint crc32 = ms.ReadUInt32BE(); + } + + class RealTimeParameters + { + public RealTimeParameters(uint value) + { + DeltaT = (value & 0xfff00000) >> 20; + TableBoundary = (value & 0x00080000) != 0; + FrameBoundary = (value & 0x00040000) != 0; + Address = (value & 0x0003ffff); + } + + public uint Address { get; private set; } + + public bool FrameBoundary { get; private set; } + + public bool TableBoundary { get; private set; } + + public uint DeltaT { get; private set; } + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs new file mode 100644 index 0000000..c42e2b0 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Ietf.Rfc768; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + class MultiprotocolEncapsulationDecoder : IPsiProcessor + { + public IMultiprotocolEncapsulationEventHandler EventHandler { get; } + + public MultiprotocolEncapsulationDecoder(IMultiprotocolEncapsulationEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + if (ms.Length < 12) + return; + byte tableId = ms.ReadUInt8(); + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + bool privateIndicator = (readUInt16Be & 0x4000) != 0; + int sectionLength = readUInt16Be & 0x0fff; + + byte[] macAddress = new byte[6]; + macAddress[5] = ms.ReadUInt8(); + macAddress[4] = ms.ReadUInt8(); + + byte readUInt8 = ms.ReadUInt8(); + int payloadScramblingControl = (readUInt8 & 0x30) >> 4; + if (payloadScramblingControl != 0) + return; //We don't know how to unscramble these. + int addressScramblingControl = (readUInt8 & 0x0c) >> 2; + bool llcSnap = (readUInt8 & 0x02) != 0; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + macAddress[3] = ms.ReadUInt8(); + macAddress[2] = ms.ReadUInt8(); + macAddress[1] = ms.ReadUInt8(); + macAddress[0] = ms.ReadUInt8(); + + long length = ms.GetAvailableBytes() - 4; + if (length <= 0) + { + return; + } + byte[] payload = ms.ReadBytes(length); + if (llcSnap) + { + HandleLlcSnap(sourcePid,payload); + } + else + { + HandleIpDatagram(sourcePid,payload); + } + + } + + private void HandleLlcSnap(int sourcePid, byte[] payload) + { + } + + private void HandleIpDatagram(int sourcePid, byte[] payload) + { + EventHandler.OnIpDatagram(sourcePid, payload); + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs new file mode 100644 index 0000000..b99bdaf --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Ietf.Rfc971; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + interface IMultiprotocolEncapsulationEventHandler + { + void OnIpv4PacketArrival(InternetHeader internetHeader, byte[] ipv4Packet); + void OnIpDatagram(int sourcePid, byte[] payload); + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/ObjectCarouselEventHandler.cs b/skyscraper8/Dvb/DataBroadcasting/ObjectCarouselEventHandler.cs new file mode 100644 index 0000000..44f44b8 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/ObjectCarouselEventHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Event; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Stream; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + public interface ObjectCarouselEventHandler + { + void NotifyFileArrival(VfsFile vfsFile); + void NotifySubStream(ProgramMapping vfsProgramMapping, Tap tap, ushort[] eventIds, Dictionary context, EventList_T eventListT, Info_T infoT); + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/ObjectCarouselUtilities.cs b/skyscraper8/Dvb/DataBroadcasting/ObjectCarouselUtilities.cs new file mode 100644 index 0000000..b06d454 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/ObjectCarouselUtilities.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ionic.Zlib; +using skyscraper5.DsmCc; +using skyscraper5.DsmCc.Message; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors; +using skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Event; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Stream; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + internal static class ObjectCarouselUtilities + { + public static bool TestForObjectCarousel(DownloadServerInitiate dsi) + { + byte[] buffer = dsi.PrivateData; + if (buffer[4] != 0x73) + return false; + if (buffer[5] != 0x72) + return false; + if (buffer[6] != 0x67) + return false; + + InteroperableObjectReference interoperableObjectReference = new InteroperableObjectReference(dsi.PrivateData); + if (interoperableObjectReference.TaggedProfiles == null && interoperableObjectReference.TypeId == null) + return false; + switch (interoperableObjectReference.TypeId) + { + case "srg": + break; + default: + throw new NotImplementedException(interoperableObjectReference.TypeId); + } + bool profileProcessed = interoperableObjectReference.TaggedProfiles.Length == 0; + + foreach (TaggedProfile taggedProfile in interoperableObjectReference.TaggedProfiles) + { + switch (taggedProfile.ProfileIdTag) + { + case 0x49534f06: //TAG_BIOP (BIOP Profile Body) + BiopProfileBody biopProfileBody = new BiopProfileBody(taggedProfile.ProfileData); + profileProcessed = true; + SelectorType_0x01 selectorType0X01 = (SelectorType_0x01)biopProfileBody.ConnBinder.OriginTap.Selector; + Debug.WriteLine("DSM-CC: {0:X8} Got Download Server Initiate. The selector says to look for {1:x8}", dsi.Header.TransactionId, selectorType0X01.TransactionId); + break; + default: + throw new NotImplementedException(String.Format("{0:X8", taggedProfile.ProfileIdTag)); + } + } + + if (!profileProcessed) + throw new DsmCcException("Failed to find processable IOP Profile in DSM-CC message."); + + Debug.WriteLine("DSM-CC: {0:X8} Got Download Server Initiate", dsi.Header.TransactionId); + return true; + } + + public static bool TestForObjectCarousel(Stream ms, CompressedModuleDescriptor moduleCompressedModuleDescriptor) + { + if (moduleCompressedModuleDescriptor != null) + { + switch (moduleCompressedModuleDescriptor.CompressionMethod) + { + case (byte)'x': + ms = new Ionic.Zlib.ZlibStream(ms, CompressionMode.Decompress); + break; + case 0x08: + ms = new Ionic.Zlib.ZlibStream(ms, CompressionMode.Decompress); + break; + default: + ms.Close(); + ms.Dispose(); + return false; + } + } + + uint magic = ms.ReadUInt32BE(); + return magic == 0x42494F50; + } + + public static void ExtractBiopModule(ModuleInfo module, Stream compressedBuffer, ObjectCarouselMetadata vfs) + { + bool zlib = false; + Stream uncompressedBuffer; + if (module.CompressedModuleDescriptor != null) + { + switch (module.CompressedModuleDescriptor.CompressionMethod) + { + case (byte)'x': + uncompressedBuffer = new Ionic.Zlib.ZlibStream(compressedBuffer, CompressionMode.Decompress); + zlib = true; + break; + case 0x08: + uncompressedBuffer = new Ionic.Zlib.ZlibStream(compressedBuffer, CompressionMode.Decompress); + zlib = true; + break; + default: + //File.WriteAllBytes(string.Format("unknown_compressor_0x{0:X2}.bin", module.CompressedModuleDescriptor.CompressionMethod), compressedBuffer); ; + throw new NotImplementedException(nameof(module.CompressedModuleDescriptor)); + } + } + else + { + uncompressedBuffer = compressedBuffer; + } + + Stream ms = uncompressedBuffer; + uint magicNumber = ms.ReadUInt32BE(); + if (zlib) + { + compressedBuffer.Position = 0; + uncompressedBuffer = new Ionic.Zlib.ZlibStream(compressedBuffer, CompressionMode.Decompress); + ms = new MemoryStream(); + uncompressedBuffer.CopyTo(ms); + ms.Position = 0; + } + else + { + ms.Position = 0; + } + switch (magicNumber) + { + case 0x42494F50: //BIOP + AssembleBiopModule(ms, module, vfs); + break; + default: + throw new NotImplementedException(String.Format("{0:X8}", magicNumber)); + } + } + + private static void AssembleBiopModule(Stream ms, ModuleInfo module, ObjectCarouselMetadata vfs) + { + while (true) + { + if (ms.Position == ms.Length) + break; + uint magicNumber = ms.ReadUInt32BE(); + if (magicNumber != 0x42494F50) + throw new NotImplementedException(String.Format("{0:X8}", magicNumber)); + + byte biop_version_major = ms.ReadUInt8(); + byte biop_version_minor = ms.ReadUInt8(); + byte byte_order = ms.ReadUInt8(); + if (byte_order != 0x00) + throw new NotImplementedException("little endian"); + + byte message_type = ms.ReadUInt8(); + if (message_type != 0x00) + throw new NotImplementedException(String.Format("{0} {0:X2}", nameof(message_type), message_type)); + + uint message_size = ms.ReadUInt32BE(); + if (ms.GetAvailableBytes() < message_size) + break; + MemoryStream messageMs = new MemoryStream(ms.ReadBytes(message_size), false); + + byte objectKeyLength = messageMs.ReadUInt8(); + byte[] objectKeyData = messageMs.ReadBytes(objectKeyLength); + + uint objectKindLength = messageMs.ReadUInt32BE(); + string objectKindData = Encoding.ASCII.GetString(messageMs.ReadBytes(objectKindLength)).TrimEnd((char)0); + + switch (objectKindData) + { + case "fil": + DecodeFileMessage(messageMs, module, objectKeyData, vfs); + break; + case "srg": + VfsDirectory newRoot = DecodeServiceRegistryMessage(messageMs, module, objectKeyData, vfs); + if (vfs.rootDirectory == null) + { + vfs.rootDirectory = newRoot; + vfs.vfsDirectories.Add(vfs.rootDirectory); + } + vfs.DirectoriesCreated++; + break; + case "dir": + VfsDirectory outputDir = vfs.vfsDirectories.Find(x => x.MatchObjectKey(objectKeyData)); + if (outputDir != null && !outputDir.ContainsVfsEntries) + { + DecodeServiceRegistryMessage(messageMs, module, objectKeyData, vfs, outputDir); + vfs.DirectoriesCreated++; + } + break; + case "ste": + DecodeStreamEvent(messageMs, module, objectKeyData, vfs); + break; + default: + throw new NotImplementedException(objectKindData); + } + } + } + + private static void DecodeFileMessage(MemoryStream ms, ModuleInfo module, byte[] objectKeyData, ObjectCarouselMetadata vfs) + { + ushort objectInfoLength = ms.ReadUInt16BE(); + ulong contentSize = ms.ReadUInt64BE(); + objectInfoLength -= 8; + byte[] objectInfoData = ms.ReadBytes(objectInfoLength); + + byte serviceContextListCount = ms.ReadUInt8(); + Dictionary serviceContexts = null; + for (byte i = 0; i < serviceContextListCount; i++) + { + uint contextId = ms.ReadUInt32BE(); + ushort contextDataLength = ms.ReadUInt16BE(); + byte[] contextData = ms.ReadBytes(contextDataLength); + if (serviceContexts == null) + serviceContexts = new Dictionary(); + serviceContexts.Add(contextId, contextData); + } + + uint messageBodyLength = ms.ReadUInt32BE(); + uint contentLength = ms.ReadUInt32BE(); + byte[] contentData = ms.ReadBytes(contentLength); + + VfsFile vfsFile = vfs.vfsFiles.Find(x => x.MatchObjectKey(objectKeyData)); + if (vfsFile != null) + { + vfsFile.FileContent = contentData; + vfs.EventHandler.NotifyFileArrival(vfsFile); + vfs.LastExtractions++; + } + else + { + return; + } + } + + private static VfsDirectory DecodeServiceRegistryMessage(MemoryStream ms, ModuleInfo module, byte[] objectKeyData, ObjectCarouselMetadata vfs, VfsDirectory copyFrom = null) + { + VfsDirectory result = new VfsDirectory(); + if (copyFrom != null) + result = copyFrom; + ushort objectInfoLength = ms.ReadUInt16BE(); + byte[] objectInfoData = ms.ReadBytes(objectInfoLength); + + byte serviceContextListCount = ms.ReadUInt8(); + Dictionary serviceContexts = null; + for (int i = 0; i < serviceContextListCount; i++) + { + if (serviceContexts == null) + serviceContexts = new Dictionary(); + uint contextId = ms.ReadUInt32BE(); + ushort contextDataLength = ms.ReadUInt16BE(); + byte[] contextData = ms.ReadBytes(contextDataLength); + serviceContexts.Add(contextId, contextData); + } + + uint messageBodyLength = ms.ReadUInt32BE(); + ushort bindingsCount = ms.ReadUInt16BE(); + + for (ushort i = 0; i < bindingsCount; i++) + { + Name name = new Name(ms); + byte bindingType = ms.ReadUInt8(); + InteroperableObjectReference ior = new InteroperableObjectReference(ms); + + bool profileProcessed = ior.TaggedProfiles.Length == 0; + + foreach (TaggedProfile taggedProfile in ior.TaggedProfiles) + { + switch (taggedProfile.ProfileIdTag) + { + case 0x49534f06: //TAG_BIOP (BIOP Profile Body) + BiopProfileBody biopProfileBody = new BiopProfileBody(taggedProfile.ProfileData); + KeyValuePair keyValuePair = name.NameComponents.First(); + string valueType = keyValuePair.Value.ToLowerInvariant(); + switch (valueType) + { + case "fil": + VfsFile vfsFile = new VfsFile(vfs.SourcePid, vfs.ProgramMapping); + vfsFile.Name = keyValuePair.Key; + vfsFile.ParentDirectory = result; + vfsFile.ObjectLocation = biopProfileBody.ObjectLocation; + vfs.vfsFiles.Add(vfsFile); + result.files.Add(vfsFile); + profileProcessed = true; + break; + case "dir": + VfsDirectory vfsDirectory = new VfsDirectory(); + vfsDirectory.Name = keyValuePair.Key; + vfsDirectory.ParentDirectory = result; + vfsDirectory.ObjectLocation = biopProfileBody.ObjectLocation; + vfs.vfsDirectories.Add(vfsDirectory); + result.subdirectories.Add(vfsDirectory); + profileProcessed = true; + break; + case "ste": + VfsEvent vfsEvent = new VfsEvent(); + vfsEvent.Name = keyValuePair.Key; + vfsEvent.ParentDirectory = result; + vfsEvent.ObjectLocation = biopProfileBody.ObjectLocation; + vfs.vfsEvents.Add(vfsEvent); + result.events.Add(vfsEvent); + profileProcessed = true; + break; + default: + throw new NotImplementedException(keyValuePair.Value); + } + break; + default: + throw new NotImplementedException(String.Format("{0:X8} context = new Dictionary(); + for (int i = 0; i < eventListT.N3; i++) + { + uint contextId = ms.ReadUInt32BE(); + ushort contextDataLength = ms.ReadUInt16BE(); + if (contextDataLength > ms.GetAvailableBytes()) + return; + string contextData = Encoding.UTF8.GetString(ms.ReadBytes(contextDataLength)); + context.Add(contextId, contextData); + } + + uint messageBodyLength = ms.ReadUInt32BE(); + byte tapsCount = ms.ReadUInt8(); + Tap[] taps = new Tap[tapsCount]; + for (int i = 0; i < tapsCount; i++) + { + taps[i] = new Tap(ms); + } + + byte eventIdsCount = ms.ReadUInt8(); + ushort[] eventIds = new ushort[eventIdsCount]; + for (int i = 0; i < eventIdsCount; i++) + eventIds[i] = ms.ReadUInt16BE(); + + for (int i = 0; i < tapsCount; i++) + { + TapUseDefinitions tud = (TapUseDefinitions)taps[i].Use; + switch (tud) + { + case TapUseDefinitions.STR_EVENT_USE: + vfs.EventHandler.NotifySubStream(vfs.ProgramMapping, taps[i], eventIds, context, eventListT, infoT); + vfs.DirectoriesCreated++; + break; + default: + throw new NotImplementedException(String.Format("{0:X4}", tud)); + } + } + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/ObjectCarouselMetadata.cs b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/ObjectCarouselMetadata.cs new file mode 100644 index 0000000..77f0ba4 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/ObjectCarouselMetadata.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs +{ + class ObjectCarouselMetadata + { + public ObjectCarouselMetadata(ObjectCarouselEventHandler eventHandler, int sourcePid, ProgramMapping programMapping) + { + vfsDirectories = new List(); + vfsFiles = new List(); + this.EventHandler = eventHandler; + this.SourcePid = sourcePid; + this.ProgramMapping = programMapping; + vfsEvents = new List(); + } + + public VfsDirectory rootDirectory; + public List vfsDirectories; + public List vfsFiles; + public List vfsEvents; + public ObjectCarouselEventHandler EventHandler { get; private set; } + public int SourcePid { get; private set; } + public ProgramMapping ProgramMapping { get; private set; } + public int LastExtractions { get; set; } + public int DirectoriesCreated { get; set; } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsBase.cs b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsBase.cs new file mode 100644 index 0000000..c9713db --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsBase.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop; + +namespace skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs +{ + public abstract class VfsBase + { + protected VfsBase() + { + SkyscrpaerUiData = new Dictionary(); + } + + public ObjectLocation ObjectLocation { get; set; } + public VfsDirectory ParentDirectory; + public string Name; + public Dictionary SkyscrpaerUiData { get; private set; } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + if (ParentDirectory != null) + { + sb.Append(ParentDirectory.ToString()); + sb.Append(Path.DirectorySeparatorChar); + } + + sb.Append(Name); + return sb.ToString(); + } + + public bool MatchObjectKey(byte[] r) + { + if (r == null) + return false; + if (ObjectLocation == null) + return false; + if (ObjectLocation.ObjectKey == null) + return false; + + byte[] l = ObjectLocation.ObjectKey; + if (l.Length != r.Length) + return false; + + for (int i = 0; i < l.Length; i++) + { + if (l[i] != r[i]) + return false; + } + + return true; + } + + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsDirectory.cs b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsDirectory.cs new file mode 100644 index 0000000..a4f1d90 --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsDirectory.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop; + +namespace skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs +{ + public class VfsDirectory : VfsBase + { + public VfsDirectory() + { + files = new List(); + subdirectories = new List(); + events = new List(); + } + + public List files; + public List subdirectories; + public List events; + + public bool ContainsVfsEntries + { + get + { + if (files.Count > 0) + return true; + if (subdirectories.Count > 0) + return true; + if (events.Count > 0) + return true; + return false; + } + } + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsEvent.cs b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsEvent.cs new file mode 100644 index 0000000..f4b909c --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsEvent.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop; + +namespace skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs +{ + public class VfsEvent : VfsBase + { + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsFile.cs b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsFile.cs new file mode 100644 index 0000000..841d65b --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/SkyscraperVfs/VfsFile.cs @@ -0,0 +1,19 @@ +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs +{ + public class VfsFile : VfsBase + { + public int SourcePid { get; } + public ProgramMapping ProgramMapping { get; } + + public VfsFile(int sourcePid, ProgramMapping programMapping) + { + SourcePid = sourcePid; + ProgramMapping = programMapping; + } + + public byte[] FileContent { get; internal set; } + + } +} diff --git a/skyscraper8/Dvb/DataBroadcasting/TaggedProfile.cs b/skyscraper8/Dvb/DataBroadcasting/TaggedProfile.cs new file mode 100644 index 0000000..04fc67c --- /dev/null +++ b/skyscraper8/Dvb/DataBroadcasting/TaggedProfile.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.DataBroadcasting +{ + class TaggedProfile + { + public uint ProfileIdTag { get; set; } + public byte[] ProfileData { get; set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x40_NetworkNameDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x40_NetworkNameDescriptor.cs new file mode 100644 index 0000000..e4e841c --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x40_NetworkNameDescriptor.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x40,"NIT")] + [BannedTable("PMT","BAT")] + class NetworkNameDescriptor : TsDescriptor + { + public NetworkNameDescriptor(byte[] buffer) + { + NetworkName = En300468AnnexATextDecoder.GetInstance().Decode(buffer); + } + + public string NetworkName { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x41_ServiceListDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x41_ServiceListDescriptor.cs new file mode 100644 index 0000000..8b57880 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x41_ServiceListDescriptor.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x41,"NIT","BAT")] + [BannedTable("PMT","TSDT","EIT")] + public class ServiceListDescriptor : TsDescriptor + { + public ServiceListDescriptor(byte[] buffer) + { + if (buffer == null) + return; + if (buffer.Length % 3 != 0) + return; + + Services = new Service[buffer.Length / 3]; + for (int i = 0; i < buffer.Length; i += 3) + { + (buffer[i], buffer[i + 1]) = (buffer[i + 1], buffer[i]); + Services[i / 3] = new Service(BitConverter.ToUInt16(buffer, i), (ServiceDescriptor.ServiceTypeCoding)buffer[i + 2]); + } + + Valid = true; + } + + public Service[] Services { get; private set; } + + + public struct Service + { + public ushort ServiceId { get; } + public ServiceDescriptor.ServiceTypeCoding ServiceType { get; } + + public Service(ushort serviceId, ServiceDescriptor.ServiceTypeCoding serviceType) + { + ServiceId = serviceId; + ServiceType = serviceType; + } + + public bool Equals(Service other) + { + return ServiceId == other.ServiceId; + } + + public override bool Equals(object obj) + { + return obj is Service other && Equals(other); + } + + public override int GetHashCode() + { + return ServiceId.GetHashCode(); + } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x42_StuffingDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x42_StuffingDescriptor.cs new file mode 100644 index 0000000..d7df77b --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x42_StuffingDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x42,"NIT","BAT","SDT","EIT","TSDT","CAT")] + [BannedTable("PMT","TOT")] + class StuffingDescriptor : TsDescriptor + { + public StuffingDescriptor(byte[] buffer) + { + //"The IRDs may discard the stuffing bytes." + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x43_SatelliteDeliverySystemDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x43_SatelliteDeliverySystemDescriptor.cs new file mode 100644 index 0000000..4655591 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x43_SatelliteDeliverySystemDescriptor.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x43,"NIT")] + [BannedTable("BAT","TSDT")] + public class SatelliteDeliverySystemDescriptor : TsDescriptor + { + public SatelliteDeliverySystemDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + Frequency = ms.ReadUInt32BE().UnpackBcd(); + OrbitalPosition = (float)ms.ReadUInt16BE().UnpackBcd() / 10.0f; + + byte readUInt8 = ms.ReadUInt8(); + East = (readUInt8 & 0x80) != 0; + Polarization = (PolarizationEnum)((readUInt8 & 0x60) >> 5); + int temp1 = ((readUInt8 & 0x18) >> 3); + S2 = (readUInt8 & 0x04) != 0; + ModulationType = (readUInt8 & 0x03); + + uint readUInt32Be = ms.ReadUInt32BE(); + SymbolRate = (readUInt32Be >> 4).UnpackBcd(); + FecInner = (InnerFecScheme)(readUInt32Be & 0x0000000f); + + if (S2) + { + switch (temp1) + { + case 0: + RollOff = 0.35f; + break; + case 1: + RollOff = 0.25f; + break; + case 2: + RollOff = 0.20f; + break; + default: + break; + } + } + } + + public int ModulationType { get; private set; } + + public float? RollOff { get; set; } + + public InnerFecScheme FecInner { get; set; } + + public long SymbolRate { get; set; } + + public bool S2 { get; set; } + + public PolarizationEnum Polarization { get; set; } + + public bool East { get; set; } + + public float OrbitalPosition { get; set; } + + public long Frequency { get; set; } + + public enum PolarizationEnum + { + HorizontalLinear = 0, + VerticalLinear = 1, + LeftCircular = 2, + RightCircular = 3 + } + + /*public enum ModulationTypeForSattelite + { + Auto = 0, + QPSK = 2, + _8PSK = 1, + _16QAM = 3 + }*/ + + public enum InnerFecScheme + { + NotDefined = 0, + OneHalf = 1, + TwoOfThree = 2, + ThreeOfFour = 3, + FiveOfSix = 4, + SevenOfEight = 5, + EightOfNine = 6, + ThreeOfFive = 7, + FourOfFive = 8, + NineOfTen = 9, + NoConventionalCoding = 15 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x44_CableDeliverySystemDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x44_CableDeliverySystemDescriptor.cs new file mode 100644 index 0000000..35ea636 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x44_CableDeliverySystemDescriptor.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x44,"NIT")] + [BannedTable("PMT")] + public class CableDeliverySystemDescriptor : TsDescriptor + { + public CableDeliverySystemDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Frequency = ms.ReadUInt32BE(); + FecOuter = (OuterFecScheme)(ms.ReadUInt16BE() & 0x000f); + Modulation = (ms.ReadUInt8()); + + uint readUInt32Be = ms.ReadUInt32BE(); + SymbolRate = ((readUInt32Be & 0xfffffff0) >> 4).UnpackBcd(); + FecInner = ((SatelliteDeliverySystemDescriptor.InnerFecScheme)(readUInt32Be & 0x0000000f)); + } + + public byte Modulation { get; private set; } + + public SatelliteDeliverySystemDescriptor.InnerFecScheme FecInner { get; private set; } + + public long SymbolRate { get; private set; } + + public OuterFecScheme FecOuter { get; private set; } + + public uint Frequency { get; private set; } + + public enum OuterFecScheme : ushort + { + NotDefined = 0, + NoOuterFec = 1, + ReedSolomon204_188 = 2 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x45_VbiDataDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x45_VbiDataDescriptor.cs new file mode 100644 index 0000000..7715f28 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x45_VbiDataDescriptor.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x45,"PMT")] + [BannedTable("TSDT","BAT")] + public class VbiDataDescriptor : TsDescriptor + { + public VbiDataDescriptor(byte[] buffer) + { + List result = new List(); + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 2) + { + DataService child = new DataService(); + result.Add(child); + + child.DataServiceId = ms.ReadUInt8(); + int dataServiceLeng = ms.ReadUInt8(); + byte[] data = ms.ReadBytes(dataServiceLeng); + if (child.DataServiceId == 0x01 || child.DataServiceId == 0x02 || child.DataServiceId == 0x04 || child.DataServiceId == 0x05 || child.DataServiceId == 0x06 || child.DataServiceId == 0x07) + { + child.FieldParity = new bool[data.Length]; + child.LineOffset = new int[data.Length]; + for (int i = 0; i < data.Length; i++) + { + child.FieldParity[i] = (data[i] & 0x20) != 0; + child.LineOffset[i] = (data[i] & 0x1f); + } + } + else + { + //Reserved? Don't care! + } + } + + VbiData = result.AsReadOnly(); + } + + public ReadOnlyCollection VbiData { get; private set; } + + public class DataService + { + public byte DataServiceId { get; set; } + + public bool[] FieldParity { get; set; } + + public int[] LineOffset { get; set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x46_VbiTeletextDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x46_VbiTeletextDescriptor.cs new file mode 100644 index 0000000..2a16fc9 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x46_VbiTeletextDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x46,"PMT")] + class VbiTeletextDescriptor : TsDescriptor + { + public VbiTeletextDescriptor(byte[] buffer) + { + if (buffer.Length % 5 != 0) + return; + + TeletextDescriptor td = new TeletextDescriptor(buffer); + this.Teletexts = td.Teletexts; + } + + public TeletextDescriptor.TeletextDescriptorTeletext[] Teletexts { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x47_BouquetNameDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x47_BouquetNameDescriptor.cs new file mode 100644 index 0000000..01055e4 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x47_BouquetNameDescriptor.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x47,"BAT")] + [BannedTable("SDT")] + class BouquetNameDescriptor : TsDescriptor + { + public BouquetNameDescriptor(byte[] buffer) + { + BouquetName = En300468AnnexATextDecoder.GetInstance().Decode(buffer); + } + + public string BouquetName { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs new file mode 100644 index 0000000..2140ebf --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x48,"SDT")] + [BannedTable("TSDT","PMT")] + public class ServiceDescriptor : TsDescriptor + { + public ServiceDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ServiceType = (ServiceTypeCoding)ms.ReadUInt8(); + byte serviceProviderNameLength = ms.ReadUInt8(); + ServiceProviderName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(serviceProviderNameLength)); + byte serviceNameLength = ms.ReadUInt8(); + ServiceName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(serviceNameLength)); + } + + public ServiceDescriptor(string serviceName, string serviceProviderName, ServiceTypeCoding serviceType) + { + ServiceName = serviceName; + ServiceProviderName = serviceProviderName; + ServiceType = serviceType; + } + + public string ServiceName { get; private set; } + + public string ServiceProviderName { get; private set; } + + public ServiceTypeCoding ServiceType { get; private set; } + + public enum ServiceTypeCoding : byte + { + TV_MPEG2 = 0x01, + Radio = 0x02, + Teletext = 0x03, + NvodReference = 0x04, + NvodTimeShifted = 0x05, + Mosaic_MPEG2 = 0x06, + FmRadio = 0x07, + DvbSrm = 0x08, //ETSI TS 102 770 + Radio_AAC = 0x0a, + Mosaic_H264 = 0x0b, + DataBroadcast = 0x0c, + CommonInterfaceUsage = 0x0d, //CENELEC EN 50221 + RcsMap = 0x0e, //ETSI EN 301 790 + RcsFls = 0x0f, //ETSI EN 301 790 + DvbMhp = 0x10, + HDTV_MPEG2 = 0x11, + TV_H264 = 0x16, + H264SdNvodTimeShifted = 0x17, + H264SdNvodReference = 0x18, + HDTV_H264 = 0x19, + H264HdNvodTimeShifted = 0x1a, + H264HdNvodReference = 0x1b, + _3DTV_H264 = 0x1c, + H264StereoscopicHdNvodTimeShifted = 0x1d, + H264StereoscopicHdNvodRefence = 0x1e, + TV_HEVC = 0x1f, + _4KTV_HEVC = 0x20, + } + + public override byte[] Serialize() + { + byte[] nameBuffer = En300468AnnexATextDecoder.GetInstance().Encode(ServiceName); + if (nameBuffer.Length > byte.MaxValue) + throw new Exception("Service name too long"); + + byte[] providerBuffer = En300468AnnexATextDecoder.GetInstance().Encode(ServiceProviderName); + if (providerBuffer.Length > byte.MaxValue) + throw new Exception("Service Provider Name too long"); + + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8((byte)ServiceType); + + ms.WriteUInt8((byte)providerBuffer.Length); + ms.Write(providerBuffer, 0, providerBuffer.Length); + + ms.WriteUInt8((byte)nameBuffer.Length); + ms.Write(nameBuffer, 0, nameBuffer.Length); + + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x49_CountryAvailabilityDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x49_CountryAvailabilityDescriptor.cs new file mode 100644 index 0000000..fca12c0 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x49_CountryAvailabilityDescriptor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x49,"BAT","SDT")] + [BannedTable("PMT","TSDT")] + class CountryAvailabilityDescriptor : TsDescriptor + { + public CountryAvailabilityDescriptor(byte[] buffer) + { + if (buffer.Length == 0) + { + CountryCodes = new string[0]; + return; + } + + CountryAvailabilityFlag = (buffer[0] & 0x80) != 0; + + int numCountries = (buffer.Length - 1) / 3; + CountryCodes = new string[numCountries]; + for (int i = 0; i < numCountries; i++) + { + CountryCodes[i] = Encoding.ASCII.GetString(buffer, 1 + (i * 3), 3); + } + + } + + public string[] CountryCodes { get; private set; } + + public bool CountryAvailabilityFlag { get; private set; } + + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x4A_LinkageDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x4A_LinkageDescriptor.cs new file mode 100644 index 0000000..2af5419 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x4A_LinkageDescriptor.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4a,"NIT","BAT","SDT","EIT")] + [BannedTable("PMT")] + public class LinkageDescriptor : TsDescriptor + { + public LinkageDescriptor() + { + + } + + public LinkageDescriptor(byte[] buffer) + { + if (buffer.Length < 7) + { + Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + TransportStreamId = ms.ReadUInt16BE(); + OriginalNetworkId = ms.ReadUInt16BE(); + ServiceId = ms.ReadUInt16BE(); + LinkageType = (LinkageTypeEnum)ms.ReadUInt8(); + + if (ms.GetAvailableBytes() > 0) + { + byte linkageTypeRaw = (byte)LinkageType; + if (LinkageType == LinkageTypeEnum.MobileHandover) + { + byte readUInt8 = ms.ReadUInt8(); + HandoverType = (HandoverTypeCoding)((readUInt8 & 0xf0) >> 4); + HandoverOriginType = (readUInt8 & 0x01) != 0; + if (HandoverType == HandoverTypeCoding.NeighbouringCountry || + HandoverType == HandoverTypeCoding.LocalVariationOfSameService || + HandoverType == HandoverTypeCoding.AssociatedService) + { + HandoverNetworkId = ms.ReadUInt16BE(); + } + + if (HandoverOriginType.HasValue) + if (!HandoverOriginType.Value) + HandoverInitialServiceId = ms.ReadUInt16BE(); + } + else if (LinkageType == LinkageTypeEnum.EventLinkage) + { + TargetEventId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + TargetEventListed = (readUInt8 & 0x80) != 0; + TargetEventSimulcasted = (readUInt8 & 0x40) != 0; + } + else if ((byte)LinkageType >= 0x0e && (byte)LinkageType <= 0x1f) + { + byte loopLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < loopLength) + return; + byte[] extendedBytes = ms.ReadBytes(loopLength); + ExtendedEventLinkages = UnpackExtendedEventLinkageInfo(extendedBytes).ToArray(); + } + else if (LinkageType == LinkageTypeEnum.SystemSoftwareUpdate) + { + byte ouiDataLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < ouiDataLength) + { + Valid = false; + return; + } + byte[] ouiData = ms.ReadBytes(ouiDataLength); + bool ouiValid = false; + + void DecodeOuiData(byte[] ouiData) + { + SystemSoftwareUpdateLinkStructure = new List(); + MemoryStream ms = new MemoryStream(ouiData, false); + while (ms.GetAvailableBytes() >= 4) + { + byte[] oui = ms.ReadBytes(3); + byte selectorLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < selectorLength) + { + return; + } + byte[] selector = ms.ReadBytes(selectorLength); + OuiPrivateData opd = new OuiPrivateData(oui, selector); + SystemSoftwareUpdateLinkStructure.Add(opd); + } + ouiValid = true; + } + DecodeOuiData(ouiData); + if (!ouiValid) + { + Valid = false; + return; + } + } + else if (LinkageType == LinkageTypeEnum.SystemSoftwareUpdateNit) + { + TableType = (TableTypeCoding)ms.ReadUInt8(); + } + else if (linkageTypeRaw >= 0x80 && linkageTypeRaw <= 0xfe) + { + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else if (LinkageType == LinkageTypeEnum.TsContainingCompleteNetworkSi) + { + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else if (LinkageType == LinkageTypeEnum.EpgService) + { + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else if (LinkageType == LinkageTypeEnum.IpMacNotificationService) + { + byte platformIdDataLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < platformIdDataLength) + { + Valid = false; + return; + } + byte[] platformBuffer = ms.ReadBytes(platformIdDataLength); + ParseIpMacNotificationLinkage(platformBuffer); + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else if (LinkageType == LinkageTypeEnum.TsContainingIntBatOrNit) + { + TableType = (TableTypeCoding)ms.ReadUInt8(); + if (TableType == TableTypeCoding.Bat) + { + BouquetId = ms.ReadUInt16BE(); + } + } + else if (LinkageType == LinkageTypeEnum.DataBroadcastService) + { + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else if (LinkageType == LinkageTypeEnum.InformationService) + { + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else if ((byte)LinkageType >= 0x21) + { + //Reserved for future use? + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + } + else + { + throw new NotImplementedException(String.Format("{0:X2}",(byte)LinkageType)); + } + } + + this.Valid = true; + } + + private void ParseIpMacNotificationLinkage(byte[] buffer) + { + IpMacNotificationLinkages = new List(); + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 3) + { + IpMacNotificationLinkage child = new IpMacNotificationLinkage(); + child.Id = ms.ReadUInt16BE(); + child.Id <<= 8; + child.Id += ms.ReadUInt8(); + byte platformNameLoopLength = ms.ReadUInt8(); + while (platformNameLoopLength > 0) + { + string iso639LangCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + platformNameLoopLength -= 3; + byte platformNameLength = ms.ReadUInt8(); + platformNameLoopLength--; + string platformName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(platformNameLength)); + platformNameLoopLength -= platformNameLength; + if (child.Names == null) + child.Names = new List>(); + child.Names.Add(new Tuple(iso639LangCode, platformName)); + } + } + } + + public class IpMacNotificationLinkage + { + public int Id { get; set; } + public List> Names { get; set; } + } + + public List IpMacNotificationLinkages { get; private set; } + + public ushort? BouquetId { get; set; } + + public List SystemSoftwareUpdateLinkStructure { get; set; } + public class OuiPrivateData + { + public OuiPrivateData(byte[] oui, byte[] selector) + { + this.OUI = oui; + this.Selector = selector; + } + + public byte[] OUI { get; set; } + public byte[] Selector { get; set; } + } + public byte[] PrivateDataBytes { get; set; } + + public TableTypeCoding? TableType { get; set; } + + public enum TableTypeCoding : byte + { + Nit = 1, + Bat = 2 + } + public class ExtendedEventLinkageInfo + { + public ushort TargetEventId { get; internal set; } + public bool TargetListed { get; internal set; } + public bool EventSimulcast { get; internal set; } + public int LinkType { get; internal set; } + public TargetIdTypeEncoding TargetIdType { get; internal set; } + public ushort? UserDefinedId { get; set; } + public ushort? TargetTransportStreamId { get; set; } + public ushort? TargetOriginalNetworkId { get; set; } + public ushort? TargetServiceId { get; set; } + } + + public enum TargetIdTypeEncoding + { + TransportStreamId = 0, + TargetTransportStreamId = 1, + Wildcard = 2, + UserDefinedId = 3, + } + + private IEnumerable UnpackExtendedEventLinkageInfo(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while ((ms.Length - ms.Position) >= 3) + { + ExtendedEventLinkageInfo child = new ExtendedEventLinkageInfo(); + child.TargetEventId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + child.TargetListed = (readUInt8 & 0x80) != 0; + child.EventSimulcast = (readUInt8 & 0x40) != 0; + child.LinkType = (readUInt8 & 0x30) >> 4; + child.TargetIdType = (TargetIdTypeEncoding)((readUInt8 & 0x0c) >> 2); + bool originalNetworkIdFlag = (readUInt8 & 0x02) != 0; + bool serviceIdFlag = (readUInt8 & 0x01) != 0; + if (child.TargetIdType == TargetIdTypeEncoding.UserDefinedId) + { + child.UserDefinedId = ms.ReadUInt16BE(); + } + else + { + if (child.TargetIdType == TargetIdTypeEncoding.TargetTransportStreamId) + { + child.TargetTransportStreamId = ms.ReadUInt16BE(); + } + + if (originalNetworkIdFlag) + { + child.TargetOriginalNetworkId = ms.ReadUInt16BE(); + } + + if (serviceIdFlag) + { + child.TargetServiceId = ms.ReadUInt16BE(); + } + } + + yield return child; + } + } + + public ExtendedEventLinkageInfo[] ExtendedEventLinkages { get; private set; } + + public bool? TargetEventSimulcasted { get; private set; } + + public bool? TargetEventListed { get; private set; } + + public ushort? TargetEventId { get; private set; } + + public ushort? HandoverInitialServiceId { get; private set; } + + + public ushort? HandoverNetworkId { get; set; } + public bool? HandoverOriginType { get; private set; } + public HandoverTypeCoding? HandoverType { get; private set; } + + public LinkageTypeEnum LinkageType { get; private set; } + + public ushort ServiceId { get; private set; } + + public ushort OriginalNetworkId { get; private set; } + + public ushort TransportStreamId { get; private set; } + + + public enum LinkageTypeEnum : byte + { + Reserved = 0, + InformationService = 1, + EpgService = 2, + CaReplacementService = 3, + TsContainingCompleteNetworkSi = 4, + ServiceReplacementService = 5, + DataBroadcastService = 6, + RcsMap = 7, + MobileHandover = 8, + SystemSoftwareUpdate = 9, /* ETSI TS 102 006 */ + SystemSoftwareUpdateNit = 0x0a, /* ETSI TS 102 006 */ + IpMacNotificationService = 0x0b, /* ETSI EN 301 192 */ + TsContainingIntBatOrNit = 0x0c, /* ETSI EN 301 192 */ + EventLinkage = 0x0d, + DownloadableFont = 0x20 /* ETSI EN 303 560 */ + } + + public enum HandoverTypeCoding + { + Reserved = 0, + NeighbouringCountry = 1, + LocalVariationOfSameService = 2, + AssociatedService = 3 + } + + protected bool Equals(LinkageDescriptor other) + { + return LinkageType == other.LinkageType && ServiceId == other.ServiceId && OriginalNetworkId == other.OriginalNetworkId && TransportStreamId == other.TransportStreamId; + } + + 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((LinkageDescriptor)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine((int)LinkageType, ServiceId, OriginalNetworkId, TransportStreamId); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x4B_NvodReferenceDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x4B_NvodReferenceDescriptor.cs new file mode 100644 index 0000000..bf4fea8 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x4B_NvodReferenceDescriptor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4b,"SDT")] + public class NvodReferenceDescriptor : TsDescriptor + { + public NvodReferenceDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + NvodReferences = new NvodReference[buffer.Length / 6]; + for (int i = 0; i < NvodReferences.Length; i++) + { + NvodReferences[i] = new NvodReference(); + NvodReferences[i].TransportStreamId = ms.ReadUInt16BE(); + NvodReferences[i].OriginalNetworkId = ms.ReadUInt16BE(); + NvodReferences[i].ServiceId = ms.ReadUInt16BE(); + } + } + + public class NvodReference + { + public ushort TransportStreamId { get; set; } + public ushort OriginalNetworkId { get; set; } + public ushort ServiceId { get; set; } + } + + public NvodReference[] NvodReferences { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x4C_TimeShiftedServiceDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x4C_TimeShiftedServiceDescriptor.cs new file mode 100644 index 0000000..8dfb585 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x4C_TimeShiftedServiceDescriptor.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4c,"SDT")] + [BannedTable("TSDT","EIT")] + class TimeShiftedServiceDescriptor : TsDescriptor + { + public TimeShiftedServiceDescriptor(byte[] buffer) + { + (buffer[0], buffer[1]) = (buffer[1], buffer[0]); + ReferenceServiceId = BitConverter.ToUInt16(buffer, 0); + } + + public ushort ReferenceServiceId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x4D_ShortEventDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x4D_ShortEventDescriptor.cs new file mode 100644 index 0000000..1349d32 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x4D_ShortEventDescriptor.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4d,"EIT","RCT")] + [BannedTable("PMT")] + class ShortEventDescriptor : TsDescriptor + { + public ShortEventDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Iso639LanguageCode = Encoding.UTF8.GetString(ms.ReadBytes(3)); + + int eventNameLength = ms.ReadUInt8(); + EventName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(eventNameLength)); + + int textLength = ms.ReadUInt8(); + if (textLength > 0) + { + if (ms.GetAvailableBytes() >= textLength) + { + Text = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(textLength)); + Valid = true; + } + else + { + Valid = false; + } + } + } + + public string Text { get; private set; } + + public string EventName { get; private set; } + + public string Iso639LanguageCode { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x4E_ExtendedEventDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x4E_ExtendedEventDescriptor.cs new file mode 100644 index 0000000..52fde66 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x4E_ExtendedEventDescriptor.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4e,"EIT")] + class ExtendedEventDescriptor : TsDescriptor + { + private int _lastDescriptorNumber; + + public ExtendedEventDescriptor(byte[] buffer) + { + if (buffer.Length == 0) + return; + + MemoryStream ms = new MemoryStream(buffer, false); + + byte readUInt8 = ms.ReadUInt8(); + DescriptorNumber = (readUInt8 & 0xf0) >> 4; + LastDescriptorNumber = (readUInt8 & 0x0f); + + Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + + byte lengthOfItems = ms.ReadUInt8(); + if (lengthOfItems > ms.GetAvailableBytes()) + return; + byte[] itemsBuffer = ms.ReadBytes(lengthOfItems); + if (!ParseItems(itemsBuffer)) + { + Valid = false; + return; + } + + byte textLength = ms.ReadUInt8(); + Text = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(textLength)); + this.Valid = !string.IsNullOrEmpty(Text); + } + + public string Text { get; private set; } + + private bool ParseItems(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.Length - ms.Position > 0) + { + byte itemDescriptionLength = ms.ReadUInt8(); + if (itemDescriptionLength > ms.GetAvailableBytes()) + { + return false; + } + string itemDescription = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(itemDescriptionLength)); + byte itemLength = ms.ReadUInt8(); + string item = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(itemLength)); + + if (Items == null) + Items = new List>(); + Items.Add(new KeyValuePair(itemDescription, item)); + } + return true; + } + + public string Iso639LanguageCode { get; private set; } + + public int LastDescriptorNumber + { + get + { + return _lastDescriptorNumber; + } + private set => _lastDescriptorNumber = value; + } + + public int DescriptorNumber { get; private set; } + + public List> Items { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x4F_TimeShiftedEventDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x4F_TimeShiftedEventDescriptor.cs new file mode 100644 index 0000000..1903968 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x4F_TimeShiftedEventDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4f,"EIT")] + class TimeShiftedEventDescriptor : TsDescriptor + { + public TimeShiftedEventDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ReferenceServiceId = ms.ReadUInt16BE(); + ReferenceEventId = ms.ReadUInt16BE(); + } + + public ushort ReferenceEventId { get; private set; } + + public ushort ReferenceServiceId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x50_ComponentDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x50_ComponentDescriptor.cs new file mode 100644 index 0000000..1e030dc --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x50_ComponentDescriptor.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x50,"EIT","SDT")] + [BannedTable("PMT","TSDT","BAT")] + public class ComponentDescriptor : TsDescriptor + { + public ComponentDescriptor(byte[] buffer) + { + if (buffer == null) + { + this.Valid = false; + return; + } + if (buffer.Length < 6) + { + this.Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + + StreamContentExt = readUInt8 & 0xf0 >> 4; + StreamContent = readUInt8 & 0x0f; + ComponentType = ms.ReadUInt8(); + ComponentTag = ms.ReadUInt8(); + Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + Text = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(ms.GetAvailableBytes())); + this.Valid = true; + } + + public string Text { get; private set; } + + public string Iso639LanguageCode { get; private set; } + + public byte ComponentTag { get; private set; } + + public byte ComponentType { get; private set; } + + public int StreamContent { get; private set; } + + public int StreamContentExt { get; private set; } + + protected bool Equals(ComponentDescriptor other) + { + return Iso639LanguageCode == other.Iso639LanguageCode && ComponentTag == other.ComponentTag && ComponentType == other.ComponentType && StreamContent == other.StreamContent && StreamContentExt == other.StreamContentExt; + } + + 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((ComponentDescriptor)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Iso639LanguageCode, ComponentTag, ComponentType, StreamContent, StreamContentExt); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x51_MosaicDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x51_MosaicDescriptor.cs new file mode 100644 index 0000000..9f031d4 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x51_MosaicDescriptor.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x51,"SDT","PMT","SIT")] + [BannedTable("TSDT")] + class MosaicDescriptor : TsDescriptor + { + public MosaicDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + MosaicEntryPoint = (readUInt8 & 0x80) != 0; + NumberOfHorizontalElementaryCells = (readUInt8 & 0x70) >> 4; + NumberOfHorizontalElementaryCells++; + NumberOfVerticalElementaryCells = (readUInt8 & 0x07); + NumberOfVerticalElementaryCells++; + + int totalCells = NumberOfHorizontalElementaryCells * NumberOfVerticalElementaryCells; + Cells = new MosaicCell[totalCells]; + for (int i = 0; i < totalCells; i++) + { + MosaicCell child = new MosaicCell(); + Cells[i] = child; + + if (ms.GetAvailableBytes() < 2) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + child.LogicalCellId = (readUInt16Be & 0xfc00) >> 10; + if (child.LogicalCellId > 64) + return; + child.LogicalCellPresentationInfo = (readUInt16Be & 0x0007); + byte elementaryCellFieldLength = ms.ReadUInt8(); + + child.ElementaryCellIds = ms.ReadBytes(elementaryCellFieldLength); + for (int j = 0; j < child.ElementaryCellIds.Length; j++) + { + child.ElementaryCellIds[j] &= 0x3f; + } + + child.CellLinkageInfo = ms.ReadUInt8(); + if (child.CellLinkageInfo == 0x01) + { + child.BouquetId = ms.ReadUInt16BE(); + } + else if (child.CellLinkageInfo == 0x02) + { + child.OriginalNetworkId = ms.ReadUInt16BE(); + child.TransportStreamId = ms.ReadUInt16BE(); + child.ServiceId = ms.ReadUInt16BE(); + } + else if (child.CellLinkageInfo == 0x03) + { + child.OriginalNetworkId = ms.ReadUInt16BE(); + child.TransportStreamId = ms.ReadUInt16BE(); + child.ServiceId = ms.ReadUInt16BE(); + } + else if (child.CellLinkageInfo == 0x04) + { + child.OriginalNetworkId = ms.ReadUInt16BE(); + child.TransportStreamId = ms.ReadUInt16BE(); + child.ServiceId = ms.ReadUInt16BE(); + child.EventId = ms.ReadUInt16BE(); + } + } + + this.Valid = true; + } + + public MosaicCell[] Cells { get; private set; } + public class MosaicCell + { + public int LogicalCellId { get; set; } + public int LogicalCellPresentationInfo { get; set; } + public byte[] ElementaryCellIds { get; set; } + public byte CellLinkageInfo { get; set; } + public ushort? BouquetId { get; set; } + public ushort? OriginalNetworkId { get; set; } + public ushort? TransportStreamId { get; set; } + public ushort? ServiceId { get; set; } + public ushort? EventId { get; set; } + } + + public int NumberOfVerticalElementaryCells { get; private set; } + + public int NumberOfHorizontalElementaryCells { get; private set; } + + public bool MosaicEntryPoint { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x52_StreamIdentifierDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x52_StreamIdentifierDescriptor.cs new file mode 100644 index 0000000..fa63f43 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x52_StreamIdentifierDescriptor.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x52,"PMT")] + [BannedTable("TSDT","EIT")] + class StreamIdentifierDescriptor : TsDescriptor + { + public StreamIdentifierDescriptor(byte[] buffer) + { + if (buffer.Length != 1) + { + Valid = false; + return; + } + + ComponentTag = buffer[0]; + Valid = true; + } + + public StreamIdentifierDescriptor(byte componentTag) + { + ComponentTag = componentTag; + } + + public byte ComponentTag { get; private set; } + + public override byte[] Serialize() + { + return new byte[] { ComponentTag }; + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x53_CaIdentifierDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x53_CaIdentifierDescriptor.cs new file mode 100644 index 0000000..746f1b5 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x53_CaIdentifierDescriptor.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x53,"BAT","SDT","EIT")] + [BannedTable("TSDT")] + class CaIdentifierDescriptor : TsDescriptor + { + public CaIdentifierDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CaSystemIds = new ushort[buffer.Length / 2]; + for (int i = 0; i < CaSystemIds.Length; i++) + { + CaSystemIds[i] = ms.ReadUInt16BE(); + } + } + + public ushort[] CaSystemIds; + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x54_ContentDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x54_ContentDescriptor.cs new file mode 100644 index 0000000..51586ad --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x54_ContentDescriptor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x54,"EIT")] + [BannedTable("SDT","PMT","BAT","TSDT")] + class ContentDescriptor : TsDescriptor + { + public ContentDescriptor(byte[] buffer) + { + if (buffer.Length % 2 != 0) + return; + + Content = new Tuple[buffer.Length / 2]; + for (int i = 0; i < buffer.Length; i += 2) + { + Content[i / 2] = new Tuple((buffer[i] & 0xf0) >> 4, buffer[i] & 0x0f, buffer[i + 1]); + } + + this.Valid = true; + } + + public Tuple[] Content { get; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x55_ParentalRatingDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x55_ParentalRatingDescriptor.cs new file mode 100644 index 0000000..02a870f --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x55_ParentalRatingDescriptor.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x55,"EIT")] + [BannedTable("PMT")] + class ParentalRatingDescriptor : TsDescriptor + { + public ParentalRatingDescriptor(byte[] buffer) + { + if (buffer.Length % 4 != 0) + return; + + ParentalRatings = new Tuple[buffer.Length / 4]; + for (int i = 0; i < buffer.Length; i += 4) + { + string countryCode = Encoding.ASCII.GetString(buffer, i, 3); + byte age = buffer[i + 3]; + if (age >= 0x00 && age <= 0x0f) + { + ParentalRatings[i / 4] = new Tuple(countryCode, age + 3); + } + } + + base.Valid = true; + } + + + public Tuple[] ParentalRatings; + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x56_TeletextDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x56_TeletextDescriptor.cs new file mode 100644 index 0000000..63be0bc --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x56_TeletextDescriptor.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x56,"PMT")] + [BannedTable("TSDT","EIT")] + public class TeletextDescriptor : TsDescriptor + { + public TeletextDescriptor(byte[] buffer) + { + if (buffer.Length % 5 != 0) + throw new DvbDescriptorBrokenException("Teletext descriptor length not divisible by 5!"); + + Teletexts = new TeletextDescriptorTeletext[buffer.Length / 5]; + for (int i = 0; i < Teletexts.Length; i++) + { + Teletexts[i] = new TeletextDescriptorTeletext(); + Teletexts[i].Iso639LanguageCode = Encoding.ASCII.GetString(buffer, i * 5, 3); + Teletexts[i].TeletextType = (TeletextType)(buffer[(i * 5) + 1] & 0xf8 >> 3); + Teletexts[i].TeletextMagazineNumber = (buffer[(i * 5) + 1] & 0x07); + Teletexts[i].TeletextPageNumber = buffer[(i * 5) + 2]; + } + } + + public class TeletextDescriptorTeletext + { + public string Iso639LanguageCode { get; internal set; } + public TeletextType TeletextType { get; internal set; } + public int TeletextMagazineNumber { get; internal set; } + public byte TeletextPageNumber { get; internal set; } + } + + public enum TeletextType + { + Reserved = 0, + InitialTeletextPage = 1, + TeletextSubtitlePage = 2, + AdditionalInformationPage = 3, + ProgrammeSchedulePage = 4, + TeletextSubtitlePageForHearingImpairedPeople = 5 + } + + public TeletextDescriptorTeletext[] Teletexts { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x57_TelephoneDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x57_TelephoneDescriptor.cs new file mode 100644 index 0000000..3296a0a --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x57_TelephoneDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x57,"SDT","EIT")] + [BannedTable("TSDT")] + class TelephoneDescriptor : TsDescriptor + { + public TelephoneDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x58_LocalTimeOffsetDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x58_LocalTimeOffsetDescriptor.cs new file mode 100644 index 0000000..496497e --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x58_LocalTimeOffsetDescriptor.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x58,"TOT")] + public class LocalTimeOffsetDescriptor : TsDescriptor + { + public LocalTimeOffsetDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + LocalTimeOffsets = new LocalTime[buffer.Length / 13]; + for (int i = 0; i < LocalTimeOffsets.Length; i++) + { + LocalTimeOffsets[i] = new LocalTime(); + LocalTimeOffsets[i].CountryCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + + byte readUInt8 = ms.ReadUInt8(); + LocalTimeOffsets[i].CountryRegionId = (readUInt8 & 0xfc) >> 2; + LocalTimeOffsets[i].LocalTimeOffsetPolarity = (readUInt8 & 0x01) != 0; + LocalTimeOffsets[i].LocalTimeOffset = ReadTimeOffset(ms); + + DateTime? tmp = ms.ReadEtsiEn300468AnnexCDateTime(); + if (tmp == null) + { + Valid = false; + return; + } + LocalTimeOffsets[i].TimeOfChange = tmp.Value; + + LocalTimeOffsets[i].NextTimeOffset = ReadTimeOffset(ms); + } + Valid = true; + } + + public LocalTimeOffsetDescriptor(LocalTime[] offsets) + { + this.LocalTimeOffsets = offsets; + } + + private TimeSpan ReadTimeOffset(Stream s) + { + long a = s.ReadUInt8().UnpackBcd(); + long b = s.ReadUInt8().UnpackBcd(); + return new TimeSpan((int)a, (int)b, 0); + } + + public LocalTime[] LocalTimeOffsets { get; } + + public class LocalTime + { + public string CountryCode { get; internal set; } + public int CountryRegionId { get; internal set; } + public bool LocalTimeOffsetPolarity { get; internal set; } + public TimeSpan LocalTimeOffset { get; internal set; } + public DateTime TimeOfChange { get; internal set; } + public TimeSpan NextTimeOffset { get; internal set; } + } + + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x59_SubtitlingDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x59_SubtitlingDescriptor.cs new file mode 100644 index 0000000..1fe46db --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x59_SubtitlingDescriptor.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x59,"PMT")] + [BannedTable("TSDT")] + public class SubtitlingDescriptor : TsDescriptor + { + public SubtitlingDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + int num = buffer.Length / 8; + Subtitlings = new Subtitling[num]; + for (int i = 0; i < num; i++) + { + Subtitlings[i] = new Subtitling(); + Subtitlings[i].Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + Subtitlings[i].SubtitlingType = ms.ReadUInt8(); + Subtitlings[i].CompositionPageId = ms.ReadUInt16BE(); + Subtitlings[i].AncillaryPageId = ms.ReadUInt16BE(); + } + } + + public Subtitling[] Subtitlings { get; private set; } + public class Subtitling + { + public string Iso639LanguageCode { get; internal set; } + public byte SubtitlingType { get; internal set; } + + public bool IsEtsiEn300743 + { + get + { + return (SubtitlingType >= 0x10 && SubtitlingType <= 0x15) || + (SubtitlingType >= 0x20 && SubtitlingType <= 0x25); + } + } + + public ushort CompositionPageId { get; internal set; } + public ushort AncillaryPageId { get; internal set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x5A_TerristialDeliverySystemDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x5A_TerristialDeliverySystemDescriptor.cs new file mode 100644 index 0000000..cbdfe26 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x5A_TerristialDeliverySystemDescriptor.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x5a,"NIT")] + public class TerristialDeliverySystemDescriptor : TsDescriptor + { + public TerristialDeliverySystemDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CentreFrequency = ms.ReadUInt32BE(); + + byte readUInt8 = ms.ReadUInt8(); + Bandwidth = 8 - ((readUInt8 & 0xe0) >> 5); + Priority = (readUInt8 & 0x10) != 0; + TimeSlicingIndicator = (readUInt8 & 0x80) != 0; + MpeFecIndicator = (readUInt8 & 0x40) != 0; + + readUInt8 = ms.ReadUInt8(); + Constellation = ((readUInt8 & 0xc0) >> 6); + HierarchyInformation = (HierarchySignallingFormat)((readUInt8 & 0x38) >> 3); + CodeRateHpStream = (CodeRate)(readUInt8 & 0x07); + + readUInt8 = ms.ReadUInt8(); + CodeRateLpStream = (CodeRate)((readUInt8 & 0xe0) >> 5); + GuardInterval = ((readUInt8 & 0x18) >> 3); + TransmissionMode = (readUInt8 & 0x06) >> 1; + OtherFrequencyFlag = (readUInt8 & 0x01) != 0; + } + + public int Constellation { get; set; } + + public bool OtherFrequencyFlag { get; private set; } + + public int TransmissionMode { get; private set; } + + public int GuardInterval { get; private set; } + + public CodeRate CodeRateLpStream { get; private set; } + + public CodeRate CodeRateHpStream { get; private set; } + + public HierarchySignallingFormat HierarchyInformation { get; private set; } + + public bool MpeFecIndicator { get; private set; } + + public bool TimeSlicingIndicator { get; private set; } + + public bool Priority { get; private set; } + + public int Bandwidth { get; private set; } + + public uint CentreFrequency { get; private set; } + + public enum HierarchySignallingFormat : byte + { + NonHierarchialNative = 0, + Alpha1Native = 1, + Alpha2Native = 2, + Alpha4Native = 3, + NonHierarchialInDepth = 4, + Alpha1InDepth = 5, + Alpha2InDepth = 6, + Alpha4InDepth = 7 + } + + public enum CodeRate : byte + { + Half = 0, + TwoThird = 1, + ThreeFourth = 2, + FiveSixth = 3, + SevenEigth = 4 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x5B_MultilingualNetworkNameDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x5B_MultilingualNetworkNameDescriptor.cs new file mode 100644 index 0000000..20adffb --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x5B_MultilingualNetworkNameDescriptor.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x5B,"NIT")] + class MultilingualNetworkNameDescriptor : TsDescriptor + { + public MultilingualNetworkNameDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + List> result = new List>(); + while (ms.GetAvailableBytes() > 4) + { + string k = Encoding.ASCII.GetString(ms.ReadBytes(3)); + byte networkNameLength = ms.ReadUInt8(); + string v = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(networkNameLength)); + result.Add(new KeyValuePair(k, v)); + } + + MultilingualNetworkName = result.AsReadOnly(); + } + + public ReadOnlyCollection> MultilingualNetworkName { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x5C_MultilingualBouquetNameDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x5C_MultilingualBouquetNameDescriptor.cs new file mode 100644 index 0000000..229ce43 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x5C_MultilingualBouquetNameDescriptor.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x5C,"BAT")] + [BannedTable("EIT")] + public class MultilingualBouquetNameDescriptor : TsDescriptor + { + public MultilingualBouquetNameDescriptor(byte[] buffer) + { + if (buffer == null) + { + Valid = false; + MultilingualBouquetName = new Dictionary(); + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + Dictionary result = new Dictionary(); + while (ms.GetAvailableBytes() > 4) + { + string k = Encoding.ASCII.GetString(ms.ReadBytes(3)); + byte networkNameLength = ms.ReadUInt8(); + string v = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(networkNameLength)); + result[k] = v; + } + + MultilingualBouquetName = result; + } + + public Dictionary MultilingualBouquetName { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x5D_MultilingualServiceNameDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x5D_MultilingualServiceNameDescriptor.cs new file mode 100644 index 0000000..df18b64 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x5D_MultilingualServiceNameDescriptor.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x5D,"SDT")] + [BannedTable("PMT")] + public class MultilingualServiceNameDescriptor : TsDescriptor + { + public MultilingualServiceNameDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + List result = new List(); + while (ms.GetAvailableBytes() > 4) + { + MultilingualServiceName child = new MultilingualServiceName(); + child.Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + byte serviceProviderNameLength = ms.ReadUInt8(); + child.ServiceProviderName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(serviceProviderNameLength)); + byte serviceNameLength = ms.ReadUInt8(); + child.ServiceName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(serviceNameLength)); + result.Add(child); + } + + ServiceNames = result.AsReadOnly(); + } + + public ReadOnlyCollection ServiceNames { get; private set; } + + + public class MultilingualServiceName + { + public string Iso639LanguageCode { get; set; } + public string ServiceProviderName { get; set; } + public string ServiceName { get; set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x5E_MultilingualComponentDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x5E_MultilingualComponentDescriptor.cs new file mode 100644 index 0000000..39787b2 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x5E_MultilingualComponentDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x5e,"EIT")] + [BannedTable("PMT")] + class MultilingualComponentDescriptor + { + public MultilingualComponentDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x5F_PrivateDataSpecifierDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x5F_PrivateDataSpecifierDescriptor.cs new file mode 100644 index 0000000..c8b4b7f --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x5F_PrivateDataSpecifierDescriptor.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x5f,"NIT","BAT","SDT","EIT","PMT")] + [BannedTable("CAT")] // <-- Scientific Atlanta is known to put private_data_specifier_descriptors into the CAT, however this is a private extension. + public class PrivateDataSpecifierDescriptor : TsDescriptor + { + public PrivateDataSpecifierDescriptor(byte[] buffer) + { + if (buffer.Length != 4) + { + Valid = false; + return; + } + + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + PrivateDataSpecifier = BitConverter.ToUInt32(buffer, 0); + } + + public PrivateDataSpecifierDescriptor(uint value) + { + PrivateDataSpecifier = value; + } + + public uint PrivateDataSpecifier { get; private set; } + + public override byte[] Serialize() + { + byte[] bytes = BitConverter.GetBytes(PrivateDataSpecifier); + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + return bytes; + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x60_ServiceMoveDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x60_ServiceMoveDescriptor.cs new file mode 100644 index 0000000..8adc4e6 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x60_ServiceMoveDescriptor.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x60,"PMT")] + class ServiceMoveDescriptor : TsDescriptor + { + public ServiceMoveDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + NewOriginalNetworkId = ms.ReadUInt16BE(); + NewTransportStreamId = ms.ReadUInt16BE(); + NewServiceId = ms.ReadUInt16BE(); + } + + public ushort NewServiceId { get; private set; } + + public ushort NewTransportStreamId { get; private set; } + + public ushort NewOriginalNetworkId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x62_FrequencyListDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x62_FrequencyListDescriptor.cs new file mode 100644 index 0000000..6b78725 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x62_FrequencyListDescriptor.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x62,"NIT")] + public class FrequencyListDescriptor : TsDescriptor + { + public FrequencyListDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + CodingType = (CodingTypeValue)(readUInt8 & 0x03); + + long maxI = ms.GetAvailableBytes() / 4; + CentreFrequencies = new uint[maxI]; + for (int i = 0; i < maxI; i++) + { + CentreFrequencies[i] = ms.ReadUInt32BE(); + } + } + + public uint[] CentreFrequencies { get; private set; } + + public CodingTypeValue CodingType { get; private set; } + + public enum CodingTypeValue + { + NotDefined = 0, + Satellite = 1, + Cable = 2, + Terristial = 3 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x63_PartialTransportStreamDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x63_PartialTransportStreamDescriptor.cs new file mode 100644 index 0000000..2452f36 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x63_PartialTransportStreamDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x63,"SIT")] + [BannedTable("PMT")] + class PartialTransportStreamDescriptor : TsDescriptor + { + public PartialTransportStreamDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x64_DataBroadcastDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x64_DataBroadcastDescriptor.cs new file mode 100644 index 0000000..28b6caf --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x64_DataBroadcastDescriptor.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x64,"SDT","EIT")] + class DataBroadcastDescriptor : TsDescriptor + { + public DataBroadcastDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + DataBroadcastId = ms.ReadUInt16BE(); + ComponentTag = ms.ReadUInt8(); + byte selectorLength = ms.ReadUInt8(); + Selector = ms.ReadBytes(selectorLength); + Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + byte textLength = ms.ReadUInt8(); + Text = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(textLength)); + } + + public DataBroadcastDescriptor(ushort dataBroadcastId, byte componentTag, byte[] selector, string languageCode, string text) + { + DataBroadcastId = dataBroadcastId; + ComponentTag = componentTag; + Selector = selector; + Iso639LanguageCode = languageCode; + Text = text; + } + + + public string Text { get; private set; } + + public string Iso639LanguageCode { get; private set; } + + public byte[] Selector { get; private set; } + + public byte ComponentTag { get; private set; } + + public ushort DataBroadcastId { get; private set; } + + public override byte[] Serialize() + { + MemoryStream ms = new MemoryStream(); + ms.WriteUInt16BE(DataBroadcastId); + ms.WriteUInt8(ComponentTag); + + byte selectorLength = 0; + if (Selector != null) + { + if (Selector.Length > 255) + throw new Exception("Selector too long"); + selectorLength = (byte)Selector.Length; + } + + ms.WriteUInt8(selectorLength); + if (Selector != null) + ms.Write(Selector, 0, Selector.Length); + ms.Write(Encoding.ASCII.GetBytes(Iso639LanguageCode), 0, 3); + + byte[] textBuffer = En300468AnnexATextDecoder.GetInstance().Encode(Text); + if (textBuffer.Length > 255) + throw new Exception("Text too long!"); + + ms.WriteUInt8((byte)textBuffer.Length); + ms.Write(textBuffer, 0, textBuffer.Length); + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x65_ScramblingDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x65_ScramblingDescriptor.cs new file mode 100644 index 0000000..d8352e3 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x65_ScramblingDescriptor.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x65,"PMT")] + [BannedTable("TSDT","EIT")] + class ScramblingDescriptor : TsDescriptor + { + public ScramblingDescriptor(byte[] buffer) + { + if (buffer.Length == 0) + { + Valid = false; + return; + } + ScramblingMode = buffer[0]; + Valid = true; + } + + public byte ScramblingMode { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x66_DataBroadcastIdDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x66_DataBroadcastIdDescriptor.cs new file mode 100644 index 0000000..b8b927d --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x66_DataBroadcastIdDescriptor.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x66,"PMT")] + [BannedTable("BAT")] + public class DataBroadcastIdDescriptor : TsDescriptor + { + public DataBroadcastIdDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + DataBroadcastId = ms.ReadUInt16BE(); + if (DataBroadcastId == 0x000b) + { + if (ms.GetAvailableBytes() == 0) + return; + byte platformIdDataLength = ms.ReadUInt8(); + ParseIpMacNotification(ms.ReadBytes(platformIdDataLength)); + } + DataBroadcastSelector = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public DataBroadcastIdDescriptor(ushort id) + { + DataBroadcastId = id; + } + + private void ParseIpMacNotification(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + IpMacNotificationInfo = new IpMacPlatform[buffer.Length / 5]; + for (int i = 0; i < IpMacNotificationInfo.Length; i++) + { + IpMacPlatform child = new IpMacPlatform(); + child.PlatformId = ms.ReadUInt24BE(); + child.ActionType = ms.ReadUInt8(); + byte readUInt8 = ms.ReadUInt8(); + child.IntVersioningFlag = (readUInt8 & 0x20) != 0; + child.IntVersion = (readUInt8 & 0x1f); + IpMacNotificationInfo[i] = child; + } + } + + public class IpMacPlatform + { + public uint PlatformId { get; set; } + public byte ActionType { get; set; } + public bool IntVersioningFlag { get; set; } + public int IntVersion { get; set; } + } + + public IpMacPlatform[] IpMacNotificationInfo { get; private set; } + + public byte[] DataBroadcastSelector { get; private set; } + + public ushort DataBroadcastId { get; private set; } + + public override byte[] Serialize() + { + byte length = 2; + if (DataBroadcastSelector != null) + if (DataBroadcastSelector.Length > 253) + throw new Exception("DataBroadcastSelector too long!"); + + if (DataBroadcastSelector != null) + length += (byte)(DataBroadcastSelector.Length); + + byte[] bytes = BitConverter.GetBytes(DataBroadcastId); + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + byte[] result = new byte[length]; + result[0] = bytes[0]; + result[1] = bytes[1]; + if (DataBroadcastSelector != null) + Array.Copy(DataBroadcastSelector, 0, result, 2, DataBroadcastSelector.Length); + + return result; + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x67_TransportStreamDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x67_TransportStreamDescriptor.cs new file mode 100644 index 0000000..aab0d73 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x67_TransportStreamDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x67,"TSDT")] + [BannedTable("NIT","BAT","SDT","EIT","TOT","PMT")] + class TransportStreamDescriptor : TsDescriptor + { + public TransportStreamDescriptor(byte[] buffer) + { + Compliance = Encoding.ASCII.GetString(buffer); + } + + public string Compliance { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x68_DsngDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x68_DsngDescriptor.cs new file mode 100644 index 0000000..0cf8bc0 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x68_DsngDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x68,"TSDT")] + [BannedTable("EIT")] + class DsngDescriptor : TsDescriptor + { + public DsngDescriptor(byte[] buffer) + { + StationIdentification = Encoding.ASCII.GetString(buffer); + } + + public string StationIdentification { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x69_PdcDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x69_PdcDescriptor.cs new file mode 100644 index 0000000..1642398 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x69_PdcDescriptor.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x69,"EIT")] + [BannedTable("PMT","TSDT")] + class PdcDescriptor : TsDescriptor + { + public PdcDescriptor(byte[] buffer) + { + int tmp = buffer[0]; + tmp <<= 8; + tmp += buffer[1]; + tmp <<= 8; + tmp += buffer[2]; + + Minute = (tmp & 0x0000003f); + Hour = (tmp & 0x000007c0) >> 6; + Month = (tmp & 0x00007800) >> 11; + Day = (tmp & 0x000f8000) >> 15; + this.Valid = !((Month > 12) || (Hour > 23) || (Minute > 59) || (Day > 30)); + } + + public int Day { get; private set; } + + public int Month { get; private set; } + + public int Hour { get; private set; } + + public int Minute { get; private set; } + + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x6A_Ac3Descriptor.cs b/skyscraper8/Dvb/Descriptors/0x6A_Ac3Descriptor.cs new file mode 100644 index 0000000..1939ae1 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x6A_Ac3Descriptor.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x6a,"PMT")] + public class Ac3Descriptor : TsDescriptor + { + public Ac3Descriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + byte readUInt8 = ms.ReadUInt8(); + + bool componentTypeFlag = (readUInt8 & 0x80) != 0; + bool bsidFlag = (readUInt8 & 0x40) != 0; + bool mainIdFlag = (readUInt8 & 0x20) != 0; + bool asvcFlag = (readUInt8 & 0x10) != 0; + + if (componentTypeFlag) + { + ComponentType = (ComponentTypeEnum)ms.ReadUInt8(); + } + + if (bsidFlag) + { + BSID = ms.ReadUInt8() & 0x1f; + } + + if (mainIdFlag) + { + MainId = ms.ReadUInt8(); + } + + if (asvcFlag) + { + Asvc = ms.ReadUInt8(); + } + } + + public byte? Asvc { get; private set; } + + public byte? MainId { get; private set; } + + public int? BSID { get; private set; } + + public ComponentTypeEnum? ComponentType { get; private set; } + + public enum ComponentTypeEnum : byte + { + Ac3 = 0, + EAc3 = 1 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x6B_AncillaryDataDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x6B_AncillaryDataDescriptor.cs new file mode 100644 index 0000000..a680d4e --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x6B_AncillaryDataDescriptor.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x6b,"PMT")] + public class AncillaryDataDescriptor : TsDescriptor + { + public AncillaryDataDescriptor(byte[] buffer) + { + if (buffer.Length != 1) + { + return; + } + + AncillaryDataIdentifier = buffer[0]; + this.Valid = true; + } + + public byte AncillaryDataIdentifier { get; private set; } + + //ETSI TS 101 154 seems like a fun read. + public bool RdsOnly => AncillaryDataIdentifier == 0x40; + public bool RDS => (AncillaryDataIdentifier & 0x40) != 0; + public bool Mpeg4AncillaryData => (AncillaryDataIdentifier & 0x20) != 0; + public bool ScaleFactorErrorCheck => (AncillaryDataIdentifier & 0x10) != 0; + public bool DabAncillaryData => (AncillaryDataIdentifier & 0x08) != 0; + public bool AnnouncementSwitchingData => (AncillaryDataIdentifier & 0x04) != 0; + public bool ExtendedAncillaryData => (AncillaryDataIdentifier & 0x02) != 0; + public bool DvdVideoAncillaryData => (AncillaryDataIdentifier & 0x01) != 0; + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x6C_CellListDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x6C_CellListDescriptor.cs new file mode 100644 index 0000000..53bb946 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x6C_CellListDescriptor.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x6c,"NIT")] + [BannedTable("PMT","TSDT")] + public class CellListDescriptor : TsDescriptor + { + public CellListDescriptor(byte[] buffer) + { + List resultCells = new List(); + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 10) + { + Cell child = new Cell(); + child.CellId = ms.ReadUInt16BE(); + child.CellLatitude = ms.ReadUInt16BE(); + child.CellLongitude = ms.ReadUInt16BE(); + + uint readUInt32Be = ms.ReadUInt32BE(); + child.ExtentOfLatitude = (readUInt32Be & 0xfff00000) >> 20; + child.ExtentOfLongitude = (readUInt32Be & 0x000fff00) >> 8; + + uint subcellInfoLoopLength = (readUInt32Be & 0x000000ff); + subcellInfoLoopLength /= 8; + child.Subcells = new Subcell[subcellInfoLoopLength]; + for (int i = 0; i < subcellInfoLoopLength; i++) + { + Subcell subchild = new Subcell(); + child.Subcells[i] = subchild; + + subchild.CellIdExtension = ms.ReadUInt8(); + subchild.SubcellLatitude = ms.ReadUInt16BE(); + subchild.SubcellLongitude = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + subchild.SubcellExtentOfLatitude = readUInt8; + subchild.SubcellExtentOfLatitude <<= 4; + + readUInt8 = ms.ReadUInt8(); + subchild.SubcellExtentOfLatitude += ((readUInt8 & 0xf0) >> 4); + subchild.SubcellExtentOfLongitude = (readUInt8 & 0x0f); + subchild.SubcellExtentOfLongitude <<= 4; + + readUInt8 = ms.ReadUInt8(); + subchild.SubcellExtentOfLongitude += readUInt8; + } + + resultCells.Add(child); + } + + Cells = resultCells.AsReadOnly(); + } + + public ReadOnlyCollection Cells { get; private set; } + + public class Cell + { + public ushort CellId { get; set; } + public ushort CellLatitude { get; set; } + public ushort CellLongitude { get; set; } + public uint ExtentOfLatitude { get; set; } + public uint ExtentOfLongitude { get; set; } + + public Subcell[] Subcells { get; set; } + } + + public class Subcell + { + public byte CellIdExtension { get; set; } + public ushort SubcellLatitude { get; set; } + public ushort SubcellLongitude { get; set; } + public int SubcellExtentOfLatitude { get; set; } + public int SubcellExtentOfLongitude { get; set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x6D_CellFrequencyLinkDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x6D_CellFrequencyLinkDescriptor.cs new file mode 100644 index 0000000..88e657e --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x6D_CellFrequencyLinkDescriptor.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x6d,"NIT")] + [BannedTable("TSDT")] + public class CellFrequencyLinkDescriptor : TsDescriptor + { + public CellFrequencyLinkDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 7) + { + Cell child = new Cell(); + child.CellId = ms.ReadUInt16BE(); + child.Frequency = ms.ReadUInt32BE(); + byte subcellInfoLoopLength = ms.ReadUInt8(); + if (subcellInfoLoopLength > 0) + { + throw new NotImplementedException("subcellInfoLoop"); + } + + if (Cells == null) + { + Cells = new List(); + } + + Cells.Add(child); + } + } + + public List Cells { get; private set; } + + public class Cell + { + public ushort CellId { get; internal set; } + public uint Frequency { get; internal set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x6E_AnnouncementSupportDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x6E_AnnouncementSupportDescriptor.cs new file mode 100644 index 0000000..4976662 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x6E_AnnouncementSupportDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x6e,"SDT")] + [BannedTable("TSDT","PMT")] + class AnnouncementSupportDescriptor : TsDescriptor + { + public AnnouncementSupportDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x6F_ApplicationSignallingDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x6F_ApplicationSignallingDescriptor.cs new file mode 100644 index 0000000..665ca2f --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x6F_ApplicationSignallingDescriptor.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x6f,"PMT")] + [BannedTable("TSDT","NIT","EIT")] + public class ApplicationSignallingDescriptor : TsDescriptor + { + public ApplicationSignallingDescriptor(byte[] buffer) + { + if (buffer.Length % 3 != 0) + { + Valid = false; + return; + } + + Applications = new ApplicationSignal[buffer.Length / 3]; + + for (int i = 0; i < Applications.Length; i++) + { + Applications[i] = new ApplicationSignal(); + Applications[i].ApplicationType = BitConverter.ToUInt16(buffer, i * 3); + Applications[i].ApplicationType &= 0x7fff; + Applications[i].AitVersionNumber = buffer[(i * 3) + 2] & 0x1f; + } + Valid = true; + } + + public ApplicationSignal[] Applications { get; private set; } + + public class ApplicationSignal + { + public int ApplicationType { get; internal set; } + public int AitVersionNumber { get; internal set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x70_AdaptionFieldDataDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x70_AdaptionFieldDataDescriptor.cs new file mode 100644 index 0000000..9435063 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x70_AdaptionFieldDataDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x70,"PMT")] + [BannedTable("EIT")] + class AdaptionFieldDataDescriptor : TsDescriptor + { + public AdaptionFieldDataDescriptor(byte[] buffer) + { + AdaptionFieldDataIdentifier = buffer[0]; + } + + public byte AdaptionFieldDataIdentifier { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x71_ServiceIdentifierDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x71_ServiceIdentifierDescriptor.cs new file mode 100644 index 0000000..b347b72 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x71_ServiceIdentifierDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x71,"SDT")] + [BannedTable("PMT")] + class ServiceIdentifierDescriptor : TsDescriptor + { + public ServiceIdentifierDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x72_ServiceAvailabilityDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x72_ServiceAvailabilityDescriptor.cs new file mode 100644 index 0000000..3c7b8ce --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x72_ServiceAvailabilityDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x72,"SDT")] + [BannedTable("TSDT","EIT")] + class ServiceAvailabilityDescriptor : TsDescriptor + { + public ServiceAvailabilityDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x73_DefaultAuthorityDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x73_DefaultAuthorityDescriptor.cs new file mode 100644 index 0000000..3f55fb8 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x73_DefaultAuthorityDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x73,"NIT","BAT","SDT")] + [BannedTable("TSDT","EIT")] + class DefaultAuthorityDescriptor : TsDescriptor + { + public DefaultAuthorityDescriptor(byte[] buffer) + { + DefaultAuthority = Encoding.UTF8.GetString(buffer); + } + + public string DefaultAuthority { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x74_RelatedContentDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x74_RelatedContentDescriptor.cs new file mode 100644 index 0000000..9a0c900 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x74_RelatedContentDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x74,"PMT")] + [BannedTable("TSDT","EIT")] + class RelatedContentDescriptor : TsDescriptor + { + public RelatedContentDescriptor(byte[] buffer) + { + Valid = buffer.Length == 0; + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x75_TvaIdDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x75_TvaIdDescriptor.cs new file mode 100644 index 0000000..01f92ce --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x75_TvaIdDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x75,"EIT")] + [BannedTable("BAT","TSDT")] + class TvaIdDescriptor + { + public TvaIdDescriptor(byte[] buffer) + { + throw new NotImplementedException(nameof(TvaIdDescriptor)); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x76_ContentIdentifierDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x76_ContentIdentifierDescriptor.cs new file mode 100644 index 0000000..5e8f095 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x76_ContentIdentifierDescriptor.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x76,"EIT")] + [BannedTable("PMT")] + public class ContentIdentifierDescriptor : TsDescriptor + { + public ContentIdentifierDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + List result = new List(); + while (ms.GetAvailableBytes() > 0) + { + Crid child = new Crid(); + byte readUInt8 = ms.ReadUInt8(); + child.CridType = (CridType)((readUInt8 & 0xfc) >> 2); + child.CridLocation = (CridLocation)(readUInt8 & 0x03); + if ((int)child.CridLocation == 0x00) + { + byte cridLength = ms.ReadUInt8(); + child.CridBytes = ms.ReadBytes(cridLength); + } + + if ((int)child.CridLocation == 0x01) + { + child.CridRef = ms.ReadUInt16BE(); + } + + result.Add(child); + } + + Crids = result.AsReadOnly(); + } + + public ReadOnlyCollection Crids { get; private set; } + + public class Crid + { + public CridType CridType { get; set; } + public CridLocation CridLocation { get; set; } + public byte[] CridBytes { get; set; } + + public string CridBytesAsString + { + get + { + return Encoding.ASCII.GetString(CridBytes); + } + } + + public ushort? CridRef { get; set; } + } + + public enum CridType : byte + { + Undefined = 0, + SelfSingle = 1, + SelfSeries = 2, + Recommendation = 3 + } + + public enum CridLocation : byte + { + WithinDescriptor = 0, + CIT = 1 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x77_TimeSliceFecIdentifierDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x77_TimeSliceFecIdentifierDescriptor.cs new file mode 100644 index 0000000..254575d --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x77_TimeSliceFecIdentifierDescriptor.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x77,"NIT","CAT","INT")] + [BannedTable("BAT")] + internal class TimeSliceFecIdentifierDescriptor : TsDescriptor + { + public TimeSliceFecIdentifierDescriptor(byte[] buffer) + { + TimeSlicing = (buffer[0] & 0x80) != 0; + MpeFec = (buffer[0] & 0x60) >> 5; + FrameSize = (buffer[0] & 0x1f); + MaxBurstDuration = buffer[1]; + MaxAverageRate = (buffer[2] & 0xf0) >> 4; + TimeSliceFecId = (buffer[2] & 0x0f); + if (buffer.Length > 3) + { + throw new NotImplementedException("id_selector_byte"); + } + } + + public int TimeSliceFecId { get; private set; } + + public int MaxAverageRate { get; private set; } + + public byte MaxBurstDuration { get; private set; } + + public int FrameSize { get; private set; } + + public int MpeFec { get; private set; } + + public bool TimeSlicing { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x78_EcmRepetitionRateDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x78_EcmRepetitionRateDescriptor.cs new file mode 100644 index 0000000..febe9e4 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x78_EcmRepetitionRateDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x78,"PMT")] + [BannedTable("TSDT")] + class EcmRepetitionRateDescriptor : TsDescriptor + { + public EcmRepetitionRateDescriptor(byte[] buffer) + { + throw new NotImplementedException(GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x79_S2SatelliteDeliverySystemDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x79_S2SatelliteDeliverySystemDescriptor.cs new file mode 100644 index 0000000..7f17b45 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x79_S2SatelliteDeliverySystemDescriptor.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x79,"NIT")] + [BannedTable("BAT","PMT")] + public class S2SatelliteDeliverySystemDescriptor : TsDescriptor + { + public S2SatelliteDeliverySystemDescriptor(byte[] buffer) + { + if (buffer.Length == 0) + return; + + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + bool scramblingSequenceSelector = (readUInt8 & 0x80) != 0; + bool multipleInputStreamFlag = (readUInt8 & 0x40) != 0; + bool notTimesliceFlag = (readUInt8 & 0x10) != 0; + TsGsMode = (TsGsModeCoding)(readUInt8 & 0x03); + if (scramblingSequenceSelector) + { + ScramblingSequenceIndex = (readUInt8 & 0x03) << 16; + ScramblingSequenceIndex += ms.ReadUInt16BE(); + } + + if (multipleInputStreamFlag) + { + InputStreamIdentifier = ms.ReadUInt8(); + } + + if (notTimesliceFlag) + { + if (ms.GetAvailableBytes() > 0) + TimesliceNumber = ms.ReadUInt8(); + } + + this.Valid = true; + } + + public byte TimesliceNumber { get; private set; } + + public byte? InputStreamIdentifier { get; private set; } + + public int? ScramblingSequenceIndex { get; private set; } + + public TsGsModeCoding TsGsMode { get; private set; } + + public enum TsGsModeCoding : byte + { + GenericPacketized = 0, + Gse = 1, + TransportStream = 2 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x7A_EnhancedAc3Descriptor.cs b/skyscraper8/Dvb/Descriptors/0x7A_EnhancedAc3Descriptor.cs new file mode 100644 index 0000000..488dcfe --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x7A_EnhancedAc3Descriptor.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x7a,"PMT")] + class EnhancedAc3Descriptor : TsDescriptor + { + public EnhancedAc3Descriptor(byte[] buffer) + { + int ptr = 0; + byte flags = buffer[ptr++]; + bool componentTypeFlags = (flags & 0x80) != 0; + bool bsidFlag = (flags & 0x40) != 0; + bool mainIdFlag = (flags & 0x20) != 0; + bool asvcFlag = (flags & 0x10) != 0; + MixInfoExists = (flags & 0x08) != 0; + bool substream1Flag = (flags & 0x04) != 0; + bool substream2Flag = (flags & 0x02) != 0; + bool substream3Flag = (flags & 0x01) != 0; + if (componentTypeFlags) + ComponentType = buffer[ptr++]; + if (bsidFlag) + BSID = buffer[ptr++]; + if (mainIdFlag) + MainId = buffer[ptr++]; + if (asvcFlag) + ASVC = buffer[ptr++]; + if (substream1Flag) + Substream1 = buffer[ptr++]; + if (substream2Flag) + Substream2 = buffer[ptr++]; + if (substream3Flag) + Substream3 = buffer[ptr++]; + } + + public byte? Substream3 { get; private set; } + + public byte? Substream2 { get; private set; } + + public byte? Substream1 { get; private set; } + + public bool MixInfoExists { get; private set; } + + public byte? ASVC { get; private set; } + + public byte? MainId { get; private set; } + + public byte? BSID { get; private set; } + + public byte? ComponentType { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x7C_AacDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x7C_AacDescriptor.cs new file mode 100644 index 0000000..953c3ce --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x7C_AacDescriptor.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x7c,"PMT")] + class AacDescriptor : TsDescriptor + { + public AacDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ProfileAndLevel = ms.ReadUInt8(); + if (ms.GetAvailableBytes() > 0) + { + byte readUInt8 = ms.ReadUInt8(); + bool aacTypeFlag = (readUInt8 & 0x80) != 0; + SaocDeFlag = (readUInt8 & 0x40) != 0; + if (aacTypeFlag) + { + if (ms.GetAvailableBytes() == 0) + { + Valid = false; + return; + } + AacType = ms.ReadUInt8(); + } + + AdditionalInfo = ms.ReadBytes(ms.GetAvailableBytes()); + } + } + + public byte[] AdditionalInfo { get; private set; } + + public byte? AacType { get; private set; } + + public bool? SaocDeFlag { get; private set; } + + public byte ProfileAndLevel { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x7E_FtaContentManagementDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x7E_FtaContentManagementDescriptor.cs new file mode 100644 index 0000000..435dda6 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x7E_FtaContentManagementDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x7E,"NIT","BAT","SDT","EIT")] + class FtaContentManagementDescriptor : TsDescriptor + { + public FtaContentManagementDescriptor(byte[] buffer) + { + DoNotScramble = (buffer[0] & 0x08) != 0; + ControlRemoteAccessOverInternet = (buffer[0] & 0x06) >> 1; + DoNotApplyRevocation = (buffer[0] & 0x01) != 0; + } + + public bool DoNotApplyRevocation { get; private set; } + + public int ControlRemoteAccessOverInternet { get; private set; } + + public bool DoNotScramble { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0x7f_ExtensionDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x7f_ExtensionDescriptor.cs new file mode 100644 index 0000000..dfc655c --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0x7f_ExtensionDescriptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [DescriptorConversionNeeded(typeof(DvbExtensionDescriptorConverter))] + [TsDescriptor(0x7f,"NIT","BAT","SDT","EIT","TOT","PMT")] + class ExtensionDescriptor : TsDescriptor + { + public ExtensionDescriptor(byte[] buffer) + { + DescriptorTagExtension = buffer[0]; + + SelectorBytes = new byte[buffer.Length - 1]; + Array.Copy(buffer, 1, SelectorBytes, 0, buffer.Length - 1); + } + + public byte DescriptorTagExtension { get; private set; } + public byte[] SelectorBytes { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/0xFF_ReservedForFutureUse.cs b/skyscraper8/Dvb/Descriptors/0xFF_ReservedForFutureUse.cs new file mode 100644 index 0000000..9160d4a --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/0xFF_ReservedForFutureUse.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xff)] + [BannedTable("BAT","EIT","PMT")] + class _0xFF_ReservedForFutureUse : TsDescriptor + { + public _0xFF_ReservedForFutureUse(byte[] buffer) + { + throw new NotSupportedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x04_T2DeliverySystemDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x04_T2DeliverySystemDescriptor.cs new file mode 100644 index 0000000..180fb4b --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x04_T2DeliverySystemDescriptor.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x04,"NIT")] + public class T2DeliverySystemDescriptor : Dvb.ExtensionDescriptor + { + public T2DeliverySystemDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + PlpId = ms.ReadUInt8(); + T2SystemId = ms.ReadUInt16BE(); + + if (buffer.Length > 4) + { + byte readUInt8 = ms.ReadUInt8(); + SisoMiso = (readUInt8 & 0xc0) >> 6; + Bandwidth = (readUInt8 & 0x3c) >> 2; + + readUInt8 = ms.ReadUInt8(); + GuardInterval = (readUInt8 & 0xe0) >> 5; + TransmissionMode = (readUInt8 & 0x1c) >> 2; + OtherFrequencyFlag = (readUInt8 & 0x02) != 0; + TfsFlag = (readUInt8 & 0x01) != 0; + + List cellInfos = new List(); + while (ms.GetAvailableBytes() > 2) + { + CellInfo child = new CellInfo(); + child.CellId = ms.ReadUInt16BE(); + if (TfsFlag.Value) + { + byte frequencyLoopLength = ms.ReadUInt8(); + frequencyLoopLength /= 4; + child.CentreFrequencies = new uint[frequencyLoopLength]; + for (int i = 0; i < frequencyLoopLength; i++) + child.CentreFrequencies[i] = ms.ReadUInt32BE(); + } + else + { + child.CentreFrequencies = new uint[1]; + child.CentreFrequencies[0] = ms.ReadUInt32BE(); + } + + byte subcellInfoLength = ms.ReadUInt8(); + subcellInfoLength /= 5; + child.SubcellInfos = new SubcellInfo[subcellInfoLength]; + for (int i = 0; i < subcellInfoLength; i++) + { + child.SubcellInfos[i] = new SubcellInfo(); + child.SubcellInfos[i].CellIdExtension = ms.ReadUInt8(); + child.SubcellInfos[i].TransposerFrequency = ms.ReadUInt32BE(); + } + + cellInfos.Add(child); + } + + CellInfos = cellInfos.AsReadOnly(); + } + } + + public ReadOnlyCollection CellInfos { get; private set; } + + public class SubcellInfo + { + public byte CellIdExtension { get; set; } + public uint TransposerFrequency { get; set; } + } + + public class CellInfo + { + public ushort CellId { get; set; } + public uint[] CentreFrequencies { get; set; } + public SubcellInfo[] SubcellInfos { get; set; } + } + + public bool? TfsFlag { get; private set; } + + public bool? OtherFrequencyFlag { get; private set; } + + public int? TransmissionMode { get; private set; } + + public int? GuardInterval { get; private set; } + + public int? Bandwidth { get; private set; } + + public int? SisoMiso { get; private set; } + + public ushort T2SystemId { get; private set; } + + public byte PlpId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x06_SupplemenatryAudioDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x06_SupplemenatryAudioDescriptor.cs new file mode 100644 index 0000000..baa05c6 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x06_SupplemenatryAudioDescriptor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x06,"PMT")] + public class SupplemenatryAudioDescriptor : Dvb.ExtensionDescriptor + { + public SupplemenatryAudioDescriptor(byte[] buffer) + { + MixType = ((buffer[0] & 0x80) != 0); + EditorialClassification = (EditorialClassificationEnum)((buffer[0] & 0x7c) >> 2); + LanguageCodePresent = ((buffer[0] & 0x01) != 0); + if (LanguageCodePresent) + Iso639LanguageCode = Encoding.ASCII.GetString(buffer, 1, 3); + } + + public string Iso639LanguageCode { get; private set; } + + public bool LanguageCodePresent { get; private set; } + + public bool MixType { get; private set; } + public EditorialClassificationEnum EditorialClassification { get; private set; } + + public enum EditorialClassificationEnum + { + MainAudio = 0, + AudioDescriptionVisuallyImpaired = 1, + CleanAudio = 2, + SpokenSubtitles = 3, + DependentParametricDataStream = 4, + UnspecificSupplementaryAudio = 0x17 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x08_MessageDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x08_MessageDescriptor.cs new file mode 100644 index 0000000..0444502 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x08_MessageDescriptor.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + /** + * create table public.dvb_sdt_messages + ( + tsid integer not null, + onid integer not null, + sid integer not null, + msgid integer not null, + language_code varchar(3) not null, + message text not null, + dateadded timestamp default current_timestamp not null, + constraint dvb_sdt_messages_pk + primary key (tsid, onid, sid, msgid), + constraint dvb_sdt_messages_dvb_sdt_tsid_onid_sid_fk + foreign key (tsid, onid, sid) references public.dvb_sdt + ); +*/ + [SkyscraperPlugin] + [ExtensionDescriptor(0x08,"NIT","BAT","SDT","EIT")] + public class MessageDescriptor : Dvb.ExtensionDescriptor + { + public MessageDescriptor(byte[] buffer) + { + if (buffer == null) + { + Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + MessageId = ms.ReadUInt8(); + Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + Message = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(ms.GetAvailableBytes())); + } + + public string Message { get; set; } + + public string Iso639LanguageCode { get; set; } + + public byte MessageId { get; set; } + + protected bool Equals(MessageDescriptor other) + { + return Message == other.Message && Iso639LanguageCode == other.Iso639LanguageCode; + } + + 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((MessageDescriptor)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Message, Iso639LanguageCode); + } + + public override string ToString() + { + return $"{nameof(MessageId)}: {MessageId}, {nameof(Message)}: {Message}"; + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x09_TargetRegionDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x09_TargetRegionDescriptor.cs new file mode 100644 index 0000000..f50e410 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x09_TargetRegionDescriptor.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x09, "NIT", "BAT","SDT")] + public class TargetRegionDescriptor : Dvb.ExtensionDescriptor + { + public TargetRegionDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CountryCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + + List _targetRegions = new List(); + while (ms.GetAvailableBytes() > 1) + { + byte readUInt8 = ms.ReadUInt8(); + bool countryCodeFlag = (readUInt8 & 0x04) != 0; + int regionDepth = (readUInt8 & 0x03); + + TargetRegion childRegion = new TargetRegion(); + if (countryCodeFlag) + { + childRegion.CountryCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + } + + if (regionDepth >= 1) + { + childRegion.PrimaryRegionCode = ms.ReadUInt8(); + if (regionDepth >= 2) + { + childRegion.SecondaryRegionCode = ms.ReadUInt8(); + if (regionDepth == 3) + { + childRegion.TertiaryRegionCode = ms.ReadUInt16BE(); + } + } + } + + _targetRegions.Add(childRegion); + } + TargetRegions = _targetRegions; + } + + public string CountryCode { get; private set; } + + public IReadOnlyList TargetRegions { get; private set; } + + public class TargetRegion + { + public string CountryCode { get; set; } + public byte? PrimaryRegionCode { get; set; } + public byte? SecondaryRegionCode { get; set; } + public ushort? TertiaryRegionCode { get; set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x0A_TargetRegionNameDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x0A_TargetRegionNameDescriptor.cs new file mode 100644 index 0000000..4bdfbdb --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x0A_TargetRegionNameDescriptor.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x0A, "NIT","BAT")] + public class TargetRegionNameDescriptor : Dvb.ExtensionDescriptor + { + public TargetRegionNameDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CountryCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + Iso639LanguageCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + + List regions = new List(); + while (ms.GetAvailableBytes() > 2) + { + byte readUInt8 = ms.ReadUInt8(); + int regionDepth = (readUInt8 & 0xc0) >> 6; + int regionNameLength = (readUInt8 & 0x3f); + + TargetRegionName childRegionName = new TargetRegionName(); + childRegionName.RegionName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(regionNameLength)); + childRegionName.PrimaryRegionCode = ms.ReadUInt8(); + if (regionDepth >= 2) + { + childRegionName.SecondaryRegionCode = ms.ReadUInt8(); + if (regionDepth == 3) + { + childRegionName.TertiaryRegionCode = ms.ReadUInt16BE(); + } + } + + regions.Add(childRegionName); + + } + Regions = regions.AsReadOnly(); + } + + public string Iso639LanguageCode { get; set; } + + public string CountryCode { get; set; } + + public IReadOnlyList Regions { get; private set; } + + public class TargetRegionName + { + public string RegionName { get; internal set; } + public byte PrimaryRegionCode { get; set; } + public byte? SecondaryRegionCode { get; set; } + public ushort? TertiaryRegionCode { get; set; } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x0B_ServiceRelocatedDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x0B_ServiceRelocatedDescriptor.cs new file mode 100644 index 0000000..5e15ac5 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x0B_ServiceRelocatedDescriptor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x0B,"SDT")] + class ServiceRelocatedDescriptor : Dvb.ExtensionDescriptor + { + public ServiceRelocatedDescriptor(byte[] buffer) + { + if (buffer.Length != 6) + throw new DvbDescriptorBrokenException("expected 6 bytes"); + + MemoryStream ms = new MemoryStream(buffer,false); + OldOriginalNetworkId = ms.ReadUInt16BE(); + OldTransportStreamId = ms.ReadUInt16BE(); + OldServiceId = ms.ReadUInt16BE(); + } + + public ushort OldServiceId { get; private set; } + + public ushort OldTransportStreamId { get; private set; } + + public ushort OldOriginalNetworkId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x0C_XaitPidDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x0C_XaitPidDescriptor.cs new file mode 100644 index 0000000..740907d --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x0C_XaitPidDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x0C, "NIT")] + class XaitPidDescriptor : Dvb.ExtensionDescriptor + { + public XaitPidDescriptor(byte[] buffer) + { + (buffer[1], buffer[0]) = (buffer[0], buffer[1]); + XaitPid = BitConverter.ToUInt16(buffer, 0); + } + + public ushort XaitPid { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x11_T2MIDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x11_T2MIDescriptor.cs new file mode 100644 index 0000000..6af148c --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x11_T2MIDescriptor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x11,"PMT")] + internal class _0x11_T2MIDescriptor : Dvb.ExtensionDescriptor + { + public _0x11_T2MIDescriptor(byte[] buffer) + { + T2MiStreamId = buffer[0] & 0x07; + NumT2MiStreams = buffer[1] & 0x07; + NumT2MiStreams++; + PcrIscrCommonClockFlag = (buffer[2] & 0x01) != 0; + } + + public int T2MiStreamId { get; private set; } + public int NumT2MiStreams { get; private set; } + public bool PcrIscrCommonClockFlag { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x13_UriLinkageDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x13_UriLinkageDescriptor.cs new file mode 100644 index 0000000..232d236 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x13_UriLinkageDescriptor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x13,"NIT","BAT","SDT","EIT","PMT","SIT")] + class UriLinkageDescriptor : Dvb.ExtensionDescriptor + { + public UriLinkageDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, true); + UriLinkageType = ms.ReadUInt8(); + byte uriLength = ms.ReadUInt8(); + Uri = Encoding.ASCII.GetString(ms.ReadBytes(uriLength)); + if (UriLinkageType == 0x00 || UriLinkageType == 0x01) + { + MinPollingInterval = ms.ReadUInt16BE(); + } + } + + public ushort? MinPollingInterval { get; private set; } + + public byte UriLinkageType { get; private set; } + public string Uri { get; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x15_Ac4Descriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x15_Ac4Descriptor.cs new file mode 100644 index 0000000..cbba873 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x15_Ac4Descriptor.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x15,"PMT")] + class Ac4Descriptor : Dvb.ExtensionDescriptor + { + public Ac4Descriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte flags = ms.ReadUInt8(); + bool ac4ConfigFlag = (flags & 0x80) != 0; + bool ac4TocFlag = (flags & 0x40) != 0; + if (ac4ConfigFlag) + { + flags = ms.ReadUInt8(); + Ac4DialogEnhancementEnabled = (flags & 0x80) != 0; + Ac4ChannelMode = (flags & 0x60) >> 5; + } + + if (ac4TocFlag) + { + byte ac4TocLen = ms.ReadUInt8(); + Ac4Dsi = ms.ReadBytes(ac4TocLen); + } + } + + public byte[] Ac4Dsi { get; private set; } + + public int Ac4ChannelMode { get; private set; } + + public bool Ac4DialogEnhancementEnabled { get; private set; } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x19_AudioPreselectionDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x19_AudioPreselectionDescriptor.cs new file mode 100644 index 0000000..a711b91 --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x19_AudioPreselectionDescriptor.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x19,"PMT")] + public class AudioPreselectionDescriptor : Dvb.ExtensionDescriptor + { + public AudioPreselectionDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + int numPreselections = (ms.ReadUInt8() & 0xf8) >> 3; + Preselections = new AudioPreselection[numPreselections]; + for (int i = 0; i < numPreselections; i++) + { + AudioPreselection child = new AudioPreselection(); + Preselections[i] = child; + + byte readUInt8 = ms.ReadUInt8(); + child.PreselectionId = (readUInt8 & 0xf8) >> 3; + child.AudioRenderingIndication = (AudioPreselection.RenderingIndication)(readUInt8 & 0x07); + + readUInt8 = ms.ReadUInt8(); + child.AudioDescription = (readUInt8 & 0x80) != 0; + child.SpokenSubtitles = (readUInt8 & 0x40) != 0; + child.DialogueEnhancement = (readUInt8 & 0x20) != 0; + child.InteractivityEnabled = (readUInt8 & 0x10) != 0; + bool languageCodePresent = (readUInt8 & 0x08) != 0; + bool textLabelPresent = (readUInt8 & 0x04) != 0; + bool multiStreamPresent = (readUInt8 & 0x02) != 0; + bool futureExtension = (readUInt8 & 0x01) != 0; + if (languageCodePresent) + child.Iso639LangaugeCode = Encoding.ASCII.GetString(ms.ReadBytes(3)); + if (textLabelPresent) + child.MessageId = ms.ReadUInt8(); + if (multiStreamPresent) + { + int numAuxComponents = (ms.ReadUInt8() & 0xe0) >> 5; + child.ComponentTags = ms.ReadBytes(numAuxComponents); + } + + if (futureExtension) + { + int futureExtensionLength = ms.ReadUInt8() & 0x1f; + child.FutureExtension = ms.ReadBytes(futureExtensionLength); + } + } + } + + public AudioPreselection[] Preselections { get; private set; } + + public class AudioPreselection + { + public int PreselectionId { get; set; } + public RenderingIndication AudioRenderingIndication { get; set; } + public bool AudioDescription { get; set; } + public bool SpokenSubtitles { get; set; } + public bool DialogueEnhancement { get; set; } + public bool InteractivityEnabled { get; set; } + public string Iso639LangaugeCode { get; set; } + public byte MessageId { get; set; } + public byte[] ComponentTags { get; set; } + public byte[] FutureExtension { get; set; } + + public enum RenderingIndication : byte + { + NoPreference = 0, + Stereo = 1, + TwoDimensional = 2, + ThreeDimensional = 3, + Headphones = 4 + } + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/0x20_TtmlSubtitlingDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/0x20_TtmlSubtitlingDescriptor.cs new file mode 100644 index 0000000..f91118a --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/0x20_TtmlSubtitlingDescriptor.cs @@ -0,0 +1,79 @@ +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using System.IO; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.src.Dvb.Descriptors.Extension +{ + [SkyscraperPlugin] + [ExtensionDescriptor(0x20, "PMT")] + class _0x20_TtmlSubtitlingDescriptor : ExtensionDescriptor + { + public _0x20_TtmlSubtitlingDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Iso639LanguageCode = ms.ReadUTF8FixedLength(3); + + byte byteA = ms.ReadUInt8(); + SubtitlePurpose = (SubtitlePurposeEnum)((byteA >> 2) & 0x3f); + TtsSustainability = (TtsSustainabilityEnum)(byteA & 0x03); + + byte byteB = ms.ReadUInt8(); + bool essentialFontUsageFlag = (byteB & 0x80) != 0; + bool qualifierPresentFlag = (byteB & 0x40) != 0; + int ttmlProfileCount = (byteB & 0x0f); + DvbTtmlProbile = ms.ReadBytes(ttmlProfileCount); + if (qualifierPresentFlag) + { + Qualifier = (uint?)ms.ReadUInt32BE(); + } + if (essentialFontUsageFlag) + { + int fontCount = ms.ReadUInt8(); + FontId = new int[fontCount]; + for (int i = 0; i < FontId.Length; i++) + { + FontId[i] = ms.ReadUInt8() & 0x7f; + } + } + + byte textLength = ms.ReadUInt8(); + TextChar = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(textLength)); + Valid = true; + } + + public string Iso639LanguageCode { get; } + public SubtitlePurposeEnum SubtitlePurpose { get; } + public TtsSustainabilityEnum TtsSustainability { get; } + public byte[] DvbTtmlProbile { get; } + public uint? Qualifier { get; } + public int[] FontId { get; } + public string TextChar { get; } + + public enum SubtitlePurposeEnum : byte + { + SameLanguageDialogue = 0x00, + OtherLanguageDialogue = 0x01, + AllDialogue = 0x02, + HardOfHearing = 0x10, + OtherLanguageDialogueWithHardOfHearing = 0x11, + AllDialogueWithHardOfHearing = 0x12, + AudioDescription = 0x30, + ContentRelatedCommentary = 0x31 + } + + public enum TtsSustainabilityEnum : byte + { + Unknown = 0x00, + Suitable = 0x01, + Unsuitable = 0x02 + } + } +} diff --git a/skyscraper8/Dvb/Descriptors/Extension/ExtensionUserDefinedDescriptor.cs b/skyscraper8/Dvb/Descriptors/Extension/ExtensionUserDefinedDescriptor.cs new file mode 100644 index 0000000..0b7499e --- /dev/null +++ b/skyscraper8/Dvb/Descriptors/Extension/ExtensionUserDefinedDescriptor.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; + +namespace skyscraper5.Dvb.Descriptors.Extension +{ + class ExtensionUserDefinedDescriptor : UserDefinedDescriptor + { + public ExtensionUserDefinedDescriptor(byte descriptorTag, byte[] data) : base(descriptorTag, data) + { + } + } +} diff --git a/skyscraper8/Dvb/DvbDescriptorBrokenException.cs b/skyscraper8/Dvb/DvbDescriptorBrokenException.cs new file mode 100644 index 0000000..156c3ab --- /dev/null +++ b/skyscraper8/Dvb/DvbDescriptorBrokenException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb +{ + class DvbDescriptorBrokenException : DvbException + { + public DvbDescriptorBrokenException(string s, params object[] args) : base(s, args) + { + } + + } +} diff --git a/skyscraper8/Dvb/DvbException.cs b/skyscraper8/Dvb/DvbException.cs new file mode 100644 index 0000000..bad4055 --- /dev/null +++ b/skyscraper8/Dvb/DvbException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Dvb +{ + class DvbException : Mpeg2Exception + { + public DvbException(string s, params object[] args) + : base(String.Format(s, args)) + { + } + + protected DvbException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Dvb/DvbExtensionDescriptorConverter.cs b/skyscraper8/Dvb/DvbExtensionDescriptorConverter.cs new file mode 100644 index 0000000..f6ade08 --- /dev/null +++ b/skyscraper8/Dvb/DvbExtensionDescriptorConverter.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb +{ + class DvbExtensionDescriptorConverter : IDescriptorConverter + { + private readonly Tuple[] knownDescriptors; + + public DvbExtensionDescriptorConverter() + { + knownDescriptors = PluginManager.GetInstance().GetExtensionDescriptors(); + } + + + private const bool CRASH_ON_UNIMPLEMENTED_DESCRIPTOR = true; + + private TsDescriptor UnpackExtensionDescriptor(Dvb.Descriptors.ExtensionDescriptor extensionDescriptor, string callingTable) + { + if (extensionDescriptor.DescriptorTagExtension >= 0x80) + { + return new ExtensionUserDefinedDescriptor(extensionDescriptor.DescriptorTagExtension, extensionDescriptor.SelectorBytes); + } + if (knownDescriptors[extensionDescriptor.DescriptorTagExtension] == null) + { + string format = String.Format("Extension Descriptor 0x{0:X2} ({0}) not implemented.", extensionDescriptor.DescriptorTagExtension); + if (CRASH_ON_UNIMPLEMENTED_DESCRIPTOR) + throw new NotImplementedException(format); + else + Console.WriteLine(format); + return null; + } + + if (!knownDescriptors[extensionDescriptor.DescriptorTagExtension].Item2.Matches(callingTable)) + return null; + + object result = knownDescriptors[extensionDescriptor.DescriptorTagExtension].Item1.Invoke(new object[] { extensionDescriptor.SelectorBytes }); + return (ExtensionDescriptor)result; + } + + public TsDescriptor Convert(TsDescriptor descriptor, string tableName) + { + Dvb.Descriptors.ExtensionDescriptor extensionDescriptor = descriptor as Dvb.Descriptors.ExtensionDescriptor; + if (extensionDescriptor == null) + throw new ArgumentException(nameof(descriptor)); + + return UnpackExtensionDescriptor(extensionDescriptor, tableName); + } + } +} diff --git a/skyscraper8/Dvb/ExtensionDescriptor.cs b/skyscraper8/Dvb/ExtensionDescriptor.cs new file mode 100644 index 0000000..8a6cb9e --- /dev/null +++ b/skyscraper8/Dvb/ExtensionDescriptor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Dvb +{ + public abstract class ExtensionDescriptor : TsDescriptor + { + + } +} diff --git a/skyscraper8/Dvb/ExtensionDescriptorAttribute.cs b/skyscraper8/Dvb/ExtensionDescriptorAttribute.cs new file mode 100644 index 0000000..2f8825f --- /dev/null +++ b/skyscraper8/Dvb/ExtensionDescriptorAttribute.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class ExtensionDescriptorAttribute : Attribute + { + public byte DescriptorId { get; } + public string[] CompatibleTables { get; } + + public ExtensionDescriptorAttribute(byte descriptorId, params string[] compatibleTables) + { + DescriptorId = descriptorId; + CompatibleTables = compatibleTables; + } + + public bool Matches(string table) + { + foreach (string compatibleTable in CompatibleTables) + { + if (compatibleTable == table) + return true; + } + + return false; + } + } +} diff --git a/skyscraper8/Dvb/Psi/BatEventHandler.cs b/skyscraper8/Dvb/Psi/BatEventHandler.cs new file mode 100644 index 0000000..8aaa399 --- /dev/null +++ b/skyscraper8/Dvb/Psi/BatEventHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Dvb.Psi +{ + interface IBatEventHandler + { + void OnBatBouquet(BatBouquet batBouquet); + void OnBatTransportStream(BatBouquet batBouquet, BatTransportStream child); + } +} diff --git a/skyscraper8/Dvb/Psi/BatParser.cs b/skyscraper8/Dvb/Psi/BatParser.cs new file mode 100644 index 0000000..8e7ea7d --- /dev/null +++ b/skyscraper8/Dvb/Psi/BatParser.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Psi +{ + class BatParser : IPsiProcessor + { + public IBatEventHandler EventHandler { get; } + + public BatParser(IBatEventHandler batEventHandler) + { + EventHandler = batEventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + + byte tableId = ms.ReadUInt8(); + if (tableId != 0x4a) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + return; + int sectionLength = (readUInt16Be & 0x0fff); + + ushort bouquetId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + readUInt16Be = ms.ReadUInt16BE(); + int bouquetDescriptorsLength = (readUInt16Be & 0x0fff); + + byte[] bouquetDescriptorsData = ms.ReadBytes(bouquetDescriptorsLength); + IEnumerable outerDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(bouquetDescriptorsData, "BAT"); + BatBouquet batBouquet = ParseOuterDescriptors(outerDescriptors,bouquetId); + EventHandler.OnBatBouquet(batBouquet); + + readUInt16Be = ms.ReadUInt16BE(); + int transportStreamLoopLength = (readUInt16Be & 0x0fff); + byte[] transportStreamLoopData = ms.ReadBytes(transportStreamLoopLength); + ParseTransportStream(transportStreamLoopData, batBouquet); + + uint crc32 = ms.ReadUInt32BE(); + } + + private void ParseTransportStream(byte[] transportStreamLoopData, BatBouquet batBouquet) + { + MemoryStream ms = new MemoryStream(transportStreamLoopData, false); + while (ms.GetAvailableBytes() > 6) + { + ushort transportStreamId = ms.ReadUInt16BE(); + ushort originalNetworkId = ms.ReadUInt16BE(); + + int transportDescriptorsLength = ms.ReadUInt16BE() & 0x0fff; + if (ms.GetAvailableBytes() < transportDescriptorsLength) + break; + byte[] transportDescriptorsData = ms.ReadBytes(transportDescriptorsLength); + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(transportDescriptorsData, "BAT"); + BatTransportStream child = ParseInnerDescriptors(transportStreamId, originalNetworkId, descriptors); + EventHandler.OnBatTransportStream(batBouquet, child); + } + } + + private BatTransportStream ParseInnerDescriptors(ushort transportStreamId, ushort originalNetworkId, IEnumerable descriptors) + { + BatTransportStream result = new BatTransportStream(transportStreamId, originalNetworkId); + foreach (TsDescriptor dvbDescriptor in descriptors) + { + string name = dvbDescriptor.GetType().Name; + switch (name) + { + case nameof(ServiceListDescriptor): + ServiceListDescriptor serviceListDescriptor = (ServiceListDescriptor)dvbDescriptor; + if (serviceListDescriptor.Services == null) + break; + if (result.ServiceList == null) + result.ServiceList = new List(serviceListDescriptor.Services); + result.ServiceList.AddRange(serviceListDescriptor.Services); + break; + case nameof(UserDefinedDescriptor): + if (!result.PrivateDataSpecifier.HasValue) + continue; + UserDefinedDescriptor udd = (UserDefinedDescriptor)dvbDescriptor; + TsDescriptor tsDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor(udd, result.PrivateDataSpecifier.Value, "BAT"); + if (tsDescriptor == null) + break; + HandleUserDefinedDescriptor(tsDescriptor, result); + break; + case nameof(PrivateDataSpecifierDescriptor): + result.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(StuffingDescriptor): + break; + case nameof(CountryAvailabilityDescriptor): + CountryAvailabilityDescriptor cad = (CountryAvailabilityDescriptor)dvbDescriptor; + if (cad.CountryCodes.Length == 0) + break; + foreach (string cadCountryCode in cad.CountryCodes) + { + result.CountryAvailability.Add(cadCountryCode, cad.CountryAvailabilityFlag); + } + break; + case nameof(DefaultAuthorityDescriptor): + DefaultAuthorityDescriptor defaultAuthorityDescriptor = (DefaultAuthorityDescriptor)dvbDescriptor; + result.DefaultAuthority = defaultAuthorityDescriptor.DefaultAuthority; + break; + default: + throw new NotImplementedException(name); + } + } + + return result; + } + + private void HandleUserDefinedDescriptor(TsDescriptor unpacked, BatTransportStream outputTs) + { + Attribute customAttribute = unpacked.GetType().GetCustomAttribute(typeof(DescriptorPluginBatHandlerAttribute)); + if (customAttribute == null) + { + throw new NotImplementedException(String.Format("{0} has no {1} defined.", unpacked.GetType().Name, nameof(DescriptorPluginBatHandlerAttribute))); + } + + DescriptorPluginBatHandlerAttribute handlerAttribute = (DescriptorPluginBatHandlerAttribute)customAttribute; + handlerAttribute.Handler.HandleBat(outputTs, unpacked); + } + + private BatBouquet ParseOuterDescriptors(IEnumerable descriptors, ushort bouquetId) + { + BatBouquet result = new BatBouquet(bouquetId); + foreach (TsDescriptor dvbDescriptor in descriptors) + { + string name = dvbDescriptor.GetType().Name; + switch (name) + { + case nameof(BouquetNameDescriptor): + BouquetNameDescriptor bouquetNameDescriptor = (BouquetNameDescriptor)dvbDescriptor; + result.BouquetName = bouquetNameDescriptor.BouquetName; + break; + case nameof(LinkageDescriptor): + LinkageDescriptor linkageDescriptor = (LinkageDescriptor)dvbDescriptor; + result.Linkages.Add(linkageDescriptor); + break; + case nameof(CountryAvailabilityDescriptor): + CountryAvailabilityDescriptor countryAvailabilityDescriptor = (CountryAvailabilityDescriptor)dvbDescriptor; + foreach (string countryCode in countryAvailabilityDescriptor.CountryCodes) + { + result.CountryAvailabilityDictionary[countryCode] = countryAvailabilityDescriptor.CountryAvailabilityFlag; + } + break; + case nameof(PrivateDataSpecifierDescriptor): + result.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(UserDefinedDescriptor): + if (!result.PrivateDataSpecifier.HasValue) + break; + TsDescriptor udd = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, result.PrivateDataSpecifier.Value, "BAT"); + if (udd == null) + break; + throw new NotImplementedException(nameof(UserDefinedDescriptor)); + case nameof(UriLinkageDescriptor): + UriLinkageDescriptor uld = (UriLinkageDescriptor)dvbDescriptor; + result.UriLinkageType = uld.UriLinkageType; + result.Uri = uld.Uri; + result.MinPollingInterval = uld.MinPollingInterval; + break; + case nameof(MultilingualBouquetNameDescriptor): + MultilingualBouquetNameDescriptor mbnd = (MultilingualBouquetNameDescriptor)dvbDescriptor; + result.MultilingualBouquetName = mbnd; + break; + case nameof(FtaContentManagementDescriptor): + FtaContentManagementDescriptor ftacmd = (FtaContentManagementDescriptor)dvbDescriptor; + result.ControlRemoteAccessOverInternet = ftacmd.ControlRemoteAccessOverInternet; + result.DoNotApplyRevocation = ftacmd.DoNotApplyRevocation; + result.DoNotScramble = ftacmd.DoNotScramble; + break; + case nameof(ServiceListDescriptor): + Console.WriteLine("Don't forget to implement the service list descriptor in the BatParser!"); + break; + default: + throw new NotImplementedException(name); + } + } + + return result; + } + } +} diff --git a/skyscraper8/Dvb/Psi/EitEventHandler.cs b/skyscraper8/Dvb/Psi/EitEventHandler.cs new file mode 100644 index 0000000..ab1a807 --- /dev/null +++ b/skyscraper8/Dvb/Psi/EitEventHandler.cs @@ -0,0 +1,11 @@ +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Dvb.Psi +{ + interface IEitEventHandler + { + void SetTransportStreamId(ushort transportStreamId); + void SetNetworkId(ushort networkId, bool forceOverwrite = false); + void OnEitEvent(EitEvent eitEvent); + } +} diff --git a/skyscraper8/Dvb/Psi/EitParser.cs b/skyscraper8/Dvb/Psi/EitParser.cs new file mode 100644 index 0000000..b5a74a0 --- /dev/null +++ b/skyscraper8/Dvb/Psi/EitParser.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Psi +{ + class EitParser : IPsiProcessor + { + public EitParser(IEitEventHandler eventHandler) + { + this.EventHandler = eventHandler; + } + + public IEitEventHandler EventHandler { get; private set; } + + public void GatherPsi(PsiSection section, int sourcePid) + { + byte[] buffer = section.GetDataCopy(); + MemoryStream ms = new MemoryStream(buffer, false); + + byte tableId = ms.ReadUInt8(); + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + { + //According to en_300468v011601p.pdf, page 34 this shall be set to true + return; + } + int sectionLength = (readUInt16Be & 0x0fff); + + bool isActualTs = false; + if (tableId == 0x4e) + isActualTs = true; + else if (tableId == 0x4f) + isActualTs = false; + else if (tableId >= 0x50 && tableId <= 0x5f) + isActualTs = true; + else if (tableId >= 0x60 && tableId <= 0x6f) + isActualTs = false; + else if (tableId < 0x4e) + return; + else if (tableId >= 0x70) + return; + else + throw new NotImplementedException("wait, what?"); + + if (ms.Length <= 14) + return; + + ushort serviceId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + ushort transportStreamId = ms.ReadUInt16BE(); + ushort originalNetworkId = ms.ReadUInt16BE(); + if (isActualTs) + { + EventHandler.SetTransportStreamId(transportStreamId); + EventHandler.SetNetworkId(originalNetworkId); + } + + byte segmentLastSectionNumber = ms.ReadUInt8(); + byte lastTableId = ms.ReadUInt8(); + + while ((ms.Length - ms.Position) > 7) + { + EitEvent eitEvent = new EitEvent(serviceId, transportStreamId, originalNetworkId); + eitEvent.EventId = ms.ReadUInt16BE(); + + DateTime? tmp = ms.ReadEtsiEn300468AnnexCDateTime(); + if (tmp == null) + { + return; + } + + eitEvent.StartTime = tmp.Value; + + eitEvent.Duration = ReadEitDuration(ms); + + readUInt16Be = ms.ReadUInt16BE(); + eitEvent.RunningStatus = (RunningStatus)((readUInt16Be & 0xe000) >> 13); + eitEvent.FreeCa = (readUInt16Be & 0x1000) != 0; + + int descriptorsLoopLength = readUInt16Be & 0x0fff; + if (ms.GetAvailableBytes() < descriptorsLoopLength) + break; + byte[] readBytes = ms.ReadBytes(descriptorsLoopLength); + UnpackDescriptors(readBytes, eitEvent); + if (!string.IsNullOrEmpty(eitEvent.EventName)) + EventHandler.OnEitEvent(eitEvent); + else + return; + } + + uint crc32 = ms.ReadUInt32BE(); + } + + private static TimeSpan ReadEitDuration(MemoryStream ms) + { + long hours = ms.ReadUInt8().UnpackBcd(); + long minutes = ms.ReadUInt8().UnpackBcd(); + long seconds = ms.ReadUInt8().UnpackBcd(); + return new TimeSpan((int)hours, (int)minutes, (int)seconds); + } + + private void UnpackDescriptors(byte[] input, EitEvent output) + { + IEnumerable dvbDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "EIT"); + foreach (TsDescriptor dvbDescriptor in dvbDescriptors) + { + string name = dvbDescriptor.GetType().Name; + switch (name) + { + case nameof(ShortEventDescriptor): + ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor)dvbDescriptor; + if (string.IsNullOrEmpty(shortEventDescriptor.Text) && string.IsNullOrEmpty(shortEventDescriptor.EventName)) + return; + output.Iso639LanguageCode = shortEventDescriptor.Iso639LanguageCode; + output.EventName = shortEventDescriptor.EventName; + output.Text = shortEventDescriptor.Text; + break; + case nameof(ExtendedEventDescriptor): + ExtendedEventDescriptor extendedEventDescriptor = (ExtendedEventDescriptor)dvbDescriptor; + if (extendedEventDescriptor.Text == null) + break; + if (!string.IsNullOrEmpty(output.Iso639LanguageCode)) + { + if (!output.Iso639LanguageCode.Equals(extendedEventDescriptor.Iso639LanguageCode)) + break; + } + else + { + output.Iso639LanguageCode = extendedEventDescriptor.Iso639LanguageCode; + } + extendedEventDescriptor.Items?.ForEach(output.AddItem); + if (output.ExtendedText == null) + output.ExtendedText = new string[extendedEventDescriptor.LastDescriptorNumber + 1]; + if (extendedEventDescriptor.DescriptorNumber == output.ExtendedText.Length) + { + string[] resized = new string[output.ExtendedText.Length + 1]; + Array.Copy(output.ExtendedText, 0, resized, 0, output.ExtendedText.Length); + output.ExtendedText = resized; + } + output.ExtendedText[extendedEventDescriptor.DescriptorNumber] = extendedEventDescriptor.Text; + break; + case nameof(PdcDescriptor): + PdcDescriptor pdcDescriptor = (PdcDescriptor)dvbDescriptor; + if (pdcDescriptor.Valid) + { + output.Pdc = new DateTime(output.StartTime.Year, pdcDescriptor.Month, pdcDescriptor.Day, + pdcDescriptor.Hour, pdcDescriptor.Minute, 0); + } + break; + case nameof(ComponentDescriptor): + ComponentDescriptor componentDescriptor = (ComponentDescriptor)dvbDescriptor; + if (!componentDescriptor.Valid) + break; + if (output.Components == null) + output.Components = new HashSet(); + output.Components.Add(componentDescriptor); + break; + case nameof(ContentDescriptor): + ContentDescriptor cd = (ContentDescriptor)dvbDescriptor; + output.Content = cd.Content; + break; + case nameof(PrivateDataSpecifierDescriptor): + output.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(UserDefinedDescriptor): + if (!output.PrivateDataSpecifier.HasValue) + break; + TsDescriptor unpacked = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, output.PrivateDataSpecifier.Value, "EIT"); + if (unpacked == null) + break; + HandleUserDefinedDescriptor(unpacked, output); + break; + case nameof(ParentalRatingDescriptor): + ParentalRatingDescriptor ratingDescriptor = (ParentalRatingDescriptor)dvbDescriptor; + output.ParentalRatings = ratingDescriptor.ParentalRatings; + break; + case nameof(LinkageDescriptor): + LinkageDescriptor ld = (LinkageDescriptor)dvbDescriptor; + output.Linkages.Add(ld); + break; + case nameof(CaIdentifierDescriptor): + CaIdentifierDescriptor cid = (CaIdentifierDescriptor)dvbDescriptor; + output.CaSystems = cid.CaSystemIds; + break; + case nameof(TimeShiftedEventDescriptor): + TimeShiftedEventDescriptor tsed = (TimeShiftedEventDescriptor)dvbDescriptor; + output.ReferenceServiceId = tsed.ReferenceServiceId; + output.ReferenceEventId = tsed.ReferenceEventId; + break; + case nameof(ContentIdentifierDescriptor): + ContentIdentifierDescriptor coid = (ContentIdentifierDescriptor)dvbDescriptor; + output.Crids = coid.Crids; + break; + case nameof(FtaContentManagementDescriptor): + FtaContentManagementDescriptor fsnd = (FtaContentManagementDescriptor)dvbDescriptor; + output.ControlRemoteAccessOverInternet = fsnd.ControlRemoteAccessOverInternet; + output.DoNotApplyRevocation = fsnd.DoNotApplyRevocation; + output.DoNotScramble = fsnd.DoNotScramble; + break; + default: + throw new NotImplementedException(name); + } + } + } + + private void HandleUserDefinedDescriptor(TsDescriptor unpacked, EitEvent output) + { + Attribute customAttribute = unpacked.GetType().GetCustomAttribute(typeof(DescriptorPluginEitHandlerAttribute)); + if (customAttribute == null) + { + throw new NotImplementedException(String.Format("{0} has no {1} defined.", unpacked.GetType().Name, nameof(DescriptorPluginEitHandlerAttribute))); + } + + DescriptorPluginEitHandlerAttribute handlerAttribute = (DescriptorPluginEitHandlerAttribute)customAttribute; + handlerAttribute.Handler.HandleEit(output, unpacked); + } + } +} diff --git a/skyscraper8/Dvb/Psi/Model/BatBouquet.cs b/skyscraper8/Dvb/Psi/Model/BatBouquet.cs new file mode 100644 index 0000000..2e25da0 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/BatBouquet.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; + +namespace skyscraper5.Dvb.Psi.Model +{ + public class BatBouquet + { + public ushort BouquetId { get; } + + //0x47 Bouquet Name Descriptor + public string BouquetName { get; set; } + + //0x4A Linkage Descriptor + public List Linkages { get; set; } + + //0x49 Country Availability Descriptor + public Dictionary CountryAvailabilityDictionary { get; } + + //0x5f Private Data Specifier Descriptor + public uint? PrivateDataSpecifier { get; set; } + + //extension 0x13 uri linkage descriptor + public byte? UriLinkageType { get; set; } + public string Uri { get; set; } + public ushort? MinPollingInterval { get; set; } + + //0x5b multilingual network name descriptor + public MultilingualBouquetNameDescriptor MultilingualBouquetName { get; set; } + + //0x7e fta content management descriptor + public int? ControlRemoteAccessOverInternet { get; set; } + public bool? DoNotApplyRevocation { get; set; } + public bool? DoNotScramble { get; set; } + + public BatBouquet(ushort bouquetId) + { + BouquetId = bouquetId; + Linkages = new List(); + CountryAvailabilityDictionary = new Dictionary(); + } + + public bool NeedUpdate(BatBouquet other) + { + if ("Irdeto BAT".Equals(this.BouquetName) && "IrĦeto BAT".Equals(other.BouquetName)) + return false; + + if (this.BouquetName != null) + { + if (other.BouquetName != null) + if (!this.BouquetName.Equals(other.BouquetName)) + return true; + } + + if (other.PrivateDataSpecifier.HasValue) + if (this.PrivateDataSpecifier != other.PrivateDataSpecifier) + return true; + + if (!this.UriLinkageType.HasValue && other.UriLinkageType.HasValue) + return true; + + if (this.Uri != null) + if (other.Uri != null) + if (!this.Uri.Equals(other.Uri)) + return true; + + if (this.MinPollingInterval != other.MinPollingInterval) + return true; + + if (other.MultilingualBouquetName != null && this.MultilingualBouquetName == null) + return true; + + if (other.MultilingualBouquetName != null) + { + if (other.MultilingualBouquetName.MultilingualBouquetName.Count > 0 && this.MultilingualBouquetName.MultilingualBouquetName.Count == 0) + return true; + } + + if (other.MultilingualBouquetName != null && this.MultilingualBouquetName != null) + { + foreach (KeyValuePair keyValuePair in other.MultilingualBouquetName.MultilingualBouquetName) + { + bool found = false; + foreach (KeyValuePair innerKv in this.MultilingualBouquetName + .MultilingualBouquetName) + { + if (innerKv.Key.Equals(keyValuePair.Key)) + found = true; + } + + if (!found) + return true; + } + } + + if (this.ControlRemoteAccessOverInternet != other.ControlRemoteAccessOverInternet) + return true; + + if (this.DoNotApplyRevocation != other.DoNotApplyRevocation) + return true; + + if (this.DoNotScramble != other.DoNotScramble) + return true; + + return false; + } + + public string TryGetName() + { + if (!string.IsNullOrEmpty(BouquetName)) + return BouquetName; + + if (MultilingualBouquetName != null) + { + foreach (KeyValuePair keyValuePair in MultilingualBouquetName.MultilingualBouquetName) + { + return keyValuePair.Value; + } + } + + return String.Format("??? (0x{0:X4})", BouquetId); + } + } +} diff --git a/skyscraper8/Dvb/Psi/Model/BatTransportStream.cs b/skyscraper8/Dvb/Psi/Model/BatTransportStream.cs new file mode 100644 index 0000000..ca84ff3 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/BatTransportStream.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Skyscraper; + +namespace skyscraper5.Dvb.Psi.Model +{ + public class BatTransportStream + { + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public List ServiceList { get; set; } + + //from 0x5f private data specifier descriptor + public uint? PrivateDataSpecifier { get; set; } + + //from private descriptor 0x83 nordig private logical channel descriptor, + //might also appear elsewhere + public List LCNs { get; set; } + + //from country availability descriptor + public Dictionary CountryAvailability { get; set; } + + //from default authority descriptor + public string DefaultAuthority { get; set; } + + public BatTransportStream(ushort transportStreamId, ushort originalNetworkId) + { + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + CountryAvailability = new Dictionary(); + } + + public bool NeedsUpdate(BatTransportStream child) + { + if (ServiceList != null && child.ServiceList != null) + { + foreach (ServiceListDescriptor.Service service in ServiceList) + { + if (!child.ServiceList.Contains(service)) + return true; + } + + foreach (ServiceListDescriptor.Service service in child.ServiceList) + { + if (!ServiceList.Contains(service)) + return true; + } + } + + if (ServiceList == null && child.ServiceList != null) + return true; + + if (!this.PrivateDataSpecifier.HasValue && child.PrivateDataSpecifier.HasValue) + return true; + + foreach (KeyValuePair keyValuePair in CountryAvailability) + { + if (!child.CountryAvailability.ContainsKey(keyValuePair.Key)) + return true; + if (child.CountryAvailability[keyValuePair.Key] != keyValuePair.Value) + return true; + } + + if (this.DefaultAuthority != null) + { + if (!this.DefaultAuthority.Equals(child.DefaultAuthority)) + return true; + } + + return false; + } + + protected bool Equals(BatTransportStream other) + { + return TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId; + } + + 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((BatTransportStream)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(TransportStreamId, OriginalNetworkId); + } + } +} diff --git a/skyscraper8/Dvb/Psi/Model/EitEvent.cs b/skyscraper8/Dvb/Psi/Model/EitEvent.cs new file mode 100644 index 0000000..317e116 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/EitEvent.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; + +namespace skyscraper5.Dvb.Psi.Model +{ + public class EitEvent + { + protected bool Equals(EitEvent other) + { + return ServiceId == other.ServiceId && TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId && EventId == other.EventId && StartTime.Equals(other.StartTime); + } + + 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((EitEvent)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(ServiceId, TransportStreamId, OriginalNetworkId, EventId, StartTime); + } + + public ushort ServiceId { get; } + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public ushort EventId { get; internal set; } + public DateTime StartTime { get; internal set; } + public TimeSpan Duration { get; internal set; } + public RunningStatus RunningStatus { get; internal set; } + public bool FreeCa { get; internal set; } + + //Short Event Descriptor + public string Iso639LanguageCode { get; set; } + public string EventName { get; set; } + public string Text { get; set; } + + //Extended Event Descriptor + public List Items { get; private set; } + public string[] ExtendedText { get; internal set; } + + //PDC Descriptor + public DateTime? Pdc { get; set; } + + //Component Descriptor + public HashSet Components { get; set; } + + //Content Descriptor + public Tuple[] Content { get; set; } + + //Private Data Specifier Descriptor + public uint? PrivateDataSpecifier { get; set; } + + //ARD's VPS Descriptor + public string VpsString { get; set; } + + //Parental Rating Descriptor + public Tuple[] ParentalRatings { get; set; } + + //Linkage Descriptor + public List Linkages { get; private set; } + + //Ca Identifier Descriptor + public ushort[] CaSystems { get; set; } + + //Time Shifted Event Descriptor + public ushort? ReferenceServiceId { get; set; } + public ushort? ReferenceEventId { get; set; } + + //Content Identifier Descriptor + public ReadOnlyCollection Crids { get; set; } + + //FTA content management descriptor + public int? ControlRemoteAccessOverInternet { get; set; } + public bool? DoNotApplyRevocation { get; set; } + public bool? DoNotScramble { get; set; } + + + public EitEvent(ushort serviceId, ushort transportStreamId, ushort originalNetworkId) + { + ServiceId = serviceId; + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + Linkages = new List(); + } + + public void AddItem(KeyValuePair keyValuePair) + { + if (Items == null) + Items = new List(); + EitEventItem newItem = new EitEventItem(keyValuePair.Key, keyValuePair.Value, Items.Count); + if (Items == null) + Items = new List(); + if (!Items.Contains(newItem)) + Items.Add(newItem); + } + + public class EitEventItem + { + public string Description { get; } + public string Item { get; } + public int Ordinal { get; } + + public EitEventItem(string description, string item, int ordinal) + { + Description = description; + Item = item; + Ordinal = ordinal; + } + + protected bool Equals(EitEventItem other) + { + return Description == other.Description && Item == other.Item; + } + + 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((EitEventItem)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Description, Item); + } + } + } +} diff --git a/skyscraper8/Dvb/Psi/Model/NitNetwork.cs b/skyscraper8/Dvb/Psi/Model/NitNetwork.cs new file mode 100644 index 0000000..649ffd2 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/NitNetwork.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; + +namespace skyscraper5.Dvb.Psi.Model +{ + public class NitNetwork + { + public ushort NetworkId { get; } + public string Name { get; set; } + public List Linkages { get; private set; } + public uint? PrivateDataSpecifierId { get; set; } + + //from 0x6c cell list descriptor + public ReadOnlyCollection Cells { get; set; } + + //from extension 0x0c xait pid descriptor + public ushort? XaitPid { get; set; } + + //from 0x05b multilingual network name descriptor + public ReadOnlyCollection> MultilingualNetworkName { get; set; } + + //from extension 0x0a target region name descriptor + public IReadOnlyList RegionNames { get; set; } + public string RegionNameCountryCode { get; set; } + public string RegionNameLanguageCode { get; set; } + + //from extension 0x09 target region descriptor + public IReadOnlyList Regions { get; set; } + public string RegionCountryCode { get; set; } + + //from extension 0x08 message descriptor + public HashSet Messages { get; set; } + + //from extension 0x13 uri linkage descriptor + public ushort? MinPollingInterval { get; set; } + public string Uri { get; set; } + public byte? UriLinkageType { get; set; } + + //from descriptor 0x41 + public ServiceListDescriptor.Service[] ServiceList { get; set; } + + public NitNetwork(ushort networkId) + { + NetworkId = networkId; + Linkages = new List(); + } + + protected bool Equals(NitNetwork other) + { + return NetworkId == other.NetworkId; + } + + 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((NitNetwork)obj); + } + + public override int GetHashCode() + { + return NetworkId.GetHashCode(); + } + + public bool NeedsUpdate(NitNetwork other) + { + if (this.Name == null && other.Name != null) + return true; + + if (this.Name != null) + { + if (other.Name != null) + { + this.Name = this.Name.Trim('\0'); + other.Name = other.Name.Trim('\0'); + if (!this.Name.Equals(other.Name)) + return true; + } + else + { + return false; + } + } + + if (this.PrivateDataSpecifierId != null && other.PrivateDataSpecifierId != null) + { + if (this.PrivateDataSpecifierId != other.PrivateDataSpecifierId) + return true; + } + + if (this.PrivateDataSpecifierId != null && other.PrivateDataSpecifierId == null) + return true; + + if (this.XaitPid != other.XaitPid) + return true; + + if (this.RegionNameCountryCode != null) + { + if (!this.RegionNameCountryCode.Equals(other.RegionNameCountryCode)) + return true; + } + + if (this.RegionNameLanguageCode != null) + { + if (!this.RegionNameLanguageCode.Equals(other.RegionNameLanguageCode)) + return true; + } + + if (this.RegionCountryCode != null) + { + if (!this.RegionCountryCode.Equals(other.RegionCountryCode)) + return true; + } + + if (this.MinPollingInterval != other.MinPollingInterval) + return true; + + if (this.Uri != null) + { + if (!this.Uri.Equals(this.Uri)) + return true; + } + + if (this.UriLinkageType != other.UriLinkageType) + return true; + + return false; + } + + public void Sanitize() + { + if (Name != null) + { + Name = Name.Replace("\0", ""); + } + } + } +} diff --git a/skyscraper8/Dvb/Psi/Model/NitTransportStream.cs b/skyscraper8/Dvb/Psi/Model/NitTransportStream.cs new file mode 100644 index 0000000..59f28b1 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/NitTransportStream.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Skyscraper; + +namespace skyscraper5.Dvb.Psi.Model +{ + public class NitTransportStream + { + public enum TransportMedium + { + Unknown, + DVB_S, + DVB_S2, + DVB_T2, + DVB_C + } + + public NitTransportStream(ushort originalNetworkId, ushort transportStreamId) + { + OriginalNetworkId = originalNetworkId; + TransportStreamId = transportStreamId; + DeliveryMethod = TransportMedium.Unknown; + Linkages = new List(); + LogicalChannels = new List(); + } + + public TransportMedium DeliveryMethod { get; set; } + public ushort OriginalNetworkId { get; } + public ushort TransportStreamId { get; } + public bool? East { get; set; } + public SatelliteDeliverySystemDescriptor.InnerFecScheme? FecInner { get; set; } + public long? Frequency { get; set; } + + public float? OrbitalPosition { get; set; } + public SatelliteDeliverySystemDescriptor.PolarizationEnum? Polarization { get; set; } + public float? RollOff { get; set; } + public bool? S2 { get; set; } + public long? SymbolRate { get; set; } + + //From 0x41 service list descriptor + public ServiceListDescriptor Services { get; set; } + + //From 0x79 s2 satellite system descriptor + public int? ScramblingSequenceIndex { get; set; } + public byte? InputStreamIdentifier { get; set; } + public byte? TimesliceNumber { get; set; } + public S2SatelliteDeliverySystemDescriptor.TsGsModeCoding? TsGsMode { get; set; } + + //From 0x5F private data specifier descriptor + public uint? PrivateDataSpecifierId { get; set; } + + //From 0x4A linkage descriptor + public List Linkages { get; internal set; } + + //From User Defined Descriptor + public List LogicalChannels { get; private set; } + + //From extension 0x04 T2 Delivery System Descriptor + public bool? TfsFlag { get; set; } + public int? Bandwidth { get; set; } + public ReadOnlyCollection CellInfos { get; set; } + public int? GuardInterval { get; set; } + public bool? OtherFrequencyFlag { get; set; } + public byte? PlpId { get; set; } + public int? SisoMiso { get; set; } + public ushort? T2SystemId { get; set; } + public int? TransmissionMode { get; set; } + + //From 0x62 Frequency list Descriptor + public FrequencyListDescriptor.CodingTypeValue? CodingType { get; set; } + public uint[] CentreFrequencies { get; set; } + + //From 0x6c Cell List Descriptor + public ReadOnlyCollection Cells { get; set; } + + //From 0x44 cable delivery system descriptor + public int? ModulationType { get; set; } + + //From 0x5a terristial delivery system descriptor + public CableDeliverySystemDescriptor.OuterFecScheme? FecOuter { get; set; } + public TerristialDeliverySystemDescriptor.CodeRate? CodeRateHpStream { get; set; } + public TerristialDeliverySystemDescriptor.CodeRate? CodeRateLpStream { get; set; } + public TerristialDeliverySystemDescriptor.HierarchySignallingFormat? HierarchyInformation { get; set; } + public bool? MpeFecIndicator { get; set; } + public bool? Priority { get; set; } + public int? TimeSlicingIndicator { get; set; } + + //from network name descriptor + public string NetworkName { get; set; } + + //from target region descriptor + public IReadOnlyList TargetRegions { get; set; } + public string TargetRegionCountryCode { get; set; } + + //from cell frequency list descriptor + public List CellFrequencies { get; set; } + + public double GetT2Frequency() + { + foreach (T2DeliverySystemDescriptor.CellInfo cellInfo in CellInfos) + { + foreach (uint cellInfoCentreFrequency in cellInfo.CentreFrequencies) + { + return (double)cellInfoCentreFrequency / 100000.0; + } + } + + throw new DvbException("not dvb-t2"); + } + + public string RenderForUi() + { + switch (DeliveryMethod) + { + case TransportMedium.DVB_T2: + if (!Frequency.HasValue) + return String.Format("DVB-T2 Cell {0}", this.CellInfos[0].CellId); + return String.Format("DVB-T2: {0}/{1}", Frequency, SymbolRate); + case TransportMedium.DVB_C: + return String.Format("DVB-C: {0}", Frequency); + default: + throw new NotImplementedException(DeliveryMethod.ToString()); + } + } + + public string RenderModulation() + { + switch (DeliveryMethod) + { + case TransportMedium.DVB_C: + switch (ModulationType) + { + case 0x01: return "16-QAM"; + case 0x02: return "32-QAM"; + case 0x03: return "64-QAM"; + case 0x04: return "128-QAM"; + case 0x05: return "256-QAM"; + default: return "???"; + } + default: + throw new NotImplementedException(DeliveryMethod.ToString()); + } + } + + protected bool Equals(NitTransportStream other) + { + return OriginalNetworkId == other.OriginalNetworkId && TransportStreamId == other.TransportStreamId; + } + + 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((NitTransportStream)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (OriginalNetworkId.GetHashCode() * 397) ^ TransportStreamId.GetHashCode(); + } + } + + public bool NeedUpdate(NitTransportStream other) + { + if (other.East != this.East) + return true; + + if (other.FecInner != this.FecInner) + return true; + + if (other.Frequency != this.Frequency) + return true; + + if (other.Polarization != this.Polarization) + return true; + + if (other.S2 != this.S2) + return true; + + if (other.SymbolRate != this.SymbolRate) + return true; + + if (other.ScramblingSequenceIndex != this.ScramblingSequenceIndex) + return true; + + if (other.InputStreamIdentifier != this.InputStreamIdentifier) + return true; + + if (other.TimesliceNumber != this.TimesliceNumber) + return true; + + if (other.TsGsMode != this.TsGsMode) + return true; + + if (other.PrivateDataSpecifierId != this.PrivateDataSpecifierId) + return true; + + if (other.TfsFlag != this.TfsFlag) + return true; + + if (other.Bandwidth != this.Bandwidth) + return true; + + if (other.GuardInterval != this.GuardInterval) + return true; + + if (other.OtherFrequencyFlag != this.OtherFrequencyFlag) + return true; + + if (other.PlpId != this.PlpId) + return true; + + if (other.SisoMiso != this.SisoMiso) + return true; + + if (other.T2SystemId != this.T2SystemId) + return true; + + if (other.TransmissionMode != this.TransmissionMode) + return true; + + if (other.CodingType != this.CodingType) + return true; + + if (other.ModulationType != this.ModulationType) + return true; + + if (other.FecOuter != this.FecOuter) + return true; + + if (other.CodeRateHpStream != this.CodeRateHpStream) + return true; + + if (other.CodeRateLpStream != this.CodeRateLpStream) + return true; + + if (other.HierarchyInformation != this.HierarchyInformation) + return true; + + if (other.MpeFecIndicator != this.MpeFecIndicator) + return true; + + if (other.Priority != this.Priority) + return true; + + if (other.TimeSlicingIndicator != this.TimeSlicingIndicator) + return true; + + if (this.NetworkName == null && other.NetworkName != null) + return true; + + if (this.NetworkName != null) + if (!this.NetworkName.Equals(other.NetworkName)) + return true; + + if (this.TargetRegionCountryCode == null && other.TargetRegionCountryCode != null) + return true; + + if (this.TargetRegionCountryCode != null) + if (!this.TargetRegionCountryCode.Equals(other.TargetRegionCountryCode)) + return true; + + return false; + } + } +} diff --git a/skyscraper8/Dvb/Psi/Model/RunningStatus.cs b/skyscraper8/Dvb/Psi/Model/RunningStatus.cs new file mode 100644 index 0000000..ae1b97e --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/RunningStatus.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.Psi.Model +{ + public enum RunningStatus + { + Undefined = 0x00, + NotRunning = 0x01, + Starting = 0x02, + Pausing = 0x03, + Running = 0x04, + OffAir = 0x05, + + } +} diff --git a/skyscraper8/Dvb/Psi/Model/SdtService.cs b/skyscraper8/Dvb/Psi/Model/SdtService.cs new file mode 100644 index 0000000..0b9f549 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Model/SdtService.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Dvb.Psi.Model +{ + public class SdtService + { + public ushort ServiceId { get; } + public bool EitScheduleFlag { get; } + public bool EitPresentFollowingFlag { get; } + public RunningStatus RunningStatus { get; } + public bool FreeCaMode { get; } + + //From 0x48 Service Descriptor + public string ServiceName { get; set; } + public string ServiceProviderName { get; set; } + public ServiceDescriptor.ServiceTypeCoding? ServiceType { get; set; } + + //From 0x5f Private Data Specifier + public uint? PrivateDataSpecifier { get; set; } + + //From 0x53 Ca Identifier Descriptor + public ushort[] CaIdentifiers { get; set; } + + //From 0x50 Component Descriptor + + public List Components { get; set; } + + //From 0x64 data braodcast descriptor + public ushort? DataBroadcastId { get; set; } + public byte[] Selector { get; set; } + + //From 0x49 country availability descriptor + public Dictionary CountryAvailability { get; set; } + + //From 0x4a linkage descriptor + public HashSet Linkages { get; set; } + + //From 0x4c time shifted service descriptor + public ushort? ReferenceServiceId { get; set; } + + //From 0x4b nvod reference descriptor + public NvodReferenceDescriptor.NvodReference[] NvodReferences { get; set; } + + //From 0x73 default authority descriptor + public string DefaultAuthority { get; set; } + + //From 0x5d multilingual service name descriptor + public ReadOnlyCollection MultilingualServiceName { get; set; } + + //From 0x7e fta content management descriptor + public int? ControlRemoteAccessOverInternet { get; set; } + public bool? DoNotApplyRevocation { get; set; } + public bool? DoNotScramble { get; set; } + + //From extension 0x07 service relocated descriptor + public ushort? OldOriginalNetworkId { get; set; } + public ushort? OldServiceId { get; set; } + public ushort? OldTransportStreamId { get; set; } + + //From Data Broadcast Descriptor + public byte? ComponentTag { get; set; } + public string Iso639LanguageCode { get; set; } + public string Text { get; set; } + + //From Message Descriptor + public List Messages { get; set; } + + public SdtService(ushort serviceId, bool eitScheduleFlag, bool eitPresentFollowingFlag, RunningStatus runningStatus, bool freeCaMode) + { + ServiceId = serviceId; + EitScheduleFlag = eitScheduleFlag; + EitPresentFollowingFlag = eitPresentFollowingFlag; + RunningStatus = runningStatus; + FreeCaMode = freeCaMode; + } + + protected bool Equals(SdtService other) + { + return ServiceId == other.ServiceId; + } + + 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((SdtService)obj); + } + + public override int GetHashCode() + { + return ServiceId.GetHashCode(); + } + + public bool NeedsUpdate(SdtService newer) + { + if (this.EitScheduleFlag != newer.EitScheduleFlag) + return true; + if (this.EitPresentFollowingFlag != newer.EitPresentFollowingFlag) + return true; + if (this.FreeCaMode != newer.FreeCaMode) + return true; + if (!this.ServiceType.Equals(newer.ServiceType)) + return true; + + if (!this.PrivateDataSpecifier.HasValue && newer.PrivateDataSpecifier.HasValue) + return true; + + if (!this.ComponentTag.HasValue && newer.ComponentTag.HasValue) + return true; + + if (!this.DataBroadcastId.HasValue && newer.DataBroadcastId.HasValue) + return true; + + if (!this.ReferenceServiceId.Equals(newer.ReferenceServiceId)) + return true; + if (!this.ControlRemoteAccessOverInternet.Equals(newer.ControlRemoteAccessOverInternet)) + return true; + if (!this.DoNotApplyRevocation.Equals(newer.DoNotApplyRevocation)) + return true; + if (!this.DoNotScramble.Equals(newer.DoNotScramble)) + return true; + return false; + } + + public string GetCaSystemName() + { + if (CaIdentifiers == null) + return ""; + if (CaIdentifiers.Length == 0) + return ""; + + return CaSystemNames.GetHumanReadableName(CaIdentifiers[0]); + } + } +} diff --git a/skyscraper8/Dvb/Psi/NitEventHandler.cs b/skyscraper8/Dvb/Psi/NitEventHandler.cs new file mode 100644 index 0000000..7a52203 --- /dev/null +++ b/skyscraper8/Dvb/Psi/NitEventHandler.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Dvb.Psi +{ + interface INitEventHandler + { + void SetNetworkId(ushort networkId, bool forceOverrite = false); + void OnNitTransportStream(ushort networkId, NitTransportStream transportStream); + void OnNitNetwork(NitNetwork nitNetwork); + } +} diff --git a/skyscraper8/Dvb/Psi/NitParser.cs b/skyscraper8/Dvb/Psi/NitParser.cs new file mode 100644 index 0000000..0619eb4 --- /dev/null +++ b/skyscraper8/Dvb/Psi/NitParser.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.Psi +{ + class NitParser : IPsiProcessor + { + public INitEventHandler EventHandler { get; } + + public NitParser(INitEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy()); + byte tableId = ms.ReadUInt8(); + bool actualNetwork; + switch (tableId) + { + case 0x40: + actualNetwork = true; + break; + case 0x41: + actualNetwork = false; + break; + case 0x72: + //stuffing area + return; + default: + return; + } + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxSelector = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxSelector) + return; + int sectionLength = readUInt16Be & 0x0fff; + + ushort networkId = ms.ReadUInt16BE(); + NitNetwork nitNetwork = new NitNetwork(networkId); + if (actualNetwork) + { + //I'd trust the NIT more than the EIT. + EventHandler.SetNetworkId(networkId,true); + } + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e) >> 1; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + readUInt16Be = ms.ReadUInt16BE(); + int networkDescriptorsLength = readUInt16Be & 0x0fff; + byte[] networkDescriptorsBuffer = ms.ReadBytes(networkDescriptorsLength); + IEnumerable networkDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(networkDescriptorsBuffer, "NIT"); + foreach (TsDescriptor dvbDescriptor in networkDescriptors) + { + switch (dvbDescriptor.GetType().Name) + { + case nameof(NetworkNameDescriptor): + NetworkNameDescriptor networkNameDescriptor = (NetworkNameDescriptor)dvbDescriptor; + nitNetwork.Name = networkNameDescriptor.NetworkName; + break; + case nameof(LinkageDescriptor): + LinkageDescriptor linkageDescriptor = (LinkageDescriptor)dvbDescriptor; + nitNetwork.Linkages.Add(linkageDescriptor); + break; + case nameof(PrivateDataSpecifierDescriptor): + nitNetwork.PrivateDataSpecifierId = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(UserDefinedDescriptor): + if (!nitNetwork.PrivateDataSpecifierId.HasValue) + break; + TsDescriptor descriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, nitNetwork.PrivateDataSpecifierId.Value, "NIT"); + if (descriptor == null) + break; + switch (descriptor) + { + default: + throw new NotImplementedException(descriptor.GetType().Name); + } + break; + case nameof(CellListDescriptor): + nitNetwork.Cells = ((CellListDescriptor)dvbDescriptor).Cells; + break; + case nameof(XaitPidDescriptor): + XaitPidDescriptor xpd = (XaitPidDescriptor)dvbDescriptor; + nitNetwork.XaitPid = xpd.XaitPid; + break; + case nameof(MultilingualNetworkNameDescriptor): + MultilingualNetworkNameDescriptor mnnd = (MultilingualNetworkNameDescriptor)dvbDescriptor; + nitNetwork.MultilingualNetworkName = mnnd.MultilingualNetworkName; + break; + case nameof(TargetRegionNameDescriptor): + TargetRegionNameDescriptor trnd = (TargetRegionNameDescriptor)dvbDescriptor; + nitNetwork.RegionNames = trnd.Regions; + nitNetwork.RegionNameCountryCode = trnd.CountryCode; + nitNetwork.RegionNameLanguageCode = trnd.Iso639LanguageCode; + break; + case nameof(TargetRegionDescriptor): + TargetRegionDescriptor trd = (TargetRegionDescriptor)dvbDescriptor; + nitNetwork.Regions = trd.TargetRegions; + nitNetwork.RegionCountryCode = trd.CountryCode; + break; + case nameof(MessageDescriptor): + MessageDescriptor message = (MessageDescriptor)dvbDescriptor; + if (nitNetwork.Messages == null) + nitNetwork.Messages = new HashSet(); + nitNetwork.Messages.Add(message); + break; + case nameof(UriLinkageDescriptor): + UriLinkageDescriptor uriLinkage = (UriLinkageDescriptor)dvbDescriptor; + nitNetwork.MinPollingInterval = uriLinkage.MinPollingInterval; + nitNetwork.Uri = uriLinkage.Uri; + nitNetwork.UriLinkageType = uriLinkage.UriLinkageType; + break; + case nameof(ServiceListDescriptor): + ServiceListDescriptor serviceList = (ServiceListDescriptor)dvbDescriptor; + nitNetwork.ServiceList = serviceList.Services; + break; + case nameof(ExtensionUserDefinedDescriptor): + ExtensionUserDefinedDescriptor eudd = (ExtensionUserDefinedDescriptor)dvbDescriptor; + //might be everything... idk... + break; + default: + throw new NotImplementedException(dvbDescriptor.GetType().Name); + } + } + EventHandler.OnNitNetwork(nitNetwork); + + readUInt16Be = ms.ReadUInt16BE(); + int transportStreamLoopLength = readUInt16Be & 0x0fff; + byte[] transportStreamsBuffer = ms.ReadBytes(transportStreamLoopLength); + UnpackTransportStreams(networkId,transportStreamsBuffer); + + uint crc32 = ms.ReadUInt32BE(); + } + + private void UnpackTransportStreams(ushort networkId, byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while ((ms.Length - ms.Position) > 6) + { + ushort transportStreamId = ms.ReadUInt16BE(); + ushort originalNetworkId = ms.ReadUInt16BE(); + NitTransportStream child = new NitTransportStream(originalNetworkId, transportStreamId); + + ushort readUInt16Be = ms.ReadUInt16BE(); + int transportDescriptorsLength = readUInt16Be & 0x0fff; + if (transportDescriptorsLength > ms.GetAvailableBytes()) + return; + byte[] transportDescriptorBuffer = ms.ReadBytes(transportDescriptorsLength); + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(transportDescriptorBuffer, "NIT"); + foreach (TsDescriptor dvbDescriptor in descriptors) + { + switch (dvbDescriptor.GetType().Name) + { + case nameof(SatelliteDeliverySystemDescriptor): + SatelliteDeliverySystemDescriptor satelliteDeliverySystemDescriptor = (SatelliteDeliverySystemDescriptor)dvbDescriptor; + child.East = satelliteDeliverySystemDescriptor.East; + child.FecInner = satelliteDeliverySystemDescriptor.FecInner; + child.Frequency = satelliteDeliverySystemDescriptor.Frequency; + child.ModulationType = satelliteDeliverySystemDescriptor.ModulationType; + child.OrbitalPosition = satelliteDeliverySystemDescriptor.OrbitalPosition; + child.Polarization = satelliteDeliverySystemDescriptor.Polarization; + child.RollOff = satelliteDeliverySystemDescriptor.RollOff; + child.S2 = satelliteDeliverySystemDescriptor.S2; + child.SymbolRate = satelliteDeliverySystemDescriptor.SymbolRate; + child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_S; + break; + case nameof(ServiceListDescriptor): + ServiceListDescriptor sld = (ServiceListDescriptor)dvbDescriptor; + child.Services = sld; + break; + case nameof(S2SatelliteDeliverySystemDescriptor): + S2SatelliteDeliverySystemDescriptor s2sdsd = (S2SatelliteDeliverySystemDescriptor)dvbDescriptor; + child.ScramblingSequenceIndex = s2sdsd.ScramblingSequenceIndex; + child.InputStreamIdentifier = s2sdsd.InputStreamIdentifier; + child.TimesliceNumber = s2sdsd.TimesliceNumber; + child.TsGsMode = s2sdsd.TsGsMode; + if (child.East.HasValue) + { + child.S2 = true; + child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_S2; + } + + break; + case nameof(PrivateDataSpecifierDescriptor): + child.PrivateDataSpecifierId = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(UserDefinedDescriptor): + if (!child.PrivateDataSpecifierId.HasValue) + break; + TsDescriptor unpacked = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, child.PrivateDataSpecifierId.Value, "NIT"); + if (unpacked == null) + break; + HandleUserDefinedDescriptor(unpacked, child); + break; + case nameof(LinkageDescriptor): + child.Linkages.Add((LinkageDescriptor)dvbDescriptor); + break; + case nameof(T2DeliverySystemDescriptor): + child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_T2; + T2DeliverySystemDescriptor t2dsd = (T2DeliverySystemDescriptor)dvbDescriptor; + child.TfsFlag = t2dsd.TfsFlag; + child.Bandwidth = t2dsd.Bandwidth; + child.CellInfos = t2dsd.CellInfos; + child.GuardInterval = t2dsd.GuardInterval; + child.OtherFrequencyFlag = t2dsd.OtherFrequencyFlag; + child.PlpId = t2dsd.PlpId; + child.SisoMiso = t2dsd.SisoMiso; + child.T2SystemId = t2dsd.T2SystemId; + child.TransmissionMode = t2dsd.TransmissionMode; + break; + case nameof(FrequencyListDescriptor): + FrequencyListDescriptor fld = (FrequencyListDescriptor)dvbDescriptor; + child.CodingType = fld.CodingType; + child.CentreFrequencies = fld.CentreFrequencies; + break; + case nameof(CellListDescriptor): + CellListDescriptor cld = (CellListDescriptor)dvbDescriptor; + child.Cells = cld.Cells; + break; + case nameof(CableDeliverySystemDescriptor): + CableDeliverySystemDescriptor cdsd = (CableDeliverySystemDescriptor)dvbDescriptor; + child.Frequency = cdsd.Frequency; + child.SymbolRate = cdsd.SymbolRate; + child.FecInner = cdsd.FecInner; + child.FecOuter = cdsd.FecOuter; + child.ModulationType = cdsd.Modulation; + child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_C; + break; + case nameof(TerristialDeliverySystemDescriptor): + TerristialDeliverySystemDescriptor tdsd = (TerristialDeliverySystemDescriptor)dvbDescriptor; + child.Bandwidth = tdsd.Bandwidth; + child.Frequency = tdsd.CentreFrequency; + child.CodeRateHpStream = tdsd.CodeRateHpStream; + child.CodeRateLpStream = tdsd.CodeRateLpStream; + child.ModulationType = tdsd.Constellation; + child.GuardInterval = tdsd.GuardInterval; + child.HierarchyInformation = tdsd.HierarchyInformation; + child.MpeFecIndicator = tdsd.MpeFecIndicator; + child.OtherFrequencyFlag = tdsd.OtherFrequencyFlag; + child.Priority = tdsd.Priority; + child.TimeSlicingIndicator = tdsd.TimeSlicingIndicator ? 1 : 0; + child.TransmissionMode = tdsd.TransmissionMode; + break; + case nameof(NetworkNameDescriptor): + NetworkNameDescriptor nnd = (NetworkNameDescriptor)dvbDescriptor; + child.NetworkName = nnd.NetworkName; + break; + case nameof(TargetRegionDescriptor): + TargetRegionDescriptor trd = (TargetRegionDescriptor)dvbDescriptor; + child.TargetRegions = trd.TargetRegions; + child.TargetRegionCountryCode = trd.CountryCode; + break; + case nameof(CellFrequencyLinkDescriptor): + CellFrequencyLinkDescriptor cfld = (CellFrequencyLinkDescriptor)dvbDescriptor; + child.CellFrequencies = cfld.Cells; + break; + default: + throw new NotImplementedException(dvbDescriptor.GetType().Name); + } + } + + EventHandler.OnNitTransportStream(networkId, child); + } + } + + private void HandleUserDefinedDescriptor(TsDescriptor unpacked, NitTransportStream outputTs) + { + Attribute customAttribute = unpacked.GetType().GetCustomAttribute(typeof(DescriptorPluginNitHandlerAttribute)); + if (customAttribute == null) + { + throw new NotImplementedException(String.Format("{0} has no {1} defined.", unpacked.GetType().Name, nameof(DescriptorPluginNitHandlerAttribute))); + } + + DescriptorPluginNitHandlerAttribute handlerAttribute = (DescriptorPluginNitHandlerAttribute)customAttribute; + handlerAttribute.Handler.HandleNit(outputTs, unpacked); + } + } +} diff --git a/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs b/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs new file mode 100644 index 0000000..1208ac9 --- /dev/null +++ b/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Dvb.Psi +{ + class Pid0x11Decoder : IPsiProcessor + { + + public Pid0x11Decoder(IBatEventHandler batEventHandler, ISdtEventHandler sdtEventHandler) + { + BatParser = new BatParser(batEventHandler); + SdtParser = new SdtParser(sdtEventHandler); + } + + public BatParser BatParser { get; } + public SdtParser SdtParser { get; } + + public void GatherPsi(PsiSection section, int sourcePid) + { + switch (section.TableId) + { + case 0x42: //SDT for current TS + SdtParser.GatherPsi(section, sourcePid); + break; + case 0x46: //SDT for other TS + SdtParser.GatherPsi(section, sourcePid); + break; + case 0x4a: //BAT + BatParser.GatherPsi(section, sourcePid); + break; + default: + return; + } + } + } +} diff --git a/skyscraper8/Dvb/Psi/RstEventHandler.cs b/skyscraper8/Dvb/Psi/RstEventHandler.cs new file mode 100644 index 0000000..81addd2 --- /dev/null +++ b/skyscraper8/Dvb/Psi/RstEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Dvb.Psi +{ + interface IRstEventHandler + { + void NotifyRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus); + } +} diff --git a/skyscraper8/Dvb/Psi/RstParser.cs b/skyscraper8/Dvb/Psi/RstParser.cs new file mode 100644 index 0000000..424b9d9 --- /dev/null +++ b/skyscraper8/Dvb/Psi/RstParser.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Dvb.Psi +{ + class RstParser : IPsiProcessor + { + public IRstEventHandler EventHandler { get; } + + public RstParser(IRstEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + + byte tableId = ms.ReadUInt8(); + if (tableId != 0x71) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (sectionSyntaxIndicator) + return; + int sectionLength = (readUInt16Be & 0x0fff); + + sectionLength /= 9; + for (int i = 0; i < sectionLength; i++) + { + uint transportStreamId = ms.ReadUInt16BE(); + uint originalNetworkId = ms.ReadUInt16BE(); + uint serviceId = ms.ReadUInt16BE(); + uint eventId = ms.ReadUInt16BE(); + RunningStatus runningStatus = (RunningStatus)(int)(ms.ReadUInt8() & 0x07); + EventHandler.NotifyRunningStatus(transportStreamId, originalNetworkId, serviceId, eventId, runningStatus); + } + } + } +} diff --git a/skyscraper8/Dvb/Psi/SdtEventHandler.cs b/skyscraper8/Dvb/Psi/SdtEventHandler.cs new file mode 100644 index 0000000..8fb0ab0 --- /dev/null +++ b/skyscraper8/Dvb/Psi/SdtEventHandler.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Dvb.Psi +{ + interface ISdtEventHandler + { + void OnSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + void SetNetworkId(ushort networkId); + void SetTransportStreamId(ushort transportStreamId); + } +} diff --git a/skyscraper8/Dvb/Psi/SdtParser.cs b/skyscraper8/Dvb/Psi/SdtParser.cs new file mode 100644 index 0000000..9259e53 --- /dev/null +++ b/skyscraper8/Dvb/Psi/SdtParser.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Psi +{ + class SdtParser : IPsiProcessor + { + public ISdtEventHandler EventHandler { get; } + + public SdtParser(ISdtEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(),false); + + byte tableId = ms.ReadUInt8(); + if (tableId != 0x42 && tableId != 0x46) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + return; + int sectionLength = (readUInt16Be & 0x0fff); + + ushort transportStreamId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + ushort originalNetworkId = ms.ReadUInt16BE(); + ms.ReadUInt8(); //reserved future use + + if (tableId == 0x42) + { + //0x42 meaning current mux + EventHandler.SetNetworkId(originalNetworkId); + EventHandler.SetTransportStreamId(transportStreamId); + } + + while (ms.GetAvailableBytes() > 4) + { + ReadService(ms, transportStreamId, originalNetworkId); + } + + uint crc32 = ms.ReadUInt32BE(); + } + + private void ReadService(MemoryStream ms, ushort transportStreamId, ushort originalNetworkId) + { + ushort serviceId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + bool eitScheduleFlag = (readUInt8 & 0x02) != 0; + bool eitPresentFollowingFlag = (readUInt8 & 0x01) != 0; + + ushort readUInt16Be = ms.ReadUInt16BE(); + int rs = (readUInt16Be & 0xe000) >> 13; + RunningStatus runningStatus = (RunningStatus)rs; + bool freeCaMode = (readUInt16Be & 0x1000) != 0; + int descriptorsLoopLength = readUInt16Be & 0xfff; + + SdtService child = new SdtService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode); + + byte[] descriptorsData = ms.ReadBytes(descriptorsLoopLength); + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorsData, "SDT"); + foreach (TsDescriptor dvbDescriptor in unpackDescriptors) + { + string name = dvbDescriptor.GetType().Name; + switch (name) + { + case nameof(ServiceDescriptor): + ServiceDescriptor serviceDescriptor = (ServiceDescriptor)dvbDescriptor; + child.ServiceName = serviceDescriptor.ServiceName; + child.ServiceProviderName = serviceDescriptor.ServiceProviderName; + child.ServiceType = serviceDescriptor.ServiceType; + break; + case nameof(UserDefinedDescriptor): + if (!child.PrivateDataSpecifier.HasValue) + break; + TsDescriptor tsDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, child.PrivateDataSpecifier.Value, "SDT"); + if (tsDescriptor == null) + break; + ParseUserDefinedDescriptor(tsDescriptor, child); + break; + case nameof(PrivateDataSpecifierDescriptor): + child.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(CaIdentifierDescriptor): + child.CaIdentifiers = ((CaIdentifierDescriptor)dvbDescriptor).CaSystemIds; + if (child.CaIdentifiers.Length > 1) + Array.Sort(child.CaIdentifiers); + break; + case nameof(ComponentDescriptor): + ComponentDescriptor cd = (ComponentDescriptor)dvbDescriptor; + if (child.Components == null) + child.Components = new List(); + child.Components.Add(cd); + break; + case nameof(DataBroadcastDescriptor): + DataBroadcastDescriptor dbd = (DataBroadcastDescriptor)dvbDescriptor; + child.ComponentTag = dbd.ComponentTag; + child.DataBroadcastId = dbd.DataBroadcastId; + child.Selector = dbd.Selector; + child.Iso639LanguageCode = dbd.Iso639LanguageCode; + child.Text = dbd.Text; + break; + case nameof(CountryAvailabilityDescriptor): + CountryAvailabilityDescriptor cad = (CountryAvailabilityDescriptor)dvbDescriptor; + foreach (string cadCountryCode in cad.CountryCodes) + { + if (child.CountryAvailability == null) + child.CountryAvailability = new Dictionary(); + child.CountryAvailability.Add(cadCountryCode, cad.CountryAvailabilityFlag); + } + break; + case nameof(LinkageDescriptor): + LinkageDescriptor ld = (LinkageDescriptor)dvbDescriptor; + if (child.Linkages == null) + child.Linkages = new HashSet(); + child.Linkages.Add(ld); + break; + case nameof(TimeShiftedServiceDescriptor): + child.ReferenceServiceId = ((TimeShiftedServiceDescriptor)dvbDescriptor).ReferenceServiceId; + break; + case nameof(NvodReferenceDescriptor): + child.NvodReferences = ((NvodReferenceDescriptor)dvbDescriptor).NvodReferences; + break; + case nameof(ExtensionUserDefinedDescriptor): + if (!child.PrivateDataSpecifier.HasValue) + break; + TsDescriptor extTsDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, child.PrivateDataSpecifier.Value, "SDT"); + if (extTsDescriptor == null) + break; + ParseUserDefinedDescriptor(extTsDescriptor, child); + break; + case nameof(DefaultAuthorityDescriptor): + DefaultAuthorityDescriptor defaultAuthorityDescriptor = (DefaultAuthorityDescriptor)dvbDescriptor; + child.DefaultAuthority = defaultAuthorityDescriptor.DefaultAuthority; + break; + case nameof(MultilingualServiceNameDescriptor): + MultilingualServiceNameDescriptor msnd = (MultilingualServiceNameDescriptor)dvbDescriptor; + child.MultilingualServiceName = msnd.ServiceNames; + break; + case nameof(FtaContentManagementDescriptor): + FtaContentManagementDescriptor fsnd = (FtaContentManagementDescriptor)dvbDescriptor; + child.ControlRemoteAccessOverInternet = fsnd.ControlRemoteAccessOverInternet; + child.DoNotApplyRevocation = fsnd.DoNotApplyRevocation; + child.DoNotScramble = fsnd.DoNotScramble; + break; + case nameof(ServiceRelocatedDescriptor): + ServiceRelocatedDescriptor serviceRelocatedDescriptor = (ServiceRelocatedDescriptor)dvbDescriptor; + child.OldOriginalNetworkId = serviceRelocatedDescriptor.OldOriginalNetworkId; + child.OldServiceId = serviceRelocatedDescriptor.OldServiceId; + child.OldTransportStreamId = serviceRelocatedDescriptor.OldTransportStreamId; + break; + case nameof(MessageDescriptor): + if (child.Messages == null) + child.Messages = new List(); + + MessageDescriptor message = (MessageDescriptor)dvbDescriptor; + child.Messages.Add(message); + break; + default: + throw new NotImplementedException(name); + } + } + + EventHandler.OnSdtService(transportStreamId, originalNetworkId, child); + } + + private void ParseUserDefinedDescriptor(TsDescriptor input, SdtService output) + { + if (input == null) + return; + + string name = input.ToString(); + switch (name) + { + default: + throw new NotImplementedException(name); + } + } + + private void ParseUserDefinedExtensionDescriptor(TsDescriptor input, SdtService output) + { + if (input == null) + return; + + string name = input.ToString(); + switch (name) + { + default: + throw new NotImplementedException(name); + } + } + } +} diff --git a/skyscraper8/Dvb/Psi/TdtParserHandler.cs b/skyscraper8/Dvb/Psi/TdtParserHandler.cs new file mode 100644 index 0000000..0d303df --- /dev/null +++ b/skyscraper8/Dvb/Psi/TdtParserHandler.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.Psi +{ + interface ITdtEventHandler + { + void OnTdtTime(DateTime utcTime); + } +} diff --git a/skyscraper8/Dvb/Psi/TimetableParser.cs b/skyscraper8/Dvb/Psi/TimetableParser.cs new file mode 100644 index 0000000..50ddae0 --- /dev/null +++ b/skyscraper8/Dvb/Psi/TimetableParser.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Psi +{ + class TimetableParser : IPsiProcessor + { + public ITdtEventHandler TdtEventHandler { get; } + public ITotEventHandler TotEventHandler { get; } + + public TimetableParser(ITdtEventHandler tdtEventHandler, ITotEventHandler totEventHandler) + { + TdtEventHandler = tdtEventHandler; + TotEventHandler = totEventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + switch (section.TableId) + { + case 0x73: //time_offset_section + ParseTot(section.GetDataCopy()); + break; + case 0x70: //time_and_date_table + ParseTdt(section.GetDataCopy()); + break; + default: + return; + } + } + + private void ParseTdt(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte table_id = ms.ReadUInt8(); + if (table_id != 0x70) + throw new NotImplementedException("not a tdt"); + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + int sectionLength = readUInt16Be & 0x0fff; + + if (ms.GetAvailableBytes() < 5) + return; + + DateTime? utcTime = ms.ReadEtsiEn300468AnnexCDateTime(); + if (utcTime != null) + TdtEventHandler.OnTdtTime(utcTime.Value); + } + + private void ParseTot(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte table_id = ms.ReadUInt8(); + if (table_id != 0x73) + throw new NotImplementedException("not a tot"); + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + int sectionLength = readUInt16Be & 0x0fff; + + DateTime? utcTime = ms.ReadEtsiEn300468AnnexCDateTime(); + if (utcTime == null) + return; + + + int descriptorsLoopLength = ms.ReadUInt16BE() & 0x0fff; + + LocalTimeOffsetDescriptor ltod = null; + + byte[] descriptorsData = ms.ReadBytes(descriptorsLoopLength); + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorsData, "TOT"); + foreach (TsDescriptor dvbDescriptor in unpackDescriptors) + { + string name = dvbDescriptor.GetType().Name; + switch (name) + { + case nameof(LocalTimeOffsetDescriptor): + ltod = (LocalTimeOffsetDescriptor)dvbDescriptor; + break; + default: + throw new NotImplementedException(name); + } + } + + if (ltod != null) + { + if (ltod.Valid) + { + TotEventHandler.OnTotTime(utcTime.Value, ltod); + } + } + } + } +} diff --git a/skyscraper8/Dvb/Psi/TotEventHandler.cs b/skyscraper8/Dvb/Psi/TotEventHandler.cs new file mode 100644 index 0000000..24732fe --- /dev/null +++ b/skyscraper8/Dvb/Psi/TotEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; + +namespace skyscraper5.Dvb.Psi +{ + interface ITotEventHandler + { + void OnTotTime(DateTime utcTime, LocalTimeOffsetDescriptor ltod); + } +} diff --git a/skyscraper8/Dvb/Subtitling/ClutDefinitionSegment.cs b/skyscraper8/Dvb/Subtitling/ClutDefinitionSegment.cs new file mode 100644 index 0000000..9972579 --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/ClutDefinitionSegment.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Subtitling +{ + internal class ClutDefinitionSegment + { + public ClutDefinitionSegment(byte[] segmentData) + { + MemoryStream ms = new MemoryStream(segmentData, false); + ClutId = ms.ReadUInt8(); + ClutVersionNumber = (ms.ReadUInt8()) >> 4; + + ClutEntries = new List(); + while (ms.GetAvailableBytes() > 0) + { + ClutEntry child = new ClutEntry(); + child.ClutEntryId = ms.ReadUInt8(); + byte readUInt8 = ms.ReadUInt8(); + child._2bitEntryClutFlag = (readUInt8 & 0x80) != 0; + child._4bitEntryClutFlag = (readUInt8 & 0x40) != 0; + child._8bitEntryClutFlag = (readUInt8 & 0x20) != 0; + child.FullRangeFlag = (readUInt8 & 0x01) != 0; + if (child.FullRangeFlag) + { + if (ms.GetAvailableBytes() < 4) + break; + child.Y = ms.ReadUInt8(); + child.Cr = ms.ReadUInt8(); + child.Cb = ms.ReadUInt8(); + child.T = ms.ReadUInt8(); + } + else + { + if (ms.GetAvailableBytes() < 2) + break; + ushort readUInt16Be = ms.ReadUInt16BE(); + child.Y = (byte)((readUInt16Be & 0xfc00) >> 10); + child.Cr = (byte)((readUInt16Be & 0x03c0) >> 6); + child.Cb = (byte)((readUInt16Be & 0x003c) >> 2); + child.T = (byte)((readUInt16Be & 0x0003)); + + child.Y = (byte)((double)child.Y * 1.25); + child.Cr *= 2; + child.Cb *= 2; + child.T *= 4; + } + + ClutEntries.Add(child); + } + } + + public List ClutEntries { get; private set; } + public int ClutVersionNumber { get; private set; } + + public byte ClutId { get; private set; } + + public class ClutEntry + { + public byte ClutEntryId { get; internal set; } + public bool _2bitEntryClutFlag { get; internal set; } + public bool _4bitEntryClutFlag { get; internal set; } + public bool _8bitEntryClutFlag { get; internal set; } + public bool FullRangeFlag { get; internal set; } + public byte Y { get; internal set; } + public byte Cr { get; internal set; } + public byte Cb { get; internal set; } + public byte T { get; internal set; } + + public override string ToString() + { + return + $"{nameof(ClutEntryId)}: {ClutEntryId}, {nameof(Y)}: {Y}, {nameof(Cr)}: {Cr}, {nameof(Cb)}: {Cb}, {nameof(T)}: {T}"; + } + } + } +} \ No newline at end of file diff --git a/skyscraper8/Dvb/Subtitling/DisplayDefinitionSegment.cs b/skyscraper8/Dvb/Subtitling/DisplayDefinitionSegment.cs new file mode 100644 index 0000000..564338c --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/DisplayDefinitionSegment.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Subtitling +{ + public class DisplayDefinitionSegment + { + public DisplayDefinitionSegment(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + int ddsVersionNumber = readUInt8 & 0xf0 >> 4; + VersionNumber = ddsVersionNumber; + DisplayWindowFlag = (readUInt8 & 0x08) != 0; + DisplayWidth = ms.ReadUInt16BE(); + DisplayHeight = ms.ReadUInt16BE(); + if (DisplayWindowFlag) + { + DisplayWindowHorizontalPositionMinimum = ms.ReadUInt16BE(); + DisplayWindowHorizontalPositionMaximum = ms.ReadUInt16BE(); + DisplayWindowVerticalPositionMinimum = ms.ReadUInt16BE(); + DisplayWindowVerticalPositionMaximum = ms.ReadUInt16BE(); + } + } + + public DisplayDefinitionSegment() + { + DisplayWidth = 719; + DisplayHeight = 575; + } + + public int VersionNumber { get; set; } + public bool DisplayWindowFlag { get; set; } + public ushort DisplayWidth { get; set; } + public ushort DisplayHeight { get; set; } + public ushort? DisplayWindowHorizontalPositionMinimum { get; set; } + public ushort? DisplayWindowHorizontalPositionMaximum { get; set; } + public ushort? DisplayWindowVerticalPositionMinimum { get; set; } + public ushort? DisplayWindowVerticalPositionMaximum { get; set; } + } +} diff --git a/skyscraper8/Dvb/Subtitling/Model/ClutFamily.cs b/skyscraper8/Dvb/Subtitling/Model/ClutFamily.cs new file mode 100644 index 0000000..b1cf205 --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/Model/ClutFamily.cs @@ -0,0 +1,127 @@ +namespace skyscraper5.Dvb.Subtitling.Model +{ + public class ClutFamily + { + public ClutFamily() + { + GenerateDefault8bitClut(); + GenerateDefault4bitClut(); + GenerateDefault2bitClut(); + } + + public Color[] _8bitClut { get; set; } + public Color[] _4bitClut { get; set; } + public Color[] _2bitClut { get; set; } + + + private void GenerateDefault8bitClut() + { + _8bitClut = new Color[256]; + for (int i = 0; i < 256; i++) + { + int b1 = (i & 0x80) >> 7; + int b2 = (i & 0x40) >> 6; + int b3 = (i & 0x20) >> 5; + int b4 = (i & 0x10) >> 4; + int b5 = (i & 0x08) >> 3; + int b6 = (i & 0x04) >> 2; + int b7 = (i & 0x02) >> 1; + int b8 = (i & 0x01); + if (b1 == 0 && b5 == 0) + { + if (b2 == 0 && b3 == 0 && b4 == 0) + { + if (b6 == 0 && b7 == 0 && b8 == 0) + _8bitClut[i].T = 255; + else + { + _8bitClut[i].Pr = (byte)(255 * b8); + _8bitClut[i].Y = (byte)(255 * b7); + _8bitClut[i].Pb = (byte)(255 * b6); + _8bitClut[i].T = 191; + } + } + else + { + _8bitClut[i].Pr = (byte)(84 * b8 + 170 * b4); + _8bitClut[i].Y = (byte)(84 * b7 + 170 * b3); + _8bitClut[i].Pb = (byte)(84 * b6 + 170 * b2); + _8bitClut[i].T = 0; + } + } + else if (b1 == 0 && b5 == 1) + { + _8bitClut[i].Pr = (byte)(84 * b8 + 170 * b4); + _8bitClut[i].Y = (byte)(84 * b7 + 170 * b3); + _8bitClut[i].Pb = (byte)(84 * b6 + 170 * b2); + _8bitClut[i].T = 127; + } + else if (b1 == 1 && b5 == 0) + { + _8bitClut[i].Pr = (byte)(42 * b8 + 84 * b4 + 127); + _8bitClut[i].Y = (byte)(42 * b7 + 84 * b3 + 127); + _8bitClut[i].Pb = (byte)(42 * b6 + 84 * b2 + 127); + _8bitClut[i].T = 0; + } + else if (b1 == 1 && b5 == 1) + { + _8bitClut[i].Pr = (byte)(42 * b8 + 84 * b4); + _8bitClut[i].Y = (byte)(42 * b7 + 84 * b3); + _8bitClut[i].Pb = (byte)(42 * b6 + 84 * b2); + _8bitClut[i].T = 0; + } + } + } + + private void GenerateDefault4bitClut() + { + _4bitClut = new Color[16]; + for (int i = 0; i < 15; i++) + { + int b1 = (i & 0x08) >> 3; + int b2 = (i & 0x04) >> 2; + int b3 = (i & 0x02) >> 1; + int b4 = (i & 0x01); + if (b1 == 0) + { + if (b2 == 0 && b3 == 0 && b4 == 0) + { + _4bitClut[i].T = 255; + } + else + { + _4bitClut[i].Pr = (byte)(255 * b4); + _4bitClut[i].Y = (byte)(255 * b3); + _4bitClut[i].Pb = (byte)(255 * b2); + _4bitClut[i].T = 0; + } + } + else + { + _4bitClut[i].Pr = (byte)(127 * b4); + _4bitClut[i].Y = (byte)(127 * b3); + _4bitClut[i].Pb = (byte)(127 * b2); + _4bitClut[i].T = 0; + } + } + } + + private void GenerateDefault2bitClut() + { + _2bitClut = new Color[4]; + _2bitClut[0].T = 255; + _2bitClut[1].Pb = 255; + _2bitClut[1].Y = 255; + _2bitClut[1].Pr = 255; + _2bitClut[1].T = 0; + _2bitClut[2].Pb = 0; + _2bitClut[2].Y = 0; + _2bitClut[2].Pr = 0; + _2bitClut[2].T = 0; + _2bitClut[3].Pb = 127; + _2bitClut[3].Y = 127; + _2bitClut[3].Pr = 127; + _2bitClut[3].T = 0; + } + } +} diff --git a/skyscraper8/Dvb/Subtitling/Model/Color.cs b/skyscraper8/Dvb/Subtitling/Model/Color.cs new file mode 100644 index 0000000..f77a21c --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/Model/Color.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.Subtitling.Model +{ + public struct Color + { + public Color(byte y, byte pb, byte pr, byte t) + { + Y = y; + Pb = pb; + Pr = pr; + T = t; + } + + public Color(byte y, byte pb, byte pr) + { + Y = y; + Pb = pb; + Pr = pr; + T = 0; + } + + public byte Y { get; set; } + public byte Pb { get; set; } + public byte Pr { get; set; } + public byte T { get; set; } + + public override string ToString() + { + return $"{nameof(T)}: {T}, {nameof(Pr)}: {Pr}, {nameof(Y)}: {Y}, {nameof(Pb)}: {Pb}"; + } + } +} diff --git a/skyscraper8/Dvb/Subtitling/Model/ObjectDimensions.cs b/skyscraper8/Dvb/Subtitling/Model/ObjectDimensions.cs new file mode 100644 index 0000000..9110e3b --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/Model/ObjectDimensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.Subtitling.Model +{ + public class ObjectDimensions + { + public ObjectDimensions(int width, int height) + { + Width = width; + Height = height; + } + + public int Width { get; } + public int Height { get; } + } +} diff --git a/skyscraper8/Dvb/Subtitling/Model/Page.cs b/skyscraper8/Dvb/Subtitling/Model/Page.cs new file mode 100644 index 0000000..93bf0ba --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/Model/Page.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.Subtitling.Model +{ + public class Page + { + public Page() + { + Regions = new RegionCompositionSegment[255]; + ObjectData = new List(); + } + + public DisplayDefinitionSegment DisplayDefinition { get; set; } + public RegionCompositionSegment[] Regions { get; set; } + public PageCompositionSegment PageComposition { get; set; } + public List ObjectData { get; set; } + + private ClutFamily[] clutFamilies; + public ClutFamily GetClutFamiliy(byte clutId) + { + if (clutFamilies == null) + clutFamilies = new ClutFamily[255]; + + if (clutFamilies[clutId] == null) + clutFamilies[clutId] = new ClutFamily(); + + return clutFamilies[clutId]; + } + + public ObjectDimensions GenerateObjectDimensions(ushort objectId) + { + for (int i = 0; i < Regions.Length; i++) + { + if (Regions[i] == null) + continue; + + RegionCompositionSegment rcsCandidate = Regions[i]; + foreach (RegionCompositionSegment.Object regionCandidate in rcsCandidate.Objects) + { + if (regionCandidate.Id == objectId) + { + int width = rcsCandidate.RegionWidth - regionCandidate.HorizontalPosition; + int height = rcsCandidate.RegionHeight - regionCandidate.VerticalPosition; + return new ObjectDimensions(width, height); + } + } + } + + return null; + } + } +} diff --git a/skyscraper8/Dvb/Subtitling/ObjectDataSegment.cs b/skyscraper8/Dvb/Subtitling/ObjectDataSegment.cs new file mode 100644 index 0000000..3e8259e --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/ObjectDataSegment.cs @@ -0,0 +1,204 @@ +//#define ENABLE_DECODER + +using System; +using System.IO; +using skyscraper5.Dvb.Subtitling.Model; +using skyscraper5.Skyscraper.Drawing; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Subtitling +{ + public class ObjectDataSegment + { + + public ObjectDataSegment(byte[] segmentData,Page page) + { + if (page == null) + { + return; + } + + if (segmentData.Length == 0) + { + return; + } + + MemoryStream ms = new MemoryStream(segmentData, false); + Id = ms.ReadUInt16BE(); + ObjectDimensions = page.GenerateObjectDimensions(Id); + if (ObjectDimensions == null) + return; + byte readUInt8 = ms.ReadUInt8(); + VersionNumber = (readUInt8 & 0xf0) >> 4; + CodingMethod = (readUInt8 & 0x0c) >> 2; + NonModifyingColorFlag = (readUInt8 & 0x02) != 0; +#if ENABLE_DECODER + switch (CodingMethod) + { + case 0: + DecodePixels(ms); + break; + default: + throw new NotImplementedException(String.Format("{0} {1}", nameof(CodingMethod), CodingMethod)); + } +#else + //TODO: Maybe have another go at decoding this weird RLE variant? + ObjectData = ms.ReadBytes(ms.GetAvailableBytes()); +#endif + } + +#if ENABLE_DECODER + private void DecodePixels(MemoryStream ms) + { + ushort TopFieldDataBlockLength = ms.ReadUInt16BE(); + if (TopFieldDataBlockLength > ms.Length) + return; + ushort BottomFieldDataBlockLength = ms.ReadUInt16BE(); + if (BottomFieldDataBlockLength > ms.Length) + return; + byte[] topFieldBuffer = ms.ReadBytes(TopFieldDataBlockLength); + byte[] bottomFieldBuffer = ms.ReadBytes(BottomFieldDataBlockLength); + + MemoryStream output = new MemoryStream(new byte[ObjectDimensions.Width * ObjectDimensions.Height]); + DecodePixelData(topFieldBuffer, output); + + output.Position = ObjectDimensions.Width; + DecodePixelData(bottomFieldBuffer, output); + ObjectData = output.ToArray(); + output.Dispose(); + } + + private void DecodePixelData(byte[] buffer,MemoryStream output) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 0) + { + byte dataType = ms.ReadUInt8(); + //Refer to EN 300 743 V1.6.1, page 41 + switch (dataType) + { + case 0x10: + throw new NotImplementedException(String.Format("{0} 0x{1:X2}", nameof(dataType), dataType)); + case 0x11: + try + { + InBitStream bitstream = new InBitStream(ms); + CodeStringSignal signal = CodeStringSignal.Continue; + do + { + signal = Decode4bitPixelCodeString(bitstream, output); + } while (signal == CodeStringSignal.Continue); + + if (!bitstream.IsByteAligned) + bitstream.ReadBitsAsByte(4); + } + catch (EndOfStreamException eose) + { + Console.WriteLine("Couldn't decode a subtitle frame. Note that this decoder is not yet working."); + } + break; + case 0x12: + case 0x20: + case 0x21: + case 0x22: + throw new NotImplementedException(String.Format("{0} 0x{1:X2}", nameof(dataType), dataType)); + case 0xf0: + long currentLine = output.Position / ObjectDimensions.Width; + long currentLineOffset = currentLine * ObjectDimensions.Width; + output.Position = currentLineOffset; + output.Position += ObjectDimensions.Width; + output.Position += ObjectDimensions.Width; + break; + default: + break; + } + } + output.Flush(); + } + + enum CodeStringSignal + { + Continue, + EndOfString + } + + private CodeStringSignal Decode4bitPixelCodeString(InBitStream input, MemoryStream output) + { + byte _4bitPixelCode = input.ReadBitsAsByte(4); + if (_4bitPixelCode != 0) + { + _4bitPixelCode = input.ReadBitsAsByte(4); + output.WriteByte(_4bitPixelCode); + } + else + { + bool switch1 = input.ReadBitAsBoolean(); + if (!switch1) + { + byte runLength39 = input.ReadBitsAsByte(3); + if (runLength39 != 0) + { + runLength39 += 2; + output.WriteUInt8Repeat(0, runLength39); + } + else + { + return CodeStringSignal.EndOfString; + } + } + else + { + bool switch2 = input.ReadBitAsBoolean(); + if (!switch2) + { + byte runLength47 = input.ReadBitsAsByte(2); + runLength47 += 4; + _4bitPixelCode = input.ReadBitsAsByte(4); + output.WriteUInt8Repeat(_4bitPixelCode, runLength47); + } + else + { + byte switch3 = input.ReadBitsAsByte(2); + switch (switch3) + { + case 0b00: + output.WriteByte(0); + break; + case 0b01: + output.WriteByte(0); + output.WriteByte(0); + break; + case 0b10: + byte runLength9_24 = input.ReadBitsAsByte(4); + runLength9_24 += 9; + _4bitPixelCode = input.ReadBitsAsByte(4); + output.WriteUInt8Repeat(_4bitPixelCode, runLength9_24); + break; + case 0b11: + ushort runLength25_280 = input.ReadBitsAsByte(8); + runLength25_280 += 25; + _4bitPixelCode = input.ReadBitsAsByte(4); + output.WriteUInt8Repeat(_4bitPixelCode, runLength25_280); + break; + } + } + } + } + return CodeStringSignal.Continue; + } + +#endif + + private ObjectDimensions ObjectDimensions; + public bool NonModifyingColorFlag { get; private set; } + + public int CodingMethod { get; private set; } + + public int VersionNumber { get; private set; } + + public ushort Id { get; private set; } + + public byte[] ObjectData { get; private set; } + + } +} \ No newline at end of file diff --git a/skyscraper8/Dvb/Subtitling/PageCompositionSegment.cs b/skyscraper8/Dvb/Subtitling/PageCompositionSegment.cs new file mode 100644 index 0000000..3f3267c --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/PageCompositionSegment.cs @@ -0,0 +1,40 @@ +using System.IO; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Subtitling +{ + public class PageCompositionSegment + { + public PageCompositionSegment(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + PageTimeOut = ms.ReadUInt8(); + byte readUInt8 = ms.ReadUInt8(); + PageVersionNumber = readUInt8 & 0xf0 >> 4; + PageState = readUInt8 & 0x0c >> 2; + + long numRegions = ms.GetAvailableBytes() / 6; + Regions = new Region[numRegions]; + for (int i = 0; i < numRegions; i++) + { + Regions[i] = new Region(); + Regions[i].RegionId = ms.ReadUInt8(); + ms.ReadUInt8(); + Regions[i].RegionHorizontalAddress = ms.ReadUInt16BE(); + Regions[i].RegionVerticalAddress = ms.ReadUInt16BE(); + } + } + + public Region[] Regions { get; set; } + public byte PageTimeOut { get; set; } + public int PageVersionNumber { get; set; } + public int PageState { get; set; } + + public class Region + { + public byte RegionId { get; set; } + public ushort RegionHorizontalAddress { get; set; } + public ushort RegionVerticalAddress { get; set; } + } + } +} \ No newline at end of file diff --git a/skyscraper8/Dvb/Subtitling/RegionCompositionSegment.cs b/skyscraper8/Dvb/Subtitling/RegionCompositionSegment.cs new file mode 100644 index 0000000..1089190 --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/RegionCompositionSegment.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Subtitling +{ + public class RegionCompositionSegment + { + public RegionCompositionSegment(byte[] segmentData) + { + MemoryStream ms = new MemoryStream(segmentData, false); + RegionId = ms.ReadUInt8(); + byte readUInt8 = ms.ReadUInt8(); + RegionVersionNumber = (readUInt8 & 0xf0) >> 4; + RegionFillFlag = (readUInt8 & 0x08) != 0; + RegionWidth = ms.ReadUInt16BE(); + RegionHeight = ms.ReadUInt16BE(); + + readUInt8 = ms.ReadUInt8(); + RegionLevelOfCompatibility = (readUInt8 & 0xe0) >> 5; + RegionDepth = (readUInt8 & 0x1c); + ClutId = ms.ReadUInt8(); + Region8bitPixelCode = ms.ReadUInt8(); + + readUInt8 = ms.ReadUInt8(); + Region4bitPixelCode = (readUInt8 & 0xf0) >> 4; + Region2bitPixelCode = (readUInt8 & 0x0c) >> 2; + + List objects = new List(); + while (ms.Position < ms.Length) + { + Object child = new Object(); + child.Id = ms.ReadUInt16BE(); + + ushort readUInt16Be = ms.ReadUInt16BE(); + child.Type = (readUInt16Be & 0xc000) >> 14; + child.ProviderFlag = (readUInt16Be & 0x3000) >> 12; + child.HorizontalPosition = (readUInt16Be & 0x0fff); + child.VerticalPosition = (ms.ReadUInt16BE() & 0x0fff); + if (child.Type == 0x01 && child.Type == 0x02) + { + child.ForegroundPixelCode = ms.ReadUInt8(); + child.BackgroundPixelCode = ms.ReadUInt8(); + } + + objects.Add(child); + } + + Objects = objects.AsReadOnly(); + } + + public ReadOnlyCollection Objects { get; private set; } + + public int Region2bitPixelCode { get; private set; } + + public int Region4bitPixelCode { get; private set; } + + public byte Region8bitPixelCode { get; private set; } + + public byte ClutId { get; private set; } + + public int RegionDepth { get; private set; } + + public int RegionLevelOfCompatibility { get; private set; } + + public ushort RegionHeight { get; private set; } + + public ushort RegionWidth { get; private set; } + + public bool RegionFillFlag { get; private set; } + + public int RegionVersionNumber { get; private set; } + + public byte RegionId { get; private set; } + + public class Object + { + public ushort Id { get; internal set; } + public int Type { get; internal set; } + public int ProviderFlag { get; internal set; } + public int HorizontalPosition { get; internal set; } + public int VerticalPosition { get; internal set; } + public byte ForegroundPixelCode { get; internal set; } + public byte BackgroundPixelCode { get; internal set; } + } + } +} \ No newline at end of file diff --git a/skyscraper8/Dvb/Subtitling/SubtitleDecoder.cs b/skyscraper8/Dvb/Subtitling/SubtitleDecoder.cs new file mode 100644 index 0000000..ab2e50f --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/SubtitleDecoder.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Subtitling.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.Subtitling +{ + class SubtitleDecoder : IPesProcessor + { + private readonly ISubtitleEventHandler _eventHandler; + private DisplayDefinitionSegment _displayDefinition; + private Page[] _pages; + + public SubtitleDecoder(ISubtitleEventHandler eventHandler) + { + _eventHandler = eventHandler; + } + + private bool PayloadMissing(byte[] buffer) + { + if (buffer == null) + return true; + if (buffer.Length < 8) + return true; + byte cmp = buffer[0]; + for (int i = 1; i < buffer.Length; i++) + { + if (buffer[i] != cmp) + return false; + } + return true; + } + + public void ProcessPesPacket(PesPacket packet) + { + if (_pages == null) + { + _pages = new Page[ushort.MaxValue]; + } + + byte[] payload = packet.Payload; + if (PayloadMissing(payload)) + return; + MemoryStream ms = new MemoryStream(payload, false); + + byte data_identifier = ms.ReadUInt8(); + if (data_identifier != 0x20) + return; + + byte subtitle_stream_id = ms.ReadUInt8(); + if (subtitle_stream_id != 0x00) + return; + + while (true) + { + byte syncByte = ms.ReadUInt8(); + if (syncByte != 0x0f) + { + return; + } + + byte segmentType = ms.ReadUInt8(); + ushort pageId = ms.ReadUInt16BE(); + ushort segmentLength = ms.ReadUInt16BE(); + if (segmentLength > ms.GetAvailableBytes()) + return; + byte[] segmentData = ms.ReadBytes(segmentLength); + HandleSegment(segmentType, pageId, segmentData); + } + } + + private void HandleSegment(byte segmentType, ushort pageId, byte[] segmentData) + { + switch (segmentType) + { + case 0x10: + PageCompositionSegment pcs = new PageCompositionSegment(segmentData); + _pages[pageId] = new Page(); + if (_displayDefinition == null) + { + _displayDefinition = new DisplayDefinitionSegment(); + } + _pages[pageId].DisplayDefinition = _displayDefinition; + _pages[pageId].PageComposition = pcs; + break; + case 0x11: + RegionCompositionSegment rcs = new RegionCompositionSegment(segmentData); + _pages[pageId].Regions[rcs.RegionId] = rcs; + break; + case 0x12: + ClutDefinitionSegment cds = new ClutDefinitionSegment(segmentData); + ClutFamily clutFamiliy = _pages[pageId].GetClutFamiliy(cds.ClutId); + ApplyClutPatches(cds, clutFamiliy); + break; + case 0x13: + ObjectDataSegment ods = new ObjectDataSegment(segmentData, _pages[pageId]); + if (ods.ObjectData != null) + { + _pages[pageId].ObjectData.Add(ods); + } + break; + case 0x14: + this._displayDefinition = new DisplayDefinitionSegment(segmentData); + break; + case 0x80: + _eventHandler.NotifyOfSubtitleLine(_pages[pageId]); + break; + case 0xff: + //stuffing + break; + default: + throw new NotImplementedException(String.Format("{0} 0x{1:X2}", nameof(segmentType), segmentType)); + } + } + + private void ApplyClutPatches(ClutDefinitionSegment input, ClutFamily output) + { + foreach (ClutDefinitionSegment.ClutEntry inputClutEntry in input.ClutEntries) + { + if (inputClutEntry._2bitEntryClutFlag) + { + if (inputClutEntry.ClutEntryId > 3) + break; + output._2bitClut[inputClutEntry.ClutEntryId] = new Color(inputClutEntry.Y, inputClutEntry.Cb, inputClutEntry.Cr, inputClutEntry.T); + } + else if (inputClutEntry._4bitEntryClutFlag) + { + if (inputClutEntry.ClutEntryId > output._4bitClut.Length) + continue; + output._4bitClut[inputClutEntry.ClutEntryId] = new Color(inputClutEntry.Y, inputClutEntry.Cb, + inputClutEntry.Cr, inputClutEntry.T); + } + else if (inputClutEntry._8bitEntryClutFlag) + output._8bitClut[inputClutEntry.ClutEntryId] = new Color(inputClutEntry.Y, inputClutEntry.Cb, inputClutEntry.Cr, inputClutEntry.T); + else + continue; + } + } + } +} diff --git a/skyscraper8/Dvb/Subtitling/SubtitleEventHandler.cs b/skyscraper8/Dvb/Subtitling/SubtitleEventHandler.cs new file mode 100644 index 0000000..9ae6268 --- /dev/null +++ b/skyscraper8/Dvb/Subtitling/SubtitleEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Subtitling.Model; + +namespace skyscraper5.Dvb.Subtitling +{ + interface ISubtitleEventHandler + { + void NotifyOfSubtitleLine(Page page); + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/CompatibilityDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/CompatibilityDescriptor.cs new file mode 100644 index 0000000..4ab1449 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/CompatibilityDescriptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.CarouselIntention +{ + class CompatibilityDescriptor + { + public CompatibilityDescriptor(MemoryStream ms) + { + ushort compatibilityDescriptorLength = ms.ReadUInt16BE(); + ushort descriptorCount = ms.ReadUInt16BE(); + Compatibilities = new Compatibility[descriptorCount]; + for (int i = 0; i < descriptorCount; i++) + { + Compatibilities[i] = new Compatibility(ms); + } + } + + public Compatibility[] Compatibilities { get; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/GroupInfo.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/GroupInfo.cs new file mode 100644 index 0000000..a2d4145 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/GroupInfo.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Dvb.SystemSoftwareUpdate.CarouselIntention; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.CarouselIntention +{ + class GroupInfo + { + public GroupInfo(MemoryStream ms) + { + GroupId = ms.ReadUInt32BE(); + uint groupSize = ms.ReadUInt32BE(); + Compatibility = new CompatibilityDescriptor(ms); + + if (ms.GetAvailableBytes() < 2) + { + GroupInfoData = new byte[0]; + PrivateData = new byte[0]; + return; + } + + ushort groupInfoLength = ms.ReadUInt16BE(); + if (ms.GetAvailableBytes() < groupInfoLength) + { + GroupInfoData = new byte[0]; + PrivateData = new byte[0]; + return; + } + GroupInfoData = ms.ReadBytes(groupInfoLength); + + if (ms.GetAvailableBytes() < 2) + { + PrivateData = new byte[0]; + return; + } + + ushort privateDataLength = ms.ReadUInt16BE(); + PrivateData = ms.ReadBytes(privateDataLength); + } + + public byte[] PrivateData { get; private set; } + + public byte[] GroupInfoData { get; private set; } + + public CompatibilityDescriptor Compatibility { get; private set; } + + public uint GroupId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/GroupInfoIndication.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/GroupInfoIndication.cs new file mode 100644 index 0000000..853468e --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/CarouselIntention/GroupInfoIndication.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.CarouselIntention +{ + class GroupInfoIndication + { + public GroupInfoIndication(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ushort numberOfGroups = ms.ReadUInt16BE(); + GroupInfos = new GroupInfo[numberOfGroups]; + for (int i = 0; i < numberOfGroups; i++) + { + GroupInfos[i] = new GroupInfo(ms); + } + } + + public GroupInfo[] GroupInfos { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x01_SchedulingDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x01_SchedulingDescriptor.cs new file mode 100644 index 0000000..e307e91 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x01_SchedulingDescriptor.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x01,"UNT")] + class SchedulingDescriptor : TsDescriptor + { + public SchedulingDescriptor(byte[] buffer) + { + DateTime? tmp; + + MemoryStream ms = new MemoryStream(buffer, false); + tmp = ms.ReadEtsiEn300468AnnexCDateTime(); + if (tmp == null) + { + Valid = false; + return; + } + StartDateTime = tmp.Value; + + + tmp = ms.ReadEtsiEn300468AnnexCDateTime(); + if (tmp == null) + { + Valid = false; + return; + } + EndDateTime = tmp.Value; + + byte readUInt8 = ms.ReadUInt8(); + FinalAvailability = (readUInt8 & 0x80) != 0; + PeriodicityFlag = (readUInt8 & 0x40) != 0; + int periodUnit = (readUInt8 & 0x30); + int durationUnit = (readUInt8 & 0x0c); + int estimatedCycleTimeUnit = (readUInt8 & 0x03); + Period = DecodeTiming(periodUnit, ms.ReadUInt8()); + Duration = DecodeTiming(durationUnit, ms.ReadUInt8()); + EstimatedCycleTime = DecodeTiming(durationUnit, ms.ReadUInt8()); + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] PrivateData { get; private set; } + + public TimeSpan EstimatedCycleTime { get; private set; } + + public TimeSpan Duration { get; private set; } + + public TimeSpan Period { get; private set; } + + private TimeSpan DecodeTiming(int periodUnit, byte _base) + { + int totalSeconds = _base; + switch (periodUnit) + { + case 0b00: + break; + case 0b01: + totalSeconds *= 60; + break; + case 0b10: + totalSeconds *= 3600; + break; + case 0b11: + totalSeconds *= 3600; + totalSeconds *= 24; + break; + default: + throw new NotImplementedException(String.Format("{0} {1:X2}", nameof(periodUnit), periodUnit)); + } + + return new TimeSpan(0, 0, totalSeconds); + } + + + public bool PeriodicityFlag { get; private set; } + + public bool FinalAvailability { get; private set; } + + public DateTime? EndDateTime { get; private set; } + + public DateTime? StartDateTime { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x02_UpdateDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x02_UpdateDescriptor.cs new file mode 100644 index 0000000..db4efdb --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x02_UpdateDescriptor.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x02,"UNT")] + class UpdateDescriptor : TsDescriptor + { + public UpdateDescriptor(byte[] buffer) + { + UpdateFlag = buffer[0] & 0xc0; + UpdateMethod = buffer[0] & 0x3c; + UpdatePriority = buffer[0] & 0x03; + + if (buffer.Length > 1) + { + PrivateData = new byte[buffer.Length - 1]; + Array.Copy(buffer, 1, PrivateData, 0, PrivateData.Length); + } + } + + public int UpdatePriority { get; private set; } + + public int UpdateMethod { get; private set; } + + public int UpdateFlag { get; private set; } + + public byte[] PrivateData { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x03_SsuLocationDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x03_SsuLocationDescriptor.cs new file mode 100644 index 0000000..4383c73 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x03_SsuLocationDescriptor.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x03,"UNT")] + class SsuLocationDescriptor : TsDescriptor + { + public SsuLocationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + DataBroadcastId = ms.ReadUInt16BE(); + if (DataBroadcastId == 0x000a) + { + AssociationTag = ms.ReadUInt16BE(); + } + + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] PrivateData { get; private set; } + + public ushort? AssociationTag { get; private set; } + + public ushort DataBroadcastId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x06_TargetSmartcardDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x06_TargetSmartcardDescriptor.cs new file mode 100644 index 0000000..e7126fc --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x06_TargetSmartcardDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x06,"UNT")] + class TargetSmartcardDescriptor : TsDescriptor + { + public TargetSmartcardDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SuperCaSystemId = ms.ReadUInt32BE(); + SmartCardNumber = BitConverter.ToString(ms.ReadBytes(ms.GetAvailableBytes())).Replace("-", ""); + } + + public string SmartCardNumber { get; set; } + + public uint SuperCaSystemId { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x07_TargetMacAddressDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x07_TargetMacAddressDescriptor.cs new file mode 100644 index 0000000..81e540a --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x07_TargetMacAddressDescriptor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x07,"UNT")] + class TargetMacAddressDescriptor : TsDescriptor + { + public TargetMacAddressDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + MacAddressMask = BitConverter.ToString(ms.ReadBytes(6)); + + long numMacAddressMatches = ms.GetAvailableBytes() / 6; + MacAddressMatches = new string[numMacAddressMatches]; + for (int i = 0; i < numMacAddressMatches; i++) + { + MacAddressMatches[i] = BitConverter.ToString(ms.ReadBytes(6)); + } + } + + public string[] MacAddressMatches { get; private set; } + + public string MacAddressMask { get; set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x08_TargetSerialNumberDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x08_TargetSerialNumberDescriptor.cs new file mode 100644 index 0000000..f4aa493 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x08_TargetSerialNumberDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x08,"UNT")] + class TargetSerialNumberDescriptor : TsDescriptor + { + public TargetSerialNumberDescriptor(byte[] buffer) + { + SerialData = buffer; + } + + public byte[] SerialData { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x0b_SsuSubgroupAssociationDescriptor.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x0b_SsuSubgroupAssociationDescriptor.cs new file mode 100644 index 0000000..b0b81c3 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Descriptors/0x0b_SsuSubgroupAssociationDescriptor.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0b,"UNT")] + class SsuSubgroupAssociationDescriptor : TsDescriptor + { + public SsuSubgroupAssociationDescriptor(byte[] buffer) + { + SubgroupTag = buffer; + } + + public byte[] SubgroupTag { get; private set; } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Model/Compatibility.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/Compatibility.cs new file mode 100644 index 0000000..86db2f4 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/Compatibility.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ionic.Crc; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Model +{ + public class Compatibility + { + public Compatibility(MemoryStream input) + { + if (input.GetAvailableBytes() < 2) + return; + DescriptorType = input.ReadUInt8(); + if (DescriptorType == 0) + return; + byte descriptorLength = input.ReadUInt8(); + if (descriptorLength == 0) + return; + if (DescriptorType >= 0x03) + { + if (input.GetAvailableBytes() >= descriptorLength) + PrivateData = input.ReadBytes(descriptorLength); + return; + } + if (input.GetAvailableBytes() < 6) + return; + SpecifierType = input.ReadUInt8(); + SpecifierData = BitConverter.ToString(input.ReadBytes(3)); + Model = input.ReadUInt16BE(); + if (input.GetAvailableBytes() < 2) + return; + Version = input.ReadUInt16BE(); + if (input.GetAvailableBytes() == 0) + + return; + byte subDescriptorCount = input.ReadUInt8(); + SubDescriptors = new Compatibility[subDescriptorCount]; + for (int i = 0; i < subDescriptorCount; i++) + { + if (input.GetAvailableBytes() == 0) + return; + SubDescriptors[i] = new Compatibility(input); + } + } + + public byte[] PrivateData { get; private set; } + public Compatibility[] SubDescriptors { get; } + public string SpecifierData { get; private set; } + public ushort Model { get; } + public byte SpecifierType { get; private set; } + public byte DescriptorType { get; private set; } + public ushort Version { get; } + + public override string ToString() + { + if (DescriptorType >= 0x03) + { + CRC32 crc32 = new CRC32(); + int hash = crc32.GetCrc32(new MemoryStream(PrivateData)); + return String.Format("Private Type {0:X2} ({0:X8})", DescriptorType, hash); + } + else + { + return String.Format("{0}, Model {1}", SpecifierData, Model); + } + } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Model/Platform.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/Platform.cs new file mode 100644 index 0000000..877d238 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/Platform.cs @@ -0,0 +1,64 @@ +using System; +using System.Text; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Model +{ + public class Platform + { + //descriptor 0x02 update descriptor + public int? UpdateFlag { get; set; } + public int? UpdateMethod { get; set; } + public int? UpdatePriority { get; set; } + + //descriptor 0x03 ssu location descriptor + public ushort? DataBroadcastId { get; set; } + public ushort? AssociationTag { get; set; } + public byte[] PrivateData { get; set; } + + //descriptor 0x06 target smartcard descriptor + public string SmartCardNumber { get; set; } + public uint? SuperCaSystemId { get; set; } + + //descriptor 0x08 target serial number descriptor + public byte[] SerialData { get; set; } + + //descriptor 0x07 target mac address descriptor + public string MacAddressMask { get; set; } + public string[] MacAddressMatches { get; set; } + + //descriptor 0x0b ssu subgroup association descriptor + public byte[] SubgroupTag { get; set; } + + public override string ToString() + { + if (AssociationTag.HasValue) + return AssociationTag.Value.ToString(); + else if (!string.IsNullOrEmpty(MacAddressMask)) + return MacAddressMask; + else + throw new NotImplementedException(); + } + + /// + /// Use this convenience function to build an identifying column. + /// + /// Depends on what descriptors are present + public int GetFlagsForSqlKey() + { + int result = 0; + if (UpdateFlag.HasValue) + result |= 0x02; + if (DataBroadcastId.HasValue) + result |= 0x04; + if (SuperCaSystemId.HasValue) + result |= 0x20; + if (SerialData != null) + result |= 0x80; + if (MacAddressMatches != null) + result |= 0x40; + if (SubgroupTag != null) + result |= 0x400; + return result; + } + } +} \ No newline at end of file diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Model/UpdateNotificationGroup.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/UpdateNotificationGroup.cs new file mode 100644 index 0000000..b40fc5e --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/UpdateNotificationGroup.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Model +{ + public class UpdateNotificationGroup + { + public byte ActionType { get; } + public byte OuiHash { get; } + public string Oui { get; } + public byte ProcessingOrder { get; } + + //from descriptor 0x03 ssu location descriptor + public ushort? DataBroadcastId { get; set; } + public ushort? AssociationTag { get; set; } + public byte[] PrivateData { get; set; } + + //from descriptor 0x02 update descriptor + public int? UpdateFlag { get; set; } + public int? UpdateMethod { get; set; } + public int? UpdatePriority { get; set; } + + + public UpdateNotificationGroup(byte actionType, byte ouiHash, string oui, byte processingOrder) + { + ActionType = actionType; + OuiHash = ouiHash; + Oui = oui; + ProcessingOrder = processingOrder; + } + + public override string ToString() + { + return $"{nameof(Oui)}: {Oui}"; + } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/Model/UpdateNotificationTarget.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/UpdateNotificationTarget.cs new file mode 100644 index 0000000..146b890 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/Model/UpdateNotificationTarget.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ionic.Crc; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate.Model +{ + public class UpdateNotificationTarget + { + public UpdateNotificationTarget() + { + Compatibilities = new List(); + Platforms = new List(); + } + + public List Compatibilities { get; private set; } + + public List Platforms { get; private set; } + + private void HashDescriptor(BinaryWriter bw, Compatibility compatibility) + { + bw.Write(compatibility.DescriptorType); + if (compatibility.DescriptorType >= 0x03) + { + bw.Write(compatibility.PrivateData); + return; + } + else + { + bw.Write(compatibility.Model); + bw.Write(compatibility.SpecifierData); + bw.Write(compatibility.SpecifierType); + bw.Write(compatibility.Version); + foreach (Compatibility compatibilitySubDescriptor in compatibility.SubDescriptors) + HashDescriptor(bw, compatibilitySubDescriptor); + } + } + public override int GetHashCode() + { + MemoryStream hccs = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(hccs); + foreach (Compatibility compatibility in Compatibilities) + { + HashDescriptor(bw, compatibility); + } + + hccs.Position = 0; + + CRC32 crc32 = new CRC32(); + return crc32.GetCrc32(hccs); + } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/SystemSoftwareUpdateInfo.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/SystemSoftwareUpdateInfo.cs new file mode 100644 index 0000000..a9a11db --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/SystemSoftwareUpdateInfo.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate +{ + class SystemSoftwareUpdateInfo : Validatable + { + public SystemSoftwareUpdateInfo(byte[] buffer) + { + if (buffer.Length == 0) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + byte ouiDataLength = ms.ReadUInt8(); + if (ouiDataLength > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + byte[] ouiBytes = ms.ReadBytes(ouiDataLength); + DecodeOuis(ouiBytes); + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + this.Valid = true; + } + + public byte[] PrivateData { get; private set; } + + private void DecodeOuis(byte[] buffer) + { + List result = new List(); + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 5) + { + Oui oui = new Oui(); + oui.OUI = BitConverter.ToString(ms.ReadBytes(3)); + byte readUInt8 = ms.ReadUInt8(); + oui.UpdateType = readUInt8 & 0x0f; + readUInt8 = ms.ReadUInt8(); + oui.UpdateVersioningFlag = readUInt8 & 0x20; + oui.UpdateVersion = readUInt8 & 0x1f; + byte selectorLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() >= selectorLength) + oui.Selector = ms.ReadBytes(selectorLength); + result.Add(oui); + } + + Ouis = result.AsReadOnly(); + } + + public ReadOnlyCollection Ouis { get; private set; } + + public class Oui + { + public string OUI { get; set; } + public int UpdateType { get; set; } + public int UpdateVersioningFlag { get; set; } + public int UpdateVersion { get; set; } + public byte[] Selector { get; set; } + } + + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/UntDecoder.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/UntDecoder.cs new file mode 100644 index 0000000..4110d43 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/UntDecoder.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate +{ + class UntDecoder : IPsiProcessor + { + public UpdateNotificationEventHandler EventHandler { get; } + public ushort ProgramNumber { get; } + public ProgramMapping ProgramMapping { get; } + + public UntDecoder(UpdateNotificationEventHandler eventHandler, ProgramMapping programMapping) + { + EventHandler = eventHandler; + ProgramNumber = programMapping.ProgramNumber; + ProgramMapping = programMapping; + } + + + public void GatherPsi(PsiSection section, int sourcePid) + { + byte[] buffer = section.GetDataCopy(); + MemoryStream ms = new MemoryStream(buffer, false); + byte tableId = ms.ReadUInt8(); + if (tableId != 0x4b) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + return; + int sectionLength = (readUInt16Be & 0x0fff); + + byte actionType = ms.ReadUInt8(); + byte ouiHash = ms.ReadUInt8(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = readUInt8 & 0x3e; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + string oui = BitConverter.ToString(ms.ReadBytes(3)); + byte processingOrder = ms.ReadUInt8(); + + UpdateNotificationGroup common = new UpdateNotificationGroup(actionType, ouiHash, oui, processingOrder); + CommonDescriptorLoop(ms, common); + while (ms.GetAvailableBytes() > 4) + { + UpdateNotificationTarget target = new UpdateNotificationTarget(); + CompatibilityDescriptorLoop(ms, target); + + ushort platformLoopLength = ms.ReadUInt16BE(); + if (ms.GetAvailableBytes() < platformLoopLength) + return; + byte[] platformLoopData = ms.ReadBytes(platformLoopLength); + PlatformLoop(platformLoopData, target); + EventHandler.UpdateNotification(common, target, ProgramNumber); + } + + uint crc32 = ms.ReadUInt32BE(); + } + + private void PlatformLoop(byte[] buffer, UpdateNotificationTarget output) + { + MemoryStream input = new MemoryStream(buffer, false); + + while (input.GetAvailableBytes() > 0) + { + Platform platform = new Platform(); + + //Target Descriptor Loop + int targetDescriptorLoopLength = input.ReadUInt16BE() & 0x0fff; + byte[] targetDescriptorData = input.ReadBytes(targetDescriptorLoopLength); + TargetDescriptorLoop(targetDescriptorData, platform); + + //Operational Descriptor Loop + int operationalDescriptorLoopLength = input.ReadUInt16BE() & 0x0fff; + byte[] operationalDescriptorData = input.ReadBytes(operationalDescriptorLoopLength); + OperationalDescriptorLoop(operationalDescriptorData, platform); + + output.Platforms.Add(platform); + } + } + + private void OperationalDescriptorLoop(byte[] input, Platform output) + { + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "UNT"); + foreach (TsDescriptor descriptor in unpackDescriptors) + { + string name = descriptor.GetType().Name; + switch (name) + { + case nameof(SchedulingDescriptor): + SchedulingDescriptor sd = (SchedulingDescriptor)descriptor; + if (!sd.StartDateTime.HasValue && !sd.EndDateTime.HasValue) + break; + throw new NotImplementedException(nameof(SchedulingDescriptor)); + case nameof(UpdateDescriptor): + UpdateDescriptor ud = (UpdateDescriptor)descriptor; + output.UpdateFlag = ud.UpdateFlag; + output.UpdateMethod = ud.UpdateMethod; + output.UpdatePriority = ud.UpdatePriority; + break; + case nameof(SsuLocationDescriptor): + SsuLocationDescriptor ssuld = (SsuLocationDescriptor)descriptor; + output.DataBroadcastId = ssuld.DataBroadcastId; + output.AssociationTag = ssuld.AssociationTag; + output.PrivateData = ssuld.PrivateData; + break; + case nameof(SsuSubgroupAssociationDescriptor): + SsuSubgroupAssociationDescriptor ssusgad = (SsuSubgroupAssociationDescriptor)descriptor; + output.SubgroupTag = ssusgad.SubgroupTag; + break; + default: + throw new NotImplementedException(name); + } + } + } + + private void TargetDescriptorLoop(byte[] input, Platform output) + { + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "UNT"); + foreach (TsDescriptor tsDescriptor in unpackDescriptors) + { + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(TargetSmartcardDescriptor): + TargetSmartcardDescriptor tsd = (TargetSmartcardDescriptor)tsDescriptor; + output.SmartCardNumber = tsd.SmartCardNumber; + output.SuperCaSystemId = tsd.SuperCaSystemId; + break; + case nameof(TargetSerialNumberDescriptor): + TargetSerialNumberDescriptor tsnd = (TargetSerialNumberDescriptor)tsDescriptor; + output.SerialData = tsnd.SerialData; + break; + case nameof(TargetMacAddressDescriptor): + TargetMacAddressDescriptor tmad = (TargetMacAddressDescriptor)tsDescriptor; + output.MacAddressMask = tmad.MacAddressMask; + output.MacAddressMatches = tmad.MacAddressMatches; + break; + default: + throw new NotImplementedException(name); + } + } + } + + private void CompatibilityDescriptorLoop(MemoryStream input, UpdateNotificationTarget output) + { + ushort compatibilityDescriptorLength = input.ReadUInt16BE(); + ushort descriptorCount = input.ReadUInt16BE(); + for (int i = 0; i < descriptorCount; i++) + { + Compatibility child = new Compatibility(input); + output.Compatibilities.Add(child); + } + } + + private void CommonDescriptorLoop(MemoryStream input, UpdateNotificationGroup output) + { + int length = input.ReadUInt16BE() & 0x0fff; + byte[] commonDescriptorLoop = input.ReadBytes(length); + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(commonDescriptorLoop, "UNT"); + foreach (TsDescriptor descriptor in unpackDescriptors) + { + string name = descriptor.GetType().Name; + switch (name) + { + case nameof(SsuLocationDescriptor): + SsuLocationDescriptor ssuld = (SsuLocationDescriptor)descriptor; + output.DataBroadcastId = ssuld.DataBroadcastId; + output.AssociationTag = ssuld.AssociationTag; + output.PrivateData = ssuld.PrivateData; + break; + case nameof(UpdateDescriptor): + UpdateDescriptor ud = (UpdateDescriptor)descriptor; + output.UpdateFlag = ud.UpdateFlag; + output.UpdateMethod = ud.UpdateMethod; + output.UpdatePriority = ud.UpdatePriority; + break; + default: + throw new NotImplementedException(name); + } + } + } + } +} diff --git a/skyscraper8/Dvb/SystemSoftwareUpdate/UpdateNotificationEventHandler.cs b/skyscraper8/Dvb/SystemSoftwareUpdate/UpdateNotificationEventHandler.cs new file mode 100644 index 0000000..b2b1ef7 --- /dev/null +++ b/skyscraper8/Dvb/SystemSoftwareUpdate/UpdateNotificationEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; + +namespace skyscraper5.Dvb.SystemSoftwareUpdate +{ + interface UpdateNotificationEventHandler + { + void UpdateNotification(UpdateNotificationGroup common, UpdateNotificationTarget target, ushort ProgramNumber); + } +} diff --git a/skyscraper8/Dvb/TvAnytime/DvbBinaryLocator.cs b/skyscraper8/Dvb/TvAnytime/DvbBinaryLocator.cs new file mode 100644 index 0000000..e16041d --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/DvbBinaryLocator.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.TvAnytime +{ + public class DvbBinaryLocator + { + public DvbBinaryLocator(MemoryStream ms) + { + ushort readUInt16Be = ms.ReadUInt16BE(); + IdentifierType = (readUInt16Be & 0xc000) >> 14; + ScheduledTimeReliability = (readUInt16Be & 0x2000) != 0; + InlineService = (readUInt16Be & 0x1000) != 0; + this.Reserved = (readUInt16Be & 0x0800) != 0; + StartDate = (readUInt16Be & 0x07fc) >> 2; + if (!InlineService) + { + DvbServiceTripletId = (readUInt16Be & 0x0003); + DvbServiceTripletId <<= 8; + DvbServiceTripletId += ms.ReadUInt8(); + } + else + { + TransportStreamId = ms.ReadUInt16BE(); + OriginalNetworkId = ms.ReadUInt16BE(); + ServiceId = ms.ReadUInt16BE(); + } + + StartTime = ms.ReadUInt16BE(); + Duration = new TimeSpan(0, 0, ms.ReadUInt16BE() * 2); + + if (IdentifierType == 1) + { + EventId = ms.ReadUInt16BE(); + } + + if (IdentifierType == 2) + { + TvaId = ms.ReadUInt16BE(); + } + + if (IdentifierType == 3) + { + TvaId = ms.ReadUInt16BE(); + Component = ms.ReadUInt8(); + } + + if (IdentifierType == 0 && ScheduledTimeReliability) + { + byte readUInt8 = ms.ReadUInt8(); + EarlyStartWindow = (readUInt8 & 0xe0) >> 5; + LateEndWindow = (readUInt8 & 0x1f); + } + } + + public int? LateEndWindow { get; set; } + + public int? EarlyStartWindow { get; set; } + + public byte? Component { get; set; } + + public ushort? TvaId { get; set; } + + public ushort? EventId { get; set; } + + public TimeSpan Duration { get; set; } + + public ushort StartTime { get; set; } + + public ushort? ServiceId { get; set; } + + public ushort? OriginalNetworkId { get; set; } + + public ushort? TransportStreamId { get; set; } + + public int? DvbServiceTripletId { get; private set; } + + public int StartDate { get; private set; } + + public bool Reserved { get; private set; } + + public bool InlineService { get; private set; } + + public bool ScheduledTimeReliability { get; private set; } + + public int IdentifierType { get; private set; } + } +} diff --git a/skyscraper8/Dvb/TvAnytime/Pid0x12Decoder.cs b/skyscraper8/Dvb/TvAnytime/Pid0x12Decoder.cs new file mode 100644 index 0000000..f7e33da --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/Pid0x12Decoder.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Dvb.TvAnytime +{ + internal class Pid0x12Decoder : IPsiProcessor + { + + public Pid0x12Decoder(IEitEventHandler eitEventHandler) + { + eitParser = new EitParser(eitEventHandler); + } + private EitParser eitParser; + + + public void GatherPsi(PsiSection section, int sourcePid) + { + byte[] data = section.GetData(); + byte tableId = data[0]; + byte syntax = data[1]; + bool sectionSyntaxIndicator = (syntax & 0x80) != 0; + bool privateIndicator = (syntax & 0x40) != 0; + + bool actualSchedule = ((tableId >= 0x50) && (tableId <= 0x5f)); + bool otherSchedule = ((tableId >= 0x60) && (tableId <= 0x6f)); + + if (tableId == 0x4e || tableId == 0x4f || actualSchedule || otherSchedule) + { + if (sectionSyntaxIndicator) + { + eitParser.GatherPsi(section, sourcePid); + } + return; + } + else if (tableId == 0x77 && sectionSyntaxIndicator && privateIndicator) + { + throw new NotImplementedException("CIT"); // ETSI TS 102 323 V1.5.1 (2012-01) + } + } + } +} diff --git a/skyscraper8/Dvb/TvAnytime/Rct.cs b/skyscraper8/Dvb/TvAnytime/Rct.cs new file mode 100644 index 0000000..8f49380 --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/Rct.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.TvAnytime +{ + public class Rct + { + public int ServiceId { get; set; } + public ushort YearOffset { get; set; } + public RctLinkInfo[] LinkInfo { get; set; } + } +} diff --git a/skyscraper8/Dvb/TvAnytime/RctEventHandler.cs b/skyscraper8/Dvb/TvAnytime/RctEventHandler.cs new file mode 100644 index 0000000..42004fd --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/RctEventHandler.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.TvAnytime +{ + internal interface IRctEventHandler + { + void OnRelatedContent(Rct result, ushort resultProgramNumber); + } +} diff --git a/skyscraper8/Dvb/TvAnytime/RctLinkInfo.cs b/skyscraper8/Dvb/TvAnytime/RctLinkInfo.cs new file mode 100644 index 0000000..87e3d36 --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/RctLinkInfo.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Text; + +namespace skyscraper5.Dvb.TvAnytime +{ + public class RctLinkInfo + { + public RctLinkInfo(byte[] linkInfoBytes) + { + //ETSI TS 102 323 V1.5.1 (2012-01), page 95 + MemoryStream ms = new MemoryStream(linkInfoBytes, false); + byte readUInt8 = ms.ReadUInt8(); + LinkType = (LinkTypeValue)((readUInt8 >> 4) & 0x0f); + HowRelatedClassificationSchemeId = (readUInt8 & 0x03); + HowRelatedClassificationSchemeId <<= 2; + + ushort readUInt16Be = ms.ReadUInt16BE(); + HowRelatedClassificationSchemeId += ((readUInt16Be & 0xf000) >> 12); + TermId = readUInt16Be & 0x0fff; + + readUInt8 = ms.ReadUInt8(); + GroupId = ((readUInt8 >> 4) & 0x0f); + Precedence = (readUInt8 & 0x0f); + + if (LinkType == LinkTypeValue.Uri || LinkType == LinkTypeValue.BothBinaryUri) + { + byte uriLength = ms.ReadUInt8(); + MediaUri = Encoding.ASCII.GetString(ms.ReadBytes(uriLength)); + } + + if (LinkType == LinkTypeValue.Binary || LinkType == LinkTypeValue.BothBinaryUri) + { + this.DvbBinaryLocator = new DvbBinaryLocator(ms); + } + + readUInt8 = ms.ReadUInt8(); + int numberItems = readUInt8 & 0x3f; + Items = new KeyValuePair[numberItems]; + for (int m = 0; m < numberItems; m++) + { + string key = Encoding.ASCII.GetString(ms.ReadBytes(3)); + byte promotionalTextLength = ms.ReadUInt8(); + string value = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(promotionalTextLength)); + Items[m] = new KeyValuePair(key, value); + } + + readUInt16Be = ms.ReadUInt16BE(); + DefaultIconFlag = (readUInt16Be & 0x8000) != 0; + IconId = (readUInt16Be & 0x7000) >> 12; + + int descriptorLoopLength = readUInt16Be & 0x0fff; + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(ms.ReadBytes(descriptorLoopLength), "RCT"); + foreach (TsDescriptor unpackDescriptor in unpackDescriptors) + { + switch (unpackDescriptor.GetType().Name) + { + case nameof(ShortEventDescriptor): + ShortEventDescriptor sed = (ShortEventDescriptor)unpackDescriptor; + this.EventName = sed.EventName; + this.EventLanguageCode = sed.Iso639LanguageCode; + this.EventText = sed.Text; + break; + default: + throw new NotImplementedException(unpackDescriptor.GetType().Name); + } + } + } + + public string EventText { get; set; } + + public string EventLanguageCode { get; set; } + + public string EventName { get; set; } + + public int IconId { get; private set; } + + public bool DefaultIconFlag { get; private set; } + + public KeyValuePair[] Items { get; set; } + + public DvbBinaryLocator DvbBinaryLocator { get; set; } + + public string MediaUri { get; private set; } + + public int Precedence { get; private set; } + + public int GroupId { get; private set; } + + public int TermId { get; private set; } + + public int HowRelatedClassificationSchemeId { get; private set; } + + public LinkTypeValue LinkType { get; private set; } + + public enum LinkTypeValue + { + Uri = 0, + Binary = 1, + BothBinaryUri = 2, + Descriptor = 3, + Reserved = 4, + } + + public string GetIdentifierString() + { + switch (LinkType) + { + case LinkTypeValue.Uri: + return MediaUri; + default: + throw new NotImplementedException(LinkType.ToString()); + } + } + } +} diff --git a/skyscraper8/Dvb/TvAnytime/RctParser.cs b/skyscraper8/Dvb/TvAnytime/RctParser.cs new file mode 100644 index 0000000..5ee773b --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/RctParser.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Dvb.TvAnytime +{ + internal class RctParser : IPsiProcessor + { + public IRctEventHandler EventHandler { get; } + public ushort ResultProgramNumber { get; } + + public RctParser(IRctEventHandler eventHandler, ushort resultProgramNumber) + { + EventHandler = eventHandler; + ResultProgramNumber = resultProgramNumber; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + //found in ETSI TS 102 323 V1.5.1 (2012-01) + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + + if (ms.ReadUInt8() != 0x76) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + return; + + bool tableIdExtensionFlag = (readUInt16Be & 0x4000) != 0; + int sectionLength = readUInt16Be & 0x0fff; + ms = new MemoryStream(ms.ReadBytes(sectionLength), false); + + Rct result = new Rct(); + result.ServiceId = ms.ReadUInt16BE(); + if (tableIdExtensionFlag) + result.ServiceId = 0; + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x7e) >> 1; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + result.YearOffset = ms.ReadUInt16BE(); + + byte linkCount = ms.ReadUInt8(); + result.LinkInfo = new RctLinkInfo[linkCount]; + for (int j = 0; j < linkCount; j++) + { + readUInt16Be = ms.ReadUInt16BE(); + int linkInfoLength = readUInt16Be & 0x0fff; + byte[] linkInfoBytes = ms.ReadBytes(linkInfoLength); + result.LinkInfo[j] = new RctLinkInfo(linkInfoBytes); + } + + readUInt16Be = ms.ReadUInt16BE(); + int descriptorLoopLength = readUInt16Be & 0x0fff; + if (descriptorLoopLength > 0) + { + byte[] descriptorLoop = ms.ReadBytes(descriptorLoopLength); + throw new NotImplementedException(); + } + + uint crc32 = ms.ReadUInt32BE(); + EventHandler.OnRelatedContent(result, ResultProgramNumber); + } + } +} diff --git a/skyscraper8/Dvb/TvAnytime/RntEventHandler.cs b/skyscraper8/Dvb/TvAnytime/RntEventHandler.cs new file mode 100644 index 0000000..371f7be --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/RntEventHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Dvb.TvAnytime +{ + interface IRntEventHandler + { + } +} diff --git a/skyscraper8/Dvb/TvAnytime/RntParser.cs b/skyscraper8/Dvb/TvAnytime/RntParser.cs new file mode 100644 index 0000000..c22618e --- /dev/null +++ b/skyscraper8/Dvb/TvAnytime/RntParser.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Dvb.TvAnytime +{ + class RntParser : IPsiProcessor + { + public IRntEventHandler EventHandler { get; } + + public RntParser(IRntEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Dvb/UserDefinedDescriptorAttribute.cs b/skyscraper8/Dvb/UserDefinedDescriptorAttribute.cs new file mode 100644 index 0000000..7c39bb3 --- /dev/null +++ b/skyscraper8/Dvb/UserDefinedDescriptorAttribute.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Dvb +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class UserDefinedDescriptorAttribute : Attribute + { + public uint PrivateDataSpecifier { get; } + public byte DescriptorId { get; } + public string[] CompatibleTables { get; } + + public UserDefinedDescriptorAttribute(uint privateDataSpecifier, byte descriptorId, params string[] compatibleTables) + { + PrivateDataSpecifier = privateDataSpecifier; + DescriptorId = descriptorId; + CompatibleTables = compatibleTables; + } + + public bool Matches(uint privateDataSpecifier, string table) + { + foreach (string compatibleTable in CompatibleTables) + { + if (compatibleTable == table && privateDataSpecifier == this.PrivateDataSpecifier) + return true; + } + + return false; + } + } +} diff --git a/skyscraper8/Dvb/UserDefinedDescriptorUnpacker.cs b/skyscraper8/Dvb/UserDefinedDescriptorUnpacker.cs new file mode 100644 index 0000000..94eec68 --- /dev/null +++ b/skyscraper8/Dvb/UserDefinedDescriptorUnpacker.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Dvb +{ + //TODO: can we get these? https://www.streamguru.de/mpeg-analyzer/supported-descriptor-list/supported-descriptor-list-user-private-ranges/ + + class UserDefinedDescriptorUnpacker + { + private const bool WARN_ON_MISSING = true; + private static UserDefinedDescriptorUnpacker _instance; + + public static UserDefinedDescriptorUnpacker GetInstance() + { + if (_instance == null) + { + _instance = new UserDefinedDescriptorUnpacker(); + } + return _instance; + } + + private UserDefinedDescriptorUnpacker() + { + } + + private bool IsBannedTable(ConstructorInfo constructorInfo, string askingTable) + { + Type sourceType = constructorInfo.DeclaringType; + Attribute attribute = sourceType.GetCustomAttribute(typeof(BannedTableAttribute)); + if (attribute == null) + return false; + BannedTableAttribute bannedTableAttribute = (BannedTableAttribute)attribute; + foreach (string table in bannedTableAttribute.Tables) + { + if (table.Equals(askingTable)) + return true; + } + return false; + } + + private IReadOnlyDictionary constructors; + + public TsDescriptor UnpackUserDefinedDescriptor(UserDefinedDescriptor userDefinedDescriptor, uint privateDataSpecifier, string lookupTable) + { + if (constructors == null) + constructors = PluginManager.GetInstance().GetUserDefinedDescriptors(); + + + foreach (KeyValuePair keyValuePair in constructors) + { + UserDefinedDescriptorAttribute key = keyValuePair.Key; + if (key.DescriptorId != userDefinedDescriptor.DescriptorTag) + continue; + if (IsBannedTable(keyValuePair.Value, lookupTable)) + return null; + if (!key.Matches(privateDataSpecifier,lookupTable)) + continue; + + ConstructorInfo constructorInfo = keyValuePair.Value; + return (TsDescriptor)constructorInfo.Invoke(new object[] { userDefinedDescriptor.Data }); + } + WarnMissingDescriptor(userDefinedDescriptor.DescriptorTag, privateDataSpecifier); + return null; + } + + private HashSet> warned; + + private void WarnMissingDescriptor(byte descriptorTag, uint privateDataSpecifier, bool silent = false) + { + if (!WARN_ON_MISSING) + return; + + if (warned == null) + warned = new HashSet>(); + + if (warned.Add(new KeyValuePair(descriptorTag, privateDataSpecifier))) + { + if (!silent) + { + Debug.WriteLine("Missing descriptor 0x{0:X2} for private data specifier 0x{0:X8}", descriptorTag, + privateDataSpecifier); + } + } + } + } +} diff --git a/skyscraper8/Hdmv/Descriptors/0x88_CopyControlDescriptor.cs b/skyscraper8/Hdmv/Descriptors/0x88_CopyControlDescriptor.cs new file mode 100644 index 0000000..02e6edb --- /dev/null +++ b/skyscraper8/Hdmv/Descriptors/0x88_CopyControlDescriptor.cs @@ -0,0 +1,72 @@ +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper; + +namespace skyscraper5.Hdmv.Descriptors +{ + [UserDefinedDescriptor(0x48444d56, 0x88, "PMT")] + internal class CopyControlDescriptor : TsDescriptor + { + public CopyControlDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CaSystemId = ms.ReadUInt16BE(); + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + if (CaSystemId == 0x0fff) + PrivateData = new AacsPrivateData((byte[])PrivateData); + Valid = true; + } + + public ushort CaSystemId { get; } + + public object PrivateData { get; } + + public class AacsPrivateData : Validatable + { + public AacsPrivateData(byte[] buffer) + { + if (buffer.Length != 2) + { + Valid = false; + return; + } + (buffer[0], buffer[1]) = (buffer[1], buffer[0]); + ushort data = BitConverter.ToUInt16(buffer, 0); + RetentionMoveMode = (data & 0x4000) != 0; + RetentionState = (data & 0x3800); + EPN = (data & 0x0400) != 0; + CCI = (CciMeanings)((data & 0x0300) >> 8); + //Reserved = (data & 0x00f8); + ImageConstraintToken = (data & 0x0004) != 0; + ApsType = (data & 0x0002); + + Valid = true; + OriginalData = buffer; + } + + public bool RetentionMoveMode { get; } + public int RetentionState { get; } + public bool EPN { get; } + public CciMeanings CCI { get; } + public bool ImageConstraintToken { get; } + public int ApsType { get; } + + public enum CciMeanings + { + CopyControlNotAsserted = 0, + ReservedForNoMoreCopy = 1, + CopyOneGeneration = 2, + NeverCopy = 3 + } + + public byte[] OriginalData { get; } + } + } +} diff --git a/skyscraper8/Hdmv/HdmvReader.cs b/skyscraper8/Hdmv/HdmvReader.cs new file mode 100644 index 0000000..7648018 --- /dev/null +++ b/skyscraper8/Hdmv/HdmvReader.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Hdmv +{ + internal class HdmvReader : IDisposable + { + public Stream Wrapped { get; } + + public HdmvReader(Stream wrapped) + { + Wrapped = wrapped; + if (!wrapped.CanRead) + throw new ArgumentException("not readable", nameof(wrapped)); + + buffer = new byte[188]; + } + + private byte[] buffer; + public byte[] ReadPacket() + { + if (Wrapped.Read(buffer, 0, 4) != 4) + return null; + + if (Wrapped.Read(buffer, 0, 188) != 188) + return null; + + if (buffer[0] != 0x47) + return null; + + return buffer; + } + + public void Dispose() + { + Wrapped?.Dispose(); + } + } +} diff --git a/skyscraper8/Hdmv/M2TsToTs.cs b/skyscraper8/Hdmv/M2TsToTs.cs new file mode 100644 index 0000000..a73142c --- /dev/null +++ b/skyscraper8/Hdmv/M2TsToTs.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Hdmv +{ + internal class M2TsToTs + { + public FileInfo Infile { get; } + public FileInfo Outfile { get; } + + public M2TsToTs(FileInfo infile, FileInfo outfile) + { + Infile = infile; + Outfile = outfile; + } + + public void Run() + { + FileStream inStream = Infile.OpenRead(); + FileStream outStream = Outfile.OpenWrite(); + HdmvReader hdmvReader = new HdmvReader(inStream); + byte[] buffer; + + while (true) + { + byte[] readPacket = hdmvReader.ReadPacket(); + if (readPacket != null) + outStream.Write(readPacket, 0, 188); + else + break; + } + inStream.Close(); + outStream.Flush(); + outStream.Close(); + } + } +} diff --git a/skyscraper8/Id3/Frames/PrivateFrame.cs b/skyscraper8/Id3/Frames/PrivateFrame.cs new file mode 100644 index 0000000..bd5306c --- /dev/null +++ b/skyscraper8/Id3/Frames/PrivateFrame.cs @@ -0,0 +1,24 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Id3.Frames +{ + internal class PrivateFrame : Id3Frame + { + public PrivateFrame(byte[] buffer, ushort flags) + : base(flags) + { + MemoryStream ms = new MemoryStream(buffer, false); + OwnerIdentifier = ms.ReadAsciiNullTerminated(); + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public string OwnerIdentifier { get; } + public byte[] PrivateData { get; } + } +} diff --git a/skyscraper8/Id3/Id3ErrorState.cs b/skyscraper8/Id3/Id3ErrorState.cs new file mode 100644 index 0000000..9d30317 --- /dev/null +++ b/skyscraper8/Id3/Id3ErrorState.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Id3 +{ + internal enum Id3ErrorState + { + InvalidMagic, + PacketTooShort + } +} diff --git a/skyscraper8/Id3/Id3Frame.cs b/skyscraper8/Id3/Id3Frame.cs new file mode 100644 index 0000000..a7bb44a --- /dev/null +++ b/skyscraper8/Id3/Id3Frame.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Id3 +{ + internal abstract class Id3Frame + { + protected Id3Frame(ushort entryFlags) + { + TagAlterPreservation = (entryFlags & 0x4000) != 0; + FileAlterPreservation = (entryFlags & 0x2000) != 0; + ReadOnly = (entryFlags & 0x1000) != 0; + GroupingIdentity = (entryFlags & 0x0040) != 0; + Compression = (entryFlags & 0x0008) != 0; + Encryption = (entryFlags & 0x0004) != 0; + Unsynchronisation = (entryFlags & 0x0002) != 0; + DataLengthIndicator = (entryFlags & 0x0001) != 0; + } + + public bool TagAlterPreservation { get; } + public bool FileAlterPreservation { get; } + public bool ReadOnly { get; } + public bool GroupingIdentity { get; } + public bool Compression { get; } + public bool Encryption { get; } + public bool Unsynchronisation { get; } + public bool DataLengthIndicator { get; } + } +} diff --git a/skyscraper8/Id3/Id3Handler.cs b/skyscraper8/Id3/Id3Handler.cs new file mode 100644 index 0000000..a2ce52f --- /dev/null +++ b/skyscraper8/Id3/Id3Handler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Id3 +{ + internal interface Id3Handler + { + void OnId3Error(Id3ErrorState state); + void OnId3Tag(Id3Tag tag); + } +} diff --git a/skyscraper8/Id3/Id3PesProcessor.cs b/skyscraper8/Id3/Id3PesProcessor.cs new file mode 100644 index 0000000..ef2aeaa --- /dev/null +++ b/skyscraper8/Id3/Id3PesProcessor.cs @@ -0,0 +1,92 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Scraper.Utils; +using skyscraper5.src.Id3.Frames; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Id3 +{ + internal class Id3PesProcessor : IPesProcessor + { + public Id3PesProcessor(Id3Handler handler) + { + this.handler = handler; + } + + private Id3Handler handler; + + public void ProcessPesPacket(PesPacket packet) + { + MemoryStream ms = new MemoryStream(packet.Payload, false); + if (ms.ReadUInt8() != 'I') + { + handler.OnId3Error(Id3ErrorState.InvalidMagic); + return; + } + if (ms.ReadUInt8() != 'D') + { + handler.OnId3Error(Id3ErrorState.InvalidMagic); + return; + } + if (ms.ReadUInt8() != '3') + { + handler.OnId3Error(Id3ErrorState.InvalidMagic); + return; + } + + byte major = ms.ReadUInt8(); + byte minor = ms.ReadUInt8(); + Version version = new Version(2, major, minor); + Id3Tag frame = new Id3Tag(version); + + byte flags = ms.ReadUInt8(); + frame.Unsynchronisation = ((flags & 0x80) != 0); + bool extendedHeaderPresent = ((flags & 0x40) != 0); + frame.ExperimentalIndicator = ((flags & 0x20) != 0); + bool footerPresent = ((flags & 0x10) != 0); + + uint size = ms.ReadUInt32BE(); + if (ms.GetAvailableBytes() < size) + { + handler.OnId3Error(Id3ErrorState.PacketTooShort); + return; + } + + if (extendedHeaderPresent) + { + throw new NotImplementedException("ID3 extended header"); + } + + long dataLeft = size; + while (dataLeft > 0) + { + uint entryFourcc = ms.ReadUInt32BE(); + dataLeft -= 4; + uint entrySize = ms.ReadUInt32BE(); + dataLeft -= 4; + ushort entryFlags = ms.ReadUInt16BE(); + dataLeft -= 2; + byte[] entryData = ms.ReadBytes(entrySize); + dataLeft -= entrySize; + + Id3Frame child = null; + switch(entryFourcc) + { + case 1347570006: //PRIV + child = new PrivateFrame(entryData, entryFlags); + break; + default: + string v = Encoding.ASCII.GetString(BitConverter.GetBytes(entryFourcc)); + throw new NotImplementedException(String.Format("ID3 Frame {0}", v)); + } + frame.Frames.Add(child); + } + handler.OnId3Tag(frame); + } + } +} diff --git a/skyscraper8/Id3/Id3Tag.cs b/skyscraper8/Id3/Id3Tag.cs new file mode 100644 index 0000000..c4dbf4c --- /dev/null +++ b/skyscraper8/Id3/Id3Tag.cs @@ -0,0 +1,33 @@ +using skyscraper5.src.Id3.Frames; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Id3 +{ + internal class Id3Tag + { + public Id3Tag(Version version) + { + Version = version; + this.Frames = new List(); + } + + public Version Version { get; } + public List Frames { get; } + public bool Unsynchronisation { get; internal set; } + public bool ExperimentalIndicator { get; internal set; } + + internal bool IsAllPrivate() + { + foreach (var frame in Frames) + { + if (!(frame is PrivateFrame)) + return false; + } + return true; + } + } +} diff --git a/skyscraper8/Ietf/Rfc2236/IgmpMessage.cs b/skyscraper8/Ietf/Rfc2236/IgmpMessage.cs new file mode 100644 index 0000000..b02c54e --- /dev/null +++ b/skyscraper8/Ietf/Rfc2236/IgmpMessage.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Ietf.Rfc2236 +{ + internal class IgmpMessage + { + public IgmpMessage(byte[] buffer) + { + Type = (IgmpType)buffer[0]; + MaxResponseTime = new TimeSpan(0, 0, 0, 0, ((int)buffer[1]) * 100); + Checksum = BitConverter.ToUInt16(buffer, 2); + + ReadOnlySpan rosb = new ReadOnlySpan(buffer, 4, 4); + GroupAddress = new IPAddress(rosb); + } + + public IPAddress GroupAddress { get; private set; } + + public ushort Checksum { get; private set; } + + public TimeSpan MaxResponseTime { get; private set; } + + public IgmpType Type { get; private set; } + } +} diff --git a/skyscraper8/Ietf/Rfc2236/IgmpType.cs b/skyscraper8/Ietf/Rfc2236/IgmpType.cs new file mode 100644 index 0000000..fdc66f5 --- /dev/null +++ b/skyscraper8/Ietf/Rfc2236/IgmpType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Ietf.Rfc2236 +{ + internal enum IgmpType : byte + { + Query = 0x11, + Igmp1Membership = 0x12, + Igmp2Membership = 0x16, + Igmp2QuitMembership = 0x17, + Igmp3Membership = 0x22 + } +} diff --git a/skyscraper8/Ietf/Rfc2460/Ipv6Header.cs b/skyscraper8/Ietf/Rfc2460/Ipv6Header.cs new file mode 100644 index 0000000..b0bba55 --- /dev/null +++ b/skyscraper8/Ietf/Rfc2460/Ipv6Header.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Ietf.Rfc2460 +{ + internal class Ipv6Header : Validatable + { + public Ipv6Header(byte[] buffer) + { + if (buffer.Length < 40) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + Version = (readUInt8 & 0xf0) >> 4; + TrafficClass = (readUInt8 & 0x0f); + TrafficClass <<= 4; + + readUInt8 = ms.ReadUInt8(); + TrafficClass += (readUInt8 & 0xf0) >> 4; + FlowLabel = (readUInt8 & 0x0f); + + FlowLabel += ms.ReadUInt16BE(); + + PayloadLength = ms.ReadUInt16BE(); + NextHeader = ms.ReadUInt8(); + HopLimit = ms.ReadUInt8(); + SourceAddress = new IPAddress(ms.ReadBytes(16)); + DestinationAddress = new IPAddress(ms.ReadBytes(16)); + Valid = true; + } + + public IPAddress DestinationAddress { get; set; } + + public IPAddress SourceAddress { get; set; } + + public byte HopLimit { get; set; } + + public byte NextHeader { get; set; } + + public ushort PayloadLength { get; set; } + + public int Version { get; } + + public int TrafficClass { get; } + + public int FlowLabel { get; } + } +} diff --git a/skyscraper8/Ietf/Rfc768/UserDatagram.cs b/skyscraper8/Ietf/Rfc768/UserDatagram.cs new file mode 100644 index 0000000..d04b082 --- /dev/null +++ b/skyscraper8/Ietf/Rfc768/UserDatagram.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Ietf.Rfc768 +{ + public class UserDatagram : Validatable + { + public UserDatagram(byte[] buffer) + { + if (buffer.Length < 8) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + SourcePort = ms.ReadUInt16BE(); + DestinationPort = ms.ReadUInt16BE(); + ushort length = ms.ReadUInt16BE(); + ushort checksum = ms.ReadUInt16BE(); + Payload = ms.ReadBytes(ms.GetAvailableBytes()); + Valid = true; + } + + public byte[] Payload { get; private set; } + + public ushort DestinationPort { get; set; } + + public ushort SourcePort { get; private set; } + } +} diff --git a/skyscraper8/Ietf/Rfc826/ArpHeader.cs b/skyscraper8/Ietf/Rfc826/ArpHeader.cs new file mode 100644 index 0000000..c9f7c03 --- /dev/null +++ b/skyscraper8/Ietf/Rfc826/ArpHeader.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Ietf.Rfc826 +{ + internal class ArpHeader + { + public ArpHeader(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + HardwareType = ms.ReadUInt16BE(); + ProtocolType = ms.ReadUInt16BE(); + HardwareLength = ms.ReadUInt8(); + ProtocolLength = ms.ReadUInt8(); + Operation = (OperationEnum)ms.ReadUInt16BE(); + SenderHardwareAddress = new PhysicalAddress(ms.ReadBytes(HardwareLength)); + SenderProtocolAddress = new IPAddress(ms.ReadBytes(ProtocolLength)); + TargetHardwareAddress = new PhysicalAddress(ms.ReadBytes(HardwareLength)); + TargetProtocolAddress = new IPAddress(ms.ReadBytes(ProtocolLength)); + } + + public IPAddress TargetProtocolAddress { get; set; } + + public PhysicalAddress TargetHardwareAddress { get; set; } + + public IPAddress SenderProtocolAddress { get; set; } + + public PhysicalAddress SenderHardwareAddress { get; set; } + + public byte ProtocolLength { get; set; } + + public byte HardwareLength { get; set; } + + public OperationEnum Operation { get; private set; } + public enum OperationEnum : ushort + { + Request = 1, + Reply = 2 + } + public ushort HardwareType { get; set; } + public ushort ProtocolType { get; private set; } + + } +} diff --git a/skyscraper8/Ietf/Rfc971/InternetHeader.cs b/skyscraper8/Ietf/Rfc971/InternetHeader.cs new file mode 100644 index 0000000..13111a3 --- /dev/null +++ b/skyscraper8/Ietf/Rfc971/InternetHeader.cs @@ -0,0 +1,119 @@ +using System.IO; +using System.Net; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Ietf.Rfc971 +{ + public class InternetHeader + { + public InternetHeader(byte[] ipv4Header) + { + if (ipv4Header.Length < 20) + { + ChecksumValid = false; + return; + } + + MemoryStream ms = new MemoryStream(ipv4Header, false); + byte readUInt8 = ms.ReadUInt8(); + Version = (readUInt8 & 0xf0) >> 4; + IHL = (readUInt8 & 0x0f); + + TOS = new TypeOfService(ms.ReadUInt8()); + TotalLength = ms.ReadUInt16BE(); + Identification = ms.ReadUInt16BE(); + + ushort readUInt16Be = ms.ReadUInt16BE(); + DontFragment = (readUInt16Be & 0x4000) != 0; + MoreFragments = (readUInt16Be & 0x2000) != 0; + FragmentOffset = (readUInt16Be & 0x1fff); + + TimeToLive = ms.ReadUInt8(); + Protocol = ms.ReadUInt8(); + HeaderChecksum = ms.ReadUInt16BE(); + + HeaderLength = IHL * 4; + + SourceAddress = new IPAddress(ms.ReadBytes(4)); + DestinationAddress = new IPAddress(ms.ReadBytes(4)); + + int optionsLength = HeaderLength - (int)ms.Position; + + Options = ms.ReadBytes(optionsLength); + CalculateChecksum(ipv4Header, IHL); + } + + public int HeaderLength { get; private set; } + + private void CalculateChecksum(byte[] buffer, int ihl) + { + MemoryStream ms = new MemoryStream(buffer, false); + ushort checksum = ms.ReadUInt16BE(); //Version + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Total Length + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Identification + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Flags + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //TTL + + ms.ReadUInt16BE(); + AddWithCarry(ref checksum, 0); //Checksum + + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Source IP + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Source IP + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Destination IP + AddWithCarry(ref checksum, ms.ReadUInt16BE()); //Destination IP + if (IHL > 5) + { + int left = IHL; + left -= 5; + for (int i = 0; i < left; i++) + { + AddWithCarry(ref checksum, ms.ReadUInt16BE()); + AddWithCarry(ref checksum, ms.ReadUInt16BE()); + } + } + + checksum = (ushort)~checksum; + + CalculatedChecksum = (ushort)checksum; + ChecksumValid = CalculatedChecksum == HeaderChecksum; + } + + private void AddWithCarry(ref ushort checksum, ushort addMe) + { + ushort oldChecksum = checksum; + checksum += addMe; + if (checksum < oldChecksum) + checksum++; + } + + public byte[] Options { get; private set; } + + public IPAddress DestinationAddress { get; private set; } + + public IPAddress SourceAddress { get; private set; } + + public ushort HeaderChecksum { get; private set; } + + public byte Protocol { get; private set; } + + public byte TimeToLive { get; private set; } + + public int FragmentOffset { get; private set; } + + public bool MoreFragments { get; private set; } + + public bool DontFragment { get; private set; } + + public ushort Identification { get; private set; } + + public ushort TotalLength { get; private set; } + + public TypeOfService TOS { get; private set; } + public int IHL { get; private set; } + + public int Version { get; private set; } + + public bool ChecksumValid { get; private set; } + public ushort CalculatedChecksum { get; private set; } + } +} diff --git a/skyscraper8/Ietf/Rfc971/TypeOfService.cs b/skyscraper8/Ietf/Rfc971/TypeOfService.cs new file mode 100644 index 0000000..8761598 --- /dev/null +++ b/skyscraper8/Ietf/Rfc971/TypeOfService.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Ietf.Rfc971 +{ + public class TypeOfService + { + public TypeOfService(byte typeOfService) + { + Precedence = (PrecedenceEnum)((typeOfService & 0xe0) >> 5); + LowDelay = (typeOfService & 0x10) != 0; + HighThroughput = (typeOfService & 0x80) != 0; + HighReliability = (typeOfService & 0x40) != 0; + } + + + public bool HighReliability { get; private set; } + + public bool HighThroughput { get; private set; } + + public bool LowDelay { get; private set; } + + public PrecedenceEnum Precedence { get; private set; } + + public enum PrecedenceEnum + { + Routine = 0, + Priority = 1, + Immediate = 2, + Flash = 3, + FlashOverride = 4, + Critic = 5, + InternetworkControl = 6, + NetworkControl = 7 + } + } +} diff --git a/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs b/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs new file mode 100644 index 0000000..1162d41 --- /dev/null +++ b/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel +{ + internal enum InteractionChannelErrorState + { + UnexpectedTable, + HeaderInvalid, + CmtInvalid, + TbtpInvalid, + SctInvalid, + FctInvalid, + TctInvalid, + SptInvalid, + RmtInvalid, + TimInvalid, + TmstInvalid, + Fct2Invalid, + Tbtp2Invalid, + Tmst2Invalid + } +} diff --git a/skyscraper8/InteractionChannel/InteractionChannelException.cs b/skyscraper8/InteractionChannel/InteractionChannelException.cs new file mode 100644 index 0000000..8f6e5d6 --- /dev/null +++ b/skyscraper8/InteractionChannel/InteractionChannelException.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel +{ + + [Serializable] + public class InteractionChannelException : SkyscraperException + { + public InteractionChannelException() { } + public InteractionChannelException(string message) : base(message) { } + public InteractionChannelException(string message, Exception inner) : base(message, inner) { } + protected InteractionChannelException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + + + [Serializable] + public class NoInteractionChannelHandlerException : Exception + { + public NoInteractionChannelHandlerException() { } + public NoInteractionChannelHandlerException(string message) : base(message) { } + public NoInteractionChannelHandlerException(string message, Exception inner) : base(message, inner) { } + protected NoInteractionChannelHandlerException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/skyscraper8/InteractionChannel/InteractionChannelHandler.cs b/skyscraper8/InteractionChannel/InteractionChannelHandler.cs new file mode 100644 index 0000000..a746e21 --- /dev/null +++ b/skyscraper8/InteractionChannel/InteractionChannelHandler.cs @@ -0,0 +1,30 @@ +using skyscraper5.Mpeg2; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel +{ + internal interface InteractionChannelHandler + { + void OnCorrectionMessage(ushort interactiveNetworkId, Cmt cmt); + void OnFrameComposition(ushort interactiveNetworkId, Fct fct); + void OnInteractionChannelError(InteractionChannelErrorState unexpectedTable); + void OnRcsMap(Rmt rmt); + void OnSatellitePosition(ushort interactiveNetworkId, Spt spt); + void OnSuperframeComposition(ushort interactiveNetworkId, Sct sct); + void OnTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp); + void OnTimeslotComposition(ushort interactiveNetworkId, Tct tct); + void OnCorrectionMessage(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd); + void OnContentionControl(PhysicalAddress macAddress, _0xab_ContentionControlDescriptor ccd); + void OnCorrectionControl(PhysicalAddress macAddress, _0xac_CorrectionControlDescriptor descriptor); + void OnNetworkLayerInfo(PhysicalAddress macAddress, _0xa0_NetworkLayerInfoDescriptor descriptor); + void OnTransmissionModeSupport(ushort interactiveNetworkId, Tmst tmst); + int GetRmtTransmissionStandard(ushort networkId); + } +} diff --git a/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs b/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs new file mode 100644 index 0000000..761c9bc --- /dev/null +++ b/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs @@ -0,0 +1,287 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model2; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel +{ + internal class InteractionChannelPsiGatherer : IPsiProcessor + { + private UserDefinedDescriptor _expectedTable; + public UserDefinedDescriptor ExpectedTables + { + get => _expectedTable; + set + { + if (value.DescriptorTag != 0xa7) + { + throw new ArgumentException(String.Format("Expected a {0} descriptor.", 0xa7)); + } + _expectedTable = value; + } + } + + private InteractionChannelHandler _handler; + public InteractionChannelHandler Handler + { + get => _handler; + set => _handler = value; + } + + public ushort? NetworkId { get; private set; } + + + //Maybe also check ETSI EN 301 545-2 V1.4.0 (2023-10) ? + + //en_301790v010501p_RCS_Map.pdf + //page 78 + public void GatherPsi(PsiSection section, int sourcePid) + { + if (_handler == null) + { + throw new NoInteractionChannelHandlerException(); + } + byte[] buffer = section.GetDataCopy(); + if (_expectedTable != null) + { + if (!_expectedTable.Data.Contains(buffer[0])) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.UnexpectedTable); + return; + } + } + MemoryStream ms = new MemoryStream(buffer, false); + switch (buffer[0]) + { + case 0x40: //NIT + //see ETSI EN 301 545-2 + throw new NotImplementedException("NIT"); + case 0x41: //RMT + Rmt rmt = new Rmt(ms); + if (!rmt.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.RmtInvalid); + return; + } + _handler.OnRcsMap(rmt); + return; + case 0x4D: //SAT + throw new NotImplementedException("SAT"); + case 0x70: //TDT + throw new NotImplementedException("TDT"); + case 0xA0: //SCT + InteractionChannelSiSectionHeader sctHeader = new InteractionChannelSiSectionHeader(ms); + if (!sctHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = sctHeader.InteractiveNetworkId; + Sct sct = new Sct(ms); + if (!sct.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.SctInvalid); + return; + } + Handler.OnSuperframeComposition(sctHeader.InteractiveNetworkId, sct); + return; + case 0xA1: //FCT + InteractionChannelSiSectionHeader fctHeader = new InteractionChannelSiSectionHeader(ms); + if (!fctHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = fctHeader.InteractiveNetworkId; + Fct fct = new Fct(ms); + if (!fct.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.FctInvalid); + return; + } + Handler.OnFrameComposition(fctHeader.InteractiveNetworkId, fct); + return; + case 0xA2: //TCT + InteractionChannelSiSectionHeader tctHeader = new InteractionChannelSiSectionHeader(ms); + if (!tctHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = tctHeader.InteractiveNetworkId; + Tct tct = new Tct(ms); + if (!tct.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.TctInvalid); + return; + } + Handler.OnTimeslotComposition(tctHeader.InteractiveNetworkId, tct); + return; + case 0xA3: //SPT + InteractionChannelSiSectionHeader sptHeader = new InteractionChannelSiSectionHeader(ms); + if (!sptHeader.Valid) + { + _handler.OnInteractionChannelError (InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = sptHeader.InteractiveNetworkId; + Spt spt = new Spt(ms); + if (!spt.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.SptInvalid); + return; + } + Handler.OnSatellitePosition(sptHeader.InteractiveNetworkId, spt); + return; + case 0xA4: //CMT + InteractionChannelSiSectionHeader cmtHeader = new InteractionChannelSiSectionHeader(ms); + if (!cmtHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = cmtHeader.InteractiveNetworkId; + Cmt cmt = new Cmt(ms); + if (!cmt.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.CmtInvalid); + return; + } + Handler.OnCorrectionMessage(cmtHeader.InteractiveNetworkId, cmt); + return; + case 0xA5: //TBTP + InteractionChannelSiSectionHeader tbtpHeader = new InteractionChannelSiSectionHeader(ms); + if (!tbtpHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = tbtpHeader.InteractiveNetworkId; + Tbtp tbtp = new Tbtp(ms); + if (!tbtp.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.TbtpInvalid); + return; + } + Handler.OnTerminalBurstTimePlan(tbtpHeader.InteractiveNetworkId, tbtp); + return; + case 0xA6: //PCR packet payload + throw new NotImplementedException("PCR packet payload"); + case 0xAA: //Transmission Mode Support Table + InteractionChannelSiSectionHeader tmstHeader = new InteractionChannelSiSectionHeader(ms); + if (!tmstHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = tmstHeader.InteractiveNetworkId; + Tmst tmst = new Tmst(ms); + if (!tmst.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.TmstInvalid); + return; + } + Handler.OnTransmissionModeSupport(tmstHeader.InteractiveNetworkId, tmst); + return; + case 0xAB: //FCT2 + InteractionChannelSiSectionHeader fct2Header = new InteractionChannelSiSectionHeader(ms); + if (!fct2Header.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = fct2Header.InteractiveNetworkId; + Fct2 fct2 = new Fct2(ms); + if (!fct2.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.Fct2Invalid); + return; + } + throw new NotImplementedException("FCT2"); + case 0xAC: //BCT + //see en_30154502v010401p.pdf, page 49 + throw new NotImplementedException("BCT"); + case 0xAD: //TBTP2 + InteractionChannelSiSectionHeader tbtp2Header = new InteractionChannelSiSectionHeader(ms); + if (!tbtp2Header.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = tbtp2Header.InteractiveNetworkId; + Tbtp2 tbtp2 = new Tbtp2(ms); + if (!tbtp2.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.Tbtp2Invalid); + return; + } + throw new NotImplementedException("TBTP2"); + case 0xAE: //TMST2 + InteractionChannelSiSectionHeader tmst2Header = new InteractionChannelSiSectionHeader(ms); + if (!tmst2Header.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + NetworkId = tmst2Header.InteractiveNetworkId; + int transmissionStandard = _handler.GetRmtTransmissionStandard(NetworkId.Value); + Tmst2 tmst2 = new Tmst2(ms,transmissionStandard); + if (!tmst2.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.Tmst2Invalid); + return; + } + if (tmst2.TransmissionModes.Length == 0) + return; + throw new NotImplementedException("TMST2"); + case 0xAF: //FAT + //see en_30154502v010401p.pdf, page 49 + throw new NotImplementedException("TMST2"); + case 0xB0: //TIM + InteractionChannelDsmCcPrivateSectionHeader timHeader = new InteractionChannelDsmCcPrivateSectionHeader(ms); + if (!timHeader.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid); + return; + } + Tim tim = new Tim(ms, timHeader.IsBroadcast); + if (!tim.Valid) + { + _handler.OnInteractionChannelError(InteractionChannelErrorState.TimInvalid); + return; + } + tim.Handle(timHeader.MacAddress, NetworkId, Handler); + return; + case 0xB1: //LL_FEC_parity_data_table + throw new NotImplementedException("LL_FEC_parity_data_table"); + case 0xB2: //MMT2 + //see en_30154502v010401p.pdf, page 49 + throw new NotImplementedException("MMT2"); + default: + if (buffer[0] == 0xa7 || buffer[0] == 0xa8 || buffer[0] == 0xa9) + { + //reserved + break; + } + if (buffer[0] >= 0xb2 || buffer[0] <= 0xbf) + { + //reserved for future use + break; + } + if (buffer[0] >= 0xc0 || buffer[0] <= 0xfe) + { + //user defined + break; + } + throw new NotImplementedException(String.Format("ETSI EN 301 790 V1.5.1 0x{0:X2}", buffer[0])); + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Cmt.cs b/skyscraper8/InteractionChannel/Model/Cmt.cs new file mode 100644 index 0000000..eccbc00 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Cmt.cs @@ -0,0 +1,65 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Cmt : Validatable + { + public Cmt(MemoryStream ms) + { + InBitStream ibs = new InBitStream(ms); + byte entryLoopCount = ibs.ReadBitsAsByte(8); + entryLoopCount++; + Entries = new CmtEntry[entryLoopCount]; + + for (int i = 0; i < entryLoopCount; i++) + { + Entries[i] = new CmtEntry(); + Entries[i].GroupId = ibs.ReadBitsAsByte(8); + Entries[i].LoginId = ibs.ReadBitsAsUInt16(16); + + bool timeCorrectionFlag = ibs.ReadBitAsBoolean(); + bool powerCorrectionFlag = ibs.ReadBitAsBoolean(); + bool frequencyCorrectionFlag = ibs.ReadBitAsBoolean(); + Entries[i].SlotType = (CorrectionMessageSlotType)ibs.ReadBitsAsByte(2); + Entries[i].BurstTimeScaling = ibs.ReadBitsAsByte(3); + if (timeCorrectionFlag) + Entries[i].BurstTimeCorrection = ibs.ReadBitsAsByte(8); + + if (powerCorrectionFlag) + { + bool powerControlFlag = ibs.ReadBitAsBoolean(); + if (powerControlFlag) + Entries[i].PowerCorrection = ibs.ReadBitsAsByte(7); + else + Entries[i].EsN0 = ibs.ReadBitsAsByte(7); + } + + if (frequencyCorrectionFlag) + Entries[i].FrequencyCorrection = ibs.ReadBitsAsUInt16(16); + } + uint crc32 = ibs.ReadBitsAsUint(32); + Valid = true; + } + + public CmtEntry[] Entries; + + public class CmtEntry + { + public byte GroupId { get; internal set; } + public ushort LoginId { get; internal set; } + public CorrectionMessageSlotType SlotType { get; internal set; } + public int BurstTimeScaling { get; internal set; } + public uint? BurstTimeCorrection { get; internal set; } + public int? PowerCorrection { get; internal set; } + public int? EsN0 { get; internal set; } + public ushort FrequencyCorrection { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/CorrectionMessageSlotType.cs b/skyscraper8/InteractionChannel/Model/CorrectionMessageSlotType.cs new file mode 100644 index 0000000..3e0356f --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/CorrectionMessageSlotType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public enum CorrectionMessageSlotType : byte + { + TRF = 0, + CSC = 1, + ACQ = 2, + SYNC = 3 + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0x4a_LinkageDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0x4a_LinkageDescriptor.cs new file mode 100644 index 0000000..3aa9bd6 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0x4a_LinkageDescriptor.cs @@ -0,0 +1,85 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.src.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x4a,"RMT")] + public class _0x4a_LinkageDescriptor : TsDescriptor + { + public _0x4a_LinkageDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + this.TransportStreamId = ms.ReadUInt16BE(); + this.OriginalNetworkId = ms.ReadUInt16BE(); + this.ServiceId = ms.ReadUInt16BE(); + this.LinkageType = ms.ReadUInt8(); + this.InteractiveNetworkId = ms.ReadUInt16BE(); + + byte populationIdLoopCount = ms.ReadUInt8(); populationIdLoopCount++; + PopulationIds = new Population[populationIdLoopCount]; + for (int i = 0; i < populationIdLoopCount; i++) + { + if (ms.GetAvailableBytes() < 4) + { + Valid = false; + return; + } + PopulationIds[i] = new Population(); + PopulationIds[i].Base = ms.ReadUInt16BE(); + PopulationIds[i].Mask = ms.ReadUInt16BE(); + } + + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + Valid = true; + } + + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public ushort ServiceId { get; } + public byte LinkageType { get; } + public ushort InteractiveNetworkId { get; } + public Population[] PopulationIds { get; } + public byte[] PrivateData { get; } + + public class Population + { + public ushort Base { get; internal set; } + public ushort Mask { get; internal set; } + + public override string ToString() + { + return String.Format("({0},{1})", Base, Mask); + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("ONID {0}, TSID {1}", OriginalNetworkId, TransportStreamId); + + if (PopulationIds != null) + { + for (int i = 0; i < PopulationIds.Length; i++) + { + sb.Append(", ({0},{1})", PopulationIds[i].Base, PopulationIds[i].Mask); + } + } + return sb.ToString(); + } + + public DatabaseKeyRmtLinkage ToKey() + { + return new DatabaseKeyRmtLinkage(TransportStreamId, OriginalNetworkId, ServiceId, LinkageType, InteractiveNetworkId); + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa0_NetworkLayerInfoDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa0_NetworkLayerInfoDescriptor.cs new file mode 100644 index 0000000..80c9c07 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa0_NetworkLayerInfoDescriptor.cs @@ -0,0 +1,23 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa0,"TIM")] + public class _0xa0_NetworkLayerInfoDescriptor : TsDescriptor + { + public _0xa0_NetworkLayerInfoDescriptor(byte[] buffer) + { + MessageBuffer = (byte[])buffer.Clone(); + Valid = true; + } + + public byte[] MessageBuffer { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa1_CorrectionMessageDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa1_CorrectionMessageDescriptor.cs new file mode 100644 index 0000000..e895aaa --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa1_CorrectionMessageDescriptor.cs @@ -0,0 +1,83 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa1, "TIM")] + public class _0xa1_CorrectionMessageDescriptor : TsDescriptor + { + public _0xa1_CorrectionMessageDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + InBitStream ibs = new InBitStream(ms); + + bool timeCorrectionFlag = ibs.ReadBitAsBoolean(); + bool powerCorrectionFlag = ibs.ReadBitAsBoolean(); + bool frequencyCorrectionFlag = ibs.ReadBitAsBoolean(); + SlotType = (CorrectionMessageSlotType)ibs.ReadBitsAsByte(2); + BurstTimeScaling = ibs.ReadBitsAsUInt16(3); + + if (timeCorrectionFlag) + { + BurstTimeCorrection = ibs.ReadBitsAsByte(8); + } + + if (powerCorrectionFlag) + { + bool powerControlFlag = ibs.ReadBitAsBoolean(); + if (powerControlFlag) + { + PowerCorrection = ibs.ReadBitsAsByte(7); + } + else + { + EsN0 = ibs.ReadBitsAsByte(7); + } + } + + if (frequencyCorrectionFlag) + { + FrequencyCorrection = ibs.ReadBitsAsUInt16(16); + } + + Valid = true; + } + + public _0xa1_CorrectionMessageDescriptor(CorrectionMessageSlotType slotType, int burstTimeScaling, uint? burstTimeCorrection, int? powerCorrection, int? esN0, ushort frequencyCorrection) + { + SlotType = slotType; + BurstTimeScaling = burstTimeScaling; + BurstTimeCorrection = burstTimeCorrection; + PowerCorrection = powerCorrection; + EsN0 = esN0; + FrequencyCorrection = frequencyCorrection; + } + + public CorrectionMessageSlotType SlotType { get; } + public int BurstTimeScaling { get; internal set; } + public uint? BurstTimeCorrection { get; internal set; } + public int? PowerCorrection { get; internal set; } + public int? EsN0 { get; internal set; } + public ushort FrequencyCorrection { get; internal set; } + + public override bool Equals(object obj) + { + return obj is _0xa1_CorrectionMessageDescriptor descriptor && + SlotType == descriptor.SlotType && + BurstTimeScaling == descriptor.BurstTimeScaling && + BurstTimeCorrection == descriptor.BurstTimeCorrection && + PowerCorrection == descriptor.PowerCorrection && + EsN0 == descriptor.EsN0 && + FrequencyCorrection == descriptor.FrequencyCorrection; + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa2_LoginInitializeDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa2_LoginInitializeDescriptor.cs new file mode 100644 index 0000000..b5f7508 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa2_LoginInitializeDescriptor.cs @@ -0,0 +1,97 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [TsDescriptor(0xa2,"TIM")] + internal class _0xa2_LoginInitializeDescriptor : TsDescriptor + { + public _0xa2_LoginInitializeDescriptor(byte[] buffer) + { + InBitStream ibs = new InBitStream(new MemoryStream(buffer, false)); + GroupId = ibs.ReadBitsAsByte(8); + LogonId = ibs.ReadBitsAsUint(16); + + byte reserved = ibs.ReadBitsAsByte(2); + ContinuousCarrier = ibs.ReadBitAsBoolean(); + SecurityHandshakeRequired = ibs.ReadBitAsBoolean(); + PrefixFlag = ibs.ReadBitAsBoolean(); + DataUnitLabelingFlag = ibs.ReadBitAsBoolean(); + MiniSlotFlag = ibs.ReadBitAsBoolean(); + ContentionBasedMiniSlotFlag = ibs.ReadBitAsBoolean(); + + reserved = ibs.ReadBitsAsByte(1); + CapacityTypeFlag = ibs.ReadBitAsBoolean(); + TrafficBurstType = ibs.ReadBitAsBoolean(); + if (!TrafficBurstType) + { + Connectivity = ibs.ReadBitAsBoolean(); + if (!Connectivity.Value) + { + reserved = ibs.ReadBitsAsByte(4); + ReturnVPI = ibs.ReadBitsAsByte(8); + ReturnVCI = ibs.ReadBitsAsUInt16(16); + } + else + { + reserved = ibs.ReadBitsAsByte(4); + ReturnSignallingVPI = ibs.ReadBitsAsByte(8); + ReturnSignallingVCI = ibs.ReadBitsAsUInt16(16); + reserved = ibs.ReadBitsAsByte(8); + ForwardSignallingVPI = ibs.ReadBitsAsByte(8); + ForwardSignallingVCI = ibs.ReadBitsAsUInt16(16); + } + } + else + { + ReturnTrfPid = ibs.ReadBitsAsUInt16(13); + reserved = ibs.ReadBitsAsByte(3); + ReturnCtrlMngmPid = ibs.ReadBitsAsUInt16(13); + } + if (!CapacityTypeFlag) + { + if (ibs.GetBitsAvailable() < 80) + { + Valid = false; + return; + } + CraLevel = ibs.ReadBitsAsUint(24); + reserved = ibs.ReadBitsAsByte(5); + VbdcMax = ibs.ReadBitsAsUint(11); + RbdcMax = ibs.ReadBitsAsUint(24); + RbdcTimeout = ibs.ReadBitsAsUint(16); + } + Valid = true; + } + + public byte GroupId { get; } + public uint LogonId { get; } + public bool ContinuousCarrier { get; } + public bool SecurityHandshakeRequired { get; } + public bool PrefixFlag { get; } + public bool DataUnitLabelingFlag { get; } + public bool MiniSlotFlag { get; } + public bool ContentionBasedMiniSlotFlag { get; } + public bool CapacityTypeFlag { get; } + public bool TrafficBurstType { get; } + public bool? Connectivity { get; } + public byte? ReturnVPI { get; } + public ushort? ReturnVCI { get; } + public byte? ReturnSignallingVPI { get; } + public ushort? ReturnSignallingVCI { get; } + public ushort? ReturnTrfPid { get; } + public ushort? ReturnCtrlMngmPid { get; } + public uint? CraLevel { get; } + public uint? VbdcMax { get; } + public uint? RbdcMax { get; } + public uint? RbdcTimeout { get; } + public byte ForwardSignallingVPI { get; } + public ushort ForwardSignallingVCI { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa3_AcqAssignDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa3_AcqAssignDescriptor.cs new file mode 100644 index 0000000..414002d --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa3_AcqAssignDescriptor.cs @@ -0,0 +1,38 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa3,"TIM")] + internal class _0xa3_AcqAssignDescriptor : TsDescriptor + { + public _0xa3_AcqAssignDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + AchievedTimeThreshold = ms.ReadUInt8(); + AchievedFrequencyThreshold = ms.ReadUInt16BE(); + Repeats = ms.ReadUInt8() & 0x3f; + StartSuperframe = ms.ReadUInt16BE(); + FrameNumber = ms.ReadUInt8() & 0x1f; + RepeatPeriod = ms.ReadUInt8() & 0x3f; + SlotNumber = ms.ReadUInt16BE() & 0x07ff; + Valid = true; + } + + public byte AchievedTimeThreshold { get; } + public ushort AchievedFrequencyThreshold { get; } + public int Repeats { get; } + public ushort StartSuperframe { get; } + public int FrameNumber { get; } + public int RepeatPeriod { get; } + public int SlotNumber { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa4_SyncAssignDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa4_SyncAssignDescriptor.cs new file mode 100644 index 0000000..5cebe91 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa4_SyncAssignDescriptor.cs @@ -0,0 +1,38 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa4,"TIM")] + internal class _0xa4_SyncAssignDescriptor : TsDescriptor + { + public _0xa4_SyncAssignDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SyncAchievedTimeThreshold = ms.ReadUInt8(); + MaxSyncTries = ms.ReadUInt8(); + SyncAchievedFrequencyThreshold = ms.ReadUInt16BE(); + SyncStartSuperframe = ms.ReadUInt16BE(); + SyncFrameNumber = (ms.ReadUInt8() & 0x1f); + SyncRepeatPeriod = ms.ReadUInt16BE(); + SyncSlotNumber = (ms.ReadUInt16BE() & 0x07ff); + Valid = true; + } + + public byte SyncAchievedTimeThreshold { get; } + public byte MaxSyncTries { get; } + public ushort SyncAchievedFrequencyThreshold { get; } + public ushort SyncStartSuperframe { get; } + public int SyncFrameNumber { get; } + public ushort SyncRepeatPeriod { get; } + public int SyncSlotNumber { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa5_EncryptedLoginIdDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa5_EncryptedLoginIdDescriptor.cs new file mode 100644 index 0000000..74a4b2c --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa5_EncryptedLoginIdDescriptor.cs @@ -0,0 +1,42 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa5,"TIM")] + internal class _0xa5_EncryptedLoginIdDescriptor : TsDescriptor + { + public _0xa5_EncryptedLoginIdDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + IdStartTime = ms.ReadUInt16BE(); + IdUpdatePeriod = ms.ReadUInt16BE(); + byte idLoopCount = ms.ReadUInt8(); idLoopCount++; + + int requiredLength = idLoopCount * 2; + if (ms.GetAvailableBytes() < requiredLength) + { + Valid = false; + return; + } + EncryptedLoginId = new ushort[idLoopCount]; + for (int i = 0; i < idLoopCount; i++) + { + EncryptedLoginId[i] = ms.ReadUInt16BE(); + } + Valid = true; + } + + public ushort IdStartTime { get; } + public ushort IdUpdatePeriod { get; } + public ushort[] EncryptedLoginId { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa8_SatelliteForwardLinkDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa8_SatelliteForwardLinkDescriptor.cs new file mode 100644 index 0000000..3d2f697 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa8_SatelliteForwardLinkDescriptor.cs @@ -0,0 +1,138 @@ +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa8,"RMT")] + public class _0xa8_SatelliteForwardLinkDescriptor : TsDescriptor + { + public _0xa8_SatelliteForwardLinkDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SatelliteId = ms.ReadUInt8(); + BeamId = ms.ReadUInt16BE(); + NccId = ms.ReadUInt8(); + + byte byteA = ms.ReadUInt8(); + LinkUsage = (ForwardLinkUsageCodes)((byteA & 0xe0) >> 5); + LocalLinkId = (byteA & 0x1f); + + Frequency = ms.ReadUInt32BE(); + OrbitalPosition = ms.ReadUInt16BE(); + + byte byteB = ms.ReadUInt8(); + East = (byteB & 0x80) != 0; + Polarization = (ForwardLinkPolarization)((byteB & 0x60) >> 5); + TransmissionStandard = (TransmissionStandardEnum)((byteB & 0x18) >> 3); + if (TransmissionStandard == TransmissionStandardEnum.DVBS) + { + int assertion = ((byteB & 0x07)); + if (assertion != 1) + { + Valid = false; + return; + } + } + else if (TransmissionStandard == TransmissionStandardEnum.DVBS2_CCM || TransmissionStandard == TransmissionStandardEnum.DVBS2_ACM) + { + ScramblingSequenceSelector = (byteB & 0x04) != 0; + int rollOff = (byteB & 0x03); + switch(rollOff) + { + case 0: RollOff = null; break; + case 1: RollOff = 0.2; break; + case 2: RollOff = 0.25; break; + case 3: RollOff = 0.35; break; + } + } + SymbolRate = ms.ReadUInt24BE(); + if (TransmissionStandard == TransmissionStandardEnum.DVBS) + { + byte byteC = ms.ReadUInt8(); + FecInner = (InnerFecScheme?)((byteC & 0xf0) >> 4); + int reserved = (byteC & 0x0f); + } + else if (TransmissionStandard == TransmissionStandardEnum.DVBS2_CCM || TransmissionStandard == TransmissionStandardEnum.DVBS2_ACM) + { + InputStreamIndentifier = ms.ReadUInt8(); + if (ScramblingSequenceSelector.Value == false) + { + byte byteC = ms.ReadUInt8(); + SpreadingCodeSelector = ((byteC & 0xe0) >> 5); + ScramblingSequenceIndex = (byteC & 0x03); + ScramblingSequenceIndex <<= 16; + ScramblingSequenceIndex += ms.ReadUInt16BE(); + } + } + + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + Valid = true; + } + + public byte SatelliteId { get; } + public ushort BeamId { get; } + public byte NccId { get; } + public ForwardLinkUsageCodes LinkUsage { get; } + public int LocalLinkId { get; } + public uint Frequency { get; } + public ushort OrbitalPosition { get; } + public bool East { get; } + public ForwardLinkPolarization Polarization { get; } + public TransmissionStandardEnum TransmissionStandard { get; } + public bool? ScramblingSequenceSelector { get; } + public double? RollOff { get; } + public uint SymbolRate { get; } + public InnerFecScheme? FecInner { get; } + public byte? InputStreamIndentifier { get; } + public int? SpreadingCodeSelector { get; } + public int ScramblingSequenceIndex { get; } + public byte[] PrivateDataBytes { get; } + + public enum ForwardLinkUsageCodes + { + Combined = 0, + Signalling = 1, + Data = 2, + Release = 3 + } + + public enum ForwardLinkPolarization + { + Horizontal = 0, + Vertical = 1, + Left = 2, + Right = 3 + } + + public enum TransmissionStandardEnum + { + DVBS = 0, + DVBS2_CCM = 1, + DVBS2_ACM = 2 + } + + public enum InnerFecScheme + { + AHalf = 0, + TwoThirds = 1, + ThreeQuarters = 2, + FiveSixths = 3, + SevenEights = 4, + Omitted = 5 + } + + public override string ToString() + { + return String.Format("{0} kHz,{1}, {2} sym/s", Frequency, Polarization.ToString()[0], SymbolRate); + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xa9_SatelliteReturnLinkDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xa9_SatelliteReturnLinkDescriptor.cs new file mode 100644 index 0000000..27b31d4 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xa9_SatelliteReturnLinkDescriptor.cs @@ -0,0 +1,47 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xa9,"RMT")] + public class _0xa9_SatelliteReturnLinkDescriptor : TsDescriptor + { + public _0xa9_SatelliteReturnLinkDescriptor(byte[] bytes) + { + MemoryStream ms = new MemoryStream(bytes, false); + SatelliteId = ms.ReadUInt8(); + BeamId = ms.ReadUInt16BE(); + GatewayId = ms.ReadUInt8(); + byte reserved = ms.ReadUInt8(); + OrbitalPosition = ms.ReadUInt16BE(); + Eastern = ((ms.ReadUInt8() & 0x01) != 0); + SuperframeId = ms.ReadUInt8(); + TxFrequencyOffset = ms.ReadUInt24BE(); + PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes()); + Valid = true; + } + + public byte SatelliteId { get; } + public ushort BeamId { get; } + public byte GatewayId { get; } + public ushort OrbitalPosition { get; } + public bool Eastern { get; } + public byte SuperframeId { get; } + public uint TxFrequencyOffset { get; } + public byte[] PrivateDataBytes { get; } + + public override string ToString() + { + return String.Format("Sat-ID {0}, Beam-ID {1}, Gateway-ID {2}, Superframe-ID {3}, Freq-Offset {4}", SatelliteId, BeamId, GatewayId, SuperframeId, TxFrequencyOffset); + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xaa_TableUpdateDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xaa_TableUpdateDescriptor.cs new file mode 100644 index 0000000..a0920e8 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xaa_TableUpdateDescriptor.cs @@ -0,0 +1,47 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xaa,"TIM")] + internal class _0xaa_TableUpdateDescriptor : TsDescriptor + { + public _0xaa_TableUpdateDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + InteractiveNetworkId = ms.ReadUInt16BE(); + + long availableBytes = ms.GetAvailableBytes(); + if (availableBytes % 2 != 0) + { + Valid = false; + return; + } + + Updates = new TableUpdate[availableBytes / 2]; + for (int i = 0; i < Updates.Length; i++) + { + Updates[i] = new TableUpdate(); + Updates[i].TableId = ms.ReadUInt8(); + Updates[i].NewVersion = ms.ReadUInt8() & 0x1f; + } + Valid = true; + } + + public ushort InteractiveNetworkId { get; } + public TableUpdate[] Updates { get; } + public struct TableUpdate + { + public byte TableId { get; internal set; } + public int NewVersion { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xab_ContentionControlDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xab_ContentionControlDescriptor.cs new file mode 100644 index 0000000..369796f --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xab_ContentionControlDescriptor.cs @@ -0,0 +1,49 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xab, "TIM")] + public class _0xab_ContentionControlDescriptor : TsDescriptor + { + public _0xab_ContentionControlDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SuperframeId = ms.ReadUInt8(); + CscResponseTimeout = ms.ReadUInt32BE(); + CscMaxLosses = ms.ReadUInt8(); + MaxTimeBeforeRetry = ms.ReadUInt32BE(); + Valid = true; + } + + public byte SuperframeId { get; } + public uint CscResponseTimeout { get; } + public byte CscMaxLosses { get; } + public uint MaxTimeBeforeRetry { get; } + + public _0xab_ContentionControlDescriptor(byte superframeId, uint cscResponseTimeout, byte cscMaxLosses, uint maxTimeBeforeRetry) + { + SuperframeId = superframeId; + CscResponseTimeout = cscResponseTimeout; + CscMaxLosses = cscMaxLosses; + MaxTimeBeforeRetry = maxTimeBeforeRetry; + } + + public override bool Equals(object obj) + { + return obj is _0xab_ContentionControlDescriptor descriptor && + SuperframeId == descriptor.SuperframeId && + CscResponseTimeout == descriptor.CscResponseTimeout && + CscMaxLosses == descriptor.CscMaxLosses && + MaxTimeBeforeRetry == descriptor.MaxTimeBeforeRetry; + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xac_CorrectionControlDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xac_CorrectionControlDescriptor.cs new file mode 100644 index 0000000..d3e7cba --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xac_CorrectionControlDescriptor.cs @@ -0,0 +1,51 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.src.Mpeg2.PacketFilter; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xac,"TIM")] + public class _0xac_CorrectionControlDescriptor : TsDescriptor + { + public _0xac_CorrectionControlDescriptor(byte[] buffer) + { + MemoryStream ms =new MemoryStream(buffer); + AcqResponseTimeout = ms.ReadUInt32BE(); + SyncResponseTimeout = ms.ReadUInt32BE(); + AcqMaxLosses = ms.ReadUInt8(); + SyncMaxLosses = ms.ReadUInt8(); + Valid = true; + } + + public uint AcqResponseTimeout { get; } + public uint SyncResponseTimeout { get; } + public byte AcqMaxLosses { get; } + public byte SyncMaxLosses { get; } + + public _0xac_CorrectionControlDescriptor(uint acqResponseTimeout, uint syncResponseTimeout, byte acqMaxLosses, byte syncMaxLosses) + { + AcqResponseTimeout = acqResponseTimeout; + SyncResponseTimeout = syncResponseTimeout; + AcqMaxLosses = acqMaxLosses; + SyncMaxLosses = syncMaxLosses; + } + + public override bool Equals(object obj) + { + return obj is _0xac_CorrectionControlDescriptor descriptor && + AcqResponseTimeout == descriptor.AcqResponseTimeout && + SyncResponseTimeout == descriptor.SyncResponseTimeout && + AcqMaxLosses == descriptor.AcqMaxLosses && + SyncMaxLosses == descriptor.SyncMaxLosses; + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xad_ForwardInteractionPathDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xad_ForwardInteractionPathDescriptor.cs new file mode 100644 index 0000000..827f771 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xad_ForwardInteractionPathDescriptor.cs @@ -0,0 +1,55 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xad,"TIM")] + internal class _0xad_ForwardInteractionPathDescriptor : TsDescriptor + { + public _0xad_ForwardInteractionPathDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 5) + { + if (Paths == null) + Paths = new List(); + + ForwardInteractionPath child = new ForwardInteractionPath(); + child.OriginalNetworkId = ms.ReadUInt16BE(); + child.TransportStreamId = ms.ReadUInt16BE(); + int pidLoopCount = ms.ReadUInt8() & 0x0f; + + int sizeRequired = pidLoopCount * 2; + if (ms.GetAvailableBytes() < sizeRequired) + { + Valid = false; + return; + } + child.Pids = new ushort[pidLoopCount]; + for (int i = 0; i < pidLoopCount; i++) + { + child.Pids[i] = ms.ReadUInt16BE(); + } + Paths.Add(child); + } + Valid = true; + } + + public class ForwardInteractionPath + { + public ushort OriginalNetworkId { get; internal set; } + public ushort TransportStreamId { get; internal set; } + public ushort[] Pids { get; internal set; } + } + + public List Paths { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xae_ReturnInteractionPathDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xae_ReturnInteractionPathDescriptor.cs new file mode 100644 index 0000000..0c7526e --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xae_ReturnInteractionPathDescriptor.cs @@ -0,0 +1,114 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xae,"TIM")] + internal class _0xae_ReturnInteractionPathDescriptor : TsDescriptor + { + public _0xae_ReturnInteractionPathDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte bytea = ms.ReadUInt8(); + ContinuousCarrier = (bytea & 0x10) != 0; + + int networkRoutingLabelLoopCount = (bytea & 0x0f); + networkRoutingLabelLoopCount++; + RoutingLabels = new RoutingLabel[networkRoutingLabelLoopCount]; + for (int i = 0; i < networkRoutingLabelLoopCount; i++) + { + RoutingLabels[i] = new RoutingLabel(); + byte byteb = ms.ReadUInt8(); + RoutingLabels[i].AllocationDesallocationFlag = (byteb & 0x02) != 0; + bool pidFlag = (byteb & 0x01) != 0; + if (pidFlag) + { + byte pidLoopCount = ms.ReadUInt8(); + pidLoopCount++; + int bytesRequired = pidLoopCount * 2; + if (ms.GetAvailableBytes() < bytesRequired) + { + Valid = false; + return; + } + + RoutingLabels[i].PIDs = new ushort[pidLoopCount]; + for (int j = 0; j < pidLoopCount; j++) + { + RoutingLabels[i].PIDs[j] = ms.ReadUInt16BE(); + } + } + + byte bytec = ms.ReadUInt8(); + bool vpiVciFlag = (bytec & 0x01) != 0; + if (vpiVciFlag) + { + byte vpiVciCount = ms.ReadUInt8(); + vpiVciCount++; + int bytesRequired = vpiVciCount * 2; + if (ms.GetAvailableBytes() < bytesRequired) + { + Valid = false; + return; + } + + RoutingLabels[i].VpiVciPairs = new VpiVciPair[vpiVciCount]; + for (int k = 0; k < vpiVciCount; k++) + { + RoutingLabels[i].VpiVciPairs[k] = new VpiVciPair(); + RoutingLabels[i].VpiVciPairs[k].VPI = ms.ReadUInt8(); + RoutingLabels[i].VpiVciPairs[k].VCI = ms.ReadUInt16BE(); + } + } + + byte byted = ms.ReadUInt8(); + bool routeIdFlag = (byted & 0x01) != 0; + if (routeIdFlag) + { + byte routeIdCount = ms.ReadUInt8(); + routeIdCount++; + int bytesRequired = routeIdCount * 2; + if (ms.GetAvailableBytes() < bytesRequired) + { + Valid = false; + return; + } + + RoutingLabels[i].RouteIds = new ushort[routeIdCount]; + for (int l = 0; l < routeIdCount; l++) + { + RoutingLabels[i].RouteIds[l] = ms.ReadUInt16LE(); + } + } + + ChannelId = ms.ReadUInt8() & 0x0f; + } + } + + public bool ContinuousCarrier { get; } + public RoutingLabel[] RoutingLabels { get; } + public int ChannelId { get; } + + public class RoutingLabel + { + public bool AllocationDesallocationFlag { get; internal set; } + public ushort[] PIDs { get; internal set; } + public VpiVciPair[] VpiVciPairs { get; internal set; } + public ushort[] RouteIds { get; internal set; } + } + + public class VpiVciPair + { + public byte VPI { get; internal set; } + public ushort VCI { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xaf_ConnectionControlDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xaf_ConnectionControlDescriptor.cs new file mode 100644 index 0000000..6e0000c --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xaf_ConnectionControlDescriptor.cs @@ -0,0 +1,32 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xaf,"TIM")] + internal class _0xaf_ConnectionControlDescriptor : TsDescriptor + { + public _0xaf_ConnectionControlDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SuperframeId = ms.ReadUInt8(); + CscResponseTimeout = ms.ReadUInt32BE(); + CscMaxLosses = ms.ReadUInt8(); + MaxTimeBeforeRetry = ms.ReadUInt32BE(); + Valid = true; + } + + public byte SuperframeId { get; } + public uint CscResponseTimeout { get; } + public byte CscMaxLosses { get; } + public uint MaxTimeBeforeRetry { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xb1_CorrectionMessageExtensionDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xb1_CorrectionMessageExtensionDescriptor.cs new file mode 100644 index 0000000..e11a3f1 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xb1_CorrectionMessageExtensionDescriptor.cs @@ -0,0 +1,32 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xb1,"TIM")] + internal class _0xb1_CorrectionMessageExtensionDescriptor : TsDescriptor + { + public _0xb1_CorrectionMessageExtensionDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SuperframeId = ms.ReadUInt8(); + SuperframeCount = ms.ReadUInt16BE(); + FrameNumber = ms.ReadUInt8() & 0x1f; + SlotNumber = (ms.ReadUInt16BE() & 0x07ff); + Valid = true; + } + + public byte SuperframeId { get; } + public ushort SuperframeCount { get; } + public int FrameNumber { get; } + public int SlotNumber { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xb2_ReturnTransmissionModesDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xb2_ReturnTransmissionModesDescriptor.cs new file mode 100644 index 0000000..71ccb4d --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xb2_ReturnTransmissionModesDescriptor.cs @@ -0,0 +1,74 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xb2,"TIM")] + public class _0xb2_ReturnTransmissionModesDescriptor : TsDescriptor + { + public _0xb2_ReturnTransmissionModesDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte superframeIdLoopCount = ms.ReadUInt8(); + + int spaceRequired = superframeIdLoopCount * 5; + if (ms.GetAvailableBytes() < spaceRequired) + { + Valid = false; + return; + } + + Superframes = new Superframe[superframeIdLoopCount]; + for (int i = 0; i < superframeIdLoopCount; i++) + { + Superframes[i] = new Superframe(); + Superframes[i].SuperframeId = ms.ReadUInt8(); + + byte bytea = ms.ReadUInt8(); + Superframes[i].ScramblingSequenceIndex = (bytea & 0x03); + Superframes[i].ScramblingSequenceIndex <<= 16; + Superframes[i].ScramblingSequenceIndex += ms.ReadUInt16BE(); + + byte transmissionModeCount = ms.ReadUInt8(); + Superframes[i].TransmissionModes = new TransmissionMode[transmissionModeCount]; + for (int j = 0; j < transmissionModeCount; j++) + { + Superframes[i].TransmissionModes[j] = new TransmissionMode(); + Superframes[i].TransmissionModes[j].InnerCodePuncturing = ms.ReadUInt8() & 0x0f; + Superframes[i].TransmissionModes[j].Modulation = ms.ReadUInt8() & 0x1f; + Superframes[i].TransmissionModes[j].TimeslotPayloadType = ms.ReadUInt8(); + + byte byteb = ms.ReadUInt8(); + Superframes[i].TransmissionModes[j].EsNoThresholdFlag = (byteb & 0x80) != 0; + Superframes[i].TransmissionModes[j].EsNoThreshold = (byteb & 0x7f); + } + } + Valid = true; + } + + public Superframe[] Superframes { get; } + public class Superframe + { + public byte SuperframeId { get; internal set; } + public int ScramblingSequenceIndex { get; internal set; } + public TransmissionMode[] TransmissionModes { get; internal set; } + } + + public class TransmissionMode + { + public int InnerCodePuncturing { get; internal set; } + public int Modulation { get; internal set; } + public byte TimeslotPayloadType { get; internal set; } + public bool EsNoThresholdFlag { get; internal set; } + public int EsNoThreshold { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xb3_MeshLogonInitializeDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xb3_MeshLogonInitializeDescriptor.cs new file mode 100644 index 0000000..eaf4189 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xb3_MeshLogonInitializeDescriptor.cs @@ -0,0 +1,101 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xb3,"TIM")] + internal class _0xb3_MeshLogonInitializeDescriptor : TsDescriptor + { + public _0xb3_MeshLogonInitializeDescriptor(byte[] buffer) + { + InBitStream ibs = new InBitStream(new MemoryStream(buffer, false)); + GroupId = ibs.ReadBitsAsByte(8); + LogonId = ibs.ReadBitsAsUint(16); + + byte reserved = ibs.ReadBitsAsByte(2); + ContinuousCarrier = ibs.ReadBitAsBoolean(); + SecurityHandshakeRequired = ibs.ReadBitAsBoolean(); + PrefixFlag = ibs.ReadBitAsBoolean(); + DataUnitLabelingFlag = ibs.ReadBitAsBoolean(); + MiniSlotFlag = ibs.ReadBitAsBoolean(); + ContentionBasedMiniSlotFlag = ibs.ReadBitAsBoolean(); + + MeshConnectivityFlag = ibs.ReadBitAsBoolean(); + CapacityTypeFlag = ibs.ReadBitAsBoolean(); + TrafficBurstType = ibs.ReadBitAsBoolean(); + if (!TrafficBurstType) + { + Connectivity = ibs.ReadBitAsBoolean(); + if (!Connectivity.Value) + { + reserved = ibs.ReadBitsAsByte(4); + ReturnVPI = ibs.ReadBitsAsByte(8); + ReturnVCI = ibs.ReadBitsAsUInt16(16); + } + else + { + reserved = ibs.ReadBitsAsByte(4); + ReturnSignallingVPI = ibs.ReadBitsAsByte(8); + ReturnSignallingVCI = ibs.ReadBitsAsUInt16(16); + reserved = ibs.ReadBitsAsByte(8); + ForwardSignallingVPI = ibs.ReadBitsAsByte(8); + ForwardSignallingVCI = ibs.ReadBitsAsUInt16(16); + } + } + else + { + ReturnTrfPid = ibs.ReadBitsAsUInt16(13); + reserved = ibs.ReadBitsAsByte(3); + ReturnCtrlMngmPid = ibs.ReadBitsAsUInt16(13); + } + if (!CapacityTypeFlag) + { + if (ibs.GetBitsAvailable() < 80) + { + Valid = false; + return; + } + CraLevel = ibs.ReadBitsAsUint(24); + reserved = ibs.ReadBitsAsByte(5); + VbdcMax = ibs.ReadBitsAsUint(11); + RbdcMax = ibs.ReadBitsAsUint(24); + RbdcTimeout = ibs.ReadBitsAsUint(16); + } + Valid = true; + } + + public byte GroupId { get; } + public uint LogonId { get; } + public bool ContinuousCarrier { get; } + public bool SecurityHandshakeRequired { get; } + public bool PrefixFlag { get; } + public bool DataUnitLabelingFlag { get; } + public bool MiniSlotFlag { get; } + public bool ContentionBasedMiniSlotFlag { get; } + public bool MeshConnectivityFlag { get; } + public bool CapacityTypeFlag { get; } + public bool TrafficBurstType { get; } + public bool? Connectivity { get; } + public byte ReturnVPI { get; } + public ushort ReturnVCI { get; } + public byte ReturnSignallingVPI { get; } + public ushort ReturnSignallingVCI { get; } + public byte ForwardSignallingVPI { get; } + public ushort ForwardSignallingVCI { get; } + public ushort ReturnTrfPid { get; } + public ushort ReturnCtrlMngmPid { get; } + public uint CraLevel { get; } + public uint VbdcMax { get; } + public uint RbdcMax { get; } + public uint RbdcTimeout { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xb5_ImplementationTypeDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xb5_ImplementationTypeDescriptor.cs new file mode 100644 index 0000000..c8e3f14 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xb5_ImplementationTypeDescriptor.cs @@ -0,0 +1,60 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using skyscraper5.Skyscraper.IO; + + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xb5,"TIM")] + internal class _0xb5_ImplementationTypeDescriptor : TsDescriptor + { + public _0xb5_ImplementationTypeDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + NccProtocolVersion = ms.ReadUInt8(); + + byte bytea = ms.ReadUInt8(); + bool reserved1 = (bytea & 0x80) != 0; + bool reserved2 = (bytea & 0x40) != 0; + LocationUpdateAllowedFlag = (bytea & 0x20) != 0; + RbdcAcceptedFlag = (bytea & 0x10) != 0; + VbdcAcceptedFlag = (bytea & 0x08) != 0; + AvdbcAcceptedFlag = (bytea & 0x04) != 0; + TimingOffsetFlag = (bytea & 0x02) != 0; + TimingReferenceFlag = (bytea & 0x01) != 0; + + C2PProtocolVersion = ms.ReadUInt8() & 0x07; + HubTypeId = ms.ReadUInt24BE(); + HubSwId = ms.ReadUInt24BE(); + byte userOptionsCount = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < userOptionsCount) + { + Valid = false; + return; + } + + UserOptions = ms.ReadBytes(userOptionsCount); + //skip reserved bytes + Valid = true; + } + + public byte NccProtocolVersion { get; } + public bool LocationUpdateAllowedFlag { get; } + public bool RbdcAcceptedFlag { get; } + public bool VbdcAcceptedFlag { get; } + public bool AvdbcAcceptedFlag { get; } + public bool TimingOffsetFlag { get; } + public bool TimingReferenceFlag { get; } + public int C2PProtocolVersion { get; } + public uint HubTypeId { get; } + public uint HubSwId { get; } + public byte[] UserOptions { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Descriptors/0xb6_LlFecIdentifierDescriptor.cs b/skyscraper8/InteractionChannel/Model/Descriptors/0xb6_LlFecIdentifierDescriptor.cs new file mode 100644 index 0000000..49e135a --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Descriptors/0xb6_LlFecIdentifierDescriptor.cs @@ -0,0 +1,68 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0xb6,"TIM")] + internal class _0xb6_LlFecIdentifierDescriptor : TsDescriptor + { + public _0xb6_LlFecIdentifierDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte loopCount = ms.ReadUInt8(); + + int required = loopCount * 8; + if (ms.GetAvailableBytes() > 8) + { + Valid = false; + return; + } + + LlFecIdentifiers = new LlFecIdentifier[loopCount]; + for (int i = 0; i < loopCount; i++) + { + ushort v = ms.ReadUInt16BE(); + LlFecIdentifiers[i] = new LlFecIdentifier(); + LlFecIdentifiers[i].LinkDirection = (v & 0x8000) != 0; + LlFecIdentifiers[i].EncapsulationType = (v & 0x4000) != 0; + if (!LlFecIdentifiers[i].EncapsulationType) + { + LlFecIdentifiers[i].ElementaryStreamId = (v & 0x1fff); + } + else + { + LlFecIdentifiers[i].GseFecId = (v & 0x3fff); + } + + v = ms.ReadUInt16BE(); + LlFecIdentifiers[i].LlFec = (v & 0xc000) >> 14; + LlFecIdentifiers[i].FrameSize = (v & 0x0e00) >> 9; + LlFecIdentifiers[i].BufferTimeout = (v & 0x01e0) >> 6; + LlFecIdentifiers[i].Dscp = (v & 0x001f); + uint reserved = ms.ReadUInt32BE(); + } + } + + private LlFecIdentifier[] LlFecIdentifiers { get; } + + class LlFecIdentifier + { + public bool LinkDirection { get; internal set; } + public bool EncapsulationType { get; internal set; } + public int ElementaryStreamId { get; internal set; } + public int GseFecId { get; internal set; } + public int LlFec { get; internal set; } + public int FrameSize { get; internal set; } + public int BufferTimeout { get; internal set; } + public int Dscp { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Fct.cs b/skyscraper8/InteractionChannel/Model/Fct.cs new file mode 100644 index 0000000..e7a1ee1 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Fct.cs @@ -0,0 +1,69 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Fct : Validatable + { + public Fct(MemoryStream ms) + { + byte frameIdLoopCount = ms.ReadUInt8(); frameIdLoopCount++; + Frames = new Frame[frameIdLoopCount]; + for (int i = 0; i < frameIdLoopCount; i++) + { + if (ms.GetAvailableBytes() < 10) + { + Valid = false; + return; + } + Frames[i] = new Frame(); + Frames[i].FrameId = ms.ReadUInt8(); + Frames[i].FrameDuration = ms.ReadUInt32BE(); + Frames[i].TotalTimeslotCount = ms.ReadUInt16BE() & 0x07ff; + Frames[i].StartTimeslotNumber = ms.ReadUInt16BE() & 0x07ff; + byte timeslotLoopCount = ms.ReadUInt8(); timeslotLoopCount++; + Frames[i].Timeslots = new Frame.Timeslot[timeslotLoopCount]; + for (int j = 0; j < timeslotLoopCount; j++) + { + if (ms.GetAvailableBytes() < 9) + { + Valid = false; + return; + } + Frames[i].Timeslots[j] = new Frame.Timeslot(); + Frames[i].Timeslots[j].TimeslotFrequencyOffset = ms.ReadUInt24BE(); + Frames[i].Timeslots[j].TimeslotTimeOffset = ms.ReadUInt32BE(); + Frames[i].Timeslots[j].TimeslotId = ms.ReadUInt8(); + Frames[i].Timeslots[j].RepeatCount = ms.ReadUInt8(); + } + } + uint crc32 = ms.ReadUInt32BE(); + Valid = true; + } + + public class Frame + { + public byte FrameId { get; internal set; } + public uint FrameDuration { get; internal set; } + public int TotalTimeslotCount { get; internal set; } + public int StartTimeslotNumber { get; internal set; } + public Timeslot[] Timeslots { get; internal set; } + + public class Timeslot + { + public uint TimeslotFrequencyOffset { get; internal set; } + public uint TimeslotTimeOffset { get; internal set; } + public byte TimeslotId { get; internal set; } + public byte RepeatCount { get; internal set; } + } + } + + public Frame[] Frames { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/InteractionChannelDsmCcPrivateSectionHeader.cs b/skyscraper8/InteractionChannel/Model/InteractionChannelDsmCcPrivateSectionHeader.cs new file mode 100644 index 0000000..f0acd35 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/InteractionChannelDsmCcPrivateSectionHeader.cs @@ -0,0 +1,90 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + internal class InteractionChannelDsmCcPrivateSectionHeader : Validatable + { + public InteractionChannelDsmCcPrivateSectionHeader(MemoryStream ms) + { + TableId = ms.ReadUInt8(); + + byte byteA = ms.ReadUInt8(); + SectionSyntaxIndicator = (byteA & 0x80) != 0; + PrivateIndicator = (byteA & 0x40) != 0; + int reserved = (byteA & 0x30) >> 4; + SectionLength = (byteA & 0x0f); + SectionLength <<= 8; + SectionLength += ms.ReadUInt8(); + if (SectionLength != ms.GetAvailableBytes()) + { + Valid = false; + return; + } + + byte[] mac = new byte[6]; + mac[5] = ms.ReadUInt8(); + mac[4] = ms.ReadUInt8(); + + byte byteB = ms.ReadUInt8(); + int reservedB = ((byteB & 0xC0) >> 6); + PayloadScramblingControl = ((byteB & 0x30) >> 4); + AddressScramblingControl = ((byteB & 0x0C) >> 2); + LlcSnap = (byteB & 0x02) != 0; + if (LlcSnap) + { + Valid = false; + return; + } + CurrentNextIndicator = (byteB & 0x01) != 0; + if (!CurrentNextIndicator) + { + Valid = false; + return; + } + + SectionNumber = ms.ReadUInt8(); + LastSectionNumber = ms.ReadUInt8(); + mac[3] = ms.ReadUInt8(); + mac[2] = ms.ReadUInt8(); + mac[1] = ms.ReadUInt8(); + mac[0] = ms.ReadUInt8(); + + MacAddress = new PhysicalAddress(mac); + Valid = true; + } + + public byte TableId { get; } + public bool SectionSyntaxIndicator { get; } + public bool PrivateIndicator { get; } + public int SectionLength { get; } + public int PayloadScramblingControl { get; } + public int AddressScramblingControl { get; } + public bool LlcSnap { get; } + public bool CurrentNextIndicator { get; } + public PhysicalAddress MacAddress { get; } + public byte SectionNumber { get; } + public byte LastSectionNumber { get; } + + private bool? _isBroadcast; + public bool IsBroadcast + { + get + { + if (!_isBroadcast.HasValue) + { + PhysicalAddress broadcast = new PhysicalAddress(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }); + _isBroadcast = MacAddress.Equals(broadcast); + } + return _isBroadcast.Value; + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/InteractionChannelSiSectionHeader.cs b/skyscraper8/InteractionChannel/Model/InteractionChannelSiSectionHeader.cs new file mode 100644 index 0000000..100c5de --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/InteractionChannelSiSectionHeader.cs @@ -0,0 +1,71 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + internal class InteractionChannelSiSectionHeader : Validatable + { + public InteractionChannelSiSectionHeader(MemoryStream ms) + { + TableId = ms.ReadUInt8(); + + byte byte2 = ms.ReadUInt8(); + SectionSyntaxIndicator = (byte2 & 0x80) != 0; + if (!SectionSyntaxIndicator) + { + Valid = false; + return; + } + + bool reserved = (byte2 & 0x40) != 0; + int reserved2 = (byte2 & 0x30) >> 4; + + SectionLength = (byte2 & 0x0f); + SectionLength <<= 4; + SectionLength += ms.ReadUInt8(); + long AvailableBytes = ms.GetAvailableBytes(); + if (AvailableBytes < SectionLength) + { + Valid = false; + return; + } + if (SectionLength > 1021) + { + Valid = false; + return; + } + if (AvailableBytes > 1021) + { + Valid = false; + return; + } + + InteractiveNetworkId = ms.ReadUInt16BE(); + + byte byte6 = ms.ReadUInt8(); + int reserved3 = (byte6 & 0xc0) >> 6; + VersionNumber = (byte6 & 0x3e) >> 1; + CurrentNextIndicator = (byte6 & 0x01) != 0; + + SectionNumber = ms.ReadUInt8(); + LastSectionNumber = ms.ReadUInt8(); + + Valid = true; + } + + public byte TableId { get; private set; } + public bool SectionSyntaxIndicator { get; private set; } + public int SectionLength { get; } + public ushort InteractiveNetworkId { get; } + public int VersionNumber { get; } + public bool CurrentNextIndicator { get; } + public byte SectionNumber { get; } + public byte LastSectionNumber { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Rmt.cs b/skyscraper8/InteractionChannel/Model/Rmt.cs new file mode 100644 index 0000000..9c01c6a --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Rmt.cs @@ -0,0 +1,176 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.Scraper.Storage.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Rmt : Validatable + { + private static TsDescriptorUnpacker tsDescriptorUnpacker; + + public Rmt(MemoryStream ms) + { + if (tsDescriptorUnpacker == null) + tsDescriptorUnpacker = TsDescriptorUnpacker.GetInstance(); + + byte tableId = ms.ReadUInt8(); + if (tableId != 0x41) + { + Valid = false; + return; + } + + byte byteA = ms.ReadUInt8(); + bool sectionSyntaxIndicator = (byteA & 0x80) != 1; + if (!sectionSyntaxIndicator) + { + Valid = false; + return; + } + + bool reservedFutureUse = (byteA & 0x40) != 1; + int reserved = (byteA & 0x30) >> 4; + + int sectionLength = (byteA & 0x0f); + sectionLength <<= 8; + sectionLength += ms.ReadUInt8(); + if (sectionLength != ms.GetAvailableBytes()) + { + Valid = false; + return; + } + + NetworkId = ms.ReadUInt16BE(); + + byte byteB = ms.ReadUInt8(); + reserved = (byteB & 0xc0) >> 6; + int versionNumber = (byteB & 0x3e) >> 1; + bool currentNextIndicator = (byteB & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + + byte byteC = ms.ReadUInt8(); + int reservedFutureUseB = (byteC & 0xf0) >> 4; + int networkDescriptorsLength = (byteC & 0x0f); + networkDescriptorsLength += ms.ReadUInt8(); + + if (ms.GetAvailableBytes() < networkDescriptorsLength) + { + Valid = false; + return; + } + + byte[] networkDescriptors = ms.ReadBytes(networkDescriptorsLength); + IEnumerable enumerable = tsDescriptorUnpacker.UnpackDescriptors(networkDescriptors, "RMT"); + foreach(TsDescriptor descriptor in enumerable) + { + if (!descriptor.Valid) + { + Valid = false; + return; + } + if (descriptor.GetDescriptorId() == 0x4a) + { + if (Linkages == null) + Linkages = new List<_0x4a_LinkageDescriptor> (); + Linkages.Add((_0x4a_LinkageDescriptor)descriptor); + continue; + } + throw new NotImplementedException(); + } + + byte byteD = ms.ReadUInt8(); + int reservedFutureUseC = (byteD & 0xf0) >> 4; + int transportStreamLoopLength = (byteD & 0x0f); + transportStreamLoopLength <<= 8; + transportStreamLoopLength += ms.ReadUInt8(); + byte[] transportStreamLoop = ms.ReadBytes(transportStreamLoopLength); + TransportStreams = ReadTransportStreamLoop(transportStreamLoop); + + Valid = true; + } + + private IReadOnlyList ReadTransportStreamLoop(byte[] transportStreamLoop) + { + List result = new List(); + + MemoryStream ms = new MemoryStream(transportStreamLoop); + while (ms.GetAvailableBytes() >= 6) + { + TransportStream child = new TransportStream(); + result.Add(child); + child.TransportStreamId = ms.ReadUInt16BE(); + child.OriginalNetworkId = ms.ReadUInt16BE(); + + byte byteD = ms.ReadUInt8(); + int reservedFutureUseC = (byteD & 0xf0) >> 4; + int transportDescriptorsLength = (byteD & 0x0f); + transportDescriptorsLength <<= 8; + transportDescriptorsLength += ms.ReadUInt8(); + if (transportDescriptorsLength > 0) + { + byte[] networkDescriptors = ms.ReadBytes(transportDescriptorsLength); + IEnumerable enumerable = tsDescriptorUnpacker.UnpackDescriptors(networkDescriptors, "RMT"); + foreach (TsDescriptor descriptor in enumerable) + { + if (!descriptor.Valid) + { + child.Valid = false; + return result.AsReadOnly(); + } + byte id = descriptor.GetDescriptorId(); + switch (id) + { + case 0xa8: + if (child.SatelliteForwardLink != null) + throw new NotImplementedException("multiple forward links?"); + child.SatelliteForwardLink = (_0xa8_SatelliteForwardLinkDescriptor)descriptor; + break; + case 0xa9: + if (child.SatelliteReturnLink != null) + throw new NotImplementedException("multiple return links?"); + child.SatelliteReturnLink = (_0xa9_SatelliteReturnLinkDescriptor)descriptor; + break; + default: + throw new NotImplementedException(); + } + } + } + child.Valid = true; + } + + return result.AsReadOnly(); + } + + public ushort NetworkId { get; } + public List<_0x4a_LinkageDescriptor> Linkages { get; } + public IReadOnlyList TransportStreams { get; } + + public class TransportStream : Validatable + { + public ushort TransportStreamId { get; internal set; } + public ushort OriginalNetworkId { get; internal set; } + public _0xa9_SatelliteReturnLinkDescriptor SatelliteReturnLink { get; internal set; } + public _0xa8_SatelliteForwardLinkDescriptor SatelliteForwardLink { get; internal set; } + + public override string ToString() + { + return String.Format("ONID {0}, TSID {1}", OriginalNetworkId, TransportStreamId); + } + + public DatabaseKeyRmtTransportStream ToKey(ushort networkId) + { + return new DatabaseKeyRmtTransportStream(networkId, TransportStreamId, OriginalNetworkId); + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Sct.cs b/skyscraper8/InteractionChannel/Model/Sct.cs new file mode 100644 index 0000000..0b76cd0 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Sct.cs @@ -0,0 +1,77 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Sct : Validatable + { + public Sct(MemoryStream ms) + { + InBitStream ibs = new InBitStream(ms); + uint superframeLoopCount = ibs.ReadBitsAsUint(8); superframeLoopCount++; + Superframes = new Superframe[superframeLoopCount]; + for (int i = 0; i < superframeLoopCount; i++) + { + Superframes[i] = new Superframe(); + Superframes[i].SuperframeId = ibs.ReadBitsAsUint(8); + Superframes[i].LargeTimingUncertainityFlag = ibs.ReadBitAsBoolean(); + uint reserved = ibs.ReadBitsAsUint(5); + Superframes[i].UplinkPolarization = (Superframe.PolarizationDefinition)ibs.ReadBitsAsUint(2); + Superframes[i].SuperframeStartTimeBase = ibs.ReadBitsAsULong(33); + uint reserved2 = ibs.ReadBitsAsUint(6); + Superframes[i].SuperframeStartTimeExt = ibs.ReadBitsAsUint(9); + Superframes[i].SuperframeDuration = ibs.ReadBitsAsUint(32); + Superframes[i].SuperframeCentreFrequency = ibs.ReadBitsAsUint(32); + Superframes[i].SuperframeCounter = ibs.ReadBitsAsUint(16); + uint reserved3 = ibs.ReadBitsAsUint(3); + uint frameLoopCount = ibs.ReadBitsAsUint(5); frameLoopCount++; + Superframes[i].Frames = new Superframe.Frame[frameLoopCount]; + for (uint j = 0; j < frameLoopCount; j++) + { + Superframes[i].Frames[j] = new Superframe.Frame(); + Superframes[i].Frames[j].FrameId = ibs.ReadBitsAsUint(8); + Superframes[i].Frames[j].FrameStartTime = ibs.ReadBitsAsUint(32); + Superframes[i].Frames[j].FrameCentreFrequencyOffset = ibs.ReadBitsAsUint(24); + } + } + uint crc32 = ibs.ReadBitsAsUint(32); + Valid = true; + } + + public Superframe[] Superframes { get; } + + public class Superframe + { + public uint SuperframeId { get; internal set; } + public bool LargeTimingUncertainityFlag { get; internal set; } + public PolarizationDefinition UplinkPolarization { get; internal set; } + public ulong SuperframeStartTimeBase { get; internal set; } + public uint SuperframeStartTimeExt { get; internal set; } + public uint SuperframeDuration { get; internal set; } + public uint SuperframeCentreFrequency { get; internal set; } + public uint SuperframeCounter { get; internal set; } + public Frame[] Frames { get; internal set; } + + public enum PolarizationDefinition : uint + { + Horizontal = 0, + Vertical = 1, + Left = 2, + Right = 3, + } + + public class Frame + { + public uint FrameId { get; internal set; } + public uint FrameStartTime { get; internal set; } + public uint FrameCentreFrequencyOffset { get; internal set; } + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Spt.cs b/skyscraper8/InteractionChannel/Model/Spt.cs new file mode 100644 index 0000000..980df28 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Spt.cs @@ -0,0 +1,39 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Spt : Validatable + { + public Spt(MemoryStream ms) + { + byte satelliteLoopCount = ms.ReadUInt8(); satelliteLoopCount++; + Satellites = new Satellite[satelliteLoopCount]; + for (int i = 0; i < satelliteLoopCount; i++) + { + Satellites[i] = new Satellite(); + Satellites[i].Id = ms.ReadUInt8(); + Satellites[i].X = ms.ReadUInt32BE(); + Satellites[i].Y = ms.ReadUInt32BE(); + Satellites[i].Z = ms.ReadUInt32BE(); + } + uint crc32 = ms.ReadUInt32BE(); + Valid = true; + } + + public Satellite[] Satellites { get; } + public class Satellite + { + public byte Id { get; internal set; } + public uint X { get; internal set; } + public uint Y { get; internal set; } + public uint Z { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Tbtp.cs b/skyscraper8/InteractionChannel/Model/Tbtp.cs new file mode 100644 index 0000000..60a820e --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Tbtp.cs @@ -0,0 +1,80 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Tbtp : Validatable + { + public const bool EIGHT_BIT_CHANNEL_ID = true; + + public Tbtp(MemoryStream ms) + { + InBitStream ibs = new InBitStream(ms); + GroupId = ibs.ReadBitsAsUint(8); + SuperframeCount = ibs.ReadBitsAsUint(16); + + uint reserved = ibs.ReadBitsAsUint(3); + uint frameLoopCount = ibs.ReadBitsAsUint(5); frameLoopCount++; + Frames = new TbtpFrame[frameLoopCount]; + for (uint i = 0; i < frameLoopCount; i++) + { + uint reserved2 = ibs.ReadBitsAsUint(3); + Frames[i] = new TbtpFrame(); + Frames[i].FrameNumber = ibs.ReadBitsAsUint(5); + uint reserved3 = ibs.ReadBitsAsUint(5); + uint btpLoopCount = ibs.ReadBitsAsUint(11); btpLoopCount++; + Frames[i].BTP = new TbtpFrame.BtpEntity[btpLoopCount]; + for (uint j = 0; j < btpLoopCount; j++) + { + Frames[i].BTP[j] = new TbtpFrame.BtpEntity(); + Frames[i].BTP[j].LogonId = ibs.ReadBitsAsUint(16); + bool multipleChannelsFlag = ibs.ReadBitAsBoolean(); + Frames[i].BTP[j].AssignmentType = (TbtpFrame.BtpEntity.AssignType)ibs.ReadBitsAsUint(2); + Frames[i].BTP[j].VbdcQueueEmptyFlag = ibs.ReadBitAsBoolean(); + bool reserved4 = ibs.ReadBitAsBoolean(); + Frames[i].BTP[j].StartSlot = ibs.ReadBitsAsUint(11); + if (multipleChannelsFlag) + { + Frames[i].BTP[j].ChannelId = ibs.ReadBitsAsUint(8); + } + Frames[i].BTP[j].AssignmentCount = ibs.ReadBitsAsUint(8); + } + } + uint crc32 = ibs.ReadBitsAsUint(32); + Valid = true; + } + + public uint GroupId { get; } + public uint SuperframeCount { get; } + public TbtpFrame[] Frames { get; } + + public class TbtpFrame + { + public uint FrameNumber { get; internal set; } + public BtpEntity[] BTP { get; internal set; } + + public class BtpEntity + { + public uint LogonId { get; internal set; } + public AssignType AssignmentType { get; internal set; } + public bool VbdcQueueEmptyFlag { get; internal set; } + public uint StartSlot { get; internal set; } + public uint? ChannelId { get; internal set; } + public uint AssignmentCount { get; internal set; } + + public enum AssignType + { + OneTimeAssignment = 0, + RepeatingAssignment = 1, + AssignmentRelease = 2 + } + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Tct.cs b/skyscraper8/InteractionChannel/Model/Tct.cs new file mode 100644 index 0000000..0af8b59 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Tct.cs @@ -0,0 +1,160 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + internal class Tct : Validatable + { + public Tct(MemoryStream ms) + { + byte timeslotLoopCount = ms.ReadUInt8(); timeslotLoopCount++; + Timeslots = new Timeslot[timeslotLoopCount]; + for (int i = 0; i < timeslotLoopCount; i++) + { + if (ms.GetAvailableBytes() < 9) + { + Valid = false; + return; + } + Timeslots[i] = new Timeslot(); + Timeslots[i].TimeslotId = ms.ReadUInt8(); + Timeslots[i].SymbolRate = ms.ReadUInt24BE(); + Timeslots[i].TimeslotDuration = ms.ReadUInt24BE(); + Timeslots[i].BurstStartOffset = ms.ReadUInt16BE(); + + byte byteA = ms.ReadUInt8(); + Timeslots[i].InnerCodeType = (byteA & 0x80) != 0; + Timeslots[i].InnerTypeOrdering = (byteA & 0x40) != 0; + Timeslots[i].OuterCoding = (byteA & 0x30) >> 4; + Timeslots[i].InnerCodePuncturing = (Timeslot.InnerCodingEnum)(byteA & 0x0f); + + byte byteB = ms.ReadUInt8(); + Timeslots[i].Modulation = (byteB & 0xf8) >> 3; + Timeslots[i].BasebandShaping = (Timeslot.ReturnLinkBasebandShaping)(byteB & 0x07); + + Timeslots[i].TimeslotPayloadType = (Timeslot.TimeslotType)ms.ReadUInt8(); + byte byteC = ms.ReadUInt8(); + Timeslots[i].RouteIdFlag = (byteA & 0x80) != 0; + Timeslots[i].AcmFlag = (byteA & 0x40) != 0; + Timeslots[i].MobFlag = (byteA & 0x20) != 0; + Timeslots[i].SacLength = (byteA & 0x1f); + + byte byteD = ms.ReadUInt8(); + Timeslots[i].RequestFlag = (byteD & 0x80) != 0; + Timeslots[i].MandCFlag = (byteD & 0x40) != 0; + Timeslots[i].GroupIdFlag = (byteD & 0x20) != 0; + Timeslots[i].LogonIdFlag = (byteD & 0x10) != 0; + Timeslots[i].CapacityRequestsNumber = (byteD & 0x0e); + Timeslots[i].NewPermutation = (byteD & 0x01) != 0; + if (Timeslots[i].InnerCodeType && Timeslots[i].NewPermutation) + { + Timeslots[i].P0 = ms.ReadUInt8(); + Timeslots[i].P1 = ms.ReadUInt16BE(); + Timeslots[i].P2 = ms.ReadUInt16BE(); + Timeslots[i].P3 = ms.ReadUInt16BE(); + } + + int preambleLength = ms.ReadUInt8(); + InBitStream ibs = new InBitStream(ms); + long neededBits = preambleLength * 2; + if (neededBits > ibs.GetBitsAvailable()) + { + Valid = false; + return; + } + for (int j = 0; j < preambleLength; j++) + { + Timeslots[i].PreambleSymbol <<= 2; + Timeslots[i].PreambleSymbol += ibs.ReadBitsAsByte(2); + } + + } + uint crc32 = ms.ReadUInt32BE(); + Valid = true; + } + + public Timeslot[] Timeslots { get; } + + public class Timeslot + { + public byte TimeslotId { get; internal set; } + public uint SymbolRate { get; internal set; } + public uint TimeslotDuration { get; internal set; } + public ushort BurstStartOffset { get; internal set; } + public bool InnerCodeType { get; internal set; } + public bool InnerTypeOrdering { get; internal set; } + public int OuterCoding { get; internal set; } + public InnerCodingEnum InnerCodePuncturing { get; internal set; } + public int Modulation { get; internal set; } + public ReturnLinkBasebandShaping BasebandShaping { get; internal set; } + public TimeslotType TimeslotPayloadType { get; internal set; } + public bool RouteIdFlag { get; internal set; } + public bool AcmFlag { get; internal set; } + public bool MobFlag { get; internal set; } + public int SacLength { get; internal set; } + public bool RequestFlag { get; internal set; } + public bool MandCFlag { get; internal set; } + public bool GroupIdFlag { get; internal set; } + public bool LogonIdFlag { get; internal set; } + public int CapacityRequestsNumber { get; internal set; } + public bool NewPermutation { get; internal set; } + public byte P0 { get; internal set; } + public ushort P1 { get; internal set; } + public ushort P2 { get; internal set; } + public ushort P3 { get; internal set; } + public long PreambleSymbol { get; internal set; } + + public enum InnerCodingEnum + { + Half = 0, + TwoThirds = 1, + ThreeFourths = 2, + FiveSixths = 3, + SevenEights = 4, + OneThirds = 5, + TwoFifths = 6, + FourFifths = 7, + SixSevenths = 8, + OneFourths = 9, + ThreeFifths = 10, + EightNinths = 11, + NineTenths = 12, + Omitted = 15 + } + + public enum ReturnLinkBasebandShaping + { + _0_35 = 0, + _0_25 = 1, + _0_20 = 2 + } + + public enum TimeslotType + { + TRF_One_ATM = 0x01, + TRF_Two_ATM = 0x02, + TRF_Four_ATM = 0x04, + TRF_MPEG2 = 0x05, + CSC = 0x06, + ACQ = 0x07, + SYNC = 0x08, + DVBS2_Normal_Pilots = 0x09, + DVBS2_Normal = 0x0a, + DVBS2_Short_Pilots = 0x0b, + DVBS2_Short = 0x0c, + DVBS2_Extension = 0x0d + } + + public override string ToString() + { + return String.Format("ID = {0}, SymbolRate = {1}, PayloadType = {2}, Acm = {3},", this.TimeslotId, SymbolRate, TimeslotPayloadType, AcmFlag); + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Tim.cs b/skyscraper8/InteractionChannel/Model/Tim.cs new file mode 100644 index 0000000..26c10b5 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Tim.cs @@ -0,0 +1,292 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + public class Tim : Validatable + { + public Tim(MemoryStream ms, bool broadcast) + { + Broadcast = broadcast; + Status = ms.ReadUInt8(); + + byte descriptorLoopCount = ms.ReadUInt8(); descriptorLoopCount++; + int minimumRequiredLength = descriptorLoopCount * 2; + if (ms.GetAvailableBytes() < minimumRequiredLength) + { + Valid = false; + return; + } + Descriptors = new TsDescriptor[descriptorLoopCount]; + for (int i = 0; i < descriptorLoopCount; i++) + { + //we don't have a way to read single descriptors so far + byte descriptorTag = ms.ReadUInt8(); + byte descriptorLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < minimumRequiredLength) + { + Valid = false; + return; + } + byte[] descriptorBuffer = ms.ReadBytes(descriptorLength); + switch(descriptorTag) + { + case 0x4a: + //Linkage_descriptor + //must appear in RMT, not TIM + Valid = false; + return; + case 0xa0: + Descriptors[i] = new _0xa0_NetworkLayerInfoDescriptor(descriptorBuffer); + break; + case 0xa1: + Descriptors[i] = new _0xa1_CorrectionMessageDescriptor(descriptorBuffer); + break; + case 0xa2: + Descriptors[i] = new _0xa2_LoginInitializeDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xa3: + Descriptors[i] = new _0xa3_AcqAssignDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xa4: + Descriptors[i] = new _0xa4_SyncAssignDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xa5: + Descriptors[i] = new _0xa5_EncryptedLoginIdDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xa7: + //RCS_content_descriptor + //can't appear in a TIM. + Valid = false; + return; + case 0xa8: + Descriptors[i] = new _0xa8_SatelliteForwardLinkDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xa9: + Descriptors[i] = new _0xa9_SatelliteReturnLinkDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xaa: + Descriptors[i] = new _0xaa_TableUpdateDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xab: + Descriptors[i] = new _0xab_ContentionControlDescriptor(descriptorBuffer); + break; + case 0xac: + Descriptors[i] = new _0xac_CorrectionControlDescriptor(descriptorBuffer); + break; + case 0xad: + Descriptors[i] = new _0xad_ForwardInteractionPathDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xae: + Descriptors[i] = new _0xae_ReturnInteractionPathDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xb1: + Descriptors[i] = new _0xb1_CorrectionMessageExtensionDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xb5: + Descriptors[i] = new _0xb5_ImplementationTypeDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xaf: + Descriptors[i] = new _0xaf_ConnectionControlDescriptor(descriptorBuffer); + break; + case 0xb2: + Descriptors[i] = new _0xb2_ReturnTransmissionModesDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xb3: + Descriptors[i] = new _0xb3_MeshLogonInitializeDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + case 0xb6: + Descriptors[i] = new _0xb6_LlFecIdentifierDescriptor(descriptorBuffer); + if (!Descriptors[i].Valid) + { + Valid = false; + return; + } + break; + default: + if (descriptorTag >= 0x00 && descriptorTag <= 0x49) + { + //Reserved + continue; + } + if (descriptorTag >= 0x4b && descriptorTag <= 0x9f) + { + //Reserved + continue; + } + if (descriptorTag >= 0xb7 && descriptorTag <= 0xdf) + { + //Reserved + continue; + } + if (descriptorTag >= 0xe0 && descriptorTag <= 0xfe) + { + Descriptors[i] = new UserDefinedDescriptor(descriptorTag, descriptorBuffer); + break; + } + if (descriptorTag == 0xff) + { + //Forbidden + Valid = false; + return; + } + throw new NotImplementedException(String.Format("TIM Parse 0x{0:X2}", descriptorTag)); + } + } + + byte[] padBytes = ms.ReadBytes(ms.GetAvailableBytes() - 4); + uint crc32 = ms.ReadUInt32BE(); + Valid = true; + } + + public bool Broadcast { get; } + public byte Status { get; } + + public TsDescriptor[] Descriptors { get; } + + public bool IDEncrypt => (Status & 0x80) != 0; + public bool LogonFail => (!Broadcast & ((Status & 0x40) != 0)); + public bool LogonDenied => (!Broadcast & ((Status & 0x20) != 0)); + public bool Logoff => (!Broadcast & ((Status & 0x10) != 0)); + public bool TransmitDisable => (!Broadcast & ((Status & 0x08) != 0)); + public bool RainFadeRelease => (!Broadcast & ((Status & 0x04) != 0)); + public bool RainFadeDetect => (!Broadcast & ((Status & 0x02) != 0)); + public bool WakeUp => (!Broadcast & ((Status & 0x01) != 0)); + + public bool CscLinkFailureRec => (Broadcast & ((Status & 0x10) != 0)); + public bool LinkFailureRecovery => (Broadcast & ((Status & 0x08) != 0)); + public bool ReturnLinkFailure => (Broadcast & ((Status & 0x04) != 0)); + public bool NccReceiveFailure => (Broadcast & ((Status & 0x02) != 0)); + public bool SchedulerFailure => (Broadcast & ((Status & 0x01) != 0)); + + internal void Handle(PhysicalAddress macAddress, ushort? networkId, InteractionChannelHandler handler) + { + foreach(TsDescriptor descriptor in Descriptors) + { + if (descriptor == null) + { + continue; + } + if (descriptor is UserDefinedDescriptor) + { + continue; + } + if (!descriptor.Valid) + { + Valid = false; + return; + } + byte id = descriptor.GetDescriptorId(); + switch(id) + { + case 0xa1: + if (Broadcast) + { + if (networkId.HasValue) + { + handler.OnCorrectionMessage(networkId.Value, ToCmt((_0xa1_CorrectionMessageDescriptor)descriptor)); + } + } + else + { + handler.OnCorrectionMessage(macAddress, ((_0xa1_CorrectionMessageDescriptor)descriptor)); + } + break; + case 0xab: + handler.OnContentionControl(macAddress, ((_0xab_ContentionControlDescriptor)descriptor)); + break; + case 0xac: + handler.OnCorrectionControl(macAddress, (_0xac_CorrectionControlDescriptor)descriptor); + break; + case 0xa0: + handler.OnNetworkLayerInfo(macAddress, (_0xa0_NetworkLayerInfoDescriptor)descriptor); + break; + default: + if (id >= 0xe0 && id <= 0xfe) + break; + throw new NotImplementedException(String.Format("TIM Handle 0x{0:X2}", id)); + } + } + } + + private Cmt ToCmt(_0xa1_CorrectionMessageDescriptor descriptor) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/InteractionChannel/Model/Tmst.cs b/skyscraper8/InteractionChannel/Model/Tmst.cs new file mode 100644 index 0000000..7894452 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model/Tmst.cs @@ -0,0 +1,24 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model +{ + internal class Tmst : Validatable + { + public Tmst(MemoryStream ms) + { + byte transmissionModeCount = ms.ReadUInt8(); + Modes = ms.ReadBytes(transmissionModeCount); + uint crc32 = ms.ReadUInt32BE(); + Valid = true; + } + + public byte[] Modes { get; } + } +} diff --git a/skyscraper8/InteractionChannel/Model2/Fct2.cs b/skyscraper8/InteractionChannel/Model2/Fct2.cs new file mode 100644 index 0000000..d1879f6 --- /dev/null +++ b/skyscraper8/InteractionChannel/Model2/Fct2.cs @@ -0,0 +1,18 @@ +using skyscraper5.Skyscraper; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model2 +{ + internal class Fct2 : Validatable + { + public Fct2(MemoryStream ms) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/InteractionChannel/Model2/Tbtp2.cs b/skyscraper8/InteractionChannel/Model2/Tbtp2.cs new file mode 100644 index 0000000..54b8c0b --- /dev/null +++ b/skyscraper8/InteractionChannel/Model2/Tbtp2.cs @@ -0,0 +1,137 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model2 +{ + internal class Tbtp2 : Validatable + { + public Tbtp2(MemoryStream ms) + { + GroupId = ms.ReadUInt8(); + SuperframeSequence = ms.ReadUInt8(); + AssignmentContext = ms.ReadUInt8(); + SuperframeCount = ms.ReadUInt8(); + AssignmentFormat = ms.ReadUInt8(); + + byte frameLoopCount = ms.ReadUInt8(); + frameLoopCount++; + Frames = new Frame[frameLoopCount]; + for (int i = 0; i < Frames.Length; i++) + { + Frames[i] = new Frame(); + Frames[i].FrameNumber = ms.ReadUInt8(); + Frames[i].AssignmentOffset = ms.ReadUInt16BE(); + ushort assignmentLoopCount = ms.ReadUInt16BE(); + assignmentLoopCount++; + long sizeRequired = assignmentLoopCount * GetFormatSize(); + if (ms.GetAvailableBytes() < sizeRequired) + { + Valid = false; + return; + } + Frames[i].Assignments = new Assignment[assignmentLoopCount]; + for (int j = 0; j < assignmentLoopCount; j++) + { + Frames[i].Assignments[j] = new Assignment(); + if (AssignmentFormat == 0) + { + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(6); + } + if (AssignmentFormat == 1) + { + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(1); + } + if (AssignmentFormat == 2) + { + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(2); + } + if (AssignmentFormat == 3) + { + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(3); + } + if (AssignmentFormat == 10) + { + Frames[i].Assignments[j].DynamicTxType = ms.ReadUInt8(); + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(1); + } + if (AssignmentFormat == 11) + { + Frames[i].Assignments[j].DynamicTxType = ms.ReadUInt8(); + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(2); + } + if (AssignmentFormat == 12) + { + Frames[i].Assignments[j].DynamicTxType = ms.ReadUInt8(); + Frames[i].Assignments[j].AssignmentId = ms.ReadBytes(3); + } + if (AssignmentFormat > 127) + { + CannotParse = true; + return; + } + } + } + + long remainingContent = ms.GetAvailableBytes(); + remainingContent -= 4; + if (remainingContent > 0) + { + long reservedPerUnit = remainingContent; + reservedPerUnit -= frameLoopCount; + reservedPerUnit /= frameLoopCount; + } + + Valid = true; + CannotParse = false; + } + + public byte GroupId { get; } + public byte SuperframeSequence { get; } + public byte AssignmentContext { get; } + public byte SuperframeCount { get; } + public byte AssignmentFormat { get; } + public Frame[] Frames { get; } + + public class Frame + { + public byte FrameNumber { get; internal set; } + public ushort AssignmentOffset { get; internal set; } + public Assignment[] Assignments { get; internal set; } + } + + public class Assignment + { + public byte[] AssignmentId { get; internal set; } + public byte? DynamicTxType { get; internal set; } + } + + public bool? CannotParse { get; } + + public int GetFormatSize() + { + switch(AssignmentFormat) + { + case 0: return 6; + case 1: return 1; + case 2: return 2; + case 3: return 3; + case 10: return 2; + case 11: return 3; + case 12: return 4; + default: + if (AssignmentFormat > 127) + { + Valid = false; + return 100; + } + return 0; + } + } + } +} diff --git a/skyscraper8/InteractionChannel/Model2/Tmst2.cs b/skyscraper8/InteractionChannel/Model2/Tmst2.cs new file mode 100644 index 0000000..781155d --- /dev/null +++ b/skyscraper8/InteractionChannel/Model2/Tmst2.cs @@ -0,0 +1,48 @@ +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel.Model2 +{ + internal class Tmst2 : Validatable + { + public Tmst2(MemoryStream ms, int transmissionStandard) + { + CommonSystemMargin = ms.ReadUInt8(); + byte transmissionModeCount = ms.ReadUInt8(); + TransmissionModes = new TransmissionMode[transmissionModeCount]; + for (int i = 0; i < transmissionModeCount; i++) + { + TransmissionModes[i] = new TransmissionMode(); + byte bytea = ms.ReadUInt8(); + TransmissionModes[i].FrameLength = (bytea & 0xc0) >> 6; + TransmissionModes[i].PilotSymbols = (bytea & 0x20) != 0; + TransmissionModes[i].Modcod = (bytea & 0x1f); + TransmissionModes[i].ModcodSystemMargin = ms.ReadUInt8(); + TransmissionModes[i].ISI = ms.ReadUInt8(); + } + if (transmissionStandard == 3) + { + throw new NotImplementedException("Transmisison standard 3"); + } + Valid = true; + } + + public byte CommonSystemMargin { get; } + public TransmissionMode[] TransmissionModes { get; } + + public class TransmissionMode + { + public int FrameLength { get; internal set; } + public bool PilotSymbols { get; internal set; } + public int Modcod { get; internal set; } + public byte ModcodSystemMargin { get; internal set; } + public byte ISI { get; internal set; } + } + } +} diff --git a/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs b/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs new file mode 100644 index 0000000..ccb8771 --- /dev/null +++ b/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs @@ -0,0 +1,81 @@ +using skyscraper5.Mpeg2; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.InteractionChannel +{ + internal class NullInteractionChannelHandler : InteractionChannelHandler + { + public int GetRmtTransmissionStandard(ushort networkId) + { + return 0; + } + + public void OnContentionControl(PhysicalAddress macAddress, _0xab_ContentionControlDescriptor ccd) + { + } + + public void OnCorrectionControl(PhysicalAddress macAddress, _0xac_CorrectionControlDescriptor descriptor) + { + } + + public void OnCorrectionMessage(ushort interactiveNetworkId, Cmt cmt) + { + } + + public void OnCorrectionMessage(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + } + + public void OnFrameComposition(ushort interactiveNetworkId, Fct fct) + { + } + + public void OnInteractionChannelError(InteractionChannelErrorState unexpectedTable) + { + } + + public void OnNetworkLayerInfo(PhysicalAddress macAddress, TsDescriptor descriptor) + { + } + + public void OnNetworkLayerInfo(PhysicalAddress macAddress, _0xa0_NetworkLayerInfoDescriptor descriptor) + { + throw new NotImplementedException(); + } + + public void OnRcsMap(Rmt rmt) + { + } + + public void OnSatellitePosition(ushort interactiveNetworkId, Spt spt) + { + } + + public void OnSuperframeComposition(ushort interactiveNetworkId, Sct sct) + { + } + + public void OnTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp) + { + } + + public void OnTerminalInformation(PhysicalAddress macAddress, Tim tim) + { + } + + public void OnTimeslotComposition(ushort interactiveNetworkId, Tct tct) + { + } + + public void OnTransmissionModeSupport(ushort interactiveNetworkId, Tmst tmst) + { + } + } +} diff --git a/skyscraper8/Iso14496_1/Descriptors/InitialObjectDescriptor.cs b/skyscraper8/Iso14496_1/Descriptors/InitialObjectDescriptor.cs new file mode 100644 index 0000000..5f6f840 --- /dev/null +++ b/skyscraper8/Iso14496_1/Descriptors/InitialObjectDescriptor.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Iso14496_1.Descriptors +{ + [Iso14496_1Descriptor(0x02)] + public class InitialObjectDescriptor : Iso14496_1Descriptor + { + public InitialObjectDescriptor(byte[] buffer) + : base(buffer.Length) + { + MemoryStream ms = new MemoryStream(buffer, false); + ushort readUInt16Be = ms.ReadUInt16BE(); + ObjectDescriptorId = (readUInt16Be & 0xffc0) >> 6; + UrlFlag = (readUInt16Be & 0x0020) != 0; + IncludeInlineProfileLevelFlag = (readUInt16Be & 0x0010) != 0; + + if (UrlFlag) + { + byte urlLength = ms.ReadUInt8(); + UrlString = Encoding.ASCII.GetString(ms.ReadBytes(urlLength)); + } + else + { + OdProfileLevelIndication = ms.ReadUInt8(); + SceneProfileLevelIndication = ms.ReadUInt8(); + AudioProfileLevelIndication = ms.ReadUInt8(); + VisualProfileLevelIndication = ms.ReadUInt8(); + GraphicsProfileLevelIndication = ms.ReadUInt8(); + } + + UnpackDescriptors(ms.ReadBytes(ms.GetAvailableBytes())); + } + + private void UnpackDescriptors(byte[] buffer) + { + IEnumerable descriptors = Iso14496_1DescriptorUnpacker.GetInstance().UnpackDescriptors(buffer); + foreach (Iso14496_1Descriptor descriptor in descriptors) + { + string name = descriptor.GetType().Name; + switch (name) + { + default: + throw new NotImplementedException(name); + } + } + } + + public byte? GraphicsProfileLevelIndication { get; private set; } + + public byte? VisualProfileLevelIndication { get; private set; } + + public byte? AudioProfileLevelIndication { get; private set; } + + public byte? SceneProfileLevelIndication { get; private set; } + + public byte? OdProfileLevelIndication { get; private set; } + + public string UrlString { get; private set; } + + public bool IncludeInlineProfileLevelFlag { get; private set; } + + public bool UrlFlag { get; private set; } + + public int ObjectDescriptorId { get; private set; } + } +} diff --git a/skyscraper8/Iso14496_1/Iso14496_1Descriptor.cs b/skyscraper8/Iso14496_1/Iso14496_1Descriptor.cs new file mode 100644 index 0000000..c0c5931 --- /dev/null +++ b/skyscraper8/Iso14496_1/Iso14496_1Descriptor.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Iso14496_1 +{ + public class Iso14496_1Descriptor + { + protected Iso14496_1Descriptor(int size) + { + SizeOfInstance = size; + } + public int SizeOfInstance { get; protected set; } + } +} diff --git a/skyscraper8/Iso14496_1/Iso14496_1DescriptorAttribute.cs b/skyscraper8/Iso14496_1/Iso14496_1DescriptorAttribute.cs new file mode 100644 index 0000000..d52b232 --- /dev/null +++ b/skyscraper8/Iso14496_1/Iso14496_1DescriptorAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Iso14496_1 +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class Iso14496_1DescriptorAttribute : Attribute + { + public byte Tag { get; } + + public Iso14496_1DescriptorAttribute(byte tag) + { + Tag = tag; + } + } +} diff --git a/skyscraper8/Iso14496_1/Iso14496_1DescriptorUnpacker.cs b/skyscraper8/Iso14496_1/Iso14496_1DescriptorUnpacker.cs new file mode 100644 index 0000000..8cd1ec3 --- /dev/null +++ b/skyscraper8/Iso14496_1/Iso14496_1DescriptorUnpacker.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Iso14496_1 +{ + class Iso14496_1DescriptorUnpacker + { + private static Iso14496_1DescriptorUnpacker _instance; + + private Iso14496_1DescriptorUnpacker() + { + + } + + public static Iso14496_1DescriptorUnpacker GetInstance() + { + if (_instance == null) + { + _instance = new Iso14496_1DescriptorUnpacker(); + } + + return _instance; + } + + public IEnumerable UnpackDescriptors(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 0) + { + byte tag = ms.ReadUInt8(); + if (tag >= 0x15 && tag <= 0x3f) + { + //I have ISO 14496-1 Second edition available. + //This area is reserved, and since there is no length in the descriptors, + //we can't know when the next descriptor starts, we'll just abort. + yield break; + } + + if (tag >= 0x4b) + { + //Above 0x4b is either reserved for OCI extensions, otherwise reserved, + //user private or forbidden according to ISO 14496-1 Second edition. + //This area is reserved, and since there is no length in the descriptors, + //we can't know when the next descriptor starts, we'll just abort. + yield break; + } + + throw new NotImplementedException(this.GetType().Name); + } + } + } +} diff --git a/skyscraper8/Mheg5/Mheg5DataBroadcastIdSelector.cs b/skyscraper8/Mheg5/Mheg5DataBroadcastIdSelector.cs new file mode 100644 index 0000000..041bd79 --- /dev/null +++ b/skyscraper8/Mheg5/Mheg5DataBroadcastIdSelector.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mheg5 +{ + class Mheg5DataBroadcastIdSelector + { + public Mheg5DataBroadcastIdSelector(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + List applications = new List(); + while (ms.GetAvailableBytes() >= 4) + { + Application child = new Application(); + child.ApplicationTypeCode = ms.ReadUInt16BE(); + child.BootPriorityHint = ms.ReadUInt8(); + byte applicationSpecificDataLength = ms.ReadUInt8(); + child.ApplicationSpecificData = ms.ReadBytes(applicationSpecificDataLength); + applications.Add(child); + } + + this.Applications = applications.AsReadOnly(); + } + + public ReadOnlyCollection Applications { get; private set; } + + public class Application + { + public ushort ApplicationTypeCode { get; set; } + public byte BootPriorityHint { get; set; } + public byte[] ApplicationSpecificData { get; set; } + } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x00_ApplicationDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x00_ApplicationDescriptor.cs new file mode 100644 index 0000000..3512529 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x00_ApplicationDescriptor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x00,"AIT")] + public class ApplicationDescriptor : TsDescriptor + { + public ApplicationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + int application_profiles_length = ms.ReadUInt8(); + if (application_profiles_length % 5 != 0) + throw new MhpException("{0} % 5 != 0", nameof(application_profiles_length)); + application_profiles_length /= 5; + + ApplicationProfiles = new ApplicationProfileEncoding[application_profiles_length]; + for (int i = 0; i < application_profiles_length; i++) + { + ApplicationProfiles[i] = new ApplicationProfileEncoding(); + ApplicationProfiles[i].ApplicationProfile = ms.ReadUInt16BE(); + ApplicationProfiles[i].Major = ms.ReadUInt8(); + ApplicationProfiles[i].Minor = ms.ReadUInt8(); + ApplicationProfiles[i].Micro = ms.ReadUInt8(); + } + + byte readUInt8 = ms.ReadUInt8(); + ServiceBoundFlag = ((readUInt8 & 0x80)) != 0; + Visibility = ((readUInt8 & 0x60) >> 5); + + ApplicationPriority = ms.ReadUInt8(); + + long availableBytes = ms.GetAvailableBytes(); + TransportProtocolLabel = new byte[availableBytes]; + for (long i = 0; i < availableBytes; i++) + { + TransportProtocolLabel[i] = ms.ReadUInt8(); + } + } + + public byte[] TransportProtocolLabel { get; private set; } + + public byte ApplicationPriority { get; private set; } + + public int Visibility { get; private set; } + + public bool ServiceBoundFlag { get; private set; } + + public ApplicationProfileEncoding[] ApplicationProfiles { get; private set; } + + public struct ApplicationProfileEncoding + { + public ushort ApplicationProfile { get; internal set; } + public byte Major { get; internal set; } + + public byte Minor { get; internal set; } + public byte Micro { get; internal set; } + } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x01_ApplicationNameDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x01_ApplicationNameDescriptor.cs new file mode 100644 index 0000000..6fe9e82 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x01_ApplicationNameDescriptor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x01,"AIT")] + class ApplicationNameDescriptor : TsDescriptor + { + public ApplicationNameDescriptor(byte[] buffer) + { + Dictionary temp = new Dictionary(); + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() >= 4) + { + string key = Encoding.ASCII.GetString(ms.ReadBytes(3)); + byte valueLength = ms.ReadUInt8(); + string value = Encoding.ASCII.GetString(ms.ReadBytes(valueLength)); + temp.Add(key, value); + } + ApplicationNames = new ReadOnlyDictionary(temp); + } + + public ReadOnlyDictionary ApplicationNames { get; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x02_TransportProtocolDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x02_TransportProtocolDescriptor.cs new file mode 100644 index 0000000..ecb973a --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x02_TransportProtocolDescriptor.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mhp.Descriptors.InteractionTransportSelectors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x02,"AIT")] + public class TransportProtocolDescriptor : TsDescriptor + { + public TransportProtocolDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ProtocolId = ms.ReadUInt16BE(); + TransportProtocolLabel = ms.ReadUInt8(); + + if (ms.GetAvailableBytes() > 0) + { + switch (ProtocolId) + { + case 0x0001: + Selector = new ObjectCarouselTransportSelector(ms); + break; + case 0x0003: + Selector = new InteractionTransportSelector(ms); + break; + default: + Console.WriteLine("TransportProtocolDescriptor: Unknown ProtocolId 0x{0:X4}", ProtocolId); + //TODO: Generischen Selektor bauen + Selector = null; + break; + } + } + } + + public byte TransportProtocolLabel { get; private set; } + + public ushort ProtocolId { get; private set; } + + public TransportProtocolSelector Selector { get; private set; } + + protected bool Equals(TransportProtocolDescriptor other) + { + return TransportProtocolLabel == other.TransportProtocolLabel && ProtocolId == other.ProtocolId && Selector.Equals(other.Selector); + } + + 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((TransportProtocolDescriptor)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = TransportProtocolLabel.GetHashCode(); + hashCode = (hashCode * 397) ^ ProtocolId.GetHashCode(); + hashCode = (hashCode * 397) ^ Selector.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x03_DvbJApplicationDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x03_DvbJApplicationDescriptor.cs new file mode 100644 index 0000000..dcb4ab8 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x03_DvbJApplicationDescriptor.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x03,"AIT")] + class DvbJApplicationDescriptor : TsDescriptor + { + public DvbJApplicationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + List result = new List(); + while (ms.GetAvailableBytes() > 0) + { + byte parameterLength = ms.ReadUInt8(); + result.Add(Encoding.ASCII.GetString(ms.ReadBytes(parameterLength))); + } + + Args = result.ToArray(); + } + + public string[] Args { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x04_DvbJApplicationLocationDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x04_DvbJApplicationLocationDescriptor.cs new file mode 100644 index 0000000..a096837 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x04_DvbJApplicationLocationDescriptor.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x04,"AIT")] + class DvbJApplicationLocationDescriptor : TsDescriptor + { + public DvbJApplicationLocationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte baseDirectoryLength = ms.ReadUInt8(); + BaseDirectory = Encoding.ASCII.GetString(ms.ReadBytes(baseDirectoryLength)); + byte classPathExtensionLength = ms.ReadUInt8(); + ClassPathExtension = Encoding.ASCII.GetString(ms.ReadBytes(classPathExtensionLength)); + InitialClass = Encoding.ASCII.GetString(ms.ReadBytes(ms.GetAvailableBytes())); + } + + public string InitialClass { get; private set; } + + public string ClassPathExtension { get; private set; } + + public string BaseDirectory { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x05_ExternalApplicationAuthorisationDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x05_ExternalApplicationAuthorisationDescriptor.cs new file mode 100644 index 0000000..95bcd3e --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x05_ExternalApplicationAuthorisationDescriptor.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x05,"AIT")] + public class ExternalApplicationAuthorisationDescriptor : TsDescriptor + { + public ExternalApplicationAuthorisationDescriptor(byte[] buffer) + { + Authorisations = new ExternalAuthorization[buffer.Length / 7]; + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < Authorisations.Length; i++) + { + ExternalAuthorization child = new ExternalAuthorization(); + Authorisations[i] = child; + + child.OrganisationId = ms.ReadUInt32BE(); + child.ApplicationId = ms.ReadUInt16BE(); + child.ApplicationPriority = ms.ReadUInt8(); + } + } + + public ExternalAuthorization[] Authorisations { get; set; } + public class ExternalAuthorization + { + public uint OrganisationId { get; set; } + public ushort ApplicationId { get; set; } + public byte ApplicationPriority { get; set; } + } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x10_ApplicationStorageDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x10_ApplicationStorageDescriptor.cs new file mode 100644 index 0000000..c71d904 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x10_ApplicationStorageDescriptor.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x10,"AIT")] + class ApplicationStorageDescriptor : TsDescriptor + { + public ApplicationStorageDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, true); + StorageProperty = ms.ReadUInt8(); + + byte readUInt8 = ms.ReadUInt8(); + NotLaunchableFromBroadcast = (readUInt8 & 0x80) != 0; + LaunchableCompletelyFromCache = (readUInt8 & 0x40) != 0; + IsLaunchableFromOlderVersion = (readUInt8 & 0x20) != 0; + + Version = ms.ReadUInt32BE() & 0xefffffff; + Priority = ms.ReadUInt8(); + } + + public byte Priority { get; private set; } + + public uint Version { get; private set; } + + public bool IsLaunchableFromOlderVersion { get; private set; } + + public bool LaunchableCompletelyFromCache { get; private set; } + + public bool NotLaunchableFromBroadcast { get; private set; } + + public byte StorageProperty { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x15_SimpleApplicationLocationDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x15_SimpleApplicationLocationDescriptor.cs new file mode 100644 index 0000000..a691304 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x15_SimpleApplicationLocationDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x15, "AIT")] + class SimpleApplicationLocationDescriptor : TsDescriptor + + { + public SimpleApplicationLocationDescriptor(byte[] buffer) + { + InitialPath = Encoding.ASCII.GetString(buffer); + } + + public string InitialPath { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x16_ApplicationUsageDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x16_ApplicationUsageDescriptor.cs new file mode 100644 index 0000000..d091291 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x16_ApplicationUsageDescriptor.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x16,"AIT")] + class ApplicationUsageDescriptor : TsDescriptor + { + public ApplicationUsageDescriptor(byte[] buffer) + { + UsageType = buffer[0]; + } + + public byte UsageType { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x17_SimpleApplicationBoundaryDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x17_SimpleApplicationBoundaryDescriptor.cs new file mode 100644 index 0000000..3c9e29c --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x17_SimpleApplicationBoundaryDescriptor.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x17,"AIT")] + class SimpleApplicationBoundaryDescriptor : TsDescriptor + { + public SimpleApplicationBoundaryDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + byte boundaryExtensionCount = ms.ReadUInt8(); + BoundaryExtensions = new string[boundaryExtensionCount]; + for (int i = 0; i < boundaryExtensionCount; i++) + { + byte boundaryExtensionLength = ms.ReadUInt8(); + BoundaryExtensions[i] = Encoding.ASCII.GetString(ms.ReadBytes(boundaryExtensionLength)); + } + } + + public string[] BoundaryExtensions { get; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x66_OcapAbstractServiceDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x66_OcapAbstractServiceDescriptor.cs new file mode 100644 index 0000000..16b7778 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x66_OcapAbstractServiceDescriptor.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + /// + /// Found at https://books.google.de/books?id=L5vcAwAAQBAJ&pg=PA96&lpg=PA96&dq=%22Abstract+Service+Descriptor%22&source=bl&ots=BnUrTob2Qr&sig=ACfU3U3tDpf8lRpWaMKlwkizTiDCRItjuA&hl=de&sa=X&ved=2ahUKEwiMt_S7wen3AhU357sIHb3EBLwQ6AF6BAg7EAM#v=onepage&q=%22Abstract%20Service%20Descriptor%22&f=false + /// + [SkyscraperPlugin] + [TsDescriptor(0x66,"AIT")] + class OcapAbstractServiceDescriptor : TsDescriptor + { + public OcapAbstractServiceDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + ushort readUInt16Be = ms.ReadUInt16BE(); + ServiceId = (uint)(readUInt16Be << 8); + ServiceId += ms.ReadUInt8(); + + AutoSelect = (ms.ReadUInt8() & 0x01) != 0; + + ServiceName = Encoding.ASCII.GetString(ms.ReadBytes(ms.GetAvailableBytes())); + } + + public string ServiceName { get; private set; } + + public bool AutoSelect { get; private set; } + + public uint ServiceId { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x67_OcapUnboundApplicationDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x67_OcapUnboundApplicationDescriptor.cs new file mode 100644 index 0000000..21e350b --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x67_OcapUnboundApplicationDescriptor.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + /// + /// Found at https://www.tvwithoutborders.com/tutorials/an-introduction-to-ocap/ocap-application-signalling/ + /// + [TsDescriptor(0x67,"AIT")] + [SkyscraperPlugin] + class OcapUnboundApplicationDescriptor : TsDescriptor + { + public OcapUnboundApplicationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + ServiceId = ms.ReadUInt24BE(); + VersionNumber = ms.ReadUInt32BE(); + } + + public uint VersionNumber { get; private set; } + + public uint ServiceId { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x69_OcapApplicationStorageDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x69_OcapApplicationStorageDescriptor.cs new file mode 100644 index 0000000..1738ffc --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x69_OcapApplicationStorageDescriptor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [TsDescriptor(0x69,"AIT")] + [SkyscraperPlugin] + class OcapApplicationStorageDescriptor : TsDescriptor + { + public OcapApplicationStorageDescriptor(byte[] buffer) + { + (buffer[1], buffer[0]) = (buffer[0], buffer[1]); + StoragePriority = BitConverter.ToUInt16(buffer, 0); + LaunchOrder = buffer[2]; + } + + public byte LaunchOrder { get; private set; } + + public ushort StoragePriority { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x70_LabelDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x70_LabelDescriptor.cs new file mode 100644 index 0000000..e904c05 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x70_LabelDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + [TsDescriptor(0x70,"DataCarousel")] + [SkyscraperPlugin] + internal class LabelDescriptor : TsDescriptor + { + public LabelDescriptor(byte[] buffer) + { + Label = Encoding.ASCII.GetString(buffer); + Label = Label.Trim('\0'); + } + + public string Label { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/0x72_ConnectionRequirementDescriptor.cs b/skyscraper8/Mhp/Descriptors/0x72_ConnectionRequirementDescriptor.cs new file mode 100644 index 0000000..06010fc --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/0x72_ConnectionRequirementDescriptor.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mhp.Descriptors +{ + /// + /// Didn't find the spec for this one, found an implementation in "sedec-2.0-java" + /// + [TsDescriptor(0x72,"AIT")] + [SkyscraperPlugin] + class ConnectionRequirementDescriptor : TsDescriptor + { + public ConnectionRequirementDescriptor(byte[] buffer) + { + IpConnectionRequired = (buffer[0] & 0x01) != 0; + } + + public bool IpConnectionRequired { get; private set; } + } +} diff --git a/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/InteractionTransportSelector.cs b/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/InteractionTransportSelector.cs new file mode 100644 index 0000000..adc4ef5 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/InteractionTransportSelector.cs @@ -0,0 +1,53 @@ +using System.IO; +using System.Text; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mhp.Descriptors.InteractionTransportSelectors +{ + public class InteractionTransportSelector : TransportProtocolSelector + { + internal InteractionTransportSelector(MemoryStream ms) : base(3) + { + byte readUInt8 = ms.ReadUInt8(); + UrlBase = Encoding.ASCII.GetString(ms.ReadBytes(readUInt8)); + + byte urlExtensionCount = ms.ReadUInt8(); + UrlExtensions = new string[urlExtensionCount]; + for (int i = 0; i < urlExtensionCount; i++) + { + byte urlExtensionLength = ms.ReadUInt8(); + UrlExtensions[i] = Encoding.ASCII.GetString(ms.ReadBytes(urlExtensionLength)); + } + } + + public string UrlBase { get; private set; } + + public string[] UrlExtensions { get; private set; } + + protected bool Equals(InteractionTransportSelector other) + { + return UrlBase == other.UrlBase && UrlExtensions.Equals(other.UrlExtensions); + } + + 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((InteractionTransportSelector)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (UrlBase.GetHashCode() * 397) ^ UrlExtensions.GetHashCode(); + } + } + + public override string ToString() + { + return UrlBase; + } + } +} diff --git a/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/ObjectCarouselTransportSelector.cs b/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/ObjectCarouselTransportSelector.cs new file mode 100644 index 0000000..8f16b6a --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/ObjectCarouselTransportSelector.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mhp.Descriptors.InteractionTransportSelectors +{ + public class ObjectCarouselTransportSelector : TransportProtocolSelector + { + internal ObjectCarouselTransportSelector(MemoryStream ms) : base(1) + { + byte readUInt8 = ms.ReadUInt8(); + RemoteConnection = (readUInt8 & 0x80) != 0; + if (RemoteConnection) + { + OriginalNetworkId = ms.ReadUInt16BE(); + TransportStreamId = ms.ReadUInt16BE(); + ServiceId = ms.ReadUInt16BE(); + } + + ComponentTag = ms.ReadUInt8(); + } + + public byte ComponentTag { get; set; } + + public ushort? ServiceId { get; set; } + + public ushort? TransportStreamId { get; set; } + + public ushort? OriginalNetworkId { get; set; } + + public bool RemoteConnection { get; private set; } + + protected bool Equals(ObjectCarouselTransportSelector other) + { + return ComponentTag == other.ComponentTag && ServiceId == other.ServiceId && TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId && RemoteConnection == other.RemoteConnection; + } + + 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((ObjectCarouselTransportSelector)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = ComponentTag.GetHashCode(); + hashCode = (hashCode * 397) ^ ServiceId.GetHashCode(); + hashCode = (hashCode * 397) ^ TransportStreamId.GetHashCode(); + hashCode = (hashCode * 397) ^ OriginalNetworkId.GetHashCode(); + hashCode = (hashCode * 397) ^ RemoteConnection.GetHashCode(); + return hashCode; + } + } + + public override string ToString() + { + if (ServiceId.HasValue) + return String.Format("DSM-CC Network {2}, TS #{3}, Service {1}, Component Tag {0}", ComponentTag, ServiceId.Value, OriginalNetworkId.Value, TransportStreamId.Value); + return String.Format("DSM-CC Component Tag {0}", ComponentTag, ServiceId); + } + } +} diff --git a/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/TransportProtocolSelector.cs b/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/TransportProtocolSelector.cs new file mode 100644 index 0000000..f5642a3 --- /dev/null +++ b/skyscraper8/Mhp/Descriptors/InteractionTransportSelectors/TransportProtocolSelector.cs @@ -0,0 +1,17 @@ +namespace skyscraper5.Mhp.Descriptors.InteractionTransportSelectors +{ + public abstract class TransportProtocolSelector + { + public ushort ProtocolId { get; } + + protected TransportProtocolSelector(ushort protocolId) + { + ProtocolId = protocolId; + } + + public abstract override int GetHashCode(); + + public abstract override bool Equals(object? obj); + + } +} diff --git a/skyscraper8/Mhp/MhpException.cs b/skyscraper8/Mhp/MhpException.cs new file mode 100644 index 0000000..7f43c01 --- /dev/null +++ b/skyscraper8/Mhp/MhpException.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; + +namespace skyscraper5.Mhp +{ + class MhpException : DvbException + { + public MhpException(string s, params object[] args) + : base(String.Format(s, args)) + { + } + } +} diff --git a/skyscraper8/Mhp/Si/AitEventHandler.cs b/skyscraper8/Mhp/Si/AitEventHandler.cs new file mode 100644 index 0000000..a3584f4 --- /dev/null +++ b/skyscraper8/Mhp/Si/AitEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mhp.Si.Model; + +namespace skyscraper5.Mhp.Si +{ + interface IAitEventHandler + { + void OnAitApplication(AitApplication aitApplication, ushort programNumber); + } +} diff --git a/skyscraper8/Mhp/Si/AitParser.cs b/skyscraper8/Mhp/Si/AitParser.cs new file mode 100644 index 0000000..15daf01 --- /dev/null +++ b/skyscraper8/Mhp/Si/AitParser.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mhp.Si +{ + class AitParser : IPsiProcessor + { + public IAitEventHandler EventHandler { get; } + public ushort ProgramNumber { get; } + public object Tag { get; set; } + + public AitParser(IAitEventHandler eventHandler, ushort resultProgramNumber) + { + EventHandler = eventHandler; + ProgramNumber = resultProgramNumber; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + byte table_id = ms.ReadUInt8(); + if (table_id != 0x74) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool section_syntax_indicator = (readUInt16Be & 0x8000) != 0; + if (!section_syntax_indicator) + return; + int section_length = (readUInt16Be & 0x0fff); + + readUInt16Be = ms.ReadUInt16BE(); + bool test_application_flag = (readUInt16Be & 0x8000) != 0; + int application_type = (readUInt16Be & 0x7fff); //1 = DVB-J, 2 = DVB-HTML, 16 = HBBTV + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x37) >> 2; + bool current_next_indicator = (readUInt8 & 0x01) != 0; + + byte section_number = ms.ReadUInt8(); + byte last_section_number = ms.ReadUInt8(); + + readUInt16Be = ms.ReadUInt16BE(); + int common_descriptors_length = (readUInt16Be & 0x0fff); + if (ms.GetAvailableBytes() < common_descriptors_length) + return; + byte[] common_descriptors = ms.ReadBytes(common_descriptors_length); + + ExternalApplicationAuthorisationDescriptor.ExternalAuthorization[] externalAuthorizations = null; + TransportProtocolDescriptor transportProtocolDescriptor = null; + OcapAbstractServiceDescriptor ocapAbstractServiceDescriptor = null; + PrivateDataSpecifierDescriptor privateDataSpecifierDescriptor = null; + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(common_descriptors, "AIT"); + foreach (TsDescriptor tsDescriptor in descriptors) + { + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(ExternalApplicationAuthorisationDescriptor): + externalAuthorizations = ((ExternalApplicationAuthorisationDescriptor)tsDescriptor).Authorisations; + break; + case nameof(TransportProtocolDescriptor): + transportProtocolDescriptor = (TransportProtocolDescriptor)tsDescriptor; + break; + case nameof(OcapAbstractServiceDescriptor): + ocapAbstractServiceDescriptor = (OcapAbstractServiceDescriptor)tsDescriptor; + break; + case nameof(PrivateDataSpecifierDescriptor): + privateDataSpecifierDescriptor = (PrivateDataSpecifierDescriptor)tsDescriptor; + break; + case nameof(UserDefinedDescriptor): + if (privateDataSpecifierDescriptor == null) + continue; + throw new NotImplementedException(nameof(UserDefinedDescriptor)); + default: + throw new NotImplementedException(tsDescriptor.GetType().Name); + } + } + + readUInt16Be = ms.ReadUInt16BE(); + int application_loop_length = (readUInt16Be & 0x0fff); + byte[] application_loop = ms.ReadBytes(application_loop_length); + ParseApplicationLoop(application_loop, externalAuthorizations, transportProtocolDescriptor, ocapAbstractServiceDescriptor); + + uint crc32 = ms.ReadUInt32BE(); + } + + private void ParseApplicationLoop(byte[] applicationLoop, ExternalApplicationAuthorisationDescriptor.ExternalAuthorization[] externalAuthorizations, TransportProtocolDescriptor transportProtocolDescriptor, OcapAbstractServiceDescriptor ocapAbstractServiceDescriptor) + { + MemoryStream ms = new MemoryStream(applicationLoop, false); + + while (ms.GetAvailableBytes() > 0) + { + ApplicationIdentifier applicationIdentifier = new ApplicationIdentifier(); + applicationIdentifier.OrganisationId = ms.ReadUInt32BE(); + applicationIdentifier.ApplicationId = ms.ReadUInt16BE(); + byte applicationControlCode = ms.ReadUInt8(); + AitApplication aitApplication = new AitApplication(applicationIdentifier, applicationControlCode); + + ushort readUInt16Be = ms.ReadUInt16BE(); + int application_descriptors_loop_length = readUInt16Be & 0x0fff; + byte[] applicationDescriptorsLoop = ms.ReadBytes(application_descriptors_loop_length); + ParseApplicationDescriptors(applicationDescriptorsLoop, aitApplication); + + if (externalAuthorizations != null) + aitApplication.ExternalAuthorizations = externalAuthorizations; + + if (ocapAbstractServiceDescriptor != null) + { + aitApplication.AutoSelect = ocapAbstractServiceDescriptor.AutoSelect; + aitApplication.ServiceId = ocapAbstractServiceDescriptor.ServiceId; + aitApplication.ServiceName = ocapAbstractServiceDescriptor.ServiceName; + } + + if (transportProtocolDescriptor != null && !aitApplication.TransportProtocols.Contains(transportProtocolDescriptor)) + { + aitApplication.TransportProtocols.Add(transportProtocolDescriptor); + } + EventHandler.OnAitApplication(aitApplication, ProgramNumber); + } + } + + private void ParseApplicationDescriptors(byte[] input, AitApplication output) + { + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "AIT"); + foreach (TsDescriptor tsDescriptor in descriptors) + { + //See Page 248 on ETSI TS 102 812 + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(TransportProtocolDescriptor): + TransportProtocolDescriptor transportProtocolDescriptor = (TransportProtocolDescriptor)tsDescriptor; + output.TransportProtocols.Add(transportProtocolDescriptor); + break; + case nameof(ApplicationDescriptor): + ApplicationDescriptor applicationDescriptor = (ApplicationDescriptor)tsDescriptor; + output.ApplicationProfiles = applicationDescriptor.ApplicationProfiles; + output.ApplicationPriority = applicationDescriptor.ApplicationPriority; + output.ServiceBoundFlag = applicationDescriptor.ServiceBoundFlag; + output.TransportProtocolLabel = applicationDescriptor.TransportProtocolLabel; + output.Visibility = applicationDescriptor.Visibility; + break; + case nameof(ApplicationNameDescriptor): + output.ApplicationName = ((ApplicationNameDescriptor)tsDescriptor).ApplicationNames; + break; + case nameof(SimpleApplicationLocationDescriptor): + output.InitialPath = ((SimpleApplicationLocationDescriptor)tsDescriptor).InitialPath; + break; + case nameof(SimpleApplicationBoundaryDescriptor): + output.BoundaryExtensions = ((SimpleApplicationBoundaryDescriptor)tsDescriptor).BoundaryExtensions; + break; + case nameof(ApplicationUsageDescriptor): + output.ApplicationUsageCode = ((ApplicationUsageDescriptor)tsDescriptor).UsageType; + break; + case nameof(DvbJApplicationDescriptor): + output.JvmArguments = ((DvbJApplicationDescriptor)tsDescriptor).Args; + break; + case nameof(DvbJApplicationLocationDescriptor): + DvbJApplicationLocationDescriptor dvbJald = (DvbJApplicationLocationDescriptor)tsDescriptor; + output.JvmBaseDirectory = dvbJald.BaseDirectory; + output.JvmClassPathExtension = dvbJald.ClassPathExtension; + output.JvmInitialClass = dvbJald.InitialClass; + break; + case nameof(ApplicationStorageDescriptor): + ApplicationStorageDescriptor asd = (ApplicationStorageDescriptor)tsDescriptor; + output.Version = asd.Version; + output.IsLaunchableFromOlderVersion = asd.IsLaunchableFromOlderVersion; + output.LaunchableCompletelyFromCache = asd.LaunchableCompletelyFromCache; + output.NotLaunchableFromBroadcast = asd.NotLaunchableFromBroadcast; + output.ApplicationStoragePriority = asd.Priority; + output.StorageProperty = asd.StorageProperty; + break; + case nameof(OcapUnboundApplicationDescriptor): + OcapUnboundApplicationDescriptor uad = (OcapUnboundApplicationDescriptor)tsDescriptor; + output.ServiceId = uad.ServiceId; + output.VersionNumber = uad.VersionNumber; + break; + case nameof(OcapApplicationStorageDescriptor): + OcapApplicationStorageDescriptor oasd = (OcapApplicationStorageDescriptor)tsDescriptor; + output.LaunchOrder = oasd.LaunchOrder; + output.StoragePriority = oasd.StoragePriority; + break; + case nameof(ConnectionRequirementDescriptor): + ConnectionRequirementDescriptor crd = (ConnectionRequirementDescriptor)tsDescriptor; + output.IpConnectionRequired = crd.IpConnectionRequired; + break; + default: + throw new NotImplementedException(); + } + } + } + } + + + public sealed class ApplicationIdentifierComparer : IComparer + { + public int Compare(ApplicationIdentifier x, ApplicationIdentifier y) + { + var organisationIdComparison = x.OrganisationId.CompareTo(y.OrganisationId); + if (organisationIdComparison != 0) return organisationIdComparison; + return x.ApplicationId.CompareTo(y.ApplicationId); + } + } + + public struct ApplicationIdentifier + { + public uint OrganisationId { get; set; } + public ushort ApplicationId { get; set; } + + public bool Equals(ApplicationIdentifier other) + { + return OrganisationId == other.OrganisationId && ApplicationId == other.ApplicationId; + } + + public override bool Equals(object obj) + { + return obj is ApplicationIdentifier other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((int)OrganisationId * 397) ^ ApplicationId.GetHashCode(); + } + } + } +} diff --git a/skyscraper8/Mhp/Si/Model/AitApplication.cs b/skyscraper8/Mhp/Si/Model/AitApplication.cs new file mode 100644 index 0000000..70ce6d7 --- /dev/null +++ b/skyscraper8/Mhp/Si/Model/AitApplication.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mhp.Descriptors.InteractionTransportSelectors; + +namespace skyscraper5.Mhp.Si.Model +{ + public class AitApplication + { + public ApplicationIdentifier ApplicationIdentifier { get; } + public byte ApplicationControlCode { get; } + + //Transport Protocol Descriptors + public List TransportProtocols { get; } + + //Application Descriptor + public ApplicationDescriptor.ApplicationProfileEncoding[] ApplicationProfiles { get; set; } + public byte? ApplicationPriority { get; set; } + public bool? ServiceBoundFlag { get; set; } + public byte[] TransportProtocolLabel { get; set; } + public int? Visibility { get; set; } + + //Application Name Descriptor + public ReadOnlyDictionary ApplicationName { get; set; } + + //Simple Application Location Descriptor + public string InitialPath { get; set; } + + //Simple Application Boundary Descriptor + public string[] BoundaryExtensions { get; set; } + + //Application Usage Descriptor + public byte? ApplicationUsageCode { get; set; } + + //0x03 dvb-j application descriptor + public string[] JvmArguments { get; set; } + + //0x04 dvb-j application location descriptor + public string JvmBaseDirectory { get; set; } + public string JvmClassPathExtension { get; set; } + public string JvmInitialClass { get; set; } + + //0x10 application storage descriptor + public uint? Version { get; set; } + public bool? IsLaunchableFromOlderVersion { get; set; } + public bool? LaunchableCompletelyFromCache { get; set; } + public bool? NotLaunchableFromBroadcast { get; set; } + public byte? ApplicationStoragePriority { get; set; } + public byte? StorageProperty { get; set; } + + //0x05 external application authorization descriptor + public ExternalApplicationAuthorisationDescriptor.ExternalAuthorization[] ExternalAuthorizations { get; set; } + + //0x66 abstract service descriptor + public bool? AutoSelect { get; set; } + public uint? ServiceId { get; set; } + public string ServiceName { get; set; } + + //0x68 unbound application descriptor + public uint? VersionNumber { get; set; } + + //0x69 ocap application storage descriptor + public byte? LaunchOrder { get; set; } + public ushort? StoragePriority { get; set; } + + //0x72 connection requirement descriptor + public bool? IpConnectionRequired { get; set; } + + public AitApplication(ApplicationIdentifier applicationIdentifier, byte applicationControlCode) + { + ApplicationIdentifier = applicationIdentifier; + ApplicationControlCode = applicationControlCode; + TransportProtocols = new List(); + } + + public string TryGetName() + { + if (ApplicationName != null) + { + foreach (KeyValuePair keyValuePair in ApplicationName) + { + return keyValuePair.Value; + } + } + + return string.Format("{0} / {1}", ApplicationIdentifier.OrganisationId, ApplicationIdentifier.ApplicationId); + } + + public string ConcatJvmArguments() + { + if (JvmArguments == null) + return null; + + if (JvmArguments.Length == 0) + return null; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < JvmArguments.Length; i++) + { + sb.Append(JvmArguments[i]); + sb.Append(" "); + } + return sb.ToString(); + } + } +} diff --git a/skyscraper8/Mpeg2/AdaptionFieldTooLargeException.cs b/skyscraper8/Mpeg2/AdaptionFieldTooLargeException.cs new file mode 100644 index 0000000..06ddca5 --- /dev/null +++ b/skyscraper8/Mpeg2/AdaptionFieldTooLargeException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + class AdaptionFieldTooLargeException : Mpeg2Exception + { + public AdaptionFieldTooLargeException() : base("") + { + } + } +} diff --git a/skyscraper8/Mpeg2/BannedTableAttribute.cs b/skyscraper8/Mpeg2/BannedTableAttribute.cs new file mode 100644 index 0000000..05cdd2e --- /dev/null +++ b/skyscraper8/Mpeg2/BannedTableAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class BannedTableAttribute : Attribute + { + public BannedTableAttribute(params string[] tables) + { + this.Tables = tables; + } + + public string[] Tables { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Crc32.cs b/skyscraper8/Mpeg2/Crc32.cs new file mode 100644 index 0000000..3c896f9 --- /dev/null +++ b/skyscraper8/Mpeg2/Crc32.cs @@ -0,0 +1,133 @@ +using System.IO; +using skyscraper5.Skyscraper.IO; +using static System.Collections.Specialized.BitVector32; + +namespace skyscraper5.Mpeg2 +{ + internal class DvbCrc32 + { + private DvbCrc32() + { + } + + private static readonly uint[] table = + { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + public static bool ValidatePsi(PsiSection section) + { + byte[] data = section.GetData(); + uint crc = 0xffffffff; + int offset = 0; + int end = section.BytesContained; + while (offset < end) + { + uint b = (crc >> 24) & 0xff; + int c = (data[offset]) & 0xff; + crc = (crc << 8) ^ table[b ^ c]; + offset++; + } + return crc == 0; + } + + public static bool ValidateCrc(MemoryStream ms, int offset, int end) + { + long restorePosition = ms.Position; + + uint crc = 0xffffffff; + while (offset < end) + { + ms.Position = offset; + uint b = (crc >> 24) & 0xff; + int c = (ms.ReadUInt8()) & 0xff; + crc = (crc << 8) ^ table[b ^ c]; + offset++; + } + + ms.Position = restorePosition; + return crc == 0; + } + + public static uint CreateCrc(MemoryStream ms, int offset, int end) + { + long restorePosition = ms.Position; + + uint crc = 0xffffffff; + while (offset < end) + { + ms.Position = offset; + uint b = (crc >> 24) & 0xff; + int c = (ms.ReadUInt8()) & 0xff; + crc = (crc << 8) ^ table[b ^ c]; + offset++; + } + + ms.Position = restorePosition; + return crc; + } + } +} diff --git a/skyscraper8/Mpeg2/DescriptorConversionNeededAttribute.cs b/skyscraper8/Mpeg2/DescriptorConversionNeededAttribute.cs new file mode 100644 index 0000000..1f8ac58 --- /dev/null +++ b/skyscraper8/Mpeg2/DescriptorConversionNeededAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class DescriptorConversionNeededAttribute : Attribute + { + public Type ConverterType { get; } + + public DescriptorConversionNeededAttribute(Type converterType) + { + ConverterType = converterType; + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x00_Reserved.cs b/skyscraper8/Mpeg2/Descriptors/0x00_Reserved.cs new file mode 100644 index 0000000..206334f --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x00_Reserved.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x00)] + [BannedTable("SDT","PMT","TSDT","BAT","NIT","CAT", "DataCarousel")] + class Reserved : TsDescriptor + { + public Reserved(byte[] buffer) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x01_Reserved.cs b/skyscraper8/Mpeg2/Descriptors/0x01_Reserved.cs new file mode 100644 index 0000000..43e3565 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x01_Reserved.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x01)] + [BannedTable("PMT","BAT","TSDT")] + class _0x01_Reserved + { + public _0x01_Reserved(byte[] buffer) + { + throw new Mpeg2Exception(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x02_VideoStreamDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x02_VideoStreamDescriptor.cs new file mode 100644 index 0000000..a459eeb --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x02_VideoStreamDescriptor.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x02,"PMT")] + [BannedTable("TSDT","BAT")] + class VideoStreamDescriptor : TsDescriptor + { + public VideoStreamDescriptor(byte[] buffer) + { + if (buffer.Length != 1 && buffer.Length != 3) + { + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + MultipleFramerateFlag = (readUInt8 & 0x80) != 0; + DecodeFramerateCode((readUInt8 & 0x78) >> 3); + Mpeg1OnlyFlag = (readUInt8 & 0x04) != 0; + ConstrainedParameterFlag = (readUInt8 & 0x02) != 0; + StillPictureFlag = (readUInt8 & 0x01) != 0; + if (Mpeg1OnlyFlag) + { + ProfileAndLevelIndication = ms.ReadUInt8(); + readUInt8 = ms.ReadUInt8(); + ChromaFormat = (readUInt8 & 0xc0) >> 6; + FrameRateExtensionFlag = (readUInt8 & 0x20) >> 5; + } + + this.Valid = true; + } + + public int? FrameRateExtensionFlag { get; private set; } + + public int? ChromaFormat { get; private set; } + + public byte? ProfileAndLevelIndication { get; private set; } + + public bool StillPictureFlag { get; private set; } + + public bool ConstrainedParameterFlag { get; private set; } + + public bool Mpeg1OnlyFlag { get; private set; } + + private void DecodeFramerateCode(int i) + { + switch (i) + { + case 0b0001: FrameRate = 24000.0 / 1001.0; break; + case 0b0010: FrameRate = 24; break; + case 0b0011: FrameRate = 25; break; + case 0b0100: FrameRate = 30000.0 / 1001.0; break; + case 0b0101: FrameRate = 30; break; + case 0b0110: FrameRate = 50; break; + case 0b0111: FrameRate = 60000.0 / 1001.0; break; + case 0b1000: FrameRate = 60; break; + } + } + + public double FrameRate { get; private set; } + + + public bool MultipleFramerateFlag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x03_AudioStreamDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x03_AudioStreamDescriptor.cs new file mode 100644 index 0000000..989dd9a --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x03_AudioStreamDescriptor.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(3,"PMT")] + [BannedTable("BAT","TSDT")] + class AudioStreamDescriptor : TsDescriptor + { + public AudioStreamDescriptor(byte[] buffer) + { + if (buffer.Length != 1) + return; + + byte uint8 = buffer[0]; + + FreeFormat = (uint8 & 0x8000) != 0; + ID = (uint8 & 0x4000) != 0; + Layer = (uint8 & 0x3000); + VariableRateAudio = (uint8 & 0x08) != 0; + int Reserved = (uint8 & 0x07); + base.Valid = true; + } + + public bool VariableRateAudio { get; set; } + + public int Layer { get; private set; } + + public bool ID { get; private set; } + + public bool FreeFormat { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x04_HierarchyDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x04_HierarchyDescriptor.cs new file mode 100644 index 0000000..124d677 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x04_HierarchyDescriptor.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x04,"PMT")] + [BannedTable("BAT","TSDT","NIT")] + class HierarchyDescriptor : TsDescriptor + { + public HierarchyDescriptor(byte[] buffer) + { + if (buffer.Length != 4) + { + Valid = false; + return; + } + + NoViewScalabilityFlag = (buffer[0] & 0x80) != 0; + NoTemporalScalabilityFlag = (buffer[0] & 0x40) != 0; + NoSpatialScalabilityFlag = (buffer[0] & 0x20) != 0; + NoQualityScalabilityFlag = (buffer[0] & 0x10) != 0; + HierarchyType = (buffer[0] & 0x0f); + HierarchyLayerType = (buffer[1] & 0x3f); + TrefPresentFlag = (buffer[2] & 0x80) != 0; + HierarchyEmbeddedLayerIndex = (buffer[3] & 0x3f); + HierarchyChannel = (buffer[4] & 0x3f); + Valid = true; + } + + public int HierarchyChannel { get; private set; } + + public int HierarchyEmbeddedLayerIndex { get; private set; } + + public bool TrefPresentFlag { get; private set; } + + public int HierarchyLayerType { get; private set; } + + public int HierarchyType { get; private set; } + + public bool NoQualityScalabilityFlag { get; private set; } + + public bool NoSpatialScalabilityFlag { get; private set; } + + public bool NoTemporalScalabilityFlag { get; private set; } + + public bool NoViewScalabilityFlag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x05_RegistrationDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x05_RegistrationDescriptor.cs new file mode 100644 index 0000000..24f6fd1 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x05_RegistrationDescriptor.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x05,"PMT","TSDT")] + [BannedTable("SDT")] + class RegistrationDescriptor : TsDescriptor + { + public RegistrationDescriptor(byte[] buffer) + { + if (buffer.Length < 4) + return; + MemoryStream ms = new MemoryStream(buffer, false); + FormatIdentifier = ms.ReadUInt32BE(); + AdditionalIdentificationInfo = ms.ReadBytes(ms.GetAvailableBytes()); + this.Valid = true; + } + + public byte[] AdditionalIdentificationInfo { get; private set; } + + public uint FormatIdentifier { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x06_DataStreamAlignmentDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x06_DataStreamAlignmentDescriptor.cs new file mode 100644 index 0000000..3389203 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x06_DataStreamAlignmentDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x06,"PMT")] + [BannedTable("TSDT")] + class DataStreamAlignmentDescriptor : TsDescriptor + { + public DataStreamAlignmentDescriptor(byte[] buffer) + { + AlignmentType = buffer[0]; + } + + public byte AlignmentType { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x07_TargetBackgroundGridDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x07_TargetBackgroundGridDescriptor.cs new file mode 100644 index 0000000..6a32f71 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x07_TargetBackgroundGridDescriptor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x07,"PMT")] + [BannedTable("TSDT")] + class TargetBackgroundGridDescriptor : TsDescriptor + { + public TargetBackgroundGridDescriptor(byte[] buffer) + { + if (buffer.Length != 4) + return; + + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x08_VideoWindowDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x08_VideoWindowDescriptor.cs new file mode 100644 index 0000000..b145e32 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x08_VideoWindowDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x08,"PMT")] + [BannedTable("TSDT")] + class VideoWindowDescriptor + { + public VideoWindowDescriptor(byte[] buffer) + { + throw new NotImplementedException(GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x09_CaDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x09_CaDescriptor.cs new file mode 100644 index 0000000..aab7de7 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x09_CaDescriptor.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x09,"CAT","PMT")] + [BannedTable("NIT")] + public class CaDescriptor : TsDescriptor + { + public CaDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + CaSystemId = ms.ReadUInt16BE(); + CaPid = ms.ReadUInt16BE() & 0x1fff; + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public CaDescriptor(ushort caSystemId, int caPid, byte[] privateData) + { + CaSystemId = caSystemId; + CaPid = caPid; + PrivateData = privateData; + } + + public byte[] PrivateData { get; private set; } + + public int CaPid { get; private set; } + + public ushort CaSystemId { get; private set; } + + protected bool Equals(CaDescriptor other) + { + return CaPid == other.CaPid && CaSystemId == other.CaSystemId; + } + + 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((CaDescriptor)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CaPid, CaSystemId); + } + + public override byte[] Serialize() + { + MemoryStream ms = new MemoryStream(); + ms.WriteUInt16BE(CaSystemId); + ms.WriteUInt16BE((ushort)(CaPid & 0x1fff)); + if (PrivateData != null) + ms.Write(PrivateData, 0, PrivateData.Length); + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x0A_Iso639LanguageDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x0A_Iso639LanguageDescriptor.cs new file mode 100644 index 0000000..b38d2cd --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x0A_Iso639LanguageDescriptor.cs @@ -0,0 +1,34 @@ +using System.Text; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(10, "PMT")] + [BannedTable("TSDT","SDT")] + class Iso639LanguageDescriptor : TsDescriptor + { + public Iso639LanguageDescriptor(byte[] buffer) + { + if (buffer.Length != 4) + return; + + Iso639LanguageCode = Encoding.ASCII.GetString(buffer, 0, 3); + AudioType = (AudioType)buffer[3]; + this.Valid = true; + } + + public string Iso639LanguageCode { get; private set; } + + public AudioType AudioType { get; private set; } + } + + public enum AudioType : byte + { + Undefined = 0, + CleanEffects = 1, + HearingImpaired = 2, + VisualImpairedCommentary = 3 + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x0B_SystemClockDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x0B_SystemClockDescriptor.cs new file mode 100644 index 0000000..a04167e --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x0B_SystemClockDescriptor.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0b,"PMT")] + [BannedTable("TSDT")] + class SystemClockDescriptor : TsDescriptor + { + public SystemClockDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + ExternalClockReferenceIndicator = (readUInt8 & 0x80) != 0; + ClockAccuracyInteger = (readUInt8 & 0x3f); + ClockAccuracyExponent = (ms.ReadUInt8() & 0xe0) >> 5; + } + + public int ClockAccuracyExponent { get; private set; } + + public int ClockAccuracyInteger { get; private set; } + public bool ExternalClockReferenceIndicator { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x0C_MultiplexBufferUtilizationDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x0C_MultiplexBufferUtilizationDescriptor.cs new file mode 100644 index 0000000..4f236cf --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x0C_MultiplexBufferUtilizationDescriptor.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0c,"PMT")] + [BannedTable("EIT")] + class MultiplexBufferUtilizationDescriptor : TsDescriptor + { + public MultiplexBufferUtilizationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + ushort readUInt16Be = ms.ReadUInt16BE(); + BoundValidFlag = (readUInt16Be & 0x8000) != 0; + LtwOffsetLowerBound = (readUInt16Be & 0x7fff); + readUInt16Be = ms.ReadUInt16BE(); + LtwOffsetUpperBound = (readUInt16Be & 0x7ffe) >> 1; + } + + public int LtwOffsetUpperBound { get; private set; } + + public int LtwOffsetLowerBound { get; private set; } + + public bool BoundValidFlag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x0D_CopyrightDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x0D_CopyrightDescriptor.cs new file mode 100644 index 0000000..398c815 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x0D_CopyrightDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0d,"PMT")] + [BannedTable("EIT")] + internal class _0x0D_CopyrightDescriptor : TsDescriptor + { + public _0x0D_CopyrightDescriptor(byte[] buffer) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x0E_MaximumBitrateDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x0E_MaximumBitrateDescriptor.cs new file mode 100644 index 0000000..0c72c87 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x0E_MaximumBitrateDescriptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0e,"PMT")] + class MaximumBitrateDescriptor : TsDescriptor + { + public MaximumBitrateDescriptor(byte[] buffer) + { + byte[] realBuffer = new byte[4]; + realBuffer[2] = buffer[0]; + realBuffer[1] = buffer[1]; + realBuffer[0] = buffer[2]; + uint uInt32 = BitConverter.ToUInt32(realBuffer, 0); + MaximumBitrate = uInt32 & (0x3fffff); + MaximumBitrate *= 50; + } + + public uint MaximumBitrate { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x0F_PrivateDataIndicatorDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x0F_PrivateDataIndicatorDescriptor.cs new file mode 100644 index 0000000..4a97a70 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x0F_PrivateDataIndicatorDescriptor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x0f,"PMT")] + class PrivateDataIndicatorDescriptor : TsDescriptor + { + public PrivateDataIndicatorDescriptor(byte[] buffer) + { + if (buffer.Length != 4) + return; + + Array.Reverse(buffer); + PrivateDataIndicator = BitConverter.ToUInt32(buffer); + this.Valid = true; + } + + public uint PrivateDataIndicator { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x10_SmoothingBufferDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x10_SmoothingBufferDescriptor.cs new file mode 100644 index 0000000..fa8267f --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x10_SmoothingBufferDescriptor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x10,"PMT")] + [BannedTable("TSDT","EIT")] + class SmoothingBufferDescriptor : TsDescriptor + { + public SmoothingBufferDescriptor(byte[] buffer) + { + byte[] tmpBuffer = new byte[4]; + tmpBuffer[0] = buffer[2]; + tmpBuffer[1] = buffer[1]; + tmpBuffer[2] = buffer[0]; + SbLeakRate = BitConverter.ToUInt32(tmpBuffer, 0) & 0x003fffff; + tmpBuffer[0] = buffer[5]; + tmpBuffer[1] = buffer[4]; + tmpBuffer[2] = buffer[3]; + SbSize = BitConverter.ToUInt32(tmpBuffer, 0) & 0x003fffff; + } + + public uint SbSize { get; private set; } + + public uint SbLeakRate { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x11_StdDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x11_StdDescriptor.cs new file mode 100644 index 0000000..8bb22c6 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x11_StdDescriptor.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x11,"PMT")] + [BannedTable("TSDT")] + class StdDescriptor : TsDescriptor + { + public StdDescriptor(byte[] buffer) + { + LeakValidFlag = (buffer[0] & 0x01) != 0; + } + + public bool LeakValidFlag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x12_IbpDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x12_IbpDescriptor.cs new file mode 100644 index 0000000..954b6ae --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x12_IbpDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x12,"PMT")] + [BannedTable("TSDT")] + class IbpDescriptor + { + public IbpDescriptor(byte[] buffer) + { + throw new NotImplementedException(GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x1C_Mpeg4AudioDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x1C_Mpeg4AudioDescriptor.cs new file mode 100644 index 0000000..de71303 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x1C_Mpeg4AudioDescriptor.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x1c,"PMT")] + class Mpeg4AudioDescriptor : TsDescriptor + { + public Mpeg4AudioDescriptor(byte[] buffer) + { + Mpeg4AudioProfileAndLevel = buffer[0]; + } + + public byte Mpeg4AudioProfileAndLevel { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x1D_IodDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x1D_IodDescriptor.cs new file mode 100644 index 0000000..d5b3971 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x1D_IodDescriptor.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Iso14496_1; +using skyscraper5.Iso14496_1.Descriptors; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x1d,"PMT")] + [BannedTable("TSDT")] + class IodDescriptor : TsDescriptor + { + public IodDescriptor(byte[] buffer) + { + ScopeOfIodLabel = buffer[0]; + IodLabel = buffer[1]; + + if (buffer[2] == 0x02) + { + byte[] iodBuffer = new byte[buffer.Length - 3]; + Array.Copy(buffer, 3, iodBuffer, 0, buffer.Length - 3); + InitialObjectDescriptor = new InitialObjectDescriptor(iodBuffer); + } + } + + public byte IodLabel { get; private set; } + + public byte ScopeOfIodLabel { get; private set; } + + public InitialObjectDescriptor InitialObjectDescriptor { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x1E_SLDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x1E_SLDescriptor.cs new file mode 100644 index 0000000..0a18a2c --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x1E_SLDescriptor.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x1e,"PMT")] + [BannedTable("TSDT")] + class SLDescriptor : TsDescriptor + { + public SLDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + EsId = ms.ReadUInt16BE(); + } + + public ushort EsId { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x1f_FmcDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x1f_FmcDescriptor.cs new file mode 100644 index 0000000..8689490 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x1f_FmcDescriptor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x1f,"PMT")] + [BannedTable("SDT","TSDT")] + public class FmcDescriptor : TsDescriptor + { + public FmcDescriptor(byte[] buffer) + { + if (buffer.Length % 3 == 0) + return; + FlexMuxChannels = new Fmc[buffer.Length / 3]; + + MemoryStream ms = new MemoryStream(buffer, false); + for (int i = 0; i < FlexMuxChannels.Length; i++) + { + Fmc child = new Fmc(); + FlexMuxChannels[i] = child; + + child.EsId = ms.ReadUInt16BE(); + child.FlexMuxChannel = ms.ReadUInt8(); + } + } + + public Fmc[] FlexMuxChannels { get; private set; } + public class Fmc + { + public ushort EsId { get; set; } + public byte FlexMuxChannel { get; set; } + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x20_ExternalEsIdDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x20_ExternalEsIdDescriptor.cs new file mode 100644 index 0000000..8565e89 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x20_ExternalEsIdDescriptor.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x20,"PMT")] + [BannedTable("TSDT","EIT")] + class ExternalEsIdDescriptor : TsDescriptor + { + public ExternalEsIdDescriptor(byte[] buffer) + { + if (buffer.Length != 2) + { + Valid = false; + return; + } + //TODO: Implement this! + // see T-REC-H.222.0-202106-S!!PDF-E.pdf + // page 118, paragraph 2.6.46 + throw new NotImplementedException(nameof(ExternalEsIdDescriptor)); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x21_MuxcodeDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x21_MuxcodeDescriptor.cs new file mode 100644 index 0000000..2988462 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x21_MuxcodeDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x21,"PMT")] + [BannedTable("BAT")] + class MuxcodeDescriptor : TsDescriptor + { + public MuxcodeDescriptor(byte[] buffer) + { + throw new NotImplementedException(GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x22_FmxBufferSizeDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x22_FmxBufferSizeDescriptor.cs new file mode 100644 index 0000000..e42cc9a --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x22_FmxBufferSizeDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x22,"PMT")] + [BannedTable("BAT","TSDT")] + class FmxBufferSizeDescriptor + { + public FmxBufferSizeDescriptor(byte[] buffer) + { + throw new NotImplementedException(nameof(FmxBufferSizeDescriptor)); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x24_ContentLabelingDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x24_ContentLabelingDescriptor.cs new file mode 100644 index 0000000..2e56ac8 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x24_ContentLabelingDescriptor.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x24,"PMT")] + [BannedTable("EIT")] //According to ITU-T Rec. H.222.0 this one may also be included in "tables to describe segments of programs or elementary streams", which would include an EIT, but each time I tried it, nothing sensible came out. + class ContentLabelingDescriptor + { + public ContentLabelingDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + MetadataApplicationFormat = ms.ReadUInt16BE(); + if (MetadataApplicationFormat == 0xffff) + { + MetadataApplicationFormatIdentifier = ms.ReadUInt32BE(); + } + + byte readUInt8 = ms.ReadUInt8(); + ContentReferenceIdRecordFlag = (readUInt8 & 0x80) != 0; + ContentTimeBaseIndicator = (readUInt8 & 0x78) >> 3; + if (ContentReferenceIdRecordFlag) + { + byte contentReferenceIdRecordLength = ms.ReadUInt8(); + ContentReferenceId = ms.ReadBytes(contentReferenceIdRecordLength); + } + + if (ContentTimeBaseIndicator == 1 || ContentTimeBaseIndicator == 2) + { + ContentTimeBaseValue = ms.ReadUInt8() & 0x01; + ContentTimeBaseValue <<= 32; + ContentTimeBaseValue += ms.ReadUInt32BE(); + + MetadataTimeBaseValue = ms.ReadUInt8() & 0x01; + MetadataTimeBaseValue <<= 32; + MetadataTimeBaseValue += ms.ReadUInt32BE(); + } + + if (ContentTimeBaseIndicator == 2) + { + ContentId = ms.ReadUInt8() & 0x07f; + } + + if (ContentTimeBaseIndicator >= 3 && ContentTimeBaseIndicator <= 7) + { + byte timeBaseAssociationDataLength = ms.ReadUInt8(); + TimeBaseAssociationData = ms.ReadBytes(timeBaseAssociationDataLength); + } + + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] PrivateData { get; private set; } + + public byte[] TimeBaseAssociationData { get; private set; } + + public int? ContentId { get; private set; } + + public long? MetadataTimeBaseValue { get; private set; } + + public long? ContentTimeBaseValue { get; private set; } + + public byte[] ContentReferenceId { get; private set; } + + public int ContentTimeBaseIndicator { get; private set; } + + public bool ContentReferenceIdRecordFlag { get; private set; } + + public uint? MetadataApplicationFormatIdentifier { get; private set; } + + public ushort MetadataApplicationFormat { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x25_MetadataPointerDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x25_MetadataPointerDescriptor.cs new file mode 100644 index 0000000..21154fe --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x25_MetadataPointerDescriptor.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x25,"PMT")] + [BannedTable("BAT")] + class MetadataPointerDescriptor : TsDescriptor + { + public MetadataPointerDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + MetadataApplicationFormat = ms.ReadUInt16BE(); + if (MetadataApplicationFormat == 0xffff) + { + MetadataApplicationFormatIdentifier = ms.ReadUInt32BE(); + } + + MetadataFormat = ms.ReadUInt8(); + if (MetadataFormat == 0xff) + { + MetadataFormatIdentifier = ms.ReadUInt32BE(); + } + + MetadataServiceId = ms.ReadUInt8(); + + byte readUInt8 = ms.ReadUInt8(); + MetadataLocatorRecordFlag = (readUInt8 & 0x80) != 0; + MpegCarriageFlag = (readUInt8 & 0x60) >> 5; + + if (MetadataLocatorRecordFlag) + { + byte metadataLocatorRecordLength = ms.ReadUInt8(); + MetadataLocatorRecord = ms.ReadBytes(metadataLocatorRecordLength); + } + + if (MpegCarriageFlag <= 2) + { + ProgramNumber = ms.ReadUInt16BE(); + } + + if (MpegCarriageFlag == 1) + { + TransportStreamLocation = ms.ReadUInt16BE(); + TransportStreamId = ms.ReadUInt16BE(); + } + + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public byte[] PrivateData { get; private set; } + + public ushort? TransportStreamId { get; private set; } + + public ushort? TransportStreamLocation { get; private set; } + + public ushort? ProgramNumber { get; private set; } + + public byte[] MetadataLocatorRecord { get; private set; } + + public int MpegCarriageFlag { get; private set; } + + public bool MetadataLocatorRecordFlag { get; private set; } + + public byte MetadataServiceId { get; private set; } + + public uint? MetadataFormatIdentifier { get; private set; } + + public ushort MetadataFormat { get; private set; } + + public uint? MetadataApplicationFormatIdentifier { get; private set; } + + public ushort MetadataApplicationFormat { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x26_MetadataDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x26_MetadataDescriptor.cs new file mode 100644 index 0000000..33ba569 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x26_MetadataDescriptor.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; +using System.IO; +using skyscraper5.Skyscraper.IO; + + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x26, "PMT")] + [BannedTable("NIT")] + class MetadataDescriptor : TsDescriptor + { + public MetadataDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + MetadataApplicationFormat = ms.ReadUInt16BE(); + if (MetadataApplicationFormat == 0xffff) + { + MetadataApplicationFormatIdentifier = ms.ReadUInt32BE(); + } + MetadataFormat = ms.ReadUInt8(); + if (MetadataFormat == 0xff) + { + MetadataFormatIdentifier = ms.ReadUInt32BE(); + } + MetadataServiceId = ms.ReadUInt8(); + byte bytea = ms.ReadUInt8(); + DecoderConfigFlag = (bytea & 0xe0) >> 7; + bool DsmCcFlag = (bytea & 0x10) != 0; + if (DsmCcFlag) + { + byte serviceIdentificationLength = ms.ReadUInt8(); + ServiceIdentificationRecord = ms.ReadBytes(serviceIdentificationLength); + } + if (DecoderConfigFlag == 0b001) + { + byte decoderConfigLength = ms.ReadUInt8(); + DecoderConfig = ms.ReadBytes(decoderConfigLength); + } + if (DecoderConfigFlag == 0b011) + { + byte decConfigIdentificationRecordLength = ms.ReadUInt8(); + DecoderConfigIdentificationRecord = ms.ReadBytes(decConfigIdentificationRecordLength); + } + if (DecoderConfigFlag == 0b100) + { + DecoderConfigMetadataServiceId = ms.ReadUInt16BE(); + } + if (DecoderConfigFlag == 0b101 || DecoderConfigFlag == 0xb110) + { + byte reservedDataLength = ms.ReadUInt8(); + this.Reserved = ms.ReadBytes(reservedDataLength); + } + PrivateData = ms.ReadBytes(ms.GetAvailableBytes()); + Valid = true; + } + + public ushort MetadataApplicationFormat { get; } + public uint? MetadataApplicationFormatIdentifier { get; private set; } + public byte MetadataFormat { get; } + public uint? MetadataFormatIdentifier { get; } + public byte MetadataServiceId { get; } + public int DecoderConfigFlag { get; } + public byte[] ServiceIdentificationRecord { get; } + public byte[] DecoderConfig { get; } + public byte[] DecoderConfigIdentificationRecord { get; } + public ushort DecoderConfigMetadataServiceId { get; } + public byte[] Reserved { get; } + public byte[] PrivateData { get; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x27_MetadataStdDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x27_MetadataStdDescriptor.cs new file mode 100644 index 0000000..7417b0f --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x27_MetadataStdDescriptor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x27,"PMT")] + class MetadataStdDescriptor : TsDescriptor + { + public MetadataStdDescriptor(byte[] buffer) + { + MetadataInputLeakRate = buffer[0] & 0x3f; + MetadataInputLeakRate <<= 8; + MetadataInputLeakRate += buffer[1]; + MetadataInputLeakRate <<= 8; + MetadataInputLeakRate += buffer[2]; + + MetadataBufferSize = buffer[3] & 0x3f; + MetadataBufferSize <<= 8; + MetadataBufferSize += buffer[4]; + MetadataBufferSize <<= 8; + MetadataBufferSize += buffer[5]; + + MetadataOutputLeakRate = buffer[6] & 0x3f; + MetadataOutputLeakRate <<= 8; + MetadataOutputLeakRate += buffer[7]; + MetadataOutputLeakRate <<= 8; + MetadataOutputLeakRate += buffer[8]; + } + + public int MetadataOutputLeakRate { get; private set; } + + public int MetadataBufferSize { get; private set; } + + public int MetadataInputLeakRate { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x28_AvcVideoDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x28_AvcVideoDescriptor.cs new file mode 100644 index 0000000..3d2d7df --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x28_AvcVideoDescriptor.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x28,"PMT")] + class AvcVideoDescriptor : TsDescriptor + { + public AvcVideoDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + ProfileRdc = ms.ReadUInt8(); + byte readUInt8 = ms.ReadUInt8(); + ConstraintSet0Flag = (readUInt8 & 0x80) != 0; + ConstraintSet1Flag = (readUInt8 & 0x40) != 0; + ConstraintSet2Flag = (readUInt8 & 0x20) != 0; + ConstraintSet3Flag = (readUInt8 & 0x10) != 0; + ConstraintSet4Flag = (readUInt8 & 0x08) != 0; + ConstraintSet5Flag = (readUInt8 & 0x04) != 0; + AvcCompatibleFlags = (readUInt8 & 0x03); + LevelIdc = ms.ReadUInt8(); + byte uInt8 = ms.ReadUInt8(); + AvcStillPresent = (readUInt8 & 0x80) != 0; + Avc24HourPictureFlag = (readUInt8 & 0x40) != 0; + FramePackingSeiNotPresent = (readUInt8 & 0x20) != 0; + } + + public bool FramePackingSeiNotPresent { get; private set; } + + public bool Avc24HourPictureFlag { get; private set; } + + public bool AvcStillPresent { get; private set; } + + public byte LevelIdc { get; private set; } + + public int AvcCompatibleFlags { get; private set; } + + public bool ConstraintSet5Flag { get; private set; } + + public bool ConstraintSet4Flag { get; private set; } + + public bool ConstraintSet3Flag { get; private set; } + + public bool ConstraintSet2Flag { get; private set; } + + public bool ConstraintSet1Flag { get; private set; } + + public bool ConstraintSet0Flag { get; private set; } + + public byte ProfileRdc { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x2A_AvcTimingAndHrdDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x2A_AvcTimingAndHrdDescriptor.cs new file mode 100644 index 0000000..cc1affe --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x2A_AvcTimingAndHrdDescriptor.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x2a,"PMT")] + class AvcTimingAndHrdDescriptor : TsDescriptor + { + public AvcTimingAndHrdDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + HrdManagementValidFlag = (readUInt8 & 0x80) != 0; + PictureAndTimingInfoPresentFlag = (readUInt8 & 0x01) != 0; + if (PictureAndTimingInfoPresentFlag) + { + _90khzFlag = (ms.ReadUInt8() & 0x80) != 0; + if (!_90khzFlag.Value) + { + N = ms.ReadUInt32BE(); + K = ms.ReadUInt32BE(); + } + NumUnitsInTick = ms.ReadUInt32BE(); + } + + readUInt8 = ms.ReadUInt8(); + FixedFrameRateFlag = (readUInt8 & 0x80) != 0; + TemporalPocFlag = (readUInt8 & 0x40) != 0; + PictureToDisplayConversionFlag = (readUInt8 & 0x20) != 0; + } + + public bool PictureToDisplayConversionFlag { get; private set; } + + public bool TemporalPocFlag { get; private set; } + + public bool FixedFrameRateFlag { get; private set; } + + public uint? NumUnitsInTick { get; private set; } + + public uint? K { get; private set; } + + public uint? N { get; private set; } + + public bool? _90khzFlag { get; private set; } + + public bool PictureAndTimingInfoPresentFlag { get; private set; } + + public bool HrdManagementValidFlag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x2B_Mpeg2AacAudioDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x2B_Mpeg2AacAudioDescriptor.cs new file mode 100644 index 0000000..6ef6fba --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x2B_Mpeg2AacAudioDescriptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x2b,"PMT")] + class Mpeg2AacAudioDescriptor : TsDescriptor + { + public Mpeg2AacAudioDescriptor(byte[] buffer) + { + AacProfile = buffer[0]; + AacChannelConfiguration = buffer[1]; + AacAdditionalInformation = buffer[2]; + } + + public byte AacAdditionalInformation { get; set; } + + public byte AacChannelConfiguration { get; set; } + + public byte AacProfile { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x2C_FlexmuxTimingDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x2C_FlexmuxTimingDescriptor.cs new file mode 100644 index 0000000..b5f4c6d --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x2C_FlexmuxTimingDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x2c,"PMT")] + [BannedTable("EIT")] + internal class _0x2C_FlexmuxTimingDescriptor : TsDescriptor + { + public _0x2C_FlexmuxTimingDescriptor(byte[] buffer) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x2D_Mpeg4TextDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x2D_Mpeg4TextDescriptor.cs new file mode 100644 index 0000000..7641844 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x2D_Mpeg4TextDescriptor.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x2d,"PMT")] + class Mpeg4TextDescriptor : TsDescriptor + { + public Mpeg4TextDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + TextFormat = ms.ReadUInt8(); + if (TextFormat != 1) + return; + ushort textConfigLength = ms.ReadUInt16BE(); + if (ms.GetAvailableBytes() < textConfigLength) + return; + + byte[] textConfig = ms.ReadBytes(textConfigLength); + FormatSpecificTextConfig = new _3GPPSpecificTextConfig(textConfig); + } + + public byte TextFormat { get; private set; } + + public _FormatSpecificTextConfig FormatSpecificTextConfig { get; private set; } + + public class _FormatSpecificTextConfig + { + + } + + public class _3GPPSpecificTextConfig : _FormatSpecificTextConfig + { + public _3GPPSpecificTextConfig(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + //see ourdev_631444SHQH4Y.pdf, Page 21 + throw new NotFiniteNumberException(nameof(_3GPPSpecificTextConfig)); + } + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x2E_Mpeg4AudioExtensionDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x2E_Mpeg4AudioExtensionDescriptor.cs new file mode 100644 index 0000000..249b431 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x2E_Mpeg4AudioExtensionDescriptor.cs @@ -0,0 +1,42 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x2e, "PMT")] + public class _0x2E_Mpeg4AudioExtensionDescriptor : TsDescriptor + { + public _0x2E_Mpeg4AudioExtensionDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte firstByte = ms.ReadUInt8(); + bool ascFlag = (firstByte & 0x80) != 0; + int numOfLoops = (firstByte & 0x0f); + AudioProfileLevelIndication = ms.ReadBytes(numOfLoops); + if (ascFlag) + { + byte ascSize = ms.ReadUInt8(); + AudioSpecificConfig = ms.ReadBytes(ascSize); + } + Valid = true; + } + + /// + /// encoded as specified for the audioprofileLevelIndication field in 1.5.2.1 in ISO/IEC 14496-3 + /// + public byte[] AudioProfileLevelIndication { get; } + + /// + /// The audioSpecificConfig() of the associated ISO/IEC 14496-3 audio stream, as specified in 1.6.2.1 in ISO/IEC 14496-3. + /// + public byte[] AudioSpecificConfig { get; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x2F_AuxiliaryVideoStreamDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x2F_AuxiliaryVideoStreamDescriptor.cs new file mode 100644 index 0000000..cd74233 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x2F_AuxiliaryVideoStreamDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x2f,"PMT")] + [BannedTable("TSDT")] + class AuxiliaryVideoStreamDescriptor : TsDescriptor + { + public AuxiliaryVideoStreamDescriptor(byte[] buffer) + { + throw new NotImplementedException(GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x31_MvcExtensionDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x31_MvcExtensionDescriptor.cs new file mode 100644 index 0000000..643fc77 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x31_MvcExtensionDescriptor.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x31,"PMT")] + class MvcExtensionDescriptor : TsDescriptor + { + public MvcExtensionDescriptor(byte[] buffer) + { + if (buffer.Length != 8) + return; + + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x32_J2KVideoDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x32_J2KVideoDescriptor.cs new file mode 100644 index 0000000..f5d0b72 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x32_J2KVideoDescriptor.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x32,"PMT")] + class J2KVideoDescriptor : TsDescriptor + { + public J2KVideoDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ushort readUInt16Be = ms.ReadUInt16BE(); + bool extendedCapabilityFlag = (readUInt16Be & 0x8000) != 0; + ProfileAndLevel = (readUInt16Be & 0x7fff); + HorizontalSize = ms.ReadUInt32BE(); + VerticalSize = ms.ReadUInt32BE(); + MaxBitRate = ms.ReadUInt32BE(); + MaxBufferSize = ms.ReadUInt32BE(); + DenFrameRate = ms.ReadUInt16BE(); + NumFrameRate = ms.ReadUInt16BE(); + if (extendedCapabilityFlag) + { + byte readUInt8 = ms.ReadUInt8(); + StripeFlag = (readUInt8 & 0x80) != 0; + BlockFlag = (readUInt8 & 0x40) != 0; + MdmFlag = (readUInt8 & 0x20) != 0; + if ((readUInt8 & 0x1f) != 0) + return; //invalid if not all zero + } + else + { + //T-REC-H.222.0-201808-S!!PDF-E.pdf, page 126 + throw new NotImplementedException(nameof(J2KVideoDescriptor)); + } + + throw new NotImplementedException(nameof(J2KVideoDescriptor)); + } + + public bool MdmFlag { get; set; } + + public bool BlockFlag { get; private set; } + + public bool StripeFlag { get; private set; } + + public ushort NumFrameRate { get; private set; } + + public ushort DenFrameRate { get; private set; } + + public uint MaxBufferSize { get; private set; } + + public uint MaxBitRate { get; private set; } + + public uint VerticalSize { get; private set; } + + public uint HorizontalSize { get; private set; } + + public int ProfileAndLevel { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x33_MvcOperationPointDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x33_MvcOperationPointDescriptor.cs new file mode 100644 index 0000000..d33c5f5 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x33_MvcOperationPointDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x033,"PMT")] + [BannedTable("TSDT")] + class MvcOperationPointDescriptor : TsDescriptor + { + public MvcOperationPointDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x34_Mpeg2StereoscopicVideoFormatDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x34_Mpeg2StereoscopicVideoFormatDescriptor.cs new file mode 100644 index 0000000..44188ac --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x34_Mpeg2StereoscopicVideoFormatDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x34,"PMT")] + [BannedTable("TSDT","BAT")] + class Mpeg2StereoscopicVideoFormatDescriptor : TsDescriptor + { + public Mpeg2StereoscopicVideoFormatDescriptor(byte[] buffer) + { + throw new NotImplementedException(GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x37_TransportProfileDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x37_TransportProfileDescriptor.cs new file mode 100644 index 0000000..0445ea3 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x37_TransportProfileDescriptor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x37,"PMT")] + [BannedTable("TSDT")] + class TransportProfileDescriptor + { + public TransportProfileDescriptor(byte[] buffer) + { + throw new NotImplementedException(this.GetType().Name); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x38_HevcVideoDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x38_HevcVideoDescriptor.cs new file mode 100644 index 0000000..8794f8f --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x38_HevcVideoDescriptor.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x38,"PMT")] + [BannedTable("EIT")] + class HevcVideoDescriptor : TsDescriptor + { + public HevcVideoDescriptor(byte[] buffer) + { + if (buffer.Length != 13 && buffer.Length != 15) + return; + + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + ProfileSpace = (readUInt8 & 0xc0) >> 6; + TierFlag = (readUInt8 & 0x20) != 0; + ProfileIdc = (readUInt8 & 0x1f); + ProfileCompatibilityIndication = ms.ReadUInt32BE(); + + readUInt8 = ms.ReadUInt8(); + ProgressiveSourceFlag = (readUInt8 & 0x80) != 0; + InterlacedSourceFlag = (readUInt8 & 0x40) != 0; + NonPackedConstraintFlag = (readUInt8 & 0x20) != 0; + FrameOnlyConstraintFlag = (readUInt8 & 0x10) != 0; + + Copied44bits = readUInt8 & 0x0f; + Copied44bits <<= 32; + Copied44bits += ms.ReadUInt32BE(); + Copied44bits <<= 8; + Copied44bits += ms.ReadUInt8(); + + LevelIdc = ms.ReadUInt8(); + + readUInt8 = ms.ReadUInt8(); + bool temporalLayerSubsetFlag = (readUInt8 & 0x80) != 0; + HevcStillPresentFlag = (readUInt8 & 0x40) != 0; + Hevc24hrPicturePresentFlag = (readUInt8 & 0x20) != 0; + SubPicHrdParamsNotPresentFlag = (readUInt8 & 0x10) != 0; + HdrWcgIdc = (readUInt8 & 0x02); + + if (temporalLayerSubsetFlag) + { + TemporalIdMin = (ms.ReadUInt8() & 0xe0) >> 5; + TemporalIdMax = (ms.ReadUInt8() & 0xe0) >> 5; + } + + base.Valid = true; + } + + public int? TemporalIdMax { get; private set; } + + public int? TemporalIdMin { get; private set; } + + public int HdrWcgIdc { get; private set; } + + public bool SubPicHrdParamsNotPresentFlag { get; private set; } + + public bool Hevc24hrPicturePresentFlag { get; private set; } + + public bool HevcStillPresentFlag { get; private set; } + + public byte LevelIdc { get; private set; } + + public long Copied44bits { get; private set; } + + public bool FrameOnlyConstraintFlag { get; private set; } + + public bool NonPackedConstraintFlag { get; private set; } + + public bool InterlacedSourceFlag { get; private set; } + + public bool ProgressiveSourceFlag { get; private set; } + + public uint ProfileCompatibilityIndication { get; private set; } + + public int ProfileIdc { get; private set; } + + public bool TierFlag { get; private set; } + + public int ProfileSpace { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x39_Reserved.cs b/skyscraper8/Mpeg2/Descriptors/0x39_Reserved.cs new file mode 100644 index 0000000..6a9dc6b --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x39_Reserved.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x39)] + [BannedTable("PMT")] + class _0x39_Reserved : TsDescriptor + { + public _0x39_Reserved(byte[] buffer) + { + throw new NotSupportedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x3A_Reserved.cs b/skyscraper8/Mpeg2/Descriptors/0x3A_Reserved.cs new file mode 100644 index 0000000..4ea6d7f --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x3A_Reserved.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x3a)] + [BannedTable("PMT")] + class _0x3A_Reserved : TsDescriptor + { + public _0x3A_Reserved(byte[] buffer) + { + throw new NotSupportedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x3B_Reserved.cs b/skyscraper8/Mpeg2/Descriptors/0x3B_Reserved.cs new file mode 100644 index 0000000..afaa289 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x3B_Reserved.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x3b)] + [BannedTable("TSDT")] + class _0x3B_Reserved + { + public _0x3B_Reserved(byte[] buffer) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/0x3F_ExtensionDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/0x3F_ExtensionDescriptor.cs new file mode 100644 index 0000000..5aa2cbb --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/0x3F_ExtensionDescriptor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x3f,"PMT")] + [DescriptorConversionNeeded(typeof(Mpeg2ExtensionDescriptorConverter))] + class ExtensionDescriptor : TsDescriptor + { + public ExtensionDescriptor(byte[] buffer) + { + ExtensionDescriptorTag = buffer[0]; + SelectorBytes = new byte[buffer.Length - 1]; + Array.Copy(buffer, 1, SelectorBytes, 0, SelectorBytes.Length); + } + + public byte[] SelectorBytes { get; private set; } + + public byte ExtensionDescriptorTag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/Descriptors/UserDefinedDescriptor.cs b/skyscraper8/Mpeg2/Descriptors/UserDefinedDescriptor.cs new file mode 100644 index 0000000..0f47281 --- /dev/null +++ b/skyscraper8/Mpeg2/Descriptors/UserDefinedDescriptor.cs @@ -0,0 +1,19 @@ +namespace skyscraper5.Mpeg2.Descriptors +{ + public class UserDefinedDescriptor : TsDescriptor + { + public byte DescriptorTag { get; } + public byte[] Data { get; } + + public UserDefinedDescriptor(byte descriptorTag, byte[] data) + { + DescriptorTag = descriptorTag; + Data = data; + } + + public override byte GetDescriptorId() + { + return DescriptorTag; + } + } +} diff --git a/skyscraper8/Mpeg2/ExtensionDescriptors/0x03_HevcTimingAndHrdDescriptor.cs b/skyscraper8/Mpeg2/ExtensionDescriptors/0x03_HevcTimingAndHrdDescriptor.cs new file mode 100644 index 0000000..4724f08 --- /dev/null +++ b/skyscraper8/Mpeg2/ExtensionDescriptors/0x03_HevcTimingAndHrdDescriptor.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2.ExtensionDescriptors +{ + [SkyscraperPlugin] + [Mpeg2ExtensionDescriptor(0x03)] + class HevcTimingAndHrdDescriptor : TsDescriptor + { + public HevcTimingAndHrdDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + HrdManagementValidFlag = (readUInt8 & 0x80) != 0; + TargetScheduleIdxNotPresentFlag = (readUInt8 & 0x40) != 0; + TargetScheduleIdx = (readUInt8 & 0x3e) >> 1; + PictureAndTimingInfoPresentFlag = (readUInt8 & 0x01) != 0; + if (PictureAndTimingInfoPresentFlag) + { + _90khzFlag = (ms.ReadUInt8() & 0x80) != 0; + if (!_90khzFlag.Value) + { + N = ms.ReadUInt32BE(); + K = ms.ReadUInt32BE(); + } + + NumUnitsInTick = ms.ReadUInt32BE(); + } + } + + public uint NumUnitsInTick { get; private set; } + + public uint? K { get; private set; } + + public uint? N { get; private set; } + + public bool? _90khzFlag { get; private set; } + + public bool PictureAndTimingInfoPresentFlag { get; private set; } + + public int TargetScheduleIdx { get; private set; } + + public bool TargetScheduleIdxNotPresentFlag { get; private set; } + + public bool HrdManagementValidFlag { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/IDescriptorConverter.cs b/skyscraper8/Mpeg2/IDescriptorConverter.cs new file mode 100644 index 0000000..5fa730e --- /dev/null +++ b/skyscraper8/Mpeg2/IDescriptorConverter.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + interface IDescriptorConverter + { + TsDescriptor Convert(TsDescriptor descriptor, string callingTable); + } +} diff --git a/skyscraper8/Mpeg2/IPayloadUnitDecoder.cs b/skyscraper8/Mpeg2/IPayloadUnitDecoder.cs new file mode 100644 index 0000000..43375ea --- /dev/null +++ b/skyscraper8/Mpeg2/IPayloadUnitDecoder.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Mpeg2 +{ + public interface IPayloadUnitDecoder + { + void PacketLoss(); + } +} diff --git a/skyscraper8/Mpeg2/IPesProcessor.cs b/skyscraper8/Mpeg2/IPesProcessor.cs new file mode 100644 index 0000000..da26a16 --- /dev/null +++ b/skyscraper8/Mpeg2/IPesProcessor.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Mpeg2 +{ + interface IPesProcessor + { + void ProcessPesPacket(PesPacket packet); + } +} diff --git a/skyscraper8/Mpeg2/IPsiDecoderTransformer.cs b/skyscraper8/Mpeg2/IPsiDecoderTransformer.cs new file mode 100644 index 0000000..14f8e91 --- /dev/null +++ b/skyscraper8/Mpeg2/IPsiDecoderTransformer.cs @@ -0,0 +1,14 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2 +{ + public interface IPsiDecoderTransformer + { + void Transform(PsiDecoder psiDecoder); + } +} diff --git a/skyscraper8/Mpeg2/IPsiProcessor.cs b/skyscraper8/Mpeg2/IPsiProcessor.cs new file mode 100644 index 0000000..08cd769 --- /dev/null +++ b/skyscraper8/Mpeg2/IPsiProcessor.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Mpeg2 +{ + public interface IPsiProcessor + { + void GatherPsi(PsiSection section, int sourcePid); + } +} diff --git a/skyscraper8/Mpeg2/ITsPacketProcessor.cs b/skyscraper8/Mpeg2/ITsPacketProcessor.cs new file mode 100644 index 0000000..09ca55b --- /dev/null +++ b/skyscraper8/Mpeg2/ITsPacketProcessor.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Mpeg2 +{ + public interface ITsPacketProcessor + { + void PushPacket(TsPacket packet); + } +} diff --git a/skyscraper8/Mpeg2/InvalidTsPacketException.cs b/skyscraper8/Mpeg2/InvalidTsPacketException.cs new file mode 100644 index 0000000..b9ff5c5 --- /dev/null +++ b/skyscraper8/Mpeg2/InvalidTsPacketException.cs @@ -0,0 +1,9 @@ +namespace skyscraper5.Mpeg2 +{ + internal class InvalidTsPacketException : Mpeg2Exception + { + public InvalidTsPacketException() : base("Invalid TS Packet", "") + { + } + } +} diff --git a/skyscraper8/Mpeg2/Mpeg2Exception.cs b/skyscraper8/Mpeg2/Mpeg2Exception.cs new file mode 100644 index 0000000..174f34f --- /dev/null +++ b/skyscraper8/Mpeg2/Mpeg2Exception.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + class Mpeg2Exception : SkyscraperException + { + public Mpeg2Exception(string s, params object[] args) + : base(String.Format(s, args)) + { + } + + protected Mpeg2Exception( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Mpeg2/Mpeg2ExtensionDescriptorAttribute.cs b/skyscraper8/Mpeg2/Mpeg2ExtensionDescriptorAttribute.cs new file mode 100644 index 0000000..ef0ef90 --- /dev/null +++ b/skyscraper8/Mpeg2/Mpeg2ExtensionDescriptorAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class Mpeg2ExtensionDescriptorAttribute : Attribute + { + public byte Tag { get; } + + public Mpeg2ExtensionDescriptorAttribute(byte tag) + { + Tag = tag; + } + } +} diff --git a/skyscraper8/Mpeg2/Mpeg2ExtensionDescriptorConverter.cs b/skyscraper8/Mpeg2/Mpeg2ExtensionDescriptorConverter.cs new file mode 100644 index 0000000..a84354f --- /dev/null +++ b/skyscraper8/Mpeg2/Mpeg2ExtensionDescriptorConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Mpeg2 +{ + class Mpeg2ExtensionDescriptorConverter : IDescriptorConverter + { + private ConstructorInfo[] constructors; + public Mpeg2ExtensionDescriptorConverter() + { + constructors = PluginManager.GetInstance().GetMpeg2ExtensionDescriptors(); + } + + public TsDescriptor Convert(TsDescriptor descriptor, string callingTable) + { + ExtensionDescriptor extensionDescriptor = (ExtensionDescriptor)descriptor; + ConstructorInfo constructorInfo = constructors[extensionDescriptor.ExtensionDescriptorTag]; + if (constructorInfo == null) + { + //Defined in T-REC-H.222.0-201808-S!!PDF-E.pdf + //Page 135 + throw new NotImplementedException(String.Format("MPEG-2 Extension Descriptor {0:X2}", extensionDescriptor.ExtensionDescriptorTag)); + } + + object invoke = constructorInfo.Invoke(new object[] { extensionDescriptor.SelectorBytes }); + TsDescriptor result = (TsDescriptor)invoke; + return result; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/BasePacketFilter.cs b/skyscraper8/Mpeg2/PacketFilter/BasePacketFilter.cs new file mode 100644 index 0000000..6082631 --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/BasePacketFilter.cs @@ -0,0 +1,39 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + public abstract class BasePacketFilter : IPacketFilter + { + public BasePacketFilter() + { + _filterEnabled = true; + } + + /// + /// If this is set to false, every packet will be processed further. + /// If thie is set to true, every packet will be inspected by the implementer. + /// + public bool FilterEnabled => _filterEnabled; + private bool _filterEnabled; + + public bool PassPacket(TsPacket packet) + { + if (!_filterEnabled) + return true; + + return PassPacketEx(packet); + } + + /// + /// If this returns true, the packet will be put into the next filter. If all filters return true, the packet will be processed. + /// If this returns false, the packet will be discarded. + /// + /// Whether to continue processing the packet + protected abstract bool PassPacketEx(TsPacket packet); + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/IPacketFilter.cs b/skyscraper8/Mpeg2/PacketFilter/IPacketFilter.cs new file mode 100644 index 0000000..cb4d47c --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/IPacketFilter.cs @@ -0,0 +1,19 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + public interface IPacketFilter + { + /// + /// If this returns true, the packet will be put into the next filter. If all filters return true, the packet will be processed. + /// If this returns false, the packet will be discarded. + /// + /// Whether to continue processing the packet + bool PassPacket(TsPacket packet); + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/NullPacketFilter.cs b/skyscraper8/Mpeg2/PacketFilter/NullPacketFilter.cs new file mode 100644 index 0000000..225fa68 --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/NullPacketFilter.cs @@ -0,0 +1,20 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + /// + /// This filter will just let all packets through. + /// + internal class NullPacketFilter : IPacketFilter + { + public bool PassPacket(TsPacket packet) + { + return true; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/PidWhitelistFilter.cs b/skyscraper8/Mpeg2/PacketFilter/PidWhitelistFilter.cs new file mode 100644 index 0000000..440a48d --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/PidWhitelistFilter.cs @@ -0,0 +1,33 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + /// + /// This filter can be configured at runtime to only let PIDs of certain packets through. By default all PIDs are allowed. + /// + internal class PidWhitelistFilter : IPacketFilter + { + public PidWhitelistFilter() + { + Whitelist = new bool[0x1fff]; + SetAll(true); + } + + public bool[] Whitelist { get; private set; } + + public void SetAll(bool value) + { + Array.Fill(Whitelist, value); + } + + public bool PassPacket(TsPacket packet) + { + return Whitelist[packet.PID]; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs b/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs new file mode 100644 index 0000000..f452b81 --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs @@ -0,0 +1,23 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + /// + /// This filter nukes packets which are marked scrambled. + /// + /// + [PluginPriority(1000)] + public class ScrambleFilter : BasePacketFilter + { + protected override bool PassPacketEx(TsPacket packet) + { + return packet.TSC == 0; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/SkipFilter.cs b/skyscraper8/Mpeg2/PacketFilter/SkipFilter.cs new file mode 100644 index 0000000..f980b11 --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/SkipFilter.cs @@ -0,0 +1,44 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + + /// + /// Nukes the first n Packets, lets all others through + /// + /// + [PluginPriority(10)] + public class SkipFilter : IPacketFilter + { + /// + /// The amount of packets that fit into CrazyScan's default buffer size. + /// + public const ulong CRAZYSCAN_BUFFER = 512; + + private ulong _toSkip, _skipped; + + public SkipFilter(ulong toSkip = CRAZYSCAN_BUFFER) + { + this._toSkip = toSkip; + } + + public ulong ToSkip { get { return _toSkip; } } + public ulong Skipped { get { return _skipped; } } + + public bool PassPacket(TsPacket packet) + { + if (_toSkip > _skipped) + { + _skipped++; + return false; + } + return true; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/TeiFilter.cs b/skyscraper8/Mpeg2/PacketFilter/TeiFilter.cs new file mode 100644 index 0000000..7eae53c --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/TeiFilter.cs @@ -0,0 +1,23 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + /// + /// Kicks out packets with the TEI Flag set. + /// + /// + [PluginPriority(1000)] + public class TeiFilter : BasePacketFilter + { + protected override bool PassPacketEx(TsPacket packet) + { + return !packet.TEI; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/TeiOnOffFilter.cs b/skyscraper8/Mpeg2/PacketFilter/TeiOnOffFilter.cs new file mode 100644 index 0000000..6df0809 --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/TeiOnOffFilter.cs @@ -0,0 +1,49 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + [PluginPriority(999)] + public class TeiOnOffFilter : BasePacketFilter + { + public TeiOnOffFilter() + { + state = new bool?[8192]; + suspicions = new byte[8192]; + } + private bool?[] state; + private byte[] suspicions; + private const byte LIMIT = 3; + + protected override bool PassPacketEx(TsPacket packet) + { + bool scrambled = packet.TSC != 0; + + if (!state[packet.PID].HasValue) + { + state[packet.PID] = scrambled; + return true; + } + + if (suspicions[packet.PID] >= LIMIT) + { + return false; + } + + if (state[packet.PID].Value != scrambled) + { + if (scrambled) + suspicions[packet.PID]++; + + state[packet.PID] = scrambled; + } + + return true; + } + } +} diff --git a/skyscraper8/Mpeg2/PacketFilter/TsRecorder.cs b/skyscraper8/Mpeg2/PacketFilter/TsRecorder.cs new file mode 100644 index 0000000..a496706 --- /dev/null +++ b/skyscraper8/Mpeg2/PacketFilter/TsRecorder.cs @@ -0,0 +1,106 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Mpeg2.PacketFilter +{ + /// + /// This filter lets all packets through, but writes them to disk. + /// + [PluginPriority(20)] + public class TsRecorder : IPacketFilter, IDisposable + { + public bool PassPacket(TsPacket packet) + { + if (recordingBufferedStream != null) + { + recordingBufferedStream.Write(packet.RawPacket, 0, packet.RawPacket.Length); + } + return true; + } + + + private const string RECORDING_FILENAME_MASK = "skyscraper_{0}.ts"; + public bool Recording { get; set; } + public DirectoryInfo RecordingOutputDirectory { get; set; } + + private BufferedStream recordingBufferedStream; + private FileStream recordingFileStream; + private string recordingFilename; + + public bool PrepareRecording() + { + if (!Recording) + return false; + + if (RecordingOutputDirectory == null) + return false; + + DirectoryInfo rootDirectory = RecordingOutputDirectory.Root; + DriveInfo driveInfo = new DriveInfo(rootDirectory.FullName); + long driveFreeSpace = driveInfo.TotalFreeSpace; + long driveTotalSize = driveInfo.TotalSize; + long tenPercentOfDriveTotal = driveTotalSize / 10; + if (driveFreeSpace > tenPercentOfDriveTotal) + return true; + + FileInfo[] fileInfos = RecordingOutputDirectory.GetFiles(String.Format(RECORDING_FILENAME_MASK, "*")); + Array.Sort(fileInfos, (x, y) => DateTime.Compare(x.CreationTimeUtc, y.CreationTimeUtc)); + long sizeOfAllKnown = fileInfos.Select(x => x.Length).Sum(); + long driveFreeSpaceAfterDeleting = driveFreeSpace + sizeOfAllKnown; + if (driveFreeSpaceAfterDeleting < tenPercentOfDriveTotal) + return false; + + for (int i = 0; i < fileInfos.Length; i++) + { + fileInfos[i].Delete(); + Console.WriteLine(String.Format("Deleted for rotation: {0}", fileInfos[i].Name), 8); + fileInfos[i] = null; + driveInfo = new DriveInfo(rootDirectory.FullName); + driveFreeSpace = driveInfo.TotalFreeSpace; + if (driveFreeSpace > tenPercentOfDriveTotal) + return true; + } + + driveInfo = new DriveInfo(rootDirectory.FullName); + driveFreeSpace = driveInfo.TotalFreeSpace; + if (driveFreeSpace > tenPercentOfDriveTotal) + return true; + else + return false; + } + + public void CreateBufferedStream() + { + FileInfo recordingFileInfo = new FileInfo(Path.Combine(RecordingOutputDirectory.FullName, recordingFilename)); + recordingFileInfo.Directory.EnsureExists(); + recordingFileStream = recordingFileInfo.OpenWrite(); + recordingBufferedStream = new BufferedStream(recordingFileStream); + } + + public void Dispose() + { + if (recordingBufferedStream != null) + { + recordingBufferedStream.Flush(); + recordingBufferedStream.Close(); + recordingBufferedStream.Dispose(); + recordingBufferedStream = null; + recordingFileStream.Close(); + recordingFileStream.Dispose(); + recordingFileStream = null; + } + } + + public void SetNextFilename(string recordingFilename) + { + this.recordingFilename = recordingFilename; + } + } +} diff --git a/skyscraper8/Mpeg2/PcrMonitor.cs b/skyscraper8/Mpeg2/PcrMonitor.cs new file mode 100644 index 0000000..a8aff63 --- /dev/null +++ b/skyscraper8/Mpeg2/PcrMonitor.cs @@ -0,0 +1,107 @@ +using System; + +namespace skyscraper5.Mpeg2 +{ + public class PcrMonitor + { + internal PcrMonitor(TsPacket packet) + { + Analyze(packet); + } + + internal void Analyze(TsPacket packet) + { + if (packet.Adaption == null) + return; + if (!packet.Adaption.Valid) + return; + if (!packet.Adaption.PcrPresent) + return; + + if (StartPcr == null) + { + StartPcr = packet.Adaption.PCR; + SourcePid = packet.PID; + StartRealTime = DateTime.Now; + } + else + { + if (packet.PID == SourcePid) + { + LastPcr = packet.Adaption.PCR; + LastRealTime = DateTime.Now; + } + } + } + + public uint SourcePid { get; private set; } + + public ulong? LastPcr { get; private set; } + + public ulong? StartPcr { get; private set; } + + public DateTime StartRealTime { get; private set; } + public DateTime? LastRealTime { get; private set; } + + public TimeSpan StartPcrTimeStamp + { + get + { + ulong seconds = StartPcr.Value / 27000000; + ulong ticks = seconds * TimeSpan.TicksPerSecond; + return new TimeSpan((long)ticks); + } + } + + public TimeSpan? EndPcrTimeStamp + { + get + { + if (LastPcr == null) + return null; + + ulong seconds = LastPcr.Value / 27000000; + ulong ticks = seconds * TimeSpan.TicksPerSecond; + return new TimeSpan((long)ticks); + } + } + + public TimeSpan? PcrDuration + { + get + { + if (EndPcrTimeStamp == null) + return null; + + return new TimeSpan(EndPcrTimeStamp.Value.Ticks - StartPcrTimeStamp.Ticks); + } + } + + public TimeSpan? RealTimeDuration + { + get + { + if (LastRealTime == null) + return null; + + return LastRealTime.Value - StartRealTime; + } + } + + public double? Performance + { + get + { + if (!PcrDuration.HasValue) + return null; + if (!RealTimeDuration.HasValue) + return null; + + double pcr = PcrDuration.Value.TotalSeconds; + double real = RealTimeDuration.Value.TotalSeconds; + double result = pcr / real; + return Math.Round(result, 1); + } + } + } +} diff --git a/skyscraper8/Mpeg2/PesDecoder.cs b/skyscraper8/Mpeg2/PesDecoder.cs new file mode 100644 index 0000000..2fc95b4 --- /dev/null +++ b/skyscraper8/Mpeg2/PesDecoder.cs @@ -0,0 +1,57 @@ +using System.IO; + +namespace skyscraper5.Mpeg2 +{ + class PesDecoder : ITsPacketProcessor, IPayloadUnitDecoder + { + public IPesProcessor Sink { get; } + + public PesDecoder(IPesProcessor sink) + { + Sink = sink; + } + + private MemoryStream ms; + private long pushed; + private long discarded; + public object Tag { get; set; } + + public void PushPacket(TsPacket packet) + { + if (packet.Payload == null) + return; + + pushed++; + if (packet.PayloadUnitStart) + { + if (ms != null) + { + //Payload start UND es gibt schon was, also muss die Unit zu Ende sein? + PesPacket pesPacket = new PesPacket(ms.ToArray()); + Sink.ProcessPesPacket(pesPacket); + } + ms = new MemoryStream(); + ms.WriteByte(0); + ms.Write(packet.Payload, 0, packet.Payload.Length); + + } + else + { + if (ms == null) + { + //Kein Payload Starter UND es gibt noch nichts. Können wir also wegschmeißen. + discarded++; + } + else + { + ms.Write(packet.Payload, 0, packet.Payload.Length); + } + } + } + + public void PacketLoss() + { + ms = null; + } + } +} diff --git a/skyscraper8/Mpeg2/PesPacket.cs b/skyscraper8/Mpeg2/PesPacket.cs new file mode 100644 index 0000000..0bada52 --- /dev/null +++ b/skyscraper8/Mpeg2/PesPacket.cs @@ -0,0 +1,472 @@ +using System; +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + class PesPacket + { + private const byte PROGRAM_STREAM_MAP = 0b10111100; + private const byte PRIVATE_STREAM_1 = 0b10111101; + private const byte PADDING_STREAM = 0b10111110; + private const byte PRIVATE_STREAM_2 = 0b10111111; + private const byte ECM_STREAM = 0b11110000; + private const byte EMM_STREAM = 0b11110001; + private const byte DSMCC_STREAM = 0b11110010; + private const byte ISO13522_STREAM = 0b11110011; + private const byte H222_1_TYPE_A = 0b11110100; + private const byte H222_1_TYPE_B = 0b11110101; + private const byte H222_1_TYPE_C = 0b11110110; + private const byte H222_1_TYPE_D = 0b11110111; + private const byte H222_1_TYPE_E = 0b11111000; + private const byte ANCILLARY_STREAM = 0b11111001; + private const byte ISO14496_1_SL = 0b11111010; + private const byte ISO14496_1_FLEXMUX = 0b11111011; + private const byte PROGRAM_STREAM_DIRECTORY = 0b11111111; + + private const byte FAST_FORWARD = 0b00000000; + private const byte SLOW_MOTION = 0b00000001; + private const byte FREEZE_FRAME = 0b00000010; + private const byte FAST_REVERSE = 0b00000011; + private const byte SLOW_REVERSE = 0b00000100; + + public PesPacket(byte[] toArray) + { + int suspicious = 0; + byte msStartOffset; + if (toArray[0] == 0x00 & toArray[1] == 0x00 && toArray[2] == 0x00 && toArray[3] == 0x01) + msStartOffset = 1; + else if (toArray[0] == 0x00 & toArray[1] == 0x00 && toArray[2] == 0x01) + msStartOffset = 0; + else + return; + + MemoryStream ms = new MemoryStream(toArray, msStartOffset, toArray.Length - msStartOffset, false); + if (ms.ReadByte() != 0) + throw new DvbException("invalid pes packet"); + if (ms.ReadByte() != 0) + throw new DvbException("invalid pes packet"); + if (ms.ReadByte() != 1) + throw new DvbException("invalid pes packet"); + + StreamId = ms.ReadUInt8(); + PesPacketLength = ms.ReadUInt16BE(); + + if (PesPacketLength > ms.GetAvailableBytes()) + return; + + toArray = ms.ReadBytes(PesPacketLength); + if (PesPacketLength == 0) + return; + ms = new MemoryStream(toArray); + if (StreamId != PROGRAM_STREAM_MAP && + StreamId != PADDING_STREAM && + StreamId != PRIVATE_STREAM_2 && + StreamId != ECM_STREAM && + StreamId != EMM_STREAM && + StreamId != PROGRAM_STREAM_DIRECTORY && + StreamId != DSMCC_STREAM && + StreamId != H222_1_TYPE_E) + { + byte readUInt8 = ms.ReadUInt8(); + int magic10 = readUInt8 & (0xc0) >> 6; + if (magic10 != 1 && magic10 != 3) + suspicious++; + PesScramblingControl = (readUInt8 & 0x030); + PesPriority = (readUInt8 & 0x08) != 0; + DataAlignmentIndicator = (readUInt8 & 0x04) != 0; + Copyright = (readUInt8 & 0x02) != 0; + OriginalOrCopy = (readUInt8 & 0x01) != 0; + + readUInt8 = ms.ReadUInt8(); + PtsDtsFlags = (readUInt8 & 0xc0) >> 6; + EscrFlag = (readUInt8 & 0x20) != 0; + EsRateFlag = (readUInt8 & 0x10) != 0; + DsmTrickModeFlag = (readUInt8 & 0x08) != 0; + AdditionalCopyInfoFlag = (readUInt8 & 0x04) != 0; + PesCrcFlag = (readUInt8 & 0x02) != 0; + PesExtensionFlag = (readUInt8 & 0x01) != 0; + + PesHeaderDataLength = ms.ReadUInt8(); + if (PesHeaderDataLength > ms.GetAvailableBytes()) + return; + byte[] PesHeaderData = ms.ReadBytes(PesHeaderDataLength); + MemoryStream ms2 = new MemoryStream(PesHeaderData, false); + + if (PtsDtsFlags == 0b10) + { + //PTS + readUInt8 = ms2.ReadUInt8(); + int magic0010 = ((readUInt8 & 0b0010)); + if (magic0010 == 0) + suspicious++; + + PTS = (readUInt8 & 0xE0) >> 1; + PTS <<= 3; + bool markerBit = ((readUInt8 & 0x01) != 0); + if (!markerBit) + { + //throw new DvbException("invalid pes packet"); + //invalid pes packet + return; + } + + ushort readUInt16Be = ms2.ReadUInt16BE(); + int i = (readUInt16Be & 0xFFFE) >> 1; + PTS += i; + PTS <<= 15; + markerBit = ((readUInt16Be & 0x0001) != 0); + if (!markerBit) + { + //throw new DvbException("invalid pes packet"); + //invalid pes packet + return; + } + + if (ms2.GetAvailableBytes() < 2) + { + return; + } + readUInt16Be = ms2.ReadUInt16BE(); + i = (readUInt16Be & 0xFFFE) >> 1; + PTS += i; + markerBit = ((readUInt16Be & 0x0001) != 0); + if (!markerBit) + { + //broken packet, discard it. + return; + } + } + + if (PtsDtsFlags == 0b11) + { + //PTS + readUInt8 = ms2.ReadUInt8(); + int magic0010 = ((readUInt8 & 0b0010) >> 4); + if (magic0010 == 0) + return; + + PTS = (readUInt8 & 0xE0) >> 1; + PTS <<= 3; + bool markerBit = ((readUInt8 & 0x01) != 0); + if (!markerBit) + throw new DvbException("invalid pes packet"); + + ushort readUInt16Be = ms2.ReadUInt16BE(); + int i = (readUInt16Be & 0xFFFE) >> 1; + PTS += i; + PTS <<= 15; + markerBit = ((readUInt16Be & 0x0001) != 0); + if (!markerBit) + throw new DvbException("invalid pes packet"); + + readUInt16Be = ms2.ReadUInt16BE(); + i = (readUInt16Be & 0xFFFE) >> 1; + PTS += i; + markerBit = ((readUInt16Be & 0x0001) != 0); + if (!markerBit) + throw new DvbException("invalid pes packet"); + + //DTS + readUInt8 = ms2.ReadUInt8(); + int magic0001 = ((readUInt8 & 0b0001) >> 4); + if (magic0001 == 0) + throw new DvbException("invalid pes packet"); + + DTS = (readUInt8 & 0xE0) >> 1; + DTS <<= 3; + markerBit = ((readUInt8 & 0x01) != 0); + if (!markerBit) + throw new DvbException("invalid pes packet"); + + readUInt16Be = ms2.ReadUInt16BE(); + i = (readUInt16Be & 0xFFFE) >> 1; + DTS += i; + DTS <<= 15; + markerBit = ((readUInt16Be & 0x0001) != 0); + if (!markerBit) + throw new DvbException("invalid pes packet"); + + readUInt16Be = ms2.ReadUInt16BE(); + i = (readUInt16Be & 0xFFFE) >> 1; + DTS += i; + markerBit = ((readUInt16Be & 0x0001) != 0); + if (!markerBit) + throw new DvbException("invalid pes packet"); + } + + if (EscrFlag) + { + ulong escrBinary = ms2.ReadUInt32BE(); + escrBinary <<= 16; + escrBinary += ms2.ReadUInt16BE(); + + //ESCR Base 32...30 + EscrBase = (escrBinary & 0x0000380000000000) >> 40; + EscrBase <<= 3; + + //Marker Flag + bool markerBit = (escrBinary & 0x0000040000000000) != 0; + if (!markerBit) + throw new DvbException("invalid pes packet"); + + //ESCR Base 29...15 + ulong temp = (escrBinary & 0x000003FFF8000000) >> 27; + EscrBase += temp; + EscrBase <<= 15; + + //Marker Flag + markerBit = (escrBinary & 0x0000000004000000) != 0; + if (!markerBit) + throw new DvbException("invalid pes packet"); + + //ESCR Base 14...0 + temp = (escrBinary & 0x0000000003FFF800); + EscrBase += temp; + + //Marker Flag + markerBit = (escrBinary & 0x0000000000000400) != 0; + if (!markerBit) + throw new DvbException("invalid pes packet"); + + //ESCR Extension + EscrExtension = (escrBinary & 0x00000000000003FE) >> 1; + + //Marker Flag + markerBit = (escrBinary & 0x0000000000000001) != 0; + if (!markerBit) + throw new DvbException("invalid pes packet"); + } + + if (EsRateFlag) + { + uint esBinary = ms2.ReadUInt16BE(); + esBinary <<= 8; + esBinary += ms.ReadUInt8(); + + //Marker Flag + bool markerFlag = (esBinary & 0x00800000) != 0; + if (!markerFlag) + throw new DvbException("invalid pes packet"); + + //ES Rate + EsRate = (esBinary & 0x007FFFFE) >> 1; + + markerFlag = (esBinary & 0x00000001) != 0; + if (!markerFlag) + { + //Broken packet, so discard it? + if (ms.GetAvailableBytes() == 0) + return; + } + } + + if (DsmTrickModeFlag) + { + readUInt8 = ms2.ReadUInt8(); + TrickModeControl = (readUInt8 & 0xE0) >> 5; + if (TrickModeControl == FAST_FORWARD) + { + FieldId = (readUInt8 & 0x18) >> 3; + IntraSliceRefresh = ((readUInt8 & 0x04) >> 2) != 0; + FrequencyTruncation = ((readUInt8 & 0x03)); + } + if (TrickModeControl == SLOW_MOTION) + { + RepCntrl = (readUInt8 & 0x1F); + } + if (TrickModeControl == FREEZE_FRAME) + { + FieldId = (readUInt8 & 0x18) >> 3; + } + if (TrickModeControl == FAST_REVERSE) + { + FieldId = (readUInt8 & 0x18) >> 3; + IntraSliceRefresh = ((readUInt8 & 0x04) >> 2) != 0; + FrequencyTruncation = ((readUInt8 & 0x03)); + } + if (TrickModeControl == SLOW_REVERSE) + { + RepCntrl = (readUInt8 & 0x1F); + } + } + + if (AdditionalCopyInfoFlag) + { + readUInt8 = ms2.ReadUInt8(); + if ((readUInt8 & 0x80) == 0) + throw new DvbException("invalid pes packet"); + AdditionalCopyInfo = readUInt8 & 0x7f; + } + + if (PesCrcFlag) + { + if (ms2.GetAvailableBytes() < 2) + return; + PreviousPesPacketCrc = ms2.ReadUInt16BE(); + } + + if (PesExtensionFlag) + { + readUInt8 = ms2.ReadUInt8(); + PesPrivateDataFlag = (readUInt8 & 0x80) != 0; + PackHeaderFieldFlag = (readUInt8 & 0x40) != 0; + ProgramPacketSequenceCounterFlag = (readUInt8 & 0x20) != 0; + PstdBufferFlag = (readUInt8 & 0x10) != 0; + PesExtensionFlag2 = (readUInt8 & 0x01) != 0; + + if (PesPrivateDataFlag) + { + PesPrivateData = ms2.ReadBytes(16); + } + + if (PackHeaderFieldFlag) + { + PackFieldLength = ms2.ReadUInt8(); + if (ms2.GetAvailableBytes() < PackFieldLength) + { + return; + } + PackHeader = new ProgramStreamPackHeader(ms2.ReadBytes(PackFieldLength)); + } + + if (ProgramPacketSequenceCounterFlag) + { + readUInt8 = ms2.ReadUInt8(); + if ((readUInt8 & 0x80) == 0) + throw new DvbException("invalid pes packet"); + + ProgramPacketSequenceCounter = readUInt8 & 0x7f; + + readUInt8 = ms2.ReadUInt8(); + if ((readUInt8 & 0x80) == 0) + throw new DvbException("invalid pes packet"); + Mpeg1Mpeg2Identifier = (readUInt8 & 0x40) != 0; + OriginalStuffLength = readUInt8 & 0x3f; + } + + if (PstdBufferFlag) + { + readUInt8 = ms2.ReadUInt8(); + int magic01 = (readUInt8 & 0xc0) >> 6; + } + + if (PesExtensionFlag2) + { + readUInt8 = ms2.ReadUInt8(); + if ((readUInt8 & 0x80) == 0) + throw new DvbException("invalid pes packet"); + + PesExtensionFieldLength = readUInt8 & 0x7f; + ms2.Position += PesExtensionFieldLength; //all these bytes are reserved! + } + } + + long packetLen = PesPacketLength - PesHeaderDataLength; + packetLen -= 3; + if (packetLen > ms.GetAvailableBytes()) + return; + Payload = ms.ReadBytes(packetLen); + + } + else if (StreamId == PROGRAM_STREAM_MAP || + StreamId == PRIVATE_STREAM_2 || + StreamId == ECM_STREAM || + StreamId == EMM_STREAM || + StreamId == PROGRAM_STREAM_DIRECTORY || + StreamId == DSMCC_STREAM || + StreamId == H222_1_TYPE_E) + { + long packetLen = PesPacketLength - PesHeaderDataLength; + Payload = ms.ReadBytes(packetLen); + } + else if (StreamId == PADDING_STREAM) + { + Payload = ms.ReadBytes(ms.GetAvailableBytes()); + } + + } + + public ProgramStreamPackHeader PackHeader { get; private set; } + + public byte[] Payload { get; set; } + + + public int PesExtensionFieldLength { get; private set; } + + public int OriginalStuffLength { get; private set; } + + public bool Mpeg1Mpeg2Identifier { get; private set; } + + public int ProgramPacketSequenceCounter { get; private set; } + + public int PackFieldLength { get; private set; } + + public byte[] PesPrivateData { get; private set; } + + public bool PesExtensionFlag2 { get; private set; } + + public bool PstdBufferFlag { get; private set; } + + public bool ProgramPacketSequenceCounterFlag { get; private set; } + + public bool PackHeaderFieldFlag { get; private set; } + + public bool PesPrivateDataFlag { get; private set; } + + public ushort PreviousPesPacketCrc { get; private set; } + + public int AdditionalCopyInfo { get; private set; } + + public int RepCntrl { get; private set; } + + public int FrequencyTruncation { get; private set; } + + public bool IntraSliceRefresh { get; private set; } + + public int FieldId { get; private set; } + + public int TrickModeControl { get; private set; } + + public uint EsRate { get; private set; } + + public ulong EscrExtension { get; private set; } + + public ulong EscrBase { get; set; } + + public long DTS { get; private set; } + + public long PTS { get; private set; } + + public byte PesHeaderDataLength { get; private set; } + + public bool PesExtensionFlag { get; private set; } + + public bool PesCrcFlag { get; set; } + + public bool AdditionalCopyInfoFlag { get; private set; } + + public bool DsmTrickModeFlag { get; private set; } + + public bool EsRateFlag { get; private set; } + + public bool EscrFlag { get; private set; } + + public int PtsDtsFlags { get; private set; } + + public bool OriginalOrCopy { get; private set; } + + public bool Copyright { get; private set; } + + public bool DataAlignmentIndicator { get; private set; } + + public bool PesPriority { get; private set; } + + public int PesScramblingControl { get; private set; } + + public ushort PesPacketLength { get; private set; } + + public byte StreamId { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/ProgramStreamPackHeader.cs b/skyscraper8/Mpeg2/ProgramStreamPackHeader.cs new file mode 100644 index 0000000..69a3a8a --- /dev/null +++ b/skyscraper8/Mpeg2/ProgramStreamPackHeader.cs @@ -0,0 +1,100 @@ +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + class ProgramStreamPackHeader + { + public ProgramStreamPackHeader(byte[] readBytes) + { + MemoryStream ms = new MemoryStream(readBytes, false); + if (ms.ReadUInt32BE() != 0x000001BA) + throw new DvbException("invalid pack header"); + + byte readUInt8 = ms.ReadUInt8(); + int magic01 = (readUInt8 & 0xc0) >> 6; + if (magic01 != 1) + throw new DvbException("invalid pack header"); + + SystemClockReferenceBase = (readUInt8 & 0x38) >> 3; + SystemClockReferenceBase <<= 2; + + if ((readUInt8 & 0x04) != 0) + throw new DvbException("invalid pack header"); + + SystemClockReferenceBase += (readUInt8 & 0x03); + SystemClockReferenceBase <<= 8; + + readUInt8 = ms.ReadUInt8(); + SystemClockReferenceBase += readUInt8; + SystemClockReferenceBase <<= 5; + + readUInt8 = ms.ReadUInt8(); + SystemClockReferenceBase += ((readUInt8 & 0xf8) >> 3); + SystemClockReferenceBase <<= 2; + + if ((readUInt8 & 0x04) != 0) + throw new DvbException("invalid pack header"); + + SystemClockReferenceBase += ((readUInt8 & 0x03)); + SystemClockReferenceBase <<= 8; + + readUInt8 = ms.ReadUInt8(); + SystemClockReferenceBase += readUInt8; + SystemClockReferenceBase <<= 5; + + readUInt8 = ms.ReadUInt8(); + SystemClockReferenceBase += ((readUInt8 & 0xf8) >> 3); + + if ((readUInt8 & 0x04) != 0) + throw new DvbException("invalid pack header"); + + SystemClockReferenceExtension = ((readUInt8 & 0x03)); + SystemClockReferenceExtension <<= 7; + + readUInt8 = ms.ReadUInt8(); + SystemClockReferenceExtension += ((readUInt8 & 0xfe) >> 1); + + if ((readUInt8 & 0x01) != 0) + throw new DvbException("invalid pack header"); + + ProgramMuxRate = ms.ReadUInt16BE(); + ProgramMuxRate <<= 6; + + readUInt8 = ms.ReadUInt8(); + ProgramMuxRate += (readUInt8 & 0xfc); + + if ((readUInt8 & 0x02) != 0) + throw new DvbException("invalid pack header"); + + if ((readUInt8 & 0x01) != 0) + throw new DvbException("invalid pack header"); + + readUInt8 = ms.ReadUInt8(); + PackStuffingLength = readUInt8 & 0x07; + + ms.Position += PackStuffingLength; + + long available = ms.GetAvailableBytes(); + if (available >= 4) + { + if (ms.ReadUInt32BE() == 0x000001BB) + { + ms.Position -= 4; + SystemHeader = new ProgramStreamSystemHeader(ms.ReadBytes(available)); + } + } + } + + public ProgramStreamSystemHeader SystemHeader { get; private set; } + + public int PackStuffingLength { get; private set; } + + public long ProgramMuxRate { get; private set; } + + public long SystemClockReferenceExtension { get; private set; } + + public long SystemClockReferenceBase { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/ProgramStreamSystemHeader.cs b/skyscraper8/Mpeg2/ProgramStreamSystemHeader.cs new file mode 100644 index 0000000..8d5a7b3 --- /dev/null +++ b/skyscraper8/Mpeg2/ProgramStreamSystemHeader.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + class ProgramStreamSystemHeader + { + public ProgramStreamSystemHeader(byte[] readBytes) + { + MemoryStream ms = new MemoryStream(readBytes, false); + if (ms.ReadUInt32BE() != 0x000001BB) + throw new DvbException("invalid program stream system header"); + + ushort headerLength = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + if ((readUInt8 & 0x80) != 0) + throw new DvbException("invalid program stream system header"); + + RateBound = readUInt8 & 0x7f; + + ushort readUInt16Be = ms.ReadUInt16BE(); + RateBound += ((readUInt8 & 0xfffe) >> 1); + + if ((readUInt8 & 0x01) != 0) + throw new DvbException("invalid program stream system header"); + + readUInt8 = ms.ReadUInt8(); + AudioBound = ((readUInt8 & 0xfc) >> 2); + + FixedFlag = (readUInt8 & 0x02) != 0; + CspsFlag = (readUInt8 & 0x01) != 0; + + readUInt8 = ms.ReadUInt8(); + SystemAudioLockFlag = ((readUInt8 & 0x80) != 0); + SystemVideoLockFlag = ((readUInt8 & 0x40) != 0); + if ((readUInt8 & 0x20) != 0) + throw new DvbException("invalid program stream system header"); + + VideoBound = (readUInt8 & 0x1f); + + readUInt8 = ms.ReadUInt8(); + PacketRateRestrictionFlag = ((readUInt8 & 0x80) != 0); + + while (ms.GetAvailableBytes() > 3) + { + byte streamId = ms.ReadUInt8(); + if ((streamId & 0x80) == 0) + break; + + ushort uInt16Be = ms.ReadUInt16BE(); + if ((uInt16Be & 0xc000) != 0xc000) + break; + + bool pStdBufferBoundScale = (uInt16Be & 0x2000) != 0; + int pStdBuffSizeBound = (uInt16Be & 0x1fff); + + ElementaryStream es = new ElementaryStream(streamId, pStdBufferBoundScale, pStdBuffSizeBound); + if (ElementaryStreams == null) + ElementaryStreams = new List(); + ElementaryStreams.Add(es); + } + } + + public bool PacketRateRestrictionFlag { get; private set; } + + public int VideoBound { get; private set; } + + public bool SystemVideoLockFlag { get; private set; } + + public bool SystemAudioLockFlag { get; private set; } + + public bool CspsFlag { get; private set; } + + public bool FixedFlag { get; private set; } + + public int AudioBound { get; private set; } + + public long RateBound { get; private set; } + + public List ElementaryStreams { get; private set; } + + internal class ElementaryStream + { + public byte StreamId { get; } + public bool PStdBufferBoundScale { get; } + public int PStdBuffSizeBound { get; } + + public ElementaryStream(byte streamId, bool pStdBufferBoundScale, int pStdBuffSizeBound) + { + StreamId = streamId; + PStdBufferBoundScale = pStdBufferBoundScale; + PStdBuffSizeBound = pStdBuffSizeBound; + } + } + } +} diff --git a/skyscraper8/Mpeg2/Psi/CatEventHandler.cs b/skyscraper8/Mpeg2/Psi/CatEventHandler.cs new file mode 100644 index 0000000..8a15541 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/CatEventHandler.cs @@ -0,0 +1,9 @@ +using skyscraper5.Mpeg2.Descriptors; + +namespace skyscraper5.Mpeg2.Psi +{ + interface ICatEventHandler + { + void NotifyOfCaSystem(CaDescriptor caDescriptor, bool fromPmt = false); + } +} diff --git a/skyscraper8/Mpeg2/Psi/CatParser.cs b/skyscraper8/Mpeg2/Psi/CatParser.cs new file mode 100644 index 0000000..6f953d6 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/CatParser.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2.Psi +{ + class CatParser : IPsiProcessor + { + public ICatEventHandler EventHandler { get; } + + public CatParser(ICatEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy()); + if (ms.GetAvailableBytes() < 9) + return; + byte tableId = ms.ReadUInt8(); + if (tableId != 0x01) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxSelector = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxSelector) + return; + int sectionLength = readUInt16Be & 0x0fff; + + ms.ReadUInt16BE(); //16 of 18 reversed bits + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e) >> 1; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + long availableBytes = ms.GetAvailableBytes() - 4; + byte[] descriptors = ms.ReadBytes(availableBytes); + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptors, "CAT"); + + uint? privateDataSpecifier = null; + foreach (TsDescriptor dvbDescriptor in unpackDescriptors) + { + string name = dvbDescriptor.GetType().Name; + switch (name) + { + case nameof(CaDescriptor): + CaDescriptor caDescriptor = (CaDescriptor)dvbDescriptor; + EventHandler.NotifyOfCaSystem(caDescriptor); + break; + case nameof(StuffingDescriptor): + break; + case nameof(UserDefinedDescriptor): + UserDefinedDescriptor udd = (UserDefinedDescriptor)dvbDescriptor; + if (!privateDataSpecifier.HasValue) + continue; + TsDescriptor unpackedDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor(udd, privateDataSpecifier.Value, "CAT"); + if (unpackedDescriptor != null) + continue; + throw new NotImplementedException(); + default: + throw new NotImplementedException(name); + } + } + + uint crc32 = ms.ReadUInt32BE(); + } + } +} diff --git a/skyscraper8/Mpeg2/Psi/IpmpEventHandler.cs b/skyscraper8/Mpeg2/Psi/IpmpEventHandler.cs new file mode 100644 index 0000000..d227032 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/IpmpEventHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2.Psi +{ + interface IIpmpEventHandler + { + } +} diff --git a/skyscraper8/Mpeg2/Psi/IpmpParser.cs b/skyscraper8/Mpeg2/Psi/IpmpParser.cs new file mode 100644 index 0000000..0e84554 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/IpmpParser.cs @@ -0,0 +1,24 @@ +using skyscraper5.Skyscraper.Scraper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2.Psi +{ + class IpmpParser : IPsiProcessor + { + public IpmpParser(SkyscraperContext skyscraperContext) + { + SkyscraperContext = skyscraperContext; + } + + public SkyscraperContext SkyscraperContext { get; } + + public void GatherPsi(PsiSection section, int sourcePid) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Mpeg2/Psi/Model/PmtStreamType.cs b/skyscraper8/Mpeg2/Psi/Model/PmtStreamType.cs new file mode 100644 index 0000000..6b14090 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/Model/PmtStreamType.cs @@ -0,0 +1,54 @@ +namespace skyscraper5.Mpeg2.Psi.Model +{ + public enum PmtStreamType : byte + { + IsoReserved = 0, + Iso11172Video = 1, + H262 = 2, + Iso11172Audio = 3, + Iso13818_3Audio = 4, + Iso13818_1PrivateSections = 5, + Iso13818_1PesPackets = 6, + Iso13522_Mheg = 7, + Iso13818_1DsmCc = 8, + H222_1 = 9, + Iso13818_6TypeA = 0x0a, + Iso13818_6TypeB = 0x0b, + Iso13818_6TypeC = 0x0c, + Iso13818_6TypeD = 0x0d, + Iso13818_1Auxilary = 0x0e, + Iso13818_7AudioADTS = 0x0f, + Iso14496_2Visual = 0x10, + Iso14496_3Audio = 0x11, + Iso14496_1OrPesFlexmux = 0x12, + Iso14496_1OrIso14496Flexmux = 0x13, + Iso13818_6Download = 0x14, + MetadataInPesPackets = 0x15, + MetadataInMetadataSections = 0x16, + MetadataInDsmCcDataCarousel = 0x17, + MetadataInDsmCcObjectCarousel = 0x18, + MetadataInDsmCcSynchronizedDownloadProtocal = 0x19, + IpmpStream = 0x1a, + AvcVideoStream = 0x1b, + Iso14496_3AudioWithoutAdditionalTransport = 0x1c, + Iso14496_17Text = 0x1d, + AuxillaryVideoStream = 0x1e, + SvcVideoSubstream = 0x1f, + MvcVideoSubstream = 0x20, + Jpeg2000Video = 0x21, + Mpeg2_3D = 0x22, + H264_3D = 0x23, + HevcVideoStream = 0x24, + HevcTemporalVideoSubset = 0x25, + MvcdVideoSubstream = 0x26, + TimelineAndExternalMediaInformationStream = 0x27, /* ITU-T H.222.0 Annex T */ + HevcEnhancement_AnnexG = 0x28, + HevcTemporalEnhancement_AnnexG = 0x29, + HevcEnhancement_AnnexH = 0x2a, + HevcTemporalEnhancement_AnnexH = 0x2b, + GreenAccessUnit = 0x2c, + Iso23008_3Main = 0x2d, + Iso23008_3Auxiliary = 0x2e, + QualityAccessUnits = 0x2f, + } +} diff --git a/skyscraper8/Mpeg2/Psi/Model/ProgramMapping.cs b/skyscraper8/Mpeg2/Psi/Model/ProgramMapping.cs new file mode 100644 index 0000000..6d713a3 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/Model/ProgramMapping.cs @@ -0,0 +1,146 @@ +using System.Collections.Generic; +using skyscraper5.Iso14496_1; +using skyscraper5.Iso14496_1.Descriptors; +using skyscraper5.Mpeg2.Descriptors; + +namespace skyscraper5.Mpeg2.Psi.Model +{ + public class ProgramMapping + { + public ushort ProgramNumber { get; } + public int PcrPid { get; } + public List Streams { get; } + + //from 0x0e maximum bitrrate descriptor + public uint? MaximumBitrate { get; set; } + + //from 0x0c multiplex buffer utilization descriptor + public bool? MultiplexBufferUtilizationBoundValidFlag { get; set; } + public int? MultiplexLtwOffsetLowerBound { get; set; } + public int? MultiplexLtwOffsetUpperBound { get; set; } + + //from 0x0b system clock descriptor + public int? ClockAccuracyExponent { get; set; } + public int? ClockAccuracyInteger { get; set; } + public bool? ExternalClockReferenceIndicator { get; set; } + + //from 0x10 smoothing buffer descriptor + public uint? SbLeakRate { get; set; } + public uint? SbSize { get; set; } + + //from 0x09 ca system descriptor + public int? CaPid { get; set; } + public ushort? CaSystemId { get; set; } + public byte[] CaPrivateData { get; set; } + + //from 0x5f private data specifier descriptor + public uint? PrivateDataSpecifier { get; set; } + + //from 0x05 registration descriptor + public byte[] RegistrationAdditionalIdentificationInfo { get; set; } + public uint? RegistrationFormatIdentifier { get; set; } + + //from 0x60 service move descriptor + public ushort? NewOriginalNetworkId { get; set; } + public ushort? NewServiceId { get; set; } + public ushort? NewTransportStreamId { get; set; } + + //from 0x52 stream identifier descriptor + public byte? ComponentTag { get; set; } + + //from 0x38 metadata std descriptor + public int? MetadataInputLeakRate { get; set; } + public int? MetadataBufferSize { get; set; } + public int? MetadataOutputLeakRate { get; set; } + + //from 0x65 scrambling descriptor + public byte? ScramblingMode { get; set; } + + //from 0x1e sl descriptor + public ushort? EsId { get; set; } + + //from 0x0a iso 639 language descriptor + public AudioType? AudioType { get; set; } + public string Iso639LanguageCode { get; set; } + + //from 0x1d iod descriptor + public InitialObjectDescriptor InitialObjectDescriptor { get; set; } + public byte? ScopeOfIodLabel { get; set; } + public byte? IodLabel { get; set; } + + //from 0x0f private data indicator descriptor + public uint? PrivateDataIndicator { get; set; } + + //From 0x02 video stream descriptor + public double? FrameRate { get; set; } + public int? ChromaFormat { get; set; } + public bool? ConstrainedParameterFlag { get; set; } + public int? FrameRateExtensionFlag { get; set; } + public bool? Mpeg1OnlyFlag { get; set; } + public bool? MultipleFramerateFlag { get; set; } + public byte? ProfileAndLevelIndication { get; set; } + public bool? StillPictureFlag { get; set; } + + //from 0x25 metadata pointer descriptor + public ushort? MetadataProgramNumber { get; set; } + public ushort? MetadataApplicationFormat { get; set; } + public uint? MetadataApplicationFormatIdentifier { get; set; } + public ushort? MetadataFormat { get; set; } + public uint? MetadataFormatIdentifier { get; set; } + public byte[] MetadataLocatorRecord { get; set; } + public bool? MetadataLocatorRecordFlag { get; set; } + public byte? MetadataServiceId { get; set; } + public int? MpegCarriageFlag { get; set; } + public byte[] MetadataPrivateData { get; set; } + public ushort? TransportStreamId { get; set; } + public ushort? TransportStreamLocation { get; set; } + + //From 0x03 audio stream descriptor + public bool? FreeFormat { get; set; } + public bool? AudioStreamId { get; set; } + public int? Layer { get; set; } + public bool? VariableRateAudio { get; set; } + + //from hdmv descriptor 0x88 + public byte[] CopyControlPrivateData { get; internal set; } + + //from 0x06 data stream alignment descriptor + public byte? AlignmentType { get; internal set; } + + //from 0x04 hierarchy descriptory + public int? HierarchyType { get; internal set; } + public int? HierarchyLayerType { get; internal set; } + public bool? TrefPresentFlag { get; internal set; } + public int? HierarchyChannel { get; internal set; } + public bool? NoViewScalabilityFlag { get; internal set; } + public int? HierarchyEmbeddedLayerIndex { get; internal set; } + public bool? NoQualityScalabilityFlag { get; internal set; } + public bool? NoSpatialScalabilityFlag { get; internal set; } + public bool? NoTemporalScalabilityFlag { get; internal set; } + + public ProgramMapping(ushort programNumber, int pcrPid) + { + ProgramNumber = programNumber; + PcrPid = pcrPid; + Streams = new List(); + } + + protected bool Equals(ProgramMapping other) + { + return ProgramNumber == other.ProgramNumber; + } + + 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((ProgramMapping)obj); + } + + public override int GetHashCode() + { + return ProgramNumber.GetHashCode(); + } + } +} diff --git a/skyscraper8/Mpeg2/Psi/Model/ProgramMappingStream.cs b/skyscraper8/Mpeg2/Psi/Model/ProgramMappingStream.cs new file mode 100644 index 0000000..dc431c5 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/Model/ProgramMappingStream.cs @@ -0,0 +1,236 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Mpeg2.Descriptors; + +namespace skyscraper5.Mpeg2.Psi.Model +{ + public class ProgramMappingStream + { + public override string ToString() + { + return $"{nameof(StreamType)}: {StreamType}, {nameof(ElementaryPid)}: {ElementaryPid:X4}"; + } + + public PmtStreamType StreamType { get; } + public int ElementaryPid { get; } + + //From 0x52_StreamIdentifierDescriptor + public byte? ComponentTag { get; set; } + + //From 0x10_Iso639LanguageDescriptor + public AudioType? AudioType { get; set; } + public string Iso639LanguageCode { get; set; } + + //From 0x56_TeletextDescriptor + public TeletextDescriptor.TeletextDescriptorTeletext[] Teletexts { get; set; } + + //From 0x56_ApplicationSignallingDescriptor + public List Applications { get; set; } + + //From 0x13_CarouselIdentifierDescriptor + public byte? CarouselFormatId { get; set; } + public uint? CarouselId { get; set; } + + //From 0x66_DataBroadcastIdDescriptor + public ushort? DataBroadcastId { get; set; } + public byte[] DataBroadcastSelector { get; set; } + + public DataBroadcastIdDescriptor.IpMacPlatform[] IpMacNotificationInfo { get; set; } + + //From Extension 0x06_SupplementaryAudioDescriptor + public bool? SupplementaryIndependantStream { get; set; } + public SupplemenatryAudioDescriptor.EditorialClassificationEnum? EditorialClassification { get; set; } + public string SupplementaryLanguageCode { get; set; } + + //From 0x6A_Ac3Descriptor + public byte? Asvc { get; set; } + public int? BSID { get; set; } + public Ac3Descriptor.ComponentTypeEnum? ComponentType { get; set; } + public byte? MainId { get; set; } + + //From 0x02_VideoStreamDescriptor + public double? FrameRate { get; set; } + public bool? Mpeg1OnlyFlag { get; set; } + public int? ChromaFormat { get; set; } + public bool? ConstrainedParameterFlag { get; set; } + public int? FrameRateExtensionFlag { get; set; } + public bool? MultipleFramerateFlag { get; set; } + public byte? ProfileAndLevelIndication { get; set; } + public bool? StillPictureFlag { get; set; } + + //From 0x06 Data Stream Alignment Descriptor + public byte? AlignmentType { get; set; } + + //From 0x0e Maximum Bitrate Descriptor + public uint? MaximumBitrate { get; set; } + + //From 0x03 Audio Stream Descriptor + public bool? AudioStreamId { get; set; } + public bool? FreeFormat { get; set; } + public int? Layer { get; set; } + public bool? VariableRateAudio { get; set; } + + //From 0x28 Avc Video Descriptor + public bool? ConstraintSet0Flag { get; set; } + public bool? Avc24HourPictureFlag { get; set; } + public int? AvcCompatibleFlags { get; set; } + public bool? AvcStillPresent { get; set; } + public bool? ConstraintSet1Flag { get; set; } + public bool? ConstraintSet2Flag { get; set; } + public bool? ConstraintSet3Flag { get; set; } + public bool? ConstraintSet4Flag { get; set; } + public bool? ConstraintSet5Flag { get; set; } + public bool? FramePackingSeiNotPresent { get; set; } + public byte? LevelIdc { get; set; } + public byte? ProfileRdc { get; set; } + + //From 0x09 ca descriptor + public int? CaPid { get; set; } + public ushort? CaSystemId { get; set; } + public byte[] CaPrivateData { get; set; } + + //From 0x5f private data specifier descriptor + public uint? PrivateDataSpecifier { get; set; } + + //From 0x59 subtitling descriptor + public SubtitlingDescriptor.Subtitling[] Subtitlings { get; set; } + + //From 0x05 registration descriptor + public uint? FormatIdentifier { get; set; } + public byte[] AdditionalIdentificationInfo { get; set; } + + //From 0x14 association tag descriptor + public byte[] AssociationTagSelector { get; set; } + public uint? AssociationTagTransactionId { get; set; } + public ushort? AssociationTagUse { get; set; } + public byte[] AssociationTagPrivateData { get; set; } + public uint? AssociationTagTimeOut { get; set; } + + //From 0x45 vbi data descriptor + public ReadOnlyCollection VbiData { get; set; } + + //From 0x7c aac descriptor + public byte? AacType { get; set; } + public byte[] AacAdditionalInfo { get; set; } + public byte? AacProfileAndLevel { get; set; } + public bool? SaocDeFlag { get; set; } + + //From 0x6b ancillary data descriptor + public AncillaryDataDescriptor AncillaryDataDescriptor { get; set; } + + //From 0x7a enhanced ac3 descriptor + public bool? MixInfoExists { get; set; } + public byte? Substream1 { get; set; } + public byte? Substream2 { get; set; } + public byte? Substream3 { get; set; } + + //From 0x38 hevc video descriptor + public long? Copied44bits { get; set; } + public bool? FrameOnlyConstraintFlag { get; set; } + public int? HdrWcgIdc { get; set; } + public bool? Hevc24hrPicturePresentFlag { get; set; } + public bool? HevcStillPresentFlag { get; set; } + public bool? InterlacedSourceFlag { get; set; } + public bool? NonPackedConstraintFlag { get; set; } + public uint? ProfileCompatibilityIndication { get; set; } + public int? ProfileIdc { get; set; } + public int? ProfileSpace { get; set; } + public bool? ProgressiveSourceFlag { get; set; } + public bool? SubPicHrdParamsNotPresentFlag { get; set; } + public int? TemporalIdMax { get; set; } + public bool? TierFlag { get; set; } + public int? TemporalIdMin { get; set; } + + //from extension descriptor 0x15 ac4 descriptor + public int? Ac4ChannelMode { get; set; } + public bool? Ac4DialogEnhancement { get; set; } + public byte[] Ac4Dsi { get; set; } + + //from extension descriptor 0x19 audio preselection descriptor + public AudioPreselectionDescriptor.AudioPreselection[] AudioPreselection { get; set; } + + //from 0x11 std descriptor + public bool? LeakValidFlag { get; set; } + + //from 0x1f fmc descriptor + public FmcDescriptor.Fmc[] FlexMuxChannels { get; set; } + + //from 0x10 smoothing buffer descriptor + public uint? SbLeakRate { get; set; } + public uint? SbSize { get; set; } + + //from 0x0f private data indicator descriptor + public uint? PrivateDataIndicator { get; set; } + + //from mpeg-2 extension descriptor 0x03 hevc timing and hrd + public bool? _90khzFlag { get; set; } + public bool? PictureAndTimingInfoPresentFlag { get; set; } + public bool? HdrManagementValidFlag { get; set; } + public uint? K { get; set; } + public uint? N { get; set; } + public uint? NumUnitsInTick { get; set; } + public int? TargetScheduleIdx { get; set; } + public bool? TargetScheduleIdxNotPresentFlag { get; set; } + + //From 0x70 adaption field data descriptor + public byte? AdaptionFieldDataIdentifier { get; set; } + + //From 0x2a avc timing and hrd descriptor + public bool? FixedFrameRateFlag { get; set; } + public bool? PictureToDisplayConversionFlag { get; set; } + public bool? TemporalPocFlag { get; set; } + + //From 0x1c mpeg4 audio descriptor + public byte? Mpeg4AudioProfileAndLevel { get; set; } + + //From 0x2b mpeg2 aac audio descriptor + public byte? AacChannelConfiguration { get; set; } + public byte? AacProfile { get; set; } + public byte? AacAdditionalInformation { get; set; } + + //from 0x65 scrambling descriptor + public byte? ScramblingMode { get; set; } + + //from 0x74 related content descriptor + public bool? RelatedContentDescriptorPresent { get; set; } + + //from 0x46 vbi teletext descriptor + public TeletextDescriptor.TeletextDescriptorTeletext[] VbiTeletexts { get; internal set; } + + //from extension 0x11, t2mi descriptor + public int? NumT2MiStreams { get; internal set; } + public int? T2MiStreamId { get; internal set; } + public bool? PcrIscrCommonClockFlag { get; internal set; } + + //from 0x2e, MPEG-4 Audio Extension Descriptor + public byte[] AudioSpecifConfig { get; internal set; } + public byte[] AudioProfileLevelIndication { get; internal set; } + public uint? MetadataApplicationFormatIdentifier { get; internal set; } + public ushort MetadataApplicationFormat { get; internal set; } + public byte MetadataFormat { get; internal set; } + public uint? MetadataFormatIdentifier { get; internal set; } + public byte MetadataServiceId { get; internal set; } + public int DecoderConfigFlag { get; internal set; } + public byte[] ServiceIdentificationRecord { get; internal set; } + public byte[] DecoderConfig { get; internal set; } + public byte[] DecoderConfigIdentificationRecord { get; internal set; } + public ushort DecoderConfigMetadataServiceId { get; internal set; } + public byte[] Reserved { get; internal set; } + public byte[] PrivateData { get; internal set; } + + public List UnknownUserDefines; + public ProgramMappingStream(PmtStreamType streamType, int elementaryPid) + { + StreamType = streamType; + ElementaryPid = elementaryPid; + } + + public bool IsUserPrivateStream() + { + return ((byte)StreamType) >= 0x80; + } + + } +} diff --git a/skyscraper8/Mpeg2/Psi/PatEventHandler.cs b/skyscraper8/Mpeg2/Psi/PatEventHandler.cs new file mode 100644 index 0000000..6d5291a --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/PatEventHandler.cs @@ -0,0 +1,9 @@ +namespace skyscraper5.Mpeg2.Psi +{ + interface IPatEventHandler + { + void NetworkPidFromPat(int networkPid); + void ProgramMapPidFromPat(int pmtPid, ushort programNumber); + void SetTransportStreamId(ushort transportStreamId); + } +} diff --git a/skyscraper8/Mpeg2/Psi/PatParser.cs b/skyscraper8/Mpeg2/Psi/PatParser.cs new file mode 100644 index 0000000..9685329 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/PatParser.cs @@ -0,0 +1,67 @@ +using System.IO; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2.Psi +{ + class PatParser : IPsiProcessor + { + public IPatEventHandler PatEventHandler { get; } + + public PatParser(IPatEventHandler patEventHandler) + { + PatEventHandler = patEventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + if (ms.GetAvailableBytes() < 8) + return; + byte tableId = ms.ReadUInt8(); + if (tableId != 0 || sourcePid != 0) + return; //Discard if PAT from weird PID + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + bool zero = (readUInt16Be & 0x4000) != 0; + bool reserved = (readUInt16Be & 0x3000) != 0; + int sectionLength = (readUInt16Be & 0x0fff); + + ushort transportStreamId = ms.ReadUInt16BE(); + if (sourcePid == 0) + PatEventHandler.SetTransportStreamId(transportStreamId); + + byte readUInt8 = ms.ReadUInt8(); + int reserved2 = (readUInt8 & 0xc0) >> 6; + int versionNumber = (readUInt8 & 0x3e) >> 1; + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + byte lastSectionNumber = ms.ReadUInt8(); + + long numProgs = ms.Length; + numProgs -= 4; //for crc32 + numProgs -= 8; //already wasted + numProgs /= 4; //length of each program + + for (long i = 0; i < numProgs; i++) + { + ushort programNumber = ms.ReadUInt16BE(); + readUInt16Be = ms.ReadUInt16BE(); + int reversed3 = (readUInt16Be & 0xe000) >> 13; + + if (programNumber == 0) + { + int networkPid = (readUInt16Be & 0x1fff); + PatEventHandler?.NetworkPidFromPat(networkPid); + } + else + { + int programMapPid = (readUInt16Be & 0x1fff); + PatEventHandler?.ProgramMapPidFromPat(programMapPid, programNumber); + } + } + + } + } +} diff --git a/skyscraper8/Mpeg2/Psi/PmtEventHandler.cs b/skyscraper8/Mpeg2/Psi/PmtEventHandler.cs new file mode 100644 index 0000000..1802d3b --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/PmtEventHandler.cs @@ -0,0 +1,9 @@ +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Mpeg2.Psi +{ + interface IPmtEventHandler + { + void PmtEvent(ProgramMapping result, int pmtPid); + } +} diff --git a/skyscraper8/Mpeg2/Psi/PmtParser.cs b/skyscraper8/Mpeg2/Psi/PmtParser.cs new file mode 100644 index 0000000..3011a74 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/PmtParser.cs @@ -0,0 +1,587 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Descriptors.Extension; +using skyscraper5.Hdmv.Descriptors; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.ExtensionDescriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.IO; +using skyscraper5.src.Mpeg2.Descriptors; + +namespace skyscraper5.Mpeg2.Psi +{ + class PmtParser : IPsiProcessor + { + public IPmtEventHandler EventHandler { get; } + + public PmtParser(IPmtEventHandler eventHandler) + { + EventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream buffer = new MemoryStream(section.GetDataCopy()); + + byte table_id = buffer.ReadUInt8(); + if (table_id != 0x02) + return; + + ushort readUInt16Be = buffer.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + { + //According to T-REC-H.222.0-201808-S!!PDF-E.pdf, page 71 - this is supposed to be set to false. + return; + } + bool zero = (readUInt16Be & 0x4000) != 0; + int reserved = (readUInt16Be & 0x3000) >> 12; + int sectionLength = (readUInt16Be & 0x0fff); + + ushort programNumber = buffer.ReadUInt16BE(); + + byte readUInt8 = buffer.ReadUInt8(); + int reserved2 = (readUInt8 & 0xc0) >> 6; + int versionNumber = (readUInt8 & 0x3e); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = buffer.ReadUInt8(); + + byte lastSectionNumber = buffer.ReadUInt8(); + + readUInt16Be = buffer.ReadUInt16BE(); + int reserved3 = (readUInt16Be & 0xe000) >> 13; + int pcrPid = (readUInt16Be & 0x1fff); + + readUInt16Be = buffer.ReadUInt16BE(); + int reserved4 = (readUInt16Be & 0xf000) >> 12; + int programInfoLength = (readUInt16Be & 0x0fff) & 0x3ff; + + ProgramMapping result = new ProgramMapping(programNumber, pcrPid); + if (programInfoLength > 0) + { + byte[] descriptorBuffer = buffer.ReadBytes(programInfoLength); + ParseOuterDescriptors(result, descriptorBuffer); + } + + while ((buffer.Length - buffer.Position) > 4) + { + PmtStreamType streamType = (PmtStreamType)buffer.ReadUInt8(); + + readUInt16Be = buffer.ReadUInt16BE(); + int reserved5 = (readUInt16Be & 0xe000) >> 13; + int elementaryPid = (readUInt16Be & 0x1fff); + + readUInt16Be = buffer.ReadUInt16BE(); + int reserved6 = (readUInt16Be & 0xf000) >> 12; + int esInfoLength = (readUInt16Be & 0xfff) & 0x3ff; + if (esInfoLength > buffer.GetAvailableBytes()) + return; + + byte[] descriptorBuffer2 = buffer.ReadBytes(esInfoLength); + ProgramMappingStream streamChild = new ProgramMappingStream(streamType, elementaryPid); + result.Streams.Add(streamChild); + ParseInnerDescriptors(streamChild, descriptorBuffer2); + } + + EventHandler?.PmtEvent(result, sourcePid); + } + + private void ParseInnerDescriptors(ProgramMappingStream output, byte[] inputBuffer) + { + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(inputBuffer, "PMT"); + foreach (TsDescriptor dvbDescriptor in descriptors) + { + switch (dvbDescriptor.GetType().Name) + { + case nameof(StreamIdentifierDescriptor): + output.ComponentTag = ((StreamIdentifierDescriptor)dvbDescriptor).ComponentTag; + break; + case nameof(Iso639LanguageDescriptor): + Iso639LanguageDescriptor iso639Language = (Iso639LanguageDescriptor)dvbDescriptor; + output.Iso639LanguageCode = iso639Language.Iso639LanguageCode; + output.AudioType = iso639Language.AudioType; + break; + case nameof(TeletextDescriptor): + TeletextDescriptor teletextDescriptor = (TeletextDescriptor)dvbDescriptor; + output.Teletexts = teletextDescriptor.Teletexts; + break; + case nameof(ApplicationSignallingDescriptor): + ApplicationSignallingDescriptor alApplicationSignallingDescriptor = (ApplicationSignallingDescriptor)dvbDescriptor; + if (output.Applications == null) + output.Applications = new List(); + output.Applications.AddRange(alApplicationSignallingDescriptor.Applications); + break; + case nameof(CarouselIdentifierDescriptor): + CarouselIdentifierDescriptor carouselIdentifierDescriptor = (CarouselIdentifierDescriptor)dvbDescriptor; + output.CarouselFormatId = carouselIdentifierDescriptor.FormatId; + output.CarouselId = carouselIdentifierDescriptor.CarouselId; + break; + case nameof(DataBroadcastIdDescriptor): + DataBroadcastIdDescriptor dataBroadcastIdDescriptor = (DataBroadcastIdDescriptor)dvbDescriptor; + output.DataBroadcastId = dataBroadcastIdDescriptor.DataBroadcastId; + output.DataBroadcastSelector = dataBroadcastIdDescriptor.DataBroadcastSelector; + output.IpMacNotificationInfo = dataBroadcastIdDescriptor.IpMacNotificationInfo; + break; + case nameof(SupplemenatryAudioDescriptor): + SupplemenatryAudioDescriptor supplemenatryAudioDescriptor = (SupplemenatryAudioDescriptor)dvbDescriptor; + output.SupplementaryIndependantStream = supplemenatryAudioDescriptor.MixType; + output.EditorialClassification = supplemenatryAudioDescriptor.EditorialClassification; + output.SupplementaryLanguageCode = supplemenatryAudioDescriptor.Iso639LanguageCode; + break; + case nameof(Ac3Descriptor): + Ac3Descriptor ac3Descriptor = (Ac3Descriptor)dvbDescriptor; + output.Asvc = ac3Descriptor.Asvc; + output.BSID = ac3Descriptor.BSID; + output.ComponentType = ac3Descriptor.ComponentType; + output.MainId = ac3Descriptor.MainId; + break; + case nameof(VideoStreamDescriptor): + VideoStreamDescriptor videoStreamDescriptor = (VideoStreamDescriptor)dvbDescriptor; + output.FrameRate = videoStreamDescriptor.FrameRate; + output.Mpeg1OnlyFlag = videoStreamDescriptor.Mpeg1OnlyFlag; + output.ChromaFormat = videoStreamDescriptor.ChromaFormat; + output.ConstrainedParameterFlag = videoStreamDescriptor.ConstrainedParameterFlag; + output.FrameRateExtensionFlag = videoStreamDescriptor.FrameRateExtensionFlag; + output.MultipleFramerateFlag = videoStreamDescriptor.MultipleFramerateFlag; + output.ProfileAndLevelIndication = videoStreamDescriptor.ProfileAndLevelIndication; + output.StillPictureFlag = videoStreamDescriptor.StillPictureFlag; + break; + case nameof(DataStreamAlignmentDescriptor): + output.AlignmentType = ((DataStreamAlignmentDescriptor)dvbDescriptor).AlignmentType; + break; + case nameof(MaximumBitrateDescriptor): + MaximumBitrateDescriptor maximumBitrateDescriptor = ((MaximumBitrateDescriptor)dvbDescriptor); + output.MaximumBitrate = maximumBitrateDescriptor.MaximumBitrate; + break; + case nameof(AudioStreamDescriptor): + AudioStreamDescriptor audioStreamDescriptor = ((AudioStreamDescriptor)dvbDescriptor); + output.AudioStreamId = audioStreamDescriptor.ID; + output.FreeFormat = audioStreamDescriptor.FreeFormat; + output.Layer = audioStreamDescriptor.Layer; + output.VariableRateAudio = audioStreamDescriptor.VariableRateAudio; + break; + case nameof(AvcVideoDescriptor): + AvcVideoDescriptor avcVideoDescriptor = (AvcVideoDescriptor)dvbDescriptor; + output.Avc24HourPictureFlag = avcVideoDescriptor.Avc24HourPictureFlag; + output.AvcCompatibleFlags = avcVideoDescriptor.AvcCompatibleFlags; + output.AvcStillPresent = avcVideoDescriptor.AvcStillPresent; + output.ConstraintSet0Flag = avcVideoDescriptor.ConstraintSet0Flag; + output.ConstraintSet1Flag = avcVideoDescriptor.ConstraintSet1Flag; + output.ConstraintSet2Flag = avcVideoDescriptor.ConstraintSet2Flag; + output.ConstraintSet3Flag = avcVideoDescriptor.ConstraintSet3Flag; + output.ConstraintSet4Flag = avcVideoDescriptor.ConstraintSet4Flag; + output.ConstraintSet5Flag = avcVideoDescriptor.ConstraintSet5Flag; + output.FramePackingSeiNotPresent = avcVideoDescriptor.FramePackingSeiNotPresent; + output.LevelIdc = avcVideoDescriptor.LevelIdc; + output.ProfileRdc = avcVideoDescriptor.ProfileRdc; + break; + case nameof(CaDescriptor): + CaDescriptor caDescriptor = (CaDescriptor)dvbDescriptor; + output.CaPid = caDescriptor.CaPid; + output.CaSystemId = caDescriptor.CaSystemId; + output.CaPrivateData = caDescriptor.PrivateData; + break; + case nameof(UserDefinedDescriptor): + if (!output.PrivateDataSpecifier.HasValue) + { + if (output.UnknownUserDefines == null) + { + output.UnknownUserDefines = new List(); + } + output.UnknownUserDefines.Add((UserDefinedDescriptor)dvbDescriptor); + continue; + } + TsDescriptor userDefinedDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, output.PrivateDataSpecifier.Value, "PMT"); + if (userDefinedDescriptor == null) + { + if (output.UnknownUserDefines == null) + { + output.UnknownUserDefines = new List(); + } + output.UnknownUserDefines.Add((UserDefinedDescriptor)dvbDescriptor); + continue; + } + throw new NotImplementedException(nameof(UserDefinedDescriptor)); + case nameof(PrivateDataSpecifierDescriptor): + output.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier; + break; + case nameof(SubtitlingDescriptor): + output.Subtitlings = ((SubtitlingDescriptor)dvbDescriptor).Subtitlings; + break; + case nameof(RegistrationDescriptor): + RegistrationDescriptor rd = ((RegistrationDescriptor)dvbDescriptor); + output.FormatIdentifier = rd.FormatIdentifier; + output.AdditionalIdentificationInfo = rd.AdditionalIdentificationInfo; + break; + case nameof(AssociationTagDescriptor): + AssociationTagDescriptor atd = (AssociationTagDescriptor)dvbDescriptor; + output.AssociationTagSelector = atd.Selector; + output.AssociationTagTransactionId = atd.TransactionId; + output.AssociationTagUse = atd.Use; + output.AssociationTagPrivateData = atd.PrivateData; + output.AssociationTagTimeOut = atd.TimeOut; + break; + case nameof(VbiDataDescriptor): + output.VbiData = ((VbiDataDescriptor)dvbDescriptor).VbiData; + break; + case nameof(AacDescriptor): + AacDescriptor aacDescriptor = ((AacDescriptor)dvbDescriptor); + output.AacType = aacDescriptor.AacType; + output.AacAdditionalInfo = aacDescriptor.AdditionalInfo; + output.AacProfileAndLevel = aacDescriptor.ProfileAndLevel; + output.SaocDeFlag = aacDescriptor.SaocDeFlag; + break; + case nameof(AncillaryDataDescriptor): + AncillaryDataDescriptor acd = (AncillaryDataDescriptor)dvbDescriptor; + output.AncillaryDataDescriptor = ((AncillaryDataDescriptor)dvbDescriptor); + break; + case nameof(EnhancedAc3Descriptor): + EnhancedAc3Descriptor eac3d = ((EnhancedAc3Descriptor)dvbDescriptor); + output.MainId = eac3d.MainId; + output.BSID = eac3d.BSID; + if (eac3d.ComponentType != null) + output.ComponentType = (Ac3Descriptor.ComponentTypeEnum)eac3d.ComponentType; + output.Asvc = eac3d.ASVC; + output.MixInfoExists = eac3d.MixInfoExists; + output.Substream1 = eac3d.Substream1; + output.Substream2 = eac3d.Substream2; + output.Substream3 = eac3d.Substream3; + break; + case nameof(HevcVideoDescriptor): + HevcVideoDescriptor hvd = (HevcVideoDescriptor)dvbDescriptor; + output.Copied44bits = hvd.Copied44bits; + output.LevelIdc = hvd.LevelIdc; + output.FrameOnlyConstraintFlag = hvd.FrameOnlyConstraintFlag; + output.HdrWcgIdc = hvd.HdrWcgIdc; + output.Hevc24hrPicturePresentFlag = hvd.Hevc24hrPicturePresentFlag; + output.HevcStillPresentFlag = hvd.HevcStillPresentFlag; + output.InterlacedSourceFlag = hvd.InterlacedSourceFlag; + output.NonPackedConstraintFlag = hvd.NonPackedConstraintFlag; + output.ProfileCompatibilityIndication = hvd.ProfileCompatibilityIndication; + output.ProfileIdc = hvd.ProfileIdc; + output.ProfileSpace = hvd.ProfileSpace; + output.ProgressiveSourceFlag = hvd.ProgressiveSourceFlag; + output.SubPicHrdParamsNotPresentFlag = hvd.SubPicHrdParamsNotPresentFlag; + output.TemporalIdMax = hvd.TemporalIdMax; + output.TierFlag = hvd.TierFlag; + output.TemporalIdMin = hvd.TemporalIdMin; + break; + case nameof(Ac4Descriptor): + Ac4Descriptor ac4d = (Ac4Descriptor)dvbDescriptor; + output.Ac4ChannelMode = ac4d.Ac4ChannelMode; + output.Ac4DialogEnhancement = ac4d.Ac4DialogEnhancementEnabled; + output.Ac4Dsi = ac4d.Ac4Dsi; + break; + case nameof(AudioPreselectionDescriptor): + output.AudioPreselection = ((AudioPreselectionDescriptor)dvbDescriptor).Preselections; + break; + case nameof(StdDescriptor): + output.LeakValidFlag = ((StdDescriptor)dvbDescriptor).LeakValidFlag; + break; + case nameof(VbiTeletextDescriptor): + VbiTeletextDescriptor vbiTeletext = ((VbiTeletextDescriptor)dvbDescriptor); + if (vbiTeletext.Teletexts == null) + break; + output.VbiTeletexts = vbiTeletext.Teletexts; + break; + case nameof(FmcDescriptor): + FmcDescriptor fd = ((FmcDescriptor)dvbDescriptor); + output.FlexMuxChannels = fd.FlexMuxChannels; + break; + case nameof(MvcExtensionDescriptor): + MvcExtensionDescriptor med = (MvcExtensionDescriptor)dvbDescriptor; + throw new NotImplementedException(nameof(MvcExtensionDescriptor)); + case nameof(J2KVideoDescriptor): + J2KVideoDescriptor j2kvd = (J2KVideoDescriptor)dvbDescriptor; + throw new NotImplementedException(nameof(J2KVideoDescriptor)); + case nameof(Mpeg4TextDescriptor): + Mpeg4TextDescriptor mtd = (Mpeg4TextDescriptor)dvbDescriptor; + if (mtd.FormatSpecificTextConfig == null) + break; + throw new NotImplementedException(nameof(Mpeg4TextDescriptor)); + case nameof(SmoothingBufferDescriptor): + SmoothingBufferDescriptor sbd = (SmoothingBufferDescriptor)dvbDescriptor; + output.SbLeakRate = sbd.SbLeakRate; + output.SbSize = sbd.SbSize; + break; + case nameof(PrivateDataIndicatorDescriptor): + PrivateDataIndicatorDescriptor pdid = (PrivateDataIndicatorDescriptor)dvbDescriptor; + output.PrivateDataIndicator = pdid.PrivateDataIndicator; + break; + case nameof(HierarchyDescriptor): + HierarchyDescriptor hd = (HierarchyDescriptor)dvbDescriptor; + if (!hd.Valid) + break; + throw new NotImplementedException(nameof(HierarchyDescriptor)); + case nameof(HevcTimingAndHrdDescriptor): + HevcTimingAndHrdDescriptor hahd = (HevcTimingAndHrdDescriptor)dvbDescriptor; + output._90khzFlag = hahd._90khzFlag; + output.PictureAndTimingInfoPresentFlag = hahd.PictureAndTimingInfoPresentFlag; + output.HdrManagementValidFlag = hahd.HrdManagementValidFlag; + output.K = hahd.K; + output.N = hahd.N; + output.NumUnitsInTick = hahd.NumUnitsInTick; + output.TargetScheduleIdx = hahd.TargetScheduleIdx; + output.TargetScheduleIdxNotPresentFlag = hahd.TargetScheduleIdxNotPresentFlag; + break; + case nameof(AdaptionFieldDataDescriptor): + output.AdaptionFieldDataIdentifier = ((AdaptionFieldDataDescriptor)dvbDescriptor).AdaptionFieldDataIdentifier; + break; + case nameof(AvcTimingAndHrdDescriptor): + AvcTimingAndHrdDescriptor atahd = (AvcTimingAndHrdDescriptor)dvbDescriptor; + output._90khzFlag = atahd._90khzFlag; + output.PictureAndTimingInfoPresentFlag = atahd.PictureAndTimingInfoPresentFlag; + output.NumUnitsInTick = atahd.NumUnitsInTick; + output.K = atahd.K; + output.N = atahd.N; + output.HdrManagementValidFlag = atahd.HrdManagementValidFlag; + output.FixedFrameRateFlag = atahd.FixedFrameRateFlag; + output.PictureToDisplayConversionFlag = atahd.PictureToDisplayConversionFlag; + output.TemporalPocFlag = atahd.TemporalPocFlag; + break; + case nameof(Mpeg4AudioDescriptor): + output.Mpeg4AudioProfileAndLevel = ((Mpeg4AudioDescriptor)dvbDescriptor).Mpeg4AudioProfileAndLevel; + break; + case nameof(Mpeg2AacAudioDescriptor): + Mpeg2AacAudioDescriptor mpeg2aacad = ((Mpeg2AacAudioDescriptor)dvbDescriptor); + output.AacChannelConfiguration = mpeg2aacad.AacChannelConfiguration; + output.AacAdditionalInformation = mpeg2aacad.AacAdditionalInformation; + output.AacProfile = mpeg2aacad.AacProfile; + break; + case nameof(ScramblingDescriptor): + ScramblingDescriptor sd = ((ScramblingDescriptor)dvbDescriptor); + output.ScramblingMode = sd.ScramblingMode; + break; + case nameof(RelatedContentDescriptor): + output.RelatedContentDescriptorPresent = true; + break; + case nameof(_0x11_T2MIDescriptor): + _0x11_T2MIDescriptor t2mid = (_0x11_T2MIDescriptor)dvbDescriptor; + output.NumT2MiStreams = t2mid.NumT2MiStreams; + output.T2MiStreamId = t2mid.T2MiStreamId; + output.PcrIscrCommonClockFlag = t2mid.PcrIscrCommonClockFlag; + break; + case nameof(ExternalEsIdDescriptor): + if (!dvbDescriptor.Valid) + break; + throw new NotImplementedException(nameof(ExternalEsIdDescriptor)); + case nameof(_0x2E_Mpeg4AudioExtensionDescriptor): + _0x2E_Mpeg4AudioExtensionDescriptor mp4audioExtension = (_0x2E_Mpeg4AudioExtensionDescriptor)dvbDescriptor; + output.AudioSpecifConfig = mp4audioExtension.AudioSpecificConfig; + output.AudioProfileLevelIndication = mp4audioExtension.AudioProfileLevelIndication; + break; + case nameof(MetadataDescriptor): + MetadataDescriptor metadataDescriptor = (MetadataDescriptor)dvbDescriptor; + output.MetadataApplicationFormat = metadataDescriptor.MetadataApplicationFormat; + output.MetadataApplicationFormatIdentifier = metadataDescriptor.MetadataApplicationFormatIdentifier; + output.MetadataFormat = metadataDescriptor.MetadataFormat; + output.MetadataFormatIdentifier = metadataDescriptor.MetadataFormatIdentifier; + output.MetadataServiceId = metadataDescriptor.MetadataServiceId; + output.DecoderConfigFlag = metadataDescriptor.DecoderConfigFlag; + output.ServiceIdentificationRecord = metadataDescriptor.ServiceIdentificationRecord; + output.DecoderConfig = metadataDescriptor.DecoderConfig; + output.DecoderConfigIdentificationRecord = metadataDescriptor.DecoderConfigIdentificationRecord; + output.DecoderConfigMetadataServiceId = metadataDescriptor.DecoderConfigMetadataServiceId; + output.Reserved = metadataDescriptor.Reserved; + output.PrivateData = metadataDescriptor.PrivateData; + break; + default: + throw new NotImplementedException(dvbDescriptor.GetType().Name); + } + } + } + + private void ParseOuterDescriptors(ProgramMapping output, byte[] inputBuffer) + { + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(inputBuffer, "PMT"); + foreach (TsDescriptor tsDescriptor in descriptors) + { + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(MaximumBitrateDescriptor): + output.MaximumBitrate = ((MaximumBitrateDescriptor)tsDescriptor).MaximumBitrate; + break; + case nameof(MultiplexBufferUtilizationDescriptor): + MultiplexBufferUtilizationDescriptor mbud = (MultiplexBufferUtilizationDescriptor)tsDescriptor; + output.MultiplexBufferUtilizationBoundValidFlag = mbud.BoundValidFlag; + output.MultiplexLtwOffsetLowerBound = mbud.LtwOffsetLowerBound; + output.MultiplexLtwOffsetUpperBound = mbud.LtwOffsetUpperBound; + break; + case nameof(SystemClockDescriptor): + SystemClockDescriptor scd = (SystemClockDescriptor)tsDescriptor; + output.ClockAccuracyExponent = scd.ClockAccuracyExponent; + output.ClockAccuracyInteger = scd.ClockAccuracyInteger; + output.ExternalClockReferenceIndicator = scd.ExternalClockReferenceIndicator; + break; + case nameof(SmoothingBufferDescriptor): + SmoothingBufferDescriptor sbd = (SmoothingBufferDescriptor)tsDescriptor; + output.SbLeakRate = sbd.SbLeakRate; + output.SbSize = sbd.SbSize; + break; + case nameof(CaDescriptor): + CaDescriptor caDescriptor = (CaDescriptor)tsDescriptor; + output.CaPid = caDescriptor.CaPid; + output.CaSystemId = caDescriptor.CaSystemId; + output.CaPrivateData = caDescriptor.PrivateData; + break; + case nameof(PrivateDataSpecifierDescriptor): + output.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)tsDescriptor).PrivateDataSpecifier; + break; + case nameof(UserDefinedDescriptor): + uint registry; + if (output.PrivateDataSpecifier.HasValue) + registry = output.PrivateDataSpecifier.Value; + else if (output.RegistrationFormatIdentifier.HasValue) + registry = output.RegistrationFormatIdentifier.Value; + else + break; + + TsDescriptor userDefinedDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)tsDescriptor, registry, "PMT"); + if (userDefinedDescriptor == null) + continue; + name = userDefinedDescriptor.GetType().Name; + switch(name) + { + case nameof(CopyControlDescriptor): + CopyControlDescriptor ccd = (CopyControlDescriptor)userDefinedDescriptor; + output.CaSystemId = ccd.CaSystemId; + if (ccd.CaSystemId == 0x0fff) + output.CopyControlPrivateData = ((CopyControlDescriptor.AacsPrivateData)ccd.PrivateData).OriginalData; + else + output.CopyControlPrivateData = (byte[])ccd.PrivateData; + break; + default: + throw new NotImplementedException(name); + } + break; + case nameof(RegistrationDescriptor): + RegistrationDescriptor registrationDescriptor = ((RegistrationDescriptor)tsDescriptor); + output.RegistrationFormatIdentifier = registrationDescriptor.FormatIdentifier; + output.RegistrationAdditionalIdentificationInfo = registrationDescriptor.AdditionalIdentificationInfo; + break; + case nameof(ServiceMoveDescriptor): + ServiceMoveDescriptor smd = (ServiceMoveDescriptor)tsDescriptor; + output.NewOriginalNetworkId = smd.NewOriginalNetworkId; + output.NewServiceId = smd.NewServiceId; + output.NewTransportStreamId = smd.NewTransportStreamId; + break; + case nameof(StreamIdentifierDescriptor): + StreamIdentifierDescriptor sid = (StreamIdentifierDescriptor)tsDescriptor; + output.ComponentTag = sid.ComponentTag; + break; + case nameof(MetadataStdDescriptor): + MetadataStdDescriptor mstdd = (MetadataStdDescriptor)tsDescriptor; + output.MetadataInputLeakRate = mstdd.MetadataInputLeakRate; + output.MetadataBufferSize = mstdd.MetadataBufferSize; + output.MetadataOutputLeakRate = mstdd.MetadataOutputLeakRate; + break; + case nameof(ScramblingDescriptor): + output.ScramblingMode = ((ScramblingDescriptor)tsDescriptor).ScramblingMode; + break; + case nameof(DeferredAssociationTagsDescriptor): + DeferredAssociationTagsDescriptor datd = (DeferredAssociationTagsDescriptor)tsDescriptor; + if (datd.AssociationTag == null) + break; + throw new NotImplementedException(nameof(DeferredAssociationTagsDescriptor)); + case nameof(SLDescriptor): + output.EsId = ((SLDescriptor)tsDescriptor).EsId; + break; + case nameof(TeletextDescriptor): + //This should be in the inner loops. + break; + case nameof(Mpeg4TextDescriptor): + Mpeg4TextDescriptor mp4TextDescriptor = (Mpeg4TextDescriptor)tsDescriptor; + if (mp4TextDescriptor.FormatSpecificTextConfig == null) + break; + throw new NotImplementedException(nameof(Mpeg4TextDescriptor)); + case nameof(PrivateDataIndicatorDescriptor): + PrivateDataIndicatorDescriptor pdid = (PrivateDataIndicatorDescriptor)tsDescriptor; + output.PrivateDataIndicator = pdid.PrivateDataIndicator; + break; + case nameof(VideoStreamDescriptor): + VideoStreamDescriptor vsd = (VideoStreamDescriptor)tsDescriptor; + output.FrameRate = vsd.FrameRate; + output.ChromaFormat = vsd.ChromaFormat; + output.ConstrainedParameterFlag = vsd.ConstrainedParameterFlag; + output.FrameRateExtensionFlag = vsd.FrameRateExtensionFlag; + output.Mpeg1OnlyFlag = vsd.Mpeg1OnlyFlag; + output.MultipleFramerateFlag = vsd.MultipleFramerateFlag; + output.ProfileAndLevelIndication = vsd.ProfileAndLevelIndication; + output.StillPictureFlag = vsd.StillPictureFlag; + break; + case nameof(TargetBackgroundGridDescriptor): + TargetBackgroundGridDescriptor tbgd = (TargetBackgroundGridDescriptor)tsDescriptor; + throw new NotImplementedException(nameof(TargetBackgroundGridDescriptor)); + case nameof(MosaicDescriptor): + MosaicDescriptor md = (MosaicDescriptor)tsDescriptor; + throw new NotImplementedException(nameof(MosaicDescriptor)); + case nameof(Iso639LanguageDescriptor): + Iso639LanguageDescriptor i639ld = (Iso639LanguageDescriptor)tsDescriptor; + output.AudioType = i639ld.AudioType; + output.Iso639LanguageCode = i639ld.Iso639LanguageCode; + break; + case nameof(IodDescriptor): + IodDescriptor iodD = (IodDescriptor)tsDescriptor; + output.InitialObjectDescriptor = iodD.InitialObjectDescriptor; + output.ScopeOfIodLabel = iodD.ScopeOfIodLabel; + output.IodLabel = iodD.IodLabel; + break; + case nameof(MetadataPointerDescriptor): + MetadataPointerDescriptor mpd = (MetadataPointerDescriptor)tsDescriptor; + output.MetadataProgramNumber = mpd.ProgramNumber; + output.MetadataApplicationFormat = mpd.MetadataApplicationFormat; + output.MetadataApplicationFormatIdentifier = mpd.MetadataApplicationFormatIdentifier; + output.MetadataFormat = mpd.MetadataFormat; + output.MetadataFormatIdentifier = mpd.MetadataFormatIdentifier; + output.MetadataLocatorRecord = mpd.MetadataLocatorRecord; + output.MetadataLocatorRecordFlag = mpd.MetadataLocatorRecordFlag; + output.MetadataServiceId = mpd.MetadataServiceId; + output.MpegCarriageFlag = mpd.MpegCarriageFlag; + output.MetadataPrivateData = mpd.PrivateData; + output.TransportStreamId = mpd.TransportStreamId; + output.TransportStreamLocation = mpd.TransportStreamLocation; + break; + case nameof(AudioStreamDescriptor): + AudioStreamDescriptor asd = (AudioStreamDescriptor)tsDescriptor; + output.FreeFormat = asd.FreeFormat; + output.AudioStreamId = asd.ID; + output.Layer = asd.Layer; + output.VariableRateAudio = asd.VariableRateAudio; + break; + case nameof(DataStreamAlignmentDescriptor): + DataStreamAlignmentDescriptor dsad = (DataStreamAlignmentDescriptor)tsDescriptor; + output.AlignmentType = dsad.AlignmentType; + break; + case nameof(HierarchyDescriptor): + HierarchyDescriptor hd = (HierarchyDescriptor)tsDescriptor; + output.HierarchyType = hd.HierarchyType; + output.HierarchyLayerType = hd.HierarchyLayerType; + output.TrefPresentFlag = hd.TrefPresentFlag; + output.HierarchyChannel = hd.HierarchyChannel; + output.NoViewScalabilityFlag = hd.NoViewScalabilityFlag; + output.HierarchyEmbeddedLayerIndex = hd.HierarchyEmbeddedLayerIndex; + output.NoQualityScalabilityFlag = hd.NoQualityScalabilityFlag; + output.NoSpatialScalabilityFlag = hd.NoSpatialScalabilityFlag; + output.NoTemporalScalabilityFlag = hd.NoTemporalScalabilityFlag; + break; + case nameof(ApplicationSignallingDescriptor): + ApplicationSignallingDescriptor asd2 = (ApplicationSignallingDescriptor)tsDescriptor; + if (!asd2.Valid) + break; + throw new NotImplementedException(nameof(ApplicationSignallingDescriptor)); + default: + throw new NotImplementedException(name); + } + + } + + } + } +} diff --git a/skyscraper8/Mpeg2/Psi/TsdtEventHandler.cs b/skyscraper8/Mpeg2/Psi/TsdtEventHandler.cs new file mode 100644 index 0000000..0751e56 --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/TsdtEventHandler.cs @@ -0,0 +1,8 @@ +namespace skyscraper5.Mpeg2.Psi +{ + interface ITsdtEventHandler + { + void NotifyOfCompliance(string compliance); + void NotifiyStationIdentification(string stationIdentification); + } +} diff --git a/skyscraper8/Mpeg2/Psi/TsdtParser.cs b/skyscraper8/Mpeg2/Psi/TsdtParser.cs new file mode 100644 index 0000000..370776f --- /dev/null +++ b/skyscraper8/Mpeg2/Psi/TsdtParser.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2.Psi +{ + class TsdtParser : IPsiProcessor + { + public TsdtParser(ITsdtEventHandler tsdtEventHandler) + { + this.EventHandler = tsdtEventHandler; + } + + public ITsdtEventHandler EventHandler { get; private set; } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + byte tableId = ms.ReadUInt8(); + if (tableId != 0x03) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + int sectionLength = (readUInt16Be & 0x0fff); + if (sectionLength > 1021) + return; + ms.ReadUInt16BE(); //reserved + + if (ms.GetAvailableBytes() == 0) + return; + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + if (ms.GetAvailableBytes() == 0) + return; + byte sectionNumber = ms.ReadUInt8(); + if (ms.GetAvailableBytes() == 0) + return; + byte lastSectionNumber = ms.ReadUInt8(); + if (sectionNumber > lastSectionNumber) + return; + + if (ms.GetAvailableBytes() < 4) + return; + byte[] descriptorBytes = ms.ReadBytes(ms.GetAvailableBytes() - 4); + ParseDescriptors(descriptorBytes); + + uint crc32 = ms.ReadUInt32BE(); + } + + private void ParseDescriptors(byte[] buffer) + { + RegistrationDescriptor registrationDescriptor = null; + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(buffer, "TSDT"); + foreach (TsDescriptor tsDescriptor in descriptors) + { + string name = tsDescriptor.GetType().Name; + switch (name) + { + case nameof(TransportStreamDescriptor): + EventHandler.NotifyOfCompliance(((TransportStreamDescriptor)tsDescriptor).Compliance); + break; + case nameof(DsngDescriptor): + EventHandler.NotifiyStationIdentification(((DsngDescriptor)tsDescriptor).StationIdentification); + break; + case nameof(UserDefinedDescriptor): + if (registrationDescriptor == null) + break; + TsDescriptor userDefinedDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor( + (UserDefinedDescriptor)tsDescriptor, registrationDescriptor.FormatIdentifier, "TSDT"); + if (userDefinedDescriptor == null) + break; + throw new NotImplementedException(userDefinedDescriptor.GetType().Name); + case nameof(RegistrationDescriptor): + RegistrationDescriptor rd = (RegistrationDescriptor)tsDescriptor; + registrationDescriptor = rd; + break; + case nameof(StuffingDescriptor): + //"The IRDs may discard the stuffing bytes." + break; + default: + throw new NotImplementedException(name); + } + } + } + } +} diff --git a/skyscraper8/Mpeg2/PsiDecoder.cs b/skyscraper8/Mpeg2/PsiDecoder.cs new file mode 100644 index 0000000..afa6775 --- /dev/null +++ b/skyscraper8/Mpeg2/PsiDecoder.cs @@ -0,0 +1,148 @@ +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + public class PsiDecoder : ITsPacketProcessor, IPayloadUnitDecoder + { + public IPsiProcessor PsiProcessor { get; } + + public PsiDecoder(int pid, IPsiProcessor psiProcessor) + { + PsiProcessor = psiProcessor; + this.pid = pid; + } + + private int pid; + + public void PushPacket(TsPacket packet) + { + if (packet.Payload == null) + return; + if (packet.PID != pid) + return; + + + bool psiJustStarted = false; + if (psiSection == null) + { + if (packet.PayloadUnitStart) + { + psiSection = new PsiSection(); + psiSection.Need = 3; + psiSection.HeaderComplete = false; + psiJustStarted = true; + } + else + { + return; + } + } + + MemoryStream payload = new MemoryStream(packet.Payload); + long available = payload.Length - payload.Position; + if (psiJustStarted) + { + long startOffset = packet.PayloadStartOffset; + if (startOffset > 0) + { + startOffset = payload.Position + startOffset; + if (startOffset > payload.Length) + { + OnPsiSectionTooLong?.Invoke(packet, psiJustStarted, psiSection); + PacketLoss(); + return; + } + payload.Position += startOffset; + available -= startOffset; + } + } + while (available > 0) + { + if (available > psiSection.Need) + { + //Noch genug im Buffer, um need auf 0 zu bekommen. + byte[] temp1 = new byte[psiSection.Need]; + if (payload.Read(temp1, 0, psiSection.Need) != psiSection.Need) + throw new EndOfStreamException("failed to fill psi buffer"); + available -= psiSection.Need; + psiSection.Append(temp1); + + if (!psiSection.HeaderComplete) + { + psiSection.HeaderComplete = true; + ushort sectionLength = psiSection.SectionLength; + psiSection.Need = sectionLength; + } + else + { + if (psiSection.Valid) + { + lock (this) + { + PsiProcessor.GatherPsi(psiSection, pid); + } + OnValidSection?.Invoke(psiSection, pid, available); + } + else + { + if (!DvbCrc32.ValidatePsi(psiSection)) + { + OnCrcError?.Invoke(psiSection, pid,available); + } + } + + psiSection = null; + + bool newSection = false; + if (available > 0) + { + byte payloadPos = payload.ReadUInt8(); + payload.Position--; + if (payloadPos != 0xff) + newSection = true; + } + + if (newSection) + { + psiSection = new PsiSection(); + psiSection.Need = 3; + psiSection.HeaderComplete = false; + } + else + { + available = 0; + } + } + } + else + { + //Nicht mehr genug da + byte[] filler = payload.ReadBytes(available); + psiSection.Append(filler); + psiSection.Need -= (int)available; + available = 0; + } + } + } + + private PsiSection psiSection; + public void PacketLoss() + { + psiSection = null; + } + + public bool HeaderComplete => psiSection.HeaderComplete; + public int NeededBytes => psiSection.Need; + + public delegate void PsiSectionTooLong(TsPacket packet, bool justStarted, PsiSection section); + + public event PsiSectionTooLong OnPsiSectionTooLong; + + public delegate void OnSection(PsiSection section, int pid, long available); + public event OnSection OnValidSection; + public event OnSection OnCrcError; + } + +} diff --git a/skyscraper8/Mpeg2/PsiSection.cs b/skyscraper8/Mpeg2/PsiSection.cs new file mode 100644 index 0000000..3ce58c4 --- /dev/null +++ b/skyscraper8/Mpeg2/PsiSection.cs @@ -0,0 +1,104 @@ +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + public class PsiSection + { + public int Need { get; internal set; } + public bool HeaderComplete { get; internal set; } + + public ushort SectionLength + { + get + { + if (!HeaderComplete) + throw new DvbException("PSI Section not completed"); + + byte[] databuffer = data.GetBuffer(); + MemoryStream subms = new MemoryStream(databuffer, 1, 2, false); + ushort result = subms.ReadUInt16BE(); + result &= 0x0fff; + return result; + } + } + + public bool Valid => Validate(); + + public bool Validate() + { + if (!HeaderComplete) + return false; + + bool validCrc32 = false; + + if (HasCrc32) + validCrc32 = DvbCrc32.ValidatePsi(this); + + return !HasCrc32 || validCrc32; + } + + public bool HasCrc32 + { + get + { + if (TableId == 0x70 || TableId == 0x71 || TableId == 0x72 || TableId == 0x7E) + return false; + + return (SyntaxIndicator != 0) || TableId == 0x73; + } + } + + public int SyntaxIndicator + { + get + { + if (!HeaderComplete) + throw new DvbException("incomplete psi"); + + int syntaxIndicator = data.GetBuffer()[1] & 0x80; + return syntaxIndicator; + } + } + + public int TableId + { + get + { + if (!HeaderComplete) + throw new DvbException("header incomplete"); + + int tableId = ((int)data.GetBuffer()[0]) & 0xff; + return tableId; + } + } + + private MemoryStream data; + private int receivedBytes; + public void Append(byte[] temp1) + { + Append(temp1, 0, temp1.Length); + } + + public void Append(byte[] temp1, int offset, int count) + { + if (data == null) + data = new MemoryStream(); + data.Write(temp1, offset, count); + receivedBytes += temp1.Length; + } + + public byte[] GetData() + { + return data.GetBuffer(); + } + + public byte[] GetDataCopy() + { + return data.ToArray(); + } + + public int BytesContained => receivedBytes; + } +} diff --git a/skyscraper8/Mpeg2/TsAdaptionField.cs b/skyscraper8/Mpeg2/TsAdaptionField.cs new file mode 100644 index 0000000..ab07cf4 --- /dev/null +++ b/skyscraper8/Mpeg2/TsAdaptionField.cs @@ -0,0 +1,182 @@ +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + public class DvbAdaptionField : Validatable + { + public DvbAdaptionField(Stream binaryReader, bool TEI) + { + byte adaptionFieldLength = binaryReader.ReadUInt8(); + if (adaptionFieldLength > 183) + { + Valid = false; + return; + } + byte[] adaptionField = binaryReader.ReadBytes(adaptionFieldLength); + binaryReader = new MemoryStream(adaptionField); + this.Length = adaptionFieldLength; + + if (adaptionFieldLength == 0) + { + Valid = true; + return; //Seems valid according to ISO 13818-1 + } + + byte bitmask = binaryReader.ReadUInt8(); + Discontinuity = ((bitmask & 0x80) >> 7) != 0; + RandomAccess = ((bitmask & 0x40) >> 6) != 0; + PriorityIndicator = ((bitmask & 0x20) >> 5) != 0; + PcrPresent = ((bitmask & 0x10) >> 4) != 0; + OpcrPresent = ((bitmask & 0x08) >> 3) != 0; + SplicingPointPresent = ((bitmask & 0x04) >> 2) != 0; + TransportPrivateDataPresent = ((bitmask & 0x02) >> 1) != 0; + AdaptionFieldExtensionPresent = ((bitmask & 0x01)) != 0; + + if (PcrPresent) + { + if (binaryReader.GetAvailableBytes() < 6) + { + Valid = false; + return; + } + uint pcrA = binaryReader.ReadUInt32BE(); + ushort pcrB = binaryReader.ReadUInt16BE(); + ulong pcr_base = ((ulong)pcrA << 1) | ((ulong)pcrB >> 15); + ulong pcr_ext = (ulong)pcrB & 0x01ff; + PCR = pcr_base * 300 + pcr_ext; + } + if (OpcrPresent) + { + if (binaryReader.GetAvailableBytes() < 6) + { + Valid = false; + return; + } + uint opcrA = binaryReader.ReadUInt32BE(); + ushort opcrB = binaryReader.ReadUInt16BE(); + ulong pcr_base = ((ulong)opcrA << 1) | ((ulong)opcrB >> 15); + ulong pcr_ext = (ulong)opcrB & 0x01ff; + OPCR = pcr_base * 300 + pcr_ext; + } + if (SplicingPointPresent) + { + if (binaryReader.GetAvailableBytes() == 0) + { + Valid = false; + return; + } + SplicingPoint = binaryReader.ReadUInt8(); + } + + if (TransportPrivateDataPresent) + { + if (binaryReader.GetAvailableBytes() == 0) + { + Valid = false; + return; + } + byte TransportPrivateDataLength = binaryReader.ReadUInt8(); + long maxLen = binaryReader.Length - binaryReader.Position; + if (TransportPrivateDataLength <= maxLen) + { + TransportPrivateData = binaryReader.ReadBytes(TransportPrivateDataLength); + } + else + { + Valid = false; + return; + } + } + + if (AdaptionFieldExtensionPresent) + { + AdaptionFieldExtension = new TsAdaptionFieldExtension(); + + if (binaryReader.GetAvailableBytes() == 0) + { + Valid = false; + return; + } + + byte extensionFlags1 = binaryReader.ReadUInt8(); + AdaptionFieldExtension.LtwFlag = (extensionFlags1 & 0x80) != 0; + AdaptionFieldExtension.PiecewiseRateFlag = (extensionFlags1 & 0x40) != 0; + AdaptionFieldExtension.SeamlessSpliceFlag = (extensionFlags1 & 0x20) != 0; + + if (AdaptionFieldExtension.LtwFlag) + { + if (binaryReader.GetAvailableBytes() < 2) + { + Valid = false; + return; + } + + ushort ltwRaw = binaryReader.ReadUInt16BE(); + AdaptionFieldExtension.LtwValidFlag = (ltwRaw & 0x8000) != 0; + AdaptionFieldExtension.LtwOffset = (ltwRaw & 0x7fff); + } + + if (AdaptionFieldExtension.PiecewiseRateFlag) + { + if (binaryReader.GetAvailableBytes() < 3) + { + Valid = false; + return; + } + + int piecewiseRaw = binaryReader.ReadByte() & 0xff; + piecewiseRaw += (binaryReader.ReadUInt16BE() & 0xffff) >> 8; + piecewiseRaw = piecewiseRaw & 0x3fffff; + AdaptionFieldExtension.PiecewiseRate = piecewiseRaw; + } + + if (AdaptionFieldExtension.SeamlessSpliceFlag) + { + if (binaryReader.Length - binaryReader.Position < 5) + { + Valid = false; + return; + } + + byte spliceRawA = binaryReader.ReadUInt8(); + AdaptionFieldExtension.SpliceType = spliceRawA & 0xf0; + AdaptionFieldExtension.DtsNextAccessUnit = (long)(spliceRawA & 0x0f); + AdaptionFieldExtension.DtsNextAccessUnit += (((long)binaryReader.ReadUInt32BE() & 0xfffefffeL) >> 4); + } + } + + Valid = true; + } + + public bool PriorityIndicator { get; private set; } + + public bool RandomAccess { get; private set; } + + public bool Discontinuity { get; private set; } + + public TsAdaptionFieldExtension AdaptionFieldExtension { get; private set; } + + public bool AdaptionFieldExtensionPresent { get; private set; } + + public byte[] TransportPrivateData { get; private set; } + + public bool TransportPrivateDataPresent { get; private set; } + + public byte SplicingPoint { get; private set; } + + public bool SplicingPointPresent { get; private set; } + + public ulong OPCR { get; set; } + + public bool OpcrPresent { get; private set; } + + public ulong PCR { get; private set; } + + public bool PcrPresent { get; private set; } + + public byte Length { get; private set; } + } +} diff --git a/skyscraper8/Mpeg2/TsAdaptionFieldExtension.cs b/skyscraper8/Mpeg2/TsAdaptionFieldExtension.cs new file mode 100644 index 0000000..f4d56f7 --- /dev/null +++ b/skyscraper8/Mpeg2/TsAdaptionFieldExtension.cs @@ -0,0 +1,14 @@ +namespace skyscraper5.Mpeg2 +{ + public class TsAdaptionFieldExtension + { + public bool LtwFlag { get; internal set; } + public bool LtwValidFlag { get; internal set; } + public int LtwOffset { get; internal set; } + public bool PiecewiseRateFlag { get; internal set; } + public int PiecewiseRate { get; internal set; } + public bool SeamlessSpliceFlag { get; internal set; } + public int SpliceType { get; internal set; } + public long DtsNextAccessUnit { get; internal set; } + } +} diff --git a/skyscraper8/Mpeg2/TsContext.cs b/skyscraper8/Mpeg2/TsContext.cs new file mode 100644 index 0000000..7bbaa52 --- /dev/null +++ b/skyscraper8/Mpeg2/TsContext.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Runtime.CompilerServices; +using skyscraper5.Skyscraper; +using skyscraper5.src.Mpeg2.PacketFilter; + +namespace skyscraper5.Mpeg2 +{ + public class TsContext : ITsPacketProcessor + { + private ITsPacketProcessor[] processors; + private uint[] continuities; + private ulong[] pidPackets; + + public long PacketLossEvents { get; private set; } + public bool FirstPacketDone { get; private set; } + public long PacketsRead { get; private set; } + public long TheoreticalOffset { get; private set; } + public List FilterChain { get; set; } + + public void PushPacket(TsPacket packet) + { + if (TcpProxyEnabled && tcpTsProxy != null) + tcpTsProxy.PushPacket(packet); + + InjectIntoPcrMonitor(packet); + + if (!FirstPacketDone) + { + FirstPacketDone = true; + pidPackets = new ulong[0x2000]; + } + else + { + PacketsRead++; + TheoreticalOffset += 188; + } + + pidPackets[packet.PID]++; + + if (FilterChain == null || FilterChain.Count == 0) + throw new InvalidOperationException("The filter chain has not been initialized."); + + for (int i = 0; i < FilterChain.Count; i++) + { + if (FilterChain[i] == null) + continue; + + if (!FilterChain[i].PassPacket(packet)) + return; + } + + if (processors == null) + return; + + if (processors[packet.PID] == null) + return; + + bool continuity = EnsureContinuity(packet); + processors[packet.PID].PushPacket(packet); + } + + private bool EnsureContinuity(TsPacket packet) + { + //Found in ISO 13818-1.pdf, page 38 + if (packet.AdaptionFieldControl == 2 || packet.AdaptionFieldControl == 0) + return true; + + uint pid = packet.PID; + if (continuities[pid] == UInt32.MaxValue) + { + continuities[pid] = packet.Continuity; + } + else + { + uint oldContinuity = continuities[pid]; + uint newContinuity = packet.Continuity; + if (newContinuity == (oldContinuity + 1) || (oldContinuity == 15 && newContinuity == 0)) + { + continuities[pid] = packet.Continuity; + } + else + { + continuities[pid] = UInt32.MaxValue; + IPayloadUnitDecoder payloadUnitDecoder = processors[pid] as IPayloadUnitDecoder; + if (payloadUnitDecoder != null) + payloadUnitDecoder.PacketLoss(); + PacketLossEvents++; + OnPacketLoss?.Invoke(pid, oldContinuity, newContinuity, PacketLossEvents); + return false; + } + } + + return true; + } + + private DateTime packetLossMeasPrev, packetLossMeasCurr; + private ulong packetLossMeas; + + public void PushPacket(byte[] buffer) + { + PushPacket(new TsPacket(buffer)); + } + + public bool RegisterPacketProcessor(int pid, ITsPacketProcessor packageProcessor) + { + if (processors == null) + { + processors = new ITsPacketProcessor[8192]; + continuities = new uint[8192]; + Array.Fill(continuities, UInt32.MaxValue); + } + + bool result = processors[pid] == null; + processors[pid] = packageProcessor; + return result; + } + + public bool IsPidProcessorPresent(int pid) + { + if (processors == null) + return false; + + return processors[pid] != null; + } + + public event TsPacketLossHandler OnPacketLoss; + + public int[] GetOccupiedPids() + { + int numListeners = 0; + for (int i = 0; i < processors.Length; i++) + { + if (processors[i] != null) + numListeners++; + } + + int[] result = new int[numListeners]; + int ptr = 0; + for (int i = 0; i < processors.Length; i++) + { + if (processors[i] != null) + result[ptr++] = i; + } + + return result; + } + + public ulong[] GetPidStatistics() + { + if (pidPackets == null) + return null; + + ulong[] clone = (ulong[])pidPackets.Clone(); + return clone; + } + + public ulong GetNumberOfPacketsOnPid(int pid) + { + return pidPackets[pid]; + } + + #region TCP Proxy + + private bool _tcpProxyEnabled; + private TcpTsProxy tcpTsProxy; + + public bool TcpProxyEnabled + { + get => _tcpProxyEnabled; + set + { + if (value) + { + tcpTsProxy = new TcpTsProxy(); + } + else + { + if (tcpTsProxy != null) + tcpTsProxy.Dispose(); + tcpTsProxy = null; + } + _tcpProxyEnabled = value; + } + } + + public IPEndPoint GetTcpProxyEndPoint() + { + if (!_tcpProxyEnabled) + return null; + if (tcpTsProxy == null) + return null; + return tcpTsProxy.GetEndPoint(); + } + + #endregion + + #region PCR Monitor + public PcrMonitor PcrMonitor { get; private set; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void InjectIntoPcrMonitor(TsPacket tspacket) + { + if (tspacket.Adaption == null) + return; + if (!tspacket.Adaption.Valid) + return; + if (!tspacket.Adaption.PcrPresent) + return; + + if (PcrMonitor != null) + PcrMonitor.Analyze(tspacket); + else + PcrMonitor = new PcrMonitor(tspacket); + } + #endregion + + } + + public delegate void TsPacketLossHandler(uint pid, uint oldContinuity, uint newContinuity, long packetLossEvents); + + +} diff --git a/skyscraper8/Mpeg2/TsContextPacketLossReporter.cs b/skyscraper8/Mpeg2/TsContextPacketLossReporter.cs new file mode 100644 index 0000000..dbffffa --- /dev/null +++ b/skyscraper8/Mpeg2/TsContextPacketLossReporter.cs @@ -0,0 +1,41 @@ +using skyscraper5.Skyscraper.Webserver.Metrics; + +namespace skyscraper5.Mpeg2 +{ + internal class TsContextPacketLossReporter + { + private TsContextPacketLossReporter() + { + MetricsResource.GetInstance().AddCollector(this); + } + + private static TsContextPacketLossReporter _instance; + + [MetricType(MetricType.Counter)] + public ulong Dropped_Scrambled { get; internal set; } + + [MetricType(MetricType.Counter)] + public ulong Discontinuity { get; internal set; } + + [MetricType(MetricType.Counter)] + public ulong Dropped_Tei { get; internal set; } + + [MetricType(MetricType.Counter)] + public ulong Input { get; internal set; } + + [MetricType(MetricType.Counter)] + public ulong OK { get; internal set; } + + public static TsContextPacketLossReporter GetInstance() + { + if (_instance == null) + { + _instance = new TsContextPacketLossReporter(); + } + + return _instance; + } + + + } +} diff --git a/skyscraper8/Mpeg2/TsDescriptor.cs b/skyscraper8/Mpeg2/TsDescriptor.cs new file mode 100644 index 0000000..0f1a6e4 --- /dev/null +++ b/skyscraper8/Mpeg2/TsDescriptor.cs @@ -0,0 +1,39 @@ +using System; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper; + +namespace skyscraper5.Mpeg2 +{ + public abstract class TsDescriptor : Validatable + { + public virtual byte GetDescriptorId() + { + Type type = this.GetType(); + object[] customAttributes = type.GetCustomAttributes(false); + foreach (object customAttribute in customAttributes) + { + string name = customAttribute.GetType().Name; + switch (name) + { + case "NullableContextAttribute": continue; + case "NullableAttribute": continue; + case "SkyscraperPluginAttribute": continue; + case "UserDefinedDescriptorAttribute": + UserDefinedDescriptorAttribute udda = (UserDefinedDescriptorAttribute)customAttribute; + return udda.DescriptorId; + case "TsDescriptorAttribute": + TsDescriptorAttribute tda = (TsDescriptorAttribute)customAttribute; + return tda.DescriptorId; + default: + throw new NotImplementedException(name); + } + } + throw new NotImplementedException(String.Format("{0} does not reveal it's Descriptor ID")); + } + + public virtual byte[] Serialize() + { + throw new NotImplementedException(String.Format("{0} does not implement Serialize()", this.GetType().Name)); + } + } +} diff --git a/skyscraper8/Mpeg2/TsDescriptorAttribute.cs b/skyscraper8/Mpeg2/TsDescriptorAttribute.cs new file mode 100644 index 0000000..6d6bf1c --- /dev/null +++ b/skyscraper8/Mpeg2/TsDescriptorAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace skyscraper5.Mpeg2 +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class TsDescriptorAttribute : Attribute + { + public byte DescriptorId { get; } + public string[] CompatibleTables { get; } + + public TsDescriptorAttribute(byte descriptorId, params string[] compatibleTables) + { + DescriptorId = descriptorId; + CompatibleTables = compatibleTables; + } + + public bool Matches(string table) + { + foreach (string compatibleTable in CompatibleTables) + { + if (compatibleTable == table) + return true; + } + + return false; + } + } +} diff --git a/skyscraper8/Mpeg2/TsDescriptorUnpacker.cs b/skyscraper8/Mpeg2/TsDescriptorUnpacker.cs new file mode 100644 index 0000000..e2dc04d --- /dev/null +++ b/skyscraper8/Mpeg2/TsDescriptorUnpacker.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using skyscraper5.Dvb; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using ExtensionDescriptor = skyscraper5.Dvb.ExtensionDescriptor; + +namespace skyscraper5.Mpeg2 +{ + class TsDescriptorUnpacker + { + private const bool CRASH_ON_UNIMPLEMENTED_DESCRIPTOR = true; + private static TsDescriptorUnpacker _instance; + private IReadOnlyDictionary descriptorMap; + private Dictionary descriptorConverters; + private IReadOnlyDictionary bannedTables; + private TsDescriptorUnpacker() + { + PluginManager pluginManager = PluginManager.GetInstance(); + descriptorConverters = new Dictionary(); + bannedTables = pluginManager.GetDescriptorTableBans(); + descriptorMap = pluginManager.GetDescriptorMappings(); + + if (descriptorMap.Count == 0) + { + throw new AccessViolationException("Wait, this isn't supposed to happen! There are no descriptor types in my assembly!"); + } + + userPrivates = new bool[byte.MaxValue + 1]; + } + + + + private bool[] userPrivates; + + public void SetUserDefined(byte descriptorId, bool userDefined) + { + userPrivates[descriptorId] = userDefined; + } + + public static TsDescriptorUnpacker GetInstance() + { + if (_instance == null) + { + _instance = new TsDescriptorUnpacker(); + } + + return _instance; + } + + + public IEnumerable UnpackDescriptors(byte[] buffer, string callingTable) + { + MemoryStream ms = new MemoryStream(buffer, false); + while (ms.GetAvailableBytes() > 2) + { + byte descriptorTag = ms.ReadUInt8(); + byte descriptorLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < descriptorLength) + break; + + if (bannedTables.ContainsKey(callingTable)) + { + bool[] upsertBannedTable = bannedTables[callingTable]; + if (upsertBannedTable[descriptorTag]) + continue; + } + + byte[] descriptorContent = ms.ReadBytes(descriptorLength); + ConstructorInfo[] descriptorAttributes = descriptorMap[callingTable]; + if (descriptorAttributes[descriptorTag] == null) + { + if (userPrivates[descriptorTag]) + { + yield return new UserDefinedDescriptor(descriptorTag, descriptorContent); + continue; + } + string format = String.Format("Descriptor 0x{0:X2} ({0}) not implemented for table \"{1}\".", descriptorTag,callingTable); + if (CRASH_ON_UNIMPLEMENTED_DESCRIPTOR) + throw new NotImplementedException(format); + else + Console.WriteLine(format); + continue; + } + //if (!descriptorAttributes[descriptorTag].Matches(callingTable)) + // continue; + + object result = descriptorAttributes[descriptorTag].Invoke(new object[] { descriptorContent }); + + Attribute attribute = result.GetType().GetCustomAttributes(typeof(DescriptorConversionNeededAttribute)).FirstOrDefault(); + if (attribute != null) + { + DescriptorConversionNeededAttribute conversionNeededAttribute = (DescriptorConversionNeededAttribute)attribute; + Type converterType = conversionNeededAttribute.ConverterType; + IDescriptorConverter converter = null; + if (descriptorConverters.ContainsKey(converterType)) + { + converter = descriptorConverters[converterType]; + } + else + { + converter = (IDescriptorConverter)Activator.CreateInstance(converterType); + descriptorConverters.Add(converterType, converter); + } + result = converter.Convert((TsDescriptor)result, callingTable); + if (result == null) + continue; + } + + yield return (TsDescriptor)result; + } + yield break; + } + + + } +} diff --git a/skyscraper8/Mpeg2/TsPacket.cs b/skyscraper8/Mpeg2/TsPacket.cs new file mode 100644 index 0000000..4fa85c0 --- /dev/null +++ b/skyscraper8/Mpeg2/TsPacket.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Mpeg2 +{ + public class TsPacket + { + public TsPacket(byte[] buffer) + { + Serial = serialGen++; + + RawPacket = (byte[])buffer.Clone(); + if (buffer.Length != 188) + throw new ArgumentException("buffer.Length != 188"); + + MemoryStream br = new MemoryStream(buffer, false); + uint anInt = br.ReadUInt32BE(); + uint syncBytes = (anInt & 0xff000000) >> 24; + if (syncBytes != 'G') + { + throw new InvalidTsPacketException(); + } + + TEI = (((anInt & 0x800000)) >> 23) != 0; + PayloadUnitStart = (((anInt & 0x400000)) >> 22) != 0; + Priority = ((anInt & 0x200000)) >> 21; + PID = ((anInt & 0x1fff00)) >> 8; + TSC = ((anInt & 0xc0)) >> 6; + AdaptionFieldControl = ((anInt & 0x30)) >> 4; + Continuity = ((anInt & 0xf)); + if (PID == 0x1FFF) + { + return; + } + + if (TEI) + return; + + if ((buffer[3] & 0x20) != 0) + { + //Skip adaption field if present + } + + if ((buffer[1] & 0x40) != 0) + { + //Unit start + } + + int payloadOffset = 4; + if (PayloadUnitStart) + { + PayloadStartOffset = buffer[4] & 0xff; + payloadOffset++; + } + + switch (AdaptionFieldControl) + { + case 1: + br.Position = payloadOffset; + long PayloadLengthB = 188 - payloadOffset; + Payload = br.ReadBytes(PayloadLengthB); + break; + case 2: + Adaption = new DvbAdaptionField(br, TEI); + if (!Adaption.Valid) + TEI = true; + break; + case 3: + Adaption = new DvbAdaptionField(br, TEI); + if (!Adaption.Valid) + { + TEI = true; + break; + } + payloadOffset += Adaption.Length; + br.Position = payloadOffset; + long PayloadLength = (188 - payloadOffset); + Payload = br.ReadBytes((int)PayloadLength); + break; + default: + TEI = true; + break; + } + } + + private static ulong serialGen; + public DvbAdaptionField Adaption { get; private set; } + + public bool TEI { get; private set; } + public bool PayloadUnitStart { get; private set; } + public uint Priority { get; private set; } + public uint PID { get; private set; } + + /// + /// 0 = not scrambled + /// 1 = reserved + /// 2 = scrambled, even key + /// 3 = scrambled, odd key + /// + public uint TSC { get; private set; } + public uint AdaptionFieldControl { get; private set; } + public uint Continuity { get; private set; } + public int PayloadStartOffset { get; private set; } + public byte[] Payload { get; private set; } + public bool PayloadFlagSet => AdaptionFieldControl == 1 || AdaptionFieldControl == 3; + public byte[] RawPacket { get; private set; } + public ulong Serial { get; private set; } + + /// + /// Retrieving the raw packet, including adaption field and Payload, but without the four byte header. + /// + /// + public byte[] GetBody() + { + byte[] buffer = new byte[184]; + Array.Copy(RawPacket, 4, buffer, 0, 184); + return buffer; + } + } +} diff --git a/skyscraper8/Mpeg2/TsPacketError.cs b/skyscraper8/Mpeg2/TsPacketError.cs new file mode 100644 index 0000000..52f8dd7 --- /dev/null +++ b/skyscraper8/Mpeg2/TsPacketError.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Mpeg2 +{ + public enum TsPacketError + { + NoError, + InvalidAdaption + } +} diff --git a/skyscraper8/NuGet.Config b/skyscraper8/NuGet.Config new file mode 100644 index 0000000..a2740cb --- /dev/null +++ b/skyscraper8/NuGet.Config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/skyscraper8/Passing.cs b/skyscraper8/Passing.cs new file mode 100644 index 0000000..a4fb50d --- /dev/null +++ b/skyscraper8/Passing.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.IO.TunerInterface; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5 +{ + internal class Passing + { + private IScraperStroage scraperStroage; + private IStreamReader streamReader; + private List tuners; + private List satellitePositions; + private ISkyscraperEventLogger eventLogger; + + public Passing() + { + + } + + public bool Boot() + { + + Ini ini = PluginManager.GetInstance().Ini; + + int requiredFactoryId = ini.ReadValue("startup", "storage", -1); + if (requiredFactoryId == -1) + { + Console.WriteLine("Could not determine storage factory from ini file. Configure it using the UI!"); + return false; + } + + ScraperStorageFactoryConnectionManager connectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + + DirectoryInfo di = new DirectoryInfo("."); + FileInfo[] fileInfos = di.GetFiles("skyscraper5.Storage.*.dll"); + + foreach (KeyValuePair keyValuePair in connectionManager.GetKnownFactories()) + { + Console.WriteLine("Found Storage Factory #{0}, {1}", keyValuePair.Key.Id, keyValuePair.Key.DisplayName); + } + + string iniCategoryName = String.Format("storage{0}", requiredFactoryId); + if (!ini.ContainsKey(iniCategoryName)) + { + Console.WriteLine("Could not find configuration for the storage factory. Configure it using the UI!"); + return false; + } + + KeyValuePair valuePair = connectionManager.GetKnownFactories().First(x => x.Key.Id == requiredFactoryId); + if (valuePair.Key == null) + { + Console.WriteLine("Storage Factory {0} was not loaded. Reconfigure with the UI!", requiredFactoryId); + return false; + } + + string factoryCname = String.Format("storage{0}", valuePair.Key.Id); + PluginManager.GetInstance().AutoconfigureObject(factoryCname, valuePair.Value); + + Console.WriteLine("Booting {0}...", valuePair.Key.DisplayName); + scraperStroage = valuePair.Value.CreateScraperStroage(); + if (scraperStroage == null) + { + Console.WriteLine("The storage factory didn't create a storage."); + return false; + } + + TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance(); + ReadOnlyCollection> tunerFactories = tunerFactoryConnectionManager.GetKnownFactories(); + int tunerFactory = ini.ReadValue("startup", "tunerFactory", 0); + KeyValuePair bootingTuner = tunerFactories.First(x => x.Key.Id == tunerFactory); + if (bootingTuner.Value == null) + { + Console.WriteLine("The tuner factory #{0} didn't load.", tunerFactory); + return false; + } + + TunerFactoryConnectionManager.ConfigureFactoryFromIni(bootingTuner, ini); + + streamReader = bootingTuner.Value.CreateStreamReader(); + if (streamReader == null) + { + Console.WriteLine("The tuner factory #{0} didn't create a stream reader."); + return false; + } + + try + { + streamReader.CheckForDVB(); + } + catch (BadImageFormatException e) + { + Console.WriteLine("The configured tuner factory is not suitable for the current processor architecture. Tuning won't work, therefore functionality will be severely limited. Please configure a Tuner Factory Class suitable for this processor architecture in order to get the best experience. If you need to run crazycat69's StreamReader.dll on 64-bit machines, try RemoteStreamReader."); + streamReader = new NullTunerFactory().CreateStreamReader(); + } + + List foundTuners = new List(); + bool checkForDvbExEx = streamReader.CheckForDVBExEx((index, name, type) => + { + TunerMetadata tuner = new TunerMetadata(index, name, type); + foundTuners.Add(tuner); + }); + + foreach (TunerMetadata foundTuner in foundTuners) + { + streamReader.StopDVB(); + + bool startDvbEx = streamReader.StartDvbEx(foundTuner.Index); + if (!startDvbEx) + { + Console.WriteLine("Failed to start {0}", foundTuner.Name); + Thread.Sleep(1000); + continue; + } + + foundTuner.Caps = streamReader.GetCaps(); + + byte[] macBuffer = new byte[6]; + bool mac = streamReader.GetMAC(macBuffer); + if (!mac) + { + Console.WriteLine("Failed to read MAC Address of {0}", foundTuner.Name); + Thread.Sleep(1000); + } + else + { + foundTuner.MacAddress = new PhysicalAddress(macBuffer); + } + + bool stopDvb = streamReader.StopDVB(); + if (!stopDvb) + { + Console.WriteLine("Failed to stop {0}", foundTuner.Name); + Thread.Sleep(1000); + continue; + } +; + if (scraperStroage.UiTunerTestFor(foundTuner)) + { + scraperStroage.UiTunerGetConfiguration(foundTuner); + } + + if (tuners == null) + tuners = new List(); + tuners.Add(foundTuner); + } + + satellitePositions = scraperStroage.UiSatellitesListAll(); + + return true; + } + + private HeadlessJob GetNextJob() + { + HeadlessJob headlessJob = scraperStroage.GetQueuedJob(); + if (headlessJob != null) + { + return headlessJob; + } + + return new HeadlessJob(HeadlessJobType.BlindscanEverything); + } + + public void Run() + { + while (true) + { + HeadlessJob headlessJob = GetNextJob(); + Run(headlessJob); + if (!headlessJob.isSynthetic) + { + scraperStroage.SetQueuedJobComplete(headlessJob); + } + } + } + + private void Run(HeadlessJob job) + { + switch (job.jobType) + { + case HeadlessJobType.Sleep: + Thread.Sleep(job.iArg1); + break; + case HeadlessJobType.MassImport: + DirectoryInfo di = new DirectoryInfo(job.sArg1); + MassImportDirectory(di); + break; + case HeadlessJobType.BlindscanEverything: + PerformBlindscanEverything(); + break; + case HeadlessJobType.ReimportByTag1: + ReimportTag(job.iArg1); + break; + default: + throw new NotImplementedException(job.jobType.ToString()); + } + } + + private void PerformBlindscanEverything() + { + if (tuners == null) + tuners = new List(); + if (tuners.Count == 0) + throw new ApplicationException("I'd need to blindscan everything, but I don't have any tuners I could use!"); + + throw new NotImplementedException(nameof(PerformBlindscanEverything)); + } + + private void ReimportTag(int tag1) + { + IReadOnlyList queue = scraperStroage.ListImportFileByTag1(tag1); + foreach(string filename in queue) + { + FileInfo fi = new FileInfo(filename); + if (!fi.Exists) + continue; + ScrapeStream(fi); + } + } + + private void MassImportDirectory(DirectoryInfo sourceDir) + { + foreach (FileSystemInfo fileSystemInfo in sourceDir.GetFileSystemInfos()) + { + DirectoryInfo di = fileSystemInfo as DirectoryInfo; + FileInfo fi = fileSystemInfo as FileInfo; + if (di != null) + { + MassImportDirectory(di); + } + else if (fi != null) + { + if (fi.Length == 0) + continue; + + if (!fi.Extension.ToLowerInvariant().Equals(".ts")) + continue; + + if (scraperStroage.ImportFileKnown(fi)) + continue; + + FileStream fileStream = fi.OpenRead(); + Stopwatch stopwatch = Stopwatch.StartNew(); + int tstype; + ScrapeStream(fileStream,true, out tstype); + stopwatch.Stop(); + Console.WriteLine("Importing {0} took {1}",fi.Name, stopwatch.Elapsed); + scraperStroage.WaitForCompletion(); + scraperStroage.ImportMarkFileAsKnown(fi, stopwatch.Elapsed, tstype); + fileStream.Close(); + } + else + { + throw new NotImplementedException(fileSystemInfo.GetType().ToString()); + } + } + } + + private void ScrapeStream(FileInfo fi) + { + FileStream fileStream = fi.OpenRead(); + ScrapeStream(fileStream, true, out _); + fileStream.Close(); + fileStream.Dispose(); + } + + private void ScrapeStream(Stream inStream, bool disk, out int tstype) + { + if (eventLogger == null) + eventLogger = new ConsoleEventLogger(); + + SkyscraperContext skyscraperContext = new SkyscraperContext(new TsContext(), eventLogger, scraperStroage); + skyscraperContext.SourceIsDisk = disk; + skyscraperContext.InitalizeFilterChain(); + skyscraperContext.IngestFromStream(inStream); + skyscraperContext.Dispose(); + tstype = skyscraperContext.SpecialTsType; + } + } +} diff --git a/skyscraper8/Privates/BSkyBPrivate.cs b/skyscraper8/Privates/BSkyBPrivate.cs new file mode 100644 index 0000000..f551b28 --- /dev/null +++ b/skyscraper8/Privates/BSkyBPrivate.cs @@ -0,0 +1,16 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Privates +{ + internal class BSkyBPrivate : ITsPacketProcessor + { + public void PushPacket(TsPacket packet) + { + } + } +} diff --git a/skyscraper8/Privates/OpenTvPrivate.cs b/skyscraper8/Privates/OpenTvPrivate.cs new file mode 100644 index 0000000..a3e3d72 --- /dev/null +++ b/skyscraper8/Privates/OpenTvPrivate.cs @@ -0,0 +1,16 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Privates +{ + internal class OpenTvPrivate : ITsPacketProcessor + { + public void PushPacket(TsPacket packet) + { + } + } +} diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs new file mode 100644 index 0000000..fcd823e --- /dev/null +++ b/skyscraper8/Program.cs @@ -0,0 +1,729 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using skyscraper5.Abertis; +using skyscraper5.Hdmv; +using skyscraper5.Mpeg2; +using skyscraper5.Scorcher; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.IO.TunerInterface; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.RecordingImporter; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.FrameGrabber; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.Storage.Filesystem; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; +using skyscraper5.Skyscraper.Webserver; +using skyscraper5.src.Aac; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Sophtainer; +using skyscraper5.T2MI; +using skyscraper5.src.Mpeg2.PacketFilter; + +namespace skyscraper5 +{ + class Program + { + static void Main(string[] args) + { + ffmpegFrameGrabber.CanStart(); + Console.WriteLine("{1}? {0}", Environment.Is64BitOperatingSystem, "Is 64-bit OS?"); + Console.WriteLine("{1}? {0}", Environment.Is64BitProcess, "Is 64-bit Process?"); + + PluginManager.GetInstance(); + + if (args.Length != 0) + { + if (args[0].ToLowerInvariant().EndsWith(".ts")) + { + FileInfo fi = new FileInfo(args[0]); + if (fi.Exists) + HandleSingleFile(fi); + else + Console.WriteLine("{0} missing.", fi.FullName); + return; + } + + if (args[0].Equals("aactest")) + { + new AacTestProgram().Run(); + return; + } + if (args[0].ToLowerInvariant().Equals("cscan")) + { + HandleCrazyScanToTestingSystem(args[1]); + return; + } + if (args[0].ToLowerInvariant().Equals("cscan-live")) + { + HandleCrazyScanToLiveSystem(args[1]); + return; + } + if (args[0].ToLowerInvariant().Equals("cscan-live-notimeout")) + { + HandleCrazyScanToLiveSystem(args[1], false); + return; + } + + if (args[0].ToLowerInvariant().Equals("udpin")) + { + HandleUdpTesting(); + return; + } + if (args[0].ToLowerInvariant().Equals("udp-agent")) + { + HandleUdpLive(); + return; + } + + if (args[0].Equals("abertest")) + { + FileInfo fi = new FileInfo(args[1]); + if (!fi.Exists) + { + Console.WriteLine("{0} not found", fi.FullName); + return; + } + AbertisDemoProgram abertisDemo = new AbertisDemoProgram(fi); + abertisDemo.Run(); + return; + } + + if (args[0].ToLowerInvariant().Equals("t2mitest")) + { + new TestApp(args[1]).Run(); + return; + } + + if (args[0].ToLowerInvariant().Equals("ioctltest")) + { + IoctlTest(); + return; + } + + if (args[0].ToLowerInvariant().Equals("m2tstots")) + { + FileInfo infile = new FileInfo(args[1]); + FileInfo outfile = new FileInfo(args[2]); + new M2TsToTs(infile,outfile).Run(); + return; + } + + if (Directory.Exists(args[0])) + { + DirectoryInfo doThisDir = new DirectoryInfo(args[0]); + ProcessDirectory(doThisDir); + return; + } + + if (args[0].ToLowerInvariant().Equals("proxytest")) + { + TcpTsProxy tcpTsProxy = new TcpTsProxy(); + Thread.Sleep(5000); + tcpTsProxy.Dispose(); + return; + } + + if (args[0].ToLowerInvariant().Equals("importtest")) + { + DirectoryInfo doThisDir = new DirectoryInfo(args[1]); + IScraperStroage imss = new InMemoryScraperStorage(); + ConsoleEventLogger cel = new ConsoleEventLogger(); + TsFileCollectionImporter importer = new TsFileCollectionImporter(imss, doThisDir, cel); + importer.Run(); + return; + } + + + //fsimport "D:\Skyscraper" "E:\\" + if (args[0].ToLowerInvariant().Equals("fsimport")) + { + DirectoryInfo srcDir = new DirectoryInfo(args[1]); + DirectoryInfo tgtDir = new DirectoryInfo(args[2]); + IScraperStroage fss = new FilesystemScraperStorage(tgtDir); + ConsoleEventLogger cel = new ConsoleEventLogger(); + TsFileCollectionImporter importer = new TsFileCollectionImporter(fss, srcDir, cel); + importer.Run(); + return; + } + + if (args[0].ToLowerInvariant().Equals("webservertest")) + { + SkyscraperWebserver webserverTest = SkyscraperWebserver.GetInstance(); + webserverTest.Start(); + while (webserverTest.IsListening) + { + Thread.Sleep(1000); + } + + return; + } + + if (args[0].ToLowerInvariant().Equals("gpstest")) + { + GpsManager.GpsTest(); + return; + } + + if (args[0].ToLowerInvariant().Equals("mk-cable-csv")) + { + new CableFrequencyListGenerator().Run(); + return; + } + + if (args[0].ToLowerInvariant().Equals("what-can-i-receive")) + { + WhatCanIReceive.StandaloneProgram(); + return; + } + + if (args[0].ToLowerInvariant().Equals("make-dummy-ts")) + { + DummyTsGenerator dummyTsGenerator = new DummyTsGenerator(); + dummyTsGenerator.Run(); + return; + } + + if (args[0].ToLowerInvariant().Equals("merge-bl-jobs")) + { + MergeDbBlindscanJobs.RunAsMain(); + return; + } + + if (args[0].ToLowerInvariant().Equals("file-live")) + { + Program.HandleFileToLiveSystem(args[1]); + return; + } + } + + Passing passing = new Passing(); + if (!passing.Boot()) + { + Environment.Exit(1); + return; + } + passing.Run(); + } + + private static void StreamReaderTest(IStreamReader streamReader) + { + List streamReaderTuners = StreamReaderScanController.GetTuners(streamReader); + if (streamReaderTuners.Count == 0) + { + Console.WriteLine("No tuners, lmao."); + return; + } + + //FileInfo fi = new FileInfo("blindscan.json"); + StreamReaderScanController srsc = new StreamReaderScanController(streamReader, streamReaderTuners[0]); + //srsc.SetDefaultDvbCParameters(); + srsc.SetDefaultKuBandParameters(); + srsc.SetParameter(StreamReaderScanController.StreamReaderParameter.ScanDiseqcType, 2); + srsc.SetParameter(StreamReaderScanController.StreamReaderParameter.DiseqcSwitchPosition, 1); + srsc.RunBlindscan(); + srsc.Dispose(); + return; + } + + private static void ProcessDirectory(DirectoryInfo di) + { + ISkyscraperEventLogger eventLogger = new ConsoleEventLogger(); + IScraperStroage scraperStroage = new InMemoryScraperStorage(); + + //DirectoryInfo di = new DirectoryInfo(@"E:\Skyscraper\Astra 19.2"); + FileInfo[] fileInfos = di.GetFiles("*.ts"); + foreach (FileInfo fileInfo in fileInfos) + { + Console.WriteLine(new string('_', Console.WindowWidth - 1)); + Console.WriteLine("Processing: {0}", fileInfo.Name); + SkyscraperContext skyscraper = new SkyscraperContext(new TsContext(), eventLogger, scraperStroage); + //StreamTypeAutodetectionTest streamTypeAutodetectionTest = new StreamTypeAutodetectionTest(); + FileStream fileStream = fileInfo.OpenRead(); + + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + skyscraper.IngestFromStream(fileStream); + //streamTypeAutodetectionTest.IngestFromStream(fileStream); + stopwatch.Stop(); + + Console.WriteLine("Time to process: " + stopwatch.Elapsed.TotalSeconds); + + Console.WriteLine("File was: {0}", fileInfo.Name); + PrintPidStatistics(skyscraper); + } + } + + private static void PrintPidStatistics(SkyscraperContext skyscraper) + { + Console.WriteLine("Unattached PIDs:"); + ulong[] pidStatistics = skyscraper.DvbContext.GetPidStatistics(); + if (pidStatistics == null) + return; + for (int i = 0; i < pidStatistics.Length; i++) + { + if (pidStatistics[i] != 0) + { + if (!skyscraper.DvbContext.IsPidProcessorPresent(i)) + { + Console.WriteLine("{0:X4}", i); + } + } + } + } + + private static void HandleUdpTesting() + { + HandleUdpInput(new ConsoleEventLogger(), new InMemoryScraperStorage()); + } + + private static void HandleUdpLive() + { + ConsoleEventLogger eventLogger = new ConsoleEventLogger(); + ScraperStorageFactoryConnectionManager connectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + IScraperStorageFactory factory = connectionManager.AutoGetDefaultFactory(); + IScraperStroage scraperStorage = factory.CreateScraperStroage(); + HandleUdpInput(eventLogger, scraperStorage); + } + + private static void HandleUdpInput(ISkyscraperEventLogger eventLogger, IScraperStroage scraperStorage) + { + NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface networkInterface in networkInterfaces) + { + IPInterfaceProperties interfaceProperties = networkInterface.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in interfaceProperties.UnicastAddresses) + { + Console.WriteLine("Listening on: {0}", unicastAddress.Address.ToString()); + } + } + + //tsp -v --control-port 9001 -I dvb --delivery-system DVB-S2 --frequency 11421000000 --symbol-rate 22000000 --polarity horizontal -O ip 127.0.0.1:9001 + //SkyscraperWebserver.GetInstance().Start(); + //scraperStorage = new FilesystemScraperStorage(new DirectoryInfo("C:\\scraped_data")); + + SkyscraperContext skyscraper = null; + + UdpClient udpServer = new UdpClient(new IPEndPoint(IPAddress.Any, 9003)); + IPEndPoint remote = null; + IPEndPoint oldRemote = null; + int numPackets; + byte[] singlePacket = new byte[188]; + while (true) + { + byte[] buffer = udpServer.Receive(ref remote); + if (!remote.Equals(oldRemote)) + { + oldRemote = remote; + skyscraper = new SkyscraperContext(new TsContext(), eventLogger, scraperStorage); + skyscraper.InitalizeFilterChain(); + skyscraper.EnableTimeout = true; + Console.WriteLine("Got zapped by {0}", remote); + } + + numPackets = buffer.Length / 188; + for (int i = 0; i < numPackets; i++) + { + Array.Copy(buffer, i * 188, singlePacket, 0, 188); + skyscraper.IngestSinglePacket(singlePacket); + } + } + } + + private static void HandleCrazyScanToTestingSystem(string url) + { + + IScraperStroage scraperStorage = new InMemoryScraperStorage(); + HandleCrazyScan(url, scraperStorage, true); + } + + private static void HandleFileToLiveSystem(string filename) + { + ScraperStorageFactoryConnectionManager connectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + IScraperStorageFactory factory = connectionManager.AutoGetDefaultFactory(); + IScraperStroage scraperStorage = factory.CreateScraperStroage(); + FileInfo fi = new FileInfo(filename); + if (!fi.Exists) + { + Console.WriteLine("{0} not found.", fi.FullName); + return; + } + + ISkyscraperEventLogger eventLogger = new ConsoleEventLogger(); + SkyscraperContext skyscraper = new SkyscraperContext(new TsContext(), eventLogger, scraperStorage); + skyscraper.InitalizeFilterChain(); + skyscraper.EnableTimeout = false; + skyscraper.TcpProxyEnabled = false; + FileStream fileStream = fi.OpenRead(); + skyscraper.IngestFromStream(fileStream); + fileStream.Close(); + fileStream.Dispose(); + skyscraper.Dispose(); + } + + + + private static void HandleCrazyScanToLiveSystem(string url, bool withTimeout = true) + { + ScraperStorageFactoryConnectionManager connectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + IScraperStorageFactory factory = connectionManager.AutoGetDefaultFactory(); + IScraperStroage storage = factory.CreateScraperStroage(); + storage.Ping(); + HandleCrazyScan(url, storage, withTimeout); + } + + private static void HandleCrazyScan(string url, IScraperStroage scraperStorage, bool timeout) + { + ISkyscraperEventLogger eventLogger = new ConsoleEventLogger(); + + if (!url.StartsWith("tcp://")) + { + Console.WriteLine("Use TCP, please."); + return; + } + + if (url.EndsWith("/")) + { + Console.WriteLine("Don't use that trailing slash, please."); + return; + } + + url = url.Substring(6); + string[] strings = url.Split(':'); + string hostname = strings[0]; + int port = Int32.Parse(strings[1]); + + TcpClient tcpClient = new TcpClient(hostname, port); + + SkyscraperContext skyscraper = new SkyscraperContext(new TsContext(), eventLogger, scraperStorage); + skyscraper.InitalizeFilterChain(new SkipFilter(1024)); + //skyscraper.EnableTimeout = timeout; + //skyscraper.TimeoutSeconds = 10; + skyscraper.TcpProxyEnabled = true; + skyscraper.IngestFromStream(new BufferedStream(tcpClient.GetStream(), 96256 * 2)); + } + + private static void HandleSingleFile(FileInfo fi) + { + //Environment.CurrentDirectory = fi.Directory.FullName; + TsContext tsContext = new TsContext(); + ISkyscraperEventLogger eventLogger = new ConsoleEventLogger(); + IScraperStroage scraperStorage = new InMemoryScraperStorage(); + SkyscraperContext skyscraper = new SkyscraperContext(tsContext, eventLogger, scraperStorage); + skyscraper.InitalizeFilterChain(); + FileStream fileStream = fi.OpenRead(); + skyscraper.IngestFromStream(fileStream); + skyscraper.Dispose(); + fileStream.Close(); + + Console.WriteLine(""); + Console.WriteLine("Current Network ID: {0}", skyscraper.CurrentNetworkId); + Console.WriteLine("Current Transport Stream ID: {0}", skyscraper.CurrentTransportStreamId); + if (tsContext.PcrMonitor != null) + { + Console.WriteLine("Processing Performance: {0}", tsContext.PcrMonitor.Performance); + } + else + { + Console.WriteLine("No PCR detected."); + } + Console.WriteLine("DNS Records found: {0}", scraperStorage.DnsCountA()); + Console.WriteLine(""); + + Console.WriteLine(new string('-', Console.WindowWidth)); + Console.WriteLine(""); + Console.ReadKey(true); + } + + private static void IoctlTest() + { + /* + int dvbSatSearchLnb = LibDvbV5.DvbSatSearchLnb("EXTENDED"); + LibDvbV5SatLnb lnb = new LibDvbV5SatLnb(dvbSatSearchLnb); + + LibDvbV5Device device = new LibDvbV5Device(); + device.SetLog(8, null); + device.Find(); + + LibDvbV5DeviceList findDeviceByDeliverySystem = device.FindDeviceByDeliverySystem(LibDvbV5FrontendDeliverySystem.SYS_DVBC_ANNEX_A); + if (findDeviceByDeliverySystem == null) + return; + + LibDvbV5DeviceList demuxerName = device.GetSisterDevice(findDeviceByDeliverySystem, DvbDeviceType.Demux); + LibDvbV5DeviceList dvrName = device.GetSisterDevice(findDeviceByDeliverySystem, DvbDeviceType.Dvr); + + LibDvb5FrontendParameters frontend = findDeviceByDeliverySystem.ToFrontendParameters(); + frontend.SetDeliverySystem(LibDvbV5FrontendDeliverySystem.SYS_DVBC_ANNEX_A); + frontend.StoreParameter(17, (uint)LibDvbV5FrontendDeliverySystem.SYS_DVBC_ANNEX_A); + frontend.StoreParameter(3, 610000000); //freq + frontend.StoreParameter(8, 6900000); //symbol rate + frontend.SetParameters(); + Thread.Sleep(400); + frontend.GetStatistics(); + Thread.Sleep(400); + uint retrieveStats = frontend.RetrieveStats(512); + if ((retrieveStats & 0x10) != 0) + { + Console.WriteLine("got lock!"); + LibDvbV5OpenDescriptor dvrFd = device.DeviceOpen(dvrName); + if (dvrFd == null) + throw new IOException("open failed"); + dvrFd.SetBufferSize(1024 * 1024); + + LibDvbV5OpenDescriptor demuxFd = device.DeviceOpen(demuxerName); + if (demuxFd == null) + throw new IOException("open failed"); + + demuxFd.DemuxSetPesFilter(0x2000, DemuxerPesType.DMX_PES_OTHER); + + FileStream fileStream = File.OpenWrite(String.Format("{0}.ts", DateTime.Now.Ticks)); + byte[] packBuffer = new byte[188]; + int readLen = 0; + for (int i = 0; i < 9001; i++) + { + readLen = dvrFd.Read(packBuffer, 188); + if (readLen > 0) + { + fileStream.Write(packBuffer, 0, 188); + } + } + + fileStream.Flush(true); + fileStream.Close(); + demuxFd.Dispose(); + dvrFd.Dispose(); + } + else + { + Console.WriteLine("no lock :("); + } + frontend.Dispose(); + device.Dispose(); + Thread.Sleep(1000); + return;*/ + /*FileInfo fi = new FileInfo("/dev/video0"); + SafeUnixHandle safeUnixHandle = UnsafeLibCNativeMethodsWrappers.OpenDevice(fi); + UnsafeLibCNativeMethodsWrappers.GetCapability(safeUnixHandle); + UnsafeLibCNativeMethodsWrappers.Close(safeUnixHandle);*/ + + //LocalStreamReader streamReader = LocalStreamReader.GetInstance(); + IStreamReader streamReader = new NullStreamReader(); + + bool checkForDvb = streamReader.CheckForDVB(); + if (checkForDvb) + { + streamReader.CheckForDVBEx((x, y) => { Console.WriteLine("{0}, {1}", x, y); }); + if (streamReader.StartDvbEx(0)) + { + + STD_TYPE ttype = default; + bool result = streamReader.GetTunerType(ref ttype); + if (!result) + Console.WriteLine("oh no"); + + Caps caps = streamReader.GetCaps(); + + BlScanCallback scanCallback = + (ref SearchResult searchResult) => + { + Console.WriteLine("{0}/{1}/{2}",searchResult.Freq,searchResult.Pol,searchResult.SR); + }; + + /*UnsafeStreamReaderMethods.AirScanCallback airScanCallback = + (ref UnsafeStreamReaderMethods.SearchResult2 searchResult2) => + { + Console.WriteLine(searchResult2.Freq); + };*/ + AirScanCallback altTest = (ref SearchResult2 searchResult) => + { + Console.WriteLine(searchResult.Freq); + }; + + //int tpNum = 0; + //result = UnsafeStreamReaderMethods.BLScanEx(10744000, 8000, 1, 9750000, 10600000, 11700000, 1, UnsafeStreamReaderMethods.STD_TYPE.STD_DVBS2, ref sr1); + //result = UnsafeStreamReaderMethods.BLScan(10744000, 8000, 0, 9750000, 10600000, 11700000, 1000, ref sr1); + /*result = UnsafeStreamReaderMethods.SetChannelExEx(10744000, 22000000, 0, + UnsafeStreamReaderMethods.VITERBIRATE_TYPE.VR_5_6, 9750000, 10600000, 11700000, + UnsafeStreamReaderMethods.MOD_TYPE.MOD_QPSK, 0, 0, + UnsafeStreamReaderMethods.ROLLOFF_TYPE.ROLLOFF_AUTO);*/ + /* + result = UnsafeStreamReaderMethods.SendDiSEqC(2, + UnsafeStreamReaderMethods.DiSEqC_Opcode.DISEQC_HIGH_NIBBLE | + UnsafeStreamReaderMethods.DiSEqC_Opcode.DISEQC_LOW_BAND | + UnsafeStreamReaderMethods.DiSEqC_Opcode.DISEQC_HORIZONTAL | + UnsafeStreamReaderMethods.DiSEqC_Opcode.DISEQC_POSITION_B | + UnsafeStreamReaderMethods.DiSEqC_Opcode.DISEQC_OPTION_A); + result = UnsafeStreamReaderMethods.SetChannel(10775000, 29900000, 0, + UnsafeStreamReaderMethods.VITERBIRATE_TYPE.VR_3_5, 9750000, 10600000, 11700000); + + + for (int i = 0; i < 100; i++) + { + Thread.Sleep(1000); + } + */ + + + SearchResult2 sr1 = default; + SearchResult2 sr2 = default; + int tpNum = default; + IntPtr allocHGlobal = Marshal.AllocHGlobal(1024); + result = streamReader.AirScan(100000, 800000, 3000, 8000, (int)STD_TYPE.STD_DVBC, allocHGlobal, ref tpNum, altTest); + SearchResult2 ptrToStructure = (SearchResult2)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult2)); + Marshal.FreeHGlobal(allocHGlobal); + Console.WriteLine(tpNum); + + /* + byte[] buffer = new byte[1024]; + + int offset = 0; + FileStream fileStream = File.OpenWrite("tbs5580.bin"); + result = true; + while (result) + { + result = UnsafeStreamReaderMethods.GetEEPROM(buffer, offset, 8); + if (result) + { + fileStream.Write(buffer, 0, 8); + offset += 8; + } + } + + fileStream.Flush(true); + fileStream.Close(); + */ + + /* + * + double snr = default; + for (uint i = 100; i < 200; i++) + { + result = UnsafeStreamReaderMethods.RFScan2(i * 1000, UnsafeStreamReaderMethods.STD_TYPE.STD_DVBC, ref snr); + if (result) + Console.WriteLine("{0} -> {1}", i, snr); + } + + */ + /* + UnsafeStreamReaderMethods.SearchResult sr1 = default; + UnsafeStreamReaderMethods.SearchResult sr2 = default; + int tpNum = default; + result = UnsafeStreamReaderMethods.SetChannel(10744000, 22000000, 0, 0, 9750000, 10600000, 11700000); + ulong numPackets = 0; + IntPtr filterPtr = default; + byte[] buffer = new byte[188]; + FileStream fs = File.OpenWrite("test.ts"); + result = UnsafeStreamReaderMethods.SetFilter(8192, ((data, length) => + { + //Console.WriteLine("Packet {0}", numPackets++); + Marshal.Copy(data, buffer, 0, length); + fs.Write(buffer, 0, 188); + }), 0x02, 1, ref filterPtr); + + sbyte[] iq = new sbyte[200]; + for (int i = 0; i < 150; i++) + { + Thread.Sleep(100); + UnsafeStreamReaderMethods.IQScan(0, iq, 100); + Console.WriteLine("{0},{1} - {2},{3} - {4},{5} - {6},{7}", iq[0], iq[1], iq[2], iq[3], iq[4], iq[5], iq[6], iq[7]); + } + result = UnsafeStreamReaderMethods.DelFilter(filterPtr); + fs.Flush(true); + fs.Close(); + */ + //result = UnsafeStreamReaderMethods.BLScan(11068, 8000, 1, 975000, 10600000, 11700000, 1, ref sr); + + /* + for (int i = 10700; i < 12750; i += 3) + { + result = UnsafeStreamReaderMethods.BLScan(i, 0, 1, 975000, 10600000, 11700000, 1, ref sr1); + if (sr1.SR != 0) + { + Console.Write("\r\n{0}/{1}/{2}", i, sr1.Pol, sr1.SR); + } + else + { + Console.Write("."); + } + result = UnsafeStreamReaderMethods.BLScan(i, 0, 0, 975000, 10600000, 11700000, 1, ref sr1); + if (sr1.SR != 0) + { + Console.Write("\r\n{0}/{1}/{2}", i, sr1.Pol, sr1.SR); + } + else + { + Console.Write("."); + } + }*/ + + //result = UnsafeStreamReaderMethods.BLScan2(10700, 12750, 0, 975000, 10600000, 11700000, ref sr1, ref tpNum, scanCallback); + + /* + byte[] macBuffer = new byte[6]; + result = UnsafeStreamReaderMethods.GetMAC(macBuffer); + */ + /* + double m = default; + for (int i = 10700; i < 12750; i += 1) + { + result = UnsafeStreamReaderMethods.RFScan(i, 0, 975000, 10600000, 11700000, out m); + Console.WriteLine("{2} {0} -> {1}", i, m, result); + } + */ + + + /* + result = UnsafeStreamReaderMethods.SetChannel(10744000, 22000000, 0, 0, 9750000, 10600000, 11700000); + + if (File.Exists("test.ts")) + File.Delete("test.ts"); + + FileStream fileStream = File.OpenWrite("test.ts"); + IntPtr filterPtr = IntPtr.Zero; + byte[] packet = new byte[188]; + UnsafeStreamReaderMethods.StdcallDvbCallback callback = (bytes, k) => + { + Marshal.Copy(bytes, packet, 0, k); + fileStream.Write(packet, 0, 188); + }; + result = UnsafeStreamReaderMethods.SetFilter(8192, callback, 0x02, 1, ref filterPtr); + + if (!result) + throw new NotImplementedException("no filter"); + + IntPtr rcPtr = IntPtr.Zero; + result = UnsafeStreamReaderMethods.SetRemoteControl(0, -1, (bytes, k) => + { + Console.WriteLine(k); + }, + ref rcPtr); + + result = UnsafeStreamReaderMethods.DelFilter(rcPtr); + result = UnsafeStreamReaderMethods.DelFilter(filterPtr); + */ + + /* + if (!UnsafeStreamReaderMethods.StopDVB()) + Console.WriteLine("wtf"); + */ + /* + fileStream.Flush(true); + fileStream.Close(); + */ + } + } + + } + } +} diff --git a/skyscraper8/Properties/launchSettings.json b/skyscraper8/Properties/launchSettings.json new file mode 100644 index 0000000..3b6a88a --- /dev/null +++ b/skyscraper8/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "skyscraper8": { + "commandName": "Project", + "commandLineArgs": "cscan \"tcp://172.20.20.202:6970\"", + "remoteDebugEnabled": false + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + } +} \ No newline at end of file diff --git a/skyscraper8/Rds/IDsn.cs b/skyscraper8/Rds/IDsn.cs new file mode 100644 index 0000000..41e2d7b --- /dev/null +++ b/skyscraper8/Rds/IDsn.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds +{ + interface IDsn + { + public byte DataSetNumber { get; set; } + } +} diff --git a/skyscraper8/Rds/IPsn.cs b/skyscraper8/Rds/IPsn.cs new file mode 100644 index 0000000..d206449 --- /dev/null +++ b/skyscraper8/Rds/IPsn.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds +{ + interface IPsn + { + public byte ProgrammeServiceNumber { get; set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x02_PS.cs b/skyscraper8/Rds/Messages/0x02_PS.cs new file mode 100644 index 0000000..448b14f --- /dev/null +++ b/skyscraper8/Rds/Messages/0x02_PS.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x02,RdsMessageHeaderType.DsnPsn)] + class PS : RdsMessage, IDsn, IPsn + { + public PS(byte[] buffer) + { + ProgrammeService2 = Encoding.ASCII.GetString(buffer, 0, 8); + } + + public byte DataSetNumber { get; set; } + public byte ProgrammeServiceNumber { get; set; } + + public string ProgrammeService2 { get; private set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x03_TA_TP.cs b/skyscraper8/Rds/Messages/0x03_TA_TP.cs new file mode 100644 index 0000000..9ccccf5 --- /dev/null +++ b/skyscraper8/Rds/Messages/0x03_TA_TP.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x03,RdsMessageHeaderType.DsnPsn)] + class TA_TP : RdsMessage, IDsn, IPsn + { + public TA_TP(byte[] buffer) + { + this.TP = (buffer[0] & 0x02) != 0; + this.TA = (buffer[0] & 0x01) != 0; + } + + public bool TA { get; private set; } + + public bool TP { get; private set; } + + public byte DataSetNumber { get; set; } + public byte ProgrammeServiceNumber { get; set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x07_PTY.cs b/skyscraper8/Rds/Messages/0x07_PTY.cs new file mode 100644 index 0000000..da6277c --- /dev/null +++ b/skyscraper8/Rds/Messages/0x07_PTY.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x07,RdsMessageHeaderType.DsnPsn)] + public class PTY : RdsMessage, IDsn, IPsn + { + public PTY(byte[] buffer) + { + Pty = (ProgrammeTypeCodes)buffer[0]; + } + + public ProgrammeTypeCodes Pty { get; private set; } + + public enum ProgrammeTypeCodes : byte + { + News = 1, + CurrentAffairs = 2, + Information = 3, + Sport = 4, + Education = 5, + Drama = 6, + Culture = 7, + Science = 8, + Varied = 9, + PopMusic = 10, + RockMusic = 11, + EasyListeningMusic = 12, + LightClassical = 13, + SeriousClassical = 14, + OtherMusic = 15, + Weather = 16, + Finance = 17, + ChildrensProgrammes = 18, + SocialAffairs = 19, + Religion = 20, + PhoneIn = 21, + Travel = 22, + Leisure = 23, + JazzMusic = 24, + CountryMusic = 25, + NationalMusic = 26, + OldiesMusic = 27, + FolkMusic = 28, + Documentary = 29, + AlarmTest = 30, + Alarm = 31 + } + + public byte DataSetNumber { get; set; } + public byte ProgrammeServiceNumber { get; set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x0A_RT.cs b/skyscraper8/Rds/Messages/0x0A_RT.cs new file mode 100644 index 0000000..357e569 --- /dev/null +++ b/skyscraper8/Rds/Messages/0x0A_RT.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x0a,RdsMessageHeaderType.DsnPsnMel)] + class RT : RdsMessage, IDsn, IPsn + { + public RT(byte[] buffer) + { + BufferConfiguration = (buffer[0] & 0x60) >> 5; + NumberOfTransmissions = (buffer[0] & 0x1f); + Text = Encoding.ASCII.GetString(buffer, 1, buffer.Length - 1); + } + + public string Text { get; private set; } + + public int NumberOfTransmissions { get; private set; } + + public int BufferConfiguration { get; private set; } + public byte DataSetNumber { get; set; } + public byte ProgrammeServiceNumber { get; set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x0D_RealTimeClock.cs b/skyscraper8/Rds/Messages/0x0D_RealTimeClock.cs new file mode 100644 index 0000000..d0053cd --- /dev/null +++ b/skyscraper8/Rds/Messages/0x0D_RealTimeClock.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x0d,RdsMessageHeaderType.None)] + class RealTimeClock : RdsMessage + { + public RealTimeClock(byte[] buffer) + { + int year = buffer[0]; + byte month = buffer[1]; + byte day = buffer[2]; + byte hours = buffer[3]; + byte minutes = buffer[4]; + byte seconds = buffer[5]; + byte centiseconds = buffer[6]; + LocalTimeOffsetUnchaned = buffer[7] == 0xff; + + int yearResult = DateTime.Now.Year / 100; + yearResult *= 100; + yearResult += year; + + int centisecondsResult = centiseconds *= 10; + + Timestamp = new DateTime(yearResult, month, day, hours, minutes, seconds, centisecondsResult); + } + + public bool LocalTimeOffsetUnchaned { get; private set; } + + public DateTime Timestamp { get; private set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x17_RequestMessage.cs b/skyscraper8/Rds/Messages/0x17_RequestMessage.cs new file mode 100644 index 0000000..35b1daa --- /dev/null +++ b/skyscraper8/Rds/Messages/0x17_RequestMessage.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x17,RdsMessageHeaderType.Mel)] + class RequestMessage : RdsMessage + { + private static RdsMessageAttribute[] attributes; + private static void Initalize() + { + if (attributes == null) + attributes = new RdsMessageAttribute[Byte.MaxValue]; + else + return; + + Type[] typeCandidates = typeof(RdsDecoder).Assembly.GetTypes(); + bool validTypeFound = false; + foreach (Type typeCandidate in typeCandidates) + { + Attribute customAttributes = typeCandidate.GetCustomAttributes(typeof(RdsMessageAttribute)).FirstOrDefault(); + if (customAttributes == null) + continue; + + RdsMessageAttribute rdsAttribute = customAttributes as RdsMessageAttribute; + attributes[rdsAttribute.MessageElementCode] = rdsAttribute; + validTypeFound = true; + } + + if (!validTypeFound) + throw new RdsException("failed to load rds message types"); + } + + public RequestMessage(byte[] buffer) + { + Initalize(); + byte ptr = 0; + CodeOfRequestedMessage = buffer[ptr++]; + + RdsMessageAttribute attribute = attributes[CodeOfRequestedMessage]; + if (attribute == null) + throw new NotImplementedException(String.Format("RDS Message 0x{1:X2}", CodeOfRequestedMessage)); + + if (attribute.NeedsDsn) + Dsn = buffer[ptr++]; + + if (attribute.NeedsPsn) + Psn = buffer[ptr++]; + + FurtherInformation = new byte[buffer.Length - ptr]; + Array.Copy(buffer, ptr, FurtherInformation, 0, buffer.Length - ptr); + } + + public byte[] FurtherInformation { get; private set; } + + public byte? Psn { get; set; } + + public byte? Dsn { get; private set; } + + public byte CodeOfRequestedMessage { get; private set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x40_OdaConfigurationAndShortMessageCommand.cs b/skyscraper8/Rds/Messages/0x40_OdaConfigurationAndShortMessageCommand.cs new file mode 100644 index 0000000..1cd3182 --- /dev/null +++ b/skyscraper8/Rds/Messages/0x40_OdaConfigurationAndShortMessageCommand.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x40,RdsMessageHeaderType.None)] + class OdaConfigurationAndShortMessageCommand : RdsMessage + { + public OdaConfigurationAndShortMessageCommand(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ApplicationGroupType = ms.ReadUInt8(); + AID = ms.ReadUInt16BE(); + BufferConfiguration = ms.ReadUInt8(); + Message = ms.ReadBytes(2); + OdaDataInputTimeout = ms.ReadUInt8(); + } + + public byte OdaDataInputTimeout { get; private set; } + + public byte[] Message { get; private set; } + + public byte BufferConfiguration { get; private set; } + + public ushort AID { get; private set; } + + public byte ApplicationGroupType { get; private set; } + + } +} diff --git a/skyscraper8/Rds/Messages/0x42_OdaFreeFormatGroup.cs b/skyscraper8/Rds/Messages/0x42_OdaFreeFormatGroup.cs new file mode 100644 index 0000000..2297bff --- /dev/null +++ b/skyscraper8/Rds/Messages/0x42_OdaFreeFormatGroup.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x42,RdsMessageHeaderType.None)] + class OdaFreeFormatGroup : RdsMessage + { + public OdaFreeFormatGroup(byte[] buffer) + { + ApplicationGroupTypeCode = buffer[0]; + + byte configuration = buffer[1]; + PrioritySetting = (configuration & 0x30) >> 4; + ModeSelection = (configuration & 0x0c) >> 2; + BufferConfiguration = configuration & 0x03; + + + Blocks = new byte[5]; + Array.Copy(buffer, 2, Blocks, 0, 5); + } + + public byte[] Blocks { get; private set; } + + public int BufferConfiguration { get; private set; } + + public int ModeSelection { get; private set; } + + public int PrioritySetting { get; private set; } + + public byte ApplicationGroupTypeCode { get; private set; } + } +} diff --git a/skyscraper8/Rds/Messages/0x46_OdaDataCommand.cs b/skyscraper8/Rds/Messages/0x46_OdaDataCommand.cs new file mode 100644 index 0000000..ec6c647 --- /dev/null +++ b/skyscraper8/Rds/Messages/0x46_OdaDataCommand.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Rds.Messages +{ + [RdsMessage(0x46,RdsMessageHeaderType.Mel)] + class OdaDataCommand : RdsMessage + { + public OdaDataCommand(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + //buffer.Length == msg element 1 + AID = ms.ReadUInt16BE(); //msg element 2 + 3 + + byte med = ms.ReadUInt8(); //msg element 4 + ShortMessageFlag = (med & 0x40) != 0; + BufferConfiguration = (med & 0x3); + OdaData = ms.ReadBytes(ms.GetAvailableBytes()); //msg element 5 + } + + public byte[] OdaData { get; private set; } + + public int BufferConfiguration { get; private set; } + + public bool ShortMessageFlag { get; private set; } + + public ushort AID { get; private set; } + } +} diff --git a/skyscraper8/Rds/RdsDecoder.cs b/skyscraper8/Rds/RdsDecoder.cs new file mode 100644 index 0000000..ad835f7 --- /dev/null +++ b/skyscraper8/Rds/RdsDecoder.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Rds +{ + class RdsDecoder + { + private static ConstructorInfo[] constructorInfos; + private static RdsMessageAttribute[] messageTypes; + + private static void InitializeMessageTypes() + { + if (constructorInfos == null) + { + constructorInfos = new ConstructorInfo[Byte.MaxValue]; + messageTypes = new RdsMessageAttribute[Byte.MaxValue]; + } + else + return; + + Type[] typeCandidates = typeof(RdsDecoder).Assembly.GetTypes(); + bool validTypeFound = false; + foreach (Type typeCandidate in typeCandidates) + { + Attribute customAttributes = typeCandidate.GetCustomAttributes(typeof(RdsMessageAttribute)).FirstOrDefault(); + if (customAttributes == null) + continue; + + RdsMessageAttribute rdsAttribute = customAttributes as RdsMessageAttribute; + ConstructorInfo constructorInfo = typeCandidate.GetConstructor(new Type[] { typeof(byte[]) }); + if (constructorInfo == null) + throw new RdsException("constructor for {0} missing.", typeCandidate.Name); + messageTypes[rdsAttribute.MessageElementCode] = rdsAttribute; + constructorInfos[rdsAttribute.MessageElementCode] = constructorInfo; + validTypeFound = true; + } + + if (!validTypeFound) + throw new RdsException("failed to load rds message types"); + } + + public RdsMessage DecodeRdsMessage(byte[] packetPayload) + { + if (packetPayload.Length == 2 && packetPayload[0] == 0xfe && packetPayload[1] == 0xff) + return null; + + MemoryStream level1 = new MemoryStream(packetPayload, false); + RdsMessageStream ms = new RdsMessageStream(level1); + + if (ms.ReadUInt8() != 0xfe) + return null; + + //Message container decodieren. + ms.BeginCrcCalculation(); + ushort add = ms.ReadUInt16BE(); + int siteAddress = add & 0xffc0; + int encoderAddress = add & 0x003f; + + byte sqc = ms.ReadUInt8(); + + byte mfl = ms.ReadUInt8(); + + byte[] msg = ms.ReadBytes(mfl); + ms.EndCrcCalculation(); + + ushort crc = ms.ReadUInt16BE(); + if (!ms.ValidateCrc(crc)) + return null; + + if (ms.ReadUInt8() != 0xff) + return null; + + RdsMessage result = DecodeInnerMessage(siteAddress, encoderAddress, sqc, msg); + return result; + } + + private RdsMessage DecodeInnerMessage(int siteAddress, int encoderAddress, byte sequenceCounter, byte[] buffer) + { + // file:///C:/Users/Anwender/Desktop/Pile%20of%20Shame/UECP_6_02_final_060912.pdf + InitializeMessageTypes(); + + MemoryStream ms = new MemoryStream(buffer, false); + byte mec = ms.ReadUInt8(); + if (messageTypes[mec] == null || constructorInfos[mec] == null) + throw new NotImplementedException(String.Format("RDS Message Type {0:X2}", mec)); + + RdsMessageAttribute rdsMessageAttribute = messageTypes[mec]; + ConstructorInfo rdsMessageConstructor = constructorInfos[mec]; + RdsMessage result = null; + switch (rdsMessageAttribute.HeaderType) + { + case RdsMessageHeaderType.Mel: + byte mel = ms.ReadUInt8(); + result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(mel) }); + break; + case RdsMessageHeaderType.None: + result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(ms.GetAvailableBytes()) }); + break; + case RdsMessageHeaderType.DsnPsn: + byte dsn = ms.ReadUInt8(); + byte psn = ms.ReadUInt8(); + result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(ms.GetAvailableBytes()) }); + ((IDsn)result).DataSetNumber = dsn; + ((IPsn)result).ProgrammeServiceNumber = psn; + break; + case RdsMessageHeaderType.DsnPsnMel: + byte dsn2 = ms.ReadUInt8(); + byte psn2 = ms.ReadUInt8(); + byte mel2 = ms.ReadUInt8(); + result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(mel2) }); + ((IDsn)result).DataSetNumber = dsn2; + ((IPsn)result).ProgrammeServiceNumber = psn2; + break; + default: + throw new NotImplementedException(rdsMessageAttribute.HeaderType.ToString()); + } + + result.SiteAddress = siteAddress; + result.EncoderAddress = encoderAddress; + result.SequenceNumber = sequenceCounter; + return result; + } + + } +} diff --git a/skyscraper8/Rds/RdsEventHandler.cs b/skyscraper8/Rds/RdsEventHandler.cs new file mode 100644 index 0000000..f70fc70 --- /dev/null +++ b/skyscraper8/Rds/RdsEventHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Rds.Messages; + +namespace skyscraper5.Rds +{ + interface RdsEventHandler + { + void SetRdsProgrammeServiceName(int programNumber, string programmeService2); + bool TestCanHandleRdsMessages(int programNumber); + void SetRdsRadioText(int programNumber, string text); + void SetCurrentTime(DateTime currentTime); + void SetCurrentProgrammeType(int programNumber, PTY.ProgrammeTypeCodes pty); + void MarkAsRdsTrafficInformationProgramme(int programNumber); + } +} diff --git a/skyscraper8/Rds/RdsException.cs b/skyscraper8/Rds/RdsException.cs new file mode 100644 index 0000000..3c29bd7 --- /dev/null +++ b/skyscraper8/Rds/RdsException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds +{ + class RdsException : SkyscraperException + { + public RdsException(string s, params object[] args) + : base(String.Format(s, args)) + { + } + } +} diff --git a/skyscraper8/Rds/RdsMessage.cs b/skyscraper8/Rds/RdsMessage.cs new file mode 100644 index 0000000..9017b33 --- /dev/null +++ b/skyscraper8/Rds/RdsMessage.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds +{ + public class RdsMessage + { + public int SiteAddress { get; set; } + public int EncoderAddress { get; set; } + public byte SequenceNumber { get; set; } + + public RdsMessageAttribute MessageAttribute + { + get + { + Type type = this.GetType(); + object[] customAttributes = type.GetCustomAttributes(typeof(RdsMessageAttribute),false); + if (customAttributes.Length == 0) + return null; + else + return (RdsMessageAttribute)customAttributes[0]; + } + } + } +} diff --git a/skyscraper8/Rds/RdsMessageAttribute.cs b/skyscraper8/Rds/RdsMessageAttribute.cs new file mode 100644 index 0000000..a4130f4 --- /dev/null +++ b/skyscraper8/Rds/RdsMessageAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class RdsMessageAttribute : Attribute + { + public byte MessageElementCode { get; } + public RdsMessageHeaderType HeaderType { get; } + + public RdsMessageAttribute(byte messageElementCode,RdsMessageHeaderType headerType) + { + MessageElementCode = messageElementCode; + HeaderType = headerType; + } + + public bool NeedsDsn => HeaderType == RdsMessageHeaderType.DsnMel || + HeaderType == RdsMessageHeaderType.DsnPsnMel || + HeaderType == RdsMessageHeaderType.DsnPsn; + + public bool NeedsPsn => HeaderType == RdsMessageHeaderType.PsnMel || + HeaderType == RdsMessageHeaderType.DsnPsn || + HeaderType == RdsMessageHeaderType.DsnPsnMel; + } +} diff --git a/skyscraper8/Rds/RdsMessageHeaderType.cs b/skyscraper8/Rds/RdsMessageHeaderType.cs new file mode 100644 index 0000000..af71774 --- /dev/null +++ b/skyscraper8/Rds/RdsMessageHeaderType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Rds +{ + public enum RdsMessageHeaderType + { + Mel, + None, + DsnPsnMel, + DsnMel, + PsnMel, + DsnPsn + } +} diff --git a/skyscraper8/Rds/RdsMessageStream.cs b/skyscraper8/Rds/RdsMessageStream.cs new file mode 100644 index 0000000..33ef739 --- /dev/null +++ b/skyscraper8/Rds/RdsMessageStream.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Rds +{ + class RdsMessageStream : Stream + { + private readonly Stream _wrapped; + + public RdsMessageStream(Stream wrapped) + { + _wrapped = wrapped; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + byte[] inputByte = new byte[1]; + int result = 0; + for (int i = 0; i < count; i++) + { + if (_wrapped.Read(inputByte, 0, 1) != 1) + throw new IOException("failed to read byte"); + UpdateCrc(inputByte[0]); + if (inputByte[0] == 0xfd) + { + if (_wrapped.Read(inputByte, 0, 1) != 1) + throw new IOException("failed to read byte"); + UpdateCrc(inputByte[0]); + if (inputByte[0] == 0x00) + buffer[offset++] = 0xfd; + else if (inputByte[0] == 0x01) + buffer[offset++] = 0xfe; + else if (inputByte[0] == 0x02) + buffer[offset++] = 0xff; + else + throw new RdsException("invalid rds message"); + result++; + } + else + { + buffer[offset++] = inputByte[0]; + result++; + } + } + return result; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => _wrapped.Length; + + public override long Position + { + get => _wrapped.Position; + set => throw new NotSupportedException(); + } + + private bool crcEnabled; + private ushort tempCrc; + public void BeginCrcCalculation() + { + if (crcEnabled) + throw new InvalidOperationException("crc already running"); + crcEnabled = true; + tempCrc = 0xffff; + } + + public void EndCrcCalculation() + { + if (!crcEnabled) + throw new InvalidOperationException("crc not running"); + crcEnabled = false; + tempCrc = (ushort)((tempCrc ^ 0xFFFF) & 0xFFFF); + } + + private void UpdateCrc(byte value) + { + if (!crcEnabled) + return; + + tempCrc = (ushort)((((tempCrc >> 8) & 0xFF) | (tempCrc << 8)) & 0xFFFF); + tempCrc ^= value; + tempCrc ^= (ushort)(((tempCrc & 0xff) >> 4) & 0xFFFF); + tempCrc ^= (ushort)(((tempCrc << 8) << 4) & 0xFFFF); + tempCrc ^= (ushort)((((tempCrc & 0xff) << 4) << 1) & 0xFFFF); + } + + public bool ValidateCrc(byte l, byte r) + { + return ValidateCrc((ushort)((l << 8) + r)); + } + + public bool ValidateCrc(ushort crc) + { + return crc == tempCrc; + } + } +} diff --git a/skyscraper8/Rds/RdsPesProcessor.cs b/skyscraper8/Rds/RdsPesProcessor.cs new file mode 100644 index 0000000..4c65f2e --- /dev/null +++ b/skyscraper8/Rds/RdsPesProcessor.cs @@ -0,0 +1,77 @@ +using System; +using skyscraper5.Mpeg2; +using skyscraper5.Rds.Messages; + +namespace skyscraper5.Rds +{ + class RdsPesProcessor : IPesProcessor + { + public RdsPesProcessor(int _sourcePid, int _programNumber, RdsEventHandler _eventHandler) + { + programNumber = _programNumber; + decoder = new RdsDecoder(); + sourcePid = _sourcePid; + eventHandler = _eventHandler; + } + private RdsDecoder decoder; + private int sourcePid; + private RdsEventHandler eventHandler; + private int programNumber; + private bool gotMessages; + + public void ProcessPesPacket(PesPacket packet) + { + if (packet.Payload == null) + return; + RdsMessage rdsMessage = decoder.DecodeRdsMessage(packet.Payload); + if (rdsMessage == null) + return; + if (!gotMessages) + { + if (eventHandler.TestCanHandleRdsMessages(programNumber)) + gotMessages = true; + else + return; + } + + string rdsMessageType = rdsMessage.GetType().Name; + switch (rdsMessageType) + { + case nameof(OdaDataCommand): + OdaDataCommand odc = (OdaDataCommand)rdsMessage; + //ProcessOdaDataCommand(odc); + //File.WriteAllBytes(String.Format("odaData_{0:X4}_{1:X16}.bin", sourcePid, packet.PTS), odc.OdaData); + break; + case nameof(OdaConfigurationAndShortMessageCommand): + //no clue what to do with these... + OdaConfigurationAndShortMessageCommand ocasmc = (OdaConfigurationAndShortMessageCommand)rdsMessage; + //File.WriteAllBytes(String.Format("odaShortMessage_{0:X4}_{1:X16}.bin", sourcePid, packet.PTS), ocasmc.Message); + break; + case nameof(PS): + eventHandler.SetRdsProgrammeServiceName(programNumber,((PS)rdsMessage).ProgrammeService2); + break; + case nameof(RT): + eventHandler.SetRdsRadioText(programNumber, ((RT)rdsMessage).Text); + break; + case nameof(OdaFreeFormatGroup): + //no clue what to do with this... + OdaFreeFormatGroup offg = (OdaFreeFormatGroup)rdsMessage; + //File.WriteAllBytes(String.Format("odaFreeFormat_{0:X4}_{1:X16}.bin", sourcePid, packet.PTS), offg.Blocks); + break; + case nameof(RealTimeClock): + eventHandler.SetCurrentTime(((RealTimeClock)rdsMessage).Timestamp); + break; + case nameof(PTY): + eventHandler.SetCurrentProgrammeType(programNumber,((PTY)rdsMessage).Pty); + break; + case nameof(TA_TP): + TA_TP tatp = (TA_TP)rdsMessage; + if (tatp.TP) + eventHandler.MarkAsRdsTrafficInformationProgramme(programNumber); + break; + default: + throw new NotImplementedException(rdsMessageType); + } + } + } +} diff --git a/skyscraper8/Scorcher/CatGenerator.cs b/skyscraper8/Scorcher/CatGenerator.cs new file mode 100644 index 0000000..0f34acc --- /dev/null +++ b/skyscraper8/Scorcher/CatGenerator.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + internal class CatGenerator : PsiGenerator + { + public CatGenerator() : base(new TimeSpan(0,0,4), 0x0001) + { + caDescriptors = new List(); + } + + private List caDescriptors; + private byte[] SerializeDescriptors() + { + MemoryStream ms = new MemoryStream(); + foreach (CaDescriptor descriptor in caDescriptors) + { + ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag + + byte[] descriptorBytes = descriptor.Serialize(); + ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length + ms.Write(descriptorBytes, 0, descriptorBytes.Length); + } + return ms.ToArray(); + } + public override byte[] BuildPsi() + { + byte[] descriptorBuffer = SerializeDescriptors(); + int sectionLength = 5 + descriptorBuffer.Length + 4; + + //------------------------------------- + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8(0x01); //table id + + byte byte1 = 0; + byte1 |= 0x80; //section syntax indicator + //'0' + byte1 |= 0x30; //reserved + byte1 += (byte)((sectionLength & 0x0300) >> 8); + ms.WriteUInt8(byte1); //section length, part 1 + + byte byte2 = (byte)(sectionLength & 0x00ff); + ms.WriteUInt8(byte2); //section length, part 2 + + ms.WriteUInt16BE(0xffff); //reserved, part 1 + + byte byte3 = 0; + byte3 |= 0xc0; //reserved, part 2 + byte3 |= (byte)((versionNumber << 1) & 0xff); + byte3 |= 0x01; //current next indicator + ms.WriteUInt8(byte3); + + ms.WriteUInt8(0x00); //section number + ms.WriteUInt8(0x00); //last_section_number + + ms.Write(descriptorBuffer, 0, descriptorBuffer.Length); + + uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position); + ms.WriteUInt32BE(crc); + + return ms.ToArray(); + } + + public void AddCaSystem(ushort caSystemId, ushort caPid, byte[] privateData) + { + CaDescriptor caDescriptor = new CaDescriptor(caSystemId, caPid, privateData); + caDescriptors.Add(caDescriptor); + versionNumber++; + } + } +} diff --git a/skyscraper8/Scorcher/DataPipingGenerator.cs b/skyscraper8/Scorcher/DataPipingGenerator.cs new file mode 100644 index 0000000..8e75cc7 --- /dev/null +++ b/skyscraper8/Scorcher/DataPipingGenerator.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Scorcher +{ + internal class DataPipingGenerator : IPacketGenerator + { + public DataPipingGenerator(uint pid) + { + this.backlog = new Queue(); + this.pid = pid; + this.pusi = true; + } + + private bool pusi; + private Queue backlog; + private long backlogLength; + public void Append(byte[] buffer) + { + backlog.Enqueue(buffer); + backlogLength += buffer.Length; + } + + private byte[] currentItem; + private int currentItemLength; + private int currentItemOffset; + private readonly uint pid; + + private int CurrentItemRemaining + { + get + { + return currentItem != null ? currentItemLength - currentItemOffset : 0; + } + } + + public bool PacketAvailable() + { + if ((CurrentItemRemaining + backlogLength) > 184) + return true; + + return false; + } + + private int continuity; + public byte[] GeneratePacket() + { + byte[] buildHeader = TsPacketHeaderBuilder.BuildHeader(pusi, pid, continuity++); + int bytesLeft = 188; + byte[] buffer = new byte[188]; + Array.Copy(buildHeader, 0, buffer, 0, buildHeader.Length); + bytesLeft -= 4; + while (bytesLeft > 0) + { + if (CurrentItemRemaining == 0) + { + currentItem = backlog.Dequeue(); + currentItemLength = currentItem.Length; + currentItemOffset = 0; + backlogLength -= currentItem.Length; + } + + int copyLength = Math.Min(CurrentItemRemaining, bytesLeft); + Array.Copy(currentItem, 0, buffer, 188 - bytesLeft, copyLength); + currentItemOffset += copyLength; + bytesLeft -= copyLength; + } + return buffer; + } + } +} diff --git a/skyscraper8/Scorcher/DummyTsGenerator.cs b/skyscraper8/Scorcher/DummyTsGenerator.cs new file mode 100644 index 0000000..cfcad92 --- /dev/null +++ b/skyscraper8/Scorcher/DummyTsGenerator.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Scorcher +{ + internal class DummyTsGenerator + { + public DummyTsGenerator() + { + fi = new FileInfo(string.Format("{0}.ts", DateTime.Now.Ticks)); + outputStream = fi.OpenWrite(); + tsGeneratorStream = new TsGeneratorStream(); + } + + private FileInfo fi; + private FileStream outputStream; + private TsGeneratorStream tsGeneratorStream; + + public void Run() + { + byte[] buffer = new byte[188]; + + Random rng = new Random(); + tsGeneratorStream.TransportStreamId = (0x0111); + PmtGenerator prog1 = tsGeneratorStream.AddProgram(0x0222, 0x0333); + prog1.AddDescriptor(new PrivateDataSpecifierDescriptor(0x534f5048)); + PmtGenerator prog2 = tsGeneratorStream.AddProgram(0x0444, 0x0555); + prog2.AddDescriptor(new PrivateDataSpecifierDescriptor(0x4f504849)); + PmtGenerator prog3 = tsGeneratorStream.AddProgram(0x0666, 0x0777); + prog3.AddDescriptor(new PrivateDataSpecifierDescriptor(0x50484941)); + PmtGeneratorStream stream1 = prog3.AddStream(0x80, 0x0888); + stream1.AddDescriptor(new PrivateDataSpecifierDescriptor(0x53504831)); + PmtGeneratorStream stream2 = prog3.AddStream(0x81, 0x0999); + stream2.AddDescriptor(new PrivateDataSpecifierDescriptor(0x53504832)); + + tsGeneratorStream.AddCaSystem(0x534f, 0x0bbb, Encoding.ASCII.GetBytes("Sophia")); + tsGeneratorStream.GenerateTdt = true; + tsGeneratorStream.GenerateTot = false; + tsGeneratorStream.OriginalNetworkId = 0x0ccc; + + SdtGeneratorService sdt1 = tsGeneratorStream.AddService(0x0222); + sdt1.AddName("Skyscraper Test Service 1", "sophia.net", ServiceDescriptor.ServiceTypeCoding.DataBroadcast); + sdt1.AddDescriptor(new PrivateDataSpecifierDescriptor(0x53504831)); + SdtGeneratorService sdt2 = tsGeneratorStream.AddService(0x0444); + sdt2.AddName("Skyscraper Test Service 2", "sophia.net", ServiceDescriptor.ServiceTypeCoding.DataBroadcast); + sdt2.AddDescriptor(new PrivateDataSpecifierDescriptor(0x53504832)); + SdtGeneratorService sdt3 = tsGeneratorStream.AddService(0x0666); + sdt3.AddName("Skyscraper Test Service 3", "sophia.net", ServiceDescriptor.ServiceTypeCoding.DataBroadcast); + sdt3.AddDescriptor(new PrivateDataSpecifierDescriptor(0x53504833)); + + tsGeneratorStream.Write(Encoding.UTF8.GetBytes("Weit hinten, hinter den Wortbergen, fern der Länder Vokalien und Konsonantien leben die Blindtexte. Abgeschieden wohnen sie in Buchstabhausen an der Küste des Semantik, eines großen Sprachozeans. Ein kleines Bächlein namens Duden fließt durch ihren Ort und versorgt sie mit den nötigen Regelialien. Es ist ein paradiesmatisches Land, in dem einem gebratene Satzteile in den Mund fliegen. Nicht einmal von der allmächtigen Interpunktion werden die Blindtexte beherrscht – ein geradezu unorthographisches Leben. Eines Tages aber beschloß eine kleine Zeile Blindtext, ihr Name war Lorem Ipsum, hinaus zu gehen in die weite Grammatik. Der große Oxmox riet ihr davon ab, da es dort wimmele von bösen Kommata, wilden Fragezeichen und hinterhältigen Semikoli, doch das Blindtextchen ließ sich nicht beirren. Es packte seine sieben Versalien, schob sich sein Initial in den Gürtel und machte sich auf den Weg. Als es die ersten Hügel des Kursivgebirges erklommen hatte, warf es einen letzten Blick zurück auf die Skyline seiner Heimatstadt Buchstabhausen, die Headline von Alphabetdorf und die Subline seiner eigenen Straße, der Zeilengasse. Wehmütig lief ihm eine rhetorische Frage über die Wange, dann setzte es seinen Weg fort. Unterwegs traf es eine Copy. Die Copy warnte das Blindtextchen, da, wo sie herkäme wäre sie zigmal umgeschrieben worden und alles, was von ihrem Ursprung noch übrig wäre, sei das Wort \"und\" und das Blindtextchen solle umkehren und wieder in sein eigenes, sicheres Land zurückkehren. Doch alles Gutzureden konnte es nicht überzeugen und so dauerte es nicht lange, bis ihm ein paar heimtückische Werbetexter auflauerten, es mit Longe und Parole betrunken machten und es dann in ihre Agentur schleppten, wo sie es für ihre Projekte wieder und wieder mißbrauchten. Und wenn es nicht umgeschrieben wurde, dann benutzen Sie es immernoch.")); + + for (int i = 0; i < 20; i++) + { + for (int j = 0; j < 100; j++) + { + tsGeneratorStream.Read(buffer, 0, 188); + outputStream.Write(buffer, 0, 188); + } + + Thread.Sleep(1000); + } + + outputStream.Flush(); + outputStream.Close(); + + outputStream = fi.OpenRead(); + TsContext tsContext = new TsContext(); + SkyscraperContext sc = new SkyscraperContext(tsContext, null, null, 0); + sc.IngestFromStream(outputStream); + outputStream.Close(); + + } + } +} diff --git a/skyscraper8/Scorcher/IPacketGenerator.cs b/skyscraper8/Scorcher/IPacketGenerator.cs new file mode 100644 index 0000000..7fad93e --- /dev/null +++ b/skyscraper8/Scorcher/IPacketGenerator.cs @@ -0,0 +1,8 @@ +namespace skyscraper5.Scorcher +{ + public interface IPacketGenerator + { + bool PacketAvailable(); + byte[] GeneratePacket(); + } +} diff --git a/skyscraper8/Scorcher/NullPacketGenerator.cs b/skyscraper8/Scorcher/NullPacketGenerator.cs new file mode 100644 index 0000000..4c3f7ce --- /dev/null +++ b/skyscraper8/Scorcher/NullPacketGenerator.cs @@ -0,0 +1,22 @@ +using System; + +namespace skyscraper5.Scorcher +{ + internal class NullPacketGenerator : IPacketGenerator + { + public bool PacketAvailable() + { + return true; + } + + public byte[] GeneratePacket() + { + byte[] buffer = new byte[188]; + Array.Fill(buffer, (byte)0xff, 0, 188); + buffer[0] = (byte)'G'; + buffer[1] = 0x1f; + buffer[3] = 0x10; + return buffer; + } + } +} diff --git a/skyscraper8/Scorcher/PatGenerator.cs b/skyscraper8/Scorcher/PatGenerator.cs new file mode 100644 index 0000000..8fe3ee8 --- /dev/null +++ b/skyscraper8/Scorcher/PatGenerator.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + internal class PatGenerator : PsiGenerator + { + + public PatGenerator(ushort transportStreamId) : base(new TimeSpan(0,0,0,2), 0x0000) + { + TransportStreamId = transportStreamId; + content = new Dictionary(); + } + + public ushort TransportStreamId { get; } + private Dictionary content; + + public void AddProgram(ushort programId, ushort pmtPid) + { + content.Add(programId, pmtPid); + versionNumber++; + } + + public override byte[] BuildPsi() + { + ushort sectionLength = (ushort)((content.Count * 4) + 5 + 4); + sectionLength &= 0x03ff; + if (versionNumber > 32) + versionNumber %= 32; + + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8(0); //Table ID + + byte byte2 = 0; + byte2 |= 0x80; //section syntax indicator + //byte2 &= 0x40; //'0' + byte2 |= 0x30; //reserved + byte2 += (byte)((sectionLength & 0x0300) >> 8); + ms.WriteUInt8(byte2); //section length, part 1 + + byte byte3 = (byte)(sectionLength & 0x00ff); + ms.WriteUInt8(byte3); //section length, part 2 + + ms.WriteUInt16BE(TransportStreamId); //transport stream id + + + byte byte4 = 0; + byte4 |= 0xc0; //reserved + byte4 |= (byte)(versionNumber << 1); //version number + byte4 |= 0x01; //current_next_indicator + ms.WriteUInt8(byte4); + + ms.WriteUInt8(0); //section number + ms.WriteUInt8(0); //last section number + foreach (var (program_number, program_map_pid) in content) + { + ms.WriteUInt16BE(program_number); + ms.WriteUInt16BE((ushort)(program_map_pid & 0x1fff)); + } + + uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position); + ms.WriteUInt32BE(crc); + + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Scorcher/PmtGenerator.cs b/skyscraper8/Scorcher/PmtGenerator.cs new file mode 100644 index 0000000..e6ec937 --- /dev/null +++ b/skyscraper8/Scorcher/PmtGenerator.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + public class PmtGenerator : PsiGenerator + { + public ushort ProgramNumber { get; } + public ushort PcrPid { get; } + + public PmtGenerator(uint pid, ushort programNumber, ushort PcrPid) : base(new TimeSpan(0,0,3), pid) + { + ProgramNumber = programNumber; + this.PcrPid = PcrPid; + descriptors = new List(); + streams = new List(); + } + + private List descriptors; + private List streams; + + private byte[] SerializeDescriptors() + { + MemoryStream ms = new MemoryStream(); + foreach (TsDescriptor descriptor in descriptors) + { + ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag + + byte[] descriptorBytes = descriptor.Serialize(); + ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length + ms.Write(descriptorBytes, 0, descriptorBytes.Length); + } + return ms.ToArray(); + } + + private byte[] SerializeStreams() + { + MemoryStream ms = new MemoryStream(); + foreach (PmtGeneratorStream stream in streams) + { + ms.WriteUInt8(stream.StreamType); + ms.WriteUInt16BE((ushort)(stream.ElementaryPid | 0xe000)); + + byte[] descriptorLoop = stream.SerializeDescriptors(); + ms.WriteUInt16BE((ushort)(descriptorLoop.Length | 0xf000)); //ES_Info_length + ms.Write(descriptorLoop, 0, descriptorLoop.Length); + } + + return ms.ToArray(); + } + + public override byte[] BuildPsi() + { + byte[] descriptorBuffer = SerializeDescriptors(); + int programInfoLength = descriptorBuffer.Length; + byte[] streamBuffer = SerializeStreams(); + int sectionLength = 9 + descriptorBuffer.Length + streamBuffer.Length + 4; + + //TODO: calculate section length here + + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8(0x02); //table_id + + byte byte2 = 0; + byte2 |= 0x80; //section_syntax_indicator + //'0' + byte2 |= 0x30; //reserved + byte2 += (byte)((sectionLength & 0x0300) >> 8); + ms.WriteUInt8(byte2); //section length, part 1 + + byte byte3 = (byte)(sectionLength & 0x00ff); + ms.WriteUInt8(byte3); //section length, part 2 + + ms.WriteUInt16BE(ProgramNumber); + + byte byte4 = 0; + byte4 |= 0xc0; //reserved + byte4 |= (byte)((versionNumber & 0x1f) << 1); //version number + byte4 |= 0x01; //current next indicator + ms.WriteUInt8(byte4); + + ms.WriteUInt8(0); //section number + ms.WriteUInt8(0); //last section number + + ms.WriteUInt16BE((ushort)(PcrPid | 0xe000)); //reserved & PCR_PID + + ms.WriteUInt16BE((ushort)(programInfoLength | 0xf000)); //reserved & program info length + + ms.Write(descriptorBuffer, 0, descriptorBuffer.Length); //descriptors() + + ms.Write(streamBuffer, 0, streamBuffer.Length); + + uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position); + ms.WriteUInt32BE(crc); + + return ms.ToArray(); + } + + public void AddDescriptor(PrivateDataSpecifierDescriptor privateDataSpecifierDescriptor) + { + descriptors.Add(privateDataSpecifierDescriptor); + versionNumber++; + } + + public PmtGeneratorStream AddStream(byte streamType, ushort pid) + { + PmtGeneratorStream stream = new PmtGeneratorStream(streamType, pid); + streams.Add(stream); + versionNumber++; + return stream; + } + } +} diff --git a/skyscraper8/Scorcher/PmtGeneratorStream.cs b/skyscraper8/Scorcher/PmtGeneratorStream.cs new file mode 100644 index 0000000..211b4d7 --- /dev/null +++ b/skyscraper8/Scorcher/PmtGeneratorStream.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + public class PmtGeneratorStream + { + public PmtGeneratorStream(byte type, ushort pid) + { + descriptors = new List(); + this.StreamType = type; + this.ElementaryPid = pid; + } + + private List descriptors; + public byte StreamType { get; } + public ushort ElementaryPid { get; } + + public byte[] SerializeDescriptors() + { + MemoryStream ms = new MemoryStream(); + foreach (TsDescriptor descriptor in descriptors) + { + ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag + + byte[] descriptorBytes = descriptor.Serialize(); + ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length + ms.Write(descriptorBytes, 0, descriptorBytes.Length); + } + return ms.ToArray(); + } + + public void AddDescriptor(TsDescriptor privateDataSpecifierDescriptor) + { + descriptors.Add(privateDataSpecifierDescriptor); + } + } +} diff --git a/skyscraper8/Scorcher/PsiGenerator.cs b/skyscraper8/Scorcher/PsiGenerator.cs new file mode 100644 index 0000000..74669b7 --- /dev/null +++ b/skyscraper8/Scorcher/PsiGenerator.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Scorcher +{ + public abstract class PsiGenerator : IPacketGenerator + { + protected PsiGenerator(TimeSpan timeInbetween, uint pid) + { + this.packetQueue = new Queue(); + this.apperanceTimeSpan = timeInbetween; + this.pid = pid; + } + + private uint pid; + private TimeSpan apperanceTimeSpan; + private DateTime lastBroadcast; + private bool pusi; + private int continuity; + private Queue packetQueue; + protected int versionNumber; + + public abstract byte[] BuildPsi(); + + private void EnqueuePackets() + { + byte[] buildPsi = BuildPsi(); + int numPackets = 0; + for (int i = 0; i < buildPsi.Length; numPackets++) + { + byte[] newPacket = new byte[188]; + Array.Fill(newPacket, (byte)0xff); + byte[] buildHeader = TsPacketHeaderBuilder.BuildHeader(numPackets == 0, pid, continuity++); + Array.Copy(buildHeader, 0, newPacket, 0, buildHeader.Length); + + byte packetSizeLeft = 188; + packetSizeLeft -= (byte)buildHeader.Length; + if (numPackets == 0) + { + newPacket[buildHeader.Length] = 0; + packetSizeLeft--; + } + + int copySize = Math.Min(packetSizeLeft, buildPsi.Length - i); + Array.Copy(buildPsi, i, newPacket, 188 - packetSizeLeft, copySize); + i += copySize; + packetQueue.Enqueue(newPacket); + } + } + + public bool PacketAvailable() + { + if (packetQueue.Count > 0) + return true; + + DateTime whenNeeded = lastBroadcast + apperanceTimeSpan; + return DateTime.Now >= whenNeeded; + } + + public byte[] GeneratePacket() + { + if (packetQueue.Count == 0) + EnqueuePackets(); + + byte[] result = packetQueue.Dequeue(); + lastBroadcast = DateTime.Now; + return result; + } + } +} diff --git a/skyscraper8/Scorcher/SdtGenerator.cs b/skyscraper8/Scorcher/SdtGenerator.cs new file mode 100644 index 0000000..46b8a21 --- /dev/null +++ b/skyscraper8/Scorcher/SdtGenerator.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + internal class SdtGenerator : PsiGenerator + { + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + + public SdtGenerator(ushort transportStreamId, ushort originalNetworkId) : base(new TimeSpan(0, 0, 4), 0x0011) + { + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + services = new List(); + } + + public SdtGeneratorService CreateService(ushort serviceId, bool eitScheduleFlag = false, + bool eitPresentFollowingFlag = false, RunningStatus runningStatus = RunningStatus.Running, + bool freeCaMode = false) + { + SdtGeneratorService result = new SdtGeneratorService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode); + services.Add(result); + return result; + } + + private List services; + + private byte[] SerializeServices() + { + MemoryStream ms = new MemoryStream(); + foreach (SdtGeneratorService service in services) + { + byte[] descriptorBuffer = service.SerializeDescriptors(); + ushort descriptorLength = (ushort)descriptorBuffer.Length; + + ms.WriteUInt16BE(service.ServiceId); + + byte byteA = 0; + byteA |= 0xfc; //reserved + if (service.EitScheduleFlag) + byteA |= 0x02; + if (service.EitPresentFollowingFlag) + byteA |= 0x01; + ms.WriteUInt8(byteA); + + byte byteB = 0; + byteB |= (byte)((int)service.RunningStatus << 5); + if (service.FreeCaMode) + byteB |= 0x10; + byteB |= (byte)((descriptorLength & 0x0f00) >> 8); + ms.WriteUInt8(byteB); + + ms.WriteUInt8((byte)(descriptorLength & 0x00ff)); + ms.Write(descriptorBuffer, 0, descriptorBuffer.Length); + } + + return ms.ToArray(); + } + + public override byte[] BuildPsi() + { + byte[] serializedServices = SerializeServices(); + + ushort sectionLength = (ushort)(2 + 3 + 2 + 1 + serializedServices.Length + 4); //TODO: calculate section length + if (sectionLength > 1021) + throw new Exception("section too long"); + sectionLength &= 0x3fff; + + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8(0x42); + + byte byteA = 0; + byteA |= 0x80; //section_syntax_indicator + byteA |= 0x40; //reserved_future_use + byteA |= 0x30; //reserved + byteA |= (byte)(sectionLength >> 8); + ms.WriteUInt8(byteA); + + byte byteB = (byte)(sectionLength & 0x00ff); + ms.WriteUInt8(byteB); + + ms.WriteUInt16BE(TransportStreamId); + + byte byteC = 0; + byteC |= 0xc0; //reserved + byteC |= (byte)(versionNumber << 1); //version_number + byteC |= 0x01; //current_next_indicator + ms.WriteUInt8(byteC); + + ms.WriteUInt8(0x00); //section number + ms.WriteUInt8(0x00); //last section number + + ms.WriteUInt16BE(OriginalNetworkId); //original network id + ms.WriteUInt8(0xff); //reserved future use + + ms.Write(serializedServices, 0, serializedServices.Length); + + uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position); + ms.WriteUInt32BE(crc); + + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Scorcher/SdtGeneratorService.cs b/skyscraper8/Scorcher/SdtGeneratorService.cs new file mode 100644 index 0000000..df474c8 --- /dev/null +++ b/skyscraper8/Scorcher/SdtGeneratorService.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + public class SdtGeneratorService + { + public SdtGeneratorService(ushort serviceId, bool eitScheduleFlag = false, bool eitPresentFollowingFlag = false, RunningStatus runningStatus = RunningStatus.Running, bool freeCaMode = false) + { + ServiceId = serviceId; + EitScheduleFlag = eitScheduleFlag; + EitPresentFollowingFlag = eitPresentFollowingFlag; + descriptors = new List(); + RunningStatus = runningStatus; + FreeCaMode = false; + } + + public ushort ServiceId { get; private set; } + public bool EitScheduleFlag { get; } + public bool EitPresentFollowingFlag { get; private set; } + public RunningStatus RunningStatus { get; set; } + public bool FreeCaMode { get; set; } + + private List descriptors; + + public byte[] SerializeDescriptors() + { + MemoryStream ms = new MemoryStream(); + foreach (TsDescriptor descriptor in descriptors) + { + ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag + + byte[] descriptorBytes = descriptor.Serialize(); + ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length + ms.Write(descriptorBytes, 0, descriptorBytes.Length); + } + return ms.ToArray(); + } + + private bool hasName; + + public bool AddName(string name, string providerName, ServiceDescriptor.ServiceTypeCoding type) + { + if (hasName) + return false; + + ServiceDescriptor sd = new ServiceDescriptor(name, providerName, type); + descriptors.Add(sd); + hasName = true; + return true; + } + + public void AddDescriptor(TsDescriptor descriptor) + { + descriptors.Add(descriptor); + } + } +} diff --git a/skyscraper8/Scorcher/TdtGenerator.cs b/skyscraper8/Scorcher/TdtGenerator.cs new file mode 100644 index 0000000..2541ba8 --- /dev/null +++ b/skyscraper8/Scorcher/TdtGenerator.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + internal class TdtGenerator : PsiGenerator + { + + private static long MilliSecPerDay = (24 * 3600) * 1000; + private static long JulianEpochOffset = -40587 * MilliSecPerDay; + + internal static byte[] EncodeMjd(DateTime dt) + { + //shamelessly stolen from TSDuck's tsMJD.cpp + long time_ms = dt.ToUnixTime() * 1000; + + if (time_ms < JulianEpochOffset) + { + return null; + } + + long d = (time_ms - JulianEpochOffset) / 1000; //seconds since MJD epoch + byte[] days = BitConverter.GetBytes((ushort)(d / (24 * 3600))); + if (BitConverter.IsLittleEndian) + (days[1], days[0]) = (days[0], days[1]); + byte[] result = new byte[5]; + result[0] = days[0]; + result[1] = days[1]; + result[2] = EncodeBCD((int)((d / 3600) % 24)); + result[3] = EncodeBCD((int)((d / 60) % 60)); + result[4] = EncodeBCD((int)(d % 60)); + return result; + } + + private static byte EncodeBCD(int value) + { + byte result = 0; + result += (byte)(value % 10); + value /= 10; + result += (byte)((value % 10) << 4); + return result; + } + + public TdtGenerator() : base(new TimeSpan(0,0,0,1), 0x0014) + { + } + + public override byte[] BuildPsi() + { + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8(0x70); + + byte byte1 = 0; + //section syntax indicator = 0 + byte1 |= 0x40; //reserved future_use + byte1 |= 0x30; + ms.WriteUInt8(byte1); + + ms.WriteUInt8(5); //Section_length (always 5) + ms.Write(EncodeMjd(DateTime.Now), 0, 5); + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Scorcher/TotGenerator.cs b/skyscraper8/Scorcher/TotGenerator.cs new file mode 100644 index 0000000..e75f599 --- /dev/null +++ b/skyscraper8/Scorcher/TotGenerator.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scorcher +{ + internal class TotGenerator : PsiGenerator + { + public TotGenerator() : base(new TimeSpan(0,0,1), 0x0014) + { + descriptors = new List(); + } + + private List descriptors; + + private byte[] SerializeDescriptors() + { + MemoryStream ms = new MemoryStream(); + foreach (TsDescriptor descriptor in descriptors) + { + ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag + + byte[] descriptorBytes = descriptor.Serialize(); + ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length + ms.Write(descriptorBytes, 0, descriptorBytes.Length); + } + return ms.ToArray(); + } + + public override byte[] BuildPsi() + { + byte[] descriptors = SerializeDescriptors(); + int descriptorsLoopLength = descriptors.Length; + int sectionLength = 5 + 2 + descriptorsLoopLength + 4; + + //---------------------------------- + MemoryStream ms = new MemoryStream(); + ms.WriteUInt8(0x73); + + byte byte1 = 0; + //section syntax indicator + byte1 |= 0x40; //reserved_future_use + byte1 |= 0x30; //reserved + byte1 += (byte)((sectionLength & 0x0f00) >> 8); + ms.WriteUInt8(byte1); //section length, part 1 + + byte byte2 = (byte)(sectionLength & 0x00ff); + ms.WriteUInt8(byte2); //section length, part 2 + + ms.Write(TdtGenerator.EncodeMjd(DateTime.Now), 0, 5); + + byte byte3 = 0; + byte3 |= 0xf0; //reserved + byte3 += (byte)((descriptorsLoopLength & 0x0f00) >> 8); + ms.WriteUInt8(byte3); //section length, part 1 + + byte byte4 = (byte)(descriptorsLoopLength & 0x00ff); + ms.WriteUInt8(byte4); //section length, part 2 + + ms.Write(descriptors, 0, descriptors.Length); + + uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position); + ms.WriteUInt32BE(crc); + + return ms.ToArray(); + } + } +} diff --git a/skyscraper8/Scorcher/TsGeneratorStream.cs b/skyscraper8/Scorcher/TsGeneratorStream.cs new file mode 100644 index 0000000..ed173b4 --- /dev/null +++ b/skyscraper8/Scorcher/TsGeneratorStream.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.IO; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper; + +namespace skyscraper5.Scorcher +{ + public class TsGeneratorStream : Stream + { + public TsGeneratorStream() + { + nullPacketGenerator = new NullPacketGenerator(); + unusedPids = new List(); + for (uint i = 32; i < 8186; i++) + { + unusedPids.Add(i); + } + unusedPrograms = new List(); + for (ushort i = ushort.MinValue; i < ushort.MaxValue; i++) + { + unusedPrograms.Add(i); + } + rng = new Random(); + } + + + private SdtGenerator sdtGenerator; + private TotGenerator totGenerator; + private TdtGenerator tdtGenerator; + private CatGenerator catGenerator; + private List pmtGenerators; + private NullPacketGenerator nullPacketGenerator; + private DataPipingGenerator dataPipingGenerator; + private PatGenerator patGenerator; + + private Random rng; + private List unusedPids; + private List unusedPrograms; + + #region Public Methods + + public ushort? DataPipingPid { get; private set; } + public SdtGeneratorService AddService(ushort serviceId, bool eitScheduleFlag = false, bool eitPresentFollowingFlag = false, RunningStatus runningStatus = RunningStatus.Running, bool freeCaMode = false) + { + if (!TransportStreamId.HasValue) + TransportStreamId = rng.NextUInt16(); + if (!OriginalNetworkId.HasValue) + OriginalNetworkId = rng.NextUInt16(); + if (sdtGenerator == null) + sdtGenerator = new SdtGenerator(TransportStreamId.Value, OriginalNetworkId.Value); + + return sdtGenerator.CreateService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode); + } + + public PmtGenerator AddProgram(ushort programId, ushort pmtPid, ushort pcrPid = 0x1fff) + { + if (!TransportStreamId.HasValue) + TransportStreamId = rng.NextUInt16(); + if (patGenerator == null) + patGenerator = new PatGenerator(TransportStreamId.Value); + + patGenerator.AddProgram(programId, pmtPid); + + if (pmtGenerators == null) + { + pmtGenerators = new List(); + } + + PmtGenerator pmtGenerator = new PmtGenerator(pmtPid, programId, pcrPid); + pmtGenerators.Add(pmtGenerator); + unusedPids.Remove(pmtPid); + unusedPids.Remove(pcrPid); + return pmtGenerator; + } + + public void AddCaSystem(ushort caSystemId, ushort caPid, byte[] privateData = null) + { + if (catGenerator == null) + catGenerator = new CatGenerator(); + + catGenerator.AddCaSystem(caSystemId, caPid, privateData); + } + + public ushort? TransportStreamId { get; set; } + public ushort? OriginalNetworkId { get; set; } + + public bool GenerateTdt + { + get => tdtGenerator != null; + set + { + if (value) + tdtGenerator = new TdtGenerator(); + else + tdtGenerator = null; + } + } + + public bool GenerateTot + { + get => totGenerator != null; + set + { + if (value) + totGenerator = new TotGenerator(); + else + totGenerator = null; + } + } + #endregion + + + private byte[] GetNextPacket() + { + if (sdtGenerator != null) + { + if (sdtGenerator.PacketAvailable()) + { + return sdtGenerator.GeneratePacket(); + } + } + if (totGenerator != null) + { + if (totGenerator.PacketAvailable()) + { + return totGenerator.GeneratePacket(); + } + } + if (tdtGenerator != null) + { + if (tdtGenerator.PacketAvailable()) + { + return tdtGenerator.GeneratePacket(); + } + } + if (catGenerator != null) + { + if (catGenerator.PacketAvailable()) + { + return catGenerator.GeneratePacket(); + } + } + if (pmtGenerators != null) + { + foreach (PmtGenerator pmtGenerator in pmtGenerators) + { + if (pmtGenerator.PacketAvailable()) + { + return pmtGenerator.GeneratePacket(); + } + } + } + if (patGenerator != null) + { + if (patGenerator.PacketAvailable()) + { + return patGenerator.GeneratePacket(); + } + } + if (dataPipingGenerator != null) + { + if (dataPipingGenerator.PacketAvailable()) + { + return dataPipingGenerator.GeneratePacket(); + } + } + return nullPacketGenerator.GeneratePacket(); + } + + + public override void Flush() + { + throw new NotImplementedException(); + } + + private void ValidateArgument(long number, string name) + { + if (number % 188 != 0) + { + long didYouMean = number; + didYouMean /= 188; + didYouMean++; + didYouMean *= 188; + throw new IOException(String.Format("{0} must be dividable by 188! You said {1}, but you probably meant {2}.", number, didYouMean)); + } + } + + private long totalBytesDelivered; + public override int Read(byte[] buffer, int offset, int count) + { + int result = 0; + ValidateArgument(count, nameof(count)); + for (int i = 0; i < count / 188; i++) + { + byte[] nextPacket = GetNextPacket(); + Array.Copy(nextPacket, 0, buffer, offset, 188); + offset += 188; + totalBytesDelivered += 188; + result += 188; + } + return result; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// Artificially limits the length of the TS. + /// + /// + /// + public override void SetLength(long value) + { + ValidateArgument(value, nameof(value)); + throw new NotImplementedException(); + } + + /// + /// Writes something into the data-pipe PID. This will create a service inside the TS, if needed. + /// + /// + /// + /// + /// + public override void Write(byte[] buffer, int offset, int count) + { + if (!DataPipingPid.HasValue) + { + DataPipingPid = (ushort)rng.NextListItem(unusedPids, true); + } + if (!DataPipingServicePresent) + { + ushort programId = rng.NextListItem(unusedPrograms, true); + ushort pmtPid = (ushort)rng.NextListItem(unusedPids, true); + PmtGenerator pmtGenerator = AddProgram(programId, pmtPid); + PmtGeneratorStream pmtGeneratorStream = pmtGenerator.AddStream(0xff, DataPipingPid.Value); + pmtGeneratorStream.AddDescriptor(new DataBroadcastIdDescriptor(0x0001)); + pmtGeneratorStream.AddDescriptor(new StreamIdentifierDescriptor(0x01)); + pmtGeneratorStream.AddDescriptor(new PrivateDataSpecifierDescriptor(0x534f5048)); + SdtGeneratorService service = AddService(programId); + service.AddName("Scorcher Data Piping", "sophia.net", ServiceDescriptor.ServiceTypeCoding.DataBroadcast); + service.AddDescriptor(new DataBroadcastDescriptor(0x0001, 0x01, null, "eng", "Data Pipe")); + service.AddDescriptor(new PrivateDataSpecifierDescriptor(0x534f5048)); + DataPipingServicePresent = true; + } + + if (dataPipingGenerator == null) + { + dataPipingGenerator = new DataPipingGenerator(DataPipingPid.Value); + } + + byte[] dataToEnqueue = new byte[count]; + Array.Copy(buffer, offset, dataToEnqueue, 0, count); + dataPipingGenerator.Append(dataToEnqueue); + } + + private bool DataPipingServicePresent; + + public override bool CanRead { get; } + public override bool CanSeek { get; } + public override bool CanWrite { get; } + public override long Length { get; } + public override long Position { get; set; } + } +} diff --git a/skyscraper8/Scorcher/TsPacketHeaderBuilder.cs b/skyscraper8/Scorcher/TsPacketHeaderBuilder.cs new file mode 100644 index 0000000..be05689 --- /dev/null +++ b/skyscraper8/Scorcher/TsPacketHeaderBuilder.cs @@ -0,0 +1,58 @@ +using System; + +namespace skyscraper5.Scorcher +{ + internal class TsPacketHeaderBuilder + { + public static byte[] BuildHeader(bool pusi, uint pid, int continuityCounter) + { + return BuildHeader(false, pusi, false, pid, 0, continuityCounter, null, true); + } + + public static byte[] BuildHeader(bool tei, bool pusi, bool transportPriority, uint pid, uint tsc, int continuityCounter, byte[] adaptionField = null, bool withPayload = true) + { + byte[] buffer = new byte[4 + (adaptionField != null ? adaptionField.Length + 1 : 0)]; + buffer[0] = (byte)'G'; + + if (tei) + buffer[1] |= 0x80; + + if (pusi) + buffer[1] |= 0x40; + + if (transportPriority) + buffer[1] |= 0x20; + + if (pid > 0x1fff) + throw new ArgumentOutOfRangeException(nameof(pid)); + + if ((pid & 0x1000) != 0) + buffer[1] |= 0x10; + + uint pid2 = (pid & 0x0f00) >> 8; + buffer[1] += (byte)pid2; + + buffer[2] = (byte)(pid & 0x00ff); + + if (tsc > 3) + throw new ArgumentOutOfRangeException(nameof(tsc)); + + tsc <<= 6; + buffer[3] += (byte)tsc; + + if (adaptionField != null) + { + buffer[3] |= 0x20; + buffer[4] = (byte)adaptionField.Length; + Array.Copy(adaptionField, 0, buffer, 5, adaptionField.Length); + } + + if (withPayload) + buffer[3] |= 0x10; + + continuityCounter &= 0x0000000f; + buffer[3] += (byte)continuityCounter; + return buffer; + } + } +} diff --git a/skyscraper8/Scte35/Descriptors/0x00_AvailDescriptor.cs b/skyscraper8/Scte35/Descriptors/0x00_AvailDescriptor.cs new file mode 100644 index 0000000..7fb7618 --- /dev/null +++ b/skyscraper8/Scte35/Descriptors/0x00_AvailDescriptor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Scte35.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x00,"SCTE35")] + public class AvailDescriptor : TsDescriptor + { + public AvailDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Identifier = ms.ReadUInt32BE(); + ProviderAvailId = ms.ReadUInt32BE(); + } + + public uint ProviderAvailId { get; set; } + + public uint Identifier { get; private set; } + } +} diff --git a/skyscraper8/Scte35/Descriptors/0x02_SegmentationDescriptor.cs b/skyscraper8/Scte35/Descriptors/0x02_SegmentationDescriptor.cs new file mode 100644 index 0000000..b8f4082 --- /dev/null +++ b/skyscraper8/Scte35/Descriptors/0x02_SegmentationDescriptor.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Scte35.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x02, "SCTE35")] + public class SegmentationDescriptor : TsDescriptor + { + public SegmentationDescriptor(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Identifier = ms.ReadUInt32BE(); + SegmentationEventId = ms.ReadUInt32BE(); + byte readUInt8 = ms.ReadUInt8(); + SegmentationEventCancelIndicator = (readUInt8 & 0x80) != 0; + if (!SegmentationEventCancelIndicator) + { + readUInt8 = ms.ReadUInt8(); + ProgramSegmentationFlag = (readUInt8 & 0x80) != 0; + SegmentationDurationFlag = (readUInt8 & 0x40) != 0; + bool deliveryNotRestrictedFlag = (readUInt8 & 0x20) != 0; + if (!deliveryNotRestrictedFlag) + { + WebDeliveryFlag = (readUInt8 & 0x10) != 0; + NoRegionalBlackoutFlag = (readUInt8 & 0x08) != 0; + ArchiveAllowedFlag = (readUInt8 & 0x04) != 0; + DeviceRestrictions = (readUInt8 & 0x03); + } + else + { + //reserved + } + + if (!ProgramSegmentationFlag.Value) + { + byte componentCount = ms.ReadUInt8(); + Components = new Component[componentCount]; + for (int i = 0; i < componentCount; i++) + { + Components[i] = new Component(); + Components[i].ComponentTag = ms.ReadUInt8(); + readUInt8 = ms.ReadUInt8(); + Components[i].PtsOffset = ms.ReadUInt32BE(); + Components[i].PtsOffset <<= 1; + Components[i].PtsOffset += (uint)(readUInt8 & 0x01); + } + } + + if (SegmentationDurationFlag.Value) + { + readUInt8 = ms.ReadUInt8(); + SegmentationDuration = ms.ReadUInt32BE(); + SegmentationDuration <<= 8; + SegmentationDuration += readUInt8; + } + + SegmentationUpidType = ms.ReadUInt8(); + byte segmentationUpidLength = ms.ReadUInt8(); + SegmentationUpid = Encoding.ASCII.GetString(ms.ReadBytes(segmentationUpidLength)); + + SegmentationTypeId = ms.ReadUInt8(); + SegmentNum = ms.ReadUInt8(); + SegmentsExpected = ms.ReadUInt8(); + if (SegmentationTypeId.Value == 0x34 || SegmentationTypeId.Value == 0x36 || + SegmentationTypeId.Value == 0x38 || SegmentationTypeId.Value == 0x3a) + { + SubSegmentNum = ms.ReadUInt8(); + SubSegmentsExpected = ms.ReadUInt8(); + } + } + } + + public byte? SubSegmentsExpected { get; set; } + + public byte? SubSegmentNum { get; set; } + + public byte? SegmentsExpected { get; set; } + + public byte? SegmentNum { get; set; } + + public byte? SegmentationTypeId { get; set; } + + public string SegmentationUpid { get; set; } + + public byte? SegmentationUpidType { get; set; } + + public ulong? SegmentationDuration { get; set; } + + public Component[] Components { get; set; } + + public class Component + { + public byte ComponentTag { get; set; } + public ulong PtsOffset { get; set; } + } + + public bool? ProgramSegmentationFlag { get; set; } + + public bool SegmentationEventCancelIndicator { get; set; } + + public int? DeviceRestrictions { get; set; } + + public bool? ArchiveAllowedFlag { get; set; } + + public bool? NoRegionalBlackoutFlag { get; set; } + + public bool? WebDeliveryFlag { get; set; } + + public bool? SegmentationDurationFlag { get; set; } + + public uint SegmentationEventId { get; set; } + + public uint Identifier { get; set; } + } +} diff --git a/skyscraper8/Scte35/Descriptors/0x03_TimeDescriptor.cs b/skyscraper8/Scte35/Descriptors/0x03_TimeDescriptor.cs new file mode 100644 index 0000000..96cf9e2 --- /dev/null +++ b/skyscraper8/Scte35/Descriptors/0x03_TimeDescriptor.cs @@ -0,0 +1,54 @@ +using skyscraper5.Mpeg2; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Scte35.Descriptors +{ + [SkyscraperPlugin] + [TsDescriptor(0x03, "SCTE35")] + public class _0x03_TimeDescriptor : TsDescriptor + { + public _0x03_TimeDescriptor(byte[] buffer) + { + if (buffer.Length < 16) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer); + Identifier = ms.ReadUInt32BE(); + + byte[] taiSecondsBuffer = new byte[8]; + taiSecondsBuffer[7] = ms.ReadUInt8(); + taiSecondsBuffer[6] = ms.ReadUInt8(); + taiSecondsBuffer[5] = ms.ReadUInt8(); + taiSecondsBuffer[4] = ms.ReadUInt8(); + taiSecondsBuffer[3] = ms.ReadUInt8(); + taiSecondsBuffer[2] = ms.ReadUInt8(); + + TaiSeconds = BitConverter.ToUInt64(taiSecondsBuffer); + if (TaiSeconds > 281474976710656UL) + { + Valid = false; + return; + } + TaiNs = ms.ReadUInt32BE(); + UtcOffset = ms.ReadUInt16BE(); + Valid = true; + } + + public ushort UtcOffset { get; set; } + + public uint TaiNs { get; set; } + + public ulong TaiSeconds { get; set; } + + public uint Identifier { get; private set; } + } +} diff --git a/skyscraper8/Scte35/IScte35EventHandler.cs b/skyscraper8/Scte35/IScte35EventHandler.cs new file mode 100644 index 0000000..c5fae8a --- /dev/null +++ b/skyscraper8/Scte35/IScte35EventHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Scte35.Descriptors; + +namespace skyscraper5.Scte35 +{ + interface IScte35EventHandler + { + void NotifySpliceInsert(ushort ProgramNumber, SpliceInsert spliceInsert); + void NotifyTimeSignal(ushort programNumber, TimeSignal timeSignal); + } +} diff --git a/skyscraper8/Scte35/Scte35DescriptorCollection.cs b/skyscraper8/Scte35/Scte35DescriptorCollection.cs new file mode 100644 index 0000000..73bc27e --- /dev/null +++ b/skyscraper8/Scte35/Scte35DescriptorCollection.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Scte35.Descriptors; + +namespace skyscraper5.Scte35 +{ + public class Scte35DescriptorCollection + { + public AvailDescriptor availDescriptor; + public SegmentationDescriptor segmentationDescriptor; + public _0x03_TimeDescriptor timeDescriptor; + } +} diff --git a/skyscraper8/Scte35/Scte35Expception.cs b/skyscraper8/Scte35/Scte35Expception.cs new file mode 100644 index 0000000..4b50a3f --- /dev/null +++ b/skyscraper8/Scte35/Scte35Expception.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Scte35 +{ + class Scte35Exception : SkyscraperException + { + public Scte35Exception(string s, params object[] args) + : base(String.Format(s, args)) + { + } + } +} diff --git a/skyscraper8/Scte35/Scte35SiDecoder.cs b/skyscraper8/Scte35/Scte35SiDecoder.cs new file mode 100644 index 0000000..0b719bb --- /dev/null +++ b/skyscraper8/Scte35/Scte35SiDecoder.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Scte35.Descriptors; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Scte35 +{ + class Scte35SiDecoder : IPsiProcessor + { + public ushort ProgramNumber { get; } + public IScte35EventHandler EventHandler { get; } + + public Scte35SiDecoder(ushort _programNumber, IScte35EventHandler _eventHandler) + { + ProgramNumber = _programNumber; + EventHandler = _eventHandler; + } + + + public const byte IMPLEMENTED_VERSION = 0; + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(), false); + byte tableId = ms.ReadUInt8(); + if (tableId != 0xfc) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + bool privateIndicator = (readUInt16Be & 0x4000) != 0; + int sectionLength = (readUInt16Be & 0x0fff); + + byte protocolVersion = ms.ReadUInt8(); + if (protocolVersion > IMPLEMENTED_VERSION) + return; + + byte readUInt8 = ms.ReadUInt8(); + bool encryptedPacket = (readUInt8 & 0x80) != 0; + int encryptionAlgorithm = (readUInt8 & 0x7e) >> 1; + if (encryptedPacket && encryptionAlgorithm != 0) + return; + + long ptsAdjustment = (readUInt8 & 0x01); + ptsAdjustment <<= 1; + ptsAdjustment += ms.ReadUInt32BE(); + + byte cwIndex = ms.ReadUInt8(); + + uint readUInt32Be = ms.ReadUInt32BE(); + uint tier = (readUInt32Be & 0xfff00000) >> 20; + uint spliceCommandLength = (readUInt32Be & 0x000fff00) >> 8; + uint spliceCommandType = (readUInt32Be & 0x000000ff); + if (ms.GetAvailableBytes() < spliceCommandLength) + return; + byte[] spliceCommand = ms.ReadBytes(spliceCommandLength); + + TimeSignal timeSignal = null; + SpliceInsert spliceInsert = null; + switch (spliceCommandType) + { + case 0: + SpliceNull(spliceCommand); + return; + case 5: + spliceInsert = new SpliceInsert(spliceCommand); + break; + case 6: + timeSignal = this.TimeSignal(spliceCommand); + break; + case 7: + BandwidthReservation(spliceCommand); + return; + case 0xff: + PrivateCommand(spliceCommand); + return; + default: + throw new NotImplementedException(String.Format("SCTE35 Command {0:X2}", spliceCommandType)); + } + + int descriptorLoopLength = ms.ReadUInt16BE() & 0x0fff; + if (ms.GetAvailableBytes() < descriptorLoopLength) + return; + byte[] descriptorLoop = ms.ReadBytes(descriptorLoopLength); + Scte35DescriptorCollection descriptorOutput = new Scte35DescriptorCollection(); + IEnumerable descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorLoop, "SCTE35"); + foreach (TsDescriptor descriptor in descriptors) + { + string name = descriptor.GetType().Name; + switch (name) + { + case nameof(AvailDescriptor): + descriptorOutput.availDescriptor = (AvailDescriptor)descriptor; + break; + case nameof(SegmentationDescriptor): + descriptorOutput.segmentationDescriptor = (SegmentationDescriptor)descriptor; + break; + case nameof(_0x03_TimeDescriptor): + descriptorOutput.timeDescriptor = (_0x03_TimeDescriptor)descriptor; + break; + default: + throw new NotImplementedException(name); + } + } + + long stuffingLength = ms.GetAvailableBytes(); + stuffingLength -= 4; //for crc_32 + if (encryptedPacket) + stuffingLength -= 4; //for e_crc_32 + if (stuffingLength < 0) + return; + + byte[] alignmentStuffing = ms.ReadBytes(stuffingLength); + if (encryptedPacket) + { + uint eCrc32 = ms.ReadUInt32BE(); + throw new NotImplementedException(String.Format("{0} {1}", nameof(encryptedPacket), nameof(eCrc32))); + } + + uint crc32 = ms.ReadUInt32BE(); + + if (spliceInsert != null) + { + spliceInsert.Descriptors = descriptorOutput; + EventHandler.NotifySpliceInsert(ProgramNumber, spliceInsert); + } + else if (timeSignal != null) + { + timeSignal.Descriptors = descriptorOutput; + EventHandler.NotifyTimeSignal(ProgramNumber, timeSignal); + } + else + { + throw new NotImplementedException("No idea how to handle this SCTE-35 data."); + } + } + + private TimeSignal TimeSignal(byte[] spliceCommand) + { + MemoryStream ms = new MemoryStream(spliceCommand, false); + TimeSignal result = new TimeSignal(); + result.Value = ms.ReadSpliceTime(); + ms.Close(); + return result; + } + + private void BandwidthReservation(byte[] spliceCommand) + { + } + + private void SpliceNull(byte[] buffer) + { + + } + + private void PrivateCommand(byte[] buffer) + { + //We have no idea what could be in there, so just skip it. + } + + } + +} diff --git a/skyscraper8/Scte35/SpliceInsert.cs b/skyscraper8/Scte35/SpliceInsert.cs new file mode 100644 index 0000000..4d07b90 --- /dev/null +++ b/skyscraper8/Scte35/SpliceInsert.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scte35 +{ + public class SpliceInsert + { + public SpliceInsert(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + SpliceEventId = ms.ReadUInt32BE(); + SpliceEventCancelIndicator = (ms.ReadUInt8() & 0x80) != 0; + if (!SpliceEventCancelIndicator) + { + byte readUInt8 = ms.ReadUInt8(); + OutOfNetworkIndicator = (readUInt8 & 0x80) != 0; + ProgramSpliceFlag = (readUInt8 & 0x40) != 0; + DurationFlag = (readUInt8 & 0x20) != 0; + SpliceImmediateFlag = (readUInt8 & 0x10) != 0; + if (ProgramSpliceFlag.Value && !SpliceImmediateFlag.Value) + SpliceTime = ms.ReadSpliceTime(); + if (!ProgramSpliceFlag.Value) + { + byte componentCount = ms.ReadUInt8(); + Components = new Tuple[componentCount]; + for (int i = 0; i < componentCount; i++) + { + byte componentTag = ms.ReadUInt8(); + ulong? spliceTime = null; + if (!SpliceImmediateFlag.Value) + spliceTime = ms.ReadSpliceTime(); + Components[i] = new Tuple(componentTag, spliceTime); + } + } + + if (DurationFlag.Value) + { + readUInt8 = ms.ReadUInt8(); + DurationAutoReturn = (readUInt8 & 0x80) != 0; + Duration = (ulong)(readUInt8 & 0x01); + Duration <<= 32; + Duration += ms.ReadUInt32BE(); + } + + UniqueProgramId = ms.ReadUInt16BE(); + AvailNum = ms.ReadUInt8(); + AvailsExpected = ms.ReadUInt8(); + } + } + + public byte? AvailsExpected { get; private set; } + + public byte? AvailNum { get; private set; } + + public ushort? UniqueProgramId { get; private set; } + + public ulong? Duration { get; private set; } + + public bool? DurationAutoReturn { get; set; } + + public Tuple[] Components { get; private set; } + + public bool? SpliceImmediateFlag { get; private set; } + + public bool? DurationFlag { get; private set; } + + public bool? ProgramSpliceFlag { get; private set; } + + public bool? OutOfNetworkIndicator { get; private set; } + + public bool SpliceEventCancelIndicator { get; private set; } + + public uint SpliceEventId { get; private set; } + public ulong? SpliceTime { get; } + public Scte35DescriptorCollection Descriptors { get; set; } + } +} diff --git a/skyscraper8/Scte35/SpliceTime.cs b/skyscraper8/Scte35/SpliceTime.cs new file mode 100644 index 0000000..f4ec31a --- /dev/null +++ b/skyscraper8/Scte35/SpliceTime.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Scte35 +{ + static class SpliceTime + { + public static ulong? ReadSpliceTime(this Stream s) + { + byte readUInt8 = s.ReadUInt8(); + bool timeSpecifiedFlag = (readUInt8 & 0x80) != 0; + if (timeSpecifiedFlag) + { + ulong result = (ulong)(readUInt8 & 0x01); + result <<= 32; + result += s.ReadUInt32BE(); + return result; + } + return null; + } + } +} diff --git a/skyscraper8/Scte35/TimeSignal.cs b/skyscraper8/Scte35/TimeSignal.cs new file mode 100644 index 0000000..3dc3d8a --- /dev/null +++ b/skyscraper8/Scte35/TimeSignal.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Scte35 +{ + public class TimeSignal + { + public ulong? Value { get; set; } + public Scte35DescriptorCollection Descriptors { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/ArrayExtensions.cs b/skyscraper8/Skyscraper/ArrayExtensions.cs new file mode 100644 index 0000000..4bc43af --- /dev/null +++ b/skyscraper8/Skyscraper/ArrayExtensions.cs @@ -0,0 +1,18 @@ +using System; + +namespace skyscraper5.Skyscraper +{ + internal static class ArrayExtensions + { + public static void Shuffle(this T[] collection) + { + Random rng = new Random(); + for (int i = 0; i < collection.Length; i++) + { + int lOffset = rng.Next(collection.Length); + int rOffset = rng.Next(collection.Length); + (collection[lOffset], collection[rOffset]) = (collection[rOffset], collection[lOffset]); + } + } + } +} diff --git a/skyscraper8/Skyscraper/BaseLcn.cs b/skyscraper8/Skyscraper/BaseLcn.cs new file mode 100644 index 0000000..6668051 --- /dev/null +++ b/skyscraper8/Skyscraper/BaseLcn.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper +{ + //Neither MPEG-2 nor DVB know of LCNs, and there are many different implementations of LCNs + //supplied by multiple service providers. + public class BaseLcn + { + public ushort ServiceId { get; set; } + public int LogicalChannelNumber { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/ByteArrayExtensions.cs b/skyscraper8/Skyscraper/ByteArrayExtensions.cs new file mode 100644 index 0000000..dceb8d8 --- /dev/null +++ b/skyscraper8/Skyscraper/ByteArrayExtensions.cs @@ -0,0 +1,16 @@ +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper +{ + internal static class ByteArrayExtensions + { + private static ByteArrayComparer _comparer; + public static bool IsEqual(this byte[] a, byte[] b) + { + if (_comparer == null) + _comparer = new ByteArrayComparer(); + + return _comparer.Compare(a, b) == 0; + } + } +} diff --git a/skyscraper8/Skyscraper/CableFrequencyListGenerator.cs b/skyscraper8/Skyscraper/CableFrequencyListGenerator.cs new file mode 100644 index 0000000..3cf5a2a --- /dev/null +++ b/skyscraper8/Skyscraper/CableFrequencyListGenerator.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.IO.TunerInterface; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; +using Console = System.Console; + +namespace skyscraper5.Skyscraper +{ + internal class CableFrequencyListGenerator + { + public CableFrequencyListGenerator() + { + + } + + public void Run() + { + Ini ini = PluginManager.GetInstance().Ini; + TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance(); + ITunerFactory tunerFactory = tunerFactoryConnectionManager.AutoGetConfiguredTunerFactory(); + IStreamReader streamReader = tunerFactory.CreateStreamReader(); + int usableTuner = -1; + if (!streamReader.CheckForDVBExEx((x, y, z) => + { + if (usableTuner == -1 && z == STD_TYPE.STD_DVBC) + usableTuner = x; + })) + { + Console.WriteLine("Failed to check for tuners."); + return; + } + if (usableTuner == -1) + { + Console.WriteLine("no dvb-c tuner present, quitting."); + return; + } + + if (!streamReader.StartDvbEx(usableTuner)) + { + Console.WriteLine("Failed to start the tuner."); + streamReader.StopDVB(); + if (!streamReader.StartDvbEx(usableTuner)) + { + Console.WriteLine("Giving up..."); + return; + } + } + + IntPtr pSearchResult = Marshal.AllocHGlobal(ushort.MaxValue); + int tpNum = 0; + SearchResult2 sr2 = default; + if (!streamReader.AirScan(48000, 1000000, 3000, 8000, (int)STD_TYPE.STD_DVBC, pSearchResult, ref tpNum, (ref SearchResult2 searchResult) => SearchResult2Callback(searchResult))) + { + Console.WriteLine("Failed to run AirScan..."); + return; + } + + string filename = String.Format("dvb-c_{0}.csv", DateTime.Now.ToUnixTime()); + FileStream fileStream = File.OpenWrite(filename); + StreamWriter streamWriter = new StreamWriter(fileStream); + + foreach (SearchResult2 searchResult2 in searchResults) + { + double outFreq = searchResult2.Freq; + outFreq = Math.Round((double)outFreq / (double)1000.0, 0) * 1000; + outFreq /= 1000; + + Console.WriteLine("Tuning to {0} Mhz", outFreq); + if (!streamReader.SetChannel2((uint)searchResult2.Freq, (uint)searchResult2.BW)) + { + Console.WriteLine("Failed to tune."); + continue; + } + + SearchResult2 recheck = default; + if (!streamReader.SignalInfo2(ref recheck)) + { + Console.WriteLine("Failed to get signal info."); + continue; + } + + if (!recheck.Lock) + { + Console.WriteLine("No lock."); + continue; + } + + double outSymRate = searchResult2.SR; + outSymRate = Math.Round((double)outSymRate / (double)10000.0, 0) * 10000; + outSymRate /= 1000; + string outModType = ((MOD_TYPE)searchResult2.ModType).ToString(); + string outLine = String.Format("{0} Mhz;{1} kSym/s;{2}", outFreq, outSymRate, outModType); + streamWriter.WriteLine(outLine); + + StreamReaderScraperController scraper = new StreamReaderScraperController(streamReader); + scraper.ScraperStroage = new InMemoryScraperStorage(); + scraper.Recording = false; + scraper.EventLogger = new ProgressBarLogger(); + Console.Write("Running PSI acquisition..."); + scraper.Run(); + Console.WriteLine(".DONE"); + + IScraperStroage data = scraper.ScraperStroage; + IEnumerable> pmtEntries = data.SelectAllPmt().ToList(); + List> tuples = data.SelectAllSdt().ToList(); + foreach (var (tsId, networkId, programMapping) in pmtEntries) + { + SdtService sdt = data.SelectSdtById(networkId, tsId, programMapping.ProgramNumber); + if (sdt == null) + { + continue; + } + outLine = String.Format("{0};{1};{2};{3};{4}", programMapping.ProgramNumber,sdt.ServiceProviderName,sdt.ServiceName,GetCaSystemName(programMapping,sdt),sdt.ServiceType); + streamWriter.WriteLine(outLine); + streamWriter.Flush(); + } + streamWriter.WriteLine(""); + + } + + if (!streamReader.StopDVB()) + { + Console.WriteLine("Failed to properly close the tuner!"); + return; + } + streamWriter.Flush(); + streamWriter.Close(); + } + + private string GetCaSystemName(ProgramMapping pmt, SdtService sdt) + { + if (pmt.CaSystemId.HasValue) + { + return CaSystemNames.GetHumanReadableName(pmt.CaSystemId.Value); + } + + if (sdt.CaIdentifiers != null) + { + if (sdt.CaIdentifiers.Length > 0) + { + return CaSystemNames.GetHumanReadableName(sdt.CaIdentifiers[0]); + } + } + + return ""; + } + + private List searchResults; + private void SearchResult2Callback(SearchResult2 searchResult) + { + if (searchResults == null) + searchResults = new List(); + searchResults.Add(searchResult); + Console.WriteLine("Heard something at {0}", searchResult.Freq / 1000); + } + + private class ProgressBarLogger : ISkyscraperEventLogger + { + public ProgressBarLogger() + { + eventsGoal = 1; + } + private ulong eventsGoal; + private ulong eventsDone; + private char[] animation = new char[] { '\\', '-', '/', '|' }; + public void Log(TimeSpan duration, DateTime eventTimestamp, SkyscraperContextEvent eventType, string name = null) + { + eventsDone++; + Console.Write('\b'); + if (eventsDone == eventsGoal) + { + Console.Write("."); + eventsGoal *= 2; + } + Console.Write(animation[(int)eventsDone % animation.Length]); + } + + public ulong NumEvents => eventsDone; + } + } +} diff --git a/skyscraper8/Skyscraper/ClassVersionAttribute.cs b/skyscraper8/Skyscraper/ClassVersionAttribute.cs new file mode 100644 index 0000000..812e02d --- /dev/null +++ b/skyscraper8/Skyscraper/ClassVersionAttribute.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class ClassVersionAttribute : Attribute + { + public ClassVersionAttribute(int major,int minor,int build,int revision) + { + Major = major; + Minor = minor; + Build = build; + Revision = revision; + } + + public int Major { get; } + public int Minor { get; } + public int Build { get; } + public int Revision { get; } + + public Version ToVersion() + { + return new Version(Major, Minor, Build, Revision); + } + } +} diff --git a/skyscraper8/Skyscraper/DateTimeExtensions.cs b/skyscraper8/Skyscraper/DateTimeExtensions.cs new file mode 100644 index 0000000..18e7891 --- /dev/null +++ b/skyscraper8/Skyscraper/DateTimeExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper +{ + public static class DateTimeExtensions + { + public static long ToUnixTime(this DateTime dt) + { + double totalSeconds = dt.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; + long tv = (long)totalSeconds; + return tv; + } + + public static long ToJavaMillis(this DateTime dt) + { + return ToUnixTime(dt) * 1000; + } + } +} diff --git a/skyscraper8/Skyscraper/DigitalDevicesBbFrameReader.cs b/skyscraper8/Skyscraper/DigitalDevicesBbFrameReader.cs new file mode 100644 index 0000000..d1c039f --- /dev/null +++ b/skyscraper8/Skyscraper/DigitalDevicesBbFrameReader.cs @@ -0,0 +1,59 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper +{ + internal class DigitalDevicesBbFrameReader : ITsPacketProcessor + { + private IGsEventHandler mpeEventHandler; + + public DigitalDevicesBbFrameReader(IGsEventHandler mpeEventHandler) + { + this.mpeEventHandler = mpeEventHandler; + } + + private MemoryStream outbuf; + private bool annoncementDone; + public void PushPacket(TsPacket packet) + { + byte[] packets = packet.RawPacket; + int pid = (packets[1]); + pid &= 0x01f; + pid <<= 8; + pid |= (packets[2]); + if (pid != 0x010e) + return; + + if ((packets[8] & 0xff) == 0xb8) + { + if (outbuf != null) + { + byte[] chi = outbuf.ToArray(); + byte[] ipPacket = IpPacketFinder.LookForIpPacket(outbuf.ToArray(), 32); + if (ipPacket != null) + { + if (!annoncementDone) + { + mpeEventHandler.GsIpTrafficDetected(); + annoncementDone = true; + } + mpeEventHandler.OnIpDatagram(0x0118, ipPacket); + } + } + outbuf = new MemoryStream(); + outbuf.Write(packets,8, packets[7]); + } + else + { + if (outbuf != null) + outbuf.Write(packets, 9, packets[7] - 1); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Drawing/PaletteExporter.cs b/skyscraper8/Skyscraper/Drawing/PaletteExporter.cs new file mode 100644 index 0000000..2ebfd71 --- /dev/null +++ b/skyscraper8/Skyscraper/Drawing/PaletteExporter.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Subtitling.Model; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Skyscraper.Drawing +{ + static class PaletteExporter + { + public static void ExportPalette(string outputFileName, Color[] colors, bool includeAlpha, PaletteFormat fmt) + { + ExportPalette(new FileInfo(outputFileName), colors, includeAlpha, fmt); + } + public static void ExportPalette(FileInfo fi, Color[] colors, bool includeAlpha, PaletteFormat fmt) + { + if (fi.Exists) + fi.Delete(); + FileStream fs = fi.OpenWrite(); + ExportPalette(fs, colors, includeAlpha, fmt); + fs.Flush(true); + fs.Close(); + } + public static void ExportPalette(Stream output, Color[] colors, bool includeAlpha, PaletteFormat fmt) + { + for (int i = 0; i < colors.Length; i++) + { + switch (fmt) + { + case PaletteFormat.Raw: + output.WriteByte(colors[i].Pb); + output.WriteByte(colors[i].Y); + output.WriteByte(colors[i].Pr); + if (includeAlpha) + output.WriteByte(colors[i].T); + break; + default: + throw new NotImplementedException(fmt.ToString()); + } + } + } + } +} diff --git a/skyscraper8/Skyscraper/Drawing/PaletteFormat.cs b/skyscraper8/Skyscraper/Drawing/PaletteFormat.cs new file mode 100644 index 0000000..cd75fd9 --- /dev/null +++ b/skyscraper8/Skyscraper/Drawing/PaletteFormat.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Skyscraper.Drawing +{ + enum PaletteFormat + { + Raw + } +} diff --git a/skyscraper8/Skyscraper/Drawing/PointF.cs b/skyscraper8/Skyscraper/Drawing/PointF.cs new file mode 100644 index 0000000..4457f3e --- /dev/null +++ b/skyscraper8/Skyscraper/Drawing/PointF.cs @@ -0,0 +1,47 @@ +using System; + +namespace skyscraper5.Skyscraper.Drawing +{ + public struct PointF + { + public PointF(double x, double y) + { + this.x = x; + this.y = y; + } + + private double x, y; + + public double X + { + get => x; + set => x = value; + } + + public double Y + { + get => y; + set => y = value; + } + + public bool Equals(PointF other) + { + return x.Equals(other.x) && y.Equals(other.y); + } + + public override bool Equals(object obj) + { + return obj is PointF other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(x, y); + } + + public override string ToString() + { + return $"{nameof(x)}: {x}, {nameof(y)}: {y}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Drawing/Polygon.cs b/skyscraper8/Skyscraper/Drawing/Polygon.cs new file mode 100644 index 0000000..9e740f0 --- /dev/null +++ b/skyscraper8/Skyscraper/Drawing/Polygon.cs @@ -0,0 +1,106 @@ +using System; +using System.Globalization; +using System.Text; + +namespace skyscraper5.Skyscraper.Drawing +{ + public class Polygon + { + public Polygon(string polygonString) + { + string[] separated = polygonString.Split('\t'); + polygonPoints = new PointF[separated.Length]; + for (int i = 0; i < separated.Length; i++) + { + if (String.IsNullOrEmpty(separated[i])) + continue; + string[] components = separated[i].Split(','); + polygonPoints[i] = new PointF(Double.Parse(components[0],CultureInfo.InvariantCulture), Double.Parse(components[1],CultureInfo.InvariantCulture)); + } + } + + private PointF[] polygonPoints; + + public PointF[] GetCopyOfPoints() + { + PointF[] copy = new PointF[polygonPoints.Length]; + Array.Copy(polygonPoints,0,copy,0,polygonPoints.Length); + return copy; + } + + public string GetPolygonString() + { + StringBuilder sb = new StringBuilder(); + foreach (PointF point in polygonPoints) + { + sb.Append(point.X.ToString(CultureInfo.InvariantCulture)); + sb.Append(","); + sb.Append(point.Y.ToString(CultureInfo.InvariantCulture)); + sb.Append('\t'); + } + + return sb.ToString().Trim('\t'); + } + + public override string ToString() + { + return String.Format("{0} points.", polygonPoints); + } + + public bool Wraps(PointF positionOnEath) + { + int crossings = 0; + PointF[] path = polygonPoints; + + //for each edge + for (int i = 0; i < path.Length; i++) + { + PointF a = path[i]; + int j = i + 1; + if (j >= path.Length) + j = 0; + + PointF b = path[j]; + if (RayCrossesSegment(positionOnEath, a, b)) + crossings++; + } + + return (crossings % 2 == 1); + } + + private static bool RayCrossesSegment(PointF point, PointF a, PointF b) + { + double px = point.X; + double py = point.Y; + double ax = a.X; + double ay = a.Y; + double bx = b.X; + double by = b.Y; + if (ay > by) + { + ax = b.X; + ay = b.Y; + bx = a.X; + by = a.Y; + } + // alter longitude to cater for 180 degree crossings + if (px < 0) + px += 360; + if (ax < 0) + ax += 360; + if (bx < 0) + bx += 360; + + if (py.Equals(ay) || py.Equals(by)) + py += 0.00000001; + if ((py > by || py < ay) || (px > Math.Max(ax, bx))) + return false; + if (px < Math.Min(ax, bx)) + return true; + + double red = (!ax.Equals(bx)) ? ((by - ay) / (bx - ax)) : Double.PositiveInfinity; + double blue = (!ax.Equals(px)) ? ((py - ay) / (px - ax)) : Double.PositiveInfinity; + return (blue >= red); + } + } +} diff --git a/skyscraper8/Skyscraper/Equipment/BaseEquipment.cs b/skyscraper8/Skyscraper/Equipment/BaseEquipment.cs new file mode 100644 index 0000000..0ec9dc8 --- /dev/null +++ b/skyscraper8/Skyscraper/Equipment/BaseEquipment.cs @@ -0,0 +1,11 @@ +using System; + +namespace skyscraper5.Skyscraper.Equipment +{ + public class BaseEquipment + { + public int Id { get; set; } + public string Name { get; set; } + public DateTime DateAdded { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/Equipment/Dish.cs b/skyscraper8/Skyscraper/Equipment/Dish.cs new file mode 100644 index 0000000..bd79724 --- /dev/null +++ b/skyscraper8/Skyscraper/Equipment/Dish.cs @@ -0,0 +1,54 @@ +using System; + +namespace skyscraper5.Skyscraper.Equipment +{ + public class DishType : BaseEquipment + { + public DishType(string name, int diameter, DishShape shape) + { + this.Name = name; + this.Diameter = diameter; + this.Shape = shape; + } + + public DishType() + { + } + + public int Diameter { get; set; } + + public DishShape Shape { get; set; } + + protected bool Equals(DishType other) + { + return Diameter == other.Diameter && Shape == other.Shape; + } + + 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((DishType)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Diameter, (int)Shape); + } + + public override string ToString() + { + return this.Name; + } + } + + public enum DishShape : int + { + Offset, + PrimeFocus, + Mesh, + Mobisat, + Selfsat + } +} diff --git a/skyscraper8/Skyscraper/Equipment/EquipmentUtilities.cs b/skyscraper8/Skyscraper/Equipment/EquipmentUtilities.cs new file mode 100644 index 0000000..f42877b --- /dev/null +++ b/skyscraper8/Skyscraper/Equipment/EquipmentUtilities.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.Skyscraper.Equipment +{ + public static class EquipmentUtilities + { + public static void InsertDefaultLnbTypes(IScraperStroage storage) + { + List knownLnbTypes = storage.UiLnbTypesListAll(); + foreach (LnbType defaultLnbType in GetDefaultLnbTypes()) + { + if (!knownLnbTypes.Contains(defaultLnbType)) + storage.UiLnbTypesAdd(defaultLnbType); + } + } + + private static IEnumerable GetDefaultLnbTypes() + { + LnbType lnbType = new LnbType(); + lnbType.Lof1 = 9750; + lnbType.Lof2 = 10600; + lnbType.LofSw = 11700; + lnbType.MinimumFrequency = 10700; + lnbType.MaximumFrequency = 12750; + lnbType.Name = "Generic Universal LNB"; + yield return lnbType; + } + + public static void InsertDefaultDishTypes(IScraperStroage storage) + { + List knownDishTypes = storage.UiDishTypesListAll(); + foreach (DishType defaultDishType in GetDefaultDishTypes()) + { + if (!knownDishTypes.Contains(defaultDishType)) + storage.UiDishTypesAdd(defaultDishType); + } + } + + private static IEnumerable GetDefaultDishTypes() + { + int[] commonSizes = new int[] { 60, 80, 85, 100 }; + foreach (int commonSize in commonSizes) + { + DishType dishType = new DishType(); + dishType.Diameter = commonSize; + dishType.Shape = DishShape.Offset; + dishType.Name = String.Format("Generic {0} cm Offset dish antenna", commonSize); + yield return dishType; + } + } + } +} diff --git a/skyscraper8/Skyscraper/Equipment/LNB.cs b/skyscraper8/Skyscraper/Equipment/LNB.cs new file mode 100644 index 0000000..322429e --- /dev/null +++ b/skyscraper8/Skyscraper/Equipment/LNB.cs @@ -0,0 +1,49 @@ +using System; + +namespace skyscraper5.Skyscraper.Equipment +{ + public class LnbType : BaseEquipment + { + public LnbType() { } + + public LnbType(string name, int lof1, int lof2, int lofSw, int minFreq, int maxFreq) + { + this.Name = name; + this.Lof1 = lof1; + this.Lof2 = lof2; + this.LofSw = lofSw; + this.MinimumFrequency = minFreq; + this.MaximumFrequency = maxFreq; + } + + public int Lof1 { get; set; } + public int Lof2 { get; set; } + public int LofSw { get; set; } + + public int MinimumFrequency { get; set; } + public int MaximumFrequency { get; set; } + + protected bool Equals(LnbType other) + { + return Lof1 == other.Lof1 && Lof2 == other.Lof2 && LofSw == other.LofSw && MinimumFrequency == other.MinimumFrequency && MaximumFrequency == other.MaximumFrequency; + } + + 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((LnbType)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Lof1, Lof2, LofSw, MinimumFrequency, MaximumFrequency); + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/skyscraper8/Skyscraper/FrequencyListGenerator/BlindscanResult.cs b/skyscraper8/Skyscraper/FrequencyListGenerator/BlindscanResult.cs new file mode 100644 index 0000000..51a2015 --- /dev/null +++ b/skyscraper8/Skyscraper/FrequencyListGenerator/BlindscanResult.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.FrequencyListGenerator +{ + + public enum BlindscanResultState + { + Found = 0, + SettingDiseqc = 1, + Done = 100, + NoLock = 101, + MisFailure = 102, + FilterFailure = 103, + DelFilterFailed = 104, + Scraping = 2, + Tuning = 3, + /// + /// Die SetChannel Funktion von StreamReader hat einen Fehler gemeldet. + /// + TuningFailed = 105, + ModFailure = 106, + PlsFailure = 107, + BlScanFailure = 108, + DataSaving = 4 + } +} diff --git a/skyscraper8/Skyscraper/FrequencyListGenerator/DbBlindscanJob.cs b/skyscraper8/Skyscraper/FrequencyListGenerator/DbBlindscanJob.cs new file mode 100644 index 0000000..611e224 --- /dev/null +++ b/skyscraper8/Skyscraper/FrequencyListGenerator/DbBlindscanJob.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; + +namespace skyscraper5.src.Skyscraper.FrequencyListGenerator +{ + public class DbBlindscanJob + { + public DbBlindscanJob(Guid jobGuid, PhysicalAddress tunerMac, STD_TYPE tunerStandard, int diseqCIndex, + IGpsReceiver jobContextGps, SatellitePosition satellitePosition) + { + this.JobGuid = jobGuid; + this.TunerMAC = tunerMac; + this.TunerStandard = tunerStandard; + this.DiseqCIndex = diseqCIndex; + this.HorizontalHighState = DbBlindscanJobPolarizationStatus.NOT_SELECTED; + this.HorizontalLowState = DbBlindscanJobPolarizationStatus.NOT_SELECTED; + this.VerticalHighState = DbBlindscanJobPolarizationStatus.NOT_SELECTED; + this.VerticalLowState = DbBlindscanJobPolarizationStatus.NOT_SELECTED; + if (jobContextGps != null) + { + if (jobContextGps.HasLock) + { + this.GpsCoordinate = jobContextGps.Coordinate.Clone(); + } + } + + this.SatPosition = satellitePosition; + } + + + public SatellitePosition SatPosition { get; private set; } + + public GpsCoordinate? GpsCoordinate { get; private set; } + + public Guid JobGuid { get; private set; } + public PhysicalAddress TunerMAC { get; private set; } + public STD_TYPE TunerStandard { get; private set; } + public int DiseqCIndex { get; private set; } + + public DbBlindscanJobPolarizationStatus HorizontalLowState; + public DbBlindscanJobPolarizationStatus HorizontalHighState; + public DbBlindscanJobPolarizationStatus VerticalLowState; + public DbBlindscanJobPolarizationStatus VerticalHighState; + + public DateTime DateAdded { get; set; } + + public override string ToString() + { + return $"{nameof(SatPosition)}: {SatPosition}, {nameof(JobGuid)}: {JobGuid}"; + } + + /// + /// + /// + /// Set to 1 to return just the Position or Tuner standard, Set to 2 to get the scan states. + /// + public string ToString(int mode) + { + switch(mode) + { + case 1: + if (SatPosition != null) + return String.Format("{0:0.0}° {1}", SatPosition.angle, SatPosition.cardinalDirection == 0 ? "E" : "W"); + return TunerStandard.ToString(); + case 2: + return String.Format("({0}/{1}/{2}/{3})", (int)HorizontalLowState, (int)HorizontalHighState, (int)VerticalLowState, (int)VerticalHighState); + default: + return ToString(); + } + } + + } + + public enum DbBlindscanJobPolarizationStatus : int + { + SELECTED_WAITING = 0, + SELECTED_BLINDSCANNING = 1, + SELECTED_SCRAPING = 2, + SELECTED_DONE = 100, + NOT_SELECTED = 101, + /// + /// Die Blindscan Funktion in StreamReader.dll hat einen Fehler gemeldet. + /// + SELECTED_FAILED = 102, + /// + /// Die SendDiseqc Funktion in StreamReader.dll hat einen Fehler gemeldet. + /// + SELECTED_FAILED_DISEQC = 103, + /// + /// Der vorliegende Scan zielt nicht auf einen Satelliten ab. + /// + NOT_SELECTED_NON_S_HINT = 105, + } +} diff --git a/skyscraper8/Skyscraper/FrequencyListGenerator/DbBlindscanJobStorage.cs b/skyscraper8/Skyscraper/FrequencyListGenerator/DbBlindscanJobStorage.cs new file mode 100644 index 0000000..c2ca334 --- /dev/null +++ b/skyscraper8/Skyscraper/FrequencyListGenerator/DbBlindscanJobStorage.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; + +namespace skyscraper5.src.Skyscraper.FrequencyListGenerator +{ + public interface IDbBlindscanJobStorage + { + void InsertBlindscanJob(DbBlindscanJob jobInDb); + void UpdateJobState(DbBlindscanJob jobInDb); + void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, SearchResult2 searchResult2); + void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, BlindscanResultState blindscanResultState, SearchResult2 searchResult2); + void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1, SearchResult2 resultSr2, HumanReadableService humanReadableService); + bool TestForIncompleteJob(); + + DbBlindscanJob GetPastBlindscanJob(long offset); + + void DeleteBlindscanJob(Guid guid); + void DeleteBlindscanResults(Guid jobGuid, int i); + void MoveBlScanResultsToAnotherJob(Guid jobGuid1, Guid jobGuid2, int j); + } +} diff --git a/skyscraper8/Skyscraper/FrequencyListGenerator/FrequencyListeGeneratorJunction.cs b/skyscraper8/Skyscraper/FrequencyListGenerator/FrequencyListeGeneratorJunction.cs new file mode 100644 index 0000000..0fc8c9c --- /dev/null +++ b/skyscraper8/Skyscraper/FrequencyListGenerator/FrequencyListeGeneratorJunction.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Net; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Teletext.Wss; + +namespace skyscraper5.src.Skyscraper.FrequencyListGenerator +{ + internal class FrequencyListGeneratorJunction : ISkyscraperUiJunction + { + public FrequencyListGeneratorJunction() + { + sdtServices = new Dictionary(); + } + + private Dictionary sdtServices; + private Dictionary programMappings; + + public void NotifyEvent(EitEvent eitEvent) + { + } + + public void NotifySdtService(SdtService sdtService) + { + if (!sdtServices.ContainsKey(sdtService.ServiceId)) + sdtServices.Add(sdtService.ServiceId, sdtService); + } + + public void NotifyPatProgram(int pmtPid, ushort programId) + { + + } + + public void NotifyPmtProgram(ProgramMapping result, int pmtPid) + { + if (!programMappings.ContainsKey(result.ProgramNumber)) + programMappings.Add(result.ProgramNumber, result); + } + + public void NotifyNit(NitTransportStream transportStream) + { + + } + + public void NotifyMpeTraffic(IpTrafficInfo iti, int ipv4PacketLength) + { + + } + + public void NotifyAit(AitApplication aitApplication) + { + + } + + public void DsmCcModuleAdd(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion) + { + + } + + public void DsmCcModuleProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, + double moduleInfoDownloadProgress) + { + + } + + public void DsmCcModuleComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + + } + + public void NotifyWss(ushort programNumber, WssDataBlock wssDataBlock) + { + + } + + public void NotifyStreamTypeDetection(string contestantTag, int pid) + { + + } + + public void NotifyBat(BatBouquet batBouquet) + { + + } + + public void NotifyBatTs(ushort batBouquetBouquetId, BatTransportStream child) + { + + } + + public void DsmCcVfs(VfsFile vfsFile) + { + + } + + public void NotifyTot(DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + + } + + public void NotifyTdt(DateTime utcTime) + { + + } + + public void NotifyCat(CaDescriptor caDescriptor) + { + + } + + public void NotifyScte35(ushort programNumber, SpliceInsert spliceInsert) + { + + } + + public void NotifyScte35(ushort programNumber, TimeSignal spliceInsert) + { + + } + + public void SetMemorySaverMode(bool saveMemory) + { + + } + + public void NotifyDocsisCarrier(DocsisEnvironment docsisEnvironment) + { + + } + + public void NotifyDocsisFrequency(uint? frequency, bool isUpstream, object mmm) + { + + } + + public void SetGseMode() + { + + } + + public void ShowFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, + int mappingStreamElementaryPid, byte[] imageData) + { + + } + + public void NotifyBlockstreamCarrier() + { + + } + + public IEnumerable GetServices() + { + foreach (var (pmtPid, pmt) in programMappings) + { + ushort serviceId = pmt.ProgramNumber; + if (!sdtServices.ContainsKey(serviceId)) + continue; + SdtService service = sdtServices[serviceId]; + string providerName = service.ServiceProviderName; + string serviceName = service.ServiceName; + ushort? caId = GetCaId(pmt, service); + ServiceDescriptor.ServiceTypeCoding serviceType = service.ServiceType.GetValueOrDefault(); + yield return new HumanReadableService(serviceId, providerName, serviceName, caId, serviceType); + } + } + + private ushort? GetCaId(ProgramMapping pmt, SdtService sdt) + { + if (pmt.CaSystemId.HasValue) + { + return pmt.CaSystemId.Value; + } + + foreach (ProgramMappingStream stream in pmt.Streams) + { + if (stream.CaSystemId.HasValue) + return stream.CaSystemId.Value; + } + + if (sdt.CaIdentifiers != null) + { + if (sdt.CaIdentifiers.Length > 0) + { + return sdt.CaIdentifiers[0]; + } + } + + return null; + } + } +} diff --git a/skyscraper8/Skyscraper/FrequencyListGenerator/HumanReadableService.cs b/skyscraper8/Skyscraper/FrequencyListGenerator/HumanReadableService.cs new file mode 100644 index 0000000..3ffc874 --- /dev/null +++ b/skyscraper8/Skyscraper/FrequencyListGenerator/HumanReadableService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.src.Skyscraper.FrequencyListGenerator +{ + public class HumanReadableService + { + public ushort ServiceId { get; private set; } + public string ProviderName { get; private set; } + public string ServiceName { get; private set; } + public ushort? CaId { get; private set; } + public ServiceDescriptor.ServiceTypeCoding ServiceType { get; private set; } + + public HumanReadableService(ushort serviceId, string providerName, string serviceName, ushort? caId, + ServiceDescriptor.ServiceTypeCoding serviceType) + { + ServiceId = serviceId; + ProviderName = providerName; + ServiceName = serviceName; + CaId = caId; + ServiceType = serviceType; + + if (ProviderName == null) + ProviderName = "???"; + if (ServiceName == null) + ServiceName = "???"; + } + + public override string ToString() + { + return $"{nameof(ServiceId)}: {ServiceId}, {nameof(ProviderName)}: {ProviderName}, {nameof(ServiceName)}: {ServiceName}, {nameof(CaId)}: {CaId}, {nameof(ServiceType)}: {ServiceType}"; + } + } +} diff --git a/skyscraper8/Skyscraper/FrequencyListGenerator/MergeDbBlindscanJobs.cs b/skyscraper8/Skyscraper/FrequencyListGenerator/MergeDbBlindscanJobs.cs new file mode 100644 index 0000000..37b8a08 --- /dev/null +++ b/skyscraper8/Skyscraper/FrequencyListGenerator/MergeDbBlindscanJobs.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.src.Skyscraper.FrequencyListGenerator +{ + internal class MergeDbBlindscanJobs + { + public static void RunAsMain() + { + ScraperStorageFactoryConnectionManager storageFactoryConnectionManager = ScraperStorageFactoryConnectionManager.GetInstance(); + IScraperStorageFactory storageFactory = storageFactoryConnectionManager.AutoGetDefaultFactory(); + IScraperStroage storage = storageFactory.CreateScraperStroage(); + + List jobs = GetMergeableBlindscanJobs(storage); + if (jobs.Count <= 1) + { + Console.WriteLine("Nothing to merge!"); + return; + } + + MergeBlindscanJobs(jobs, storage); + } + + public static void MergeBlindscanJobs(List jobs, IDbBlindscanJobStorage storage) + { + //Zuerst schauen ob im Ziel irgendwas gesäubert werden muss. + DbBlindscanJob mergeTarget = jobs[0]; + DbBlindscanJobPolarizationStatus[] targetCleaningModes = new DbBlindscanJobPolarizationStatus[] + { + mergeTarget.HorizontalLowState, mergeTarget.HorizontalHighState, mergeTarget.VerticalLowState, mergeTarget.VerticalHighState + }; + for (int i = 0; i < targetCleaningModes.Length; i++) + { + switch(targetCleaningModes[i]) + { + case DbBlindscanJobPolarizationStatus.SELECTED_DONE: + continue; + case DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING: + storage.DeleteBlindscanResults(mergeTarget.JobGuid, i); + continue; + case DbBlindscanJobPolarizationStatus.SELECTED_WAITING: + continue; + default: + throw new NotImplementedException(targetCleaningModes[i].ToString()); + } + } + + for (int i = 1; i < jobs.Count; i++) + { + DbBlindscanJob mergeSource = jobs[i]; + DbBlindscanJobPolarizationStatus[] modes = new DbBlindscanJobPolarizationStatus[] + { + mergeSource.HorizontalLowState, mergeSource.HorizontalHighState, mergeSource.VerticalLowState, mergeSource.VerticalHighState + }; + for (int j = 0; j < modes.Length; j++) + { + bool deleted = false; + switch(modes[j]) + { + case DbBlindscanJobPolarizationStatus.NOT_SELECTED: + continue; + case DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING: + storage.DeleteBlindscanJob(mergeSource.JobGuid); + deleted = true; + break; + case DbBlindscanJobPolarizationStatus.SELECTED_WAITING: + continue; + case DbBlindscanJobPolarizationStatus.SELECTED_DONE: + storage.MoveBlScanResultsToAnotherJob(mergeSource.JobGuid, mergeTarget.JobGuid, j); + continue; + default: + throw new NotImplementedException(modes[j].ToString()); + } + if (!deleted) + storage.DeleteBlindscanJob(mergeTarget.JobGuid); + } + } + } + + public static List GetMergeableBlindscanJobs(IScraperStroage storage) + { + + List mergableJobs = new List(); + DbBlindscanJob firstJob = null; + bool hl = false, hh = false, vl = false, vh = false; + long offset = 0; + + while (true) + { + DbBlindscanJob blindscanJob = storage.GetPastBlindscanJob(offset++); + if (blindscanJob == null) + break; + + if (blindscanJob.HorizontalLowState == DbBlindscanJobPolarizationStatus.SELECTED_DONE) + hl = true; + if (blindscanJob.HorizontalHighState == DbBlindscanJobPolarizationStatus.SELECTED_DONE) + hh = true; + if (blindscanJob.VerticalLowState == DbBlindscanJobPolarizationStatus.SELECTED_DONE) + vl = true; + if (blindscanJob.VerticalHighState == DbBlindscanJobPolarizationStatus.SELECTED_DONE) + vh = true; + + if (firstJob == null) + { + firstJob = blindscanJob; + mergableJobs.Add(firstJob); + continue; + } + + if (blindscanJob.DiseqCIndex != firstJob.DiseqCIndex) + break; + if (!blindscanJob.SatPosition.Equals(firstJob.SatPosition)) + break; + if (!blindscanJob.TunerMAC.Equals(firstJob.TunerMAC)) + break; + if (blindscanJob.TunerStandard != firstJob.TunerStandard) + break; + + mergableJobs.Add(blindscanJob); + + if (hl && hh && vl && vh) + break; + } + + mergableJobs.Reverse(); + return mergableJobs; + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/AdditionalNmeaData.cs b/skyscraper8/Skyscraper/Gps/AdditionalNmeaData.cs new file mode 100644 index 0000000..5405fd7 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/AdditionalNmeaData.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Gps +{ + public class AdditionalNmeaData + { + public AdditionalNmeaData() + { + + } + + #region GPVTG + + public double? CourseMagnetic { get; set; } + public double? CourseTrue { get; set; } + public double? SpeedKnots { get; set; } + public double? SpeedKph { get; set; } + + #endregion + + #region GPZDA + + public DateTimeOffset? FixDateTime { get; set; } + + #endregion + + #region GPGGA + + public string Quality { get; set; } + public double? Altitude { get; set; } + public string AltitudeUnits { get; set; } + public int? DpgsStationId { get; set; } + public TimeSpan? FixTime { get; set; } + public double? GeoidalSeparation { get; set; } + public string GeoidalSeparationUnits { get; set; } + public double? Hdop { get; set; } + public int? NumberOfSatellites { get; set; } + public TimeSpan? TimeSinceLastDgpsUpdate { get; set; } + + #endregion + + #region GPGSA + + public string Fix { get; set; } + public string Mode { get; set; } + public double? Pdop { get; set; } + public int[] SatelliteIDs { get; set; } + public double? Vdop { get; set; } + + #endregion + + #region GPGLL + + public string ModeIndicator { get; set; } + public bool? DataActive { get; set; } + + #endregion + + #region GPRMC + + public bool? Active { get; set; } + public double? MagneticVariation { get; set; } + + #endregion + + #region GPGSV + + public char? GnssSignalId { get; set; } + public int? SatellitesInView { get; set; } + + public void AddSatellite(int vehicleId, char vehicleGnssSignalId, double vehicleAzimuth, + double vehicleElevation, int vehicleSignalToNoiseRatio, string system, string talkerId) + { + GpsSatellite satellite = new GpsSatellite(vehicleId, vehicleGnssSignalId, vehicleAzimuth, vehicleElevation, + vehicleSignalToNoiseRatio, system, talkerId); + if (satellites == null) + satellites = new HashSet(); + satellites.Add(satellite); + } + + private HashSet satellites; + + public IReadOnlyCollection Satellites + { + get + { + if (satellites == null) + return null; + return satellites; + } + } + #endregion + + #region GPGBS + + public double? LatitudeError { get; set; } + public double? LongitudeError { get; set; } + public double? AltitudeError { get; set; } + public int? SatelliteId { get; set; } + public double? MissedDetectionProbability { get; set; } + public double? BiasEstimate { get; set; } + public double? StandardDeviation { get; set; } + #endregion + } +} diff --git a/skyscraper8/Skyscraper/Gps/DummyGpsReceiver.cs b/skyscraper8/Skyscraper/Gps/DummyGpsReceiver.cs new file mode 100644 index 0000000..96224b7 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/DummyGpsReceiver.cs @@ -0,0 +1,29 @@ +namespace skyscraper5.Skyscraper.Gps +{ + public class DummyGpsReceiver : IGpsReceiver + { + private readonly bool enabled; + private readonly GpsCoordinate coordinate; + + public DummyGpsReceiver(bool enabled, float lat, float lon) + { + this.enabled = enabled; + this.coordinate = new GpsCoordinate(lat, lon); + } + + public void Start() + { + + } + + public void Stop() + { + + } + + public bool HasLock => enabled; + public GpsCoordinate Coordinate => coordinate; + public bool ProvidesAdditionalNmeaData => false; + public AdditionalNmeaData AdditionalNmeaData => null; + } +} diff --git a/skyscraper8/Skyscraper/Gps/DummyGpsReceiverFactory.cs b/skyscraper8/Skyscraper/Gps/DummyGpsReceiverFactory.cs new file mode 100644 index 0000000..205a28e --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/DummyGpsReceiverFactory.cs @@ -0,0 +1,17 @@ +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Gps +{ + [SkyscraperPlugin] + [PluginPriority(0)] + public class DummyGpsReceiverFactory : IGpsReceiverFactory + { + public bool Enabled { get; set; } + public float Latitude { get; set; } + public float Longitude { get; set; } + public IGpsReceiver CreateGpsReceiver() + { + return new DummyGpsReceiver(Enabled, Latitude, Longitude); + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/GeoIp/GeoIp.cs b/skyscraper8/Skyscraper/Gps/GeoIp/GeoIp.cs new file mode 100644 index 0000000..d5d46e4 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GeoIp/GeoIp.cs @@ -0,0 +1,158 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; + +namespace skyscraper5.Skyscraper.Gps.GeoIp +{ + internal class GeoIp : IGpsReceiver + { + public void Start() + { + keepTryping = true; + workerThread = new Thread(WorkerThreadFunction); + workerThread.Priority = ThreadPriority.Lowest; + workerThread.Start(); + } + + public void Stop() + { + keepTryping = false; + } + + private bool gotLock; + public bool HasLock => gotLock; + + private GpsCoordinate _resolvedCoordinate; + + public GpsCoordinate Coordinate => _resolvedCoordinate; + public bool ProvidesAdditionalNmeaData => false; + public AdditionalNmeaData AdditionalNmeaData => null; + + + private bool keepTryping; + private Thread workerThread; + + private void WorkerThreadFunction() + { + while (keepTryping) + { + try + { + TryGeoIp(); + } + catch (GeoIpException e) + { + Console.WriteLine(e.Error); + Thread.Sleep(e.CooldownTime); + } + } + } + + private void TryGeoIp() + { + DateTime currentDateTime = DateTime.Now; + string fname = string.Format("dbip-city-lite-{0}-{1}.mmdb.gz", currentDateTime.Year, currentDateTime.Month); + FileInfo mmdbFileInfo = new FileInfo(fname); + if (!mmdbFileInfo.Exists) + { + if (!DownloadMmdb(fname)) + { + mmdbFileInfo = LookForOlderMmdb(); + if (mmdbFileInfo == null) + { + throw new GeoIpException(GeoIpError.NoDatabaseAndDownloadFailed, 60000); + } + } + } + + MaxMindDb mmdb = new MaxMindDb(mmdbFileInfo); + IPAddress ipAddress = GetPublicIPAddress(); + _resolvedCoordinate = mmdb.Locate(ipAddress); + gotLock = true; + keepTryping = false; + } + + #region Get MMDB from disk or internet + private bool DownloadMmdb(string version) + { + string url = string.Format("https://download.db-ip.com/free/{0}", version); + WebClient wc = new WebClient(); + wc.DownloadFile(url, version); + return true; + } + + private FileInfo LookForOlderMmdb() + { + DirectoryInfo currentDirectory = new DirectoryInfo("."); + FileInfo[] fileInfos = currentDirectory.GetFiles("dbip-city-lite-*.mmdb.gz"); + if (fileInfos.Length == 0) + return null; + fileInfos.OrderBy(x => x.Name); + return fileInfos[0]; + } +#endregion + + #region IP Fetching strategies + private IPAddress GetPublicIPAddress() + { + RetrievePublicIpAddress[] strategies = new RetrievePublicIpAddress[] + { TryIfconfigMe, TryICanHazIp, TryIpInfoIo, TryApiIpifyOrg, TryIdentMe }; + strategies.Shuffle(); + + for (int i = 0; i < strategies.Length; i++) + { + try + { + return strategies[i](); + } + catch (Exception e) + { + Debug.WriteLine("Failed to retrieve IP using {0}", strategies[i].Method.Name); + Thread.Sleep(1000); + } + } + return null; + } + + private delegate IPAddress RetrievePublicIpAddress(); + private IPAddress TryIfconfigMe() + { + WebClient wc = new WebClient(); + string downloadString = wc.DownloadString(new Uri("http://ifconfig.me")); + return IPAddress.Parse(downloadString); + } + + private IPAddress TryICanHazIp() + { + WebClient wc = new WebClient(); + string downloadString = wc.DownloadString(new Uri("http://icanhazip.com")); + downloadString = downloadString.Replace("\n", ""); + return IPAddress.Parse(downloadString); + } + + private IPAddress TryIpInfoIo() + { + WebClient wc = new WebClient(); + string downloadString = wc.DownloadString(new Uri("http://ipinfo.io/ip")); + return IPAddress.Parse(downloadString); + } + + private IPAddress TryApiIpifyOrg() + { + WebClient wc = new WebClient(); + string downloadString = wc.DownloadString(new Uri("http://api.ipify.org")); + return IPAddress.Parse(downloadString); + } + + private IPAddress TryIdentMe() + { + WebClient wc = new WebClient(); + string downloadString = wc.DownloadString(new Uri("http://ident.me")); + return IPAddress.Parse(downloadString); + } + #endregion + } +} diff --git a/skyscraper8/Skyscraper/Gps/GeoIp/GeoIpException.cs b/skyscraper8/Skyscraper/Gps/GeoIp/GeoIpException.cs new file mode 100644 index 0000000..b1426d4 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GeoIp/GeoIpException.cs @@ -0,0 +1,30 @@ +using System; + +namespace skyscraper5.Skyscraper.Gps.GeoIp +{ + [Serializable] + public class GeoIpException : GpsException + { + public GeoIpError Error { get; } + public int CooldownTime { get; } + + public GeoIpException(GeoIpError error, int cooldownTime, string somethingBadHappened) + { + Error = error; + CooldownTime = cooldownTime; + } + + public GeoIpException(GeoIpError error, int cooldownTime) + : this(error,cooldownTime,error.ToString()) + { + } + } + + public enum GeoIpError + { + NoDatabaseAndDownloadFailed, + NotGzip, + InvalidMmdb, + MmdbError + } +} diff --git a/skyscraper8/Skyscraper/Gps/GeoIp/GeoIpFactory.cs b/skyscraper8/Skyscraper/Gps/GeoIp/GeoIpFactory.cs new file mode 100644 index 0000000..f75b575 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GeoIp/GeoIpFactory.cs @@ -0,0 +1,14 @@ +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Gps.GeoIp +{ + [SkyscraperPlugin] + [PluginPriority(4)] + internal class GeoIpFactory : IGpsReceiverFactory + { + public IGpsReceiver CreateGpsReceiver() + { + return new GeoIp(); + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/GeoIp/MaxMindDb.cs b/skyscraper8/Skyscraper/Gps/GeoIp/MaxMindDb.cs new file mode 100644 index 0000000..0f3890e --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GeoIp/MaxMindDb.cs @@ -0,0 +1,513 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Text; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Skyscraper.Gps.GeoIp +{ + internal class MaxMindDb : IDisposable + { + public MaxMindDb(FileInfo fi) + { + FileStream fileStream = fi.OpenRead(); + if (fileStream.ReadUInt8() != 0x1f) + throw new GeoIpException(GeoIpError.NotGzip, 1000); + if (fileStream.ReadUInt8() != 0x8b) + throw new GeoIpException(GeoIpError.NotGzip, 1000); + + fileStream.Position = 0; + + ms = new MemoryStream(); + GZipStream gzipReader = new GZipStream(fileStream, CompressionMode.Decompress); + gzipReader.CopyTo(ms); + gzipReader.Close(); + fileStream.Close(); + + byte[] arrayed = ms.ToArray(); + ms = new MemoryStream(arrayed); + int metadataOffset = FindMetadataOffset(arrayed); + ms.Position = metadataOffset; + Tuple[] metadata = (Tuple[])ReadObject(); + SanitizeMetadata(metadata); + ipv4StartNode = FindIpv4StartNode(); + } + + public GpsCoordinate Locate(IPAddress ipAddress) + { + int prefixLength; + uint pointer = FindAddressInTree(ipAddress, out prefixLength); + + uint resolved = pointer + DataPointerOffset; + if (resolved >= ms.Length) + { + throw new GeoIpException(GeoIpError.MmdbError, 1000, "DB seems broken."); + } + + ms.Position = resolved; + + Tuple[] packedObject = (Tuple[])ReadObject(); + GpsCoordinate result = UnravelObject(packedObject); + return result; + } + + private GpsCoordinate UnravelObject(Tuple[] raw) + { + string cityName = null; + string countryName = null; + GpsCoordinate? location = null; + foreach (Tuple tuple in raw) + { + switch (tuple.Item1) + { + case "city": + cityName = UnravelCityName((Tuple [])tuple.Item2); + break; + case "continent": + //we won't need that + break; + case "country": + countryName = UnravelCountry((Tuple[])tuple.Item2); + break; + case "location": + location = UnravelLocation((Tuple[])tuple.Item2); + break; + case "subdivisions": + //probably not interesting to us + break; + default: + throw new NotImplementedException(tuple.Item1); + } + } + + if (location.HasValue) + return location.Value; + + if (!string.IsNullOrEmpty(countryName) && !string.IsNullOrEmpty(cityName)) + return GeoDecode(cityName, countryName); + + throw new NotImplementedException(nameof(UnravelObject)); + } + + private GpsCoordinate GeoDecode(string cityName, string countryName) + { + //TODO: Hardcode some coordinates + throw new NotImplementedException(); + } + + private GpsCoordinate? UnravelLocation(Tuple[] raw) + { + double? lon = null, lat = null; + foreach (Tuple tuple in raw) + { + switch (tuple.Item1) + { + case "latitude": + lat = (double)tuple.Item2; + break; + case "longitude": + lon = (double)tuple.Item2; + break; + default: + throw new NotImplementedException(tuple.Item1); + } + } + + if (lon.HasValue && lat.HasValue) + return new GpsCoordinate((float)lat.Value, (float)lon.Value); + else + return null; + } + + private string UnravelCountry(Tuple[] raw) + { + string isoCode; + foreach (Tuple tuple in raw) + { + switch (tuple.Item1) + { + case "geoname_id": + case "is_in_european_union": + break; //not interesting to us. + case "iso_code": + isoCode = (string)tuple.Item2; + break; + case "names": + string countryName = UnravelLocalizedString((Tuple[])tuple.Item2); + return countryName; + default: + throw new NotImplementedException(tuple.Item1); + } + } + + throw new NotImplementedException(nameof(UnravelCountry)); + } + + private string UnravelCityName(Tuple[] raw) + { + foreach (Tuple tuple in raw) + { + switch (tuple.Item1) + { + case "names": + return UnravelLocalizedString((Tuple[])tuple.Item2); + default: + throw new NotImplementedException(tuple.Item1); + } + } + + throw new NotImplementedException(nameof(UnravelCityName)); + } + + private string UnravelLocalizedString(Tuple[] raw) + { + string otherLanguage = null; + foreach (Tuple tuple in raw) + { + switch (tuple.Item1) + { + case "en": + return (string)tuple.Item2; + default: + otherLanguage = (string)tuple.Item2; + break; + } + } + + return string.IsNullOrEmpty(otherLanguage) ? null : otherLanguage; + } + + private uint DataPointerOffset + { + get + { + return SearchTreeSectionSize - nodeCount; + } + } + + private void SanitizeMetadata(Tuple[] metadata) + { + ushort? binary_format_major_version = null, binary_format_minor_version = null; + foreach (Tuple tuple in metadata) + { + switch (tuple.Item1) + { + case "node_count": + nodeCount = (uint)tuple.Item2; + break; + case "record_size": + recordSize = (ushort)tuple.Item2; + break; + case "ip_version": + ipVersion = (ushort)tuple.Item2; + break; + case "database_type": + databaseType = (string)tuple.Item2; + break; + case "languages": + object[] language2 = (object[])tuple.Item2; + languages = new string[language2.Length]; + for (int i = 0; i < language2.Length; i++) + { + languages[i] = (string)language2[i]; + } + break; + case "binary_format_major_version": + binary_format_major_version = (ushort)tuple.Item2; + break; + case "binary_format_minor_version": + binary_format_minor_version = (ushort)tuple.Item2; + break; + case "build_epoch": + ulong unixTimeStamp64 = (ulong)tuple.Item2; + build = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + build = build.AddSeconds(unixTimeStamp64).ToLocalTime(); + break; + case "description": + Tuple[] descs = (Tuple[])tuple.Item2; + description = new Dictionary(); + foreach (var (item1, item2) in descs) + { + description.Add(item1, (string)item2); + } + break; + } + } + if (binary_format_major_version.HasValue && binary_format_minor_version.HasValue) + binaryFormatVersion = new Version((int)binary_format_major_version, (int)binary_format_minor_version); + } + + private MemoryStream ms; + private uint nodeCount; + private ushort recordSize; + private string databaseType; + private string[] languages; + private DateTime build; + private Dictionary description; + private Version binaryFormatVersion; + private ushort ipVersion; + private uint ipv4StartNode; + + private int NodeByteSize + { + get + { + return recordSize / 4; + } + } + private uint SearchTreeSectionSize + { + get + { + return (uint)(((recordSize * 2) / 8) * nodeCount); + } + } + public void Dispose() + { + ms.Dispose(); + } + + private int FindMetadataOffset(byte[] buffer) + { + int offset = buffer.Length - (128 * 1024); + for (; offset < buffer.Length; offset++) + { + if (buffer[offset + 0] == 0xab) + if (buffer[offset + 1] == 0xcd) + if (buffer[offset + 2] == 0xef) + if (buffer[offset + 3] == 'M') + if (buffer[offset + 4] == 'a') + if (buffer[offset + 5] == 'x') + if (buffer[offset + 6] == 'M') + if (buffer[offset + 7] == 'i') + if (buffer[offset + 8] == 'n') + if (buffer[offset + 9] == 'd') + if (buffer[offset + 10] == '.') + if (buffer[offset + 11] == 'c') + if (buffer[offset + 12] == 'o') + if (buffer[offset + 13] == 'm') + return offset + 14; + } + + throw new GeoIpException(GeoIpError.InvalidMmdb, 1000); + } + + private object ReadObject() + { + byte controlByte = ms.ReadUInt8(); + + int type = controlByte >> 5; + int size = (controlByte & 0x01f); + + if (type == 0) + { + type = ms.ReadUInt8() + 7; + } + + if (size == 29) + { + size += ms.ReadUInt8(); + } + else if (size == 30) + { + throw new NotImplementedException(); + } + else if (size == 31) + { + throw new NotImplementedException(); + } + + switch (type) + { + case 1: //Pointer + int pointerSizeType = size >> 3; + pointerSizeType &= 0x03; + uint pointerValue = (uint)(size & 0x07); + if (pointerSizeType == 0) + { + byte a = ms.ReadUInt8(); + pointerValue <<= 8; + pointerValue += a; + } + else if (pointerSizeType == 1) + { + pointerValue <<= 16; + pointerValue += (uint)(ms.ReadUInt8() << 8); + pointerValue += (uint)(ms.ReadUInt8()); + pointerValue += 2048; + } + else if (pointerSizeType == 2) + { + pointerValue <<= 24; + pointerValue += (uint)(ms.ReadUInt8() << 16); + pointerValue += (uint)(ms.ReadUInt8() << 8); + pointerValue += (uint)(ms.ReadUInt8()); + pointerValue += 526336; + } + else if (pointerSizeType == 3) + throw new NotImplementedException("ptrSizeType 3"); + else + throw new AccessViolationException(); + + long stackedPosition = ms.Position; + pointerValue += SearchTreeSectionSize; + pointerValue += 16; + ms.Position = pointerValue; + object result = ReadObject(); + ms.Position = stackedPosition; + return result; + case 2: //UTF-8 String + byte[] utf8Bytes = ms.ReadBytes(size); + string utf8String = Encoding.UTF8.GetString(utf8Bytes); + return utf8String; + case 3: + byte[] doubleBufferBacking = ms.ReadBytes(8); + ReadOnlySpan doubleBuffer = new ReadOnlySpan(doubleBufferBacking); + double doubleResult = BinaryPrimitives.ReadDoubleBigEndian(doubleBuffer); + return doubleResult; + case 4: //bytes + byte[] bytesBytes = ms.ReadBytes(size); + return bytesBytes; + case 5: //uint16 + ushort uint16Result; + if (size == 1) + uint16Result = ms.ReadUInt8(); + else if (size == 0) + uint16Result = 0; + else + throw new NotImplementedException(String.Format("uint16 with {0} bytes", size)); + return uint16Result; + case 6: //uint32 + uint uint32Result; + if (size == 4) + uint32Result = ms.ReadUInt32BE(); + else if (size == 3) + { + uint32Result = ms.ReadUInt16BE(); + uint32Result <<= 8; + uint32Result += ms.ReadUInt8(); + } + else + throw new NotImplementedException(String.Format("uint32 with {0} bytes", size)); + return uint32Result; + case 7: //map + Tuple[] resultMap = new Tuple[size]; + for (int i = 0; i < size; i++) + { + string key = (string)ReadObject(); + object value = ReadObject(); + resultMap[i] = new Tuple(key, value); + } + return resultMap; + case 9: //uint64 + ulong uint64Result; + if (size == 8) + uint64Result = ms.ReadUInt64BE(); + else if (size == 4) + uint64Result = ms.ReadUInt32BE(); + else + throw new NotImplementedException(String.Format("uint64 with {0} bytes", size)); + return uint64Result; + case 11: //array + object[] resultArray = new object[size]; + for (int i = 0; i < size; i++) + { + resultArray[i] = ReadObject(); + } + + return resultArray; + case 14: //boolean + bool boolResult; + if (size == 1) + boolResult = true; + else if (size == 0) + boolResult = false; + else + throw new GeoIpException(GeoIpError.MmdbError, 1000, "Bad boolean data"); + return boolResult; + default: + //https://maxmind.github.io/MaxMind-DB/ + throw new NotImplementedException(String.Format("type {0}", type)); + } + } + + private uint FindAddressInTree(IPAddress address, out int prefixLength) + { + byte[] rawAddress = address.GetAddressBytes(); + + int bitLength = rawAddress.Length * 8; + uint record = ipVersion == 6 && bitLength == 32 ? ipv4StartNode : 0; + uint nodeCount = this.nodeCount; + + var i = 0; + for (; i < bitLength && record < nodeCount; i++) + { + int bit = 1 & (rawAddress[i >> 3] >> (7 - (i % 8))); + record = ReadNode(record, bit); + } + prefixLength = i; + if (record == nodeCount) + { + // record is empty + return 0; + } + if (record > nodeCount) + { + // record is a data pointer + return record; + } + throw new GeoIpException(GeoIpError.MmdbError, 1000, "Something bad happened"); + } + + private uint FindIpv4StartNode() + { + if (ipVersion == 4) + return 0; + + uint node = 0; + for (int i = 0; i < 96 && node < this.nodeCount; i++) + { + node = ReadNode(node); + } + return node; + } + + private uint ReadNode(uint nodeNumber, int index = 0) + { + ms.Position = nodeNumber * NodeByteSize; + ushort size = recordSize; + + switch (size) + { + case 24: + { + ms.Position = ms.Position + (index * 3); + //return _database.ReadVarInt(offset, 3); + throw new NotImplementedException(); + } + case 28: + { + if (index == 0) + { + uint v = ms.ReadUInt32BE(); + return (v & 0xF0) << 20 | (0xFFFFFF & (v >> 8)); + } + + ms.Position += 3; + return ms.ReadUInt32BE() & 0x0FFFFFFF; + } + case 32: + { + ms.Position = ms.Position + (index * 4); + //return _database.ReadInt(offset); + throw new NotImplementedException(); + } + } + + throw new NotImplementedException($"Unknown record size: {size}"); + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/GpsCoordinate.cs b/skyscraper8/Skyscraper/Gps/GpsCoordinate.cs new file mode 100644 index 0000000..51b9561 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GpsCoordinate.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; + +namespace skyscraper5.Skyscraper.Gps +{ + public struct GpsCoordinate + { + public GpsCoordinate(float lon, float lat) + { + this.Latitude = lat; + this.Longitude = lon; + } + + public float Latitude { get; set; } + + public float Longitude { get; set; } + + public bool Equals(GpsCoordinate other) + { + return Latitude.Equals(other.Latitude) && Longitude.Equals(other.Longitude); + } + + public override bool Equals(object obj) + { + return obj is GpsCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Latitude, Longitude); + } + + public override string ToString() + { + return String.Format("{0}, {1}", Latitude.ToString(CultureInfo.InvariantCulture), Longitude.ToString(CultureInfo.InvariantCulture)); + } + + public GpsCoordinate Clone() + { + GpsCoordinate copy = new GpsCoordinate(); + copy.Latitude = this.Latitude; + copy.Longitude = this.Longitude; + return copy; + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/GpsException.cs b/skyscraper8/Skyscraper/Gps/GpsException.cs new file mode 100644 index 0000000..6615ca9 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GpsException.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.Serialization; + +namespace skyscraper5.Skyscraper.Gps +{ + [Serializable] + public class GpsException : SkyscraperException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public GpsException() + { + } + + public GpsException(string message) : base(message) + { + } + + public GpsException(string message, Exception inner) : base(message, inner) + { + } + + protected GpsException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/GpsManager.cs b/skyscraper8/Skyscraper/Gps/GpsManager.cs new file mode 100644 index 0000000..ca78a57 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GpsManager.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Gps +{ + public class GpsManager + { + /// + /// Reads the ID of the GPS Receiver configured in skyscraper5.ini + /// + /// The ID of the GPS Receiver configured in skyscraper5.ini, or 0 if none is configured. + public static int GetConfiguredGpsId() + { + Ini ini = PluginManager.GetInstance().Ini; + if (ini == null) + return 0; + + if (!ini.ContainsKey("startup")) + return 0; + + IniSection section = ini["startup"]; + if (!section.ContainsKey("gps")) + return 0; + + return Convert.ToInt32(section["gps"]); + } + + /// + /// Retrieves the factory for a GPS receiver. + /// + /// The ID of the GPS Receiver you want. + /// An instance of a fitting Factory class, or null if the GPS receiver is not known. + public static IGpsReceiverFactory GetGpsReceiverFactoryById(int id) + { + ReadOnlyDictionary gpsReceiverFactories = PluginManager.GetInstance().GetGpsReceiverFactories(); + if (gpsReceiverFactories.ContainsKey(id)) + return gpsReceiverFactories[id]; + else + return null; + } + + /// + /// Populates a GPS receiever factory from the settings found in skyscraper5.ini + /// + /// The factory to configure. + public static void AutoconfigureGpsReceiverFactory(IGpsReceiverFactory factory) + { + long id = PluginPrioritySorter.GetPriorityFromObject(factory); + string catName = String.Format("gps{0}", id); + PluginManager.GetInstance().AutoconfigureObject(catName, factory); + } + + internal static void GpsTest() + { + int configuredGpsId = GetConfiguredGpsId(); + IGpsReceiverFactory factory = GetGpsReceiverFactoryById(configuredGpsId); + AutoconfigureGpsReceiverFactory(factory); + IGpsReceiver gpsReceiver = factory.CreateGpsReceiver(); + + Console.WriteLine("Starting GPS Receiver..."); + gpsReceiver.Start(); + + Console.Write("Waiting for GPS Data to become available"); + int waiter = 1; + while (!gpsReceiver.HasLock) + { + Thread.Sleep(waiter); + waiter *= 2; + if (waiter > 1000) + waiter = 1000; + Console.Write("."); + } + Console.WriteLine("...OK!"); + + Console.WriteLine(); + Console.WriteLine("Lock at: {0}", gpsReceiver.Coordinate); + Console.WriteLine(); + + gpsReceiver.Stop(); + } + + public static ReadOnlyDictionary GetGpsTunerFactories() + { + ReadOnlyDictionary gpsReceiverFactories = PluginManager.GetInstance().GetGpsReceiverFactories(); + return gpsReceiverFactories; + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/GpsSatellite.cs b/skyscraper8/Skyscraper/Gps/GpsSatellite.cs new file mode 100644 index 0000000..f7869e0 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/GpsSatellite.cs @@ -0,0 +1,44 @@ +using System; + +namespace skyscraper5.Skyscraper.Gps +{ + public class GpsSatellite + { + public int Id { get; } + public char SignalId { get; } + public double Azimuth { get; } + public double Elevation { get; } + public int SignalToNoiseRatio { get; } + public string System { get; } + public string TalkerId { get; } + + public GpsSatellite(int id, char signalId, double azimuth, double elevation, int signalToNoiseRatio, string system, string talkerId) + { + Id = id; + SignalId = signalId; + Azimuth = azimuth; + Elevation = elevation; + SignalToNoiseRatio = signalToNoiseRatio; + System = system; + TalkerId = talkerId; + } + + protected bool Equals(GpsSatellite other) + { + return Id == other.Id && SignalId == other.SignalId && System == other.System && TalkerId == other.TalkerId; + } + + 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((GpsSatellite)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, SignalId, System, TalkerId); + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/IGpsReceiver.cs b/skyscraper8/Skyscraper/Gps/IGpsReceiver.cs new file mode 100644 index 0000000..f3b7938 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/IGpsReceiver.cs @@ -0,0 +1,14 @@ +namespace skyscraper5.Skyscraper.Gps +{ + public interface IGpsReceiver + { + public void Start(); + public void Stop(); + + public bool HasLock { get; } + public GpsCoordinate Coordinate { get; } + public bool ProvidesAdditionalNmeaData { get; } + + public AdditionalNmeaData AdditionalNmeaData { get; } + } +} diff --git a/skyscraper8/Skyscraper/Gps/IGpsReceiverFactory.cs b/skyscraper8/Skyscraper/Gps/IGpsReceiverFactory.cs new file mode 100644 index 0000000..e9773eb --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/IGpsReceiverFactory.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Skyscraper.Gps +{ + public interface IGpsReceiverFactory + { + IGpsReceiver CreateGpsReceiver(); + } +} diff --git a/skyscraper8/Skyscraper/Gps/SatelliteBeam.cs b/skyscraper8/Skyscraper/Gps/SatelliteBeam.cs new file mode 100644 index 0000000..4aaae60 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/SatelliteBeam.cs @@ -0,0 +1,43 @@ +using System; + +namespace skyscraper5.Skyscraper.Gps +{ + public class SatelliteBeam + { + public int Id { get; } + public float Position { get; } + public string Name { get; } + public DateTime ProcessTimestamp { get; } + + public SatelliteBeam(int id, float position, string name, DateTime processTimestamp) + { + Id = id; + Position = position; + Name = name; + ProcessTimestamp = processTimestamp; + } + + protected bool Equals(SatelliteBeam other) + { + return Id == other.Id && ProcessTimestamp.Equals(other.ProcessTimestamp); + } + + 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((SatelliteBeam)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, ProcessTimestamp); + } + + public override string ToString() + { + return $"{nameof(Position)}: {Position}, {nameof(Name)}: {Name}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/SatelliteBeamFootprint.cs b/skyscraper8/Skyscraper/Gps/SatelliteBeamFootprint.cs new file mode 100644 index 0000000..802e1d1 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/SatelliteBeamFootprint.cs @@ -0,0 +1,48 @@ +using System; +using skyscraper5.Skyscraper.Drawing; + +namespace skyscraper5.Skyscraper.Gps +{ + public class SatelliteBeamFootprint + { + public int BeamId { get; } + public DateTime ProcessTimestamp { get; } + public string Name { get; } + public DateTime DateAdded { get; } + public string PolygonId { get; } + public Polygon Polygon { get; private set; } + + public SatelliteBeamFootprint(int beamId, DateTime processTimestamp, string name, string polygon, DateTime dateAdded, string polygonId) + { + BeamId = beamId; + ProcessTimestamp = processTimestamp; + Name = name; + DateAdded = dateAdded; + PolygonId = polygonId; + Polygon = new Polygon(polygon); + } + + protected bool Equals(SatelliteBeamFootprint other) + { + return BeamId == other.BeamId && ProcessTimestamp.Equals(other.ProcessTimestamp) && Name == other.Name && PolygonId == other.PolygonId; + } + + 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((SatelliteBeamFootprint)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(BeamId, ProcessTimestamp, Name, PolygonId); + } + + public override string ToString() + { + return $"{nameof(BeamId)}: {BeamId}, {nameof(Name)}: {Name}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Gps/WhatCanIReceive.cs b/skyscraper8/Skyscraper/Gps/WhatCanIReceive.cs new file mode 100644 index 0000000..13b8099 --- /dev/null +++ b/skyscraper8/Skyscraper/Gps/WhatCanIReceive.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using skyscraper5.Skyscraper.Drawing; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.Skyscraper.Gps +{ + public class WhatCanIReceive + { + public static void StandaloneProgram() + { + PluginManager pluginManager = PluginManager.GetInstance(); + IScraperStorageFactory storageFactory = ScraperStorageFactoryConnectionManager.GetInstance().AutoGetDefaultFactory(); + IScraperStroage scraperStroage = storageFactory.CreateScraperStroage(); + + WhatCanIReceive instance = new WhatCanIReceive(scraperStroage); + + int configuredGpsId = GpsManager.GetConfiguredGpsId(); + IGpsReceiverFactory receiverFactory = GpsManager.GetGpsReceiverFactoryById(configuredGpsId); + GpsManager.AutoconfigureGpsReceiverFactory(receiverFactory); + IGpsReceiver gpsReceiver = receiverFactory.CreateGpsReceiver(); + gpsReceiver.Start(); + while (!gpsReceiver.HasLock) + Thread.Sleep(100); + GpsCoordinate coordinate = gpsReceiver.Coordinate; + PointF point = new PointF(coordinate.Longitude, coordinate.Latitude); + + IEnumerable> receptableSatellites = instance.GetReceptableSatellites(point); + foreach (var (beam, footprint) in receptableSatellites) + { + Console.WriteLine("[{2}] {0} with {1}", beam.Name, footprint.Name, beam.Position); + } + gpsReceiver.Stop(); + } + + public IScraperStroage Storage { get; } + + public WhatCanIReceive(IScraperStroage storage) + { + Storage = storage; + } + + public IEnumerable> GetReceptableSatellites(PointF positionOnEath) + { + IEnumerable satelliteBeams = Storage.BeamsSelectEnabled(); + foreach (SatelliteBeam satelliteBeam in satelliteBeams) + { + if (satelliteBeam.Position > positionOnEath.Y + 80) + continue; + if (satelliteBeam.Position < positionOnEath.Y - 80) + continue; + List footprints = Storage.BeamsSelectFootprints(satelliteBeam.Id, satelliteBeam.ProcessTimestamp); + footprints.Sort((x, y) => String.Compare(y.Name, x.Name, StringComparison.Ordinal)); + foreach (SatelliteBeamFootprint footprint in footprints) + { + if (footprint.Polygon.Wraps(positionOnEath)) + { + yield return new Tuple(satelliteBeam, footprint); + break; + } + } + } + yield break; + } + } +} diff --git a/skyscraper8/Skyscraper/GsEventHandler.cs b/skyscraper8/Skyscraper/GsEventHandler.cs new file mode 100644 index 0000000..6c9e640 --- /dev/null +++ b/skyscraper8/Skyscraper/GsEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper +{ + internal interface IGsEventHandler + { + void OnIpDatagram(int pid, byte[] payload); + void GsIpTrafficDetected(); + } +} diff --git a/skyscraper8/Skyscraper/Hasher.cs b/skyscraper8/Skyscraper/Hasher.cs new file mode 100644 index 0000000..9ed257d --- /dev/null +++ b/skyscraper8/Skyscraper/Hasher.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ionic.Crc; + +namespace skyscraper5.Skyscraper +{ + public class Hasher + { + public static int HashObjects(params object[] objects) + { + CRC32 crc32 = new CRC32(); + foreach (object o in objects) + { + string name = o.GetType().Name; + switch (name) + { + case nameof(Int32): + crc32.SlurpBlock(BitConverter.GetBytes((int)o), 0, 4); + break; + case nameof(UInt16): + crc32.SlurpBlock(BitConverter.GetBytes((ushort)o), 0, 2); + break; + case nameof(Byte): + crc32.SlurpBlock(new byte[] { (byte)o }, 0, 1); + break; + case nameof(String): + byte[] bytes = Encoding.ASCII.GetBytes(o.ToString()); + crc32.SlurpBlock(bytes, 0, bytes.Length); + break; + default: + throw new NotImplementedException(name); + } + } + return crc32.Crc32Result; + } + } +} diff --git a/skyscraper8/Skyscraper/Headless/HeadlessJob.cs b/skyscraper8/Skyscraper/Headless/HeadlessJob.cs new file mode 100644 index 0000000..200a9a0 --- /dev/null +++ b/skyscraper8/Skyscraper/Headless/HeadlessJob.cs @@ -0,0 +1,34 @@ +using System; + +namespace skyscraper5.Skyscraper.Headless +{ + public class HeadlessJob + { + public HeadlessJob() + { + + } + + public HeadlessJob(HeadlessJobType type) + { + uuid = Guid.NewGuid(); + jobType = type; + isSynthetic = true; + } + + public Guid uuid; + public HeadlessJobType jobType; + public Int32 iArg1; + public string sArg1; + public bool isSynthetic; + } + + public enum HeadlessJobType + { + Sleep, + BlindscanEverything, + NitScan, + MassImport, + ReimportByTag1 + } +} diff --git a/skyscraper8/Skyscraper/IO/BcdDecoder.cs b/skyscraper8/Skyscraper/IO/BcdDecoder.cs new file mode 100644 index 0000000..f6ccc7a --- /dev/null +++ b/skyscraper8/Skyscraper/IO/BcdDecoder.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.IO +{ + internal static class BcdDecoder + { + public static long UnpackBcd(this byte i) + { + return UnpackBcd(new byte[] { i }); + } + public static long UnpackBcd(this ushort i) + { + return UnpackBcd(BitConverter.GetBytes(i)); + } + public static long UnpackBcd(this uint i) + { + return UnpackBcd(BitConverter.GetBytes(i)); + } + + public static long UnpackBcd(byte[] buffer) + { + Array.Reverse(buffer); + long result = 0; + for (int i = 0; i < buffer.Length; i++) + { + result *= 10; + int i1 = ((buffer[i] & 0xf0) >> 4); + result += i1; + result *= 10; + int i2 = ((buffer[i] & 0x0f)); + result += i2; + } + return result; + } + } +} diff --git a/skyscraper8/Skyscraper/IO/Constants.cs b/skyscraper8/Skyscraper/IO/Constants.cs new file mode 100644 index 0000000..0aa4a78 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/Constants.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.IO +{ + public static class Constants + { + public const int TS_PACKET_SIZE = 188; + public const int LEAST_COMMON_MULTIPLE_TS_IN_512_BLOCKS = 24064; + public const int LEAST_COMMON_MULTIPLE_TS_IN_2K_BLOCKS = 96256; + public const int LEAST_COMMON_MULTIPLE_TS_IN_4K_BLOCKS = 192512; + } +} diff --git a/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Delegates.cs b/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Delegates.cs new file mode 100644 index 0000000..19d627a --- /dev/null +++ b/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Delegates.cs @@ -0,0 +1,38 @@ +using System; + +namespace skyscraper5.Skyscraper.IO.CrazycatStreamReader +{ + /// + /// A matching called is called from a filter whenever one or more packets was received. + /// + /// The pointer where the received can be read from. + /// The length of data received in bytes + public delegate void StdcallDvbCallback(IntPtr data, int length); + + /// + /// A matching called is called from a filter whenever one or more packets was received. + /// + /// The pointer where the received can be read from. + /// The length of data received in bytes + /// The PID from which the data came from. + public delegate void StdcallDvbCallbackEx(IntPtr data, int length, int pid); + + /// + /// A matching method is called for each tuner detected by CheckForDVBEx. + /// + /// The ID to use in StartDvbEx + /// The name of the tuner. + public delegate void DvbEnumCallback(int index, string name); + + /// + /// A matching method is called for each detected tuner by CheckForDVBExEx + /// + /// The ID to put into StartDvbEx + /// The name of the tuner + /// The network type supported by the tuner. + public delegate void DvbEnumCallbackEx(int index, string name, STD_TYPE type); + + public delegate void AirScanCallback(ref SearchResult2 searchResult); + + public delegate void BlScanCallback(ref SearchResult searchResult); +} diff --git a/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Enums.cs b/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Enums.cs new file mode 100644 index 0000000..dc85cdd --- /dev/null +++ b/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Enums.cs @@ -0,0 +1,502 @@ +using System; + +namespace skyscraper5.Skyscraper.IO.CrazycatStreamReader +{ + /// + /// FEC error correction methods. + /// + public enum VITERBIRATE_TYPE : int + { + VR_AUTO = 0, // DVB-S: automatic detection of viterbi rate + VR_1_2 = 1, + VR_2_3 = 2, + VR_3_4 = 3, + VR_4_5 = 4, + VR_5_6 = 5, + VR_6_7 = 6, + VR_7_8 = 7, + VR_8_9 = 8, + VR_9_10 = 9, + VR_1_4 = 10, + VR_1_3 = 11, + VR_2_5 = 12, + VR_3_5 = 13, + VR_5_9 = 14, + VR_7_9 = 15, + VR_8_15 = 16, + VR_11_15 = 17, + VR_13_18 = 18, + VR_9_20 = 19, + VR_11_20 = 20, + VR_23_36 = 21, + VR_25_36 = 22, + VR_13_45 = 23, + VR_26_45 = 24, + VR_28_45 = 25, + VR_32_45 = 26, + VR_77_90 = 27, + } + + /// + /// Modulation type + /// + public enum MOD_TYPE : int + { + /// + /// Automatic detection of modulation type + /// + MOD_AUTO = 0, + + /// + /// QPSK (DVB-S) + /// + MOD_QPSK = 1, + + /// + /// 8PSK (DVB-S2) + /// + MOD_8PSK = 2, + + /// + /// 16APSK (DVB-S2) + /// + MOD_16APSK = 3, + + /// + /// 32APSK (DVB-S2) + /// + MOD_32APSK = 4, + + /// + /// QPSK (DVB-S2) + /// + MOD_QPSK2 = 5, + + /// + /// Turbo QPSK + /// + MOD_QPSK_T = 6, + + /// + /// Turbo 8PSK + /// + MOD_8PSK_T = 7, + + /// + /// Turbo 16QAM + /// + MOD_16QAM_T = 8, + + /// + /// DirectTV DSS + /// + MOD_DSS = 9, + + /// + /// BPSK + /// + MOD_BPSK = 10, + + /// + /// Offset QPSK + /// + MOD_OQPSK = 11, + + /// + /// Digicipher II Combo + /// + MOD_DC2_COMBO = 12, + + /// + /// Digicipher II Split(I) + /// + MOD_DC2_SPLIT_I = 13, + + /// + /// Digicipher II Split(Q) + /// + MOD_DC2_SPLIT_Q = 14, + + /// + /// Digicipher II Offset QPSK + /// + MOD_DC2_OQPSK = 15, + + /// + /// 8-Level Vestigial Sideband + /// + MOD_8VSB = 16, + + /// + /// 16-Level Vestigial Sideband + /// + MOD_16VSB = 17, + MOD_16QAM = 18, + MOD_32QAM = 19, + MOD_64QAM = 20, + MOD_128QAM = 21, + MOD_256QAM = 22, + + /// + /// 512QAM (DVB-C2) + /// + MOD_512QAM = 23, + + /// + /// 1024QAM (DVB-C2) + /// + MOD_1024QAM = 24, + + /// + /// 4096QAM (DVB-C2) + /// + MOD_4096QAM = 25, + + /// + /// VL-SNR (DVB-S2X) + /// + MOD_VLSNR = 26, + + /// + /// 64APSK (DVB-S2X) + /// + MOD_64APSK = 27, + + /// + /// 128APSK (DVB-S2X) + /// + MOD_128APSK = 28, + + /// + /// 256APSK (DVB-S2X) + /// + MOD_256APSK = 29, + + /// + /// 8PSK_L (DVB-S2X) + /// + MOD_8PSK_L = 30, + + /// + /// 16APSK_L (DVB-S2X) + /// + MOD_16APSK_L = 31, + + /// + /// 32APSK_L (DVB-S2X) + /// + MOD_32APSK_L = 32, + + /// + /// 64APSK_L (DVB-S2X) + /// + MOD_64APSK_L = 33, + + /// + /// 256APSK_L (DVB-S2X) + /// + MOD_256APSK_L = 34, + + /// + /// 1024APSK (DVB-S2X) + /// + MOD_1024APSK = 35, + } + + /// + /// Network types + /// + public enum STD_TYPE : int + { + /// + /// Autodetect the network type. + /// + STD_AUTO = 0, + + /// + /// A DVB-S compliant satellite network. + /// + STD_DVBS = 1, + + /// + /// A DVB-S2 compliant satellite network. + /// + STD_DVBS2 = 2, + + /// + /// The target network is DirecTV + /// + STD_DIRECTV = 3, + + /// + /// A DVB-Turbo compliant satellite network. + /// + STD_TURBO = 4, + + /// + /// A Digicipher II compliant cable or satellite network. Usually found in America. + /// + STD_DC2 = 5, + + /// + /// A DVB-T compliant network. + /// + STD_DVBT = 6, + + /// + /// A DVB-T2 compliant network. + /// + STD_DVBT2 = 7, + + /// + /// A DVB-C compliant cable television network. + /// + STD_DVBC = 8, + + /// + /// A DVB-C2 compliant cable television network. + /// + STD_DVBC2 = 9, + + /// + /// An ATSC compliant network. American. + /// + STD_ATSC = 10, + + /// + /// An ISDB-T compliant network. Commonly found in Japan or Brazil. + /// + STD_ISDBT = 11, + + /// + /// An ISDB-S compliant network. Commonly found in Japan. + /// + STD_ISDBS = 12, + } + + /// + /// Possible values for the signal-curve steepness. + /// + public enum ROLLOFF_TYPE + { + ROLLOFF_AUTO = 0, // Automatic detection of S2-rolloff + ROLLOFF_35, // 35% RollOff + ROLLOFF_25, // 25% RollOff + ROLLOFF_20, // 20% RollOff + ROLLOFF_15, // 15% RollOff + ROLLOFF_10, // 10% RollOff + ROLLOFF_5, // 5% RollOff + } + + /// + /// DVB-S2 specific modulations + /// + public enum S2MODCODE : int + { + S2_DUMMY_PLF = 0, + S2_QPSK_14 = 1, + S2_QPSK_13 = 2, + S2_QPSK_25 = 3, + S2_QPSK_12 = 4, + S2_QPSK_35 = 5, + S2_QPSK_23 = 6, + S2_QPSK_34 = 7, + S2_QPSK_45 = 8, + S2_QPSK_56 = 9, + S2_QPSK_89 = 10, + S2_QPSK_910 = 11, + S2_8PSK_35 = 12, + S2_8PSK_23 = 13, + S2_8PSK_34 = 14, + S2_8PSK_56 = 15, + S2_8PSK_89 = 16, + S2_8PSK_910 = 17, + S2_16APSK_23 = 18, + S2_16APSK_34 = 19, + S2_16APSK_45 = 20, + S2_16APSK_56 = 21, + S2_16APSK_89 = 22, + S2_16APSK_910 = 23, + S2_32APSK_34 = 24, + S2_32APSK_45 = 25, + S2_32APSK_56 = 26, + S2_32APSK_89 = 27, + S2_32APSK_910 = 28, + S2X_VLSNR1 = 64, + S2X_VLSNR2 = 29, + S2X_QPSK_13_45 = 30, + S2X_QPSK_9_20 = 31, + S2X_QPSK_11_20 = 32, + S2X_8APSK_5_9_L = 33, + S2X_8APSK_26_45_L = 34, + S2X_8PSK_23_36 = 35, + S2X_8PSK_25_36 = 36, + S2X_8PSK_13_18 = 37, + S2X_16APSK_1_2_L = 38, + S2X_16APSK_8_15_L = 39, + S2X_16APSK_5_9_L = 40, + S2X_16APSK_26_45 = 41, + S2X_16APSK_3_5 = 42, + S2X_16APSK_3_5_L = 43, + S2X_16APSK_28_45 = 44, + S2X_16APSK_23_36 = 45, + S2X_16APSK_2_3_L = 46, + S2X_16APSK_25_36 = 47, + S2X_16APSK_13_18 = 48, + S2X_16APSK_7_9 = 49, + S2X_16APSK_77_90 = 50, + S2X_32APSK_2_3_L = 51, + S2X_32APSK_32_45 = 89, + S2X_32APSK_11_15 = 52, + S2X_32APSK_7_9 = 53, + S2X_64APSK_32_45_L = 54, + S2X_64APSK_11_15 = 55, + S2X_64APSK_7_9 = 95, + S2X_64APSK_4_5 = 97, + S2X_64APSK_5_6 = 99, + S2X_128APSK_3_4 = 56, + S2X_128APSK_7_9 = 57, + S2X_256APSK_29_45_L = 58, + S2X_256APSK_2_3_L = 59, + S2X_256APSK_31_45_L = 60, + S2X_256APSK_32_45 = 61, + S2X_256APSK_11_15_L = 62, + S2X_256APSK_3_4 = 63, + S2X_QPSK_11_45 = 64, + S2X_QPSK_4_15 = 65, + S2X_QPSK_14_45 = 66, + S2X_QPSK_7_15 = 67, + S2X_QPSK_8_15 = 68, + S2X_QPSK_32_45 = 69, + S2X_8PSK_7_15 = 70, + S2X_8PSK_8_15 = 71, + S2X_8PSK_26_45 = 72, + S2X_8PSK_32_45 = 73, + S2X_16APSK_7_15 = 74, + S2X_16APSK_8_15 = 75, + S2X_16APSK_26_45_S = 76, + S2X_16APSK_3_5_S = 77, + S2X_16APSK_32_45 = 78, + S2X_32APSK_2_3 = 79, + S2X_32APSK_32_45_S = 80, + + /* POFF Modes */ + S2X_8PSK = 81, + S2X_32APSK = 82, + S2X_256APSK = 83, + + /* PON Modes */ + S2X_16APSK = 84, + S2X_64APSK = 85, + S2X_1024APSK = 86, + } + + /// + /// These flags can be OR-ed together to form a DISEqC Switch Opcode + /// + [Flags] + public enum DiSEqC_Opcode : byte + { + /// + /// Magic bits, set this. + /// + DISEQC_HIGH_NIBBLE = 0xF0, + DISEQC_LOW_BAND = 0x00, + DISEQC_HIGH_BAND = 0x01, + DISEQC_VERTICAL = 0x00, + DISEQC_HORIZONTAL = 0x02, + DISEQC_POSITION_A = 0x00, + DISEQC_POSITION_B = 0x04, + DISEQC_OPTION_A = 0x00, + DISEQC_OPTION_B = 0x08 + } + + + [Flags] + public enum Caps : int + { + BitFilter = 0x01, + SR_RC = 0x02, + + /// + /// The tuner can act as a networking interface. + /// + SR_GETMAC = 0x04, + SR_SIGNALEX = 0x08, + SR_SIMPLETB = 0x10, + + /// + /// The tuner supports up to 4 LNBs. + /// + SR_DISEQC10 = 0x20, + + /// + /// The tuner supports up to 16 LNBs. + /// + SR_DISEQC1X = 0x40, + + /// + /// The tuner supports rotatable satellite dishes. + /// + SR_DISEQC2X = 0x80, + + /// + /// The tuner can mearue RF Strengths of satellite frequencies. + /// + SR_RFSCAN = 0x100, + + /// + /// The tuner is happy with approximate frequencies. + /// + SR_BLSCAN = 0x200, + + /// + /// The tuner can measure constellation data for satellite networks. + /// + SR_IQSCAN = 0x400, + SR_MISSEL = 0x800, + + /// + /// The tuner has an EEPROM + /// + SR_EEPROM = 0x1000, + + /// + /// The tuner can report signal information for satellite networks. + /// + SR_SIGINFO = 0x2000, + + /// + /// The tuner can report signal information for non-satellite networks. + /// + SR_SIGINFO2 = 0x4000, + SR_RFSCAN2 = 0x10000, + + /// + /// The tuner can blind-scan satellite networks. + /// + SR_BLSCAN2 = 0x20000, + + /// + /// The tuner supports Physical Layer scrambling + /// + SR_PLSSEL = 0x40000, + + /// + /// The tuner can measure constellations for non-satellite networks. + /// + SR_IQSCAN2 = 0x80000, + SR_BBFRAME = 0x100000, + SR_MODSEL = 0x200000, + SR_MODINV = 0x400000, + + /// + /// The tuner can blind-scan non-satellite networks. + /// + SR_AIRSCAN = 0x800000, + SR_CIRSCAN = 0x1000000, + SR_FFTSCAN = 0x2000000, + SR_CARESNO = 0x4000000 + } + +} diff --git a/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Structs.cs b/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Structs.cs new file mode 100644 index 0000000..b06dc29 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/CrazycatStreamReader/Structs.cs @@ -0,0 +1,276 @@ +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.CrazycatStreamReader +{ + /// + /// DVB-S / DVB-S2 / DVB-S2X Tuning data + /// + [StructLayout(LayoutKind.Sequential)] + public struct SearchResult + { + /// + /// Whether a valid signal is present. + /// + public bool Lock; + + /// + /// Frequency (Khz) + /// + public int Freq; + + /// + /// Polarization + /// + public int Pol; + + /// + /// Symbol rate (Sym) + /// + public int SR; + + /// + /// Tracking standard + /// + public int StdType; + + /// + /// Modulation type + /// + public int ModType; + + public VITERBIRATE_TYPE FEC; + + /// + /// Spectral inversion + /// + public int Inversion; + + public int RollOff; + + /// + /// Pilot (DVB-S2 only) + /// + public int Pilot; + + /// + /// Frame type (DVB-S2 only) + /// + public int Frame; + + /// + /// Coding modulation (DVB-S2 only) + /// + public int CodingType; + + /// + /// Stream type (DVB-S2 only) + /// + public int StreamType; + + /// + /// Multiple Input Streams number. 0 - Single stream, 1-16 - Multiple streams + /// + public int MIS; + + /// + /// // Input Streams IDs (DVB-S2 only) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] IS; + + /// + /// Input Stream Synchronisation + /// + public byte ISSYI; + + /// + /// Null Packet Deletion + /// + public byte NPD; + + /// + /// Physical Layer Scrambling code + /// + public int PLS; + + /// + /// Carrier width (Khz) + /// + public int CW; + + // /// + // /// BitRate (bit/s) + // /// + //public int BitRate; + + /// + /// RF level (dBm, from -100 to 0) + /// + public int RFLevel; + + /// + /// Signal/Noise rate (dB) + /// + public double SNR; + + /// + /// Bit Error Rate (post FEC) + /// + public double BER; + + /// + /// Bit Error Rate (pre FEC) + /// + public double preBER; + } + + /// + /// DVB-S2 specific tuning data + /// + [StructLayout(LayoutKind.Sequential)] + public struct S2Mode + { + public S2MODCODE modcode; + public bool pilot; + public int frameLen; + } + + /// + /// DVB-T2 / DVB-C Tuning data. + /// + [StructLayout(LayoutKind.Sequential)] + public struct SearchResult2 + { + /// + /// Whether the tuning is valid. If this is false, there is no channel. + /// + public bool Lock; + + /// + /// Frequency (Khz) + /// + public int Freq; + + /// + /// Band width (Khz) + /// + public int BW; + + /// + /// Symbol rate (Sym) + /// + public int SR; + + /// + /// Tracking standard + /// + public int StdType; + + /// + /// Modulation type + /// + public int ModType; + + public int FEC; + public int FECLP; + + /// + /// Spectral inversion + /// + public int Inversion; + + /// + /// Transmission mode + /// + public int TransMode; + + /// + /// Guard interval + /// + public int Guard; + + public int Hierarchy; + + /// + /// Number of elements in the LID array. + /// + public int PLP; + + /// + /// ID's of the physical layer pipes. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] LID; + + /// + /// Pilot Patern (DVB-T2 only) + /// + public int Pilot; + + /// + /// S1 Signaling (DVB-T2 only) + /// + public int S1Type; + + /// + /// Payload (DVB-T2/C2 only) + /// + public int Payload; + + /// + /// Frame type (DVB-T2 only) + /// + public int Frame; + + /// + /// Bw Extended (DVB-T2 only) + /// + public int BwExt; + + /// + /// DVB-T2 mode (Base or Lite) + /// + public int ModeT2; + + /// + /// DVB-T2 version + /// + public int VerT2; + + /// + /// Cell ID (DVB-T/T2) + /// + public int CellID; + + /// + /// System/Net ID (DVB-T2) + /// + public int NetID; + + /// + /// BitRate (bit/s) + /// + public int BitRate; + + /// + /// RF level (dBm, from -100 to 0) + /// + public int RFLevel; + + /// + /// Signal/Noise rate (dB) + /// + public double SNR; + + /// + /// Bit Error Rate (after FEC) + /// + public double BER; + + /// + /// Bit Error Rate (before FEC) + /// + public double preBER; + } + +} diff --git a/skyscraper8/Skyscraper/IO/DirectoryInfoExtensions.cs b/skyscraper8/Skyscraper/IO/DirectoryInfoExtensions.cs new file mode 100644 index 0000000..acf53a9 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/DirectoryInfoExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.IO +{ + public static class DirectoryInfoExtensions + { + public static void EnsureExists(this DirectoryInfo di) + { + if (di.Exists) + return; + di.Parent.EnsureExists(); + di.Create(); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/En300468AnnexCDateTime.cs b/skyscraper8/Skyscraper/IO/En300468AnnexCDateTime.cs new file mode 100644 index 0000000..38a120e --- /dev/null +++ b/skyscraper8/Skyscraper/IO/En300468AnnexCDateTime.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; + +namespace skyscraper5.Skyscraper.IO +{ + public static class En300468AnnexCDateTime + { + public static DateTime? ReadEtsiEn300468AnnexCDateTime(this Stream s) + { + return ReadDateTime(s.ReadBytes(5)); + } + public static DateTime? ReadDateTime(byte[] buffer) + { + if (buffer.Length != 5) + throw new ArgumentException("need 5 bytes"); + + (buffer[1], buffer[0]) = (buffer[0], buffer[1]); + int mjd = BitConverter.ToUInt16(buffer, 0); + int y1 = (int)(((double)mjd - 15078.2) / 365.25); + int m1 = (int)((mjd - 14956.1 - (int)((double)y1 * 365.25)) / 30.6001); + int d = mjd - 14956 - (int)((double)y1 * 365.25) - (int)((double)m1 * 30.6001); + int k = 0; + if (m1 == 14 || m1 == 15) + k = 1; + int y = y1 + k + 1900; + int m = m1 - 1 - k * 12; + + m1--; + + int hour = (int)buffer[2].UnpackBcd(); + int minute = (int)buffer[3].UnpackBcd(); + int second = (int)buffer[4].UnpackBcd(); + + if (minute > 60) + return null; + + if (m == -1) + return null; + + if (m < 0) + return null; + + return new DateTime(y, m, d, hour, minute, second); + } + + /* + * + + int mjd = payload.getShort() & 0xffff; + int y1 = (int)(((double)mjd - 15078.2) / 365.25); + int m1 = (int)((mjd - 14956.1 - (int)((double)y1 * 365.25)) / 30.6001); + int d = mjd - 14956 - (int)((double)y1 * 365.25) - (int)((double)m1 * 30.6001); + int k = 0; + if (m1 == 14 || m1 == 15) + k = 1; + int y = y1 + k + 1900; + int m = m1 - 1 - k * 12; + + byte ho = payload.get(); + String hourString = singleByteToHex(ho); + if (hourString.contains("A") | hourString.contains("B") | hourString.contains("C") | hourString.contains("D") | hourString.contains("E") | hourString.contains("F")) + return null; + int hour = Integer.parseInt(hourString); + byte mi = payload.get(); + String minuteString = singleByteToHex(mi); + if (minuteString.contains("A") | minuteString.contains("B") | minuteString.contains("C") | minuteString.contains("D") | minuteString.contains("E") | minuteString.contains("F")) + return null; + int minute = Integer.parseInt(minuteString); + byte se = payload.get(); + String secondString = singleByteToHex(se); + if (secondString.contains("A") | secondString.contains("B") | secondString.contains("C") | secondString.contains("D") | secondString.contains("E") | secondString.contains("F")) + return null; + int second = Integer.parseInt(secondString); + + Date time = new GregorianCalendar(y, m - 1, d, hour, minute, second).getTime(); + return time; + */ + } +} diff --git a/skyscraper8/Skyscraper/IO/IStreamReader.cs b/skyscraper8/Skyscraper/IO/IStreamReader.cs new file mode 100644 index 0000000..73f7643 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/IStreamReader.cs @@ -0,0 +1,437 @@ +using System; +using System.Runtime.InteropServices; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; + +namespace skyscraper5.Skyscraper.IO +{ + public interface IStreamReader : IDisposable + { + /// + /// Checks whether any sort of tuner is present in this system. + /// + /// Whether a tuner is present. + bool CheckForDVB(); + + /// + /// Checks whether one or more tuner is present in the system. + /// + /// A method which will be called for each tuner found. + /// Whether a tuner was found + bool CheckForDVBEx([MarshalAs(UnmanagedType.FunctionPtr)] DvbEnumCallback func); + + /// + /// Checks whether one or more tuner is present in the system. + /// + /// A method which will be called for each tuner found. + /// Whether a tuner was found + bool CheckForDVBExEx([MarshalAs(UnmanagedType.FunctionPtr)] DvbEnumCallbackEx func); + + /// + /// Boots the first system tuner and sets it as the currently active tuner. Note that + /// only one tuner can be active at the same time. + /// + /// Whether a tuner was booted sucessfully. + bool StartDVB(); + + /// + /// Boots a tuner and makes it the currently active tuner. Note that only one tuner + /// can be active at the same time. + /// + /// The ID of the tuner to boot + /// Whether the turner booted sucessfully. + bool StartDvbEx(int index); + + /// + /// Shuts down the currently active tuner. + /// + /// Whether the tuner was shut down sucessfully + bool StopDVB(); + + /// + /// Retrieves the tuner standard. + /// + /// A Pointer in which the tuner type will be put into. + /// Whether the tuner reports a standard + bool GetTunerType(ref STD_TYPE type); + + /// + /// Sends a raw command to a DiSEqC switch with up to four positions. + /// + /// 0 for disabling DiSEqc, 1 for a tone-burst, 2 for enabling DiSEqC 1.0 + /// The opcode to send. + /// Whether the command was sent + bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data); + + /// + /// Send a raw command to a DiSEqC-Motor, ignoring any potential responses. + /// + /// The buffer containing the raw command + /// The length of the command + /// Whether the command was sent + bool SendDiseqCmd(byte[] buffer, int length); + + /// + /// Supposed to send a raw command to a DiSEqC-Motor and awaits a response. + /// Since I couldn't test this - (I don't have a motor on my dish!) this will merely + /// throw a NotImplementedException + /// + /// The command to send to the motor + /// The length of the command to send to the motor. + /// A byte array to store the motor's response in. + /// Expected length of the motor's response + /// Throws a NotImplementedException + bool SendDiseqCmdEx(IntPtr pCmd, int length, IntPtr reply, int replyLength); + + /// + /// Set the tuner to a satellite frequency + /// + /// The frequency in kHz. + /// The symbol rate in Sym/s. (e.g. 27500 * 1000 = 27500000) + /// 0 for horizontal, 1 for vertical. + /// The error correction method used by the channel. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// Whether the tuner complied with the request + bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw); + + /// + /// Set the tuner to a satellite frequency + /// + /// The frequency in kHz. + /// The symbol rate in Sym/s. (e.g. 27500 * 1000 = 27500000) + /// 0 for horizontal, 1 for vertical. + /// The error correction method used by the channel. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The modulation type used by the channel. + /// Whether the tuner complied with the request. + bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod); + + /// + /// Set the tuner to a satellite frequency + /// + /// The frequency in kHz. + /// The symbol rate in Sym/s. (e.g. 27500 * 1000 = 27500000) + /// 0 for horizontal, 1 for vertical. + /// The error correction method used by the channel. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The modulation type used by the channel. + /// Spectral inversion: 0 for autodetection, 1 for no spectral inversion, 2 for spectral inversion. + /// S2 Pilot Symbols: 0 for autodetection, 1 for no pilot symbols, 2 for pilot symbols + /// + /// + bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod, int inv, int pilot, ROLLOFF_TYPE rolloff); + + /// + /// Starts receiving data from the tuner. + /// + /// The PID you want to receive. Set this to 8192 if you want ALL pids. + /// The function to call when a packet was received. + /// Set this to 0x02 for all that is holy. Others might cause blue-screens. + /// Set this to 1 to receive single packets, or 2 to receive (a varying amount of) multiple packets. + /// The pointer you can use with DelFilter. + /// Whether the filter was started sucesfully. + bool SetFilter(int pid, [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallback func, int callbackType, int size, ref IntPtr lpFilterNum); + + /// + /// Starts receiving data from the tuner. + /// + /// The PID you want to receive. Set this to 8192 if you want ALL pids. + /// The function to call when a packet was received. + /// Set this to 0x02 for all that is holy. Others might cause blue-screens. + /// Set this to 1 to receive single packets, or 2 to receive (a varying amount of) multiple packets. + /// The pointer you can use with DelFilter. + /// Whether the filter was started sucesfully. + bool SetFilterEx(int pid, [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallbackEx lpFunc, int callbackType, int size, ref IntPtr lpFilterNum); + + bool SetBitFilter(int pid, IntPtr filterData, IntPtr filterMask, byte filterLength, [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallback lpFunc, ref IntPtr lpFilterNum); + + /// + /// Creates a filter to read data from the virtual IR Channel of the tuner. + /// + /// The remote control type. 0x00 works for all remote types. + /// The adress of the remote control. Set to -1 for all remotes. + /// The function to call when an IR Command was received. + /// A pointer to the filter to use with DelFilter. + /// Whether the listening to a remote command is working. + bool SetRemoteControl(int irtype, short devAddr, [MarshalAs(UnmanagedType.FunctionPtr)] StdcallDvbCallback func, ref IntPtr lpFilterNum); + + /// + /// Stops reading data. + /// + /// The pointer to the filter which should be stopped. + /// Whether the filter could be stopped. + bool DelFilter(IntPtr filterNum); + + /// + /// Reports the signal strength and quality. For some tuners you'll need GetSignalStrength instead. + /// + /// A float pointer which will be set to the signal strength. + /// A float pointer which will be set to the signal quality. + /// Whether these information could be reported. + bool GetSignal(ref int pStrength, ref int pQuality); + + /// + /// An alternative method to read the signal strength and quality. + /// + /// A float pointer which will be set to the signal strength. + /// A float pointer which will be set to the signal quality. + /// Whether these information could be reported. + bool GetSignalStrength(ref float pStrength, ref float pQuality); + + /// + /// Gets information about the current signal. + /// + /// Pointer to a float which will be set to the Signal-to-Noise ratio. + /// Pointer to a float which will be set to the biterror rate. + /// Whether the data could be reported. + bool GetSignalEx(ref float pSNR, ref float pBER); + + /// + /// Gets information about the current signal. + /// + /// Pointer to a boolean which will be set to true if a signal is present. + /// Pointer to a boolean which will be set to true if a signal lock is available. + /// Pointer to an integer which will be set to the RF Level. + /// Pointer to a float which will be set to the Signal-to-Noise ratio. + /// Pointer to a float which will be set to the biterror rate. + /// Whether the data could be reported. + bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, ref float pSNR, ref float pBER); + + /// + /// Read signal statistics into an integer array. + /// + /// Pointer to an array which will receive the following elements: Locked?, Signal Quality, Signal Present?, Signal Strength + /// Whether the statistics could be reported. + bool Statistic(int[] pStat); + + /// + /// Reads the MAC Adress of a tuner. + /// + /// A byte array to put the MAC into. + /// Whether the MAC was sucessfully read. + bool GetMAC(byte[] pMac); + + /// + /// Gets the TunerMetadata capabilities. + /// + /// A bitmask containing all the tuner capabilities. + Caps GetCaps(); + + /// + /// Gets the RF Strength of a satellite frequency. + /// + /// An approximate channel frequency in kHz + /// 0 for horizontal, 1 for vertical + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The pointer to put the result in. + /// Whether the probe was sucessful. + bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel); + + bool FFTInit(); + + bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, byte nb_acc, IntPtr pTab, IntPtr pBegin, IntPtr pNum); + + /// + /// Given an approximate satellite frequency, tune to the nearest channel. + /// + /// An approximate channel frequency in kHz + /// The range where to look for in kHz + /// 0 for horizontal, 1 for vertical + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The minimum acceptable symbol rate. Depending on your tuner you either want 200 or 1000. + /// The exact tuning data + /// Whether a channel could be found + bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, ref SearchResult pSearchResult); + + /// + /// Given an approximate satellite frequency, tune to the nearest channel. + /// + /// An approximate channel frequency in kHz + /// The range where to look for in kHz + /// 0 for horizontal, 1 for vertical + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The minimum acceptable symbol rate. Depending on your tuner you either want 200 or 1000. + /// The network type + /// The exact tuning data + /// Whether a channel could be found + bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, STD_TYPE std, ref SearchResult pSearchResult); + + /// + /// Performs a blind scan on a satellite network. + /// This method blocks until the scan is complete. + /// + /// The frequency to the start the scan on in kHz (10700000 for Ku-Band) + /// The frequency to end the scan on in kHz (12750000 for Ku-Band) + /// 0 for horizontal, 1 for vertical. + /// Local Oscillator Frequency 1 of the LNB in kHz. (9750000 for Ku-Band) + /// Local Oscillator Frequency 2 of the LNB in kHz. (10600000 for Ku-Band) + /// Border Frequency for switching between Low Band and High Band in kHz (11700000 for Ku-Band) + /// The struct to put the last found channel in. + /// Number of channels found. + /// The function to call when a channel was found. + /// Whether the blind scan was performed sucessfully. + bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, IntPtr pSearchResult, ref int pTpNum, [MarshalAs(UnmanagedType.FunctionPtr)] BlScanCallback lpFunc); + + /// + /// Retrieve extended carrier info after lock for a satelite signal. + /// + /// Struct to output the data to + /// Whether the carrier info could be read. + bool SignalInfo(ref SearchResult pSearchResult); + + /// + /// Read constellation data for a satellite signal. + /// + /// Type of constellation data to read. You probably want 0. + /// Output Array + /// Number of Constellation points to read. Must be (pIQ.Length / 2) + /// Whether the constellation data was read + bool IQScan(uint input, sbyte[] pIQ, uint num); + + /// + /// Read constellation data for a non-satellite signal. + /// + /// Type of constellation data to read. You probably want 0. + /// Output Array + /// Number of Constellation points to read. Must be (pIQ.Length / 2) + /// Whether the constellation data was read + bool IQScan2(uint point, short[] pIQ, uint num); + + bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint); + + bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, ref int pMinDelayNs, ref int pMaxDelayNs); + + bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays); + + bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum); + + bool CarEsNo(ref ushort cnum, ref double pEsNo); + + bool MISSel(bool bEnable, byte misFilter, byte misFilterMask); + + /// + /// Sets the Physical Layer Scrambling credentials. + /// + /// The PLS mode. + /// The PLS key. + /// Whether the tuner complied. + bool PLSSel(byte plsMode, uint code); + + /// + /// Gets the current Physical Layer Scrambling key. + /// + /// The pointer to write the PLS Mode in. + /// The pointer to write the PLS Key in. + /// Whether the tuner sent valid PLS credentials. + bool PLSGet(byte pMode, ref uint pCode); + + bool ModSel(ref S2Mode ps2Modes, uint num); + + bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum); + + /// + /// Tune to DVB-T2 / DVB-C Channel + /// + /// Frequency in kHz + /// Channel Bandwidth in kHz (usually 8000) + /// Whether the tuner complied with the request. + bool SetChannel2(uint freq, uint bandwidth); + + /// + /// Tune to DVB-T2 / DVB-C channel. + /// + /// Frequency in kH + /// Channel Bandwidth in kHz (usually 8000) + /// Type of network + /// ??? (Setting this to 0 works.) + /// Whether the tuner complied with the request. + bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream); + + /// + /// Tune to DVB-T2 / DVB-C Channel. Beware, will freeze for a minute when SymbolRate is wrong. + /// + /// Frequency in kHz + /// Channel Bandwidth in kHz (usually 8000) + /// Sybol Rate in kiloSymbols (6900000 for Vodafone) + /// Type of network + /// ??? Setting this to 0 works. + /// Whether the tuner complied with the request. + bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, int stream); + + /// + /// Retrieve extended carrier info after lock for a non-sattelite signal. + /// + /// Outputs the Signal information + /// Whether the signal information was read sucessfully + bool SignalInfo2(ref SearchResult2 si2); + + + /// + /// Get RF Level from FM / UHF / VHF Frequency + /// + /// Frequency in kHz + /// TunerMetadata standard + /// Output pointer + /// whether the operation completed sucessfully + bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel); + + /// + /// Performs a blind scan on non-satellite networks. + /// This method blocks until the scan is complete! + /// + /// Starting Frequency in kHz + /// End Frequency in kHz + /// Frequency increment in kHz (3000 is usually a safe choice here) + /// System bandwidth (usually 8000) + /// Type of Network + /// Outputs the final channel. Not really necessary. + /// Outputs the number of channels. + /// Function to call when a channel was found. + /// Whether the scan was performed sucessfully + bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, IntPtr pSearchResult, ref int pTpNum, [MarshalAs(UnmanagedType.FunctionPtr)] AirScanCallback lpFunc); + + /// + /// Reads from the EEPROM of the tuner. + /// + /// Buffer to put the read data in. + /// Offset from where to start reading from the EEPROM + /// How many bytes to read + /// Wheter the reading operation was sucessful. + bool GetEEPROM(byte[] buffer, int offset, int len); + + /// + /// Supposed to write data to the device's EEPROM. + /// This method is not implemented on purpose because I neither have the balls to try + /// this, nor the money to replace my tuner in case I mess up. + /// This will throw a NotSupportedException when calling this. + /// + /// Buffer of data to write to the EEPROM. + /// Offset in the EEPROM to start writing. + /// How many bytes to write to the EEPROM + /// Throws NotSupportedException + bool SetEEPROM(byte[] buffer, int offset, int len); + + /// + /// This is a skyscraper5 extension. It is supposed to return the version of the software used for tuning. + /// + /// For example: 1.2.5.176 + Version GetEngineVersion(); + + /// + /// This is a skyscraper5 extension. It is supposed to return the name of the software used for tuning. + /// + /// For example: "StreamReaderEx for BDA Tuners + string GetEngineName(); + } +} diff --git a/skyscraper8/Skyscraper/IO/InBitStream.cs b/skyscraper8/Skyscraper/IO/InBitStream.cs new file mode 100644 index 0000000..9764781 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/InBitStream.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.IO +{ + class InBitStream + { + public InBitStream(MemoryStream wrapped) + { + _wrapped = wrapped; + bitPointer = 8; + bits = new bool[8]; + } + + private readonly MemoryStream _wrapped; + private byte bitPointer; + private bool[] bits; + + private byte ReadBit() + { + if (bitPointer == 8) + { + byte readUInt8 = _wrapped.ReadUInt8(); + bits[0] = (readUInt8 & 0x80) != 0; + bits[1] = (readUInt8 & 0x40) != 0; + bits[2] = (readUInt8 & 0x20) != 0; + bits[3] = (readUInt8 & 0x10) != 0; + bits[4] = (readUInt8 & 0x08) != 0; + bits[5] = (readUInt8 & 0x04) != 0; + bits[6] = (readUInt8 & 0x02) != 0; + bits[7] = (readUInt8 & 0x01) != 0; + bitPointer = 0; + } + + bool result = bits[bitPointer++]; + return (byte)(result ? 0x01 : 0x00); + } + + public uint ReadBitsAsUint(byte amount) + { + if (amount > 32) + throw new ArgumentOutOfRangeException(nameof(amount)); + + uint result = 0; + for (byte i = 0; i < amount; i++) + { + result <<= 1; + result += ReadBit(); + } + + return result; + } + + public ulong ReadBitsAsULong(byte amount) + { + if (amount > 64) + throw new ArgumentOutOfRangeException(nameof(amount)); + + ulong result = 0; + for (byte i = 0; i < amount; i++) + { + result <<= 1; + result += ReadBit(); + } + return result; + } + + public byte ReadBitsAsByte(byte amount) + { + if (amount > 8) + throw new ArgumentOutOfRangeException(nameof(amount)); + + return (byte)ReadBitsAsUint(amount); + } + + public bool ReadBitAsBoolean() + { + return ReadBit() == 1 ? true : false; + } + + public ushort ReadBitsAsUInt16(byte amount) + { + if (amount > 16) + throw new ArgumentOutOfRangeException(nameof(amount)); + + ushort result = 0; + for (byte i = 0; i < amount; i++) + { + result <<= 1; + result += ReadBit(); + } + return result; + } + + public void SkipBits(ulong amount) + { + for (ulong i = 0; i < amount; i++) + ReadBit(); + } + + public byte[] ReadBitsAsByteArray(uint v) + { + byte[] result = new byte[v / 8]; + int offset = 0; + while (v > 0) + { + uint nextSize = Math.Min(8, v); + result[offset++] = ReadBitsAsByte(8); + v -= nextSize; + } + return result; + } + + public bool IsByteAligned => bitPointer == 8; + + public long GetBitsAvailable() + { + long result = _wrapped.GetAvailableBytes(); + result += (8 - bitPointer); + return result; + } + } +} diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/DemuxerOutput.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/DemuxerOutput.cs new file mode 100644 index 0000000..3d6907a --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/DemuxerOutput.cs @@ -0,0 +1,9 @@ +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public enum DemuxerOutput : int + { + DMX_OUT_DECODER, + DMX_OUT_TAP, + DMX_OUT_TS_TAP + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/DemuxerPesType.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/DemuxerPesType.cs new file mode 100644 index 0000000..bd5ef96 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/DemuxerPesType.cs @@ -0,0 +1,12 @@ +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public enum DemuxerPesType : int + { + DMX_PES_AUDIO, + DMX_PES_VIDEO, + DMX_PES_TELETEXT, + DMX_PES_SUBTITLE, + DMX_PES_PCR, + DMX_PES_OTHER + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/DvbDeviceType.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/DvbDeviceType.cs new file mode 100644 index 0000000..a4d7abc --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/DvbDeviceType.cs @@ -0,0 +1,12 @@ +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public enum DvbDeviceType + { + Frontend, + Demux, + Dvr, + Net, + Ca, + CaSec + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/DvbFrontendInfo.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/DvbFrontendInfo.cs new file mode 100644 index 0000000..743f696 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/DvbFrontendInfo.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public struct DvbFrontendInfo + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string name; + public FrontendType type; + public uint frequency_min; + public uint frequency_max; + public uint frequency_stepsize; + public uint frequency_tolerance; + public uint symbolrate_min; + public uint symbolrate_max; + public uint symbolrate_tolerance; + public uint notifier_delay; + public FrontendCapabilities fe_caps; + } + } \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/FrontendCapabilities.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/FrontendCapabilities.cs new file mode 100644 index 0000000..7723c5d --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/FrontendCapabilities.cs @@ -0,0 +1,39 @@ +using System; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + [Flags] + public enum FrontendCapabilities : uint { + FE_IS_STUPID = 0, + FE_CAN_INVERSION_AUTO = 0x1, + FE_CAN_FEC_1_2 = 0x2, + FE_CAN_FEC_2_3 = 0x4, + FE_CAN_FEC_3_4 = 0x8, + FE_CAN_FEC_4_5 = 0x10, + FE_CAN_FEC_5_6 = 0x20, + FE_CAN_FEC_6_7 = 0x40, + FE_CAN_FEC_7_8 = 0x80, + FE_CAN_FEC_8_9 = 0x100, + FE_CAN_FEC_AUTO = 0x200, + FE_CAN_QPSK = 0x400, + FE_CAN_QAM_16 = 0x800, + FE_CAN_QAM_32 = 0x1000, + FE_CAN_QAM_64 = 0x2000, + FE_CAN_QAM_128 = 0x4000, + FE_CAN_QAM_256 = 0x8000, + FE_CAN_QAM_AUTO = 0x10000, + FE_CAN_TRANSMISSION_MODE_AUTO = 0x20000, + FE_CAN_BANDWIDTH_AUTO = 0x40000, + FE_CAN_GUARD_INTERVAL_AUTO = 0x80000, + FE_CAN_HIERARCHY_AUTO = 0x100000, + FE_CAN_8VSB = 0x200000, + FE_CAN_16VSB = 0x400000, + FE_HAS_EXTENDED_CAPS = 0x800000, + FE_CAN_MULTISTREAM = 0x4000000, + FE_CAN_TURBO_FEC = 0x8000000, + FE_CAN_2G_MODULATION = 0x10000000, + FE_NEEDS_BENDING = 0x20000000, + FE_CAN_RECOVER = 0x40000000, + FE_CAN_MUTE_TS = 0x80000000 + }; +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/FrontendType.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/FrontendType.cs new file mode 100644 index 0000000..740ee1f --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/FrontendType.cs @@ -0,0 +1,10 @@ +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public enum FrontendType + { + FE_QPSK, + FE_QAM, + FE_OFDM, + FE_ATSC + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5.cs new file mode 100644 index 0000000..009d84a --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + internal static class LibDvbV5 + { + public const int O_RDONLY = 0; + public const int O_RDWR = 2; + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_alloc", SetLastError = true)] + internal static extern IntPtr DvbDevAlloc(); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_close")] + internal static extern void DvbDevClose(IntPtr openDev); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_find")] + internal static extern int DvbDevFind(IntPtr dvb, int enableMonitor); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_free")] + internal static extern void DvbDevFree(IntPtr dvb); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_open")] + internal static extern IntPtr DvbDevOpen(IntPtr dvb, string sysname, int flags); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_seek_by_sysname")] + internal static extern IntPtr DvbDevSeekBySysname(IntPtr dvb, uint adapter, uint num, DvbDeviceType type); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_set_log")] + internal static extern IntPtr DvbDevSetLog(IntPtr dvb, uint verbose, [MarshalAs(UnmanagedType.FunctionPtr)] LibDvbV5LogFunction logfunc); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_fe_open")] + internal static extern IntPtr DvbFrontendOpen(uint adapter, uint frontend, uint verbose, uint useLegacyCall); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_fe_close")] + internal static extern void DvbFrontendClose(IntPtr ptr); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_fe_set_parms")] + internal static extern int DvbFrontendSetParameters(IntPtr ptr); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_fe_store_parm")] + internal static extern int DvbFrontentStoreParameter(IntPtr ptr, uint cmd, uint value); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_print_all_lnb")] + internal static extern void DvbPrintAllLnb(); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_sat_get_lnb")] + internal static extern IntPtr DvbSatGetLnb(int index); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_sat_search_lnb")] + internal static extern int DvbSatSearchLnb(string name); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_fe_get_stats")] + internal static extern int DvbFrontendGetStatistics(IntPtr parms); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_fe_retrieve_stats")] + internal static extern int DvbFrontendRetrieveStats(IntPtr parms, uint cmd, ref uint value); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_set_bufsize")] + internal static extern int DvbDeviceSetBufferSize(IntPtr openDev, int bufferSize); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_dmx_set_pesfilter")] + internal static extern int DvbDeviceDemuxerSetPesFilter(IntPtr openDev, int pid, DemuxerPesType type,DemuxerOutput output, int bufferSize); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_dev_read")] + internal static extern int DvbDeviceRead(IntPtr openDev, byte[] buf, int count); + + [DllImport("libdvbv5.so.0", EntryPoint = "dvb_set_sys")] + internal static extern int DvbSetSystem(IntPtr parms, LibDvbV5FrontendDeliverySystem sys); + } + + public delegate void LibDvbV5LogFunction(int level, string fmt); +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5Device.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5Device.cs new file mode 100644 index 0000000..930ded8 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5Device.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public class LibDvbV5Device : IDisposable + { + public LibDvbV5Device() + { + dvbDevAlloc = LibDvbV5.DvbDevAlloc(); + } + + private IntPtr dvbDevAlloc; + + public void Find() + { + if (LibDvbV5.DvbDevFind(dvbDevAlloc, 0) != 0) + { + throw new Exception("dvb_dev_find failed"); + } + + DvbDeviceStruct unpacked = Marshal.PtrToStructure(dvbDevAlloc); + LibDvbV5DeviceList[] devices = new LibDvbV5DeviceList[unpacked.numDevices]; + IntPtr currentDevicePtr = unpacked.devices; + for (int i = 0; i < unpacked.numDevices; i++) + { + devices[i] = new LibDvbV5DeviceList(currentDevicePtr); + currentDevicePtr = new IntPtr(currentDevicePtr.ToInt64() + devices[i].SizeOf); + } + KnownDevices = devices; + } + public LibDvbV5DeviceList[] KnownDevices { get; private set; } + + public void Dispose() + { + LibDvbV5.DvbDevFree(dvbDevAlloc); + dvbDevAlloc = IntPtr.Zero; + } + + public LibDvbV5DeviceList SeekBySystemName(uint adapter, uint num, DvbDeviceType type) + { + IntPtr result = LibDvbV5.DvbDevSeekBySysname(dvbDevAlloc, adapter, num, type); + if (result== IntPtr.Zero) + { + throw new Exception("desired device was not found"); + } + + return new LibDvbV5DeviceList(result); + } + + private struct DvbDeviceStruct + { + public IntPtr devices; + public int numDevices; + public IntPtr feParams; + } + + public LibDvbV5OpenDescriptor DeviceOpen(LibDvbV5DeviceList sysname) + { + int permissions = LibDvbV5.O_RDWR; + if (sysname.DvbType == DvbDeviceType.Dvr) + permissions = LibDvbV5.O_RDONLY; + + IntPtr dvbDevOpen = LibDvbV5.DvbDevOpen(this.dvbDevAlloc, sysname.SystemName, permissions); + if (dvbDevOpen == IntPtr.Zero) + return null; + return new LibDvbV5OpenDescriptor(dvbDevOpen); + } + + public void SetLog(uint verbosityLevel, LibDvbV5LogFunction logFunction) + { + LibDvbV5.DvbDevSetLog(this.dvbDevAlloc, verbosityLevel, logFunction); + } + + public LibDvbV5DeviceList FindDeviceByDeliverySystem(LibDvbV5FrontendDeliverySystem deliverySystem) + { + if (KnownDevices == null) + Find(); + + foreach (LibDvbV5DeviceList deviceList in KnownDevices) + { + if (deviceList.DvbType != DvbDeviceType.Frontend) + continue; + + uint? adapterNo = deviceList.GuessAdapterNo(); + uint? deviceNo = deviceList.GuessDeviceNo(); + + if (!adapterNo.HasValue || !deviceNo.HasValue) + continue; + + LibDvb5FrontendParameters frontend = new LibDvb5FrontendParameters(adapterNo.Value, deviceNo.Value); + LibDvbV5FrontendDeliverySystem[] deliverySystems = frontend.GetDeliverySystems(); + frontend.Dispose(); + foreach (LibDvbV5FrontendDeliverySystem libDvbV5FrontendDeliverySystem in deliverySystems) + { + if (libDvbV5FrontendDeliverySystem == deliverySystem) + { + return deviceList; + } + } + } + + return null; + } + + public LibDvbV5DeviceList GetSisterDevice(LibDvbV5DeviceList findDeviceByDeliverySystem, DvbDeviceType demux) + { + uint? guessAdapterNo = findDeviceByDeliverySystem.GuessAdapterNo(); + if (guessAdapterNo == null) + return null; + + return SeekBySystemName(guessAdapterNo.Value, 0, demux); + } + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5DeviceList.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5DeviceList.cs new file mode 100644 index 0000000..ff83925 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5DeviceList.cs @@ -0,0 +1,132 @@ +using System; +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public class LibDvbV5DeviceList + { + public LibDvbV5DeviceList(IntPtr ptr) + { + StructDevList ptrToStructure = Marshal.PtrToStructure(ptr); + pointer = ptr; + SizeOf = Marshal.SizeOf(ptrToStructure); + SystemPath = Marshal.PtrToStringAuto(ptrToStructure.syspath); + Path = Marshal.PtrToStringAuto(ptrToStructure.path); + SystemName = Marshal.PtrToStringAuto(ptrToStructure.sysname); + DvbType = ptrToStructure.dvbType; + BusAddress = Marshal.PtrToStringAuto(ptrToStructure.busAddr); + BusId = Marshal.PtrToStringAuto(ptrToStructure.busId); + Manufacturer = Marshal.PtrToStringAuto(ptrToStructure.manufacturer); + Product = Marshal.PtrToStringAuto(ptrToStructure.product); + Serial = Marshal.PtrToStringAuto(ptrToStructure.serial); + } + + public string? Serial { get; } + + public string? Product { get; } + + public string? Manufacturer { get; } + + public string? BusId { get; } + + public string? BusAddress { get; } + + public string? SystemName { get; } + + public string? Path { get; } + + public string? SystemPath { get; } + + + private IntPtr pointer; + public int SizeOf { get; } + public DvbDeviceType DvbType { get; } + + struct StructDevList + { + public IntPtr syspath; + public IntPtr path; + public IntPtr sysname; + public DvbDeviceType dvbType; + public IntPtr busAddr; + public IntPtr busId; + public IntPtr manufacturer; + public IntPtr product; + public IntPtr serial; + } + + public override string ToString() + { + return String.Format("{0} {1} {2} ({3:X16})",Manufacturer,Product,DvbType,pointer.ToInt64()); + } + + protected bool Equals(LibDvbV5DeviceList other) + { + return pointer.Equals(other.pointer); + } + + 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((LibDvbV5DeviceList)obj); + } + + public override int GetHashCode() + { + return pointer.GetHashCode(); + } + + + public uint? GuessAdapterNo() + { + string[] strings = SystemName.Split('.'); + if (!strings[0].StartsWith("dvb")) + return null; + + return extractNumber(strings[0]); + } + + public uint? GuessDeviceNo() + { + string[] strings = SystemName.Split('.'); + if (!strings[0].StartsWith("dvb")) + return null; + + return extractNumber(strings[1]); + } + + private static uint extractNumber(string str) + { + int magnitude = 1; + uint result = 0; + for (int i = 0; i < str.Length; i++) + { + if (Char.IsDigit(str[i])) + { + int addMe = str[i] - 0x30; + addMe *= magnitude; + magnitude *= 10; + result += (uint)addMe; + } + } + + return result; + } + + public LibDvb5FrontendParameters ToFrontendParameters() + { + if (this.DvbType != DvbDeviceType.Frontend) + throw new InvalidOperationException("i'm not a frontend"); + + uint? adapterNo = GuessAdapterNo(); + uint? deviceNo = GuessDeviceNo(); + + if (!adapterNo.HasValue || !deviceNo.HasValue) + return null; + + return new LibDvb5FrontendParameters(adapterNo.Value, deviceNo.Value); + } + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5FrontendDeliverySystem.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5FrontendDeliverySystem.cs new file mode 100644 index 0000000..64ad4fc --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5FrontendDeliverySystem.cs @@ -0,0 +1,34 @@ +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public enum LibDvbV5FrontendDeliverySystem : int + { + SYS_UNDEFINED, + /** + * European cable TV + */ + SYS_DVBC_ANNEX_A, + /** + * American cable TV + */ + SYS_DVBC_ANNEX_B, + SYS_DVBT, + SYS_DSS, + SYS_DVBS, + SYS_DVBS2, + SYS_DVBH, + SYS_ISDBT, + SYS_ISDBS, + SYS_ISDBC, + SYS_ATSC, + SYS_ATSCMH, + SYS_DTMB, + SYS_CMMB, + SYS_DAB, + SYS_DVBT2, + SYS_TURBO, + /** + * Japanese cable TV + */ + SYS_DVBC_ANNEX_C, + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5FrontendParameters.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5FrontendParameters.cs new file mode 100644 index 0000000..0239b4b --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5FrontendParameters.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public class LibDvb5FrontendParameters : IDisposable + { + internal LibDvb5FrontendParameters(uint adapter, uint frontend) + { + ptr = LibDvbV5.DvbFrontendOpen(adapter, frontend, 1, 0); + wrapped = Marshal.PtrToStructure(ptr); + + /* + IntPtr satNumberOffset = Marshal.OffsetOf("sat_number"); + IntPtr satNumberPtr = new IntPtr(ptr.ToInt64() + satNumberOffset.ToInt64()); + int readInt32 = Marshal.ReadInt32(ptr, satNumberOffset.ToInt32()); + Marshal.WriteInt32(ptr, satNumberOffset.ToInt32(), 2); + wrapped = Marshal.PtrToStructure(ptr); + */ + } + + private IntPtr ptr; + private FrontendParametersWrapped wrapped; + + internal struct FrontendParametersWrapped + { + public DvbFrontendInfo info; + public uint version; + public int has_v5_stats; + public LibDvbV5FrontendDeliverySystem current_sys; + public int num_system; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public LibDvbV5FrontendDeliverySystem[] system; + public int legacy_fe; + public int abort; + public int lna; + public IntPtr lnb; + public int sat_number; + public uint freq_bqf; + public uint diseqc_wait; + public uint verbose; + public IntPtr logfunc; + public IntPtr default_charset; + public IntPtr output_charset; + } + + public void Dispose() + { + LibDvbV5.DvbFrontendClose(ptr); + } + + public void StoreParameter(uint cmd, uint value) + { + if (LibDvbV5.DvbFrontentStoreParameter(ptr, cmd, value) != 0) + { + throw new IOException("failed to store parameter"); + } + } + + public void SetParameters() + { + if (LibDvbV5.DvbFrontendSetParameters(ptr) != 0) + { + throw new IOException("failed to set parameters"); + } + } + + public void SetLnb(LibDvbV5SatLnb lnb) + { + IntPtr offset = Marshal.OffsetOf("lnb"); + Marshal.WriteIntPtr(ptr, offset.ToInt32(), lnb.ptr); + wrapped = Marshal.PtrToStructure(ptr); + } + + public void GetStatistics() + { + if (LibDvbV5.DvbFrontendGetStatistics(ptr) != 0) + { + throw new IOException("failed"); + } + } + + public uint RetrieveStats(uint cmd) + { + uint value = UInt32.MaxValue; + if (LibDvbV5.DvbFrontendRetrieveStats(ptr, cmd, ref value) != 0) + { + throw new IOException("invalid value"); + } + + return value; + } + + public LibDvbV5FrontendDeliverySystem[] GetDeliverySystems() + { + LibDvbV5FrontendDeliverySystem[] result = new LibDvbV5FrontendDeliverySystem[wrapped.num_system]; + Array.Copy(wrapped.system, 0, result, 0, wrapped.num_system); + return result; + } + + public void SetDeliverySystem(LibDvbV5FrontendDeliverySystem deliverySystem) + { + if (LibDvbV5.DvbSetSystem(this.ptr, deliverySystem) != 0) + { + throw new ArgumentException("unsupported system"); + } + wrapped = Marshal.PtrToStructure(ptr); + } + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5OpenDescriptor.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5OpenDescriptor.cs new file mode 100644 index 0000000..6f50190 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5OpenDescriptor.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public class LibDvbV5OpenDescriptor : IDisposable + { + public IntPtr Pointer { get; private set; } + + internal LibDvbV5OpenDescriptor(IntPtr pointer) + { + Pointer = pointer; + } + + protected bool Equals(LibDvbV5OpenDescriptor other) + { + return Pointer.Equals(other.Pointer); + } + + 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((LibDvbV5OpenDescriptor)obj); + } + + public override int GetHashCode() + { + return Pointer.GetHashCode(); + } + + public override string ToString() + { + return String.Format("{0} 0x{X:16}", nameof(LibDvbV5DeviceList), Pointer.ToInt64()); + } + + public void Dispose() + { + LibDvbV5.DvbDevClose(Pointer); + Pointer = IntPtr.Zero; + } + + public void SetBufferSize(int bufferSize) + { + if (LibDvbV5.DvbDeviceSetBufferSize(Pointer, bufferSize) != 0) + { + throw new IOException("failed to set buffer size"); + } + } + + public void DemuxSetPesFilter(int pid, DemuxerPesType pesType) + { + if (LibDvbV5.DvbDeviceDemuxerSetPesFilter(Pointer, pid, pesType, DemuxerOutput.DMX_OUT_TS_TAP, 0) != 0) + { + throw new IOException("setPesFilter failed"); + } + } + + public int Read(byte[] buf, int count) + { + int result = LibDvbV5.DvbDeviceRead(Pointer, buf, count); + if (result == -1) + throw new IOException("read failed"); + return result; + } + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5SatLnb.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5SatLnb.cs new file mode 100644 index 0000000..9460337 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5SatLnb.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public class LibDvbV5SatLnb + { + public LibDvbV5SatLnb(int index) + { + ptr = LibDvbV5.DvbSatGetLnb(index); + _wrapped = Marshal.PtrToStructure(ptr); + } + + internal IntPtr ptr; + private Wrapped _wrapped; + + private struct Wrapped + { + public string name; + public string alias; + public uint lowfreq; + public uint highfreq; + public uint rangeswitch; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public FrequencyRange[] freqrange; + } + + private struct FrequencyRange + { + public uint low; + public uint high; + } + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5SatPolarization.cs b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5SatPolarization.cs new file mode 100644 index 0000000..6542f94 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/LibDvbV5/LibDvbV5SatPolarization.cs @@ -0,0 +1,11 @@ +namespace skyscraper5.Skyscraper.IO.LibDvbV5 +{ + public enum LibDvbV5SatPolarization + { + Off = 0, + Horizontal = 1, + Vertical = 2, + C_Left = 3, + C_Right = 4 + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/PeException.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/PeException.cs new file mode 100644 index 0000000..6cf18e7 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/PeException.cs @@ -0,0 +1,57 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + + [Serializable] + public class PeException : SkyscraperIOException + { + public PeException() { } + public PeException(string message) : base(message) { } + public PeException(string message, Exception inner) : base(message, inner) { } + protected PeException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + + [Serializable] + public class PeSectionNotFoundException : PeException + { + public PeSectionNotFoundException() { } + public PeSectionNotFoundException(string message) : base(message) { } + public PeSectionNotFoundException(string message, Exception inner) : base(message, inner) { } + protected PeSectionNotFoundException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + + [Serializable] + public class ResourceEntryException : PeException + { + public ResourceEntryException() { } + public ResourceEntryException(string message) : base(message) { } + public ResourceEntryException(string message, Exception inner) : base(message, inner) { } + protected ResourceEntryException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + + [Serializable] + public class VarNotFoundException : ResourceEntryException + { + public VarNotFoundException() { } + public VarNotFoundException(string message) : base(message) { } + public VarNotFoundException(string message, Exception inner) : base(message, inner) { } + protected VarNotFoundException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/PeFile.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/PeFile.cs new file mode 100644 index 0000000..3e1afcb --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/PeFile.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.IO.Enumeration; +using System.IO; +using System.Linq; +using System.Reflection.PortableExecutable; +using System.Runtime.ConstrainedExecution; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using System.Runtime.InteropServices; +using skyscraper5.Skyscraper; + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + public class PeFile : IDisposable + { + const ushort PortableExecutable = 17744; + const ushort ZbykowskiExecutable = 23117; + private PeHeader header; + private PeSection[] sections; + private bool ownsStream; + private bool disposed; + private Stream stream; + public PeOptionalHeader OptionalHeader { get; private set; } + + + public PeFile(Stream str, bool ownsStream = true) + { + long peOffset = FindPeOffset(str); + str.Position = peOffset; + header.mMagic = str.ReadUInt32LE(); + header.mMachine = str.ReadUInt16LE(); + header.mNumberOfSections = str.ReadUInt16LE(); + header.mTimeDateStamp = str.ReadUInt32LE(); + header.mPointerToSymbolTable = str.ReadUInt32LE(); + header.mNumberOfSymbols = str.ReadUInt32LE(); + header.mSizeOfOptionalHeader = str.ReadUInt16LE(); + header.mCharacteristics = str.ReadUInt16LE(); + if (header.mSizeOfOptionalHeader != 0) + { + byte[] optionalHeaderBytes = str.ReadBytes(header.mSizeOfOptionalHeader); + OptionalHeader = new PeOptionalHeader(optionalHeaderBytes); + } + + sections = new PeSection[header.mNumberOfSections]; + + for (int i = 0; i < header.mNumberOfSections; i++) + { + sections[i].sectionName = str.ReadBytes(8); + sections[i].virtualSize = str.ReadUInt32LE(); + sections[i].virtualAddress = str.ReadUInt32LE(); + sections[i].sizeOfRawData = str.ReadUInt32LE(); + sections[i].pointerToRawData = str.ReadUInt32LE(); + str.Position += 16; + } + this.stream = str; + this.ownsStream = ownsStream; + } + + private long FindPeOffset(Stream str) + { + ushort magic = str.ReadUInt16LE(); + switch (magic) + { + case ZbykowskiExecutable: + str.Position = 0x3C; + uint peAddress = str.ReadUInt32LE(); + str.Position = peAddress; + magic = str.ReadUInt16LE(); + if (magic == PortableExecutable) + { + return peAddress; + } + else + { + throw new PeException("Found a MZ header, but not a PE header."); + } + case PortableExecutable: + return 0; + default: + throw new PeException("Failed to find a PE header."); + } + } + + public void Dispose() + { + if (disposed) + throw new ObjectDisposedException(this.ToString()); + if (ownsStream) + { + stream.Close(); + stream.Dispose(); + } + disposed = true; + } + + public PeSectionStream GetSectionStream(string sectionName) + { + if (sectionName.Length > 8) + throw new ArgumentException(String.Format("{0} too long.", nameof(sectionName))); + if (disposed) + throw new ObjectDisposedException(this.ToString()); + foreach(PeSection section in sections) + { + if (section.ToString().Equals(sectionName)) + return GetSectionStream(section); + } + throw new PeSectionNotFoundException(sectionName); + } + + public PeSectionStream GetSectionStream(PeSection section) + { + if (!sections.Contains(section, new PeSectionEqualityComparer())) + throw new ArgumentException(String.Format("{0} must be a {1} in this {2}", nameof(section), nameof(PeSection), nameof(PeFile))); + + return new PeSectionStream(this, section, stream); + } + } + + struct PeHeader + { + public UInt32 mMagic; // PE\0\0 or 0x00004550 + public UInt16 mMachine; + public UInt16 mNumberOfSections; + public UInt32 mTimeDateStamp; + public UInt32 mPointerToSymbolTable; + public UInt32 mNumberOfSymbols; + public UInt16 mSizeOfOptionalHeader; + public UInt16 mCharacteristics; + }; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PeSection + { + public byte[] sectionName; + public uint virtualSize, virtualAddress, sizeOfRawData; + internal uint pointerToRawData; + + public override bool Equals(object obj) + { + return obj is PeSection section && + EqualityComparer.Default.Equals(sectionName, section.sectionName) && + virtualSize == section.virtualSize && + virtualAddress == section.virtualAddress && + sizeOfRawData == section.sizeOfRawData; + } + + public override int GetHashCode() + { + return HashCode.Combine(sectionName, virtualSize, virtualAddress, sizeOfRawData); + } + + public override string ToString() + { + return Encoding.ASCII.GetString(sectionName).Split('\0')[0]; + } + + + } + + public class PeOptionalHeader : Validatable + { + internal PeOptionalHeader(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ushort magic = ms.ReadUInt16LE(); + if (magic != 0x010b && magic != 0x020b) + { + Valid = false; + return; + } + byte majorLinker = ms.ReadUInt8(); + byte minorLinker = ms.ReadUInt8(); + LinkerVersion = new Version(majorLinker, minorLinker); + SizeOfCode = ms.ReadUInt32LE(); + SizeOfInitializedData = ms.ReadUInt32LE(); + SizeOfUninitializedData = ms.ReadUInt32LE(); + AddressOfEntryPoint = ms.ReadUInt32LE(); + BaseOfCode = ms.ReadUInt32LE(); + BaseOfData = ms.ReadUInt32LE(); + ImageBase = ms.ReadUInt32LE(); + SectionAlignment = ms.ReadUInt32LE(); + FileAlignment = ms.ReadUInt32LE(); + ushort majorOs = ms.ReadUInt16LE(); + ushort minorOs = ms.ReadUInt16LE(); + OperatingSystemVersion = new Version(majorOs, minorOs); + ushort majorImage = ms.ReadUInt16LE(); + ushort minorImage = ms.ReadUInt16LE(); + ImageVersion = new Version(majorImage, minorImage); + ushort majorSubsystem = ms.ReadUInt16LE(); + ushort minorSubsystem = ms.ReadUInt16LE(); + SubsystemVersion = new Version(majorSubsystem, minorSubsystem); + Win32VersionValue = ms.ReadUInt32LE(); + SizeOfImage = ms.ReadUInt32LE(); + SizeOfHeaders = ms.ReadUInt32LE(); + Checksum = ms.ReadUInt32LE(); + this.Subsystem = ms.ReadUInt16LE(); + this.DllCharacteristics = (DllCharacteristics)ms.ReadUInt16LE(); + SizeOfStackReserve = ms.ReadUInt32LE(); + SizeOfStackCommit = ms.ReadUInt32LE(); + SizeOfHeapReserve = ms.ReadUInt32LE(); + SizeOfHeapCommit = ms.ReadUInt32LE(); + LoaderFlags = ms.ReadUInt32LE(); + uint numRva = ms.ReadUInt32LE(); + DataDirectories = new DataDirectory[numRva]; + for (uint i = 0; i < numRva; i++) + { + uint rva = ms.ReadUInt32LE(); + uint size = ms.ReadUInt32LE(); + DataDirectories[i] = new DataDirectory(rva, size); + } + } + + public Version LinkerVersion { get; } + public uint SizeOfCode { get; } + public uint SizeOfInitializedData { get; } + public uint SizeOfUninitializedData { get; } + public uint AddressOfEntryPoint { get; } + public uint BaseOfCode { get; } + public uint BaseOfData { get; } + public uint ImageBase { get; } + public uint SectionAlignment { get; } + public uint FileAlignment { get; } + public Version OperatingSystemVersion { get; } + public Version ImageVersion { get; } + public Version SubsystemVersion { get; } + public uint Win32VersionValue { get; } + public uint SizeOfImage { get; } + public uint SizeOfHeaders { get; } + public uint Checksum { get; } + public ushort Subsystem { get; } + public DllCharacteristics DllCharacteristics { get; } + public uint SizeOfStackReserve { get; } + public uint SizeOfStackCommit { get; } + public uint SizeOfHeapReserve { get; } + public uint SizeOfHeapCommit { get; } + public uint LoaderFlags { get; } + + public DataDirectory[] DataDirectories { get; private set; } + + public class DataDirectory + { + public DataDirectory(uint relativeVirtualAddress, uint size) + { + RelativeVirtualAddress = relativeVirtualAddress; + Size = size; + } + + public uint RelativeVirtualAddress { get; } + public uint Size { get; } + } + } + +} diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/PeSectionEqualityComparer.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/PeSectionEqualityComparer.cs new file mode 100644 index 0000000..9beb52d --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/PeSectionEqualityComparer.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + internal class PeSectionEqualityComparer : IEqualityComparer + { + public bool Equals(PeSection x, PeSection y) + { + if (x.virtualSize != y.virtualSize) + return false; + if (x.sizeOfRawData != y.sizeOfRawData) + return false; + if (x.virtualAddress != y.virtualAddress) + return false; + if (!x.ToString().Equals(y.ToString())) + return false; + return true; + } + + public int GetHashCode([DisallowNull] PeSection obj) + { + return obj.GetHashCode(); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/PeSectionStream.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/PeSectionStream.cs new file mode 100644 index 0000000..7314351 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/PeSectionStream.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + public class PeSectionStream : Stream + { + internal PeSectionStream(PeFile peFile, PeSection section, Stream parentStream) + { + PeFile = peFile; + Section = section; + this.parentStream = parentStream; + } + + public PeFile PeFile { get; } + public PeSection Section { get; } + private int internalPosition; + private readonly Stream parentStream; + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => Section.virtualSize; + + public override long Position + { + get => internalPosition; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (value > Section.virtualSize) + throw new ArgumentOutOfRangeException(nameof(value)); + internalPosition = (int)value; + } + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + int endAddress = internalPosition + count; + if (endAddress > Length) + { + count = (int)Length - internalPosition; + if (count == 0) + throw new EndOfStreamException(); + } + + parentStream.Position = Section.pointerToRawData + internalPosition; + int result = parentStream.Read(buffer, offset, count); + internalPosition += result; + return result; + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch(origin) + { + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + case SeekOrigin.End: + Position = Length + offset; + break; + } + return Position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/RsrcParser.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/RsrcParser.cs new file mode 100644 index 0000000..470b14d --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/RsrcParser.cs @@ -0,0 +1,146 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + public static class RsrcParser + { + public static ImageResourceDirectory ParseRsrc(PeSectionStream s, int depth = 0) + { + ImageResourceDirectory result = new ImageResourceDirectory(); + result.Characteristics = s.ReadInt32LE(); + result.TimeDateStamp = s.ReadInt32LE(); + int major = s.ReadUInt16LE(); + int minor = s.ReadUInt16LE(); + result.Version = new Version(major, minor); + result.NumNamedEntries = s.ReadInt16LE(); + result.NumIdEntries = s.ReadInt16LE(); + result.Depth = depth; + + ImageResourceDirectoryEntry[] entries = new ImageResourceDirectoryEntry[result.NumEntries]; + for (int i = 0; i < entries.Length; i++) + { + entries[i] = new ImageResourceDirectoryEntry(); + entries[i].NameId = s.ReadUInt32LE(); + entries[i].Data = s.ReadUInt32LE(); + + if (entries[i].UsingNameId) + { + entries[i].Name = entries[i].NameId.ToString(); + } + else + { + long currentPos = s.Position; + s.Position = entries[i].Lower31BitOfNameId; + int length = s.ReadUInt16LE() * 2; + entries[i].Name = s.ReadUTF8FixedLength(length); + s.Position = currentPos; + } + + if (entries[i].IsSubdirectory) + { + long currentPos = s.Position; + s.Position = entries[i].NextDirOffset; + entries[i].Payload = ParseRsrc(s,depth + 1); + s.Position = currentPos; + } + else + { + long currentPos = s.Position; + s.Position = entries[i].NextDirOffset; + + ImageResourceDataEntry info = new ImageResourceDataEntry(); + info.Data = s.ReadInt32LE(); + info.Size = s.ReadInt32LE(); + info.CodePage = s.ReadInt32LE(); + info.Reserved = s.ReadInt32LE(); + info.SetContents(s, info.Data, info.Size,s.Section); + entries[i].Payload = info; + + s.Position = currentPos; + } + } + result.Entries = entries; + return result; + } + } + + public class ImageResourceDirectory + { + public int Characteristics { get; internal set; } + public int TimeDateStamp { get; internal set; } + public Version Version { get; internal set; } + public short NumNamedEntries { get; internal set; } + public short NumIdEntries { get; internal set; } + + public int NumEntries => NumNamedEntries + NumIdEntries; + + public ImageResourceDirectoryEntry[] Entries { get; internal set; } + public int Depth { get; internal set; } + } + + public class ImageResourceDirectoryEntry + { + public uint NameId { get; internal set; } + public uint Data { get; internal set; } + + public bool UsingNameId + { + get + { + return (NameId >> 31) != -1; + } + } + + public uint Lower31BitOfNameId + { + get + { + return (NameId & 0x7FFFFFFF); + } + } + + public bool IsSubdirectory + { + get + { + return (Data >> 31) == 1; + } + } + + public uint NextDirOffset + { + get + { + return Data & 0x7FFFFFFF; + } + } + + public string Name { get; internal set; } + public object Payload { get; internal set; } + } + + public class ImageResourceDataEntry + { + public int Data { get; internal set; } + public int Size { get; internal set; } + public int CodePage { get; internal set; } + public int Reserved { get; internal set; } + public byte[] Buffer { get; private set; } + + internal void SetContents(Stream s, int data, int size, PeSection section) + { + long currentPosition = s.Position; + s.Position = data - section.virtualAddress; + this.Buffer = new byte[size]; + if (s.Read(this.Buffer, 0, size) != size) + throw new EndOfStreamException(); + s.Position = currentPosition; + } + } +} diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/RsrcUtility.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/RsrcUtility.cs new file mode 100644 index 0000000..758501a --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/RsrcUtility.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + public static class RsrcUtility + { + public static VsVersionInfo TryGetVersionInfo(ImageResourceDirectory ird) + { + if (ird.Depth != 0) + throw new ArgumentException(string.Format("Need the top {0}", nameof(ImageResourceDirectory), nameof(ird))); + + ImageResourceDirectoryEntry manifestDirectoryEntry = ird.Entries.FirstOrDefault(x => x.UsingNameId && x.NameId == 16); + if (manifestDirectoryEntry == null) + throw new ResourceEntryException("Could not find manifest resource"); + + ImageResourceDirectory nameDirectory = (ImageResourceDirectory)manifestDirectoryEntry.Payload; + ImageResourceDirectoryEntry nameDirectoryEntry = nameDirectory.Entries.FirstOrDefault(); + if (nameDirectoryEntry == null) + throw new ResourceEntryException("Could not find the first entry in the manifest resource."); + + ImageResourceDirectory languageDirectory = (ImageResourceDirectory)nameDirectoryEntry.Payload; + ImageResourceDirectoryEntry languageDirectoryEntry = languageDirectory.Entries.FirstOrDefault(); + if (languageDirectory == null) + throw new ResourceEntryException("Could not find the first entry in the manifest langauge resource."); + + ImageResourceDataEntry languageData = (ImageResourceDataEntry)languageDirectoryEntry.Payload; + byte[] languageDataBuffer = languageData.Buffer; + + return new VsVersionInfo(languageDataBuffer); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/PortableExecutables/VsVersionInfo.cs b/skyscraper8/Skyscraper/IO/PortableExecutables/VsVersionInfo.cs new file mode 100644 index 0000000..8036913 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/PortableExecutables/VsVersionInfo.cs @@ -0,0 +1,251 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.IO.PortableExecutables +{ + public class VsVersionInfo + { + public VsVersionInfo(byte[] languageDataBuffer) + { + MemoryStream ms = new MemoryStream(languageDataBuffer, false); + ushort wLength = ms.ReadUInt16LE(); + ushort wValueLength = ms.ReadUInt16LE(); + this.Type = ms.ReadUInt16LE(); + string szKey = ms.ReadUTF16NullTerminated(); + ushort Padding1 = ms.ReadUInt16LE(); + if (wValueLength != 0) + { + byte[] fixedFileInfoBuffer = ms.ReadBytes(wValueLength); + FixedFileInfo = new VsFixedFileInfo(fixedFileInfoBuffer); + } + while (ms.Position % 4 != 0) + ms.ReadUInt16LE(); + + while (ms.GetAvailableBytes() > 0) + { + uint var_wLength = ms.ReadUInt16LE(); + if (var_wLength == 0) + continue; + byte[] varBuffer = ms.ReadBytes(var_wLength - 2); + Var currentChild = new Var(var_wLength, varBuffer); + if (Vars == null) + Vars = new List(); + Vars.Add(currentChild); + } + } + + public VsFixedFileInfo FixedFileInfo { get; } + public ushort Type { get; } + + public List Vars { get; } + + const string DICTIONARY_ROOT = "StringFileInfo"; + public Dictionary ToDictionary() + { + if (Vars == null) + throw new VarNotFoundException("The root VS_VERSION_INFO element does not contain any children."); + + Var stringFileInfoVar = Vars.FirstOrDefault(x => x.Key.Equals(DICTIONARY_ROOT)); + if (stringFileInfoVar == null) + throw new VarNotFoundException(String.Format("Could not find the {0} {1}", DICTIONARY_ROOT, nameof(Var))); + if (stringFileInfoVar.Vars == null) + throw new VarNotFoundException(String.Format("The {0} does not contain any sub-{1}s", DICTIONARY_ROOT, nameof(Var))); + + Var languageVar = stringFileInfoVar.Vars.FirstOrDefault(); + if (languageVar == null) + throw new VarNotFoundException(String.Format("Could not find any language related {1}s in {0}", DICTIONARY_ROOT,nameof(Var))); + if (languageVar.Vars == null) + throw new VarNotFoundException(String.Format("Did not find any {0}s in the first language related {0}.", nameof(Var))); + + Dictionary result = new Dictionary(); + foreach(Var var in languageVar.Vars) + { + if (var.Type == 1) + { + result.Add(var.Key, var.TextValue); + } + } + return result; + } + } + + public class VsFixedFileInfo + { + public VsFixedFileInfo(byte[] value) + { + MemoryStream ms = new MemoryStream(value, false); + uint dwSignature = ms.ReadUInt32LE(); + uint dwStructVersion = ms.ReadUInt32LE(); + uint dwFileVersionMS = ms.ReadUInt32LE(); + uint dwFileVersionLS = ms.ReadUInt32LE(); + uint dwProductVersionMS = ms.ReadUInt32LE(); + uint dwProductVersionLS = ms.ReadUInt32LE(); + uint dwFileFlagsMask = ms.ReadUInt32LE(); + FileFlags = (FileFlagsEnum)ms.ReadUInt32LE(); + FileOs = (FileOsEnum)ms.ReadUInt32LE(); + FileType = (FileTypeEnum)ms.ReadUInt32LE(); + FileSubtype = ms.ReadUInt32LE(); + uint dwFileDateMS = ms.ReadUInt32LE(); + uint dwFileDateLS = ms.ReadUInt32LE(); + } + + public FileFlagsEnum FileFlags { get; } + public FileOsEnum FileOs { get; } + public FileTypeEnum FileType { get; } + public uint FileSubtype { get; } + + [Flags] + public enum FileFlagsEnum : uint + { + /// + /// Die Datei enthält Debuginformationen oder wird mit aktivierten Debugfeatures kompiliert. + /// + Debug = 0x01, + /// + /// Die Versionsstruktur der Datei wurde dynamisch erstellt. daher können einige Elemente in dieser Struktur leer oder falsch sein. Dieses Flag sollte niemals in den VS_VERSIONINFO Daten einer Datei festgelegt werden. + /// + InfoInferred = 0x10, + /// + /// Die Datei wurde geändert und ist nicht mit der ursprünglichen Versanddatei mit derselben Versionsnummer identisch. + /// + Patched = 0x04, + /// + /// Die Datei ist eine Entwicklungsversion, kein kommerziell freigegebenes Produkt. + /// + Prerelease = 0x02, + /// + /// Die Datei wurde nicht mithilfe von Standardfreigabeprozeduren erstellt. Wenn dieses Flag festgelegt ist, sollte die StringFileInfo-Struktur einen PrivateBuild-Eintrag enthalten. + /// + PrivateBuild = 0x08, + /// + /// Die Datei wurde vom ursprünglichen Unternehmen mithilfe von Standard-Releaseprozeduren erstellt, ist aber eine Variante der normalen Datei mit derselben Versionsnummer. Wenn dieses Flag festgelegt ist, sollte die StringFileInfo-Struktur einen SpecialBuild-Eintrag enthalten. + /// + SpecialBuild = 0x20 + } + + [Flags] + public enum FileOsEnum : uint + { + Dos = 0x00010000, + Nt = 0x00040000, + Windows16 = 0x00000001, + Windows32 = 0x00000004, + Os216 = 0x00020000, + Os232 = 0x00030000, + Pm16 = 0x00000002, + Pm32 = 0x00000003, + Unknown = 0x00000000, + } + + public enum FileTypeEnum : uint + { + App = 0x00000001, + Dll = 0x00000002, + Drv = 0x00000003, + Font = 0x00000004, + StaticLib = 0x000000007, + Unknown = 0x00000000, + Vxd = 0x00000005, + } + + public enum FileSubtypeDriverEnum : uint + { + Comm = 0x00000000A, + Display = 0x00000004, + Installable = 0x00000008, + Keyboard = 0x00000002, + Language = 0x00000003, + Mouse = 0x00000005, + Network = 0x00000006, + Printer = 0x00000001, + Sound = 0x000000009, + System = 0x000000007, + VersionedPrinter = 0x0000000C, + Unknown = 0x00000000 + } + + public enum FileSubtypeFontEnum : uint + { + Raster = 0x00000001, + TrueType = 0x00000003, + Vector = 0x00000002, + Unknown = 0x00000000 + } + } + + public class Var + { + public Var(uint wLength, byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + ushort wValueLength = ms.ReadUInt16LE(); + this.Type = ms.ReadUInt16LE(); + Key = ms.ReadUTF16NullTerminated(); + while (((ms.Position + 2) % 4) != 0) + ms.ReadUInt16LE(); + if (this.Type == 1) + { + byte[] textBuffer = ms.ReadBytes(wValueLength * 2); + TextValue = Encoding.Unicode.GetString(textBuffer).Trim('\0'); + } + else + { + BinaryValue = ms.ReadBytes(wValueLength); + } + + while (ms.GetAvailableBytes() > 0) + { + while (ms.GetAvailableBytes() > 0) + { + uint var_wLength = ms.ReadUInt16LE(); + if (var_wLength == 0) + continue; + byte[] varBuffer = ms.ReadBytes(var_wLength - 2); + Var currentChild = new Var(var_wLength, varBuffer); + if (Vars == null) + Vars = new List(); + Vars.Add(currentChild); + } + } + + } + + public ushort Type { get; } + public string Key { get; } + + public List Vars { get; } + public string TextValue { get; } + public byte[] BinaryValue { get; } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(Key); + sb.Append(" = "); + if (Type == 1) + { + if (string.IsNullOrEmpty(TextValue)) + { + sb.Append(""); + } + sb.Append(TextValue); + } + else + { + sb.Append("0x"); + sb.Append(BitConverter.ToString(BinaryValue).Replace("-","")); + } + + if (Vars != null) + { + sb.AppendFormat(" ({0} children)", Vars.Count); + } + return sb.ToString(); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderClient.cs b/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderClient.cs new file mode 100644 index 0000000..7c5396d --- /dev/null +++ b/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderClient.cs @@ -0,0 +1,909 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Net.Sockets; + +namespace skyscraper5.Skyscraper.IO.RemoteStreamReader +{ + public class RemoteStreamReaderClient : IDisposable, IStreamReader + { + public RemoteStreamReaderClient(IPEndPoint remote) + { + this.remoteEndPoint = remote; + this.Reconnect(); + } + + private IPEndPoint remoteEndPoint; + + private void Reconnect() + { + if (remoteEndPoint == null) + { + if (this.TcpStream != null) + { + this.remoteEndPoint = (IPEndPoint)this.TcpStream.Socket.RemoteEndPoint; + } + } + TcpClient tcpClient = new TcpClient(); + tcpClient.Connect(remoteEndPoint); + + TcpClient = tcpClient; + TcpStream = tcpClient.GetStream(); + + RemoteStreamReaderConstants greeting = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + if (greeting != RemoteStreamReaderConstants.SERVER_READY) + { + tcpClient.Close(); + throw new RemoteStreamReaderException("Server is either busy or there is a protocol violation."); + } + } + + public NetworkStream TcpStream { get; set; } + + public TcpClient TcpClient { get; set; } + + public bool Disposed { get; private set; } + + public void Dispose() + { + if (TcpStream != null) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_DISPOSE); + TcpStream.Flush(); + RemoteStreamReaderConstants dispoResponse = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + if (dispoResponse != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL) + { + Console.WriteLine("Warning: Server didn't dispose correctly."); + } + TcpStream.Dispose(); + } + TcpClient?.Dispose(); + Disposed = true; + } + + public void NoOperation() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_NOOP); + TcpStream.Flush(); + RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL) + throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(NoOperation))); + } + + public string GetHostname() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_HOSTNAME); + TcpStream.Flush(); + RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL) + throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(GetHostname))); + + string result = TcpStream.ReadUTF8(); + return result; + } + + public string GetUsername() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_USERNAME); + TcpStream.Flush(); + RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL) + throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(GetUsername))); + + string result = TcpStream.ReadUTF8(); + return result; + } + + public DateTime GetDateTime() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_DATE_TIME); + TcpStream.Flush(); + RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL) + throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(GetUsername))); + + long int64 = TcpStream.ReadInt64BE(); + return new DateTime(int64); + } + + public bool CheckForDVB() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB); + TcpStream.Flush(); + + RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (response) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(response.ToString()); + } + } + + public bool CheckForDVBEx(DvbEnumCallback func) + { + throw new NotImplementedException(); + } + + public bool CheckForDVBExEx(DvbEnumCallbackEx func) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB_EX_EX); + TcpStream.Flush(); + + while (true) + { + uint opcode = TcpStream.ReadUInt32BE(); + switch (opcode) + { + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + case (uint)RemoteStreamReaderConstants.ENUMERATE_CHECK_FOR_DVB_EX_EX: + int index = TcpStream.ReadInt32BE(); + string name = TcpStream.ReadUTF8(); + STD_TYPE type = (STD_TYPE)TcpStream.ReadInt32BE(); + func(index, name, type); + break; + default: + throw new RemoteStreamReaderException(string.Format("Protocol violation in {0}", nameof(CheckForDVBExEx))); + } + } + } + + public bool StartDVB() + { + throw new NotImplementedException(); + } + + public bool StartDvbEx(int index) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_START_DVB_EX); + TcpStream.WriteInt32BE(index); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool StopDVB() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_STOP_DVB); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool GetTunerType(ref STD_TYPE type) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_TUNER_TYPE); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + type = (STD_TYPE)TcpStream.ReadInt32BE(); + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SEND_DISEQC); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, diseqcType); + byte sendMe = (byte)data; + NetworkStreamExtensions.WriteUInt8(TcpStream, sendMe); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool SendDiseqCmd(byte[] buffer, int length) + { + throw new NotImplementedException(); + } + + public bool SendDiseqCmdEx(IntPtr pCmd, int length, IntPtr reply, int replyLength) + { + throw new NotImplementedException(); + } + + public bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL); + TcpStream.WriteInt32BE(freq); + TcpStream.WriteInt32BE(symbrate); + TcpStream.WriteInt32BE(pol); + TcpStream.WriteInt32BE((int)fec); + TcpStream.WriteInt32BE(lof1); + TcpStream.WriteInt32BE(lof2); + TcpStream.WriteInt32BE(lofsw); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod) + { + throw new NotImplementedException(); + } + + public bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod, + int inv, int pilot, ROLLOFF_TYPE rolloff) + { + throw new NotImplementedException(); + } + + public bool SetFilter(int pid, StdcallDvbCallback func, int callbackType, int size, ref IntPtr lpFilterNum) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SET_FILTER); + TcpStream.WriteInt32BE(pid); + TcpStream.WriteInt32BE(size); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + lpFilterNum = TcpStream.ReadIntPtr(); + IPEndPoint remoteListener = TcpStream.ReadIPEndPoint(); + IPEndPoint masterEndPoint = TcpClient.Client.RemoteEndPoint as IPEndPoint; + IPEndPoint streamSource = new IPEndPoint(masterEndPoint.Address, remoteListener.Port); + if (filterClients == null) + filterClients = new Dictionary(); + FilterClient childClient = new FilterClient(streamSource, func); + filterClients.Add(lpFilterNum, childClient); + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + private Dictionary filterClients; + class FilterClient : IDisposable + { + private readonly StdcallDvbCallback _stdcallDvbCallback; + private readonly TcpClient _tcpClient; + private readonly BufferedStream _bufferedStream; + public Thread Thread { get; private set; } + private readonly NetworkStream _tcpStream; + + public FilterClient(IPEndPoint streamSource, StdcallDvbCallback stdcallDvbCallback) + { + _stdcallDvbCallback = stdcallDvbCallback; + _tcpClient = new TcpClient(); + _tcpClient.Connect(streamSource); + _tcpStream = _tcpClient.GetStream(); + _bufferedStream = new BufferedStream(_tcpStream, 96256); + Thread = new Thread(Run); + Thread.Name = string.Format("Remote Filter @{0}", streamSource.ToString()); + Thread.Start(); + } + + private bool disposed; + public void Dispose() + { + disposed = true; + _tcpClient?.Dispose(); + } + + private byte[] safeBuffer; + private IntPtr unsafeBuffer; + + private void Run() + { + safeBuffer = new byte[188]; + unsafeBuffer = Marshal.AllocHGlobal(188); + while (!disposed) + { + try + { + if (_bufferedStream.Read(safeBuffer, 0, 188) != 188) + throw new IOException("omg wut?"); + } + catch (NotSupportedException e) + { + //Disposal race condition. + continue; + } + catch (ObjectDisposedException e) + { + break; + } + catch (IOException e) + { + break; + } + catch (ThreadInterruptedException tie) + { + break; + } + + + Marshal.Copy(safeBuffer, 0, unsafeBuffer, 188); + _stdcallDvbCallback(unsafeBuffer, 188); + } + + Marshal.FreeHGlobal(unsafeBuffer); + + if (!disposed) + Dispose(); + } + } + + public bool SetFilterEx(int pid, StdcallDvbCallbackEx lpFunc, int callbackType, int size, ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool SetBitFilter(int pid, IntPtr filterData, IntPtr filterMask, byte filterLength, StdcallDvbCallback lpFunc, + ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool SetRemoteControl(int irtype, short devAddr, StdcallDvbCallback func, ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool DelFilter(IntPtr filterNum) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_DEL_FILTER); + TcpStream.WriteIntPtr(filterNum); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + filterClients[filterNum].Dispose(); + filterClients[filterNum].Thread.Interrupt(); + filterClients.Remove(filterNum); + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool GetSignal(ref int pStrength, ref int pQuality) + { + throw new NotImplementedException(); + } + + public bool GetSignalStrength(ref float pStrength, ref float pQuality) + { + throw new NotImplementedException(); + } + + public bool GetSignalEx(ref float pSNR, ref float pBER) + { + throw new NotImplementedException(); + } + + public bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, ref float pSNR, ref float pBER) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_SIGNAL_EX_EX); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + pPresent = TcpStream.ReadBoolean(); + pLock = TcpStream.ReadBoolean(); + pRFLevel = TcpStream.ReadInt32BE(); + pSNR = TcpStream.ReadFloat(); + pBER = TcpStream.ReadFloat(); + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool Statistic(int[] pStat) + { + throw new NotImplementedException(); + } + + public bool GetMAC(byte[] pMac) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_MAC); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + if (TcpStream.Read(pMac, 0, 6) != 6) + throw new EndOfStreamException(); + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public Caps GetCaps() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_CAPS); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + Caps caps = (Caps)TcpStream.ReadInt32BE(); + return caps; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_RF_SCAN); + TcpStream.WriteInt32BE(freq); + TcpStream.WriteInt32BE(pol); + TcpStream.WriteInt32BE(lof1); + TcpStream.WriteInt32BE(lof2); + TcpStream.WriteInt32BE(lofsw); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + pRFLevel = NetworkStreamExtensions.ReadDouble(TcpStream); + return true; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool FFTInit() + { + throw new NotImplementedException(); + } + + public bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, byte nb_acc, IntPtr pTab, + IntPtr pBegin, IntPtr pNum) + { + throw new NotImplementedException(); + } + + public bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, + ref SearchResult pSearchResult) + { + throw new NotImplementedException(); + } + + public bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, STD_TYPE std, ref SearchResult pSearchResult) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN_EX); + TcpStream.WriteInt32BE(freq); + TcpStream.WriteInt32BE(freq_range); + TcpStream.WriteInt32BE(pol); + TcpStream.WriteInt32BE(lof1); + TcpStream.WriteInt32BE(lof2); + TcpStream.WriteInt32BE(lofsw); + TcpStream.WriteInt32BE(minsr); + TcpStream.WriteInt32BE((int)std); + uint opcode = TcpStream.ReadUInt32BE(); + switch (opcode) + { + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + int sizeOf = Marshal.SizeOf(typeof(SearchResult)); + byte[] enumerateBuffer = new byte[sizeOf]; + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + TcpStream.Read(enumerateBuffer, 0, sizeOf); + Marshal.Copy(enumerateBuffer, 0, allocHGlobal, sizeOf); + pSearchResult = (SearchResult)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult)); + Marshal.FreeHGlobal(allocHGlobal); + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(((RemoteStreamReaderConstants)opcode).ToString()); + } + } + + public bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, IntPtr pSearchResult, ref int pTpNum, BlScanCallback lpFunc) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN2); + TcpStream.WriteInt32BE(freq_start); + TcpStream.WriteInt32BE(freq_stop); + TcpStream.WriteInt32BE(pol); + TcpStream.WriteInt32BE(lof1); + TcpStream.WriteInt32BE(lof2); + TcpStream.WriteInt32BE(lofsw); + TcpStream.Flush(); + + int sizeOf = Marshal.SizeOf(typeof(SearchResult)); + byte[] enumerateBuffer = new byte[sizeOf]; + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + + while (true) + { + uint opcode = TcpStream.ReadUInt32BE(); + switch (opcode) + { + case (uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME: + break; + case (uint)RemoteStreamReaderConstants.ENUMERATE_BL_SCAN_2: + TcpStream.Read(enumerateBuffer, 0, sizeOf); + Marshal.Copy(enumerateBuffer, 0, allocHGlobal, sizeOf); + SearchResult sr = (SearchResult)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult)); + lpFunc(ref sr); + break; + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + pTpNum = TcpStream.ReadInt32BE(); + Marshal.FreeHGlobal(allocHGlobal); + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + Marshal.FreeHGlobal(allocHGlobal); + return false; + default: + throw new NotImplementedException(((RemoteStreamReaderConstants)opcode).ToString()); + } + } + } + + public bool SignalInfo(ref SearchResult pSearchResult) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + int sizeOf = Marshal.SizeOf(typeof(SearchResult)); + byte[] readBytes = TcpStream.ReadBytes(sizeOf); + + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf); + pSearchResult = (SearchResult)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult)); + + //are we sane? + Array.Fill(readBytes, (byte)0); + Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf); + + Marshal.FreeHGlobal(allocHGlobal); + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + + throw new NotImplementedException(); + } + + public bool IQScan(uint input, sbyte[] pIQ, uint num) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_IQ_SCAN); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, input); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, num); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + byte[] tmp = TcpStream.ReadByteArray(); + for (long i = 0; i < num * 2; i++) + { + pIQ[i] = (sbyte)tmp[i]; + } + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public bool IQScan2(uint point, short[] pIQ, uint num) + { + throw new NotImplementedException(); + } + + public bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint) + { + throw new NotImplementedException(); + } + + public bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, ref int pMinDelayNs, ref int pMaxDelayNs) + { + throw new NotImplementedException(); + } + + public bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays) + { + throw new NotImplementedException(); + } + + public bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum) + { + throw new NotImplementedException(); + } + + public bool CarEsNo(ref ushort cnum, ref double pEsNo) + { + throw new NotImplementedException(); + } + + public bool MISSel(bool bEnable, byte misFilter, byte misFilterMask) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_MIS_SEL); + TcpStream.WriteBoolean(bEnable); + NetworkStreamExtensions.WriteUInt8(TcpStream, misFilter); + NetworkStreamExtensions.WriteUInt8(TcpStream, misFilterMask); + TcpStream.Flush(); + uint result = TcpStream.ReadUInt32BE(); + switch (result) + { + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(((RemoteStreamReaderConstants)result).ToString()); + } + } + + public bool PLSSel(byte plsMode, uint code) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_PLS_SEL); + NetworkStreamExtensions.WriteUInt8(TcpStream, plsMode); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, code); + TcpStream.Flush(); + uint result = TcpStream.ReadUInt32BE(); + switch (result) + { + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(((RemoteStreamReaderConstants)result).ToString()); + } + } + + public bool PLSGet(byte pMode, ref uint pCode) + { + throw new NotImplementedException(); + } + + public bool ModSel(ref S2Mode ps2Modes, uint num) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_MOD_SEL); + TcpStream.WriteInt32BE(ps2Modes.frameLen); + TcpStream.WriteInt32BE((int)ps2Modes.modcode); + TcpStream.WriteBoolean(ps2Modes.pilot); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, num); + TcpStream.Flush(); + uint result = TcpStream.ReadUInt32BE(); + switch (result) + { + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + ps2Modes.frameLen = TcpStream.ReadInt32BE(); + ps2Modes.modcode = (S2MODCODE)TcpStream.ReadInt32BE(); + ps2Modes.pilot = TcpStream.ReadBoolean(); + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(((RemoteStreamReaderConstants)result).ToString()); + } + } + + public bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum) + { + throw new NotImplementedException(); + } + + public bool SetChannel2(uint freq, uint bandwidth) + { + if (!TcpStream.Socket.Connected) + { + Reconnect(); + } + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL_2); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, freq); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, bandwidth); + TcpStream.Flush(); + + RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (response) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + return true; + case RemoteStreamReaderConstants.COMMAND_FAILED: + return false; + default: + throw new NotImplementedException(response.ToString()); + } + } + + public bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream) + { + throw new NotImplementedException(); + } + + public bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, int stream) + { + throw new NotImplementedException(); + } + + public bool SignalInfo2(ref SearchResult2 si2) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO_2); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + int sizeOf = Marshal.SizeOf(typeof(SearchResult2)); + byte[] readBytes = TcpStream.ReadBytes(sizeOf); + + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf); + si2 = (SearchResult2)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult2)); + + //are we sane? + Array.Fill(readBytes, (byte)0); + Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf); + + Marshal.FreeHGlobal(allocHGlobal); + return true; + default: + throw new NotImplementedException(result.ToString()); + } + + throw new NotImplementedException(); + } + + public bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel) + { + throw new NotImplementedException(); + } + + public bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, IntPtr pSearchResult, ref int pTpNum, AirScanCallback lpFunc) + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_AIR_SCAN); + TcpStream.WriteInt32BE(freq_start); + TcpStream.WriteInt32BE(freq_stop); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, step); + NetworkStreamExtensions.WriteUInt32BE(TcpStream, bandwidth); + TcpStream.WriteInt32BE(std); + TcpStream.Flush(); + + int sizeOf = Marshal.SizeOf(typeof(SearchResult2)); + byte[] enumerateBuffer = new byte[sizeOf]; + IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf); + + while (true) + { + uint opcode = TcpStream.ReadUInt32BE(); + switch (opcode) + { + case (uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME: + break; + case (uint)RemoteStreamReaderConstants.ENUMERATE_AIR_SCAN: + TcpStream.Read(enumerateBuffer, 0, sizeOf); + Marshal.Copy(enumerateBuffer, 0, allocHGlobal, sizeOf); + SearchResult2 sr = (SearchResult2)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult2)); + lpFunc(ref sr); + break; + case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + pTpNum = TcpStream.ReadInt32BE(); + Marshal.FreeHGlobal(allocHGlobal); + return true; + case (uint)RemoteStreamReaderConstants.COMMAND_FAILED: + Marshal.FreeHGlobal(allocHGlobal); + return false; + } + } + } + + public bool GetEEPROM(byte[] buffer, int offset, int len) + { + throw new NotImplementedException(); + } + + public bool SetEEPROM(byte[] buffer, int offset, int len) + { + throw new NotImplementedException(); + } + + public Version GetEngineVersion() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_VERSION); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + Version payload = TcpStream.ReadVersion(); + return payload; + default: + throw new NotImplementedException(result.ToString()); + } + } + + public string GetEngineName() + { + NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_NAME); + TcpStream.Flush(); + + RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE(); + switch (result) + { + case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: + string payload = TcpStream.ReadUTF8(); + return payload; + default: + throw new NotImplementedException(result.ToString()); + } + } + } +} diff --git a/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderConstants.cs b/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderConstants.cs new file mode 100644 index 0000000..f3d3427 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderConstants.cs @@ -0,0 +1,45 @@ +namespace skyscraper5.Skyscraper.IO.RemoteStreamReader +{ + public enum RemoteStreamReaderConstants : uint + { + SERVER_BUSY_WITH_ANOTHER_CLIENT, + SERVER_READY, + COMMAND_NOT_UNDERSTOOD, + REQUEST_NOOP, + COMMAND_SUCCESSFUL, + REQUEST_HOSTNAME, + REQUEST_USERNAME, + REQUEST_DATE_TIME, + REQUEST_DISPOSE, + REQUEST_CHECK_FOR_DVB_EX_EX, + ENUMERATE_CHECK_FOR_DVB_EX_EX, + COMMAND_FAILED, + REQUEST_START_DVB_EX, + REQUEST_STOP_DVB, + REQUEST_GET_TUNER_TYPE, + REQUEST_GET_CAPS, + REQUEST_SEND_DISEQC, + REQUEST_RF_SCAN, + REQUEST_SET_CHANNEL, + REQUEST_SIGNAL_INFO, + REQUEST_SET_FILTER, + REQUEST_DEL_FILTER, + REQUEST_BL_SCAN2, + COMMAND_WILL_TAKE_SOME_TIME, + ENUMERATE_BL_SCAN_2, + REQUEST_CHECK_FOR_DVB, + REQUEST_GET_MAC, + REQUEST_AIR_SCAN, + ENUMERATE_AIR_SCAN, + REQUEST_SET_CHANNEL_2, + REQUEST_SIGNAL_INFO_2, + REQUEST_MIS_SEL, + REQUEST_PLS_SEL, + REQUEST_BL_SCAN_EX, + REQUEST_MOD_SEL, + REQUEST_IQ_SCAN, + REQUEST_GET_SIGNAL_EX_EX, + REQUEST_SKYSCRAPER_ENGINE_NAME, + REQUEST_SKYSCRAPER_ENGINE_VERSION + } +} diff --git a/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderException.cs b/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderException.cs new file mode 100644 index 0000000..eb929e0 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/RemoteStreamReader/RemoteStreamReaderException.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.Serialization; + +namespace skyscraper5.Skyscraper.IO.RemoteStreamReader +{ + [Serializable] + public class RemoteStreamReaderException : SkyscraperIOException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public RemoteStreamReaderException() + { + } + + public RemoteStreamReaderException(string message) : base(message) + { + } + + public RemoteStreamReaderException(string message, Exception inner) : base(message, inner) + { + } + + protected RemoteStreamReaderException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Skyscraper/IO/SkyscraperIOException.cs b/skyscraper8/Skyscraper/IO/SkyscraperIOException.cs new file mode 100644 index 0000000..6f4dbd2 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/SkyscraperIOException.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.Serialization; + +namespace skyscraper5.Skyscraper.IO +{ + [Serializable] + public class SkyscraperIOException : SkyscraperException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public SkyscraperIOException() + { + } + + public SkyscraperIOException(string message) : base(message) + { + } + + public SkyscraperIOException(string message, Exception inner) : base(message, inner) + { + } + + protected SkyscraperIOException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Skyscraper/IO/StreamExtensions.cs b/skyscraper8/Skyscraper/IO/StreamExtensions.cs new file mode 100644 index 0000000..c2e04c6 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/StreamExtensions.cs @@ -0,0 +1,279 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace skyscraper5.Skyscraper.IO +{ + public static class StreamExtensions + { + private static byte[] buffer = new byte[8]; + + [DebuggerStepThrough] + public static ushort ReadUInt16BE(this Stream stream) + { + if (stream.Read(buffer, 0, 2) != 2) + throw new EndOfStreamException(); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 2); + return BitConverter.ToUInt16(buffer, 0); + } + + public static short ReadInt16LE(this Stream stream) + { + if (stream.Read(buffer, 0, 2) != 2) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 2); + return BitConverter.ToInt16(buffer, 0); + } + + public static ushort ReadUInt16LE(this Stream stream) + { + if (stream.Read(buffer, 0, 2) != 2) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 2); + return BitConverter.ToUInt16(buffer, 0); + } + + [DebuggerStepThrough] + public static uint ReadUInt32BE(this Stream stream) + { + if (stream.Read(buffer, 0, 4) != 4) + throw new EndOfStreamException(); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToUInt32(buffer, 0); + } + + [DebuggerStepThrough] + public static uint ReadUInt32LE(this Stream stream) + { + if (stream.Read(buffer, 0, 4) != 4) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToUInt32(buffer, 0); + } + + public static int ReadInt32LE(this Stream stream) + { + if (stream.Read(buffer, 0, 4) != 4) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToInt32(buffer, 0); + } + + public static long ReadInt64LE(this Stream stream) + { + if (stream.Read(buffer, 0, 8) != 8) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToInt64(buffer, 0); + } + + public static ulong ReadUInt64LE(this Stream stream) + { + if (stream.Read(buffer, 0, 8) != 8) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToUInt64(buffer, 0); + } + + public static ulong ReadUInt64BE(this Stream stream) + { + if (stream.Read(buffer, 0, 8) != 8) + throw new EndOfStreamException(); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 4); + return BitConverter.ToUInt64(buffer, 0); + } + + [DebuggerStepThrough] + public static byte[] ReadBytes(this Stream stream, long length) + { + if (length > Int32.MaxValue) + throw new ArgumentOutOfRangeException(); + if (length == 0) + return new byte[0]; + if (length < 0) + throw new ArgumentOutOfRangeException(); + + int length32 = (int)length; + + byte[] buffer = new byte[length32]; + if (stream.Read(buffer, 0, length32) != length32) + throw new EndOfStreamException(); + + return buffer; + } + + [DebuggerStepThrough] + public static byte ReadUInt8(this Stream stream) + { + if (stream.Read(buffer, 0, 1) != 1) + throw new EndOfStreamException(); + + return buffer[0]; + } + + [DebuggerStepThrough] + public static long GetAvailableBytes(this Stream stream) + { + return stream.Length - stream.Position; + } + + public static void WriteUInt8Repeat(this Stream stream, byte uint8, int repetitions) + { + byte[] buffer = new byte[repetitions]; + Array.Fill(buffer, uint8); + stream.Write(buffer, 0, repetitions); + } + + public static uint ReadUInt24BE(this Stream stream) + { + uint result = stream.ReadUInt16BE(); + result <<= 8; + result += stream.ReadUInt8(); + return result; + } + + public static double ReadDouble(this Stream stream) + { + if (stream.Read(buffer, 0, 8) != 8) + throw new EndOfStreamException(); + if (!BitConverter.IsLittleEndian) + Array.Reverse(buffer, 0, 8); + return BitConverter.ToDouble(buffer, 0); + } + + public static void WriteUInt8(this Stream stream, byte value) + { + byte[] buffer = new byte[1]; + buffer[0] = value; + stream.Write(buffer, 0, 1); + } + + public static void WriteUInt16BE(this Stream stream, ushort value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + (buffer[0], buffer[1]) = (buffer[1], buffer[0]); + stream.Write(buffer, 0, 2); + } + public static void WriteUInt16LE(this Stream stream, ushort value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (!BitConverter.IsLittleEndian) + (buffer[0], buffer[1]) = (buffer[1], buffer[0]); + stream.Write(buffer, 0, 2); + } + + public static void WriteUInt32BE(this Stream stream, uint value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + (buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]); + stream.Write(buffer, 0, 4); + } + public static void WriteUInt32LE(this Stream stream, uint value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (!BitConverter.IsLittleEndian) + (buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]); + stream.Write(buffer, 0, 4); + } + + public static void WriteInt32LE(this Stream stream, int value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (!BitConverter.IsLittleEndian) + (buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]); + stream.Write(buffer, 0, 4); + } + + public static void WriteInt64LE(this Stream stream, long value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (!BitConverter.IsLittleEndian) + (buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]) = (buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2], buffer[1], buffer[0]); + stream.Write(buffer, 0, 8); + } + + public static string ReadUTF8FixedLength(this Stream stream, int length) + { + byte[] buffer = new byte[length]; + if (stream.Read(buffer, 0, length) != length) + throw new EndOfStreamException(); + return Encoding.UTF8.GetString(buffer); + } + + public static string ReadUTF16NullTerminated(this Stream stream) + { + StringBuilder sb = new StringBuilder(); + do + { + char c = (char)stream.ReadUInt16LE(); + if (c == 0) + return sb.ToString(); + sb.Append(c); + } while (stream.Position != stream.Length); + return sb.ToString(); + } + + [DebuggerStepThrough] + public static string ReadAsciiNullTerminated(this Stream stream) + { + StringBuilder sb = new StringBuilder(); + do + { + char c = (char)stream.ReadUInt8(); + if (c == 0) + return sb.ToString(); + sb.Append(c); + } while(stream.Position != stream.Length); + return sb.ToString(); + } + + /// + /// Writes a variable-length integer to the stream. + /// (code generated by ChatGPT, model GPT-4o) + /// + public static void WriteVarInt(this Stream stream, long value) + { + while ((value & ~0x7F) != 0) // More than 7 bits left? + { + stream.WriteByte((byte)((value & 0x7F) | 0x80)); // Write 7 bits + set highest bit + value >>= 7; // Shift right by 7 bits + } + stream.WriteByte((byte)value); // Write last byte (highest bit unset) + } + + /// + /// Reads a variable-length integer from the stream. + /// (code generated by ChatGPT, model GPT-4o) + /// + public static int ReadVarInt(this Stream stream) + { + int result = 0; + int shift = 0; + byte currentByte; + + do + { + currentByte = (byte)stream.ReadByte(); + result |= (currentByte & 0x7F) << shift; // Extract 7 bits and shift + shift += 7; + } + while ((currentByte & 0x80) != 0); // Continue while highest bit is set + + return result; + } + + } +} diff --git a/skyscraper8/Skyscraper/IO/StreamReaderScanController.cs b/skyscraper8/Skyscraper/IO/StreamReaderScanController.cs new file mode 100644 index 0000000..439d0ee --- /dev/null +++ b/skyscraper8/Skyscraper/IO/StreamReaderScanController.cs @@ -0,0 +1,468 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage.Filesystem; + +namespace skyscraper5.Skyscraper.IO +{ + internal class StreamReaderScanController : IDisposable + { + private readonly IStreamReader streamReader; + + public static List GetTuners(IStreamReader streamReader) + { + List result = new List(); + bool checkForDvbExEx = streamReader.CheckForDVBExEx((index, name, type) => + { + result.Add(new StreamReaderTuner(index, name, type)); + }); + if (!checkForDvbExEx) + { + return new List(); + } + return result; + } + + public List GetTunersByStandard(STD_TYPE stdType) + { + return GetTuners(streamReader).Where(x => x.Type == stdType).ToList(); + } + + public StreamReaderTuner GetTunerByStandard(STD_TYPE stdType) + { + List streamReaderTuners = GetTunersByStandard(stdType); + if (streamReaderTuners.Count == 0) + return null; + else + return streamReaderTuners[0]; + } + + private bool busy; + private PhysicalAddress physicalAddress; + private STD_TYPE stdType; + private Caps caps; + private StreamReaderTuner tunerMetadata; + private static int instances = 0; + private bool disposed; + + public StreamReaderScanController(IStreamReader streamReader, StreamReaderTuner tuner) + { + this.streamReader = streamReader; + + if (instances > 0) + { + throw new StreamReaderException("Only one StreamReader may be active, dispose the other one first."); + } + + if (tuner != null) + { + tunerMetadata = tuner; + if (!streamReader.StartDvbEx(tuner.Index)) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.StartDvbEx))); + } + } + else + { + if (!streamReader.StartDVB()) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.StartDVB))); + } + } + + caps = streamReader.GetCaps(); + if (!streamReader.GetTunerType(ref stdType)) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.GetTunerType))); + } + + if (caps.HasFlag(Caps.SR_GETMAC)) + { + byte[] macBuffer = new byte[6]; + if (streamReader.GetMAC(macBuffer)) + { + physicalAddress = new PhysicalAddress(macBuffer); + } + } + + string[] strings = Enum.GetNames(typeof(StreamReaderParameter)); + parameters = new uint[strings.Length]; + paramsSet = new bool[strings.Length]; + + if (stdType == STD_TYPE.STD_DVBC) + SetDefaultDvbCParameters(); + } + + private bool[] paramsSet; + private uint[] parameters; + public enum StreamReaderParameter : int + { + ScanStartFreq, + ScanEndFreq, + ScanStep, + ScanBandwidth, + ScanStdType, + ScanLof1, + ScanLofSwitch, + ScanLof2, + ScanDiseqcType, + DiseqcSwitchPosition + } + + public void SetParameter(StreamReaderParameter key, uint value) + { + parameters[(int)key] = value; + paramsSet[(int)key] = true; + } + + public uint GetParameter(StreamReaderParameter key) + { + if (!paramsSet[(int)key]) + { + throw new StreamReaderException(string.Format("{0} was not set.", key.ToString())); + } + return parameters[(int)key]; + } + + public void SetDefaultDvbCParameters() + { + SetParameter(StreamReaderParameter.ScanStartFreq, 48000); + SetParameter(StreamReaderParameter.ScanEndFreq, 1000000); + SetParameter(StreamReaderParameter.ScanStep, 3000); + SetParameter(StreamReaderParameter.ScanBandwidth, 8000); + SetParameter(StreamReaderParameter.ScanStdType, (uint)STD_TYPE.STD_DVBC); + } + + public void SetDefaultKuBandParameters() + { + SetParameter(StreamReaderParameter.ScanLof1, 9750000); + SetParameter(StreamReaderParameter.ScanLofSwitch, 11700000); + SetParameter(StreamReaderParameter.ScanLof2, 10600000); + SetParameter(StreamReaderParameter.ScanStartFreq, 10700000); + SetParameter(StreamReaderParameter.ScanEndFreq, 12750000); + } + + public void Dispose() + { + if (busy) + { + throw new StreamReaderException("TunerMetadata is busy."); + } + + streamReader.StopDVB(); + instances--; + disposed = true; + } + + public class StreamReaderTuner + { + public int Index { get; } + public string Name { get; } + public STD_TYPE Type { get; } + + public StreamReaderTuner(int index, string name, STD_TYPE type) + { + Index = index; + Name = name; + Type = type; + } + } + + public bool Tune(BlindScanResult blindScanResult) + { + switch (stdType) + { + case STD_TYPE.STD_DVBC: + Console.WriteLine("Tuning to {0} Mhz", blindScanResult.SearchResult2.Freq / 1000.0); + bool channel2 = streamReader.SetChannel2((uint)blindScanResult.SearchResult2.Freq, (uint)blindScanResult.SearchResult2.BW); + if (!channel2) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.SetChannel2))); + } + + SearchResult2 sr2 = default; + bool signalInfo2 = streamReader.SignalInfo2(ref sr2); + if (!signalInfo2) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.SetChannel2))); + } + + return sr2.Lock; + case STD_TYPE.STD_DVBS: + Console.WriteLine("Tuning to {0} Mhz", blindScanResult.SearchResult1.Freq / 1000.0); + SearchResult searchResult1 = blindScanResult.SearchResult1; + uint lof1 = GetParameter(StreamReaderParameter.ScanLof1); + uint lof2 = GetParameter(StreamReaderParameter.ScanLof2); + uint lofSw = GetParameter(StreamReaderParameter.ScanLofSwitch); + if (!streamReader.SetChannel(searchResult1.Freq, searchResult1.SR, searchResult1.Pol, searchResult1.FEC, (int)lof1, (int)lof2, (int)lofSw)) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.SetChannel))); + } + + SearchResult cmp = default; + bool signalInfo = streamReader.SignalInfo(ref cmp); + if (!signalInfo) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.SignalInfo))); + } + return cmp.Lock; + default: + throw new NotImplementedException(); + } + } + + private void SearchResult2Callback(BlindScanResults results, SearchResult2 sr2Ptr) + { + BlindScanResult bbsr = new BlindScanResult(); + bbsr.System = stdType; + bbsr.SearchResult2 = sr2Ptr; + results.Transponders.Add(bbsr); + + Console.WriteLine("{0} Mhz, {1} ksyms, {2}", sr2Ptr.Freq / 1000.0, sr2Ptr.SR / 1000, (MOD_TYPE)sr2Ptr.ModType); + } + + private void SearchResult2Callback(BlindScanResults results, SearchResult sr2Ptr) + { + BlindScanResult bbsr = new BlindScanResult(); + bbsr.SearchResult1 = sr2Ptr; + bbsr.System = stdType; + results.Transponders.Add(bbsr); + + Console.WriteLine("{0} Mhz, {1} ksyms, {2}", sr2Ptr.Freq / 1000.0, sr2Ptr.SR / 1000, (MOD_TYPE)sr2Ptr.ModType); + } + + private void SendDiseqcCommand(int diseqcType, bool highBand, bool horizontal) + { + DiSEqC_Opcode myOpcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE; + if (highBand) + myOpcode |= DiSEqC_Opcode.DISEQC_HIGH_BAND; + else + myOpcode |= DiSEqC_Opcode.DISEQC_LOW_BAND; + + if (horizontal) + myOpcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL; + else + myOpcode |= DiSEqC_Opcode.DISEQC_VERTICAL; + + if (diseqcType == 2) + { + uint parameter = GetParameter(StreamReaderParameter.DiseqcSwitchPosition); + switch (parameter) + { + case 1: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A; + break; + case 2: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B; + break; + case 3: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A; + break; + case 4: + myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B; + myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B; + break; + default: + throw new ArgumentOutOfRangeException(nameof(StreamReaderParameter.DiseqcSwitchPosition)); + } + } + + bool sendDiSEqC = streamReader.SendDiSEqC((uint)diseqcType, myOpcode); + if (!sendDiSEqC) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.SendDiSEqC))); + } + } + + public void RunBlindscan() + { + if (busy) + throw new StreamReaderException("TunerMetadata is busy"); + + int startFreq = (int)GetParameter(StreamReaderParameter.ScanStartFreq); + int endFreq = (int)GetParameter(StreamReaderParameter.ScanEndFreq); + + BlindScanResults result = new BlindScanResults(); + result.Standard = stdType; + switch (stdType) + { + case STD_TYPE.STD_DVBC: + if (caps.HasFlag(Caps.SR_AIRSCAN)) + { + + uint step = GetParameter(StreamReaderParameter.ScanStep); + uint bandwidth = GetParameter(StreamReaderParameter.ScanBandwidth); + STD_TYPE stdType = (STD_TYPE)GetParameter(StreamReaderParameter.ScanStdType); + SearchResult2 sr2 = default; + int tpNum = default; + IntPtr pSearchResult = Marshal.AllocHGlobal(ushort.MaxValue); + busy = true; + bool airScan = streamReader.AirScan(startFreq, endFreq, step, bandwidth, (int)stdType, pSearchResult, ref tpNum, (ref SearchResult2 searchResult) => SearchResult2Callback(result, searchResult)); + if (airScan) + { + Scrape(result); + } + busy = false; + Marshal.FreeHGlobal(pSearchResult); + if (!airScan) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.GetTunerType))); + } + } + else + { + throw new NotImplementedException("Don't know how to blindscan with this tuner."); + } + + break; + case STD_TYPE.STD_DVBS: + if (caps.HasFlag(Caps.SR_BLSCAN2)) + { + SearchResult sr1 = default; + int tpNum = default; + + int lof1 = (int)GetParameter(StreamReaderParameter.ScanLof1); + int lof2 = (int)GetParameter(StreamReaderParameter.ScanLof2); + int lofSw = (int)GetParameter(StreamReaderParameter.ScanLofSwitch); + int diseqc = (int)GetParameter(StreamReaderParameter.ScanDiseqcType); + + IntPtr allocHGlobal = Marshal.AllocHGlobal(ushort.MaxValue); + + if (lofSw != 0) + { + busy = true; + + result.Transponders.Clear(); + SendDiseqcCommand(diseqc, false, true); + bool hLower = streamReader.BLScan2(startFreq, lofSw, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult searchResult) => SearchResult2Callback(result, searchResult)); + if (hLower) + { + Scrape(result); + } + + result.Transponders.Clear(); + SendDiseqcCommand(diseqc, true, true); + bool hUpper = streamReader.BLScan2(lofSw, endFreq, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult searchResult) => SearchResult2Callback(result, searchResult)); + if (hUpper) + { + Scrape(result); + } + + result.Transponders.Clear(); + SendDiseqcCommand(diseqc, false, false); + bool vLower = streamReader.BLScan2(startFreq, lofSw, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult searchResult) => SearchResult2Callback(result, searchResult)); + if (vLower) + { + Scrape(result); + } + + result.Transponders.Clear(); + SendDiseqcCommand(diseqc, true, false); + bool vUpper = streamReader.BLScan2(lofSw, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult searchResult) => SearchResult2Callback(result, searchResult)); + if (vUpper) + { + Scrape(result); + } + + busy = false; + if (!hLower && !hUpper && !vLower && !vUpper) + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.BLScan2))); + } + else + { + throw new NotImplementedException("non switching lnb"); + } + + Marshal.FreeHGlobal(allocHGlobal); + } + else + { + throw new NotImplementedException("Don't know how to blindscan with this tuner."); + } + break; + default: + throw new NotImplementedException(stdType.ToString()); + } + } + + private StreamReaderScraperController srsc2; + private void Scrape(BlindScanResults blindScanResults) + { + if (srsc2 == null) + { + srsc2 = new StreamReaderScraperController(streamReader); + srsc2.RecordingOutputDirectory = new DirectoryInfo("recordings"); + srsc2.EventLogger = new ConsoleEventLogger(); + srsc2.Recording = true; + srsc2.ScraperStroage = new FilesystemScraperStorage(new DirectoryInfo("srtest_results")); + } + + foreach (BlindScanResult blindScanResult in blindScanResults.Transponders) + { + if (Tune(blindScanResult)) + { + srsc2.SetNamingHint(blindScanResult); + srsc2.Run(); + } + else + Console.WriteLine("Lock failed."); + } + } + } + + [Serializable] + class StreamReaderException : SkyscraperIOException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public StreamReaderException() + { + } + + public StreamReaderException(string message) : base(message) + { + } + + public StreamReaderException(string message, Exception inner) : base(message, inner) + { + } + + protected StreamReaderException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } + + class BlindScanResults + { + public BlindScanResults() + { + Transponders = new List(); + } + + public List Transponders { get; private set; } + public STD_TYPE Standard { get; set; } + } + + class BlindScanResult + { + public SearchResult2 SearchResult2 { get; set; } + public SearchResult SearchResult1 { get; set; } + public STD_TYPE System { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/IO/StreamReaderScraperController.cs b/skyscraper8/Skyscraper/IO/StreamReaderScraperController.cs new file mode 100644 index 0000000..1c03a81 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/StreamReaderScraperController.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.Skyscraper.IO +{ + internal class StreamReaderScraperController + { + private readonly IStreamReader streamReader; + + public StreamReaderScraperController(IStreamReader streamReader) + { + this.streamReader = streamReader; + } + + + private const string RECORDING_FILENAME_MASK = "skyscraper_{0}.ts"; + + public ISkyscraperEventLogger EventLogger { get; set; } + + public IScraperStroage ScraperStroage { get; set; } + + public bool Recording { get; set; } + + public DirectoryInfo RecordingOutputDirectory { get; set; } + + private bool PrepareRecording() + { + if (!Recording) + return false; + + if (RecordingOutputDirectory == null) + return false; + + DirectoryInfo rootDirectory = RecordingOutputDirectory.Root; + DriveInfo driveInfo = new DriveInfo(rootDirectory.FullName); + long driveFreeSpace = driveInfo.TotalFreeSpace; + long driveTotalSize = driveInfo.TotalSize; + long tenPercentOfDriveTotal = driveTotalSize / 10; + if (driveFreeSpace > tenPercentOfDriveTotal) + return true; + + FileInfo[] fileInfos = RecordingOutputDirectory.GetFiles(string.Format(RECORDING_FILENAME_MASK, "*")); + Array.Sort(fileInfos, (x, y) => DateTime.Compare(x.CreationTimeUtc, y.CreationTimeUtc)); + long sizeOfAllKnown = fileInfos.Select(x => x.Length).Sum(); + long driveFreeSpaceAfterDeleting = driveFreeSpace + sizeOfAllKnown; + if (driveFreeSpaceAfterDeleting < tenPercentOfDriveTotal) + return false; + + for (int i = 0; i < fileInfos.Length; i++) + { + fileInfos[i].Delete(); + Console.WriteLine("Deleted for rotation: {0}", fileInfos[i].Name); + fileInfos[i] = null; + driveInfo = new DriveInfo(rootDirectory.FullName); + driveFreeSpace = driveInfo.TotalFreeSpace; + if (driveFreeSpace > tenPercentOfDriveTotal) + return true; + } + driveInfo = new DriveInfo(rootDirectory.FullName); + driveFreeSpace = driveInfo.TotalFreeSpace; + if (driveFreeSpace > tenPercentOfDriveTotal) + return true; + else + return false; + } + + private bool HasLock(bool cableTv) + { + if (cableTv) + { + SearchResult2 sr2 = new SearchResult2(); + if (!streamReader.SignalInfo2(ref sr2)) + return false; + return sr2.Lock; + } + else + { + SearchResult sr = new SearchResult(); + if (!streamReader.SignalInfo(ref sr)) + return false; + return sr.Lock; + } + } + + private ulong packetsReceivedInTotal; + private Queue packetsQueue; + private void DvbCallback(IntPtr data, int length) + { + if (length % 188 == 0) + { + byte[] buffer = new byte[length]; + Marshal.Copy(data, buffer, 0, length); + lock (packetsQueue) + { + packetsQueue.Enqueue(buffer); + } + packetsReceivedInTotal++; + } + else + { + Console.WriteLine("odd packet size!"); + } + } + + private bool HasPackets() + { + lock (packetsQueue) + { + return packetsQueue.Count > 0; + } + } + + private DateTime startedAt; + private bool StopConditionMet() + { + if (startedAt == DateTime.MinValue) + startedAt = DateTime.Now; + + if (packetsReceivedInTotal == 0 && (DateTime.Now - startedAt).TotalSeconds > 5) + return true; + + if (!skyscraperContext.EnableTimeout) + { + skyscraperContext.TimeoutSeconds = 10; + skyscraperContext.EnableTimeout = true; + } + + if (packetsReceivedInTotal > 0 && skyscraperContext.IsAbortConditionMet()) + return true; + + return false; + } + + private void DrainPackets() + { + if (packetsQueue.Count == 0) + return; + + DateTime startedAt = DateTime.Now; + byte[] singlePacketBuffer = new byte[188]; + byte[] packetBuffer = null; + while (packetsQueue.Count > 0) + { + packetBuffer = packetsQueue.Dequeue(); + if (recordingBufferedStream != null) + recordingBufferedStream.Write(packetBuffer, 0, packetBuffer.Length); + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + + } + } + + } + + private BufferedStream recordingBufferedStream; + private FileStream recordingFileStream; + private SkyscraperContext skyscraperContext; + public void Run() + { + bool cableTvMode = false; + Caps caps = streamReader.GetCaps(); + if (caps.HasFlag(Caps.SR_SIGINFO)) + { + cableTvMode = false; + } + else if (caps.HasFlag(Caps.SR_SIGINFO2)) + { + cableTvMode = true; + } + else + { + throw new NotImplementedException("Couldn't figure out what signal info to use."); + } + + if (!HasLock(cableTvMode)) + { + throw new NotImplementedException("No lock."); + } + + int misCounter = 1; + int misMode = 0; + byte[] s2_is = null; + if (!cableTvMode) + { + SearchResult sr = default; + if (!streamReader.SignalInfo(ref sr)) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.SignalInfo))); + } + + if (!sr.Lock) + return; + + misMode = sr.MIS; + s2_is = sr.IS; + } + + for (int mis = 0; mis < misCounter; mis++) + { + if (misMode != 0) + { + Console.WriteLine("Selecting MIS IS {0}", s2_is[mis]); + bool misSel = streamReader.MISSel(misMode != 0, s2_is[mis], 0xff); + if (!misSel) + { + throw new StreamReaderException(string.Format("{0} failed", nameof(streamReader.MISSel))); + } + } + + //Start Filter + if (PrepareRecording()) + { + string recordingFileName = GenerateRecordingFilename(mis, misMode); + FileInfo recordingFileInfo = new FileInfo(recordingFileName); + recordingFileInfo.Directory.EnsureExists(); + recordingFileStream = recordingFileInfo.OpenWrite(); + recordingBufferedStream = new BufferedStream(recordingFileStream); + } + + IntPtr filterReference = IntPtr.MaxValue; + packetsQueue = new Queue(); + bool filter = streamReader.SetFilter(8192, DvbCallback, 0x02, 2, ref filterReference); + if (!filter) + { + throw new Exception("failed to set filter"); + } + + //Use the Filter + skyscraperContext = new SkyscraperContext(new TsContext(), EventLogger, ScraperStroage); + skyscraperContext.TcpProxyEnabled = true; + byte[] singlePacketBuffer = new byte[188]; + while (!StopConditionMet()) + { + if (!HasPackets()) + { + Thread.Sleep(100); + continue; + } + + byte[] packetBuffer = null; + lock (packetsQueue) + { + packetBuffer = packetsQueue.Dequeue(); + } + + if (recordingBufferedStream != null) + recordingBufferedStream.Write(packetBuffer, 0, packetBuffer.Length); + + + for (int i = 0; i < packetBuffer.Length; i += 188) + { + Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188); + skyscraperContext.IngestSinglePacket(singlePacketBuffer); + } + } + + //Stop Filter + bool stopped = streamReader.DelFilter(filterReference); + if (!stopped) + { + throw new Exception("failed to stop filter"); + } + + DrainPackets(); + if (recordingBufferedStream != null) + { + recordingBufferedStream.Flush(); + recordingBufferedStream.Close(); + recordingBufferedStream.Dispose(); + recordingBufferedStream = null; + recordingFileStream.Close(); + recordingFileStream.Dispose(); + recordingFileStream = null; + } + } + } + + private string GenerateRecordingFilename(int mis, int misMode) + { + DateTime dt = DateTime.Now; + string dateFmt = string.Format("{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}", dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute); + if (namingHint != null) + { + switch (namingHint.System) + { + case STD_TYPE.STD_DVBS: + string resultS = null; + if (misMode == 0) + { + resultS = string.Format("{0}_{1}_{2}_{3}.ts", dateFmt, namingHint.SearchResult1.Freq / 1000, namingHint.SearchResult1.Pol == 0 ? "H" : "V", namingHint.SearchResult1.SR / 1000); + } + else + { + resultS = string.Format("{0}_{1}_{2}_{3}_MIS{4}.ts", dateFmt, namingHint.SearchResult1.Freq / 1000, namingHint.SearchResult1.Pol == 0 ? "H" : "V", namingHint.SearchResult1.SR / 1000, mis); + } + + return Path.Combine(RecordingOutputDirectory.FullName, resultS); + default: + throw new NotImplementedException(namingHint.System.ToString()); + } + } + else + { + return Path.Combine(RecordingOutputDirectory.FullName, string.Format(RECORDING_FILENAME_MASK, dateFmt)); + } + } + + private BlindScanResult namingHint; + public void SetNamingHint(BlindScanResult blindScanResult) + { + namingHint = blindScanResult; + } + } +} diff --git a/skyscraper8/Skyscraper/IO/TunerInterface/ITunerFactory.cs b/skyscraper8/Skyscraper/IO/TunerInterface/ITunerFactory.cs new file mode 100644 index 0000000..3aa2546 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/TunerInterface/ITunerFactory.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Skyscraper.IO.TunerInterface +{ + public interface ITunerFactory + { + IStreamReader CreateStreamReader(); + } +} diff --git a/skyscraper8/Skyscraper/IO/TunerInterface/NullTunerFactory.cs b/skyscraper8/Skyscraper/IO/TunerInterface/NullTunerFactory.cs new file mode 100644 index 0000000..1be5db5 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/TunerInterface/NullTunerFactory.cs @@ -0,0 +1,307 @@ +using System; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.IO.TunerInterface +{ + [SkyscraperPlugin] + [TunerFactoryId(0,"No tuner")] + public class NullTunerFactory : ITunerFactory + { + private static NullStreamReader nsr; + public IStreamReader CreateStreamReader() + { + if (nsr == null) + nsr = new NullStreamReader(); + return nsr; + } + } + + [ClassVersion(1,0,0,0)] + internal class NullStreamReader : IStreamReader + { + public bool CheckForDVB() + { + return false; + } + + public bool CheckForDVBEx(DvbEnumCallback func) + { + return false; + } + + public bool CheckForDVBExEx(DvbEnumCallbackEx func) + { + return false; + } + + public bool StartDVB() + { + throw new NotImplementedException(); + } + + public bool StartDvbEx(int index) + { + throw new NotImplementedException(); + } + + public bool StopDVB() + { + throw new NotImplementedException(); + } + + public bool GetTunerType(ref STD_TYPE type) + { + throw new NotImplementedException(); + } + + public bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data) + { + throw new NotImplementedException(); + } + + public bool SendDiseqCmd(byte[] buffer, int length) + { + throw new NotImplementedException(); + } + + public bool SendDiseqCmdEx(IntPtr pCmd, int length, IntPtr reply, int replyLength) + { + throw new NotImplementedException(); + } + + public bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw) + { + throw new NotImplementedException(); + } + + public bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod) + { + throw new NotImplementedException(); + } + + public bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod, + int inv, int pilot, ROLLOFF_TYPE rolloff) + { + throw new NotImplementedException(); + } + + public bool SetFilter(int pid, StdcallDvbCallback func, int callbackType, int size, ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool SetFilterEx(int pid, StdcallDvbCallbackEx lpFunc, int callbackType, int size, ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool SetBitFilter(int pid, IntPtr filterData, IntPtr filterMask, byte filterLength, StdcallDvbCallback lpFunc, + ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool SetRemoteControl(int irtype, short devAddr, StdcallDvbCallback func, ref IntPtr lpFilterNum) + { + throw new NotImplementedException(); + } + + public bool DelFilter(IntPtr filterNum) + { + throw new NotImplementedException(); + } + + public bool GetSignal(ref int pStrength, ref int pQuality) + { + throw new NotImplementedException(); + } + + public bool GetSignalStrength(ref float pStrength, ref float pQuality) + { + throw new NotImplementedException(); + } + + public bool GetSignalEx(ref float pSNR, ref float pBER) + { + throw new NotImplementedException(); + } + + public bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, ref float pSNR, ref float pBER) + { + throw new NotImplementedException(); + } + + public bool Statistic(int[] pStat) + { + throw new NotImplementedException(); + } + + public bool GetMAC(byte[] pMac) + { + throw new NotImplementedException(); + } + + public Caps GetCaps() + { + throw new NotImplementedException(); + } + + public bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel) + { + throw new NotImplementedException(); + } + + public bool FFTInit() + { + throw new NotImplementedException(); + } + + public bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, byte nb_acc, IntPtr pTab, + IntPtr pBegin, IntPtr pNum) + { + throw new NotImplementedException(); + } + + public bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, + ref SearchResult pSearchResult) + { + throw new NotImplementedException(); + } + + public bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, STD_TYPE std, + ref SearchResult pSearchResult) + { + throw new NotImplementedException(); + } + + public bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, IntPtr pSearchResult, + ref int pTpNum, BlScanCallback lpFunc) + { + throw new NotImplementedException(); + } + + public bool SignalInfo(ref SearchResult pSearchResult) + { + throw new NotImplementedException(); + } + + public bool IQScan(uint input, sbyte[] pIQ, uint num) + { + throw new NotImplementedException(); + } + + public bool IQScan2(uint point, short[] pIQ, uint num) + { + throw new NotImplementedException(); + } + + public bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint) + { + throw new NotImplementedException(); + } + + public bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, ref int pMinDelayNs, ref int pMaxDelayNs) + { + throw new NotImplementedException(); + } + + public bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays) + { + throw new NotImplementedException(); + } + + public bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum) + { + throw new NotImplementedException(); + } + + public bool CarEsNo(ref ushort cnum, ref double pEsNo) + { + throw new NotImplementedException(); + } + + public bool MISSel(bool bEnable, byte misFilter, byte misFilterMask) + { + throw new NotImplementedException(); + } + + public bool PLSSel(byte plsMode, uint code) + { + throw new NotImplementedException(); + } + + public bool PLSGet(byte pMode, ref uint pCode) + { + throw new NotImplementedException(); + } + + public bool ModSel(ref S2Mode ps2Modes, uint num) + { + throw new NotImplementedException(); + } + + public bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum) + { + throw new NotImplementedException(); + } + + public bool SetChannel2(uint freq, uint bandwidth) + { + throw new NotImplementedException(); + } + + public bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream) + { + throw new NotImplementedException(); + } + + public bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, int stream) + { + throw new NotImplementedException(); + } + + public bool SignalInfo2(ref SearchResult2 si2) + { + throw new NotImplementedException(); + } + + public bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel) + { + throw new NotImplementedException(); + } + + public bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, IntPtr pSearchResult, ref int pTpNum, + AirScanCallback lpFunc) + { + throw new NotImplementedException(); + } + + public bool GetEEPROM(byte[] buffer, int offset, int len) + { + throw new NotImplementedException(); + } + + public bool SetEEPROM(byte[] buffer, int offset, int len) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + } + + public Version GetEngineVersion() + { + Type t = this.GetType(); + object[] objArray = t.GetCustomAttributes(typeof(ClassVersionAttribute),false); + ClassVersionAttribute versionAttrib = (ClassVersionAttribute)objArray[0]; + return versionAttrib.ToVersion(); + } + + public string GetEngineName() + { + return this.GetType().Name; + } + } +} diff --git a/skyscraper8/Skyscraper/IO/TunerInterface/RemoteStreamReaderTunerFactory.cs b/skyscraper8/Skyscraper/IO/TunerInterface/RemoteStreamReaderTunerFactory.cs new file mode 100644 index 0000000..6fdb6fa --- /dev/null +++ b/skyscraper8/Skyscraper/IO/TunerInterface/RemoteStreamReaderTunerFactory.cs @@ -0,0 +1,22 @@ +using System.Net; +using skyscraper5.Skyscraper.IO.RemoteStreamReader; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.IO.TunerInterface +{ + [SkyscraperPlugin] + [TunerFactoryId(2,"skyscraper5 Remote Stream Reader")] + internal class RemoteStreamReaderTunerFactory : ITunerFactory + { + public string Hostname { get; set; } + + public int Port { get; set; } + + public IStreamReader CreateStreamReader() + { + IPAddress[] hostAddresses = Dns.GetHostAddresses(Hostname); + IPEndPoint remoteIpEndPoint = new IPEndPoint(hostAddresses[0], Port); + return new RemoteStreamReaderClient(remoteIpEndPoint); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/TunerInterface/TunerFactoryConnectionManager.cs b/skyscraper8/Skyscraper/IO/TunerInterface/TunerFactoryConnectionManager.cs new file mode 100644 index 0000000..82ef1eb --- /dev/null +++ b/skyscraper8/Skyscraper/IO/TunerInterface/TunerFactoryConnectionManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Runtime.Serialization; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.IO.TunerInterface +{ + public sealed class TunerFactoryConnectionManager + { + private TunerFactoryConnectionManager() + { + factories = PluginManager.GetInstance().GetTunerFactories(); + if (factories.Count == 0) + { + throw new NullReferenceException("No tuner factories loaded!"); + } + } + + private static TunerFactoryConnectionManager _instance; + + public static TunerFactoryConnectionManager GetInstance() + { + if (_instance == null) + { + _instance = new TunerFactoryConnectionManager(); + } + return _instance; + } + + private ReadOnlyDictionary factories; + + public ReadOnlyCollection> GetKnownFactories() + { + ReadOnlyCollection> readOnlyCollection = factories.ToList().AsReadOnly(); + return readOnlyCollection; + } + + public static void ConfigureFactoryFromIni(KeyValuePair bootingStorage, Ini ini) + { + string categoryName = String.Format("tunerFactory{0}", bootingStorage.Key.Id); + PluginManager.GetInstance().AutoconfigureObject(categoryName, bootingStorage.Value); + } + + public ITunerFactory AutoGetConfiguredTunerFactory() + { + Ini ini = PluginManager.GetInstance().Ini; + int neededFactory = ini.ReadValue("startup", "tunerFactory", -2); + if (neededFactory == -2) + { + throw new NoTunerFactoryConfiguredException(); + } + + ReadOnlyCollection> readOnlyCollection = GetKnownFactories(); + foreach (var keyValuePair in readOnlyCollection) + { + if (keyValuePair.Key.Id == neededFactory) + { + ConfigureFactoryFromIni(keyValuePair, ini); + return keyValuePair.Value; + } + } + + throw new TunerFactoryNotFoundException(); + } + } + +} diff --git a/skyscraper8/Skyscraper/IO/TunerInterface/TunerFactoryId.cs b/skyscraper8/Skyscraper/IO/TunerInterface/TunerFactoryId.cs new file mode 100644 index 0000000..6ee0525 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/TunerInterface/TunerFactoryId.cs @@ -0,0 +1,20 @@ +using System; + +namespace skyscraper5.Skyscraper.IO.TunerInterface +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class TunerFactoryIdAttribute : Attribute + { + public int Id { get; } + public string DisplayName { get; } + + public string TemporaryUUID { get; } + + public TunerFactoryIdAttribute(int id, string displayName) + { + Id = id; + DisplayName = displayName; + TemporaryUUID = Guid.NewGuid().ToString(); + } + } +} diff --git a/skyscraper8/Skyscraper/IO/TunerInterface/TunerInterfaceExceptions.cs b/skyscraper8/Skyscraper/IO/TunerInterface/TunerInterfaceExceptions.cs new file mode 100644 index 0000000..d1044e5 --- /dev/null +++ b/skyscraper8/Skyscraper/IO/TunerInterface/TunerInterfaceExceptions.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.Serialization; + +namespace skyscraper5.Skyscraper.IO.TunerInterface +{ + [Serializable] + public class TunerInterfaceException : SkyscraperIOException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public TunerInterfaceException() + { + } + + public TunerInterfaceException(string message) : base(message) + { + } + + public TunerInterfaceException(string message, Exception inner) : base(message, inner) + { + } + + protected TunerInterfaceException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } + + + [Serializable] + public class NoTunerFactoryConfiguredException : TunerInterfaceException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public NoTunerFactoryConfiguredException() + { + } + + public NoTunerFactoryConfiguredException(string message) : base(message) + { + } + + public NoTunerFactoryConfiguredException(string message, Exception inner) : base(message, inner) + { + } + + protected NoTunerFactoryConfiguredException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } + + [Serializable] + public class TunerFactoryNotFoundException : TunerInterfaceException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public TunerFactoryNotFoundException() + { + } + + public TunerFactoryNotFoundException(string message) : base(message) + { + } + + public TunerFactoryNotFoundException(string message, Exception inner) : base(message, inner) + { + } + + protected TunerFactoryNotFoundException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Ini.cs b/skyscraper8/Skyscraper/Ini.cs new file mode 100644 index 0000000..89d2eae --- /dev/null +++ b/skyscraper8/Skyscraper/Ini.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace skyscraper5.Skyscraper +{ + public class Ini : Dictionary + { + public Ini() + { + + } + + public Ini (string filename) + { + string[] iniData = File.ReadAllLines (filename); + IniSection currentSection = null; + foreach (string line in iniData) { + string s = line; + s = s.Trim (); + + if (s.StartsWith (";")) + continue; + + if (s.Contains (";")) { + s = s.Split (';') [0]; + } + + if (s.StartsWith ("[") && s.EndsWith ("]")) { + string sectionName = s.Split ('[') [1].Split (']') [0]; + currentSection = new IniSection (); + this [sectionName] = currentSection; + continue; + } + + if (s.Contains ("=")) + { + string[] args = s.Split ('='); + args [0].Trim (); + args [1].Trim (); + currentSection [args [0]] = args [1]; + } + } + + } + + public byte[] Export() + { + MemoryStream ms = new MemoryStream(); + StreamWriter sw = new StreamWriter(ms, Encoding.UTF8); + foreach (KeyValuePair section in this) + { + sw.WriteLine("[{0}]", section.Key); + foreach (KeyValuePair entry in section.Value) + { + sw.WriteLine("{0}={1}", entry.Key, entry.Value); + } + + sw.WriteLine(); + } + sw.Flush(); + return ms.ToArray(); + } + + public void Export(FileInfo fi) + { + byte[] export = Export(); + FileStream fs = new FileStream(fi.FullName, FileMode.Create); + fs.Write(export, 0, export.Length); + fs.Flush(true); + fs.Close(); + } + + public bool ReadValue(string category, string key, bool defaultVaule) + { + if (!ContainsKey(category)) + return defaultVaule; + + IniSection section = this[category]; + if (!section.ContainsKey(key)) + return defaultVaule; + + string s = section[key]; + if (s.Equals("0")) + return false; + if (s.Equals("1")) + return true; + return Boolean.Parse(s); + } + + public int ReadValue(string category, string key, int defaultVaule) + { + if (!ContainsKey(category)) + return defaultVaule; + + IniSection section = this[category]; + if (!section.ContainsKey(key)) + return defaultVaule; + + string s = section[key]; + return Int32.Parse(s); + } + + public string ReadValue(string category, string key, string defaultVaule) + { + if (!ContainsKey(category)) + return defaultVaule; + + IniSection section = this[category]; + if (!section.ContainsKey(key)) + return defaultVaule; + + string s = section[key]; + return s; + } + + public void WriteValue(string category, string key, int value) + { + if (!ContainsKey(category)) + Add(category, new IniSection()); + + IniSection section = this[category]; + section[key] = value.ToString(); + } + + public void WriteValue(string category, string key, string value) + { + if (!ContainsKey(category)) + Add(category, new IniSection()); + + IniSection section = this[category]; + section[key] = value; + } + + public void WriteValue(string category, string key, bool value) + { + WriteValue(category, key, value ? 1 : 0); + } + + public object ReadValue(string category, string key, float defaultVaule) + { + if (!ContainsKey(category)) + return defaultVaule; + + IniSection section = this[category]; + if (!section.ContainsKey(key)) + return defaultVaule; + + string s = section[key]; + return Single.Parse(s,CultureInfo.InvariantCulture); + } + + public void WriteValue(string category, string key, long value) + { + if (!ContainsKey(category)) + Add(category, new IniSection()); + + IniSection section = this[category]; + section[key] = value.ToString(); + } + + public void WriteValue(string category, string key, float value) + { + if (!ContainsKey(category)) + Add(category, new IniSection()); + + IniSection section = this[category]; + section[key] = value.ToString(CultureInfo.InvariantCulture); + } + } + + public class IniSection : Dictionary + { + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/IpPacketFinder.cs b/skyscraper8/Skyscraper/IpPacketFinder.cs new file mode 100644 index 0000000..915cba1 --- /dev/null +++ b/skyscraper8/Skyscraper/IpPacketFinder.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Ietf.Rfc971; + +namespace skyscraper5.Skyscraper +{ + internal class IpPacketFinder + { + public static byte[] LookForIpPacket(byte[] gsPacket, int searchDepth = 16) + { + byte[] ipBuffer = new byte[20]; + + for (int i = 1; i < searchDepth; i++) + { + int v4HeaderEnd = i + 20; + if (v4HeaderEnd > gsPacket.Length) + return null; + + if (gsPacket[i] == 0x45) + { + Array.Copy(gsPacket, i, ipBuffer, 0, ipBuffer.Length); + InternetHeader ipv4 = new InternetHeader(ipBuffer); + if (!ipv4.ChecksumValid) + continue; + int payloadStart = i + ipBuffer.Length; + int payloadEnd = i + ipv4.TotalLength; + if (payloadEnd > gsPacket.Length) + return null; + int packetLength = payloadEnd - i; + + byte[] finalPacket = new byte[packetLength]; + Array.Copy(gsPacket, i, finalPacket, 0, packetLength); + return finalPacket; + } + } + + return null; + } + } +} diff --git a/skyscraper8/Skyscraper/Net/CidrSubnet.cs b/skyscraper8/Skyscraper/Net/CidrSubnet.cs new file mode 100644 index 0000000..ae6a65e --- /dev/null +++ b/skyscraper8/Skyscraper/Net/CidrSubnet.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Net +{ + public class CidrSubnet + { + public IPAddress IpAddress { get; } + public byte Length { get; } + + public CidrSubnet(IPAddress ipAddress, byte length) + { + IpAddress = ipAddress; + Length = length; + } + + public override string ToString() + { + return String.Format("{0}/{1}", IpAddress.ToString(), Length); + } + } +} diff --git a/skyscraper8/Skyscraper/Net/IpTrafficInfo.cs b/skyscraper8/Skyscraper/Net/IpTrafficInfo.cs new file mode 100644 index 0000000..9a47231 --- /dev/null +++ b/skyscraper8/Skyscraper/Net/IpTrafficInfo.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.src.Skyscraper.Scraper; + +namespace skyscraper5.Skyscraper.Net +{ + public class IpTrafficInfo + { + internal static IpTrafficInfo FromInternetHeader(InternetHeader ih) + { + IpTrafficInfo child = new IpTrafficInfo(); + child.Source = ih.SourceAddress; + child.Target = ih.DestinationAddress; + child.Protocol = ih.Protocol; + return child; + } + + public IPAddress Source { get; set; } + public IPAddress Target { get; set; } + + public byte Protocol { get; set; } + public string SourceName { get; internal set; } + public string TargetName { get; internal set; } + + public bool Equals(IpTrafficInfo other) + { + return Equals(Source, other.Source) && Equals(Target, other.Target) && Protocol == other.Protocol; + } + + public override bool Equals(object obj) + { + return obj is IpTrafficInfo other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Source, Target, Protocol); + } + + public static string GetProtocolName(byte Protocol) + { + switch (Protocol) + { + case 0: return "HOPOPT"; + case 1: return "ICMP"; + case 2: return "IGMP"; + case 4: return "IP-in-IP!"; + case 6: return "TCP"; + case 17: return "UDP"; + case 27: return "RDP"; /** check **/ + case 41: return "IPv6!"; + case 47: return "GRE!"; + case 50: return "ESP"; + case 83: return "VINES"; + case 88: return "EIGRP"; + case 89: return "OSPF"; + case 103: return "PIM"; + case 112: return "VRRP"; + case 115: return "L2TP"; + case 129: return "IPLT"; + case 132: return "SCTP"; + case 139: return "HIP"; + case 253: return "RFC3692"; + case 254: return "RFC3692"; + default: + if (Protocol >= 145) + return String.Format("Unassigned Protocol {0}", Protocol); + return String.Format("Unknown Protocol {0}", Protocol); + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + if (!string.IsNullOrEmpty(SourceName)) + sb.Append(SourceName); + else + sb.Append(Source.ToString()); + + sb.Append(" -> "); + + if (!string.IsNullOrEmpty(TargetName)) + sb.Append(TargetName); + else + sb.Append(Target.ToString()); + + sb.Append(" ("); + sb.Append(GetProtocolName(Protocol)); + sb.Append(")"); + return sb.ToString(); + } + + } +} diff --git a/skyscraper8/Skyscraper/Net/Pcap/PcapWriter.cs b/skyscraper8/Skyscraper/Net/Pcap/PcapWriter.cs new file mode 100644 index 0000000..59502b7 --- /dev/null +++ b/skyscraper8/Skyscraper/Net/Pcap/PcapWriter.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; + +namespace skyscraper5.Skyscraper.Net.Pcap +{ + internal class PcapWriter : IDisposable + { + public DateTime StartTime { get; private set; } + public TcpdumpNetworkType NetworkType { get; } + private readonly Stream outputStream; + private readonly BinaryWriter binaryWriter; + private bool disposed; + + public PcapWriter(Stream outputStream, TcpdumpNetworkType networkType) + { + NetworkType = networkType; + this.outputStream = outputStream; + binaryWriter = new BinaryWriter(outputStream); + binaryWriter.Write(0xa1b2c3d4); + binaryWriter.Write((ushort)2); + binaryWriter.Write((ushort)4); + binaryWriter.Write(0); + binaryWriter.Write((uint)0); + binaryWriter.Write((uint)65536); + binaryWriter.Write((uint)networkType); + StartTime = DateTime.Now; + } + + public void Dispose() + { + if (disposed) + return; + + disposed = true; + binaryWriter.Flush(); + outputStream.Flush(); + outputStream.Close(); + outputStream.Dispose(); + } + + public void WritePacket(byte[] buffer) + { + if (disposed) + return; + + TimeSpan span = DateTime.Now - StartTime; + binaryWriter.Write((uint)span.TotalSeconds); + binaryWriter.Write((uint)(span.Milliseconds * 1000)); + binaryWriter.Write((uint)buffer.Length); + binaryWriter.Write((uint)buffer.Length); + binaryWriter.Write(buffer); + } + } +} diff --git a/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs b/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs new file mode 100644 index 0000000..4913c1c --- /dev/null +++ b/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Net.Pcap +{ + internal enum TcpdumpNetworkType : uint + { + Ethernet = 1, + TokenRing = 6, + PPPoE = 51, + RawIp = 101, + WLAN = 105, + } +} diff --git a/skyscraper8/Skyscraper/Net/Sockets/NetworkStreamExtensions.cs b/skyscraper8/Skyscraper/Net/Sockets/NetworkStreamExtensions.cs new file mode 100644 index 0000000..706e5a1 --- /dev/null +++ b/skyscraper8/Skyscraper/Net/Sockets/NetworkStreamExtensions.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Skyscraper.Net.Sockets +{ + public static class NetworkStreamExtensions + { + public static void WriteInt32BE(this NetworkStream stream, int value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + stream.Write(buffer, 0, 4); + } + + public static int ReadInt32BE(this NetworkStream stream) + { + byte[] buffer = new byte[4]; + if (stream.Read(buffer, 0, 4) != 4) + throw new EndOfStreamException(); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + return BitConverter.ToInt32(buffer, 0); + } + + public static void WriteUInt32BE(this NetworkStream stream, uint value) + { + byte[] bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + stream.Write(bytes, 0, 4); + } + + public static void WriteUTF8(this NetworkStream stream, string value) + { + stream.WriteUInt32BE(4); + if (value == null) + { + stream.WriteBoolean(true); + return; + } + else + { + stream.WriteBoolean(false); + } + byte[] bytes = Encoding.UTF8.GetBytes(value); + stream.WriteUInt32BE((uint)bytes.Length); + stream.Write(bytes, 0, bytes.Length); + } + + public static string ReadUTF8(this NetworkStream stream) + { + uint magic = stream.ReadUInt32BE(); + if (magic != 4) + throw new InvalidDataException("Expected an utf-8, but got something weird."); + + bool isNull = stream.ReadBoolean(); + if (isNull) + return null; + + uint length = stream.ReadUInt32BE(); + byte[] result = stream.ReadBytes(length); + return Encoding.UTF8.GetString(result); + } + + public static void WriteGuid(this NetworkStream stream, Guid guid) + { + stream.WriteUInt32BE(5); + stream.WriteByteArray(guid.ToByteArray()); + } + + public static byte[] ReadByteArray(this NetworkStream stream) + { + uint magic = stream.ReadUInt32BE(); + if (magic != 6) + throw new IOException("excepted a byte[], but got junk"); + uint len = stream.ReadUInt32BE(); + byte[] buffer = new byte[len]; + if (stream.Read(buffer, 0, buffer.Length) != buffer.Length) + throw new EndOfStreamException(); + return buffer; + } + + public static void WriteByteArray(this NetworkStream stream, byte[] buffer) + { + stream.WriteUInt32BE(6); + stream.WriteUInt32BE((uint)buffer.Length); + stream.Write(buffer, 0, buffer.Length); + } + + public static void WriteBoolean(this NetworkStream stream, bool value) + { + byte[] buffer = new byte[1] { 0 }; + if (value) + buffer[0] = 1; + stream.Write(buffer, 0, 1); + } + + public static bool ReadBoolean(this NetworkStream stream) + { + byte[] buffer = new byte[1]; + if (stream.Read(buffer, 0, 1) != 1) + throw new EndOfStreamException("failed to read boolean"); + switch (buffer[0]) + { + case 0: + return false; + case 1: + return true; + default: + throw new EncoderFallbackException("expected to read 1 or 0, but got junk"); + } + } + + public static void WriteInt64BE(this NetworkStream stream, long value) + { + byte[] bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + stream.Write(bytes, 0, 8); + } + + public static long ReadInt64BE(this NetworkStream stream) + { + byte[] buffer = new byte[8]; + if (stream.Read(buffer, 0, 8) != 8) + throw new EndOfStreamException("failed to read int64"); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + return BitConverter.ToInt64(buffer, 0); + } + + public static void WriteUInt8(this NetworkStream stream, byte uint8) + { + byte[] buffer = new byte[1]; + buffer[0] = uint8; + stream.Write(buffer, 0, 1); + } + + public static byte ReadUInt8(this NetworkStream stream) + { + byte[] buffer = new byte[1]; + if (stream.Read(buffer,0,1) != 1) + throw new EndOfStreamException("failed to read uint8"); + return buffer[0]; + } + + public static void WriteDouble(this NetworkStream stream, double value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + stream.Write(buffer, 0, 8); + } + + public static double ReadDouble(this NetworkStream stream) + { + byte[] buffer = new byte[8]; + if (stream.Read(buffer,0,8) != 8) + throw new EndOfStreamException("failed to read double"); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + return BitConverter.ToDouble(buffer, 0); + } + + public static void WriteFloat(this NetworkStream stream, float value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + stream.Write(buffer, 0, 4); + } + + public static float ReadFloat(this NetworkStream stream) + { + byte[] buffer = new byte[4]; + if (stream.Read(buffer, 0, 4) != 4) + throw new EndOfStreamException("failed to read float"); + if (BitConverter.IsLittleEndian) + Array.Reverse(buffer); + return BitConverter.ToSingle(buffer, 0); + } + + public static void WriteIntPtr(this NetworkStream stream, IntPtr ptr) + { + stream.WriteUInt32BE(7); + stream.WriteInt32BE(ptr.ToInt32()); + stream.WriteInt64BE(ptr.ToInt64()); + } + + public static IntPtr ReadIntPtr(this NetworkStream stream) + { + uint magic = stream.ReadUInt32BE(); + if (magic != 7) + throw new InvalidDataException("expected intPtr but got junk"); + + int ptr32 = stream.ReadInt32BE(); + long ptr64 = stream.ReadInt64BE(); + if (Environment.Is64BitProcess) + return new IntPtr(ptr64); + else + return new IntPtr(ptr32); + } + + public static void WriteIPEndPoint(this NetworkStream stream, IPEndPoint ptr) + { + stream.WriteUInt32BE(8); + stream.WriteByteArray(ptr.Address.GetAddressBytes()); + stream.WriteInt32BE(ptr.Port); + } + + public static IPEndPoint ReadIPEndPoint(this NetworkStream stream) + { + uint magic = stream.ReadUInt32BE(); + if (magic != 8) + throw new InvalidDataException("expected IPEndPoint but got junk"); + + byte[] adrBuffer = stream.ReadByteArray(); + int port = stream.ReadInt32BE(); + + IPAddress addr = new IPAddress(adrBuffer); + IPEndPoint result = new IPEndPoint(addr, port); + return result; + } + + public static void WriteVersion(this NetworkStream stream, Version version) + { + stream.WriteUInt32BE(9); + stream.WriteInt32BE(version.Major); + stream.WriteInt32BE(version.Minor); + stream.WriteInt32BE(version.Build); + stream.WriteInt32BE(version.Revision); + } + + public static Version ReadVersion(this NetworkStream stream) + { + uint magic = stream.ReadUInt32BE(); + if (magic != 9) + throw new InvalidDataException("expected Version but got junk"); + + int major = stream.ReadInt32BE(); + int minor = stream.ReadInt32BE(); + int build = stream.ReadInt32BE(); + int revision = stream.ReadInt32BE(); + + return new Version(major,minor,build,revision); + } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/ByteArrayComparer.cs b/skyscraper8/Skyscraper/Plugins/ByteArrayComparer.cs new file mode 100644 index 0000000..4e71a4d --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/ByteArrayComparer.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Plugins +{ + internal class ByteArrayComparer : IComparer + { + public int Compare(byte[] x, byte[] y) + { + int lResult = x.Length.CompareTo(y.Length); + if (lResult != 0) + return lResult; + + for (int i = 0; i < x.Length; i++) + { + int cResult = x[i].CompareTo(y[i]); + if (cResult != 0) + return cResult; + } + + return 0; + } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/DescriptorPluginBatHandler.cs b/skyscraper8/Skyscraper/Plugins/DescriptorPluginBatHandler.cs new file mode 100644 index 0000000..5d19e18 --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/DescriptorPluginBatHandler.cs @@ -0,0 +1,10 @@ +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Plugins +{ + public interface DescriptorPluginBatHandler + { + void HandleBat(BatTransportStream outputTs, TsDescriptor unpackedDescriptor); + } +} diff --git a/skyscraper8/Skyscraper/Plugins/DescriptorPluginBatHandlerAttribute.cs b/skyscraper8/Skyscraper/Plugins/DescriptorPluginBatHandlerAttribute.cs new file mode 100644 index 0000000..b6154ed --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/DescriptorPluginBatHandlerAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace skyscraper5.Skyscraper.Plugins +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class DescriptorPluginBatHandlerAttribute : Attribute + { + public DescriptorPluginBatHandlerAttribute(Type handler) + { + object boxed = Activator.CreateInstance(handler); + DescriptorPluginBatHandler unboxed = (DescriptorPluginBatHandler)boxed; + if (unboxed == null) + { + throw new NotImplementedException(String.Format("{0} is not an {1}", handler.Name, nameof(DescriptorPluginBatHandler))); + } + + Handler = unboxed; + } + + public DescriptorPluginBatHandler Handler { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/DescriptorPluginEitHandler.cs b/skyscraper8/Skyscraper/Plugins/DescriptorPluginEitHandler.cs new file mode 100644 index 0000000..e4b1e86 --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/DescriptorPluginEitHandler.cs @@ -0,0 +1,10 @@ +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Plugins +{ + public interface DescriptorPluginEitHandler + { + void HandleEit(EitEvent outputEvent, TsDescriptor unpackedDescriptor); + } +} diff --git a/skyscraper8/Skyscraper/Plugins/DescriptorPluginEitHandlerAttribute.cs b/skyscraper8/Skyscraper/Plugins/DescriptorPluginEitHandlerAttribute.cs new file mode 100644 index 0000000..ee18338 --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/DescriptorPluginEitHandlerAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace skyscraper5.Skyscraper.Plugins +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class DescriptorPluginNitHandlerAttribute : Attribute + { + public DescriptorPluginNitHandlerAttribute(Type handler) + { + object boxed = Activator.CreateInstance(handler); + DescriptorPluginNitHandler unboxed = (DescriptorPluginNitHandler)boxed; + if (unboxed == null) + { + throw new NotImplementedException(String.Format("{0} is not an {1}", handler.Name, nameof(DescriptorPluginEitHandler))); + } + + Handler = unboxed; + } + + public DescriptorPluginNitHandler Handler { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/DescriptorPluginNitHandler.cs b/skyscraper8/Skyscraper/Plugins/DescriptorPluginNitHandler.cs new file mode 100644 index 0000000..1ebe8fb --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/DescriptorPluginNitHandler.cs @@ -0,0 +1,10 @@ +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Plugins +{ + public interface DescriptorPluginNitHandler + { + void HandleNit(NitTransportStream output, TsDescriptor unpacked); + } +} diff --git a/skyscraper8/Skyscraper/Plugins/DescriptorPluginNitHandlerAttribute.cs b/skyscraper8/Skyscraper/Plugins/DescriptorPluginNitHandlerAttribute.cs new file mode 100644 index 0000000..c6da41b --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/DescriptorPluginNitHandlerAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace skyscraper5.Skyscraper.Plugins +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class DescriptorPluginEitHandlerAttribute : Attribute + { + public DescriptorPluginEitHandlerAttribute(Type handler) + { + object boxed = Activator.CreateInstance(handler); + DescriptorPluginEitHandler unboxed = (DescriptorPluginEitHandler)boxed; + if (unboxed == null) + { + throw new NotImplementedException(String.Format("{0} is not an {1}", handler.Name, nameof(DescriptorPluginEitHandler))); + } + + Handler = unboxed; + } + + public DescriptorPluginEitHandler Handler { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/IPluginLoadHook.cs b/skyscraper8/Skyscraper/Plugins/IPluginLoadHook.cs new file mode 100644 index 0000000..ed58e3a --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/IPluginLoadHook.cs @@ -0,0 +1,7 @@ +namespace skyscraper5.Skyscraper.Plugins +{ + public interface IPluginLoadHook + { + void OnLoad(); + } +} diff --git a/skyscraper8/Skyscraper/Plugins/ISkyscraperMpePlugin.cs b/skyscraper8/Skyscraper/Plugins/ISkyscraperMpePlugin.cs new file mode 100644 index 0000000..084a5a9 --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/ISkyscraperMpePlugin.cs @@ -0,0 +1,14 @@ +using System; +using skyscraper5.Ietf.Rfc971; + +namespace skyscraper5.Skyscraper.Plugins +{ + public interface ISkyscraperMpePlugin + { + void ConnectToStorage(object[] connector, PluginLogMessage logger); + void SetContext(DateTime? currentTime); + bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet); + void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet); + bool StopProcessingAfterThis(); + } +} diff --git a/skyscraper8/Skyscraper/Plugins/PluginLogMessage.cs b/skyscraper8/Skyscraper/Plugins/PluginLogMessage.cs new file mode 100644 index 0000000..526770e --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/PluginLogMessage.cs @@ -0,0 +1,4 @@ +namespace skyscraper5.Skyscraper.Plugins +{ + public delegate void PluginLogMessage(string message); +} diff --git a/skyscraper8/Skyscraper/Plugins/PluginManager.cs b/skyscraper8/Skyscraper/Plugins/PluginManager.cs new file mode 100644 index 0000000..28a9a8c --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/PluginManager.cs @@ -0,0 +1,602 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using skyscraper5.Docsis; +using skyscraper5.DsmCc; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.IO.TunerInterface; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.T2MI; + +namespace skyscraper5.Skyscraper.Plugins +{ + internal class PluginManager + { + private PluginManager() + { + _mpePlugins = new List(); + _gpsReceiverFactories = new Dictionary(); + descriptorBannedTables = new Dictionary(); + descriptorMap = new Dictionary(); + _streamTypeAutodetectionContestants = new List(); + _t2MiPacketConstructorInfos = new ConstructorInfo[256]; + scraperStorageFactories = new Dictionary(); + tunerFactories = new Dictionary(); + _docsisMacManagementMessageTypes = new Dictionary(); + _userDefinedDescriptors = new Dictionary(); + _extensionDescriptors = new Tuple[256]; + _dsmCcMessages = new ConstructorInfo[ushort.MaxValue]; + _mpeg2ExtensionDescriptors = new ConstructorInfo[256]; + + ScanAssembly(this.GetType().Assembly); + + FileInfo iniFileInfo = new FileInfo(iniFilename); + if (iniFileInfo.Exists) + { + Ini = new Ini(iniFileInfo.FullName); + if (Ini.ContainsKey("plugins")) + { + IniSection iniSection = Ini["plugins"]; + foreach (KeyValuePair valuePair in iniSection) + { + FileInfo pluginFileInfo = new FileInfo(valuePair.Value); + if (!pluginFileInfo.Exists) + continue; + + if (resolveDirectoryInfos == null) + resolveDirectoryInfos = new HashSet(); + if (IsOnWindows()) + { + string runtimePath = Path.Combine(pluginFileInfo.Directory.FullName, "runtimes", "win", "lib", "netstandard2.0"); + DirectoryInfo runtimeDi = new DirectoryInfo(runtimePath); + if (runtimeDi.Exists) + resolveDirectoryInfos.Add(runtimeDi); + } + resolveDirectoryInfos.Add(pluginFileInfo.Directory); + + Assembly assembly = Assembly.LoadFile(pluginFileInfo.FullName); + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + ScanAssembly(assembly); + } + } + } + else + { + Console.WriteLine(String.Format("{0} was not found. Create it using the UI!", iniFileInfo.FullName)); + } + } + + private HashSet resolveDirectoryInfos; + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + Debug.WriteLine("Trying to resolve: {0}", args.Name); + string[] strings = args.Name.Split(','); + string fname = strings[0] + ".dll"; + + foreach (DirectoryInfo directoryInfo in resolveDirectoryInfos) + { + string path = Path.Combine(directoryInfo.FullName, fname); + if (File.Exists(path)) + { + Assembly assembly = Assembly.LoadFile(path); + if (UsableAssembly(args.Name, assembly.GetName())) + return assembly; + } + } + + return null; + } + + private bool IsOnWindows() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.Win32NT: return true; + case PlatformID.MacOSX: return false; + case PlatformID.Unix: return true; + case PlatformID.Other: return false; + default: + throw new NotImplementedException(Environment.OSVersion.Platform.ToString()); + } + } + + private bool UsableAssembly(string requestedName, AssemblyName loadedName) + { + AssemblyName an = new AssemblyName(requestedName); + if (an.Equals(loadedName)) + return true; + + if (an.Name.Equals(loadedName.Name)) + { + if (an.Version <= loadedName.Version) + { + byte[] ltoken = an.GetPublicKeyToken(); + byte[] rtoken = loadedName.GetPublicKeyToken(); + bool result = ltoken.IsEqual(rtoken); + return result; + } + } + + return false; + } + + + private const string iniFilename = "skyscraper5.ini"; + private static PluginManager _instance; + + public static PluginManager GetInstance() + { + if (_instance == null) + { + _instance = new PluginManager(); + } + return _instance; + } + + private Type dnsParserPluginType = typeof(IDnsParser); + private Type pluginLoadHookType = typeof(IPluginLoadHook); + private Type docsisMacManagementType = typeof(MacManagementMessage); + private Type tunerFactoryType = typeof(ITunerFactory); + private Type scraperStorageFactoryType = typeof(IScraperStorageFactory); + private Type t2MiPacketType = typeof(T2MiPacket); + private Type mpePluginType = typeof(ISkyscraperMpePlugin); + private Type gpsReceiverFactoryType = typeof(IGpsReceiverFactory); + private Type streamTypeAutodetectionContestantType = typeof(Contestant); + private PluginPrioritySorter sorter = new PluginPrioritySorter(); + + private List _dnsParsers; + private List _mpePlugins; + private Dictionary _gpsReceiverFactories; + private Dictionary descriptorMap; + private Dictionary descriptorBannedTables; + private List _streamTypeAutodetectionContestants; + private ConstructorInfo[] _t2MiPacketConstructorInfos; + private Dictionary scraperStorageFactories; + private Dictionary tunerFactories; + private Dictionary _docsisMacManagementMessageTypes; + private Dictionary _userDefinedDescriptors; + private Tuple[] _extensionDescriptors; + private ConstructorInfo[] _dsmCcMessages; + private ConstructorInfo[] _mpeg2ExtensionDescriptors; + + private void ScanAssembly(Assembly assembly) + { + foreach (Type type in assembly.GetTypes()) + { + bool isSkyscraperPlugin = type.GetCustomAttributes(typeof(SkyscraperPluginAttribute)).Any(); + if (!isSkyscraperPlugin) + continue; + + if (type.IsAssignableTo(mpePluginType)) + { + ISkyscraperMpePlugin mpePlugin = (ISkyscraperMpePlugin)Activator.CreateInstance(type); + _mpePlugins.Add(mpePlugin); + continue; + } + else if (type.IsAssignableTo(gpsReceiverFactoryType)) + { + IGpsReceiverFactory gpsReceiverFactory = (IGpsReceiverFactory)Activator.CreateInstance(type); + int gpsReceiverKey = PluginPrioritySorter.GetPriorityFromObject(gpsReceiverFactory); + _gpsReceiverFactories.Add(gpsReceiverKey, gpsReceiverFactory); + continue; + } + else if (type.GetCustomAttribute(typeof(TsDescriptorAttribute)) != null) + { + HandleTsDescriptor(type); + continue; + } + else if (type.IsAssignableTo(streamTypeAutodetectionContestantType)) + { + _streamTypeAutodetectionContestants.Add(type); + continue; + } + else if (type.IsAssignableTo(t2MiPacketType)) + { + HandleT2MiPacketType(type); + continue; + } + else if (type.IsAssignableTo(scraperStorageFactoryType)) + { + HandleScraperStorageFactory(type); + continue; + } + else if (type.IsAssignableTo(tunerFactoryType)) + { + HandleTunerFactory(type); + continue; + } + else if (type.IsAssignableTo(docsisMacManagementType)) + { + HandleDocsisMacManagementType(type); + continue; + } + else if (type.GetCustomAttribute(typeof(UserDefinedDescriptorAttribute)) != null) + { + HandleUserDefinedDescriptor(type); + continue; + } + else if (type.GetCustomAttribute(typeof(ExtensionDescriptorAttribute)) != null) + { + HandleExtensionDescriptor(type); + continue; + } + else if (type.GetCustomAttribute(typeof(DsmCcMessageAttribute)) != null) + { + HandleDsmCcMessage(type); + continue; + } + else if (type.GetCustomAttribute(typeof(Mpeg2ExtensionDescriptorAttribute)) != null) + { + HandleMpeg2ExtensionDescriptor(type); + continue; + } + else if (type.IsAssignableTo(pluginLoadHookType)) + { + IPluginLoadHook loadHook = (IPluginLoadHook)Activator.CreateInstance(type); + loadHook.OnLoad(); + continue; + } + else if (type.IsAssignableTo(dnsParserPluginType)) + { + HandleDnsParser(type); + continue; + } + + throw new NotImplementedException(); + } + + _mpePlugins.Sort(sorter); + } + + #region DNS Plugin + private void HandleDnsParser(Type type) + { + if (_dnsParsers == null) + _dnsParsers = new List(); + + IDnsParser idp = (IDnsParser)Activator.CreateInstance(type); + _dnsParsers.Add(idp); + } + #endregion DNS Plugin + + private void HandleMpeg2ExtensionDescriptor(Type typeCandidate) + { + bool valid = false; + + Type targetAttributeType = typeof(Mpeg2ExtensionDescriptorAttribute); + Type byteArrayType = typeof(byte[]); + + object[] attributes = typeCandidate.GetCustomAttributes(targetAttributeType, false); + if (attributes.Length == 0) + return; + Mpeg2ExtensionDescriptorAttribute attribute = (Mpeg2ExtensionDescriptorAttribute)attributes[0]; + + ConstructorInfo constructorInfo = typeCandidate.GetConstructor(new Type[] { byteArrayType }); + if (constructorInfo == null) + throw new NotImplementedException(String.Format("{0} does not implement the correct constructor.", typeCandidate.Name)); + + _mpeg2ExtensionDescriptors[attribute.Tag] = constructorInfo; + valid = true; + + if (!valid) + { + throw new NotImplementedException(String.Format("{0} does not implement any MPEG-2 Extension Descriptors.", typeCandidate.Name)); + } + } + + #region DSM-CC Messages + + private void HandleDsmCcMessage(Type type) + { + bool valid = false; + Attribute attribute = type.GetCustomAttributes(typeof(DsmCcMessageAttribute)).FirstOrDefault(); + if (attribute != null) + { + DsmCcMessageAttribute dsmCcMessageAttribute = (DsmCcMessageAttribute)attribute; + ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) }); + if (constructorInfo == null) + return; + + _dsmCcMessages[dsmCcMessageAttribute.MessageId] = constructorInfo; + valid = true; + } + + if (!valid) + throw new ArgumentException(String.Format("{0} does not contain DSM-CC Message definitions.", type.FullName)); + } + + #endregion + + #region DVB Extension Descriptors + private void HandleExtensionDescriptor(Type type) + { + bool foundSomeExtensionTypes = false; + + Assembly assembly = this.GetType().Assembly; + Type[] types = assembly.GetTypes(); + + Attribute attribute = type.GetCustomAttributes(typeof(ExtensionDescriptorAttribute)).FirstOrDefault(); + if (attribute != null) + { + ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) }); + if (constructorInfo == null) + return; + + ExtensionDescriptorAttribute unboxedAttribute = attribute as ExtensionDescriptorAttribute; + _extensionDescriptors[unboxedAttribute.DescriptorId] = new Tuple(constructorInfo, unboxedAttribute); + foundSomeExtensionTypes = true; + } + + if (!foundSomeExtensionTypes) + { + throw new AccessViolationException("Wait, this isn't supposed to happen! There are no extension descriptor types in my assembly!"); + } + } + #endregion + + #region User Defined Descriptors + private void HandleUserDefinedDescriptor(Type type) + { + bool valid = false; + Attribute attribute = type.GetCustomAttributes(typeof(UserDefinedDescriptorAttribute)).FirstOrDefault(); + if (attribute != null) + { + UserDefinedDescriptorAttribute userDefinedDescriptorAttribute = (UserDefinedDescriptorAttribute)attribute; + ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) }); + if (constructorInfo == null) + return; + + _userDefinedDescriptors.Add(userDefinedDescriptorAttribute, constructorInfo); + valid = true; + } + + if (!valid) + throw new ArgumentException(String.Format("{0} does not contain User defined descriptors.")); + } + #endregion + + #region DOCSIS MAC Management Type + + private void HandleDocsisMacManagementType(Type type1) + { + foreach (MacManagementMessageTypeAttribute macManagementMessageTypeAttribute in type1.GetCustomAttributes()) + { + _docsisMacManagementMessageTypes.Add(macManagementMessageTypeAttribute, type1); + } + } + #endregion + + #region Tuner Factory + private Type tunerFactoryIdType = typeof(TunerFactoryIdAttribute); + + private void HandleTunerFactory(Type type) + { + object[] customAttributes = type.GetCustomAttributes(tunerFactoryIdType, false); + if (customAttributes.Length == 0) + return; + + TunerFactoryIdAttribute attribute = (TunerFactoryIdAttribute)customAttributes[0]; + + ITunerFactory issf = (ITunerFactory)Activator.CreateInstance(type); + tunerFactories.Add(attribute, issf); + } + #endregion + + #region Storage Factory + private Type scraperStorageFactoryIdType = typeof(ScrapeStorageFactoryIdAttribute); + private void HandleScraperStorageFactory(Type type) + { + object[] customAttributes = type.GetCustomAttributes(scraperStorageFactoryIdType, false); + if (customAttributes.Length == 0) + return; + + ScrapeStorageFactoryIdAttribute attribute = (ScrapeStorageFactoryIdAttribute)customAttributes[0]; + + IScraperStorageFactory issf = (IScraperStorageFactory)Activator.CreateInstance(type); + scraperStorageFactories.Add(attribute, issf); + } + + #endregion + + #region T2-MI Packet Types + + private Type[] t2MiConstructorPattern = new Type[] { typeof(T2MIHeader), typeof(byte[]) }; + private Type t2MiPacketTypeAttribute = typeof(T2MiPacketTypeAttribute); + private void HandleT2MiPacketType(Type type) + { + ConstructorInfo constructorInfo = type.GetConstructor(t2MiConstructorPattern); + if (constructorInfo == null) + return; + + Attribute customAttribute = type.GetCustomAttribute(t2MiPacketTypeAttribute); + if (customAttribute == null) + { + throw new T2MiException(String.Format("{0} is missing the {1}", type.Name, t2MiPacketTypeAttribute.Name)); + } + + T2MiPacketTypeAttribute packetType = (T2MiPacketTypeAttribute)customAttribute; + _t2MiPacketConstructorInfos[packetType.PacketType] = constructorInfo; + } + #endregion + + #region Ts Descriptor + private void HandleTsDescriptor(Type type) + { + Attribute attribute = type.GetCustomAttributes(typeof(TsDescriptorAttribute)).FirstOrDefault(); + if (attribute != null) + { + ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) }); + if (constructorInfo == null) + return; + + TsDescriptorAttribute unboxedAttribute = attribute as TsDescriptorAttribute; + foreach (string compatibleTable in unboxedAttribute.CompatibleTables) + { + ConstructorInfo[] constructorInfos = UpsertTable(compatibleTable); + constructorInfos[unboxedAttribute.DescriptorId] = constructorInfo; + } + + attribute = type.GetCustomAttributes(typeof(BannedTableAttribute)).FirstOrDefault(); + if (attribute != null) + { + BannedTableAttribute bannedTableAttribute = attribute as BannedTableAttribute; + foreach (string table in bannedTableAttribute.Tables) + { + bool[] upsertBannedTable = UpsertBannedTable(table); + upsertBannedTable[unboxedAttribute.DescriptorId] = true; + } + } + } + } + + private ConstructorInfo[] UpsertTable(string table) + { + if (!descriptorMap.ContainsKey(table)) + { + descriptorMap[table] = new ConstructorInfo[256]; + } + return descriptorMap[table]; + } + + private bool[] UpsertBannedTable(string table) + { + if (!descriptorBannedTables.ContainsKey(table)) + { + descriptorBannedTables[table] = new bool[256]; + } + return descriptorBannedTables[table]; + } + #endregion + + + public Ini Ini { get; private set; } + + public IReadOnlyList GetDnsParsers() + { + if (_dnsParsers == null) + return new List(); + return _dnsParsers.AsReadOnly(); + } + + public Tuple[] GetExtensionDescriptors() + { + Tuple[] target = new Tuple[256]; + _extensionDescriptors.CopyTo(target, 0); + return target; + } + + public ReadOnlyDictionary GetDocsisMacManagementMessageTypes() + { + return new ReadOnlyDictionary(_docsisMacManagementMessageTypes); + } + public ReadOnlyDictionary GetTunerFactories() + { + ReadOnlyDictionary result = + new ReadOnlyDictionary(tunerFactories); + return result; + } + public ReadOnlyDictionary GetScraperStorageFactories() + { + ReadOnlyDictionary result = + new ReadOnlyDictionary( + scraperStorageFactories); + return result; + } + + public ConstructorInfo[] GetT2MiPacketTypes() + { + ConstructorInfo[] t2MiPacketConstructorInfos = _t2MiPacketConstructorInfos; + ConstructorInfo[] result = new ConstructorInfo[t2MiPacketConstructorInfos.Length]; + Array.Copy(t2MiPacketConstructorInfos, 0, result, 0, t2MiPacketConstructorInfos.Length); + return result; + } + + public ReadOnlyCollection GetMpePlugins() + { + return _mpePlugins.AsReadOnly(); + } + + public ReadOnlyDictionary GetGpsReceiverFactories() + { + return new ReadOnlyDictionary(_gpsReceiverFactories); + } + + public ReadOnlyDictionary GetDescriptorMappings() + { + return new ReadOnlyDictionary(descriptorMap); + } + + public ReadOnlyDictionary GetDescriptorTableBans() + { + return new ReadOnlyDictionary(descriptorBannedTables); + } + + public ReadOnlyCollection GetStreamTypeAutodetectionContestants() + { + return _streamTypeAutodetectionContestants.AsReadOnly(); + } + + public ReadOnlyDictionary GetUserDefinedDescriptors() + { + return new ReadOnlyDictionary(_userDefinedDescriptors); + } + + public ConstructorInfo[] GetDsmCcMessages() + { + ConstructorInfo[] output = new ConstructorInfo[_dsmCcMessages.Length]; + Array.Copy(_dsmCcMessages, 0, output, 0, _dsmCcMessages.Length); + return output; + } + + public ConstructorInfo[] GetMpeg2ExtensionDescriptors() + { + ConstructorInfo[] output = new ConstructorInfo[_mpeg2ExtensionDescriptors.Length]; + Array.Copy(_mpeg2ExtensionDescriptors, 0, output, 0, _mpeg2ExtensionDescriptors.Length); + return output; + } + + public void AutoconfigureObject(string categoryName, object targetObject) + { + if (Ini == null) + Ini = new Ini(); + Type bootingStorageType = targetObject.GetType(); + PropertyInfo[] propertyInfos = bootingStorageType.GetProperties(); + foreach (PropertyInfo propertyInfo in propertyInfos) + { + Type propertyInfoPropertyType = propertyInfo.PropertyType; + object toSet; + switch (propertyInfoPropertyType.Name) + { + case nameof(String): + toSet = Ini.ReadValue(categoryName, propertyInfo.Name, ""); + break; + case nameof(Int32): + toSet = Ini.ReadValue(categoryName, propertyInfo.Name, 0); + break; + case nameof(Boolean): + toSet = Ini.ReadValue(categoryName, propertyInfo.Name, false); + break; + case nameof(Single): + toSet = Ini.ReadValue(categoryName, propertyInfo.Name, 0.0f); + break; + case nameof(UInt16): + toSet = Ini.ReadValue(categoryName, propertyInfo.Name, 0); + toSet = Convert.ToUInt16((int)toSet); + break; + default: + throw new NotImplementedException(propertyInfoPropertyType.Name); + } + + propertyInfo.SetValue(targetObject, toSet); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/PluginPriorityAttribute.cs b/skyscraper8/Skyscraper/Plugins/PluginPriorityAttribute.cs new file mode 100644 index 0000000..530df0d --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/PluginPriorityAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace skyscraper5.Skyscraper.Plugins +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class PluginPriorityAttribute : Attribute + { + public int Priority { get; } + + public PluginPriorityAttribute(int priority) + { + Priority = priority; + } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/PluginPrioritySorter.cs b/skyscraper8/Skyscraper/Plugins/PluginPrioritySorter.cs new file mode 100644 index 0000000..3a44483 --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/PluginPrioritySorter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Plugins +{ + internal class PluginPrioritySorter : IComparer + { + public int Compare(object x, object y) + { + long xv = GetPriorityFromObject(x); + long yv = GetPriorityFromObject(y); + return xv.CompareTo(yv); + } + + public static int GetPriorityFromObject(object obj) + { + if (obj == null) + return 0; + + Type type = obj.GetType(); + + object[] customAttributes = type.GetCustomAttributes(typeof(PluginPriorityAttribute), false); + foreach (object customAttribute in customAttributes) + { + PluginPriorityAttribute ppa = (PluginPriorityAttribute)customAttribute; + return ppa.Priority; + } + + return 0; + } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/SkyscraperPluginAttribute.cs b/skyscraper8/Skyscraper/Plugins/SkyscraperPluginAttribute.cs new file mode 100644 index 0000000..4c538f1 --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/SkyscraperPluginAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace skyscraper5.Skyscraper.Plugins +{ + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public sealed class SkyscraperPluginAttribute : Attribute + { + public SkyscraperPluginAttribute() + { + + } + } +} diff --git a/skyscraper8/Skyscraper/QualificationTool.cs b/skyscraper8/Skyscraper/QualificationTool.cs new file mode 100644 index 0000000..10019d3 --- /dev/null +++ b/skyscraper8/Skyscraper/QualificationTool.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper +{ + /// + /// Inspired by the Waters Instrument Qualitifcation Tool, this class contains methods to check for possible issues + /// + public static class QualificationTool + { + public static QualificationToolResultEnum QualifyTunerFactory(string ProductName, Version version) + { + switch(ProductName) + { + case "StreamReaderEx for BDA Tuners": + return QualifyStreamReaderVersion(version); + case "NullStreamReader": //We're not gonna spam multiple error messages because the tuner factory is missing + return QualificationToolResultEnum.Good; + default: + Console.WriteLine("Unknown Tuner factory product: {0}", ProductName); + return QualificationToolResultEnum.Unknown; + } + } + + public static QualificationToolResultEnum QualifyStreamReaderVersion(Version version) + { + string versionString = version.ToString(); + switch(versionString) + { + case "1.2.5.194": return QualificationToolResultEnum.Bad; //Won't find most transponders with a 6903x + case "1.2.5.176": return QualificationToolResultEnum.Bad; //Won't find most transponders with a 6903x + case "1.2.4.101": return QualificationToolResultEnum.Good; //Works great, but is reather slow with a 6903x + default: return QualificationToolResultEnum.Unknown; + } + } + } + + public enum QualificationToolResultEnum : int + { + /// + /// Use this if the product works 100% fine. + /// + Good = 1, + /// + /// Use this if the product works, but has minor hiccups which won't distract or annoy the user. + /// + PossibleIssues = 2, + /// + /// Use this if the product either doesn't work, or only works badly, meaning + /// + Bad = 3, + Unknown + } +} diff --git a/skyscraper8/Skyscraper/RandomExtensions.cs b/skyscraper8/Skyscraper/RandomExtensions.cs new file mode 100644 index 0000000..be9e30e --- /dev/null +++ b/skyscraper8/Skyscraper/RandomExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper +{ + internal static class RandomExtensions + { + + public static T NextListItem(this Random rng, List collection, bool discard = false) + { + int ptr = rng.Next(collection.Count); + T result = collection[ptr]; + if (discard) + collection.RemoveAt(ptr); + return result; + } + + public static ushort NextUInt16(this Random rng) + { + return (ushort)rng.Next(ushort.MaxValue); + } + } +} diff --git a/skyscraper8/Skyscraper/RecordingImporter/TsFileCollectionImporter.cs b/skyscraper8/Skyscraper/RecordingImporter/TsFileCollectionImporter.cs new file mode 100644 index 0000000..2d245b0 --- /dev/null +++ b/skyscraper8/Skyscraper/RecordingImporter/TsFileCollectionImporter.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.Skyscraper.RecordingImporter +{ + internal class TsFileCollectionImporter + { + public IScraperStroage ScraperStorage { get; } + public DirectoryInfo RootDirectory { get; } + public ISkyscraperEventLogger EventLogger { get; } + + public TsFileCollectionImporter(IScraperStroage scraperStorage, DirectoryInfo rootDirectory, ISkyscraperEventLogger eventLogger) + { + ScraperStorage = scraperStorage; + RootDirectory = rootDirectory; + EventLogger = eventLogger; + } + + public void Run() + { + Perform(RootDirectory); + } + + private void Perform(DirectoryInfo di) + { + foreach (DirectoryInfo subdirectory in di.GetDirectories()) + { + Perform(subdirectory); + } + + foreach (FileInfo fileInfo in di.GetFiles("*.ts")) + { + Import(fileInfo); + } + } + + private void Import(FileInfo fi) + { + if (ScraperStorage.ImportFileKnown(fi)) + return; + + SkyscraperContext skyscraper = new SkyscraperContext(new TsContext(), EventLogger, ScraperStorage); + skyscraper.InitalizeFilterChain(); + FileStream fileStream = fi.OpenRead(); + + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + skyscraper.IngestFromStream(fileStream); + stopwatch.Stop(); + fileStream.Close(); + + Console.WriteLine("Took {0} seconds. " + stopwatch.Elapsed.TotalSeconds); + Console.WriteLine("File was: {0}", fi.Name); + + ScraperStorage.ImportMarkFileAsKnown(fi, stopwatch.Elapsed, skyscraper.SpecialTsType); + } + } +} diff --git a/skyscraper8/Skyscraper/SatellitePosition.cs b/skyscraper8/Skyscraper/SatellitePosition.cs new file mode 100644 index 0000000..6b19acb --- /dev/null +++ b/skyscraper8/Skyscraper/SatellitePosition.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper +{ + public class SatellitePosition + { + public SatellitePosition() + { + } + + public SatellitePosition(float angle, int cardinalDirection, string name) + { + this.angle = angle; + this.cardinalDirection = cardinalDirection; + this.name = name; + } + + public float angle; + public int cardinalDirection; + public string name; + + public int Checksum => GetChecksum(angle, cardinalDirection); + + public static int GetChecksum(float angle, int cardinal) + { + int result = (int)(angle * 10.0f); + if (cardinal == 1) + result += 3600; + return result; + } + + public override string ToString() + { + return String.Format("{0:0.0}° {1} {2}", angle, cardinalDirection == 0 ? "E" : "W", name); + } + + public static SatellitePosition FromChecksum(int checksum) + { + bool isWest = false; + if (checksum >= 3600) + { + checksum -= 3600; + isWest = true; + } + + float angle = (float)checksum / 10.0f; + int cardinal = isWest ? 1 : 0; + string name = String.Format("{0}°{1}", angle, isWest ? "W" : "E"); + return new SatellitePosition(angle, cardinal, name); + } + + protected bool Equals(SatellitePosition other) + { + return angle.Equals(other.angle) && cardinalDirection == other.cardinalDirection; + } + + 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((SatellitePosition)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(angle, cardinalDirection); + } + + } +} diff --git a/skyscraper8/Skyscraper/Scraper/CaSystemNames.cs b/skyscraper8/Skyscraper/Scraper/CaSystemNames.cs new file mode 100644 index 0000000..186e7fa --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/CaSystemNames.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper +{ + //Some of these are found at https://en.wikipedia.org/wiki/Conditional_access + //or at https://www.dvbservices.com/identifiers/ca_system_id + public static class CaSystemNames + { + private static readonly ushort[] nagravision_ids = new ushort[] + { + 0x1800, 0x1802, 0x1803, 0x1804, 0x1805, 0x1807, + 0x1811, 0x1812, 0x1814, 0x1815, + 0x181B, 0x181D, 0x1830, 0x1831, 0x1833, 0x1835, 0x1836, 0x1837, + 0x183D, 0x183e, 0x1842, 0x1843, 0x1856, 0x1860, 0x1861, + 0x1867, 0x186a, 0x186c, 0x186d, 0x1870, 0x1871, 0x1873, 0x1874, + 0x1875, 0x1878, 0x1880, 0x1881, 0x1882, 0x1883, 0x1884, 0x1886, + 0x1887, 0x188A, 0x188D, 0x18a0, 0x18A1, + 0x1838, /* https://www.boxpirates.to/index.php?thread/136817-alles-zum-unity-media-hack-nur-noch-hier-rein-unity-media-in-nagra2-ist-wieder-o/&pageNo=20 */ + 0x187E, /* https://www.digital-forum.it/showthread.php?212937-Arriva-la-Smart-Card-Satellitare-per-la-visione-gratuita-dei-canali-Rai-via-satellite&p=7337652 */ + 0x1850, /* https://forums.openpli.org/topic/62690-problemen-met-lezen-van-ziggo-card-et9500-cccam/ */ + 0x1868, /* https://forums.openpli.org/topic/62690-problemen-met-lezen-van-ziggo-card-et9500-cccam/ */ + 0x1854, /* TSReaderLite */ + 0x181f, /* TSReaderLite */ + 0x181c, /* TSReaderLite */ + 0x1855, /* TSReaderLite */ + 0x1844, /* TSReaderLite */ + 0x1840, /* TSReaderLite */ + 0x18fe, + 0x1813, 0x1817, 0x1818, 0x1819, 0x1863, 0x1801, 0x1810, 0x1702, 0x1722, 0x1762, + 0x1834, + 0x1879 + }; + + + private static ushort[] conax_ids = new ushort[] + { + 0x0B00, 0x0B01, 0x0B02, 0x0b0f, 0x0b10, 0x0b1c, 0x0B1D, + 0x0BAA, 0x0BC1, + 0x0b77, /*according to TsReaderLite */ + }; + + private static ushort[] videoguard_ids = new ushort[] + { + 0x09f0, + + 0x06F8, 0x090f, 0x091e, 0x091F, 0x092b, 0x092f, + 0x0931, 0x093e, 0x0940, 0x094C, 0x0952, 0x095E, + 0x098C, 0x098D, 0x09AF, 0x09B2, 0x09BD, 0x09be, + 0x09C4, 0x09c7, 0x09D0, 0x09E9, + 0x098e, /* http://www.hifi-forum.de/viewthread-177-6364.html#18 */ + 0x0900, + 0x0927, 0x093b, 0x0963, 0x09cd, + 0x0930, 0x0942, + 0x0911, 0x0960, + + }; + + private static ushort[] cryptoworks_ids = new ushort[] + { + 0x0D00, 0x0D01, 0x0D02, 0x0D03, 0x0D05, 0x0D06, 0x0D0F, 0x0D22, + 0x0D70, 0x0D95, 0x0D96, 0x0d97, 0x0D98, 0x0d07, 0x0d20 + }; + + private static ushort[] irdeto_ids = new ushort[] + { + 0x0602, 0x0603, 0x0604, 0x0606, 0x0608, 0x0609, 0x0610, 0x0614, + 0x0616, 0x0622, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, + 0x0630, 0x0634, 0x0640, 0x0641, 0x0647, 0x0648, 0x0650, 0x0653, + 0x0655, 0x0656, 0x0658, 0x0659, 0x0660, 0x0662, 0x0664, 0x0666, + 0x0668, 0x0690, 0x0694, 0x0695, 0x0696, 0x0697, 0x0699, 0x069A, + 0x069B, 0x069F, 0x06ad, 0x06CB, 0x06E1, 0x06E2, 0x06ED, 0x06EE, + 0x06F0, 0x06F3, + 0x0600, + 0x0646, /* TSReaderLite */ + 0x06EA, /* TSReaderLite */ + 0x0692, + 0x0919, 0x0961, 0x09ac, + 0x0642 + }; + + public static string GetHumanReadableName(ushort id) + { + if (id == 0x0500) + return "Viaccess"; + + if (id == 0x4b24) + return "Prowill AB"; + + if (id == 0x4b63) + return "redCrypter"; + + if (id == 0x0100 || id == 0x0104 || id == 0x0180 || id == 0x0186) + return "Mediaguard"; + + if (id == 0x4b64) + return "Samsung TVKey"; + + foreach (ushort ndsId in videoguard_ids) + if (ndsId == id) + return "NDS Videoguard"; + + if (id == 0x4af4) + return "Marlin Crypt"; + + foreach (ushort nagravisionId in nagravision_ids) + if (nagravisionId == id) + return "Nagravision"; + + foreach (ushort conaxId in conax_ids) + if (conaxId == id) + return "Conax"; + + foreach (ushort cryptoworksId in cryptoworks_ids) + if (cryptoworksId == id) + return "Cryptoworks"; + + foreach (ushort irdetoId in irdeto_ids) + if (irdetoId == id) + return "Irdeto"; + + if (id == 0x4ad0 || id == 0x4ad1 || id == 0x7be1) + return "XCrypt"; + + if (id == 0x4adc) + return "SafeAccess"; + + if (id == 0x4aea) + return "Cryptoguard"; + + if (id == 0x4AFC) + return "Panaccess"; + + if (id == 0x0007 || id == 0x4aee || id == 0x5581) + return "Bulcrypt"; + + if (id == 0x4AE0 || id == 0x4AE1) + return "DRE-Crypt"; + + if (id == 0x2200 || id == 0x223b || id == 0x22e2 || id == 0x22E3) + return "CodiCrypt"; + + if (id == 0x4A70) + return "DreamCrypt"; + + if (id == 0x4ABF) + return "CTI-CAS"; + + if (id == 0x1751) + return "Betacrypt"; + + if (id == 0x4AD4) + return "Widevine"; + + if (id == 0x4AA0 || id == 0x4AA1) + return "KeyFly"; + + if (id == 0x4A60 || id == 0x4A61 || id == 0x4A63) + return "Neotioncrypt"; //"Skycrypt" + + if (id == 0x2600) + return "BISS"; + + if (id == 0x0e00 || id == 0x0e01) + return "PowerVU"; + + // https://wiki.zebradem.com/wiki/index.php?title=Zugangsberechtigungssystem + if (id == 0x4800) + return "AccessGate"; + + if (id == 0x0700) + return "Digicipher 2"; + + if (id == 0x5501) + return "Griffin"; + + if (id == 0x1000) + return "RAS"; + + if (id == 0xa101) + return "RusCrypto"; + + if (id == 0x4b00) + return "SafeView"; + + if (id == 0x5500) + return "Z-Crypt"; + + if (id == 0x4abf) + return "Wellfly"; + + //from OSCAM + if (id == 0x1010) + return "Tandberg/Director"; + + //https://en.wikipedia.org/wiki/Conditional_access + if (id == 0x4aeb) + return "Abel Quintic"; + + if (id == 0x4af0 || id == 0x4af2 || id == 0x4b4b) + return "ABV"; + + if (id == 0x4b19) + return "RCAS"; + + if (id == 0x4b30 || id == 0x4b31) + return "ViCAS"; + + if (id == 0x4a20) + return "AlphaCrypt"; + + if (id == 0x1700 || id == 0x1701) + return "VCAS"; + + if (id >= 0x1703 && id <= 0x1721) + return "VCAS"; + + if (id >= 0x1723 && id <= 0x1761) + return "VCAS"; + + if (id >= 0x1763 && id <= 0x17ff) + return "VCAS"; + + if (id >= 0x5601 && id <= 0x5604) + return "VCAS"; + + if (id == 0x2610) + return "BISS 2"; + + if (id >= 0x27a0 && id <= 0x27a4) + return "ICAS"; + + if (id == 0x4900) + return "China Crypt"; + + if (id == 0x22f0) + return "Codicrypt"; + + if (id == 0x0b03 || id == 0x0b04 || id == 0x0b05 || id == 0x0b06 || id == 0x0b07) + return "Conax CAS 3"; + + if (id == 0x4ae4) + return "CoreCrypt"; + + if (id == 0x4347) + return "CryptOn"; + + if (id == 0x4a10) + return "EasyCas"; + + if (id == 0x2719 || id == 0xead0) + return "InCrypt Cas"; + + if (id == 0x0464) + return "EuroDec"; + + if (id == 0x5448) + return "Gospell VisionCrypt"; + + if (id == 0x4a02 || id == 0x4af6 || id == 0x4b00 || id == 0x4b01 || id == 0x4b02) + return "Tongfang"; + + if (id == 0x4ac1) + return "Latens"; + + if (id == 0x4a80) + return "ThalesCrypt"; + + if (id == 0x56d0) + return "Onnet"; + + //https://de.wikipedia.org/wiki/Zugangsberechtigungssystem + if (id == 0x4ade) + return "CerberCrypt"; + + if (id == 0x4aef) + return "NetUP"; + + if (id == 0x1845) + return "Nagravision [???]"; + + if (id == 0x405f) + return "sophia.net [Skyscraper Proprietary]"; + + if (id == 21319) + return "GkWare"; + + if (id >= 0x2200 && id <= 0x22ff) + return "Harmonic"; + + if (id == 0x4b4a) + return "Topwell"; + + if (id >= 0x4ab0 && id <= 0x4abf) + return "Compunicate"; + + if (id == 0x0649) + return "Irdeto (?)"; + + if (id > 0xaa01) + return String.Format("Invalid 0x{0:X4}", id); + + if (id == 0x09b5) + return "NDS Videoguard (?)"; + + return "???"; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/ChildEventLogger.cs b/skyscraper8/Skyscraper/Scraper/ChildEventLogger.cs new file mode 100644 index 0000000..9ce6ed0 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/ChildEventLogger.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper +{ + internal class ChildEventLogger : ISkyscraperEventLogger + { + public ISkyscraperEventLogger Wrapped { get; } + public string ChildName { get; } + + public ChildEventLogger(ISkyscraperEventLogger wrapped, string childName) + { + Wrapped = wrapped; + ChildName = childName; + } + + public void Log(TimeSpan duration, DateTime eventTimestamp, SkyscraperContextEvent eventType, string name = null) + { + if (name == null) + name = String.Empty; + Wrapped.Log(duration, eventTimestamp, eventType, String.Format("<{0}> {1}", ChildName, name)); + NumEvents++; + } + + public ulong NumEvents { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/ConsoleEventLogger.cs b/skyscraper8/Skyscraper/Scraper/ConsoleEventLogger.cs new file mode 100644 index 0000000..3cc47c7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/ConsoleEventLogger.cs @@ -0,0 +1,31 @@ +using System; +using System.Text; + +namespace skyscraper5.Skyscraper.Scraper +{ + internal class ConsoleEventLogger : ISkyscraperEventLogger + { + public void Log(TimeSpan duration, DateTime eventTimestamp, SkyscraperContextEvent eventType, string name = null) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0} {1}] ", eventTimestamp.ToShortDateString(), eventTimestamp.ToShortTimeString()); + sb.AppendFormat("{0}",eventType.ToString()); + + if (!string.IsNullOrEmpty(name) && !string.IsNullOrWhiteSpace(name)) + { + sb.AppendFormat(" ({0}) ", name); + } + + + if (eventType != SkyscraperContextEvent.StartPacketProcessing && eventType != SkyscraperContextEvent.TdtTime && eventType != SkyscraperContextEvent.TotTime) + { + sb.AppendFormat(" ({0} ms)", duration.TotalMilliseconds); + } + + Console.WriteLine(sb.ToString()); + NumEvents++; + } + + public ulong NumEvents { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Dns/DnsParseResult.cs b/skyscraper8/Skyscraper/Scraper/Dns/DnsParseResult.cs new file mode 100644 index 0000000..2283f09 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Dns/DnsParseResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Dns +{ + public class DnsParseResult + { + public List Records { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Dns/DnsRecord.cs b/skyscraper8/Skyscraper/Scraper/Dns/DnsRecord.cs new file mode 100644 index 0000000..c994751 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Dns/DnsRecord.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Dns +{ + public class DnsRecord + { + public string Domain { get; set; } + public Tuple RecordType { get; set; } + public TimeSpan TTL { get; set; } + public string ResourceData { get; set; } + public IPAddress IP { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Dns/IDnsDataSource.cs b/skyscraper8/Skyscraper/Scraper/Dns/IDnsDataSource.cs new file mode 100644 index 0000000..d26d1d0 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Dns/IDnsDataSource.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Dns +{ + public interface IDnsDataSource + { + long DnsCountA(); + string DnsIpToName(IPAddress source); + void RememberDnsRecord(DnsRecord record); + bool TestForIp(IPAddress iP); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Dns/IDnsParser.cs b/skyscraper8/Skyscraper/Scraper/Dns/IDnsParser.cs new file mode 100644 index 0000000..e54c6de --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Dns/IDnsParser.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Dns +{ + public interface IDnsParser + { + DnsParseResult Parse(byte[] data); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Dns/SkyscraperDnsCache.cs b/skyscraper8/Skyscraper/Scraper/Dns/SkyscraperDnsCache.cs new file mode 100644 index 0000000..23aa2a9 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Dns/SkyscraperDnsCache.cs @@ -0,0 +1,164 @@ +using skyscraper5.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Security.AccessControl; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Dns +{ + public class SkyscraperDnsCache + { + public SkyscraperDnsCache(IDnsDataSource dataSource) + { + _dnsDataSource = dataSource; + } + + private Dictionary _dnsCache; + private IDnsDataSource _dnsDataSource; + private static long cacheHits, cacheMiss; + private static long dbMiss; + + + internal string GetDnsName(IPAddress source) + { + if (_dnsCache == null) + _dnsCache = new Dictionary(); + if (!_dnsCache.ContainsKey(source)) + { + string fromDataSource = null; + if (!IsSpecialIpAddress(source)) + fromDataSource = _dnsDataSource.DnsIpToName(source); + else + fromDataSource = source.ToString(); + + _dnsCache.Add(source, fromDataSource); + return fromDataSource; + } + + if (_dnsCache.ContainsKey(source) && _dnsCache[source] != null) + { + cacheHits++; + } + else + { + cacheMiss++; + } + + if (_dnsCache[source] == null) + { + _dnsCache.Remove(source); + return GetDnsName(source); + } + return _dnsCache[source]; + } + + private bool packetProcessingDisabled; + private IDnsParser dnsPacketParser; + public KeyValuePair LastLearned { get; private set; } + internal bool ProcessDnsPacket(byte[] payload) + { + if (packetProcessingDisabled) + return false; + + if (dnsPacketParser == null) + { + IReadOnlyList dnsParsers = PluginManager.GetInstance().GetDnsParsers(); + switch(dnsParsers.Count) + { + case 0: + packetProcessingDisabled = true; + return false; + case 1: + dnsPacketParser = dnsParsers[0]; + break; + default: + throw new NotImplementedException("more than 1 dns parser"); + } + } + + DnsParseResult dnsParseResult = dnsPacketParser.Parse(payload); + if (dnsParseResult == null) + return false; + if (dnsParseResult.Records == null) + return false; + if (dnsParseResult.Records.Count == 0) + return false; + if (_dnsCache == null) + _dnsCache = new Dictionary(); + bool result = false; + foreach(DnsRecord record in dnsParseResult.Records) + { + bool isIpAddress = (record.RecordType.Item2.Equals("A") || record.RecordType.Item2.Equals("AAAA")); + if (isIpAddress) + { + _dnsCache[record.IP] = record.Domain; + LastLearned = new KeyValuePair(record.Domain, record.IP); + bool ipCheck = !_dnsDataSource.TestForIp(record.IP); + if (ipCheck) + result = true; + else + dbMiss++; + } + _dnsDataSource.RememberDnsRecord(record); + } + return result; + } + + private bool IsSpecialIpAddress(IPAddress ipAddress) + { + byte[] bytes = ipAddress.GetAddressBytes(); + if (bytes[0] == 0) //Current Network + return true; + + if (bytes[0] == 10) //Local communications + return true; + + if (bytes[0] == 10 && bytes[1] >= 64 && bytes[1] <= 127) //Shared Address Space + return true; + + if (bytes[0] == 127) //Loopback + return true; + + if (bytes[0] == 169 && bytes[1] == 254) //Link local addresses + return true; + + if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) //Local communications + return true; + + if (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 0) //IETF Protocol assignments + return true; + + if (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 2) //TEST-NET-1 + return true; + + if (bytes[0] == 192 && bytes[1] == 88 && bytes[2] == 99) //Reserved + return true; + + if (bytes[0] == 192 && bytes[1] == 168) //Local communications + return true; + + if (bytes[0] == 198 && (bytes[1] == 18 || bytes[1] == 19)) + return true; + + if (bytes[0] == 198 && (bytes[1] == 51 && bytes[2] == 100)) //TEST-NET-2 + return true; + + if (bytes[0] == 203 && bytes[1] == 0 && bytes[2] == 113) //TEST-NET-2 + return true; + + if (bytes[0] >= 224 && bytes[0] <= 239) //Multicast + return true; + + if (bytes[0] >= 240 && bytes[0] <= 255) //Reserved + return true; + return false; + } + } + + +} diff --git a/skyscraper8/Skyscraper/Scraper/FrameGrabber/FrameGrabberEventHandler.cs b/skyscraper8/Skyscraper/Scraper/FrameGrabber/FrameGrabberEventHandler.cs new file mode 100644 index 0000000..aaae8b7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/FrameGrabber/FrameGrabberEventHandler.cs @@ -0,0 +1,8 @@ +namespace skyscraper5.Skyscraper.Scraper.FrameGrabber +{ + internal interface IFrameGrabberEventHandler + { + bool TestForFrame(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid); + void NotifyCompletedFrame(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid, byte[] imageData); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/FrameGrabber/ffmpegFrameGrabber.cs b/skyscraper8/Skyscraper/Scraper/FrameGrabber/ffmpegFrameGrabber.cs new file mode 100644 index 0000000..5bc5c78 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/FrameGrabber/ffmpegFrameGrabber.cs @@ -0,0 +1,163 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Threading; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Skyscraper.Scraper.FrameGrabber +{ + // ffmpeg -i tcp://127.0.0.1:6969 -map 0:p:10352:v:0 -vframes 1 test.png + + internal class ffmpegFrameGrabber : ITsPacketProcessor + { + public int CurrentNetworkId { get; } + public int TransportStreamId { get; } + public ProgramMapping Mapping { get; } + public ProgramMappingStream MappingStream { get; } + public IFrameGrabberEventHandler EventHandler { get; } + public IPEndPoint TsProxyEndPoint { get; set; } + public ffmpegFrameGrabber(int currentNetworkId, int transportStreamId, ProgramMapping mapping, ProgramMappingStream mappingStream, IFrameGrabberEventHandler eventHandler) + { + CurrentNetworkId = currentNetworkId; + TransportStreamId = transportStreamId; + Mapping = mapping; + MappingStream = mappingStream; + EventHandler = eventHandler; + State = FrameGrabberJobState.WAITING; + + if (eventHandler.TestForFrame(currentNetworkId, transportStreamId, mapping.ProgramNumber, mappingStream.ElementaryPid)) + { + State = FrameGrabberJobState.DONE; + return; + } + + } + + private void FrameGrabberThread() + { + string outputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".jpg"); + + IPEndPoint endpoint = TsProxyEndPoint; + if (TsProxyEndPoint.Address.Equals(IPAddress.Any)) + { + endpoint = new IPEndPoint(IPAddress.Loopback, endpoint.Port); + } + + Process ffmpeg = new Process(); + ffmpeg.StartInfo.FileName = "ffmpeg"; + ffmpeg.StartInfo.UseShellExecute = true; + ffmpeg.StartInfo.RedirectStandardOutput = false; + ffmpeg.StartInfo.RedirectStandardError = false; + ffmpeg.StartInfo.WindowStyle = ProcessWindowStyle.Minimized; + ffmpeg.StartInfo.Arguments = String.Format("-i tcp://{0} -map 0:p:{1}:v:0 -vframes 1 -vf yadif -qmin 1 -qscale:v 1 \"{2}\"", endpoint.ToString(), Mapping.ProgramNumber, outputPath); + ffmpeg.Start(); + ffmpeg.WaitForExit(); + + if (File.Exists(outputPath)) + { + ImageData = File.ReadAllBytes(outputPath); + File.Delete(outputPath); + State = FrameGrabberJobState.SUCCESS; + BusyFrameGrabbers--; + } + else + { + State = FrameGrabberJobState.FAIL; + BusyFrameGrabbers--; + } + } + + public byte[] ImageData { get; set; } + + private Thread workerThread; + + public FrameGrabberJobState State { get; private set; } + + public enum FrameGrabberJobState + { + WAITING, + BUSY, + DONE, + SUCCESS, + FAIL + } + + public void PushPacket(TsPacket packet) + { + if (State == FrameGrabberJobState.DONE) + return; + if (TsProxyEndPoint == null) + return; + if (startable.HasValue && !startable.Value) + return; + if (workerThread != null) + { + switch (State) + { + case FrameGrabberJobState.BUSY: + return; + case FrameGrabberJobState.FAIL: + State = FrameGrabberJobState.DONE; + return; + case FrameGrabberJobState.SUCCESS: + State = FrameGrabberJobState.DONE; + EventHandler.NotifyCompletedFrame(CurrentNetworkId, TransportStreamId, Mapping.ProgramNumber, MappingStream.ElementaryPid, ImageData); + return; + case FrameGrabberJobState.DONE: + return; + default: + throw new NotImplementedException(); + } + } + else + { + workerThread = new Thread(FrameGrabberThread); + workerThread.Priority = ThreadPriority.Lowest; + workerThread.Start(); + State = FrameGrabberJobState.BUSY; + BusyFrameGrabbers++; + } + } + + public static int BusyFrameGrabbers { get; private set; } + private static bool? startable; + public static bool CanStart() + { + if (startable.HasValue) + return startable.Value; + + Console.WriteLine("Testing for a working ffmpeg..."); + Process ffmpeg = new Process(); + ffmpeg.StartInfo.FileName = "ffmpeg"; + ffmpeg.StartInfo.UseShellExecute = false; + ffmpeg.StartInfo.RedirectStandardOutput = true; + ffmpeg.StartInfo.RedirectStandardError = true; + try + { + ffmpeg.Start(); + ffmpeg.WaitForExit(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + startable = false; + return false; + } + + string line = ffmpeg.StandardError.ReadLine(); + if (line.StartsWith("ffmpeg version 2") || line.StartsWith("ffmpeg version 5.")) + { + Console.WriteLine("Found {0}", line); + startable = true; + return true; + } + int exitCode = ffmpeg.ExitCode; + Debug.WriteLine("ffmpeg exit code {0}", exitCode); + startable = false; + Console.WriteLine(new string('_', Console.WindowWidth)); + return false; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/FreesatTunnelScraper.cs b/skyscraper8/Skyscraper/Scraper/FreesatTunnelScraper.cs new file mode 100644 index 0000000..d696e6d --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/FreesatTunnelScraper.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Skyscraper.Scraper +{ + internal class FreesatTunnelScraper : IBatEventHandler, ISdtEventHandler, ITdtEventHandler, ITotEventHandler + { + + private ushort? networkId; + private DateTime? currentDateTime; + + public void OnBatBouquet(BatBouquet batBouquet) + { + } + + public void OnBatTransportStream(BatBouquet batBouquet, BatTransportStream child) + { + } + + public void OnSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + } + + public void OnTdtTime(DateTime utcTime) + { + this.currentDateTime = utcTime; + } + + public void OnTotTime(DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + this.currentDateTime = utcTime; + } + + public void SetNetworkId(ushort networkId) + { + this.networkId = networkId; + } + + public void SetTransportStreamId(ushort transportStreamId) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/ISkyscraperContext.cs b/skyscraper8/Skyscraper/Scraper/ISkyscraperContext.cs new file mode 100644 index 0000000..ea8bd17 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/ISkyscraperContext.cs @@ -0,0 +1,29 @@ +using skyscraper5.src.Mpeg2.PacketFilter; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper +{ + public interface ISkyscraperContext : IDisposable + { + public void IngestFromStream(Stream stream); + public void IngestSinglePacket(byte[] buffer); + public bool IsAbortConditionMet(); + public bool EnableTimeout { get; set; } + + public int TimeoutSeconds { get; set; } + + public bool CancelOnNextPacket { get; set; } + + public ISkyscraperUiJunction UiJunction { get; set; } + + public bool TcpProxyEnabled { get; set; } + + public void InitalizeFilterChain(params IPacketFilter[] args); + + } +} diff --git a/skyscraper8/Skyscraper/Scraper/ISkyscraperEventLogger.cs b/skyscraper8/Skyscraper/Scraper/ISkyscraperEventLogger.cs new file mode 100644 index 0000000..5b2e59b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/ISkyscraperEventLogger.cs @@ -0,0 +1,10 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper +{ + public interface ISkyscraperEventLogger + { + void Log(TimeSpan duration, DateTime eventTimestamp, SkyscraperContextEvent eventType, string name = null); + ulong NumEvents { get; } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/ISkyscraperUiJunction.cs b/skyscraper8/Skyscraper/Scraper/ISkyscraperUiJunction.cs new file mode 100644 index 0000000..b076d3e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/ISkyscraperUiJunction.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Net; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.Teletext.Wss; + +namespace skyscraper5.Skyscraper.Scraper +{ + public interface ISkyscraperUiJunction + { + void NotifyEvent(EitEvent eitEvent); + void NotifySdtService(SdtService sdtService); + void NotifyPatProgram(int pmtPid, ushort programId); + void NotifyPmtProgram(ProgramMapping result, int pmtPid); + void NotifyNit(NitTransportStream transportStream); + void NotifyMpeTraffic(IpTrafficInfo iti, int ipv4PacketLength); + void NotifyAit(AitApplication aitApplication); + void DsmCcModuleAdd(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion); + void DsmCcModuleProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, double moduleInfoDownloadProgress); + void DsmCcModuleComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion); + void NotifyWss(ushort programNumber, WssDataBlock wssDataBlock); + void NotifyStreamTypeDetection(string contestantTag, int pid); + void NotifyBat(BatBouquet batBouquet); + void NotifyBatTs(ushort batBouquetBouquetId, BatTransportStream child); + void DsmCcVfs(VfsFile vfsFile); + void NotifyTot(DateTime utcTime, LocalTimeOffsetDescriptor ltod); + void NotifyTdt(DateTime utcTime); + void NotifyCat(CaDescriptor caDescriptor); + void NotifyScte35(ushort programNumber, SpliceInsert spliceInsert); + void NotifyScte35(ushort programNumber, TimeSignal spliceInsert); + void SetMemorySaverMode(bool saveMemory); + void NotifyDocsisCarrier(DocsisEnvironment docsisEnvironment); + void NotifyDocsisFrequency(uint? frequency, bool isUpstream, object mmm); + void SetGseMode(); + void ShowFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid, byte[] imageData); + void NotifyBlockstreamCarrier(); + + IEnumerable GetServices(); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/ScraperException.cs b/skyscraper8/Skyscraper/Scraper/ScraperException.cs new file mode 100644 index 0000000..32d31bc --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/ScraperException.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper +{ + [Serializable] + public class ScraperException : SkyscraperException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public ScraperException() + { + } + + public ScraperException(string message) : base(message) + { + } + + public ScraperException(string message, Exception inner) : base(message, inner) + { + } + + protected ScraperException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs new file mode 100644 index 0000000..c696d25 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs @@ -0,0 +1,2393 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; +using skyscraper5.Abertis; +using skyscraper5.Docsis; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Event; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Stream; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.Subtitling; +using skyscraper5.Dvb.Subtitling.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Ietf.Rfc768; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Mheg5; +using skyscraper5.Mhp.Descriptors; +using skyscraper5.Mhp.Descriptors.InteractionTransportSelectors; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds; +using skyscraper5.Rds.Messages; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Net; +using skyscraper5.Skyscraper.Net.Pcap; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.FrameGrabber; +using skyscraper5.Skyscraper.Scraper.Storage; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.Skyscraper.Scraper.Utils; +using skyscraper5.src.Id3; +using skyscraper5.src.InteractionChannel; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Mpeg2; +using skyscraper5.src.Mpeg2.PacketFilter; +using skyscraper5.src.Privates; +using skyscraper5.src.Skyscraper; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.src.Teletext; +using skyscraper5.T2MI; +using skyscraper5.T2MI.Packets; +using skyscraper5.T2MI.Packets.AdressingFunctions; +using skyscraper5.Teletext; +using skyscraper5.Teletext.Vps; +using skyscraper5.Teletext.Wss; +using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform; +using RntParser = skyscraper5.Dvb.TvAnytime.RntParser; + +namespace skyscraper5.Skyscraper.Scraper +{ + public class SkyscraperContext : IPatEventHandler, IPmtEventHandler, INitEventHandler, ITeletextPageHandler, + IBatEventHandler, ISdtEventHandler, ICatEventHandler, ITdtEventHandler, ITotEventHandler, ITsdtEventHandler, + IEitEventHandler, IAitEventHandler, ISubtitleEventHandler, + UpdateNotificationEventHandler, DataCarouselEventHandler, RdsEventHandler, IScte35EventHandler, + IAutodetectionEventHandler, IRstEventHandler, IRntEventHandler, IMultiprotocolEncapsulationEventHandler, ObjectCarouselEventHandler, T2MIEventHandler, + IDisposable, IFrameGrabberEventHandler, IntEventHandler, IRctEventHandler, IGsEventHandler, ISkyscraperContext, IDocsisEventHandler, AbertisDecoderEventHandler, Id3Handler, + InteractionChannelHandler + { + public const bool ALLOW_STREAM_TYPE_AUTODETECTION = true; + public const bool ALLOW_FFMPEG_FRAMEGRABBER = true; + public const bool ENABLE_MPE_TO_PCAP = true; + + public TsContext DvbContext { get; } + public ISkyscraperEventLogger EventLogger { get; } + public IScraperStroage ScraperStorage { get; } + + public bool SourceIsDisk { get; set; } + + public PsiDecoder PatDecoder { get; private set; } + public PsiDecoder CatDecoder { get; private set; } + public PsiDecoder NitDecoder { get; private set; } + public PsiDecoder Pid0x11Decoder { get; private set; } + public PsiDecoder Pid0x12Decoder { get; private set; } + public IPsiDecoderTransformer PmtDecoderTransformer { get; set; } + public SkyscraperContext(TsContext dvbContext, ISkyscraperEventLogger eventLogger = null, + IScraperStroage scraperStorage = null, int skipAtStart = 0, int? currentNetworkId = null, int? currentTransportStreamId = null) + { + TsDescriptorUnpacker descriptorUnpacker = TsDescriptorUnpacker.GetInstance(); + for (byte i = 0x80; i < 0xfe; i++) + { + descriptorUnpacker.SetUserDefined(i, true); + } + + descriptorUnpacker.SetUserDefined(0xfe, true); + + DvbContext = dvbContext; + EventLogger = eventLogger; + if (EventLogger == null) + EventLogger = new ConsoleEventLogger(); + + //PAT + PatDecoder = new PsiDecoder(0, new PatParser(this)); + dvbContext.RegisterPacketProcessor(0, PatDecoder); + //PMT autodetect from PAT + + //CAT + CatDecoder = new PsiDecoder(1, new CatParser(this)); + dvbContext.RegisterPacketProcessor(1, CatDecoder); + + //TSDT (experimental) + dvbContext.RegisterPacketProcessor(2, new PsiDecoder(2, new TsdtParser(this))); + + //IPMP (experimental) + dvbContext.RegisterPacketProcessor(3, new PsiDecoder(3, new IpmpParser(this))); + + //NIT + NitDecoder = new PsiDecoder(0x10, new NitParser(this)); + dvbContext.RegisterPacketProcessor(0x10, NitDecoder); + + //SDT / BAT + Pid0x11Decoder = new PsiDecoder(0x11, new Pid0x11Decoder(this, this)); + dvbContext.RegisterPacketProcessor(0x11, Pid0x11Decoder); + + //EIT + //dvbContext.RegisterPacketProcessor(0x12, new PsiDecoder(0x12, new EitParser(this))); + Pid0x12Decoder = new PsiDecoder(0x12, new Pid0x12Decoder(this)); + dvbContext.RegisterPacketProcessor(0x12, Pid0x12Decoder); + + //RST + DvbContext.RegisterPacketProcessor(0x13, new PsiDecoder(0x13, new RstParser(this))); + + //TDT + //TOT handled by TDT processor + dvbContext.RegisterPacketProcessor(0x14, new PsiDecoder(0x14, new TimetableParser(this, this))); + + //RNT + dvbContext.RegisterPacketProcessor(0x16, new PsiDecoder(0x16, new RntParser(this))); + + //DIT + //PID 0x1E + //won't need these. We operate on broadcasted stuff, so we shouldn't encounter partial TS + + //DIT + //PID 0x1F + //won't need these. We operate on broadcasted stuff, so we shouldn't encounter partial TS + + if (scraperStorage == null) + scraperStorage = new InMemoryScraperStorage(); + ScraperStorage = scraperStorage; + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + DsmCcsToLookFor = new Dictionary(); + SsusToLookFor = new List>(); + + this.skipBufferRemain = skipAtStart; + } + + public int? CurrentNetworkId { get; private set; } + public int? CurrentTransportStreamId { get; private set; } + + public TeiFilter TeiFilter { get; private set; } + public ScrambleFilter ScrambleFilter { get; private set; } + public TeiOnOffFilter TeiOnOffFilter { get; private set; } + public void InitalizeFilterChain(params IPacketFilter[] args) + { + if (DvbContext.FilterChain != null) + throw new InvalidOperationException("The filter chain was already initalized."); + if (TeiFilter == null) + TeiFilter = new TeiFilter(); + if (ScrambleFilter == null) + ScrambleFilter = new ScrambleFilter(); + if (TeiOnOffFilter == null) + TeiOnOffFilter = new TeiOnOffFilter(); + + List result = new List(); + result.AddRange(args); + result.Add(TeiFilter); + result.Add(ScrambleFilter); + result.Add(TeiOnOffFilter); + result.Sort(new PluginPrioritySorter()); + DvbContext.FilterChain = result; + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append("IN"); + for (int i = 0; i < result.Count; i++) + { + stringBuilder.Append(" -> "); + if (result[i] != null) + { + stringBuilder.Append(result[i].GetType().Name); + } + else + { + stringBuilder.Append("(null)"); + } + } + stringBuilder.Append(" -> OUT"); + Console.WriteLine("Built filter chain: {0}", stringBuilder.ToString()); + } + + public void IngestFromStream(Stream stream) + { + byte[] buffer = new byte[188]; + while (true) + { + if (IsAbortConditionMet()) + break; + + if (stream.Read(buffer, 0, 188) != 188) + break; + + try + { + IngestSinglePacket(buffer); + } + catch (InvalidTsPacketException) + { + break; + } + } + + stream.Close(); + runningEvents = null; + } + + private bool skipBufferStarted; + private int skipBufferRemain; + private bool firstPacketDone = false; + private ulong totalPacketsPushed; + + public void IngestSinglePacket(byte[] buffer) + { + if (buffer.Length != 188) + throw new ArgumentException(String.Format("{0}.{1} != {2}", nameof(buffer), nameof(buffer.Length), + 188)); + + if (skipBufferRemain > 0) + { + if (!skipBufferStarted) + { + LogEvent(SkyscraperContextEvent.StartPacketProcessing, String.Format("Skipping {0} packets before begin.", skipBufferRemain)); + skipBufferStarted = true; + } + skipBufferRemain--; + } + + if (!firstPacketDone) + { + LogEvent(SkyscraperContextEvent.StartPacketProcessing); + if (buffer[0] == 0x47 && buffer[1] == 0x41 && buffer[2] == 0x18 && (buffer[3] & 0x10) != 0 && buffer[4] == 0x00 && buffer[5] == 0x80 && buffer[6] == 0x00) + { + DvbContext.RegisterPacketProcessor(0x0118, new Tbs6903xGsReader(this)); + UiJunction?.SetGseMode(); + LogEvent(SkyscraperContextEvent.SpecialTsMode, "TBS 6903-X GSE Pseudo TS detected."); + SpecialTsType = 2; + } + if (buffer[0] == 0x47 && buffer[1] == 0x41 && buffer[2] == 0x0e && (buffer[3] & 0x10) != 0 && buffer[4] == 0x00 && buffer[5] == 0x80 && buffer[6] == 0x00) + { + DvbContext.RegisterPacketProcessor(0x010e, new DigitalDevicesBbFrameReader(this)); + UiJunction?.SetGseMode(); + LogEvent(SkyscraperContextEvent.SpecialTsMode, "Digital Devices BBFrame Pseudo TS detected."); + SpecialTsType = 3; + } + firstPacketDone = true; + } + + DvbContext.PushPacket(buffer); + if (totalPacketsPushed++ == 1000) + { + CheckSpecialTs(); + } + } + + private DocsisPacketProcessor docsisPacketProcessor; + public int SpecialTsType { get; private set; } + private void CheckSpecialTs() + { + ulong[] pidStatistics = DvbContext.GetPidStatistics(); + if (pidStatistics == null) + { + totalPacketsPushed = 0; + return; + } + int occupiedPids = pidStatistics.Where(x => x > 0).Count(); + int numTotal = (int)totalPacketsPushed; + int num1fff = (int)pidStatistics[0x1fff]; + double percentage1fff = (double)num1fff * 100.0 / (double)numTotal; + if (true) + { + if (pidStatistics[0x0118] > 0) + { + int num118 = (int)pidStatistics[0x0118]; + double p = (double)num118 * 100.0 / numTotal; + if (p > 58) + { + if (!DvbContext.IsPidProcessorPresent(0x0118)) + { + DvbContext.RegisterPacketProcessor(0x0118, new Tbs6903xGsReader(this)); + UiJunction?.SetGseMode(); + LogEvent(SkyscraperContextEvent.SpecialTsMode, "TBS 6903-X GSE Pseudo TS detected."); + SpecialTsType = 2; + return; + } + } + } + if (pidStatistics[0x010e] > 0) + { + int num10e = (int)pidStatistics[0x010e]; + double p = (double)num10e * 100.0 / numTotal; + if (occupiedPids == 1) + { + if (p > 20) + { + if (!DvbContext.IsPidProcessorPresent(0x010e)) + { + DvbContext.RegisterPacketProcessor(0x010e, new DigitalDevicesBbFrameReader(this)); + UiJunction?.SetGseMode(); + LogEvent(SkyscraperContextEvent.SpecialTsMode, "Digital Devices BBFRAME data TS detected."); + SpecialTsType = 3; + return; + } + } + } + } + } + + if (occupiedPids <= 2) + { + if (pidStatistics[0x1ffe] > 10 && num1fff > 10) + { + docsisPacketProcessor = new DocsisPacketProcessor(this); + DvbContext.RegisterPacketProcessor(0x1ffe, docsisPacketProcessor); + UiJunction?.NotifyDocsisCarrier(docsisPacketProcessor.DocsisEnvironment); + LogEvent(SkyscraperContextEvent.SpecialTsMode, "DOCSIS Carrier TS detected."); + SpecialTsType = 4; + return; + } + + int num20 = (int)pidStatistics[0x0020]; + double percentage20 = (double)num20 * 100.0 / (double)numTotal; + if (percentage20 > 99.0 && percentage1fff < 1.0) + { + MultiprotocolEncapsulationDecoder blockstreamDecoder = new MultiprotocolEncapsulationDecoder(this); + DvbContext.RegisterPacketProcessor(0x0020, new PsiDecoder(0x0020,blockstreamDecoder)); + UiJunction?.NotifyBlockstreamCarrier(); + LogEvent(SkyscraperContextEvent.SpecialTsMode, "Blockstream Carrier TS detected."); + SpecialTsType = 5; + return; + } + } + } + + + [DebuggerStepThrough] + private void LogEvent(SkyscraperContextEvent eventType, string name = null) + { + DateTime eventTimestamp = DateTime.Now; + TimeSpan duration = eventTimestamp - lastEventTimestamp; + EventLogger.Log(duration, eventTimestamp, eventType, name); + if (eventType != SkyscraperContextEvent.TdtTime && eventType != SkyscraperContextEvent.TotTime && eventType != SkyscraperContextEvent.Scte35TimeSignal) + { + lastEventTimestamp = eventTimestamp; + } + } + + private DateTime lastEventTimestamp; + + public void NetworkPidFromPat(int networkPid) + { + if (DvbContext.IsPidProcessorPresent(networkPid)) + return; + + DvbContext.RegisterPacketProcessor(networkPid, new PsiDecoder(networkPid, new NitParser(this))); + LogEvent(SkyscraperContextEvent.NetworkPidFromPat); + } + + public void ProgramMapPidFromPat(int pmtPid, ushort programId) + { + SpecialTsType = 1; + if (CurrentNetworkId.HasValue && CurrentTransportStreamId.HasValue) + { + ScraperStorage.StorePatEntry(CurrentNetworkId.Value, CurrentTransportStreamId.Value, pmtPid, programId); + } + + if (DvbContext.IsPidProcessorPresent(pmtPid)) + return; + + PsiDecoder pmtParser = new PsiDecoder(pmtPid, new PmtParser(this)); + if (PmtDecoderTransformer != null) + { + PmtDecoderTransformer.Transform(pmtParser); + } + + DvbContext.RegisterPacketProcessor(pmtPid, pmtParser); + LogEvent(SkyscraperContextEvent.ProgramMapPidFromPat); + UiJunction?.NotifyPatProgram(pmtPid, programId); + } + + public void SetTransportStreamId(ushort transportStreamId) + { + if (!CurrentTransportStreamId.HasValue) + { + CurrentTransportStreamId = transportStreamId; + return; + } + } + + private List> SsusToLookFor; + private Dictionary DsmCcsToLookFor; + + public void PmtEvent(ProgramMapping result, int pmtPid) + { + bool logworthy = false; + if (CurrentNetworkId.HasValue && CurrentTransportStreamId.HasValue) + { + + if (!ScraperStorage.TestForPmtEvent(CurrentNetworkId.Value, CurrentTransportStreamId.Value, result)) + { + if (ScraperStorage.StorePmtEvent(CurrentNetworkId.Value, CurrentTransportStreamId.Value, result)) + logworthy = true; + } + + if (result.CaSystemId.HasValue && result.CaPid.HasValue) + { + CaDescriptor caDescriptor = new CaDescriptor(result.CaSystemId.Value, result.CaPid.Value, result.CaPrivateData); + NotifyOfCaSystem(caDescriptor, true); + } + } + + bool allPrivateStreams = true; + foreach (ProgramMappingStream mappingStream in result.Streams) + { + if (!mappingStream.IsUserPrivateStream()) + { + allPrivateStreams = false; + break; + } + } + + byte madeUpComponentTags = 1; + foreach (ProgramMappingStream mappingStream in result.Streams) + { + if (mappingStream.CaPid.HasValue) + { + if (!DvbContext.IsPidProcessorPresent(mappingStream.CaPid.Value)) + { + NotifyOfCaSystem(new CaDescriptor(mappingStream.CaSystemId.Value, mappingStream.CaPid.Value, mappingStream.CaPrivateData),true); + } + } + + if (!DvbContext.IsPidProcessorPresent(mappingStream.ElementaryPid)) + { + List> ssuKvs = SsusToLookFor.FindAll(x => x.Key == result.ProgramNumber); + bool foundSsu = false; + foreach (KeyValuePair keyValuePair in ssuKvs) + { + byte ssuComponentTag = keyValuePair.Value; + if (ssuComponentTag == mappingStream.ComponentTag) + { + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, new DataCarouselDecoder(mappingStream, result, DataCarouselIntention.SoftwareUpdate, this))); + SsusToLookFor.Remove(keyValuePair); + foundSsu = true; + break; + } + } + + if (foundSsu) + continue; + + + StreamType guessedStreamType = GuessStreamType(mappingStream, result.Streams.Count, result.RegistrationFormatIdentifier, allPrivateStreams); + switch (guessedStreamType) + { + case StreamType.Video: + ITsPacketProcessor videoPacketProcessor = null; + if (ALLOW_FFMPEG_FRAMEGRABBER) + { + if (TcpProxyEnabled) + { + if (!result.CaPid.HasValue) + { + if (!mappingStream.CaPid.HasValue) + { + if (CurrentNetworkId.HasValue && CurrentTransportStreamId.HasValue) + { + if (!ScraperStorage.TestForFramegrab(CurrentNetworkId.Value, CurrentTransportStreamId.Value, result.ProgramNumber, mappingStream.ElementaryPid)) + { + ffmpegFrameGrabber ffmfg = new ffmpegFrameGrabber(CurrentNetworkId.Value, CurrentTransportStreamId.Value, result, mappingStream, this); + ffmfg.TsProxyEndPoint = DvbContext.GetTcpProxyEndPoint(); + videoPacketProcessor = ffmfg; + } + else + { + videoPacketProcessor = new PacketDiscarder(); + } + } + } + } + } + } + + if (videoPacketProcessor != null) + { + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, videoPacketProcessor); + } + + break; + case StreamType.Teletext: + if (!CurrentNetworkId.HasValue) + break; + if (!CurrentTransportStreamId.HasValue) + break; + TeletextPesProcessor ttp = new TeletextPesProcessor(this, CurrentNetworkId.Value, CurrentTransportStreamId.Value, result.ProgramNumber); + if (result.PrivateDataSpecifier.HasValue) + ttp.PrivateDataSpecifier = result.PrivateDataSpecifier; + if (mappingStream.PrivateDataSpecifier.HasValue) + ttp.PrivateDataSpecifier = mappingStream.PrivateDataSpecifier; + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PesDecoder(ttp)); + break; + case StreamType.Audio: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PacketDiscarder()); + break; + case StreamType.Application: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, + new PsiDecoder(mappingStream.ElementaryPid, new AitParser(this, result.ProgramNumber))); + break; + case StreamType.Ignorable: + break; + case StreamType.HbbTv: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, + new PsiDecoder(mappingStream.ElementaryPid, + new DataCarouselDecoder(mappingStream, result, DataCarouselIntention.NoIntention, this))); + break; + case StreamType.SystemSoftwareUpdate: + SystemSoftwareUpdateInfo ssuInfo = + new SystemSoftwareUpdateInfo(mappingStream.DataBroadcastSelector); + if (ssuInfo.Valid) + TryRegisterSystemSoftwareUpdateInfo(ssuInfo, mappingStream, result); + break; + case StreamType.Subtitles: + PesDecoder pesDecoder = new PesDecoder(new SubtitleDecoder(this)); + pesDecoder.Tag = "subs"; + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, pesDecoder); + //DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PacketDumper(new FileInfo("subtitle.ts"))); + break; + case StreamType.DvbMhp: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, + new PsiDecoder(mappingStream.ElementaryPid, + new DataCarouselDecoder(mappingStream, result, DataCarouselIntention.NoIntention, this))); + break; + case StreamType.Scte35: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, + new PsiDecoder(mappingStream.ElementaryPid, + new Scte35SiDecoder(result.ProgramNumber, this))); + break; + case StreamType.RDS: + RdsPesProcessor rdsPesProcessor = new RdsPesProcessor(mappingStream.ElementaryPid, + result.ProgramNumber, this); + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, + new PesDecoder(rdsPesProcessor)); + break; + case StreamType.MultiprotocolEncapsulation: + MultiprotocolEncapsulationDecoder mpeDec = new MultiprotocolEncapsulationDecoder(this); + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, + new PsiDecoder(mappingStream.ElementaryPid, mpeDec)); + break; + case StreamType.Mheg5: + Mheg5DataBroadcastIdSelector selector = + new Mheg5DataBroadcastIdSelector(mappingStream.DataBroadcastSelector); + //These looked like Object Carousels to me + if (!mappingStream.ComponentTag.HasValue) + MakeUpComponentTags(result); + //ObjectCarouselDecoder ocd = new ObjectCarouselDecoder(mappingStream.ElementaryPid, this, result, mappingStream.ComponentTag.Value); + DataCarouselDecoder dcd = new DataCarouselDecoder(mappingStream, result, DataCarouselIntention.NoIntention, this); + //ocd.Tag = StreamType.Mheg5; + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, dcd)); + break; + case StreamType.TryAutodetect: + StreamTypeAutodetection sta = new StreamTypeAutodetection(mappingStream.ElementaryPid, this); + sta.ProgramContext.Program = result; + sta.ProgramContext.Stream = mappingStream; + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, sta); + break; + case StreamType.IpMacNotification: + IntParser intParser = new IntParser(this); + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, intParser)); + break; + case StreamType.RelatedContentTable: + RctParser rctParser = new RctParser(this, result.ProgramNumber); + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, rctParser)); + break; + case StreamType.T2Mi: + T2MIDecoder t2miDecoder = new T2MIDecoder(mappingStream.ElementaryPid, this); + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, t2miDecoder); + break; + case StreamType.InteractionChannelForSatellite: + InteractionChannelPsiGatherer interactionChannelDecoder = new InteractionChannelPsiGatherer(); + interactionChannelDecoder.ExpectedTables = mappingStream.UnknownUserDefines[0]; + interactionChannelDecoder.Handler = this; + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, interactionChannelDecoder)); + break; + case StreamType.OpenTvPrivate: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new OpenTvPrivate()); + break; + case StreamType.BSkyBPrivate: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new BSkyBPrivate()); + break; + case StreamType.Id3: + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PesDecoder(new Id3PesProcessor(this))); + break; + default: + throw new NotImplementedException(String.Format( + "Detection of Stream type for PID 0x{0:X4} in Service {1}", pmtPid, + result.ProgramNumber)); + } + } + } + + if (logworthy) + LogEvent(SkyscraperContextEvent.PmtEvent, String.Format("#{0}", result.ProgramNumber)); + + UiJunction?.NotifyPmtProgram(result, pmtPid); + } + + public void MakeUpComponentTags(ProgramMapping target) + { + bool[] usedTags = new bool[byte.MaxValue]; + foreach (ProgramMappingStream programMappingStream in target.Streams) + { + if (programMappingStream.ComponentTag.HasValue) + { + usedTags[programMappingStream.ComponentTag.Value] = true; + } + } + + usedTags[0] = true; + + foreach (ProgramMappingStream mappingStream in target.Streams) + { + if (!mappingStream.ComponentTag.HasValue) + { + for (byte b = 0; b < Byte.MaxValue; b++) + { + if (!usedTags[b]) + { + usedTags[b] = true; + mappingStream.ComponentTag = b; + break; + } + } + } + } + } + + private void TryRegisterSystemSoftwareUpdateInfo(SystemSoftwareUpdateInfo info, + ProgramMappingStream mappingStream, ProgramMapping mapping) + { + if (DvbContext.IsPidProcessorPresent(mappingStream.ElementaryPid)) + return; + + //TODO: Scrape this once we have an SSU Event handler. + foreach (SystemSoftwareUpdateInfo.Oui infoOui in info.Ouis) + { + switch (infoOui.UpdateType) + { + //Found on ETSI TS 102 006, Page 13 + case 0: //proprietary update solution + if (ALLOW_STREAM_TYPE_AUTODETECTION) + { + StreamTypeAutodetection sta = new StreamTypeAutodetection(mappingStream.ElementaryPid, this); + sta.ProgramContext.Stream = mappingStream; + sta.ProgramContext.Program = mapping; + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, sta); + } + else + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PacketDiscarder()); + break; + case 1: //standard update carousel (i.e. without notification table) via broadcast + if (!mappingStream.ComponentTag.HasValue) + { + MakeUpComponentTags(mapping); + } + + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, new DataCarouselDecoder(mappingStream, mapping, DataCarouselIntention.SoftwareUpdate, this))); + break; + case 2: //system software update carousel with notification table (UNT) both available via broadcast + case 3: //system software update signalled via broadcast UNT, update available from the return channel + case 4: //system software update signalled via broadcast UNT, update available from the internet + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, new UntDecoder(this, mapping))); + break; + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + //reserved for future use as of 2015-06 + //ignore it. + break; + default: + throw new NotImplementedException(String.Format("{0} = {1:X2}", nameof(infoOui.UpdateType), + infoOui.UpdateType)); + } + } + } + + private StreamType GuessStreamType(ProgramMappingStream stream, int totalStreams, + uint? parentRegistrationFormatIdentifier, bool allPrivateStreams) + { + if (stream.StreamType == PmtStreamType.H262) + return StreamType.Video; + if (stream.AvcStillPresent.HasValue) + return StreamType.Video; + if (stream.AudioType.HasValue) + return StreamType.Audio; + if (stream.StreamType == PmtStreamType.AvcVideoStream) + return StreamType.Video; + if (stream.StreamType == PmtStreamType.Iso11172Audio) + return StreamType.Audio; + if (stream.StreamType == PmtStreamType.Iso13818_3Audio) + return StreamType.Audio; + if (stream.StreamType == PmtStreamType.Iso13818_7AudioADTS) + return StreamType.Audio; + if (stream.StreamType == PmtStreamType.HevcVideoStream) + return StreamType.Video; + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Ac4ChannelMode.HasValue) + return StreamType.Audio; //AC-4 Audio + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.BSID.HasValue) + return StreamType.Audio; + if (stream.StreamType == PmtStreamType.Iso14496_3Audio && stream.AacProfileAndLevel.HasValue) + return StreamType.Audio; + if ((int)stream.StreamType == 0x81 && stream.ComponentType.HasValue) + return StreamType.Audio; //AC-3 Audio, but weird. + if (stream.Teletexts != null) + { + for (int i = 0; i < stream.Teletexts.Length; i++) + { + if (stream.Teletexts[i] != null) + { + return StreamType.Teletext; + } + } + } + + if (stream.Applications != null) + { + return StreamType.Application; + } + + if (stream.DataBroadcastId == 0x0123) + { + return StreamType.HbbTv; + } + + if (stream.DataBroadcastId == 0x000a) + { + return StreamType.SystemSoftwareUpdate; + } + + if (!ALLOW_STREAM_TYPE_AUTODETECTION) + { + if ((int)stream.StreamType == 0xc0 || (int)stream.StreamType == 0xc1) + { + if (stream.PrivateDataSpecifier.HasValue) + throw new NotImplementedException(String.Format("{0}", nameof(stream.PrivateDataSpecifier))); + else + return StreamType.Ignorable; + } + } + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Subtitlings != null && + stream.Subtitlings.Length > 0) + { + return StreamType.Subtitles; + } + + if (stream.DataBroadcastId == 0xf0) + { + return StreamType.DvbMhp; + } + + if (!ALLOW_STREAM_TYPE_AUTODETECTION) + { + if (!stream.DataBroadcastId.HasValue && stream.StreamType == PmtStreamType.Iso13818_1PrivateSections && + totalStreams == 1) + { + return StreamType.Ignorable; + } + } + + if (stream.FormatIdentifier.HasValue && stream.FormatIdentifier == 0x43554549 && + (int)stream.StreamType == 0x86) + { + return StreamType.Scte35; + } + + if ((int)stream.StreamType == 0x86 && parentRegistrationFormatIdentifier.HasValue && + parentRegistrationFormatIdentifier.Value == 0x43554549) + { + return StreamType.Scte35; + } + + if (stream.DataBroadcastId == 0x0007) //According to dvbservices.com, this is an Object Carousel + { + return StreamType.DvbMhp; + } + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.VbiData != null) + { + return StreamType.Teletext; + } + + if ((byte)stream.StreamType == 0x89 && stream.AncillaryDataDescriptor != null && + stream.AncillaryDataDescriptor.RdsOnly) + return StreamType.RDS; + + if (!ALLOW_STREAM_TYPE_AUTODETECTION) + { + if (stream.StreamType == PmtStreamType.Iso13818_6TypeB && !stream.DataBroadcastId.HasValue) + return StreamType.Ignorable; + + if (stream.StreamType == PmtStreamType.Iso13818_6TypeC && !stream.DataBroadcastId.HasValue) + return StreamType.Ignorable; + + if (stream.DataBroadcastId.HasValue && stream.DataBroadcastId.Value == 0x0140) + { + //Proprietary by CANAL+, does not seem to be documented. + return StreamType.Ignorable; + } + } + + if (stream.DataBroadcastId.HasValue && stream.DataBroadcastId.Value == 0x0005 /*&& stream.StreamType == PmtStreamType.Iso13818_6TypeD*/) + { + return StreamType.MultiprotocolEncapsulation; + } + + if (!ALLOW_STREAM_TYPE_AUTODETECTION) + { + if (allPrivateStreams && !stream.DataBroadcastId.HasValue && !stream.PrivateDataSpecifier.HasValue && + !parentRegistrationFormatIdentifier.HasValue) + return StreamType.Ignorable; + + if (stream.DataBroadcastId == 0x010b) + { + //NDS France Technologies system software download + //sadly undocumented. + return StreamType.Ignorable; + } + + if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && totalStreams == 1 && + !stream.DataBroadcastId.HasValue && !stream.PrivateDataSpecifier.HasValue) + { + //A single private stream. We have no clue how to deal with it, at all. + return StreamType.Ignorable; + } + } + + if (stream.DataBroadcastId == 0x0106) + { + //Defined in ETSI ES 202 184 V2.4.1 + return StreamType.Mheg5; + } + + if (stream.DataBroadcastId == 0x000b) + { + return StreamType.IpMacNotification; + } + + if (stream.RelatedContentDescriptorPresent.HasValue) + { + if (stream.RelatedContentDescriptorPresent.Value && stream.StreamType == PmtStreamType.Iso13818_1PrivateSections) + { + return StreamType.RelatedContentTable; + } + } + + if (stream.NumT2MiStreams.HasValue && stream.StreamType == PmtStreamType.Iso13818_1PesPackets) + { + return StreamType.T2Mi; + } + + if (stream.UnknownUserDefines != null) + { + if (stream.UnknownUserDefines.Count == 1) + { + if (stream.UnknownUserDefines[0].DescriptorTag == 0xa7) + { + bool interactionChannelPossible = stream.UnknownUserDefines[0].Data.All(x => x == 0x40 || x == 0x41 || x == 0x4d || x == 0x70 || x >= 0xA0); + if (interactionChannelPossible) + { + + return StreamType.InteractionChannelForSatellite; + } + } + } + } + + if (stream.PrivateDataSpecifier == 0x00000002) + { + if (stream.UnknownUserDefines != null) + { + UserDefinedDescriptor userDefinedDescriptor = stream.UnknownUserDefines.Find(x => x.DescriptorTag == 0xb0); + if (userDefinedDescriptor != null) + { + //BSkyB EPG Related descriptor + return StreamType.BSkyBPrivate; + } + } + } + + if (stream.MetadataApplicationFormat == 0xffff && stream.MetadataApplicationFormatIdentifier == 0x49443320 && stream.MetadataFormat == 0xff && stream.MetadataFormatIdentifier == 0x49443320) + { + return StreamType.Id3; + } + + if (ALLOW_STREAM_TYPE_AUTODETECTION) + { + //Abandon all hope, ye who enter here... + return StreamType.TryAutodetect; + } + + return StreamType.Unknown; + } + + private int onidHits, onidMisses; + + public void SetNetworkId(ushort networkId) + { + SetNetworkId(networkId, false); + } + + public void SetNetworkId(ushort networkId, bool forceOverwrite = false) + { + if (CurrentNetworkId == null) + { + CurrentNetworkId = networkId; + return; + } + + if (CurrentNetworkId != networkId) + { + onidMisses++; + if (forceOverwrite) + { + CurrentNetworkId = networkId; + } + } + else + onidHits++; + } + + private EitEvent[] runningEvents; + public void OnEitEvent(EitEvent eitEvent) + { + if (ScraperStorage.StoreEitEvent(eitEvent)) + LogEvent(SkyscraperContextEvent.EitEvent, eitEvent.EventName); + + UiJunction?.NotifyEvent(eitEvent); + + if (eitEvent.RunningStatus == RunningStatus.Running) + { + if (runningEvents == null) + runningEvents = new EitEvent[UInt16.MaxValue]; + runningEvents[eitEvent.ServiceId] = eitEvent; + } + } + + public void OnNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + UiJunction?.NotifyNit(transportStream); + + string name; + switch (transportStream.DeliveryMethod) + { + case NitTransportStream.TransportMedium.DVB_S: + case NitTransportStream.TransportMedium.DVB_S2: + name = String.Format("{3}°{4}/{0}/{1}/{2}", transportStream.Frequency / 100, + transportStream.Polarization.ToString().Substring(0, 1), + (float)transportStream.SymbolRate / 10.0f, transportStream.OrbitalPosition, + transportStream.East.Value ? "E" : "W"); + break; + case NitTransportStream.TransportMedium.DVB_T2: + if (transportStream.Frequency != null) + { + throw new NotImplementedException(); + } + if (transportStream.CellInfos == null) + return; + if (transportStream.CellInfos.Count == 0) + return; + name = String.Format("Center Frequency: {0}", transportStream.GetT2Frequency()); + break; + case NitTransportStream.TransportMedium.Unknown: + return; + case NitTransportStream.TransportMedium.DVB_C: + name = String.Format("{0}/{1}/{2}", transportStream.Frequency / 100000, transportStream.SymbolRate, + transportStream.RenderModulation()); + break; + default: + throw new NotImplementedException(transportStream.DeliveryMethod.ToString()); + } + + if (!ScraperStorage.TestForNitTransportStream(networkId, transportStream)) + { + ScraperStorage.StoreNitTransportStream(networkId, transportStream); + LogEvent(SkyscraperContextEvent.OnNitTransportStream, name); + return; + } + + if (ScraperStorage.UpdateNitTransportStream(networkId, transportStream)) + { + LogEvent(SkyscraperContextEvent.OnNitTransportStreamUpdate, name); + return; + } + } + + public void OnNitNetwork(NitNetwork nitNetwork) + { + if (!string.IsNullOrEmpty(nitNetwork.Name)) + { + + } + if (nitNetwork.XaitPid.HasValue && CurrentNetworkId.HasValue) + { + ushort nitNetworkXaitPid = nitNetwork.XaitPid.Value; + if (nitNetwork.NetworkId == CurrentNetworkId.Value) + { + if (!DvbContext.IsPidProcessorPresent(nitNetworkXaitPid)) + { + AitParser aitParser = new AitParser(this, 0x00); + aitParser.Tag = "XAIT"; + DvbContext.RegisterPacketProcessor(nitNetworkXaitPid, + new PsiDecoder(nitNetworkXaitPid, aitParser)); + LogEvent(SkyscraperContextEvent.XaitDetected, String.Format("PID {0}", nitNetworkXaitPid)); + } + } + } + + if (!ScraperStorage.TestForNitNetwork(nitNetwork)) + { + ScraperStorage.StoreNitNetwork(nitNetwork); + LogEvent(SkyscraperContextEvent.OnNitNetwork, nitNetwork.Name); + return; + } + + if (ScraperStorage.UpdateNitNetwork(nitNetwork)) + { + LogEvent(SkyscraperContextEvent.OnNitNetworkUpdate, nitNetwork.Name); + return; + } + } + + + private HashSet _vpsCoordinates; + + public void OnVpsData(int networkId, int transportStreamId, ushort programNumber, VpsDataBlock vpsDataField) + { + //We don't really need VPS data, but we'll use it as another way of making sure we + //got everything. + if (_vpsCoordinates == null) + { + _vpsCoordinates = new HashSet(); + } + + VpsCoordinate vpsCoordinate = new VpsCoordinate(programNumber, vpsDataField.Month, vpsDataField.Day, + vpsDataField.Hour, vpsDataField.Minute); + if (_vpsCoordinates.Add(vpsCoordinate)) + { + LogEvent(SkyscraperContextEvent.VpsData, + String.Format("{0:D2}.{1:D2} {2:D2}:{3:D2} {4}", vpsCoordinate.Day, vpsCoordinate.Month, + vpsCoordinate.Hour, vpsCoordinate.Minute, vpsCoordinate.ProgramNumber)); + } + } + + + private Dictionary _wssDataBlocks; + + public void OnWssData(int networkId, int transportStreamId, ushort programNumber, WssDataBlock wssDataBlock) + { + if (_wssDataBlocks == null) + { + _wssDataBlocks = new Dictionary(); + } + + if (!_wssDataBlocks.ContainsKey(programNumber)) + { + _wssDataBlocks.Add(programNumber, wssDataBlock); + LogEvent(SkyscraperContextEvent.WssData, programNumber.ToString()); + UiJunction?.NotifyWss(programNumber, wssDataBlock); + } + } + + public void OnTeletextPage(int networkId, int transportStreamId, ushort programNumber, + TeletextMagazine magazine) + { + if (!currentTime.HasValue) + return; + + ScraperStorage.StoreTeletextPage(networkId, transportStreamId, programNumber, magazine, currentTime.Value); + } + + public void OnBatBouquet(BatBouquet batBouquet) + { + UiJunction?.NotifyBat(batBouquet); + if (ScraperStorage.TestForBatBouquet(batBouquet)) + { + if (ScraperStorage.UpdateBatBouquet(batBouquet)) + { + LogEvent(SkyscraperContextEvent.BatBouquetUpdate, batBouquet.BouquetName); + } + } + else + { + ScraperStorage.StoreBatBouquet(batBouquet); + LogEvent(SkyscraperContextEvent.BatBouquet, batBouquet.BouquetName); + } + } + + public void OnBatTransportStream(BatBouquet batBouquet, BatTransportStream child) + { + UiJunction?.NotifyBatTs(batBouquet.BouquetId, child); + string name = String.Format("{0},{1}", batBouquet.BouquetId, child.OriginalNetworkId); + if (ScraperStorage.TestForBatTransportStream(batBouquet.BouquetId, child)) + { + if (ScraperStorage.UpdateBatTransportStream(batBouquet.BouquetId, child)) + { + LogEvent(SkyscraperContextEvent.BatTransportStreamUpdate, name); + } + } + else + { + ScraperStorage.StoreBatTransportStream(batBouquet.BouquetId, child); + LogEvent(SkyscraperContextEvent.BatTransportStream, name); + } + } + + public void OnSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (ScraperStorage.TestForSdtService(transportStreamId, originalNetworkId, sdtService)) + { + if (!IsChild) + { + if (ScraperStorage.UpdateSdtService(transportStreamId, originalNetworkId, sdtService)) + { + LogEvent(SkyscraperContextEvent.OnSdtServiceUpdate, sdtService.ServiceName); + } + } + } + else + { + ScraperStorage.StoreSdtService(transportStreamId, originalNetworkId, sdtService); + LogEvent(SkyscraperContextEvent.OnSdtService, sdtService.ServiceName); + } + + UiJunction?.NotifySdtService(sdtService); + } + + private DateTime? currentTime; + private DateTime? currentTimeForTim; + + public void SetCurrentTime(DateTime currentTime) + { + if (!this.currentTime.HasValue) + { + this.currentTime = currentTime; + } + currentTimeForTim = currentTime; + } + + public void SetCurrentProgrammeType(int programNumber, PTY.ProgrammeTypeCodes pty) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (ScraperStorage.UpdateRdsPty(CurrentNetworkId.Value, CurrentTransportStreamId.Value, programNumber, pty)) + { + LogEvent(SkyscraperContextEvent.RdsPty, String.Format("{0} -> {1}", programNumber, pty.ToString())); + } + } + + public void MarkAsRdsTrafficInformationProgramme(int programNumber) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (ScraperStorage.MarkAsRdsTrafficInformationProgramme(CurrentNetworkId.Value, + CurrentTransportStreamId.Value, programNumber)) + { + LogEvent(SkyscraperContextEvent.RdsTrafficInfoDetected, String.Format("{0}", programNumber)); + } + } + + public void OnTotTime(DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + UiJunction?.NotifyTot(utcTime, ltod); + SetCurrentTime(utcTime); + + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (ScraperStorage.UpdateTimeOffsetTable(CurrentNetworkId.Value, CurrentTransportStreamId.Value, utcTime, + ltod)) + LogEvent(SkyscraperContextEvent.TotTime, utcTime.ToString()); + } + + public void OnTdtTime(DateTime utcTime) + { + UiJunction?.NotifyTdt(utcTime); + SetCurrentTime(utcTime); + + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (ScraperStorage.UpdateTimeAndDate(CurrentNetworkId.Value, CurrentTransportStreamId.Value, utcTime)) + LogEvent(SkyscraperContextEvent.TdtTime, utcTime.ToString()); + } + + public void OnAitApplication(AitApplication aitApplication, ushort programNumber) + { + UiJunction?.NotifyAit(aitApplication); + foreach (TransportProtocolDescriptor transportProtocol in aitApplication.TransportProtocols) + { + switch (transportProtocol.ProtocolId) + { + case 0x01: + ObjectCarouselTransportSelector octs = + (ObjectCarouselTransportSelector)transportProtocol.Selector; + DsmCcsToLookFor[programNumber] = octs.ComponentTag; + break; + case 0x03: + //The interwebs! + break; + default: + if (transportProtocol.Selector == null) + continue; + throw new NotImplementedException(String.Format("{0} {1:x2}", + nameof(transportProtocol.ProtocolId), transportProtocol.ProtocolId)); + } + } + + if (!ScraperStorage.TestForAitApplication(aitApplication.ApplicationIdentifier)) + { + ScraperStorage.StoreAitApplication(aitApplication); + LogEvent(SkyscraperContextEvent.AitApplication, aitApplication.TryGetName()); + } + } + + public void NotifyFileArrival(VfsFile vfsFile) + { + UiJunction?.DsmCcVfs(vfsFile); + + if (!CurrentTransportStreamId.HasValue) + return; + if (!CurrentNetworkId.HasValue) + return; + + if (ScraperStorage.ObjectCarouselFileArrival(vfsFile, CurrentTransportStreamId.Value, CurrentNetworkId.Value)) + { + LogEvent(SkyscraperContextEvent.FileArrival, String.Format("PID {0:X4}, Path {1}", vfsFile.SourcePid, vfsFile.ToString())); + } + + } + + public void NotifySubStream(ProgramMapping programMapping, Tap tap, ushort[] eventIds, Dictionary context, EventList_T eventListT, Info_T infoT) + { + foreach (ProgramMappingStream mappingStream in programMapping.Streams) + { + if (mappingStream.ComponentTag.HasValue) + { + ushort l = mappingStream.ComponentTag.Value; + ushort r = tap.AssociationTag; + if (l == r) + { + if (!DvbContext.IsPidProcessorPresent(mappingStream.ElementaryPid)) + { + //DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, new ObjectCarouselDecoder(mappingStream.ElementaryPid, this, programMapping, mappingStream.ComponentTag.Value))); + DvbContext.RegisterPacketProcessor(mappingStream.ElementaryPid, new PsiDecoder(mappingStream.ElementaryPid, new DataCarouselDecoder(mappingStream, programMapping, DataCarouselIntention.NoIntention, this))); + LogEvent(SkyscraperContextEvent.SubStreamFromObjectCarousel, String.Format("PID {0:X4}", mappingStream.ElementaryPid)); + } + } + } + } + } + + public void NotifyOfCaSystem(CaDescriptor caDescriptor, bool fromPmt = false) + { + if (caDescriptor.CaSystemId == 0x0000) + return; //Invalid CAID + + if (!CurrentNetworkId.HasValue) + return; + if (!CurrentTransportStreamId.HasValue) + return; + + if (!fromPmt) + { + UiJunction?.NotifyCat(caDescriptor); + if (!ScraperStorage.TestForCaSystem(CurrentNetworkId.Value, CurrentTransportStreamId.Value, caDescriptor.CaPid)) + { + ScraperStorage.StoreCaSystem(CurrentNetworkId.Value, CurrentTransportStreamId.Value, caDescriptor); + LogEvent(SkyscraperContextEvent.CaSystem, String.Format("{0} on PID {1:X4}", CaSystemNames.GetHumanReadableName(caDescriptor.CaSystemId), caDescriptor.CaPid)); + } + } + + if (!DvbContext.IsPidProcessorPresent(caDescriptor.CaPid)) + { + //These PIDs for CA Systems contain ECM and EMM messages. + //They are specific for *every* CA System. + //It is probably not worth reverse-engineering every single one of them. + //So we just discard these packages. + DvbContext.RegisterPacketProcessor(caDescriptor.CaPid, new PacketDiscarder()); + } + } + + public void NotifyOfSubtitleLine(Page page) + { + + } + + public void UpdateNotification(UpdateNotificationGroup common, UpdateNotificationTarget target, ushort ProgramNumber) + { + /*int hashCode = target.GetHashCode(); + if (!ScraperStorage.TestForUpdateNotification(hashCode, common)) + { + foreach (Compatibility compatibility in target.Compatibilities) + { + foreach (Platform platform in target.Platforms) + { + ScraperStorage.StoreUpdateNotification(hashCode, common, compatibility, platform); + LogEvent(SkyscraperContextEvent.UpdateNotification, + String.Format("{0} -> {1}", compatibility, platform)); + } + } + }*/ + foreach (Compatibility compatibility in target.Compatibilities) + { + foreach (Platform platform in target.Platforms) + { + ScraperStorage.StoreUpdateNotification(0, common, compatibility, platform); + } + } + + + if (common.AssociationTag.HasValue) + { + Platform platform = new Platform(); + platform.DataBroadcastId = common.DataBroadcastId; + platform.PrivateData = common.PrivateData; + platform.AssociationTag = common.AssociationTag; + platform.UpdateFlag = common.UpdateFlag; + platform.UpdateMethod = common.UpdateMethod; + platform.UpdatePriority = common.UpdatePriority; + target.Platforms.Add(platform); + } + + foreach (Platform targetPlatform in target.Platforms) + { + if (!targetPlatform.AssociationTag.HasValue) + continue; + if (!SsusToLookFor.Any(x => x.Key == ProgramNumber && x.Value == (byte)targetPlatform.AssociationTag)) + { + SsusToLookFor.Add(new KeyValuePair(ProgramNumber, + (byte)targetPlatform.AssociationTag.Value)); + } + } + } + + private Dictionary _dataCarouselProgresses; + + public bool IsModuleWanted(int elementaryPid, ushort moduleId, byte moduleVersion) + { + if (!CurrentNetworkId.HasValue) + return false; + + if (!CurrentTransportStreamId.HasValue) + return false; + + if (ScraperStorage.IsDsmCcModuleBlacklisted(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleId, moduleVersion)) + return false; + + if (ScraperStorage.IsDsmCcModuleWanted(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleId, moduleVersion)) + return true; + + return false; + } + + public void NotifyOfNewModule(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (_dataCarouselProgresses == null) + _dataCarouselProgresses = new Dictionary(); + + DatabaseKeyDsmCcModule key = new DatabaseKeyDsmCcModule(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleInfoModuleId, moduleInfoModuleVersion); + + if (dedupModules.Contains(key)) + return; + + if (ScraperStorage.IsDsmCcModuleWanted(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleInfoModuleId, moduleInfoModuleVersion)) + { + if (!_dataCarouselProgresses.ContainsKey(key)) + { + _dataCarouselProgresses.Add(key, 0.0); + } + LogEvent(SkyscraperContextEvent.NotifyOfNewModule, String.Format("PID {2:X4}, Module #{0:X4}, Version {1}", moduleInfoModuleId, moduleInfoModuleVersion, elementaryPid)); + UiJunction?.DsmCcModuleAdd(elementaryPid, moduleInfoModuleId, moduleInfoModuleVersion); + } + } + + public void NotifyModuleDownloadProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, double moduleInfoDownloadProgress) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (_dataCarouselProgresses == null) + return; + + DatabaseKeyDsmCcModule key = new DatabaseKeyDsmCcModule(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleInfoModuleId, moduleInfoModuleVersion); + if (_dataCarouselProgresses.ContainsKey(key)) + { + _dataCarouselProgresses[key] = moduleInfoDownloadProgress; + LogEvent(SkyscraperContextEvent.ModuleDownloadProgress, String.Format("PID {3:X4}, Module #{0}, Version {1}, {2}% done", moduleInfoModuleId, moduleInfoModuleVersion, moduleInfoDownloadProgress, elementaryPid)); + UiJunction?.DsmCcModuleProgress(elementaryPid, moduleInfoModuleId, moduleInfoModuleVersion, moduleInfoDownloadProgress); + } + } + + public ObjectCarouselEventHandler GetObjectCarouselEventHandler() + { + return this; + } + + public void NotifyDownloadComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result, bool isObjectCarousel) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (_dataCarouselProgresses == null) + return; + + if (isObjectCarousel) + throw new NotImplementedException(); + + UiJunction?.SetMemorySaverMode(true); + ScraperStorage.DataCarouselModuleArrival(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleModuleId, moduleModuleVersion, result); + UiJunction?.SetMemorySaverMode(false); + + LogEvent(SkyscraperContextEvent.ModuleDownloadComplete, String.Format("Module {0}, Version {1}", moduleModuleId, moduleModuleVersion)); + + DatabaseKeyDsmCcModule key = new DatabaseKeyDsmCcModule(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleModuleId, moduleModuleVersion); + _dataCarouselProgresses.Remove(key); + + UiJunction?.DsmCcModuleComplete(elementaryPid, moduleModuleId, moduleModuleVersion); + } + + public void DsmCcDoItNowEvent(ProgramMapping programMapping, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + if (!CurrentTransportStreamId.HasValue) + return; + if (!CurrentNetworkId.HasValue) + return; + if (!currentTime.HasValue) + return; + ScraperStorage.StoreDsmCcDoItNowEvent(currentTime.Value, CurrentNetworkId.Value, CurrentNetworkId.Value, programMapping.ProgramNumber, descriptorListStreamEventDescriptor, pid); + + //These "do-it-now" Events are specific for _EVERY_ application and probably not + //worth extending the scraping time. They are most likely subtitles or commands + //for game-show play-along apps. Therefore we won't create a log entry for these, as it + //would increase scraping duration. + } + + private List dedupModules = new List(); + public void PreventDsmCcModuleRepetition(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + dedupModules.Add(new DatabaseKeyDsmCcModule(CurrentNetworkId.Value, CurrentTransportStreamId.Value, elementaryPid, moduleModuleId, moduleModuleVersion)); + } + + public void SetRdsProgrammeServiceName(int programNumber, string programmeService2) + { + if (ScraperStorage.UpdateRdsProgrammeServiceName(CurrentNetworkId.Value, CurrentTransportStreamId.Value, + programNumber, programmeService2)) + { + LogEvent(SkyscraperContextEvent.RdsProgrammeServiceName, + String.Format("{0:X4}, {1:X4}", programNumber, programmeService2)); + } + } + + public bool TestCanHandleRdsMessages(int programNumber) + { + if (!CurrentNetworkId.HasValue) + return false; + + if (!CurrentTransportStreamId.HasValue) + return false; + + if (!ScraperStorage.TestForKnownRdsData(CurrentNetworkId.Value, CurrentTransportStreamId.Value, + programNumber)) + { + ScraperStorage.EnableRdsCollection(CurrentNetworkId.Value, CurrentTransportStreamId.Value, + programNumber); + LogEvent(SkyscraperContextEvent.BeginRdsRecording, + String.Format("{0:X4},{1:X4},{2:X4}", CurrentNetworkId.Value, CurrentTransportStreamId.Value, + programNumber)); + } + + return true; + } + + public void SetRdsRadioText(int programNumber, string text) + { + if (ScraperStorage.UpdateRdsRadioText(CurrentNetworkId.Value, CurrentTransportStreamId.Value, programNumber, + text)) + { + LogEvent(SkyscraperContextEvent.RdsText, String.Format("{0:X4} [{1}]", programNumber, text)); + } + } + + public void NotifySpliceInsert(ushort ProgramNumber, SpliceInsert spliceInsert) + { + UiJunction?.NotifyScte35(ProgramNumber, spliceInsert); + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (!ScraperStorage.TestForScte35SpliceInsert(CurrentNetworkId.Value, CurrentTransportStreamId.Value, + ProgramNumber, spliceInsert)) + { + ScraperStorage.StoreScte35SpliceInsert(CurrentNetworkId.Value, CurrentTransportStreamId.Value, ProgramNumber, spliceInsert); + LogEvent(SkyscraperContextEvent.Scte35Splice, + String.Format("Program {0:X4}, Splice at {1:X16}", ProgramNumber, spliceInsert.SpliceTime)); + } + } + + public void NotifyTimeSignal(ushort programNumber, TimeSignal timeSignal) + { + UiJunction?.NotifyScte35(programNumber, timeSignal); + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (!currentTime.HasValue) + return; + + ScraperStorage.SetScte35TimeSignal(CurrentNetworkId.Value, CurrentTransportStreamId.Value, currentTime.Value, programNumber, timeSignal); + LogEvent(SkyscraperContextEvent.Scte35TimeSignal, String.Format("Program {0:X4}, Time Signal {1:X16}", programNumber, timeSignal.Value)); + } + + public void NotifyOfCompliance(string compliance) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (!ScraperStorage.IsCompliant(CurrentNetworkId.Value, CurrentTransportStreamId.Value, compliance)) + { + ScraperStorage.MarkAsCompliant(CurrentNetworkId.Value, CurrentTransportStreamId.Value, compliance); + LogEvent(SkyscraperContextEvent.Compliance, compliance); + } + } + + public void NotifiyStationIdentification(string stationIdentification) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (ScraperStorage.SetStationIdentification(CurrentNetworkId.Value, CurrentTransportStreamId.Value, + stationIdentification)) + { + LogEvent(SkyscraperContextEvent.StationIdentification, stationIdentification); + } + } + + private FreesatTunnelScraper freesatTunnel; + public void AutodetectionSucessful(int pid, Contestant contestant, ProgramContext programContext) + { + UiJunction?.NotifyStreamTypeDetection(contestant.Tag, pid); + LogEvent(SkyscraperContextEvent.StreamTypeAutodetection, String.Format("{0} on PID {1:X4}", contestant.Tag, pid)); + contestant.DeclareWinner(this, pid, programContext); + return; + } + + private PcapWriter[] pcapWriters; + public void OnIpDatagram(int pid, byte[] payload) + { + if (ENABLE_MPE_TO_PCAP) + { + if (pcapWriters == null) + pcapWriters = new PcapWriter[0x1fff]; + if (pcapWriters[pid] == null) + { + FileStream fs = File.OpenWrite(String.Format("{0:X4},{1}.pcap", pid,DateTime.Now.Ticks)); + pcapWriters[pid] = new PcapWriter(fs, TcpdumpNetworkType.RawIp); + } + pcapWriters[pid].WritePacket(payload); + } + + int ipVersion = (payload[0] & 0xf0) >> 4; + if (ipVersion == 4) + { + if (Array.TrueForAll(payload, x => x == 0)) + return; + int ihl = (payload[0] & 0x0f); + ihl *= 32; + ihl /= 8; + byte[] ipv4Header = new byte[ihl]; + if (payload.Length < ihl) + return; + Array.Copy(payload, 0, ipv4Header, 0, ihl); + InternetHeader internetHeader = new InternetHeader(ipv4Header); + if (!internetHeader.ChecksumValid) + return; + byte[] ipv4Packet = new byte[payload.Length - ihl]; + Array.Copy(payload, ihl, ipv4Packet, 0, payload.Length - ihl); + OnIpv4PacketArrival(internetHeader, ipv4Packet); + } + else if (ipVersion == 6) + { + //throw new NotSupportedException("IPv6"); + } + else + { + //throw new Exception("invalid ip packet"); + } + } + + public void GsIpTrafficDetected() + { + LogEvent(SkyscraperContextEvent.SpecialTsMode, "Valid IP Traffic detected"); + } + + private HashSet trafficInfos; + private ReadOnlyCollection mpePlugins; + public SkyscraperDnsCache DnsCache { get; private set; } + + private bool ProcessDns(IpTrafficInfo trafficInfo, byte[] ipv4Packet) + { + if (DnsCache == null) + DnsCache = new SkyscraperDnsCache(ScraperStorage); + //Handle DNS + bool result = false; + if (trafficInfo.Protocol == 0x11) + { + UserDatagram udpPacket = new UserDatagram(ipv4Packet); + if (udpPacket.Valid) + { + if (udpPacket.SourcePort == 53) + { + result = DnsCache.ProcessDnsPacket(udpPacket.Payload); + } + } + } + trafficInfo.SourceName = DnsCache.GetDnsName(trafficInfo.Source); + trafficInfo.TargetName = DnsCache.GetDnsName(trafficInfo.Target); + return result; + } + public void OnIpv4PacketArrival(InternetHeader internetHeader, byte[] ipv4Packet) + { + //There are as many use cases as there are internet applications in these. + //Not really required to decode all of them, if you ask me. + /* + * One known use is LX9SES on Astra 23.5, with the following arguments: + * -> Destination Address always 228.64.0.56 + * -> Source Address always 10.225.49.1 + * -> Protocol always 0x11 (UDP) + */ + if (trafficInfos == null) + trafficInfos = new HashSet(); + + IpTrafficInfo iti = IpTrafficInfo.FromInternetHeader(internetHeader); + if (ProcessDns(iti, ipv4Packet)) + { + LogEvent(SkyscraperContextEvent.LearnDns, String.Format("{0} = {1}", DnsCache.LastLearned.Value, DnsCache.LastLearned.Key)); + } + UiJunction?.NotifyMpeTraffic(iti, ipv4Packet.Length); + + if (trafficInfos.Add(iti)) + { + LogEvent(SkyscraperContextEvent.IpTraffic, iti.ToString()); + } + + if (mpePlugins == null) + { + mpePlugins = PluginManager.GetInstance().GetMpePlugins(); + object[] connector = ScraperStorage.GetPluginConnector(); + PluginLogMessage messager = message => LogEvent(SkyscraperContextEvent.PluginMessage, message); + foreach (ISkyscraperMpePlugin plugin in mpePlugins) + { + try + { + plugin.ConnectToStorage(connector, messager); + } + catch (Exception e) + { + + } + } + } + + + foreach (ISkyscraperMpePlugin plugin in mpePlugins) + { + if (plugin.CanHandlePacket(internetHeader, ipv4Packet)) + { + plugin.SetContext(currentTime); + plugin.HandlePacket(internetHeader, ipv4Packet); + if (plugin.StopProcessingAfterThis()) + return; + } + } + /* + if (internetHeader.Protocol == 0x11) + { + UserDatagram userDatagram = new UserDatagram(ipv4Packet); + if (!userDatagram.Valid) + return; + + if (internetHeader.SourceAddress.Equals(_fpUrmetSrc) && internetHeader.DestinationAddress.Equals(_fpUrmetDst)) + { + //Found on Astra 19.2 12604/H - not the slightest idea what this does. + //Service name is FP URMET. + return; + } + else + { + ipPackets++; + } + } + else if (internetHeader.Protocol == 0x02) + { + IgmpMessage igmpMessage = new IgmpMessage(ipv4Packet); + //Also Found on Astra 19.2 12604/H, I don't think we'll need these. + return; + } + else + { + ipPackets++; + }*/ + } + + public void NotifyRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, + RunningStatus runningStatus) + { + if (!currentTime.HasValue) + return; + + if (ScraperStorage.StoreRunningStatus(transportStreamId, originalNetworkId, serviceId, eventId, runningStatus, currentTime.Value)) + { + LogEvent(SkyscraperContextEvent.RunningStatusChange, String.Format("({0:X4},{1:X4},{2:X4},{3:X4}) -> {4}", originalNetworkId, transportStreamId, serviceId, eventId, runningStatus)); + } + } + + public bool EnableTimeout { get; set; } + + public int TimeoutSeconds { get; set; } + + public bool CancelOnNextPacket { get; set; } + public ISkyscraperUiJunction UiJunction { get; set; } + + public bool IsAbortConditionMet() + { + if (ffmpegFrameGrabber.BusyFrameGrabbers > 0) + return false; + + if (EnableTimeout && TimeoutSeconds == 0) + TimeoutSeconds = 10; + + if (!firstPacketDone) + return false; + + if (CancelOnNextPacket) + { + LogEvent(SkyscraperContextEvent.AbortConditionMet, "Aborted from another thread."); + CancelOnNextPacket = false; + return true; + } + + if (EnableTimeout) + { + TimeSpan sinceLastEvent = DateTime.Now - lastEventTimestamp; + if (sinceLastEvent.TotalSeconds > TimeoutSeconds) + { + LogEvent(SkyscraperContextEvent.AbortConditionMet, String.Format("Event Timeout after {0} seconds", TimeoutSeconds)); + return true; + } + } + + return false; + } + + public void OnT2MiPacketLoss(int pid, byte expectedPacket, T2MIHeader header) + { + } + + private SkyscraperContext[][] childSkyscrapers; + public void OnT2MiPacket(int pid, byte basebandFramePlpId, byte[] basebandPacket) + { + if (childSkyscrapers == null) + childSkyscrapers = new SkyscraperContext[0x1fff][]; + if (childSkyscrapers[pid] == null) + { + childSkyscrapers[pid] = new SkyscraperContext[256]; + } + if (childSkyscrapers[pid][basebandFramePlpId] == null) + { + childSkyscrapers[pid][basebandFramePlpId] = new SkyscraperContext(new TsContext(), new ChildEventLogger(EventLogger, String.Format("PLP {0}", basebandFramePlpId)), ScraperStorage); + childSkyscrapers[pid][basebandFramePlpId].IsChild = true; + childSkyscrapers[pid][basebandFramePlpId].InitalizeFilterChain(); + LogEvent(SkyscraperContextEvent.T2MiPlpDetected, String.Format("PID {0:X4}, PLP {1}", pid, basebandFramePlpId)); + } + + childSkyscrapers[pid][basebandFramePlpId].IngestSinglePacket(basebandPacket); + } + + public bool IsChild { get; set; } + + public void OnT2MiTimestamp(int pid, _0x20_DvbT2Timestamp t2Timestamp) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (childSkyscrapers == null) + return; + + if (childSkyscrapers[pid] == null) + return; + + bool HasEvents(SkyscraperContext[] skyscrapers) + { + for (int i = 0; i < skyscrapers.Length; i++) + { + if (skyscrapers[i] == null) + continue; + + if (skyscrapers[i].EventLogger.NumEvents >= 2) + return true; + } + + return false; + } + + if (!HasEvents(childSkyscrapers[pid])) + return; + + DateTime resolveTime = t2Timestamp.ResolveTime(); + DateTime knownTimestamp = ScraperStorage.T2MiGetTimestamp(CurrentNetworkId.Value, CurrentTransportStreamId.Value, pid); + if (resolveTime > knownTimestamp) + { + ScraperStorage.T2MiSetTimestamp(CurrentNetworkId.Value, CurrentTransportStreamId.Value, pid, resolveTime); + } + } + + public void OnT2MiIqData(int relatedPid, _0x31_IqData iqData) + { + + } + + public void OnT2MiIqData(int relatedPid, _0x01_IqData iqData2) + { + + } + + public void OnT2MiError(int relatedPid, int skyscraperErrorCode) + { + + } + + public void OnT2MiL1Current(int relatedPid, _0x10_L1Current l1Current) + { + //No clue about this data. It's not really interesting. + } + + public void OnT2MiArbitraryCellInsertion(int relatedPid, _0x02_ArbitraryCellInsertion arbitraryCellInsertion) + { + + } + + public void OnT2MiBalancingCells(int relatedPid, byte frameIndex, uint numActiveBiasCellsPerP2) + { + + } + + public void OnT2MiL1Future(int relatedPid, _0x11_L1Future l1Future) + { + if (l1Future.FutureData.L1DynNext.Length == 0) + return; + + throw new NotImplementedException(); + } + + public void OnT2MiIndividualAddressing(int relatedPid, _0x21_IndividualAddressing individualAddressing) + { + if (!CurrentNetworkId.HasValue) + return; + + if (!CurrentTransportStreamId.HasValue) + return; + + if (childSkyscrapers == null) + return; + + if (childSkyscrapers[relatedPid] == null) + return; + + foreach (AddressingFunction function in individualAddressing.Commands) + { + if (!ScraperStorage.T2MiTestForTransmitter(CurrentNetworkId, CurrentTransportStreamId, relatedPid, function.TxIdentifier)) + ScraperStorage.T2MiRememberTransmitter(CurrentNetworkId, CurrentTransportStreamId, relatedPid, function.TxIdentifier); + + switch (function.Tag) + { + case 0x00: + _0x00_TransmitterTimeOffset tto = (_0x00_TransmitterTimeOffset)function; + ScraperStorage.T2MiSetTransmitterTimeOffset(CurrentNetworkId, CurrentTransportStreamId, relatedPid, function.TxIdentifier, tto.TimeOffset); + break; + default: + throw new NotImplementedException(String.Format("Individual Addressing function 0x{0:X2}", function.Tag)); + } + } + } + + public bool TcpProxyEnabled + { + get => DvbContext.TcpProxyEnabled; + set => DvbContext.TcpProxyEnabled = value; + } + + public void Dispose() + { + if (TcpProxyEnabled) + TcpProxyEnabled = false; + + + + if (ENABLE_MPE_TO_PCAP) + { + if (pcapWriters != null) + { + for (int i = 0; i < pcapWriters.Length; i++) + { + if (pcapWriters[i] != null) + pcapWriters[i].Dispose(); + } + } + } + + if (DvbContext.TcpProxyEnabled) + { + DvbContext.TcpProxyEnabled = false; + } + + if (docsisPacketProcessor != null) + { + docsisPacketProcessor.Dispose(); + } + + if (!SourceIsDisk) + { + if (_dataCarouselProgresses != null) + { + foreach (KeyValuePair item in _dataCarouselProgresses) + { + if (item.Value == 0.0) + continue; + + ScraperStorage.FailDsmCcDownload(item.Key, item.Value); + } + } + } + + ScraperStorage.WaitForCompletion(); + } + + public bool TestForFrame(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid) + { + return ScraperStorage.TestForFramegrab(currentNetworkId, transportStreamId, mappingProgramNumber, mappingStreamElementaryPid); + } + + public void NotifyCompletedFrame(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, + int mappingStreamElementaryPid, byte[] imageData) + { + ScraperStorage.StoreFramegrab(currentNetworkId, transportStreamId, mappingProgramNumber, (ushort)mappingStreamElementaryPid, imageData); + LogEvent(SkyscraperContextEvent.GotFramegrab, String.Format("Program {0:X4}, PID {1:X4}", mappingProgramNumber, mappingStreamElementaryPid)); + UiJunction?.ShowFramegrab(currentNetworkId, transportStreamId, mappingProgramNumber, mappingStreamElementaryPid, imageData); + } + + public void OnIpMacNotification(int sourcePid, Dvb.DataBroadcasting.IntModel.Platform platform, Target target, Operational operational) + { + IEnumerable ipMacNotifications = IpMacNotification.FlatMap(platform, target, operational); + foreach (IpMacNotification notification in ipMacNotifications) + { + if (!ScraperStorage.TestForIpMacNotification(notification)) + { + ScraperStorage.StoreIpMacNotification(notification); + LogEvent(SkyscraperContextEvent.IpMacNotification, String.Format("{0} -> {1}", platform.Name, notification.TargetName)); + } + } + } + + public void OnRelatedContent(Rct result, ushort resultProgramNumber) + { + if (result.LinkInfo.Length == 0) + return; + if (runningEvents == null) + return; + if (runningEvents[resultProgramNumber] == null) + return; + + EitEvent lEvent = runningEvents[resultProgramNumber]; + + foreach (RctLinkInfo rctLinkInfo in result.LinkInfo) + { + if (!ScraperStorage.TestForRelatedContent(lEvent, rctLinkInfo)) + { + ScraperStorage.SetRelatedContent(lEvent, rctLinkInfo); + LogEvent(SkyscraperContextEvent.RelatedContent, String.Format("Program {0}, Event {1} -> {2}", resultProgramNumber, lEvent.EventId, rctLinkInfo.GetIdentifierString())); + } + } + } + + private bool warnedMissingLocation; + + private void WarnMissingLocation() + { + if (!warnedMissingLocation) + { + LogEvent(SkyscraperContextEvent.LocationMissing); + warnedMissingLocation = true; + } + } + + public void OnParticipantDetected(PhysicalAddress pa) + { + int? currentLocation = ScraperStorage.GetCurrentLocationId(); + if (currentLocation.HasValue) + { + LogEvent(SkyscraperContextEvent.DocsisParticipant, BitConverter.ToString(pa.GetAddressBytes())); + ScraperStorage.StoreDocsisParticipant(pa, currentLocation.Value); + } + else + { + WarnMissingLocation(); + } + } + + public void OnCmtsTimestamp(PhysicalAddress source, uint timing) + { + //DOCSIS Timestamps are not interesting to us. + } + + public void OnUpstreamChannel(UpstreamChannelDescriptor mmm) + { + UiJunction?.NotifyDocsisFrequency(mmm.Frequency, true, mmm); + if (!mmm.Frequency.HasValue) + return; + + int? currentLocation = ScraperStorage.GetCurrentLocationId(); + if (!currentLocation.HasValue) + { + WarnMissingLocation(); + return; + } + + if (!ScraperStorage.TestForDocsisUpstreamChannel(mmm.Source, mmm.Frequency.Value,currentLocation.Value)) + { + ScraperStorage.StoreDocsisUpstreamChannel(mmm, currentLocation.Value); + LogEvent(SkyscraperContextEvent.DocsisUpstreamChannel, String.Format("{0} -> {1}", mmm.Source.ToString(), mmm.Frequency.Value)); + } + } + + public void OnDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel) + { + UiJunction?.NotifyDocsisFrequency(downstreamActiveChannel.Frequency, false, downstreamActiveChannel); + + if (!downstreamActiveChannel.Frequency.HasValue) + return; + + int? currentLocation = ScraperStorage.GetCurrentLocationId(); + if (!currentLocation.HasValue) + { + WarnMissingLocation(); + return; + } + + if (!ScraperStorage.TestForDocsisDownstreamChannel(physicalAddress, downstreamActiveChannel,currentLocation.Value)) + { + ScraperStorage.StoreDocsisDownstreamChannel(physicalAddress, downstreamActiveChannel,currentLocation.Value); + LogEvent(SkyscraperContextEvent.DocsisDownstreamChannel, String.Format("{0} -> {1}", physicalAddress.ToString(), downstreamActiveChannel.Frequency.Value)); + } + } + + public void OnLearnedIpFromMac(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress) + { + int? currentLocation = ScraperStorage.GetCurrentLocationId(); + if (!currentLocation.HasValue) + { + WarnMissingLocation(); + return; + } + + if (ScraperStorage.SetCmtsIp(arpHeaderSenderHardwareAddress, arpHeaderSenderProtocolAddress)) + { + LogEvent(SkyscraperContextEvent.LearnedCmtsIp,String.Format("{0} -> {1}",arpHeaderSenderHardwareAddress.ToString(),arpHeaderSenderProtocolAddress.ToString())); + } + } + + public void OnAbertisPacket(int pid, byte[] outBuffer) + { + if (childSkyscrapers == null) + childSkyscrapers = new SkyscraperContext[0x1fff][]; + if (childSkyscrapers[pid] == null) + childSkyscrapers[pid] = new SkyscraperContext[1]; + if (childSkyscrapers[pid][0] == null) + childSkyscrapers[pid][0] = new SkyscraperContext(new TsContext(), new ChildEventLogger(EventLogger, String.Format("Abertis Tunnel PID {0:X4}",pid)), ScraperStorage); + + childSkyscrapers[pid][0].IngestSinglePacket(outBuffer); + } + + public void OnAbertisSyncLoss(int pid, int oldPosition = -1, long newPosition = -1) + { + if (childSkyscrapers == null) + DvbContext.RegisterPacketProcessor(pid, new PacketDiscarder()); + else if (childSkyscrapers[pid] == null) + DvbContext.RegisterPacketProcessor(pid, new PacketDiscarder()); + else if (childSkyscrapers[pid][0] == null) + DvbContext.RegisterPacketProcessor(pid, new PacketDiscarder()); + else if (!childSkyscrapers[pid][0].firstPacketDone) + DvbContext.RegisterPacketProcessor(pid, new PacketDiscarder()); + else + throw new NotImplementedException(); + } + + public void OnMonochromeData(int networkId, int transportStreamId, ushort programNumber, MonochromeDataField result) + { + throw new NotImplementedException(); + } + + void Id3Handler.OnId3Error(Id3ErrorState state) + { + throw new NotImplementedException(); + } + + void Id3Handler.OnId3Tag(Id3Tag tag) + { + if (tag.IsAllPrivate()) + return; + throw new NotImplementedException(); + } + + void InteractionChannelHandler.OnCorrectionMessage(ushort interactiveNetworkId, Cmt cmt) + { + foreach(Cmt.CmtEntry entry in cmt.Entries) + { + if (!ScraperStorage.TestForCmtEntry(interactiveNetworkId,entry)) + { + LogEvent(SkyscraperContextEvent.CorrectionMessage, String.Format("Network ID = {0}, Group ID = {1}, Logon ID = {2}", interactiveNetworkId, entry.GroupId, entry.LoginId)); + ScraperStorage.InsertCmtEntry(interactiveNetworkId, entry); + } + } + } + + void InteractionChannelHandler.OnFrameComposition(ushort interactiveNetworkId, Fct fct) + { + foreach(Fct.Frame frame in fct.Frames) + { + if (!ScraperStorage.TestForFrameComposition(interactiveNetworkId,frame)) + { + LogEvent(SkyscraperContextEvent.FrameComposition, String.Format("Network ID = {0}, Frame = {1}", interactiveNetworkId, frame.FrameId)); + ScraperStorage.InsertFctFrame(interactiveNetworkId, frame); + } + } + } + + private bool[] interactionChannelErrors; + void InteractionChannelHandler.OnInteractionChannelError(InteractionChannelErrorState unexpectedTable) + { + if (interactionChannelErrors == null) + { + interactionChannelErrors = new bool[Enum.GetNames(typeof(InteractionChannelErrorState)).Length]; + } + int offset = (int)unexpectedTable; + if (!interactionChannelErrors[offset]) + { + LogEvent(SkyscraperContextEvent.InteractionChannelError, unexpectedTable.ToString()); + interactionChannelErrors[offset] = true; + } + } + + public void OnRcsMap(Rmt rmt) + { + if (rmt.Linkages != null) + { + foreach(_0x4a_LinkageDescriptor linkage in rmt.Linkages) + { + if (!ScraperStorage.TestForRmtLinkage(linkage)) + { + LogEvent(SkyscraperContextEvent.RmtLinkage, String.Format("{0} -> {1},{2}", linkage.InteractiveNetworkId, linkage.OriginalNetworkId, linkage.TransportStreamId)); + ScraperStorage.InsertRmtLinkage(linkage); + } + } + } + if (rmt.TransportStreams != null) + { + foreach(Rmt.TransportStream transportStream in rmt.TransportStreams) + { + if (!ScraperStorage.TestForRmtTransportStream(rmt.NetworkId, transportStream)) + { + LogEvent(SkyscraperContextEvent.RmtTransportStream, String.Format("{0} -> {1},{2}", rmt.NetworkId, transportStream.OriginalNetworkId, transportStream.TransportStreamId)); + ScraperStorage.InsertRmtTransportStream(rmt.NetworkId, transportStream); + } + } + } + } + + public void OnSatellitePosition(ushort interactiveNetworkId, Spt spt) + { + foreach (Spt.Satellite satellite in spt.Satellites) + { + if (!ScraperStorage.TestForSatellitePosition(interactiveNetworkId,satellite)) + { + LogEvent(SkyscraperContextEvent.SatellitePosition, String.Format("NID = {0}, Satellite ID = {1}", interactiveNetworkId, satellite.Id)); + ScraperStorage.StoreSatellitePosition(interactiveNetworkId, satellite); + } + } + } + + void InteractionChannelHandler.OnSuperframeComposition(ushort interactiveNetworkId, Sct sct) + { + foreach(Sct.Superframe superframe in sct.Superframes) + { + if (!ScraperStorage.TestForSuperframeComposition(interactiveNetworkId,superframe)) + { + LogEvent(SkyscraperContextEvent.SuperframeComposition, String.Format("Network ID = {0}, Superframe ID = {1}", interactiveNetworkId, superframe.SuperframeId)); + ScraperStorage.StoreSuperframeComposition(interactiveNetworkId, superframe); + } + } + } + + void InteractionChannelHandler.OnTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp) + { + foreach (Tbtp.TbtpFrame frame in tbtp.Frames) + { + foreach (Tbtp.TbtpFrame.BtpEntity btp in frame.BTP) + { + if (ScraperStorage.TestForTerminalBurstTimePlan(interactiveNetworkId,tbtp.GroupId,btp.LogonId)) + { + LogEvent(SkyscraperContextEvent.TerminalBurstTimePlan, String.Format("Network ID = {0}, Group ID = {1}, Logon ID = {2}", interactiveNetworkId, tbtp.GroupId, btp.LogonId)); + ScraperStorage.StoreTerminalBurstTimePlan(interactiveNetworkId, tbtp.GroupId, tbtp.SuperframeCount, frame.FrameNumber, btp); + } + } + } + } + + void InteractionChannelHandler.OnTimeslotComposition(ushort interactiveNetworkId, Tct tct) + { + throw new NotImplementedException(); + } + + public void OnCorrectionMessage(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + if (!ScraperStorage.TestForTim(mac)) + { + LogEvent(SkyscraperContextEvent.Tim, String.Format("for participant {0}", mac.ToString())); + ScraperStorage.CreateTim(mac); + } + if (ScraperStorage.CorrectTim(mac, cmd)) + { + LogEvent(SkyscraperContextEvent.TimCorrection, String.Format("for participant {0}", mac.ToString())); + } + } + + public void OnContentionControl(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew) + { + if (!ScraperStorage.TestForTim(mac)) + { + LogEvent(SkyscraperContextEvent.Tim, String.Format("for participant {0}", mac.ToString())); + ScraperStorage.CreateTim(mac); + } + if (ScraperStorage.ContentionTim(mac,ccdNew)) + { + LogEvent(SkyscraperContextEvent.TimContentionControl, String.Format("for participant {0}", mac.ToString())); + } + } + + public void OnCorrectionControl(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor) + { + if (!ScraperStorage.TestForTim(mac)) + { + LogEvent(SkyscraperContextEvent.Tim, String.Format("for participant {0}", mac.ToString())); + ScraperStorage.CreateTim(mac); + } + if (ScraperStorage.CorrectionControlTim(mac,descriptor)) + { + LogEvent(SkyscraperContextEvent.TimCorrectionControl, String.Format("for participant {0}", mac.ToString())); + } + } + + private List networkLayerInfos; + public void OnNetworkLayerInfo(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid) + { + if (currentTimeForTim == null) + return; + if (!ScraperStorage.TestForTim(mac)) + { + LogEvent(SkyscraperContextEvent.Tim, String.Format("for participant {0}", mac.ToString())); + ScraperStorage.CreateTim(mac); + } + if (ScraperStorage.NetworkLayerInfoTim(mac,nlid,currentTimeForTim.Value)) + { + if (networkLayerInfos == null) + networkLayerInfos = new List(); + if (!networkLayerInfos.Contains(mac)) + { + LogEvent(SkyscraperContextEvent.TimNetworkLayerInfo, String.Format("for participant {0}", mac.ToString())); + networkLayerInfos.Add(mac); + } + } + } + + void InteractionChannelHandler.OnTransmissionModeSupport(ushort interactiveNetworkId, Tmst tmst) + { + byte[] knownTmst = ScraperStorage.GetTmst(interactiveNetworkId); + if (knownTmst == null) + { + LogEvent(SkyscraperContextEvent.TransmissionModeSupport, String.Format("Network ID = {0}, Modes = {1}", interactiveNetworkId, BitConverter.ToString(tmst.Modes))); + ScraperStorage.InsertTmst(interactiveNetworkId, tmst.Modes); + } + else if (Array.Equals(knownTmst,tmst.Modes)) + { + LogEvent(SkyscraperContextEvent.TransmissionModeUpdate,String.Format("Network ID = {0}, Old Modes = {2}, New Modes = {1}", interactiveNetworkId, BitConverter.ToString(tmst.Modes), BitConverter.ToString(knownTmst))); + ScraperStorage.UpdateTmst(interactiveNetworkId, tmst.Modes); + } + } + + public int GetRmtTransmissionStandard(ushort networkId) + { + return ScraperStorage.GetRmtTransmissionStandard(networkId); + } + + public void AutodetectionRuleOut(int pid, Contestant contestant, ProgramContext programContext) + { + LogEvent(SkyscraperContextEvent.StreamTypeAutodetection, String.Format("PID 0x{0:X4} probably isn't a {1}", pid, contestant.Tag)); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs new file mode 100644 index 0000000..d55b8a7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs @@ -0,0 +1,75 @@ +namespace skyscraper5.Skyscraper.Scraper +{ + public enum SkyscraperContextEvent + { + StartPacketProcessing, + NetworkPidFromPat, + ProgramMapPidFromPat, + OnNitNetwork, + OnNitNetworkUpdate, + OnNitTransportStream, + OnNitTransportStreamUpdate, + PmtEvent, + VpsData, + WssData, + OnSdtServiceUpdate, + OnSdtService, + BatBouquetUpdate, + BatBouquet, + BatTransportStream, + BatTransportStreamUpdate, + TotTime, + TdtTime, + EitEvent, + AitApplication, + FileArrival, + SubStreamFromObjectCarousel, + CaSystem, + UpdateNotification, + NotifyOfNewModule, + ModuleDownloadProgress, + ModuleDownloadComplete, + BeginRdsRecording, + RdsProgrammeServiceName, + RdsText, + RdsPty, + RdsTrafficInfoDetected, + Scte35Splice, + Compliance, + StationIdentification, + XaitDetected, + StreamTypeAutodetection, + RunningStatusChange, + AbortConditionMet, + Scte35TimeSignal, + T2MiPlpDetected, + IpMacNotification, + RelatedContent, + SpecialTsMode, + IpTraffic, + DocsisParticipant, + DocsisUpstreamChannel, + DocsisDownstreamChannel, + LearnedCmtsIp, + LocationMissing, + GotFramegrab, + Preamble, + PluginMessage, + LearnDns, + TerminalBurstTimePlan, + CorrectionMessage, + InteractionChannelError, + TransmissionModeSupport, + TransmissionModeUpdate, + RmtLinkage, + RmtTransportStream, + SuperframeComposition, + FrameComposition, + SatellitePosition, + Tim, + TimCorrection, + TimContentionControl, + TimCorrectionControl, + TimNetworkLayerInfo + } +} diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContextFactory.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContextFactory.cs new file mode 100644 index 0000000..58c1f53 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContextFactory.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper.Storage; + +namespace skyscraper5.Skyscraper.Scraper +{ + public class SkyscraperContextFactory + { + private SkyscraperContextFactory() + { + + } + + public static ISkyscraperContext CreateSkyscraper(ISkyscraperEventLogger eventLogger, IScraperStroage storage) + { + TsContext tsContext = new TsContext(); + SkyscraperContext result = new SkyscraperContext(tsContext, eventLogger, storage); + return result; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemScraperStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemScraperStorage.cs new file mode 100644 index 0000000..791b4e7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemScraperStorage.cs @@ -0,0 +1,1409 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; +using Newtonsoft.Json; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds.Messages; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.src.Skyscraper.Scraper.Storage.InMemory; +using skyscraper5.Teletext; +using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem +{ + internal class FilesystemScraperStorage : IScraperStroage + { + private readonly DirectoryInfo rootDirectory; + private readonly string importFilesKnownFilename; + private readonly JsonSerializerSettings jsonSerializerSettings; + + public FilesystemScraperStorage(DirectoryInfo rootDirectory) + { + this.rootDirectory = rootDirectory; + this.importFilesKnownFilename = Path.Combine(rootDirectory.FullName, "import_files_known.json"); + this.jsonSerializerSettings = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.Indented + }; + } + + public bool TestForNitNetwork(NitNetwork nitNetwork) + { + string combine = Path.Combine(rootDirectory.FullName, "NIT", nitNetwork.NetworkId.ToString(), "info.json"); + return File.Exists(combine); + } + + public void StoreNitNetwork(NitNetwork nitNetwork) + { + string combine = Path.Combine(rootDirectory.FullName, "NIT", nitNetwork.NetworkId.ToString(), "info.json"); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(nitNetwork, Formatting.Indented, jsonSerializerSettings)); + } + + public bool UpdateNitNetwork(NitNetwork nitNetwork) + { + string combine = Path.Combine(rootDirectory.FullName, "NIT", nitNetwork.NetworkId.ToString(), "info.json"); + FileInfo fi = new FileInfo(combine); + NitNetwork stored = JsonConvert.DeserializeObject(File.ReadAllText(fi.FullName)); + bool needsUpdate = stored.NeedsUpdate(nitNetwork); + if (needsUpdate) + StoreNitNetwork(nitNetwork); + return needsUpdate; + } + + public bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + string combine = Path.Combine(rootDirectory.FullName, "NIT", networkId.ToString(), String.Format("{0}.json", transportStream.TransportStreamId)); + return File.Exists(combine); + } + + public void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + string combine = Path.Combine(rootDirectory.FullName, "NIT", networkId.ToString(), String.Format("{0}.json", transportStream.TransportStreamId)); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(transportStream, Formatting.Indented, jsonSerializerSettings)); + } + + public bool UpdateNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + string combine = Path.Combine(rootDirectory.FullName, "NIT", networkId.ToString(), String.Format("{0}.json", transportStream.TransportStreamId)); + FileInfo fi = new FileInfo(combine); + NitTransportStream nitTransportStream = JsonConvert.DeserializeObject(File.ReadAllText(combine)); + if (nitTransportStream.NeedUpdate(nitTransportStream)) + { + StoreNitTransportStream(networkId, transportStream); + return true; + } + return false; + } + + public bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId) + { + string combine = Path.Combine(rootDirectory.FullName, "PAT", currentNetworkId.ToString(), currentTransportStreamId.ToString(), String.Format("{0}.n", programId)); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + return false; + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, pmtPid.ToString()); + return true; + } + + public bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping result) + { + string combine = Path.Combine(rootDirectory.FullName, "PMT", currentNetworkId.ToString(), + currentTransportStreamId.ToString(), String.Format("{0}.json", result.ProgramNumber)); + return File.Exists(combine); + } + + public bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + string combine = Path.Combine(rootDirectory.FullName, "PMT", currentNetworkId.ToString(), + currentTransportStreamId.ToString(), String.Format("{0}.json", mapping.ProgramNumber)); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + return false; + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(mapping, Formatting.Indented, jsonSerializerSettings)); + return true; + } + + public bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + ushort pageNo = magazine.HumanReadablePageNumber; + if (pageNo > 999) + return false; + + string combine = Path.Combine(rootDirectory.FullName, "Teletext", timestamp.Year.ToString(), timestamp.Month.ToString(), timestamp.Day.ToString(), timestamp.Hour.ToString(), networkId.ToString(), transportStreamId.ToString(), programNumber.ToString(), String.Format("{0}.txt",pageNo)); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + return false; + EnsureDirectoryExists(fi.Directory); + FileStream fileStream = fi.OpenWrite(); + magazine.WriteOut(fileStream); + fileStream.Flush(); + fileStream.Close(); + return true; + } + + public bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + string combine = Path.Combine(rootDirectory.FullName, "SDT", originalNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.json", sdtService.ServiceId.ToString())); + return File.Exists(combine); + } + + public bool TestForTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, + DateTime timestamp) + { + ushort pageNo = magazine.HumanReadablePageNumber; + if (pageNo > 999) + return false; + + string combine = Path.Combine(rootDirectory.FullName, "Teletext", timestamp.Year.ToString(), timestamp.Month.ToString(), timestamp.Day.ToString(), timestamp.Hour.ToString(), networkId.ToString(), transportStreamId.ToString(), programNumber.ToString(), String.Format("{0}.txt", pageNo)); + return File.Exists(combine); + } + + public void MarkTeletextPageAsKnown(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, + DateTime timestamp) + { + StoreTeletextPage(networkId, transportStreamId, programNumber, magazine, timestamp); + } + + public bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + string combine = Path.Combine(rootDirectory.FullName, "SDT", originalNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.json", sdtService.ServiceId.ToString())); + SdtService stored = JsonConvert.DeserializeObject(File.ReadAllText(combine)); + if (stored.NeedsUpdate(sdtService)) + { + StoreSdtService(transportStreamId, originalNetworkId, sdtService); + return true; + } + return false; + } + + public void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + string combine = Path.Combine(rootDirectory.FullName, "SDT", originalNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.json", sdtService.ServiceId.ToString())); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(combine,JsonConvert.SerializeObject(sdtService, Formatting.Indented, jsonSerializerSettings)); + } + + public bool TestForBatBouquet(BatBouquet batBouquet) + { + string combine = Path.Combine(rootDirectory.FullName, "BAT", batBouquet.BouquetId.ToString(), "info.json"); + return File.Exists(combine); + } + + public bool UpdateBatBouquet(BatBouquet batBouquet) + { + if (!TestForBatBouquet(batBouquet)) + { + StoreBatBouquet(batBouquet); + return true; + } + string combine = Path.Combine(rootDirectory.FullName, "BAT", batBouquet.BouquetId.ToString(), "info.json"); + BatBouquet stored = JsonConvert.DeserializeObject(File.ReadAllText(combine)); + if (stored.NeedUpdate(batBouquet)) + { + StoreBatBouquet(batBouquet); + return true; + } + return false; + } + + public void StoreBatBouquet(BatBouquet batBouquet) + { + string combine = Path.Combine(rootDirectory.FullName, "BAT", batBouquet.BouquetId.ToString(), "info.json"); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(batBouquet, Formatting.Indented, jsonSerializerSettings)); + } + + public bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + string combine = Path.Combine(rootDirectory.FullName, "BAT", batBouquetBouquetId.ToString(), child.OriginalNetworkId.ToString(), String.Format("{0}.json", child.TransportStreamId)); + return File.Exists(combine); + } + + public bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + string combine = Path.Combine(rootDirectory.FullName, "BAT", batBouquetBouquetId.ToString(), child.OriginalNetworkId.ToString(), String.Format("{0}.json", child.TransportStreamId)); + FileInfo fi = new FileInfo(combine); + if (!fi.Exists) + { + StoreBatTransportStream(batBouquetBouquetId, child); + return true; + } + BatTransportStream stored = JsonConvert.DeserializeObject(File.ReadAllText(combine)); + if (stored.NeedsUpdate(child)) + { + StoreBatTransportStream(batBouquetBouquetId, child); + return true; + } + return false; + } + + public void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + string combine = Path.Combine(rootDirectory.FullName, "BAT", batBouquetBouquetId.ToString(), child.OriginalNetworkId.ToString(), String.Format("{0}.json", child.TransportStreamId)); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(child, Formatting.Indented, jsonSerializerSettings)); + } + + public bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime utcTime, + LocalTimeOffsetDescriptor ltod) + { + string combine = Path.Combine(rootDirectory.FullName, "TOT", currentNetworkId.ToString(), String.Format("{0}.n", currentTransportStreamId)); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + { + long l = long.Parse(File.ReadAllText(fi.FullName)); + DateTime savedDate = new DateTime(l); + if (utcTime > savedDate) + { + File.WriteAllText(fi.FullName, utcTime.Ticks.ToString()); + return true; + } + } + else + { + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, utcTime.Ticks.ToString()); + combine = Path.Combine(rootDirectory.FullName, "TOT", currentNetworkId.ToString(), String.Format("{0}.json", currentTransportStreamId)); + File.WriteAllText(combine, JsonConvert.SerializeObject(ltod, Formatting.Indented, jsonSerializerSettings)); + return true; + } + return false; + } + + public bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + string combine = Path.Combine(rootDirectory.FullName, "TDT", currentNetworkId.ToString(), String.Format("{0}.n", currentTransportStreamId)); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + { + long l = long.Parse(File.ReadAllText(fi.FullName)); + DateTime savedDate = new DateTime(l); + if (utcTime > savedDate) + { + File.WriteAllText(fi.FullName, utcTime.Ticks.ToString()); + return true; + } + } + else + { + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, utcTime.Ticks.ToString()); + return true; + } + return false; + } + + private void EnsureDirectoryExists(DirectoryInfo di) + { + if (di.Exists) + return; + EnsureDirectoryExists(di.Parent); + di.Create(); + di.Refresh(); + } + + public bool StoreEitEvent(EitEvent eitEvent) + { + string path = Path.Combine(rootDirectory.FullName, "EIT", eitEvent.StartTime.Year.ToString(), + eitEvent.StartTime.Month.ToString(), eitEvent.StartTime.Day.ToString(), + eitEvent.OriginalNetworkId.ToString(), eitEvent.TransportStreamId.ToString(), + eitEvent.ServiceId.ToString(), String.Format("{0}.json", eitEvent.EventId)); + FileInfo outFileInfo = new FileInfo(path); + if (outFileInfo.Exists) + return false; + EnsureDirectoryExists(outFileInfo.Directory); + File.WriteAllText(outFileInfo.FullName, JsonConvert.SerializeObject(eitEvent, Formatting.Indented, jsonSerializerSettings)); + return true; + } + + public bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier) + { + string combine = Path.Combine(rootDirectory.FullName, "AIT", aitApplicationApplicationIdentifier.OrganisationId.ToString(), String.Format("{0}.json",aitApplicationApplicationIdentifier.ApplicationId.ToString())); + return File.Exists(combine); + } + + public void StoreAitApplication(AitApplication aitApplication) + { + string combine = Path.Combine(rootDirectory.FullName, "AIT", aitApplication.ApplicationIdentifier.OrganisationId.ToString(), String.Format("{0}.json", aitApplication.ApplicationIdentifier.ApplicationId)); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(aitApplication, Formatting.Indented, jsonSerializerSettings)); + } + + public bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId) + { + string combine = Path.Combine(rootDirectory.FullName, "DSM-CC_Objects", networkId.ToString(), transportStreamId.ToString(), vfsFile.SourcePid.ToString(), vfsFile.ToString().Substring(1)); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + return false; + EnsureDirectoryExists(fi.Directory); + File.WriteAllBytes(fi.FullName,vfsFile.FileContent); + return true; + } + + public bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid) + { + string combine = Path.Combine(rootDirectory.FullName, "CAT", currentNetworkId.ToString(), currentTransportStreamId.ToString(), String.Format("{0}.json", caDescriptorCaPid)); + return File.Exists(combine); + } + + public void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + string combine = Path.Combine(rootDirectory.FullName, "CAT", currentNetworkId.ToString(), currentTransportStreamId.ToString(), String.Format("{0}.json", caDescriptor.CaPid)); + FileInfo fi = new FileInfo(combine); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(caDescriptor, Formatting.Indented, jsonSerializerSettings)); + } + + public bool TestForUpdateNotification(int hashCode, UpdateNotificationGroup common) + { + string combine = Path.Combine(rootDirectory.FullName, "UNT", common.Oui, "index.json"); + return File.Exists(combine); + } + + public void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + string combine = Path.Combine(rootDirectory.FullName, "UNT", common.Oui, compatibility.Version.ToString(), String.Format("{0}.json", platform.ToString())); + FileInfo platformFi = new FileInfo(combine); + EnsureDirectoryExists(platformFi.Directory); + if (!platformFi.Exists) + File.WriteAllText(platformFi.FullName, JsonConvert.SerializeObject(platform, Formatting.Indented, jsonSerializerSettings)); + else + return; + + combine = Path.Combine(rootDirectory.FullName, "UNT", common.Oui, compatibility.Version.ToString(), "index.json"); + FileInfo compatFi = new FileInfo(combine); + if (!compatFi.Exists) + File.WriteAllText(compatFi.FullName, JsonConvert.SerializeObject(compatibility, Formatting.Indented, jsonSerializerSettings)); + else + return; + + combine = Path.Combine(rootDirectory.FullName, "UNT", common.Oui, "index.json"); + FileInfo commonFi = new FileInfo(combine); + if (!commonFi.Exists) + File.WriteAllText(commonFi.FullName, JsonConvert.SerializeObject(common, Formatting.Indented, jsonSerializerSettings)); + } + + public void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion, Stream result) + { + string combine = Path.Combine(rootDirectory.FullName, "DSM-CC_Data", currentNetworkId.ToString(), currentTransportStreamId.ToString(), elementaryPid.ToString(), String.Format("{0}_V{1}.bin", moduleId, moduleVersion)); + FileInfo fi = new FileInfo(combine); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + FileStream outputStream = fi.OpenWrite(); + result.CopyTo(outputStream); + outputStream.Close(); + outputStream.Dispose(); + } + } + + public bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + string combine = Path.Combine(rootDirectory.FullName, "RDS", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), "marker.bin"); + return File.Exists(combine); + } + + public void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + string combine = Path.Combine(rootDirectory.FullName, "RDS", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), "marker.bin"); + FileInfo fi = new FileInfo(combine); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + FileStream fileStream = fi.Open(FileMode.CreateNew); + fileStream.Flush(true); + fileStream.Close(); + } + } + + public bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, + string programmeService2) + { + string combine = Path.Combine(rootDirectory.FullName, "RDS", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), "ps.txt"); + FileInfo fi = new FileInfo(combine); + string alreadyKnown = ""; + if (fi.Exists) + { + alreadyKnown = File.ReadAllText(combine); + } + + if (!alreadyKnown.Equals(programmeService2)) + { + File.WriteAllText(combine, programmeService2); + return true; + } + return false; + } + + public bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text) + { + string combine = Path.Combine(rootDirectory.FullName, "RDS", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), "rt.txt"); + FileInfo fi = new FileInfo(combine); + string alreadyKnown = ""; + if (fi.Exists) + { + alreadyKnown = File.ReadAllText(combine); + } + + if (!alreadyKnown.Equals(text)) + { + File.WriteAllText(combine, text); + return true; + } + return false; + } + + public bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, PTY.ProgrammeTypeCodes pty) + { + string combine = Path.Combine(rootDirectory.FullName, "RDS", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), "pty.bin"); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + { + byte[] readAllBytes = File.ReadAllBytes(combine); + if (readAllBytes[0] != (byte)pty) + { + byte[] tmp = new byte[] { (byte)pty }; + File.WriteAllBytes(combine, tmp); + return true; + } + return false; + } + else + { + byte[] tmp = new byte[] { (byte)pty }; + File.WriteAllBytes(combine, tmp); + return true; + } + } + + public bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + string combine = Path.Combine(rootDirectory.FullName, "RDS", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), "ti.bin"); + FileInfo fi = new FileInfo(combine); + if (!fi.Exists) + { + FileStream fileStream = fi.OpenWrite(); + fileStream.Flush(true); + fileStream.Close(); + return true; + } + return false; + } + + public bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + string path = Path.Combine(rootDirectory.FullName, "SCTE-35",currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), String.Format("{0}.json", spliceInsert.SpliceEventId)); + return File.Exists(path); + } + + public void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + string path = Path.Combine(rootDirectory.FullName, "SCTE-35", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), String.Format("{0}.json", spliceInsert.SpliceEventId)); + FileInfo fi = new FileInfo(path); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(spliceInsert, Formatting.Indented, jsonSerializerSettings)); + } + } + + public bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + string path = Path.Combine(rootDirectory.FullName, "TSDT", currentNetworkId.ToString(), currentTransportStreamId.ToString(), String.Format("{0}.5ds",compliance)); + return File.Exists(path); + } + + public void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + string path = Path.Combine(rootDirectory.FullName, "TSDT", currentNetworkId.ToString(), currentTransportStreamId.ToString(), String.Format("{0}.5ds", compliance)); + FileInfo fi = new FileInfo(path); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + fi.Create().Close(); + } + } + + public bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string stationIdentification) + { + string path = Path.Combine(rootDirectory.FullName, "TSDT", currentNetworkId.ToString(), String.Format("{0}.999", currentTransportStreamId)); + FileInfo fi = new FileInfo(path); + if (fi.Exists) + { + string text = File.ReadAllText(fi.FullName); + if (text.Equals(stationIdentification)) + return false; + } + else + { + EnsureDirectoryExists(fi.Directory); + } + + File.WriteAllText(fi.FullName, stationIdentification); + return true; + } + + + public bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, + byte moduleVersion) + { + string combine = Path.Combine(rootDirectory.FullName, "DSM-CC_Data", currentNetworkId.ToString(), currentTransportStreamId.ToString(), elementaryPid.ToString(), String.Format("{0}_V{1}.bin", moduleId, moduleVersion)); + return !File.Exists(combine); + } + + public void StoreDsmCcDoItNowEvent(DateTime timestamp, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + string combine = Path.Combine(rootDirectory.FullName, "DSM-CC_Events", currentNetworkId.ToString(), currentTransportStreamId.ToString(), pid.ToString(), String.Format("event{0}_{1}.bin", descriptorListStreamEventDescriptor.EventId, timestamp.ToUnixTime())); + FileInfo fi = new FileInfo(combine); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + File.WriteAllBytes(combine, descriptorListStreamEventDescriptor.PrivateData); + } + } + + public bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, + RunningStatus runningStatus, DateTime currentTime) + { + string combine = Path.Combine(rootDirectory.FullName, "RST", originalNetworkId.ToString(), transportStreamId.ToString(), serviceId.ToString(), eventId.ToString(), String.Format("{0}.999", runningStatus.ToString())); + FileInfo fi = new FileInfo(combine); + if (fi.Exists) + return false; + + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, currentTime.ToUnixTime().ToString()); + return true; + } + + + + public void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, TimeSignal timeSignal) + { + string path = Path.Combine(rootDirectory.FullName, "SCTE-35", currentNetworkId.ToString(), currentTransportStreamId.ToString(), programNumber.ToString(), String.Format("TimeSignal_{0}.json", currentTime.ToUnixTime())); + FileInfo fi = new FileInfo(path); + if (fi.Exists) + return; + fi.Directory.EnsureExists(); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(timeSignal, Formatting.Indented, jsonSerializerSettings)); + } + + public bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid) + { + string path = Path.Combine(rootDirectory.FullName, "0-Framegrabs", currentNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.jpg", mappingProgramNumber)); + return File.Exists(path); + } + + + public void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort pid, byte[] imageData) + { + string path = Path.Combine(rootDirectory.FullName, "0-Framegrabs", currentNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.jpg", mappingProgramNumber)); + FileInfo fi = new FileInfo(path); + fi.Directory.EnsureExists(); + File.WriteAllBytes(path, imageData); + } + + public bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int locationId) + { + string path = Path.Combine(rootDirectory.FullName, "DOCSIS", "UpstreamChannels", String.Format("Location_{0}", locationId), mmmSource.ToString(), mmmFrequency.ToString() + ".json"); + return File.Exists(path); + } + + public void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int locationId) + { + string fname = Path.Combine(rootDirectory.FullName, "DOCSIS", "UpstreamChannels", String.Format("Location_{0}", locationId), mmm.Source.ToString(), mmm.Frequency.ToString() + ".json"); + FileInfo fi = new FileInfo(fname); + fi.Directory.EnsureExists(); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(mmm, Formatting.Indented, jsonSerializerSettings)); + } + + public bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int locationId) + { + string fname = Path.Combine(rootDirectory.FullName, "DOCSIS", "DownstreamChannels", String.Format("Location_{0}", locationId), physicalAddress.ToString(), downstreamActiveChannel.Frequency.Value.ToString() + ".json"); + return File.Exists(fname); + } + + public void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int locationId) + { + string fname = Path.Combine(rootDirectory.FullName, "DOCSIS", "DownstreamChannels", String.Format("Location_{0}",locationId), physicalAddress.ToString(), downstreamActiveChannel.Frequency.Value.ToString() + ".json"); + FileInfo fi = new FileInfo(fname); + fi.Directory.EnsureExists(); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(downstreamActiveChannel, Formatting.Indented, jsonSerializerSettings)); + } + + public bool SetCmtsIp(PhysicalAddress physicalAddress, IPAddress ip) + { + string fname = Path.Combine(rootDirectory.FullName, "DOCSIS", "CMTS_IP", physicalAddress.ToString() + ".txt"); + FileInfo fi = new FileInfo(fname); + IPAddress ondisk = IPAddress.None; + if (fi.Exists) + { + ondisk = new IPAddress(File.ReadAllBytes(fname)); + } + + if (!ondisk.Equals(ip)) + { + fi.Directory.EnsureExists(); + File.WriteAllBytes(fi.FullName, ip.GetAddressBytes()); + return true; + } + + return false; + } + + private DsmCcModuleBlacklist dsmCcBlacklist; + public bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + if (dsmCcBlacklist == null) + { + string fname = Path.Combine(rootDirectory.FullName, "dsmcc_blacklist.csv"); + FileInfo fi = new FileInfo(fname); + dsmCcBlacklist = new DsmCcModuleBlacklist(fi); + } + + return dsmCcBlacklist.IsBlacklisted(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + } + + public int? GetCurrentLocationId() + { + string fname = Path.Combine(rootDirectory.FullName, "docsis_location.txt"); + FileInfo fi = new FileInfo(fname); + if (fi.Exists) + { + string readAllText = File.ReadAllText(fi.FullName); + return Int32.Parse(readAllText); + } + + return null; + } + + public void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation) + { + string fname = Path.Combine(rootDirectory.FullName, "DOCSIS", "Participants", String.Format("Location_{0}", currentLocation), pa.ToString() + ".tmp"); + FileInfo fi = new FileInfo(fname); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + fi.Create().Close(); + } + } + + public bool TestForIpMacNotification(IpMacNotification notification) + { + string path = Path.Combine(rootDirectory.FullName, "INT", String.Format("{0}.json", notification.PlatformId)); + return File.Exists(path); + } + + public void StoreIpMacNotification(IpMacNotification notification) + { + string path = Path.Combine(rootDirectory.FullName, "INT", String.Format("{0}.json", notification.PlatformId)); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(notification, Formatting.Indented, jsonSerializerSettings)); + } + + private KnownTsMemory knownTss; + private List importFilesKnown; + + public bool ImportFileKnown(FileInfo fi) + { + if (knownTss == null) + knownTss = new KnownTsMemory(importFilesKnownFilename); + return knownTss.ImportFileKnown(fi); + } + + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan ts, int tstype) + { + if (knownTss == null) + knownTss = new KnownTsMemory(importFilesKnownFilename); + knownTss.ImportMarkFileAsKnown(fi, ts, tstype); + } + + public DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid) + { + string path = Path.Combine(rootDirectory.FullName, "T2-MI", currentNetworkId.ToString(), currentTransportStreamId.ToString(),String.Format("{0}.timestamp", pid)); + if (!File.Exists(path)) + { + return DateTime.MinValue; + } + + string readAllText = File.ReadAllText(path); + long l = long.Parse(readAllText); + DateTime result = new DateTime(l); + return result; + } + + public void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + string path = Path.Combine(rootDirectory.FullName, "T2-MI", currentNetworkId.ToString(), currentTransportStreamId.ToString(), String.Format("{0}.timestamp", pid)); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, resolveTime.Ticks.ToString()); + } + + private string BuildRctIdentifier(RctLinkInfo rctLinkInfo) + { + char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); + StringBuilder sb = new StringBuilder(); + if (rctLinkInfo.LinkType == RctLinkInfo.LinkTypeValue.Uri) + { + sb.Append("A_"); + char[] charArray = rctLinkInfo.MediaUri.ToCharArray(); + foreach (char c in charArray) + { + if (!invalidFileNameChars.Contains(c)) + sb.Append(c); + else + sb.Append('_'); + } + } + else + { + throw new NotImplementedException(rctLinkInfo.LinkType.ToString()); + } + + sb.Append(".json"); + return sb.ToString(); + } + + public bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + string path = Path.Combine(rootDirectory.FullName, "RCT", lEvent.StartTime.Year.ToString(), lEvent.StartTime.Month.ToString(), + lEvent.StartTime.Day.ToString(), lEvent.OriginalNetworkId.ToString(), lEvent.TransportStreamId.ToString(), lEvent.ServiceId.ToString(), + lEvent.EventId.ToString(), BuildRctIdentifier(rctLinkInfo)); + return File.Exists(path); + } + + public void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + string path = Path.Combine(rootDirectory.FullName, "RCT", lEvent.StartTime.Year.ToString(), lEvent.StartTime.Month.ToString(), + lEvent.StartTime.Day.ToString(), lEvent.OriginalNetworkId.ToString(), lEvent.TransportStreamId.ToString(), lEvent.ServiceId.ToString(), + lEvent.EventId.ToString(), BuildRctIdentifier(rctLinkInfo)); + FileInfo fi = new FileInfo(path); + if (!fi.Exists) + { + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(rctLinkInfo, Formatting.Indented)); + } + } + + public List UiSatellitesListAll() + { + string path = Path.Combine(rootDirectory.FullName, "satellites.json"); + FileInfo fi = new FileInfo(path); + if (!fi.Exists) + return new List(); + else + { + return JsonConvert.DeserializeObject>(File.ReadAllText(fi.FullName)); + } + } + + public void UiSatellitesAdd(SatellitePosition newPosition) + { + string path = Path.Combine(rootDirectory.FullName, "satellites.json"); + FileInfo fi = new FileInfo(path); + List satellites; + if (!fi.Exists) + { + satellites = new List(); + } + else + { + satellites = JsonConvert.DeserializeObject>(File.ReadAllText(fi.FullName)); + } + + satellites.Add(newPosition); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(satellites, Formatting.Indented)); + } + + public void UiSatellitesDelete(SatellitePosition satellitePosition) + { + string path = Path.Combine(rootDirectory.FullName, "satellites.json"); + FileInfo fi = new FileInfo(path); + List satellites = JsonConvert.DeserializeObject>(File.ReadAllText(fi.FullName)); + + satellites.RemoveAll(x => x.Checksum == satellitePosition.Checksum); + + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(satellites, Formatting.Indented)); + } + + public bool UiTunerTestFor(TunerMetadata tuner) + { + string fname = BitConverter.ToString(tuner.MacAddress.GetAddressBytes()); + string path = Path.Combine(rootDirectory.FullName, "0-UI", "Tuners", fname + ".json"); + return File.Exists(path); + } + + public void UiTunerUpdate(TunerMetadata tuner) + { + string fname = BitConverter.ToString(tuner.MacAddress.GetAddressBytes()); + string path = Path.Combine(rootDirectory.FullName, "0-UI", "Tuners", fname + ".json"); + File.WriteAllText(path, JsonConvert.SerializeObject(tuner, Formatting.Indented)); + } + + public void UiTunerInsert(TunerMetadata tuner) + { + string fname = BitConverter.ToString(tuner.MacAddress.GetAddressBytes()); + string path = Path.Combine(rootDirectory.FullName, "0-UI", "Tuners", fname + ".json"); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(tuner, Formatting.Indented)); + } + + public void UiTunerGetConfiguration(TunerMetadata foundTuner) + { + string fname = BitConverter.ToString(foundTuner.MacAddress.GetAddressBytes()); + string path = Path.Combine(rootDirectory.FullName, "0-UI", "Tuners", fname + ".json"); + TunerMetadata copy = JsonConvert.DeserializeObject(File.ReadAllText(path)); + foundTuner.DiseqcType = copy.DiseqcType; + foundTuner.Satellites = new int[4]; + foundTuner.Satellites[0] = copy.Satellites[0]; + foundTuner.Satellites[1] = copy.Satellites[1]; + foundTuner.Satellites[2] = copy.Satellites[2]; + foundTuner.Satellites[3] = copy.Satellites[3]; + foundTuner.Lnbs = new int[4]; + foundTuner.Lnbs[0] = copy.Lnbs[0]; + foundTuner.Lnbs[1] = copy.Lnbs[1]; + foundTuner.Lnbs[2] = copy.Lnbs[2]; + foundTuner.Lnbs[3] = copy.Lnbs[3]; + } + + public HeadlessJob GetQueuedJob() + { + throw new NotImplementedException(); + } + + public void SetQueuedJobComplete(HeadlessJob headlessJob) + { + throw new NotImplementedException(); + } + + public void WaitForCompletion() + { + } + + private int uiVersion; + public void UiSetVersion(int version) + { + this.uiVersion = version; + } + + public bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + if (!currentNetworkId.HasValue) + return true; + if (!currentTransportStreamId.HasValue) + return true; + + string txName = String.Format("{0}_TX{1}", relatedPid, txIdentifier); + + string path = Path.Combine(rootDirectory.FullName, "T2-MI", currentNetworkId.ToString(), currentTransportStreamId.ToString(), txName, "test.bin"); + return File.Exists(path); + } + + public void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + if (!currentNetworkId.HasValue) + return; + if (!currentTransportStreamId.HasValue) + return; + + string txName = String.Format("{0}_TX{1}", relatedPid, txIdentifier); + + string path = Path.Combine(rootDirectory.FullName, "T2-MI", currentNetworkId.ToString(), currentTransportStreamId.ToString(), txName, "test.bin"); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + fi.OpenWrite().Close(); + } + + public void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset) + { + if (!currentNetworkId.HasValue) + return; + if (!currentTransportStreamId.HasValue) + return; + + string txName = String.Format("{0}_TX{1}", relatedPid, txIdentifier); + + string path = Path.Combine(rootDirectory.FullName, "T2-MI", currentNetworkId.ToString(), currentTransportStreamId.ToString(), txName, "timeoff.set"); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + File.WriteAllText(path, timeOffset.ToString()); + } + + public List UiLnbTypesListAll() + { + string path = Path.Combine(rootDirectory.FullName, "0-UI", "LnbTypes"); + DirectoryInfo di = new DirectoryInfo(path); + if (!di.Exists) + { + return new List(); + } + + List result = new List(); + FileInfo[] fileInfos = di.GetFiles("*.json"); + foreach (FileInfo fileInfo in fileInfos) + { + string fid = Path.GetFileNameWithoutExtension(fileInfo.Name); + if (!fid.IsNaturalNumeric()) + continue; + int iid = int.Parse(fid); + + string json = File.ReadAllText(fileInfo.FullName); + LnbType deserializeObject = JsonConvert.DeserializeObject(json); + if (deserializeObject.Id != iid) + continue; + if (result.Contains(deserializeObject)) + continue; + result.Add(deserializeObject); + } + + return result; + } + + private int GetNextJsonFileNumber(DirectoryInfo di) + { + if (!di.Exists) + { + EnsureDirectoryExists(di); + return 1; + } + + int result = 0; + foreach (FileInfo fileInfo in di.GetFiles("*.json")) + { + string fname = Path.GetFileNameWithoutExtension(fileInfo.Name); + if (!fname.IsNaturalNumeric()) + continue; + int iname = Int32.Parse(fname); + if (iname > result) + result = iname; + } + + result++; + return result; + } + + public void UiLnbTypesAdd(LnbType defaultLnbType) + { + string path = Path.Combine(rootDirectory.FullName, "0-UI", "LnbTypes"); + DirectoryInfo di = new DirectoryInfo(path); + int fileId = GetNextJsonFileNumber(di); + defaultLnbType.Id = fileId; + defaultLnbType.DateAdded = DateTime.Now; + + path = Path.Combine(rootDirectory.FullName, "0-UI", "LnbTypes", fileId.ToString() + ".json"); + string json = JsonConvert.SerializeObject(defaultLnbType, jsonSerializerSettings); + File.WriteAllText(path, json); + } + + public List UiDishTypesListAll() + { + string path = Path.Combine(rootDirectory.FullName, "0-UI", "DishTypes"); + DirectoryInfo di = new DirectoryInfo(path); + if (!di.Exists) + { + return new List(); + } + + List result = new List(); + FileInfo[] fileInfos = di.GetFiles("*.json"); + foreach (FileInfo fileInfo in fileInfos) + { + string fid = Path.GetFileNameWithoutExtension(fileInfo.Name); + if (!fid.IsNaturalNumeric()) + continue; + int iid = int.Parse(fid); + + string json = File.ReadAllText(fileInfo.FullName); + DishType deserializeObject = JsonConvert.DeserializeObject(json); + if (deserializeObject.Id != iid) + continue; + if (result.Contains(deserializeObject)) + continue; + result.Add(deserializeObject); + } + + return result; + } + + public void UiDishTypesAdd(DishType defaultDishType) + { + string path = Path.Combine(rootDirectory.FullName, "0-UI", "DishTypes"); + DirectoryInfo di = new DirectoryInfo(path); + int fileId = GetNextJsonFileNumber(di); + defaultDishType.Id = fileId; + defaultDishType.DateAdded = DateTime.Now; + + path = Path.Combine(rootDirectory.FullName, "0-UI", "DishTypes", fileId.ToString() + ".json"); + string json = JsonConvert.SerializeObject(defaultDishType, jsonSerializerSettings); + File.WriteAllText(path, json); + } + + public object[] GetPluginConnector() + { + return new object[] { rootDirectory }; + } + + private bool pinged; + public void Ping() + { + //Test if we can read + string path = Path.Combine(rootDirectory.FullName, "test_write.bin"); + FileInfo testWriteFileInfo = new FileInfo(path); + if (!testWriteFileInfo.Exists) + { + byte[] randomBuffer = new byte[4096]; + new Random().NextBytes(randomBuffer); + File.WriteAllBytes(testWriteFileInfo.FullName, randomBuffer); + } + + //Test if we can write + byte[] readBack = File.ReadAllBytes(path); + + //Touch the ping file + if (!pinged) + { + path = Path.Combine(rootDirectory.FullName, "ping.bin"); + FileStream fileStream = File.OpenWrite(path); + BinaryWriter bw = new BinaryWriter(fileStream); + bw.Write(Guid.NewGuid().ToByteArray()); + bw.Write(Environment.MachineName); + bw.Write(Environment.UserName); + bw.Write(DateTime.Now.ToUnixTime()); + + NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + bw.Write(allNetworkInterfaces.Length); + foreach (NetworkInterface allNetworkInterface in allNetworkInterfaces) + { + byte[] macBytes = allNetworkInterface.GetPhysicalAddress().GetAddressBytes(); + int macLength = macBytes.Length; + if (macLength > Byte.MaxValue) + macLength = 0; + + bw.Write((uint)1213222739); + bw.Write((byte)macLength); + bw.Write(macBytes, 0, macLength); + } + bw.Flush(); + fileStream.Close(); + pinged = true; + } + } + + public IEnumerable> SelectAllPmt() + { + throw new NotImplementedException(); + } + + public SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber) + { + throw new NotImplementedException(); + } + + public IEnumerable> SelectAllSdt() + { + throw new NotImplementedException(); + } + + public void BeamsDisableAll() + { + throw new NotImplementedException(); + } + + public void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp) + { + throw new NotImplementedException(); + } + + public void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, + string getPolygonString, string id) + { + throw new NotImplementedException(); + } + + public bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string id) + { + throw new NotImplementedException(); + } + + public void BeamsDisableSpecific(int databasePointerId, float databasePointerSatpos, string databasePointerName, + DateTime databasePointerBeamsProcessTimestamp) + { + throw new NotImplementedException(); + } + + public IEnumerable BeamsSelectEnabled() + { + throw new NotImplementedException(); + } + + public List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp) + { + throw new NotImplementedException(); + } + + public void InsertBlindscanJob(DbBlindscanJob jobInDb) + { + if (jobInDb.DateAdded == DateTime.MinValue) + jobInDb.DateAdded = DateTime.Now; + string path = Path.Combine(rootDirectory.FullName, "0-Blindscan",jobInDb.SatPosition.Checksum.ToString(), jobInDb.JobGuid.ToString(), "index.json"); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + + string json = JsonConvert.SerializeObject(jobInDb, jsonSerializerSettings); + File.WriteAllText(path, json); + } + + public void UpdateJobState(DbBlindscanJob jobInDb) + { + InsertBlindscanJob(jobInDb); + } + + public void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, + SearchResult2 searchResult2) + { + string freq = satellite ? String.Format("{0}_{1}", searchResult.Freq, searchResult.Pol) : String.Format("{0}", searchResult2); + string path = Path.Combine(rootDirectory.FullName, "0-Blindscan", jobInDb.SatPosition.Checksum.ToString(), jobInDb.JobGuid.ToString(), freq + ".json"); + string json = JsonConvert.SerializeObject(satellite ? searchResult : searchResult2, jsonSerializerSettings); + File.WriteAllText(path, json); + } + + public void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, + BlindscanResultState blindscanResultState, SearchResult2 searchResult2) + { + InsertSearchResult(jobInDb, satellite, searchResult, searchResult.Pol, searchResult2); + } + + public void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1, + SearchResult2 resultSr2, HumanReadableService humanReadableService) + { + string freq = resultSatellite ? String.Format("{0}_{1}", resultSr1.Freq, resultSr1.Pol) : String.Format("{0}", resultSr2); + string jsonName = String.Format("{0}.json", humanReadableService.ServiceId); + string path = Path.Combine(rootDirectory.FullName, "0-Blindscan", jobInDb.SatPosition.Checksum.ToString(), jobInDb.JobGuid.ToString(), freq, jsonName); + FileInfo fi = new FileInfo(path); + EnsureDirectoryExists(fi.Directory); + string json = JsonConvert.SerializeObject(humanReadableService, jsonSerializerSettings); + File.WriteAllText(path, json); + } + + public bool TestForIncompleteJob() + { + //TODO: check whether this is correct in the end + string fileName = Path.Combine(rootDirectory.FullName, "blscan_in_progress.json"); + return File.Exists(fileName); + } + + public DbBlindscanJob GetPastBlindscanJob(long offset) + { + throw new NotImplementedException(); + } + + public void DeleteBlindscanJob(Guid guid) + { + throw new NotImplementedException(); + } + + public void DeleteBlindscanResults(Guid jobGuid, int i) + { + throw new NotImplementedException(); + } + + public void MoveBlScanResultsToAnotherJob(Guid jobGuid1, Guid jobGuid2, int j) + { + throw new NotImplementedException(); + } + + public void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value) + { + string fname = Path.Combine(rootDirectory.FullName, "dsmcc_blacklist.csv"); + FileInfo fi = new FileInfo(fname); + + if (dsmCcBlacklist == null) + { + dsmCcBlacklist = new DsmCcModuleBlacklist(fi); + } + if (dsmCcBlacklist.ListPath == null) + dsmCcBlacklist.ListPath = fi; + dsmCcBlacklist.AddFailedModule(key.CurrentNetworkId, key.CurrentTransportStreamId, key.ElementaryPid, key.ModuleId, key.ModuleVersion, value); + } + + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId) + { + throw new NotImplementedException(); + } + + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp) + { + throw new NotImplementedException(); + } + + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId) + { + throw new NotImplementedException(); + } + + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp) + { + throw new NotImplementedException(); + } + + public bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + throw new NotImplementedException(); + } + + public void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + throw new NotImplementedException(); + } + + public int GetRmtTransmissionStandard(ushort networkId) + { + throw new NotImplementedException(); + } + + public byte[] GetTmst(ushort interactiveNetworkId) + { + throw new NotImplementedException(); + } + + public void InsertTmst(ushort interactiveNetworkId, byte[] modes) + { + throw new NotImplementedException(); + } + + public void UpdateTmst(ushort interactiveNetworkId, byte[] modes) + { + throw new NotImplementedException(); + } + + public bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + throw new NotImplementedException(); + } + + public void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + throw new NotImplementedException(); + } + + public bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + throw new NotImplementedException(); + } + + public void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + throw new NotImplementedException(); + } + + public bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + throw new NotImplementedException(); + } + + public void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + throw new NotImplementedException(); + } + + public bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame) + { + throw new NotImplementedException(); + } + + public void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame) + { + throw new NotImplementedException(); + } + + public bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + throw new NotImplementedException(); + } + + public void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + throw new NotImplementedException(); + } + + public bool TestForTim(PhysicalAddress mac) + { + throw new NotImplementedException(); + } + + public void CreateTim(PhysicalAddress mac) + { + throw new NotImplementedException(); + } + + public bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + throw new NotImplementedException(); + } + + public bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew) + { + throw new NotImplementedException(); + } + + public bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor) + { + throw new NotImplementedException(); + } + + public bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamped) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDbBlindscanJobs() + { + throw new NotImplementedException(); + } + + public IReadOnlyList ListImportFileByTag1(int tag1) + { + throw new NotImplementedException(); + } + + public long DnsCountA() + { + throw new NotImplementedException(); + } + + public string DnsIpToName(IPAddress source) + { + throw new NotImplementedException(); + } + + public bool TestForIp(IPAddress iP) + { + throw new NotImplementedException(); + } + + public void RememberDnsRecord(DnsRecord record) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemScraperStorageFactory.cs b/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemScraperStorageFactory.cs new file mode 100644 index 0000000..ce8c5ec --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemScraperStorageFactory.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem +{ + [SkyscraperPlugin] + [ScrapeStorageFactoryId(1,"Filesystem",false)] + internal class FilesystemScraperStorageFactory : IScraperStorageFactory + { + public string Directory { get; set; } + public IScraperStroage CreateScraperStroage() + { + return new FilesystemScraperStorage(new DirectoryInfo(Directory)); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/IScraperStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/IScraperStorage.cs new file mode 100644 index 0000000..5fe5792 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/IScraperStorage.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.NetworkInformation; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Mhp; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds.Messages; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.Scraper.Storage.Split; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.Teletext; +using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform; + +namespace skyscraper5.Skyscraper.Scraper.Storage +{ + public interface IScraperStroage : DataStorage, ObjectStorage, IDbBlindscanJobStorage, IDnsDataSource + { + bool TestForNitNetwork(NitNetwork nitNetwork); + void StoreNitNetwork(NitNetwork nitNetwork); + bool UpdateNitNetwork(NitNetwork nitNetwork); + bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream); + void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream); + bool UpdateNitTransportStream(ushort networkId, NitTransportStream transportStream); + bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId); + bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping result); + bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping); + bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp); + bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + bool TestForBatBouquet(BatBouquet batBouquet); + bool UpdateBatBouquet(BatBouquet batBouquet); + void StoreBatBouquet(BatBouquet batBouquet); + bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child); + bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child); + void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child); + bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor ltod); + bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime utcTime); + bool StoreEitEvent(EitEvent eitEvent); + bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier); + void StoreAitApplication(AitApplication aitApplication); + bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId); + bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid); + void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor); + void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform); + void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result); + bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber); + void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber); + bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, string programmeService2); + bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text); + bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, PTY.ProgrammeTypeCodes pty); + bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, int programNumber); + bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert); + void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert); + bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance); + void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance); + bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string stationIdentification); + bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion); + void StoreDsmCcDoItNowEvent(DateTime value, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid); + bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime); + void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, TimeSignal timeSignal); + bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid); + bool TestForIpMacNotification(IpMacNotification notification); + void StoreIpMacNotification(IpMacNotification notification); + bool ImportFileKnown(FileInfo fi); + void ImportMarkFileAsKnown(FileInfo fi, TimeSpan elapsed, int tstype); + DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid); + void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime); + bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo); + void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo); + List UiSatellitesListAll(); + void UiSatellitesAdd(SatellitePosition newPosition); + void UiSatellitesDelete(SatellitePosition satellitePosition); + bool UiTunerTestFor(TunerMetadata tuner); + void UiTunerUpdate(TunerMetadata tuner); + void UiTunerInsert(TunerMetadata tuner); + void UiTunerGetConfiguration(TunerMetadata foundTuner); + void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort mappingProgramNumber1, byte[] imageData); + bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int currentLocation); + void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int currentLocation); + + bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation); + void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation); + bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress); + bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion); + int? GetCurrentLocationId(); + void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation); + HeadlessJob GetQueuedJob(); + void SetQueuedJobComplete(HeadlessJob headlessJob); + + void WaitForCompletion(); + + /// + /// Tells the underlaying storage which UI Version is used. + /// 1 -> deprecated and buggy. Didn't know anything about dish types and LNB types + /// 2 -> current + /// + /// The underlaying UI Version to assume + void UiSetVersion(int v); + bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier); + void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier); + void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset); + List UiLnbTypesListAll(); + void UiLnbTypesAdd(LnbType defaultLnbType); + List UiDishTypesListAll(); + void UiDishTypesAdd(DishType defaultDishType); + object[] GetPluginConnector(); + + /// + /// Checks whether the storage engine is working properly. + /// + void Ping(); + + IEnumerable> SelectAllPmt(); + SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber); + IEnumerable> SelectAllSdt(); + void BeamsDisableAll(); + void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp); + void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string getPolygonString, string id); + bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string id); + void BeamsDisableSpecific(int databasePointerId, float databasePointerSatpos, string databasePointerName, DateTime databasePointerBeamsProcessTimestamp); + IEnumerable BeamsSelectEnabled(); + List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp); + void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value); + IReadOnlyList ListImportFileByTag1(int tag1); + bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId); + void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp); + bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry); + void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry); + + /// + /// Returns the Transmission Standard for an Interaction channel. + /// + /// Make sure this returns 0 if you don't have the necessary data to determine the Transmission Standard. + /// The network id of the Interaction channel in question. + /// The Transmission Standard for an Interaction channel. + int GetRmtTransmissionStandard(ushort networkId); + byte[] GetTmst(ushort interactiveNetworkId); + void InsertTmst(ushort interactiveNetworkId, byte[] modes); + void UpdateTmst(ushort interactiveNetworkId, byte[] modes); + bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage); + void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage); + bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream); + void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream); + bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe); + void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe); + bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame); + void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame); + bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite); + void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite); + bool TestForTim(PhysicalAddress mac); + void CreateTim(PhysicalAddress mac); + bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd); + bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew); + bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor); + bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamp); + + IEnumerable GetDbBlindscanJobs(); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/IScraperStorageFactory.cs b/skyscraper8/Skyscraper/Scraper/Storage/IScraperStorageFactory.cs new file mode 100644 index 0000000..94aba5c --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/IScraperStorageFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper.Storage +{ + public interface IScraperStorageFactory + { + IScraperStroage CreateScraperStroage(); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/DsmCcModuleXmlMemory.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/DsmCcModuleXmlMemory.cs new file mode 100644 index 0000000..3c7ffa7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/DsmCcModuleXmlMemory.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory +{ + public class DsmCcModuleXmlMemory + { + private DsmCcModuleXmlMemory() + { + _fileInfo = new FileInfo("dsmcc.xml"); + _xmlSerializer = new XmlSerializer(typeof(List)); + if (_fileInfo.Exists) + { + StreamReader streamReader = _fileInfo.OpenText(); + _list = (List)_xmlSerializer.Deserialize(streamReader); + streamReader.Close(); + } + else + { + _list = new List(); + } + } + + private FileInfo _fileInfo; + private XmlSerializer _xmlSerializer; + private List _list; + private static DsmCcModuleXmlMemory _instance; + public static DsmCcModuleXmlMemory GetInstance() + { + if (_instance == null) + { + _instance = new DsmCcModuleXmlMemory(); + } + + return _instance; + } + + public void MarkComplete(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + DsmCcModuleCoordinate coordinate = new DsmCcModuleCoordinate(currentNetworkId, currentTransportStreamId, elementaryPid, moduleModuleId, moduleModuleVersion); + if (_list.Contains(coordinate)) + return; + _list.Add(coordinate); + + FileStream fileStream = _fileInfo.OpenWrite(); + _xmlSerializer.Serialize(fileStream,_list); + fileStream.Flush(true); + fileStream.Close(); + } + + public bool IsWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + DsmCcModuleCoordinate coordinate = new DsmCcModuleCoordinate(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + return !_list.Contains(coordinate); + } + + public class DsmCcModuleCoordinate + { + [XmlAttribute] + public int NetworkId { get; set; } + + [XmlAttribute] + public int TransportStreamId { get; set; } + + [XmlAttribute] + public int Pid { get; set; } + + [XmlAttribute] + public ushort ModuleId { get; set; } + + [XmlAttribute] + public byte ModuleVersion { get; set; } + + public DsmCcModuleCoordinate() + { + + } + public DsmCcModuleCoordinate(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + NetworkId = currentNetworkId; + TransportStreamId = currentTransportStreamId; + Pid = elementaryPid; + ModuleId = moduleModuleId; + ModuleVersion = moduleModuleVersion; + } + + protected bool Equals(DsmCcModuleCoordinate other) + { + return NetworkId == other.NetworkId && TransportStreamId == other.TransportStreamId && Pid == other.Pid && ModuleId == other.ModuleId && ModuleVersion == other.ModuleVersion; + } + + 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((DsmCcModuleCoordinate)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = NetworkId; + hashCode = (hashCode * 397) ^ TransportStreamId; + hashCode = (hashCode * 397) ^ Pid; + hashCode = (hashCode * 397) ^ ModuleId.GetHashCode(); + hashCode = (hashCode * 397) ^ ModuleVersion.GetHashCode(); + return hashCode; + } + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryPluginToken.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryPluginToken.cs new file mode 100644 index 0000000..f25a2b2 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryPluginToken.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory +{ + public class InMemoryPluginToken + { + public InMemoryPluginToken() + { + this.Data = new Dictionary(); + } + + public Dictionary Data { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs new file mode 100644 index 0000000..0be5c18 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs @@ -0,0 +1,1233 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds.Messages; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.src.Skyscraper.Scraper.Storage.InMemory; +using skyscraper5.Teletext; +using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory +{ + internal class InMemoryScraperStorage : IScraperStroage + { + public InMemoryScraperStorage() + { + nitNetworks = new NitNetwork[ushort.MaxValue + 1]; + nitTransportStreams = new NitTransportStream[ushort.MaxValue + 1][]; + patEntries = new HashSet(); + pmtEntries = new ProgramMapping[ushort.MaxValue + 1][][]; + teletextPageCache = new Dictionary>(); + sdtServices = new SdtService[ushort.MaxValue + 1][][]; + batBouquets = new BatBouquet[ushort.MaxValue]; + batTransportStreams = new BatTransportStream[ushort.MaxValue + 1][]; + timeOffsets = new Tuple[ushort.MaxValue + 1][]; + tdtTimes = new DateTime[ushort.MaxValue + 1][]; + eitEvents = new HashSet(); + doItNowEvents = new List(); + caSystems = new List(); + updateHashes = new HashSet(); + updates = new List>(); + rdsTexts = new HashSet(); + ptyCodes = new PTY.ProgrammeTypeCodes[ushort.MaxValue][][]; + scte35SpliceCoordinates = new HashSet(); + compliances = new HashSet(); + stationIdentification = new string[ushort.MaxValue + 1][]; + aitApplicationCoordinates = new HashSet(); + rstCoordinates = new HashSet(); + ipMacNotificationCoordinates = new List(); + pluginToken = new InMemoryPluginToken(); + } + + private NitNetwork[] nitNetworks; + private NitTransportStream[][] nitTransportStreams; + private HashSet patEntries; + private ProgramMapping[][][] pmtEntries; + private Dictionary> teletextPageCache; + private SdtService[][][] sdtServices; + private BatBouquet[] batBouquets; + private BatTransportStream[][] batTransportStreams; + private Tuple[][] timeOffsets; + private DateTime[][] tdtTimes; + private HashSet eitEvents; + private List doItNowEvents; + private List caSystems; + private HashSet updateHashes; + private List> updates; + private bool[][][] rdsDataPresent; + private string[][][] rdsProgrammeService; + private HashSet rdsTexts; + private PTY.ProgrammeTypeCodes[][][] ptyCodes; + private bool[][][] rdsTrafficInformationPreset; + private HashSet scte35SpliceCoordinates; + private HashSet compliances; + private string[][] stationIdentification; + private HashSet aitApplicationCoordinates; + private HashSet rstCoordinates; + private List ipMacNotificationCoordinates; + private DateTime[][][] t2MiTimestamps; + private InMemoryPluginToken pluginToken; + + public bool TestForNitNetwork(NitNetwork nitNetwork) + { + return nitNetworks[nitNetwork.NetworkId] != null; + } + + public void StoreNitNetwork(NitNetwork nitNetwork) + { + nitNetworks[nitNetwork.NetworkId] = nitNetwork; + } + + public bool UpdateNitNetwork(NitNetwork l) + { + bool result = false; + NitNetwork r = nitNetworks[l.NetworkId]; + + if (string.IsNullOrEmpty(r.Name) && !string.IsNullOrEmpty(l.Name)) + { + r.Name = l.Name; + result = true; + } + + foreach (LinkageDescriptor lLinkage in l.Linkages) + { + if (!r.Linkages.Contains(lLinkage)) + { + r.Linkages.Add(lLinkage); + result = true; + } + } + + if (!r.PrivateDataSpecifierId.HasValue && l.PrivateDataSpecifierId.HasValue) + { + r.PrivateDataSpecifierId = l.PrivateDataSpecifierId; + result = true; + } + + return result; + } + + public bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + if (nitTransportStreams[transportStream.OriginalNetworkId] == null) + return false; + + return nitTransportStreams[transportStream.OriginalNetworkId][transportStream.TransportStreamId] != null; + } + + public void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + if (nitTransportStreams[transportStream.OriginalNetworkId] == null) + nitTransportStreams[transportStream.OriginalNetworkId] = new NitTransportStream[ushort.MaxValue]; + + nitTransportStreams[transportStream.OriginalNetworkId][transportStream.TransportStreamId] = transportStream; + } + + public bool UpdateNitTransportStream(ushort nid, NitTransportStream l) + { + NitTransportStream r = nitTransportStreams[l.OriginalNetworkId][l.TransportStreamId]; + + if (!r.Frequency.HasValue && l.Frequency.HasValue) + { + r.RollOff = l.RollOff; + r.S2 = l.S2; + r.Frequency = l.Frequency; + r.Polarization = l.Polarization; + r.SymbolRate = l.SymbolRate; + r.East = l.East; + r.FecInner = l.FecInner; + r.ModulationType = l.ModulationType; + r.OrbitalPosition = l.OrbitalPosition; + return true; + } + + return false; + } + + public bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId) + { + InMemoryPatEntry inMemoryPatEntry = new InMemoryPatEntry(currentNetworkId, currentTransportStreamId, pmtPid, programId); + return patEntries.Add(inMemoryPatEntry); + } + + public bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping result) + { + if (pmtEntries[currentNetworkId] == null) + return false; + + if (pmtEntries[currentNetworkId][currentTransportStreamId] == null) + return false; + + return pmtEntries[currentNetworkId][currentTransportStreamId][result.ProgramNumber] != null; + } + + public bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + if (pmtEntries[currentNetworkId] == null) + pmtEntries[currentNetworkId] = new ProgramMapping[ushort.MaxValue][]; + + if (pmtEntries[currentNetworkId][currentTransportStreamId] == null) + pmtEntries[currentNetworkId][currentTransportStreamId] = new ProgramMapping[ushort.MaxValue]; + + bool result = pmtEntries[currentNetworkId][currentTransportStreamId][mapping.ProgramNumber] == null; + pmtEntries[currentNetworkId][currentTransportStreamId][mapping.ProgramNumber] = mapping; + return result; + } + + public bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + return false; + } + + public bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtServices[transportStreamId] == null) + return false; + + if (sdtServices[transportStreamId][originalNetworkId] == null) + return false; + + return sdtServices[transportStreamId][originalNetworkId][sdtService.ServiceId] != null; + } + + public bool TestForTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + //No need to keep teletext pages in memory + return true; + } + + public void MarkTeletextPageAsKnown(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + //No need to keep teletext pages in memory + } + + public bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService l) + { + bool result = false; + SdtService r = sdtServices[transportStreamId][originalNetworkId][l.ServiceId]; + if (!string.IsNullOrEmpty(l.ServiceName) && string.IsNullOrEmpty(r.ServiceName)) + { + r.ServiceName = l.ServiceName; + r.ServiceProviderName = l.ServiceProviderName; + r.ServiceType = l.ServiceType; + result = true; + } + + if (l.PrivateDataSpecifier.HasValue && !r.PrivateDataSpecifier.HasValue) + { + r.PrivateDataSpecifier = l.PrivateDataSpecifier; + result = true; + } + + return false; + } + + public void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + if (sdtServices[transportStreamId] == null) + sdtServices[transportStreamId] = new SdtService[ushort.MaxValue + 1][]; + + if (sdtServices[transportStreamId][originalNetworkId] == null) + sdtServices[transportStreamId][originalNetworkId] = new SdtService[ushort.MaxValue + 1]; + + sdtServices[transportStreamId][originalNetworkId][sdtService.ServiceId] = sdtService; + } + + public bool TestForBatBouquet(BatBouquet batBouquet) + { + return batBouquets[batBouquet.BouquetId] != null; + } + + public bool UpdateBatBouquet(BatBouquet l) + { + bool result = false; + BatBouquet r = batBouquets[l.BouquetId]; + if (!string.IsNullOrEmpty(l.BouquetName) && string.IsNullOrEmpty(r.BouquetName)) + { + r.BouquetName = l.BouquetName; + result = true; + } + + foreach (LinkageDescriptor linkageDescriptor in l.Linkages) + { + if (!r.Linkages.Contains(linkageDescriptor)) + { + r.Linkages.Add(linkageDescriptor); + result = true; + } + } + + foreach (KeyValuePair country in l.CountryAvailabilityDictionary) + { + if (r.CountryAvailabilityDictionary.ContainsKey(country.Key)) + { + if (country.Value != r.CountryAvailabilityDictionary[country.Key]) + { + r.CountryAvailabilityDictionary[country.Key] = country.Value; + result = true; + continue; + } + } + else + { + r.CountryAvailabilityDictionary[country.Key] = country.Value; + result = true; + } + } + + return result; + } + + public void StoreBatBouquet(BatBouquet batBouquet) + { + batBouquets[batBouquet.BouquetId] = batBouquet; + } + + public bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + if (batTransportStreams[batBouquetBouquetId] == null) + return false; + + return batTransportStreams[batBouquetBouquetId][child.TransportStreamId] != null; + } + + public bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream l) + { + if (l.ServiceList == null) + return false; + + bool result = false; + BatTransportStream r = batTransportStreams[batBouquetBouquetId][l.TransportStreamId]; + foreach (ServiceListDescriptor.Service service in l.ServiceList) + { + if (r.ServiceList == null) + r.ServiceList = new List(); + if (!r.ServiceList.Contains(service)) + { + r.ServiceList.Add(service); + result = true; + } + } + + return result; + } + + public void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + if (batTransportStreams[batBouquetBouquetId] == null) + batTransportStreams[batBouquetBouquetId] = new BatTransportStream[ushort.MaxValue]; + + batTransportStreams[batBouquetBouquetId][child.TransportStreamId] = child; + } + + public bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime newTime, LocalTimeOffsetDescriptor ltod) + { + if (timeOffsets[currentNetworkId] == null) + timeOffsets[currentNetworkId] = new Tuple[ushort.MaxValue]; + + if (timeOffsets[currentNetworkId][currentTransportStreamId] != null) + { + DateTime oldTime = timeOffsets[currentNetworkId][currentTransportStreamId].Item1; + if ((newTime - oldTime).TotalSeconds < 10) + return false; + } + timeOffsets[currentNetworkId][currentTransportStreamId] = new Tuple(newTime, ltod); + return true; + } + + public bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime newTime) + { + if (tdtTimes[currentNetworkId] == null) + tdtTimes[currentNetworkId] = new DateTime[ushort.MaxValue]; + + if (tdtTimes[currentNetworkId][currentTransportStreamId] != DateTime.MinValue) + { + DateTime oldTime = tdtTimes[currentNetworkId][currentTransportStreamId]; + if ((newTime - oldTime).TotalSeconds < 10) + return false; + } + + tdtTimes[currentNetworkId][currentTransportStreamId] = newTime; + return true; + } + + public bool StoreEitEvent(EitEvent eitEvent) + { + return eitEvents.Add(eitEvent); + } + + public bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier) + { + AitApplicationCoordinate aitCoordinate = new AitApplicationCoordinate(aitApplicationApplicationIdentifier.OrganisationId, aitApplicationApplicationIdentifier.ApplicationId); + return aitApplicationCoordinates.Contains(aitCoordinate); + } + + public void StoreAitApplication(AitApplication aitApplication) + { + AitApplicationCoordinate aitApplicationCoordinate = new AitApplicationCoordinate( + aitApplication.ApplicationIdentifier.OrganisationId, + aitApplication.ApplicationIdentifier.ApplicationId); + + aitApplicationCoordinate.PointingTo = aitApplication; + aitApplicationCoordinates.Add(aitApplicationCoordinate); + } + + public bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId) + { + string outPath = String.Format("{0}{1}{2}{1}{3}{4}", networkId, Path.DirectorySeparatorChar, transportStreamId, vfsFile.SourcePid, vfsFile.ToString()); + FileInfo outFileInfo = new FileInfo(outPath); + if (outFileInfo.Exists) + return false; + EnsureDirectoryExists(outFileInfo.Directory); + File.WriteAllBytes(outFileInfo.FullName, vfsFile.FileContent); + return true; + } + + public bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid) + { + InMemoryCaSystem find = caSystems.Find(x => x.CurrentNetworkId == currentNetworkId && x.CurrentTransportStreamId == currentTransportStreamId && x.CaPid == caDescriptorCaPid); + return find != null; + } + + public void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + InMemoryCaSystem caSystem = new InMemoryCaSystem(currentNetworkId, currentTransportStreamId,caDescriptor); + caSystems.Add(caSystem); + } + + public bool TestForUpdateNotification(int hashCode, UpdateNotificationGroup common) + { + return updateHashes.Contains(hashCode); + } + + public void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + updateHashes.Add(hashCode); + updates.Add(new Tuple(hashCode, compatibility, platform)); + } + + public void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result) + { + string outPath = String.Format("{0}{1}{2}{1}{3}{1}{4}_{5}.bin", currentNetworkId, Path.DirectorySeparatorChar, currentTransportStreamId, elementaryPid, moduleModuleId, moduleModuleVersion); + FileInfo fi = new FileInfo(outPath); + if (fi.Exists) + return; + EnsureDirectoryExists(fi.Directory); + FileStream fileStream = fi.OpenWrite(); + result.CopyTo(fileStream); + fileStream.Close(); + fileStream.Dispose(); + + DsmCcModuleXmlMemory.GetInstance().MarkComplete(currentNetworkId, currentTransportStreamId, elementaryPid, moduleModuleId, moduleModuleVersion); + + } + + public bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + if (rdsDataPresent == null) + return false; + + if (rdsDataPresent[currentNetworkId] == null) + return false; + + if (rdsDataPresent[currentNetworkId][currentTransportStreamId] == null) + return false; + + return rdsDataPresent[currentNetworkId][currentTransportStreamId][programNumber]; + } + + public void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + if (rdsDataPresent == null) + rdsDataPresent = new bool[ushort.MaxValue][][]; + + if (rdsDataPresent[currentNetworkId] == null) + rdsDataPresent[currentNetworkId] = new bool[ushort.MaxValue][]; + + if (rdsDataPresent[currentNetworkId][currentTransportStreamId] == null) + rdsDataPresent[currentNetworkId][currentTransportStreamId] = new bool[ushort.MaxValue]; + + rdsDataPresent[currentNetworkId][currentTransportStreamId][programNumber] = true; + } + + public bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, string programmeService2) + { + if (rdsProgrammeService == null) + rdsProgrammeService = new string[ushort.MaxValue][][]; + + if (rdsProgrammeService[currentNetworkId] == null) + rdsProgrammeService[currentNetworkId] = new string[ushort.MaxValue][]; + + if (rdsProgrammeService[currentNetworkId][currentTransportStreamId] == null) + rdsProgrammeService[currentNetworkId][currentTransportStreamId] = new string[ushort.MaxValue]; + + bool result = !string.Equals(programmeService2, rdsProgrammeService[currentNetworkId][currentTransportStreamId][programNumber]); + rdsProgrammeService[currentNetworkId][currentTransportStreamId][programNumber] = programmeService2; + return result; + } + + public bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text) + { + InMemoryRdsText child = new InMemoryRdsText(currentNetworkId, currentTransportStreamId, programNumber, text); + bool add = rdsTexts.Add(child); + return add; + } + + public bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, PTY.ProgrammeTypeCodes pty) + { + if (ptyCodes[currentNetworkId] == null) + ptyCodes[currentNetworkId] = new PTY.ProgrammeTypeCodes[ushort.MaxValue][]; + + if (ptyCodes[currentNetworkId][currentTransportStreamId] == null) + ptyCodes[currentNetworkId][currentTransportStreamId] = new PTY.ProgrammeTypeCodes[ushort.MaxValue]; + + if (ptyCodes[currentNetworkId][currentTransportStreamId][programNumber] != pty) + { + ptyCodes[currentNetworkId][currentTransportStreamId][programNumber] = pty; + return true; + } + + return false; + } + + public bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + if (rdsTrafficInformationPreset == null) + rdsTrafficInformationPreset = new bool[ushort.MaxValue][][]; + + if (rdsTrafficInformationPreset[currentNetworkId] == null) + rdsTrafficInformationPreset[currentNetworkId] = new bool[ushort.MaxValue][]; + + if (rdsTrafficInformationPreset[currentNetworkId][currentTransportStreamId] == null) + rdsTrafficInformationPreset[currentNetworkId][currentTransportStreamId] = new bool[ushort.MaxValue]; + + if (!rdsTrafficInformationPreset[currentNetworkId][currentTransportStreamId][programNumber]) + { + rdsTrafficInformationPreset[currentNetworkId][currentTransportStreamId][programNumber] = true; + return true; + } + return false; + } + + public bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + Scte35SpliceCoordinate scte35sc = new Scte35SpliceCoordinate(currentNetworkId, currentTransportStreamId, programNumber, spliceInsert.SpliceEventId); + return scte35SpliceCoordinates.Contains(scte35sc); + } + + public void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + Scte35SpliceCoordinate scte35sc = new Scte35SpliceCoordinate(currentNetworkId, currentTransportStreamId, programNumber, spliceInsert.SpliceEventId); + scte35SpliceCoordinates.Add(scte35sc); + } + + public bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + ComplianceCoordiante cc = new ComplianceCoordiante(currentNetworkId, currentTransportStreamId, compliance); + return compliances.Contains(cc); + } + + public void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + ComplianceCoordiante cc = new ComplianceCoordiante(currentNetworkId, currentTransportStreamId, compliance); + compliances.Add(cc); + } + + public bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string newSi) + { + if (stationIdentification[currentNetworkId] == null) + stationIdentification[currentNetworkId] = new string[ushort.MaxValue]; + + bool result = !newSi.Equals(stationIdentification[currentNetworkId][currentTransportStreamId]); + stationIdentification[currentNetworkId][currentTransportStreamId] = newSi; + return result; + } + + public bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + return DsmCcModuleXmlMemory.GetInstance().IsWanted(currentNetworkId, currentTransportStreamId,elementaryPid, moduleId, moduleVersion); + } + + public void StoreDsmCcDoItNowEvent(DateTime timestamp, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + + } + + public bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime) + { + RstCoordinates rstCoordinates = new RstCoordinates(originalNetworkId, transportStreamId, serviceId, eventId, runningStatus, currentTime); + return this.rstCoordinates.Add(rstCoordinates); + } + + public void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, TimeSignal timeSignal) + { + + } + + public bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid) + { + FileInfo fi = new FileInfo(Path.Combine("screenshots", currentNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.jpg", mappingProgramNumber))); + return fi.Exists; + } + + public void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort pid, byte[] imageData) + { + FileInfo fi = new FileInfo(Path.Combine("screenshots", currentNetworkId.ToString(), transportStreamId.ToString(), String.Format("{0}.jpg", mappingProgramNumber))); + fi.Directory.EnsureExists(); + File.WriteAllBytes(fi.FullName, imageData); + } + + private HashSet _docsisUpstreamChannelCoordinates; + public bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int locationId) + { + if (_docsisUpstreamChannelCoordinates == null) + return false; + + DocsisUpstreamChannelCoordinate coord = new DocsisUpstreamChannelCoordinate(mmmSource, mmmFrequency); + return _docsisUpstreamChannelCoordinates.Contains(coord); + } + + public void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int locationId) + { + if (!mmm.Frequency.HasValue) + return; + + if (_docsisUpstreamChannelCoordinates == null) + _docsisUpstreamChannelCoordinates = new HashSet(); + + DocsisUpstreamChannelCoordinate coord = new DocsisUpstreamChannelCoordinate(mmm.Source, mmm.Frequency.Value); + _docsisUpstreamChannelCoordinates.Add(coord); + } + + private HashSet _docsisDownstreamChannelCoordinates; + public bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int locationId) + { + if (_docsisDownstreamChannelCoordinates == null) + return false; + + DocsisUpstreamChannelCoordinate coord = new DocsisUpstreamChannelCoordinate(physicalAddress, downstreamActiveChannel.Frequency.Value); + return _docsisDownstreamChannelCoordinates.Contains(coord); + } + + public void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int locationId) + { + if (_docsisDownstreamChannelCoordinates == null) + _docsisDownstreamChannelCoordinates = new HashSet(); + + DocsisUpstreamChannelCoordinate coord = new DocsisUpstreamChannelCoordinate(physicalAddress, downstreamActiveChannel.Frequency.Value); + _docsisDownstreamChannelCoordinates.Add(coord); + } + + private Dictionary cmtsIpAddresses; + public bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress) + { + if (cmtsIpAddresses == null) + cmtsIpAddresses = new Dictionary(); + + if (cmtsIpAddresses.ContainsKey(arpHeaderSenderHardwareAddress)) + { + bool result = cmtsIpAddresses[arpHeaderSenderHardwareAddress].Equals(arpHeaderSenderProtocolAddress); + cmtsIpAddresses[arpHeaderSenderHardwareAddress] = arpHeaderSenderProtocolAddress; + return !result; + } + else + { + cmtsIpAddresses[arpHeaderSenderHardwareAddress] = arpHeaderSenderProtocolAddress; + return true; + } + } + + private DsmCcModuleBlacklist dsmCcBlacklist; + public bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, + byte moduleVersion) + { + if (dsmCcBlacklist == null) + { + FileInfo fi = new FileInfo("dsmcc_blacklist.csv"); + dsmCcBlacklist = new DsmCcModuleBlacklist(fi); + } + + return dsmCcBlacklist.IsBlacklisted(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + } + + public int? GetCurrentLocationId() + { + if (!File.Exists("docsis_location.txt")) + return null; + else + { + string readAllText = File.ReadAllText("docsis_location.txt"); + return Int32.Parse(readAllText); + } + } + + private Dictionary docsisParticipants; + public void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation) + { + if (docsisParticipants == null) + docsisParticipants = new Dictionary(); + if (!docsisParticipants.ContainsKey(pa)) + docsisParticipants.Add(pa, currentLocation); + } + + public bool TestForIpMacNotification(IpMacNotification notification) + { + IpMacNotification ipMacNotification = ipMacNotificationCoordinates.Find(x => x.Equals(notification)); + return ipMacNotification != null; + } + + public void StoreIpMacNotification(IpMacNotification notification) + { + ipMacNotificationCoordinates.Add(notification); + } + + #region Ts File Collection Importer + + private KnownTsMemory knownTss; + + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan ts, int tstype) + { + if (knownTss == null) + knownTss = new KnownTsMemory("known_ts_filenames.json"); + knownTss.ImportMarkFileAsKnown(fi, ts, tstype); + } + + public bool ImportFileKnown(FileInfo fi) + { + if (knownTss == null) + knownTss = new KnownTsMemory("known_ts_filenames.json"); + return knownTss.ImportFileKnown(fi); + } + #endregion + + public DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid) + { + if (t2MiTimestamps == null) + return DateTime.MinValue; + + if (t2MiTimestamps[currentNetworkId] == null) + return DateTime.MinValue; + + if (t2MiTimestamps[currentNetworkId][currentTransportStreamId] == null) + return DateTime.MinValue; + + return t2MiTimestamps[currentNetworkId][currentTransportStreamId][pid]; + } + + public void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + if (t2MiTimestamps == null) + t2MiTimestamps = new DateTime[ushort.MaxValue][][]; + + if (t2MiTimestamps[currentNetworkId] == null) + t2MiTimestamps[currentNetworkId] = new DateTime[ushort.MaxValue][]; + + if (t2MiTimestamps[currentNetworkId][currentTransportStreamId] == null) + t2MiTimestamps[currentNetworkId][currentTransportStreamId] = new DateTime[0x1fff]; + + t2MiTimestamps[currentNetworkId][currentTransportStreamId][pid] = resolveTime; + } + + public bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + throw new NotImplementedException(); + } + + public void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + throw new NotImplementedException(); + } + + private List satellitePositions; + public List UiSatellitesListAll() + { + if (satellitePositions == null) + satellitePositions = new List(); + return satellitePositions.ToList(); + } + + public void UiSatellitesAdd(SatellitePosition newPosition) + { + if (satellitePositions == null) + satellitePositions = new List(); + satellitePositions.Add(newPosition); + } + + public void UiSatellitesDelete(SatellitePosition satellitePosition) + { + satellitePositions.RemoveAll(x => x.Checksum == satellitePosition.Checksum); + } + + private List uiTuners; + public bool UiTunerTestFor(TunerMetadata tuner) + { + if (uiTuners == null) + return false; + + return uiTuners.Exists(x => x.MacAddress.Equals(tuner.MacAddress)); + } + + public void UiTunerUpdate(TunerMetadata tuner) + { + TunerMetadata copy = uiTuners.Find(x => x.MacAddress.Equals(tuner.MacAddress)); + copy.DiseqcType = tuner.DiseqcType; + copy.Satellites[0] = tuner.Satellites[0]; + copy.Satellites[1] = tuner.Satellites[1]; + copy.Satellites[2] = tuner.Satellites[2]; + copy.Satellites[3] = tuner.Satellites[3]; + } + + public void UiTunerInsert(TunerMetadata tuner) + { + TunerMetadata copy = new TunerMetadata(tuner.Index, tuner.Name, tuner.Type); + copy.DiseqcType = tuner.DiseqcType; + copy.MacAddress = new PhysicalAddress(tuner.MacAddress.GetAddressBytes()); + copy.Satellites = new int[4]; + copy.Satellites[0] = tuner.Satellites[0]; + copy.Satellites[1] = tuner.Satellites[1]; + copy.Satellites[2] = tuner.Satellites[2]; + copy.Satellites[3] = tuner.Satellites[3]; + copy.Caps = tuner.Caps; + uiTuners.Add(copy); + } + + public void UiTunerGetConfiguration(TunerMetadata foundTuner) + { + TunerMetadata copy = uiTuners.Find(x => x.MacAddress.Equals(foundTuner.MacAddress)); + foundTuner.DiseqcType = copy.DiseqcType; + foundTuner.Satellites = new int[4]; + foundTuner.Satellites[0] = copy.Satellites[0]; + foundTuner.Satellites[1] = copy.Satellites[1]; + foundTuner.Satellites[2] = copy.Satellites[2]; + foundTuner.Satellites[3] = copy.Satellites[3]; + } + + private void EnsureDirectoryExists(DirectoryInfo di) + { + if (di.Exists) + return; + EnsureDirectoryExists(di.Parent); + di.Create(); + } + + public HeadlessJob GetQueuedJob() + { + throw new NotImplementedException(); + } + + public void SetQueuedJobComplete(HeadlessJob headlessJob) + { + throw new NotImplementedException(); + } + + public void WaitForCompletion() + { + //We're doing in-memory operations, so we'll won't have to wait for anything. + } + + public void UiSetVersion(int version) + { + //Since we're working solely in memory, we won't need to remember this. + } + + public bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + throw new NotImplementedException(); + } + + public void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + throw new NotImplementedException(); + } + + public void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset) + { + throw new NotImplementedException(); + } + + private List _lnbTypes; + public List UiLnbTypesListAll() + { + if (_lnbTypes == null) + _lnbTypes = new List(); + return _lnbTypes; + } + + public void UiLnbTypesAdd(LnbType defaultLnbType) + { + if (_lnbTypes == null) + _lnbTypes = new List(); + if (!_lnbTypes.Contains(defaultLnbType)) + _lnbTypes.Add(defaultLnbType); + } + + private List _dishTypes; + public List UiDishTypesListAll() + { + if (_dishTypes == null) + _dishTypes = new List(); + return _dishTypes; + + } + + public void UiDishTypesAdd(DishType defaultDishType) + { + if (_dishTypes == null) + _dishTypes = new List(); + if (!_dishTypes.Contains(defaultDishType)) + _dishTypes.Add(defaultDishType); + } + + public object[] GetPluginConnector() + { + return new object[] { pluginToken }; + } + + public void Ping() + { + + } + + public IEnumerable> SelectAllPmt() + { + for (int x = 0; x < pmtEntries.Length; x++) + { + if (pmtEntries[x] == null) + continue; + + for (int y = 0; y < pmtEntries[x].Length; y++) + { + if (pmtEntries[x][y] == null) + continue; + + for (int z = 0; z < pmtEntries[x][y].Length; z++) + { + if (pmtEntries[x][y][z] == null) + continue; + + yield return new Tuple(y, x, pmtEntries[x][y][z]); + } + } + } + + yield break; + } + + public SdtService SelectSdtById(int x, int y, ushort programMappingProgramNumber) + { + if (sdtServices[y] == null) + return null; + + if (sdtServices[y][x] == null) + return null; + + return sdtServices[y][x][programMappingProgramNumber]; + } + + public IEnumerable> SelectAllSdt() + { + for (int x = 0; x < sdtServices.Length; x++) + { + if (sdtServices[x] == null) + continue; + + for (int y = 0; y < sdtServices[x].Length; y++) + { + if (sdtServices[x][y] == null) + continue; + + for (int z = 0; z < sdtServices[x][y].Length; z++) + { + if (sdtServices[x][y][z] != null) + { + yield return new Tuple(x, y, sdtServices[x][y][z]); + } + } + } + } + + yield break; + } + + public void BeamsDisableAll() + { + throw new NotImplementedException(); + } + + public void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp) + { + throw new NotImplementedException(); + } + + public void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, + string getPolygonString, string id) + { + throw new NotImplementedException(); + } + + public bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name,string id) + { + throw new NotImplementedException(); + } + + public void BeamsDisableSpecific(int databasePointerId, float databasePointerSatpos, string databasePointerName, + DateTime databasePointerBeamsProcessTimestamp) + { + throw new NotImplementedException(); + } + + public IEnumerable BeamsSelectEnabled() + { + throw new NotImplementedException(); + } + + public List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp) + { + throw new NotImplementedException(); + } + + public void InsertBlindscanJob(DbBlindscanJob jobInDb) + { + throw new NotImplementedException(); + } + + public void UpdateJobState(DbBlindscanJob jobInDb) + { + throw new NotImplementedException(); + } + + public void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, + SearchResult2 searchResult2) + { + throw new NotImplementedException(); + } + + public void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, + BlindscanResultState blindscanResultState, SearchResult2 searchResult2) + { + throw new NotImplementedException(); + } + + public void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1, + SearchResult2 resultSr2, HumanReadableService humanReadableService) + { + throw new NotImplementedException(); + } + + public bool TestForIncompleteJob() + { + throw new NotImplementedException(); + } + + public DbBlindscanJob GetPastBlindscanJob(long offset) + { + throw new NotImplementedException(); + } + + public void DeleteBlindscanJob(Guid guid) + { + throw new NotImplementedException(); + } + + public void DeleteBlindscanResults(Guid jobGuid, int i) + { + throw new NotImplementedException(); + } + + public void MoveBlScanResultsToAnotherJob(Guid jobGuid1, Guid jobGuid2, int j) + { + throw new NotImplementedException(); + } + + public void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value) + { + throw new NotImplementedException(); + } + + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId) + { + throw new NotImplementedException(); + } + + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp) + { + throw new NotImplementedException(); + } + + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId) + { + throw new NotImplementedException(); + } + + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp) + { + throw new NotImplementedException(); + } + + public bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + throw new NotImplementedException(); + } + + public void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + throw new NotImplementedException(); + } + + public int GetRmtTransmissionStandard(ushort networkId) + { + throw new NotImplementedException(); + } + + public byte[] GetTmst(ushort interactiveNetworkId) + { + throw new NotImplementedException(); + } + + public void InsertTmst(ushort interactiveNetworkId, byte[] modes) + { + throw new NotImplementedException(); + } + + public void UpdateTmst(ushort interactiveNetworkId, byte[] modes) + { + throw new NotImplementedException(); + } + + public bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + throw new NotImplementedException(); + } + + public void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + throw new NotImplementedException(); + } + + public bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + throw new NotImplementedException(); + } + + public void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + throw new NotImplementedException(); + } + + public bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + throw new NotImplementedException(); + } + + public void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + throw new NotImplementedException(); + } + + public bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame) + { + throw new NotImplementedException(); + } + + public void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame) + { + throw new NotImplementedException(); + } + + public bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + throw new NotImplementedException(); + } + + public void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + throw new NotImplementedException(); + } + + public bool TestForTim(PhysicalAddress mac) + { + throw new NotImplementedException(); + } + + public void CreateTim(PhysicalAddress mac) + { + throw new NotImplementedException(); + } + + public bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + throw new NotImplementedException(); + } + + public bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew) + { + throw new NotImplementedException(); + } + + public bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor) + { + throw new NotImplementedException(); + } + + public bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamped) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDbBlindscanJobs() + { + throw new NotImplementedException(); + } + + public IReadOnlyList ListImportFileByTag1(int tag1) + { + throw new NotImplementedException(); + } + + public long DnsCountA() + { + throw new NotImplementedException(); + } + + public string DnsIpToName(IPAddress source) + { + throw new NotImplementedException(); + } + + public bool TestForIp(IPAddress iP) + { + throw new NotImplementedException(); + } + + public void RememberDnsRecord(DnsRecord record) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorageFactory.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorageFactory.cs new file mode 100644 index 0000000..05dd9cb --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorageFactory.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory +{ + [SkyscraperPlugin] + [ScrapeStorageFactoryId(0,"In-Memory",true)] + public class InMemoryScraperStorageFactory : IScraperStorageFactory + { + public IScraperStroage CreateScraperStroage() + { + return new InMemoryScraperStorage(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/KnownTsMemory.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/KnownTsMemory.cs new file mode 100644 index 0000000..7b18cac --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/KnownTsMemory.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Storage.InMemory +{ + internal class KnownTsMemory + { + public KnownTsMemory(string filename) + { + importFilesKnownFilename = filename; + } + public class KnownTs + { + public string name; + public TimeSpan timespan; + public int tsType; + } + private List importFilesKnown; + private string importFilesKnownFilename; + + private void LoadImportFilesKnown() + { + if (importFilesKnown != null) + return; + + if (File.Exists(importFilesKnownFilename)) + { + importFilesKnown = JsonConvert.DeserializeObject>(File.ReadAllText("importFilesKnown.json")); + } + else + { + importFilesKnown = new List(); + } + } + + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan ts, int tstype) + { + LoadImportFilesKnown(); + KnownTs child = new KnownTs(); + child.name = fi.FullName; + child.timespan = ts; + child.tsType = tstype; + importFilesKnown.Add(child); + File.WriteAllText(importFilesKnownFilename, JsonConvert.SerializeObject(importFilesKnown, Formatting.Indented)); + } + + public bool ImportFileKnown(FileInfo fi) + { + LoadImportFilesKnown(); + return importFilesKnown.Any(x => x.name.Equals(fi)); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/AitApplicationCoordinate.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/AitApplicationCoordinate.cs new file mode 100644 index 0000000..f8e255c --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/AitApplicationCoordinate.cs @@ -0,0 +1,38 @@ +using skyscraper5.Mhp.Si.Model; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class AitApplicationCoordinate + { + public uint OrganisationId { get; } + public ushort ApplicationId { get; } + public AitApplication PointingTo { get; set; } + + public AitApplicationCoordinate(uint organisationId, ushort applicationId) + { + OrganisationId = organisationId; + ApplicationId = applicationId; + } + + protected bool Equals(AitApplicationCoordinate other) + { + return OrganisationId == other.OrganisationId && ApplicationId == other.ApplicationId; + } + + 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((AitApplicationCoordinate)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((int)OrganisationId * 397) ^ ApplicationId.GetHashCode(); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/ComplianceCoordiante.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/ComplianceCoordiante.cs new file mode 100644 index 0000000..65c6a52 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/ComplianceCoordiante.cs @@ -0,0 +1,40 @@ +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class ComplianceCoordiante + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public string Compliance { get; } + + public ComplianceCoordiante(int currentNetworkId, int currentTransportStreamId, string compliance) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + Compliance = compliance; + } + + protected bool Equals(ComplianceCoordiante other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && Compliance == other.Compliance; + } + + 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((ComplianceCoordiante)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = CurrentNetworkId; + hashCode = (hashCode * 397) ^ CurrentTransportStreamId; + hashCode = (hashCode * 397) ^ (Compliance != null ? Compliance.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DoItNowEvent.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DoItNowEvent.cs new file mode 100644 index 0000000..0aaf9c9 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DoItNowEvent.cs @@ -0,0 +1,20 @@ +using skyscraper5.DsmCc.Descriptors; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class InMemoryDoItNowEvent + { + public int CurrentNetworkId { get; } + public int TransportStreamId { get; } + public ushort ProgramNumber { get; } + public StreamEventDescriptor DescriptorListStreamEventDescriptor { get; } + + public InMemoryDoItNowEvent(int currentNetworkId, int transportStreamId, ushort programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor) + { + CurrentNetworkId = currentNetworkId; + TransportStreamId = transportStreamId; + ProgramNumber = programNumber; + DescriptorListStreamEventDescriptor = descriptorListStreamEventDescriptor; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DocsisUpstreamChannelCoordinate.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DocsisUpstreamChannelCoordinate.cs new file mode 100644 index 0000000..6f9bfff --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DocsisUpstreamChannelCoordinate.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class DocsisUpstreamChannelCoordinate + { + public PhysicalAddress SourceAddress; + public uint Frequency; + + public DocsisUpstreamChannelCoordinate(PhysicalAddress sourceAddress, uint frequency) + { + SourceAddress = sourceAddress; + Frequency = frequency; + } + + protected bool Equals(DocsisUpstreamChannelCoordinate other) + { + return Equals(SourceAddress, other.SourceAddress) && Frequency == other.Frequency; + } + + 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((DocsisUpstreamChannelCoordinate)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(SourceAddress, Frequency); + } + + public override string ToString() + { + return $"{nameof(SourceAddress)}: {SourceAddress}, {nameof(Frequency)}: {Frequency}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DsmCcModuleBlacklist.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DsmCcModuleBlacklist.cs new file mode 100644 index 0000000..5df9be5 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/DsmCcModuleBlacklist.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class DsmCcModuleBlacklist + { + public DsmCcModuleBlacklist(FileInfo fi) + { + entries = new List>(); + if (!fi.Exists) + return; + + ListPath = fi; + StreamReader streamReader = fi.OpenText(); + while (!streamReader.EndOfStream) + { + string readLine = streamReader.ReadLine(); + if (string.IsNullOrEmpty(readLine)) + continue; + if (string.IsNullOrWhiteSpace(readLine)) + continue; + if (readLine.StartsWith("#")) + continue; + string[] strings = readLine.Split(';'); + int networkId = Int32.Parse(strings[0]); + int transportStreamId = Int32.Parse(strings[1]); + int elementaryPid = Int32.Parse(strings[2]); + ushort moduleId = UInt16.Parse(strings[3]); + byte moduleVersion = Byte.Parse(strings[4]); + //TODO: consider download progress + entries.Add(new Tuple(networkId, transportStreamId, elementaryPid, moduleId, moduleVersion)); + } + streamReader.Close(); + } + + public FileInfo ListPath { get; set; } + + private List> entries; + public bool IsBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + int count = 0; + var checkMe = new Tuple(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + foreach(Tuple entry in entries) + { + if (entry.Equals(checkMe)) + count++; + } + return count >= 3; + } + + public void AddFailedModule(int cnid, int ctsid, int pid, ushort mid, ushort mVersion, double progress) + { + ListPath.Refresh(); + FileStream fileStream = ListPath.Open(ListPath.Exists ? FileMode.Append : FileMode.CreateNew); + StreamWriter streamWriter = new StreamWriter(fileStream, System.Text.Encoding.UTF8); + streamWriter.WriteLine(String.Format("{0};{1};{2};{3};{4};{5}", cnid, ctsid, pid, mid, mVersion, progress)); + streamWriter.Flush(); + streamWriter.Close(); + fileStream.Close(); + fileStream.Dispose(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/InMemoryCaSystem.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/InMemoryCaSystem.cs new file mode 100644 index 0000000..c0270e4 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/InMemoryCaSystem.cs @@ -0,0 +1,30 @@ +using skyscraper5.Mpeg2.Descriptors; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class InMemoryCaSystem + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + + public InMemoryCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + CaPid = caDescriptor.CaPid; + CaSystemId = caDescriptor.CaSystemId; + PrivateData = caDescriptor.PrivateData; + } + + public byte[] PrivateData { get; private set; } + + public ushort CaSystemId { get; private set; } + + public int CaPid { get; private set; } + + public override string ToString() + { + return $"{CurrentNetworkId},{CurrentTransportStreamId},{CaPid},{CaSystemNames.GetHumanReadableName(CaSystemId)}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/InMemoryRdsText.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/InMemoryRdsText.cs new file mode 100644 index 0000000..f5e0368 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/InMemoryRdsText.cs @@ -0,0 +1,38 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class InMemoryRdsText + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public int ProgramNumber { get; } + public string Text { get; } + + public InMemoryRdsText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + ProgramNumber = programNumber; + Text = text; + } + + protected bool Equals(InMemoryRdsText other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && ProgramNumber == other.ProgramNumber && Text == other.Text; + } + + 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((InMemoryRdsText)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, ProgramNumber, Text); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/PatEntry.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/PatEntry.cs new file mode 100644 index 0000000..337c08b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/PatEntry.cs @@ -0,0 +1,43 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal struct InMemoryPatEntry : IEquatable + { + public InMemoryPatEntry(int networkId, int transportStreamId, int pmtPid, ushort programId) + { + this.networkId = networkId; + this.transportStreamId = transportStreamId; + this.pmtPid = pmtPid; + this.programId = programId; + } + + public int networkId, transportStreamId, pmtPid; + public ushort programId; + + public bool Equals(InMemoryPatEntry other) + { + return networkId == other.networkId && transportStreamId == other.transportStreamId && pmtPid == other.pmtPid && programId == other.programId; + } + + public override bool Equals(object obj) + { + return obj is InMemoryPatEntry other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(networkId, transportStreamId, pmtPid, programId); + } + + public static bool operator ==(InMemoryPatEntry left, InMemoryPatEntry right) + { + return left.Equals(right); + } + + public static bool operator !=(InMemoryPatEntry left, InMemoryPatEntry right) + { + return !left.Equals(right); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/RstCoordinates.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/RstCoordinates.cs new file mode 100644 index 0000000..e679351 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/RstCoordinates.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class RstCoordinates + { + public uint OriginalNetworkId { get; } + public uint TransportStreamId { get; } + public uint ServiceId { get; } + public uint EventId { get; } + public RunningStatus RunningStatus { get; } + public DateTime CurrentTime { get; } + + public RstCoordinates(uint originalNetworkId, uint transportStreamId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime) + { + OriginalNetworkId = originalNetworkId; + TransportStreamId = transportStreamId; + ServiceId = serviceId; + EventId = eventId; + RunningStatus = runningStatus; + CurrentTime = currentTime; + } + + protected bool Equals(RstCoordinates other) + { + return OriginalNetworkId == other.OriginalNetworkId && TransportStreamId == other.TransportStreamId && ServiceId == other.ServiceId && EventId == other.EventId && RunningStatus == other.RunningStatus; + } + + 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((RstCoordinates)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(OriginalNetworkId, TransportStreamId, ServiceId, EventId, (int)RunningStatus); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/Scte35SpliceCoordinate.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/Scte35SpliceCoordinate.cs new file mode 100644 index 0000000..9cadf4e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/Scte35SpliceCoordinate.cs @@ -0,0 +1,46 @@ +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal class Scte35SpliceCoordinate + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public ushort ProgramNumber { get; } + public uint SpliceInsertSpliceEventId { get; } + + public Scte35SpliceCoordinate(int currentNetworkId, int currentTransportStreamId, ushort programNumber, uint spliceInsertSpliceEventId) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + ProgramNumber = programNumber; + SpliceInsertSpliceEventId = spliceInsertSpliceEventId; + } + + protected bool Equals(Scte35SpliceCoordinate other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && ProgramNumber == other.ProgramNumber && SpliceInsertSpliceEventId == other.SpliceInsertSpliceEventId; + } + + 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((Scte35SpliceCoordinate)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = CurrentNetworkId; + hashCode = (hashCode * 397) ^ CurrentTransportStreamId; + hashCode = (hashCode * 397) ^ ProgramNumber.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)SpliceInsertSpliceEventId; + return hashCode; + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/TeletextCoordinate.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/TeletextCoordinate.cs new file mode 100644 index 0000000..a1cdeaa --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/Model/TeletextCoordinate.cs @@ -0,0 +1,33 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory.Model +{ + internal struct TeletextCoordinate + { + public bool Equals(TeletextCoordinate other) + { + return NetworkId == other.NetworkId && TransportStreamId == other.TransportStreamId && ProgramNumber == other.ProgramNumber; + } + + public override bool Equals(object obj) + { + return obj is TeletextCoordinate other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(NetworkId, TransportStreamId, ProgramNumber); + } + + public int NetworkId { get; } + public int TransportStreamId { get; } + public ushort ProgramNumber { get; } + + public TeletextCoordinate(int networkId, int transportStreamId, ushort programNumber) + { + NetworkId = networkId; + TransportStreamId = transportStreamId; + ProgramNumber = programNumber; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageException.cs b/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageException.cs new file mode 100644 index 0000000..6ef0b6b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageException.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper.Storage +{ + [Serializable] + public class ScraperStorageException : ScraperException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public ScraperStorageException() + { + } + + public ScraperStorageException(string message) : base(message) + { + } + + public ScraperStorageException(string message, Exception inner) : base(message, inner) + { + } + + protected ScraperStorageException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } + + + [Serializable] + public class StorageFactoryPluginNotLoadedException : ScraperStorageException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public StorageFactoryPluginNotLoadedException() + { + } + + public StorageFactoryPluginNotLoadedException(string message) : base(message) + { + } + + public StorageFactoryPluginNotLoadedException(string message, Exception inner) : base(message, inner) + { + } + + protected StorageFactoryPluginNotLoadedException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } + + [Serializable] + public class NoStorageFactoryConfiguredException : ScraperStorageException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public NoStorageFactoryConfiguredException() + { + } + + public NoStorageFactoryConfiguredException(string message) : base(message) + { + } + + public NoStorageFactoryConfiguredException(string message, Exception inner) : base(message, inner) + { + } + + protected NoStorageFactoryConfiguredException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageFactoryConnectionManager.cs b/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageFactoryConnectionManager.cs new file mode 100644 index 0000000..2c89c24 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageFactoryConnectionManager.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using System.Xml.XPath; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.Storage +{ + public sealed class ScraperStorageFactoryConnectionManager + { + private ScraperStorageFactoryConnectionManager() + { + factories = PluginManager.GetInstance().GetScraperStorageFactories(); + if (factories.Count == 0) + { + throw new NullReferenceException("We didn't get an ScraperStorages. There's something wrong here."); + } + } + + private static ScraperStorageFactoryConnectionManager _instance; + + public static ScraperStorageFactoryConnectionManager GetInstance() + { + if (_instance == null) + { + _instance = new ScraperStorageFactoryConnectionManager(); + } + return _instance; + } + + private ReadOnlyDictionary factories; + + public ReadOnlyCollection> GetKnownFactories() + { + ReadOnlyCollection> readOnlyCollection = factories.ToList().AsReadOnly(); + return readOnlyCollection; + } + + public static void ConfigureFactoryFromIni(KeyValuePair bootingStorage, Ini ini) + { + string catName = String.Format("storage{0}", bootingStorage.Key.Id); + PluginManager.GetInstance().AutoconfigureObject(catName, bootingStorage.Value); + } + + public IScraperStorageFactory AutoGetDefaultFactory() + { + Ini ini = PluginManager.GetInstance().Ini; + int factoryId = ini.ReadValue("startup", "storage", -1); + if (factoryId == -1) + throw new NoStorageFactoryConfiguredException(); + + IScraperStorageFactory result = null; + foreach (KeyValuePair entry in GetKnownFactories()) + { + if (factoryId == entry.Key.Id) + { + ConfigureFactoryFromIni(entry, ini); + result = entry.Value; + break; + } + } + + + if (result == null) + throw new StorageFactoryPluginNotLoadedException(); + return result; + } + } + + +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageFactoryId.cs b/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageFactoryId.cs new file mode 100644 index 0000000..c1d0497 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/ScraperStorageFactoryId.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper.Storage +{ + + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class ScrapeStorageFactoryIdAttribute : Attribute + { + public int Id { get; } + public string DisplayName { get; } + public bool VolatileStorage { get; } + + public string TemporaryUUID { get; } + + public ScrapeStorageFactoryIdAttribute(int id, string displayName, bool volatileStorage) + { + Id = id; + DisplayName = displayName; + VolatileStorage = volatileStorage; + TemporaryUUID = Guid.NewGuid().ToString(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Split/DataStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Split/DataStorage.cs new file mode 100644 index 0000000..6733c4f --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Split/DataStorage.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Mhp; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds.Messages; +using skyscraper5.Scte35; +using skyscraper5.Scte35.Descriptors; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.Teletext; +using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Split +{ + public interface DataStorage : IDbBlindscanJobStorage, IDnsDataSource + { + bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp); + bool TestForNitNetwork(NitNetwork nitNetwork); + void StoreNitNetwork(NitNetwork nitNetwork); + bool UpdateNitNetwork(NitNetwork nitNetwork); + bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream); + void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream); + bool UpdateNitTransportStream(ushort networkId, NitTransportStream transportStream); + bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId); + bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping result); + bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping); + bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService); + bool TestForBatBouquet(BatBouquet batBouquet); + bool UpdateBatBouquet(BatBouquet batBouquet); + void StoreBatBouquet(BatBouquet batBouquet); + bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child); + bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child); + void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child); + bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor ltod); + bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime utcTime); + bool StoreEitEvent(EitEvent eitEvent); + bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier); + void StoreAitApplication(AitApplication aitApplication); + bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid); + void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor); + void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform); + bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber); + void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber); + bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, string programmeService2); + bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text); + bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, PTY.ProgrammeTypeCodes pty); + bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, int programNumber); + bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert); + void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert); + bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance); + void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance); + bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string stationIdentification); + void StoreDsmCcDoItNowEvent(DateTime value, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid); + bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime); + void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, TimeSignal timeSignal); + bool TestForIpMacNotification(IpMacNotification notification); + void StoreIpMacNotification(IpMacNotification notification); + bool ImportFileKnown(FileInfo fi); + void ImportMarkFileAsKnown(FileInfo fi, TimeSpan duration, int tstype); + DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid); + void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime); + bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo); + void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo); + List UiSatellitesListAll(); + void UiSatellitesAdd(SatellitePosition newPosition); + void UiSatellitesDelete(SatellitePosition satellitePosition); + bool UiTunerTestFor(TunerMetadata tuner); + void UiTunerUpdate(TunerMetadata tuner); + void UiTunerInsert(TunerMetadata tuner); + void UiTunerGetConfiguration(TunerMetadata foundTuner); + bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int currentLocation); + void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int currentLocation); + bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation); + void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation); + bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress); + bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion); + int? GetCurrentLocationId(); + void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation); + HeadlessJob GetQueuedJob(); + void SetQueuedJobComplete(HeadlessJob headlessJob); + void WaitForCompletion(); + void UiSetVersion(int version); + bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier); + void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier); + void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset); + List UiLnbTypesListAll(); + void UiLnbTypesAdd(LnbType defaultLnbType); + List UiDishTypesListAll(); + void UiDishTypesAdd(DishType defaultDishType); + + object[] GetPluginConnector(); + void Ping(); + IEnumerable> SelectAllPmt(); + SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber); + IEnumerable> SelectAllSdt(); + void BeamsDisableAll(); + void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp); + void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string getPolygonString, string id); + bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string id); + void BeamsDisableSpecific(int databasePointerId, float databasePointerSatpos, string databasePointerName, DateTime databasePointerBeamsProcessTimestamp); + IEnumerable BeamsSelectEnabled(); + List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp); + + void InsertBlindscanJob(DbBlindscanJob jobInDb); + void UpdateJobState(DbBlindscanJob jobInDb); + void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, SearchResult2 searchResult2); + void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, BlindscanResultState blindscanResultState, SearchResult2 searchResult2); + void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1, SearchResult2 resultSr2, HumanReadableService humanReadableService); + bool TestForIncompleteJob(); + DbBlindscanJob GetPastBlindscanJob(long offset); + void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value); + IReadOnlyList ListImportFileByTag1(int tag1); + bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId); + void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp); + bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry); + void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry); + int GetRmtTransmissionStandard(ushort networkId); + byte[] GetTmst(ushort interactiveNetworkId); + void InsertTmst(ushort interactiveNetworkId, byte[] modes); + void UpdateTmst(ushort interactiveNetworkId, byte[] modes); + bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage); + void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage); + bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream); + void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream); + bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe); + void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe); + bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame); + void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame); + bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite); + void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite); + void CreateTim(PhysicalAddress mac); + bool TestForTim(PhysicalAddress mac); + bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd); + bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew); + bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor); + bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamp); + IEnumerable GetDbBlindscanJobs(); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Split/ObjectStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Split/ObjectStorage.cs new file mode 100644 index 0000000..d066ce8 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Split/ObjectStorage.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Teletext; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Split +{ + public interface ObjectStorage + { + bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId); + void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result); + bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion); + bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid); + void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort pid, byte[] imageData); + void WaitForCompletion(); + void UiSetVersion(int version); + object[] GetPluginConnector(); + + void Ping(); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Split/SplitScraperStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Split/SplitScraperStorage.cs new file mode 100644 index 0000000..f877fc6 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Split/SplitScraperStorage.cs @@ -0,0 +1,866 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.NetworkInformation; +using skyscraper5.Docsis.MacManagement; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Dvb.TvAnytime; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Rds.Messages; +using skyscraper5.Scte35; +using skyscraper5.Skyscraper.Equipment; +using skyscraper5.Skyscraper.Gps; +using skyscraper5.Skyscraper.Headless; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using skyscraper5.src.Skyscraper.FrequencyListGenerator; +using skyscraper5.src.Skyscraper.Scraper.Dns; +using skyscraper5.Teletext; +using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Split +{ + public class SplitScraperStorage : IScraperStroage + { + private readonly DataStorage dataStorage; + private readonly ObjectStorage objectStorage; + + public SplitScraperStorage(DataStorage dataStorage, ObjectStorage objectStorage) + { + this.dataStorage = dataStorage; + this.objectStorage = objectStorage; + } + + [DebuggerStepThrough] + public bool TestForNitNetwork(NitNetwork nitNetwork) + { + return dataStorage.TestForNitNetwork(nitNetwork); + } + + [DebuggerStepThrough] + public void StoreNitNetwork(NitNetwork nitNetwork) + { + dataStorage.StoreNitNetwork(nitNetwork); + } + + [DebuggerStepThrough] + public bool UpdateNitNetwork(NitNetwork nitNetwork) + { + return dataStorage.UpdateNitNetwork(nitNetwork); + } + + [DebuggerStepThrough] + public bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + return dataStorage.TestForNitTransportStream(networkId, transportStream); + } + + [DebuggerStepThrough] + public void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + dataStorage.StoreNitTransportStream(networkId, transportStream); + } + + [DebuggerStepThrough] + public bool UpdateNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + return dataStorage.UpdateNitTransportStream(networkId, transportStream); + } + + [DebuggerStepThrough] + public bool StorePatEntry(int currentNetworkId, int currentTransportStreamId, int pmtPid, ushort programId) + { + return dataStorage.StorePatEntry(currentNetworkId, currentTransportStreamId, pmtPid, programId); + } + + [DebuggerStepThrough] + public bool TestForPmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping result) + { + return dataStorage.TestForPmtEvent(currentNetworkId, currentTransportStreamId, result); + } + + [DebuggerStepThrough] + public bool StorePmtEvent(int currentNetworkId, int currentTransportStreamId, ProgramMapping mapping) + { + return dataStorage.StorePmtEvent(currentNetworkId, currentTransportStreamId, mapping); + } + + public bool StoreTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine, DateTime timestamp) + { + if (magazine.HumanReadablePageNumber > 999) + return false; + + return dataStorage.StoreTeletextPage(networkId, transportStreamId, programNumber, magazine, timestamp); + } + + [DebuggerStepThrough] + public bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + return dataStorage.TestForSdtService(transportStreamId, originalNetworkId, sdtService); + } + + [DebuggerStepThrough] + public bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + return dataStorage.UpdateSdtService(transportStreamId, originalNetworkId, sdtService); + } + + [DebuggerStepThrough] + public void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + dataStorage.StoreSdtService(transportStreamId, originalNetworkId, sdtService); + } + + [DebuggerStepThrough] + public bool TestForBatBouquet(BatBouquet batBouquet) + { + return dataStorage.TestForBatBouquet(batBouquet); + } + + [DebuggerStepThrough] + public bool UpdateBatBouquet(BatBouquet batBouquet) + { + return dataStorage.UpdateBatBouquet(batBouquet); + } + + [DebuggerStepThrough] + public void StoreBatBouquet(BatBouquet batBouquet) + { + dataStorage.StoreBatBouquet(batBouquet); + } + + [DebuggerStepThrough] + public bool TestForBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + return dataStorage.TestForBatTransportStream(batBouquetBouquetId, child); + } + + [DebuggerStepThrough] + public bool UpdateBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + return dataStorage.UpdateBatTransportStream(batBouquetBouquetId, child); + } + + [DebuggerStepThrough] + public void StoreBatTransportStream(ushort batBouquetBouquetId, BatTransportStream child) + { + dataStorage.StoreBatTransportStream(batBouquetBouquetId, child); + } + + [DebuggerStepThrough] + public bool UpdateTimeOffsetTable(int currentNetworkId, int currentTransportStreamId, DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + return dataStorage.UpdateTimeOffsetTable(currentNetworkId, currentTransportStreamId, utcTime, ltod); + } + + [DebuggerStepThrough] + public bool UpdateTimeAndDate(int currentNetworkId, int currentTransportStreamId, DateTime utcTime) + { + return dataStorage.UpdateTimeAndDate(currentNetworkId, currentTransportStreamId, utcTime); + } + + [DebuggerStepThrough] + public bool StoreEitEvent(EitEvent eitEvent) + { + return dataStorage.StoreEitEvent(eitEvent); + } + + [DebuggerStepThrough] + public bool TestForAitApplication(ApplicationIdentifier aitApplicationApplicationIdentifier) + { + return dataStorage.TestForAitApplication(aitApplicationApplicationIdentifier); + } + + [DebuggerStepThrough] + public void StoreAitApplication(AitApplication aitApplication) + { + dataStorage.StoreAitApplication(aitApplication); + } + + [DebuggerStepThrough] + public bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId) + { + return objectStorage.ObjectCarouselFileArrival(vfsFile, transportStreamId, networkId); + } + + [DebuggerStepThrough] + public bool TestForCaSystem(int currentNetworkId, int currentTransportStreamId, int caDescriptorCaPid) + { + return dataStorage.TestForCaSystem(currentNetworkId, currentTransportStreamId, caDescriptorCaPid); + } + + [DebuggerStepThrough] + public void StoreCaSystem(int currentNetworkId, int currentTransportStreamId, CaDescriptor caDescriptor) + { + dataStorage.StoreCaSystem(currentNetworkId, currentTransportStreamId, caDescriptor); + } + + [DebuggerStepThrough] + public void StoreUpdateNotification(int hashCode, UpdateNotificationGroup common, Compatibility compatibility, Platform platform) + { + dataStorage.StoreUpdateNotification(hashCode, common, compatibility, platform); + } + + [DebuggerStepThrough] + public void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result) + { + objectStorage.DataCarouselModuleArrival(currentNetworkId, currentTransportStreamId, elementaryPid, moduleModuleId, moduleModuleVersion, result); + } + + [DebuggerStepThrough] + public bool TestForKnownRdsData(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + return dataStorage.TestForKnownRdsData(currentNetworkId, currentTransportStreamId, programNumber); + } + + [DebuggerStepThrough] + public void EnableRdsCollection(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + dataStorage.EnableRdsCollection(currentNetworkId, currentTransportStreamId, programNumber); + } + + [DebuggerStepThrough] + public bool UpdateRdsProgrammeServiceName(int currentNetworkId, int currentTransportStreamId, int programNumber, string programmeService2) + { + return dataStorage.UpdateRdsProgrammeServiceName(currentNetworkId, currentTransportStreamId, programNumber, programmeService2); + } + + [DebuggerStepThrough] + public bool UpdateRdsRadioText(int currentNetworkId, int currentTransportStreamId, int programNumber, string text) + { + return dataStorage.UpdateRdsRadioText(currentNetworkId, currentTransportStreamId, programNumber, text); + } + + [DebuggerStepThrough] + public bool UpdateRdsPty(int currentNetworkId, int currentTransportStreamId, int programNumber, PTY.ProgrammeTypeCodes pty) + { + return dataStorage.UpdateRdsPty(currentNetworkId, currentTransportStreamId, programNumber, pty); + } + + [DebuggerStepThrough] + public bool MarkAsRdsTrafficInformationProgramme(int currentNetworkId, int currentTransportStreamId, int programNumber) + { + return dataStorage.MarkAsRdsTrafficInformationProgramme(currentNetworkId, currentTransportStreamId, programNumber); + } + + [DebuggerStepThrough] + public bool TestForScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + return dataStorage.TestForScte35SpliceInsert(currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + } + + [DebuggerStepThrough] + public void StoreScte35SpliceInsert(int currentNetworkId, int currentTransportStreamId, ushort programNumber, SpliceInsert spliceInsert) + { + dataStorage.StoreScte35SpliceInsert(currentNetworkId, currentTransportStreamId, programNumber, spliceInsert); + } + + [DebuggerStepThrough] + public bool IsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + return dataStorage.IsCompliant(currentNetworkId, currentTransportStreamId, compliance); + } + + [DebuggerStepThrough] + public void MarkAsCompliant(int currentNetworkId, int currentTransportStreamId, string compliance) + { + dataStorage.MarkAsCompliant(currentNetworkId, currentTransportStreamId, compliance); + } + + [DebuggerStepThrough] + public bool SetStationIdentification(int currentNetworkId, int currentTransportStreamId, string stationIdentification) + { + return dataStorage.SetStationIdentification(currentNetworkId, currentTransportStreamId, stationIdentification); + } + + [DebuggerStepThrough] + public bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + return objectStorage.IsDsmCcModuleWanted(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + } + + [DebuggerStepThrough] + public void StoreDsmCcDoItNowEvent(DateTime value, int currentNetworkId, int currentTransportStreamId, int programNumber, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + dataStorage.StoreDsmCcDoItNowEvent(value, currentNetworkId, currentTransportStreamId, programNumber, descriptorListStreamEventDescriptor, pid); + } + + [DebuggerStepThrough] + public bool StoreRunningStatus(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime) + { + return dataStorage.StoreRunningStatus(transportStreamId, originalNetworkId, serviceId, eventId, runningStatus, currentTime); + } + + [DebuggerStepThrough] + public void SetScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, DateTime currentTime, ushort programNumber, TimeSignal timeSignal) + { + dataStorage.SetScte35TimeSignal(currentNetworkId, currentTransportStreamId, currentTime, programNumber, timeSignal); + } + + [DebuggerStepThrough] + public bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid) + { + return objectStorage.TestForFramegrab(currentNetworkId, transportStreamId, mappingProgramNumber, mappingStreamElementaryPid); + } + + [DebuggerStepThrough] + public bool TestForIpMacNotification(IpMacNotification notification) + { + return dataStorage.TestForIpMacNotification(notification); + } + + [DebuggerStepThrough] + public void StoreIpMacNotification(IpMacNotification notification) + { + dataStorage.StoreIpMacNotification(notification); + } + + [DebuggerStepThrough] + public bool ImportFileKnown(FileInfo fi) + { + return dataStorage.ImportFileKnown(fi); + } + + [DebuggerStepThrough] + public void ImportMarkFileAsKnown(FileInfo fi, TimeSpan timeTaken, int tstype) + { + dataStorage.ImportMarkFileAsKnown(fi, timeTaken, tstype); + } + + [DebuggerStepThrough] + public DateTime T2MiGetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid) + { + return dataStorage.T2MiGetTimestamp(currentNetworkId, currentTransportStreamId, pid); + } + + [DebuggerStepThrough] + public void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime) + { + dataStorage.T2MiSetTimestamp(currentNetworkId, currentTransportStreamId, pid, resolveTime); + } + + [DebuggerStepThrough] + public bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + return dataStorage.TestForRelatedContent(lEvent, rctLinkInfo); + } + + [DebuggerStepThrough] + public void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) + { + dataStorage.SetRelatedContent(lEvent, rctLinkInfo); + } + + [DebuggerStepThrough] + public List UiSatellitesListAll() + { + return dataStorage.UiSatellitesListAll(); + } + + [DebuggerStepThrough] + public void UiSatellitesAdd(SatellitePosition newPosition) + { + dataStorage.UiSatellitesAdd(newPosition); + } + + [DebuggerStepThrough] + public void UiSatellitesDelete(SatellitePosition satellitePosition) + { + dataStorage.UiSatellitesDelete(satellitePosition); + } + + [DebuggerStepThrough] + public bool UiTunerTestFor(TunerMetadata tuner) + { + return dataStorage.UiTunerTestFor(tuner); + } + + [DebuggerStepThrough] + public void UiTunerUpdate(TunerMetadata tuner) + { + dataStorage.UiTunerUpdate(tuner); + } + + [DebuggerStepThrough] + public void UiTunerInsert(TunerMetadata tuner) + { + dataStorage.UiTunerInsert(tuner); + } + + [DebuggerStepThrough] + public void UiTunerGetConfiguration(TunerMetadata foundTuner) + { + dataStorage.UiTunerGetConfiguration(foundTuner); + } + + [DebuggerStepThrough] + public void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort pid, byte[] imageData) + { + objectStorage.StoreFramegrab(currentNetworkId, transportStreamId, mappingProgramNumber, pid, imageData); + } + + [DebuggerStepThrough] + public bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int locationId) + { + return dataStorage.TestForDocsisUpstreamChannel(mmmSource, mmmFrequency, locationId); + } + + [DebuggerStepThrough] + public void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int locationId) + { + dataStorage.StoreDocsisUpstreamChannel(mmm, locationId); + } + + [DebuggerStepThrough] + public bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int locationId) + { + return dataStorage.TestForDocsisDownstreamChannel(physicalAddress, downstreamActiveChannel,locationId); + } + + [DebuggerStepThrough] + public void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int locationId) + { + dataStorage.StoreDocsisDownstreamChannel(physicalAddress, downstreamActiveChannel,locationId); + } + + [DebuggerStepThrough] + public bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress) + { + return dataStorage.SetCmtsIp(arpHeaderSenderHardwareAddress, arpHeaderSenderProtocolAddress); + } + + [DebuggerStepThrough] + public bool IsDsmCcModuleBlacklisted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, + byte moduleVersion) + { + return dataStorage.IsDsmCcModuleBlacklisted(currentNetworkId, currentTransportStreamId, elementaryPid, moduleId, moduleVersion); + } + + [DebuggerStepThrough] + public int? GetCurrentLocationId() + { + return dataStorage.GetCurrentLocationId(); + } + + [DebuggerStepThrough] + public void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation) + { + dataStorage.StoreDocsisParticipant(pa, currentLocation); + } + + [DebuggerStepThrough] + public HeadlessJob GetQueuedJob() + { + return dataStorage.GetQueuedJob(); + } + + [DebuggerStepThrough] + public void SetQueuedJobComplete(HeadlessJob headlessJob) + { + dataStorage.SetQueuedJobComplete(headlessJob); + } + + [DebuggerStepThrough] + public void WaitForCompletion() + { + dataStorage.WaitForCompletion(); + objectStorage.WaitForCompletion(); + } + + [DebuggerStepThrough] + public void UiSetVersion(int version) + { + dataStorage.UiSetVersion(version); + objectStorage.UiSetVersion(version); + } + + [DebuggerStepThrough] + public bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + return dataStorage.T2MiTestForTransmitter(currentNetworkId, currentTransportStreamId, relatedPid, txIdentifier); + } + + [DebuggerStepThrough] + public void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + dataStorage.T2MiRememberTransmitter(currentNetworkId, currentTransportStreamId, relatedPid, txIdentifier); + } + + [DebuggerStepThrough] + public void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset) + { + dataStorage.T2MiSetTransmitterTimeOffset(currentNetworkId, currentTransportStreamId, relatedPid, txIdentifier, timeOffset); + } + + [DebuggerStepThrough] + public List UiLnbTypesListAll() + { + return dataStorage.UiLnbTypesListAll(); + } + + [DebuggerStepThrough] + public void UiLnbTypesAdd(LnbType defaultLnbType) + { + dataStorage.UiLnbTypesAdd(defaultLnbType); + } + + [DebuggerStepThrough] + public List UiDishTypesListAll() + { + return dataStorage.UiDishTypesListAll(); + } + + [DebuggerStepThrough] + public void UiDishTypesAdd(DishType defaultDishType) + { + dataStorage.UiDishTypesAdd(defaultDishType); + } + + public object[] GetPluginConnector() + { + object[] dataConnector = dataStorage.GetPluginConnector(); + object[] objectConnector = objectStorage.GetPluginConnector(); + + if (dataConnector != null && objectConnector == null) + return dataConnector; + + if (dataConnector == null && objectConnector != null) + return objectConnector; + + object[] result = new object[dataConnector.Length + objectConnector.Length]; + Array.Copy(dataConnector, 0, result, 0, dataConnector.Length); + Array.Copy(objectConnector, 0, result, dataConnector.Length, objectConnector.Length); + return result; + } + + [DebuggerStepThrough] + public void Ping() + { + dataStorage.Ping(); + objectStorage.Ping(); + } + + [DebuggerStepThrough] + public IEnumerable> SelectAllPmt() + { + return dataStorage.SelectAllPmt(); + } + + [DebuggerStepThrough] + public SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber) + { + return dataStorage.SelectSdtById(networkId, tsId, programMappingProgramNumber); + } + + [DebuggerStepThrough] + public IEnumerable> SelectAllSdt() + { + return dataStorage.SelectAllSdt(); + } + + [DebuggerStepThrough] + public void BeamsDisableAll() + { + dataStorage.BeamsDisableAll(); + } + + [DebuggerStepThrough] + public void BeamsEnable(int id, float satpos, string name, DateTime processTimestamp) + { + dataStorage.BeamsEnable(id, satpos, name, processTimestamp); + } + + [DebuggerStepThrough] + public void BeamFootprintStore(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, + string getPolygonString, string id) + { + dataStorage.BeamFootprintStore(databasePointerId, databasePointerBeamsProcessTimestamp, name, getPolygonString, id); + } + + [DebuggerStepThrough] + public bool TestForBeamFootprint(int databasePointerId, DateTime databasePointerBeamsProcessTimestamp, string name, string id) + { + return dataStorage.TestForBeamFootprint(databasePointerId, databasePointerBeamsProcessTimestamp, name, id); + } + + [DebuggerStepThrough] + public void BeamsDisableSpecific(int databasePointerId, float databasePointerSatpos, string databasePointerName, + DateTime databasePointerBeamsProcessTimestamp) + { + dataStorage.BeamsDisableSpecific(databasePointerId, databasePointerSatpos, databasePointerName, databasePointerBeamsProcessTimestamp); + } + + [DebuggerStepThrough] + public IEnumerable BeamsSelectEnabled() + { + return dataStorage.BeamsSelectEnabled(); + } + + [DebuggerStepThrough] + public List BeamsSelectFootprints(int satelliteBeamId, DateTime satelliteBeamProcessTimestamp) + { + return dataStorage.BeamsSelectFootprints(satelliteBeamId, satelliteBeamProcessTimestamp); + } + + [DebuggerStepThrough] + public void InsertBlindscanJob(DbBlindscanJob jobInDb) + { + dataStorage.InsertBlindscanJob(jobInDb); + } + + [DebuggerStepThrough] + public void UpdateJobState(DbBlindscanJob jobInDb) + { + dataStorage.UpdateJobState(jobInDb); + } + + [DebuggerStepThrough] + public void InsertSearchResult(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, int polarityIndex, + SearchResult2 searchResult2) + { + dataStorage.InsertSearchResult(jobInDb, satellite, searchResult, polarityIndex, searchResult2); + } + + [DebuggerStepThrough] + public void UpdateTransponderState(DbBlindscanJob jobInDb, bool satellite, SearchResult searchResult, + BlindscanResultState blindscanResultState, SearchResult2 searchResult2) + { + dataStorage.UpdateTransponderState(jobInDb, satellite, searchResult, blindscanResultState, searchResult2); + } + + [DebuggerStepThrough] + public void InsertTransponderService(DbBlindscanJob jobInDb, bool resultSatellite, SearchResult resultSr1, + SearchResult2 resultSr2, HumanReadableService humanReadableService) + { + dataStorage.InsertTransponderService(jobInDb, resultSatellite, resultSr1, resultSr2, humanReadableService); + } + + [DebuggerStepThrough] + public bool TestForIncompleteJob() + { + return dataStorage.TestForIncompleteJob(); + } + + [DebuggerStepThrough] + public DbBlindscanJob GetPastBlindscanJob(long offset) + { + return dataStorage.GetPastBlindscanJob(offset); + } + + [DebuggerStepThrough] + public void DeleteBlindscanJob(Guid guid) + { + dataStorage.DeleteBlindscanJob(guid); + } + + [DebuggerStepThrough] + public void DeleteBlindscanResults(Guid jobGuid, int polarityIndex) + { + dataStorage.DeleteBlindscanResults(jobGuid, polarityIndex); + } + + [DebuggerStepThrough] + public void MoveBlScanResultsToAnotherJob(Guid moveFrom, Guid moveTo, int polarityIndex) + { + dataStorage.MoveBlScanResultsToAnotherJob(moveFrom, moveTo, polarityIndex); + } + + [DebuggerStepThrough] + public void FailDsmCcDownload(DatabaseKeyDsmCcModule key, double value) + { + dataStorage.FailDsmCcDownload(key, value); + } + + [DebuggerStepThrough] + public IReadOnlyList ListImportFileByTag1(int tag1) + { + return dataStorage.ListImportFileByTag1(tag1); + } + + [DebuggerStepThrough] + public bool TestForTerminalBurstTimePlan(ushort interactiveNetworkId, uint groupId, uint logonId) + { + return dataStorage.TestForTerminalBurstTimePlan(interactiveNetworkId, groupId, logonId); + } + + [DebuggerStepThrough] + public void StoreTerminalBurstTimePlan(ushort interactiveNetworkId, uint gtoupId, uint superframeCount, uint frameNumber, Tbtp.TbtpFrame.BtpEntity btp) + { + dataStorage.StoreTerminalBurstTimePlan(interactiveNetworkId, gtoupId, superframeCount, frameNumber, btp); + } + + [DebuggerStepThrough] + public bool TestForCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + return dataStorage.TestForCmtEntry(interactiveNetworkId, entry); + } + + [DebuggerStepThrough] + public void InsertCmtEntry(ushort interactiveNetworkId, Cmt.CmtEntry entry) + { + dataStorage.InsertCmtEntry(interactiveNetworkId, entry); + } + + [DebuggerStepThrough] + public int GetRmtTransmissionStandard(ushort networkId) + { + return dataStorage.GetRmtTransmissionStandard(networkId); + } + + [DebuggerStepThrough] + public byte[] GetTmst(ushort interactiveNetworkId) + { + return dataStorage.GetTmst(interactiveNetworkId); + } + + [DebuggerStepThrough] + public void InsertTmst(ushort interactiveNetworkId, byte[] modes) + { + dataStorage.InsertTmst(interactiveNetworkId, modes); + } + + [DebuggerStepThrough] + public void UpdateTmst(ushort interactiveNetworkId, byte[] modes) + { + dataStorage.UpdateTmst(interactiveNetworkId, modes); + } + + [DebuggerStepThrough] + public bool TestForRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + return dataStorage.TestForRmtLinkage(linkage); + } + + [DebuggerStepThrough] + public void InsertRmtLinkage(_0x4a_LinkageDescriptor linkage) + { + dataStorage.InsertRmtLinkage(linkage); + } + + [DebuggerStepThrough] + public bool TestForRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + return dataStorage.TestForRmtTransportStream(networkId, transportStream); + } + + [DebuggerStepThrough] + public void InsertRmtTransportStream(ushort networkId, Rmt.TransportStream transportStream) + { + dataStorage.InsertRmtTransportStream(networkId, transportStream); + } + + [DebuggerStepThrough] + public bool TestForSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + return dataStorage.TestForSuperframeComposition(interactiveNetworkId, superframe); + } + + [DebuggerStepThrough] + public void StoreSuperframeComposition(ushort interactiveNetworkId, Sct.Superframe superframe) + { + dataStorage.StoreSuperframeComposition(interactiveNetworkId, superframe); + } + + [DebuggerStepThrough] + public bool TestForFrameComposition(ushort interactiveNetworkId, Fct.Frame frame) + { + return dataStorage.TestForFrameComposition(interactiveNetworkId, frame); + } + + [DebuggerStepThrough] + public void InsertFctFrame(ushort interactiveNetworkId, Fct.Frame frame) + { + dataStorage.InsertFctFrame(interactiveNetworkId, frame); + } + + [DebuggerStepThrough] + public bool TestForSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + return dataStorage.TestForSatellitePosition(interactiveNetworkId, satellite); + } + + [DebuggerStepThrough] + public void StoreSatellitePosition(ushort interactiveNetworkId, Spt.Satellite satellite) + { + dataStorage.StoreSatellitePosition(interactiveNetworkId, satellite); + } + + [DebuggerStepThrough] + public bool TestForTim(PhysicalAddress mac) + { + return dataStorage.TestForTim(mac); + } + + [DebuggerStepThrough] + public void CreateTim(PhysicalAddress mac) + { + dataStorage.CreateTim(mac); + } + + [DebuggerStepThrough] + public bool CorrectTim(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + return dataStorage.CorrectTim(mac, cmd); + } + + [DebuggerStepThrough] + public bool ContentionTim(PhysicalAddress mac, _0xab_ContentionControlDescriptor ccdNew) + { + return dataStorage.ContentionTim(mac, ccdNew); + } + + [DebuggerStepThrough] + public bool CorrectionControlTim(PhysicalAddress mac, _0xac_CorrectionControlDescriptor descriptor) + { + return dataStorage.CorrectionControlTim(mac, descriptor); + } + + [DebuggerStepThrough] + public bool NetworkLayerInfoTim(PhysicalAddress mac, _0xa0_NetworkLayerInfoDescriptor nlid, DateTime timestamped) + { + return dataStorage.NetworkLayerInfoTim(mac, nlid, timestamped); + } + + [DebuggerStepThrough] + public IEnumerable GetDbBlindscanJobs() + { + return dataStorage.GetDbBlindscanJobs(); + } + + [DebuggerStepThrough] + public long DnsCountA() + { + return dataStorage.DnsCountA(); + } + + [DebuggerStepThrough] + public string DnsIpToName(IPAddress source) + { + return dataStorage.DnsIpToName(source); + } + + [DebuggerStepThrough] + public bool TestForIp(IPAddress iP) + { + return dataStorage.TestForIp(iP); + } + + [DebuggerStepThrough] + public void RememberDnsRecord(DnsRecord record) + { + dataStorage.RememberDnsRecord(record); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyAprsPosition.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyAprsPosition.cs new file mode 100644 index 0000000..363100e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyAprsPosition.cs @@ -0,0 +1,38 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyAprsPosition + { + public DateTime Timestamp { get; } + public string Sender { get; } + public double Longitude { get; } + public double Latitude { get; } + + public DatabaseKeyAprsPosition(DateTime timestamp, string sender, double longitude, double latitude) + { + Timestamp = timestamp; + Sender = sender; + Longitude = longitude; + Latitude = latitude; + } + + protected bool Equals(DatabaseKeyAprsPosition other) + { + return Timestamp.Equals(other.Timestamp) && Sender == other.Sender && Longitude.Equals(other.Longitude) && Latitude.Equals(other.Latitude); + } + + 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((DatabaseKeyAprsPosition)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Timestamp, Sender, Longitude, Latitude); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyAprsWeatherReport.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyAprsWeatherReport.cs new file mode 100644 index 0000000..c02f17e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyAprsWeatherReport.cs @@ -0,0 +1,34 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyAprsWeatherReport + { + public string Sender { get; } + public DateTime Timestamp { get; } + + public DatabaseKeyAprsWeatherReport(string sender, DateTime timestamp) + { + Sender = sender; + Timestamp = timestamp; + } + + protected bool Equals(DatabaseKeyAprsWeatherReport other) + { + return Sender == other.Sender && Timestamp.Equals(other.Timestamp); + } + + 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((DatabaseKeyAprsWeatherReport)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Sender, Timestamp); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyBatBouquet.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyBatBouquet.cs new file mode 100644 index 0000000..45fa635 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyBatBouquet.cs @@ -0,0 +1,29 @@ +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyBatBouquet + { + public ushort Id { get; } + public DatabaseKeyBatBouquet(ushort id) + { + Id = id; + } + + protected bool Equals(DatabaseKeyBatBouquet other) + { + return Id == other.Id; + } + + 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((DatabaseKeyBatBouquet)obj); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyBatTs.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyBatTs.cs new file mode 100644 index 0000000..ac2e19b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyBatTs.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyBatTs + { + public ushort BouquetId { get; } + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + + public DatabaseKeyBatTs(ushort bouquetId, ushort transportStreamId, ushort originalNetworkId) + { + BouquetId = bouquetId; + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + } + + protected bool Equals(DatabaseKeyBatTs other) + { + return BouquetId == other.BouquetId && TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId; + } + + 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((DatabaseKeyBatTs)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(BouquetId, TransportStreamId, OriginalNetworkId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyCat.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyCat.cs new file mode 100644 index 0000000..12c3f7e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyCat.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyCat + { + public int NetworkId { get; } + public int TransportStreamId { get; } + public int Pid { get; } + + public DatabaseKeyCat(int networkId, int transportStreamId, int pid) + { + NetworkId = networkId; + TransportStreamId = transportStreamId; + Pid = pid; + } + + protected bool Equals(DatabaseKeyCat other) + { + return NetworkId == other.NetworkId && TransportStreamId == other.TransportStreamId && Pid == other.Pid; + } + + 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((DatabaseKeyCat)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(NetworkId, TransportStreamId, Pid); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyDsmCcModule.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyDsmCcModule.cs new file mode 100644 index 0000000..33cf22b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyDsmCcModule.cs @@ -0,0 +1,40 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyDsmCcModule + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public int ElementaryPid { get; } + public ushort ModuleId { get; } + public byte ModuleVersion { get; } + + public DatabaseKeyDsmCcModule(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, byte moduleVersion) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + ElementaryPid = elementaryPid; + ModuleId = moduleId; + ModuleVersion = moduleVersion; + } + + protected bool Equals(DatabaseKeyDsmCcModule other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && ElementaryPid == other.ElementaryPid && ModuleId == other.ModuleId && ModuleVersion == other.ModuleVersion; + } + + 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((DatabaseKeyDsmCcModule)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, ElementaryPid, ModuleId, ModuleVersion); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyEitEvent.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyEitEvent.cs new file mode 100644 index 0000000..4edb29d --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyEitEvent.cs @@ -0,0 +1,40 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyEitEvent + { + public ushort ServiceId { get; } + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public ushort EventId { get; } + public DateTime StartTime { get; } + + public DatabaseKeyEitEvent(ushort serviceId, ushort transportStreamId, ushort originalNetworkId, ushort eventId, DateTime startTime) + { + ServiceId = serviceId; + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + EventId = eventId; + StartTime = startTime; + } + + protected bool Equals(DatabaseKeyEitEvent other) + { + return ServiceId == other.ServiceId && TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId && EventId == other.EventId && StartTime.Equals(other.StartTime); + } + + 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((DatabaseKeyEitEvent)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(ServiceId, TransportStreamId, OriginalNetworkId, EventId, StartTime); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyInt.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyInt.cs new file mode 100644 index 0000000..a9d2eec --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyInt.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyInt : IEquatable + { + public DatabaseKeyInt(uint id) + { + Id = id; + } + + public uint Id { get; } + + public override bool Equals(object obj) + { + return Equals(obj as DatabaseKeyInt); + } + + public bool Equals(DatabaseKeyInt other) + { + return other is not null && + Id == other.Id; + } + + public override int GetHashCode() + { + return HashCode.Combine(Id); + } + + public static bool operator ==(DatabaseKeyInt left, DatabaseKeyInt right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(DatabaseKeyInt left, DatabaseKeyInt right) + { + return !(left == right); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyNitNetwork.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyNitNetwork.cs new file mode 100644 index 0000000..a984fb5 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyNitNetwork.cs @@ -0,0 +1,30 @@ +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyNitNetwork + { + public ushort Id { get; } + + public DatabaseKeyNitNetwork(ushort id) + { + Id = id; + } + + protected bool Equals(DatabaseKeyNitNetwork other) + { + return Id == other.Id; + } + + 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((DatabaseKeyNitNetwork)obj); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyNitTs.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyNitTs.cs new file mode 100644 index 0000000..adaddbc --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyNitTs.cs @@ -0,0 +1,34 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyNitTs + { + public ushort NetworkId { get; } + public ushort TransportStreamId { get; } + + public DatabaseKeyNitTs(ushort networkId, ushort transportStreamId) + { + NetworkId = networkId; + TransportStreamId = transportStreamId; + } + + protected bool Equals(DatabaseKeyNitTs other) + { + return NetworkId == other.NetworkId && TransportStreamId == other.TransportStreamId; + } + + 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((DatabaseKeyNitTs)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(NetworkId, TransportStreamId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyPatEntry.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyPatEntry.cs new file mode 100644 index 0000000..6a9f057 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyPatEntry.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyPatEntry + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public ushort ProgramId { get; } + + public DatabaseKeyPatEntry(int currentNetworkId, int currentTransportStreamId, ushort programId) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + ProgramId = programId; + } + + protected bool Equals(DatabaseKeyPatEntry other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && ProgramId == other.ProgramId; + } + + 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((DatabaseKeyPatEntry)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, ProgramId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyPmtEntry.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyPmtEntry.cs new file mode 100644 index 0000000..b282beb --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyPmtEntry.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyPmtEntry + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public ushort ProgramNumber { get; } + + public DatabaseKeyPmtEntry(int currentNetworkId, int currentTransportStreamId, ushort programNumber) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + ProgramNumber = programNumber; + } + + protected bool Equals(DatabaseKeyPmtEntry other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && ProgramNumber == other.ProgramNumber; + } + + 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((DatabaseKeyPmtEntry)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, ProgramNumber); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRmtLinkage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRmtLinkage.cs new file mode 100644 index 0000000..0082346 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRmtLinkage.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyRmtLinkage + { + public DatabaseKeyRmtLinkage(ushort transportStreamId, ushort originalNetworkId, ushort serviceId, byte linkageType, ushort interactiveNetworkId) + { + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + ServiceId = serviceId; + LinkageType = linkageType; + InteractiveNetworkId = interactiveNetworkId; + } + + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public ushort ServiceId { get; } + public byte LinkageType { get; } + public ushort InteractiveNetworkId { get; } + + public override bool Equals(object obj) + { + return obj is DatabaseKeyRmtLinkage linkage && + TransportStreamId == linkage.TransportStreamId && + OriginalNetworkId == linkage.OriginalNetworkId && + ServiceId == linkage.ServiceId && + LinkageType == linkage.LinkageType && + InteractiveNetworkId == linkage.InteractiveNetworkId; + } + + public override int GetHashCode() + { + return HashCode.Combine(TransportStreamId, OriginalNetworkId, ServiceId, LinkageType, InteractiveNetworkId); + } + + public override string ToString() + { + return String.Format("RMT Linkage: {4} -> {1},{0}, SID {2}, Type {3}", TransportStreamId, OriginalNetworkId, ServiceId, LinkageType, InteractiveNetworkId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRmtTransportStream.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRmtTransportStream.cs new file mode 100644 index 0000000..5dfb5bd --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRmtTransportStream.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyRmtTransportStream + { + public DatabaseKeyRmtTransportStream(ushort networkId, ushort transportStreamId, ushort originalNetworkId) + { + NetworkId = networkId; + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + } + + public ushort NetworkId { get; } + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + + public override bool Equals(object obj) + { + return obj is DatabaseKeyRmtTransportStream stream && + NetworkId == stream.NetworkId && + TransportStreamId == stream.TransportStreamId && + OriginalNetworkId == stream.OriginalNetworkId; + } + + public override int GetHashCode() + { + return HashCode.Combine(NetworkId, TransportStreamId, OriginalNetworkId); + } + + public override string ToString() + { + return String.Format("{0} -> {1},{2}", NetworkId, OriginalNetworkId, TransportStreamId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRst.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRst.cs new file mode 100644 index 0000000..e6377db --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyRst.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyRst : IEquatable + { + private uint transportStreamId; + private uint originalNetworkId; + private uint serviceId; + private uint eventId; + private RunningStatus runningStatus; + private DateTime currentTime; + + public DatabaseKeyRst(uint transportStreamId, uint originalNetworkId, uint serviceId, uint eventId, RunningStatus runningStatus, DateTime currentTime) + { + this.transportStreamId = transportStreamId; + this.originalNetworkId = originalNetworkId; + this.serviceId = serviceId; + this.eventId = eventId; + this.runningStatus = runningStatus; + this.currentTime = currentTime; + } + + public override bool Equals(object obj) + { + return Equals(obj as DatabaseKeyRst); + } + + public bool Equals(DatabaseKeyRst other) + { + return other is not null && + transportStreamId == other.transportStreamId && + originalNetworkId == other.originalNetworkId && + serviceId == other.serviceId && + eventId == other.eventId && + runningStatus == other.runningStatus && + currentTime == other.currentTime; + } + + public override int GetHashCode() + { + return HashCode.Combine(transportStreamId, originalNetworkId, serviceId, eventId, runningStatus, currentTime); + } + + public static bool operator ==(DatabaseKeyRst left, DatabaseKeyRst right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(DatabaseKeyRst left, DatabaseKeyRst right) + { + return !(left == right); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyScte35TimeSignal.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyScte35TimeSignal.cs new file mode 100644 index 0000000..e552410 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyScte35TimeSignal.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyScte35TimeSignal + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public ushort ProgramNumber { get; } + + public DatabaseKeyScte35TimeSignal(int currentNetworkId, int currentTransportStreamId, ushort programNumber) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + ProgramNumber = programNumber; + } + + protected bool Equals(DatabaseKeyScte35TimeSignal other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && ProgramNumber == other.ProgramNumber; + } + + 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((DatabaseKeyScte35TimeSignal)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, ProgramNumber); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeySdtService.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeySdtService.cs new file mode 100644 index 0000000..f6ed80e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeySdtService.cs @@ -0,0 +1,36 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeySdtService + { + public ushort TransportStreamId { get; } + public ushort OriginalNetworkId { get; } + public ushort ServiceId { get; } + + public DatabaseKeySdtService(ushort transportStreamId, ushort originalNetworkId, ushort serviceId) + { + TransportStreamId = transportStreamId; + OriginalNetworkId = originalNetworkId; + ServiceId = serviceId; + } + + protected bool Equals(DatabaseKeySdtService other) + { + return TransportStreamId == other.TransportStreamId && OriginalNetworkId == other.OriginalNetworkId && ServiceId == other.ServiceId; + } + + 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((DatabaseKeySdtService)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(TransportStreamId, OriginalNetworkId, ServiceId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyT2MI.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyT2MI.cs new file mode 100644 index 0000000..03da4bd --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyT2MI.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyT2MI : IEquatable + { + private readonly int nid; + private readonly int tsid; + private readonly int pid; + + public DatabaseKeyT2MI(int nid, int tsid, int pid) + { + this.nid = nid; + this.tsid = tsid; + this.pid = pid; + } + + public override bool Equals(object obj) + { + return Equals(obj as DatabaseKeyT2MI); + } + + public bool Equals(DatabaseKeyT2MI other) + { + return other is not null && + nid == other.nid && + tsid == other.tsid && + pid == other.pid; + } + + public override int GetHashCode() + { + return HashCode.Combine(nid, tsid, pid); + } + + public static bool operator ==(DatabaseKeyT2MI left, DatabaseKeyT2MI right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(DatabaseKeyT2MI left, DatabaseKeyT2MI right) + { + return !(left == right); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyT2MiTransmitter.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyT2MiTransmitter.cs new file mode 100644 index 0000000..38e846a --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyT2MiTransmitter.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyT2MiTransmitter : IEquatable + { + private readonly int currentNetworkId; + private readonly int currentTransportStreamId; + private readonly int relatedPid; + private readonly ushort txIdentifier; + + public DatabaseKeyT2MiTransmitter(int currentNetworkId, int currentTransportStreamId, int relatedPid, ushort txIdentifier) + { + this.currentNetworkId = currentNetworkId; + this.currentTransportStreamId = currentTransportStreamId; + this.relatedPid = relatedPid; + this.txIdentifier = txIdentifier; + } + + public override bool Equals(object obj) + { + return Equals(obj as DatabaseKeyT2MiTransmitter); + } + + public bool Equals(DatabaseKeyT2MiTransmitter other) + { + return other is not null && + currentNetworkId == other.currentNetworkId && + currentTransportStreamId == other.currentTransportStreamId && + relatedPid == other.relatedPid && + txIdentifier == other.txIdentifier; + } + + public override int GetHashCode() + { + return HashCode.Combine(currentNetworkId, currentTransportStreamId, relatedPid, txIdentifier); + } + + public static bool operator ==(DatabaseKeyT2MiTransmitter left, DatabaseKeyT2MiTransmitter right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(DatabaseKeyT2MiTransmitter left, DatabaseKeyT2MiTransmitter right) + { + return !(left == right); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTdt.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTdt.cs new file mode 100644 index 0000000..59d3528 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTdt.cs @@ -0,0 +1,34 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyTdt + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + + public DatabaseKeyTdt(int currentNetworkId, int currentTransportStreamId) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + } + + protected bool Equals(DatabaseKeyTdt other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId; + } + + 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((DatabaseKeyTdt)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTeletextPage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTeletextPage.cs new file mode 100644 index 0000000..bb05c8e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTeletextPage.cs @@ -0,0 +1,45 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyTeletextPage + { + public int NetworkId { get; } + public int TransportStreamId { get; } + public ushort ProgramNumber { get; } + public ushort PageNumber { get; } + public DateTime DateTime { get; } + + public DatabaseKeyTeletextPage(int networkId, int transportStreamId, ushort programNumber, ushort pageNumber, DateTime dateTime) + { + NetworkId = networkId; + TransportStreamId = transportStreamId; + ProgramNumber = programNumber; + PageNumber = pageNumber; + DateTime = dateTime; + } + + protected bool Equals(DatabaseKeyTeletextPage other) + { + return NetworkId == other.NetworkId && TransportStreamId == other.TransportStreamId && ProgramNumber == other.ProgramNumber && PageNumber == other.PageNumber && DateTime.Equals(other.DateTime); + } + + 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((DatabaseKeyTeletextPage)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(NetworkId, TransportStreamId, ProgramNumber, PageNumber, DateTime); + } + + public override string ToString() + { + return $"{nameof(ProgramNumber)}: {ProgramNumber}, {nameof(PageNumber)}: {PageNumber}, {nameof(DateTime)}: {DateTime}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTot.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTot.cs new file mode 100644 index 0000000..6865487 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyTot.cs @@ -0,0 +1,40 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyTot + { + public int CurrentNetworkId { get; } + public int CurrentTransportStreamId { get; } + public string OffsetCountryCode { get; } + public int LocalTimeOffset { get; } + public bool LocalTimeOffsetPolarity { get; } + + public DatabaseKeyTot(int currentNetworkId, int currentTransportStreamId, string offsetCountryCode, int localTimeOffset, bool localTimeOffsetPolarity) + { + CurrentNetworkId = currentNetworkId; + CurrentTransportStreamId = currentTransportStreamId; + OffsetCountryCode = offsetCountryCode; + LocalTimeOffset = localTimeOffset; + LocalTimeOffsetPolarity = localTimeOffsetPolarity; + } + + protected bool Equals(DatabaseKeyTot other) + { + return CurrentNetworkId == other.CurrentNetworkId && CurrentTransportStreamId == other.CurrentTransportStreamId && OffsetCountryCode == other.OffsetCountryCode && LocalTimeOffset == other.LocalTimeOffset && LocalTimeOffsetPolarity == other.LocalTimeOffsetPolarity; + } + + 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((DatabaseKeyTot)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CurrentNetworkId, CurrentTransportStreamId, OffsetCountryCode, LocalTimeOffset, LocalTimeOffsetPolarity); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntCompatibility.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntCompatibility.cs new file mode 100644 index 0000000..ad0b066 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntCompatibility.cs @@ -0,0 +1,40 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyUntCompatibility + { + public byte CommonActionType { get; } + public byte CommonOuiHash { get; } + public string CommonOui { get; } + public byte CommonProcessingOrder { get; } + public byte CompatibilityDescriptorType { get; } + + public DatabaseKeyUntCompatibility(byte commonActionType, byte commonOuiHash, string commonOui, byte commonProcessingOrder, byte compatibilityDescriptorType) + { + CommonActionType = commonActionType; + CommonOuiHash = commonOuiHash; + CommonOui = commonOui; + CommonProcessingOrder = commonProcessingOrder; + CompatibilityDescriptorType = compatibilityDescriptorType; + } + + protected bool Equals(DatabaseKeyUntCompatibility other) + { + return CommonActionType == other.CommonActionType && CommonOuiHash == other.CommonOuiHash && CommonOui == other.CommonOui && CommonProcessingOrder == other.CommonProcessingOrder && CompatibilityDescriptorType == other.CompatibilityDescriptorType; + } + + 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((DatabaseKeyUntCompatibility)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CommonActionType, CommonOuiHash, CommonOui, CommonProcessingOrder, CompatibilityDescriptorType); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntGroup.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntGroup.cs new file mode 100644 index 0000000..ba9b61f --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntGroup.cs @@ -0,0 +1,38 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyUntGroup + { + public byte CommonActionType { get; } + public byte CommonOuiHash { get; } + public string CommonOui { get; } + public byte CommonProcessingOrder { get; } + + public DatabaseKeyUntGroup(byte commonActionType, byte commonOuiHash, string commonOui, byte commonProcessingOrder) + { + CommonActionType = commonActionType; + CommonOuiHash = commonOuiHash; + CommonOui = commonOui; + CommonProcessingOrder = commonProcessingOrder; + } + + protected bool Equals(DatabaseKeyUntGroup other) + { + return CommonActionType == other.CommonActionType && CommonOuiHash == other.CommonOuiHash && CommonOui == other.CommonOui && CommonProcessingOrder == other.CommonProcessingOrder; + } + + 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((DatabaseKeyUntGroup)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CommonActionType, CommonOuiHash, CommonOui, CommonProcessingOrder); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntPlatform.cs b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntPlatform.cs new file mode 100644 index 0000000..13a92c7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/Utilities/DatabaseKeyUntPlatform.cs @@ -0,0 +1,42 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper.Storage.Utilities +{ + public class DatabaseKeyUntPlatform + { + public byte CommonActionType { get; } + public byte CommonOuiHash { get; } + public string CommonOui { get; } + public byte CommonProcessingOrder { get; } + public byte CompatibilityDescriptorType { get; } + public int PlatformKey { get; } + + public DatabaseKeyUntPlatform(byte commonActionType, byte commonOuiHash, string commonOui, byte commonProcessingOrder, byte compatibilityDescriptorType, int platformKey) + { + CommonActionType = commonActionType; + CommonOuiHash = commonOuiHash; + CommonOui = commonOui; + CommonProcessingOrder = commonProcessingOrder; + CompatibilityDescriptorType = compatibilityDescriptorType; + PlatformKey = platformKey; + } + + protected bool Equals(DatabaseKeyUntPlatform other) + { + return CommonActionType == other.CommonActionType && CommonOuiHash == other.CommonOuiHash && CommonOui == other.CommonOui && CommonProcessingOrder == other.CommonProcessingOrder && CompatibilityDescriptorType == other.CompatibilityDescriptorType && PlatformKey == other.PlatformKey; + } + + 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((DatabaseKeyUntPlatform)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(CommonActionType, CommonOuiHash, CommonOui, CommonProcessingOrder, CompatibilityDescriptorType, PlatformKey); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestant.cs new file mode 100644 index 0000000..6fc0013 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestant.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection +{ + public abstract class Contestant : IPayloadUnitDecoder, IDisposable + { + protected Contestant(string tag, int pid) + { + Tag = tag; + Pid = pid; + } + + public long Score { get; set; } + public string Tag { get; protected set; } + public int Pid { get; } + public ITsPacketProcessor PacketProcessor { get; protected set; } + + public override string ToString() + { + return $"{nameof(Tag)}: {Tag}, {nameof(Score)}: {Score}"; + } + + public abstract void Dispose(); + + public void PacketLoss() + { + IPayloadUnitDecoder payloadUnitDecoder = PacketProcessor as IPayloadUnitDecoder; + if (payloadUnitDecoder != null) + payloadUnitDecoder.PacketLoss(); + } + + public abstract void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/ContestantTeam.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/ContestantTeam.cs new file mode 100644 index 0000000..79859e8 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/ContestantTeam.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection +{ + class ContestantTeam : ITsPacketProcessor + { + public ITsPacketProcessor[] Members { get; } + + public ContestantTeam(params ITsPacketProcessor[] members) + { + Members = members; + } + + public void PushPacket(TsPacket packet) + { + foreach (ITsPacketProcessor processor in Members) + { + processor.PushPacket(packet); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/AbertisContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/AbertisContestant.cs new file mode 100644 index 0000000..68610a0 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/AbertisContestant.cs @@ -0,0 +1,31 @@ +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + /*internal class AbertisContestant : Contestant, AbertisDecoderEventHandler + { + public AbertisContestant(int pid) : base("Abertis", pid) + { + PacketProcessor = new AbertisDecoder(pid, this); + } + + public override void Dispose() + { + + } + + private bool scoringThrottler; + public void OnAbertisPacket(int pid, byte[] outBuffer) + { + if (scoringThrottler) + Score++; + scoringThrottler = !scoringThrottler; + } + + public void OnAbertisSyncLoss(int pid, int oldPosition = -1, long newPosition = -1) + { + Score--; + } + + //DvbContext.RegisterPacketProcessor(pid, new AbertisDecoder(pid, skyscraperContestant)); + + }*/ +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/AitContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/AitContestant.cs new file mode 100644 index 0000000..30d2b1e --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/AitContestant.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mhp; +using skyscraper5.Mhp.Si; +using skyscraper5.Mhp.Si.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class AitContestant : Contestant, IAitEventHandler + { + public AitContestant(int pid) : base("AIT", pid) + { + PacketProcessor = new PsiDecoder(pid, new AitParser(this, 0)); + } + + public void OnAitApplication(AitApplication aitApplication, ushort programNumber) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new AitParser(skyscraperContext, programContext.ProgramNumber))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/BatContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/BatContestant.cs new file mode 100644 index 0000000..823a9b2 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/BatContestant.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class BatContestant : Contestant, IBatEventHandler, IDisposable + { + public BatContestant(int pid) : base("BAT", pid) + { + PacketProcessor = new PsiDecoder(pid, new BatParser(this)); + } + + public void OnBatBouquet(BatBouquet batBouquet) + { + Score++; + } + + public void OnBatTransportStream(BatBouquet batBouquet, BatTransportStream child) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/CatContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/CatContestant.cs new file mode 100644 index 0000000..9714fc4 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/CatContestant.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Mpeg2.Psi; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class CatContestant : Contestant, ICatEventHandler, IDisposable + { + public CatContestant(int pid) : base("CAT", pid) + { + PacketProcessor = new PsiDecoder(pid, new CatParser(this)); + } + + public void NotifyOfCaSystem(CaDescriptor caDescriptor, bool fromPmt = false) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/DataCarouselContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/DataCarouselContestant.cs new file mode 100644 index 0000000..8b72188 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/DataCarouselContestant.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.DsmCc.Descriptors; +using skyscraper5.DsmCc.Message; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Dvb.DataBroadcasting.Biop; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Event; +using skyscraper5.Dvb.DataBroadcasting.Dsm.Stream; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class DataCarouselContestant : Contestant, DataCarouselEventHandler + { + public DataCarouselContestant(int pid) : base("DataCarousel", pid) + { + ProgramMappingStream pmtStream = new ProgramMappingStream(PmtStreamType.Iso13818_1DsmCc, pid); + pmtStream.ComponentTag = 3; + ProgramMapping pmt = new ProgramMapping(1, 2); + pmt.Streams.Add(pmtStream); + dataCarouselDecoder = new DataCarouselDecoder(pmtStream, pmt, DataCarouselIntention.NoIntention, this); + PacketProcessor = new PsiDecoder(pid, dataCarouselDecoder); + } + + private DataCarouselDecoder dataCarouselDecoder; + + public bool IsModuleWanted(int elementaryPid, ushort moduleId, byte moduleVersion) + { + return true; + } + + public void NotifyOfNewModule(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion) + { + Score++; + } + + public void NotifyModuleDownloadProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, + double moduleInfoDownloadProgress) + { + Score++; + } + + private ObjectCarouselContestantHelper _helper; + public ObjectCarouselEventHandler GetObjectCarouselEventHandler() + { + if (_helper == null) + { + _helper = new ObjectCarouselContestantHelper(this); + } + return _helper; + } + + public void NotifyDownloadComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion, Stream result, bool isObjectCarousel) + { + Score += 2; + } + + public void DsmCcDoItNowEvent(ProgramMapping programMapping, StreamEventDescriptor descriptorListStreamEventDescriptor, int pid) + { + Score++; + } + + public void PreventDsmCcModuleRepetition(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion) + { + } + + private class ObjectCarouselContestantHelper : ObjectCarouselEventHandler + { + private readonly DataCarouselContestant dataCarouselContestant; + + public ObjectCarouselContestantHelper(DataCarouselContestant dataCarouselContestant) + { + this.dataCarouselContestant = dataCarouselContestant; + } + + public void NotifyFileArrival(VfsFile vfsFile) + { + dataCarouselContestant.Score++; + } + + public void NotifySubStream(ProgramMapping vfsProgramMapping, Tap tap, ushort[] eventIds, Dictionary context, EventList_T eventListT, Info_T infoT) + { + dataCarouselContestant.Score++; + } + } + + public override void Dispose() + { + dataCarouselDecoder.Dispose(); + dataCarouselDecoder = null; + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + if (!programContext.Stream.ComponentTag.HasValue) + { + skyscraperContext.MakeUpComponentTags(programContext.Program); + } + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new DataCarouselDecoder(programContext.Stream, programContext.Program, DataCarouselIntention.NoIntention, skyscraperContext))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/DisqualifiedContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/DisqualifiedContestant.cs new file mode 100644 index 0000000..8fc1b27 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/DisqualifiedContestant.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.Utils; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class DisqualifiedContestant : Contestant, IDisposable + { + public DisqualifiedContestant(int pid) : base("Disqualified", pid) + { + PacketProcessor = new PacketDiscarder(); + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/EitContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/EitContestant.cs new file mode 100644 index 0000000..4dc77a7 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/EitContestant.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class EitContestant : Contestant, IEitEventHandler, IDisposable + { + public EitContestant(int pid) : base("EIT", pid) + { + PacketProcessor = new PsiDecoder(pid, new EitParser(this)); + } + + public void SetTransportStreamId(ushort transportStreamId) + { + } + + public void SetNetworkId(ushort networkId, bool forceOverwrite = false) + { + } + + public void OnEitEvent(EitEvent eitEvent) + { + if (string.IsNullOrEmpty(eitEvent.EventName)) + return; + + char[] charArray = eitEvent.EventName.ToCharArray(); + int required = charArray.Length / 2; + int found = 0; + foreach (char c in charArray) + if (c > 32 && c < 126) + found++; + + if (found > required) + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new EitParser(skyscraperContext))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Id3Contestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Id3Contestant.cs new file mode 100644 index 0000000..f3d449a --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Id3Contestant.cs @@ -0,0 +1,42 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.src.Id3; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + internal class Id3Contestant : Contestant, Id3Handler + { + public Id3Contestant(int pid) : base("ID3 Tag", pid) + { + PacketProcessor = new PesDecoder(new Id3PesProcessor(this)); + } + + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + } + + public void OnId3Error(Id3ErrorState state) + { + Score--; + } + + public void OnId3Tag(Id3Tag tag) + { + Score++; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/IntContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/IntContestant.cs new file mode 100644 index 0000000..cd4ce0d --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/IntContestant.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Dvb.DataBroadcasting.IntModel; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + internal class IntContestant : Contestant, IntEventHandler, IDisposable + { + public IntContestant(int pid) : base("INT", pid) + { + PacketProcessor = new PsiDecoder(pid,new IntParser(this)); + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + + public void OnIpMacNotification(int sourcePid, Platform platform, Target target, Operational operational) + { + Score++; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/InteractionChannelContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/InteractionChannelContestant.cs new file mode 100644 index 0000000..520ae64 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/InteractionChannelContestant.cs @@ -0,0 +1,143 @@ +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.src.InteractionChannel; +using skyscraper5.src.InteractionChannel.Model; +using skyscraper5.src.InteractionChannel.Model.Descriptors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + internal class InteractionChannelContestant : Contestant, InteractionChannelHandler, IDisposable + { + public InteractionChannelContestant(int pid) + : base("Interaction channel for satellite distribution systems",pid) + { + InteractionChannelPsiGatherer interactionChannelPsiGatherer = new InteractionChannelPsiGatherer(); + interactionChannelPsiGatherer.Handler = this; + PacketProcessor = new PsiDecoder(pid, interactionChannelPsiGatherer); + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + + } + + public int GetRmtTransmissionStandard(ushort networkId) + { + throw new NotImplementedException(); + } + + public void OnContentionControl(PhysicalAddress macAddress, _0xab_ContentionControlDescriptor descriptor) + { + Score++; + } + + public void OnCorrectionControl(PhysicalAddress macAddress, _0xac_CorrectionControlDescriptor descriptor) + { + Score++; + } + + public void OnCorrectionMessage(ushort interactiveNetworkId, Cmt cmt) + { + if (cmt.Entries.Length > 0) + { + Score++; + } + } + + public void OnCorrectionMessage(PhysicalAddress mac, _0xa1_CorrectionMessageDescriptor cmd) + { + Score++; + } + + public void OnFrameComposition(ushort interactiveNetworkId, Fct fct) + { + if (fct.Frames.Length > 0) + { + Score++; + } + } + + public void OnInteractionChannelError(InteractionChannelErrorState unexpectedTable) + { + Score--; + } + + public void OnNetworkLayerInfo(PhysicalAddress macAddress, _0xa0_NetworkLayerInfoDescriptor descriptor) + { + Score++; + } + + public void OnRcsMap(Rmt rmt) + { + if (rmt.Linkages != null) + { + if (rmt.Linkages.Count > 0) + { + Score++; + return; + } + } + if (rmt.TransportStreams != null) + { + if (rmt.TransportStreams.Count > 0) + { + Score++; + } + } + } + + public void OnSatellitePosition(ushort interactiveNetworkId, Spt spt) + { + if (spt.Satellites.Length > 0) + { + Score++; + } + } + + public void OnSuperframeComposition(ushort interactiveNetworkId, Sct sct) + { + if (sct.Superframes.Length > 0) + { + Score++; + } + } + + public void OnTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp) + { + if (tbtp.Frames.Length > 0) + { + Score++; + } + } + + public void OnTimeslotComposition(ushort interactiveNetworkId, Tct tct) + { + if (tct.Timeslots.Length > 0) + { + Score++; + } + } + + public void OnTransmissionModeSupport(ushort interactiveNetworkId, Tmst tmst) + { + if (tmst.Modes.Length > 0) + { + Score++; + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/MpeContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/MpeContestant.cs new file mode 100644 index 0000000..29f065a --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/MpeContestant.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Ietf.Rfc2460; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + internal class MpeContestant : Contestant, IMultiprotocolEncapsulationEventHandler + { + public MpeContestant(int pid) + : base("Multiprotocol Encapsulation", pid) + { + PacketProcessor = new PsiDecoder(pid, new MultiprotocolEncapsulationDecoder(this)); + } + + public override void Dispose() + { + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new MultiprotocolEncapsulationDecoder(skyscraperContext))); + } + + public void OnIpv4PacketArrival(InternetHeader internetHeader, byte[] ipv4Packet) + { + Score++; + } + + public void OnIpDatagram(int sourcePid, byte[] payload) + { + int ipVersion = (payload[0] & 0xf0) >> 4; + if (ipVersion == 4) + { + int ihl = (payload[0] & 0x0f); + if (ihl == 0) + { + Score--; + return; + } + ihl *= 32; + ihl /= 8; + byte[] ipv4Header = new byte[ihl]; + Array.Copy(payload, 0, ipv4Header, 0, ihl); + InternetHeader internetHeader = new InternetHeader(ipv4Header); + int testLength = payload.Length - ihl; + if (payload.Length != internetHeader.TotalLength) + { + Score--; + return; + } + + byte[] ipv4Packet = new byte[testLength]; + Array.Copy(payload, ihl, ipv4Packet, 0, testLength); + OnIpv4PacketArrival(internetHeader, ipv4Packet); + } + else if (ipVersion == 6) + { + Ipv6Header ipv6Header = new Ipv6Header(payload); + if (!ipv6Header.Valid) + { + Score--; + return; + } + + int testLength = ipv6Header.PayloadLength + 40; + if (payload.Length != testLength) + { + Score--; + return; + } + throw new NotSupportedException("IPv6"); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/NitContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/NitContestant.cs new file mode 100644 index 0000000..469b4cc --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/NitContestant.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class NitContestant : Contestant, INitEventHandler, IDisposable + { + public NitContestant(int pid) : base("NIT", pid) + { + PacketProcessor = new PsiDecoder(pid, new NitParser(this)); + } + + public void SetNetworkId(ushort networkId, bool forceOverrite = false) + { + } + + public void OnNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + Score++; + } + + public void OnNitNetwork(NitNetwork nitNetwork) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new NitParser(skyscraperContext))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/PatContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/PatContestant.cs new file mode 100644 index 0000000..0e7a960 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/PatContestant.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class PatContestant : Contestant, IPatEventHandler, IDisposable + { + public PatContestant(int pid) : base("PAT",pid) + { + PacketProcessor = new PsiDecoder(pid, new PatParser(this)); + } + + public void NetworkPidFromPat(int networkPid) + { + Score++; + } + + public void ProgramMapPidFromPat(int pmtPid, ushort programNumber) + { + Score++; + } + + public void SetTransportStreamId(ushort transportStreamId) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Pid0x11Contestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Pid0x11Contestant.cs new file mode 100644 index 0000000..0c927fc --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Pid0x11Contestant.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class Pid0x11Contestant : Contestant, IBatEventHandler, ISdtEventHandler, IDisposable + { + public Pid0x11Contestant(int pid) : base("BAT/SDT", pid) + { + PacketProcessor = new PsiDecoder(pid,new Pid0x11Decoder(this, this)); + } + + public void OnBatBouquet(BatBouquet batBouquet) + { + Score++; + } + + public void OnBatTransportStream(BatBouquet batBouquet, BatTransportStream child) + { + Score++; + } + + public void OnSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + FreesatTunnelScraper fts = new FreesatTunnelScraper(); + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new Pid0x11Decoder(fts, fts))); + } + + public void SetNetworkId(ushort networkId) + { + } + + public void SetTransportStreamId(ushort transportStreamId) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/PmtContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/PmtContestant.cs new file mode 100644 index 0000000..61c6831 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/PmtContestant.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class PmtContestant : Contestant, IPmtEventHandler, IDisposable + { + public PmtContestant(int pid) : base("PMT", pid) + { + PacketProcessor = new PsiDecoder(pid, new PmtParser(this)); + } + + public void PmtEvent(ProgramMapping result, int pmtPid) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new PmtParser(skyscraperContext))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/RdsContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/RdsContestant.cs new file mode 100644 index 0000000..fc392e9 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/RdsContestant.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Rds; +using skyscraper5.Rds.Messages; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class RdsContestant : Contestant, RdsEventHandler, IDisposable + { + public RdsContestant(int pid) : base("RDS", pid) + { + PacketProcessor = new PesDecoder(new RdsPesProcessor(pid, 1, this)); + } + + public void SetRdsProgrammeServiceName(int programNumber, string programmeService2) + { + Score++; + } + + public bool TestCanHandleRdsMessages(int programNumber) + { + return true; + } + + public void SetRdsRadioText(int programNumber, string text) + { + Score++; + } + + public void SetCurrentTime(DateTime currentTime) + { + Score++; + } + + public void SetCurrentProgrammeType(int programNumber, PTY.ProgrammeTypeCodes pty) + { + Score++; + } + + public void MarkAsRdsTrafficInformationProgramme(int programNumber) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Scte35Contestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Scte35Contestant.cs new file mode 100644 index 0000000..34b316b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/Scte35Contestant.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mhp.Si; +using skyscraper5.Mpeg2; +using skyscraper5.Scte35; +using skyscraper5.Scte35.Descriptors; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class Scte35Contestant : Contestant, IScte35EventHandler, IDisposable + { + public Scte35Contestant(int pid) : base("SCTE35", pid) + { + PacketProcessor = new PsiDecoder(pid, new Scte35SiDecoder(1, this)); + } + + public void NotifySpliceInsert(ushort ProgramNumber, SpliceInsert spliceInsert) + { + Score++; + } + + public void NotifyTimeSignal(ushort programNumber, TimeSignal timeSignal) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new Scte35SiDecoder(programContext.ProgramNumber, skyscraperContext))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/SdtContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/SdtContestant.cs new file mode 100644 index 0000000..3089535 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/SdtContestant.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Psi; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class SdtContestant : Contestant, ISdtEventHandler, IDisposable + { + public SdtContestant(int pid) : base("SDT", pid) + { + PacketProcessor = new PsiDecoder(pid, new SdtParser(this)); + } + + public void OnSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + + public void SetNetworkId(ushort networkId) + { + } + + public void SetTransportStreamId(ushort transportStreamId) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/SubtitleContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/SubtitleContestant.cs new file mode 100644 index 0000000..ef2092a --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/SubtitleContestant.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Subtitling; +using skyscraper5.Dvb.Subtitling.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class SubtitleContestant : Contestant, ISubtitleEventHandler, IDisposable + { + public SubtitleContestant(int pid) : base("DVBSubtitle", pid) + { + PacketProcessor = new PesDecoder(new SubtitleDecoder(this)); + } + + public void NotifyOfSubtitleLine(Page page) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + PesDecoder pesDecoder = new PesDecoder(new SubtitleDecoder(skyscraperContext)); + pesDecoder.Tag = "subs"; + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, pesDecoder); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/T2MiContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/T2MiContestant.cs new file mode 100644 index 0000000..a1b2673 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/T2MiContestant.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.T2MI; +using skyscraper5.T2MI.Packets; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + internal class T2MiContestant : Contestant, T2MIEventHandler + { + public T2MiContestant(int pid) : base("T2-MI", pid) + { + PacketProcessor = new T2MIDecoder(pid, this); + } + + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new T2MIDecoder(pid, skyscraperContext)); + } + + public void OnT2MiPacketLoss(int pid, byte expectedPacket, T2MIHeader header) + { + if (Score > 0) + Score = 0; + Score--; + } + + private bool hasPackets; + public void OnT2MiPacket(int pid, byte basebandFramePlpId, byte[] basebandPacket) + { + Score++; + hasPackets = true; + } + + public void OnT2MiTimestamp(int pid, _0x20_DvbT2Timestamp t2Timestamp) + { + if (t2Timestamp.Valid && hasPackets) + Score++; + } + + public void OnT2MiIqData(int relatedPid, _0x31_IqData iqData) + { + if (hasPackets) + Score++; + } + + public void OnT2MiIqData(int relatedPid, _0x01_IqData iqData2) + { + if (hasPackets) + Score++; + } + + public void OnT2MiError(int relatedPid, int skyscraperErrorCode) + { + Score--; + } + + public void OnT2MiL1Current(int relatedPid, _0x10_L1Current l1Current) + { + if (hasPackets) + Score++; + } + + public void OnT2MiArbitraryCellInsertion(int relatedPid, _0x02_ArbitraryCellInsertion arbitraryCellInsertion) + { + if (hasPackets) + Score++; + } + + public void OnT2MiBalancingCells(int relatedPid, byte frameIndex, uint numActiveBiasCellsPerP2) + { + if (hasPackets) + Score++; + } + + public void OnT2MiL1Future(int relatedPid, _0x11_L1Future l1Future) + { + if (hasPackets) + Score++; + } + + public void OnT2MiIndividualAddressing(int relatedPid, _0x21_IndividualAddressing individualAddressing) + { + if (hasPackets) + Score++; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TeletextContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TeletextContestant.cs new file mode 100644 index 0000000..865c79a --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TeletextContestant.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.Utils; +using skyscraper5.src.Teletext; +using skyscraper5.Teletext; +using skyscraper5.Teletext.Vps; +using skyscraper5.Teletext.Wss; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class TeletextContestant : Contestant, ITeletextPageHandler, IDisposable + { + public TeletextContestant(int pid) : base("Teletext", pid) + { + PacketProcessor = new PesDecoder(new TeletextPesProcessor(this, 0, 0, 0)); + } + + public void OnVpsData(int networkId, int transportStreamId, ushort programNumber, VpsDataBlock vpsDataField) + { + Score++; + } + + public void OnWssData(int networkId, int transportStreamId, ushort programNumber, WssDataBlock wssDataBlock) + { + Score++; + } + + public void OnTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + if (!skyscraperContext.CurrentNetworkId.HasValue) + { + //Since we need the network id for teletext, but we don't have it - we'll just have to discard this. + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PacketDiscarder()); + return; + } + TeletextPesProcessor ttp = new TeletextPesProcessor(skyscraperContext, skyscraperContext.CurrentNetworkId.Value, skyscraperContext.CurrentTransportStreamId.Value, programContext.ProgramNumber); + ttp.PrivateDataSpecifier = programContext.PrivateDataSpecifier; + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PesDecoder(ttp)); + } + + public void OnMonochromeData(int networkId, int transportStreamId, ushort programNumber, MonochromeDataField result) + { + Score++; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TimeContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TimeContestant.cs new file mode 100644 index 0000000..84c1927 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TimeContestant.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class TimeContestant : Contestant, ITdtEventHandler, ITotEventHandler, IDisposable + { + public TimeContestant(int pid) : base("TDT/TOT", pid) + { + PacketProcessor = new PsiDecoder(pid, new TimetableParser(this, this)); + } + + public void OnTdtTime(DateTime utcTime) + { + Score++; + } + + public void OnTotTime(DateTime utcTime, LocalTimeOffsetDescriptor ltod) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + FreesatTunnelScraper fts = new FreesatTunnelScraper(); + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new Pid0x11Decoder(fts, fts))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TsdtContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TsdtContestant.cs new file mode 100644 index 0000000..ac6b224 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/TsdtContestant.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class TsdtContestant : Contestant, ITsdtEventHandler, IDisposable + { + public TsdtContestant(int pid) : base("TSDT", pid) + { + PacketProcessor = new PsiDecoder(pid, new TsdtParser(this)); + } + + public void NotifyOfCompliance(string compliance) + { + Score++; + } + + public void NotifiyStationIdentification(string stationIdentification) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new TsdtParser(skyscraperContext))); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/UntContestant.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/UntContestant.cs new file mode 100644 index 0000000..4309d36 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/Contestants/UntContestant.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.SystemSoftwareUpdate; +using skyscraper5.Dvb.SystemSoftwareUpdate.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi.Model; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants +{ + [SkyscraperPlugin] + class UntContestant : Contestant, UpdateNotificationEventHandler, IDisposable + { + public UntContestant(int pid) : base("UNT", pid) + { + ProgramMapping pmt = new ProgramMapping(1, 2); + PacketProcessor = new PsiDecoder(pid, new UntDecoder(this, pmt)); + } + + public void UpdateNotification(UpdateNotificationGroup common, UpdateNotificationTarget target, ushort ProgramNumber) + { + Score++; + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/IAutodetectionEventHandler.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/IAutodetectionEventHandler.cs new file mode 100644 index 0000000..cfd6265 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/IAutodetectionEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection +{ + interface IAutodetectionEventHandler + { + void AutodetectionRuleOut(int pid, Contestant contestant, ProgramContext programContext); + void AutodetectionSucessful(int pid, Contestant contestant, ProgramContext programContext); + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/ProgramContext.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/ProgramContext.cs new file mode 100644 index 0000000..bf2194c --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/ProgramContext.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2.Psi.Model; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection +{ + public class ProgramContext + { + public ProgramMapping Program { get; set; } + + public ushort ProgramNumber => Program.ProgramNumber; + public ProgramMappingStream Stream { get; set; } + + public uint? PrivateDataSpecifier + { + get + { + if (Stream.PrivateDataSpecifier.HasValue) + return Stream.PrivateDataSpecifier; + if (Program.PrivateDataSpecifier.HasValue) + return Program.PrivateDataSpecifier; + return null; + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs new file mode 100644 index 0000000..4ecbcbb --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Psi; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection +{ + class StreamTypeAutodetection : ITsPacketProcessor, IDisposable, IPayloadUnitDecoder + { + public int Pid { get; } + public IAutodetectionEventHandler EventHandler { get; } + + public ProgramContext ProgramContext { get; } + + private const bool CATCH_EXCEPTIONS = true; + + public StreamTypeAutodetection(int pid, IAutodetectionEventHandler eventHandler) + { + Pid = pid; + EventHandler = eventHandler; + Scoreboard = CreateScoreboard(pid); + ProgramContext = new ProgramContext(); + } + + private Contestant[] Scoreboard; + private bool winnerDetermined; + private List memorials; + + public void PushPacket(TsPacket packet) + { + if (winnerDetermined) + throw new ApplicationException("Autodetection contest winner declared, but still pushing packets."); + + for (int i = 0; i < Scoreboard.Length; i++) + { + if (CATCH_EXCEPTIONS) + { + try + { + if (Scoreboard[i] != null) + Scoreboard[i].PacketProcessor.PushPacket(packet); + } + catch (Exception e) + { + if (memorials == null) + memorials = new List(); + memorials.Add(e); + Scoreboard[i].Score--; + } + if (Scoreboard[i].Score <= -10) + { + //Console.WriteLine("PID 0x{0:X4} is probably not for {1}", Pid, Scoreboard[i].GetType().Name); + EventHandler.AutodetectionRuleOut(Pid, Scoreboard[i], ProgramContext); + Scoreboard[i] = new DisqualifiedContestant(Scoreboard[i].Pid); + } + } + else + { + if (Scoreboard[i] != null) + Scoreboard[i].PacketProcessor.PushPacket(packet); + } + } + + if (SomethingHappened()) + { + Array.Sort(Scoreboard, (x, y) => y.Score.CompareTo(x.Score)); + Contestant leader = Scoreboard[0]; + Contestant runnerUp = Scoreboard[1]; + long scoreDifference = leader.Score - runnerUp.Score; + if (scoreDifference > 5) + { + EventHandler.AutodetectionSucessful(Pid, leader, ProgramContext); + winnerDetermined = true; + Dispose(); + } + } + } + + private bool SomethingHappened() + { + foreach (Contestant contestant in Scoreboard) + if (contestant.Score != 0) + return true; + return false; + } + + #region Scoreboard Creation + public static Contestant[] CreateScoreboard(int pid) + { + ReadOnlyCollection contestants = PluginManager.GetInstance().GetStreamTypeAutodetectionContestants(); + + if (contestants.Count == 0) + throw new ApplicationException("failed to load autodetection modules"); + + if (contestants.Count < 2) + throw new ApplicationException("Need at least two autodetection modules"); + + Type[] intType = new Type[] {typeof(int)}; + object[] intArgs = new object[] { pid }; + Contestant[] result = new Contestant[contestants.Count]; + for (int i = 0; i < result.Length; i++) + { + Type spawner = contestants[i]; + ConstructorInfo constructorInfo = spawner.GetConstructor(intType); + result[i] = (Contestant)constructorInfo.Invoke(intArgs); + } + + return result; + } + #endregion + + public void Dispose() + { + for (int i = 0; i < Scoreboard.Length; i++) + { + Scoreboard[i].Dispose(); + Scoreboard[i] = null; + } + } + + public void PacketLoss() + { + if (Scoreboard == null) + return; + + for (int i = 0; i < Scoreboard.Length; i++) + { + IPayloadUnitDecoder payloadUnitDecoder = Scoreboard[i].PacketProcessor as IPayloadUnitDecoder; + if (payloadUnitDecoder != null) + payloadUnitDecoder.PacketLoss(); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetectionTest.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetectionTest.cs new file mode 100644 index 0000000..fffb950 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetectionTest.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper.Utils; + +namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection +{ + class StreamTypeAutodetectionTest : IAutodetectionEventHandler + { + public StreamTypeAutodetectionTest() + { + TsDescriptorUnpacker descriptorUnpacker = TsDescriptorUnpacker.GetInstance(); + for (byte i = 0x80; i < 0xfe; i++) + { + descriptorUnpacker.SetUserDefined(i, true); + } + } + + public void PushPacket(byte[] buffer) + { + if (tsContext == null) + tsContext = new TsContext(); + TsPacket packet = new TsPacket(buffer); + int pid = (int)packet.PID; + + if (!tsContext.IsPidProcessorPresent(pid)) + { + tsContext.RegisterPacketProcessor(pid, new StreamTypeAutodetection(pid,this)); + streamsLeft++; + } + + tsContext.PushPacket(buffer); + } + + public void IngestFromStream(Stream stream) + { + byte[] buffer = new byte[188]; + while (true) + { + if (stream.Read(buffer, 0, 188) != 188) + break; + + PushPacket(buffer); + } + stream.Close(); + } + + private TsContext tsContext; + private int streamsLeft; + public void AutodetectionSucessful(int pid, Contestant contestant, ProgramContext programContext) + { + streamsLeft--; + Console.WriteLine("Detected {0} on PID {1:X4} ({2} left...)", contestant.Tag, pid, streamsLeft); + tsContext.RegisterPacketProcessor(pid, new PacketDiscarder()); + } + + public void AutodetectionRuleOut(int pid, Contestant contestant, ProgramContext programContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamType.cs b/skyscraper8/Skyscraper/Scraper/StreamType.cs new file mode 100644 index 0000000..e9a62c6 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/StreamType.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper +{ + enum StreamType + { + Video, + Audio, + Teletext, + Unknown, + Application, + HbbTv, + Ignorable, + SystemSoftwareUpdate, + Subtitles, + DvbMhp, + Scte35, + RDS, + MultiprotocolEncapsulation, + Mheg5, + TryAutodetect, + IpMacNotification, + RelatedContentTable, + T2Mi, + InteractionChannelForSatellite, + OpenTvPrivate, + BSkyBPrivate, + Id3 + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Triplet.cs b/skyscraper8/Skyscraper/Scraper/Triplet.cs new file mode 100644 index 0000000..917cbd6 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Triplet.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Scraper +{ + internal struct Triplet + { + public LT L { get; } + public MT M { get; } + public RT R { get; } + + public Triplet(LT l, MT m, RT r) + { + L = l; + M = m; + R = r; + } + + public bool Equals(Triplet other) + { + return EqualityComparer.Default.Equals(L, other.L) && EqualityComparer.Default.Equals(M, other.M) && EqualityComparer.Default.Equals(R, other.R); + } + + public override bool Equals(object obj) + { + return obj is Triplet other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(L, M, R); + } + + public override string ToString() + { + return $"{nameof(L)}: {L}, {nameof(M)}: {M}, {nameof(R)}: {R}"; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs b/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs new file mode 100644 index 0000000..4986636 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs @@ -0,0 +1,12 @@ +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.Utils +{ + class PacketDiscarder : ITsPacketProcessor + { + public void PushPacket(TsPacket packet) + { + //This is the equivalent of piping packets to /dev/null ... + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Utils/PacketDumper.cs b/skyscraper8/Skyscraper/Scraper/Utils/PacketDumper.cs new file mode 100644 index 0000000..cbe0530 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Utils/PacketDumper.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.Utils +{ + class PacketDumper : ITsPacketProcessor + { + private readonly FileStream outputFile; + + public PacketDumper(FileInfo outFile) + { + if (outFile.Exists) + outFile.Delete(); + + outputFile = outFile.OpenWrite(); + } + + public void PushPacket(TsPacket packet) + { + outputFile.Write(packet.RawPacket, 0, packet.RawPacket.Length); + outputFile.Flush(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Utils/PacketJunction.cs b/skyscraper8/Skyscraper/Scraper/Utils/PacketJunction.cs new file mode 100644 index 0000000..bb5ebde --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Utils/PacketJunction.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.Utils +{ + class PacketJunction : ITsPacketProcessor + { + private readonly ITsPacketProcessor[] _args; + + public PacketJunction(params ITsPacketProcessor[] args) + { + _args = args; + } + + public void PushPacket(TsPacket packet) + { + foreach (ITsPacketProcessor packetProcessor in _args) + packetProcessor.PushPacket(packet); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Utils/PayloadUnitDumper.cs b/skyscraper8/Skyscraper/Scraper/Utils/PayloadUnitDumper.cs new file mode 100644 index 0000000..4efba84 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Utils/PayloadUnitDumper.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.Utils +{ + class PayloadUnitDumper : IPayloadUnitDecoder, ITsPacketProcessor + { + private ulong serial; + + private MemoryStream targetPs; + private long pushed; + private long discarded; + public object Tag { get; set; } + + public void PushPacket(TsPacket packet) + { + pushed++; + if (packet.PayloadUnitStart) + { + if (targetPs != null) + { + //Unit zu Ende... + DirectoryInfo outDir = new DirectoryInfo("dumped_pus"); + if (!outDir.Exists) + outDir.Create(); + FileInfo fi = new FileInfo(Path.Combine(outDir.FullName,String.Format("PID{0:X4}_{1:D8}.pu", packet.PID, ++serial))); + if (!fi.Exists) + { + FileStream fileStream = fi.OpenWrite(); + targetPs.Position = 0; + targetPs.CopyTo(fileStream); + targetPs.Flush(); + fileStream.Close(); + targetPs.Close(); + if (Tag != null) + { + Console.WriteLine(Tag.ToString()); + } + } + } + targetPs = new MemoryStream(); + targetPs.WriteByte((byte)packet.PayloadStartOffset); + targetPs.Write(packet.Payload, 0, packet.Payload.Length); + + } + else + { + if (targetPs == null) + { + //Kein Payload Starter UND es gibt noch nichts. Können wir also wegschmeißen. + discarded++; + } + else + { + targetPs.Write(packet.Payload, 0, packet.Payload.Length); + } + } + } + + public void PacketLoss() + { + targetPs = null; + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Utils/PesDumper.cs b/skyscraper8/Skyscraper/Scraper/Utils/PesDumper.cs new file mode 100644 index 0000000..b66fb74 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Utils/PesDumper.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.Utils +{ + class PesDumper : IPesProcessor + { + public PesDumper() + { + + } + + + public void ProcessPesPacket(PesPacket packet) + { + DirectoryInfo outDi = new DirectoryInfo("dumped_pes_packets"); + if (!outDi.Exists) + outDi.Create(); + string outFileName = Path.Combine(outDi.FullName, string.Format("{0:X16}.bin", packet.PTS)); + File.WriteAllBytes(outFileName, packet.Payload); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/Utils/SiDumper.cs b/skyscraper8/Skyscraper/Scraper/Utils/SiDumper.cs new file mode 100644 index 0000000..b9c2406 --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Utils/SiDumper.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using skyscraper5.Mpeg2; + +namespace skyscraper5.Skyscraper.Scraper.Utils +{ + class SiDumper : IPsiProcessor + { + private ulong serial; + + public void GatherPsi(PsiSection section, int sourcePid) + { + byte[] buffer = section.GetDataCopy(); + + DirectoryInfo outDir = new DirectoryInfo("dumped_si"); + if (!outDir.Exists) + outDir.Create(); + FileInfo fi = new FileInfo(Path.Combine(outDir.FullName, String.Format("PID{0:X4}_{1:D8}.si", sourcePid, ++serial))); + File.WriteAllBytes(fi.FullName, buffer); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/VpsCoordinate.cs b/skyscraper8/Skyscraper/Scraper/VpsCoordinate.cs new file mode 100644 index 0000000..e95528d --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/VpsCoordinate.cs @@ -0,0 +1,40 @@ +using System; + +namespace skyscraper5.Skyscraper.Scraper +{ + class VpsCoordinate + { + public ushort ProgramNumber { get; } + public int Month { get; } + public int Day { get; } + public int Hour { get; } + public int Minute { get; } + + public VpsCoordinate(ushort programNumber, int month, int day, int hour, int minute) + { + ProgramNumber = programNumber; + Month = month; + Day = day; + Hour = hour; + Minute = minute; + } + + protected bool Equals(VpsCoordinate other) + { + return ProgramNumber == other.ProgramNumber && Month == other.Month && Day == other.Day && Hour == other.Hour && Minute == other.Minute; + } + + 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((VpsCoordinate)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(ProgramNumber, Month, Day, Hour, Minute); + } + } +} diff --git a/skyscraper8/Skyscraper/StringExtensions.cs b/skyscraper8/Skyscraper/StringExtensions.cs new file mode 100644 index 0000000..026bdf7 --- /dev/null +++ b/skyscraper8/Skyscraper/StringExtensions.cs @@ -0,0 +1,17 @@ +namespace skyscraper5.Skyscraper +{ + static class StringExtensions + { + public static bool IsNaturalNumeric(this string str) + { + char[] charArray = str.ToCharArray(); + foreach (char c in charArray) + { + if (!char.IsDigit(c)) + return false; + } + + return true; + } + } +} diff --git a/skyscraper8/Skyscraper/Tbs6903xGsReader.cs b/skyscraper8/Skyscraper/Tbs6903xGsReader.cs new file mode 100644 index 0000000..5fa0782 --- /dev/null +++ b/skyscraper8/Skyscraper/Tbs6903xGsReader.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Skyscraper +{ + internal class Tbs6903xGsReader : ITsPacketProcessor + { + private readonly IGsEventHandler mpeEventHandler; + + public Tbs6903xGsReader(IGsEventHandler mpeEventHandler) + { + this.mpeEventHandler = mpeEventHandler; + } + + private MemoryStream outbuf; + private int assembled; + private bool annoncementDone; + + public void PushPacket(TsPacket packet) + { + byte[] packets = packet.RawPacket; + int pid = (packets[1]); + pid &= 0x01f; + pid <<= 8; + pid |= (packets[2]); + if (pid != 0x0118) + return; + + if ((packets[8] & 0xff) == 0xd0) + { + if (outbuf != null) + { + byte[] chi = outbuf.ToArray(); + byte[] ipPacket = IpPacketFinder.LookForIpPacket(outbuf.ToArray()); + if (ipPacket != null) + { + if (!annoncementDone) + { + mpeEventHandler.GsIpTrafficDetected(); + annoncementDone = true; + } + mpeEventHandler.OnIpDatagram(0x0118, ipPacket); + } + } + outbuf = new MemoryStream(); + outbuf.Write(packets, 8, packets[7]); + } + else + { + int length = packets[7] - 1; + if (length + 9 <= packets.Length) + { + if (outbuf != null) + outbuf.Write(packets, 9, length); + } + else + { + //broken packet, discard. + outbuf = null; + } + } + } + } +} diff --git a/skyscraper8/Skyscraper/TcpProxy.cs b/skyscraper8/Skyscraper/TcpProxy.cs new file mode 100644 index 0000000..b1e2738 --- /dev/null +++ b/skyscraper8/Skyscraper/TcpProxy.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Scraper; + +namespace skyscraper5.Skyscraper +{ + internal class TcpTsProxy : IDisposable, ITsPacketProcessor + { + private bool disposing; + private Thread listenerThread; + private TcpListener listener; + private List tcpClients; + + //ffmpeg -i tcp://172.20.20.198:6969 -frames:v 1 -c:v png -f image2pipe -map 0:p:28726:v - >28726.png + public TcpTsProxy() + { + tcpClients = new List(); + listener = new TcpListener(IPAddress.Any, 0); + listener.Start(); + Debug.WriteLine("Listening at {0}", GetEndPoint()); + listenerThread = new Thread(Listener); + listenerThread.Start(); + } + + private void Listener() + { + while (true) + { + try + { + if (disposing) + return; + if (listener.Server == null) + return; + if (listener.Server.SafeHandle.IsClosed) + return; + if (listener.Server.SafeHandle.IsInvalid) + return; + TcpClient tcpClient = listener.AcceptTcpClient(); + lock (tcpClients) + { + tcpClients.Add(tcpClient); + } + + Debug.WriteLine("accepted tcp client {0}", tcpClient); + } + catch (SocketException e) + { + if (disposing) + return; + else + throw e; + } + } + } + + public void Dispose() + { + while (tcpClients.Count > 0) + { + TcpClient tcpClient = tcpClients[0]; + tcpClients.RemoveAt(0); + } + disposing = true; + listener.Stop(); + Debug.WriteLine("disposed tcp proxy"); + } + + public IPEndPoint GetEndPoint() + { + return listener.LocalEndpoint as IPEndPoint; + } + + public void PushPacket(TsPacket packet) + { + lock (tcpClients) + { + foreach (TcpClient tcpClient in tcpClients) + { + if (!tcpClient.Client.Connected) + { + tcpClients.Remove(tcpClient); + PushPacket(packet); + Debug.WriteLine("A TCP Client disconnected"); + return; + } + + try + { + byte[] packetRawPacket = packet.RawPacket; + tcpClient.Client.Send(packet.RawPacket); + } + catch (SocketException se) + { + } + } + } + } + } +} diff --git a/skyscraper8/Skyscraper/Text/AsciiTable.cs b/skyscraper8/Skyscraper/Text/AsciiTable.cs new file mode 100644 index 0000000..3e4e104 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/AsciiTable.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text +{ + internal class AsciiTable + { + internal static void GetAsciiChar(byte req, StringBuilder resultBuilder) + { + switch (req) + { + case 0x00: resultBuilder.Append('\0'); break; //Null + case 0x01: break; //Start of Header? + case 0x03: break; //End of text + case 0x04: break; //End of transmission? Sure doesn't look like it. + case 0x05: break; //Enquiry? + case 0x06: break; //Acknowledge? + case 0x07: resultBuilder.Append('\a'); break; //Bell + case 0x08: resultBuilder.Append('\b'); break; //Backspace + case 0x09: resultBuilder.Append('\t'); break; //Horizontal Tab + case 0x0a: resultBuilder.Append('\n'); break; //Line Feed + case 0x0b: resultBuilder.Append('\v'); break; //Vertical Tab + case 0x0c: resultBuilder.Append('\f'); break; //Form Feed + case 0x0d: resultBuilder.Append('\r'); break; //Carriage Return + case 0x0f: break; //Shift in? + case 0x10: break; //Data Link Escape? + case 0x11: break; // XON / XOFF + case 0x12: break; //Device Control 2? + case 0x13: break; //Device Control 3? + case 0x14: break; //Device Control 4? + case 0x15: break; //NAK + case 0x16: break; //Idle (SYN) + case 0x17: break; //End of Transmission Block? + case 0x18: break; //Cancel? + case 0x19: break; //End of Medium? + case 0x1a: break; //Substitute? + case 0x1b: resultBuilder.Append('\b'); break; //Escape + case 0x1c: break; //File Separator? + case 0x1d: break; //Group Separator? + case 0x1e: break; //Record Separator? + case 0x1f: break; //Unit Separator? + case 0x20: resultBuilder.Append(' '); break; + case 0x21: resultBuilder.Append('!'); break; + case 0x22: resultBuilder.Append('\"'); break; + case 0x23: resultBuilder.Append('#'); break; + case 0x24: resultBuilder.Append('$'); break; + case 0x25: resultBuilder.Append('%'); break; + case 0x26: resultBuilder.Append('&'); break; + case 0x27: resultBuilder.Append('\''); break; + case 0x28: resultBuilder.Append('('); break; + case 0x29: resultBuilder.Append(')'); break; + case 0x2a: resultBuilder.Append('*'); break; + case 0x2b: resultBuilder.Append('+'); break; + case 0x2c: resultBuilder.Append(','); break; + case 0x2d: resultBuilder.Append('-'); break; + case 0x2e: resultBuilder.Append('.'); break; + case 0x2f: resultBuilder.Append('/'); break; + case 0x30: resultBuilder.Append('0'); break; + case 0x31: resultBuilder.Append('1'); break; + case 0x32: resultBuilder.Append('2'); break; + case 0x33: resultBuilder.Append('3'); break; + case 0x34: resultBuilder.Append('4'); break; + case 0x35: resultBuilder.Append('5'); break; + case 0x36: resultBuilder.Append('6'); break; + case 0x37: resultBuilder.Append('7'); break; + case 0x38: resultBuilder.Append('8'); break; + case 0x39: resultBuilder.Append('9'); break; + case 0x3a: resultBuilder.Append(':'); break; + case 0x3b: resultBuilder.Append(';'); break; + case 0x3c: resultBuilder.Append('<'); break; + case 0x3d: resultBuilder.Append('='); break; + case 0x3e: resultBuilder.Append('>'); break; + case 0x3f: resultBuilder.Append('?'); break; + case 0x40: resultBuilder.Append('@'); break; + case 0x41: resultBuilder.Append('A'); break; + case 0x42: resultBuilder.Append('B'); break; + case 0x43: resultBuilder.Append('C'); break; + case 0x44: resultBuilder.Append('D'); break; + case 0x45: resultBuilder.Append('E'); break; + case 0x46: resultBuilder.Append('F'); break; + case 0x47: resultBuilder.Append('G'); break; + case 0x48: resultBuilder.Append('H'); break; + case 0x49: resultBuilder.Append('I'); break; + case 0x4a: resultBuilder.Append('J'); break; + case 0x4b: resultBuilder.Append('K'); break; + case 0x4c: resultBuilder.Append('L'); break; + case 0x4d: resultBuilder.Append('M'); break; + case 0x4e: resultBuilder.Append('N'); break; + case 0x4f: resultBuilder.Append('O'); break; + case 0x50: resultBuilder.Append('P'); break; + case 0x51: resultBuilder.Append('Q'); break; + case 0x52: resultBuilder.Append('R'); break; + case 0x53: resultBuilder.Append('S'); break; + case 0x54: resultBuilder.Append('T'); break; + case 0x55: resultBuilder.Append('U'); break; + case 0x56: resultBuilder.Append('V'); break; + case 0x57: resultBuilder.Append('W'); break; + case 0x58: resultBuilder.Append('X'); break; + case 0x59: resultBuilder.Append('Y'); break; + case 0x5a: resultBuilder.Append('Z'); break; + case 0x5b: resultBuilder.Append('['); break; + case 0x5c: resultBuilder.Append('\\'); break; + case 0x5d: resultBuilder.Append(']'); break; + case 0x5e: resultBuilder.Append('^'); break; + case 0x5f: resultBuilder.Append('_'); break; + case 0x60: resultBuilder.Append('`'); break; + case 0x61: resultBuilder.Append('a'); break; + case 0x62: resultBuilder.Append('b'); break; + case 0x63: resultBuilder.Append('c'); break; + case 0x64: resultBuilder.Append('d'); break; + case 0x65: resultBuilder.Append('e'); break; + case 0x66: resultBuilder.Append('f'); break; + case 0x67: resultBuilder.Append('g'); break; + case 0x68: resultBuilder.Append('h'); break; + case 0x69: resultBuilder.Append('i'); break; + case 0x6a: resultBuilder.Append('j'); break; + case 0x6b: resultBuilder.Append('k'); break; + case 0x6c: resultBuilder.Append('l'); break; + case 0x6d: resultBuilder.Append('m'); break; + case 0x6e: resultBuilder.Append('n'); break; + case 0x6f: resultBuilder.Append('o'); break; + case 0x70: resultBuilder.Append('p'); break; + case 0x71: resultBuilder.Append('q'); break; + case 0x72: resultBuilder.Append('r'); break; + case 0x73: resultBuilder.Append('s'); break; + case 0x74: resultBuilder.Append('t'); break; + case 0x75: resultBuilder.Append('u'); break; + case 0x76: resultBuilder.Append('v'); break; + case 0x77: resultBuilder.Append('w'); break; + case 0x78: resultBuilder.Append('x'); break; + case 0x79: resultBuilder.Append('y'); break; + case 0x7a: resultBuilder.Append('z'); break; + case 0x7b: resultBuilder.Append('{'); break; + case 0x7c: resultBuilder.Append('|'); break; + case 0x7d: resultBuilder.Append('}'); break; + case 0x7e: resultBuilder.Append('~'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", req)); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Text/DummyEncoding.cs b/skyscraper8/Skyscraper/Text/DummyEncoding.cs new file mode 100644 index 0000000..b3a6f0e --- /dev/null +++ b/skyscraper8/Skyscraper/Text/DummyEncoding.cs @@ -0,0 +1,45 @@ +using System; +using System.Text; + +namespace skyscraper5.Skyscraper.Text +{ + class DummyEncoding : Encoding + { + private readonly string _s; + + public DummyEncoding(string s) + { + _s = s; + } + + public override int GetByteCount(char[] chars, int index, int count) + { + throw new NotImplementedException(_s); + } + + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + { + throw new NotImplementedException(_s); + } + + public override int GetCharCount(byte[] bytes, int index, int count) + { + throw new NotImplementedException(_s); + } + + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + { + throw new NotImplementedException(_s); + } + + public override int GetMaxByteCount(int charCount) + { + throw new NotImplementedException(_s); + } + + public override int GetMaxCharCount(int byteCount) + { + throw new NotImplementedException(_s); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/En300468TextDecoder.cs b/skyscraper8/Skyscraper/Text/En300468TextDecoder.cs new file mode 100644 index 0000000..e11adfc --- /dev/null +++ b/skyscraper8/Skyscraper/Text/En300468TextDecoder.cs @@ -0,0 +1,138 @@ +using System; +using System.Text; +using skyscraper5.Dvb; + +namespace skyscraper5.Skyscraper.Text +{ + public class En300468AnnexATextDecoder + { + private En300468AnnexATextDecoder() + { + Encoding.RegisterProvider(SkyscraperEncodingProvider.GetInstance()); + + tables = new Encoding[0x20]; + tables[0x00] = Encoding.GetEncoding("dvb-iso-6937"); //ISO 6397 + tables[0x01] = Encoding.GetEncoding("dvb-iso-8859-5"); //iso 8859-5 + tables[0x02] = Encoding.GetEncoding("dvb-iso-8859-6"); //iso 8859-6 + tables[0x03] = Encoding.GetEncoding("dvb-iso-8859-7"); //iso 8859-7 + tables[0x04] = Encoding.GetEncoding("dvb-iso-8859-8"); //iso 8859-8 + tables[0x05] = Encoding.GetEncoding("dvb-iso-8859-9"); //iso 8859-9 + tables[0x06] = Encoding.GetEncoding("dvb-iso-8859-10"); //ISO/IEC 8859-10 + tables[0x07] = Encoding.GetEncoding("dvb-iso-8859-11"); //ISO/IEC 8859-11 + tables[0x09] = Encoding.GetEncoding("dvb-iso-8859-13"); //ISO/IEC 8859-13 + tables[0x0a] = Encoding.GetEncoding("dvb-iso-8859-14"); //ISO/IEC 8859-14 + tables[0x0b] = Encoding.GetEncoding("dvb-iso-8859-15"); //iso 8859-15 + tables[0x11] = Encoding.GetEncoding("dvb-iso-10646"); //iso 10646 + tables[0x12] = Encoding.GetEncoding("dvb-ksx1001-2004"); //KSX1001-2004 + tables[0x13] = Encoding.GetEncoding("dvb-gb-2312-1980"); //gb-2312 + tables[0x14] = Encoding.GetEncoding("dvb-big5"); //Big5 subset of ISO/IEC 10646 + tables[0x15] = Encoding.GetEncoding("dvb-utf8"); //UTF-8 encoding of ISO/IEC 10646 + + iso8859Mapping = new Encoding[0x10]; + iso8859Mapping[0x01] = Encoding.GetEncoding("dvb-iso-8859-1"); //iso 8859-1 + iso8859Mapping[0x02] = Encoding.GetEncoding("dvb-iso-8859-2"); //iso 8859-2 + iso8859Mapping[0x03] = Encoding.GetEncoding("dvb-iso-8859-3"); //iso 8859-3 + iso8859Mapping[0x04] = Encoding.GetEncoding("dvb-iso-8859-4"); //iso 8859-4 + iso8859Mapping[0x05] = Encoding.GetEncoding("dvb-iso-8859-5"); //iso 8859-5 + iso8859Mapping[0x06] = Encoding.GetEncoding("dvb-iso-8859-6"); //iso 8859-6 + iso8859Mapping[0x07] = Encoding.GetEncoding("dvb-iso-8859-7"); //iso 8859-7 + iso8859Mapping[0x08] = Encoding.GetEncoding("dvb-iso-8859-8"); //iso 8859-8 + iso8859Mapping[0x09] = Encoding.GetEncoding("dvb-iso-8859-9"); //iso 8859-9 + iso8859Mapping[0x0a] = Encoding.GetEncoding("dvb-iso-8859-10"); //ISO/IEC 8859-10 + iso8859Mapping[0x0b] = Encoding.GetEncoding("dvb-iso-8859-11"); //ISO/IEC 8859-11 + iso8859Mapping[0x0c] = Encoding.GetEncoding("dvb-iso-8859-12"); //ISO/IEC 8859-12 + iso8859Mapping[0x0d] = Encoding.GetEncoding("dvb-iso-8859-13"); //ISO/IEC 8859-13 + iso8859Mapping[0x0e] = Encoding.GetEncoding("dvb-iso-8859-14"); //ISO/IEC 8859-14 + iso8859Mapping[0x0f] = Encoding.GetEncoding("dvb-iso-8859-15"); //iso 8859-15 + + } + + private static En300468AnnexATextDecoder _instance; + private static Encoding[] tables; + private static Encoding[] iso8859Mapping; + + public static En300468AnnexATextDecoder GetInstance() + { + if (_instance == null) + _instance = new En300468AnnexATextDecoder(); + return _instance; + } + + public string Decode(byte[] buffer, int offset, int length) + { + byte[] copy = new byte[length]; + Array.Copy(buffer, offset, copy, 0, length); + return Decode(copy); + } + + private Encoding lastUsedEncoding; + public string Decode(byte[] buffer) + { + if (buffer.Length == 0) + return null; + if (buffer.Length == 1) + return null; + + if (buffer[0] >= 0x20) + { + lastUsedEncoding = tables[0]; + return tables[0].GetString(buffer, 0, buffer.Length); + } + else if ((buffer[0] >= 0x01 && buffer[0] <= 0x07) || (buffer[0] >= 0x09 && buffer[0] <= 0x0b) || (buffer[0] >= 0x11 && buffer[0] <= 0x14)) + { + lastUsedEncoding = tables[buffer[0]]; + return tables[buffer[0]].GetString(buffer, 1, buffer.Length - 1); + } + else if (buffer[0] == 0x10) + { + if (buffer.Length == 2) + return null; + ushort sb = buffer[1]; + ushort tb = buffer[2]; + if (sb != 0x00) + throw new NotImplementedException("reserved"); + if (tb >= 0x10) + throw new NotImplementedException("reserved"); + lastUsedEncoding = iso8859Mapping[tb]; + return iso8859Mapping[tb].GetString(buffer, 3, buffer.Length - 3); + } + else if (buffer[0] == 0x15) + { + lastUsedEncoding = tables[0x15]; + return tables[0x15].GetString(buffer, 1, buffer.Length - 1); + } + else if (buffer[0] == 0x1f) + { + //It shall be coded according to ETSI TS 101 162 + switch (buffer[1]) + { + case 0x01: //BBC, undocumented - but their Freesat Patch might help: https://www.rst38.org.uk/vdr/ + case 0x02: + return null; + default: + throw new NotImplementedException(String.Format("Encoding type ID 0x{0:X2}", buffer[1])); + } + } + else if (buffer[0] >= 0x16 && buffer[0] <= 0x1e) + { + return null; //ETSI EN 300468, Table A.3 "reserved for future use", therefore invalid. + } + + if (lastUsedEncoding != null) + { + return lastUsedEncoding.GetString(buffer); + } + throw new DvbException("failed to detect encoding"); + } + + public byte[] Encode(string serviceName) + { + byte[] utf8buffer = Encoding.UTF8.GetBytes(serviceName); + + byte[] result = new byte[utf8buffer.Length + 1]; + result[0] = 0x15; + Array.Copy(utf8buffer, 0, result, 1, utf8buffer.Length); + return result; + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-gb-2312-1980.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-gb-2312-1980.cs new file mode 100644 index 0000000..0c30ca4 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-gb-2312-1980.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-gb-2312-1980")] + class dvb_gb_2312_1980 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + + default: + resultBuilder.Append(DecodeHanzi(preprocessed[i], preprocessed[i + 1])); + i++; + break; + } + } + + return resultBuilder.ToString().ToCharArray(); + } + + //looks good: https://web.archive.org/web/20160303230643/http://cs.nyu.edu/~yusuke/tools/unicode_to_gb2312_or_gbk_table.html + private char DecodeHanzi(byte leadByte, byte charByte) + { + ushort hanzi = leadByte; + hanzi <<= 8; + hanzi += charByte; + switch (hanzi) + { + case 0xd049: return '蠭'; + case 0xd64c: return '諰'; + case 0xdc4d: return '躆'; + case 0xdc4e: return '躈'; + default: + throw new NotImplementedException(String.Format("{0:X4}", hanzi)); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-10646.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-10646.cs new file mode 100644 index 0000000..c0ed1a9 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-10646.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-10646")] + class dvb_iso_10646 : SkyscraperBaseEncoding16 + { + protected override char[] Decrypt(byte[] preprocessed) + { + if (preprocessed.Length % 2 != 0) + throw new ArgumentException("length must be divisible by 2"); + + if (BitConverter.IsLittleEndian) + for (int i = 0; i < preprocessed.Length; i += 2) + (preprocessed[i], preprocessed[i + 1]) = (preprocessed[i + 1], preprocessed[i]); + + Encoding utf16 = Encoding.GetEncoding("UTF-16"); + string result = utf16.GetString(preprocessed); + return result.ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-6937.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-6937.cs new file mode 100644 index 0000000..1697ce0 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-6937.cs @@ -0,0 +1,460 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-6937")] + class dvb_iso_6937 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0x7f: break; + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('¡'); break; + case 0xa2: resultBuilder.Append('¢'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa4: resultBuilder.Append('$'); break; + case 0xa5: resultBuilder.Append('¥'); break; + case 0xa6: resultBuilder.Append('#'); break; + case 0xa7: resultBuilder.Append('§'); break; + case 0xa8: resultBuilder.Append('¤'); break; + case 0xa9: resultBuilder.Append('‘'); break; + case 0xaa: resultBuilder.Append('“'); break; + case 0xab: resultBuilder.Append('«'); break; + case 0xac: resultBuilder.Append('←'); break; + case 0xad: resultBuilder.Append('↑'); break; + case 0xae: resultBuilder.Append('→'); break; + case 0xaf: resultBuilder.Append('↓'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('¡'); break; + case 0xb2: resultBuilder.Append('²'); break; + case 0xb3: resultBuilder.Append('³'); break; + case 0xb4: resultBuilder.Append('×'); break; + case 0xb5: resultBuilder.Append('µ'); break; + case 0xb6: resultBuilder.Append('¶'); break; + case 0xb7: resultBuilder.Append('·'); break; + case 0xb8: resultBuilder.Append('÷'); break; + case 0xb9: resultBuilder.Append('’'); break; + case 0xba: resultBuilder.Append('”'); break; + case 0xbb: resultBuilder.Append('»'); break; + case 0xbc: resultBuilder.Append('¼'); break; + case 0xbd: resultBuilder.Append('½'); break; + case 0xbe: resultBuilder.Append('¾'); break; + case 0xbf: resultBuilder.Append('¿'); break; + case 0xc0: break; + case 0xc1: + i++; + int j = preprocessed.Length; + if (i == j) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('À'); break; + case 0x45: resultBuilder.Append('È'); break; + case 0x49: resultBuilder.Append('Ì'); break; + case 0x4f: resultBuilder.Append('Ò'); break; + case 0x55: resultBuilder.Append('Ù'); break; + case 0x61: resultBuilder.Append('à'); break; + case 0x65: resultBuilder.Append('è'); break; + case 0x69: resultBuilder.Append('ì'); break; + case 0x6f: resultBuilder.Append('ò'); break; + case 0x75: resultBuilder.Append('ù'); break; + default: + i--; + break; + } + break; + case 0xc2: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Á'); break; + case 0x43: resultBuilder.Append('Ć'); break; + case 0x45: resultBuilder.Append('É'); break; + case 0x49: resultBuilder.Append('Í'); break; + case 0x4c: resultBuilder.Append('Ĺ'); break; + case 0x4e: resultBuilder.Append('Ń'); break; + case 0x4f: resultBuilder.Append('Ó'); break; + case 0x52: resultBuilder.Append('Ŕ'); break; + case 0x53: resultBuilder.Append('Ś'); break; + case 0x55: resultBuilder.Append('Ú'); break; + case 0x59: resultBuilder.Append('Ý'); break; + case 0x5a: resultBuilder.Append('Ź'); break; + case 0x61: resultBuilder.Append('á'); break; + case 0x63: resultBuilder.Append('ć'); break; + case 0x65: resultBuilder.Append('é'); break; + case 0x67: resultBuilder.Append('ģ'); break; + case 0x69: resultBuilder.Append('í'); break; + case 0x6c: resultBuilder.Append('ĺ'); break; + case 0x6e: resultBuilder.Append('ń'); break; + case 0x6f: resultBuilder.Append('ó'); break; + case 0x72: resultBuilder.Append('ŕ'); break; + case 0x73: resultBuilder.Append('ś'); break; + case 0x75: resultBuilder.Append('ú'); break; + default: + i--; + break; + } + break; + case 0xc3: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Â'); break; + case 0x43: resultBuilder.Append('Ĉ'); break; + case 0x45: resultBuilder.Append('Ê'); break; + case 0x47: resultBuilder.Append('Ĝ'); break; + case 0x48: resultBuilder.Append('Ĥ'); break; + case 0x49: resultBuilder.Append('Î'); break; + case 0x4a: resultBuilder.Append('Ĵ'); break; + case 0x4f: resultBuilder.Append('Ô'); break; + case 0x53: resultBuilder.Append('Ŝ'); break; + case 0x55: resultBuilder.Append('Û'); break; + case 0x57: resultBuilder.Append('Ŵ'); break; + case 0x59: resultBuilder.Append('Ŷ'); break; + case 0x61: resultBuilder.Append('â'); break; + case 0x63: resultBuilder.Append('ĉ'); break; + case 0x65: resultBuilder.Append('ê'); break; + case 0x67: resultBuilder.Append('ĝ'); break; + case 0x68: resultBuilder.Append('ĥ'); break; + case 0x69: resultBuilder.Append('î'); break; + case 0x6a: resultBuilder.Append('ĵ'); break; + case 0x6f: resultBuilder.Append('ô'); break; + case 0x73: resultBuilder.Append('ŝ'); break; + case 0x75: resultBuilder.Append('û'); break; + case 0x77: resultBuilder.Append('ŵ'); break; + case 0x79: resultBuilder.Append('ŷ'); break; + default: + i--; + break; + } + break; + case 0xc4: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Ã'); break; + case 0x49: resultBuilder.Append('Ĩ'); break; + case 0x4e: resultBuilder.Append('Ñ'); break; + case 0x4f: resultBuilder.Append('Õ'); break; + case 0x55: resultBuilder.Append('Ũ'); break; + case 0x61: resultBuilder.Append('ã'); break; + case 0x69: resultBuilder.Append('ĩ'); break; + case 0x6e: resultBuilder.Append('ñ'); break; + case 0x6f: resultBuilder.Append('õ'); break; + case 0x75: resultBuilder.Append('ũ'); break; + default: + i--; + break; + } + break; + case 0xc5: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Ā'); break; + case 0x45: resultBuilder.Append('Ē'); break; + case 0x49: resultBuilder.Append('Ī'); break; + case 0x4f: resultBuilder.Append('Ō'); break; + case 0x55: resultBuilder.Append('Ū'); break; + case 0x61: resultBuilder.Append('ā'); break; + case 0x65: resultBuilder.Append('ē'); break; + case 0x69: resultBuilder.Append('ī'); break; + case 0x6f: resultBuilder.Append('ō'); break; + case 0x75: resultBuilder.Append('ū'); break; + default: + i--; + break; + } + break; + case 0xc6: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Ă'); break; + case 0x47: resultBuilder.Append('Ğ'); break; + case 0x55: resultBuilder.Append('Ŭ'); break; + case 0x61: resultBuilder.Append('ă'); break; + case 0x67: resultBuilder.Append('ğ'); break; + case 0x75: resultBuilder.Append('ŭ'); break; + default: + i--; + break; + } + break; + case 0xc7: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x43: resultBuilder.Append('Ċ'); break; + case 0x45: resultBuilder.Append('Ė'); break; + case 0x47: resultBuilder.Append('Ġ'); break; + case 0x49: resultBuilder.Append('İ'); break; + case 0x5a: resultBuilder.Append('Ż'); break; + case 0x63: resultBuilder.Append('ċ'); break; + case 0x65: resultBuilder.Append('ė'); break; + case 0x67: resultBuilder.Append('ġ'); break; + case 0x7a: resultBuilder.Append('ż'); break; + default: + i--; + break; + } + break; + case 0xc8: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Ä'); break; + case 0x45: resultBuilder.Append('Ë'); break; + case 0x49: resultBuilder.Append('Ï'); break; + case 0x4f: resultBuilder.Append('Ö'); break; + case 0x55: resultBuilder.Append('Ü'); break; + case 0x59: resultBuilder.Append('Ÿ'); break; + case 0x61: resultBuilder.Append('ä'); break; + case 0x65: resultBuilder.Append('ë'); break; + case 0x69: resultBuilder.Append('ï'); break; + case 0x6f: resultBuilder.Append('ö'); break; + case 0x75: resultBuilder.Append('ü'); break; + case 0x79: resultBuilder.Append('ÿ'); break; + default: + i--; + break; + } + break; + case 0xc9: break; + case 0xca: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x41: resultBuilder.Append('Å'); break; + case 0x55: resultBuilder.Append('Ů'); break; + case 0x61: resultBuilder.Append('å'); break; + case 0x75: resultBuilder.Append('ů'); break; + default: + i--; + break; + } + break; + case 0xcb: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x43: resultBuilder.Append('Ç'); break; + case 0x47: resultBuilder.Append('Ģ'); break; + case 0x4b: resultBuilder.Append('Ķ'); break; + case 0x4c: resultBuilder.Append('Ļ'); break; + case 0x4e: resultBuilder.Append('Ņ'); break; + case 0x52: resultBuilder.Append('Ŗ'); break; + case 0x53: resultBuilder.Append('Ş'); break; + case 0x54: resultBuilder.Append('Ţ'); break; + case 0x63: resultBuilder.Append('ç'); break; + case 0x6b: resultBuilder.Append('ķ'); break; + case 0x6c: resultBuilder.Append('ļ'); break; + case 0x6e: resultBuilder.Append('ņ'); break; + case 0x72: resultBuilder.Append('ŗ'); break; + case 0x73: resultBuilder.Append('ş'); break; + case 0x74: resultBuilder.Append('ţ'); break; + default: + i--; + break; + } + break; + case 0xcc: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x30: resultBuilder.Append("0̲"); break; + case 0x31: resultBuilder.Append("1̲"); break; + case 0x32: resultBuilder.Append("2̲"); break; + case 0x33: resultBuilder.Append("3̲"); break; + case 0x34: resultBuilder.Append("4̲"); break; + case 0x35: resultBuilder.Append("5̲"); break; + case 0x36: resultBuilder.Append("6̲"); break; + case 0x37: resultBuilder.Append("7̲"); break; + case 0x38: resultBuilder.Append("8̲"); break; + case 0x39: resultBuilder.Append("9̲"); break; + case 0x61: resultBuilder.Append("a̲"); break; + case 0x62: resultBuilder.Append("b̲"); break; + case 0x63: resultBuilder.Append("c̲"); break; + case 0x64: resultBuilder.Append("d̲"); break; + case 0x65: resultBuilder.Append("e̲"); break; + case 0x66: resultBuilder.Append("f̲"); break; + case 0x67: resultBuilder.Append("g̲"); break; + case 0x68: resultBuilder.Append("h̲"); break; + case 0x69: resultBuilder.Append("i̲"); break; + case 0x6a: resultBuilder.Append("j̲"); break; + case 0x6b: resultBuilder.Append("k̲"); break; + case 0x6c: resultBuilder.Append("l̲"); break; + case 0x6d: resultBuilder.Append("m̲"); break; + case 0x6e: resultBuilder.Append("n̲"); break; + case 0x6f: resultBuilder.Append("o̲"); break; + case 0x70: resultBuilder.Append("p̲"); break; + case 0x71: resultBuilder.Append("q̲"); break; + case 0x72: resultBuilder.Append("r̲"); break; + case 0x73: resultBuilder.Append("s̲"); break; + case 0x74: resultBuilder.Append("t̲"); break; + case 0x75: resultBuilder.Append("u̲"); break; + case 0x76: resultBuilder.Append("v̲"); break; + case 0x77: resultBuilder.Append("w̲"); break; + case 0x78: resultBuilder.Append("x̲"); break; + case 0x79: resultBuilder.Append("y̲"); break; + case 0x7a: resultBuilder.Append("z̲"); break; + default: + i--; + break; + } + break; + case 0xcd: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x4f: resultBuilder.Append('Ő'); break; + case 0x55: resultBuilder.Append('Ű'); break; + case 0x6f: resultBuilder.Append('ő'); break; + case 0x75: resultBuilder.Append('ű'); break; + default: + i--; + break; + } + break; + case 0xce: + i++; + if (i == preprocessed.Length) + break; + switch(preprocessed[i]) + { + case 0x41: resultBuilder.Append('Ą'); break; + case 0x45: resultBuilder.Append('Ę'); break; + case 0x49: resultBuilder.Append('Į'); break; + case 0x55: resultBuilder.Append('Ų'); break; + case 0x61: resultBuilder.Append('ą'); break; + case 0x65: resultBuilder.Append('ę'); break; + case 0x69: resultBuilder.Append('į'); break; + case 0x75: resultBuilder.Append('ų'); break; + default: + i--; + break; + } + break; + case 0xcf: + i++; + if (i == preprocessed.Length) + break; + switch (preprocessed[i]) + { + case 0x43: resultBuilder.Append('Č'); break; + case 0x44: resultBuilder.Append('Ď'); break; + case 0x45: resultBuilder.Append('Ě'); break; + case 0x4c: resultBuilder.Append('Ľ'); break; + case 0x4e: resultBuilder.Append('Ň'); break; + case 0x52: resultBuilder.Append('Ř'); break; + case 0x53: resultBuilder.Append('Š'); break; + case 0x54: resultBuilder.Append('Ť'); break; + case 0x5a: resultBuilder.Append('Ž'); break; + case 0x63: resultBuilder.Append('č'); break; + case 0x64: resultBuilder.Append('d'); break; + case 0x65: resultBuilder.Append('ě'); break; + case 0x6c: resultBuilder.Append('ľ'); break; + case 0x6e: resultBuilder.Append('ň'); break; + case 0x72: resultBuilder.Append('ř'); break; + case 0x73: resultBuilder.Append('š'); break; + case 0x74: resultBuilder.Append('ť'); break; + case 0x7a: resultBuilder.Append('ž'); break; + default: + i--; + break; + } + break; + case 0xd0: resultBuilder.Append('―'); break; + case 0xd1: resultBuilder.Append('¹'); break; + case 0xd2: resultBuilder.Append('®'); break; + case 0xd3: resultBuilder.Append('©'); break; + case 0xd4: resultBuilder.Append('™'); break; + case 0xd5: resultBuilder.Append('♪'); break; + case 0xd6: resultBuilder.Append('¬'); break; + case 0xd7: resultBuilder.Append('¦'); break; + case 0xd8: break; + case 0xd9: break; + case 0xda: break; + case 0xdb: break; + case 0xdc: resultBuilder.Append('⅛'); break; + case 0xdd: resultBuilder.Append('⅜'); break; + case 0xde: resultBuilder.Append('⅝'); break; + case 0xdf: resultBuilder.Append('⅞'); break; + case 0xe0: resultBuilder.Append('Ω'); break; + case 0xe1: resultBuilder.Append('Æ'); break; + case 0xe2: resultBuilder.Append('Đ'); break; + case 0xe3: resultBuilder.Append('ª'); break; + case 0xe4: resultBuilder.Append('Ħ'); break; + case 0xe5: break; + case 0xe6: resultBuilder.Append('IJ'); break; + case 0xe7: resultBuilder.Append('Ŀ'); break; + case 0xe8: resultBuilder.Append('Ł'); break; + case 0xe9: resultBuilder.Append('Ø'); break; + case 0xea: resultBuilder.Append('Œ'); break; + case 0xeb: resultBuilder.Append('°'); break; + case 0xec: resultBuilder.Append('Þ'); break; + case 0xed: resultBuilder.Append('Ŧ'); break; + case 0xee: resultBuilder.Append('Ŋ'); break; + case 0xef: resultBuilder.Append('ʼn'); break; + case 0xf0: resultBuilder.Append('ĸ'); break; + case 0xf1: resultBuilder.Append('æ'); break; + case 0xf2: resultBuilder.Append('đ'); break; + case 0xf3: resultBuilder.Append('ð'); break; + case 0xf4: resultBuilder.Append('ħ'); break; + case 0xf5: resultBuilder.Append('ı'); break; + case 0xf6: resultBuilder.Append('ij'); break; + case 0xf7: resultBuilder.Append('ŀ'); break; + case 0xf8: resultBuilder.Append('ł'); break; + case 0xf9: resultBuilder.Append('ų'); break; + case 0xfa: resultBuilder.Append('œ'); break; + case 0xfb: resultBuilder.Append('ß'); break; + case 0xfc: resultBuilder.Append('þ'); break; + case 0xfd: resultBuilder.Append('ŧ'); break; + case 0xfe: resultBuilder.Append('ŋ'); break; + case 0xff: resultBuilder.Append('\u00ad'); break; + default: + byte m = preprocessed[i]; + throw new NotImplementedException(String.Format("0x{0:X2}", m)); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-1.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-1.cs new file mode 100644 index 0000000..86dd20f --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-1.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-1")] + class dvb_iso_8859_1 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb2: resultBuilder.Append('²'); break; + case 0xb4: resultBuilder.Append('´'); break; + case 0xbc: resultBuilder.Append('¼'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc7: resultBuilder.Append('Ç'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xcb: resultBuilder.Append('¼'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('à'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe2: resultBuilder.Append('â'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe7: resultBuilder.Append('ç'); break; + case 0xe8: resultBuilder.Append('è'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ê'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xec: resultBuilder.Append('ì'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xef: resultBuilder.Append('ï'); break; + case 0xf1: resultBuilder.Append('ñ'); break; + case 0xf2: resultBuilder.Append('ò'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf8: resultBuilder.Append('ø'); break; + case 0xf9: resultBuilder.Append('ù'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfc: resultBuilder.Append('ü'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs new file mode 100644 index 0000000..07e820b --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-10")] + class dvb_iso_8859_10 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa9: resultBuilder.Append('Đ'); break; + case 0xaa: resultBuilder.Append('Š'); break; + case 0xac: resultBuilder.Append('Ž'); break; + case 0xae: resultBuilder.Append('Ū'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('ą'); break; + case 0xb9: resultBuilder.Append('đ'); break; + case 0xba: resultBuilder.Append('š'); break; + case 0xbc: resultBuilder.Append('ž'); break; + case 0xbe: resultBuilder.Append('ū'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc2: resultBuilder.Append('Â'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc5: resultBuilder.Append('Å'); break; + case 0xc6: resultBuilder.Append('Æ'); break; + case 0xc8: resultBuilder.Append('Č'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xcd: resultBuilder.Append('Í'); break; + case 0xcf: resultBuilder.Append('Ï'); break; + case 0xd0: resultBuilder.Append('Ð'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdd: resultBuilder.Append('Ý'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('ā'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe2: resultBuilder.Append('â'); break; + case 0xe3: resultBuilder.Append('ã'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe6: resultBuilder.Append('æ'); break; + case 0xe8: resultBuilder.Append('č'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ę'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xee: resultBuilder.Append('î'); break; + case 0xef: resultBuilder.Append('ï'); break; + case 0xf0: resultBuilder.Append('ð'); break; + case 0xf1: resultBuilder.Append('ņ'); break; + case 0xf2: resultBuilder.Append('ō'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf5: resultBuilder.Append('õ'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf8: resultBuilder.Append('ø'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfb: resultBuilder.Append('û'); break; + case 0xfd: resultBuilder.Append('ý'); break; + case 0xfc: resultBuilder.Append('ü'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs new file mode 100644 index 0000000..b065f4e --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-13")] + class dvb_iso_8859_13 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('”'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa5: resultBuilder.Append('„'); break; + case 0xa8: resultBuilder.Append('Ø'); break; + case 0xab: resultBuilder.Append('«'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xae: resultBuilder.Append('®'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb4: resultBuilder.Append('“'); break; + case 0xb8: resultBuilder.Append('ø'); break; + case 0xbb: resultBuilder.Append('»'); break; + case 0xbf: resultBuilder.Append('æ'); break; + case 0xc3: resultBuilder.Append('Ć'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc5: resultBuilder.Append('Å'); break; + case 0xc8: resultBuilder.Append('Č'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xd0: resultBuilder.Append('Š'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xd9: resultBuilder.Append('Ł'); break; + case 0xda: resultBuilder.Append('Ś'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdd: resultBuilder.Append('Ż'); break; + case 0xde: resultBuilder.Append('Ž'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('ą'); break; + case 0xe2: resultBuilder.Append('ā'); break; + case 0xe3: resultBuilder.Append('ć'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe6: resultBuilder.Append('ę'); break; + case 0xe8: resultBuilder.Append('č'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ź'); break; + case 0xeb: resultBuilder.Append('ė'); break; + case 0xee: resultBuilder.Append('ī'); break; + case 0xf0: resultBuilder.Append('š'); break; + case 0xf1: resultBuilder.Append('ń'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf8: resultBuilder.Append('ų'); break; + case 0xf9: resultBuilder.Append('ł'); break; + case 0xfa: resultBuilder.Append('ś'); break; + case 0xfb: resultBuilder.Append('ū'); break; + case 0xfc: resultBuilder.Append('ü'); break; + case 0xfd: resultBuilder.Append('ż'); break; + case 0xfe: resultBuilder.Append('ž'); break; + case 0xff: resultBuilder.Append('’'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs new file mode 100644 index 0000000..6397fc7 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-14")] + class dvb_iso_8859_14 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc2: resultBuilder.Append('Â'); break; + case 0xc3: resultBuilder.Append('Ã'); break; + case 0xc7: resultBuilder.Append('Ç'); break; + case 0xc8: resultBuilder.Append('È'); break; + case 0xcf: resultBuilder.Append('Ï'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe7: resultBuilder.Append('ç'); break; + case 0xe8: resultBuilder.Append('è'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ê'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfc: resultBuilder.Append('ü'); break; + case 0xfd: resultBuilder.Append('ý'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs new file mode 100644 index 0000000..162eda4 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-15")] + class dvb_iso_8859_15 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('¡'); break; + case 0xa2: resultBuilder.Append('¢'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa4: resultBuilder.Append('€'); break; + case 0xa6: resultBuilder.Append('Š'); break; + case 0xa7: resultBuilder.Append('§'); break; + case 0xa8: resultBuilder.Append('š'); break; + case 0xa9: resultBuilder.Append('©'); break; + case 0xab: resultBuilder.Append('«'); break; + case 0xac: resultBuilder.Append('¬'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xae: resultBuilder.Append('®'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('±'); break; + case 0xb2: resultBuilder.Append('²'); break; + case 0xb3: resultBuilder.Append('³'); break; + case 0xb4: resultBuilder.Append('Ž'); break; + case 0xb6: resultBuilder.Append('¶'); break; + case 0xb7: resultBuilder.Append('·'); break; + case 0xb8: resultBuilder.Append('ž'); break; + case 0xba: resultBuilder.Append('º'); break; + case 0xbb: resultBuilder.Append('»'); break; + case 0xbc: resultBuilder.Append('Œ'); break; + case 0xbd: resultBuilder.Append('œ'); break; + case 0xbe: resultBuilder.Append('Ÿ'); break; + case 0xbf: resultBuilder.Append('¿'); break; + case 0xc0: resultBuilder.Append('À'); break; + case 0xc1: resultBuilder.Append('Α'); break; + case 0xc2: resultBuilder.Append('Â'); break; + case 0xc3: resultBuilder.Append('Ã'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc5: resultBuilder.Append('Å'); break; + case 0xc7: resultBuilder.Append('Ç'); break; + case 0xc8: resultBuilder.Append('È'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xca: resultBuilder.Append('Ê'); break; + case 0xcd: resultBuilder.Append('Í'); break; + case 0xce: resultBuilder.Append('Î'); break; + case 0xd2: resultBuilder.Append('Ò'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xd4: resultBuilder.Append('Ô'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xd7: resultBuilder.Append('×'); break; + case 0xd8: resultBuilder.Append('Ø'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('à'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe2: resultBuilder.Append('â'); break; + case 0xe3: resultBuilder.Append('ã'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe6: resultBuilder.Append('æ'); break; + case 0xe7: resultBuilder.Append('ç'); break; + case 0xe8: resultBuilder.Append('è'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ê'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xec: resultBuilder.Append('ì'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xee: resultBuilder.Append('î'); break; + case 0xef: resultBuilder.Append('ï'); break; + case 0xf0: resultBuilder.Append('ð'); break; + case 0xf1: resultBuilder.Append('ñ'); break; + case 0xf2: resultBuilder.Append('ò'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf5: resultBuilder.Append('õ'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf8: resultBuilder.Append('ø'); break; + case 0xf9: resultBuilder.Append('ù'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfb: resultBuilder.Append('û'); break; + case 0xfc: resultBuilder.Append('ü'); break; + case 0xfd: resultBuilder.Append('ý'); break; + case 0xff: resultBuilder.Append('ÿ'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-2.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-2.cs new file mode 100644 index 0000000..ea293ad --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-2.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-2")] + class dvb_iso_8859_2 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('Ą'); break; + case 0xa3: resultBuilder.Append('Ł'); break; + case 0xa4: resultBuilder.Append('¤'); break; + case 0xa5: resultBuilder.Append('Ľ'); break; + case 0xa6: resultBuilder.Append('Ś'); break; + case 0xa7: resultBuilder.Append('§'); break; + case 0xa8: resultBuilder.Append('¨'); break; + case 0xa9: resultBuilder.Append('Š'); break; + case 0xaa: resultBuilder.Append('Ş'); break; + case 0xab: resultBuilder.Append('Ť'); break; + case 0xac: resultBuilder.Append('Ź'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xae: resultBuilder.Append('Ž'); break; + case 0xaf: resultBuilder.Append('Ż'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('ą'); break; + case 0xb2: resultBuilder.Append('\u02db'); break; + case 0xb3: resultBuilder.Append('ł'); break; + case 0xb4: resultBuilder.Append('´'); break; + case 0xb5: resultBuilder.Append('ľ'); break; + case 0xb6: resultBuilder.Append('ś'); break; + case 0xb7: resultBuilder.Append('Ľ'); break; + case 0xb8: resultBuilder.Append('¸'); break; + case 0xb9: resultBuilder.Append('š'); break; + case 0xba: resultBuilder.Append('ş'); break; + case 0xbb: resultBuilder.Append('ť'); break; + case 0xbc: resultBuilder.Append('ź'); break; + case 0xbd: resultBuilder.Append('˝'); break; + case 0xbe: resultBuilder.Append('ž'); break; + case 0xbf: resultBuilder.Append('ż'); break; + case 0xc0: resultBuilder.Append('Ŕ'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc2: resultBuilder.Append('Â'); break; + case 0xc3: resultBuilder.Append('Ă'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc5: resultBuilder.Append('ľ'); break; + case 0xc6: resultBuilder.Append('Ć'); break; + case 0xc7: resultBuilder.Append('Ç'); break; + case 0xc8: resultBuilder.Append('Č'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xca: resultBuilder.Append('Ę'); break; + case 0xcc: resultBuilder.Append('Ě'); break; + case 0xcd: resultBuilder.Append('Í'); break; + case 0xce: resultBuilder.Append('Î'); break; + case 0xcf: resultBuilder.Append('Ď'); break; + case 0xd0: resultBuilder.Append('Đ'); break; + case 0xd1: resultBuilder.Append('Ń'); break; + case 0xd2: resultBuilder.Append('Ň'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xd4: resultBuilder.Append('Ô'); break; + case 0xd5: resultBuilder.Append('Ő'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xd7: resultBuilder.Append('×'); break; + case 0xd8: resultBuilder.Append('Ř'); break; + case 0xd9: resultBuilder.Append('Ů'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xdb: resultBuilder.Append('Ű'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdd: resultBuilder.Append('Ý'); break; + case 0xde: resultBuilder.Append('Ţ'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('ŕ'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe2: resultBuilder.Append('â'); break; + case 0xe3: resultBuilder.Append('ă'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('ĺ'); break; + case 0xe6: resultBuilder.Append('ć'); break; + case 0xe7: resultBuilder.Append('ç'); break; + case 0xe8: resultBuilder.Append('č'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ę'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xec: resultBuilder.Append('ě'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xee: resultBuilder.Append('î'); break; + case 0xef: resultBuilder.Append('ď'); break; + case 0xf0: resultBuilder.Append('đ'); break; + case 0xf1: resultBuilder.Append('ń'); break; + case 0xf2: resultBuilder.Append('ň'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf5: resultBuilder.Append('ő'); break; + case 0xf6: resultBuilder.Append('і'); break; + case 0xf8: resultBuilder.Append('ř'); break; + case 0xf9: resultBuilder.Append('ů'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfb: resultBuilder.Append('ű'); break; + case 0xfc: resultBuilder.Append('ü'); break; + case 0xfd: resultBuilder.Append('ý'); break; + case 0xfe: resultBuilder.Append('ţ'); break; + case 0xff: resultBuilder.Append('˙'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-3.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-3.cs new file mode 100644 index 0000000..dcba3e9 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-3.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-3")] + class dvb_iso_8859_3 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('Ħ'); break; + case 0xa2: resultBuilder.Append('˘'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa4: resultBuilder.Append('¤'); break; + case 0xc0: resultBuilder.Append('À'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xcd: resultBuilder.Append('Í'); break; + case 0xd1: resultBuilder.Append('Ñ'); break; + case 0xd2: resultBuilder.Append('Ò'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xe0: resultBuilder.Append('à'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe7: resultBuilder.Append('ç'); break; + case 0xe8: resultBuilder.Append('è'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xec: resultBuilder.Append('ì'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xef: resultBuilder.Append('ï'); break; + case 0xf1: resultBuilder.Append('ñ'); break; + case 0xf2: resultBuilder.Append('ò'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xfa: resultBuilder.Append('ù'); break; + case 0xfc: resultBuilder.Append('ü'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-4.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-4.cs new file mode 100644 index 0000000..c372a34 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-4.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-4")] + class dvb_iso_8859_4 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('Ą'); break; + case 0xa6: resultBuilder.Append('Ļ'); break; + case 0xa9: resultBuilder.Append('Š'); break; + case 0xaa: resultBuilder.Append('Ē'); break; + case 0xab: resultBuilder.Append('Ģ'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xae: resultBuilder.Append('Ž'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('ą'); break; + case 0xb6: resultBuilder.Append('ļ'); break; + case 0xb9: resultBuilder.Append('š'); break; + case 0xba: resultBuilder.Append('ē'); break; + case 0xbb: resultBuilder.Append('ģ'); break; + case 0xbe: resultBuilder.Append('ž'); break; + case 0xc0: resultBuilder.Append('Ā'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc7: resultBuilder.Append('Į'); break; + case 0xc8: resultBuilder.Append('Č'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xcc: resultBuilder.Append('Ė'); break; + case 0xce: resultBuilder.Append('Î'); break; + case 0xcf: resultBuilder.Append('Ī'); break; + case 0xd1: resultBuilder.Append('Ņ'); break; + case 0xd2: resultBuilder.Append('Ō'); break; + case 0xd3: resultBuilder.Append('Ķ'); break; + case 0xd5: resultBuilder.Append('Õ'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xd9: resultBuilder.Append('Ų'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xde: resultBuilder.Append('Ū'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('ā'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe3: resultBuilder.Append('ã'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe6: resultBuilder.Append('æ'); break; + case 0xe7: resultBuilder.Append('į'); break; + case 0xe8: resultBuilder.Append('č'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ę'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xec: resultBuilder.Append('ė'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xee: resultBuilder.Append('î'); break; + case 0xef: resultBuilder.Append('ī'); break; + case 0xf1: resultBuilder.Append('ņ'); break; + case 0xf2: resultBuilder.Append('ō'); break; + case 0xf3: resultBuilder.Append('ķ'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf5: resultBuilder.Append('õ'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf8: resultBuilder.Append('ø'); break; + case 0xf9: resultBuilder.Append('ų'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfc: resultBuilder.Append('ü'); break; + case 0xfe: resultBuilder.Append('ū'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-5.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-5.cs new file mode 100644 index 0000000..337d9c9 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-5.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-5")] + class dvb_iso_8859_5 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xa1: resultBuilder.Append('Ё'); break; + case 0xa3: resultBuilder.Append('Ѓ'); break; + case 0xa4: resultBuilder.Append('Є'); break; + case 0xa5: resultBuilder.Append('Ѕ'); break; + case 0xa6: resultBuilder.Append('І'); break; + case 0xa7: resultBuilder.Append('Ї'); break; + case 0xa8: resultBuilder.Append('Ј'); break; + case 0xa9: resultBuilder.Append('Љ'); break; + case 0xaa: resultBuilder.Append('Њ'); break; + case 0xac: resultBuilder.Append('Ќ'); break; + case 0xaf: resultBuilder.Append('П'); break; + case 0xb0: resultBuilder.Append('А'); break; + case 0xb1: resultBuilder.Append('Б'); break; + case 0xb2: resultBuilder.Append('В'); break; + case 0xb3: resultBuilder.Append('Г'); break; + case 0xb4: resultBuilder.Append('Д'); break; + case 0xb5: resultBuilder.Append('Е'); break; + case 0xb6: resultBuilder.Append('Ж'); break; + case 0xb7: resultBuilder.Append('З'); break; + case 0xb8: resultBuilder.Append('И'); break; + case 0xb9: resultBuilder.Append('Й'); break; + case 0xba: resultBuilder.Append('К'); break; + case 0xbb: resultBuilder.Append('Л'); break; + case 0xbc: resultBuilder.Append('М'); break; + case 0xbd: resultBuilder.Append('Н'); break; + case 0xbe: resultBuilder.Append('О'); break; + case 0xbf: resultBuilder.Append('П'); break; + case 0xc0: resultBuilder.Append('Р'); break; + case 0xc1: resultBuilder.Append('С'); break; + case 0xc2: resultBuilder.Append('Т'); break; + case 0xc3: resultBuilder.Append('У'); break; + case 0xc4: resultBuilder.Append('Ф'); break; + case 0xc5: resultBuilder.Append('Х'); break; + case 0xc6: resultBuilder.Append('Ц'); break; + case 0xc7: resultBuilder.Append('Ч'); break; + case 0xc8: resultBuilder.Append('Ш'); break; + case 0xc9: resultBuilder.Append('Щ'); break; + case 0xca: resultBuilder.Append('Ъ'); break; + case 0xcb: resultBuilder.Append('Ы'); break; + case 0xcc: resultBuilder.Append('Ь'); break; + case 0xcd: resultBuilder.Append('Э'); break; + case 0xce: resultBuilder.Append('Ю'); break; + case 0xcf: resultBuilder.Append('Я'); break; + case 0xd0: resultBuilder.Append('а'); break; + case 0xd1: resultBuilder.Append('б'); break; + case 0xd2: resultBuilder.Append('в'); break; + case 0xd3: resultBuilder.Append('г'); break; + case 0xd4: resultBuilder.Append('д'); break; + case 0xd5: resultBuilder.Append('е'); break; + case 0xd6: resultBuilder.Append('ж'); break; + case 0xd7: resultBuilder.Append('з'); break; + case 0xd8: resultBuilder.Append('и'); break; + case 0xd9: resultBuilder.Append('й'); break; + case 0xda: resultBuilder.Append('к'); break; + case 0xdb: resultBuilder.Append('л'); break; + case 0xdc: resultBuilder.Append('v'); break; + case 0xdd: resultBuilder.Append('н'); break; + case 0xde: resultBuilder.Append('о'); break; + case 0xdf: resultBuilder.Append('п'); break; + case 0xe0: resultBuilder.Append('р'); break; + case 0xe1: resultBuilder.Append('с'); break; + case 0xe2: resultBuilder.Append('т'); break; + case 0xe3: resultBuilder.Append('у'); break; + case 0xe4: resultBuilder.Append('ф'); break; + case 0xe5: resultBuilder.Append('х'); break; + case 0xe6: resultBuilder.Append('ц'); break; + case 0xe7: resultBuilder.Append('ч'); break; + case 0xe8: resultBuilder.Append('ш'); break; + case 0xe9: resultBuilder.Append('щ'); break; + case 0xea: resultBuilder.Append('ъ'); break; + case 0xeb: resultBuilder.Append('Ŧ'); break; + case 0xec: resultBuilder.Append('ь'); break; + case 0xed: resultBuilder.Append('э'); break; + case 0xee: resultBuilder.Append('ю'); break; + case 0xef: resultBuilder.Append('я'); break; + case 0xf0: resultBuilder.Append('№'); break; + case 0xf1: resultBuilder.Append('ё'); break; + case 0xf3: resultBuilder.Append('ѓ'); break; + case 0xf4: resultBuilder.Append('є'); break; + case 0xf5: resultBuilder.Append('ѕ'); break; + case 0xf6: resultBuilder.Append('і'); break; + case 0xf7: resultBuilder.Append('ї'); break; + case 0xf8: resultBuilder.Append('ј'); break; + case 0xf9: resultBuilder.Append('љ'); break; + case 0xfa: resultBuilder.Append('њ'); break; + case 0xfc: resultBuilder.Append('ќ'); break; + case 0xfd: resultBuilder.Append('§'); break; + case 0xfe: resultBuilder.Append('ў'); break; + case 0xff: resultBuilder.Append('џ'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs new file mode 100644 index 0000000..729eb59 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-6")] + class dvb_iso_8859_6 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + bool arabMode = false; + for (int i = 0; i < preprocessed.Length; i++) + { + switch (preprocessed[i]) + { + case 0x00: break; //Null + case 0x0a: resultBuilder.Append('\n'); break; + case 0x10: break; //Data Link Escape + case 0x13: break; //Device Control 3 + case 0x20: resultBuilder.Append(' '); break; + case 0x21: resultBuilder.Append('!'); break; + case 0x22: resultBuilder.Append('\"'); break; + case 0x23: resultBuilder.Append('#'); break; + case 0x24: resultBuilder.Append('$'); break; + case 0x25: resultBuilder.Append(!arabMode ? '%' : '٪'); break; + case 0x26: resultBuilder.Append('&'); break; + case 0x27: resultBuilder.Append('\''); break; + case 0x28: resultBuilder.Append('('); break; + case 0x29: resultBuilder.Append(')'); break; + case 0x2b: resultBuilder.Append('+'); break; + case 0x2c: resultBuilder.Append(','); break; + case 0x2d: resultBuilder.Append('-'); break; + case 0x2e: resultBuilder.Append('.'); break; + case 0x2f: resultBuilder.Append('/'); break; + case 0x30: resultBuilder.Append(!arabMode ? '0' : '٠'); break; + case 0x31: resultBuilder.Append(!arabMode ? '1' : '١'); break; + case 0x32: resultBuilder.Append(!arabMode ? '2' : '٢'); break; + case 0x33: resultBuilder.Append(!arabMode ? '3' : '٣'); break; + case 0x34: resultBuilder.Append(!arabMode ? '4' : '٤'); break; + case 0x35: resultBuilder.Append(!arabMode ? '5' : '٥'); break; + case 0x36: resultBuilder.Append(!arabMode ? '6' : '٦'); break; + case 0x37: resultBuilder.Append(!arabMode ? '7' : '٧'); break; + case 0x38: resultBuilder.Append(!arabMode ? '8' : '٨'); break; + case 0x39: resultBuilder.Append(!arabMode ? '9' : '٩'); break; + case 0x3a: resultBuilder.Append(':'); break; + case 0x3b: resultBuilder.Append(';'); break; + case 0x3f: resultBuilder.Append('?'); break; + case 0x40: resultBuilder.Append('@'); break; + case 0x41: resultBuilder.Append('A'); arabMode = false; break; + case 0x42: resultBuilder.Append('B'); arabMode = false; break; + case 0x43: resultBuilder.Append('C'); arabMode = false; break; + case 0x44: resultBuilder.Append('D'); arabMode = false; break; + case 0x45: resultBuilder.Append('E'); arabMode = false; break; + case 0x46: resultBuilder.Append('F'); arabMode = false; break; + case 0x47: resultBuilder.Append('G'); arabMode = false; break; + case 0x48: resultBuilder.Append('H'); arabMode = false; break; + case 0x49: resultBuilder.Append('I'); arabMode = false; break; + case 0x4a: resultBuilder.Append('J'); arabMode = false; break; + case 0x4b: resultBuilder.Append('K'); arabMode = false; break; + case 0x4c: resultBuilder.Append('L'); arabMode = false; break; + case 0x4d: resultBuilder.Append('M'); arabMode = false; break; + case 0x4e: resultBuilder.Append('N'); arabMode = false; break; + case 0x4f: resultBuilder.Append('O'); arabMode = false; break; + case 0x50: resultBuilder.Append('P'); arabMode = false; break; + case 0x51: resultBuilder.Append('Q'); arabMode = false; break; + case 0x52: resultBuilder.Append('R'); arabMode = false; break; + case 0x53: resultBuilder.Append('S'); arabMode = false; break; + case 0x54: resultBuilder.Append('T'); arabMode = false; break; + case 0x55: resultBuilder.Append('U'); arabMode = false; break; + case 0x56: resultBuilder.Append('V'); arabMode = false; break; + case 0x57: resultBuilder.Append('W'); arabMode = false; break; + case 0x58: resultBuilder.Append('X'); arabMode = false; break; + case 0x59: resultBuilder.Append('Y'); arabMode = false; break; + case 0x5a: resultBuilder.Append('Z'); arabMode = false; break; + case 0x61: resultBuilder.Append('a'); arabMode = false; break; + case 0x62: resultBuilder.Append('b'); arabMode = false; break; + case 0x63: resultBuilder.Append('c'); arabMode = false; break; + case 0x64: resultBuilder.Append('d'); arabMode = false; break; + case 0x65: resultBuilder.Append('e'); arabMode = false; break; + case 0x66: resultBuilder.Append('f'); arabMode = false; break; + case 0x67: resultBuilder.Append('g'); arabMode = false; break; + case 0x68: resultBuilder.Append('h'); arabMode = false; break; + case 0x69: resultBuilder.Append('i'); arabMode = false; break; + case 0x6a: resultBuilder.Append('j'); arabMode = false; break; + case 0x6b: resultBuilder.Append('k'); arabMode = false; break; + case 0x6c: resultBuilder.Append('l'); arabMode = false; break; + case 0x6d: resultBuilder.Append('m'); arabMode = false; break; + case 0x6e: resultBuilder.Append('n'); arabMode = false; break; + case 0x6f: resultBuilder.Append('o'); arabMode = false; break; + case 0x70: resultBuilder.Append('p'); arabMode = false; break; + case 0x71: resultBuilder.Append('q'); arabMode = false; break; + case 0x72: resultBuilder.Append('r'); arabMode = false; break; + case 0x73: resultBuilder.Append('s'); arabMode = false; break; + case 0x74: resultBuilder.Append('t'); arabMode = false; break; + case 0x75: resultBuilder.Append('u'); arabMode = false; break; + case 0x76: resultBuilder.Append('v'); arabMode = false; break; + case 0x77: resultBuilder.Append('w'); arabMode = false; break; + case 0x78: resultBuilder.Append('x'); arabMode = false; break; + case 0x79: resultBuilder.Append('y'); arabMode = false; break; + case 0x7a: resultBuilder.Append('z'); arabMode = false; break; + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xac: resultBuilder.Append('،'); arabMode = true; break; + case 0xbb: resultBuilder.Append('؛'); arabMode = true; break; + case 0xbf: resultBuilder.Append('؟'); arabMode = true; break; + case 0xc1: resultBuilder.Append('ء'); arabMode = true; break; + case 0xc2: resultBuilder.Append('آ'); arabMode = true; break; + case 0xc3: resultBuilder.Append('أ'); arabMode = true; break; + case 0xc4: resultBuilder.Append('ؤ'); arabMode = true; break; + case 0xc5: resultBuilder.Append('إ'); arabMode = true; break; + case 0xc6: resultBuilder.Append('ئ'); arabMode = true; break; + case 0xc7: resultBuilder.Append('ا'); arabMode = true; break; + case 0xc8: resultBuilder.Append('ب'); arabMode = true; break; + case 0xc9: resultBuilder.Append('ة'); arabMode = true; break; + case 0xca: resultBuilder.Append('ت'); arabMode = true; break; + case 0xcb: resultBuilder.Append('ث'); arabMode = true; break; + case 0xcc: resultBuilder.Append('ج'); arabMode = true; break; + case 0xcd: resultBuilder.Append('ح'); arabMode = true; break; + case 0xce: resultBuilder.Append('خ'); arabMode = true; break; + case 0xcf: resultBuilder.Append('د'); arabMode = true; break; + case 0xd0: resultBuilder.Append('ذ'); arabMode = true; break; + case 0xd1: resultBuilder.Append('ر'); arabMode = true; break; + case 0xd2: resultBuilder.Append('ز'); arabMode = true; break; + case 0xd3: resultBuilder.Append('آ'); arabMode = true; break; + case 0xd4: resultBuilder.Append('ش'); arabMode = true; break; + case 0xd5: resultBuilder.Append('ص'); arabMode = true; break; + case 0xd6: resultBuilder.Append('ض'); arabMode = true; break; + case 0xd7: resultBuilder.Append('ط'); arabMode = true; break; + case 0xd8: resultBuilder.Append('ظ'); arabMode = true; break; + case 0xd9: resultBuilder.Append('ع'); arabMode = true; break; + case 0xda: resultBuilder.Append('غ'); arabMode = true; break; + case 0xe0: resultBuilder.Append('ـ'); arabMode = true; break; + case 0xe1: resultBuilder.Append('ف'); arabMode = true; break; + case 0xe2: resultBuilder.Append('ق'); arabMode = true; break; + case 0xe3: resultBuilder.Append('ك'); arabMode = true; break; + case 0xe4: resultBuilder.Append('ل'); arabMode = true; break; + case 0xe5: resultBuilder.Append('م'); arabMode = true; break; + case 0xe6: resultBuilder.Append('ن'); arabMode = true; break; + case 0xe7: resultBuilder.Append('ه'); arabMode = true; break; + case 0xe8: resultBuilder.Append('و'); arabMode = true; break; + case 0xe9: resultBuilder.Append('ى'); arabMode = true; break; + case 0xea: resultBuilder.Append('ي'); arabMode = true; break; + case 0xeb: resultBuilder.Append('\u064b'); arabMode = true; break; + case 0xec: resultBuilder.Append('\u064c'); arabMode = true; break; + case 0xed: resultBuilder.Append('\u064d'); arabMode = true; break; + case 0xee: resultBuilder.Append('\u064e'); arabMode = true; break; + case 0xef: resultBuilder.Append('\u064f'); arabMode = true; break; + case 0xf0: resultBuilder.Append('\u0650'); arabMode = true; break; + case 0xf1: resultBuilder.Append('\u0651'); arabMode = true; break; + case 0xf2: resultBuilder.Append('\u0652'); arabMode = true; break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-7.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-7.cs new file mode 100644 index 0000000..91f3225 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-7.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-7")] + class dvb_iso_8859_7 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('ʻ'); break; + case 0xa2: resultBuilder.Append('’'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa4: resultBuilder.Append('€'); break; + case 0xa9: resultBuilder.Append('©'); break; + case 0xab: resultBuilder.Append('«'); break; + case 0xad: resultBuilder.Append('\u00AD'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb4: resultBuilder.Append('´'); break; + case 0xb6: resultBuilder.Append('Ά'); break; + case 0xb7: resultBuilder.Append('·'); break; + case 0xb8: resultBuilder.Append('Έ'); break; + case 0xb9: resultBuilder.Append('Ί'); break; + case 0xba: resultBuilder.Append('Ί'); break; + case 0xbb: resultBuilder.Append('»'); break; + case 0xbc: resultBuilder.Append('Ό'); break; + case 0xbe: resultBuilder.Append('Ύ'); break; + case 0xbf: resultBuilder.Append('Ώ'); break; + case 0xc0: resultBuilder.Append('ΐ'); break; + case 0xc1: resultBuilder.Append('Α'); break; + case 0xc2: resultBuilder.Append('Β'); break; + case 0xc3: resultBuilder.Append('Γ'); break; + case 0xc4: resultBuilder.Append('Δ'); break; + case 0xc5: resultBuilder.Append('Ε'); break; + case 0xc6: resultBuilder.Append('Ζ'); break; + case 0xc7: resultBuilder.Append('Η'); break; + case 0xc8: resultBuilder.Append('Θ'); break; + case 0xc9: resultBuilder.Append('Ι'); break; + case 0xca: resultBuilder.Append('Κ'); break; + case 0xcb: resultBuilder.Append('Λ'); break; + case 0xcc: resultBuilder.Append('Μ'); break; + case 0xcd: resultBuilder.Append('Ν'); break; + case 0xce: resultBuilder.Append('Ξ'); break; + case 0xcf: resultBuilder.Append('Ο'); break; + case 0xd0: resultBuilder.Append('Π'); break; + case 0xd1: resultBuilder.Append('Ρ'); break; + case 0xd3: resultBuilder.Append('Σ'); break; + case 0xd4: resultBuilder.Append('Τ'); break; + case 0xd5: resultBuilder.Append('Υ'); break; + case 0xd6: resultBuilder.Append('Φ'); break; + case 0xd7: resultBuilder.Append('Χ'); break; + case 0xd8: resultBuilder.Append('Ψ'); break; + case 0xd9: resultBuilder.Append('Ω'); break; + case 0xda: resultBuilder.Append('Ϊ'); break; + case 0xdc: resultBuilder.Append('ά'); break; + case 0xdd: resultBuilder.Append('έ'); break; + case 0xde: resultBuilder.Append('ή'); break; + case 0xdf: resultBuilder.Append('ί'); break; + case 0xe0: resultBuilder.Append('ΰ'); break; + case 0xe1: resultBuilder.Append('α'); break; + case 0xe2: resultBuilder.Append('β'); break; + case 0xe3: resultBuilder.Append('γ'); break; + case 0xe4: resultBuilder.Append('δ'); break; + case 0xe5: resultBuilder.Append('ε'); break; + case 0xe6: resultBuilder.Append('ζ'); break; + case 0xe7: resultBuilder.Append('η'); break; + case 0xe8: resultBuilder.Append('θ'); break; + case 0xe9: resultBuilder.Append('ι'); break; + case 0xea: resultBuilder.Append('κ'); break; + case 0xeb: resultBuilder.Append('λ'); break; + case 0xec: resultBuilder.Append('μ'); break; + case 0xed: resultBuilder.Append('ν'); break; + case 0xee: resultBuilder.Append('ξ'); break; + case 0xef: resultBuilder.Append('ο'); break; + case 0xf0: resultBuilder.Append('π'); break; + case 0xf1: resultBuilder.Append('ρ'); break; + case 0xf2: resultBuilder.Append('ς'); break; + case 0xf3: resultBuilder.Append('σ'); break; + case 0xf4: resultBuilder.Append('τ'); break; + case 0xf5: resultBuilder.Append('υ'); break; + case 0xf6: resultBuilder.Append('φ'); break; + case 0xf7: resultBuilder.Append('χ'); break; + case 0xf8: resultBuilder.Append('ψ'); break; + case 0xf9: resultBuilder.Append('ω'); break; + case 0xfa: resultBuilder.Append('ϊ'); break; + case 0xfb: resultBuilder.Append('ϋ'); break; + case 0xfc: resultBuilder.Append('ό'); break; + case 0xfd: resultBuilder.Append('ύ'); break; + case 0xfe: resultBuilder.Append('ώ'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs new file mode 100644 index 0000000..285697c --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-8")] + class dvb_iso_8859_8 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa2: resultBuilder.Append('¢'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa4: resultBuilder.Append('¤'); break; + case 0xa5: resultBuilder.Append('¥'); break; + case 0xa6: resultBuilder.Append('¦'); break; + case 0xa7: resultBuilder.Append('§'); break; + case 0xa8: resultBuilder.Append('¨'); break; + case 0xa9: resultBuilder.Append('©'); break; + case 0xaa: resultBuilder.Append('×'); break; + case 0xab: resultBuilder.Append('«'); break; + case 0xac: resultBuilder.Append('¬'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xae: resultBuilder.Append('®'); break; + case 0xaf: resultBuilder.Append('¯'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-9.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-9.cs new file mode 100644 index 0000000..80efca1 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-9.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-9")] + class dvb_iso_8859_9 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('¡'); break; + case 0xa2: resultBuilder.Append('¢'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xa4: resultBuilder.Append('¤'); break; + case 0xa5: resultBuilder.Append('¥'); break; + case 0xa6: resultBuilder.Append('¦'); break; + case 0xa7: resultBuilder.Append('§'); break; + case 0xa8: resultBuilder.Append('¨'); break; + case 0xa9: resultBuilder.Append('©'); break; + case 0xaa: resultBuilder.Append('ª'); break; + case 0xab: resultBuilder.Append('«'); break; + case 0xac: resultBuilder.Append('¬'); break; + case 0xad: resultBuilder.Append('\u00ad'); break; + case 0xae: resultBuilder.Append('®'); break; + case 0xaf: resultBuilder.Append('¯'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('±'); break; + case 0xb2: resultBuilder.Append('²'); break; + case 0xb3: resultBuilder.Append('³'); break; + case 0xb4: resultBuilder.Append('´'); break; + case 0xb6: resultBuilder.Append('¶'); break; + case 0xb7: resultBuilder.Append('·'); break; + case 0xb8: resultBuilder.Append('¸'); break; + case 0xb9: resultBuilder.Append('¹'); break; + case 0xba: resultBuilder.Append('ª'); break; + case 0xbb: resultBuilder.Append('»'); break; + case 0xbc: resultBuilder.Append('¼'); break; + case 0xbd: resultBuilder.Append('½'); break; + case 0xbf: resultBuilder.Append('¿'); break; + case 0xc0: resultBuilder.Append('À'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc2: resultBuilder.Append('Â'); break; + case 0xc3: resultBuilder.Append('Ã'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc5: resultBuilder.Append('Å'); break; + case 0xc6: resultBuilder.Append('Æ'); break; + case 0xc7: resultBuilder.Append('Ç'); break; + case 0xc8: resultBuilder.Append('È'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xca: resultBuilder.Append('Ê'); break; + case 0xcb: resultBuilder.Append('Ë'); break; + case 0xcc: resultBuilder.Append('Ì'); break; + case 0xcd: resultBuilder.Append('Í'); break; + case 0xce: resultBuilder.Append('Î'); break; + case 0xcf: resultBuilder.Append('Ï'); break; + case 0xd0: resultBuilder.Append('Ğ'); break; + case 0xd1: resultBuilder.Append('Ñ'); break; + case 0xd2: resultBuilder.Append('Ò'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xd4: resultBuilder.Append('Ô'); break; + case 0xd5: resultBuilder.Append('Õ'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xd7: resultBuilder.Append('×'); break; + case 0xd8: resultBuilder.Append('Ø'); break; + case 0xd9: resultBuilder.Append('Ù'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xdb: resultBuilder.Append('Û'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdd: resultBuilder.Append('İ'); break; + case 0xde: resultBuilder.Append('Ş'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('à'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe2: resultBuilder.Append('â'); break; + case 0xe3: resultBuilder.Append('ã'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe6: resultBuilder.Append('æ'); break; + case 0xe7: resultBuilder.Append('ç'); break; + case 0xe8: resultBuilder.Append('è'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ê'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xec: resultBuilder.Append('ì'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xee: resultBuilder.Append('î'); break; + case 0xef: resultBuilder.Append('ï'); break; + case 0xf0: resultBuilder.Append('ğ'); break; + case 0xf1: resultBuilder.Append('ñ'); break; + case 0xf2: resultBuilder.Append('ò'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf5: resultBuilder.Append('õ'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf7: resultBuilder.Append('÷'); break; + case 0xf8: resultBuilder.Append('ø'); break; + case 0xf9: resultBuilder.Append('ù'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfb: resultBuilder.Append('û'); break; + case 0xfc: resultBuilder.Append('ü'); break; + case 0xfd: resultBuilder.Append('ı'); break; + case 0xfe: resultBuilder.Append('ş'); break; + case 0xff: resultBuilder.Append('ÿ'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-utf8.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-utf8.cs new file mode 100644 index 0000000..5311940 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-utf8.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-utf8")] + class dvb_utf_8 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + return Encoding.UTF8.GetString(preprocessed).ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/SkyscraperBaseEncoding16.cs b/skyscraper8/Skyscraper/Text/SkyscraperBaseEncoding16.cs new file mode 100644 index 0000000..f5b9c50 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/SkyscraperBaseEncoding16.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Text.Encodings; + +namespace skyscraper5.Skyscraper.Text +{ + abstract class SkyscraperBaseEncoding16 : SkyscraperBaseEncoding8 + { + protected abstract override char[] Decrypt(byte[] preprocessed); + + protected override int GetPreProcessLength(byte[] buffer, int index, int count) + { + int result = 0; + byte t, u; + for (int i = 0; i < count; i += 2) + { + t = buffer[i + index]; + if (t == 0xe0) + { + u = buffer[i + index + 1]; + if (u >= 0x80 && u <= 0x85) + continue; //reserved for future use + if (u == 0x86) + continue; //character emphasis on + if (u == 0x87) + continue; //character emphasis off + if (u >= 0x88 && u <= 0x89) + continue; //reserved for future use + if (u == 0x8a) + result += 4; //CRLF + if (u >= 0x8b && u <= 0x9f) + continue; //user defined + } + result += 2; + } + return result; + } + + protected override byte[] PreProcess(byte[] buffer, int index, int count, int predictedLength) + { + byte[] result = new byte[predictedLength]; + int outputOffset = 0; + byte t, u; + count /= 2; + count *= 2; + for (int i = 0; i < count; i += 2) + { + t = buffer[i + index]; + if (t == 0xe0) + { + u = buffer[i + index + 1]; + if (u >= 0x80 && u <= 0x85) + continue; //reserved for future use + if (u == 0x86) + continue; //character emphasis on + if (u == 0x87) + continue; //character emphasis off + if (u >= 0x88 && u <= 0x89) + continue; //reserved for future use + if (u == 0x8a) + { //CRLF + result[outputOffset++] = 0x00; + result[outputOffset++] = 0x10; + result[outputOffset++] = 0x00; + result[outputOffset++] = 0x13; + continue; + } + if (u >= 0x8b && u <= 0x9f) + continue; //user defined + } + else + { + result[outputOffset++] = t; + result[outputOffset++] = buffer[i + index + 1]; + } + } + + return result; + } + } +} diff --git a/skyscraper8/Skyscraper/Text/SkyscraperBaseEncoding8.cs b/skyscraper8/Skyscraper/Text/SkyscraperBaseEncoding8.cs new file mode 100644 index 0000000..e79aeb8 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/SkyscraperBaseEncoding8.cs @@ -0,0 +1,150 @@ +using System; +using System.Text; + +namespace skyscraper5.Skyscraper.Text +{ + abstract class SkyscraperBaseEncoding8 : Encoding + { + protected abstract char[] Decrypt(byte[] preprocessed); + + private char[] lastDecrypted; + private byte[] lastBuffer; + private bool IsSameStringAsLast(byte[] buffer, int index, int count) + { + if (lastBuffer == null) + { + //Dies ist der erste call. + lastBuffer = new byte[count]; + Array.Copy(buffer, index, lastBuffer, 0, count); + return false; + } + + if (lastBuffer.Length != count) + { + //string länge anders als beim letzten mal, kann also nicht der selbe sein. + lastBuffer = new byte[count]; + Array.Copy(buffer, index, lastBuffer, 0, count); + return false; + } + + for (int i = 0; i < count; i++) + { + if (lastBuffer[i] != buffer[i + index]) + { + //String anders als beim letzen mal + lastBuffer = new byte[count]; + Array.Copy(buffer, index, lastBuffer, 0, count); + return false; + } + } + return true; + } + + protected virtual int GetPreProcessLength(byte[] buffer, int index, int count) + { + int result = 0; + byte t; + for (int i = 0; i < count; i++) + { + t = buffer[i + index]; + if (t >= 0x80 && t <= 0x85) + continue; //reserved for future use + if (t == 0x86) + continue; //character emphasis on + if (t == 0x87) + continue; //character emphasis off + if (t >= 0x88 && t <= 0x89) + continue; //reserved for future use + if (t == 0x8a) + result += 2; //CRLF + if (t >= 0x8b && t <= 0x9f) + continue; //user defined + result++; + } + return result; + } + + protected virtual byte[] PreProcess(byte[] buffer, int index, int count, int predictedLength) + { + byte[] result = new byte[predictedLength]; + int outputOffset = 0; + byte t; + for (int inputOffset = 0; inputOffset < count; inputOffset++) + { + t = buffer[inputOffset + index]; + if (t >= 0x80 && t <= 0x85) + continue; //reserved for future use + if (t == 0x86) + continue; //character emphasis on + if (t == 0x87) + continue; //character emphasis off + if (t >= 0x88 && t <= 0x89) + continue; //reserved for future use + if (t == 0x8a) + { + result[outputOffset++] = 0x10; + result[outputOffset++] = 0x13; + continue; + } + + if (t >= 0x8b && t <= 0x9f) + continue; //user defined + + result[outputOffset++] = t; + } + + return result; + } + + public override int GetByteCount(char[] chars, int index, int count) + { + throw new NotImplementedException(); + } + + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + { + throw new NotImplementedException(); + } + + public override int GetCharCount(byte[] bytes, int index, int count) + { + if (!IsSameStringAsLast(bytes, index, count)) + { + int preProcessLength = GetPreProcessLength(bytes, index, count); + byte[] preProcess = PreProcess(bytes, index, count, preProcessLength); + lastDecrypted = Decrypt(preProcess); + } + return lastDecrypted.Length; + } + + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + { + if (!IsSameStringAsLast(bytes, byteIndex, byteCount)) + { + int preProcessLength = GetPreProcessLength(bytes, byteIndex, byteCount); + byte[] preProcess = PreProcess(bytes, byteIndex, byteCount, preProcessLength); + int maxAllowedSize = lastDecrypted.Length; + char[] overwriting = Decrypt(preProcess); + if (overwriting.Length > maxAllowedSize) + { + throw new AccessViolationException("Oh no, I guess the multithreading went haywire..."); + } + lastDecrypted = overwriting; + } + + int result = Math.Min(chars.Length - charIndex, lastDecrypted.Length); + Array.Copy(lastDecrypted, 0, chars, charIndex, result); + return result; + } + + public override int GetMaxByteCount(int charCount) + { + throw new NotImplementedException(); + } + + public override int GetMaxCharCount(int byteCount) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/SkyscraperEncodingAttribute.cs b/skyscraper8/Skyscraper/Text/SkyscraperEncodingAttribute.cs new file mode 100644 index 0000000..36ae51f --- /dev/null +++ b/skyscraper8/Skyscraper/Text/SkyscraperEncodingAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text +{ + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + sealed class SkyscraperEncodingAttribute : Attribute + { + public SkyscraperEncodingAttribute(params string[] aliases) + { + this.Aliases = aliases; + } + + public string[] Aliases { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Text/SkyscraperEncodingProvider.cs b/skyscraper8/Skyscraper/Text/SkyscraperEncodingProvider.cs new file mode 100644 index 0000000..1589a6d --- /dev/null +++ b/skyscraper8/Skyscraper/Text/SkyscraperEncodingProvider.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Text.Encodings; + +namespace skyscraper5.Skyscraper.Text +{ + public class SkyscraperEncodingProvider : EncodingProvider + { + private Dictionary _encodings; + private static SkyscraperEncodingProvider _instance; + public static SkyscraperEncodingProvider GetInstance() + { + if (_instance == null) + _instance = new SkyscraperEncodingProvider(); + return _instance; + } + + private SkyscraperEncodingProvider() + { + Type sourceAttribute = typeof(SkyscraperEncodingAttribute); + _encodings = new Dictionary(); + Type[] types = GetType().Assembly.GetTypes(); + foreach (Type type in types) + { + object[] customAttributes = type.GetCustomAttributes(sourceAttribute, false); + if (customAttributes.Length == 0) + continue; + + Encoding instance = (Encoding)Activator.CreateInstance(type); + SkyscraperEncodingAttribute attribute = (SkyscraperEncodingAttribute)customAttributes[0]; + foreach (string attributeAlias in attribute.Aliases) + _encodings.Add(attributeAlias,instance); + } + } + + public override Encoding? GetEncoding(int codepage) + { + if (codepage == 1200) + return null; + + if (codepage == 65001) + return null; + + if (codepage == 0) + return null; + + throw new NotImplementedException(); + } + + public override Encoding? GetEncoding(string name) + { + if (!name.StartsWith("dvb-")) + return null; + + if (_encodings.ContainsKey(name)) + return _encodings[name]; + + return new DummyEncoding(name); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/SkyscraperEncodingTemplate.cs b/skyscraper8/Skyscraper/Text/SkyscraperEncodingTemplate.cs new file mode 100644 index 0000000..a5a8864 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/SkyscraperEncodingTemplate.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; + +namespace skyscraper5.Skyscraper.Text +{ + [SkyscraperEncoding("skyscraper-template-sophia")] + class SkyscraperEncodingTemplate : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + switch (preprocessed[i]) + { + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/TunerMetadata.cs b/skyscraper8/Skyscraper/TunerMetadata.cs new file mode 100644 index 0000000..be19287 --- /dev/null +++ b/skyscraper8/Skyscraper/TunerMetadata.cs @@ -0,0 +1,47 @@ +using System; +using System.Net.NetworkInformation; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; + +namespace skyscraper5.Skyscraper +{ + public class TunerMetadata + { + public int Index { get; } + public string Name { get; } + public STD_TYPE Type { get; } + public Caps Caps { get; set; } + public PhysicalAddress MacAddress { get; set; } + + public int DiseqcType; + + public int[] Satellites; + public int[] Lnbs; + public int[] Dishes; + + public TunerMetadata() + { + + } + + public TunerMetadata(int index, string name, STD_TYPE type) + { + Index = index; + Name = name; + Type = type; + Satellites = new int[4]; + Lnbs = new int[4]; + Dishes = new int[4]; + } + + public int GetNumSatsFromDiseqcType() + { + switch (DiseqcType) + { + case 0: return 1; + case 1: return 2; + case 2: return 4; + default: throw new NotImplementedException(String.Format("{0} {1}", nameof(DiseqcType), DiseqcType)); + } + } + } +} diff --git a/skyscraper8/Skyscraper/Validatable.cs b/skyscraper8/Skyscraper/Validatable.cs new file mode 100644 index 0000000..92cc786 --- /dev/null +++ b/skyscraper8/Skyscraper/Validatable.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace skyscraper5.Skyscraper +{ + public abstract class Validatable + { + private bool? _valid; + + [JsonIgnore] + public bool Valid + { + get + { + if (!_valid.HasValue) + { + throw new InvalidOperationException(String.Format("{0} doesn't know whether it's valid.", this.GetType().FullName)); + } + + return _valid.Value; + } + set + { + _valid = value; + } + } + } +} diff --git a/skyscraper8/Skyscraper/Webserver/AlreadyListeningException.cs b/skyscraper8/Skyscraper/Webserver/AlreadyListeningException.cs new file mode 100644 index 0000000..144baf3 --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/AlreadyListeningException.cs @@ -0,0 +1,8 @@ +using System; + +namespace skyscraper5.Skyscraper.Webserver +{ + internal class AlreadyListeningException : SkyscraperWebserverException + { + } +} diff --git a/skyscraper8/Skyscraper/Webserver/ISkyscraperWebserverResource.cs b/skyscraper8/Skyscraper/Webserver/ISkyscraperWebserverResource.cs new file mode 100644 index 0000000..feb46dc --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/ISkyscraperWebserverResource.cs @@ -0,0 +1,11 @@ +using System.Net; + +namespace skyscraper5.Skyscraper.Webserver +{ + internal interface ISkyscraperWebserverResource + { + bool CanHandle(HttpListenerRequest request); + + void HandleRequest(HttpListenerContext context); + } +} diff --git a/skyscraper8/Skyscraper/Webserver/Metrics/MetricHelpAttribute.cs b/skyscraper8/Skyscraper/Webserver/Metrics/MetricHelpAttribute.cs new file mode 100644 index 0000000..c83bbbd --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/Metrics/MetricHelpAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace skyscraper5.Skyscraper.Webserver.Metrics +{ + [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + sealed class MetricHelpAttribute : Attribute + { + public string Text { get; } + + public MetricHelpAttribute(string text) + { + Text = text; + } + } +} diff --git a/skyscraper8/Skyscraper/Webserver/Metrics/MetricTypeAttribute.cs b/skyscraper8/Skyscraper/Webserver/Metrics/MetricTypeAttribute.cs new file mode 100644 index 0000000..6178421 --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/Metrics/MetricTypeAttribute.cs @@ -0,0 +1,21 @@ +using System; + +namespace skyscraper5.Skyscraper.Webserver.Metrics +{ + [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + sealed class MetricTypeAttribute : Attribute + { + public MetricType MetricType { get; } + + public MetricTypeAttribute(MetricType metricType) + { + MetricType = metricType; + } + } + + enum MetricType + { + Counter, + Gauge + } +} diff --git a/skyscraper8/Skyscraper/Webserver/Metrics/MetricsResource.cs b/skyscraper8/Skyscraper/Webserver/Metrics/MetricsResource.cs new file mode 100644 index 0000000..6f65dbb --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/Metrics/MetricsResource.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; + +namespace skyscraper5.Skyscraper.Webserver.Metrics +{ + internal class MetricsResource : ISkyscraperWebserverResource + { + private MetricsResource() + { + collectors = new List(); + collectors.Add(SystemMetricCollector.GetInstance()); + } + + public static MetricsResource _instance; + + public static MetricsResource GetInstance() + { + if (_instance == null) + _instance = new MetricsResource(); + + return _instance; + } + + public bool CanHandle(HttpListenerRequest request) + { + return request.HttpMethod.Equals("GET") && request.RawUrl.Equals("/metrics"); + } + + public void HandleRequest(HttpListenerContext context) + { + context.Response.ContentType = "text/plain"; + StreamWriter sw = new StreamWriter(context.Response.OutputStream); + HashSet helpPrinted = new HashSet(); + foreach (object collector in collectors) + { + ScrapeFromObject(sw, helpPrinted, collector); + } + sw.Flush(); + sw.Close(); + } + + private void ScrapeFromObject(StreamWriter sw, HashSet helpPrinted, object collector) + { + Type type = collector.GetType(); + PropertyInfo[] propertyInfos = type.GetProperties(); + foreach (PropertyInfo propertyInfo in propertyInfos) + { + if (!IsInterestingProperty(propertyInfo)) + continue; + + string name = BuildName(type, propertyInfo.Name); + if (!helpPrinted.Contains(name)) + { + MetricHelpAttribute metricHelpAttribute = propertyInfo.GetCustomAttribute(); + if (metricHelpAttribute != null) + { + sw.Write("# HELP {1} {0}\n", metricHelpAttribute.Text, name); + } + + MetricTypeAttribute metricTypeAttribute = propertyInfo.GetCustomAttribute(); + if (metricTypeAttribute != null) + { + sw.Write("# TYPE {1} {0}\n", metricTypeAttribute.MetricType.ToString().ToLowerInvariant(), name); + } + helpPrinted.Add(name); + } + sw.Write("{0} {1}\n", name, propertyInfo.GetValue(collector)); + } + } + + private string BuildName(Type type, string propName) + { + return String.Format("{0}_{1}", type.FullName, propName).ToLowerInvariant().Replace('.', '_'); + } + + private bool IsInterestingProperty(PropertyInfo pi) + { + IEnumerable attributes = pi.GetCustomAttributes(); + foreach (Attribute attribute in attributes) + { + MetricTypeAttribute metricTypeAttribute = attribute as MetricTypeAttribute; + return metricTypeAttribute != null; + } + + return false; + } + + private List collectors; + + public void AddCollector(object collector) + { + if (collectors.Contains(collector)) + return; + + collectors.Add(collector); + } + } +} diff --git a/skyscraper8/Skyscraper/Webserver/Metrics/SystemMetricCollector.cs b/skyscraper8/Skyscraper/Webserver/Metrics/SystemMetricCollector.cs new file mode 100644 index 0000000..4a028b4 --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/Metrics/SystemMetricCollector.cs @@ -0,0 +1,50 @@ +using System; + +namespace skyscraper5.Skyscraper.Webserver.Metrics +{ + internal class SystemMetricCollector + { + private SystemMetricCollector() + { + } + + private static SystemMetricCollector _instance; + + public static SystemMetricCollector GetInstance() + { + if (_instance == null) + { + _instance = new SystemMetricCollector(); + } + + return _instance; + } + + [MetricType(MetricType.Gauge)] + public int CurrentManagedThreadId => Environment.CurrentManagedThreadId; + + [MetricType(MetricType.Gauge)] + public int ExitCode => Environment.ExitCode; + + [MetricType(MetricType.Gauge)] + public int ProcessId => Environment.ProcessId; + + [MetricType(MetricType.Gauge)] + public int ProcessorCount => Environment.ProcessorCount; + + [MetricType(MetricType.Gauge)] + public int SystemPageSize => Environment.SystemPageSize; + + [MetricType(MetricType.Gauge)] + public int TickCount => Environment.TickCount; + + [MetricType(MetricType.Gauge)] + public long TickCount64 => Environment.TickCount64; + + [MetricType(MetricType.Gauge)] + public long WorkingSet => Environment.WorkingSet; + + [MetricType(MetricType.Gauge)] + public int GcMaxGeneration => GC.MaxGeneration; + } +} diff --git a/skyscraper8/Skyscraper/Webserver/NotFoundFallbackResource.cs b/skyscraper8/Skyscraper/Webserver/NotFoundFallbackResource.cs new file mode 100644 index 0000000..79d4a74 --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/NotFoundFallbackResource.cs @@ -0,0 +1,24 @@ +using System; +using System.Net; +using System.Text; + +namespace skyscraper5.Skyscraper.Webserver +{ + internal class NotFoundFallbackResource : ISkyscraperWebserverResource + { + public bool CanHandle(HttpListenerRequest request) + { + return true; + } + + + public void HandleRequest(HttpListenerContext context) + { + context.Response.StatusCode = 404; + context.Response.ContentType = "text/plain"; + + byte[] response = Encoding.UTF8.GetBytes(String.Format("Don't know what {0} {1} means.", context.Request.HttpMethod, context.Request.RawUrl)); + context.Response.Close(response, true); + } + } +} diff --git a/skyscraper8/Skyscraper/Webserver/SkyscraperWebserver.cs b/skyscraper8/Skyscraper/Webserver/SkyscraperWebserver.cs new file mode 100644 index 0000000..b663f76 --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/SkyscraperWebserver.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using skyscraper5.Skyscraper.Webserver.Metrics; + +namespace skyscraper5.Skyscraper.Webserver +{ + internal class SkyscraperWebserver + { + private SkyscraperWebserver() + { + resources = new List(); + resources.Add(MetricsResource.GetInstance()); + } + + private static SkyscraperWebserver _instance; + + public static SkyscraperWebserver GetInstance() + { + if (_instance == null) + { + _instance = new SkyscraperWebserver(); + } + return _instance; + } + + private bool _isListening; + private HttpListener listener; + private Thread listenerThread; + private List resources; + private NotFoundFallbackResource notFoundFallback; + + public void Start() + { + if (_isListening) + { + throw new AlreadyListeningException(); + } + + _isListening = true; + if (listener == null) + { + listener = new HttpListener(); + //netsh http add urlacl url="http://+:7597/" user=Jeder + listener.Prefixes.Add("http://+:7597/"); + } + + listenerThread = new Thread(ListenerThreadFunction); + listenerThread.Priority = ThreadPriority.Lowest; + listenerThread.Name = "Webserver"; + listenerThread.Start(); + } + + private void ListenerThreadFunction() + { + listener.Start(); + while (_isListening) + { + HttpListenerContext listenerContext = listener.GetContext(); + Thread clientThread = new Thread(ClientThreadFunction); + clientThread.Priority = ThreadPriority.Lowest; + clientThread.Name = String.Format("HTTP Request for {0}", listenerContext.Request.RemoteEndPoint); + clientThread.Start(listenerContext); + } + } + + private void ClientThreadFunction(object packedListenerContext) + { + HttpListenerContext listenerContext = packedListenerContext as HttpListenerContext; + if (listenerContext == null) + return; + + foreach (var skyscraperWebserverResource in resources) + { + if (skyscraperWebserverResource.CanHandle(listenerContext.Request)) + { + skyscraperWebserverResource.HandleRequest(listenerContext); + listenerContext.Response.Close(); + return; + } + } + + if (notFoundFallback == null) + notFoundFallback = new NotFoundFallbackResource(); + + notFoundFallback.HandleRequest(listenerContext); + } + + public bool IsListening + { + get => _isListening; + } + + internal void RegisterResource(ISkyscraperWebserverResource resource) + { + if (resources.Contains(resource)) + return; + + resources.Add(resource); + } + } +} diff --git a/skyscraper8/Skyscraper/Webserver/SkyscraperWebserverException.cs b/skyscraper8/Skyscraper/Webserver/SkyscraperWebserverException.cs new file mode 100644 index 0000000..7e809e3 --- /dev/null +++ b/skyscraper8/Skyscraper/Webserver/SkyscraperWebserverException.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Webserver +{ + [Serializable] + public class SkyscraperWebserverException : Exception + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public SkyscraperWebserverException() + { + } + + public SkyscraperWebserverException(string message) : base(message) + { + } + + public SkyscraperWebserverException(string message, Exception inner) : base(message, inner) + { + } + + protected SkyscraperWebserverException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/SkyscraperException.cs b/skyscraper8/SkyscraperException.cs new file mode 100644 index 0000000..6562180 --- /dev/null +++ b/skyscraper8/SkyscraperException.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5 +{ + [Serializable] + public class SkyscraperException : Exception + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public SkyscraperException() + { + } + + public SkyscraperException(string message) : base(message) + { + } + + public SkyscraperException(string message, Exception inner) : base(message, inner) + { + } + + protected SkyscraperException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/Sophtainer/IDisposer.cs b/skyscraper8/Sophtainer/IDisposer.cs new file mode 100644 index 0000000..1901092 --- /dev/null +++ b/skyscraper8/Sophtainer/IDisposer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Sophtainer +{ + public interface IDisposer + { + bool IsDisposed { get; } + } +} diff --git a/skyscraper8/Sophtainer/OffsetStream.cs b/skyscraper8/Sophtainer/OffsetStream.cs new file mode 100644 index 0000000..f7b2fad --- /dev/null +++ b/skyscraper8/Sophtainer/OffsetStream.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Sophtainer +{ + internal class OffsetStream : Stream + { + private readonly Stream wrapped; + private readonly long offset; + private readonly IDisposer disposer; + + public OffsetStream(Stream wrapped, long offset, IDisposer sophtainer) + { + this.wrapped = wrapped; + this.offset = offset; + this.disposer = sophtainer; + } + + public override bool CanRead => wrapped.CanRead; + + public override bool CanSeek => wrapped.CanSeek; + + public override bool CanWrite => wrapped.CanWrite; + + public override long Length => wrapped.Length - offset; + + public override long Position + { + get => wrapped.Position - offset; + set => wrapped.Position = value + offset; + } + + public override void Flush() + { + if (disposer.IsDisposed) + { + throw new ObjectDisposedException(disposer.ToString()); + } + + wrapped.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (disposer.IsDisposed) + { + throw new ObjectDisposedException(disposer.ToString()); + } + + return wrapped.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (disposer.IsDisposed) + { + throw new ObjectDisposedException(disposer.ToString()); + } + + switch (origin) + { + case SeekOrigin.Begin: + wrapped.Seek(this.offset + offset, SeekOrigin.Begin); + break; + case SeekOrigin.Current: + wrapped.Seek(offset, SeekOrigin.Current); + break; + case SeekOrigin.End: + wrapped.Seek(wrapped.Length - offset, SeekOrigin.End); + break; + default: + throw new NotImplementedException(origin.ToString()); + } + return Position; + } + + public override void SetLength(long value) + { + if (disposer.IsDisposed) + { + throw new ObjectDisposedException(disposer.ToString()); + } + + wrapped.SetLength(value + offset); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (disposer.IsDisposed) + { + throw new ObjectDisposedException(disposer.ToString()); + } + + wrapped.Write(buffer, offset, count); + } + } +} diff --git a/skyscraper8/Sophtainer/Sophtainer.cs b/skyscraper8/Sophtainer/Sophtainer.cs new file mode 100644 index 0000000..cda3a71 --- /dev/null +++ b/skyscraper8/Sophtainer/Sophtainer.cs @@ -0,0 +1,192 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.AccessControl; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Sophtainer +{ + internal class Sophtainer : IDisposable, IDisposer + { + private Sophtainer(Stream backingStream) + { + this.backingStream = backingStream; + } + private const uint SOPH = 1213222739; + private const byte METADATA_DATE = 1; + private const byte METADATA_USER_NAME = 2; + private const byte METADATA_MACHINE_NAME = 3; + private const byte METADATA_UUID = 4; + private const byte METADATA_APPLICATION = 5; + private const byte METADATA_APPLICATION_VERSION = 6; + private const byte METADATA_FINAL = 255; + private Stream backingStream; + + public DateTime CreationDate { get; private set; } + public string CreationUserName { get; private set; } + public Guid GUID { get; private set; } + public string CreationMachineName { get; private set; } + public uint? Application { get; private set; } + public Version ApplicationVersion { get; private set; } + private long StreamOffset { get; set; } + + public bool IsDisposed => disposed; + + public static Sophtainer CreateSophtainer(Stream stream, uint? application = null, Version appVersion = null) + { + Sophtainer sophtainer = new Sophtainer(stream); + stream.WriteUInt8(0xDC); + stream.WriteUInt8(0xDF); + stream.WriteUInt32LE(SOPH); + stream.WriteUInt16LE(1); + + sophtainer.CreationDate = DateTime.Now; + stream.WriteUInt8(METADATA_DATE); + stream.WriteInt64LE(sophtainer.CreationDate.Ticks); + + sophtainer.CreationUserName = Environment.UserName; + stream.WriteUInt8(METADATA_USER_NAME); + WriteSophtainerString(stream, sophtainer.CreationUserName); + + sophtainer.CreationMachineName = Environment.MachineName; + stream.WriteUInt8(METADATA_MACHINE_NAME); + WriteSophtainerString(stream, sophtainer.CreationMachineName); + + sophtainer.GUID = Guid.NewGuid(); + stream.WriteUInt8(METADATA_UUID); + stream.Write(sophtainer.GUID.ToByteArray(), 0, 16); + + sophtainer.Application = application; + if (sophtainer.Application.HasValue) + { + stream.WriteUInt8(METADATA_APPLICATION); + stream.WriteUInt32LE(sophtainer.Application.Value); + } + + sophtainer.ApplicationVersion = appVersion; + if (sophtainer.ApplicationVersion != null) + { + stream.WriteUInt8(METADATA_APPLICATION_VERSION); + stream.WriteInt32LE(sophtainer.ApplicationVersion.Major); + stream.WriteInt32LE(sophtainer.ApplicationVersion.Minor); + stream.WriteInt32LE(sophtainer.ApplicationVersion.Build); + stream.WriteInt32LE(sophtainer.ApplicationVersion.Revision); + } + + stream.WriteUInt8(METADATA_FINAL); + long currentPosition = stream.Position; + int paddingSize = (int)(stream.Position / 32); + paddingSize++; + paddingSize *= 32; + paddingSize = (int)(paddingSize - currentPosition); + if (paddingSize != 0) + { + byte[] paddingBuffer = new byte[paddingSize]; + stream.Write(paddingBuffer, 0, paddingSize); + } + stream.Flush(); + sophtainer.StreamOffset = stream.Position; + return sophtainer; + } + + public static Sophtainer LoadSophtainer(Stream stream) + { + Sophtainer sophtainer = new Sophtainer(stream); + if (stream.ReadUInt8() != 0xDC) + throw new SophtainerException(SophtainerExceptionKind.InvalidMagic); + if (stream.ReadUInt8() != 0xDF) + throw new SophtainerException(SophtainerExceptionKind.InvalidMagic); + if (stream.ReadUInt32LE() != SOPH) + throw new SophtainerException(SophtainerExceptionKind.InvalidMagic); + if (stream.ReadUInt16LE() != 1) + throw new SophtainerException(SophtainerExceptionKind.WrongEndian); + + bool metadataParseComplete = false; + while (!metadataParseComplete) + { + byte metadataOpcode = stream.ReadUInt8(); + switch(metadataOpcode) + { + case METADATA_DATE: + long ticks = stream.ReadInt64LE(); + sophtainer.CreationDate = new DateTime(ticks); + break; + case METADATA_USER_NAME: + sophtainer.CreationUserName = ReadSophtainerString(stream); + break; + case METADATA_MACHINE_NAME: + sophtainer.CreationMachineName = ReadSophtainerString(stream); + break; + case METADATA_UUID: + sophtainer.GUID = new Guid(stream.ReadBytes(16)); + break; + case METADATA_APPLICATION: + sophtainer.Application = stream.ReadUInt32LE(); + break; + case METADATA_APPLICATION_VERSION: + int major = stream.ReadInt32LE(); + int minor = stream.ReadInt32LE(); + int build = stream.ReadInt32LE(); + int revision = stream.ReadInt32LE(); + sophtainer.ApplicationVersion = new Version(major, minor, build, revision); + break; + case METADATA_FINAL: + long currentPosition = stream.Position; + int paddingSize = (int)(stream.Position / 32); + paddingSize++; + paddingSize *= 32; + paddingSize = (int)(paddingSize - currentPosition); + stream.Position += paddingSize; + sophtainer.StreamOffset = stream.Position; + metadataParseComplete = true; + break; + default: + throw new SophtainerException(SophtainerExceptionKind.UnknownMetadataOpcode, new NotImplementedException(metadataOpcode.ToString())); + } + } + + return sophtainer; + } + + private static void WriteSophtainerString(Stream stream, string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + stream.WriteVarInt(bytes.Length); + stream.Write(bytes, 0, bytes.Length); + } + + private static string ReadSophtainerString(Stream stream) + { + long length = stream.ReadVarInt(); + byte[] buffer = stream.ReadBytes(length); + return Encoding.UTF8.GetString(buffer); + } + + private OffsetStream data; + internal OffsetStream GetStream() + { + if (disposed) + throw new ObjectDisposedException(this.ToString()); + + if (data == null) + { + data = new OffsetStream(backingStream, StreamOffset, this); + } + return data; + } + + private bool disposed; + public void Dispose() + { + if (disposed) + return; + + backingStream.Dispose(); + disposed = true; + } + } +} diff --git a/skyscraper8/Sophtainer/SophtainerException.cs b/skyscraper8/Sophtainer/SophtainerException.cs new file mode 100644 index 0000000..9c8e315 --- /dev/null +++ b/skyscraper8/Sophtainer/SophtainerException.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Sophtainer +{ + + [Serializable] + public class SophtainerException : Exception + { + public SophtainerException() { } + public SophtainerException(SophtainerExceptionKind message) : base(message.ToString()) { } + public SophtainerException(SophtainerExceptionKind message, Exception inner) : base(message.ToString(), inner) { } + protected SophtainerException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + public enum SophtainerExceptionKind + { + InvalidMagic, + UnknownMetadataOpcode, + WrongEndian + } +} diff --git a/skyscraper8/Sophtainer/SplitFileStream.cs b/skyscraper8/Sophtainer/SplitFileStream.cs new file mode 100644 index 0000000..a0c395d --- /dev/null +++ b/skyscraper8/Sophtainer/SplitFileStream.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + + +namespace skyscraper5.src.Sophtainer +{ + public delegate void FileStreamPartSwitchDelegate(int readSoFar, int currentOffset, int remaining); + + internal class SplitFileStream : Stream + { + private string baseFileName; + private uint partSize; + private int currentPart; + private FileStream currentPartStream; + public event FileStreamPartSwitchDelegate PartChangedDuringRead; + public SplitFileStream(string basename, uint partSize = 4294967295) + { + this.baseFileName = basename; + this.partSize = partSize; + this.currentPart = -1; + AcquireCurrentPart(); + } + + private int CountParts() + { + int result = 0; + while (true) + { + string filename = baseFileName + "." + (66000 + result++); + if (!File.Exists(filename)) + { + break; + } + } + return result - 1; + } + + private bool AcquireCurrentPart() + { + long requiredPart = Position; + requiredPart /= partSize; + if (currentPartStream != null) + currentPartStream.Position = internalPosition % partSize; + if (requiredPart == currentPart) + return false; + + if (currentPartStream != null) + { + currentPartStream.Flush(); + currentPartStream.Close(); + currentPartStream.Dispose(); + } + + string filename = baseFileName + "." + (66000 + requiredPart).ToString(); + currentPartStream = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); + currentPartStream.Position = internalPosition % partSize; + int oldPart = currentPart; + currentPart = (int)requiredPart; + return true; + } + + public override bool CanRead + { + get + { + AcquireCurrentPart(); + return currentPartStream.CanRead; + } + } + + public override bool CanSeek + { + get + { + AcquireCurrentPart(); + return currentPartStream.CanSeek; + } + } + + public override bool CanWrite + { + get + { + AcquireCurrentPart(); + return currentPartStream.CanWrite; + } + } + + public override long Length + { + get + { + int totalParts = CountParts(); + if (totalParts == 1) + { + AcquireCurrentPart(); + return currentPartStream.Length; + } + + if (totalParts == currentPart) + { + long result = 0; + for (int i = 0; i < totalParts - 1;i++) + { + string filename = baseFileName + (66000 + i); + FileInfo currentFilePart = new FileInfo(filename); + result += currentFilePart.Length; + } + result += currentPartStream.Length; + return result; + } + + long result2 = 0; + for (int i = 0; i < totalParts; i++) + { + string filename2 = baseFileName + "." + (66000 + i); + FileInfo currentFilePart = new FileInfo(filename2); + result2 += currentFilePart.Length; + } + return result2; + } + } + + private long internalPosition; + public override long Position + { + get + { + return internalPosition; + } + set + { + if (value == 0) + { + if (currentPartStream != null) + { + currentPartStream.Flush(); + currentPartStream.Close(); + } + currentPartStream = null; + currentPart = -1; + internalPosition = 0; + return; + } + + long maxValue = Length; + if (value > Length) + { + throw new ArgumentOutOfRangeException(); + } + internalPosition = value; + } + } + + public override void Flush() + { + if (currentPartStream != null) + { + currentPartStream.Flush(); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + int overallResult = 0; + while (count > 0) + { + if (AcquireCurrentPart()) + { + if (overallResult > 0) + { + if (PartChangedDuringRead != null) + { + PartChangedDuringRead(overallResult, offset, count); + } + } + } + long currentPartOffset = currentPartStream.Position; + long currentPartRemaining = partSize - currentPartOffset; + long readAmount = Math.Min(currentPartRemaining, count); + int result = currentPartStream.Read(buffer, offset, (int)readAmount); + if (result == 0) + break; + count -= result; + offset += result; + overallResult += result; + internalPosition += result; + } + return overallResult; + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch(origin) + { + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + case SeekOrigin.End: + long maxPos = Length; + Position = maxPos + offset; + break; + default: + throw new NotImplementedException(origin.ToString()); + } + return Position; + } + + public override void SetLength(long value) + { + long currentLength = Length; + if (value > currentLength) + { + Seek(currentLength, SeekOrigin.Begin); + long neededToWrite = value - currentLength; + byte[] blankBuffer = new byte[4096]; + while (neededToWrite > 0) + { + long partSize = Math.Min(blankBuffer.Length, neededToWrite); + Write(blankBuffer, 0, (int)partSize); + neededToWrite -= partSize; + } + } + else + { + throw new NotSupportedException("shrink"); + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + while (count > 0) + { + AcquireCurrentPart(); + long currentPartOffset = currentPartStream.Position; + long currentPartRemaining = partSize - currentPartOffset; + long operationSize = Math.Min(count, currentPartRemaining); + currentPartStream.Write(buffer, offset, (int)operationSize); + offset += (int)operationSize; + count -= (int)operationSize; + internalPosition += operationSize; + } + } + } +} diff --git a/skyscraper8/T2MI/Baseband/BasebandStreamType.cs b/skyscraper8/T2MI/Baseband/BasebandStreamType.cs new file mode 100644 index 0000000..53f26e8 --- /dev/null +++ b/skyscraper8/T2MI/Baseband/BasebandStreamType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Baseband +{ + internal enum T2BasebandStreamType + { + GFPS = 0, + GCS = 1, + GSE = 2, + TS = 3 + } +} diff --git a/skyscraper8/T2MI/Packets/0x00_BasebandFrame.cs b/skyscraper8/T2MI/Packets/0x00_BasebandFrame.cs new file mode 100644 index 0000000..31509a6 --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x00_BasebandFrame.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x00)] + internal class _0x00_BasebandFrame : T2MiPacket + { + public _0x00_BasebandFrame(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 3) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + FrameIndex = ms.ReadUInt8(); + PlpId = ms.ReadUInt8(); // t2packet[7] + + byte readUInt8 = ms.ReadUInt8(); //t2packet[8] + InterleavingFrameStart = (readUInt8 & 0x80) != 0; + RFU = (readUInt8 & 0x7f); + + BasebandFrame = ms.ReadBytes(ms.GetAvailableBytes()); + Valid = true; + } + + public byte[] BasebandFrame { get; private set; } + + public int RFU { get; private set; } + + public bool InterleavingFrameStart { get; private set; } + + public byte PlpId { get; private set; } + + public byte FrameIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x01_IqData.cs b/skyscraper8/T2MI/Packets/0x01_IqData.cs new file mode 100644 index 0000000..87e2acf --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x01_IqData.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x01)] + public class _0x01_IqData : T2MiPacket + { + public _0x01_IqData(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 3) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + FrameIndex = ms.ReadUInt8(); + + ushort readUInt16Be = ms.ReadUInt16BE(); + AuxId = readUInt16Be & 0xf000 >> 12; + RFU = readUInt16Be & 0x0fff; + + long numSamples = ms.GetAvailableBytes(); + numSamples *= 8; + numSamples /= 24; + IQ = new Tuple[numSamples]; + + for (long sample = 0; sample < numSamples; sample++) + { + byte bq1 = ms.ReadUInt8(); + byte bq2 = ms.ReadUInt8(); + byte bq3 = ms.ReadUInt8(); + + int i = bq1; + i <<= 4; + i += ((bq2 & 0xf0) >> 4); + if ((i & 0x00000800) != 0) + i = -1 & i; + + int q = bq2 & 0x0f; + q <<= 8; + q += bq3; + if ((i & 0x00000800) != 0) + q = -1 & i; + + IQ[sample] = new Tuple(i, q); + } + + Valid = true; + } + + public Tuple[] IQ { get; private set; } + + public int RFU { get; private set; } + + public int AuxId { get; private set; } + + public byte FrameIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x02_ArbitraryCellInsertion.cs b/skyscraper8/T2MI/Packets/0x02_ArbitraryCellInsertion.cs new file mode 100644 index 0000000..f4b30ad --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x02_ArbitraryCellInsertion.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x02)] + public class _0x02_ArbitraryCellInsertion : T2MiPacket + { + public _0x02_ArbitraryCellInsertion(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 8) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, true); + FrameIndex = ms.ReadUInt8(); + TxIdentifier = ms.ReadUInt16BE(); + ushort rfu = ms.ReadUInt16BE(); + StartCellAddress = (ms.ReadUInt8() & 0x3f); + StartCellAddress <<= 8; + StartCellAddress += ms.ReadUInt8(); + StartCellAddress <<= 8; + StartCellAddress += ms.ReadUInt8(); + StartCellAddress <<= 8; + + long numSamples = ms.GetAvailableBytes(); + numSamples *= 8; + numSamples /= 24; + IQ = new Tuple[numSamples]; + + for (long sample = 0; sample < numSamples; sample++) + { + byte bq1 = ms.ReadUInt8(); + byte bq2 = ms.ReadUInt8(); + byte bq3 = ms.ReadUInt8(); + + int i = bq1; + i <<= 4; + i += ((bq2 & 0xf0) >> 4); + if ((i & 0x00000800) != 0) + i = -1 & i; + + int q = bq2 & 0x0f; + q <<= 8; + q += bq3; + if ((i & 0x00000800) != 0) + q = -1 & i; + + IQ[sample] = new Tuple(i, q); + } + + Valid = true; + } + + public Tuple[] IQ { get; private set; } + + public int StartCellAddress { get; private set; } + + public ushort TxIdentifier { get; private set; } + + public byte FrameIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x10_L1Current.cs b/skyscraper8/T2MI/Packets/0x10_L1Current.cs new file mode 100644 index 0000000..828c58f --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x10_L1Current.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x10)] + public class _0x10_L1Current : T2MiPacket + { + public _0x10_L1Current(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 2) + { + Valid = false; + return; + } + FrameIndex = buffer[0]; + FrequencySource = (buffer[1] & 0xc0) >> 6; + L1CurrentData = new L1CurrentDataFields(buffer); + Valid = L1CurrentData.Valid; + } + + public L1CurrentDataFields L1CurrentData { get; } + public int FrequencySource { get; private set; } + + public byte FrameIndex { get; private set; } + + public class L1CurrentDataFields : Validatable + { + public L1CurrentDataFields(byte[] buffer) + { + if (buffer.Length < 23) + { + Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, 2, buffer.Length - 2, false); + L1PreSignallingBits = ms.ReadBytes(21); + if (ms.GetAvailableBytes() < 2) + { + Valid = false; + return; + } + ushort l1conflen = ms.ReadUInt16BE(); + + int l1conflenRounded = Align(l1conflen); + if (l1conflenRounded > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + + L1ConfigurablePostSignalling = ms.ReadBytes(l1conflenRounded); + ushort l1dynCurrLen = ms.ReadUInt16BE(); + + int l1dynCurrLenRounded = Align(l1dynCurrLen); + if (l1dynCurrLenRounded > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + + L1DynamicCurrentFrame = ms.ReadBytes(l1dynCurrLenRounded); + ushort l1extLen = ms.ReadUInt16BE(); + int alignedLen = Align(l1extLen); + if (alignedLen > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + L1Extension = ms.ReadBytes(alignedLen); + Valid = true; + } + + private static int Align(ushort len) + { + if (len % 8 == 0) + return len / 8; + else + { + len /= 8; + len++; + return len; + } + } + + public byte[] L1Extension { get; private set; } + + public byte[] L1DynamicCurrentFrame { get; private set; } + + public byte[] L1ConfigurablePostSignalling { get; private set; } + + public byte[] L1PreSignallingBits { get; private set; } + } + } +} diff --git a/skyscraper8/T2MI/Packets/0x11_L1Future.cs b/skyscraper8/T2MI/Packets/0x11_L1Future.cs new file mode 100644 index 0000000..f909f2c --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x11_L1Future.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x11)] + public class _0x11_L1Future : T2MiPacket + { + public _0x11_L1Future(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length == 0 || buffer.Length == 1) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + FrameIndex = ms.ReadUInt8(); + ms.ReadUInt8(); //RFU + FutureData = new FutureDataFields(ms); + Valid = FutureData.Valid; + } + + public byte FrameIndex { get; private set; } + + public FutureDataFields FutureData { get; private set; } + + public class FutureDataFields : Validatable + { + public FutureDataFields(MemoryStream ms) + { + if (2 > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + ushort l1dynNextLen = ms.ReadUInt16BE(); + if (l1dynNextLen > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + L1DynNext = ms.ReadBytes(8 * (l1dynNextLen / 8)); + + ushort l1dynNext2Len = ms.ReadUInt16BE(); + if (l1dynNext2Len > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + L1DynNext2 = ms.ReadBytes(8 * (l1dynNext2Len / 8)); + + byte numInband = ms.ReadUInt8(); + for (int i = 0; i < numInband; i++) + { + byte plpId = ms.ReadUInt8(); + ushort inbandLen = ms.ReadUInt16BE(); + if (inbandLen > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + if (InBands == null) + InBands = new byte[255][]; + InBands[plpId] = ms.ReadBytes(8 * (inbandLen / 8)); + } + Valid = true; + } + + public byte[] L1DynNext2 { get; private set; } + + public byte[] L1DynNext { get; private set; } + + public byte[][] InBands { get; private set; } + } + } +} diff --git a/skyscraper8/T2MI/Packets/0x12_P2BiasBalancingCells.cs b/skyscraper8/T2MI/Packets/0x12_P2BiasBalancingCells.cs new file mode 100644 index 0000000..7dce3a3 --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x12_P2BiasBalancingCells.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x12)] + internal class _0x12_P2BiasBalancingCells : T2MiPacket + { + public _0x12_P2BiasBalancingCells(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length != 5) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + FrameIndex = ms.ReadUInt8(); + NumActiveBiasCellsPerP2 = (ms.ReadUInt32BE() & 0x00007fff); + Valid = true; + } + + public uint NumActiveBiasCellsPerP2 { get; private set; } + + public byte FrameIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x20_DvbT2Timestamp.cs b/skyscraper8/T2MI/Packets/0x20_DvbT2Timestamp.cs new file mode 100644 index 0000000..5e5e04b --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x20_DvbT2Timestamp.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x20)] + public class _0x20_DvbT2Timestamp : T2MiPacket + { + public _0x20_DvbT2Timestamp(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 10) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + Bandwidth = (readUInt8 & 0x0f); + + SecondsSince2000 = ms.ReadUInt32BE(); + SecondsSince2000 <<= 8; + SecondsSince2000 += ms.ReadUInt8(); + + uint readUInt32Be = ms.ReadUInt32BE(); + Subseconds = (readUInt32Be & 0xffffffe0); + SecondsSince2000 >>= 13; + + UTCoffset = (readUInt32Be & 0x0000001f); + UTCoffset <<= 8; + UTCoffset += ms.ReadUInt8(); + Valid = true; + } + + public uint UTCoffset { get; private set; } + + public long Subseconds { get; private set; } + + public ulong SecondsSince2000 { get; private set; } + + public int Bandwidth { get; private set; } + + public DateTime ResolveTime() + { + DateTime dt = new DateTime(2000, 1, 1); + + ulong timeLeft = SecondsSince2000; + while (timeLeft > 0) + { + ushort stepUp = (ushort)Math.Min(uint.MaxValue, timeLeft); + if (stepUp == 0) + stepUp = 1; + dt = dt.AddSeconds(stepUp); + timeLeft -= stepUp; + } + + return dt; + } + } +} diff --git a/skyscraper8/T2MI/Packets/0x21_IndividualAddressing.cs b/skyscraper8/T2MI/Packets/0x21_IndividualAddressing.cs new file mode 100644 index 0000000..472938b --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x21_IndividualAddressing.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.T2MI.Packets.AdressingFunctions; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x21)] + public class _0x21_IndividualAddressing : T2MiPacket + { + public _0x21_IndividualAddressing(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 2) + { + Valid = false; + return; + } + bool validated = false; + + MemoryStream ms = new MemoryStream(buffer, true); + RFU = ms.ReadUInt8(); + byte individualAddressingLength = ms.ReadUInt8(); + if (individualAddressingLength > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + + ms = new MemoryStream(ms.ReadBytes(individualAddressingLength), true); + while (ms.GetAvailableBytes() > 3) + { + ushort txIdentifier = ms.ReadUInt16BE(); + byte functionLoopLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < functionLoopLength) + { + Valid = false; + return; + } + MemoryStream ms2 = new MemoryStream(ms.ReadBytes(functionLoopLength), true); + while (ms2.GetAvailableBytes() > 2) + { + AddressingFunction function = DecodeAddressingFunction(ms2); + if (function == null) + { + Valid = false; + return; + } + + if (Commands == null) + Commands = new List(); + + function.TxIdentifier = txIdentifier; + Commands.Add(function); + Valid = true; + validated = true; + } + } + + if (!validated) + Valid = false; + } + + public List Commands { get; set; } + + public byte RFU { get; private set; } + + private AddressingFunction DecodeAddressingFunction(MemoryStream ms) + { + byte functionTag = ms.ReadUInt8(); + byte functionLength = ms.ReadUInt8(); + functionLength -= 2; + if (functionLength > ms.GetAvailableBytes()) + return null; + byte[] functionBody = ms.ReadBytes(functionLength); + + AddressingFunction result = null; + switch (functionTag) + { + case 0x00: result = new _0x00_TransmitterTimeOffset(functionBody); break; + case 0x01: throw new NotImplementedException("Transmitter frequency offset"); + case 0x02: result = new _0x02_TransmitterPower(functionBody); break; + case 0x03: throw new NotImplementedException("Private data"); + case 0x04: throw new NotImplementedException("Cell ID"); + case 0x05: throw new NotImplementedException("Enable"); + case 0x06: throw new NotImplementedException("Bandwidth"); + case 0x10: throw new NotImplementedException("ACE-PAPR"); + case 0x11: result = new _0x11_TransmitterMisoGroup(functionBody); break; + case 0x12: throw new NotImplementedException("TR-PAPR"); + case 0x13: result = new _0x13_L1AcePapr(functionBody); break; + case 0x15: throw new NotImplementedException("TX-SIG FEF: Sequence Numbers"); + case 0x16: throw new NotImplementedException("TX-SIG Aux stream: Transmitter ID"); + case 0x17: throw new NotImplementedException("Frequency"); + } + + if (result == null) + return null; + + if (!result.Valid) + result = null; + + return result; + } + } +} diff --git a/skyscraper8/T2MI/Packets/0x30_Null.cs b/skyscraper8/T2MI/Packets/0x30_Null.cs new file mode 100644 index 0000000..eff2bf0 --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x30_Null.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x30)] + internal class _0x30_Null : T2MiPacket + { + public _0x30_Null(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length != 3) + { + Valid = false; + return; + } + + FefIndex = buffer[0]; + S1Field = (buffer[2] & 0x70) >> 3; + S2Field = (buffer[2] & 0x0f); + Valid = true; + } + + public int S2Field { get; private set; } + + public int S1Field { get; private set; } + + public byte FefIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x31_IqData.cs b/skyscraper8/T2MI/Packets/0x31_IqData.cs new file mode 100644 index 0000000..996acb9 --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x31_IqData.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x31)] + public class _0x31_IqData : T2MiPacket + { + public _0x31_IqData(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 3) + { + Valid = false; + return; + } + + MemoryStream ms = new MemoryStream(buffer, false); + FefIndex = ms.ReadUInt8(); + var readUInt16Be = ms.ReadUInt16BE(); + RFU = (readUInt16Be & 0xff80) >> 7; + S1Field = (readUInt16Be & 0x0070) >> 4; + S2Field = (readUInt16Be & 0x000f); + + long numSamples = ms.GetAvailableBytes(); + numSamples *= 8; + numSamples /= 24; + IQ = new Tuple[numSamples]; + + for (long sample = 0; sample < numSamples; sample++) + { + byte bq1 = ms.ReadUInt8(); + byte bq2 = ms.ReadUInt8(); + byte bq3 = ms.ReadUInt8(); + + int i = bq1; + i <<= 4; + i += ((bq2 & 0xf0) >> 4); + if ((i & 0x00000800) != 0) + i = -1 & i; + + int q = bq2 & 0x0f; + q <<= 8; + q += bq3; + if ((q & 0x00000800) != 0) + q = -1 & i; + + IQ[sample] = new Tuple(i, q); + } + + Valid = numSamples >= 1; + } + + public int S2Field { get; private set; } + + public int S1Field { get; private set; } + + public int RFU { get; private set; } + + public byte FefIndex { get; private set; } + + public Tuple[] IQ { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x32_Composite.cs b/skyscraper8/T2MI/Packets/0x32_Composite.cs new file mode 100644 index 0000000..efbdb23 --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x32_Composite.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.Plugins; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x32)] + internal class _0x32_Composite : T2MiPacket + { + public _0x32_Composite(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length != 8) + { + Valid = false; + return; + } + FefIndex = buffer[0]; + S1Field = (buffer[1] & 0x70) >> 4; + S2Field = (buffer[1] & 0x0f); + + (buffer[6], buffer[7]) = (buffer[7], buffer[6]); + NumSubparts = BitConverter.ToUInt16(buffer, 6); + Valid = true; + } + + public ushort NumSubparts { get; set; } + + public int S2Field { get; set; } + + public int S1Field { get; private set; } + + public byte FefIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/0x33_Subpart.cs b/skyscraper8/T2MI/Packets/0x33_Subpart.cs new file mode 100644 index 0000000..c338db0 --- /dev/null +++ b/skyscraper8/T2MI/Packets/0x33_Subpart.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.T2MI.Packets.Subparts; + +namespace skyscraper5.T2MI.Packets +{ + [SkyscraperPlugin] + [T2MiPacketType(0x33)] + internal class _0x33_Subpart : T2MiPacket + { + public _0x33_Subpart(T2MIHeader header, byte[] buffer) : base(header, buffer) + { + if (buffer.Length < 15) + { + Valid = false; + return; + } + MemoryStream ms = new MemoryStream(buffer, false); + FefIndex = ms.ReadUInt8(); + TxIdentifier = ms.ReadUInt16BE(); + uint rfu1 = ms.ReadUInt32BE(); + SubpartIndex = ms.ReadUInt16BE(); + SubpartVariety = ms.ReadUInt16BE(); + uint readUInt32Be = ms.ReadUInt32BE(); + uint subpartLength = (readUInt32Be & 0x003fffff); + if (subpartLength > ms.GetAvailableBytes()) + { + Valid = false; + return; + } + + SubpartVariety = ms.ReadUInt16BE(); + subpartLength -= 2; + switch (SubpartVariety) + { + case 0: + this.Subpart = new SubpartNull(); + break; + case 1: + this.Subpart = new SubpartIq(ms.ReadBytes(subpartLength)); + break; + case 2: + this.Subpart = new SubpartPrbs(ms.ReadBytes(subpartLength)); + break; + case 3: + this.Subpart = new SubpartTxSigFef(); + break; + default: + if (SubpartVariety > 0x0003) + { + PreferDiscard = true; + break; + } + throw new NotImplementedException(String.Format("T2-Mi Subpart {0:X4}", SubpartVariety)); + } + + Valid = true; + } + + public bool PreferDiscard { get; private set; } + public Subpart Subpart { get; private set; } + + public ushort SubpartVariety { get; private set; } + + public ushort SubpartIndex { get; private set; } + + public ushort TxIdentifier { get; private set; } + + public byte FefIndex { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/AdressingFunctions/0x00_TransmitterTimeOffset.cs b/skyscraper8/T2MI/Packets/AdressingFunctions/0x00_TransmitterTimeOffset.cs new file mode 100644 index 0000000..1b22e43 --- /dev/null +++ b/skyscraper8/T2MI/Packets/AdressingFunctions/0x00_TransmitterTimeOffset.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.AdressingFunctions +{ + internal class _0x00_TransmitterTimeOffset : AddressingFunction + { + public _0x00_TransmitterTimeOffset(byte[] buffer) + : base(0x00, true, true) + { + if (buffer.Length != 2) + { + Valid = false; + return; + } + + (buffer[1], buffer[0]) = (buffer[0], buffer[1]); + TimeOffset = BitConverter.ToUInt16(buffer, 0); + Valid = true; + } + + public ushort TimeOffset { get; set; } + } +} diff --git a/skyscraper8/T2MI/Packets/AdressingFunctions/0x02_TransmitterPower.cs b/skyscraper8/T2MI/Packets/AdressingFunctions/0x02_TransmitterPower.cs new file mode 100644 index 0000000..544e124 --- /dev/null +++ b/skyscraper8/T2MI/Packets/AdressingFunctions/0x02_TransmitterPower.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.AdressingFunctions +{ + internal class _0x02_TransmitterPower : AddressingFunction + { + public _0x02_TransmitterPower(byte[] buffer) + : base(0x02, true, true) + { + if (buffer.Length != 2) + { + Valid = false; + return; + } + + (buffer[1], buffer[0]) = (buffer[0], buffer[1]); + TxPower = BitConverter.ToUInt16(buffer, 0); + Valid = true; + } + + public ushort TxPower { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/AdressingFunctions/0x11_TransmitterMisoGroup.cs b/skyscraper8/T2MI/Packets/AdressingFunctions/0x11_TransmitterMisoGroup.cs new file mode 100644 index 0000000..0d7aa65 --- /dev/null +++ b/skyscraper8/T2MI/Packets/AdressingFunctions/0x11_TransmitterMisoGroup.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.AdressingFunctions +{ + internal class _0x11_TransmitterMisoGroup : AddressingFunction + { + public _0x11_TransmitterMisoGroup(byte[] buffer) : base(0x11, false, true) + { + if (buffer.Length != 1) + { + Valid = false; + return; + } + + if ((buffer[0] & 0x80) == 0) + MisoGroup = 1; + else + MisoGroup = 2; + + Valid = true; + } + + public int MisoGroup { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/AdressingFunctions/0x13_L1AcePapr.cs b/skyscraper8/T2MI/Packets/AdressingFunctions/0x13_L1AcePapr.cs new file mode 100644 index 0000000..000eb48 --- /dev/null +++ b/skyscraper8/T2MI/Packets/AdressingFunctions/0x13_L1AcePapr.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.AdressingFunctions +{ + internal class _0x13_L1AcePapr : AddressingFunction + { + public _0x13_L1AcePapr(byte[] buffer) : base(0x13, false, true) + { + if (buffer.Length != 4) + { + Valid = false; + return; + } + + (buffer[1], buffer[0]) = (buffer[0], buffer[1]); + L1AceMaxCorrection = BitConverter.ToUInt16(buffer, 0); + Valid = true; + } + + public ushort L1AceMaxCorrection { get; set; } + } +} diff --git a/skyscraper8/T2MI/Packets/AdressingFunctions/AdressingFunction.cs b/skyscraper8/T2MI/Packets/AdressingFunctions/AdressingFunction.cs new file mode 100644 index 0000000..4bad2a0 --- /dev/null +++ b/skyscraper8/T2MI/Packets/AdressingFunctions/AdressingFunction.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper5.T2MI.Packets.AdressingFunctions +{ + public abstract class AddressingFunction : Validatable + { + public byte Tag { get; } + public bool ApplicableToT { get; } + public bool ApplicableToT2 { get; } + public ushort TxIdentifier { get; set; } + + protected AddressingFunction(byte tag, bool applicableToT, bool applicableToT2) + { + Tag = tag; + ApplicableToT = applicableToT; + ApplicableToT2 = applicableToT2; + } + } +} diff --git a/skyscraper8/T2MI/Packets/Subparts/Subpart.cs b/skyscraper8/T2MI/Packets/Subparts/Subpart.cs new file mode 100644 index 0000000..35d7edf --- /dev/null +++ b/skyscraper8/T2MI/Packets/Subparts/Subpart.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.Subparts +{ + internal abstract class Subpart + { + public abstract ushort Variety { get; } + } +} diff --git a/skyscraper8/T2MI/Packets/Subparts/SubpartIq.cs b/skyscraper8/T2MI/Packets/Subparts/SubpartIq.cs new file mode 100644 index 0000000..f5f96d3 --- /dev/null +++ b/skyscraper8/T2MI/Packets/Subparts/SubpartIq.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.T2MI.Packets.Subparts +{ + internal class SubpartIq : Subpart + { + public override ushort Variety => 1; + + public SubpartIq(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + Length = (int)(ms.Length / 3); + byte a, b, c; + RealParts = new ushort[Length]; + ImaginaryParts = new ushort[Length]; + + for (int i = 0; i < Length; i++) + { + a = ms.ReadUInt8(); + b = ms.ReadUInt8(); + c = ms.ReadUInt8(); + RealParts[i] = (ushort)a; + RealParts[i] <<= 4; + RealParts[i] += (ushort)((b & 0xf0) >> 4); + ImaginaryParts[i] = (ushort)((b & 0x0f)); + ImaginaryParts[i] <<= 8; + ImaginaryParts[i] += c; + } + } + + public int Length; + public ushort[] RealParts; + public ushort[] ImaginaryParts; + } +} diff --git a/skyscraper8/T2MI/Packets/Subparts/SubpartNull.cs b/skyscraper8/T2MI/Packets/Subparts/SubpartNull.cs new file mode 100644 index 0000000..2bd2d23 --- /dev/null +++ b/skyscraper8/T2MI/Packets/Subparts/SubpartNull.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.Subparts +{ + internal class SubpartNull : Subpart + { + public override ushort Variety => 0; + } +} diff --git a/skyscraper8/T2MI/Packets/Subparts/SubpartPrbs.cs b/skyscraper8/T2MI/Packets/Subparts/SubpartPrbs.cs new file mode 100644 index 0000000..3a726e6 --- /dev/null +++ b/skyscraper8/T2MI/Packets/Subparts/SubpartPrbs.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.Subparts +{ + internal class SubpartPrbs : Subpart + { + public override ushort Variety => 2; + + public SubpartPrbs(byte[] buffer) + { + PrbsType = buffer[0]; + } + + public byte PrbsType { get; private set; } + } +} diff --git a/skyscraper8/T2MI/Packets/Subparts/SubpartTxSigFef.cs b/skyscraper8/T2MI/Packets/Subparts/SubpartTxSigFef.cs new file mode 100644 index 0000000..80676be --- /dev/null +++ b/skyscraper8/T2MI/Packets/Subparts/SubpartTxSigFef.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI.Packets.Subparts +{ + internal class SubpartTxSigFef : Subpart + { + public override ushort Variety => 3; + } +} diff --git a/skyscraper8/T2MI/T2MIDecoder.cs b/skyscraper8/T2MI/T2MIDecoder.cs new file mode 100644 index 0000000..817ac16 --- /dev/null +++ b/skyscraper8/T2MI/T2MIDecoder.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.T2MI.Baseband; +using skyscraper5.T2MI.Packets; + +namespace skyscraper5.T2MI +{ + internal class T2MIDecoder : ITsPacketProcessor, IPayloadUnitDecoder + { + public T2MIDecoder(int pid, T2MIEventHandler t2MiEventHandler) + { + PrepareForNextPacket(); + this.t2MiEventHandler = t2MiEventHandler; + this.RelatedPid = pid; + } + + public int RelatedPid { get; private set; } + + private bool headerComplete; + private int requiredBytes; + private byte[] headerBytes; + private T2MIHeader header; + private byte[] packetBuffer; + private long inputPacketsPushed; + private long outputPacketsPushed; + private T2MIEventHandler t2MiEventHandler; + + public void PushPacket(TsPacket packet) + { + inputPacketsPushed++; + bool packetPayloadUnitStart = packet.PayloadUnitStart; + int offset = 4; + if (packet.Adaption != null) + { + offset += packet.Adaption.Length; + offset++; + } + + MemoryStream ms = new MemoryStream(packet.RawPacket, false); + ms.Position = offset; + int partsProcessed = 0; + byte offsetAccordingToPus = 0; + if (packetPayloadUnitStart) + { + offsetAccordingToPus = ms.ReadUInt8(); + } + while (ms.GetAvailableBytes() > 0) + { + PushPart(ms, packetPayloadUnitStart, partsProcessed++, offsetAccordingToPus); + } + } + + private void PushPart(MemoryStream src, bool packetPayloadUnitStart, int partsProcessed, byte offsetAccordingToPUS) + { + if (partsProcessed == 0 && !packetPayloadUnitStart && !headerComplete && requiredBytes == 6) + { + //Kein Paketanfang und kein Paket was gerade zusammengebaut wird. Können wir also nicht gebrauchen + src.Position = src.Length; + return; + } + else if (partsProcessed == 0 && packetPayloadUnitStart && !headerComplete) + { + //Kein Aktives Paket, aber in diesem TS beginnt eins, also hinspulen. + src.Position += offsetAccordingToPUS; + return; + } + + if (partsProcessed > 0 && !headerComplete && !packetPayloadUnitStart) + { + src.Position = src.Length; + return; + } + else if ((packetPayloadUnitStart || (!packetPayloadUnitStart && requiredBytes < 6)) && !headerComplete) + { + //Offset schon rausgekriegt, wir müssen einen Header zusammenbauen und es beginnt ein neues Paket. + if (headerBytes == null) + headerBytes = new byte[6]; + requiredBytes -= src.Read(headerBytes, 6 - requiredBytes, requiredBytes); + if (requiredBytes == 0) + { + header = new T2MIHeader(headerBytes); + headerComplete = true; + requiredBytes = header.PayloadLengthBytes; + packetBuffer = new byte[requiredBytes]; + } + } + else if (headerComplete) + { + int aboutToRead = requiredBytes; + requiredBytes -= src.Read(packetBuffer, packetBuffer.Length - requiredBytes, aboutToRead); + if (requiredBytes == 0) + { + AssemblePacket(header, packetBuffer); + PrepareForNextPacket(); + } + } + else + { + throw new NotImplementedException("We're in a situation we didn't expect."); + } + } + + private bool firstPacketAssembled; + private byte lastPacketCounter; + private void AssemblePacket(T2MIHeader header, byte[] buffer) + { + Array.Resize(ref buffer, buffer.Length - 4); //idc, lol. + + //ensure continuity + if (firstPacketAssembled) + { + byte expectedPacket = lastPacketCounter; + expectedPacket++; + if (expectedPacket != header.PacketCount) + { + t2MiEventHandler.OnT2MiPacketLoss(RelatedPid, expectedPacket, header); + } + lastPacketCounter = header.PacketCount; + } + else + { + lastPacketCounter = header.PacketCount; + firstPacketAssembled = true; + } + + T2MiPacket t2MiPacket = DecodePacket(header, buffer); + if (t2MiPacket == null) + { + t2MiEventHandler.OnT2MiError(RelatedPid, 2); + return; + } + + if (!t2MiPacket.Valid) + { + t2MiEventHandler.OnT2MiError(RelatedPid, 1); + return; + } + + switch (t2MiPacket.Header.PacketType) + { + case 0x00: + _0x00_BasebandFrame basebandFrame = (_0x00_BasebandFrame)t2MiPacket; + DecodeBasebandFrame(basebandFrame); + break; + case 0x01: + _0x01_IqData iqData2 = (_0x01_IqData)t2MiPacket; + t2MiEventHandler.OnT2MiIqData(RelatedPid, iqData2); + break; + case 0x02: + _0x02_ArbitraryCellInsertion arbitraryCellInsertion = (_0x02_ArbitraryCellInsertion)t2MiPacket; + t2MiEventHandler.OnT2MiArbitraryCellInsertion(RelatedPid, arbitraryCellInsertion); + break; + case 0x10: + _0x10_L1Current l1Current = (_0x10_L1Current)t2MiPacket; + t2MiEventHandler.OnT2MiL1Current(RelatedPid, l1Current); + break; + case 0x11: + _0x11_L1Future l1future = (_0x11_L1Future)t2MiPacket; + t2MiEventHandler.OnT2MiL1Future(RelatedPid, l1future); + break; + case 0x12: + _0x12_P2BiasBalancingCells p2BiasBalancingCells = (_0x12_P2BiasBalancingCells)t2MiPacket; + t2MiEventHandler.OnT2MiBalancingCells(RelatedPid, p2BiasBalancingCells.FrameIndex, p2BiasBalancingCells.NumActiveBiasCellsPerP2); + break; + case 0x20: + _0x20_DvbT2Timestamp t2Timestamp = (_0x20_DvbT2Timestamp)t2MiPacket; + t2MiEventHandler.OnT2MiTimestamp(RelatedPid, t2Timestamp); + break; + case 0x21: + _0x21_IndividualAddressing individualAddressing = (_0x21_IndividualAddressing)t2MiPacket; + t2MiEventHandler.OnT2MiIndividualAddressing(RelatedPid, individualAddressing); + break; + case 0x31: + _0x31_IqData iqData = (_0x31_IqData)t2MiPacket; + t2MiEventHandler.OnT2MiIqData(RelatedPid, iqData); + break; + case 0x33: + _0x33_Subpart subpart = (_0x33_Subpart)t2MiPacket; + if (subpart.PreferDiscard) + { + t2MiEventHandler.OnT2MiError(RelatedPid, 3); + return; + } + switch (subpart.SubpartVariety) + { + case 0: + break; + default: + if (subpart.Subpart == null) + break; + throw new NotImplementedException(subpart.Subpart.ToString()); + } + break; + default: + throw new NotImplementedException(t2MiPacket.GetType().Name); + } + } + + private void DecodeBasebandFrame(_0x00_BasebandFrame basebandFrame) + { + if (basebandFrame.BasebandFrame == null) + return; + + if (basebandFrame.BasebandFrame.Length < 10) + return; + + if (basebandPacketInProgress == null) + { + basebandPacketInProgress = new bool[256]; + basebandPacket = new byte[256][]; + basebandPacketOffset = new int[256]; + } + + byte plp = basebandFrame.PlpId; + + MemoryStream ms = new MemoryStream(basebandFrame.BasebandFrame, false); + + byte readUInt8 = ms.ReadUInt8(); // t2packet[9] + T2BasebandStreamType streamType = (T2BasebandStreamType)((readUInt8 & 0xc0) >> 6); + bool isSingle = (readUInt8 & 0x20) != 0; + bool isCcm = (readUInt8 & 0x10) != 0; + bool issyi = (readUInt8 & 0x08) != 0; + bool npd = (readUInt8 & 0x04) != 0; + int ext = (readUInt8 & 0x03); + + byte matype2 = ms.ReadUInt8(); // t2packet[10] + ushort upl = ms.ReadUInt16BE(); // t2packet[11] + ushort dfl = ms.ReadUInt16BE(); // t2packet[13] + byte sync = ms.ReadUInt8(); // t2packet[15] + ushort syncD = ms.ReadUInt16BE(); // t2packet[16] + byte crc8mode = ms.ReadUInt8(); // t2packet[18] + + int uplBytes = upl / 8; + int syncDBytes = syncD / 8; + int dflBytes = dfl / 8; + + if (!basebandPacketInProgress[plp] && syncD == 65535) + { + //Kein Frame in Progress und es beginnt auch kein neuer, also weg damit. + return; + } + + + int expectedLength = (int)ms.Position + dflBytes; + if (expectedLength > basebandFrame.BasebandFrame.Length) + return; + + MemoryStream ms2 = new MemoryStream(basebandFrame.BasebandFrame, (int)ms.Position, dflBytes, false); + if (!basebandPacketInProgress[plp] && syncD != 65535) + { + ms2.Position += syncDBytes; + } + while (ms2.GetAvailableBytes() > 0) + { + if (basebandPacket[plp] == null) + { + basebandPacket[plp] = new byte[188]; + basebandPacket[plp][0] = 0x47; + basebandPacketOffset[plp] = 1; + basebandPacketInProgress[plp] = true; + continue; + } + + if (basebandPacket[plp] != null) + { + basebandPacketOffset[plp] += ms2.Read(basebandPacket[plp], basebandPacketOffset[plp], 188 - basebandPacketOffset[plp]); + if (basebandPacketOffset[plp] == 188) + { + t2MiEventHandler.OnT2MiPacket(RelatedPid, basebandFrame.PlpId, basebandPacket[plp]); + basebandPacketInProgress[plp] = false; + basebandPacket[plp] = null; + basebandPacketOffset[plp] = 0; + outputPacketsPushed++; + } + continue; + } + } + } + + private bool[] basebandPacketInProgress; + private byte[][] basebandPacket; + private int[] basebandPacketOffset; + + #region Reflection für die einzelnen Pakettypen + + private static ConstructorInfo[] packetConstructorInfos; + + private T2MiPacket DecodePacket(T2MIHeader header, byte[] buffer) + { + if (header.PacketType > 0x33 || header.PacketType == 0x03 || header.PacketType == 0x0f || header.PacketType == 0x19 || header.PacketType == 0x18 || + header.PacketType == 0x0a || header.PacketType == 0x2e || header.PacketType == 0x2b || header.PacketType == 0x1e || header.PacketType == 0x05 || + header.PacketType == 0x1f || header.PacketType == 0x1d || header.PacketType == 0x1c || header.PacketType == 0x06 || header.PacketType == 0x25 || + header.PacketType == 0x28 || header.PacketType == 0x08 || header.PacketType == 0x24 || header.PacketType == 0x17 || header.PacketType == 0x2a || + header.PacketType == 0x23 || + header.PacketType == 0x15 || + header.PacketType == 0x0e || + header.PacketType == 0x2f || + header.PacketType == 0x2d || + header.PacketType == 0x04 || + header.PacketType == 0x26 || + header.PacketType == 0x22 || + header.PacketType == 0x0d || + header.PacketType == 0x13 || + header.PacketType == 0x29 || + header.PacketType == 0x09 || + header.PacketType == 0x0b || + header.PacketType == 0x2c || + header.PacketType == 0x1a || + header.PacketType == 0x16 || + header.PacketType == 0x1b || + header.PacketType == 0x14 || + header.PacketType == 0x27 || + header.PacketType == 0x0c || + header.PacketType == 0x07) + { + //reserved for future use + return null; + } + + if (packetConstructorInfos == null) + LoadPacketTypes(); + + ConstructorInfo constructorInfo = packetConstructorInfos[header.PacketType]; + if (constructorInfo == null) + throw new NotImplementedException(String.Format("T2-MI packet type 0x{0:X2}", header.PacketType)); + + object invoke = constructorInfo.Invoke(new object[] { header, buffer }); + return (T2MiPacket) invoke; + } + + private static void LoadPacketTypes() + { + packetConstructorInfos = PluginManager.GetInstance().GetT2MiPacketTypes(); + bool invalid = packetConstructorInfos.All(x => x == null); + if (invalid) + throw new T2MiException("No Packet Types loaded"); + } + + #endregion + + public void PacketLoss() + { + PrepareForNextPacket(); + basebandPacketInProgress = null; + basebandPacket = null; + basebandPacketOffset = null; + } + + private void PrepareForNextPacket() + { + headerComplete = false; + requiredBytes = 6; + headerBytes = null; + header = null; + packetBuffer = null; + } + + } +} diff --git a/skyscraper8/T2MI/T2MIEventHandler.cs b/skyscraper8/T2MI/T2MIEventHandler.cs new file mode 100644 index 0000000..7118348 --- /dev/null +++ b/skyscraper8/T2MI/T2MIEventHandler.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.T2MI.Packets; + +namespace skyscraper5.T2MI +{ + internal interface T2MIEventHandler + { + void OnT2MiPacketLoss(int pid, byte expectedPacket, T2MIHeader header); + void OnT2MiPacket(int pid, byte basebandFramePlpId, byte[] basebandPacket); + void OnT2MiTimestamp(int pid, _0x20_DvbT2Timestamp t2Timestamp); + void OnT2MiIqData(int relatedPid, _0x31_IqData iqData); + void OnT2MiIqData(int relatedPid, _0x01_IqData iqData2); + void OnT2MiError(int relatedPid, int skyscraperErrorCode); + void OnT2MiL1Current(int relatedPid, _0x10_L1Current l1Current); + void OnT2MiArbitraryCellInsertion(int relatedPid, _0x02_ArbitraryCellInsertion arbitraryCellInsertion); + void OnT2MiBalancingCells(int relatedPid, byte frameIndex, uint numActiveBiasCellsPerP2); + void OnT2MiL1Future(int relatedPid, _0x11_L1Future l1Future); + void OnT2MiIndividualAddressing(int relatedPid, _0x21_IndividualAddressing individualAddressing); + } +} diff --git a/skyscraper8/T2MI/T2MIHeader.cs b/skyscraper8/T2MI/T2MIHeader.cs new file mode 100644 index 0000000..66a1b25 --- /dev/null +++ b/skyscraper8/T2MI/T2MIHeader.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI +{ + public class T2MIHeader + { + public T2MIHeader(byte[] buffer) + { + PacketType = buffer[0]; + PacketCount = buffer[1]; + SuperframeIndex = (buffer[2] & 0xf0) >> 4; + + RFU = (buffer[2] & 0x0f); + RFU <<= 5; + RFU = ((buffer[3] & 0xf8) >> 3); + + T2MIStreamId = (buffer[3] & 0x07); + + (buffer[4], buffer[5]) = (buffer[5], buffer[4]); + PayloadLength = BitConverter.ToUInt16(buffer, 4); + + if (PacketCount == 104) + return; + } + + public ushort PayloadLength { get; private set; } + + public int T2MIStreamId { get; set; } + + public int RFU { get; private set; } + + public int SuperframeIndex { get; private set; } + + public byte PacketCount { get; set; } + + public byte PacketType { get; private set; } + + public int PayloadLengthBytes + { + get + { + if (PayloadLength % 8 == 0) + return (PayloadLength / 8) + 4; //+4 damit wir die Checksum einfach mitnehmen können, höhöhö... + else + { + int result = PayloadLength / 8; + result++; + return result + 4; + } + } + } + + public bool NeedPadding => PayloadLength % 8 != 0; + } +} diff --git a/skyscraper8/T2MI/T2MiException.cs b/skyscraper8/T2MI/T2MiException.cs new file mode 100644 index 0000000..cb67571 --- /dev/null +++ b/skyscraper8/T2MI/T2MiException.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; + +namespace skyscraper5.T2MI +{ + [Serializable] + internal class T2MiException : DvbException + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public T2MiException() + : base("Unknown T2-Mi Error") + { + } + + public T2MiException(string message) : base(message) + { + } + + public T2MiException(string message, Exception inner) : base(message, inner) + { + } + + protected T2MiException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/skyscraper8/T2MI/T2MiPacket.cs b/skyscraper8/T2MI/T2MiPacket.cs new file mode 100644 index 0000000..39c5652 --- /dev/null +++ b/skyscraper8/T2MI/T2MiPacket.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper5.T2MI +{ + public abstract class T2MiPacket : Validatable + { + public T2MIHeader Header { get; } + + protected T2MiPacket(T2MIHeader header, byte[] buffer) + { + Header = header; + } + } +} diff --git a/skyscraper8/T2MI/T2MiPacketTypeAttribute.cs b/skyscraper8/T2MI/T2MiPacketTypeAttribute.cs new file mode 100644 index 0000000..66af5c7 --- /dev/null +++ b/skyscraper8/T2MI/T2MiPacketTypeAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.T2MI +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + sealed class T2MiPacketTypeAttribute : Attribute + { + public T2MiPacketTypeAttribute(byte packet_type) + { + PacketType = packet_type; + } + + public byte PacketType { get; private set; } + } +} diff --git a/skyscraper8/T2MI/TestApp.cs b/skyscraper8/T2MI/TestApp.cs new file mode 100644 index 0000000..f395381 --- /dev/null +++ b/skyscraper8/T2MI/TestApp.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Mpeg2; +using skyscraper5.T2MI.Packets; + +namespace skyscraper5.T2MI +{ + internal class TestApp : T2MIEventHandler + { + public TestApp(string s) + { + InputFile = new FileInfo(s); + } + + public FileInfo InputFile { get; set; } + + public void Run() + { + FileStream fileStream = InputFile.OpenRead(); + int psize = 188; + byte[] buffer = new byte[psize]; + TsContext tsContext = new TsContext(); + + T2MIDecoder t2mi = new T2MIDecoder(0x1000, this); + tsContext.RegisterPacketProcessor(0x1000, t2mi); + + while (psize == 188) + { + psize = fileStream.Read(buffer, 0, 188); + tsContext.PushPacket(buffer); + } + } + + public void OnT2MiPacketLoss(int pid, byte expectedPacket, T2MIHeader header) + { + } + + private FileStream[] fileStreams; + public void OnT2MiPacket(int pid, byte plp, byte[] basebandPacket) + { + if (fileStreams == null) + { + fileStreams = new FileStream[256]; + } + + if (fileStreams[plp] == null) + { + FileInfo fi = new FileInfo(String.Format("{0}.ts", plp)); + if (fi.Exists) + fi.Delete(); + fileStreams[plp] = fi.OpenWrite(); + } + + fileStreams[plp].Write(basebandPacket, 0, 188); + } + + public void OnT2MiTimestamp(int pid, _0x20_DvbT2Timestamp t2Timestamp) + { + + } + + public void OnT2MiIqData(int relatedPid, _0x31_IqData iqData) + { + } + + public void OnT2MiIqData(int relatedPid, _0x01_IqData iqData2) + { + + } + + public void OnT2MiError(int relatedPid, int skyscraperErrorCode) + { + + } + + public void OnT2MiL1Current(int relatedPid, _0x10_L1Current l1Current) + { + + } + + public void OnT2MiArbitraryCellInsertion(int relatedPid, _0x02_ArbitraryCellInsertion arbitraryCellInsertion) + { + + } + + public void OnT2MiBalancingCells(int relatedPid, byte frameIndex, uint numActiveBiasCellsPerP2) + { + + } + + public void OnT2MiL1Future(int relatedPid, _0x11_L1Future l1Future) + { + + } + + public void OnT2MiIndividualAddressing(int relatedPid, _0x21_IndividualAddressing individualAddressing) + { + + } + } +} diff --git a/skyscraper8/Teletext/EbuTeletextDataField.cs b/skyscraper8/Teletext/EbuTeletextDataField.cs new file mode 100644 index 0000000..0388987 --- /dev/null +++ b/skyscraper8/Teletext/EbuTeletextDataField.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Teletext +{ + public class EbuTeletextDataField : Validatable + { + public EbuTeletextDataField(byte[] dataFieldContent) + { + InversionTable.TeletextInversion(dataFieldContent,2); + + MemoryStream ms = new MemoryStream(dataFieldContent); + byte readUInt8 = ms.ReadUInt8(); + FieldParity = (readUInt8 & 0x20) != 0; + LineOffset = (readUInt8 & 0x1f); + FramingCode = ms.ReadUInt8(); + + byte mpag = ms.ReadHamming84(); + Magazine = mpag & 0x07; + PacketNumber = (mpag >> 3) & 0x1f; + + + if (PacketNumber != 0) + { + if (PacketNumber >= 26) + { + //Non-Displayable Packet Y = 26 to 31 + if (ms.GetAvailableBytes() == 0) + return; + DesignationCode = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < 39) + return; + CharacterCodes = ms.ReadBytes(39); + } + else + { + //Normal Packet (Y = 1 to 25) + if (ms.GetAvailableBytes() < 40) + return; + CharacterCodes = ms.ReadBytes(40); + } + } + else + { + //Page Header Packet (Y = 0) + //To-Do: Read Page No, Sub-code, and Control bits + PageNumber = (byte)ms.ReadHamming84().UnpackBcd(); + + Header = new PageHeader(); + ms.ReadUInt8(); //byte 8 + readUInt8 = ms.ReadUInt8(); + Header.ErasePage = (readUInt8 & 0x80) != 0; + + + ms.ReadUInt8(); //byte 10 + readUInt8 = ms.ReadUInt8(); //byte 11 + Header.Newsflash = (readUInt8 & 0x60) != 0; + Header.Subtitle = (readUInt8 & 0x80) != 0; + readUInt8 = ms.ReadUInt8(); //byte 12 + Header.SurpressHeader = (readUInt8 & 0x02) != 0; + Header.UpdateIndicator = (readUInt8 & 0x08) != 0; + Header.InterruptedSequence = (readUInt8 & 0x20) != 0; + Header.InhibitDisplay = (readUInt8 & 0x80) != 0; + readUInt8 = ms.ReadUInt8(); //byte 13 + Header.SerialMode = (readUInt8 & 0x02) != 0; + + if (ms.GetAvailableBytes() < 32) + { + Valid = false; + return; + } + CharacterCodes = ms.ReadBytes(32); + } + + if (PacketNumber < 26) + { + for (int i = 0; i < CharacterCodes.Length; i++) + { + CharacterCodes[i] &= 0x7f; + } + } + Valid = true; + } + + public byte DesignationCode { get; private set; } + + public byte PageNumber { get; set; } + + public class PageHeader + { + public bool ErasePage { get; internal set; } + public bool Newsflash { get; internal set; } + public bool Subtitle { get; internal set; } + public bool SurpressHeader { get; internal set; } + public bool UpdateIndicator { get; internal set; } + public bool InterruptedSequence { get; internal set; } + public bool InhibitDisplay { get; internal set; } + public bool SerialMode { get; internal set; } + } + + public PageHeader Header { get; private set; } + public byte[] CharacterCodes { get; private set; } + + public int PacketNumber { get; private set; } + + public int Magazine { get; private set; } + + public byte FramingCode { get; private set; } + + public int LineOffset { get; private set; } + + public bool FieldParity { get; private set; } + + } +} diff --git a/skyscraper8/Teletext/HammingCode.cs b/skyscraper8/Teletext/HammingCode.cs new file mode 100644 index 0000000..0411bbb --- /dev/null +++ b/skyscraper8/Teletext/HammingCode.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Teletext +{ + internal static class HammingCode + { + public static byte ReadHamming84(this Stream s) + { + byte a = s.ReadUInt8(); + byte b = s.ReadUInt8(); + return Unham(a, b); + } + + public static byte Unham(byte a, byte b) + { + byte c1 = unhamtab[a]; + byte c2 = unhamtab[b]; + + int tResult = (c2 << 4) | (0x0f & c1); + byte result = (byte)(tResult & 0x000000ff); + return result; + } + + private static readonly byte[] unhamtab = new byte[] { + 0x01, 0xff, 0x81, 0x01, 0xff, 0x00, 0x01, 0xff, + 0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07, + 0xff, 0x00, 0x01, 0xff, 0x00, 0x80, 0xff, 0x00, + 0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff, + 0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07, + 0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x87, + 0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff, + 0x86, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07, + 0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09, + 0x02, 0x82, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff, + 0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff, + 0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x83, 0x03, + 0x04, 0xff, 0xff, 0x05, 0x84, 0x04, 0x04, 0xff, + 0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07, + 0xff, 0x05, 0x05, 0x85, 0x04, 0xff, 0xff, 0x05, + 0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff, + 0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09, + 0x0a, 0xff, 0xff, 0x0b, 0x8a, 0x0a, 0x0a, 0xff, + 0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff, + 0xff, 0x0b, 0x0b, 0x8b, 0x0a, 0xff, 0xff, 0x0b, + 0x0c, 0x8c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff, + 0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07, + 0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x8d, 0x0d, + 0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff, + 0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x89, + 0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09, + 0x88, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09, + 0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff, + 0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09, + 0x0f, 0xff, 0x8f, 0x0f, 0xff, 0x0e, 0x0f, 0xff, + 0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff, + 0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x8e, 0xff, 0x0e, + }; + + } +} diff --git a/skyscraper8/Teletext/ITeletextPageHandler.cs b/skyscraper8/Teletext/ITeletextPageHandler.cs new file mode 100644 index 0000000..599ccb9 --- /dev/null +++ b/skyscraper8/Teletext/ITeletextPageHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.src.Teletext; +using skyscraper5.Teletext.Vps; +using skyscraper5.Teletext.Wss; + +namespace skyscraper5.Teletext +{ + interface ITeletextPageHandler + { + void OnVpsData(int networkId, int transportStreamId, ushort programNumber, VpsDataBlock vpsDataField); + void OnWssData(int networkId, int transportStreamId, ushort programNumber, WssDataBlock wssDataBlock); + void OnTeletextPage(int networkId, int transportStreamId, ushort programNumber, TeletextMagazine magazine); + void OnMonochromeData(int networkId, int transportStreamId, ushort programNumber, MonochromeDataField result); + } +} diff --git a/skyscraper8/Teletext/InversionTable.cs b/skyscraper8/Teletext/InversionTable.cs new file mode 100644 index 0000000..8ed4b08 --- /dev/null +++ b/skyscraper8/Teletext/InversionTable.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Teletext +{ + internal static class InversionTable + { + public static void TeletextInversion(byte[] buffer, int start) + { + TeletextInversion(buffer, start, buffer.Length); + } + + public static void TeletextInversion(byte[] buffer, int start, int end) + { + for (int i = start; i < end; i++) + { + buffer[i] = invtab[buffer[i]]; + } + } + + private static readonly byte[] invtab = new byte[] { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, + }; + } +} diff --git a/skyscraper8/Teletext/MonochromeDataField.cs b/skyscraper8/Teletext/MonochromeDataField.cs new file mode 100644 index 0000000..dc42b5d --- /dev/null +++ b/skyscraper8/Teletext/MonochromeDataField.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.src.Teletext +{ + public class MonochromeDataField + { + public bool FirstSegmentFlag { get; internal set; } + public bool LastSegmentFlag { get; internal set; } + public bool FieldParity { get; internal set; } + public int LineOffset { get; internal set; } + public ushort FirstPixelPosition { get; internal set; } + public byte[] Pixels { get; internal set; } + } +} diff --git a/skyscraper8/Teletext/TeletextMagazine.cs b/skyscraper8/Teletext/TeletextMagazine.cs new file mode 100644 index 0000000..0cc17a4 --- /dev/null +++ b/skyscraper8/Teletext/TeletextMagazine.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Teletext +{ + public class TeletextMagazine + { + public TeletextMagazine(int magazineNumber) + { + MagazineNumber = magazineNumber; + Lines = new byte[32][]; + } + + public int MagazineNumber { get; } + public byte[][] Lines { get; } + public EbuTeletextDataField.PageHeader Header { get; set; } + public byte PageNumber { get; set; } + + public ushort HumanReadablePageNumber + { + get + { + if (Lines[0] == null) + return 0xffff; + if (Lines[0][0] > 0x39 && Lines[0][0] < 0x30) + return 0xfffe; + if (Lines[0][1] > 0x39 && Lines[0][1] < 0x30) + return 0xfffd; + if (Lines[0][2] > 0x39 && Lines[0][2] < 0x30) + return 0xfffc; + ushort result = Lines[0][0]; + result -= 48; + result *= 100; + result += (ushort)((Lines[0][1] - 48) * 10); + result += (ushort)(Lines[0][2] - 48); + return result; + } + } + + public byte[] GetUniqueFingerprint() + { + MemoryStream ms = new MemoryStream(); + ms.Write(BitConverter.GetBytes(HumanReadablePageNumber), 0, 2); + for (int i = 1; i < 25; i++) + { + if (Lines[i] != null) + ms.Write(Lines[i], 0, 40); + } + + ms.Position = 0; + SHA256 sha256 = SHA256.Create(); + byte[] result = sha256.ComputeHash(ms); + sha256.Dispose(); + return result; + } + + public override string ToString() + { + return HumanReadablePageNumber.ToString(); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + return true; + + TeletextMagazine tm = obj as TeletextMagazine; + if (tm == null) + return false; + + for (int y = 1; y < 25; y++) + { + byte[] lLines = this.Lines[y]; + byte[] rLines = tm.Lines[y]; + if (lLines == null && rLines == null) + continue; + if (lLines == null) + return false; + if (rLines == null) + return false; + + for (int i = 0; i < 40; i++) + { + if (lLines[i] != rLines[i]) + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + return BitConverter.ToInt32(GetUniqueFingerprint(), 0); + } + + public void WriteOut(Stream fileStream) + { + for (int i = 0; i < Lines.Length; i++) + { + if (Lines[i] != null) + { + fileStream.Write(Lines[i], 0, Lines[i].Length); + } + + fileStream.WriteByte(0x0a); + } + } + + public string FormatAsString() + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Lines.Length; i++) + { + if (Lines[i] != null) + { + sb.Append(Encoding.Default.GetString(Lines[i])); + } + } + return sb.ToString(); + } + + public byte[] FormatAsByteArray() + { + int len = 0; + for (int i = 0; i < Lines.Length; i++) + { + len += 2; + if (Lines[i] == null) + continue; + + len += Lines[i].Length; + } + + byte[] buffer = new byte[len]; + int offset = 0; + for (int i = 0; i < Lines.Length; i++) + { + if (Lines[i] == null) + { + buffer[offset++] = 0x0d; + buffer[offset++] = 0x0a; + continue; + } + + byte[] line = Lines[i]; + Array.Copy(line, 0, buffer, offset, line.Length); + offset += line.Length; + buffer[offset++] = 0x0d; + buffer[offset++] = 0x0a; + } + + return buffer; + } + } +} diff --git a/skyscraper8/Teletext/TeletextPesProcessor.cs b/skyscraper8/Teletext/TeletextPesProcessor.cs new file mode 100644 index 0000000..7dfcad0 --- /dev/null +++ b/skyscraper8/Teletext/TeletextPesProcessor.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper5.src.Teletext; +using skyscraper5.Teletext.Vps; +using skyscraper5.Teletext.Wss; + +namespace skyscraper5.Teletext +{ + class TeletextPesProcessor : IPesProcessor + { + public ITeletextPageHandler PageHandler { get; } + public int NetworkId { get; } + public int TransportStreamId { get; } + public ushort ProgramNumber { get; } + + public TeletextPesProcessor(ITeletextPageHandler pageHandler, int networkId, int transportStreamId, ushort programNumber) + { + PageHandler = pageHandler; + NetworkId = networkId; + TransportStreamId = transportStreamId; + ProgramNumber = programNumber; + Magazines = new TeletextMagazine[8]; + } + + public TeletextMagazine[] Magazines { get; private set; } + public uint? PrivateDataSpecifier { get; set; } + + public void ProcessPesPacket(PesPacket packet) + { + if (packet.Payload == null) + return; + if (packet.Payload.Length == 0) + return; + MemoryStream ms = new MemoryStream(packet.Payload, false); + byte dataIdentifier = ms.ReadUInt8(); + if (dataIdentifier <= 0x0f) + { + //reserved for future use + return; + } + + if (dataIdentifier >= 0x20 && dataIdentifier <= 0x7f) + { + //reserved for future use + return; + } + if (dataIdentifier >= 0x80) + { + //user defined + return; + } + + while (ms.GetAvailableBytes() > 0) + { + byte dataUnitId = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < 1) + return; + byte dataUnitLength = ms.ReadUInt8(); + if (ms.GetAvailableBytes() < dataUnitLength) + return; + byte[] dataFieldContent = ms.ReadBytes(dataUnitLength); + switch (dataUnitId) + { + case 0x00: //reserved for future use, discard according to en_301775v010201p.pdf, page 9, so discard + case 0x01: + break; + case 0x02: //EBU Teletext non-subtitle data + case 0x03: //EBU Teletext subtitle data + EbuTeletext(dataFieldContent); + break; + case 0xc0: //Inverted Teletext (ETSI EN 301775, page 9) + case 0xc1: + EbuTeletext(dataFieldContent); + break; + case 0xc3: //VPS (ETSI EN 301775) + Vps(dataFieldContent); + break; + case 0xc4: //WSS (ETSI EN 301775) + Wss(dataFieldContent); + break; + case 0xc5: //Closed Captioning (ETSI EN 301775) + throw new NotImplementedException("closed captioning"); + case 0xc6: //monochrome data (etsi en 301775) + MonochromeData(dataFieldContent); + break; + case 0xff: //stuffing + break; + default: + if ((dataUnitId >= 0x04 && dataUnitId <= 0x7f)) + continue; //reserved according to en_301775v010201p.pdf, page 9, so discard + if ((dataUnitId >= 0xc7 && dataUnitId <= 0xfe) && !this.PrivateDataSpecifier.HasValue) + continue; + if ((dataUnitId >= 0x80 && dataUnitId <= 0xbf) && !this.PrivateDataSpecifier.HasValue) + continue; + throw new NotImplementedException(String.Format("Teletext {0} {1:X2}", nameof(dataUnitId), dataUnitId)); + } + } + } + + private void MonochromeData(byte[] dataFieldContent) + { + MemoryStream ms = new MemoryStream(dataFieldContent, false); + byte byte1 = ms.ReadUInt8(); + MonochromeDataField result = new MonochromeDataField(); + result.FirstSegmentFlag = (byte1 & 0x80) != 0; + result.LastSegmentFlag = (byte1 & 0x40) != 0; + result.FieldParity = (byte1 & 0x20) != 0; + result.LineOffset = (byte1 & 0x1f); + result.FirstPixelPosition = ms.ReadUInt16BE(); + byte nPixels = ms.ReadUInt8(); + if (nPixels > ms.GetAvailableBytes()) + return; + result.Pixels = ms.ReadBytes(nPixels); + PageHandler.OnMonochromeData(NetworkId, TransportStreamId, ProgramNumber, result); + } + + private void Wss(byte[] buffer) + { + WssDataField wssDataField = new WssDataField(buffer); + PageHandler.OnWssData(NetworkId, TransportStreamId, ProgramNumber, wssDataField.WssDataBlock); + } + + private void Vps(byte[] buffer) + { + VpsDataField vpsDataField = new VpsDataField(buffer); + PageHandler.OnVpsData(NetworkId, TransportStreamId, ProgramNumber, vpsDataField.VpsDataBlock); + } + + private void EbuTeletext(byte[] buffer) + { + EbuTeletextDataField df = new EbuTeletextDataField(buffer); + if (!df.Valid) + { + return; + } + if (df.PacketNumber == 0) + { + if (Magazines[df.Magazine] != null) + { + PageHandler.OnTeletextPage(NetworkId, TransportStreamId, ProgramNumber, Magazines[df.Magazine]); + } + Magazines[df.Magazine] = new TeletextMagazine(df.Magazine); + Magazines[df.Magazine].Lines[0] = df.CharacterCodes; + Magazines[df.Magazine].Header = df.Header; + Magazines[df.Magazine].PageNumber = df.PageNumber; + return; + } + + if (Magazines[df.Magazine] == null) + return; + + Magazines[df.Magazine].Lines[df.PacketNumber] = df.CharacterCodes; + } + } +} diff --git a/skyscraper8/Teletext/Vps/VpsDataBlock.cs b/skyscraper8/Teletext/Vps/VpsDataBlock.cs new file mode 100644 index 0000000..93b6490 --- /dev/null +++ b/skyscraper8/Teletext/Vps/VpsDataBlock.cs @@ -0,0 +1,34 @@ +using System; + +namespace skyscraper5.Teletext.Vps +{ + public class VpsDataBlock + { + public VpsDataBlock(byte[] buffer) + { + byte[] vpsBuffer = new byte[15]; + vpsBuffer[0] = 255; + vpsBuffer[1] = 0b10111010; + Array.Copy(buffer, 0, vpsBuffer, 2, 13); + + Day = (vpsBuffer[10] & 0x7c) >> 2; + + Month = (vpsBuffer[10] & 0x80) >> 7; + Month <<= 3; + Month += (vpsBuffer[11] & 0x07); + + Hour = ((vpsBuffer[11] & 0xf8) >> 3); + + Minute = ((vpsBuffer[12] & 0x3f)); + } + + + public int Minute { get; private set; } + + public int Hour { get; private set; } + + public int Month { get; private set; } + + public int Day { get; private set; } + } +} diff --git a/skyscraper8/Teletext/Vps/VpsDataField.cs b/skyscraper8/Teletext/Vps/VpsDataField.cs new file mode 100644 index 0000000..988fc2c --- /dev/null +++ b/skyscraper8/Teletext/Vps/VpsDataField.cs @@ -0,0 +1,28 @@ +using System.IO; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Teletext.Vps +{ + class VpsDataField + { + public VpsDataField(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + byte readUInt8 = ms.ReadUInt8(); + + FieldParity = (readUInt8 & 0x20) != 0; + LineOffset = (readUInt8 & 0x1f); + + byte[] readBytes = ms.ReadBytes(13); + VpsDataBlock = new VpsDataBlock(readBytes); + } + + public int LineOffset { get; private set; } + + public bool FieldParity { get; private set; } + + public VpsDataBlock VpsDataBlock { get; private set; } + + + } +} diff --git a/skyscraper8/Teletext/Wss/WssDataBlock.cs b/skyscraper8/Teletext/Wss/WssDataBlock.cs new file mode 100644 index 0000000..6ea9d3e --- /dev/null +++ b/skyscraper8/Teletext/Wss/WssDataBlock.cs @@ -0,0 +1,179 @@ +using System; +using System.Runtime.InteropServices.ComTypes; + +namespace skyscraper5.Teletext.Wss +{ + public class WssDataBlock + { + public WssDataBlock(int i) + { + bool b0 = (i & 0x8000) != 0; + bool b1 = (i & 0x4000) != 0; + bool b2 = (i & 0x2000) != 0; + bool b3 = (i & 0x2000) != 0; + + if (!b0 && !b1 && !b2 && b3) + { + AspectRatioLabel = AspectRatio._4_3; + FullFormat = true; + Position = Positioning.NotApplicable; + Lines = 576; + } + else if (b0 && !b1 && !b2 && !b3) + { + AspectRatioLabel = AspectRatio._14_9; + FullFormat = false; + Position = Positioning.Centre; + Lines = 504; + } + else if (!b0 && b1 && !b2 && !b3) + { + AspectRatioLabel = AspectRatio._14_9; + FullFormat = false; + Position = Positioning.Top; + Lines = 504; + } + else if (b0 && b1 && !b2 && b3) + { + AspectRatioLabel = AspectRatio._16_9; + FullFormat = false; + Position = Positioning.Centre; + Lines = 430; + } + else if (!b0 && !b1 && b2 && !b3) + { + AspectRatioLabel = AspectRatio._16_9; + FullFormat = false; + Position = Positioning.Top; + Lines = 430; + } + else if (b0 && !b1 && b2 && b3) + { + AspectRatioLabel = AspectRatio.UltraWidescreen; + FullFormat = false; + Position = Positioning.Centre; + Lines = null; + } + else if (!b0 && b1 && b2 && b3) + { + AspectRatioLabel = AspectRatio._14_9; + FullFormat = true; + Position = Positioning.Centre; + Lines = 576; + } + else if (b0 && b1 && b2 && !b3) + { + AspectRatioLabel = AspectRatio._16_9; + FullFormat = true; + Position = Positioning.NotApplicable; + Lines = 576; + } + + FilmMode = (i & 0x1000) != 0; + MotionAdaptiveColorPlus = (i & 0x0800) != 0; + ModulatedHelper = (i & 0x0400) != 0; + //(i & 0x0200) != 0; //reserved + SubtitlesWithinTeletext = (i & 0x0100) != 0; + + bool b9 = (i & 0x0080) != 0; + bool b10 = (i & 0x0040) != 0; + + if (!b9 && !b10) + SubtitlingMode = SubtitlingModeEnum.NoOpenSubtitles; + else if (b9 && !b10) + SubtitlingMode = SubtitlingModeEnum.InActiveImage; + else if (!b9 && b10) + SubtitlingMode = SubtitlingModeEnum.OutOfActiveImage; + + SurroundSound = (i & 0x0020) != 0; + CopyrightAsserted = (i & 0x0010) != 0; + CopyingRestricted = (i & 0x0008) != 0; + } + + public bool CopyingRestricted { get; private set; } + + public bool CopyrightAsserted { get; private set; } + + public bool SurroundSound { get; private set; } + + public SubtitlingModeEnum SubtitlingMode { get; private set; } + + public bool SubtitlesWithinTeletext { get; private set; } + + public bool ModulatedHelper { get; private set; } + + public bool MotionAdaptiveColorPlus { get; private set; } + + public bool FilmMode { get; private set; } + + public int? Lines { get; private set; } + + public Positioning Position { get; private set; } + + public bool FullFormat { get; private set; } + + public AspectRatio AspectRatioLabel { get; private set; } + + public string AspectRatioToString() + { + string s = AspectRatioLabel.ToString(); + if (s[0] == '_') + return s.Substring(1).Replace('_', ':'); + else + return s; + } + + public enum AspectRatio + { + _4_3, + _14_9, + _16_9, + UltraWidescreen + } + + public enum Positioning + { + NotApplicable, + Centre, + Top + } + + public enum SubtitlingModeEnum + { + NoOpenSubtitles, + InActiveImage, + OutOfActiveImage + } + + protected bool Equals(WssDataBlock other) + { + return CopyingRestricted == other.CopyingRestricted && CopyrightAsserted == other.CopyrightAsserted && SurroundSound == other.SurroundSound && SubtitlingMode == other.SubtitlingMode && SubtitlesWithinTeletext == other.SubtitlesWithinTeletext && ModulatedHelper == other.ModulatedHelper && MotionAdaptiveColorPlus == other.MotionAdaptiveColorPlus && FilmMode == other.FilmMode && Lines == other.Lines && Position == other.Position && FullFormat == other.FullFormat && AspectRatioLabel == other.AspectRatioLabel; + } + + 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((WssDataBlock)obj); + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(CopyingRestricted); + hashCode.Add(CopyrightAsserted); + hashCode.Add(SurroundSound); + hashCode.Add((int)SubtitlingMode); + hashCode.Add(SubtitlesWithinTeletext); + hashCode.Add(ModulatedHelper); + hashCode.Add(MotionAdaptiveColorPlus); + hashCode.Add(FilmMode); + hashCode.Add(Lines); + hashCode.Add((int)Position); + hashCode.Add(FullFormat); + hashCode.Add((int)AspectRatioLabel); + return hashCode.ToHashCode(); + } + } +} \ No newline at end of file diff --git a/skyscraper8/Teletext/Wss/WssDataField.cs b/skyscraper8/Teletext/Wss/WssDataField.cs new file mode 100644 index 0000000..e47db29 --- /dev/null +++ b/skyscraper8/Teletext/Wss/WssDataField.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper5.Teletext.Wss +{ + class WssDataField + { + public WssDataField(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte readUInt8 = ms.ReadUInt8(); + FieldParity = (readUInt8 & 0x20) != 0; + LineOffset = (readUInt8 & 0x1f); + WssDataBlock = new WssDataBlock(ms.ReadUInt16BE()); + } + + public int LineOffset { get; private set; } + public WssDataBlock WssDataBlock { get; } + public bool FieldParity { get; private set; } + } +} diff --git a/skyscraper8/skyscraper8.csproj b/skyscraper8/skyscraper8.csproj new file mode 100644 index 0000000..f3cd44e --- /dev/null +++ b/skyscraper8/skyscraper8.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + Linux + + + + + + + + diff --git a/skyscraper8/skyscraper8.csproj.user b/skyscraper8/skyscraper8.csproj.user new file mode 100644 index 0000000..b32f649 --- /dev/null +++ b/skyscraper8/skyscraper8.csproj.user @@ -0,0 +1,9 @@ + + + + skyscraper8 + + + ProjectDebugger + + \ No newline at end of file