diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml
index 3853210..1ea1dff 100644
--- a/.gitea/workflows/demo.yaml
+++ b/.gitea/workflows/demo.yaml
@@ -1,24 +1,40 @@
-name: Gitea Actions Demo
-run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
+name: 🚀 Pack skyscraper8
+run-name: 👷 ${{ gitea.actor }} is building skyscraper8
on: [push]
jobs:
- Explore-Gitea-Actions:
+ make-zip:
runs-on: self-hosted
steps:
- - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- - name: Check out repository code
+ - name: 📥 Check out repository code
uses: actions/checkout@v4
- - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- - run: echo "🖥️ The workflow is now ready to test your code on the runner."
- - name: List files in the repository
- run: |
- ls ${{ gitea.workspace }}
- - name: Show current working directory
+
+ - name: 🛠️ Building the main assembly...
working-directory: ${{ gitea.workspace }}
run: |
- pwd
- ls -d $(pwd -P)/*
- - run: echo "🍏 This job's status is ${{ job.status }}."
+ dotnet build /p:EnableWindowsTargeting=true
+
+ - name: 📄 Typesetting the readme file...
+ working-directory: ${{ gitea.workspace }}/skyscraper8.Manual
+ run: |
+ pdflatex skyscraper8.Manual.tex
+
+ - name: 📦 Packing the main assembly...
+ working-directory: ${{ gitea.workspace }}/skyscraper8/bin/Debug/net8.0
+ run: |
+ 7zz a ${{ gitea.workspace }}/skyscraper8-${{ gitea.sha }}.zip *
+
+ - name: 📚 Packing the manual...
+ working-directory: ${{ gitea.workspace }}/skyscraper8.Manual
+ run: |
+ 7zz a ${{ gitea.workspace }}/skyscraper8-${{ gitea.sha }}.zip skyscraper8.Manual.pdf
+
+ - name: ☁️ Uploading the Package...
+ uses: akkuman/gitea-release-action@v1
+ with:
+ name: ${{ gitea.sha }}
+ md5sum: false
+ sha256sum: false
+ tag_name: release_tag
+ files: |-
+ ${{ gitea.workspace }}/skyscraper8-${{ gitea.sha }}.zip
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 749f173..8130e63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,3 +124,11 @@ imgui.ini
/GUIs/skyscraper8.UI.ImGui.MonoGame/obj
/.vs/skyscraper8/CopilotIndices/17.14.995.13737
/.vs/skyscraper8/config
+/skyscraper8.Manual/skyscraper8.Manual.aux
+/skyscraper8.Manual/skyscraper8.Manual.log
+/skyscraper8.Manual/skyscraper8.Manual.out
+/skyscraper8.Manual/skyscraper8.Manual.pdf
+/skyscraper8.Manual/skyscraper8.Manual.synctex.gz
+/.vs/skyscraper8/CopilotIndices/17.14.1231.31060
+/.vs/skyscraper8/CopilotIndices/17.14.1290.42047
+/Documentation/TSDuck-Samples/experiment2/*.ts
diff --git a/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj b/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj
index 8676a00..85cd7c1 100644
--- a/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj
+++ b/BlobStorages/skyscraper5.Data.Minio/skyscraper5.Data.Minio.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs
index 85cb480..5898e64 100644
--- a/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs
+++ b/DataTableStorages/skyscraper5.Data.PostgreSql/InteractionChannel.cs
@@ -2,7 +2,9 @@
using Npgsql;
using skyscraper5.src.InteractionChannel.Model;
using skyscraper5.src.InteractionChannel.Model.Descriptors;
+using skyscraper5.src.InteractionChannel.Model2;
using skyscraper5.src.Skyscraper.Scraper.Storage.Utilities;
+using skyscraper8.InteractionChannel.Model2;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -969,5 +971,30 @@ namespace skyscraper5.Data.PostgreSql
command.Parameters.AddWithValue("@nid", NpgsqlTypes.NpgsqlDbType.Integer, (int)coordinate.Item1);
command.ExecuteNonQuery();
}
- }
+
+ public bool TestForTerminalBurstTimePlan2(ushort interactiveNetworkId, byte tbtp2GroupId, byte frameFrameNumber)
+ {
+ throw new NotImplementedException();
+ }
+ public void StoreTerminalBurstTimePlan2(ushort interactiveNetworkId, byte tbtp2GroupId, Tbtp2.Frame frame)
+ {
+ throw new NotImplementedException();
+ }
+ public bool TestForFrameComposition2(ushort networkId, Fct2.Frame fct2)
+ {
+ throw new NotImplementedException();
+ }
+ public void InsertFct2Frame(ushort networkId, Fct2.Frame frame)
+ {
+ throw new NotImplementedException();
+ }
+ public bool TestForBroadcastConfiguration(ushort networkId, byte txTypeTxType)
+ {
+ throw new NotImplementedException();
+ }
+ public void InsertBroadcastConfiguration(ushort networkId, Bct.BroadcastConfiguration txType)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
diff --git a/Documentation/TSDuck-Samples/experiment2/run.sh b/Documentation/TSDuck-Samples/experiment2/run.sh
new file mode 100644
index 0000000..50e2a1b
--- /dev/null
+++ b/Documentation/TSDuck-Samples/experiment2/run.sh
@@ -0,0 +1,22 @@
+BITRATE=6000000
+PCR_DISTANCE=6000
+PCR_PID=1001
+
+tsp -v -b $BITRATE \
+ -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 pat -c -a 1/1000 \
+ -P pmt -c -i 1 --pcr-pid $PCR_PID -p 1000 -a 1002/0x0d -a 1003/0x0d -a 1004/0x0d \
+ --add-stream-identifier --set-data-broadcast-id 1002/5 \
+ --set-data-broadcast-id 1003/5 --set-data-broadcast-id 1004/5 \
+ -P inject tdt.xml --pid 0x14 --bitrate 2000 --stuffing \
+ -P timeref --system-synchronous \
+ -P mpeinject -p 1002 -l 127.0.0.2 2000 --max-queue 8192 \
+ -P mpeinject -p 1003 -l 127.0.0.2 3000 --max-queue 8192 \
+ -P history \
+ -P until -s 60 \
+ -O file --max-size 100000000 experiment2-clean-60s.ts
diff --git a/Documentation/TSDuck-Samples/experiment2/tdt.xml b/Documentation/TSDuck-Samples/experiment2/tdt.xml
new file mode 100644
index 0000000..7f831e4
--- /dev/null
+++ b/Documentation/TSDuck-Samples/experiment2/tdt.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/skyscraper8.Manual/skyscraper8.Manual.tex b/skyscraper8.Manual/skyscraper8.Manual.tex
new file mode 100644
index 0000000..ff9d052
--- /dev/null
+++ b/skyscraper8.Manual/skyscraper8.Manual.tex
@@ -0,0 +1,282 @@
+\documentclass[10pt,a4paper]{article}
+\usepackage[utf8]{inputenc}
+\usepackage[english]{babel}
+\usepackage{amsmath}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+\usepackage{xcolor}
+\usepackage{pifont,mdframed}
+\usepackage{verbatim}
+
+
+\usepackage{geometry}
+ \geometry{
+ a4paper,
+ total={170mm,257mm},
+ left=20mm,
+ top=20mm,
+ }
+
+\usepackage{hyperref}
+\hypersetup{
+ colorlinks=true,
+ linkcolor=blue,
+ filecolor=magenta,
+ urlcolor=cyan,
+ pdftitle={skyscraper8 Manual},
+ pdfpagemode=FullScreen,
+ }
+
+
+\author{Fey}
+\title{skyscraper8 \\ A hobbyist project to learn about MPEG-II}
+\date{October 2025}
+
+\NewDocumentCommand{\codeword}{v}{%
+\texttt{\textcolor{blue}{#1}}%
+}
+
+\newenvironment{warning}
+ {\par\begin{mdframed}[linewidth=2pt,linecolor=red]%
+ \begin{list}{}{\leftmargin=1cm
+ \labelwidth=\leftmargin}\item[\Large\ding{43}]\textbf{IMPORTANT:}}
+ {\end{list}\end{mdframed}\par}
+
+
+\begin{document}
+
+\maketitle
+
+Hello! Thanks for your interest in skyscraper8, a program I'm writing to learn about satellite and Cable TV technology, mainly for fun - as a hobby, not out of professionalism.
+
+\section{An explanation of what skyscraper8 is}
+Consider a compression format like Pavlov's 7Z, Roshal's RAR, Katz' ZIP, or a container format like GNU TAR. These formats are designed to store one or multiple files in a larger container file and optionally compress these.
+
+Now consider a program like 7-zip, WinRAR, or WinZIP. These archiving programs are designed to extract files out of containers mentioned above and many other file container formats.
+
+Next, introduce the MPEG-2 Transport Stream\footnote{ISO/IEC 13818-1, ITU-T Recommendation H.222.0}. (hereinafter referred to as "TS") This is a data structure designed to carry video, audio and additional other accompanying data in real-time and for storage. Such additional data may be IP packets, an EPG, an interactive application like a game to play during commercial breaks, receiver system software updates or album art on a radio channel. A broadcaster's imagination is the only real limit here.
+
+As you can probably imagine, most of these things I named exist in a filesystem based structure on a computer. Therefore, since the receivers designed to make use of this data have to extract it from a TS, a standard computer should be able to do so as well. However, there are not many applications out there that can actually do this. \\
+A video playback application like VLC or MPC-HC treat a TS as video file, not an archive, and will play it back - rightfully so. \\
+An archiving program like WinRAR also assumes a TS is a video file and isn't even wrong about that. But that means it wouldn't know how to extract contained files from it.
+
+This is the niche gap skyscraper8 intents to cover: Extracting files transmitted along in a TS.
+
+\section{How to get TS into skyscraper8}
+skyscraper8 is a command-line operated tool. A graphical user interface is planned, but not yet fully developed. \\
+This means that you have to use the command-line interpreter of your operating system to use skyscraper8. On Windows, both cmd.exe and PowerShell are known to work well.
+On Linux and macOS, any halfway recent shell should be fine.
+
+\subsection{Using a StreamReader compatible tuner}
+\textbf{This is the preferred way} and applies to tuners like the popular TBS5927 or TBS6903x.
+\\You can use any StreamReader.dll hosting application like CrazyScan or VMA Stream Reader that is capable of streaming to TCP and have skyscraper8 read that stream.
+\\Use your hosting application and start a TCP stream like you normally would. Then call skyscraper8 from a command line:
+\begin{verbatim}
+.\skyscraper8.exe cscan tcp://127.0.0.1:6969
+\end{verbatim}
+
+\label{sec:gswarning}
+\subsubsection{Working with GS using StreamReader}
+There are a few caveats when working with StreamReader.dll to capture GS.
+
+Some older versions of StreamReader.dll are known to interfere with the STiD135's BBFrame encapsulation feature. Version 1.2.4.101 is a prominent example of doing this. It changes the encapsulation PID and also seems to frame GSE frames differently. Therefore, please make sure to use a recent version of StreamReader.dll. Version 1.2.5.208 is known to work correctly.
+
+Also, make sure to disable StreamReader.dll's internal deencapsulator. You can do this by keeping the semicolon in StreamReader.ini like this
+\begin{verbatim}
+;FrameMode=1
+\end{verbatim}
+or set it to 0 like this:
+\begin{verbatim}
+FrameMode=0
+\end{verbatim}
+
+Why is that? While StreamReader.dll's Deencapsulation works correctly in theory, it does not seem to check whether an encapsulated packet actually contains valid MPEG-II packets and will happily try to deencapsulate all other packets as well, causing a lot of Packets with invalid data to appear in the outputted TS.
+
+
+
+\subsection{Using a BDA compatible tuner}
+Use TSDuck's \codeword{tsp} and use the \codeword{dvb} input plugin and the \codeword{ip} output plugin.
+Here's how to do it:
+
+\begin{enumerate}
+\item Run skyscraper8 from the commandline using the following command:
+\begin{verbatim}
+.\skyscraper8.exe udpin
+\end{verbatim}
+\item Then run \codeword{tsp} like this:
+\begin{verbatim}
+tsp -v -I dvb -a 4 --delivery-system DVB-S2 --frequency 11,523,000,000 \
+ --symbol-rate 22,000,000 --polarity horizontal -O ip 127.0.0.1:9003
+\end{verbatim}
+\end{enumerate}
+
+\subsection{Using a video4linux compatible tuner}
+See the paragraph about BDA tuners. The same applies to video4linux compatible tuners. TSDuck's \codeword{tsp} can be used on Windows and Linux in the same way.
+
+\subsection{Using a SAT\textgreater IP capable tuner}
+This feature is intended for SAT\textgreater IP server devices like the Digital Devices Octopus NET or the Kathrein EXIP 418. If you're using a PCI/PCIe tuner card or an USB Tuner with your PC, these passages about SAT\textgreater IP do not apply to you.
+
+To use skyscraper8 with a SAT\textgreater IP device, open up a shell and use the following command scheme:
+
+\begin{verbatim}
+.\skyscraper8.exe satip IP_ADDRESS DISEQC POLARITY FREQUENCY SYSTEM SYMBOL_RATE
+\end{verbatim}
+
+Replace these variables as follows:
+\\
+\begin{tabular}{llp{5cm}}
+\hline
+IP\_ADDRESS & \parbox{13.5cm}{The IP address (for example 172.20.20.122) of your SAT\textgreater IP server, or "auto" for autodetection of a SAT\textgreater IP server.} \\
+\hline
+DISEQC & \parbox{13.5cm}{The DiSEqC 1.0 Port you want to use. Say "1" here for A/A, "2" for B/A, "3" for A/B, 4 for B/B} \\
+\hline
+POLARITY & \parbox{13.5cm}{Either "H" or "V". The SAT\textgreater IP specification also allows "L" and "R", but this is not yet implemented in skyscraper8.} \\
+\hline
+FREQUENCY & \parbox{13.5cm}{The frequency you want to tune to in MHz. (for example 12226)} \\
+\hline
+SYSTEM & \parbox{13.5cm}{SYSTEM is either "S" or "S2". The SAT\textgreater IP specification does not consider S2X, but in my testings, an S2X capable device like the Octopus NET also accepts "S2" here for S2X transponders. } \\
+\hline
+SYMBOL\_RATE & \parbox{13.5cm}{The symbol rate of the transponder you want to tune to in Ks. (for example 27500)} \\
+\hline
+\end{tabular}\\
+
+So a valid command would be for example:
+
+\begin{verbatim}
+.\skyscraper8.exe satip 172.20.20.122 2 V 12226 S2 27500
+\end{verbatim}
+to tune to the Hotbird DVB-NIP demo transponder, assuming Hotbird is at DiSEqC B/A. \\
+
+If you don't know your SAT\textgreater IP server address, the following command would accomplish the same:
+\begin{verbatim}
+.\skyscraper8.exe satip auto 2 V 12226 S2 27500"
+\end{verbatim}
+
+If your SAT\textgreater IP server features a STiD135 chipset, you can also catch GS/GSE with it.
+
+\subsection{Using prerecorded TS files}
+This is probably the simplest way to get a TS into skyscraper8. Just run the skyscraper8.exe from the commandline passing the path to your TS file as an argument.
+For example:
+\begin{verbatim}
+.\skyscraper8.exe C:\Videos\capture.ts
+\end{verbatim}
+
+If you have a folder containing multiple TS files, you can also pass a directory path as an argument:
+\begin{verbatim}
+.\skyscraper8.exe C:\Videos\Blindscans\
+\end{verbatim}
+This will cause skyscraper8 to process ALL ts files in the specified directory. Note that this will also check subdirectories.
+
+Extracted files will be written to the directory the command-line interpreter has been cd'ed to.
+
+If you are using Microsoft Windows, you can also Drag’n’Drop a TS file onto the skyscraper8.exe. The extracted data will be placed into the same directory the TS is in.
+
+
+\label{sec:cannotdo}
+\section{What skyscraper8 can not do}
+\begin{itemize}
+ \item Watch TV or Listen to Radio. Extracting files and playing back audio/video are two very different things, and skyscraper8 is not designed to do the latter. If this is something you're looking for, you probably want something like ProgDVB or DVBViewer.
+ \item Descramble encrypted channels. This is illegal in most, if not all, jurisdictions, and I want nothing to with that.
+ \item Circumvent copy protections. TS contained on Blu-Ray disc are usually scrambled using AACS. Although not too difficult, bypassing it is also a complicated legal matter, which is why I prefer not doing it.
+ \item Descramble encrypted DOCSIS\footnote{Data-Over-Cable Service Interface Specifications} Traffic. While skyscraper8 does understand DOCSIS traffic and is perfectly capable of sniffing packets from it using tuners like the TBS-6281 SE, it will drop any scrambled packets - meaning those that have the DOCSIS privacy extension enabled.
+ \item Do a professional grade analysis of a TS. There are many intricacies in broadcasting systems which are out of scope for a simple file extractor developed by a mere hobbyist. \hypertarget{bettertools}{If you're looking for a professional grade analyzing tool}, I'd recommend you to check out the tools included in VMA Video Analyzer, Dektec's StreamXpert, GkWare's StreamGuru, or the \codeword{analyze} plugin of TSDuck.
+ \item Handle MPEG-DASH streams. Currently, there are two known video formats used in DVB-NIP: HLS and MPEG-DASH. While skyscraper8 is able to extract segments of both types from a TS, I do not know of an easy way to playback MPEG-DASH formatted streams. HLS on the other hand is trivial.
+ \item Repair broken GS. When using a non STiD135-based tuner to tune to a GS, the framing gets broken in extremely weird ways. Also, when using StreamReader.dll to tune to a GS with a STiD135 - it is imperative to disable to StreamReader.dll's deencapsulator. It often breaks the framing as well. See \hyperref[sec:gswarning]{the notice about GS in Section 2.1}
+\end{itemize}
+
+\section{How skyscraper8 was made}
+skyscraper8 is developed in the C\# programming language, using Microsoft Visual Studio 2022. Therefore it requires a Microsoft .NET runtime, which is included in Microsoft Windows and freely available for most Linux distributions and Apple's macOS.
+
+The .NET assembly of skyscraper8 is not obfuscated or protected in any way. This is on purpose. If you want to study how skyscraper8 works under the hood, I hereby allow you to use tools like RedGate's Reflector or JetBrains' dotPeek to inspect the skyscraper8.dll file. I plan to release the clear source code at a later date. \\
+
+Although I can't and won't force you to give credit, if you find some of my source code inspiring or helpful and want to use it in your own projects, it would be great if you give credit to where you got it from. \\
+
+This document was typeset in \LaTeX{}, using the TeX Live distribution, and while I wrote it myself, I did use GPT-5 for proofreading the initial version of this document. The proofreading of this document is the only part of skyscraper8 for which an LLM was used. No part of the actual skyscraper8 codebase itself was written by an LLM. \\
+
+For generating artificial TS recordings to test on, TSDuck was used. \\
+
+\subsection{Experiments conducted using TSDuck}
+
+\subsubsection {Creating a TS that carried multiple sub-TS using MPE}
+
+Since skyscraper8 public release 12, it's possible to have skyscraper8 extract one or more TS carried via Multiprotocol Encapsulation. Unfortunately the dish I have at home is not aimed at satellites carrying such a stream. So I used TSDuck to build one artificially. \\
+
+The following script was used to to run \codeword{tsp}:
+
+\verbatiminput{../Documentation/TSDuck-Samples/experiment2/run.sh}
+
+Contents of tdt.xml:
+
+\verbatiminput{../Documentation/TSDuck-Samples/experiment2/tdt.xml}
+
+Before executing that script, run this beforehand, to get a stream of Stingray Spa, of course you can use any other IPTV station you'd like:
+
+\begin{verbatim}
+tsp -v -I hls https://lotus.stingray.com/manifest/ose-122ads-montreal/samsungtvplus/master.m3u8 \
+ -P regulate -P history -O ip 127.0.0.2:3000
+\end{verbatim}
+
+For the other stream, I used Offener Kanal Berlin, like so:
+
+\begin{verbatim}
+tsp -v -I hls https://alex-stream.rosebud-media.de/bounce/alexlivetv50.smil/index.m3u8 \
+ -P regulate -P history -O ip 127.0.0.2:2000
+\end{verbatim}
+
+Finally, run the script above to get a clean TS containing two other TS encapsulated in MPE.
+
+
+\subsection{Personal remarks and some useless bonus information}
+
+\subsubsection{Credits}
+skyscraper8 uses some external libraries:
+\begin{itemize}
+ \item One of the external libraries is Ionic.Zlib.Core.dll, which is part of the \href{https://github.com/DinoChiesa/DotNetZip-2025}{DotNetZip} tool set by Dino Chiesa.
+ \item Also used is Newtonsoft.Json.dll a.k.a. \href{https://github.com/JamesNK/Newtonsoft.Json}{Json.NET} by James Newton-King.
+ \item Some of the GS handling code got inspired from the \href{https://github.com/newspaperman/bbframe-tools}{bbframe-tools} written by newspaperman.
+\end{itemize}
+
+\subsubsection{How and why skyscraper8 began: A young man's dream}
+Ever since I was a child, I was fascinated by TV. But not actually watching it, rather understanding how it works. I grew up in the 90s, in an area where Cable TV was not really available, and terristial reception (yeah, those TVs with bunny ears!) only worked when it felt like it. So satellite was the best way to get my childish fix of cartoons! We can say the dish on my roof has always been a companion. Far more interesting than actually watching TV was scanning through the printed TV guides. I was curious. How did they make these? And how would I make one myself? I wondered. Of course my parents wouldn't know - they're not techies. \\
+
+Of course, as I got older, I eventually moved onto other things. Like programming, trading cards, and video games. But just like with the TV, I eventually got more interested in how these games work, instead of actually playing them. \\
+
+I got internet access much later in my life than my peers did, and when I found out that there are forums in which people discuss the technicalities about video games, it blew my mind. I used to be a frequent reader (and occasional contributor) of the XeNTaX\footnote{Unfortunately, that forum is long gone from the internet, but can still be experienced thanks to the amazing work of the people over at the Internet Archive: \url{https://web.archive.org/web/20230925120533/https://forum.xentax.com/ }} forum, from where I learned a lot! \\
+
+At the time, my circle of friends was crazy about Yu-Gi-Oh! Online 3, which was an excellent simulation of the trading card game by the same name. I was really bad at this game, not even a mediocre player. I was far more busy studying how the game worked under the hood, and eventually managed to write a program that extracts all the card graphics and the soundtrack of the game. This resulted in my guild leader having the best quality available soundtrack rips on his Youtube account. As you can probably guess, I went on and reverse engineered more and more video games. But as life goes on, you eventually graduate from school, head to university, and then get a job. Interests and people can change over time, and so did I. As I grew older I kind of lost interest in video games. Nowadays I rarely play anymore. \\
+
+But what has that to do with skyscraper8? That's easy! One day my parents, who I still stay in touch with asked me to record something off live TV. Up until that point, I totally forgot about the dish on the roof of the house my room is in, but at that point it got relevant again. Upon searching for an easy way to record satellite TV on a PC I stumbbled upon Thierry Lelegard's tsduck. With tsp's plugins, it was real easy to schedule a recording. And since I still had the natural curiosity of a game reverse engineer in me, I opened up the .ts file in a hex-editor, and that's exactly the point in time when the DX virus hit me. After reading a bit of the source code of \href{https://images.videolan.org/developers/libdvbpsi.html}{libdvbpsi}, an idea manifested in my head. And that idea became "skyscraper".
+
+\subsubsection{A Brief history of skyscrapers1 to 8}
+At that time had a job of administering some databases. My childhood fantasies of making my own TV guide were now within reach. I had "skyscraper" at home, a bunch of databases at work - and MySQL is free someware. That was shortly before COVID-19 ran wild in the world. During lockdown I had made it: A program that reads a list of frequencies from a database, record a series of TS files, parses all the DVB tables, and writes the results into the database. That was all the first skyscraper could do. It was written in Java, and missed a lot of things.
+
+skyscraper5 came about when I changed jobs. I was a software developer at that time. And since we used Java at my day job, and I wanted some distance from my job, I switched my hobbyist projects back to .NET. - .NET 5 to be exact - that's why I called it skyscraper5. That was on the 5th of March, 2022.
+
+Eventually, I upgraded to .NET 8, mainly due to dependency issues, and I renamed the project fittingly to skyscraper8. \\
+
+If you read up to this point, I wish to thank you from the bottom of my heart. I guess by now you can tell that skyscraper8 is a very personal and dear matter for me. My goal was never to make the best tool for handling TS files, there are \hyperlink{bettertools}{much better options} regarding that, but to satisfy my desire to learn. And let me tell you, I learned a lot during the development of skyscraper8, both as a person and as a programmer.
+
+
+
+\subsubsection{Music!}
+Like a lot of programmers, I do enjoy listening to music while working. Some programmers even put song references in their software. Like how MKVToolnix' version names are actually song names, or how BSD developer fk even put \href{https://www.fabiankeil.de/nutzloseinfos.html}{a list of albums on his website} listing what albums he listened to while making it. Although this is absolutely useless, I'd liketo do this as well. Therefore, here follows a list of musical albums I enjoyed listening to while developing skyscraper8 - no claim to completeness.
+
+\begin{itemize}
+ \item Bel Canto - White-Out Conditions
+ \item Beautiful World - In Existance
+ \item Kitaro - Millennia
+ \item Macross 82-99 - A Million Miles Away
+ \item Megazone 23 Vocal Collection
+ \item Moodswings - Moodfood
+ \item Opus III - Guru Mother
+ \item Satsuki Shibano / Yoshio Ojima - Caresse
+ \item SHIFT UP - Stellar Blade Original Soundtrack
+ \item Small Affairs - Small Affairs
+ \item Starmani Series - la fillette révolutionnaire UTENA
+ \item SuganoMusic - EUROBEAT FESTIVAL VOL.7.5
+ \item Yoshio Ojima - Hands-Some
+ \item Youssou N’Dour - The Guide (Wommat)
+ \item Yu-Gi-Oh! Sound Duel Quarter Century Collection
+\end{itemize}
+
+\end{document}
\ No newline at end of file
diff --git a/skyscraper8.sln.DotSettings.user b/skyscraper8.sln.DotSettings.user
index f031d72..296c055 100644
--- a/skyscraper8.sln.DotSettings.user
+++ b/skyscraper8.sln.DotSettings.user
@@ -1,2 +1,12 @@
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded<data><HostParameters type="LocalHostParameters" /><Argument type="StandaloneArgument"><Arguments IsNull="False"></Arguments><FileName IsNull="False"></FileName><WorkingDirectory IsNull="False"></WorkingDirectory><Scope><ProcessFilters /></Scope></Argument><Info type="TimelineInfo" /><CoreOptions type="CoreOptions"><CoreTempPath IsNull="False"></CoreTempPath><RemoteEndPoint IsNull="False"></RemoteEndPoint><AdditionalEnvironmentVariables /></CoreOptions><HostOptions type="HostOptions"><HostTempPath IsNull="False"></HostTempPath></HostOptions></data>
\ No newline at end of file
diff --git a/skyscraper8/Abertis/AbertisSubSkyscraperKey.cs b/skyscraper8/Abertis/AbertisSubSkyscraperKey.cs
new file mode 100644
index 0000000..0c97cb5
--- /dev/null
+++ b/skyscraper8/Abertis/AbertisSubSkyscraperKey.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace skyscraper8.Abertis
+{
+ internal class AbertisSubSkyscraperKey
+ {
+ public int Pid { get; }
+
+ public AbertisSubSkyscraperKey(int pid)
+ {
+ Pid = pid;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0:X4}", Pid);
+ }
+ }
+}
diff --git a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs
index b99bdaf..09fb4cb 100644
--- a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs
+++ b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs
@@ -7,7 +7,7 @@ using skyscraper5.Ietf.Rfc971;
namespace skyscraper5.Dvb.DataBroadcasting
{
- interface IMultiprotocolEncapsulationEventHandler
+ public interface IMultiprotocolEncapsulationEventHandler
{
void OnIpv4PacketArrival(InternetHeader internetHeader, byte[] ipv4Packet);
void OnIpDatagram(int sourcePid, byte[] payload);
diff --git a/skyscraper8/DvbNip/DvbNipReceiver.cs b/skyscraper8/DvbNip/DvbNipReceiver.cs
index b10ca4f..ed89f9d 100644
--- a/skyscraper8/DvbNip/DvbNipReceiver.cs
+++ b/skyscraper8/DvbNip/DvbNipReceiver.cs
@@ -108,9 +108,9 @@ namespace skyscraper8.DvbNip
if (isValidXml)
{
FDTInstanceType fdtAnnouncement = FluteUtilities.UnpackFluteFdt(fluteStream);
- EventHandler.FluteFileAnnouncement(fluteCoordinate.Item1, fluteCoordinate.Item2, fdtAnnouncement);
if (fdtAnnouncement != null)
{
+ EventHandler.FluteFileAnnouncement(fluteCoordinate.Item1, fluteCoordinate.Item2, fdtAnnouncement);
SetFileAssociations(fluteListener, fdtAnnouncement);
}
}
diff --git a/skyscraper8/GS/BBHeader.cs b/skyscraper8/GS/BBHeader.cs
new file mode 100644
index 0000000..13228a3
--- /dev/null
+++ b/skyscraper8/GS/BBHeader.cs
@@ -0,0 +1,82 @@
+using skyscraper5.Skyscraper;
+
+namespace skyscraper8.GSE;
+
+public class BBHeader : Validatable
+{
+ public BBHeader(byte[] bbframe, int offset)
+ {
+ ReadOnlySpan BbHeaderSpan = new ReadOnlySpan(bbframe, offset, 10);
+
+ //byte 1, MATYPE-1
+ TsGs = (BbHeaderSpan[0] & 0xc0) >> 6;
+ SisMis = (BbHeaderSpan[0] & 0x20) != 0; //1 single, 0 multi
+ CcmAcm = (BbHeaderSpan[0] & 0x10) != 0; //1 ccm, 0 acm
+ Issyi = (BbHeaderSpan[0] & 0x08) != 0; //
+ Npd = (BbHeaderSpan[0] & 0x04) != 0;
+ Ro = (BbHeaderSpan[0] & 0x03);
+
+ Matype2 = (BbHeaderSpan[1]);
+
+ UserPacketLength = BbHeaderSpan[2] << 8 | BbHeaderSpan[3];
+ UserPacketLength /= 8;
+
+ DataFieldLength = BbHeaderSpan[4] << 8 | BbHeaderSpan[5];
+ DataFieldLength /= 8;
+
+ SyncByte = BbHeaderSpan[6];
+
+ SyncD = BbHeaderSpan[7] << 8 | BbHeaderSpan[8];
+ SyncD /= 8;
+
+ //ChecksumValid = DvbCrc8.Compute(BbHeaderSpan) == 0;
+ //Valid = ChecksumValid;
+ Valid = true;
+ }
+
+ public int SyncD { get; private set; }
+
+ public byte SyncByte { get; private set; }
+
+ public int DataFieldLength { get; private set; }
+
+ public int UserPacketLength { get; private set; }
+
+ ///
+ /// If MIS, this is the Input Stream Identifier
+ ///
+ public byte Matype2 { get; private set; }
+
+ ///
+ /// Transmission Roll-off factor
+ ///
+ public int Ro { get; private set; }
+
+ ///
+ /// Null Packet Deletion
+ ///
+ public bool Npd { get; private set; }
+
+ ///
+ /// If true, the ISSY field is inserted after UP
+ ///
+ public bool Issyi { get; set; }
+
+ ///
+ /// True if CCM, False if ACM
+ ///
+ public bool CcmAcm { get; private set; }
+
+ ///
+ /// True if SIS, False if MIS
+ ///
+ public bool SisMis { get; private set; }
+
+ ///
+ /// 0 = GS, packetized
+ /// 1 = GS, continuous
+ /// 2 = GSE-HEM
+ /// 3 = Transport
+ ///
+ public int TsGs { get; private set; }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/BBframeDeencapsulator3.cs b/skyscraper8/GS/BBframeDeencapsulator3.cs
new file mode 100644
index 0000000..4031a3b
--- /dev/null
+++ b/skyscraper8/GS/BBframeDeencapsulator3.cs
@@ -0,0 +1,52 @@
+using log4net;
+using skyscraper5.Dvb.DataBroadcasting;
+using skyscraper8.Skyscraper.Scraper;
+
+namespace skyscraper8.GSE;
+
+public class BbframeDeencapsulator3 : IBbframeDeencapsulator
+{
+ public BbframeDeencapsulator3(IMultiprotocolEncapsulationEventHandler mpeEventHandler, ISubTsHandler subTsHandler)
+ {
+ _mpeEventHandler = mpeEventHandler;
+ _subTsHandler = subTsHandler;
+ }
+
+ private readonly ISubTsHandler _subTsHandler;
+ private readonly IMultiprotocolEncapsulationEventHandler _mpeEventHandler;
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+ private long numPushed;
+ public void PushPacket(byte[] bbframe)
+ {
+ //byte 0, sync byte
+ if (bbframe[0] != 0xb8)
+ {
+ if (numPushed == 0)
+ {
+ logger.InfoFormat("The stream started in the middle of a BBFrame, let's skip to the start of the next one.");
+ }
+ return;
+ }
+ numPushed++;
+
+ BBHeader bbHeader = new BBHeader(bbframe, 1);
+ if (!bbHeader.Valid)
+ return;
+
+ if (mis == null)
+ mis = new IMisHandler[256];
+ if (mis[bbHeader.Matype2] == null)
+ {
+ logger.InfoFormat("Found a stream on MIS {0}",bbHeader.Matype2);
+ mis[bbHeader.Matype2] = new GsTypeDetector(bbHeader.Matype2)
+ {
+ mpeEventHandler = this._mpeEventHandler,
+ subTsHandler = this._subTsHandler
+ };
+ }
+
+ mis[bbHeader.Matype2].PushFrame(bbHeader, new ReadOnlySpan(bbframe, 11, bbframe.Length - 11));
+ }
+
+ private IMisHandler[] mis;
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/BbframeDumper.cs b/skyscraper8/GS/BbframeDumper.cs
new file mode 100644
index 0000000..37caf26
--- /dev/null
+++ b/skyscraper8/GS/BbframeDumper.cs
@@ -0,0 +1,26 @@
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.GSE;
+
+public class BbframeDumper : IBbframeDeencapsulator, IDisposable
+{
+ public BbframeDumper(FileInfo file)
+ {
+ file.Directory.EnsureExists();
+ ourStream = file.OpenWrite();
+ }
+
+ private FileStream ourStream;
+
+ public void PushPacket(byte[] bbframe)
+ {
+ ourStream.Write(bbframe, 0, bbframe.Length);
+ }
+
+ public void Dispose()
+ {
+ ourStream.Flush();
+ ourStream.Close();
+ ourStream.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/Crc8.cs b/skyscraper8/GS/Crc8.cs
new file mode 100644
index 0000000..994613e
--- /dev/null
+++ b/skyscraper8/GS/Crc8.cs
@@ -0,0 +1,26 @@
+namespace skyscraper8.GSE;
+
+public static class DvbCrc8
+{
+ private const byte Polynomial = 0xD5; // x^8 + x^7 + x^6 + x^4 + x^2 + 1
+
+ public static byte Compute(ReadOnlySpan data)
+ {
+ byte crc = 0x00;
+
+ foreach (byte b in data)
+ {
+ crc ^= b; // XOR byte into top of crc register
+
+ for (int i = 0; i < 8; i++)
+ {
+ if ((crc & 0x80) != 0)
+ crc = (byte)((crc << 1) ^ Polynomial);
+ else
+ crc <<= 1;
+ }
+ }
+
+ return crc;
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GSE-HEM/GseHemReader.cs b/skyscraper8/GS/GSE-HEM/GseHemReader.cs
new file mode 100644
index 0000000..7f6b9b8
--- /dev/null
+++ b/skyscraper8/GS/GSE-HEM/GseHemReader.cs
@@ -0,0 +1,113 @@
+using log4net;
+using skyscraper5.Dvb.DataBroadcasting;
+using skyscraper5.Skyscraper.IO;
+using skyscraper8.GSE.GSE;
+
+namespace skyscraper8.GSE.GSE_HEM;
+
+public class GseHemReader : IMisHandler
+{
+ public GseHemReader(IMultiprotocolEncapsulationEventHandler mpeEventHandler)
+ {
+ rayBuffer = new RayBuffer();
+ this.mpeEventHandler = mpeEventHandler;
+ }
+
+ private IMultiprotocolEncapsulationEventHandler mpeEventHandler;
+ private GseLabel lastLabel;
+ private RayBuffer rayBuffer;
+ private const int BUFFER_THRESHOLD = 65536 * 2; //Twice the maximum PDU size of GSE. That way we can guarantee we have one full PDU in buffer.
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+
+ public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan)
+ {
+ MemoryStream ms = new MemoryStream(readOnlySpan.ToArray());
+ rayBuffer.Enqueue(ms, bbHeader.SyncD);
+
+ while (rayBuffer.AvailableBytes > BUFFER_THRESHOLD)
+ {
+ byte a = rayBuffer.ReadUInt8();
+ bool startIndicator = (a & 0x80) != 0;
+ bool endIndicator = (a & 0x40) != 0;
+ int labelTypeIndicator = (a & 0x30) >> 4;
+ if (!startIndicator && !endIndicator)
+ {
+ //Padding packets probably shouldn't happen in HEM.
+ rayBuffer.Resync();
+ return;
+ }
+ else
+ {
+ GsePacket child = new GsePacket(startIndicator, endIndicator, labelTypeIndicator);
+
+ int gseLength = (a & 0x0f) << 8;
+ gseLength += rayBuffer.ReadUInt8();
+ if (!startIndicator || !endIndicator)
+ {
+ //According to ETSI TS 102 606-1 V1.2.1, HEM does not fragment packets. So we lost our sync!
+ rayBuffer.Resync();
+ return;
+ }
+
+ if (startIndicator && !endIndicator)
+ {
+ throw new NotImplementedException("I didn't expect an open-ended GSE Packet in GSE-HEM. Please share a sample of this stream, so I can implement this.");
+ }
+
+ if (startIndicator)
+ {
+ child.ProtocolType = rayBuffer.ReadUInt16BE();
+ gseLength -= 2;
+ switch (labelTypeIndicator)
+ {
+ case 0:
+ child.Label = new _6byteLabel(rayBuffer.ReadBytes(6));
+ gseLength -= 6;
+ lastLabel = child.Label;
+ break;
+ case 1:
+ child.Label = new _3byteLabel(rayBuffer.ReadBytes(3));
+ gseLength -= 3;
+ lastLabel = child.Label;
+ break;
+ case 2:
+ child.Label = BroadcastLabel.GetInstance();
+ break;
+ case 3:
+ child.Label = lastLabel;
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown label type indicator: {0}", labelTypeIndicator));
+ }
+ }
+
+ if (!startIndicator && endIndicator)
+ gseLength -= 4;
+
+ child.GseDataBytes = rayBuffer.ReadBytes(gseLength);
+
+ if (!startIndicator && endIndicator)
+ throw new NotImplementedException("I didn't expect a GSE packet with a missing head in GSE-HEM. Please share a sample of this stream, so I can implement this.");
+
+ HandlePacket(child);
+ }
+ }
+ }
+
+ private void HandlePacket(GsePacket packet)
+ {
+ if (packet.StartIndicator && packet.EndIndicator)
+ {
+ switch (packet.ProtocolType)
+ {
+ case 0x0800:
+ mpeEventHandler.OnIpDatagram(0x010e,packet.GseDataBytes);
+ return;
+ default:
+ logger.WarnFormat("This GSE-HEM stream contains traffic other than IP. IP is type 0x0800, but we got 0x{0:X4} here.", packet.ProtocolType);
+ return;
+ }
+ }
+ throw new NotImplementedException(packet.ToString());
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GSE-HEM/RayBuffer.cs b/skyscraper8/GS/GSE-HEM/RayBuffer.cs
new file mode 100644
index 0000000..4ddf0b9
--- /dev/null
+++ b/skyscraper8/GS/GSE-HEM/RayBuffer.cs
@@ -0,0 +1,111 @@
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.GSE.GSE_HEM;
+
+public class RayBuffer
+{
+ private MemoryStream currentItem;
+ private Queue> queue;
+
+ public int QueuedItems
+ {
+ get
+ {
+ int result = 0;
+ if (currentItem != null)
+ result++;
+ if (queue != null)
+ result += queue.Count;
+ return result;
+ }
+ }
+
+ public void Enqueue(MemoryStream ms, int syncPoint)
+ {
+ if (currentItem == null)
+ {
+ currentItem = ms;
+ currentItem.Position = syncPoint;
+ return;
+ }
+ else
+ {
+ if (queue == null)
+ queue = new Queue>();
+ queue.Enqueue(new Tuple(ms, syncPoint));
+ }
+ }
+
+ public long AvailableBytes
+ {
+ get
+ {
+ if (currentItem == null)
+ return 0;
+
+ long result = currentItem.GetAvailableBytes();
+ if (queue != null)
+ {
+ result += queue.Select(x => x.Item1.GetAvailableBytes()).Sum();
+ }
+
+ return result;
+ }
+ }
+
+public byte ReadUInt8()
+ {
+ byte[] tmp = new byte[1];
+ if (Read(tmp, 0, 1) != 1)
+ throw new IOException("ReadUInt8 failed");
+ return tmp[0];
+ }
+
+ private int Read(byte[] outBuffer, int offset, int count)
+ {
+ int result = 0;
+ while (count > 0)
+ {
+ int stepSize = Math.Min(count, (int)currentItem.GetAvailableBytes());
+ int stepResult = currentItem.Read(outBuffer, offset, stepSize);
+ offset += stepResult;
+ count -= stepResult;
+ result += stepResult;
+ if (currentItem.GetAvailableBytes() == 0)
+ {
+ currentItem = queue.Dequeue().Item1;
+ }
+ }
+
+ return result;
+ }
+
+ public ushort ReadUInt16BE()
+ {
+ byte[] tmp = new byte[2];
+ if (Read(tmp, 0, 2) != 2)
+ throw new IOException("ReadUInt16BE failed");
+ if (BitConverter.IsLittleEndian)
+ (tmp[0], tmp[1]) = (tmp[1], tmp[0]);
+ return BitConverter.ToUInt16(tmp, 0);
+ }
+
+ public byte[] ReadBytes(int p0)
+ {
+ byte[] tmp = new byte[p0];
+ if (Read(tmp, 0, p0) != p0)
+ throw new IOException(String.Format("Reading {0} bytes failed.", p0));
+ return tmp;
+ }
+
+ public void Resync()
+ {
+ Tuple tuple = queue.Dequeue();
+ currentItem = tuple.Item1;
+ currentItem.Position = tuple.Item2;
+ if (currentItem.Position > currentItem.Length)
+ {
+ Resync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GSE/GseFragmentation.cs b/skyscraper8/GS/GSE/GseFragmentation.cs
new file mode 100644
index 0000000..838a65a
--- /dev/null
+++ b/skyscraper8/GS/GSE/GseFragmentation.cs
@@ -0,0 +1,44 @@
+namespace skyscraper8.GSE.GSE;
+
+public class GseFragmentation
+{
+ public GseFragmentation(GsePacket child)
+ {
+ this.ProtocolType = child.ProtocolType.Value;
+ this.Label = child.Label;
+ AddFragement(child);
+ }
+
+ private List fragments;
+
+ public void AddFragement(GsePacket packet)
+ {
+ if (fragments == null)
+ fragments = new List();
+
+ fragments.Add(packet.GseDataBytes);
+ }
+
+ public GseLabel Label { get; private set; }
+
+ public ushort ProtocolType { get; private set; }
+
+ public bool Validate()
+ {
+ //To be implemented...
+ return true;
+ }
+
+ public byte[] GetGseDataBytes()
+ {
+ int totalLength = fragments.Select(x => x.Length).Sum();
+ byte[] buffer = new byte[totalLength];
+ int offset = 0;
+ for (int i = 0; i < fragments.Count; i++)
+ {
+ Array.Copy(fragments[i],0,buffer,offset,fragments[i].Length);
+ offset += fragments[i].Length;
+ }
+ return buffer;
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GSE/GseLabel.cs b/skyscraper8/GS/GSE/GseLabel.cs
new file mode 100644
index 0000000..563c871
--- /dev/null
+++ b/skyscraper8/GS/GSE/GseLabel.cs
@@ -0,0 +1,65 @@
+using System.Net.NetworkInformation;
+
+namespace skyscraper8.GSE.GSE;
+
+public abstract class GseLabel
+{
+ public abstract int Length { get; }
+ public abstract string ToString();
+}
+
+public class _6byteLabel : GseLabel
+{
+ public _6byteLabel(byte[] buffer)
+ {
+ MAC = new PhysicalAddress(buffer);
+ }
+
+ public PhysicalAddress MAC { get; private set; }
+
+ override public string ToString()
+ {
+ return MAC.ToString();
+ }
+
+ public override int Length => 6;
+}
+
+public class _3byteLabel : GseLabel
+{
+ public _3byteLabel(byte[] buffer)
+ {
+ Label = buffer;
+ }
+
+ public byte[] Label { get; private set; }
+
+ override public string ToString()
+ {
+ return BitConverter.ToString(Label);
+ }
+
+ override public int Length => 3;
+}
+
+public class BroadcastLabel : GseLabel
+{
+ private BroadcastLabel() {}
+
+ override public string ToString()
+ {
+ return "";
+ }
+
+ override public int Length => 0;
+
+ private static BroadcastLabel _instance;
+ public static BroadcastLabel GetInstance()
+ {
+ if (_instance == null)
+ {
+ _instance = new BroadcastLabel();
+ }
+ return _instance;
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GSE/GsePacket.cs b/skyscraper8/GS/GSE/GsePacket.cs
new file mode 100644
index 0000000..0b1bdf5
--- /dev/null
+++ b/skyscraper8/GS/GSE/GsePacket.cs
@@ -0,0 +1,27 @@
+namespace skyscraper8.GSE.GSE;
+
+public class GsePacket
+{
+ public bool StartIndicator { get; }
+ public bool EndIndicator { get; }
+ public int LabelTypeIndicator { get; }
+ public byte? FragmentId { get; set; }
+ public ushort? TotalLength { get; set; }
+ public ushort? ProtocolType { get; set; }
+ public GseLabel Label { get; set; }
+ public byte[] GseDataBytes { get; set; }
+ public uint Crc32 { get; set; }
+
+ public GsePacket(bool startIndicator, bool endIndicator, int labelTypeIndicator)
+ {
+ StartIndicator = startIndicator;
+ EndIndicator = endIndicator;
+ LabelTypeIndicator = labelTypeIndicator;
+ }
+
+ public override string ToString()
+ {
+ return
+ $"{nameof(StartIndicator)}: {StartIndicator}, {nameof(EndIndicator)}: {EndIndicator}, {nameof(LabelTypeIndicator)}: {LabelTypeIndicator}, {nameof(FragmentId)}: {FragmentId}, {nameof(TotalLength)}: {TotalLength}, {nameof(ProtocolType)}: {ProtocolType}, {nameof(Label)}: {Label}";
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GSE/GseReader.cs b/skyscraper8/GS/GSE/GseReader.cs
new file mode 100644
index 0000000..e2272d1
--- /dev/null
+++ b/skyscraper8/GS/GSE/GseReader.cs
@@ -0,0 +1,145 @@
+using log4net;
+using skyscraper5.Dvb.DataBroadcasting;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.GSE.GSE;
+
+internal class GseReader : IMisHandler
+{
+ private GseLabel lastLabel;
+ public IMultiprotocolEncapsulationEventHandler mpeEventHandler { get; set; }
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+ public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan)
+ {
+ MemoryStream ms = new MemoryStream(readOnlySpan.ToArray());
+
+ while (ms.Position < ms.Length)
+ {
+ byte a = ms.ReadUInt8();
+ bool startIndicator = (a & 0x80) != 0;
+ bool endIndicator = (a & 0x40) != 0;
+ int labelTypeIndicator = (a & 0x30) >> 4;
+ if (!startIndicator && !endIndicator && labelTypeIndicator == 0)
+ {
+ //end of BBFrame
+ return;
+ }
+ else
+ {
+ int gseLength = (a & 0x0f);
+ gseLength <<= 8;
+ gseLength += ms.ReadUInt8();
+
+ GsePacket child = new GsePacket(startIndicator, endIndicator, labelTypeIndicator);
+ if (!startIndicator || !endIndicator)
+ {
+ child.FragmentId = ms.ReadUInt8();
+ gseLength--;
+ }
+
+ if (startIndicator && !endIndicator)
+ {
+ child.TotalLength = ms.ReadUInt16BE();
+ gseLength -= 2;
+ }
+
+ if (startIndicator)
+ {
+ child.ProtocolType = ms.ReadUInt16BE();
+ gseLength -= 2;
+ if (!endIndicator)
+ child.TotalLength -= 2;
+ switch (labelTypeIndicator)
+ {
+ case 0:
+ child.Label = new _6byteLabel(ms.ReadBytes(6));
+ gseLength -= 6;
+ if (!endIndicator)
+ child.TotalLength -= 6;
+ lastLabel = child.Label;
+ break;
+ case 1:
+ child.Label = new _3byteLabel(ms.ReadBytes(3));
+ gseLength -= 3;
+ if (!endIndicator)
+ child.TotalLength -= 3;
+ lastLabel = child.Label;
+ break;
+ case 2:
+ child.Label = BroadcastLabel.GetInstance();
+ break;
+ case 3:
+ child.Label = this.lastLabel;
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown label type: {0}", labelTypeIndicator));
+ }
+ }
+
+ if (!startIndicator && endIndicator)
+ gseLength -= 4; //for crc32
+
+ if (gseLength > ms.GetAvailableBytes())
+ return;
+ child.GseDataBytes = ms.ReadBytes(gseLength);
+
+ if (!startIndicator && endIndicator)
+ child.Crc32 = ms.ReadUInt32BE();
+
+ ProcessPacket(child);
+ }
+ }
+ }
+
+ private bool[] warnedEthertypes;
+ private GseFragmentation[] fragmentations;
+ private void ProcessPacket(GsePacket child)
+ {
+ if (!child.StartIndicator && child.EndIndicator)
+ {
+ if (fragmentations == null)
+ return;
+ GseFragmentation currentFragmentation = fragmentations[child.FragmentId.Value];
+ if (currentFragmentation == null)
+ return;
+ currentFragmentation.AddFragement(child);
+ if (currentFragmentation.Validate())
+ {
+ mpeEventHandler.OnIpDatagram(0x010e,currentFragmentation.GetGseDataBytes());
+ }
+
+ fragmentations[child.FragmentId.Value] = null;
+ return;
+ }
+
+ if (child.StartIndicator && !child.EndIndicator)
+ {
+ if (fragmentations == null)
+ fragmentations = new GseFragmentation[256];
+ fragmentations[child.FragmentId.Value] = new GseFragmentation(child);
+ return;
+ }
+
+ if (child.StartIndicator && child.EndIndicator)
+ {
+ switch (child.ProtocolType)
+ {
+ case 0x0800:
+ mpeEventHandler.OnIpDatagram(0x010e, child.GseDataBytes);
+ break;
+ default:
+ if (warnedEthertypes == null)
+ warnedEthertypes = new bool[0xffff];
+ if (!warnedEthertypes[child.ProtocolType.Value])
+ {
+ logger.WarnFormat("This GSE contains other traffic types (type {0:X4}) besides IP (type 0x0800). If it's not too much trouble, please consider submitting a sample.",child.ProtocolType.Value);
+ warnedEthertypes[child.ProtocolType.Value] = true;
+ }
+ break;
+ }
+
+ return;
+ }
+ throw new NotImplementedException(child.ToString());
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/GsTypeDetector.cs b/skyscraper8/GS/GsTypeDetector.cs
new file mode 100644
index 0000000..900af8c
--- /dev/null
+++ b/skyscraper8/GS/GsTypeDetector.cs
@@ -0,0 +1,95 @@
+using log4net;
+using skyscraper5.Dvb.DataBroadcasting;
+using skyscraper8.GS.SiminnRadiomidun;
+using skyscraper8.GSE.GSE_HEM;
+using skyscraper8.GSE.GSE;
+using skyscraper8.Skyscraper.Scraper;
+
+namespace skyscraper8.GSE;
+
+public class GsTypeDetector : IMisHandler
+{
+ public IMultiprotocolEncapsulationEventHandler mpeEventHandler { get; set; }
+ private readonly byte _misId;
+ private readonly ILog logger;
+ private HashSet> _postedStreamTypes;
+ public ISubTsHandler subTsHandler;
+ public GsTypeDetector(byte misId)
+ {
+ _misId = misId;
+ logger = LogManager.GetLogger(String.Format("{0} MIS {1}",System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name,misId));
+ }
+
+ private GseReader gseReader;
+ private GseHemReader gseHemReader;
+ private SiminnRadiomidunReader siminnRadiomidunReader;
+ private int simminRadiomidunScore;
+
+ public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan)
+ {
+ if (readOnlySpan.Length == 0)
+ {
+ return;
+ }
+
+ if (bbHeader.TsGs == 1 && bbHeader.SyncByte == 0)
+ {
+ //Looks like Continuous GSE.
+ if (gseReader == null)
+ {
+ gseReader = new GseReader();
+ gseReader.mpeEventHandler = mpeEventHandler;
+ }
+
+ gseReader.PushFrame(bbHeader, readOnlySpan);
+ return;
+ }
+
+ if (bbHeader.TsGs == 2 && bbHeader.SyncByte == 0)
+ {
+ //Looks like GSE-HEM
+ if (gseHemReader == null)
+ {
+ gseHemReader = new GseHemReader(mpeEventHandler);
+ }
+ gseHemReader.PushFrame(bbHeader, readOnlySpan);
+ return;
+ }
+
+ if (bbHeader.TsGs == 0 && bbHeader.SyncByte == 71 && bbHeader.UserPacketLength == 188)
+ {
+ simminRadiomidunScore++;
+ if (simminRadiomidunScore > 10)
+ {
+ //Looks like a Simmin Radiomidun-like stream.
+ if (siminnRadiomidunReader == null)
+ {
+ //These behave similar to T2-MI. Interesting.
+ siminnRadiomidunReader = new SiminnRadiomidunReader(_misId,subTsHandler);
+ }
+
+ siminnRadiomidunReader.PushFrame(bbHeader, readOnlySpan);
+ return;
+ }
+
+ return;
+ }
+ else
+ {
+ if (simminRadiomidunScore < 10)
+ {
+ simminRadiomidunScore = 0;
+ }
+ }
+
+ //We have no idea what this is.
+ Tuple streamTypeToPost = new Tuple(bbHeader.TsGs, bbHeader.SyncByte);
+ if (_postedStreamTypes == null)
+ _postedStreamTypes = new HashSet>();
+ if (!_postedStreamTypes.Contains(streamTypeToPost))
+ {
+ logger.WarnFormat("This GS contains packets of type {0} ({2}) with a sync byte of {1}. This is not supported yet. If it isn't too much trouble, please consider sharing a sample of this stream.",streamTypeToPost.Item1,streamTypeToPost.Item2, (TsGsType)streamTypeToPost.Item1);
+ _postedStreamTypes.Add(streamTypeToPost);
+ }
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/IBbframeDeencapsulator.cs b/skyscraper8/GS/IBbframeDeencapsulator.cs
new file mode 100644
index 0000000..b4a3b4e
--- /dev/null
+++ b/skyscraper8/GS/IBbframeDeencapsulator.cs
@@ -0,0 +1,6 @@
+namespace skyscraper8.GSE;
+
+public interface IBbframeDeencapsulator
+{
+ public void PushPacket(byte[] bbframe);
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/IMisHandler.cs b/skyscraper8/GS/IMisHandler.cs
new file mode 100644
index 0000000..93e7c51
--- /dev/null
+++ b/skyscraper8/GS/IMisHandler.cs
@@ -0,0 +1,6 @@
+namespace skyscraper8.GSE;
+
+public interface IMisHandler
+{
+ void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan);
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/POC/BbfUdpDecap.cs b/skyscraper8/GS/POC/BbfUdpDecap.cs
new file mode 100644
index 0000000..dd04066
--- /dev/null
+++ b/skyscraper8/GS/POC/BbfUdpDecap.cs
@@ -0,0 +1,261 @@
+using System.Net;
+using System.Runtime.InteropServices;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.GSE;
+
+public class BbfUdpDecap : IBbframeDeencapsulator
+{
+ public IUdpDecapOutput Sink { get; set; }
+ public long NumPushed { get; private set; }
+
+ public void SetTargetIp(IPAddress ip)
+ {
+ HasTargetIp = ip.GetAddressBytes();
+ }
+
+ public void SetTargetPort(int tmp)
+ {
+ HasTargetPort = new byte[4];
+ HasTargetPort[0] = (byte)(tmp >> 8);
+ HasTargetPort[1] = (byte)(tmp & 0xff);
+ }
+
+ private byte? HasMis;
+ private byte[] HasSourceIp;
+ private byte[] HasTargetIp;
+ private byte[] HasSourcePort;
+ private byte[] HasTargetPort;
+
+ private byte[] inData;
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct bbheader
+ {
+ public byte MaType1;
+ public byte MaType2;
+ public byte Upl1;
+ public byte Upl2;
+ public byte Dfl1;
+ public byte Dfl2;
+ public byte Sync;
+ public byte SyncD1;
+ public byte SyncD2;
+ public byte Crc8;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ unsafe struct Layer3
+ {
+ public byte l3sync;
+ public bbheader header;
+ public fixed byte Data[7254-10];
+ }
+ public unsafe void PushPacket(byte[] bbframe)
+ {
+ NumPushed++;
+
+ if (inData == null)
+ {
+ inData = new byte[8100];
+ }
+
+ if (fragmentor == null)
+ {
+ fragmentor = new byte[256][];
+ fragmentorLength = new int[256];
+ fragmentorPos = new int[256];
+ }
+ MemoryStream inbuf = new MemoryStream(bbframe);
+ Layer3 myLayer;
+ while (true)
+ {
+ if (inbuf.Peek() == 0xb8)
+ break;
+ inbuf.ReadByte();
+ }
+
+ while (true)
+ {
+ int fail = inbuf.Read(inData, 0, 11);
+ if (fail != 11)
+ break;
+ myLayer = MemoryMarshal.Read(inData);
+ if (myLayer.l3sync != 0xb8)
+ {
+ while (true)
+ {
+ if (inbuf.Peek() == 0xb8)
+ break;
+ fail = inbuf.Read(inData, 0, 1);
+ if (fail != 1)
+ break;
+ }
+ }
+
+ int bblength = myLayer.header.Dfl1 << 8 | myLayer.header.Dfl2;
+ bblength >>= 3;
+ fail = inbuf.Read(inData, 11, bblength);
+ if (fail != bblength)
+ break;
+ myLayer = MemoryMarshal.Read(inData);
+ if (HasMis.HasValue && (myLayer.header.MaType2 != HasMis.Value))
+ continue;
+
+ int pos = 0;
+ while (pos < bblength - 4)
+ {
+ int gseLength = ((myLayer.Data[pos] & 0x0f) << 8) | (myLayer.Data[pos + 1]);
+ if ((myLayer.Data[pos] & 0xf0) == 0) break;
+ if (gseLength + 2 > bblength - pos) break;
+ if (!ProcessBbFrame(&myLayer.Data[pos], gseLength))
+ break;
+ pos += gseLength += 2;
+ }
+ }
+ }
+
+ private byte[][] fragmentor;
+ private int[] fragmentorLength;
+ private int[] fragmentorPos;
+
+ private unsafe void Copy(byte* src, byte[] dst, int srcOffset, int dstOffset, int len)
+ {
+ for (int i = 0; i < len; i++)
+ {
+ dst[dstOffset + i] = src[srcOffset + i];
+ }
+ }
+ private unsafe bool ProcessBbFrame(byte* payload, int gseLength)
+ {
+ uint offset = 0;
+ uint fragId = 0;
+
+ if ((payload[0] & 0xc0) == 0x80)
+ {
+ fragId = payload[2];
+ int length=(payload[3]<<8) | payload[4];
+ if(fragmentor[fragId]!=null)
+ fragmentor[fragId] = null;
+ fragmentor[fragId] = new byte[length + 2];
+ fragmentorLength[fragId] = length + 2;
+ fragmentor[fragId][0] = payload[0];
+ fragmentor[fragId][1] = payload[1];
+ fragmentor[fragId][0] |= 0xc0;
+ Copy(payload, fragmentor[fragId], 5, 2, gseLength - 3);
+ fragmentorPos[fragId] = gseLength - 1;
+ }
+ else if ((payload[0] & 0xc0) == 0x00)
+ {
+ throw new NotImplementedException("inbetween packet");
+ }
+ else if ((payload[0] & 0xc0) == 0x40)
+ {
+ fragId = payload[2];
+ if (fragmentor[fragId] == null)
+ return true;
+
+ Copy(payload, fragmentor[fragId], 3, fragmentorPos[fragId], gseLength - 5);
+ fragmentorPos[fragId] += gseLength - 1;
+ fixed (byte* p = fragmentor[fragId])
+ {
+ ProcessBbFrame(p, fragmentorLength[fragId]);
+ }
+ fragmentor[fragId] = null;
+
+ }
+ else if ((payload[0] & 0xc0) == 0xc0)
+ {
+ if (payload[offset + 2] == 0x00 && payload[offset + 3] == 0x04)
+ {
+ throw new NotImplementedException("ethertype 0x0400");
+ }
+ else if (payload[offset + 2] == 0x00 && payload[offset + 3] == 0x00)
+ {
+ throw new NotImplementedException("ethertype 0x0000");
+ }
+ else if (payload[offset + 2] == 0x08 && payload[offset + 3] == 0x00)
+ {
+ if((payload[0]&0x30)==0x01) {
+ offset += 3; //3-byte label
+ }
+ else if((payload[0] & 0x30) == 0x00) {
+ offset += 6; //MAC address
+ }
+
+ offset += 0x10; //IPv4 header
+ if (IsSelected(&payload[offset]))
+ {
+ offset += 0x10;
+ }
+ else
+ {
+ return true;
+ }
+
+ long sinkSize = gseLength + 2 -offset;
+ byte[] sinkMe = new byte[sinkSize];
+ Copy(payload,sinkMe,(int)offset,0, sinkMe.Length);
+ Sink.Write(sinkMe);
+ return true;
+ }
+
+ //throw new NotImplementedException("complete packet");
+ }
+ else if ((payload[0] & 0x0f0) == 0x00)
+ {
+ throw new NotImplementedException("padding packet");
+ }
+ return true;
+ }
+
+ private unsafe bool IsSelected(byte* buf)
+ {
+ byte zero = buf[0];
+ byte one = buf[1];
+ byte two = buf[2];
+ byte three = buf[3];
+ byte four = buf[4];
+ byte five = buf[5];
+ byte six = buf[6];
+ byte seven = buf[7];
+
+ if(HasSourceIp != null && (!(zero==HasSourceIp[0] && one==HasSourceIp[1] && two==HasSourceIp[2] && three==HasSourceIp[3])))
+ return false;
+ if(HasTargetIp != null && (!(four==HasTargetIp[0] && five==HasTargetIp[1] && six==HasTargetIp[2] && seven==HasTargetIp[3])))
+ return false;
+ if(HasSourcePort != null &&(!(buf[0x8]==HasSourcePort[0] && buf[0x9]==HasSourcePort[1])))
+ return false;
+ if(HasTargetPort != null &&(!(buf[0xa]==HasTargetPort[0] && buf[0xb]==HasTargetPort[1])))
+ return false;
+ return true;
+ }
+
+ public interface IUdpDecapOutput
+ {
+ public void Write(byte[] buffer);
+ }
+
+ internal class UdpDecapFileOutput : IUdpDecapOutput
+ {
+ public UdpDecapFileOutput(FileInfo file)
+ {
+ file.Directory.EnsureExists();
+ ourStream = file.OpenWrite();
+ }
+
+ private FileStream ourStream;
+
+ public void Write(byte[] udpPacket)
+ {
+ ourStream.Write(udpPacket, 0, udpPacket.Length);
+ }
+
+ public void Dispose()
+ {
+ ourStream.Flush();
+ ourStream.Close();
+ ourStream.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/POC/Pts2Bbf.cs b/skyscraper8/GS/POC/Pts2Bbf.cs
new file mode 100644
index 0000000..97009e8
--- /dev/null
+++ b/skyscraper8/GS/POC/Pts2Bbf.cs
@@ -0,0 +1,57 @@
+using System.Net;
+using log4net;
+using skyscraper5.Mpeg2;
+using skyscraper5.Skyscraper.Scraper;
+using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
+using skyscraper8.Skyscraper.Scraper.Storage;
+
+namespace skyscraper8.GSE;
+
+public class Pts2Bbf
+{
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+
+ public static void Run(FileInfo file, bool useUdpDecap = false, IPEndPoint ipEndPoint = null)
+ {
+ if (!file.Exists)
+ {
+ logger.Error("File not found: " + file.FullName);
+ return;
+ }
+
+ IBbframeDeencapsulator dumper;
+ if (useUdpDecap)
+ {
+ string changeExtension = Path.ChangeExtension(file.FullName, ".sdecap");
+ BbfUdpDecap.UdpDecapFileOutput udpDecapSink = new BbfUdpDecap.UdpDecapFileOutput(new FileInfo(changeExtension));
+
+ if (ipEndPoint == null)
+ {
+ ipEndPoint = new IPEndPoint(IPAddress.Parse("239.199.2.1"), 1234);
+ }
+ BbfUdpDecap bbfUdpDecap = new BbfUdpDecap();
+ bbfUdpDecap.SetTargetPort(ipEndPoint.Port);
+ bbfUdpDecap.SetTargetIp(ipEndPoint.Address);
+ bbfUdpDecap.Sink = udpDecapSink;
+ dumper = bbfUdpDecap;
+ }
+ else
+ {
+ string changeExtension = Path.ChangeExtension(file.FullName, ".sbbf");
+ dumper = new BbframeDumper(new FileInfo(changeExtension));
+ }
+
+ FileStream fileStream = file.OpenRead();
+
+ TsContext mpeg2 = new TsContext();
+ mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(null, null, dumper));
+ DataStorage dataStorage = new InMemoryScraperStorage();
+ ObjectStorage objectStorage = new NullObjectStorage();
+ SkyscraperContext skyscraper = new SkyscraperContext(mpeg2, dataStorage, objectStorage);
+ skyscraper.InitalizeFilterChain();
+ skyscraper.IngestFromStream(fileStream);
+
+ fileStream.Close();
+ logger.Info("Pts2Bbf finished");
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/POC/Stid135Test.cs b/skyscraper8/GS/POC/Stid135Test.cs
new file mode 100644
index 0000000..41c667b
--- /dev/null
+++ b/skyscraper8/GS/POC/Stid135Test.cs
@@ -0,0 +1,30 @@
+using log4net;
+using skyscraper5.Mpeg2;
+using skyscraper5.Skyscraper.Scraper;
+using skyscraper5.Skyscraper.Scraper.Storage.Filesystem;
+using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
+using skyscraper8.Skyscraper.Scraper.Storage;
+
+namespace skyscraper8.GSE;
+
+public class Stid135Test
+{
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+ public static void Run(FileInfo file)
+ {
+ FileStream fileStream = file.OpenRead();
+
+ TsContext mpeg2 = new TsContext();
+ DataStorage dataStorage = new InMemoryScraperStorage();
+ ObjectStorage objectStorage = new FilesystemStorage(new DirectoryInfo("nip"));
+ SkyscraperContext skyscraper = new SkyscraperContext(mpeg2, dataStorage, objectStorage);
+
+ mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(skyscraper,skyscraper));
+
+ skyscraper.InitalizeFilterChain();
+ skyscraper.IngestFromStream(fileStream);
+
+ fileStream.Close();
+ logger.Info("STiD135 Tester exiting");
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/GS/SiminnRadiomidun/SiminnRadiomidunReader.cs b/skyscraper8/GS/SiminnRadiomidun/SiminnRadiomidunReader.cs
new file mode 100644
index 0000000..3d4824c
--- /dev/null
+++ b/skyscraper8/GS/SiminnRadiomidun/SiminnRadiomidunReader.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using skyscraper5.Mpeg2.Descriptors;
+using skyscraper5.Skyscraper.IO;
+using skyscraper5.T2MI;
+using skyscraper8.GSE;
+using skyscraper8.Skyscraper.Scraper;
+
+namespace skyscraper8.GS.SiminnRadiomidun
+{
+ internal class SiminnRadiomidunReader : IMisHandler
+ {
+ public SiminnRadiomidunReader(byte mis, ISubTsHandler tsOutput)
+ {
+ this.packetQueue = new Queue>();
+ this.isInSync = false;
+ this.subTsKey = new SiminnRadiomidunSubTsIdentifier(mis);
+ this.tsOutput = tsOutput;
+ }
+
+ private bool isInSync;
+ private Queue> packetQueue;
+ private MemoryStream currentItem;
+ private SiminnRadiomidunSubTsIdentifier subTsKey;
+ private ISubTsHandler tsOutput;
+
+ public long PacketQueueSize
+ {
+ get
+ {
+ if (packetQueue == null)
+ return 0;
+
+ return packetQueue.Select(x => x.Item2.Length).Sum();
+ }
+ }
+ public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan)
+ {
+ if (packetQueue == null)
+ packetQueue = new Queue>();
+
+ packetQueue.Enqueue(new Tuple(bbHeader,new MemoryStream(readOnlySpan.Slice(0,bbHeader.DataFieldLength).ToArray(), true)));
+
+ while (PacketQueueSize > 8192)
+ {
+ if (!isInSync)
+ {
+ Tuple dequeue = packetQueue.Dequeue();
+ currentItem = dequeue.Item2;
+ currentItem.Position = dequeue.Item1.SyncD;
+ isInSync = true;
+ }
+
+ byte[] outputPacketBuffer = new byte[188];
+ int result = currentItem.Read(outputPacketBuffer, 0, 188);
+ if (result == 188)
+ {
+ OutputPacket(outputPacketBuffer);
+ }
+ else
+ {
+ int bytesNeeded = 188 - result;
+ Tuple dequeue = packetQueue.Dequeue();
+ if (bytesNeeded == dequeue.Item1.SyncD)
+ {
+ //We're still in sync!
+ syncSucess++;
+ currentItem = dequeue.Item2;
+ currentItem.Read(outputPacketBuffer, result, bytesNeeded);
+ OutputPacket(outputPacketBuffer);
+ }
+ else
+ {
+ //sync loss, let's discard the packet
+ syncFail++;
+ currentItem = dequeue.Item2;
+ currentItem.Position = dequeue.Item1.SyncD;
+ }
+ }
+ }
+ }
+
+ private ulong syncSucess, syncFail;
+ private void OutputPacket(byte[] buffer)
+ {
+ buffer[0] = 0x47;
+ tsOutput.OnSubTsPacket(subTsKey, buffer);
+ }
+ }
+}
diff --git a/skyscraper8/GS/SiminnRadiomidun/SiminnRadiomidunSubTsIdentifier.cs b/skyscraper8/GS/SiminnRadiomidun/SiminnRadiomidunSubTsIdentifier.cs
new file mode 100644
index 0000000..cfd0a68
--- /dev/null
+++ b/skyscraper8/GS/SiminnRadiomidun/SiminnRadiomidunSubTsIdentifier.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace skyscraper8.GS.SiminnRadiomidun
+{
+ internal class SiminnRadiomidunSubTsIdentifier
+ {
+ public byte Mis { get; }
+
+ public SiminnRadiomidunSubTsIdentifier(byte misId)
+ {
+ Mis = misId;
+ }
+
+ protected bool Equals(SiminnRadiomidunSubTsIdentifier other)
+ {
+ return Mis == other.Mis;
+ }
+
+ 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((SiminnRadiomidunSubTsIdentifier)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return Mis.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{nameof(Mis)}{Mis}";
+ }
+ }
+}
diff --git a/skyscraper8/GS/Stid135BbFrameReader.cs b/skyscraper8/GS/Stid135BbFrameReader.cs
new file mode 100644
index 0000000..99cd8e2
--- /dev/null
+++ b/skyscraper8/GS/Stid135BbFrameReader.cs
@@ -0,0 +1,61 @@
+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;
+using skyscraper5.Dvb.DataBroadcasting;
+using skyscraper8.Skyscraper.Scraper;
+
+namespace skyscraper8.GSE
+{
+ internal class Stid135BbFrameReader : ITsPacketProcessor
+ {
+ private IBbframeDeencapsulator deencapsulator;
+
+ public Stid135BbFrameReader(IMultiprotocolEncapsulationEventHandler mpeEventHandler, ISubTsHandler subTsHandler, IBbframeDeencapsulator deencapsulator = null)
+ {
+ if (deencapsulator == null)
+ deencapsulator = new BbframeDeencapsulator3(mpeEventHandler, subTsHandler);
+
+ this.deencapsulator = deencapsulator;
+ }
+
+ private long packetsRecovered;
+ private long packetsLost;
+ 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 (outbuf == null)
+ outbuf = new MemoryStream();
+
+ if ((packets[8] & 0xff) == 0xb8)
+ {
+ if (outbuf.Length > 0)
+ {
+ byte[] completeBbframe = outbuf.ToArray();
+ deencapsulator.PushPacket(completeBbframe);
+ outbuf = new MemoryStream();
+ }
+
+ //Start indicaator
+ outbuf.Write(packets, 8, packets[7]);
+ }
+ else
+ {
+ outbuf.Write(packets, 9, packets[7] - 1);
+ }
+ }
+ }
+}
diff --git a/skyscraper8/GS/TsGsType.cs b/skyscraper8/GS/TsGsType.cs
new file mode 100644
index 0000000..94c69cc
--- /dev/null
+++ b/skyscraper8/GS/TsGsType.cs
@@ -0,0 +1,9 @@
+namespace skyscraper8.GSE;
+
+public enum TsGsType
+{
+ GenericPacketized,
+ GenericContinuous,
+ GseHem,
+ Transport
+}
\ No newline at end of file
diff --git a/skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs b/skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs
new file mode 100644
index 0000000..3d2487e
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace skyscraper8.Ieee802_1AB
+{
+ internal interface ILldpFrameHandler
+ {
+ void OnLldpFrame(LldpFrame frame);
+ }
+}
diff --git a/skyscraper8/Ieee802_1AB/LldpFrame.cs b/skyscraper8/Ieee802_1AB/LldpFrame.cs
new file mode 100644
index 0000000..d9492b1
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/LldpFrame.cs
@@ -0,0 +1,154 @@
+using System.Text;
+using skyscraper5.Skyscraper.IO;
+using skyscraper8.Ieee802_1AB.Model;
+
+namespace skyscraper8.Ieee802_1AB;
+
+public class LldpFrame
+{
+ public LldpFrame(byte[] contents)
+ {
+ MemoryStream ms = new MemoryStream(contents, false);
+ while (ms.Position < ms.Length)
+ {
+ ushort tlv = ms.ReadUInt16BE();
+ int type = (tlv & 0xfe00) >> 9;
+ int length = (tlv & 0x01ff);
+ byte[] value = ms.ReadBytes(length);
+ switch (type)
+ {
+ case 1: //Chassis
+ this.Chassis = new LldpChassis(value);
+ break;
+ case 2: //Port
+ this.Port = new LldpPort(value);
+ break;
+ case 3: //Time to Live
+ this.TimeToLive = (value[0] << 8) | value[1];
+ this.TimeToLive--;
+ break;
+ case 4:
+ this.PortDescription = Encoding.UTF8.GetString(value);
+ break;
+ case 5:
+ this.SystemName = Encoding.UTF8.GetString(value);
+ break;
+ case 6:
+ this.SystemDescription = Encoding.UTF8.GetString(value);
+ break;
+ case 7:
+ this.Capabilities = new LldpCapabilities(value);
+ break;
+ case 8:
+ this.ManagementAddress = new LldpManagementAddress(value);
+ break;
+ case 127:
+ string OrganizationUniqueCode = BitConverter.ToString(value, 0, 3);
+ byte OrganizationallyDefinedSubtype = value[3];
+ MemoryStream payload = new MemoryStream(value, 4, value.Length - 4);
+ switch (OrganizationUniqueCode)
+ {
+ case "00-80-C2": //IEEE 802.1 Chair
+ switch (OrganizationallyDefinedSubtype)
+ {
+ case 6: //VLAN ID
+ this.PortVlanIdentifier = ms.ReadUInt16BE();
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown IEEE 802.1 Subtype {0}! Please share a sample of this stream.",OrganizationallyDefinedSubtype));
+ }
+ break;
+ case "00-12-0F": //IEEE 802.3 Chair
+ switch (OrganizationallyDefinedSubtype)
+ {
+ //see 802.1AB-2009.pdf, Annex F
+ case 1: //MAC/PHY configuration/status TLV format
+ this.MacPhyConfigurationStatus = new LldpMacPhyConfigurationStatus(payload);
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown IEEE 802.3 Subtype {0}! Please share a sample of this stream.", OrganizationallyDefinedSubtype));
+ }
+ break;
+ case "00-12-BB":
+ switch (OrganizationallyDefinedSubtype) //TR-41. Don't know which document yet.
+ {
+ case 1: //Media Capabilities
+ this.MediaCapabilities = new LldpMediaCapabilities(payload);
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown TR-41 Subtype {0}! Please share a sample of this stream.", OrganizationallyDefinedSubtype));
+}
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown Organization Unique Code {0}. Please share a sample of this stream.",OrganizationUniqueCode));
+ }
+
+ break;
+ case 0: //End of LLDP PDU
+ return;
+ default:
+ throw new NotImplementedException( String.Format("LLDP Option {0} not yet supported. Please share a sample of this stream.",type));
+ }
+ }
+ }
+
+ public LldpMediaCapabilities MediaCapabilities { get; set; }
+
+ public LldpMacPhyConfigurationStatus MacPhyConfigurationStatus { get; set; }
+
+ public ushort? PortVlanIdentifier { get; set; }
+
+ public LldpManagementAddress ManagementAddress { get; private set; }
+
+ public LldpCapabilities Capabilities { get; set; }
+
+ public string SystemDescription { get; set; }
+
+ public string SystemName { get; set; }
+
+ public string PortDescription { get; set; }
+
+ public int? TimeToLive { get; set; }
+
+ public LldpPort Port { get; set; }
+
+ public LldpChassis Chassis { get; private set; }
+
+ protected bool Equals(LldpFrame other)
+ {
+ return Port.Equals(other.Port) && Chassis.Equals(other.Chassis);
+ }
+
+ 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((LldpFrame)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Port, Chassis);
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ if (!string.IsNullOrEmpty(SystemName))
+ {
+ sb.Append(SystemName);
+ }
+ else
+ {
+ sb.Append(Chassis.ToString());
+ }
+
+ sb.Append(", ");
+ sb.Append(Port.ToString());
+ return sb.ToString();
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs b/skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs
new file mode 100644
index 0000000..80e0a88
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs
@@ -0,0 +1,36 @@
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.Ieee802_1AB.Model;
+
+public class LldpCapabilities
+{
+ private readonly ushort capabilities;
+ private readonly ushort enabledCapabilities;
+
+ public LldpCapabilities(byte[] buffer)
+ {
+ MemoryStream ms = new MemoryStream(buffer);
+ this.capabilities = ms.ReadUInt16BE();
+ this.enabledCapabilities = ms.ReadUInt16BE();
+ }
+
+ public bool HasOther => (capabilities & 0x0001) != 0;
+ public bool HasRepeater => (capabilities & 0x0002) != 0;
+ public bool HasBridge => (capabilities & 0x0004) != 0;
+ public bool HasWlanAccessPoint => (capabilities & 0x0008) != 0;
+ public bool HasRouter => (capabilities & 0x0010) != 0;
+ public bool HasTelephone => (capabilities & 0x0020) != 0;
+ public bool HasDocsis => (capabilities & 0x0040) != 0;
+ public bool IsStation => (capabilities & 0x0080) != 0;
+ public bool HasCVLan => (capabilities & 0x0100) != 0;
+ public bool HasSVLan => (capabilities & 0x0200) != 0;
+ public bool HasTpmr => (capabilities & 0x0400) != 0;
+
+ public bool EnabledOther => (enabledCapabilities & 0x0001) != 0;
+ public bool EnabledRepeater => (enabledCapabilities & 0x0002) != 0;
+ public bool EnabledBridge => (enabledCapabilities & 0x0004) != 0;
+ public bool EnabledWlanAccessPoint => (enabledCapabilities & 0x0008) != 0;
+ public bool EnabledRouter => (enabledCapabilities & 0x0010) != 0;
+ public bool EnabledTelephone => (enabledCapabilities & 0x0020) != 0;
+ public bool EnabledDocsis => (enabledCapabilities & 0x0040) != 0;
+}
\ No newline at end of file
diff --git a/skyscraper8/Ieee802_1AB/Model/LldpChassis.cs b/skyscraper8/Ieee802_1AB/Model/LldpChassis.cs
new file mode 100644
index 0000000..69b96ce
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/Model/LldpChassis.cs
@@ -0,0 +1,57 @@
+using System.Net.NetworkInformation;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.Ieee802_1AB.Model;
+
+public class LldpChassis
+{
+ public LldpChassis(byte[] value)
+ {
+ MemoryStream ms = new MemoryStream(value);
+ ChassisSubtype = ms.ReadUInt8();
+ switch (ChassisSubtype)
+ {
+ case 4:
+ MacAddress = new PhysicalAddress(ms.ReadBytes(6));
+ break;
+ default:
+ throw new NotImplementedException(string.Format("Chassis Subtype {0} not yet supported. Please share a sample of this stream.", ChassisSubtype));
+ }
+ }
+
+ public PhysicalAddress MacAddress { get; set; }
+
+ public byte ChassisSubtype { get; private set; }
+
+ protected bool Equals(LldpChassis other)
+ {
+ return MacAddress.Equals(other.MacAddress) && ChassisSubtype == other.ChassisSubtype;
+ }
+
+ 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((LldpChassis)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(MacAddress, ChassisSubtype);
+ }
+
+ public override string ToString()
+ {
+ switch (ChassisSubtype)
+ {
+ case 4:
+ return MacAddress.ToString();
+ default:
+ return String.Format("Chassis Subtype {0}", ChassisSubtype);
+ }
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs b/skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs
new file mode 100644
index 0000000..5f603e5
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.Ieee802_1AB.Model
+{
+ public class LldpMacPhyConfigurationStatus
+ {
+ private readonly byte autoNegotioationStatus;
+ private readonly ushort pmdAutoNegotiation;
+
+ public LldpMacPhyConfigurationStatus(MemoryStream ms)
+ {
+ autoNegotioationStatus = ms.ReadUInt8();
+ pmdAutoNegotiation = ms.ReadUInt16BE();
+ OperationalMauType = ms.ReadUInt16BE();
+ }
+
+ public ushort OperationalMauType { get; private set; }
+
+ public bool AutonegotiationSupported => (autoNegotioationStatus & 0x01) != 0;
+ public bool AutonegotiationStatus => (autoNegotioationStatus & 0x02) != 0;
+ }
+}
diff --git a/skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs b/skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs
new file mode 100644
index 0000000..b78ccd2
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs
@@ -0,0 +1,37 @@
+using System.Net;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.Ieee802_1AB.Model;
+
+public class LldpManagementAddress
+{
+ public LldpManagementAddress(byte[] buffer)
+ {
+ MemoryStream ms = new MemoryStream(buffer, false);
+ byte managementAddressStringLength = ms.ReadUInt8();
+ ManagementAddressSubtype = ms.ReadUInt8();
+ managementAddressStringLength--;
+
+ if (ManagementAddressSubtype != 1)
+ {
+ throw new NotImplementedException("Unknown management address subtype. Please share a sample of this stream.");
+ }
+
+ ManagementAddress = new IPAddress(ms.ReadBytes(managementAddressStringLength));
+ InterfaceNumberingSubtype = ms.ReadUInt8();
+ InterfaceNumber = ms.ReadUInt32BE();
+
+ byte oidStringLength = ms.ReadUInt8();
+ ObjectIdentifier = ms.ReadBytes(oidStringLength);
+ }
+
+ public byte[] ObjectIdentifier { get; set; }
+
+ public uint InterfaceNumber { get; set; }
+
+ public byte InterfaceNumberingSubtype { get; set; }
+
+ public byte ManagementAddressSubtype { get; set; }
+
+ public IPAddress ManagementAddress { get; set; }
+}
\ No newline at end of file
diff --git a/skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs b/skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs
new file mode 100644
index 0000000..acad66d
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.Ieee802_1AB.Model
+{
+ public class LldpMediaCapabilities
+ {
+ private readonly ushort capabilities;
+ public byte NetworkConnectivity { get; set; }
+
+ public LldpMediaCapabilities(MemoryStream ms)
+ {
+ capabilities = ms.ReadUInt16BE();
+ NetworkConnectivity = ms.ReadUInt8();
+ }
+
+ public bool LldpMedCapable => (capabilities & 0x0001) != 0;
+ public bool NetworkPolicyCapable => (capabilities & 0x0002) != 0;
+ public bool LocationIdentificationCapable => (capabilities & 0x0004) != 0;
+ public bool ExtendedPowerViaMdiPse => (capabilities & 0x0008) != 0;
+ public bool ExtendedPowerViaMdiPd => (capabilities & 0x0010) != 0;
+ public bool InventoryCapable => (capabilities & 0x0020) != 0;
+ }
+}
diff --git a/skyscraper8/Ieee802_1AB/Model/LldpPort.cs b/skyscraper8/Ieee802_1AB/Model/LldpPort.cs
new file mode 100644
index 0000000..8643bc4
--- /dev/null
+++ b/skyscraper8/Ieee802_1AB/Model/LldpPort.cs
@@ -0,0 +1,79 @@
+using System.Net.NetworkInformation;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.Ieee802_1AB.Model;
+
+public class LldpPort
+{
+ public LldpPort(byte[] buffer)
+ {
+ MemoryStream ms = new MemoryStream(buffer);
+ Subtype = ms.ReadUInt8();
+ switch (Subtype)
+ {
+ case 3:
+ MacAddress = new PhysicalAddress(ms.ReadBytes(6));
+ break;
+ case 5:
+ PortId = ms.ReadUTF8FixedLength((int)ms.GetAvailableBytes());
+ break;
+ default:
+ throw new NotImplementedException(String.Format("Unknown Port Subtype {0}. Please share a sample of this stream.", Subtype));
+ }
+ }
+
+ public PhysicalAddress MacAddress { get; set; }
+ public string PortId { get; set; }
+ public byte Subtype { get; private set; }
+
+ protected bool Equals(LldpPort other)
+ {
+ switch (Subtype)
+ {
+ case 3:
+ return MacAddress.Equals(other.MacAddress) && Subtype == other.Subtype;
+ case 5:
+ return PortId == other.PortId && Subtype == other.Subtype;
+ default:
+ throw new NotImplementedException(String.Format("Unknown Port Subtype {0}. Please share a sample of this stream.", Subtype));
+ }
+ }
+
+ 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((LldpPort)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ switch (Subtype)
+ {
+ case 3:
+ return HashCode.Combine(PortId, MacAddress);
+ case 5:
+ return HashCode.Combine(PortId, Subtype);
+ default:
+ throw new NotImplementedException(String.Format("Unknown Port Subtype {0}. Please share a sample of this stream.", Subtype));
+ }
+
+ }
+
+ public override string ToString()
+ {
+ switch (Subtype)
+ {
+ case 3:
+ return MacAddress.ToString();
+ case 5:
+ return PortId;
+ default:
+ return String.Format("Port Subtype {0}", Subtype);
+ }
+ }
+}
\ No newline at end of file
diff --git a/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs b/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs
index 1162d41..ac6faf3 100644
--- a/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs
+++ b/skyscraper8/InteractionChannel/InteractionChannelErrorState.cs
@@ -21,6 +21,7 @@ namespace skyscraper5.src.InteractionChannel
TmstInvalid,
Fct2Invalid,
Tbtp2Invalid,
- Tmst2Invalid
+ Tmst2Invalid,
+ BctInvalid
}
}
diff --git a/skyscraper8/InteractionChannel/InteractionChannelHandler.cs b/skyscraper8/InteractionChannel/InteractionChannelHandler.cs
index e189686..11f45c2 100644
--- a/skyscraper8/InteractionChannel/InteractionChannelHandler.cs
+++ b/skyscraper8/InteractionChannel/InteractionChannelHandler.cs
@@ -7,6 +7,8 @@ using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
+using skyscraper5.src.InteractionChannel.Model2;
+using skyscraper8.InteractionChannel.Model2;
namespace skyscraper5.src.InteractionChannel
{
@@ -28,5 +30,8 @@ namespace skyscraper5.src.InteractionChannel
int GetRmtTransmissionStandard(ushort networkId);
void OnReturnTransmissionMOdes(PhysicalAddress macAddress, _0xb2_ReturnTransmissionModesDescriptor descriptor);
void OnConnectionControl(PhysicalAddress macAddress, _0xaf_ConnectionControlDescriptor descriptor);
- }
+ void OnTerminalBurstTimePlan2(ushort interactiveNetworkId, Tbtp2 tbtp2);
+ void OnFrameComposition2(ushort networkId, Fct2 fct2);
+ void OnBroadcastConfiguration(ushort networkId, Bct bct);
+ }
}
diff --git a/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs b/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs
index 761c9bc..11fb9a2 100644
--- a/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs
+++ b/skyscraper8/InteractionChannel/InteractionChannelPsiGatherer.cs
@@ -9,6 +9,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using skyscraper8.InteractionChannel.Model2;
namespace skyscraper5.src.InteractionChannel
{
@@ -204,10 +205,25 @@ namespace skyscraper5.src.InteractionChannel
_handler.OnInteractionChannelError(InteractionChannelErrorState.Fct2Invalid);
return;
}
- throw new NotImplementedException("FCT2");
+
+ Handler.OnFrameComposition2(NetworkId.Value, fct2);
+ return;
case 0xAC: //BCT
- //see en_30154502v010401p.pdf, page 49
- throw new NotImplementedException("BCT");
+ InteractionChannelSiSectionHeader bctHeader = new InteractionChannelSiSectionHeader(ms);
+ if (!bctHeader.Valid)
+ {
+ _handler.OnInteractionChannelError(InteractionChannelErrorState.HeaderInvalid);
+ return;
+ }
+ NetworkId = bctHeader.InteractiveNetworkId;
+ Bct bct = new Bct(ms);
+ if (!bct.Valid)
+ {
+ _handler.OnInteractionChannelError(InteractionChannelErrorState.BctInvalid);
+ return;
+ }
+ Handler.OnBroadcastConfiguration(NetworkId.Value, bct);
+ return;
case 0xAD: //TBTP2
InteractionChannelSiSectionHeader tbtp2Header = new InteractionChannelSiSectionHeader(ms);
if (!tbtp2Header.Valid)
@@ -222,7 +238,9 @@ namespace skyscraper5.src.InteractionChannel
_handler.OnInteractionChannelError(InteractionChannelErrorState.Tbtp2Invalid);
return;
}
- throw new NotImplementedException("TBTP2");
+
+ _handler.OnTerminalBurstTimePlan2(tbtp2Header.InteractiveNetworkId, tbtp2);
+ break;
case 0xAE: //TMST2
InteractionChannelSiSectionHeader tmst2Header = new InteractionChannelSiSectionHeader(ms);
if (!tmst2Header.Valid)
diff --git a/skyscraper8/InteractionChannel/Model2/Bct.cs b/skyscraper8/InteractionChannel/Model2/Bct.cs
new file mode 100644
index 0000000..586a81c
--- /dev/null
+++ b/skyscraper8/InteractionChannel/Model2/Bct.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using skyscraper5.Skyscraper;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper8.InteractionChannel.Model2
+{
+ ///
+ /// Broadcast Configuration Table as found on ETSI EN 301 545-2 V1.4.1, page 59
+ ///
+ public class Bct : Validatable
+ {
+ public Bct(MemoryStream ms)
+ {
+ byte loopCount = ms.ReadUInt8();
+ TxTypes = new BroadcastConfiguration[loopCount];
+ for (int i = 0; i < loopCount; i++)
+ {
+ TxTypes[i] = new BroadcastConfiguration();
+ TxTypes[i].TxType = ms.ReadUInt8();
+ TxTypes[i].TxContentType = ms.ReadUInt8();
+ TxTypes[i].TxFormatClass = ms.ReadUInt8();
+ byte txFormatDataLengnth = ms.ReadUInt8();
+ TxTypes[i].TxFormatData = ms.ReadBytes(txFormatDataLengnth);
+ }
+
+ Valid = true;
+ }
+
+ public BroadcastConfiguration[] TxTypes { get; private set; }
+
+ public class BroadcastConfiguration
+ {
+ public byte TxType { get; set; }
+ public byte TxContentType { get; set; }
+ public byte TxFormatClass { get; set; }
+ public byte[] TxFormatData { get; set; }
+ }
+ }
+}
diff --git a/skyscraper8/InteractionChannel/Model2/Fct2.cs b/skyscraper8/InteractionChannel/Model2/Fct2.cs
index d1879f6..a9c837a 100644
--- a/skyscraper8/InteractionChannel/Model2/Fct2.cs
+++ b/skyscraper8/InteractionChannel/Model2/Fct2.cs
@@ -5,14 +5,72 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using skyscraper5.Skyscraper.IO;
namespace skyscraper5.src.InteractionChannel.Model2
{
- internal class Fct2 : Validatable
+ public class Fct2 : Validatable
{
public Fct2(MemoryStream ms)
{
- throw new NotImplementedException();
+ byte frameTypeLoopCount = ms.ReadUInt8();
+ frameTypeLoopCount--;
+ FrameTypes = new Frame[frameTypeLoopCount];
+ for (int i = 0; i < frameTypeLoopCount; i++)
+ {
+ FrameTypes[i] = new Frame();
+ FrameTypes[i].FrameType = ms.ReadUInt8();
+ FrameTypes[i].FrameDuration = ms.ReadUInt32BE();
+ FrameTypes[i].TxFormatClass = ms.ReadUInt8();
+ FrameTypes[i].BtuDuration = ms.ReadUInt24BE();
+ FrameTypes[i].BtuCarrierBw = ms.ReadUInt24BE();
+ FrameTypes[i].BtuSymbolRate = ms.ReadUInt24BE();
+ FrameTypes[i].TimeUnitCount = ms.ReadUInt16BE();
+
+ byte gridRepeatCount = ms.ReadUInt8();
+ FrameTypes[i].GridFrequencyOffset = new uint[gridRepeatCount];
+ for (int j = 0; j < gridRepeatCount; j++)
+ {
+ if (ms.GetAvailableBytes() < 2)
+ {
+ Valid = false;
+ return;
+ }
+ FrameTypes[i].GridFrequencyOffset[j] = ms.ReadUInt24BE();
+ }
+
+ byte sectionLoopCount = ms.ReadUInt8();
+ FrameTypes[i].SectionLoop = new SectionLoop[sectionLoopCount];
+ for (int j = 0; j < sectionLoopCount; j++)
+ {
+ FrameTypes[i].SectionLoop[j].DefaultTxType = ms.ReadUInt8();
+ FrameTypes[i].SectionLoop[j].FixedAccessMethod = (ms.ReadUInt8() & 0x0f);
+ FrameTypes[i].SectionLoop[j].RepeatCount = ms.ReadUInt16BE();
+ }
+ }
+
+ Valid = true;
+ }
+
+ public Frame[] FrameTypes { get; private set; }
+ public class Frame
+ {
+ public byte FrameType { get; set; }
+ public uint FrameDuration { get; set; }
+ public byte TxFormatClass { get; set; }
+ public uint BtuDuration { get; set; }
+ public uint BtuCarrierBw { get; set; }
+ public uint BtuSymbolRate { get; set; }
+ public ushort TimeUnitCount { get; set; }
+ public uint[] GridFrequencyOffset { get; set; }
+ public SectionLoop[] SectionLoop { get; set; }
+ }
+
+ public class SectionLoop
+ {
+ public byte DefaultTxType { get; set; }
+ public int FixedAccessMethod { get; set; }
+ public ushort RepeatCount { get; set; }
}
}
}
diff --git a/skyscraper8/InteractionChannel/Model2/Tbtp2.cs b/skyscraper8/InteractionChannel/Model2/Tbtp2.cs
index 54b8c0b..ce9c0fd 100644
--- a/skyscraper8/InteractionChannel/Model2/Tbtp2.cs
+++ b/skyscraper8/InteractionChannel/Model2/Tbtp2.cs
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace skyscraper5.src.InteractionChannel.Model2
{
- internal class Tbtp2 : Validatable
+ public class Tbtp2 : Validatable
{
public Tbtp2(MemoryStream ms)
{
diff --git a/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs b/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs
index 836625c..0b2732d 100644
--- a/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs
+++ b/skyscraper8/InteractionChannel/NullInteractionChannelHandler.cs
@@ -7,6 +7,8 @@ using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
+using skyscraper5.src.InteractionChannel.Model2;
+using skyscraper8.InteractionChannel.Model2;
namespace skyscraper5.src.InteractionChannel
{
@@ -21,6 +23,25 @@ namespace skyscraper5.src.InteractionChannel
{
}
+ public void OnTerminalBurstTimePlan2(ushort interactiveNetworkId, Tbtp2 tbtp2)
+ {
+
+ }
+
+ public void OnFrameComposition2(ushort networkId, Fct2 fct2)
+ {
+
+ }
+
+ public void OnBroadcastConfiguration(ushort networkId, Bct bct)
+ {
+ }
+
+ public void OnFrameComposition2(ushort? networkId, Fct2 fct2)
+ {
+
+ }
+
public void OnContentionControl(PhysicalAddress macAddress, _0xab_ContentionControlDescriptor ccd)
{
}
@@ -29,6 +50,11 @@ namespace skyscraper5.src.InteractionChannel
{
}
+ public void OnNetworkLayerInfo(PhysicalAddress macAddress, _0xa0_NetworkLayerInfoDescriptor descriptor)
+ {
+
+ }
+
public void OnCorrectionMessage(ushort interactiveNetworkId, Cmt cmt)
{
}
@@ -44,16 +70,7 @@ namespace skyscraper5.src.InteractionChannel
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)
{
}
@@ -73,11 +90,7 @@ namespace skyscraper5.src.InteractionChannel
public void OnTerminalBurstTimePlan(ushort interactiveNetworkId, Tbtp tbtp)
{
}
-
- public void OnTerminalInformation(PhysicalAddress macAddress, Tim tim)
- {
- }
-
+
public void OnTimeslotComposition(ushort interactiveNetworkId, Tct tct)
{
}
diff --git a/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs b/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs
index f452b81..5a51573 100644
--- a/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs
+++ b/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs
@@ -17,7 +17,12 @@ namespace skyscraper5.src.Mpeg2.PacketFilter
{
protected override bool PassPacketEx(TsPacket packet)
{
- return packet.TSC == 0;
+ if (packet.TSC == 0)
+ return true;
+ else
+ {
+ return false;
+ }
}
}
}
diff --git a/skyscraper8/Mpeg2/TsContext.cs b/skyscraper8/Mpeg2/TsContext.cs
index 7bbaa52..f94245e 100644
--- a/skyscraper8/Mpeg2/TsContext.cs
+++ b/skyscraper8/Mpeg2/TsContext.cs
@@ -1,15 +1,17 @@
-using System;
+using log4net;
+using skyscraper5.Skyscraper;
+using skyscraper5.src.Mpeg2.PacketFilter;
+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 static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+ private ITsPacketProcessor[] processors;
private uint[] continuities;
private ulong[] pidPackets;
@@ -42,6 +44,8 @@ namespace skyscraper5.Mpeg2
if (FilterChain == null || FilterChain.Count == 0)
throw new InvalidOperationException("The filter chain has not been initialized.");
+ CheckBrokenEncapsulation(packet);
+
for (int i = 0; i < FilterChain.Count; i++)
{
if (FilterChain[i] == null)
@@ -61,6 +65,44 @@ namespace skyscraper5.Mpeg2
processors[packet.PID].PushPacket(packet);
}
+ private bool encapsulationBrokenConfirmed;
+ private bool CheckBrokenEncapsulation(TsPacket packet)
+ {
+ if (encapsulationBrokenConfirmed)
+ return true;
+
+ switch (packet.PID)
+ {
+ case 0x0000:
+ case 0x0001:
+ break;
+ default:
+ return false;
+ }
+
+ int occupiedPids = CountOccupiedPids();
+ if (occupiedPids < 1000)
+ return false;
+
+ if (packet.TSC == 0)
+ return false;
+
+ logger.WarnFormat("It looks like you're either having bad reception or this is a GS with broken encapsulation. This means that you either have FrameMode enabled, or are not using a STiD135.");
+ encapsulationBrokenConfirmed = true;
+ return true;
+ }
+
+ private int CountOccupiedPids()
+ {
+ int result = 0;
+ for (int i = 0; i < pidPackets.Length; i++)
+ {
+ if (pidPackets[i] > 0)
+ result++;
+ }
+ return result;
+ }
+
private bool EnsureContinuity(TsPacket packet)
{
//Found in ISO 13818-1.pdf, page 38
diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs
index e827496..cff18c6 100644
--- a/skyscraper8/Program.cs
+++ b/skyscraper8/Program.cs
@@ -29,6 +29,7 @@ using System.Runtime.InteropServices;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.Scraper.StreamAutodetection;
using skyscraper8;
+using skyscraper8.GSE;
using skyscraper8.SatIp;
using skyscraper8.SatIp.RtspResponses;
using skyscraper8.SimpleServiceDiscoveryProtocol;
@@ -40,7 +41,7 @@ namespace skyscraper5
class Program
{
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
- private const int PUBLIC_RELEASE = 9;
+ private const int PUBLIC_RELEASE = 12;
private static void IntegrationTest()
{
/*List ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList();
@@ -296,16 +297,51 @@ namespace skyscraper5
return;
}
- if (args[0].ToLowerInvariant().Equals("pcapon"))
+ if (args[0].ToLowerInvariant().Equals("pcap-on"))
{
TogglePcapConfiguration(1);
return;
}
- if (args[0].ToLowerInvariant().Equals("pcapoff"))
+ if (args[0].ToLowerInvariant().Equals("pcap-off"))
{
TogglePcapConfiguration(2);
return;
}
+
+ if (args[0].ToLowerInvariant().Equals("subts-on"))
+ {
+ ToggleSubTsDumpConfiguration(true);
+ return;
+ }
+
+ if (args[0].ToLowerInvariant().Equals("subts-off"))
+ {
+ ToggleSubTsDumpConfiguration(false);
+ return;
+ }
+
+ if (args[0].ToLowerInvariant().Equals("pts2bbf"))
+ {
+ if (args[1].ToLowerInvariant().Equals("bbfudpdecap"))
+ {
+ FileInfo fi = new FileInfo(args[2]);
+ Pts2Bbf.Run(fi, true);
+ return;
+ }
+ else
+ {
+ FileInfo fi = new FileInfo(args[1]);
+ Pts2Bbf.Run(fi, false);
+ return;
+ }
+ }
+
+ if (args[0].ToLowerInvariant().Equals("stid135test"))
+ {
+ FileInfo fi = new FileInfo(args[1]);
+ Stid135Test.Run(fi);
+ return;
+ }
}
/*Passing passing = new Passing();
@@ -317,13 +353,16 @@ namespace skyscraper5
passing.Run();*/
Console.WriteLine("You're not supposed to run me directly. Run me from the commandline using the following:");
Console.WriteLine("for example: .\\skyscraper8.exe cscan tcp://127.0.0.1:6969");
+ Console.WriteLine(" or: .\\skyscraper8.exe udpin - listens on port 9003 on all local interface for udp packets.");
Console.WriteLine(" or: .\\skyscraper8.exe \"C:\\path\\to\\file.ts\\");
Console.WriteLine(" or: .\\skyscraper8.exe \"C:\\path\\to\\my\\folder\\with\\ts\\files\\\" (for batch extraction)");
Console.WriteLine(" or: .\\skyscraper8.exe satip IP_ADDRESS DISEQC POLARITY FREQUENCY SYSTEM SYMBOL_RATE (see README file)");
- Console.WriteLine(" or: .\\skyscraper8.exe pcapon - to write a configuration file that allows PCAP writing during scraping.");
- Console.WriteLine(" or: .\\skyscraper8.exe pcapoff - to write a configuration file that turns off PCAP writing during scraping.");
- Console.WriteLine();
- Console.WriteLine("default behaviour is pcap writing on.");
+ Console.WriteLine(" or: .\\skyscraper8.exe pcap-on - to write a configuration file that allows PCAP writing during scraping.");
+ Console.WriteLine(" or: .\\skyscraper8.exe pcap-off - to write a configuration file that turns off PCAP writing during scraping.");
+ Console.WriteLine(" or: .\\skyscraper8.exe subts-on - to write a configuration file that allows extraction of nested TS.");
+ Console.WriteLine(" or: .\\skyscraper8.exe subts-off - to write a configuration file that turns off extraction of nested TS.");
+ Console.WriteLine();
+ Console.WriteLine("default behaviour is pcap writing and nested TS writing on.");
Console.WriteLine();
Console.WriteLine("Bonus features:");
Console.WriteLine(".\\skyscraper8.exe hlsproxy \"C:\\path\\to\\hls\\files\\\" - to pipe a HLS stream from a local directory into VLC.");
@@ -331,6 +370,18 @@ namespace skyscraper5
Console.WriteLine(".\\skyscraper8.exe shannon \"C:\\some\\file.bmp\" - calculates the Shannon entropy value for any given file.");
}
+ private static void ToggleSubTsDumpConfiguration(bool enabled)
+ {
+ PluginManager pluginManager = PluginManager.GetInstance();
+ if (!pluginManager.Ini.ContainsKey("subts"))
+ pluginManager.Ini.Add("subts", new IniSection());
+
+ pluginManager.Ini["subts"]["dump"] = enabled ? "1" : "0";
+ pluginManager.SaveConfiguration();
+
+ Console.WriteLine("Wrote skyscraper5.ini.");
+ }
+
private static void TogglePcapConfiguration(int value)
{
PluginManager pluginManager = PluginManager.GetInstance();
diff --git a/skyscraper8/Properties/launchSettings.json b/skyscraper8/Properties/launchSettings.json
index df6935c..5d5c369 100644
--- a/skyscraper8/Properties/launchSettings.json
+++ b/skyscraper8/Properties/launchSettings.json
@@ -2,7 +2,7 @@
"profiles": {
"skyscraper8": {
"commandName": "Project",
- "commandLineArgs": "satip auto 1 H 11141 S2 23500",
+ "commandLineArgs": "\"D:\\sorglos-iww-rww-aca-oca.ts\"",
"remoteDebugEnabled": false
},
"Container (Dockerfile)": {
diff --git a/skyscraper8/Skyscraper/IO/StreamExtensions.cs b/skyscraper8/Skyscraper/IO/StreamExtensions.cs
index 9b7b93c..97a04fa 100644
--- a/skyscraper8/Skyscraper/IO/StreamExtensions.cs
+++ b/skyscraper8/Skyscraper/IO/StreamExtensions.cs
@@ -322,5 +322,16 @@ namespace skyscraper5.Skyscraper.IO
stream.Write(buffer, 0, 8);
}
+ public static byte Peek(this Stream stream)
+ {
+ if (!stream.CanSeek)
+ throw new NotSupportedException("Stream is not peekable");
+ if (stream.Position == stream.Length)
+ throw new EndOfStreamException();
+
+ byte readUInt8 = stream.ReadUInt8();
+ stream.Position--;
+ return readUInt8;
+ }
}
}
diff --git a/skyscraper8/Skyscraper/MpeEject.cs b/skyscraper8/Skyscraper/MpeEject.cs
new file mode 100644
index 0000000..881b9cb
--- /dev/null
+++ b/skyscraper8/Skyscraper/MpeEject.cs
@@ -0,0 +1,158 @@
+using System.Net;
+using log4net;
+using skyscraper5.Ietf.Rfc768;
+using skyscraper5.Ietf.Rfc971;
+using skyscraper5.Skyscraper;
+using skyscraper5.Skyscraper.Plugins;
+using skyscraper8.Skyscraper.Net;
+using skyscraper8.Skyscraper.Scraper;
+using skyscraper8.Skyscraper.Scraper.Storage;
+
+namespace skyscraper8.Skyscraper;
+
+///
+/// This plugin does the opposite of what TSDuck's MpeInject does.
+///
+[SkyscraperPlugin]
+public class MpeEject : ISkyscraperMpePlugin
+{
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+
+ public void ConnectToStorage(object[] connector)
+ {
+ throw new NotImplementedException();
+ }
+
+ private ISubTsHandler subTsHandler;
+ public void SetContext(DateTime? currentTime, object skyscraperContext)
+ {
+ subTsHandler = skyscraperContext as ISubTsHandler;
+ if (subTsHandler == null)
+ {
+ logger.ErrorFormat("Couldn't get a handle for the SubTsHandler. Deencapsulation will not work.");
+ }
+ }
+
+ public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
+ {
+ if (internetHeader.Protocol != 0x11)
+ return false;
+
+ int payloadLength = ipv4Packet.Length - 8;
+ int tsCheck = payloadLength % 188;
+ return tsCheck == 0;
+ }
+
+ private Dictionary, MpeEjectSession> sessions;
+ private bool lastOutputSucessful;
+ public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
+ {
+ if (subTsHandler == null)
+ {
+ lastOutputSucessful = false;
+ return;
+ }
+
+ UserDatagram userDatagram = new UserDatagram(ipv4Packet);
+ IPEndPoint sourceEndpoint = new IPEndPoint(internetHeader.SourceAddress, userDatagram.SourcePort);
+ IPEndPoint targetEndpoint = new IPEndPoint(internetHeader.DestinationAddress, userDatagram.DestinationPort);
+ Tuple sessionPointer = new Tuple(sourceEndpoint, targetEndpoint);
+
+ if (sessions == null)
+ sessions = new Dictionary, MpeEjectSession>();
+
+ MpeEjectSession session = null;
+ if (sessions.ContainsKey(sessionPointer))
+ {
+ session = sessions[sessionPointer];
+ if (session.Bootstrapped && !session.Valid)
+ {
+ lastOutputSucessful = false;
+ return;
+ }
+ }
+ else
+ {
+ session = new MpeEjectSession(sourceEndpoint, targetEndpoint);
+ sessions.Add(sessionPointer, session);
+ }
+
+ if (userDatagram.Payload.Length == 188)
+ {
+ if (!session.Bootstrapped)
+ {
+ lastOutputSucessful = false;
+ return;
+ }
+
+ if (!session.Valid)
+ {
+ lastOutputSucessful = false;
+ return;
+ }
+
+ subTsHandler.OnSubTsPacket(session,userDatagram.Payload);
+ lastOutputSucessful = true;
+ return;
+ }
+
+ for (int i = 0; i < userDatagram.Payload.Length; i += 188)
+ {
+ if (userDatagram.Payload[i] != 0x47)
+ {
+ if (session.Valid)
+ {
+ logger.WarnFormat("Lost TS sync on stream for: {0} -> {1}", sourceEndpoint, targetEndpoint);
+ }
+ session.Bootstrapped = true;
+ session.Valid = false;
+ lastOutputSucessful = false;
+ return;
+ }
+ }
+
+ session.ValidDatagrams++;
+ if (session.ValidDatagrams >= 10)
+ {
+ session.Bootstrapped = true;
+ session.Valid = true;
+ byte[] packetBuffer = new byte[188];
+ for (int i = 0; i < userDatagram.Payload.Length; i += 188)
+ {
+ Array.Copy(userDatagram.Payload, i, packetBuffer, 0, 188);
+ subTsHandler.OnSubTsPacket(session, packetBuffer);
+ lastOutputSucessful = true;
+ }
+ }
+ else
+ {
+ lastOutputSucessful = false;
+ }
+ }
+
+ public bool StopProcessingAfterThis()
+ {
+ return lastOutputSucessful;
+ }
+}
+
+public class MpeEjectSession
+{
+ public IPEndPoint SourceEndpoint { get; }
+ public IPEndPoint DestinationEndpoint { get; }
+
+ public MpeEjectSession(IPEndPoint sourceEndpoint, IPEndPoint destinationEndpoint)
+ {
+ SourceEndpoint = sourceEndpoint;
+ DestinationEndpoint = destinationEndpoint;
+ }
+ public bool Bootstrapped { get; set; }
+ public bool Valid { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("{0} -> {1}", SourceEndpoint, DestinationEndpoint).SanitizeFileName();
+ }
+
+ public int ValidDatagrams { get; set; }
+}
\ No newline at end of file
diff --git a/skyscraper8/Skyscraper/OldStreamReaderDetector.cs b/skyscraper8/Skyscraper/OldStreamReaderDetector.cs
new file mode 100644
index 0000000..54cc6a7
--- /dev/null
+++ b/skyscraper8/Skyscraper/OldStreamReaderDetector.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using log4net;
+using skyscraper5.Dvb;
+using skyscraper5.Dvb.DataBroadcasting;
+using skyscraper5.Mpeg2;
+using skyscraper5.Skyscraper.IO;
+
+namespace skyscraper5.Skyscraper
+{
+ internal class OldStreamReaderDetector : ITsPacketProcessor
+ {
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+
+ public OldStreamReaderDetector()
+ {
+ }
+
+ private MemoryStream outbuf;
+ private int assembled;
+ private bool annoncementDone;
+
+ public void PushPacket(TsPacket packet)
+ {
+ if (annoncementDone)
+ return;
+
+ 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)
+ {
+ logger.WarnFormat("It seems like you're using a StreamReader.dll version which interferes with the STiD135's BBFrame encapsulation. Try updating to 1.2.5.208, this is known to work.");
+ annoncementDone = true;
+ }
+ }
+ }
+ 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/Scraper/ISubTsHandler.cs b/skyscraper8/Skyscraper/Scraper/ISubTsHandler.cs
new file mode 100644
index 0000000..2602503
--- /dev/null
+++ b/skyscraper8/Skyscraper/Scraper/ISubTsHandler.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace skyscraper8.Skyscraper.Scraper
+{
+ public interface ISubTsHandler
+ {
+ public void OnSubTsPacket(object identifier, byte[] packet);
+ }
+}
diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs
index bc3e32f..46a3a2f 100644
--- a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs
+++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs
@@ -76,10 +76,16 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Security.Policy;
using System.Text;
+using skyscraper5.src.InteractionChannel.Model2;
+using skyscraper8.Abertis;
using skyscraper8.Experimentals.NdsSsu;
using skyscraper8.Experimentals.OtvSsu;
+using skyscraper8.GSE;
+using skyscraper8.Ieee802_1AB;
+using skyscraper8.InteractionChannel.Model2;
using skyscraper8.Skyscraper.Net;
using skyscraper8.Skyscraper.Scraper;
+using skyscraper8.T2MI;
using Tsubasa.IO;
using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform;
using RntParser = skyscraper5.Dvb.TvAnytime.RntParser;
@@ -91,8 +97,8 @@ namespace skyscraper5.Skyscraper.Scraper
IEitEventHandler, IAitEventHandler, ISubtitleEventHandler,
UpdateNotificationEventHandler, DataCarouselEventHandler, RdsEventHandler, IScte35EventHandler,
IAutodetectionEventHandler, IRstEventHandler, IRntEventHandler, IMultiprotocolEncapsulationEventHandler, ObjectCarouselEventHandler, T2MIEventHandler,
- IDisposable, IFrameGrabberEventHandler, IntEventHandler, IRctEventHandler, IGsEventHandler, ISkyscraperContext, IDocsisEventHandler, AbertisDecoderEventHandler, Id3Handler,
- InteractionChannelHandler, SgtEventHandler, IDvbNipEventHandler, UleEventHandler, OtvSsuHandler, NdsSsuHandler
+ IDisposable, IFrameGrabberEventHandler, IntEventHandler, IRctEventHandler, ISkyscraperContext, IDocsisEventHandler, AbertisDecoderEventHandler, Id3Handler,
+ InteractionChannelHandler, SgtEventHandler, IDvbNipEventHandler, UleEventHandler, OtvSsuHandler, NdsSsuHandler, ISubTsHandler, ILldpFrameHandler
{
public const bool ALLOW_STREAM_TYPE_AUTODETECTION = true;
public const bool ALLOW_FFMPEG_FRAMEGRABBER = true;
@@ -293,18 +299,19 @@ namespace skyscraper5.Skyscraper.Scraper
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.");
+ DvbContext.RegisterPacketProcessor(0x0118, new OldStreamReaderDetector());
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;
- }
+ if (!DvbContext.IsPidProcessorPresent(0x010e))
+ {
+ DvbContext.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(this, this));
+ UiJunction?.SetGseMode();
+ LogEvent(SkyscraperContextEvent.SpecialTsMode, "STiD135 encapsulated GS detected.");
+ SpecialTsType = 3;
+ }
+ }
firstPacketDone = true;
}
@@ -339,10 +346,7 @@ namespace skyscraper5.Skyscraper.Scraper
{
if (!DvbContext.IsPidProcessorPresent(0x0118))
{
- DvbContext.RegisterPacketProcessor(0x0118, new Tbs6903xGsReader(this));
- UiJunction?.SetGseMode();
- LogEvent(SkyscraperContextEvent.SpecialTsMode, "TBS 6903-X GSE Pseudo TS detected.");
- SpecialTsType = 2;
+ DvbContext.RegisterPacketProcessor(0x0118, new OldStreamReaderDetector());
return;
}
}
@@ -357,9 +361,9 @@ namespace skyscraper5.Skyscraper.Scraper
{
if (!DvbContext.IsPidProcessorPresent(0x010e))
{
- DvbContext.RegisterPacketProcessor(0x010e, new DigitalDevicesBbFrameReader(this));
+ DvbContext.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(this, this));
UiJunction?.SetGseMode();
- LogEvent(SkyscraperContextEvent.SpecialTsMode, "Digital Devices BBFRAME data TS detected.");
+ LogEvent(SkyscraperContextEvent.SpecialTsMode, "STiD135 encapsulated GS detected.");
SpecialTsType = 3;
return;
}
@@ -1896,31 +1900,57 @@ namespace skyscraper5.Skyscraper.Scraper
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(), DataStorage,ObjectStorage);
- childSkyscrapers[pid][basebandFramePlpId].IsChild = true;
- childSkyscrapers[pid][basebandFramePlpId].ChildName = String.Format("PLP {0}", basebandFramePlpId);
+ if (subSkyscrapers == null)
+ subSkyscrapers = new Dictionary