Skip to content

Repository files navigation

GDL90 Parser for Go

Go implementation for parsing GDL90 messages from ADS-B devices. This library handles unstuffing, FCS (Frame Check Sequence) validation, and decoding of various GDL90 message types according to the GDL90 Data Interface Specification (560-1058-00 Rev A).

Features

  • Complete GDL90 message parsing - Supports all standard message types
  • Byte unstuffing - Handling of escape sequences (0x7D/0x7E)
  • FCS validation - CRC-CCITT checksum verification
  • Type-safe message structures - structs for all message types
  • Streaming decoder - Handle continuous UDP streams with buffering
  • Tests - Test data from specification
  • Dependencies - None

Supported Message Types

  • Heartbeat (ID 0) - Status and timing information
  • Traffic Report (ID 20) - Traffic target information
  • Ownship Report (ID 10) - Own aircraft position
  • Ownship Geometric Altitude (ID 11) - GPS altitude
  • Uplink Data (ID 7) - Ground station uplink messages
  • Height Above Terrain (ID 9) - Terrain clearance
  • Basic/Long UAT Reports (ID 30/31) - Raw UAT messages

Installation

go get github.com/gunlock/gdl90

Building Command-Line Tools

This repository includes two command-line tools in the cmd/ directory:

Build to bin/ directory (recommended)

# Build all tools to bin/
go build -o bin/calc_fcs ./cmd/calc_fcs
go build -o bin/udp_receiver ./cmd/udp_receiver

# Or build all at once
mkdir -p bin
for cmd in cmd/*; do go build -o bin/$(basename $cmd) ./$cmd; done

Build to project root

go build ./cmd/calc_fcs
go build ./cmd/udp_receiver

Available Tools

  • calc_fcs - Calculate FCS (Frame Check Sequence) for GDL-90 messages

    bin/calc_fcs "00 81 41 DB D0 08 02"
  • udp_receiver - Listen for GDL-90 messages on UDP and decode them

    bin/udp_receiver -port 4000 -verbose

Usage

Basic Example

package main

import (
	"fmt"
	"log"
	"net"

	"github.com/gunlock/gdl90"
)

func main() {
	// Create a decoder
	decoder := gdl90.NewDecoder()

	// Listen for UDP packets
	conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 4000})
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	buffer := make([]byte, 8192)

	for {
		n, _, err := conn.ReadFromUDP(buffer)
		if err != nil {
			log.Printf("Error reading: %v", err)
			continue
		}

		// Decode messages
		messages, err := decoder.Decode(buffer[:n])
		if err != nil {
			log.Printf("Decode error: %v", err)
			continue
		}

		// Process messages
		for _, msg := range messages {
			handleMessage(msg)
		}
	}
}

func handleMessage(msg gdl90.Message) {
	switch msg.ID {
	case gdl90.MsgIDHeartbeat:
		hb, err := gdl90.ParseHeartbeat(msg.Payload)
		if err != nil {
			log.Printf("Error parsing heartbeat: %v", err)
			return
		}
		fmt.Printf("Heartbeat: GPS Valid=%v, UTC Time=%ds\n",
			hb.StatusByte1.GPSPosValid, hb.TimeStamp)

	case gdl90.MsgIDTrafficReport:
		traffic, err := gdl90.ParseTrafficReport(msg.Payload)
		if err != nil {
			log.Printf("Error parsing traffic: %v", err)
			return
		}
		fmt.Printf("Traffic: %s at %.4f,%.4f alt=%dft\n",
			traffic.CallSign, traffic.Latitude, traffic.Longitude, traffic.Altitude)

	case gdl90.MsgIDOwnshipReport:
		ownship, err := gdl90.ParseOwnshipReport(msg.Payload)
		if err != nil {
			log.Printf("Error parsing ownship: %v", err)
			return
		}
		fmt.Printf("Ownship: %.4f,%.4f alt=%dft\n",
			ownship.Latitude, ownship.Longitude, ownship.Altitude)
	}
}

Decode Single Message

// Decode a single complete GDL90 message
rawMessage := []byte{0x7E, 0x00, 0x81, 0x41, 0xDB, 0xD0, 0x08, 0x02, 0xB3, 0x8B, 0x7E}

msg, err := gdl90.DecodeMessage(rawMessage)
if err != nil {
	log.Fatal(err)
}

if msg.ID == gdl90.MsgIDHeartbeat {
	hb, _ := gdl90.ParseHeartbeat(msg.Payload)
	fmt.Printf("Heartbeat received at %ds\n", hb.TimeStamp)
}

Message Structure

All GDL90 messages follow this structure:

  1. Flag byte (0x7E) - Start of message
  2. Message ID byte - Type of message
  3. Message data - Variable length payload
  4. FCS (2 bytes) - CRC-CCITT checksum
  5. Flag byte (0x7E) - End of message

Byte stuffing is applied to the message data to escape flag bytes and control characters.

API Reference

Core Functions

NewDecoder() *Decoder

Creates a new GDL90 decoder with internal buffering for streaming data.

Decoder.Decode(data []byte) ([]Message, error)

Processes raw bytes and returns all complete, validated messages.

DecodeMessage(data []byte) (*Message, error)

Decodes a single complete GDL90 message (including flag bytes).

Message Parsers

  • ParseHeartbeat(payload []byte) (*HeartbeatMessage, error)
  • ParseTrafficReport(payload []byte) (*TrafficReport, error)
  • ParseOwnshipReport(payload []byte) (*OwnshipReport, error)
  • ParseUplinkMessage(payload []byte) (*UplinkMessage, error)
  • ParseOwnshipGeoAltitude(payload []byte) (*OwnshipGeoAltitude, error)
  • ParseHeightAboveTerrain(payload []byte) (*HeightAboveTerrainMessage, error)

Utility Functions

  • Unstuff(stuffed []byte) ([]byte, error) - Remove byte stuffing
  • Stuff(data []byte) []byte - Apply byte stuffing
  • LatitudeToFloat(raw uint32) float64 - Convert 24-bit semicircle to degrees
  • LongitudeToFloat(raw uint32) float64 - Convert 24-bit semicircle to degrees
  • AltitudeToFeet(raw uint16) int32 - Convert encoded altitude to feet
  • VerticalVelocityToFPM(raw uint16) int32 - Convert vertical velocity to FPM

Testing

Running Standard Tests

Run the complete test suite:

go test -v

Run tests with coverage:

go test -cover -coverprofile=coverage.out
go tool cover -html=coverage.out

Fuzzing Tests

This library includes comprehensive fuzzing tests to ensure robustness against malformed input. All fuzz tests are seeded with valid data from the GDL-90 specification document.

Run fuzzing tests with seed data only (fast, part of standard test suite):

go test

Run actual fuzzing (generates random/mutated inputs to find edge cases):

# Fuzz the main decoder
go test -fuzz=FuzzDecoder

# Fuzz for a specific duration
go test -fuzz=FuzzDecoder -fuzztime=30s

# Fuzz with a time limit (1 minute)
go test -fuzz=FuzzDecoder -fuzztime=1m

Available fuzz tests:

  • FuzzDecoder - Main decoder with random byte streams
  • FuzzUnstuff - Byte unstuffing logic
  • FuzzCRCCompute - CRC computation
  • FuzzParseHeartbeat - Heartbeat message parsing
  • FuzzParseTrafficReport - Traffic report parsing
  • FuzzParseUplinkMessage - Uplink message parsing
  • FuzzParseOwnshipGeoAltitude - Geometric altitude parsing
  • FuzzParseHeightAboveTerrain - Terrain height parsing
  • FuzzParseOwnshipReport - Ownship report parsing
  • FuzzDecodeMessage - Complete message decoding
  • FuzzValidateFCS - FCS validation

Example fuzzing session:

# Fuzz traffic report parsing for 5 minutes
go test -fuzz=FuzzParseTrafficReport -fuzztime=5m

# If a crash is found, the failing input is saved in testdata/fuzz/
# Re-run tests to verify the fix
go test -v

Implementation Notes

  • Binary message decoding only
  • CRC-CCITT - Uses polynomial 0x1021 for checksum validation
  • Byte stuffing - Handles escape sequences for 0x7D and 0x7E bytes
  • Streaming support - Decoder maintains internal buffer for incomplete messages
  • Thread safety - Decoder is not thread-safe; use separate instances for concurrent processing

References

  • GDL90 Data Interface Specification (560-1058-00 Rev A)
  • Stratux open source project (reference implementation)

License

This implementation is provided as-is for parsing GDL90 messages. Please ensure compliance with any applicable regulations when using ADS-B data.

Contributing

Contributions are welcome! Please ensure all tests pass and add tests for new features.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages