MaskOperations.java
/*
* Copyright (c) 2025 Tanagra Space
* SPDX-License-Identifier: MIT
*/
package space.tanagra.ccsds124;
/**
* Mask and build vector operations for CCSDS 124.0-B-1 compression.
*
* <p>Implements CCSDS 124.0-B-1 Section 4:
*
* <ul>
* <li>Build Vector Update (Equation 6)
* <li>Mask Vector Update (Equation 7)
* <li>Change Vector Computation (Equation 8)
* </ul>
*/
public final class MaskOperations {
private MaskOperations() {
// Utility class
}
/**
* Updates the build vector in-place (CCSDS Eq. 6).
*
* <ul>
* <li>Bₜ = (Iₜ XOR Iₜ₋₁) OR Bₜ₋₁ (if t > 0 and ṗₜ = 0)
* <li>Bₜ = 0 (otherwise: t=0 or ṗₜ=1)
* </ul>
*
* @param build build vector to update in-place
* @param input current input Iₜ
* @param prevInput previous input Iₜ₋₁
* @param newMaskFlag true if starting new mask period (ṗₜ = 1)
*/
public static void updateBuild(
BitVector build, BitVector input, BitVector prevInput, boolean newMaskFlag) {
if (newMaskFlag) {
build.zero();
} else {
BitVector changes = input.xor(prevInput);
BitVector result = changes.or(build);
build.copyFrom(result);
}
}
/**
* Updates the build vector in-place using word-level operations (CCSDS Eq. 6).
*
* @param build build vector to update in-place
* @param input current input Iₜ
* @param prevInput previous input Iₜ₋₁
* @param newMaskFlag true if starting new mask period (ṗₜ = 1)
*/
public static void updateBuildInPlace(
BitVector build, BitVector input, BitVector prevInput, boolean newMaskFlag) {
if (newMaskFlag) {
build.zero();
} else {
// Bₜ = (Iₜ XOR Iₜ₋₁) OR Bₜ₋₁ computed in-place
int numWords = build.numWords();
for (int i = 0; i < numWords; i++) {
int changes = input.getWord(i) ^ prevInput.getWord(i);
build.setWord(i, changes | build.getWord(i));
}
}
}
/**
* Updates the mask vector in-place (CCSDS Eq. 7).
*
* <ul>
* <li>Mₜ = (Iₜ XOR Iₜ₋₁) OR Mₜ₋₁ (if ṗₜ = 0)
* <li>Mₜ = (Iₜ XOR Iₜ₋₁) OR Bₜ₋₁ (if ṗₜ = 1)
* </ul>
*
* @param mask mask vector to update in-place
* @param input current input Iₜ
* @param prevInput previous input Iₜ₋₁
* @param buildPrev previous build vector Bₜ₋₁
* @param newMaskFlag true if starting new mask period (ṗₜ = 1)
*/
public static void updateMask(
BitVector mask,
BitVector input,
BitVector prevInput,
BitVector buildPrev,
boolean newMaskFlag) {
BitVector changes = input.xor(prevInput);
BitVector result = newMaskFlag ? changes.or(buildPrev) : changes.or(mask);
mask.copyFrom(result);
}
/**
* Updates the mask vector in-place using word-level operations (CCSDS Eq. 7).
*
* @param mask mask vector to update in-place
* @param input current input Iₜ
* @param prevInput previous input Iₜ₋₁
* @param buildPrev previous build vector Bₜ₋₁
* @param newMaskFlag true if starting new mask period (ṗₜ = 1)
*/
public static void updateMaskInPlace(
BitVector mask,
BitVector input,
BitVector prevInput,
BitVector buildPrev,
boolean newMaskFlag) {
int numWords = mask.numWords();
for (int i = 0; i < numWords; i++) {
int changes = input.getWord(i) ^ prevInput.getWord(i);
if (newMaskFlag) {
mask.setWord(i, changes | buildPrev.getWord(i));
} else {
mask.setWord(i, changes | mask.getWord(i));
}
}
}
/**
* Computes the change vector (CCSDS Eq. 8).
*
* @param mask current mask vector Mₜ
* @param prevMask previous mask vector Mₜ₋₁
* @param t current time index
* @return change vector Dₜ
*/
public static BitVector computeChange(BitVector mask, BitVector prevMask, int t) {
if (t == 0) {
// CCSDS Eq. 8: D0 = 0 — both encoder and decoder start from the
// same user-specified M0, so there is no change to communicate
return new BitVector(mask.length());
} else {
return mask.xor(prevMask);
}
}
/**
* Computes change vector into pre-allocated buffer (CCSDS Eq. 8).
*
* @param result pre-allocated buffer for result
* @param mask current mask vector Mₜ
* @param prevMask previous mask vector Mₜ₋₁
* @param t current time index
*/
public static void computeChangeInto(
BitVector result, BitVector mask, BitVector prevMask, int t) {
if (t == 0) {
// CCSDS Eq. 8: D0 = 0 — no change at initialization
result.zero();
} else {
// Dₜ = Mₜ XOR Mₜ₋₁ computed directly into result
int numWords = result.numWords();
for (int i = 0; i < numWords; i++) {
result.setWord(i, mask.getWord(i) ^ prevMask.getWord(i));
}
}
}
}