blob: 9513fa0401feeaa708bd614863b5e2d412c3b0fd [file] [log] [blame]
Matthias Andreas Benkardb5d657a2022-02-03 21:14:30 +01001// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +01005package eu.mulk.jgvariant.ostree;
6
7import eu.mulk.jgvariant.core.Decoder;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +01008import eu.mulk.jgvariant.core.Signature;
9import eu.mulk.jgvariant.core.Variant;
10import java.io.ByteArrayOutputStream;
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010011import java.nio.ByteBuffer;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010012import java.nio.ByteOrder;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010013import java.text.ParseException;
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010014import java.util.ArrayList;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010015import java.util.HashMap;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010016import java.util.List;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010017import java.util.Map;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010018
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010019/**
20 * A static delta.
21 *
22 * <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT}
23 *
24 * @param metadata arbitrary user-supplied metadata.
25 * @param timestamp UNIX epoch seconds of when the commit was done.
26 * @param fromChecksum a (possibly {@link Checksum#isEmpty()}) reference to the starting commit.
27 * @param toChecksum a (non-{@link Checksum#isEmpty()}) reference to the end commit.
28 * @param commit the commit metadata of the end commit.
29 * @param dependencies a list of other {@link DeltaSuperblock}s that need to be applied before this
30 * one.
31 * @param entries a list of metadata on the {@link DeltaPartPayload}s that make up the delta.
32 * @param fallbacks a list of objects included in the delta as plain files that have to be fetched
33 * separately.
34 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010035public record DeltaSuperblock(
36 Metadata metadata,
37 long timestamp,
38 Checksum fromChecksum,
39 Checksum toChecksum,
40 Commit commit,
41 List<DeltaName> dependencies,
42 List<DeltaMetaEntry> entries,
43 List<DeltaFallback> fallbacks) {
44
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010045 /**
46 * A specifier for another static delta.
47 *
48 * <p>Used to specify {@link DeltaSuperblock#dependencies()}.
49 *
50 * @param fromChecksum the {@link DeltaSuperblock#fromChecksum()} of the referenced delta.
51 * @param toChecksum the {@link DeltaSuperblock#toChecksum()} of the referenced delta.
52 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010053 public record DeltaName(Checksum fromChecksum, Checksum toChecksum) {
54
55 private static final Decoder<DeltaName> DECODER =
56 Decoder.ofStructure(DeltaName.class, Checksum.decoder(), Checksum.decoder());
57
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010058 /**
59 * Acquires a {@link Decoder} for the enclosing type.
60 *
61 * @return a possibly shared {@link Decoder}.
62 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010063 public static Decoder<DeltaName> decoder() {
64 return DECODER;
65 }
66 }
67
68 private static final Decoder<DeltaSuperblock> DECODER =
69 Decoder.ofStructure(
Matthias Andreas Benkarda8514a32021-12-30 21:01:48 +010070 DeltaSuperblock.class,
71 Metadata.decoder(),
72 Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN),
73 Checksum.decoder(),
74 Checksum.decoder(),
75 Commit.decoder(),
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010076 Decoder.ofByteArray()
77 .map(
78 DeltaSuperblock::parseDeltaNameList, DeltaSuperblock::serializeDeltaNameList),
Matthias Andreas Benkarda8514a32021-12-30 21:01:48 +010079 Decoder.ofArray(DeltaMetaEntry.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN),
80 Decoder.ofArray(DeltaFallback.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN))
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010081 .map(DeltaSuperblock::byteSwappedIfBigEndian, DeltaSuperblock::withSpecifiedByteOrder);
82
83 private DeltaSuperblock withSpecifiedByteOrder() {
84 Map<String, Variant> extendedMetadataMap = new HashMap<>(metadata().fields());
85
86 try {
87 extendedMetadataMap.putIfAbsent(
88 "ostree.endianness", new Variant(Signature.parse("y"), (byte) 'l'));
89 } catch (ParseException e) {
90 // impossible
91 throw new IllegalStateException(e);
92 }
93
94 return new DeltaSuperblock(
95 new Metadata(extendedMetadataMap),
96 timestamp,
97 fromChecksum,
98 toChecksum,
99 commit,
100 dependencies,
101 entries,
102 fallbacks);
103 }
Matthias Andreas Benkard9006e702022-03-01 13:43:50 +0100104
105 private DeltaSuperblock byteSwappedIfBigEndian() {
106 // Fix up the endianness of the 'entries' and 'fallbacks' fields, which have
107 // unspecified byte order.
108 var endiannessMetadatum = metadata().fields().get("ostree.endianness");
109 if (endiannessMetadatum != null
110 && endiannessMetadatum.value() instanceof Byte endiannessByte
111 && endiannessByte == (byte) 'B') {
112 return byteSwapped();
113 } else {
114 return this;
115 }
116 }
Matthias Andreas Benkarda8514a32021-12-30 21:01:48 +0100117
118 private DeltaSuperblock byteSwapped() {
119 return new DeltaSuperblock(
120 metadata,
121 timestamp,
122 fromChecksum,
123 toChecksum,
124 commit,
125 dependencies,
126 entries.stream().map(DeltaMetaEntry::byteSwapped).toList(),
127 fallbacks.stream().map(DeltaFallback::byteSwapped).toList());
128 }
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100129
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +0100130 private static byte[] serializeDeltaNameList(List<DeltaName> deltaNames) {
131 var output = new ByteArrayOutputStream();
132
133 for (var deltaName : deltaNames) {
134 output.writeBytes(deltaName.fromChecksum().byteString().bytes());
135 output.writeBytes(deltaName.toChecksum().byteString().bytes());
136 }
137
138 return output.toByteArray();
139 }
140
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100141 private static List<DeltaName> parseDeltaNameList(byte[] bytes) {
142 var byteBuffer = ByteBuffer.wrap(bytes);
143 List<DeltaName> deltaNames = new ArrayList<>();
144
145 while (byteBuffer.hasRemaining()) {
146 var fromChecksum = Checksum.readFrom(byteBuffer);
147 var toChecksum = Checksum.readFrom(byteBuffer);
148 deltaNames.add(new DeltaName(fromChecksum, toChecksum));
149 }
150
151 return deltaNames;
152 }
153
154 /**
155 * Acquires a {@link Decoder} for the enclosing type.
156 *
157 * @return a possibly shared {@link Decoder}.
158 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100159 public static Decoder<DeltaSuperblock> decoder() {
160 return DECODER;
161 }
162}