Documentation
¶
Overview ¶
Package rhc implements Robust Compression of Fixed-Length Housekeeping Data per CCSDS 124.0-B-1, the POCKET+ algorithm.
Housekeeping telemetry barely changes. A voltage reading, a mode word, a thermistor count: most bits are the same in this packet as in the last one, and this algorithm exists to send only the ones that are not.
It keeps a mask, one bit per position, saying whether that position is predictable (unchanged since the last packet) or not. Predictable positions are not sent at all; the decompressor already knows them. Each output vector carries three things (clause 5.3.1):
h_t what changed in the mask lately, and how far back that reaches q_t the whole mask, when asked for u_t the values of the unpredictable bits, or the whole input when asked
Using it ¶
config := rhc.Config{
VectorLength: 512,
Robustness: 3,
NewMaskInterval: 32,
SendMaskInterval: 16,
UncompressedInterval: 16,
}
compressor, err := rhc.NewCompressor(config)
coded, bitLen, err := compressor.Compress(packet)
decompressor, err := rhc.NewDecompressor(config)
packet, err := decompressor.Decompress(coded, bitLen)
Both types hold state across the whole stream and neither is safe for concurrent use. One stream, one pair, one goroutine.
Loss ¶
The algorithm is built for a lossy link, and it is worth being precise about what that buys. Each output says how many outputs may have been lost immediately before it without stopping it being decoded. Its effective robustness level. Set Robustness to the floor you want.
But the decompressor cannot tell that anything was lost. Clause 2.2 says so outright: the standard "does not provide a mechanism for identifying the number of sequential output binary vectors that were lost", and suggests packet sequence counters as the mission's answer. So the caller must notice gaps and call NotifyLoss. Without that, a decompressor fed a stream with holes in it will reconstruct wrong bytes and not know.
Nor are there sync markers: a foreign or corrupt vector that happens to parse will be taken for a real one. Framing is the mission's job too.
After a reported gap, recovery normally trusts the next output's self-declared effective robustness level. Config.Strict withdraws that trust: a strict decompressor then accepts nothing but an uncompressed output.
What is here ¶
The compressor is the whole of the standard's normative content: inputs (clause 3), mask update (clause 4) and encoder (clause 5). The standard specifies nothing else (there is no decoder section and the conformance list in annex A has only encoder items) so the decompressor here is the encoder run backwards, which clause 2.1 lays out the requirements for. See docs/content/conformance/rhc.md.
Index ¶
- Constants
- Variables
- func AppendCount(w *BitWriter, a int) error
- func AppendRLE(w *BitWriter, v Vector) error
- func ReadCount(r *BitReader) (a int, terminator bool, err error)
- type BitReader
- type BitWriter
- type Compressor
- func (c *Compressor) Compress(input []byte) (out []byte, bitLen int, err error)
- func (c *Compressor) CompressWith(input []byte, params CycleParams) (out []byte, bitLen int, err error)
- func (c *Compressor) ForceNewMask()
- func (c *Compressor) ForceSendMask()
- func (c *Compressor) ForceUncompressed()
- func (c *Compressor) Index() int
- func (c *Compressor) Mask() Vector
- func (c *Compressor) Reset()
- type Config
- type CycleParams
- type Decompressor
- type Vector
- func (v Vector) AND(other Vector) Vector
- func (v Vector) Bytes() []byte
- func (v Vector) Clone() Vector
- func (v Vector) Extract(selector Vector) []bool
- func (v Vector) Get(i int) bool
- func (v Vector) IsZero() bool
- func (v Vector) Len() int
- func (v Vector) Not() Vector
- func (v Vector) OR(other Vector) Vector
- func (v Vector) Reverse() Vector
- func (v Vector) Set(i int, bit bool)
- func (v Vector) ShiftLeft() Vector
- func (v Vector) String() string
- func (v Vector) Weight() int
- func (v Vector) XOR(other Vector) Vector
Constants ¶
const MaxCount = 1<<16 - 1
MaxCount is the largest integer COUNT accepts, per clause 5.2.2.
const MaxRobustness = 7
MaxRobustness is the largest robustness level clause 3.3.2a allows.
const MaxVectorLength = 1<<16 - 1
MaxVectorLength is the longest input vector clause 3.2 allows.
Variables ¶
var ( // ErrDataTooShort indicates the coded bit stream ended before a field it // must contain. ErrDataTooShort = errors.New("data too short: the coded bit stream ended early") // ErrInvalidVectorLength indicates an input vector length outside the // 1 to 65535 of CCSDS 124.0-B-1 clause 3.2. ErrInvalidVectorLength = errors.New("invalid vector length: must be 1 to 65535 bits") // ErrInvalidPacketLength indicates an input packet that is not the // configured length. ErrInvalidPacketLength = errors.New("input packet is not the configured vector length") // ErrInvalidRobustness indicates a robustness level outside the 0 to 7 of // Clause 3.3.2a. ErrInvalidRobustness = errors.New("invalid robustness level: must be 0 to 7") // ErrInvalidInterval indicates a negative flag interval in the Config. // The intervals are this package's convenience, not the standard's; zero // disables one, and nothing below zero means anything. ErrInvalidInterval = errors.New("invalid flag interval: must not be negative") // ErrInvalidCount indicates a counter codeword the table of clause 5.2.2 does // not define. ErrInvalidCount = errors.New("invalid counter codeword") // ErrInvalidRunLength indicates a run-length codeword that runs past the // end of the vector it describes. ErrInvalidRunLength = errors.New("run-length codeword runs past the end of the vector") // ErrNotSynchronized indicates the decompressor has no state to work from. // // Clause 3.3.2 forces the send mask and uncompressed flags to one while // t <= R_t, so a compressor's first output always carries a whole mask and // a whole input vector. Until one of those arrives (at the start, or // after Reset, or after losing more packets than the robustness level // covers) the decompressor cannot vouch for anything and says so rather // than guessing. ErrNotSynchronized = errors.New("decompressor is not synchronized: waiting for an uncompressed output vector") // ErrVectorLengthMismatch indicates a coded vector whose embedded length // disagrees with the configured one. ErrVectorLengthMismatch = errors.New("the coded vector length does not match the configured one") // decompressor does not have. ErrMaskUnavailable = errors.New("the mask is not known: an earlier output vector carrying it was lost") )
Sentinel errors returned by the compressor and decompressor.
Functions ¶
func AppendCount ¶
AppendCount writes COUNT(a), the counter encoding function of clause 5.2.2:
A = 1 '0' 2 <= A <= 33 '110' || BIT5(A-2) A >= 34 '111' || BITE(A-2)
with E from equation 9. The three-way split is a prefix code, which is what lets the decoder tell the codewords apart without knowing how many there are: a leading '0' ends immediately, and a leading '11' promises more.
func AppendRLE ¶
AppendRLE writes RLE(v), the run-length encoding of clause 5.2.3:
RLE(a) = COUNT(C0) || ... || COUNT(C_{H(a)-1}) || '10'
where C_i is one more than the number of zeros before the ith one bit, counting from the first transmitted bit.
Trailing zeros are not encoded. Note 1 of clause 5.2.3 says why they need not be: the decoder knows the vector's length, so whatever is left after the last one bit must be zeros.
Types ¶
type BitReader ¶
type BitReader struct {
// contains filtered or unexported fields
}
BitReader reads MSB first and reports exhaustion rather than panicking.
func NewBitReader ¶
NewBitReader prepares a reader over every bit of data.
func NewBitReaderN ¶
NewBitReaderN prepares a reader over the first n bits of data.
type BitWriter ¶
type BitWriter struct {
// contains filtered or unexported fields
}
BitWriter packs bits MSB first into a growing octet slice.
The zero value is ready to use.
func (*BitWriter) Bytes ¶
Bytes returns the written bits, padding the last octet with zeros.
The padding is this package's, not the standard's: Clause 2.2 says framing an output vector is mission specific, and an octet slice is the framing this API offers. The true length is BitLen.
func (*BitWriter) WriteString ¶
WriteString appends a literal bit string such as "110", which is how the spec writes its fixed codewords.
type Compressor ¶
type Compressor struct {
// contains filtered or unexported fields
}
Compressor holds the state one binary vector stream needs.
It is not safe for concurrent use. One stream, one Compressor, one goroutine.
func NewCompressor ¶
func NewCompressor(config Config) (*Compressor, error)
NewCompressor prepares a compressor.
func (*Compressor) Compress ¶
func (c *Compressor) Compress(input []byte) (out []byte, bitLen int, err error)
Compress consumes one input vector and returns the output vector, with its length in bits.
The octet slice is padded with zeros to the next octet; clause 2.2 leaves framing to the mission, so the bit length is what the caller must carry alongside if it is packing several outputs together.
func (*Compressor) CompressWith ¶
func (c *Compressor) CompressWith(input []byte, params CycleParams) (out []byte, bitLen int, err error)
CompressWith consumes one input vector using explicit cycle parameters.
func (*Compressor) ForceNewMask ¶
func (c *Compressor) ForceNewMask()
ForceNewMask makes the next cycle set the new mask flag.
func (*Compressor) ForceSendMask ¶
func (c *Compressor) ForceSendMask()
ForceSendMask makes the next cycle send the whole mask.
func (*Compressor) ForceUncompressed ¶
func (c *Compressor) ForceUncompressed()
ForceUncompressed makes the next cycle carry the whole input vector.
This is the recovery lever: a decompressor that has lost its place is restored by an uncompressed output and nothing else.
func (*Compressor) Index ¶
func (c *Compressor) Index() int
Index returns the time index of the next input vector.
func (*Compressor) Mask ¶
func (c *Compressor) Mask() Vector
Mask returns a copy of the current mask, for inspection.
func (*Compressor) Reset ¶
func (c *Compressor) Reset()
Reset returns the compressor to its initial state: t = 0, the mask back to M_0, the build to zero (clause 4.1), and no history.
type Config ¶
type Config struct {
// VectorLength is F, the length of every input vector in bits. Clause 3.2
// allows 1 to 65535.
VectorLength int
// InitialMask is M_0, the mask the compressor starts from (clause 3.3.1). A nil
// value means all zeros, which the note under clause 3.3.1 calls "often a
// reasonable default": every position starts out predictable.
InitialMask Vector
// Robustness is R_t, the minimum required effective robustness level
// (clause 3.3.2a), 0 to 7. It is how many consecutive output vectors may be
// lost before this one and still leave the mask recoverable.
//
// Higher costs bits: the change information in h_t is ORed over R_t+1
// cycles, so more positions appear in it.
Robustness int
// NewMaskInterval is how often to set the new mask flag, in cycles. Zero
// never sets it.
//
// This is policy, not protocol. Clause 3.3.2b makes the flag user-specified at
// every cycle and says nothing about when to set it. Setting it lets
// positions go back to being predictable, which is what stops the mask
// filling up with ones over a long run; how often to pay for that is a
// mission decision.
NewMaskInterval int
// SendMaskInterval is how often to set the send mask flag, in cycles.
// Zero never sets it beyond what clause 3.3.2c forces.
//
// Also policy. Sending the whole mask lets a decompressor that has lost
// its place recover the mask without waiting for changes to describe it.
SendMaskInterval int
// UncompressedInterval is how often to set the uncompressed flag, in
// cycles. Zero never sets it beyond what clause 3.3.2d forces.
//
// Also policy, and the one that matters most for recovery: an
// uncompressed output carries the whole input vector, which is the only
// thing that restores a decompressor's previous-vector state after a gap.
UncompressedInterval int
// Strict makes the decompressor accept nothing but an uncompressed
// output after a reported loss (NotifyLoss, or an output that failed to
// parse) even when a later output's effective robustness level claims to
// reach back across the gap.
//
// The point is trust. The standard's recovery gate compares the gap
// against V_t (clause 5.3.2.2), a field the output vector declares about
// itself; nothing in the format lets a decompressor verify it, so a
// corrupt or hostile vector arriving right after a gap can claim any
// reach up to 15 and be believed. Strict mode drops that trust and waits
// for the one output that proves itself by carrying the whole input.
// The cost is availability: everything between the gap and the next
// uncompressed output is refused.
//
// It has no effect on the compressor, and none on a decompressor that
// has not been told of any loss.
Strict bool
}
Config fixes what does not change from cycle to cycle.
type CycleParams ¶
type CycleParams struct {
// Robustness is R_t (clause 3.3.2a).
Robustness int
// NewMask is the new mask flag, p-dot_t (clause 3.3.2b).
NewMask bool
// SendMask is the send mask flag, f-dot_t (clause 3.3.2c).
SendMask bool
// Uncompressed is the uncompressed flag, r-dot_t (clause 3.3.2d).
Uncompressed bool
}
CycleParams are the per-cycle parameters of clause 3.3.2.
Compress derives these from the Config. CompressWith takes them directly, for a caller driving the flags from its own logic, which clause 2.1 explicitly allows: "the decompressor is not required to actively change user defined parameters as all the information required for decompression is contained in the output bit vectors".
type Decompressor ¶
type Decompressor struct {
// contains filtered or unexported fields
}
Decompressor reconstructs a stream compressed by Compressor.
It is not safe for concurrent use.
func NewDecompressor ¶
func NewDecompressor(config Config) (*Decompressor, error)
NewDecompressor prepares a decompressor.
The configuration's VectorLength must match the compressor's. Clause 3.3.2's note lists the parameters that need not be known in advance (M_0, R_t and the three flags) and F is deliberately not among them.
func (*Decompressor) Decompress ¶
func (d *Decompressor) Decompress(data []byte, bitLen int) ([]byte, error)
Decompress consumes one output vector and returns the input vector it encodes.
bitLen is the true length of the output vector in bits, as Compress returned it. Pass zero to read to the end of the slice, which works when the vector was carried alone in its own frame.
func (*Decompressor) Mask ¶
func (d *Decompressor) Mask() Vector
Mask returns a copy of the mask as last known, for inspection.
func (*Decompressor) NotifyLoss ¶
func (d *Decompressor) NotifyLoss(count int)
NotifyLoss tells the decompressor that count output vectors were lost before the next one it will be given.
Call it when a sequence counter shows a gap. Until an output arrives whose effective robustness level covers the gap (or which carries the whole mask and the whole input) Decompress will return an error rather than a vector it cannot vouch for.
func (*Decompressor) Reset ¶
func (d *Decompressor) Reset()
Reset discards all state. The next output vector must carry the whole mask and the whole input for decompression to resume, which clause 3.3.2 guarantees a compressor's own first output does.
func (*Decompressor) Synchronized ¶
func (d *Decompressor) Synchronized() bool
Synchronized reports whether the decompressor can reconstruct the next output vector.
type Vector ¶
type Vector struct {
// contains filtered or unexported fields
}
Vector is a fixed-length binary vector.
func VectorFromBytes ¶
VectorFromBytes reads the first n bits of data, MSB first, into a vector.
func VectorFromString ¶
VectorFromString reads a literal such as "10110", which is how the spec writes example vectors.
func (Vector) Extract ¶
Extract returns the bit extraction of v relative to selector, which clause 5.2.4 writes BE(a, b): the bits of v at the positions where selector has a one, emitted in the reverse of transmission order.
The order needs spelling out, because a forward scan is the natural implementation and it is wrong. Equation 11 defines
BE(a, b) = ȧ_{g(H(b)-1)} || ... || ȧ_{g0}
where g_i is "the position of the ith '1' bit in b, starting from the MSB" , so g_0 is the first selected position in transmission order and g_{H-1} the last. Equation 1 fixes what a concatenation means: it writes the left shift as a« = {ȧ_{N-2}, ..., ȧ_0, 0} and gives the example '10111' becoming '01110', so the first listed element is the first transmitted bit. Put together, BE transmits the bit at the *last* selected position first: the output is the forward scan reversed. The independent VisionSpace PocketPlus implementation reads equation 11 the same way, reversing at every bit-extraction site.
So this walks the vector in transmission order collecting selected bits, then reverses. The decompressor mirrors the reversal when it consumes k_t and the compressed u_t.
func (Vector) Reverse ¶
Reverse returns the vector with its bits in the opposite order, which Clause 1.6.1 writes <a>.
func (Vector) ShiftLeft ¶
ShiftLeft returns the left bit-shift, which clause 1.6.1 writes a<< and equation 1 defines: every bit moves one place towards the first transmitted bit, and a zero enters at the end.
func (Vector) String ¶
String renders the vector as a bit string, which is how the spec prints them.