Index: /trunk/BNC/scripts/cmbPlot.pl
===================================================================
--- /trunk/BNC/scripts/cmbPlot.pl	(revision 11129)
+++ /trunk/BNC/scripts/cmbPlot.pl	(revision 11130)
@@ -74,4 +74,5 @@
 # for pdf generation
 my ( $png, $page, $headline, $headline_text );
+my @pngFiles;    # images to be deleted after inclusion in the PDF
 my $y0 = 180 / mm;
 my ( $x, $y, $width, $height ) = ( 40 / mm, $y0, 130 / mm, 80 / mm );
@@ -179,4 +180,5 @@
     my @datasets;    # init datasets
     my $pngName = sprintf ( "%s_CLK_CORR_%s.png", $inputFilename, $key_sys );
+    push @pngFiles, $pngName;
     my $chart = Chart::Gnuplot->new(
                                      output   => $pngName,
@@ -232,4 +234,5 @@
     my @datasets;    # init datasets
     my $pngName = sprintf ( "%s_CLK_FULL_%s.png", $inputFilename, $key_sys );
+    push @pngFiles, $pngName;
     my $chart = Chart::Gnuplot->new(
         output   => $pngName,
@@ -287,4 +290,5 @@
             my @datasets;                             # init datasets
             my $pngName = sprintf ( "%s_OFFSETSAT_%s_%s.png", $inputFilename, $key_ac, $key_sys );
+            push @pngFiles, $pngName;
             my $chartOFFSETSAT = Chart::Gnuplot->new(
                 output   => $pngName,
@@ -340,4 +344,5 @@
     my @datasets;    # init datasets
     my $pngName = sprintf ( "%s_OFFSETAC_%s.png", $inputFilename, $key_sys );
+    push @pngFiles, $pngName;
     my $chart = Chart::Gnuplot->new(
         output   => $pngName,
@@ -393,4 +398,5 @@
         my @datasets;                      # init datasets
         my $pngName = sprintf ( "%s_RES_%s_%s.png", $inputFilename, $key_ac, $key_sys );
+        push @pngFiles, $pngName;
         my $chart = Chart::Gnuplot->new(
                                          output   => $pngName,
@@ -432,6 +438,6 @@
 $pdf->save();
 $pdf->end();
-system ("rm $inputDir/*png");
-system("evince $inputDir/$pdf_name&");
+unlink @pngFiles;    # only the images written here
+if ( !fork() ) { exec( "evince", "$inputDir$pdf_name" ) or exit 1; }
 
 #foreach my $t(@array) {print"$t \n ";}
Index: /trunk/BNC/scripts/compareSP3Plot.pl
===================================================================
--- /trunk/BNC/scripts/compareSP3Plot.pl	(revision 11129)
+++ /trunk/BNC/scripts/compareSP3Plot.pl	(revision 11130)
@@ -66,4 +66,5 @@
 # for pdf generation
 my ($png, $page, $headline, $headline_text);
+my @pngFiles;    # images to be deleted after inclusion in the PDF
 my $y0 = 190/mm;
 my ($x, $y, $width, $height) = (40/mm, $y0, 130/mm, 70/mm);
@@ -134,6 +135,9 @@
             my (@datasets_r, @datasets_a, @datasets_o); # init datasets
             my $pngName_r   = sprintf ("%s_RADIAL_%s.png", $inputFilename, $key_sys);
+            push @pngFiles, $pngName_r;
             my $pngName_a   = sprintf ("%s_ALONG_%s.png", $inputFilename, $key_sys);
+            push @pngFiles, $pngName_a;
             my $pngName_o   = sprintf ("%s_OUT_%s.png", $inputFilename, $key_sys);
+            push @pngFiles, $pngName_o;
             my $chart_r = Chart::Gnuplot->new(
                           output => $pngName_r, terminal => 'png', title => "Radial component",
@@ -231,5 +235,7 @@
             my (@datasets_c, @datasets_r); # init datasets
             my $pngName_c  = sprintf ("%s_CLK_%s.png", $inputFilename, $key_sys);
+            push @pngFiles, $pngName_c;
             my $pngName_r  = sprintf ("%s_CLKRED_%s.png", $inputFilename, $key_sys);
+            push @pngFiles, $pngName_r;
             my $chart_c = Chart::Gnuplot->new(
                           output => $pngName_c, terminal => 'png',title => "Clock only",
@@ -305,4 +311,5 @@
             my @datasets_s; # init datasets
             my $pngName_s  = sprintf ("%s_SIS_%s.png", $inputFilename, $key_sys);
+            push @pngFiles, $pngName_s;
             # SIS contains the satellite offsets: y range from the data
             my $maxAbs = 0.1;
@@ -342,5 +349,6 @@
     $pdf->save();
     $pdf->end();
-    system("rm *png");
+    unlink @pngFiles;    # only the images written here
+    @pngFiles = ();
     #system("evince $inputDir/$pdf_name&");
 }
Index: /trunk/BNC/scripts/pppPlot.pl
===================================================================
--- /trunk/BNC/scripts/pppPlot.pl	(revision 11129)
+++ /trunk/BNC/scripts/pppPlot.pl	(revision 11130)
@@ -87,4 +87,5 @@
 # for pdf gerneration
 my ( $png, $page, $headline, $headline_text );
+my @pngFiles;    # images to be deleted after inclusion in the PDF
 my $y0 = 180 / mm;
 my ( $x, $y, $width, $height ) = ( 40 / mm, $y0, 130 / mm, 80 / mm );
@@ -170,4 +171,5 @@
 		DEBUG "Plot NEU ";
 		my $pngNameNEU = sprintf( "%s_NEU.png", $station );
+		push @pngFiles, $pngNameNEU;
 		my $chartNEU   = newChart("$station ($dateStr)");
 		$chartNEU->set(
@@ -207,4 +209,5 @@
 		DEBUG "Plot FIX";
 		my $pngNameFIX = sprintf( "%s_FIX.png", $station );
+		push @pngFiles, $pngNameFIX;
 		my $chartFIX   = newChart("$station ($dateStr)");
 		$chartFIX->set(
@@ -236,4 +239,5 @@
 		DEBUG "Plot TRP";
 		my $pngNameTRP = sprintf( "%s_TRP.png", $station );
+		push @pngFiles, $pngNameTRP;
 		my $chartTRP   = newChart("$station ($dateStr)");
 		$chartTRP->set( output => $pngNameTRP );
@@ -278,4 +282,5 @@
 		my @datasets      = ();                                  # init datasets
 		my $pngNameRECCLK = sprintf( "%s_RECCLK.png", $station );
+		push @pngFiles, $pngNameRECCLK;
 		$chartRECCLK->set( output => $pngNameRECCLK );
 		$chartRECCLK->set( ylabel => "Receiver Clocks [m]" );
@@ -323,4 +328,5 @@
 			my @datasets = ();    # init datasets
 			my $pngNameELE = sprintf( "%s_ELE_%s.png", $station, $key_sys );
+			push @pngFiles, $pngNameELE;
 			$chartELE->set( output => $pngNameELE );
 			$chartELE->set(
@@ -370,5 +376,7 @@
 				my ( @datasets_amb, @datasets_epo );    # init datasets
 				my $pngNameAMB = sprintf( "%s_AMB_%s_%s.png",	$station, $key_ambType, $key_sys );
+				push @pngFiles, $pngNameAMB;
 				my $pngNameEPO = sprintf( "%s_EPO_%s_%s.png",	$station, $key_ambType, $key_sys );
+				push @pngFiles, $pngNameEPO;
 				my $chartAMB = Chart::Gnuplot->new(
 					output   => $pngNameAMB,
@@ -458,4 +466,5 @@
 			my @datasets;    # init datasets
 			my $pngNameION = sprintf( "%s_ION_%s.png", $station, $ksys );
+			push @pngFiles, $pngNameION;
 			$chartION->set( output => $pngNameION );
 			# SATELLITE
@@ -504,4 +513,5 @@
 				my $pngNameBIA = sprintf( "%s_BIAS_%s_%s.png",
 					$station, $key_biasType, $key_sys );
+				push @pngFiles, $pngNameBIA;
 				$chartBIAS->set( output => $pngNameBIA );
 				$chartBIAS->set( ylabel => "Receiver Bias $key_biasType [m]" );
@@ -548,4 +558,5 @@
 				my @datasets;
 				my $pngNameRES = sprintf( "%s_RES_%s_%s.png", $station, $key_resType, $key_sys );
+				push @pngFiles, $pngNameRES;
 				$chartRES->set( output => $pngNameRES );
 				$chartRES->set(ylabel => "Residuals $key_resType [m]" );
@@ -603,4 +614,5 @@
 			my @datasets;
 			my $pngNameCLKCORR = sprintf( "%s_CLKCORR_%s.png", $station, $ksys );
+			push @pngFiles, $pngNameCLKCORR;
 			$chartCLKCORR->set( output => $pngNameCLKCORR );
 			# SATELLITE
@@ -647,4 +659,5 @@
 			$chartYAW->set( legend => { position => "outside right" } );
 			my $pngNameYAW = sprintf( "%s_YAW_%s.png", $station, $sat );
+			push @pngFiles, $pngNameYAW;
 			$chartYAW->set( output => $pngNameYAW );
 			# YAW from SSR data
@@ -696,4 +709,5 @@
 				my @datasets;
 				my $pngNameCB =  sprintf( "%s_CB_%s_%s.png", $station, $key_type, $key_sys );
+				push @pngFiles, $pngNameCB;
 				$chartCB->set( output => $pngNameCB );
 				$chartCB->set( ylabel => "Code Biases $key_type [m]" );
@@ -740,5 +754,7 @@
 				my (@datasetsJC, $datasetJC);
 				my $pngNamePB =  sprintf( "%s_PB_%s_%s.png", $station, $key_type, $key_sys );
+				push @pngFiles, $pngNamePB;
 				my $pngNameJC =  sprintf( "%s_JC_%s_%s.png", $station, $key_type, $key_sys );
+				push @pngFiles, $pngNameJC;
 				my $chartPB = newChart("$ssrData ($dateStr)");
 	        	$chartPB->set( legend => { position => "outside right" } );
@@ -799,7 +815,8 @@
 $pdf->end();
 
-system("rm *.png");
+unlink @pngFiles;    # only the images written here
+@pngFiles = ();
 if (Common::amInteractiv) {
-	system("evince $inputDir/$pdf_name");
+	system( "evince", "$inputDir$pdf_name" );
 }
 
Index: /trunk/BNC/src/RTCM/RTCM2.cpp
===================================================================
--- /trunk/BNC/src/RTCM/RTCM2.cpp	(revision 11129)
+++ /trunk/BNC/src/RTCM/RTCM2.cpp	(revision 11130)
@@ -667,5 +667,5 @@
   };
 
-  if ( 24*DW.size() < start+n-1 ) {
+  if ( 24*DW.size() < start+n ) { // last bit read: start+n-1
 #if (DEBUG>0)
     cerr << "Debug output RTCM2packet::getUnsignedBits" << endl
@@ -727,5 +727,5 @@
   };
 
-  if ( 24*DW.size() < start+n-1 ) {
+  if ( 24*DW.size() < start+n ) { // last bit read: start+n-1
 #if (DEBUG>0)
     cerr << "Debug output RTCM2packet::getUnsignedBits" << endl
Index: /trunk/BNC/src/RTCM3/RTCM3Decoder.cpp
===================================================================
--- /trunk/BNC/src/RTCM3/RTCM3Decoder.cpp	(revision 11129)
+++ /trunk/BNC/src/RTCM3/RTCM3Decoder.cpp	(revision 11130)
@@ -39,4 +39,5 @@
  * -----------------------------------------------------------------------*/
 
+#include <atomic>
 #include <iostream>
 #include <iomanip>
@@ -241,7 +242,9 @@
 /**
  * Frequency numbers of GLONASS with an offset of 100 to detect unset values.
- * Gets filled by ephemeris and data blocks and shared between different streams.
+ * Gets filled by ephemeris and data blocks and shared between different streams
+ * (MSM4/6 streams without channel numbers use those of an ephemeris stream).
+ * Atomic, as the streams are decoded in different threads.
  */
-static int GLOFreq[RTCM3_MSM_NUMSAT];
+static std::atomic<int> GLOFreq[RTCM3_MSM_NUMSAT];
 
 /*
@@ -1998,5 +2001,6 @@
   GETSTRING(antnum, antenna)
   if ((antnum > -1 && antnum < 265) &&
-      (_antType.empty() || strncmp(_antType.back()._descriptor, antenna, recnum) != 0)) {
+      (_antType.empty() || int(strlen(_antType.back()._descriptor)) != antnum ||
+       strncmp(_antType.back()._descriptor, antenna, antnum) != 0)) {
     _antType.push_back(t_antInfo());
     memcpy(_antType.back()._descriptor, antenna, antnum);
@@ -2017,5 +2021,6 @@
     GETSTRING(recsernum, recserialnum)
     if ((recnum > -1 && recnum < 265) &&
-        (_recType.empty() || strncmp(_recType.back()._descriptor, receiver, recnum) != 0)) {
+        (_recType.empty() || int(strlen(_recType.back()._descriptor)) != recnum ||
+         strncmp(_recType.back()._descriptor, receiver, recnum) != 0)) {
       _recType.push_back(t_recInfo());
       memcpy(_recType.back()._descriptor, receiver, recnum);
Index: /trunk/BNC/src/RTCM3/bits.h
===================================================================
--- /trunk/BNC/src/RTCM3/bits.h	(revision 11129)
+++ /trunk/BNC/src/RTCM3/bits.h	(revision 11130)
@@ -104,8 +104,11 @@
    b = variable to store size, s = variable to store string pointer */
 #define GETSTRING(b, s) { \
+  if(size < 1) return false; \
   b = *(data++); \
+  --size; \
+  if((b) > size) return false; \
   s = (char *) data; \
   data += b; \
-  size -= b+1; \
+  size -= b; \
 }
 
@@ -126,6 +129,7 @@
   }
 
+/* up to 56 bits: less than 8 bits are pending in the 64-bit bitbuffer */
 #define ADDBITS(a, b) { \
-    bitbuffer = (bitbuffer<<(a))|((b)&((1<<a)-1)); \
+    bitbuffer = (bitbuffer<<(a))|((uint64_t)(b)&((1ULL<<(a))-1)); \
     numbits += (a); \
     STOREBITS \
Index: /trunk/BNC/src/RTCM3/clock_and_orbit/clock_orbit.h
===================================================================
--- /trunk/BNC/src/RTCM3/clock_and_orbit/clock_orbit.h	(revision 11129)
+++ /trunk/BNC/src/RTCM3/clock_and_orbit/clock_orbit.h	(revision 11130)
@@ -605,15 +605,11 @@
 #define E_NADIR_ANGLE_DEPENDENT_CORR_RANGE_EXTENSION(a) ADDBITS(4,  a)                      /* DF+015 (new RTCM) to extend DF+021*/
 #define E_SATELLITE_SET_INDICATOR(a)                    ADDBITS(1,  a)                      /* DF+016 (new RTCM) */
-/* ADDBITS' mask "(1<<a)-1" uses a 32-bit int literal, so a plain
-   ADDBITS(64, a) is broken the same way GETSSRBITS(a, 64) was on decode
-   (see D_SATELLITE_MASK) - here even ADDBITS(32, ...) is unsafe, since
-   shifting a 32-bit int by exactly its own width is undefined behavior
-   too. Stay well clear of that edge by writing four 16-bit chunks. */
+/* ADDBITS handles up to 56 bits, so the 64-bit mask is written as two
+   32-bit halves (as GETSSRBITS(a, 64) does not work either, see
+   D_SATELLITE_MASK). */
 #define E_SATELLITE_MASK(a)                             { \
   uint64_t mask64_ = (a); \
-  ADDBITS(16, (mask64_ >> 48) & 0xFFFFU) \
-  ADDBITS(16, (mask64_ >> 32) & 0xFFFFU) \
-  ADDBITS(16, (mask64_ >> 16) & 0xFFFFU) \
-  ADDBITS(16,  mask64_        & 0xFFFFU) \
+  ADDBITS(32, mask64_ >> 32) \
+  ADDBITS(32, mask64_ & 0xFFFFFFFFU) \
 }                                                                                           /* DF394 */
 #define E_FREQUENCY_SET_INDICATOR(a)                    ADDBITS(1,  a)                      /* DF+017 (new RTCM) */
Index: /trunk/BNC/src/RTCM3/clock_and_orbit/clock_orbit_rtcm_new.cpp
===================================================================
--- /trunk/BNC/src/RTCM3/clock_and_orbit/clock_orbit_rtcm_new.cpp	(revision 11129)
+++ /trunk/BNC/src/RTCM3/clock_and_orbit/clock_orbit_rtcm_new.cpp	(revision 11130)
@@ -489,12 +489,14 @@
         if (md->Entries[i].nonDefaultIndicator &&
             md->Entries[i].additionalParameterIndicator) {
-          /* up to 255 bits, only 32 stored (leading bits zero); in pieces of
-             16 bits, as ADDBITS cannot handle more than 30 bits */
+          /* up to 255 bits, only 32 stored: leading zero bits (ADDBITS
+             handles at most 56 bits at a time), then the value */
           unsigned int k = md->Entries[i].additionalParameterNumBits;
-          while (k) {
-            unsigned int n = (k > 16) ? 16 : k;
+          while (k > 32) {
+            unsigned int n = (k - 32 > 56) ? 56 : k - 32;
+            ADDBITS(n, 0)
             k -= n;
-            E_ADDITIONAL_MODEL_PARAMETER_DATA((k < 32) ? (md->Entries[i].additionalParameterData >> k) : 0, n)
-          }
+          }
+          if (k)
+            E_ADDITIONAL_MODEL_PARAMETER_DATA(md->Entries[i].additionalParameterData, k)
       }
     }
Index: /trunk/BNC/src/bnchelp.html
===================================================================
--- /trunk/BNC/src/bnchelp.html	(revision 11129)
+++ /trunk/BNC/src/bnchelp.html	(revision 11130)
@@ -147,5 +147,5 @@
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.2 <a href="#corrint">Interval</a><br>
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.3 <a href="#corrport">Port</a><br>
-    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#ssrqc">QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File</a><br>
+    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#ssrqc">QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File, QC ANTEX File</a><br>
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#corrwait">Wait for Full Corr Epoch</a><br>
     &nbsp; &nbsp; &nbsp; 2.9 <a href="#syncout"><b>Feed Engine</b></a><br>
@@ -5097,5 +5097,5 @@
 
   <p>
-  <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File - optional</h4>
+  <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File, QC ANTEX File - optional</h4>
   </p>
   <p>
@@ -5106,5 +5106,6 @@
     e.g. 'ALL,!SIRGAS200001' for a stream referring to another reference frame than the ITRF. 'QC Reference' selects the reference of the stream comparison (see below). Select
     the 'QC Interval' for the reports (default: 1 hour). A 'QC Bias File' (Bias SINEX, OSB or DSB, e.g. a CAS DCB or
-    a GFZ OSB product) is an optional reference for the code bias check (see below). One daily file
+    a GFZ OSB product) is an optional reference for the code bias check (see below). A 'QC ANTEX File' (e.g. the
+    current igs20.atx) enables the attitude check (see below). One daily file
     '&lt;stream&gt;_S_&lt;YYYYDDD&gt;0000_01D_QC.txt' is written per stream; at the end of each interval and at the
     end of the program, a report per system is appended. Broadcast ephemerides must be received as well, e.g. via
@@ -5241,4 +5242,25 @@
     clock 0.689 (STD 0.008), radial 0.020, along 0.036, cross 0.022, SIS (radial - clock) 0.687 (STD 0.008)
   G satellites deviating from the reference, RMS SIS [m]: G02(0.38) G16(0.33) G20(0.91) G26(3.36)
+</pre>
+  <p>
+    <b>Attitude check:</b> With a 'QC ANTEX File', BNC checks for every stream sending the satellite yaw angle whether
+    its clocks are consistent with its yaw angles during noon and midnight turns. In every stream comparison (30
+    seconds), the difference of the yaw angle of BNC's attitude model ('Computed', see section 'Combination') minus the
+    yaw angle of the stream is followed continuously over time; the phase wind-up predicts a clock difference of the
+    stream to an AC following the model of lambda_IF * dpsi / 2pi (lambda_IF: ionosphere-free wavelength of the
+    reference signals, about 10.7 cm for GPS). A turn starts when the yaw difference changes by more than 20 degrees
+    against the samples 5 to 15 minutes before, and ends when it is stable again within 2 degrees for 10 minutes. For
+    each turn, the observed clock change of the stream relative to the consensus is compared with the predicted one:
+    the changes at the end of the turn, the regression slope of the observed on the predicted changes, and the clock
+    RMS during the turn without and with the predicted change. A slope between 0.7 and 1.3 with the RMS reduced by
+    at least half means that the yaw angles in the stream describe the attitude the AC used for its clocks - the
+    condition for selecting the attitude 'SSR' for this AC in the combination. Turns with a predicted change below 3
+    cm are not assessed; a clock change of at least 3 cm is then reported as not explained by the yaw difference,
+    which shows that the other ACs model this turn differently from BNC's model (the check assumes that the consensus
+    follows it), e.g.
+  </p>
+  <pre>
+  G attitude check G14 09:42:30-10:25:30: yaw difference to the model 362 deg, clock change vs. consensus 8.2 cm, predicted 10.7 cm (max 10.7 cm), slope 0.77, clock RMS 6.1 -> 2.0 cm: SSR yaw consistent with the clocks
+  G attitude check G25 07:53:00-08:51:30: yaw difference to the model 39 deg, clock change vs. consensus -9.8 cm, predicted -0.0 cm (max 1.2 cm), slope 1.84, clock RMS 5.9 -> 5.8 cm: clock change not explained by the yaw difference (the other ACs model this turn differently?)
 </pre>
   <p>
Index: /trunk/BNC/src/bncmain.cpp
===================================================================
--- /trunk/BNC/src/bncmain.cpp	(revision 11129)
+++ /trunk/BNC/src/bncmain.cpp	(revision 11130)
@@ -215,4 +215,5 @@
         "   ssrQcIntr    {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n"
         "   ssrQcBiasFile {Bias SINEX file (OSB or DSB) as reference for the SSR QC code bias check, full path [character string]}\n"
+        "   ssrQcAntexFile {ANTEX file for the SSR QC attitude check (SSR yaw vs. clocks during turns), full path [character string]}\n"
         "\n"
         "Feed Engine Panel keys:\n"
Index: /trunk/BNC/src/bncnetqueryrtp.cpp
===================================================================
--- /trunk/BNC/src/bncnetqueryrtp.cpp	(revision 11129)
+++ /trunk/BNC/src/bncnetqueryrtp.cpp	(revision 11130)
@@ -110,4 +110,5 @@
 
   const int timeOut = 5000;
+  const int maxHeaderLines = 100; // of an RTSP answer
 
   _status = running;
@@ -177,5 +178,6 @@
         QByteArray serverPort;
         QString line = in.readLine();
-        while (!line.isEmpty()) {
+        int nLines = 0;
+        while (!line.isEmpty() && ++nLines <= maxHeaderLines) {
           if (line.indexOf("Session:") == 0) {
             _session = line.mid(9).toLatin1();
@@ -226,5 +228,6 @@
               QTextStream in(_socket);
               line = in.readLine();
-              while (!line.isEmpty()) {
+              nLines = 0;
+              while (!line.isEmpty() && ++nLines <= maxHeaderLines) {
                 if (line.indexOf("200 OK") != -1) {
                   emit newMessage(_url.toEncoded(QUrl::RemoveUserInfo).replace(0,1,"")
Index: /trunk/BNC/src/bncnetqueryv1.cpp
===================================================================
--- /trunk/BNC/src/bncnetqueryv1.cpp	(revision 11129)
+++ /trunk/BNC/src/bncnetqueryv1.cpp	(revision 11130)
@@ -253,7 +253,20 @@
     bool proxyResponse = false;
     QStringList response;
+    const int maxResponseSize = 16384;
+    int responseSize = 0;
     while (true) {
       if (_socket->canReadLine()) {
         QString line = _socket->readLine();
+
+        // a caster response header is short; do not collect an endless one
+        responseSize += line.size();
+        if (responseSize > maxResponseSize) {
+          delete _socket;
+          _socket = 0;
+          _status = error;
+          emit newMessage(_url.path().toLatin1().replace(0,1,"")
+                          + ": Caster response header too long", true);
+          return;
+        }
     
         if (line.indexOf("ICY 200 OK") == -1 && 
@@ -289,4 +302,12 @@
     	break;
         }
+      }
+      else if (_socket->bytesAvailable() > maxResponseSize) { // no line end
+        delete _socket;
+        _socket = 0;
+        _status = error;
+        emit newMessage(_url.path().toLatin1().replace(0,1,"")
+                        + ": Caster response header too long", true);
+        return;
       }
       else if (!_socket->waitForReadyRead(_timeOut)) {
Index: /trunk/BNC/src/src.pri
===================================================================
--- /trunk/BNC/src/src.pri	(revision 11129)
+++ /trunk/BNC/src/src.pri	(revision 11130)
@@ -19,4 +19,11 @@
 
 win32:QMAKE_CXXFLAGS += /std:c++17
+
+# Hardening, independent of the distribution's compiler defaults: stack
+# protector, bounds checks of the std containers (abort instead of memory
+# corruption), read-only relocations. _FORTIFY_SOURCE is not set, it requires
+# optimization, which is switched off above.
+linux:QMAKE_CXXFLAGS += -fstack-protector-strong -D_GLIBCXX_ASSERTIONS
+linux:QMAKE_LFLAGS   += -Wl,-z,relro,-z,now
 
 debug:OBJECTS_DIR=.obj/debug
Index: /trunk/BNC/tests/README.md
===================================================================
--- /trunk/BNC/tests/README.md	(revision 11129)
+++ /trunk/BNC/tests/README.md	(revision 11130)
@@ -31,4 +31,5 @@
 | `tools/ssr_harness.sh` | builds `tools/ssr_harness.cpp` with the SSR decoders under ASan/UBSan and runs it |
 | `tools/ssl_check.py` | TLS checks of the Ntrip 2s client and upload (not part of `run_tests.py`) |
+| `tools/net_check.py` | UDP/RTP sender filter and upload status checks on loopback (not part of `run_tests.py`) |
 | `work/` | scratch output, not versioned |
 
@@ -100,5 +101,12 @@
   coefficients are C(d,o) = (100*d+o)/200 TECU and S = -C, both `.ssr` files
   are identical apart from the station ID. These frames overflowed the
-  coefficient and name arrays; the 4-layer VTEC crashed BNC.
+  coefficient and name arrays; the 4-layer VTEC crashed BNC. Also two RTCM
+  1033: a complete one and one whose receiver descriptor length runs beyond
+  the message (rejected; before, stale buffer content was logged).
+
+* `rawfile_hostile`: raw file with hostile chunk headers from
+  `tools/mkcrafted.py --rawfile`: the station ID `../ESCAPE0` must give
+  `Output/.._ESCAPE0_*.ssr` (no file outside the output directory), a chunk
+  header announcing 2000000000 bytes is rejected with a log message.
 
 ## Sanitizer check of the SSR decoders
@@ -115,8 +123,9 @@
 also covers the new RTCM-SSR metadata (message 60 in
 `crafted_limits.raw`: 7 entries with additional parameters of 40 and 0 bits,
-and 31 entries, which must be rejected), which `RTCM3Decoder.cpp` does not
-dispatch at present. Every decoded VTEC and metadata message is encoded
-again with the `Make*` function of the same class and decoded once more; the
-result must be identical. Exit code 0 only without sanitizer errors and
+and 31 entries, which must be rejected) and satellite antenna messages
+(BDS with mask bits in all four 16-bit quarters, GPS with shared blocks),
+which `RTCM3Decoder.cpp` does not dispatch at present. Every decoded VTEC, metadata and antenna message is
+encoded again with the `Make*` function of the same class and decoded once
+more; the result must be identical, for the antenna messages also the bytes. Exit code 0 only without sanitizer errors and
 differences.
 
@@ -135,4 +144,19 @@
 certificate for another host name (rejected) and a client certificate
 required by the caster, with and without `sslIgnoreErrors`. Takes about 3 min.
+
+## Network check
+
+```
+tests/tools/net_check.py [--bnc PATH]
+```
+
+Runs BNC live against fake servers on loopback addresses (no root needed).
+A second sender on another address sends spoofed datagrams to BNC's port: in
+the modes 'U' (Ntrip 2 UDP) and 'R' (RTSP/RTP) and in 'UN' with a sender
+address in the URL, BNC's raw output must contain only the server's data;
+'UN' with 127.0.0.1 accepts any sender, as before. An Ntrip 1 caster sending an endless
+response header is cut off, a normal Ntrip 1 stream still works. An upload
+answered with `HTTP/1.1 401` and a header containing `200` must be reported
+as broken. Takes about 2 min.
 
 ## Adding a replay case
Index: /trunk/BNC/tests/tools/bncraw.py
===================================================================
--- /trunk/BNC/tests/tools/bncraw.py	(revision 11129)
+++ /trunk/BNC/tests/tools/bncraw.py	(revision 11130)
@@ -197,6 +197,14 @@
 
 
+def frame(payload):
+    """RTCM3 frame (preamble, length, payload, CRC24Q) of a payload."""
+    head = bytes([0xD3, (len(payload) >> 8) & 0x03, len(payload) & 0xFF])
+    return head + payload + crc24q(head + payload).to_bytes(3, "big")
+
+
 def cmd_slice(args):
     stations = set(args.stations.split(",")) if args.stations else None
+    types = args.types.split(",") if args.types else None
+    parsers = {}
 
     def selected():
@@ -208,4 +216,12 @@
             if args.end and t >= args.end:
                 continue
+            if types:
+                # keep only the RTCM3 messages of the given types (or type prefixes)
+                frames = parsers.setdefault(sta, Rtcm3Framer()).feed(data)
+                kept = b"".join(frame(pl) for pl in frames
+                                if any(msg_type(pl).startswith(tt) for tt in types))
+                if not kept:
+                    continue
+                data = kept
             yield t, sta, fmt, data
 
@@ -245,4 +261,6 @@
     p.add_argument("--end", help="ISO time, exclusive")
     p.add_argument("--stations", help="comma separated staIDs to keep")
+    p.add_argument("--types", help="comma separated RTCM3 message types or prefixes to keep, "
+                                   "e.g. 1019,4076_02 (GPS ephemerides and IGS-SSR GPS messages)")
     p.set_defaults(func=cmd_slice)
 
Index: /trunk/BNC/tests/tools/fakecaster.py
===================================================================
--- /trunk/BNC/tests/tools/fakecaster.py	(revision 11129)
+++ /trunk/BNC/tests/tools/fakecaster.py	(revision 11130)
@@ -15,5 +15,7 @@
 additionally requires a client certificate signed by that CA. With --serve
 a GET request (BNC as Ntrip client) is answered with the RTCM bytes of the
-given raw file. Failed TLS handshakes are reported on stdout.
+given raw file. With --reply an upload is answered with this text instead
+of the 200 status (e.g. a rejection), and the connection is closed. Failed
+TLS handshakes are reported on stdout.
 """
 
@@ -55,4 +57,5 @@
         self.total = 0
         self.handshakes = 0
+        self.rejected = 0
         self.served = 0
 
@@ -67,5 +70,5 @@
 
 
-def serve(conn, store, timeout, tls=None, served=None):
+def serve(conn, store, timeout, tls=None, served=None, reply=None):
     conn.settimeout(timeout)
     buf = b""
@@ -89,4 +92,8 @@
                          b"Content-Type: gnss/data\r\nConnection: close\r\n\r\n" + served)
             store.served += len(served)
+            return
+        if reply is not None:
+            conn.sendall(reply)
+            store.rejected += 1
             return
         if header.startswith(b"SOURCE"):
@@ -127,4 +134,5 @@
     ap.add_argument("--tls-client-ca", help="require a client certificate of this CA")
     ap.add_argument("--serve", help="raw file whose RTCM bytes are sent on GET")
+    ap.add_argument("--reply", help="answer to uploads instead of 200 ('\\r\\n' escapes allowed)")
     args = ap.parse_args()
 
@@ -136,4 +144,7 @@
             tls.verify_mode = ssl.CERT_REQUIRED
             tls.load_verify_locations(args.tls_client_ca)
+    reply = None
+    if args.reply is not None:
+        reply = args.reply.encode("latin-1").decode("unicode_escape").encode("latin-1")
     served = None
     if args.serve:
@@ -155,5 +166,5 @@
         except socket.timeout:
             break
-        t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served),
+        t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served, reply),
                              daemon=True)
         t.start()
@@ -166,4 +177,6 @@
     if served is not None:
         print("served %d bytes" % store.served, flush=True)
+    if reply is not None:
+        print("%d upload(s) answered with --reply" % store.rejected, flush=True)
     return 0
 
Index: /trunk/BNC/tests/tools/mkcrafted.py
===================================================================
--- /trunk/BNC/tests/tools/mkcrafted.py	(revision 11129)
+++ /trunk/BNC/tests/tools/mkcrafted.py	(revision 11130)
@@ -6,5 +6,8 @@
   CRAFT0IGS0  IGS-SSR VTEC (4076/201), decoded by SsrCorrIgs, followed by
               RTCM 1300/1301/1302 with 31-character names (BNC's RTCM scan
-              lists them only for streams that also decode successfully)
+              lists them only for streams that also decode successfully);
+              in the first epoch also two RTCM 1033 (antenna and receiver
+              descriptors): a complete one and one whose receiver descriptor
+              length (200) runs beyond the end of the message
   CRAFT0RTC0  RTCM-SSR VTEC (1264), decoded by SsrCorrRtcm
   CRAFT0NEW0  new RTCM-SSR metadata (60), not dispatched by BNC at present;
@@ -12,4 +15,9 @@
                 - 7 entries (maximum), additional parameter of 40 and 0 bits
                 - 31 entries (5-bit count, more than META_MAXNUMENTRIES=7)
+              and satellite antenna messages (DF+010-DF+021):
+                - BDS (83): C01, C17, C33, C49, C64 (mask bits in all four
+                  16-bit quarters of DF394), individual blocks
+                - GPS (80): G01, G02, G32 sharing one block and one frequency
+                  block; nadir angle corrections incl. the invalid value
 
 VTEC epochs (every 5 s):
@@ -22,5 +30,13 @@
 by eye; the heights are 100 km * layer number.
 
+With --rawfile, a raw file with hostile chunk headers is written instead
+(test of the raw file reader, data/crafted_rawfile.raw):
+  ../ESCAPE0  station ID with a path: RTCM-SSR VTEC whose .ssr file must be
+              written inside the output directory
+  CRAFT0RTC0  RTCM-SSR VTEC, 5 s later
+  then a chunk header announcing 2000000000 bytes, which must be rejected
+
 Usage: mkcrafted.py <out.raw>
+       mkcrafted.py --rawfile <out.raw>
 """
 
@@ -140,4 +156,24 @@
 
 
+def descriptors(strings, truncate=False):
+    """RTCM 1033: antenna, setup ID, serial, receiver, firmware, serial."""
+    w = BitWriter()
+    w.u(1033, 12)
+    w.u(0, 12)               # reference station ID
+    ant, antser, rec, firm, recser = strings
+    w.text(ant, 8)
+    w.u(0, 8)                # antenna setup ID
+    w.text(antser, 8)
+    if truncate:
+        w.u(200, 8)          # receiver descriptor length beyond the message
+        for c in rec.encode("ascii"):
+            w.u(c, 8)
+    else:
+        w.text(rec, 8)
+        w.text(firm, 8)
+        w.text(recser, 8)
+    return frame(w.payload())
+
+
 def metadata(entries):
     """entries: list of None (default model) or number of additional bits."""
@@ -163,5 +199,47 @@
 
 
+def antenna(msg, sats, shared, nadir):
+    """Satellite antenna message; sats: PRNs (1..64) of the mask."""
+    w = BitWriter()
+    w.u(msg, 12)
+    w.u(4711, 16)            # provider ID
+    w.u(5, 6)                # satellite antenna IOD
+    w.u(1, 1)                # phase center information
+    w.u(0, 1)                # group delay information
+    w.u(1, 1)                # nadir angle dependent corrections follow
+    w.u(3, 5)                # maximum off-nadir angle: corrections 0..3 deg
+    w.u(1, 4)                # range extension: 3 + 1 = 4 bits
+    w.u(shared, 1)           # satellite set indicator
+    mask = 0
+    for prn in sats:
+        mask |= 1 << (64 - prn)
+    w.u(mask, 64)
+    for n in range(1 if shared else len(sats)):
+        w.u(shared, 1)       # frequency set indicator
+        w.u(0b101000, 6)     # frequencies 0 and 2
+        for f in range(1 if shared else 2):
+            w.u(1, 1)        # nadir correction follows
+            w.s(100 * n + 10 * f + 1, 12)    # mm
+            for deg in range(4):
+                w.s(nadir[(n + f + deg) % len(nadir)], 4)   # mm, -8: invalid
+    return frame(w.payload())
+
+
+def rawfile(path):
+    chunks = []
+    for i, sta in enumerate(["../ESCAPE0", "CRAFT0RTC0"]):
+        t = T0 + datetime.timedelta(seconds=5 * i)
+        chunks.append((t.isoformat(), sta, "RTCM_3", vtec_rtcm(t, [(2, 2)])))
+    write_chunks(path, chunks)
+    t = T0 + datetime.timedelta(seconds=10)
+    with open(path, "ab") as o:
+        o.write(("%s CRAFT0RTC0 RTCM_3 2000000000\n" % t.isoformat()).encode())
+        o.write(vtec_rtcm(t, [(2, 2)]) + b"\n")
+
+
 def main():
+    if len(sys.argv) == 3 and sys.argv[1] == "--rawfile":
+        rawfile(sys.argv[2])
+        return
     if len(sys.argv) != 2:
         sys.exit(__doc__)
@@ -175,10 +253,17 @@
         t = T0 + datetime.timedelta(seconds=5 * i)
         stamp = t.isoformat()
-        chunks.append((stamp, "CRAFT0IGS0", "RTCM_3", vtec_igs(t, layers)
-                       + service_crs() + helmert() + rtcm_crs()))
+        data = vtec_igs(t, layers) + service_crs() + helmert() + rtcm_crs()
+        if i == 0:
+            data += descriptors(["LEIAR25.R4      LEIT", "12345", "SEPT POLARX5",
+                                 "5.5.0", "3001234"])
+            data += descriptors(["TRUNCATED ANTENNA", "9", "RECEIVER", "", ""],
+                                truncate=True)
+        chunks.append((stamp, "CRAFT0IGS0", "RTCM_3", data))
         chunks.append((stamp, "CRAFT0RTC0", "RTCM_3", vtec_rtcm(t, layers)))
     stamp = (T0 + datetime.timedelta(seconds=15)).isoformat()
     chunks.append((stamp, "CRAFT0NEW0", "RTCM_3",
-                   metadata([40, 0] + [None] * 5) + metadata([None] * 31)))
+                   metadata([40, 0] + [None] * 5) + metadata([None] * 31)
+                   + antenna(83, [1, 17, 33, 49, 64], 0, [-7, 0, 7, 3])
+                   + antenna(80, [1, 2, 32], 1, [5, -8, -1, 2])))
     write_chunks(sys.argv[1], chunks)
 
Index: /trunk/BNC/tests/tools/ssr_harness.cpp
===================================================================
--- /trunk/BNC/tests/tools/ssr_harness.cpp	(revision 11129)
+++ /trunk/BNC/tests/tools/ssr_harness.cpp	(revision 11130)
@@ -7,7 +7,8 @@
 // reported by AddressSanitizer instead of silently corrupting the next member.
 //
-// Every decoded VTEC (all formats) and metadata message (new RTCM-SSR) is
-// encoded again with the Make* function of the same class and decoded once
-// more; the result must be identical (exit code 1 otherwise).
+// Every decoded VTEC (all formats), metadata and satellite antenna message
+// (new RTCM-SSR) is encoded again with the Make* function of the same class
+// and decoded once more; the result must be identical, for the antenna
+// messages also the bytes of the frame (exit code 1 otherwise).
 //
 // Usage: ssr_harness <file>   (BNC raw file or plain RTCM3 byte stream)
@@ -96,4 +97,5 @@
   int irc = t1.decode(ssr, frame, size);
   printf("  %-8s irc %3d", name, irc);
+  SsrCorrRtcmNew* rtcmNew = dynamic_cast<SsrCorrRtcmNew*>(ssr);
   if (irc >= 0 && t1.v->NumLayers) {
     double sum = 0.0;
@@ -125,5 +127,4 @@
       }
     }
-    SsrCorrRtcmNew* rtcmNew = dynamic_cast<SsrCorrRtcmNew*>(ssr);
     if (rtcmNew) {
       char buffer[CLOCKORBIT_BUFFERSIZE];
@@ -134,4 +135,20 @@
       nFailed += !ok;
     }
+  }
+  for (unsigned s = 0; rtcmNew && irc >= 0 && s < CLOCKORBIT_SATNUM; s++) {
+    if (!t1.a->SatelliteMask[s]) {
+      continue;
+    }
+    printf("  ANT type %u mask %016llx", t1.a->messageType[s],
+           (unsigned long long)t1.a->SatelliteMask[s]);
+    char buffer[CLOCKORBIT_BUFFERSIZE];
+    size_t len = rtcmNew->MakeAntenna(t1.a, t1.a->messageType[s], buffer, sizeof(buffer));
+    t_target t2;
+    bool sameBytes = len == size && memcmp(buffer, frame, size) == 0;
+    bool ok = len > 0 && t2.decode(ssr, (const unsigned char*)buffer, len) >= 0 &&
+              memcmp(t1.a, t2.a, sizeof(*t1.a)) == 0;
+    printf("  re-encoded: %s, %s", ok ? "identical" : "DIFFERENT",
+           sameBytes ? "same bytes" : "OTHER BYTES");
+    nFailed += !ok || !sameBytes;
   }
   printf("\n");
