Astro~/0.3
** Protocols / Space data link / AOS Space Data Link Protocol * PAGE 10 / 28
** Astro * Protocols
** /protocols/data-link/aos

AOS Space Data Link Protocol

CCSDS 732.0-B-4, high-rate downlink frames for Earth observation and deep space.

CCSDS 732.0-B-4 | Blue Book | pkg/aos | astro aos

Overview

AOS is the downlink protocol for missions that send a lot of data for a long time. Earth observation, lunar, deep space. It does the same job as TM but scales further in three ways: a 24-bit frame counter instead of 8 bits, an insert zone that puts a fixed field at every frame boundary, and a bitstream service for data that is not packets.

Frames are fixed length per physical channel, same as TM.

Scope

Implemented. The frame format including the optional FHEC, all three data services — M_PDU, B_PDU, and VCA: plus the VC Frame pass-through, master and virtual channel multiplexing, gap detection, and idle frames.

Somewhere else. ASM, randomization, and CADU wrapping are pkg/tmsc.

Left to you. Insert zone contents and the CLCW in the OCF. Both are mission-defined.

Field map

The primary header, 6 bytes, or 8 with FHEC. Go fields on aos.PrimaryHeader.

FieldBitsGoNotes
Transfer Frame Version Number2TFVNAlways 1 for AOS. TM uses 0.
Spacecraft Identifier8SCID0-255. Narrower than TM's 10 bits.
Virtual Channel Identifier6VCID0-63. VCID 63 is reserved for Only Idle Data.
Virtual Channel Frame Count24VCFrameCountWraps at 16,777,215
Replay Flag1ReplayFlagMarks recorded data being played back
VC Frame Count Usage Flag1VCFCUsageFlagWhether the cycle field is in use
VC Frame Count Cycle4VCFrameCountCycle0-15, extends the counter further

The rest of the frame, on aos.TransferFrame:

PartSizeGoNotes
Frame Header Error Control2 BFHECOptional. Reed-Solomon (10,6) over the protected header octets.
Insert ZonefixedInsertZoneOptional, mission-defined, same length on every frame
Data FieldvariableDataFieldIncludes the M_PDU or B_PDU header when one applies
Operational Control Field4 BOCFOptional, usually a CLCW
Frame Error Control Field2 BFECFOptional CRC-16-CCITT over the whole frame

Channel-wide settings are on aos.ChannelConfig: FrameLength, InsertZoneLen, HasOCF, HasFHEC, HasFECF, and IdlePattern.

The three data services

M_PDU carries variable-length packets, usually Space Packets. The data field opens with a 16-bit header: 5 reserved bits and an 11-bit First Header Pointer saying where the next packet starts.

FHPMeaning
0-2045Offset to the first packet header in the packet zone
0x7FEIdle data only
0x7FFNo packet starts here

B_PDU carries an octet-aligned bitstream, data that is not packets at all. The 16-bit header is 2 reserved bits and a 14-bit Bitstream Data Pointer marking the last valid bit.

BDPMeaning
0-16381Index of the last valid bit
0x3FFEAll idle
0x3FFFAll valid, nothing ends in this frame

VCA carries one opaque fixed-length SDU. No header at all. The data field is the SDU.

Gotchas

Fill is a real idle packet under M_PDU. When Flush releases a partly full frame, Astro completes the packet zone with a Space Packet at APID 0x7FF, not raw bytes. A conformant receiver would read raw fill as a packet header. If the leftover room is under 7 octets, the idle packet spans into the next frame.

A VCA SDU must fill the data field exactly. Not "at most". A receiver has no in-band way to find where a padded SDU ends, so Astro rejects a short one with ErrSizeMismatch rather than padding it and hoping.

B_PDU cannot mark more than 2047 valid octets in a partial frame. The BDP counts bits, and its top two values are reserved. On a channel with a bigger bitstream zone, Flush splits an oversized partial payload across extra frames instead of writing a pointer that means something else. That is what ErrBitstreamTooLongForPointer guards.

Idle fill is mission-managed, not standardised. Set ChannelConfig.IdlePattern to your repeating pattern. Astro uses 0xFE when you leave it empty. This is unlike TM, where the PN sequence is mandatory.

The insert zone is the same length on every frame or it is absent. It is channel configuration. A length that does not match gives ErrInvalidInsertZoneLength.

FHEC changes the header size. Turning it on makes the primary header 8 bytes instead of 6, which eats into your data field. It protects only TFVN, SCID, VCID, and the signaling field, not the frame count.

M_PDU receive needs a PacketSizer. Same as TM and TC. Without one, ErrNoPacketSizer.

Quick start

import "github.com/ravisuhag/astro/pkg/aos"

// Spacecraft 50, virtual channel 1.
frame, err := aos.NewTransferFrame(50, 1, payload,
    aos.WithVCFrameCount(7),
    aos.WithFECF(),
)
encoded, err := frame.Encode()

Decoding needs the channel configuration, because AOS does not signal it in band:

// insertZoneLen, hasOCF, hasFECF
back, err := aos.DecodeTransferFrame(encoded, 0, false, true)

fmt.Println(back.Header.SCID, back.Header.VCID, back.Header.VCFrameCount)
// 50 1 7

Get those three arguments wrong and the decode either fails or silently reads the wrong bytes as your data field. They come from ChannelConfig, which both ends agree on before the link opens.

Frame options

OptionEffect
WithInsertZone(data)Fills the insert zone. Length must match ChannelConfig.InsertZoneLen.
WithOCF(ocf)Attaches a 4-byte Operational Control Field
WithFECF()Appends the 2-byte CRC
WithFHEC()Adds Reed-Solomon header protection, making the primary header 8 bytes
WithVCFrameCount(n)Sets the 24-bit count
WithReplayFlag()Marks the frame as recorded data being played back
WithVCFCUsage(cycle)Sets the usage flag and the 4-bit cycle

Channel configuration

One struct, shared by both ends of the link:

config := aos.ChannelConfig{
    FrameLength:   256,
    InsertZoneLen: 0,
    HasOCF:        false,
    HasFHEC:       false,
    HasFECF:       true,
    IdlePattern:   nil, // nil means 0xFE
}

Unlike USLP, the OCF flag is not carried in the AOS header, so HasOCF has to be configured on both sides.

Services

Pick the service that matches what you are sending.

MultiplexingService, M_PDU

For variable-length packets. This is the common case.

vc := aos.NewVirtualChannel(1, 32)   // vcid, buffer depth
counter := aos.NewFrameCounter()

svc := aos.NewMultiplexingService(50, 1, vc, config, counter)
svc.SetPacketSizer(spp.PacketSizer)

svc.Send(spacePacketBytes)
svc.Flush()

SetPacketSizer is required before you can receive. The service has to know how long the packet at the First Header Pointer claims to be. Without it you get ErrNoPacketSizer.

Send packs into the current frame and releases it when full. Flush emits whatever is left, completing the packet zone with a real idle packet at APID 0x7FF. Skip the flush and your last packets never leave.

BitstreamService, B_PDU

For octet-aligned data that is not packets:

svc := aos.NewBitstreamService(50, 2, vc, config, counter)
svc.Send(bitstreamBytes)
svc.Flush()

Flush splits an oversized partial payload across extra frames rather than writing a Bitstream Data Pointer that would mean something else, see the gotchas.

VirtualChannelAccessService, VCA

One fixed-length SDU per frame:

svc := aos.NewVirtualChannelAccessService(50, 3, sduSize, vc, config, counter)

The SDU must fill the data field exactly. A short one gives ErrSizeMismatch rather than being padded.

VirtualChannelFrameService

Pass-through for frames you built yourself:

svc := aos.NewVirtualChannelFrameService(4, vc, config)

Channel hierarchy

mc := aos.NewMasterChannel(50, config)
mc.AddVirtualChannel(vc, 1)   // priority

pc := aos.NewPhysicalChannel("X-band", config)
pc.AddMasterChannel(mc, 1)

frame, err := pc.GetNextFrame()

GetNextFrameOrIdle() returns an idle frame instead of an error when nothing is queued, which is what a continuous-rate downlink wants.

Gap detection

det := aos.NewFrameGapDetector()
gap := det.Check(frame)

AOS counts per virtual channel only. There is no master channel frame count. The 24-bit counter plus the 4-bit cycle field is why AOS survives high rates where TM would wrap.

Errors

ErrorCause
ErrInvalidVersionTFVN is not 1
ErrInvalidSpacecraftIDOutside 0-255
ErrInvalidVCIDOutside 0-63
ErrInvalidVCFrameCountExceeds 24 bits
ErrInvalidVCFrameCountCycleOutside 0-15
ErrInvalidFirstHeaderPointerExceeds 11 bits
ErrInvalidBitstreamDataPointerExceeds 14 bits
ErrBitstreamTooLongForPointerPartial bitstream cannot be expressed by the BDP
ErrFHECMismatchHeader error control check failed
ErrCRCMismatchFECF did not match
ErrInvalidInsertZoneLengthDoes not match the configured length
ErrSizeMismatchVCA SDU does not fill the data field
ErrNoPacketSizerSetPacketSizer was not called before receiving
ErrSCIDMismatchFrame SCID differs from the master channel's

Notes

Commentary, not sourced from the standard.

Why 24 bits of frame count? An 8-bit counter wraps every 256 frames. On a high-rate downlink that is a fraction of a second, which makes it useless for anything but spotting a single dropped frame. 24 bits plus the 4-bit cycle field covers a whole pass and then some.

Why a smaller spacecraft ID than TM? AOS spent those bits on the virtual channel field and the frame count. A mission running AOS knows which spacecraft it is talking to; it needs the counter more.

Why the insert zone? Some data has to appear at a known offset in every single frame (a time code, a quality flag) so a receiver can find it without parsing the payload. Putting it in the data field would mean parsing packets first.

Why B_PDU? Not everything is a packet. Instrument output is sometimes just a bit stream, and forcing packet framing onto it adds overhead and a boundary problem that nobody wanted.

Reference