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).
- 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
- 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
go get github.com/gunlock/gdl90This repository includes two command-line tools in the cmd/ directory:
# 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; donego build ./cmd/calc_fcs
go build ./cmd/udp_receiver-
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
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 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)
}All GDL90 messages follow this structure:
- Flag byte (0x7E) - Start of message
- Message ID byte - Type of message
- Message data - Variable length payload
- FCS (2 bytes) - CRC-CCITT checksum
- Flag byte (0x7E) - End of message
Byte stuffing is applied to the message data to escape flag bytes and control characters.
Creates a new GDL90 decoder with internal buffering for streaming data.
Processes raw bytes and returns all complete, validated messages.
Decodes a single complete GDL90 message (including flag bytes).
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)
Unstuff(stuffed []byte) ([]byte, error)- Remove byte stuffingStuff(data []byte) []byte- Apply byte stuffingLatitudeToFloat(raw uint32) float64- Convert 24-bit semicircle to degreesLongitudeToFloat(raw uint32) float64- Convert 24-bit semicircle to degreesAltitudeToFeet(raw uint16) int32- Convert encoded altitude to feetVerticalVelocityToFPM(raw uint16) int32- Convert vertical velocity to FPM
Run the complete test suite:
go test -vRun tests with coverage:
go test -cover -coverprofile=coverage.out
go tool cover -html=coverage.outThis 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 testRun 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=1mAvailable fuzz tests:
FuzzDecoder- Main decoder with random byte streamsFuzzUnstuff- Byte unstuffing logicFuzzCRCCompute- CRC computationFuzzParseHeartbeat- Heartbeat message parsingFuzzParseTrafficReport- Traffic report parsingFuzzParseUplinkMessage- Uplink message parsingFuzzParseOwnshipGeoAltitude- Geometric altitude parsingFuzzParseHeightAboveTerrain- Terrain height parsingFuzzParseOwnshipReport- Ownship report parsingFuzzDecodeMessage- Complete message decodingFuzzValidateFCS- 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- 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
- GDL90 Data Interface Specification (560-1058-00 Rev A)
- Stratux open source project (reference implementation)
This implementation is provided as-is for parsing GDL90 messages. Please ensure compliance with any applicable regulations when using ADS-B data.
Contributions are welcome! Please ensure all tests pass and add tests for new features.