Skip to content

Commit d715729

Browse files
author
kristofr
committed
updated to remove bignum dep
1 parent 7797e17 commit d715729

2 files changed

Lines changed: 60 additions & 33 deletions

File tree

src/blob/handlers/BlockBlobHandler.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { convertRawHeadersToMetadata } from "../../common/utils/utils";
21
import {
32
computeTransactionalChecksums,
3+
convertRawHeadersToMetadata,
4+
getCRC64FromStream,
45
getMD5FromStream,
56
getMD5FromString,
67
newEtag
@@ -210,18 +211,31 @@ export default class BlockBlobHandler
210211
);
211212
}
212213

213-
// Compute MD5 and CRC64 in a single pass over the stored extent
214-
const stream = await this.extentStore.readExtent(
215-
persistency,
216-
context.contextId
217-
);
218-
const { md5: calculatedContentMD5, crc64: calculatedCRC64 } =
219-
await computeTransactionalChecksums(stream);
214+
// Only read the stored extent when at least one transactional checksum was provided.
215+
// Compute only what is needed to avoid unnecessary CPU work.
216+
let calculatedContentMD5: Uint8Array | undefined;
217+
let calculatedCRC64: Uint8Array | undefined;
218+
219+
if (contentMD5 !== undefined || contentCRC64 !== undefined) {
220+
const stream = await this.extentStore.readExtent(
221+
persistency,
222+
context.contextId
223+
);
224+
if (contentMD5 !== undefined && contentCRC64 !== undefined) {
225+
const result = await computeTransactionalChecksums(stream);
226+
calculatedContentMD5 = result.md5;
227+
calculatedCRC64 = result.crc64;
228+
} else if (contentMD5 !== undefined) {
229+
calculatedContentMD5 = await getMD5FromStream(stream);
230+
} else {
231+
calculatedCRC64 = await getCRC64FromStream(stream);
232+
}
233+
}
220234

221235
if (contentMD5 !== undefined) {
222236
if (typeof contentMD5 === "string") {
223237
const calculatedContentMD5String = Buffer.from(
224-
calculatedContentMD5
238+
calculatedContentMD5!
225239
).toString("base64");
226240
if (contentMD5 !== calculatedContentMD5String) {
227241
throw StorageErrorFactory.getInvalidOperation(
@@ -230,7 +244,7 @@ export default class BlockBlobHandler
230244
);
231245
}
232246
} else {
233-
if (!Buffer.from(contentMD5).equals(calculatedContentMD5)) {
247+
if (!Buffer.from(contentMD5).equals(calculatedContentMD5!)) {
234248
throw StorageErrorFactory.getInvalidOperation(
235249
context.contextId!,
236250
"Provided contentMD5 doesn't match."
@@ -240,7 +254,7 @@ export default class BlockBlobHandler
240254
}
241255

242256
if (contentCRC64 !== undefined) {
243-
if (!Buffer.from(contentCRC64).equals(Buffer.from(calculatedCRC64))) {
257+
if (!Buffer.from(contentCRC64).equals(Buffer.from(calculatedCRC64!))) {
244258
throw StorageErrorFactory.getInvalidOperation(
245259
context.contextId!,
246260
"Provided transactional CRC64 doesn't match."

src/common/utils/utils.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -174,55 +174,68 @@ export async function getMD5FromStream(
174174
// Algorithm and lookup-table approach adapted from the Azure Storage JavaScript SDK (MIT License):
175175
// https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/src/utils/crc64.ts
176176
// Polynomial: 0x42F0E1EBA9EA3693 (ECMA-182 standard, unreflected, init=0, xorout=0)
177-
const CRC64_POLY = 0x42f0e1eba9ea3693n;
177+
// Represented as two 32-bit halves (hi, lo) to avoid BigInt.
178+
const CRC64_POLY_HI = 0x42f0e1eb;
179+
const CRC64_POLY_LO = 0xa9ea3693;
178180

179-
const CRC64_TABLE: readonly bigint[] = (() => {
180-
const table: bigint[] = new Array(256);
181+
// Flat table: entry i occupies [i*2] (hi) and [i*2+1] (lo).
182+
const CRC64_TABLE: readonly number[] = (() => {
183+
const table: number[] = new Array(512);
181184
for (let i = 0; i < 256; i++) {
182-
let crc = BigInt(i) << 56n;
185+
let hi = (i << 24) >>> 0;
186+
let lo = 0;
183187
for (let j = 0; j < 8; j++) {
184-
if ((crc & 0x8000000000000000n) !== 0n) {
185-
crc = ((crc << 1n) ^ CRC64_POLY) & 0xffffffffffffffffn;
188+
if ((hi & 0x80000000) !== 0) {
189+
hi = (((hi << 1) | (lo >>> 31)) ^ CRC64_POLY_HI) >>> 0;
190+
lo = ((lo << 1) ^ CRC64_POLY_LO) >>> 0;
186191
} else {
187-
crc = (crc << 1n) & 0xffffffffffffffffn;
192+
hi = ((hi << 1) | (lo >>> 31)) >>> 0;
193+
lo = (lo << 1) >>> 0;
188194
}
189195
}
190-
table[i] = crc;
196+
table[i * 2] = hi;
197+
table[i * 2 + 1] = lo;
191198
}
192199
return table;
193200
})();
194201

195-
function crc64Accumulate(crc: bigint, chunk: Uint8Array): bigint {
202+
function crc64Accumulate(
203+
crcHi: number, crcLo: number, chunk: Uint8Array
204+
): [number, number] {
196205
for (let i = 0; i < chunk.length; i++) {
197-
const index = Number((crc >> 56n) ^ BigInt(chunk[i])) & 0xff;
198-
crc = ((crc << 8n) ^ CRC64_TABLE[index]) & 0xffffffffffffffffn;
206+
const index = ((crcHi >>> 24) ^ chunk[i]) & 0xff;
207+
const tHi = CRC64_TABLE[index * 2];
208+
const tLo = CRC64_TABLE[index * 2 + 1];
209+
crcHi = (((crcHi << 8) | (crcLo >>> 24)) ^ tHi) >>> 0;
210+
crcLo = ((crcLo << 8) ^ tLo) >>> 0;
199211
}
200-
return crc;
212+
return [crcHi, crcLo];
201213
}
202214

203-
function bigintToUint8Array(n: bigint): Uint8Array {
215+
function crc64ToUint8Array(hi: number, lo: number): Uint8Array {
204216
const buf = Buffer.allocUnsafe(8);
205-
buf.writeUInt32BE(Number(n >> 32n) >>> 0, 0);
206-
buf.writeUInt32BE(Number(n & 0xffffffffn) >>> 0, 4);
217+
buf.writeUInt32BE(hi >>> 0, 0);
218+
buf.writeUInt32BE(lo >>> 0, 4);
207219
return buf;
208220
}
209221

210222
export function getCRC64FromString(text: string): Uint8Array {
211-
return bigintToUint8Array(crc64Accumulate(0n, Buffer.from(text)));
223+
const [hi, lo] = crc64Accumulate(0, 0, Buffer.from(text));
224+
return crc64ToUint8Array(hi, lo);
212225
}
213226

214227
export async function getCRC64FromStream(
215228
stream: NodeJS.ReadableStream
216229
): Promise<Uint8Array> {
217230
return new Promise<Uint8Array>((resolve, reject) => {
218-
let crc = 0n;
231+
let hi = 0, lo = 0;
219232
stream
220233
.on("data", (chunk: Buffer | string) => {
221234
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string);
222-
crc = crc64Accumulate(crc, data);
235+
[hi, lo] = crc64Accumulate(hi, lo, data);
223236
})
224237
.on("end", () => {
225-
resolve(bigintToUint8Array(crc));
238+
resolve(crc64ToUint8Array(hi, lo));
226239
})
227240
.on("error", reject);
228241
});
@@ -237,15 +250,15 @@ export async function computeTransactionalChecksums(
237250
): Promise<{ md5: Uint8Array; crc64: Uint8Array }> {
238251
const hash = createHash("md5");
239252
return new Promise((resolve, reject) => {
240-
let crc = 0n;
253+
let hi = 0, lo = 0;
241254
stream
242255
.on("data", (chunk: Buffer | string) => {
243256
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string);
244257
hash.update(data);
245-
crc = crc64Accumulate(crc, data);
258+
[hi, lo] = crc64Accumulate(hi, lo, data);
246259
})
247260
.on("end", () => {
248-
resolve({ md5: hash.digest(), crc64: bigintToUint8Array(crc) });
261+
resolve({ md5: hash.digest(), crc64: crc64ToUint8Array(hi, lo) });
249262
})
250263
.on("error", reject);
251264
});

0 commit comments

Comments
 (0)