AN-002: RTCM SC-104 Frame Structure & Message Type Reference
On this page
Document Scope
AN-002 is a practical RTCM 3.x reference for reading binary frames, decoding MSM observations and checking reference-station records. Start with the task index, then use the field tables and complete Hex examples to compare your parser’s output.
RTCM 3 uses a six-byte frame envelope around a variable-length payload: a three-byte header and a three-byte CRC-24Q. The payload starts with a 12-bit message number. The tables below use payload-relative bit offsets, so bit 0 begins immediately after the three-byte header.
For message selection, link capacity and receiver-side diagnosis, read RTCM Unpacked: message selection, bandwidth and diagnostics. Here, the worked examples cover MSM4/MSM7, MT1005/1006 and MT1230. Signal tables distinguish transmitted IDs from library indexes and identify the version basis for modern BeiDou signals.
RTCM Message and Parser Quick Index
Jump to the question you are trying to answer:
| Task | Message or field | Go to |
|---|---|---|
| Find a frame | 0xD3, length and CRC | Frame envelope · stream synchronization |
| Decode observations | MT1071–1137, MSM1–7 | MSM content · complete MSM4 example |
| Resolve masks and signals | DF394–396, constellation-specific IDs | Mask ordering · signal tables |
| Handle missing measurements | DF397–408, DF420 | Satellite fields · cell fields and lock time |
| Read reference coordinates | MT1005 / MT1006 | ARP coordinates · antenna height |
| Check interoperability | MT1033 / MT1230 | Descriptors · GLONASS biases |
| Inspect other message groups | Legacy, ephemeris and SSR | Message reference and byte budgets |
| Test a decoder | 64-bit extraction and known outputs | C reader and test-vector checks |
RTCM Frame Architecture and Parser Fundamentals
Data Type Conventions
Read bits most significant first within each byte. Fields often cross byte boundaries. In the MSM and coordinate tables, use these conventions:
| Notation | Interpretation | Implementation detail |
|---|---|---|
| bit(n) | An n-bit pattern | Preserve the defined bit order when reading masks and flags |
| uint(n) | Unsigned integer | Range 0 to 2ⁿ−1; a reserved or unavailable code may occupy part of that range |
| int(n) | Two’s-complement integer | Sign-extend from the field’s own width before applying its scale |
| Physical value | Raw integer × resolution | Check the field’s unavailable code before converting to metres, milliseconds or dB-Hz |
Read the signed fields tabulated here with two’s-complement extraction; check other message definitions for encodings such as sign-magnitude. RTCM payloads require explicit bit extraction: C struct layout, padding and alignment cannot represent arbitrary protocol bit offsets. A 38-bit ECEF coordinate needs a result wider than a 32-bit C int. The C reader handles fields up to 64 bits with bounds checks.
General Frame Structure
The payload length excludes the header and CRC. A message-number decoder needs at least two payload bytes even though the ten-bit length field can represent zero:
| Part | Width | Meaning |
|---|---|---|
| Preamble | 8 bits | 0xD3 |
| Reserved | 6 bits | Zero in this frame format |
| Payload length L | 10 bits | 0–1023 bytes; complete frame size is L + 6 |
| Payload | L bytes | First 12 bits are DF002, the message number |
| CRC-24Q | 24 bits | Computed over header plus payload; transmitted most significant byte first |
Byte 0 : D3
Bytes 1–2 : six reserved bits + ten-bit payload length
Bytes 3..L+2 : payload; DF002 starts at the MSB of byte 3
Bytes L+3..L+5: CRC-24Q
L = ((frame[1] & 0x03) << 8) | frame[2]
message_type = (frame[3] << 4) | (frame[4] >> 4)
Capture RTCM as raw binary bytes. A text logger can alter non-printable values, line endings or character encodings and destroy frame integrity. Use Hex only as a readable representation of those bytes, as in the complete examples below.
CRC-24Q Algorithm
Validate the complete frame before dispatching its payload. The parameter set is:
| Parameter | Value |
|---|---|
| Polynomial | 0x1864CFB including the x²⁴ term; 0x864CFB when that term is implicit |
| Initial value / final XOR | 0 / 0 |
| Bit reflection | None on input or output |
| Input bytes | Header and payload, excluding the final three CRC bytes |
| Check input | ASCII bytes for 123456789 → 0xCDE703 |
#include <stddef.h>
#include <stdint.h>
static uint32_t crc24q(const uint8_t *data, size_t size)
{
uint32_t crc = 0;
for (size_t i = 0; i < size; ++i) {
crc ^= (uint32_t)data[i] << 16;
for (unsigned j = 0; j < 8; ++j) {
crc <<= 1;
if (crc & UINT32_C(0x1000000))
crc ^= UINT32_C(0x1864CFB);
}
}
return crc & UINT32_C(0xFFFFFF);
}
Frame Synchronization
A read operation is a transport boundary, not a message boundary. Preserve unfinished bytes between serial or socket reads and extract zero, one or several frames from each chunk:
| State | Action |
|---|---|
| Find candidate | Scan for 0xD3; keep the candidate until its header is available |
| Read header | Check reserved bits and extract L; reject a payload too short for the message number |
| Wait for body | Wait for L + 6 complete bytes; track candidate age separately from CRC failures |
| Validate CRC | On failure, resume searching one byte after the candidate preamble |
| Dispatch | Decode the supported message; a valid unknown type can be logged or skipped by its frame length |
A false candidate with a large length can delay processing. Set an application-specific incomplete-frame deadline and connection-reset policy, allowing enough time for a valid frame on the slowest supported link. Do not treat every embedded 0xD3 as a new frame. A ten-bit length cannot exceed 1023, so a length > 1023 condition does not detect false synchronization. The Python extractor in the companion guide demonstrates incremental buffering; its waiting behavior is explicit.
Multiple Signal Messages: MT1071–MT1137
Constellation Assignment
The message number supplies the constellation and MSM layout. The 30-bit epoch slot uses constellation-specific time conventions:
| Constellation | Message family | Epoch handling |
|---|---|---|
| GPS | 1071–1077 | DF004: GPS time, milliseconds of week |
| GLONASS | 1081–1087 | DF416: three-bit day number; DF034: 27-bit milliseconds of day in GLONASS time |
| Galileo | 1091–1097 | DF248: milliseconds of Galileo week |
| SBAS | 1101–1107 | DF004: milliseconds of GPS week |
| QZSS | 1111–1117 | DF428: milliseconds of GPS week |
| BeiDou | 1121–1127 | DF427: milliseconds of BeiDou week; normalize before combining with GPS time |
| NavIC | 1131–1137 | DF546: milliseconds of week; apply the epoch definition supported by the receiver |
Constellation-specific satellite IDs also need their own mapping. Do not prefix every set satellite-mask position with a GPS PRN label. Time normalization, week/day rollover and source association belong in the observation assembler, after frame decoding.
MSM Type Definitions: Content and Bit Cost
The last digit selects the observation layout. The following lengths exclude the common header and masks:
| MSM | Satellite bits | Cell bits | Included observations |
|---|---|---|---|
| 1 | 10 | 15 | Compact code observations; no carrier phase |
| 2 | 10 | 27 | Compact carrier phase, lock and half-cycle flag |
| 3 | 10 | 42 | Compact code and carrier phase, lock and half-cycle flag |
| 4 | 18 | 48 | Full-range code and phase, lock, half-cycle flag and CNR |
| 5 | 36 | 63 | MSM4 content plus extended satellite information and phase-range rate |
| 6 | 18 | 65 | Higher-resolution code, phase, lock and CNR |
| 7 | 36 | 80 | MSM6 content plus extended satellite information and phase-range rate |
MSM1 has fine pseudorange as well as rough-range information, but no phase observation; it cannot alone supply carrier-phase RTK. MSM2/3 use compact ranges that require ambiguity reconstruction. MSM4 and MSM7 below provide complete worked layouts. Higher field resolution describes quantization, not the accuracy of a resulting position.
MSM Payload Architecture
MSM transmits arrays one field at a time. Read a field for every selected satellite before advancing to the next satellite field. Then do the same for every active cell. The wire format is not a sequence of complete satellite objects.
Common header → satellite mask → signal mask → cell mask
DF397[all satellites] → DF398[all satellites]
DF400[all active cells] → DF401[all active cells]
DF402[all active cells] → DF420[all active cells]
DF403[all active cells] → zero padding to the next byte
MSM5/7 insert a four-bit extended-information array after DF397 and the DF399 rough-rate array after DF398. MSM7 then uses DF405, DF406, DF407, DF420, DF408 and DF404 cell arrays in that order. Keep the on-wire arrays separate from the satellite/cell objects your application builds.
MSM Header: Fields and Payload Offsets
Offsets are measured from the start of the payload, not from 0xD3. The fixed portion ends at bit 169, immediately before the variable cell mask:
| Field | Offset | Bits | Interpretation |
|---|---|---|---|
| DF002 | 0 | 12 | Message number |
| DF003 | 12 | 12 | Reference Station ID |
| Epoch | 24 | 30 | Constellation-specific time; DF004 for the GPS example |
| DF393 | 54 | 1 | Multiple Message Bit: 1 means more MSM messages follow for the observation epoch; 0 marks the last |
| DF409 | 55 | 3 | Issue Of Data Station; reserved for site-data association in the referenced layout, not an epoch counter |
| Reserved | 58 | 7 | Preserve alignment; do not treat as observations |
| DF411 | 65 | 2 | Clock Steering Indicator |
| DF412 | 67 | 2 | External Clock Indicator |
| DF417 | 69 | 1 | Divergence-Free Smoothing Indicator |
| DF418 | 70 | 3 | Smoothing Interval indicator; encoded interval, not raw seconds |
| DF394 | 73 | 64 | Satellite mask; most significant bit is ID 1 |
| DF395 | 137 | 32 | Signal mask; most significant bit is Signal ID 1 |
| DF396 | 169 | Nsat × Nsig | Cell mask over the selected satellite/signal matrix |
Keep the clock and smoothing indicators in diagnostic output, using the selected protocol revision for their enumerations. Group observations by source, station and normalized epoch. The multiple-message bit helps assemble an epoch; it is not a transport sequence number or proof that a complete epoch arrived.
Satellite, Signal and Cell Masks
Count set bits in the satellite and signal masks to obtain Nsat and Nsig. The cell mask contains Nsat × Nsig bits, including zero entries. Enumerate those bits in increasing satellite-ID order, then increasing Signal ID within each satellite. Ncell is the number of set bits in that matrix.
For GPS satellites 3, 6 and 12 with Signal IDs 2 and 17, the six-bit mask 111011 selects five cells:
| Satellite | ID 2: L1 C/A | ID 17: L2C M+L | Active cell sequence |
|---|---|---|---|
| 3 | 1 | 1 | Cell 1 → (3,2); cell 2 → (3,17) |
| 6 | 1 | 0 | Cell 3 → (6,2); no cell data for (6,17) |
| 12 | 1 | 1 | Cell 4 → (12,2); cell 5 → (12,17) |
The zero entry still occupies a mask bit. It does not occupy an entry in the later measurement arrays. Conversely, a selected cell can contain an unavailable measurement code: consume the encoded fields, then mark that measurement unavailable. Skipping its bits would corrupt every following field.
For the RTCM 3.2/3.3 MSM layout used here, keep the cell-mask matrix within 64 bits; split a larger set across messages. The constraint is Nsat × Nsig, not just Ncell. RTKLIB’s MSM encoder is a useful comparison for field order and mask handling.
H = 169 + Nsat * Nsig
MSM4 payload bits = H + 18 * Nsat + 48 * Ncell
MSM7 payload bits = H + 36 * Nsat + 80 * Ncell
Payload bytes = (payload_bits + 7) // 8
Frame bytes = payload_bytes + 6
Example: Nsat=3, Nsig=2, Ncell=5
MSM4: 469 bits + 3 padding bits = 59 payload bytes; 65 frame bytes
MSM7: 683 bits + 5 padding bits = 86 payload bytes; 92 frame bytes
MSM Satellite Data Fields
The rough range is shared by a satellite’s cells. These fields are transmitted as separate arrays in the order shown:
| Field | Type / layout | Resolution | Unavailable code and use |
|---|---|---|---|
| DF397 | uint8; MSM4–7 | 1 ms | Rough Range Integer. 255 (0xFF) is unavailable; otherwise supplies the integer milliseconds |
| Extended info | bit4; MSM5/7 | Constellation-specific | GLONASS uses it for frequency-channel information; do not apply that meaning to GPS |
| DF398 | uint10; MSM4–7 | 2⁻¹⁰ ms | Rough Range Modulo. 1023 is a valid fraction. Combine with a valid DF397; do not use 0x3FF as an invalid sentinel |
| DF399 | int14; MSM5/7 | 1 m/s | Rough Phase-Range Rate. −8192 (raw 0x2000) is unavailable; rough phase-range rate |
One millisecond of light travel is 299792.458 metres. With valid fields, rough_range_ms = DF397 + DF398/1024. A missing rough range prevents reconstruction of the dependent full range, even if the fine-range field itself is valid.
MSM Cell Fields, Unavailable Values and Lock Time
Field IDs matter: DF400 is fine pseudorange, DF401 is fine phase range, and DF402 is the four-bit lock indicator. The following table keeps raw unavailable patterns beside their signed values:
| Field | Type / layout | Resolution | Unavailable code or interpretation |
|---|---|---|---|
| DF400 | int15; MSM1/3/4/5 | 2⁻²⁴ ms | Fine Pseudorange. −16384; raw 0x4000. Fine pseudorange |
| DF401 | int22; MSM2/3/4/5 | 2⁻²⁹ ms | Fine Phase Range. −2097152; raw 0x200000. Fine phase range |
| DF402 | uint4; MSM2–5 | Encoded interval | Lock Time Indicator. Lock duration range; see table below |
| DF420 | bit1; MSM2–7 | Flag | Half-Cycle Ambiguity. 1 indicates half-cycle ambiguity; retain with the phase observation |
| DF403 | uint6; MSM4/5 | 1 dB-Hz | CNR. 0 means CNR unavailable; other observables can remain valid |
| DF404 | int15; MSM5/7 | 0.0001 m/s | Fine Phase-Range Rate. −16384; raw 0x4000. Fine phase-range rate |
| DF405 | int20; MSM6/7 | 2⁻²⁹ ms | Extended Fine Pseudorange. −524288; raw 0x80000. Fine pseudorange |
| DF406 | int24; MSM6/7 | 2⁻³¹ ms | Extended Fine Phase Range. −8388608; raw 0x800000. Fine phase range |
| DF407 | uint10; MSM6/7 | Encoded interval | Extended Lock Time Indicator. Extended lock duration; do not interpret as seconds |
| DF408 | uint10; MSM6/7 | 2⁻⁴ dB-Hz | Extended CNR. 0 means CNR unavailable; no automatic invalidation of code or phase |
For MSM4/5, valid full pseudorange is (rough_range_ms + DF400 × 2⁻²⁴) × 299792.458 metres. Replace DF400 and its scale with DF401 × 2⁻²⁹ for phase range. MSM6/7 use DF405 × 2⁻²⁹ and DF406 × 2⁻³¹. MSM5/7 phase-range rate is DF399 + DF404 × 0.0001 m/s when both rate fields are valid. Converting phase range to cycles also requires the correct signal wavelength; GLONASS FDMA depends on channel number.
DF402 reports a duration bin, not an exact duration or a cycle-slip counter. All interval limits below are milliseconds:
| Indicator | Minimum lock time | Upper bound, exclusive |
|---|---|---|
| 0 | 0 | 32 |
| 1 | 32 | 64 |
| 2 | 64 | 128 |
| 3 | 128 | 256 |
| 4 | 256 | 512 |
| 5 | 512 | 1024 |
| 6 | 1024 | 2048 |
| 7 | 2048 | 4096 |
| 8 | 4096 | 8192 |
| 9 | 8192 | 16384 |
| 10 | 16384 | 32768 |
| 11 | 32768 | 65536 |
| 12 | 65536 | 131072 |
| 13 | 131072 | 262144 |
| 14 | 262144 | 524288 |
| 15 | 524288 | No finite upper bound |
For DF407, indicators 0–63 have one-millisecond bins. For 64–703, let b = floor(i/32) − 1 and step = 2ᵇ ms: the lower bound is (i − 32b) × step, and the next bound is one step higher. Indicator 704 represents at least 67108864 ms; values above it are reserved in this mapping. Thus i=100 represents 144–148 ms, not 100 seconds. Track the same signal across epochs and keep the half-cycle flag when deriving loss-of-lock information.
Worked MSM4 Frame: Three Satellites, Two Signals, Five Cells
This constructed decoder test includes negative fine ranges, a missing cell and a half-cycle flag. The complete MT1074 frame is:
D3 00 3B 43 20 2A 52 41 21 00 00 00 12 08 00 00
00 00 00 00 20 00 40 00 76 8C 8E 91 00 20 18 00
3E 8F 06 00 00 00 FA 7D 12 01 38 83 F8 AD 00 09
C4 00 61 A8 FF 15 A1 9E 26 89 6D 74 5F 50 A0 21
25
The header decodes to Station ID 42 and GPS time-of-week 345000000 ms. All clock/smoothing flags, IODS and the multiple-message bit are zero. Satellite mask = 0x2410000000000000; signal mask = 0x40008000; cell mask = 111011. CRC-24Q is 0xA02125.
Read each array from left to right. The start offsets below refer to the payload; add 24 bits only if indexing from the first frame byte:
| Array | Start bit | Bits / entry | Values in satellite or active-cell order |
|---|---|---|---|
| DF397 | 175 | 8 | 70, 71, 72 |
| DF398 | 199 | 10 | 512, 256, 768 |
| DF400 | 229 | 15 | 1000, -2000, 0, 500, -750 |
| DF401 | 304 | 22 | 20000, -30000, 10000, 25000, -15000 |
| DF402 | 414 | 4 | 6, 7, 8, 9, 10 |
| DF420 | 434 | 1 | 0, 0, 1, 0, 0 |
| DF403 | 439 | 6 | 45, 43, 40, 47, 42 |
The first two arrays have three entries; every later array has five. Data ends at bit 468 inclusive, followed by three zero padding bits. The corresponding reconstructed ranges are:
| Cell | Satellite / signal | Pseudorange, m | Phase range, m |
|---|---|---|---|
| 1 | GPS 3 / 1C | 21135386.158023 | 21135379.457139 |
| 2 | GPS 3 / 2X | 21135332.550954 | 21135351.536791 |
| 3 | GPS 6 / 1C | 21360212.632500 | 21360218.216570 |
| 4 | GPS 12 / 1C | 21809910.254011 | 21809915.279674 |
| 5 | GPS 12 / 2X | 21809887.917733 | 21809892.943395 |
For cell 1, the shared rough range is 70 + 512/1024 = 70.5 ms. Its fine pseudorange adds 1000 × 2⁻²⁴ ms before conversion to metres. Cell 3 has a half-cycle flag of 1; the table shows its decoded phase range while retaining that ambiguity flag for downstream processing.
MSM Signal IDs: On-Wire Positions and Observation Codes
Use the one-based Signal ID transmitted by the 32-bit signal mask. A library may index its lookup array from zero, so array index 1 can correspond to Signal ID 2. A RINEX observation suffix such as 1C describes band and tracking code; it is not a mask position. Select the constellation before looking up any ID.
The tables list common signals, not every reserved or future allocation. The modern BeiDou entries use the RTCM 3.3 Amendment 2 mapping; parser cross-checks use pyrtcm 1.1.9, with the B2b entries also checked against MRTKLIB 0.7.10. Record the implemented revision and library release when comparing receivers. Preserve an unknown ID in diagnostic output rather than assigning it a guessed frequency.
GPS Signal Codes
These are one-based IDs in the GPS signal mask:
| Signal ID | Observation suffix | Signal | Frequency |
|---|---|---|---|
| 2 | 1C | L1 C/A | 1575.42 MHz |
| 3 | 1P | L1 P | 1575.42 MHz |
| 4 | 1W | L1 P(Y) | 1575.42 MHz |
| 8 | 2C | L2 C/A | 1227.60 MHz |
| 9 | 2P | L2 P | 1227.60 MHz |
| 10 | 2W | L2 P(Y) | 1227.60 MHz |
| 15 | 2S | L2C M | 1227.60 MHz |
| 16 | 2L | L2C L | 1227.60 MHz |
| 17 | 2X | L2C M+L | 1227.60 MHz |
| 22 | 5I | L5 I | 1176.45 MHz |
| 23 | 5Q | L5 Q | 1176.45 MHz |
| 24 | 5X | L5 I+Q | 1176.45 MHz |
| 30 | 1S | L1C D | 1575.42 MHz |
| 31 | 1L | L1C P | 1575.42 MHz |
| 32 | 1X | L1C D+P | 1575.42 MHz |
GLONASS Signal Codes
These are one-based IDs in the GLONASS signal mask:
| Signal ID | Observation suffix | Signal | Frequency |
|---|---|---|---|
| 2 | 1C | G1 C/A | 1602 + k×0.5625 MHz |
| 3 | 1P | G1 P | 1602 + k×0.5625 MHz |
| 8 | 2C | G2 C/A | 1246 + k×0.4375 MHz |
| 9 | 2P | G2 P | 1246 + k×0.4375 MHz |
Galileo Signal Codes
These are one-based IDs in the Galileo signal mask:
| Signal ID | Observation suffix | Signal | Frequency |
|---|---|---|---|
| 2 | 1C | E1 C | 1575.42 MHz |
| 3 | 1A | E1 A | 1575.42 MHz |
| 4 | 1B | E1 B | 1575.42 MHz |
| 5 | 1X | E1 B+C | 1575.42 MHz |
| 6 | 1Z | E1 A+B+C | 1575.42 MHz |
| 8 | 6C | E6 C | 1278.75 MHz |
| 9 | 6A | E6 A | 1278.75 MHz |
| 10 | 6B | E6 B | 1278.75 MHz |
| 11 | 6X | E6 B+C | 1278.75 MHz |
| 12 | 6Z | E6 A+B+C | 1278.75 MHz |
| 14 | 7I | E5b I | 1207.14 MHz |
| 15 | 7Q | E5b Q | 1207.14 MHz |
| 16 | 7X | E5b I+Q | 1207.14 MHz |
| 18 | 8I | E5 AltBOC I | 1191.795 MHz |
| 19 | 8Q | E5 AltBOC Q | 1191.795 MHz |
| 20 | 8X | E5 AltBOC I+Q | 1191.795 MHz |
| 22 | 5I | E5a I | 1176.45 MHz |
| 23 | 5Q | E5a Q | 1176.45 MHz |
| 24 | 5X | E5a I+Q | 1176.45 MHz |
BeiDou Signal Codes and Version Compatibility
BeiDou B1I uses modern RINEX suffixes 2I/2Q/2X. Older codebases may label those same legacy B1I signals 1I/1Q/1X. That naming difference is separate from the addition of B1C, B2a and B2b mask positions:
| Signal ID | Suffix | Signal | Frequency and compatibility |
|---|---|---|---|
| 2 / 3 / 4 | 2I / 2Q / 2X | B1I I / Q / I+Q | 1561.098 MHz; older libraries may use 1I/1Q/1X labels |
| 8 / 9 / 10 | 6I / 6Q / 6X | B3I I / Q / I+Q | 1268.52 MHz |
| 14 / 15 / 16 | 7I / 7Q / 7X | B2I I / Q / I+Q | 1207.14 MHz |
| 22 / 23 / 24 | 5D / 5P / 5X | B2a data / pilot / combined | 1176.45 MHz; check modern-signal decoder support |
| 25 / 26 / 27 | 7D / 7P / 7Z | B2b data / pilot / combined | 1207.14 MHz; a library can support only a subset |
| 30 / 31 / 32 | 1D / 1P / 1X | B1C data / pilot / combined | 1575.42 MHz; these are not the old B1I labels |
A concrete compatibility example: MRTKLIB 0.7.10 restored B2b IDs 25–27. In pyrtcm 1.1.9, the B2b lookup contains ID 25 but does not supply all three mappings. Inspect the decoded constellation, transmitted Signal ID and observation suffix together when testing B1C, B2a and B2b input.
QZSS, NavIC and SBAS MSM Families
These families use the same MSM framing and array rules, with their own satellite and signal assignments:
| Family | Message range | Common Signal IDs / suffixes | Decoder check |
|---|---|---|---|
| QZSS | 1111–1117 | 2=1C; 15/16/17=2S/2L/2X; 22/23/24=5I/5Q/5X | Apply the QZSS satellite-ID mapping; do not display the mask position as a GPS PRN |
| SBAS | 1101–1107 | 2=1C; 22/23/24=5I/5Q/5X | Map satellite-mask positions to the appropriate SBAS PRNs |
| NavIC | 1131–1137 | 22=5A in the referenced common mapping | Confirm modern signal and satellite coverage against the implemented revision |
Base-Station Coordinates and Interoperability Messages
Station Coordinates: MT1005
MT1005 and MT1006 carry the reference antenna’s ARP coordinates in Earth-Centered, Earth-Fixed (ECEF) form. The X, Y and Z fields are signed 38-bit integers with 0.0001 m resolution. They are not the ground-marker coordinates and not the rover’s solution.
MT1005 has a 152-bit, 19-byte payload. The following offsets start at DF002:
| Field | Offset | Type | Meaning |
|---|---|---|---|
| DF002 | 0 | uint12 | 1005 |
| DF003 | 12 | uint12 | Reference Station ID |
| DF021 | 24 | uint6 | Reserved for ITRF realization information; do not infer a complete coordinate reference system from this field |
| DF022 / 023 / 024 | 30 / 31 / 32 | bit1 each | GPS / GLONASS / Galileo indicators |
| DF141 | 33 | bit1 | Reference Station Indicator |
| DF025 | 34 | int38 | ARP ECEF X, 0.0001 m |
| DF142 | 72 | bit1 | Single Receiver Oscillator Indicator |
| Reserved | 73 | bit1 | Reserved |
| DF026 | 74 | int38 | ARP ECEF Y, 0.0001 m |
| DF364 | 112 | bit2 | Quarter-cycle alignment indicator for GPS observations; not a GLONASS-only flag |
| DF027 | 114 | int38 | ARP ECEF Z, 0.0001 m |
For transformations or map alignment, obtain the reference frame, realization and coordinate epoch from the base operator or correction service. The coordinate-reference guide explains why coordinates alone do not identify all of those choices.
D3 00 13 3E D0 2A 03 B9 B6 AC AD 2E 8A 03 00 04
2E 08 F6 C7 61 01 F6 32 B5
Expected output: Station ID 42; X = −2700000.1234 m, Y = 4300000.5678 m, Z = 3850000.0001 m. The complete frame is 25 bytes and its CRC is 0xF632B5. The negative X value exercises 38-bit sign extension.
Station Coordinates and Antenna Height: MT1006
MT1006 uses the same coordinate fields with DF002 set to 1006, then appends antenna height. The ECEF position still refers to the ARP:
| Field | Offset | Type | Meaning |
|---|---|---|---|
| DF028 | 152 | uint16 | Vertical antenna height from the reference marker to the ARP; 0.0001 m resolution, range 0–6.5535 m |
The ECEF coordinates already locate the ARP. Applying the marker-to-ARP height again displaces that position a second time. Recovering a marker coordinate requires the intended vertical direction and frame convention. Antenna phase-center modeling is a separate operation.
D3 00 15 3E E0 2A 03 B9 B6 AC AD 2E 8A 03 00 04
2E 08 F6 C7 61 01 3B 6A 97 4F 61
This constructed frame retains the MT1005 example coordinates and adds height 1.5210 m. It is 27 bytes long with CRC 0x974F61. Use it to confirm that the extra height does not alter the parsed X, Y or Z values.
Receiver and Antenna Descriptors: MT1033
MT1033 combines station identification with antenna and receiver strings. The strings are counted byte sequences, not zero-terminated C strings:
| Order | Field | Parsing rule |
|---|---|---|
| 1 | DF002 / DF003 | Read uint12 message number 1033 and uint12 Station ID |
| 2 | DF029 / DF030 | Read uint8 antenna-descriptor count, then that many character bytes |
| 3 | DF031 | Read uint8 Antenna Setup ID |
| 4 | DF032 / DF033 | Read uint8 antenna-serial count, then the counted bytes |
| 5 | DF227 / DF228 | Read uint8 receiver-type count, then the counted bytes |
| 6 | DF229 / DF230 | Read uint8 firmware-version count, then the counted bytes |
| 7 | DF231 / DF232 | Read uint8 receiver-serial count, then the counted bytes |
Validate each count against the remaining payload before reading or allocating storage. Keep empty strings and padding spaces distinct. Descriptor text can support antenna-model selection and diagnostics, but the string alone does not prove that a matching calibration was applied.
GLONASS Code-Phase Biases: MT1230
MT1230 describes GLONASS FDMA code-phase biases. DF421 indicates whether pseudorange and phase-range observations have been aligned; DF422 selects which bias fields are present. These are separate decisions. Read the bias fields selected by the mask even when DF421 is zero.
Bias values appear in this mask order, beginning with the most significant mask bit:
| Field | Offset / presence | Type | Meaning |
|---|---|---|---|
| DF002 / DF003 | 0 / 12 | uint12 each | Message number 1230 and Station ID |
| DF421 | 24 | bit1 | 0: code and phase observations not aligned; 1: aligned |
| Reserved | 25 | bit3 | Reserved |
| DF422 | 28 | bit4 | Mask weights 8, 4, 2, 1 select the four fields below |
| DF423 | Mask weight 8 | int16 | L1 C/A bias, 0.02 m; −32768 / raw 0x8000 unavailable |
| DF424 | Mask weight 4 | int16 | L1 P bias, same scale and unavailable value |
| DF425 | Mask weight 2 | int16 | L2 C/A bias, same scale and unavailable value |
| DF426 | Mask weight 1 | int16 | L2 P bias, same scale and unavailable value |
The first selected bias begins at payload bit 32; each present bias advances the cursor by 16 bits. Payload size is 4 + 2 × popcount(DF422) bytes. A missing field is not an explicit zero bias. Likewise, an unavailable code is not a large negative correction.
D3 00 08 4C E0 2A 0A 00 0A FF EC 29 2F 7B
Expected output: Station ID 42, DF421 = 0, mask 1010, DF423 = 10 × 0.02 = +0.20 m, DF425 = −20 × 0.02 = −0.40 m. DF424 and DF426 are absent. The frame is 14 bytes with CRC 0x292F7B. This example checks both signed decoding and field presence independently of the alignment indicator.
For applicable GLONASS FDMA observations, compare MT1230 input with the receiver’s calibration or bias-estimation support. Record the supplied bias fields and the observations accepted by the engine to establish how that base-to-rover combination handles interoperability.
Message Groups, Stream Checks and Conversion
Legacy Observation Messages: MT1001–MT1012
The Legacy observation families remain relevant when maintaining older receivers and services. Station records between those number ranges are separate messages:
| Messages | Content | Compatibility check |
|---|---|---|
| 1001 / 1002 | GPS L1; basic / extended | 1002 adds full-range ambiguity and CNR information |
| 1003 / 1004 | GPS L1/L2; basic / extended | Check tracking-code support and ambiguity reconstruction |
| 1009 / 1010 | GLONASS L1; basic / extended | Retain frequency-channel information for FDMA |
| 1011 / 1012 | GLONASS L1/L2; basic / extended | Check applicable bias treatment and antenna environment |
| 1005 / 1006 | Station ARP coordinates | Coordinate records, not observation messages |
| 1007 / 1008 | Antenna descriptor / descriptor plus serial | Metadata, not observation messages |
Ephemeris Messages
Navigation data can come from the receiver’s own decoding or an external log. These message numbers identify common broadcast-ephemeris records:
| MT | Constellation | Navigation record |
|---|---|---|
| 1019 | GPS | GPS broadcast ephemeris |
| 1020 | GLONASS | GLONASS broadcast ephemeris |
| 1042 | BeiDou | BeiDou broadcast ephemeris |
| 1044 | QZSS | QZSS broadcast ephemeris |
| 1045 | Galileo | F/NAV ephemeris |
| 1046 | Galileo | I/NAV ephemeris |
Match the navigation record and epoch to the observations used by the processor.
SSR Message Overview
SSR messages describe correction components such as orbit, clock and signal biases. They are not drop-in replacements for reference-station observations in a conventional OSR rover:
| Message range | System | Contents |
|---|---|---|
| 1057–1062 | GPS | Orbit, clock, code bias, combined orbit/clock, URA and high-rate clock |
| 1063–1068 | GLONASS | The same six correction categories |
| 1240–1245 | Galileo | The same six correction categories |
| 1246–1251 | QZSS | The same six correction categories |
| 1252–1257 | SBAS | The same six correction categories |
| 1258–1263 | BeiDou | The same six correction categories |
| 1264 | Ionosphere | VTEC model coefficients |
For later SSR extensions and provider-specific messages, match the service specification to the receiving positioning engine.
Message Sets for Parser and Receiver Validation
Build validation sets around the behavior you need to exercise, then compare them with real receiver captures:
| Test set | Include | Expected check |
|---|---|---|
| Frame extraction | Valid, corrupt, split and concatenated frames | No dispatch before CRC passes; later valid frames recover after corrupt candidates |
| Coordinates | 1005 and 1006 with positive and negative ECEF | 38-bit signed extraction; antenna height remains separate |
| MSM arrays | 1074 and 1077 with unequal cell coverage | Correct masks, field-major ordering, scales and signed values |
| Missing observations | Selected cells carrying unavailable codes | Cursor advances correctly while availability is tracked per measurement |
| GLONASS biases | 1230 with partial masks and either DF421 value | Field presence follows DF422; signed bias and unavailable values handled |
| Live session | Raw RTCM plus NMEA and connection events | Separate arriving data, forwarded data and receiver-reported solution state |
With a USB receiver such as SCOUT or SCOUT PRO, TRACE can record raw RTCM, NMEA and session events and show correction receive/forward counters. Those records let you compare delivery with receiver-reported state and correction age. Decode the captured RTCM separately for CRC, message IDs, masks and field values; TRACE’s connection status is not a field-level validation result.
Bandwidth Estimation from the Actual Masks
Use the frame-length equations above for each constellation, multiply by message rate, and add coordinate, bias and other traffic. Under the same masks, MSM7 is larger than MSM4:
| Mask configuration | MSM4 frame | MSM7 frame |
|---|---|---|
| 3 satellites × 2 signals; 5 active cells | 65 bytes | 92 bytes |
| 12 satellites × 2 signals; 24 active cells | 202 bytes | 325 bytes |
Four messages of the second size per second require 808 B/s for MSM4 or 1300 B/s for MSM7, before auxiliary messages. An 8N1 UART at 9600 bit/s provides 960 B/s ideally. Measure both average load and bursts; a radio air rate, UART rate and internet throughput are different quantities.
RTCM to RINEX 3 and PPK Inputs
RTCM observations can be converted to RINEX when the converter supports the message layouts, signal mappings and required metadata. RINEX observation types add a measurement prefix: for GPS suffix 1C, C1C is code, L1C phase, D1C Doppler and S1C signal strength. Preserve wavelength, time system, lock and half-cycle information when translating fields.
Use a converter appropriate to the source format, such as RTKLIB convbin for supported binary inputs. Verify the output header, observation codes, time span, epoch count and missing-data handling; a file that opens successfully can still have lost unsupported signals. The PPK logging guide covers the full dataset: rover raw observations, overlapping base/CORS observations and navigation data. Logging incoming corrections alone cannot recover reference data lost during a link outage.
Versioned References and Decoder Cross-Checks
Separate the specification from implementation evidence. The RTCM publication catalog lists RTCM 10403.4 with Amendment 1 dated November 2024. The MSM examples here deliberately use established 3.2/3.3 layouts; the BeiDou signal table identifies the Amendment 2 mapping rather than assuming that every older decoder has it.
The following checks make the examples reproducible:
| Reference | Role | What to compare |
|---|---|---|
| RTCM 10403 series | Protocol authority | Edition, amendments, message definitions and receiver conformance requirements |
| pyrtcm 1.1.9 | Independent example decoder | The five constructed frames below: IDs, raw/scaled values, masks and cell association |
| RTKLIB | Implementation comparison | MSM array order, signed extraction, conversion behavior and documented encoder issues |
| MRTKLIB 0.7.10 | Modern BeiDou compatibility example | Restored B2b IDs 25–27; demonstrates why release identification matters |
The five vectors were independently decoded with pyrtcm 1.1.9. They exercise signed values, cell association, array order and reference records. The troubleshooting table below adds failure cases to check.
Parser Troubleshooting
Locate the first wrong value before changing later scale factors:
| Symptom | Likely check | Concrete test |
|---|---|---|
| CRC fails on every frame | Input region, binary transport or CRC parameters | Run 123456789 → CDE703, then a complete Hex frame below |
| Parser waits despite later bytes | Incomplete candidate with plausible length | Feed a false D3/1023-byte header; inspect the application deadline and reset behavior |
| Correct MSM header, wrong observations | Mask length or field-major array order | Compare payload offsets 175, 199 and 229 in the MSM4 example |
| GPS L2 signal labeled incorrectly | Signal ID mixed with a zero-based array index | Confirm ID 17 maps to 2X, not ID 4 |
| Coordinates wrap or change sign | 32-bit truncation or missing sign extension | Read the negative 38-bit X field from the MT1005 vector |
| Constant height discrepancy | ARP, marker and antenna-height handling | Confirm DF028 was not added to coordinates already describing the ARP |
| GLONASS bias fields shift | DF421 used as presence flag or wrong mask direction | Use mask 1010 with DF421=0; only DF423 and DF425 follow |
| One invalid cell breaks all later cells | Bits skipped before consuming the encoded arrays | Insert an unavailable fine range and confirm subsequent offsets stay unchanged |
Appendix: 64-Bit Extraction and Decoder Test Vectors
The following C functions take a pointer, available byte count, bit offset and field width. A false return means the request is invalid or outside the supplied buffer; the caller must not use an output value from a failed call.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* Bit 0 is the MSB of buf[0]. Width is 1..64. */
static bool read_u64(const uint8_t *buf, size_t size,
size_t pos, unsigned width, uint64_t *out)
{
if (!buf || !out || width == 0 || width > 64 || size > SIZE_MAX / 8)
return false;
size_t total = size * 8;
if (pos > total || width > total - pos)
return false;
uint64_t value = 0;
for (unsigned i = 0; i < width; ++i) {
size_t bit = pos + i;
value = (value << 1) | ((buf[bit / 8] >> (7 - bit % 8)) & 1u);
}
*out = value;
return true;
}
/* Two's-complement fields, including 38-bit ECEF coordinates. */
static bool read_i64(const uint8_t *buf, size_t size,
size_t pos, unsigned width, int64_t *out)
{
uint64_t value;
if (!out || !read_u64(buf, size, pos, width, &value))
return false;
if (value & (UINT64_C(1) << (width - 1))) {
uint64_t mask = width == 64 ? UINT64_MAX :
(UINT64_C(1) << width) - 1;
*out = -1 - (int64_t)(mask - value);
} else {
*out = (int64_t)value;
}
return true;
}
After checking the envelope and CRC, point the reader at the payload. For an MT1005 or MT1006 payload of length L:
int64_t x_raw, y_raw, z_raw;
if (!read_i64(payload, L, 34, 38, &x_raw) ||
!read_i64(payload, L, 74, 38, &y_raw) ||
!read_i64(payload, L, 114, 38, &z_raw)) {
/* Reject the truncated payload. */
return false;
}
double x_m = (double)x_raw * 0.0001;
double y_m = (double)y_raw * 0.0001;
double z_m = (double)z_raw * 0.0001;
For MSM7, use this second constructed observation frame. It retains the MSM4 example’s station, epoch, satellite mask, signal mask and five-cell ordering, while exercising higher-resolution fields and phase-range rate:
D3 00 56 43 50 2A 52 41 21 00 00 00 12 08 00 00
00 00 00 00 20 00 40 00 76 8C 8E 90 00 10 02 01
80 7C E0 03 20 1F 40 4E 21 F6 3C 00 00 00 02 71
1F C5 68 06 1A 81 F6 D8 40 03 0D 40 07 A1 21 FB
6C 20 32 12 C6 41 F4 96 12 D0 AC 28 0B C2 A0 00
C9 FC E0 00 00 03 2F F6 A0 BA 24 64
Its expected raw arrays are listed below. Each start offset plus index × field width identifies one array element:
| Array | Start bit | Bits / entry | Raw integers |
|---|---|---|---|
| DF397 | 175 | 8 | 70, 71, 72 |
| ExtSatInfo | 199 | 4 | 0, 0, 0 |
| DF398 | 211 | 10 | 512, 256, 768 |
| DF399 | 241 | 14 | -400, 100, 250 |
| DF405 | 283 | 20 | 10000, -20000, 0, 5000, -7500 |
| DF406 | 383 | 24 | 200000, -300000, 100000, 250000, -150000 |
| DF407 | 503 | 10 | 100, 150, 200, 250, 300 |
| DF420 | 553 | 1 | 0, 0, 1, 0, 0 |
| DF408 | 558 | 10 | 720, 688, 640, 752, 672 |
| DF404 | 608 | 15 | 100, -200, 0, 50, -75 |
For the first cell, pseudorange = (70.5 + 10000 × 2⁻²⁹) × 299792.458 = 21135373.873070 m. Phase range = (70.5 + 200000 × 2⁻³¹) × 299792.458 = 21135396.209348 m. Phase-range rate = −400 + 100 × 0.0001 = −399.9900 m/s; CNR = 720/16 = 45 dB-Hz. These are decoder outputs from constructed input values.
All examples include the header and CRC. Check their lengths and checksums before comparing field values:
| Message | Payload / frame bytes | CRC-24Q | Expected distinguishing result |
|---|---|---|---|
| 1074 | 59 / 65 | A02125 | Five cells; signed fine ranges; one half-cycle flag |
| 1077 | 86 / 92 | BA2464 | Extended fields and phase-range rates |
| 1005 | 19 / 25 | F632B5 | Negative 38-bit ECEF X |
| 1006 | 21 / 27 | 974F61 | Same ECEF coordinates; separate 1.5210 m height |
| 1230 | 8 / 14 | 292F7B | DF421=0 with two present signed biases |
The reference reader was checked against every named field in these vectors, including signed 38-bit coordinates, and with invalid widths, truncated buffers and the signed 64-bit minimum. The frame extractor was checked at all 228 two-part split positions across the five frames, with byte-at-a-time input, concatenation and corrupt-CRC recovery. Add receiver captures and unavailable-value mutations to a decoder’s own tests before relying on it for the full supported message set.
Frequently Asked Questions
After a CRC-24Q failure, how should an RTCM parser resynchronize?
Resume byte-wise searching one byte after the candidate preamble. Check each new 0xD3 candidate’s reserved bits, payload length and complete CRC before dispatch. Preserve incomplete bytes across reads and apply an application-specific deadline to stalled candidates. This recovers synchronization by validation while retaining valid frames that may follow a corrupted candidate.
How do MSM masks locate one satellite-signal observation cell?
The satellite and signal masks select IDs in increasing order. Traverse their cell matrix by satellite, then signal, and retain set entries in the active-cell list. Read observation arrays field by field over that list. In the worked example, mask 111011 selects five cells from three satellites and two signals.
What is the difference between MT1005 and MT1006?
Both carry reference-station ARP coordinates as signed 38-bit ECEF values with 0.0001 metre resolution. MT1006 appends antenna height from the reference marker to the ARP. That height is separate from the ECEF position, which already describes the ARP. Obtain the coordinate reference frame and epoch from the service or base operator.
Does DF421 decide whether MT1230 bias values are present?
No. DF421 indicates whether code and phase observations are aligned. DF422 is the four-bit signal mask that selects the bias fields DF423 through DF426. Read selected values in mask order, starting with L1 C/A. Each is a signed 16-bit integer scaled by 0.02 metre; raw 0x8000 means the bias is unavailable.
Why can two RTCM decoders label BeiDou signals differently?
They may implement different signal-table revisions or use different observation-code labels. Older libraries can label B1I as 1I where modern RINEX uses 2I, and some omit newer B1C, B2a or B2b entries. Compare the transmitted one-based Signal ID, constellation, protocol revision and library release before comparing decoded signal names.
Does a valid RTCM CRC mean the observations are usable for RTK?
A matching CRC establishes frame integrity against that check, not observation usability. Decode the message and inspect supported signals, station association, time, unavailable-value codes and lock information. The receiving engine also needs its own compatible observations. Byte delivery, successful field decoding and an RTK solution are separate results that should be logged separately.
Can I read RTCM 3.x messages using a standard serial terminal or text logger?
Capture RTCM as raw binary bytes and inspect it with a Hex viewer or RTCM decoder. A serial terminal’s text display can show non-printable characters, while text logging can change byte values or line endings. Preserve a binary file for CRC and field checks; use NMEA output for readable receiver status.
Why can’t an RTCM 3.x decoder use C structs to parse the payload?
RTCM payload fields use protocol-defined bit offsets and often cross byte boundaries. C structs follow compiler layout, padding and alignment rules. Read each field explicitly by offset and width, then apply signed interpretation, unavailable-value handling and scaling. The C example supports widths up to 64 bits, including the 38-bit ECEF coordinates.
Continue exploring
RTCM Unpacked
Why correction-message selection, transport, and validation affect an RTK workflow.
LateralHost-Based RTK
See where RTCM decoding ends and host-side RTK processing responsibilities begin.
ApplyTRACE for Windows
Connect the receiver, forward corrections and record raw RTCM, NMEA and session events.