Changeset 11127 in ntrip for trunk/BNC/tests/tools


Ignore:
Timestamp:
Oct 24, 2026, 11:15:22 PM (3 weeks ago)
Author:
stuerze
Message:

test suite extended regardiing security aspects

Location:
trunk/BNC/tests/tools
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/tests/tools/bncraw.py

    r11069 r11127  
    99    info  <raw> [<raw> ...]          streams, byte counts, time span, RTCM3 message types
    1010    slice <raw> <out> [options]      cut a time window / subset of stations
     11    crs   <raw>                      decode RTCM 1300/1301/1302 (CRS messages)
    1112"""
    1213
    … …  
    110111        return "4076_%03d" % sub
    111112    return str(t)
     113
     114
     115class BitReader:
     116    def __init__(self, data):
     117        self.data = data
     118        self.pos = 0
     119
     120    def u(self, n):
     121        v = 0
     122        for _ in range(n):
     123            if self.pos >= 8 * len(self.data):
     124                raise ValueError("message too short")
     125            v = (v << 1) | ((self.data[self.pos // 8] >> (7 - self.pos % 8)) & 1)
     126            self.pos += 1
     127        return v
     128
     129    def s(self, n):
     130        v = self.u(n)
     131        return v - (1 << n) if v >> (n - 1) else v
     132
     133    def text(self):
     134        n = self.u(5)
     135        return bytes(self.u(8) for _ in range(n)).decode("latin-1")
     136
     137    def left(self):
     138        return 8 * len(self.data) - self.pos
     139
     140
     141def decode_crs(p):
     142    """One line per 1300/1301/1302 message (RTCM 10403.4, DF562-DF587)."""
     143    r = BitReader(p)
     144    t = r.u(12)
     145    if t == 1300:
     146        out = "1300 name %r CE %.2f" % (r.text(), r.u(16) / 100.0)
     147    elif t == 1301:
     148        src, tgt = r.text(), r.text()
     149        out = "1301 source %r target %r sys %d util %d mjd %d par" % (
     150            src, tgt, r.u(8), r.u(10), r.u(16) + 44244)
     151        for n in (23, 23, 23, 32, 32, 32, 25, 17, 17, 17, 17, 17, 17, 14):
     152            out += " %d" % r.s(n)
     153    else:
     154        out = "1302 name %r anchor %d plate %d links" % (r.text(), r.u(1), r.u(5))
     155        out += " %r" % [r.text() for _ in range(r.u(3))]
     156    if r.left() >= 8:  # more than the padding to the byte boundary
     157        out += "  TRAILING %d bits" % r.left()
     158    return out
    112159
    113160
    … …  
    166213
    167214
     215def cmd_crs(args):
     216    """Distinct CRS message contents per stream, with their number."""
     217    stats = collections.OrderedDict()
     218    framers = {}
     219    for t, sta, fmt, data in read_chunks(args.input):
     220        for p in framers.setdefault(sta, Rtcm3Framer()).feed(data):
     221            if msg_type(p) in ("1300", "1301", "1302"):
     222                try:
     223                    line = decode_crs(p)
     224                except ValueError as e:
     225                    line = "%s %s" % (msg_type(p), e)
     226                key = (sta, line)
     227                stats[key] = stats.get(key, 0) + 1
     228    for (sta, line), n in stats.items():
     229        print("%s %4dx %s" % (sta, n, line))
     230
     231
    168232def main():
    169233    ap = argparse.ArgumentParser(description=__doc__,
    … …  
    183247    p.set_defaults(func=cmd_slice)
    184248
     249    p = sub.add_parser("crs", help="decode CRS messages 1300/1301/1302")
     250    p.add_argument("input")
     251    p.set_defaults(func=cmd_crs)
     252
    185253    args = ap.parse_args()
    186254    args.func(args)
  • trunk/BNC/tests/tools/fakecaster.py

    r11074 r11127  
    1111
    1212    fakecaster.py --port 21011 --out upload.raw --sta CLOCK --time 2026-10-01T09:30:00
     13
     14With --tls-cert/--tls-key the caster speaks TLS (Ntrip 2s); --tls-client-ca
     15additionally requires a client certificate signed by that CA. With --serve
     16a GET request (BNC as Ntrip client) is answered with the RTCM bytes of the
     17given raw file. Failed TLS handshakes are reported on stdout.
    1318"""
    1419
    1520import argparse
     21import os
    1622import socket
     23import ssl
    1724import sys
    1825import threading
     26
     27sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    1928
    2029
    … …  
    4554        self.head = "%s %s %s" % (time, sta, fmt)
    4655        self.total = 0
     56        self.handshakes = 0
     57        self.served = 0
    4758
    4859    def write(self, data):
    … …  
    5667
    5768
    58 def serve(conn, store, timeout):
     69def serve(conn, store, timeout, tls=None, served=None):
    5970    conn.settimeout(timeout)
    6071    buf = b""
    6172    try:
     73        if tls:
     74            try:
     75                conn = tls.wrap_socket(conn, server_side=True)
     76            except (ssl.SSLError, OSError) as e:
     77                print("tls handshake failed: %s" % e, flush=True)
     78                return
     79            store.handshakes += 1
    6280        while b"\r\n\r\n" not in buf:
    6381            d = conn.recv(4096)
    … …  
    6785        header, _, pending = buf.partition(b"\r\n\r\n")
    6886        chunked = b"chunked" in header.lower()
     87        if header.startswith(b"GET") and served is not None:
     88            conn.sendall(b"HTTP/1.1 200 OK\r\nNtrip-Version: Ntrip/2.0\r\n"
     89                         b"Content-Type: gnss/data\r\nConnection: close\r\n\r\n" + served)
     90            store.served += len(served)
     91            return
    6992        if header.startswith(b"SOURCE"):
    7093            conn.sendall(b"ICY 200 OK\r\n\r\n")
    … …  
    100123    ap.add_argument("--timeout", type=float, default=600.0,
    101124                    help="stop after this many seconds without a new connection or data")
     125    ap.add_argument("--tls-cert", help="server certificate (PEM): TLS")
     126    ap.add_argument("--tls-key", help="server private key (PEM)")
     127    ap.add_argument("--tls-client-ca", help="require a client certificate of this CA")
     128    ap.add_argument("--serve", help="raw file whose RTCM bytes are sent on GET")
    102129    args = ap.parse_args()
     130
     131    tls = None
     132    if args.tls_cert:
     133        tls = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
     134        tls.load_cert_chain(args.tls_cert, args.tls_key)
     135        if args.tls_client_ca:
     136            tls.verify_mode = ssl.CERT_REQUIRED
     137            tls.load_verify_locations(args.tls_client_ca)
     138    served = None
     139    if args.serve:
     140        from bncraw import read_chunks  # noqa: E402
     141        served = b"".join(d for t, sta, fmt, d in read_chunks(args.serve))
    103142
    104143    store = Store(args.out, args.sta, args.format, args.time)
    … …  
    116155        except socket.timeout:
    117156            break
    118         t = threading.Thread(target=serve, args=(conn, store, args.timeout), daemon=True)
     157        t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served),
     158                             daemon=True)
    119159        t.start()
    120160        threads.append(t)
    … …  
    122162        t.join(args.timeout)
    123163    print("received %d bytes in %d connection(s)" % (store.total, len(threads)), flush=True)
     164    if tls:
     165        print("%d TLS handshake(s) completed" % store.handshakes, flush=True)
     166    if served is not None:
     167        print("served %d bytes" % store.served, flush=True)
    124168    return 0
    125169
  • trunk/BNC/tests/tools/ssr_harness.cpp

    r11125 r11127  
    66// struct is a separate heap block here, so a write beyond a struct is
    77// reported by AddressSanitizer instead of silently corrupting the next member.
     8//
     9// Every decoded VTEC (all formats) and metadata message (new RTCM-SSR) is
     10// encoded again with the Make* function of the same class and decoded once
     11// more; the result must be identical (exit code 1 otherwise).
    812//
    913// Usage: ssr_harness <file>   (BNC raw file or plain RTCM3 byte stream)
    … …  
    3337}
    3438
     39struct t_target {
     40  t_target()  : co(new SsrCorr::ClockOrbit()), cb(new SsrCorr::CodeBias()),
     41                v(new SsrCorr::VTEC()), pb(new SsrCorr::PhaseBias()),
     42                md(new SsrCorr::MetaData()), a(new SsrCorr::Antenna()) {}
     43  ~t_target() {delete co; delete cb; delete v; delete pb; delete md; delete a;}
     44  int decode(SsrCorr* ssr, const unsigned char* frame, size_t size) {
     45    int bytesused = 0;
     46    return ssr->GetSSR(co, cb, v, pb, md, a, (const char*)frame, size, &bytesused);
     47  }
     48  SsrCorr::ClockOrbit* co;
     49  SsrCorr::CodeBias*   cb;
     50  SsrCorr::VTEC*       v;
     51  SsrCorr::PhaseBias*  pb;
     52  SsrCorr::MetaData*   md;
     53  SsrCorr::Antenna*    a;
     54};
     55
     56static bool sameVTEC(const SsrCorr::VTEC* v1, const SsrCorr::VTEC* v2) {
     57  if (v1->EpochTime != v2->EpochTime || v1->NumLayers != v2->NumLayers ||
     58      v1->Quality != v2->Quality) {
     59    return false;
     60  }
     61  for (unsigned l = 0; l < v1->NumLayers; l++) {
     62    const SsrCorr::VTEC::IonoLayers& l1 = v1->Layers[l];
     63    const SsrCorr::VTEC::IonoLayers& l2 = v2->Layers[l];
     64    if (l1.Height != l2.Height || l1.Degree != l2.Degree || l1.Order != l2.Order) {
     65      return false;
     66    }
     67    for (unsigned d = 0; d <= l1.Degree; d++) {
     68      for (unsigned o = 0; o <= l1.Order; o++) {
     69        if (l1.Cosinus[d][o] != l2.Cosinus[d][o] || l1.Sinus[d][o] != l2.Sinus[d][o]) {
     70          return false;
     71        }
     72      }
     73    }
     74  }
     75  return true;
     76}
     77
     78static bool sameMetaData(const SsrCorr::MetaData* m1, const SsrCorr::MetaData* m2) {
     79  if (m1->SSRIOD != m2->SSRIOD || m1->SSRProviderID != m2->SSRProviderID ||
     80      m1->SSRSolutionID != m2->SSRSolutionID || m1->NumEntries != m2->NumEntries) {
     81    return false;
     82  }
     83  for (unsigned i = 0; i < m1->NumEntries; i++) {
     84    if (memcmp(&m1->Entries[i], &m2->Entries[i], sizeof(m1->Entries[i])) != 0) {
     85      return false;
     86    }
     87  }
     88  return true;
     89}
     90
     91static int nFailed = 0;
     92
     93// Decode, print a summary, encode the result again and compare
    3594static void decode(const char* name, SsrCorr* ssr, const unsigned char* frame, size_t size) {
    36   SsrCorr::ClockOrbit* co = new SsrCorr::ClockOrbit();
    37   SsrCorr::CodeBias*   cb = new SsrCorr::CodeBias();
    38   SsrCorr::VTEC*       v  = new SsrCorr::VTEC();
    39   SsrCorr::PhaseBias*  pb = new SsrCorr::PhaseBias();
    40   SsrCorr::MetaData*   md = new SsrCorr::MetaData();
    41   SsrCorr::Antenna*    a  = new SsrCorr::Antenna();
    42   int bytesused = 0;
    43 
    44   int irc = ssr->GetSSR(co, cb, v, pb, md, a, (const char*)frame, size, &bytesused);
     95  t_target t1;
     96  int irc = t1.decode(ssr, frame, size);
    4597  printf("  %-8s irc %3d", name, irc);
    46   if (irc >= 0 && v->NumLayers) {
     98  if (irc >= 0 && t1.v->NumLayers) {
    4799    double sum = 0.0;
    48100    printf("  VTEC");
    49     for (unsigned l = 0; l < v->NumLayers; l++) {
    50       const SsrCorr::VTEC::IonoLayers& layer = v->Layers[l];
     101    for (unsigned l = 0; l < t1.v->NumLayers; l++) {
     102      const SsrCorr::VTEC::IonoLayers& layer = t1.v->Layers[l];
    51103      printf(" %u/%u", layer.Degree, layer.Order);
    52104      for (unsigned d = 0; d <= layer.Degree; d++) {
    … …  
    57109    }
    58110    printf("  sum(C-S) %.3f", sum);
     111
     112    char buffer[CLOCKORBIT_BUFFERSIZE];
     113    size_t len = ssr->MakeVTEC(t1.v, 0, buffer, sizeof(buffer));
     114    t_target t2;
     115    bool ok = len > 0 && t2.decode(ssr, (const unsigned char*)buffer, len) >= 0 && sameVTEC(t1.v, t2.v);
     116    printf("  re-encoded: %s", ok ? "identical" : "DIFFERENT");
     117    nFailed += !ok;
    59118  }
    60   if (irc >= 0 && md->NumEntries) {
    61     printf("  META %u entries, additional data", md->NumEntries);
    62     for (unsigned i = 0; i < md->NumEntries; i++) {
    63       if (md->Entries[i].additionalParameterIndicator) {
    64         printf(" %u:0x%x", md->Entries[i].additionalParameterNumBits,
    65                md->Entries[i].additionalParameterData);
     119  if (irc >= 0 && t1.md->NumEntries) {
     120    printf("  META %u entries, additional data", t1.md->NumEntries);
     121    for (unsigned i = 0; i < t1.md->NumEntries; i++) {
     122      if (t1.md->Entries[i].additionalParameterIndicator) {
     123        printf(" %u:0x%x", t1.md->Entries[i].additionalParameterNumBits,
     124               t1.md->Entries[i].additionalParameterData);
    66125      }
     126    }
     127    SsrCorrRtcmNew* rtcmNew = dynamic_cast<SsrCorrRtcmNew*>(ssr);
     128    if (rtcmNew) {
     129      char buffer[CLOCKORBIT_BUFFERSIZE];
     130      size_t len = rtcmNew->MakeMetaData(t1.md, buffer, sizeof(buffer));
     131      t_target t2;
     132      bool ok = len > 0 && t2.decode(ssr, (const unsigned char*)buffer, len) >= 0 && sameMetaData(t1.md, t2.md);
     133      printf("  re-encoded: %s", ok ? "identical" : "DIFFERENT");
     134      nFailed += !ok;
    67135    }
    68136  }
    69137  printf("\n");
    70 
    71   delete co; delete cb; delete v; delete pb; delete md; delete a;
    72138}
    73139
    … …  
    111177    ii += len + 6;
    112178  }
     179  if (nFailed) {
     180    printf("%d decoded messages differ after re-encoding\n", nFailed);
     181    return 1;
     182  }
    113183  return 0;
    114184}
Note: See TracChangeset for help on using the changeset viewer.