distillate 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_serialize = require("../serialize-CHHDM4TQ.cjs");
2
+ const require_serialize = require("../serialize-DPdiySxq.cjs");
3
3
  const require_params = require("../params-J8p3bKq5.cjs");
4
4
  //#region src/blocked/blocked.ts
5
5
  const TYPE = 2;
@@ -77,6 +77,26 @@ var BlockedBloomFilter = class BlockedBloomFilter {
77
77
  get bitsPerKey() {
78
78
  return this.#numBlocks * 256 / this.#n;
79
79
  }
80
+ /** Number of bits currently set across all lanes. */
81
+ get length() {
82
+ let bits = 0;
83
+ for (let w of this.#lanes) while (w) {
84
+ w &= w - 1;
85
+ bits++;
86
+ }
87
+ return bits;
88
+ }
89
+ /**
90
+ * Estimates the current false-positive rate from the actual fill,
91
+ * `(length / totalBits) ** 8`. A split-block query checks exactly 8 lane-bits,
92
+ * so the exponent is 8 rather than a classic probe count `k`. This reflects
93
+ * how full the filter is right now, not the design target.
94
+ *
95
+ * @returns The estimated false-positive rate, `0` for an empty filter.
96
+ */
97
+ rate() {
98
+ return (this.length / (this.#numBlocks * 256)) ** 8;
99
+ }
80
100
  /**
81
101
  * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
82
102
  *
@@ -84,11 +104,14 @@ var BlockedBloomFilter = class BlockedBloomFilter {
84
104
  * @returns The reconstructed filter.
85
105
  */
86
106
  static fromBytes(bytes) {
87
- const { body } = require_serialize.readHeader(bytes);
107
+ const { type, body } = require_serialize.readHeader(bytes);
108
+ if (type !== TYPE) throw new require_serialize.SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
109
+ require_serialize.assertMinBodyLength(body.length, 12, "blocked");
88
110
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
89
111
  const numBlocks = dv.getUint32(0, true);
90
112
  const seed = dv.getUint32(4, true);
91
113
  const n = dv.getUint32(8, true);
114
+ require_serialize.assertBodyLength(body.length, 12 + numBlocks * 32, "blocked");
92
115
  const f = new BlockedBloomFilter({
93
116
  bitsPerKey: 256,
94
117
  capacity: numBlocks,
@@ -112,7 +135,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
112
135
  dv.setUint32(8, this.#n, true);
113
136
  body.set(lanes, 12);
114
137
  return require_serialize.writeHeader({
115
- version: 1,
138
+ version: 2,
116
139
  type: TYPE,
117
140
  flags: 0
118
141
  }, body);
@@ -43,6 +43,17 @@ declare class BlockedBloomFilter {
43
43
  constructor({ bitsPerKey, capacity, seed }: BlockedBloomParams);
44
44
  /** Actual bits allocated per key (`total bits / capacity`). */
45
45
  get bitsPerKey(): number;
46
+ /** Number of bits currently set across all lanes. */
47
+ get length(): number;
48
+ /**
49
+ * Estimates the current false-positive rate from the actual fill,
50
+ * `(length / totalBits) ** 8`. A split-block query checks exactly 8 lane-bits,
51
+ * so the exponent is 8 rather than a classic probe count `k`. This reflects
52
+ * how full the filter is right now, not the design target.
53
+ *
54
+ * @returns The estimated false-positive rate, `0` for an empty filter.
55
+ */
56
+ rate(): number;
46
57
  /**
47
58
  * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
48
59
  *
@@ -43,6 +43,17 @@ declare class BlockedBloomFilter {
43
43
  constructor({ bitsPerKey, capacity, seed }: BlockedBloomParams);
44
44
  /** Actual bits allocated per key (`total bits / capacity`). */
45
45
  get bitsPerKey(): number;
46
+ /** Number of bits currently set across all lanes. */
47
+ get length(): number;
48
+ /**
49
+ * Estimates the current false-positive rate from the actual fill,
50
+ * `(length / totalBits) ** 8`. A split-block query checks exactly 8 lane-bits,
51
+ * so the exponent is 8 rather than a classic probe count `k`. This reflects
52
+ * how full the filter is right now, not the design target.
53
+ *
54
+ * @returns The estimated false-positive rate, `0` for an empty filter.
55
+ */
56
+ rate(): number;
46
57
  /**
47
58
  * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
48
59
  *
@@ -1,4 +1,4 @@
1
- import { i as hash128KeyInto, n as readHeader, o as reduce, r as writeHeader } from "../serialize-5XQ5y_R-.js";
1
+ import { a as writeHeader, c as reduce, i as readHeader, n as assertBodyLength, o as hash128KeyInto, r as assertMinBodyLength, t as SerializationError } from "../serialize-DkkBKDT0.js";
2
2
  import { a as assertUint32, i as assertProbability, n as assertPositiveFinite, r as assertPositiveInt, t as ParamError } from "../params-ChTRNxM9.js";
3
3
  //#region src/blocked/blocked.ts
4
4
  const TYPE = 2;
@@ -76,6 +76,26 @@ var BlockedBloomFilter = class BlockedBloomFilter {
76
76
  get bitsPerKey() {
77
77
  return this.#numBlocks * 256 / this.#n;
78
78
  }
79
+ /** Number of bits currently set across all lanes. */
80
+ get length() {
81
+ let bits = 0;
82
+ for (let w of this.#lanes) while (w) {
83
+ w &= w - 1;
84
+ bits++;
85
+ }
86
+ return bits;
87
+ }
88
+ /**
89
+ * Estimates the current false-positive rate from the actual fill,
90
+ * `(length / totalBits) ** 8`. A split-block query checks exactly 8 lane-bits,
91
+ * so the exponent is 8 rather than a classic probe count `k`. This reflects
92
+ * how full the filter is right now, not the design target.
93
+ *
94
+ * @returns The estimated false-positive rate, `0` for an empty filter.
95
+ */
96
+ rate() {
97
+ return (this.length / (this.#numBlocks * 256)) ** 8;
98
+ }
79
99
  /**
80
100
  * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
81
101
  *
@@ -83,11 +103,14 @@ var BlockedBloomFilter = class BlockedBloomFilter {
83
103
  * @returns The reconstructed filter.
84
104
  */
85
105
  static fromBytes(bytes) {
86
- const { body } = readHeader(bytes);
106
+ const { type, body } = readHeader(bytes);
107
+ if (type !== TYPE) throw new SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
108
+ assertMinBodyLength(body.length, 12, "blocked");
87
109
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
88
110
  const numBlocks = dv.getUint32(0, true);
89
111
  const seed = dv.getUint32(4, true);
90
112
  const n = dv.getUint32(8, true);
113
+ assertBodyLength(body.length, 12 + numBlocks * 32, "blocked");
91
114
  const f = new BlockedBloomFilter({
92
115
  bitsPerKey: 256,
93
116
  capacity: numBlocks,
@@ -111,7 +134,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
111
134
  dv.setUint32(8, this.#n, true);
112
135
  body.set(lanes, 12);
113
136
  return writeHeader({
114
- version: 1,
137
+ version: 2,
115
138
  type: TYPE,
116
139
  flags: 0
117
140
  }, body);
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_serialize = require("../serialize-CHHDM4TQ.cjs");
2
+ const require_serialize = require("../serialize-DPdiySxq.cjs");
3
3
  const require_params = require("../params-J8p3bKq5.cjs");
4
4
  //#region src/core/bitset.ts
5
5
  var BitSetRangeError = class extends RangeError {
@@ -94,17 +94,22 @@ var BloomFilter = class BloomFilter {
94
94
  * @returns The reconstructed filter.
95
95
  */
96
96
  static fromBytes(bytes) {
97
- const { body } = require_serialize.readHeader(bytes);
97
+ const { type, body } = require_serialize.readHeader(bytes);
98
+ if (type !== TYPE) throw new require_serialize.SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
99
+ require_serialize.assertMinBodyLength(body.length, 14, "bloom");
98
100
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
99
101
  const m = dv.getUint32(0, true);
100
- const k = body[4] ?? 0;
101
- const seed = dv.getUint32(5, true);
102
+ const k = dv.getUint16(4, true);
103
+ const seed = dv.getUint32(6, true);
104
+ const n = dv.getUint32(10, true);
105
+ require_serialize.assertBodyLength(body.length, 14 + Math.ceil(m / 8), "bloom");
102
106
  const f = new BloomFilter({
103
107
  m,
104
108
  k,
105
109
  seed
106
110
  });
107
- f.#bits.bytes.set(body.subarray(9));
111
+ f.#n = n;
112
+ f.#bits.bytes.set(body.subarray(14));
108
113
  return f;
109
114
  }
110
115
  /**
@@ -122,25 +127,52 @@ var BloomFilter = class BloomFilter {
122
127
  this.#scratch = new Uint32Array(k);
123
128
  this.#n = Math.round(m * Math.LN2 / k);
124
129
  }
130
+ /** Number of bits in the filter. */
131
+ get m() {
132
+ return this.#m;
133
+ }
134
+ /** Number of hash probes per key. */
135
+ get k() {
136
+ return this.#k;
137
+ }
138
+ /** Hash seed. */
139
+ get seed() {
140
+ return this.#seed;
141
+ }
142
+ /** Number of bits currently set. */
143
+ get length() {
144
+ return this.#bits.count();
145
+ }
125
146
  /** Analytic design bits-per-key `m / n`. */
126
147
  get bitsPerKey() {
127
148
  return this.#m / this.#n;
128
149
  }
129
150
  /**
151
+ * Estimates the current false-positive rate from the actual fill,
152
+ * `(length / m) ** k`. This reflects how full the filter is right now, not
153
+ * the design target; it rises as keys are added.
154
+ *
155
+ * @returns The estimated false-positive rate, `0` for an empty filter.
156
+ */
157
+ rate() {
158
+ return (this.length / this.#m) ** this.#k;
159
+ }
160
+ /**
130
161
  * Serializes the filter to a portable little-endian byte layout.
131
162
  *
132
163
  * @returns The serialized filter, readable by {@link BloomFilter.fromBytes}.
133
164
  */
134
165
  toBytes() {
135
166
  const payload = this.#bits.bytes;
136
- const body = new Uint8Array(9 + payload.length);
167
+ const body = new Uint8Array(14 + payload.length);
137
168
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
138
169
  dv.setUint32(0, this.#m, true);
139
- body[4] = this.#k;
140
- dv.setUint32(5, this.#seed, true);
141
- body.set(payload, 9);
170
+ dv.setUint16(4, this.#k, true);
171
+ dv.setUint32(6, this.#seed, true);
172
+ dv.setUint32(10, this.#n, true);
173
+ body.set(payload, 14);
142
174
  return require_serialize.writeHeader({
143
- version: 1,
175
+ version: 2,
144
176
  type: TYPE,
145
177
  flags: 0
146
178
  }, body);
@@ -49,8 +49,24 @@ declare class BloomFilter {
49
49
  * {@link BloomFilter.create} unless restoring a specific configuration.
50
50
  */
51
51
  constructor({ m, k, seed }: BloomParams);
52
+ /** Number of bits in the filter. */
53
+ get m(): number;
54
+ /** Number of hash probes per key. */
55
+ get k(): number;
56
+ /** Hash seed. */
57
+ get seed(): number;
58
+ /** Number of bits currently set. */
59
+ get length(): number;
52
60
  /** Analytic design bits-per-key `m / n`. */
53
61
  get bitsPerKey(): number;
62
+ /**
63
+ * Estimates the current false-positive rate from the actual fill,
64
+ * `(length / m) ** k`. This reflects how full the filter is right now, not
65
+ * the design target; it rises as keys are added.
66
+ *
67
+ * @returns The estimated false-positive rate, `0` for an empty filter.
68
+ */
69
+ rate(): number;
54
70
  /**
55
71
  * Serializes the filter to a portable little-endian byte layout.
56
72
  *
@@ -49,8 +49,24 @@ declare class BloomFilter {
49
49
  * {@link BloomFilter.create} unless restoring a specific configuration.
50
50
  */
51
51
  constructor({ m, k, seed }: BloomParams);
52
+ /** Number of bits in the filter. */
53
+ get m(): number;
54
+ /** Number of hash probes per key. */
55
+ get k(): number;
56
+ /** Hash seed. */
57
+ get seed(): number;
58
+ /** Number of bits currently set. */
59
+ get length(): number;
52
60
  /** Analytic design bits-per-key `m / n`. */
53
61
  get bitsPerKey(): number;
62
+ /**
63
+ * Estimates the current false-positive rate from the actual fill,
64
+ * `(length / m) ** k`. This reflects how full the filter is right now, not
65
+ * the design target; it rises as keys are added.
66
+ *
67
+ * @returns The estimated false-positive rate, `0` for an empty filter.
68
+ */
69
+ rate(): number;
54
70
  /**
55
71
  * Serializes the filter to a portable little-endian byte layout.
56
72
  *
@@ -1,4 +1,4 @@
1
- import { a as probeInto, n as readHeader, r as writeHeader } from "../serialize-5XQ5y_R-.js";
1
+ import { a as writeHeader, i as readHeader, n as assertBodyLength, r as assertMinBodyLength, s as probeInto, t as SerializationError } from "../serialize-DkkBKDT0.js";
2
2
  import { a as assertUint32, i as assertProbability, r as assertPositiveInt, t as ParamError } from "../params-ChTRNxM9.js";
3
3
  //#region src/core/bitset.ts
4
4
  var BitSetRangeError = class extends RangeError {
@@ -93,17 +93,22 @@ var BloomFilter = class BloomFilter {
93
93
  * @returns The reconstructed filter.
94
94
  */
95
95
  static fromBytes(bytes) {
96
- const { body } = readHeader(bytes);
96
+ const { type, body } = readHeader(bytes);
97
+ if (type !== TYPE) throw new SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
98
+ assertMinBodyLength(body.length, 14, "bloom");
97
99
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
98
100
  const m = dv.getUint32(0, true);
99
- const k = body[4] ?? 0;
100
- const seed = dv.getUint32(5, true);
101
+ const k = dv.getUint16(4, true);
102
+ const seed = dv.getUint32(6, true);
103
+ const n = dv.getUint32(10, true);
104
+ assertBodyLength(body.length, 14 + Math.ceil(m / 8), "bloom");
101
105
  const f = new BloomFilter({
102
106
  m,
103
107
  k,
104
108
  seed
105
109
  });
106
- f.#bits.bytes.set(body.subarray(9));
110
+ f.#n = n;
111
+ f.#bits.bytes.set(body.subarray(14));
107
112
  return f;
108
113
  }
109
114
  /**
@@ -121,25 +126,52 @@ var BloomFilter = class BloomFilter {
121
126
  this.#scratch = new Uint32Array(k);
122
127
  this.#n = Math.round(m * Math.LN2 / k);
123
128
  }
129
+ /** Number of bits in the filter. */
130
+ get m() {
131
+ return this.#m;
132
+ }
133
+ /** Number of hash probes per key. */
134
+ get k() {
135
+ return this.#k;
136
+ }
137
+ /** Hash seed. */
138
+ get seed() {
139
+ return this.#seed;
140
+ }
141
+ /** Number of bits currently set. */
142
+ get length() {
143
+ return this.#bits.count();
144
+ }
124
145
  /** Analytic design bits-per-key `m / n`. */
125
146
  get bitsPerKey() {
126
147
  return this.#m / this.#n;
127
148
  }
128
149
  /**
150
+ * Estimates the current false-positive rate from the actual fill,
151
+ * `(length / m) ** k`. This reflects how full the filter is right now, not
152
+ * the design target; it rises as keys are added.
153
+ *
154
+ * @returns The estimated false-positive rate, `0` for an empty filter.
155
+ */
156
+ rate() {
157
+ return (this.length / this.#m) ** this.#k;
158
+ }
159
+ /**
129
160
  * Serializes the filter to a portable little-endian byte layout.
130
161
  *
131
162
  * @returns The serialized filter, readable by {@link BloomFilter.fromBytes}.
132
163
  */
133
164
  toBytes() {
134
165
  const payload = this.#bits.bytes;
135
- const body = new Uint8Array(9 + payload.length);
166
+ const body = new Uint8Array(14 + payload.length);
136
167
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
137
168
  dv.setUint32(0, this.#m, true);
138
- body[4] = this.#k;
139
- dv.setUint32(5, this.#seed, true);
140
- body.set(payload, 9);
169
+ dv.setUint16(4, this.#k, true);
170
+ dv.setUint32(6, this.#seed, true);
171
+ dv.setUint32(10, this.#n, true);
172
+ body.set(payload, 14);
141
173
  return writeHeader({
142
- version: 1,
174
+ version: 2,
143
175
  type: TYPE,
144
176
  flags: 0
145
177
  }, body);
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_serialize = require("../serialize-CHHDM4TQ.cjs");
2
+ const require_serialize = require("../serialize-DPdiySxq.cjs");
3
3
  //#region src/fuse/fuse.ts
4
4
  const ARITY = 3;
5
5
  const TYPE_FUSE8 = 3;
@@ -211,13 +211,16 @@ function buildState(keys, alloc) {
211
211
  function fuseStateFromBytes(bytes, expectedType) {
212
212
  const { type, body } = require_serialize.readHeader(bytes);
213
213
  if (type !== expectedType) throw new require_serialize.SerializationError(`expected AMQF type ${String(expectedType)}, got ${String(type)}`);
214
+ require_serialize.assertMinBodyLength(body.length, 16, "fuse");
214
215
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
215
216
  const seed = dv.getUint32(0, true);
216
217
  const seg = dv.getUint32(4, true);
217
218
  const segCountLen = dv.getUint32(8, true);
218
219
  const size = dv.getUint32(12, true);
219
- const laneBytes = body.subarray(16);
220
220
  const bpe = expectedType === TYPE_FUSE8 ? 1 : 2;
221
+ const expectedArrayLength = size === 0 ? 0 : segCountLen + 2 * seg;
222
+ require_serialize.assertBodyLength(body.length, 16 + expectedArrayLength * bpe, "fuse");
223
+ const laneBytes = body.subarray(16);
221
224
  const arrayLength = laneBytes.length / bpe;
222
225
  const fp = bpe === 1 ? new Uint8Array(arrayLength) : new Uint16Array(arrayLength);
223
226
  new Uint8Array(fp.buffer).set(laneBytes);
@@ -276,7 +279,7 @@ var BinaryFuse = class {
276
279
  dv.setUint32(12, this.#size, true);
277
280
  body.set(laneBytes, 16);
278
281
  return require_serialize.writeHeader({
279
- version: 1,
282
+ version: 2,
280
283
  type: this.#fp.BYTES_PER_ELEMENT === 1 ? TYPE_FUSE8 : TYPE_FUSE16,
281
284
  flags: 0
282
285
  }, body);
@@ -1,4 +1,4 @@
1
- import { i as hash128KeyInto, n as readHeader, r as writeHeader, t as SerializationError } from "../serialize-5XQ5y_R-.js";
1
+ import { a as writeHeader, i as readHeader, n as assertBodyLength, o as hash128KeyInto, r as assertMinBodyLength, t as SerializationError } from "../serialize-DkkBKDT0.js";
2
2
  //#region src/fuse/fuse.ts
3
3
  const ARITY = 3;
4
4
  const TYPE_FUSE8 = 3;
@@ -210,13 +210,16 @@ function buildState(keys, alloc) {
210
210
  function fuseStateFromBytes(bytes, expectedType) {
211
211
  const { type, body } = readHeader(bytes);
212
212
  if (type !== expectedType) throw new SerializationError(`expected AMQF type ${String(expectedType)}, got ${String(type)}`);
213
+ assertMinBodyLength(body.length, 16, "fuse");
213
214
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
214
215
  const seed = dv.getUint32(0, true);
215
216
  const seg = dv.getUint32(4, true);
216
217
  const segCountLen = dv.getUint32(8, true);
217
218
  const size = dv.getUint32(12, true);
218
- const laneBytes = body.subarray(16);
219
219
  const bpe = expectedType === TYPE_FUSE8 ? 1 : 2;
220
+ const expectedArrayLength = size === 0 ? 0 : segCountLen + 2 * seg;
221
+ assertBodyLength(body.length, 16 + expectedArrayLength * bpe, "fuse");
222
+ const laneBytes = body.subarray(16);
220
223
  const arrayLength = laneBytes.length / bpe;
221
224
  const fp = bpe === 1 ? new Uint8Array(arrayLength) : new Uint16Array(arrayLength);
222
225
  new Uint8Array(fp.buffer).set(laneBytes);
@@ -275,7 +278,7 @@ var BinaryFuse = class {
275
278
  dv.setUint32(12, this.#size, true);
276
279
  body.set(laneBytes, 16);
277
280
  return writeHeader({
278
- version: 1,
281
+ version: 2,
279
282
  type: this.#fp.BYTES_PER_ELEMENT === 1 ? TYPE_FUSE8 : TYPE_FUSE16,
280
283
  flags: 0
281
284
  }, body);
package/dist/index.cjs CHANGED
@@ -2,6 +2,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#endregion
3
3
  //#region src/index.ts
4
4
  /** The installed `distillate` package version. */
5
- const VERSION = "0.2.0";
5
+ const VERSION = "0.4.0";
6
6
  //#endregion
7
7
  exports.VERSION = VERSION;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  //#endregion
2
2
  //#region src/index.ts
3
3
  /** The installed `distillate` package version. */
4
- const VERSION = "0.2.0";
4
+ const VERSION = "0.4.0";
5
5
  //#endregion
6
6
  export { VERSION };
@@ -277,11 +277,25 @@ var UnknownVersionError = class extends SerializationError {
277
277
  var ChecksumError = class extends SerializationError {
278
278
  name = "ChecksumError";
279
279
  };
280
+ /**
281
+ * Asserts a frame body is long enough to hold its fixed params block, so the
282
+ * params can be read without running off the end.
283
+ */
284
+ function assertMinBodyLength(actual, min, context) {
285
+ if (actual < min) throw new TruncatedError(`${context}: body of ${String(actual)} bytes is shorter than the ${String(min)}-byte params block`);
286
+ }
287
+ /**
288
+ * Asserts a frame body is exactly the length its declared params imply, so a
289
+ * hostile or truncated frame is rejected before any backing store is allocated.
290
+ */
291
+ function assertBodyLength(actual, expected, context) {
292
+ if (actual !== expected) throw new TruncatedError(`${context}: body of ${String(actual)} bytes does not match the declared params (expected ${String(expected)})`);
293
+ }
280
294
  function readHeader(frame) {
281
295
  if (frame.length < 12) throw new TruncatedError(`frame of ${String(frame.length)} bytes is shorter than the minimum ${String(12)}`);
282
296
  if (frame[0] !== 65 || frame[1] !== 77 || frame[2] !== 81 || frame[3] !== 70) throw new BadMagicError("frame does not start with the AMQF magic");
283
297
  const version = frame[4] ?? 0;
284
- if (version !== 1) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
298
+ if (version !== 2) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
285
299
  if (new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(frame.length - TRAILER_SIZE, true) !== crc32(frame.subarray(0, frame.length - TRAILER_SIZE))) throw new ChecksumError("frame CRC32 does not match its contents");
286
300
  return {
287
301
  version,
@@ -297,6 +311,18 @@ Object.defineProperty(exports, "SerializationError", {
297
311
  return SerializationError;
298
312
  }
299
313
  });
314
+ Object.defineProperty(exports, "assertBodyLength", {
315
+ enumerable: true,
316
+ get: function() {
317
+ return assertBodyLength;
318
+ }
319
+ });
320
+ Object.defineProperty(exports, "assertMinBodyLength", {
321
+ enumerable: true,
322
+ get: function() {
323
+ return assertMinBodyLength;
324
+ }
325
+ });
300
326
  Object.defineProperty(exports, "hash128KeyInto", {
301
327
  enumerable: true,
302
328
  get: function() {
@@ -277,11 +277,25 @@ var UnknownVersionError = class extends SerializationError {
277
277
  var ChecksumError = class extends SerializationError {
278
278
  name = "ChecksumError";
279
279
  };
280
+ /**
281
+ * Asserts a frame body is long enough to hold its fixed params block, so the
282
+ * params can be read without running off the end.
283
+ */
284
+ function assertMinBodyLength(actual, min, context) {
285
+ if (actual < min) throw new TruncatedError(`${context}: body of ${String(actual)} bytes is shorter than the ${String(min)}-byte params block`);
286
+ }
287
+ /**
288
+ * Asserts a frame body is exactly the length its declared params imply, so a
289
+ * hostile or truncated frame is rejected before any backing store is allocated.
290
+ */
291
+ function assertBodyLength(actual, expected, context) {
292
+ if (actual !== expected) throw new TruncatedError(`${context}: body of ${String(actual)} bytes does not match the declared params (expected ${String(expected)})`);
293
+ }
280
294
  function readHeader(frame) {
281
295
  if (frame.length < 12) throw new TruncatedError(`frame of ${String(frame.length)} bytes is shorter than the minimum ${String(12)}`);
282
296
  if (frame[0] !== 65 || frame[1] !== 77 || frame[2] !== 81 || frame[3] !== 70) throw new BadMagicError("frame does not start with the AMQF magic");
283
297
  const version = frame[4] ?? 0;
284
- if (version !== 1) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
298
+ if (version !== 2) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
285
299
  if (new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(frame.length - TRAILER_SIZE, true) !== crc32(frame.subarray(0, frame.length - TRAILER_SIZE))) throw new ChecksumError("frame CRC32 does not match its contents");
286
300
  return {
287
301
  version,
@@ -291,4 +305,4 @@ function readHeader(frame) {
291
305
  };
292
306
  }
293
307
  //#endregion
294
- export { probeInto as a, hash128KeyInto as i, readHeader as n, reduce as o, writeHeader as r, SerializationError as t };
308
+ export { writeHeader as a, reduce as c, readHeader as i, assertBodyLength as n, hash128KeyInto as o, assertMinBodyLength as r, probeInto as s, SerializationError as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "distillate",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Probabilistic data structures for JavaScript. Space-efficient, approximate-membership filters (Bloom, Blocked Bloom, Binary Fuse) with tunable error and a portable binary format; zero dependencies, universal.",
5
5
  "keywords": [
6
6
  "bloom-filter",