pxsc

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package pxsc implements the Proximity-1 Coding and Synchronization Sublayer per CCSDS 211.2-B-3 (October 2019).

This is the layer beneath pkg/pxdl. It wraps each transfer frame in a Proximity Link Transmission Unit (a sync marker, the frame, and a CRC-32) and fills the gaps between them with idle data so the receiver keeps bit lock.

PLTU:  ASM (FAF320) │ transfer frame │ CRC-32
        3 octets       variable         4 octets

It plays the same role for Proximity-1 that pkg/tmsc plays for TM and pkg/tcsc for TC, and the shape of the API follows those two.

The convolutional code of clause 3.4.3 is fully supported: the encoder is in convolutional.go and a matching Viterbi decoder is in viterbi.go, taking hard decisions through Decode and the soft decisions clause 3.4.3.3 recommends through DecodeSoft. Deliberately absent are the LDPC code of clause 3.4.4, its Codeword Sync Marker, and the pseudo-randomizer of clause 3.4.5, which applies only when LDPC is used; the PICS declares all three.

Index

Constants

View Source
const (
	// ConstraintLength is the number of input bits each output symbol depends
	// on, including the current one.
	ConstraintLength = 7
	// CodeRateNumerator and CodeRateDenominator give the rate 1/2.
	CodeRateNumerator   = 1
	CodeRateDenominator = 2
)

Convolutional code parameters, per CCSDS 131.0-B as referenced by clause 3.4.3.1.

View Source
const (
	// G1 is the first connection vector, 1111001 in binary.
	G1 uint8 = 0o171
	// G2 is the second connection vector, 1011011 in binary. Its output is
	// inverted, per clause 3.4.3.1 note 1.
	G2 uint8 = 0o133
)

Connection vectors for the rate 1/2 code, in octal as CCSDS writes them: G1 = 171, G2 = 133.

View Source
const ASMSize = 3

ASMSize is the width of the sync marker in octets (clause 3.2.3.1).

View Source
const CRC32Polynomial uint32 = 0x00A00805

CRC32Polynomial is the generator of annex C, C1.3, in MSB-first form with the implicit X^32 term dropped.

View Source
const CRC32Size = 4

CRC32Size is the width of the attached CRC in octets (clause 3.2.2 c).

View Source
const DefaultMaxFrameLength = 2048

DefaultMaxFrameLength bounds a frame recovered from a PLTU when no limit is given. It matches the largest Version-3 Transfer Frame, per CCSDS 211.0-B-6 clause 3.2.2.10.2.

The coding sublayer itself sets no ceiling: Clause 3.2.2 note 1 says the maximum comes from the mission's Maximum_Frame_Length parameter. This is a safe default for a decoder that has not been told one.

View Source
const DefaultMinFrameLength = 5

DefaultMinFrameLength is the shortest Version-3 Transfer Frame: the header alone, per CCSDS 211.0-B-6 clause 3.2.2.10.2.

View Source
const IdlePatternSize = 4

IdlePatternSize is the width of one repetition in octets.

View Source
const PLTUOverhead = ASMSize + CRC32Size

PLTUOverhead is what a PLTU costs on top of the frame it carries.

Variables

View Source
var (
	// ErrDataTooShort indicates the input is shorter than the fields it must contain.
	ErrDataTooShort = errors.New("data too short for the PLTU field being read")

	// ErrInvalidASM indicates the Attached Sync Marker is not the FAF320 of
	// CCSDS 211.2-B-3 clause 3.2.3.2.
	ErrInvalidASM = errors.New("invalid attached sync marker: expected FAF320")

	// ErrCRCMismatch indicates the attached CRC-32 did not verify, so the
	// PLTU must be discarded per clause 3.6.
	ErrCRCMismatch = errors.New("CRC-32 mismatch: the PLTU is corrupt")

	// ErrFrameTooLarge indicates a transfer frame beyond the configured
	// maximum PLTU length.
	ErrFrameTooLarge = errors.New("transfer frame exceeds the maximum length")

	// ErrEmptyFrame indicates an attempt to wrap nothing in a PLTU.
	ErrEmptyFrame = errors.New("cannot build a PLTU around an empty transfer frame")

	// ErrInvalidLength indicates a symbol stream that is not a whole number of
	// coded bits, so it cannot be decoded.
	ErrInvalidLength = errors.New("symbol stream length is not a whole number of coded input bits")
)

Sentinel errors returned by the Proximity-1 coding and synchronization codecs.

View Source
var ASM = [3]byte{0xFA, 0xF3, 0x20}

ASM is the Attached Synchronization Marker of CCSDS 211.2-B-3 clause 3.2.3.2: the 24-bit pattern FAF320.

Note it is three octets, not the four that TM and AOS use. A Proximity-1 link is short and re-acquires per PLTU, so the marker is cheaper.

View Source
var IdlePattern = [4]byte{0x35, 0x2E, 0xF8, 0x53}

IdlePattern is the PN sequence of clause 3.3.2.2: hexadecimal 352EF853, repeated as needed.

Functions

func AcquisitionSequence

func AcquisitionSequence(n int) []byte

AcquisitionSequence returns n octets for the acquisition sequence of clause 3.3.3.

It is the same pattern as any other idle data. The distinction is when it is sent, not what it contains: Clause 3.3.3.1 has the transmitter radiate carrier first, then this, so the receiver can reach a reliable symbol stream before real data starts.

The duration comes from the mission's Acquisition_Idle_Duration parameter, which is why the caller passes a length rather than this package choosing one.

func ComputeCRC32

func ComputeCRC32(data []byte) uint32

ComputeCRC32 returns the Proximity-1 CRC-32 over data.

The register starts at zero and there is no final inversion, per annex C and its encoder note.

func ConvolutionalEncode

func ConvolutionalEncode(data []byte) []byte

ConvolutionalEncode encodes data with a fresh encoder. Use a ConvolutionalEncoder directly when the stream continues across calls.

func DefaultASM

func DefaultASM() []byte

DefaultASM returns the sync marker as a slice, matching the shape of pkg/tmsc.DefaultASM.

func FindASM

func FindASM(data []byte, start int) int

FindASM returns the offset of the first sync marker at or after start, or -1 when there is none.

func IdleData

func IdleData(n int) []byte

IdleData returns n octets of idle data.

Clause 3.3.2.4: whenever the end of the PN sequence is reached it repeats from the first bit, so the output is the pattern tiled to length.

func IdleSequence

func IdleSequence(n int) []byte

IdleSequence returns n octets to send while no PLTU is ready (clause 3.3.4).

func IsIdleData

func IsIdleData(data []byte) bool

IsIdleData reports whether data is a run of the idle pattern starting at the beginning of the sequence.

A receiver uses this to tell filler from a PLTU that lost its sync marker.

func TailSequence

func TailSequence(n int) []byte

TailSequence returns n octets for the tail sequence of clause 3.3.5, sent before the transmitter stops.

Its length comes from the mission's Tail_Idle_Duration parameter.

func UnwrapPLTU

func UnwrapPLTU(pltu []byte) ([]byte, error)

UnwrapPLTU recovers the transfer frame from a PLTU, checking the sync marker and the CRC-32.

Like pkg/tmsc.UnwrapCADU, this expects the marker at offset zero. Use a Synchronizer to find PLTUs in a byte stream.

func UnwrapPLTUWithLimit

func UnwrapPLTUWithLimit(pltu []byte, maxFrameLength int) ([]byte, error)

UnwrapPLTUWithLimit recovers the transfer frame, rejecting one longer than maxFrameLength octets.

func VerifyCRC32

func VerifyCRC32(codeword []byte) bool

VerifyCRC32 reports whether a block ending in its own CRC-32 checks out.

Annex C, C2.2: the syndrome of a correct codeword is zero, so running the CRC over the message and its appended check value must give zero.

func ViterbiDecode

func ViterbiDecode(symbols []byte) ([]byte, error)

ViterbiDecode decodes a complete stream with a fresh decoder, flushing the traceback window at the end.

Use a ViterbiDecoder directly when the stream continues across calls.

func WrapPLTU

func WrapPLTU(frame []byte) ([]byte, error)

WrapPLTU builds a Proximity Link Transmission Unit around a transfer frame, per clause 3.2.2: the sync marker, the frame, then a CRC-32 over the frame.

Annex C, C1.2 note 2 is explicit that the ASM is not covered by the CRC.

Types

type ConvolutionalEncoder

type ConvolutionalEncoder struct {
	// contains filtered or unexported fields
}

ConvolutionalEncoder holds the shift register between calls, so a stream can be encoded in pieces.

The register is not reset between Encode calls. That is deliberate: Clause 3.4.3.2 encodes everything transmitted as one continuous stream, PLTUs and idle data alike, so the encoder state carries across unit boundaries.

func NewConvolutionalEncoder

func NewConvolutionalEncoder() *ConvolutionalEncoder

NewConvolutionalEncoder returns an encoder with a cleared register.

func (*ConvolutionalEncoder) Encode

func (e *ConvolutionalEncoder) Encode(data []byte) []byte

Encode convolutionally encodes data, returning twice as many octets.

Bits are taken most significant first, matching the bit numbering convention of clause 1.6.2. Each input bit yields two symbols, packed the same way.

func (*ConvolutionalEncoder) EncodeBit

func (e *ConvolutionalEncoder) EncodeBit(bit uint8) (c1, c2 uint8)

EncodeBit encodes one input bit and returns the two output symbols.

Clause 3.4.3.1 note 1: the output on the G2 path is inverted.

func (*ConvolutionalEncoder) Reset

func (e *ConvolutionalEncoder) Reset()

Reset clears the shift register.

type PLTU

type PLTU struct {
	// Frame is the transfer frame the PLTU carried.
	Frame []byte
	// CRC is the attached check value.
	CRC uint32
	// Offset is where the PLTU started in the stream it came from.
	Offset int
}

PLTU is a decoded Proximity Link Transmission Unit.

func (*PLTU) Humanize

func (p *PLTU) Humanize() string

Humanize returns a human-readable summary.

func (*PLTU) Length

func (p *PLTU) Length() int

Length returns the PLTU's total width in octets.

type Synchronizer

type Synchronizer struct {
	// MinFrameLength is the shortest transfer frame to consider. For
	// Version-3 frames this is 5, the header size.
	MinFrameLength int
	// MaxFrameLength is the longest transfer frame to consider. Zero selects
	// DefaultMaxFrameLength.
	MaxFrameLength int
}

Synchronizer finds PLTUs in a byte stream, per CCSDS 211.2-B-3 clause 3.6.

A Proximity-1 stream is not a tidy sequence of units. PLTUs of different lengths are separated by runs of idle data, and the receiver has to hunt for each sync marker in turn. Worse, the marker is only 24 bits, so a random match happens roughly once every 16 million octets. The CRC is what separates a real PLTU from a coincidence.

That is why this scans rather than parses: find a marker, try the frame lengths that could follow, and accept the first one whose CRC verifies.

A Synchronizer is not safe for concurrent use.

func NewSynchronizer

func NewSynchronizer() *Synchronizer

NewSynchronizer returns a synchronizer with the Version-3 frame bounds.

func (*Synchronizer) Scan

func (s *Synchronizer) Scan(data []byte) []PLTU

Scan finds every PLTU in data.

It walks the stream looking for sync markers. At each one it tries frame lengths from the minimum upward and takes the first whose CRC-32 verifies, then resumes after that PLTU. A marker with no verifying length is skipped as a false match.

func (*Synchronizer) ScanFrames

func (s *Synchronizer) ScanFrames(data []byte) [][]byte

ScanFrames finds every PLTU and returns just the transfer frames.

type ViterbiDecoder

type ViterbiDecoder struct {
	// contains filtered or unexported fields
}

ViterbiDecoder decodes a continuous convolutionally encoded stream.

It holds the trellis between calls, so a stream arriving in pieces decodes as one, which matters because clause 3.4.3.2 encodes everything transmitted as a single stream, PLTUs and idle data alike.

A ViterbiDecoder is not safe for concurrent use.

func NewViterbiDecoder

func NewViterbiDecoder() *ViterbiDecoder

NewViterbiDecoder returns a decoder positioned at the start of a stream.

Only state zero has a finite metric, which pins the decoder to the encoder's cleared register: no path can start anywhere else.

func (*ViterbiDecoder) Decode

func (d *ViterbiDecoder) Decode(symbols []byte) ([]byte, error)

Decode decodes convolutionally encoded symbols and returns the input bits they carry, packed most significant bit first.

symbols holds two coded bits per input bit, in the order Encode produced them, so the input must be an even number of octets: eight input bits become two octets of symbols.

func (*ViterbiDecoder) DecodeSoft

func (d *ViterbiDecoder) DecodeSoft(symbols []int8) ([]byte, error)

DecodeSoft decodes from soft decisions, which clause 3.4.3.3 recommends over hard ones.

Each entry is the demodulator's confidence in one coded symbol: positive for a one, negative for a zero, and further from zero for more confident. Two entries per input bit, so the slice length must be even. The scale does not matter, only the sign and the relative magnitude; three-bit decisions in the range -4 to 3 are what clause 3.4.3.3 has in mind.

func (*ViterbiDecoder) Flush

func (d *ViterbiDecoder) Flush() []byte

Flush returns the bits still held in the traceback window, ending the stream.

Decode only emits a bit once the survivor paths have had tracebackDepth steps to converge, so the last few decisions are still pending when the symbols run out. Flush forces them out along the best surviving path, where the usual convergence argument no longer applies. The final few bits are the least reliable in the stream.

func (*ViterbiDecoder) Reset

func (d *ViterbiDecoder) Reset()

Reset returns the decoder to the start of a stream.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL