blob: 10b16619c1555e1d38696c397dbd4cb81099d4eb [file] [log] [blame]
package eu.mulk.jgvariant.ostree;
import eu.mulk.jgvariant.core.Decoder;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/**
* A static delta.
*
* <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT}
*
* @param metadata arbitrary user-supplied metadata.
* @param timestamp UNIX epoch seconds of when the commit was done.
* @param fromChecksum a (possibly {@link Checksum#isEmpty()}) reference to the starting commit.
* @param toChecksum a (non-{@link Checksum#isEmpty()}) reference to the end commit.
* @param commit the commit metadata of the end commit.
* @param dependencies a list of other {@link DeltaSuperblock}s that need to be applied before this
* one.
* @param entries a list of metadata on the {@link DeltaPartPayload}s that make up the delta.
* @param fallbacks a list of objects included in the delta as plain files that have to be fetched
* separately.
*/
public record DeltaSuperblock(
Metadata metadata,
long timestamp,
Checksum fromChecksum,
Checksum toChecksum,
Commit commit,
List<DeltaName> dependencies,
List<DeltaMetaEntry> entries,
List<DeltaFallback> fallbacks) {
/**
* A specifier for another static delta.
*
* <p>Used to specify {@link DeltaSuperblock#dependencies()}.
*
* @param fromChecksum the {@link DeltaSuperblock#fromChecksum()} of the referenced delta.
* @param toChecksum the {@link DeltaSuperblock#toChecksum()} of the referenced delta.
*/
public record DeltaName(Checksum fromChecksum, Checksum toChecksum) {
private static final Decoder<DeltaName> DECODER =
Decoder.ofStructure(DeltaName.class, Checksum.decoder(), Checksum.decoder());
/**
* Acquires a {@link Decoder} for the enclosing type.
*
* @return a possibly shared {@link Decoder}.
*/
public static Decoder<DeltaName> decoder() {
return DECODER;
}
}
private static final Decoder<DeltaSuperblock> DECODER =
Decoder.ofStructure(
DeltaSuperblock.class,
Metadata.decoder(),
Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN),
Checksum.decoder(),
Checksum.decoder(),
Commit.decoder(),
Decoder.ofByteArray().map(DeltaSuperblock::parseDeltaNameList),
Decoder.ofArray(DeltaMetaEntry.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN),
Decoder.ofArray(DeltaFallback.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN))
.map(
deltaSuperblock -> {
// Fix up the endianness of the 'entries' and 'fallbacks' fields, which have
// unspecified byte order.
var endiannessMetadatum =
deltaSuperblock.metadata().fields().get("ostree.endianness");
if (endiannessMetadatum != null
&& endiannessMetadatum.value() instanceof Byte endiannessByte
&& endiannessByte == (byte) 'B') {
return deltaSuperblock.byteSwapped();
} else {
return deltaSuperblock;
}
});
private DeltaSuperblock byteSwapped() {
return new DeltaSuperblock(
metadata,
timestamp,
fromChecksum,
toChecksum,
commit,
dependencies,
entries.stream().map(DeltaMetaEntry::byteSwapped).toList(),
fallbacks.stream().map(DeltaFallback::byteSwapped).toList());
}
private static List<DeltaName> parseDeltaNameList(byte[] bytes) {
var byteBuffer = ByteBuffer.wrap(bytes);
List<DeltaName> deltaNames = new ArrayList<>();
while (byteBuffer.hasRemaining()) {
var fromChecksum = Checksum.readFrom(byteBuffer);
var toChecksum = Checksum.readFrom(byteBuffer);
deltaNames.add(new DeltaName(fromChecksum, toChecksum));
}
return deltaNames;
}
/**
* Acquires a {@link Decoder} for the enclosing type.
*
* @return a possibly shared {@link Decoder}.
*/
public static Decoder<DeltaSuperblock> decoder() {
return DECODER;
}
}