Ccsds124.java

/*
 * Copyright (c) 2025 Tanagra Space
 * SPDX-License-Identifier: MIT
 */

package space.tanagra.ccsds124;

/**
 * CCSDS 124.0-B-1 lossless compression algorithm for satellite housekeeping telemetry.
 *
 * <p>This class provides the high-level API for compressing and decompressing data using the CCSDS
 * 124.0-B-1 algorithm.
 *
 * <p>Example usage:
 *
 * <pre>{@code
 * // Compress data
 * byte[] compressed = Ccsds124.compress(input, 90, 2, 20, 50, 100);
 *
 * // Decompress data
 * byte[] decompressed = Ccsds124.decompress(compressed, 90, 2);
 * }</pre>
 *
 * @see <a href="https://ccsds.org/Pubs/124x0b1.pdf">CCSDS 124.0-B-1</a>
 */
public final class Ccsds124 {

  /** Library version. */
  public static final String VERSION = "1.0.0";

  /** Maximum packet length in bits (CCSDS limit). */
  public static final int MAX_PACKET_LENGTH = 65535;

  /** Maximum packet length in bytes. */
  public static final int MAX_PACKET_BYTES = (MAX_PACKET_LENGTH + 7) / 8;

  /** Maximum robustness value. */
  public static final int MAX_ROBUSTNESS = 7;

  private Ccsds124() {
    // Utility class - prevent instantiation
  }

  /**
   * Returns the library version string.
   *
   * @return version string in format "major.minor.patch"
   */
  public static String version() {
    return VERSION;
  }

  /**
   * Compresses data using the CCSDS 124.0-B-1 algorithm.
   *
   * @param data input data to compress (multiple packets concatenated)
   * @param packetSize size of each packet in bytes
   * @param robustness robustness parameter R (0-7)
   * @param ptLimit Pt parameter limit
   * @param ftLimit Ft parameter limit
   * @param rtLimit Rt parameter limit
   * @return compressed data
   * @throws Ccsds124Exception if compression fails
   */
  public static byte[] compress(
      byte[] data, int packetSize, int robustness, int ptLimit, int ftLimit, int rtLimit)
      throws Ccsds124Exception {
    if (data == null) {
      throw new Ccsds124Exception("Input data cannot be null");
    }
    if (packetSize <= 0 || packetSize > MAX_PACKET_BYTES) {
      throw new Ccsds124Exception("Invalid packet size: " + packetSize);
    }
    if (robustness < 0 || robustness > MAX_ROBUSTNESS) {
      throw new Ccsds124Exception("Invalid robustness: " + robustness);
    }
    if (data.length % packetSize != 0) {
      throw new Ccsds124Exception(
          "Data length must be multiple of packet size: " + data.length + " % " + packetSize);
    }

    int numPackets = data.length / packetSize;
    int packetBits = packetSize * 8;

    Compressor compressor = new Compressor(packetBits, null, robustness, ptLimit, ftLimit, rtLimit);
    java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();

    for (int i = 0; i < numPackets; i++) {
      byte[] packetData = new byte[packetSize];
      System.arraycopy(data, i * packetSize, packetData, 0, packetSize);
      BitVector input = BitVector.fromBytes(packetData, packetBits);

      BitBuffer packetOutput = compressor.compressPacket(input);
      // Convert each packet to bytes with byte-boundary padding (matches C implementation)
      byte[] packetBytes = packetOutput.toBytes();
      output.write(packetBytes, 0, packetBytes.length);
    }

    return output.toByteArray();
  }

  /**
   * Decompresses data using the CCSDS 124.0-B-1 algorithm.
   *
   * @param data compressed data to decompress
   * @param packetSize size of each decompressed packet in bytes
   * @param robustness robustness parameter R (0-7)
   * @return decompressed data
   * @throws Ccsds124Exception if decompression fails
   */
  public static byte[] decompress(byte[] data, int packetSize, int robustness)
      throws Ccsds124Exception {
    if (data == null) {
      throw new Ccsds124Exception("Input data cannot be null");
    }
    if (packetSize <= 0 || packetSize > MAX_PACKET_BYTES) {
      throw new Ccsds124Exception("Invalid packet size: " + packetSize);
    }
    if (robustness < 0 || robustness > MAX_ROBUSTNESS) {
      throw new Ccsds124Exception("Invalid robustness: " + robustness);
    }

    int packetBits = packetSize * 8;
    Decompressor decompressor = new Decompressor(packetBits, null, robustness);
    BitReader reader = new BitReader(data, data.length * 8);

    java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();

    while (reader.remaining() > 0) {
      try {
        BitVector output = decompressor.decompressPacket(reader);
        byte[] packetBytes = output.toBytes();
        outputStream.write(packetBytes, 0, packetSize);

        // Align to byte boundary for next packet (matches C implementation)
        reader.alignByte();
      } catch (Ccsds124Exception e) {
        // End of stream or incomplete packet
        break;
      }
    }

    return outputStream.toByteArray();
  }
}