# Diss Pipeline

> Use when working on the DISS binary file processing pipeline — filters, parsers, nav_utils, config files, service descriptors, or any part of the ground-to-space uplink data flow in this eLTS Galileo project.

- Skill: `iabuelruzz/diss-pipeline` (Agent Skill)
- Install (CLI): `npx skillmds@latest add iabuelruzz/diss-pipeline`
- Raw SKILL.md: https://api.skillmd.com/api/skills/iabuelruzz/diss-pipeline/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: project-internal
- Author: IAbuElRuzz (https://skillmd.com/u/iabuelruzz)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/iabuelruzz/diss-pipeline

---


# DISS Pipeline

Expert guide for the eLTS DISS binary-file processing pipeline (C-band uplink, G1 Galileo).

## Architecture Overview

```
DISS binary file
    └─ DissFilter (src/filter/diss_filter.py)
           ├─ _process_normal_band_block()   ─► MsgSubfrParser  ─► FNavParser / INavParser / CNavParser / GNavParser
           │                                  ► IntSubfrParser
           └─ _process_s_band_block()        ─► SBandParser    ─► FNavParser / INavParser / CNavParser / GNavParser
```

Each parser delegates per-word extraction to `parser/nav_utils/{f_nav,i_nav,c_nav,g_nav}/` where the bitfield descriptors live.

---

## Key Classes

| Class | File | Role |
|---|---|---|
| `DissFilter` | `filter/diss_filter.py` | Top-level orchestrator; reads raw DISS binary, splits into normal-band and S-band blocks, dispatches to sub-parsers |
| `MsgSubfrParser` | `parser/diss/msg_subfr_parser.py` | Normal-band **Message** sub-frame (Field 1 = FNAV/INAV/SAR/CNAV, Field 2 = GNAV) |
| `IntSubfrParser` | `parser/diss/int_subfr_parser.py` | Normal-band **Integrity** sub-frame |
| `SBandParser` | `parser/diss/s_band_parser.py` | S-band 26080-byte fixed packet; dispatches by nav type and batch number |
| `FNavParser` | `parser/diss/fnav_parser.py` | Extracts F/NAV pages (normal-band + S-band) |
| `INavParser` | `parser/diss/inav_parser.py` | Extracts I/NAV pages (normal-band + S-band) |
| `CNavParser` | `parser/diss/cnav_parser.py` | Extracts C/NAV pages (S-band only) |
| `GNavParser` | `parser/diss/gnav_parser.py` | Extracts G/NAV pages (normal-band Field 2 + S-band nav/almanac/PRS) |
| `NavParser` | `parser/diss/nav_parser.py` | Abstract base class; provides `_push_message()` |

---

## Config Files

| File | Purpose |
|---|---|
| `config/s_band_config.json` | All S-band bit sizes (`HEADER`, `BATCH_START`, `G_NAV_WORD`, `G_NAV_BATCH`, `FNAV_BATCH`, `INAV_BATCH`, …) and bit offsets (`GNAV_OFFSET`, `GNAV_ALMANAC_OFFSET`, `GNAV_PRS_OFFSET`, …) |
| `config/diss_config.json` | Service descriptors, INAV uplink message sizes, file header offsets |
| `config/frames/msg_subframe.json` | Normal-band Message sub-frame layout: 12 packet slots, each 264 bits. `lookupValue` field drives `page_info` routing. Field 2 packets (6-11, G/NAV) have **no `lookupValue`**. |
| `config/common_config.json` | Shared constants used across parsers |
| `config/{f_nav,i_nav,c_nav,g_nav}/` | Per-nav-type bitfield descriptor JSON files; loaded by nav_utils uplink classes |

---

## Service Descriptors (Normal-Band)

Defined in `config/diss_config.json → SERVICE_DESCRIPTORS`:

| Descriptor | Value | Packet(s) | Nav type |
|---|---:|---|---|
| FNAV | 0 | Packets 0-1 | F/NAV |
| INAV | 1 | Packets 1-2 | I/NAV |
| CNAV | 2 | Packets 4-5 | C/NAV |
| SAR | 3 | Packet 3 | SAR |
| PRS(1/2) | 4 | Packets 6,8,10 | G/NAV first half |
| PRS(2/2) | 5 | Packets 7,9,11 | G/NAV second half |
| Dummy | 6 (110 binary) | — | Skip slot |
| Spare | 7 (111 binary) | — | Skip slot |

---

## Normal-Band Packet Layout (§4.2.4.2, `msg_subframe.json`)

Each of the 12 packets in the Message sub-frame is **264 bits (33 bytes)**:
- Bits 0-2: 3-bit **service descriptor**
- Bits 3-263: 261 bits of payload data

**Field 1** (packets 0-5): FNAV, INAV, SAR, CNAV pages — each has a `lookupValue` in `msg_subframe.json`, so `collect_lookup_info()` can build `page_info`.

**Field 2** (packets 6-11): G/NAV PRS pairs — **no `lookupValue`** → `page_info["GNAV"]` never exists → `msg_subfr_parser.extract_nav_page()` must branch for GNAV *before* the `page_info` lookup block.

---

## S-Band Packet Layout (§4.2.4.2, Figure 18)

Fixed packet size: **26080 bytes** (`S_BAND_PACKET_LEN` in `s_band_config.json`).
All bit offsets below are **data-relative** (i.e. after the 184-bit `HEADER`).

| Section | Bit offset (data-relative) | Batch size | Slot count |
|---|---:|---:|---:|
| F/NAV nav | 0 | 1000 bits | 8 slots |
| F/NAV almanac | 8000 bits | 5760 bits | 1 slot |
| I/NAV nav | 13760 bits | 816 bits | 8 slots |
| I/NAV almanac | 20288 bits | 6192 bits | 1 slot |
| G/NAV nav (`GNAV_OFFSET`) | 26480 bits | 1464 bits | 9 slots |
| G/NAV almanac (`GNAV_ALMANAC_OFFSET`) | 39656 bits | 2600 bits | 8 slots |
| G/NAV PRS (`GNAV_PRS_OFFSET`) | 60456 bits | 7408 bits | 20 slots |

Access pattern (same for all nav types):
```python
data_bits = bytes_to_bits(packet)
section = data_bits[HEADER:][slot_start : slot_start + batch_size]
```

---

## BATCH_START Structure (48 bits)

Every nav/almanac/PRS batch begins with a 48-bit header:

```
bits 0-3:   TYPE_NAV_SV_DATA  (4 bits)
bits 4-9:   LAST_VALID_WORD_IDX (6 bits)
bits 10-15: MSG_NUM            (6 bits)
bits 16-47: TOA                (32 bits)
```

Within TOA (bits 16-47 of BATCH_START):
- `TOW`: bits 16-35 (20 bits)
- `WN`:  bits 36-47 (12 bits)

---

## subframe_id and Slot Selection

```python
# src/parser/diss/common_utils.py
def generate_subframe_id(time_ms: int) -> int:
    return ((time_ms // 1000) + 1) % 30   # → 0..29
```

Used by `GNavParser.extract_gnav_page()` to select the G/NAV word slot in Field 2:
```python
slot_idx = subframe_id % 3   # → 0, 1, or 2
```

---

## Symmetry Pattern for Nav Parsers

All nav parsers follow the same pattern:

```python
class XNavParser(NavParser):
    def extract_xnav_page(self, subframe, req_page_type, subframe_id, subframe_data, msg_filt):
        """Normal-band extraction."""
        ...

    def extract_xnav_page_S_band(self, packet, batch_number, req_page_type, subframe_data, msg_filt):
        """S-band nav batch extraction."""
        ...

    def extract_xnav_almanac_S_band(self, packet, batch_number, req_page_type, subframe_data, msg_filt):
        """S-band almanac batch extraction."""
        ...
```

When adding a new nav type or method, wire it in **both**:
1. `msg_subfr_parser.MsgSubfrParser.extract_nav_page()` — normal-band routing
2. `s_band_parser.SBandParser.extract_nav_page()` — S-band routing

And add headers in `__init__` of both parsers.

---

## `_parse_packet()` Return Type

`SBandParser._parse_packet()` returns a **tuple** `(fieldIndex, number_of_mismatch)` — never a bare int. Callers unpack with:
```python
processed_count, _ = self.s_band_parser._parse_packet(...)
```

A bare `return self.fieldIndex` on early-exit paths is a bug; it must be `return self.fieldIndex, self.number_of_mismatch`.

---

## Byte Alignment Rule

`utils.bits_to_bytes(bit_string)` **requires** `len(bit_string) % 8 == 0`. Always pad before converting:
```python
pad = (-n_bits) % 8
word_bytes = bits_to_bytes(word_bits + "0" * pad)
```

G_NAV_WORD = 283 bits → pad 5 zeros → 288 bits = 36 bytes  
Pre-encryption G/NAV = 164 bits → pad 4 zeros → 168 bits = 21 bytes

