Astro~/0.3
** Protocols / Space data link / Unified Space Data Link Protocol * PAGE 11 / 28
** Astro * Protocols
** /protocols/data-link/usdl

Unified Space Data Link Protocol

CCSDS 732.1-B-3, one frame format that replaces TM, TC, and AOS.

CCSDS 732.1-B-3 | Blue Book | pkg/usdl | astro usdl

Overview

USLP folds TM, TC, and AOS into one frame format. It runs in both directions, does fixed or variable length frames, and adds a MAP layer for finer multiplexing.

It is the protocol for a mission that would otherwise run three data link stacks and maintain all of them.

Scope

Implemented. The frame format, both fixed and variable length, truncated frames, all eight TFDZ construction rules, and the three MAP services: MAPP, MAPA, and MAPO. Master and virtual channel multiplexing, gap detection over a variable-width counter, and OID idle frames with the mandatory PN fill.

Somewhere else. Sync and coding are pkg/tmsc or pkg/tcsc depending on direction.

Left to you. Insert zone contents, and the OCF, you install a supplier callback and Astro asks it for each frame.

Field map

The primary header. Its size varies, because the frame count field is 0 to 7 octets wide. Go fields on usdl.PrimaryHeader.

FieldBitsGoNotes
Transfer Frame Version Number4TFVNAlways 12 (0b1100)
Spacecraft Identifier16SCID0-65535. TM and TC get 10 bits; USLP gets 16.
Source or Destination1SourceOrDest0 = SCID is the source, 1 = the destination
Virtual Channel Identifier6VCID0-63. VCID 63 carries Only Idle Data.
MAP Identifier4MAPID0-15
End of Frame Primary Header1EndOfFPHSet on a truncated frame
Frame Length16FrameLengthTotal octets minus 1. Absent on truncated frames.
Bypass / Sequence Control1BypassSeqCtrltrue = expedited
Protocol Control Command1ProtCtrlCmdtrue = protocol control
OCF Flag1OCFFlagSignals the OCF in-band
VCF Count Length3VCFCountLen0-7 octets
VC Frame Count0-56VCFCountWidth set by the field above

The rest of the frame, on usdl.TransferFrame:

PartSizeGoNotes
Insert ZonefixedInsertZoneOptional, fixed per physical channel
TFDF Header1 or 3 BDataFieldHeaderConstruction rule, UPID, and sometimes a pointer
Transfer Frame Data ZonevariableDataField
Operational Control Field4 BOCFPresent when OCFFlag is set
Frame Error Control Field2 BFECFOptional CRC-16-CCITT

Channel settings live on usdl.ChannelConfig: FrameLength (0 means variable), HasOCF, HasFECF, InsertZoneLen, VCFCountLen, IdlePattern.

Construction rules

The TFDF header's 3-bit rule says how to read the data zone. Rules 000, 001, and 010 add a 16-bit pointer; the rest do not.

RuleLengthData zone holdsPointer
000fixedCCSDS packets, spanning framesFirst Header Pointer (0xFFFF = none starts here)
001fixedStart of a MAPA or VCA SDULast Valid Octet Pointer (0xFFFF = continues)
010fixedContinuation of that SDULast Valid Octet Pointer
011variableA continuous octet stream-
100variableFirst segment of an SDU-
101variableMiddle segment-
110variableLast segment-
111variableComplete, unsegmented SDUs or packets-

The 5-bit UPID names what is inside. Constants are in the package. The common ones: 0 Space or Encapsulation Packets, 1 COP-1 control, 2 COP-P control, 4 user octet stream, 5 Mission Specific Information-1, 7 Proximity-1 SPDUs, 31 Idle Data. UPID 3 is not assigned by SANA or by any USLP issue.

Gotchas

Two different idle fills, and they are not interchangeable. An OID frame's data zone carries the mandatory PN sequence from clause 4.1.4.1.10, a 32-cell LFSR, polynomial D0+D1+D2+D22+D32, all-ones seed, never restarted. MasterChannel holds one persistent OIDSequence for this. Your ChannelConfig.IdlePattern is a different thing: it fills spare data-zone space behind a Last Valid Octet Pointer on a non-OID frame. Mixing them up puts a project pattern where the standard wants PN.

A partly full packet zone gets an Encapsulation Idle Packet. Under rule 000, Astro completes the zone with a real idle packet and points the First Header Pointer at it, so a receiver can resynchronize. Not raw fill.

Truncated frames give up almost everything. No insert zone, no OCF, no FECF, no pointer, ErrTruncatedFrameFields if you try. The data zone needs at least one octet (ErrTruncatedFrameTooShort, minimum frame is 6 octets) and the whole frame stops at 32 octets (ErrTruncatedFrameTooLong).

An OCF channel needs a supplier or nothing sends. Set one with SetOCFSupplier on each service, and on the MasterChannel for idle frames. Without it you get ErrNoOCFSupplier. Astro refuses rather than making up an all-zero Type-1 report, because a fabricated CLCW is worse than a missing frame.

Octet streams need a variable-length channel. Clause 4.2.4.1. Calling MAPO on a fixed-length channel gives ErrOctetStreamFixedLength.

The FECF is 16 bits or nothing. USLP has no 32-bit variant, unlike some other CCSDS links. ErrInvalidFECSize if you ask for one.

Two MAPs on one VC both get their traffic. The virtual channel keeps a per-MAP receive demultiplexer, so a service pulling its own MAP's frames does not consume and discard another MAP's. That is a real bug in naive implementations.

Quick start

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

// Spacecraft 100, virtual channel 1, MAP 0.
frame, err := usdl.NewTransferFrame(100, 1, 0, payload,
    usdl.WithConstructionRule(usdl.RuleNoSegmentation),
    usdl.WithUPID(usdl.UPIDSpacePackets),
    usdl.WithVCFCount(2, 42), // 2-octet count, value 42
)
encoded, err := frame.Encode()
// fecSize, insertZoneLen. OCF presence is read from the in-band flag.
back, err := usdl.DecodeTransferFrame(encoded, usdl.FECSize16, 0)

fmt.Println(back.Header.SCID, back.Header.VCID, back.Header.MAPID, back.Header.VCFCount)
// 100 1 0 42

Decoding takes two configuration arguments, not three, USLP signals OCF presence with a header flag, so the decoder works that one out for itself. That is a real improvement over AOS.

Construction rules

The rule tells a receiver how to read the data zone. Constants match the layout table on the protocol page.

ConstantValueData zone
RulePacketsSpanning000Packets spanning frames, with a First Header Pointer
RuleStartOfSDU001Start of a MAPA or VCA SDU
RuleContinuingSDU010Continuation of that SDU
RuleOctetStream011A continuous octet stream
RuleStartingSegment100First segment
RuleContinuingSegment101Middle segment
RuleLastSegment110Last segment
RuleNoSegmentation111Complete, unsegmented

UPID constants name the payload: UPIDSpacePackets (0), UPIDCOPPControl (2), UPIDUserOctetStream (4), UPIDIdle (31), and the rest of the SANA registry.

Frame options

OptionEffect
WithConstructionRule(rule)Sets the TFDZ construction rule
WithUPID(upid)Names what is in the data zone
WithPointer(p)First Header Pointer or Last Valid Octet Pointer, for rules 000/001/010
WithVCFCount(len, n)Sets the count width in octets and its value
WithInsertZone(data)Fills the insert zone
WithOCF(ocf)Attaches the 4-byte OCF and sets the flag
WithoutFECF()Drops the CRC on this frame
WithSourceOrDest(flag)Whether the SCID names the source or the destination
WithBypassSeqCtrl()Marks the frame expedited
WithProtCtrlCmd()Marks it a protocol control command

Truncated frames

For a very short telecommand, annex D allows a stripped-down frame:

tc, err := usdl.NewTruncatedFrame(100, 1, 0, cmdBytes)

The whole frame stays within 6 to 32 octets. No insert zone, no OCF, no FECF, no pointer, asking for any of those gives ErrTruncatedFrameFields.

Channel configuration

config := usdl.ChannelConfig{
    FrameLength:   256,  // 0 means variable length
    HasOCF:        false,
    HasFECF:       true,
    InsertZoneLen: 0,
    VCFCountLen:   2,    // 0 to 7 octets
    IdlePattern:   nil,
}

FrameLength: 0 puts the channel in variable-length mode, which is what the octet stream service needs.

Services

All three operate at the MAP level. A virtual channel keeps a per-MAP receive demultiplexer, so two MAPs sharing a VC each get their own traffic.

MAPPacketService

vc := usdl.NewVirtualChannel(1, 32)
counter := usdl.NewFrameCounter()

svc := usdl.NewMAPPacketService(100, 1, 0, vc, config, counter)
svc.SetPacketSizer(spp.PacketSizer)

svc.Send(packetBytes)
svc.Flush()

On a fixed-length channel this uses rule 000 with the First Header Pointer, and Flush completes a partial frame with an Encapsulation Idle Packet. On a variable-length channel each frame carries complete packets under rule 111.

MAPAccessService

Constant-length SDUs:

svc := usdl.NewMAPAccessService(100, 1, 1, sduSize, vc, config, counter)

Fixed-length channels start an SDU under rule 001 and continue under 010, delimited by the Last Valid Octet Pointer.

MAPOctetStreamService

svc := usdl.NewMAPOctetStreamService(100, 1, 2, vc, config, counter)

Rule 011, variable-length channels only. Clause 4.2.4.1. On a fixed-length channel you get ErrOctetStreamFixedLength.

OCF suppliers

On a channel configured with HasOCF, install a supplier or nothing sends:

svc.SetOCFSupplier(func() []byte { return clcw.Encode() })
mc.SetOCFSupplier(func() []byte { return clcw.Encode() })  // for idle frames

Without one you get ErrNoOCFSupplier. Astro refuses rather than fabricating an all-zero Type-1 report, because a made-up CLCW is worse than a missing frame.

Channel hierarchy

mc := usdl.NewMasterChannel(100, config)
mc.AddVirtualChannel(vc, 1)

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

frame, err := pc.GetNextFrame()

GetNextFrameOrIdle() emits an OID frame when nothing is queued. The master channel holds one persistent OIDSequence so back-to-back idle frames carry different PN fill.

Gap detection needs the count width, since it is a managed parameter:

det := usdl.NewFrameGapDetector(config.VCFCountLen)

Errors

ErrorCause
ErrInvalidVersionTFVN is not 12
ErrInvalidSpacecraftIDOutside 0-65535
ErrInvalidVCIDOutside 0-63
ErrInvalidMAPIDOutside 0-15
ErrInvalidVCFCountLenOutside 0-7 octets
ErrInvalidVCFCountExceeds the configured field width
ErrInvalidConstructionRuleOutside 0-7
ErrInvalidPointerExceeds the data zone length
ErrInvalidFECSizeNot 0 or 2, USLP has only the 16-bit FECF
ErrTruncatedFrameFieldsTruncated frame asked for an insert zone, OCF, FECF, or pointer
ErrTruncatedFrameTooShortData zone under 1 octet
ErrTruncatedFrameTooLongFrame over 32 octets
ErrNoOCFSupplierChannel needs an OCF but none was installed
ErrOctetStreamFixedLengthMAPO on a fixed-length channel
ErrFrameLengthMismatchLength field disagrees with the buffer
ErrNoPacketSizerSetPacketSizer was not called before receiving
ErrCRCMismatchFECF did not match

Notes

Commentary, not sourced from the standard.

Why unify at all? A mission running TM down, TC up, and AOS for the high-rate instrument maintains three frame formats, three multiplexers, and three sets of test tooling. USLP is one of each.

Why a variable-width frame counter? A quiet housekeeping channel does not need 24 bits and a high-rate instrument channel does. Making the width a managed parameter lets each virtual channel pay only for what it uses.

Why 16 bits of spacecraft ID? Constellations. Ten bits is 1,024 spacecraft, which felt endless when TM was written and does not any more.

Why so many construction rules? They are the seams where TM, TC, and AOS were stitched together. Each old protocol's data zone layout survives as a rule, which is what makes migration possible without changing the receiver's mental model all at once.

Reference