blob: 168b4c5c3be3545bbe1cb1e8853d9ee09b67ca74 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { xxh3 } from "@node-rs/xxhash";
import { u128LEBufToBigint } from "../number.utils.js";
/**
* Size of the batch header in bytes.
* Layout: u64 (partitionId) + u64 (baseOffset) + u64 (baseTimestamp) +
* u64 (originTimestamp) + u64 (batchLength) + u64 (batchChecksum) +
* u32 (messageCount) + zero padding up to 256 bytes.
*/
export const BATCH_HEADER_SIZE = 256;
/**
* Size of the per-message frame header in bytes.
* Layout: u64 (checksum) + u128 (id) + u32 (offsetDelta) +
* u32 (timestampDelta) + u32 (userHeadersLength) + u32 (payloadLength) +
* u64 (reserved).
*/
export const FRAME_HEADER_SIZE = 48;
/** Size of the frame checksum field prefixing the frame header. */
const FRAME_CHECKSUM_SIZE = 8;
/**
* Batch header describing a run of message frames.
*/
export type BatchHeader = {
/** Partition the batch belongs to (zero when sent by a client) */
partitionId: bigint;
/** Offset of the first message in the batch (zero when sent by a client) */
baseOffset: bigint;
/** Server timestamp of the batch in microseconds (zero when sent by a client) */
baseTimestamp: bigint;
/** Smallest origin timestamp of the batched messages in microseconds */
originTimestamp: bigint;
/** Total batch size in bytes, header included */
batchLength: bigint;
/** XXH3-64 checksum of the batch header fields and frame checksums */
batchChecksum: bigint;
/** Number of message frames in the batch */
messageCount: number;
};
/**
* Per-message frame header.
*/
export type FrameHeader = {
/** XXH3-64 checksum of the frame past this field, payload and user headers included */
checksum: bigint;
/** Unique message identifier */
id: bigint;
/** Index of the message within the batch */
offsetDelta: number;
/** Message origin timestamp minus batch origin timestamp in microseconds */
timestampDelta: number;
/** Length of user-defined headers in bytes */
userHeadersLength: number;
/** Length of message payload in bytes */
payloadLength: number;
/** Reserved for future use, must be zero */
reserved: bigint;
};
/**
* Serializes a batch header to its 256-byte wire format.
*
* @param header - Batch header to serialize
* @returns Serialized batch header buffer
*/
export const serializeBatchHeader = (header: BatchHeader): Buffer => {
const b = Buffer.alloc(BATCH_HEADER_SIZE);
b.writeBigUInt64LE(header.partitionId, 0);
b.writeBigUInt64LE(header.baseOffset, 8);
b.writeBigUInt64LE(header.baseTimestamp, 16);
b.writeBigUInt64LE(header.originTimestamp, 24);
b.writeBigUInt64LE(header.batchLength, 32);
b.writeBigUInt64LE(header.batchChecksum, 40);
b.writeUInt32LE(header.messageCount, 48);
return b;
};
/**
* Deserializes a batch header from a buffer.
*
* @param b - Buffer containing the serialized batch header
* @param pos - Starting position in the buffer
* @returns Parsed BatchHeader object
*/
export const deserializeBatchHeader = (b: Buffer, pos = 0): BatchHeader => ({
partitionId: b.readBigUInt64LE(pos),
baseOffset: b.readBigUInt64LE(pos + 8),
baseTimestamp: b.readBigUInt64LE(pos + 16),
originTimestamp: b.readBigUInt64LE(pos + 24),
batchLength: b.readBigUInt64LE(pos + 32),
batchChecksum: b.readBigUInt64LE(pos + 40),
messageCount: b.readUInt32LE(pos + 48),
});
/**
* Deserializes a frame header from a buffer.
*
* @param b - Buffer containing the serialized frame header
* @param pos - Starting position in the buffer
* @returns Parsed FrameHeader object
*/
export const deserializeFrameHeader = (b: Buffer, pos = 0): FrameHeader => ({
checksum: b.readBigUInt64LE(pos),
id:
b.readBigUInt64LE(pos + 8) | (b.readBigUInt64LE(pos + 16) << 64n),
offsetDelta: b.readUInt32LE(pos + 24),
timestampDelta: b.readUInt32LE(pos + 28),
userHeadersLength: b.readUInt32LE(pos + 32),
payloadLength: b.readUInt32LE(pos + 36),
reserved: b.readBigUInt64LE(pos + 40),
});
/**
* Computes the XXH3-64 checksum of a complete frame.
* Covers everything past the checksum field: the remaining frame header,
* the payload, and the user headers.
*
* @param frame - Complete frame buffer [header][payload][user headers]
* @returns Frame checksum
*/
export const frameChecksum = (frame: Buffer): bigint =>
xxh3.xxh64(frame.subarray(FRAME_CHECKSUM_SIZE));
/**
* Computes the XXH3-64 checksum of a batch.
* Covers the batch header fields up to the checksum, the message count,
* and each frame checksum in message order.
*
* @param header - Batch header fields, batchChecksum ignored
* @param frameChecksums - Frame checksums in message order
* @returns Batch checksum
*/
export const batchChecksum = (
header: Omit<BatchHeader, "batchChecksum" | "messageCount">,
frameChecksums: bigint[],
): bigint => {
const b = Buffer.allocUnsafe(44 + frameChecksums.length * 8);
b.writeBigUInt64LE(header.partitionId, 0);
b.writeBigUInt64LE(header.baseOffset, 8);
b.writeBigUInt64LE(header.baseTimestamp, 16);
b.writeBigUInt64LE(header.originTimestamp, 24);
b.writeBigUInt64LE(header.batchLength, 32);
b.writeUInt32LE(frameChecksums.length, 40);
frameChecksums.forEach((checksum, index) => {
b.writeBigUInt64LE(checksum, 44 + index * 8);
});
return xxh3.xxh64(b);
};
/**
* Iggy message header containing metadata for each polled message.
*/
export type IggyMessageHeader = {
/** Message checksum for integrity verification */
checksum: bigint;
/** Unique message identifier (UUID or numeric) */
id: string | bigint;
/** Message offset within the partition */
offset: bigint;
/** Server-assigned timestamp */
timestamp: Date;
/** Client-provided origin timestamp */
originTimestamp: Date;
/** Length of user-defined headers in bytes */
userHeadersLength: number;
/** Length of message payload in bytes */
payloadLength: number;
/** Reserved for future use */
reserved: bigint;
};
/**
* Deserializes a message ID from a 16-byte buffer to BigInt.
*
* @param b - 16-byte buffer containing the message ID
* @returns Message ID as BigInt
*/
export const deserialiseMessageId = (b: Buffer) => u128LEBufToBigint(b);