From a038a8b298e3580da73f4c8ee9d0c8e33f77a607 Mon Sep 17 00:00:00 2001
From: tison
* Long || Start Byte Adr: * Adr: * || 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | - * 0 || Preamble_Longs | SerVer | FamID | Flags | BitsPFP|NumHash |-----Unused------| + * 0 || Preamble_Longs | SerVer | FamID |BitsPFP |--------Segment Length (int)------| * * || 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * 1 ||-------------------------------Construction Seed-------------------------------| - * - * || 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | - * 2 ||----------Segment Length (int)-------------|-----------Num Keys (int)----------| ** - * The raw fingerprint array starts at byte 24. + * The raw fingerprint array starts at byte 16. The layout always has three segments. */ /** @@ -654,13 +617,9 @@ private void writeToSegment(final MemorySegment seg) { posSeg.setByte((byte) PREAMBLE_LONGS); posSeg.setByte((byte) SER_VER); posSeg.setByte((byte) Family.XORFILTER.getID()); - posSeg.setByte((byte) 0); // flags posSeg.setByte((byte) bitsPerFingerprint_); - posSeg.setByte((byte) NUM_HASHES); - posSeg.setShort((short) 0); // unused - posSeg.setLong(seed_); posSeg.setInt(segmentLength_); - posSeg.setInt(numKeys_); + posSeg.setLong(seed_); MemorySegment.copy(fpSeg_, JAVA_BYTE, 0, seg, JAVA_BYTE, PREAMBLE_SIZE_BYTES, fpSeg_.byteSize()); } @@ -676,12 +635,10 @@ public String toString() { sb.append(LS); final String thisSimpleName = this.getClass().getSimpleName(); sb.append("### ").append(thisSimpleName).append(" SUMMARY: ").append(LS); - sb.append(" numItems : ").append(numKeys_).append(LS); sb.append(" numHashes : ").append(NUM_HASHES).append(LS); sb.append(" bitsPerFP : ").append(bitsPerFingerprint_).append(LS); sb.append(" capacity : ").append(getCapacity()).append(LS); sb.append(" seed : ").append(seed_).append(LS); - sb.append(" bits/item : ").append(getBitsPerItem()).append(LS); sb.append("### END SKETCH SUMMARY").append(LS); return sb.toString(); diff --git a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterBenchmark.java b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterBenchmark.java index af6ae0122..1846d744c 100644 --- a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterBenchmark.java +++ b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterBenchmark.java @@ -90,7 +90,7 @@ private static Result measure(final int numKeys, final int bits) { final Result r = new Result(); r.buildNanosPerKey = (double) buildElapsed / numKeys; r.queryNanosPerOp = (double) queryElapsed / numKeys; - r.bitsPerItem = filter.getBitsPerItem(); + r.bitsPerItem = (double) filter.getCapacity() * bits / numKeys; r.observedFpp = (double) falsePositives / numKeys; return r; } diff --git a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java index 453393eb8..d89adf86f 100644 --- a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java +++ b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java @@ -26,6 +26,7 @@ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.util.Arrays; import java.util.HexFormat; import org.apache.datasketches.common.SketchesArgumentException; @@ -137,6 +138,7 @@ public void duplicateItemsTest() { for (int i = 0; i < 1000; ++i) { builder.update(i % 100); // only 100 distinct values, each added 10 times } + assertEquals(builder.getNumItems(), 1000L); final XorFilter filter = builder.build(); for (int i = 0; i < 100; ++i) { assertTrue(filter.query(i)); @@ -148,8 +150,6 @@ public void duplicateItemsTest() { @Test public void emptyFilterTest() { final XorFilter filter = new XorFilterBuilder(8, 7L).build(); - assertTrue(filter.isEmpty()); - assertEquals(filter.getNumItems(), 0); long positives = 0; for (long i = 0; i < 100_000; ++i) { @@ -187,7 +187,6 @@ public void heapifyRoundTripTest() { final XorFilter rebuilt = XorFilter.heapify(MemorySegment.ofArray(bytes)); assertEquals(rebuilt.getBitsPerFingerprint(), filter.getBitsPerFingerprint()); - assertEquals(rebuilt.getNumItems(), filter.getNumItems()); assertEquals(rebuilt.getSeed(), filter.getSeed()); for (long i = 0; i < numItems; ++i) { assertTrue(rebuilt.query(i)); @@ -378,43 +377,25 @@ public void smallSetsTest() { builder.update(i); } final XorFilter filter = builder.build(); - assertEquals(filter.getNumItems(), n); for (long i = 0; i < n; ++i) { assertTrue(filter.query(i)); } } } - // the accessors report the expected metadata + // the serialized footprint stays near ten bits per input for an 8-bit filter @Test - public void gettersTest() { + public void spaceUsageTest() { final int numItems = 10_000; final XorFilterBuilder builder = new XorFilterBuilder(8, 2L); for (long i = 0; i < numItems; ++i) { builder.update(i); } final XorFilter filter = builder.build(); - assertEquals(filter.getNumHashes(), 3); - assertEquals(filter.getBitsPerFingerprint(), 8); - assertEquals(filter.getNumItems(), numItems); - final long capacity = filter.getCapacity(); - assertEquals(capacity, 3L * (capacity / 3)); // a multiple of the segment count - assertTrue(capacity >= numItems); // roughly 1.23 times the item count - final double bitsPerItem = filter.getBitsPerItem(); + final double bitsPerItem = (double) filter.getSerializedSizeBytes() * Byte.SIZE / numItems; assertTrue((bitsPerItem > 9.5) && (bitsPerItem < 10.5), "unexpected bits/item: " + bitsPerItem); } - // the builder counts every item added while the filter counts only the distinct ones - @Test - public void builderNumItemsTest() { - final XorFilterBuilder builder = new XorFilterBuilder(8, 4L); - for (int i = 0; i < 1000; ++i) { - builder.update(i % 100); - } - assertEquals(builder.getNumItems(), 1000L); - assertEquals(builder.build().getNumItems(), 100); - } - // 16-bit construction must also be deterministic for a fixed seed and item set @Test public void determinism16Test() { @@ -452,20 +433,24 @@ public void heapifyCorruptionBranchesTest() { } final byte[] valid = builder.build().toByteArray(); - assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 1, (byte) 99))); // serVer + assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 0, (byte) 3))); // preamble + assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 1, (byte) 1))); // old serVer assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 2, (byte) 7))); // familyID - assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 4, (byte) 5))); // bitsPerFP - assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 5, (byte) 4))); // numHashes - assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(MemorySegment.ofArray(new byte[16]))); + assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 3, (byte) 5))); // bitsPerFP + assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 7, (byte) 0x80))); // negative length + for (int length = 0; length < valid.length; ++length) { + final MemorySegment truncated = MemorySegment.ofArray(Arrays.copyOf(valid, length)); + assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(truncated)); + } } - // Images built by datasketches-rust for the same items and base seed. Both libraries peel through - // one queue per segment, so their images must be identical. + // Version 2 headers with fingerprint payloads generated by datasketches-rust for the same items + // and base seed. Both libraries peel through one queue per segment. private static final String REFERENCE_XOR8_IMAGE = - "0301160008030000956eeb2f2632d7bd0c00000004000000" + "020216080c000000956eeb2f2632d7bd" + "000000000000000400dd0000000000000000ce0000c50000000000000000000000000000"; private static final String REFERENCE_XOR16_IMAGE = - "0301160010030000956eeb2f2632d7bd0c00000004000000" + "020216100c000000956eeb2f2632d7bd" + "000000000000000000000000000004e80000dd6e00000000000000000000000000000000" + "ce9500000000c5b600000000000000000000000000000000000000000000000000000000"; @@ -473,8 +458,17 @@ public void heapifyCorruptionBranchesTest() { @Test public void referenceImplementationImageTest() { final long[] items = { 1, 2, 3, Long.MIN_VALUE }; - assertEquals(XorFilterBuilder.create(items, 8, 42L).toByteArray(), HexFormat.of().parseHex(REFERENCE_XOR8_IMAGE)); - assertEquals(XorFilterBuilder.create(items, 16, 42L).toByteArray(), HexFormat.of().parseHex(REFERENCE_XOR16_IMAGE)); + for (final int bits : new int[] { 8, 16 }) { + final byte[] image = HexFormat.of().parseHex(bits == 8 ? REFERENCE_XOR8_IMAGE : REFERENCE_XOR16_IMAGE); + assertEquals(XorFilterBuilder.create(items, bits, 42L).toByteArray(), image); + final MemorySegment seg = MemorySegment.ofArray(image); + for (final XorFilter restored : new XorFilter[] { XorFilter.heapify(seg), XorFilter.wrap(seg) }) { + assertEquals(restored.toByteArray(), image); + for (final long item : items) { + assertTrue(restored.query(item)); + } + } + } } // returns a copy of the image with a single byte replaced From c179c5bc00d5aadaed8c68ce11d14d812abe08d6 Mon Sep 17 00:00:00 2001 From: tison
* Long || Start Byte Adr:
diff --git a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java
index d89adf86f..4fe5f11ad 100644
--- a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java
+++ b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java
@@ -434,7 +434,7 @@ public void heapifyCorruptionBranchesTest() {
final byte[] valid = builder.build().toByteArray();
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 0, (byte) 3))); // preamble
- assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 1, (byte) 1))); // old serVer
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 1, (byte) 99))); // serVer
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 2, (byte) 7))); // familyID
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 3, (byte) 5))); // bitsPerFP
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 7, (byte) 0x80))); // negative length
@@ -444,13 +444,13 @@ public void heapifyCorruptionBranchesTest() {
}
}
- // Version 2 headers with fingerprint payloads generated by datasketches-rust for the same items
- // and base seed. Both libraries peel through one queue per segment.
+ // Fingerprint payloads generated by datasketches-rust for the same items and base seed.
+ // Both libraries peel through one queue per segment.
private static final String REFERENCE_XOR8_IMAGE =
- "020216080c000000956eeb2f2632d7bd"
+ "020116080c000000956eeb2f2632d7bd"
+ "000000000000000400dd0000000000000000ce0000c50000000000000000000000000000";
private static final String REFERENCE_XOR16_IMAGE =
- "020216100c000000956eeb2f2632d7bd"
+ "020116100c000000956eeb2f2632d7bd"
+ "000000000000000000000000000004e80000dd6e00000000000000000000000000000000"
+ "ce9500000000c5b600000000000000000000000000000000000000000000000000000000";
From 1ed3eab435a4767d672ce5797eb53f66b14d78bb Mon Sep 17 00:00:00 2001
From: tison
Date: Sun, 8 Nov 2026 11:50:56 +0800
Subject: [PATCH 3/4] fix(xor): reserve a flags byte in the serialized header
---
.../apache/datasketches/common/Family.java | 2 +-
.../filters/xorfilter/XorFilter.java | 26 ++++++++++++++-----
.../filters/xorfilter/XorFilterTest.java | 22 +++++++++++-----
3 files changed, 35 insertions(+), 15 deletions(-)
diff --git a/src/main/java/org/apache/datasketches/common/Family.java b/src/main/java/org/apache/datasketches/common/Family.java
index b8d90d586..fef730d2a 100644
--- a/src/main/java/org/apache/datasketches/common/Family.java
+++ b/src/main/java/org/apache/datasketches/common/Family.java
@@ -167,7 +167,7 @@ public enum Family {
/**
* Xor Filter
*/
- XORFILTER(22, "XORFILTER", 2, 2);
+ XORFILTER(22, "XORFILTER", 3, 3);
private static final Map lookupID = new HashMap<>();
private static final Map lookupFamName = new HashMap<>();
diff --git a/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java b/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java
index e00d9e71d..06344ef63 100644
--- a/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java
+++ b/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java
@@ -61,7 +61,7 @@
*/
public final class XorFilter implements MemorySegmentStatus {
private static final int SER_VER = 1;
- private static final int PREAMBLE_LONGS = 2;
+ private static final int PREAMBLE_LONGS = 3;
private static final long PREAMBLE_SIZE_BYTES = (long) PREAMBLE_LONGS * Long.BYTES;
// number of hash functions / segments used by the three-partite construction
@@ -178,16 +178,21 @@ private static XorFilter internalHeapifyOrWrap(final MemorySegment seg, final bo
final int familyID = posSeg.getByte();
checkArgument((preLongs < Family.XORFILTER.getMinPreLongs()) || (preLongs > Family.XORFILTER.getMaxPreLongs()),
- "Possible corruption: Incorrect number of preamble bytes specified in header");
+ "Possible corruption: Incorrect number of preamble longs specified in header");
checkArgument(serVer != SER_VER, "Possible corruption: Unrecognized serialization version: " + serVer);
checkArgument(familyID != Family.XORFILTER.getID(), "Possible corruption: Incorrect FamilyID for xor filter. Found: " + familyID);
+ final int flags = posSeg.getByte();
+ checkArgument(flags != 0, "Possible corruption: Unsupported xor filter flags: " + flags);
final int bitsPerFingerprint = posSeg.getByte();
checkArgument((bitsPerFingerprint != 8) && (bitsPerFingerprint != 16),
"Possible corruption: Fingerprint width must be 8 or 16. Found: " + bitsPerFingerprint);
+ posSeg.getByte(); // reserved
+ posSeg.getShort(); // reserved
+ final long seed = posSeg.getLong();
final int segmentLength = posSeg.getInt();
checkArgument(segmentLength < 1, "Possible corruption: Segment length must be strictly positive. Found: " + segmentLength);
- final long seed = posSeg.getLong();
+ posSeg.getInt(); // reserved
final long fingerprintBytes = (long) NUM_HASHES * segmentLength * (bitsPerFingerprint >>> 3);
checkArgument(seg.byteSize() < (PREAMBLE_SIZE_BYTES + fingerprintBytes),
@@ -564,19 +569,22 @@ public long getSerializedSizeBytes() {
}
/*
- * An Xor Filter's serialized image uses 2 longs of preamble:
+ * An Xor Filter's serialized image uses 3 longs of preamble:
*
*
* Long || Start Byte Adr:
* Adr:
* || 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- * 0 || Preamble_Longs | SerVer | FamID |BitsPFP |--------Segment Length (int)------|
+ * 0 || Preamble_Longs | SerVer | FamID | Flags | BitsPFP|---------Reserved--------|
*
* || 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
* 1 ||-------------------------------Construction Seed-------------------------------|
+ *
+ * || 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
+ * 2 ||----------Segment Length (int)-------------|-------------Reserved-------------|
*
*
- * The raw fingerprint array starts at byte 16. The layout always has three segments.
+ * The raw fingerprint array starts at byte 24. The layout always has three segments.
*/
/**
@@ -617,9 +625,13 @@ private void writeToSegment(final MemorySegment seg) {
posSeg.setByte((byte) PREAMBLE_LONGS);
posSeg.setByte((byte) SER_VER);
posSeg.setByte((byte) Family.XORFILTER.getID());
+ posSeg.setByte((byte) 0); // flags
posSeg.setByte((byte) bitsPerFingerprint_);
- posSeg.setInt(segmentLength_);
+ posSeg.setByte((byte) 0); // reserved
+ posSeg.setShort((short) 0); // reserved
posSeg.setLong(seed_);
+ posSeg.setInt(segmentLength_);
+ posSeg.setInt(0); // reserved
MemorySegment.copy(fpSeg_, JAVA_BYTE, 0, seg, JAVA_BYTE, PREAMBLE_SIZE_BYTES, fpSeg_.byteSize());
}
diff --git a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java
index 4fe5f11ad..7e690ef20 100644
--- a/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java
+++ b/src/test/java/org/apache/datasketches/filters/xorfilter/XorFilterTest.java
@@ -433,24 +433,27 @@ public void heapifyCorruptionBranchesTest() {
}
final byte[] valid = builder.build().toByteArray();
- assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 0, (byte) 3))); // preamble
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 0, (byte) 2))); // preamble
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 1, (byte) 99))); // serVer
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 2, (byte) 7))); // familyID
- assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 3, (byte) 5))); // bitsPerFP
- assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 7, (byte) 0x80))); // negative length
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 4, (byte) 5))); // bitsPerFP
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 19, (byte) 0x80))); // negative length
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(corrupt(valid, 3, (byte) 1))); // flags
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.wrap(corrupt(valid, 3, (byte) 1)));
for (int length = 0; length < valid.length; ++length) {
final MemorySegment truncated = MemorySegment.ofArray(Arrays.copyOf(valid, length));
assertThrows(SketchesArgumentException.class, () -> XorFilter.heapify(truncated));
+ assertThrows(SketchesArgumentException.class, () -> XorFilter.wrap(truncated));
}
}
// Fingerprint payloads generated by datasketches-rust for the same items and base seed.
// Both libraries peel through one queue per segment.
private static final String REFERENCE_XOR8_IMAGE =
- "020116080c000000956eeb2f2632d7bd"
+ "0301160008000000956eeb2f2632d7bd0c00000000000000"
+ "000000000000000400dd0000000000000000ce0000c50000000000000000000000000000";
private static final String REFERENCE_XOR16_IMAGE =
- "020116100c000000956eeb2f2632d7bd"
+ "0301160010000000956eeb2f2632d7bd0c00000000000000"
+ "000000000000000000000000000004e80000dd6e00000000000000000000000000000000"
+ "ce9500000000c5b600000000000000000000000000000000000000000000000000000000";
@@ -460,9 +463,14 @@ public void referenceImplementationImageTest() {
final long[] items = { 1, 2, 3, Long.MIN_VALUE };
for (final int bits : new int[] { 8, 16 }) {
final byte[] image = HexFormat.of().parseHex(bits == 8 ? REFERENCE_XOR8_IMAGE : REFERENCE_XOR16_IMAGE);
- assertEquals(XorFilterBuilder.create(items, bits, 42L).toByteArray(), image);
+ final XorFilter built = XorFilterBuilder.create(items, bits, 42L);
+ assertEquals(built.toByteArray(), image);
final MemorySegment seg = MemorySegment.ofArray(image);
- for (final XorFilter restored : new XorFilter[] { XorFilter.heapify(seg), XorFilter.wrap(seg) }) {
+ for (final XorFilter restored : new XorFilter[] { built, XorFilter.heapify(seg), XorFilter.wrap(seg) }) {
+ assertEquals(restored.getNumHashes(), 3);
+ assertEquals(restored.getBitsPerFingerprint(), bits);
+ assertEquals(restored.getCapacity() % 3, 0L);
+ assertTrue(restored.getCapacity() >= items.length);
assertEquals(restored.toByteArray(), image);
for (final long item : items) {
assertTrue(restored.query(item));
From c2632d47fb2e3bfd4ff587e3f7216f415fc4b984 Mon Sep 17 00:00:00 2001
From: tison
Date: Sun, 8 Nov 2026 12:46:23 +0800
Subject: [PATCH 4/4] docs(xor): explain serialized header padding
---
.../filters/xorfilter/XorFilter.java | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java b/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java
index 06344ef63..be4291550 100644
--- a/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java
+++ b/src/main/java/org/apache/datasketches/filters/xorfilter/XorFilter.java
@@ -187,12 +187,13 @@ private static XorFilter internalHeapifyOrWrap(final MemorySegment seg, final bo
final int bitsPerFingerprint = posSeg.getByte();
checkArgument((bitsPerFingerprint != 8) && (bitsPerFingerprint != 16),
"Possible corruption: Fingerprint width must be 8 or 16. Found: " + bitsPerFingerprint);
- posSeg.getByte(); // reserved
- posSeg.getShort(); // reserved
+ // Skip padding to the seed at byte 8.
+ posSeg.getByte();
+ posSeg.getShort();
final long seed = posSeg.getLong();
final int segmentLength = posSeg.getInt();
checkArgument(segmentLength < 1, "Possible corruption: Segment length must be strictly positive. Found: " + segmentLength);
- posSeg.getInt(); // reserved
+ posSeg.getInt(); // Skip padding to the end of the three-long preamble.
final long fingerprintBytes = (long) NUM_HASHES * segmentLength * (bitsPerFingerprint >>> 3);
checkArgument(seg.byteSize() < (PREAMBLE_SIZE_BYTES + fingerprintBytes),
@@ -575,13 +576,13 @@ public long getSerializedSizeBytes() {
* Long || Start Byte Adr:
* Adr:
* || 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- * 0 || Preamble_Longs | SerVer | FamID | Flags | BitsPFP|---------Reserved--------|
+ * 0 || Preamble_Longs | SerVer | FamID | Flags | BitsPFP|---------Padding---------|
*
* || 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
* 1 ||-------------------------------Construction Seed-------------------------------|
*
* || 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
- * 2 ||----------Segment Length (int)-------------|-------------Reserved-------------|
+ * 2 ||----------Segment Length (int)-------------|-------------Padding--------------|
*
*
* The raw fingerprint array starts at byte 24. The layout always has three segments.
@@ -627,11 +628,12 @@ private void writeToSegment(final MemorySegment seg) {
posSeg.setByte((byte) Family.XORFILTER.getID());
posSeg.setByte((byte) 0); // flags
posSeg.setByte((byte) bitsPerFingerprint_);
- posSeg.setByte((byte) 0); // reserved
- posSeg.setShort((short) 0); // reserved
+ // Pad to the seed at byte 8.
+ posSeg.setByte((byte) 0);
+ posSeg.setShort((short) 0);
posSeg.setLong(seed_);
posSeg.setInt(segmentLength_);
- posSeg.setInt(0); // reserved
+ posSeg.setInt(0); // Pad the preamble to three 64-bit words.
MemorySegment.copy(fpSeg_, JAVA_BYTE, 0, seg, JAVA_BYTE, PREAMBLE_SIZE_BYTES, fpSeg_.byteSize());
}