Skip to content

Commit 2eb5ef6

Browse files
committed
crypto: optimize normalizeAlgorithm hot path
Replace O(n) for...in loop with case-insensitive StringPrototypeToUpperCase comparisons per algorithm with O(1) SafeMap lookup. The map is pre-built at module init alongside kSupportedAlgorithms. Hoist the opts object literal used in normalizeAlgorithm to module level to avoid allocating identical { prefix, context } objects on every call. Pre-compute ObjectKeys() for simpleAlgorithmDictionaries entries at module init to avoid allocating a new keys array on every normalizeAlgorithm call. Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent ed05549 commit 2eb5ef6

File tree

1 file changed

+56
-49
lines changed

1 file changed

+56
-49
lines changed

lib/internal/crypto/util.js

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
ObjectKeys,
1717
ObjectPrototypeHasOwnProperty,
1818
PromiseWithResolvers,
19+
SafeMap,
1920
StringPrototypeToUpperCase,
2021
Symbol,
2122
TypedArrayPrototypeGetBuffer,
@@ -453,8 +454,11 @@ const experimentalAlgorithms = [
453454
];
454455

455456
// Transform the algorithm definitions into the operation-keyed structure
457+
// Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
458+
// for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
456459
function createSupportedAlgorithms(algorithmDefs) {
457460
const result = {};
461+
const nameMap = {};
458462

459463
for (const { 0: algorithmName, 1: operations } of ObjectEntries(algorithmDefs)) {
460464
// Skip algorithms that are conditionally not supported
@@ -465,6 +469,8 @@ function createSupportedAlgorithms(algorithmDefs) {
465469

466470
for (const { 0: operation, 1: dict } of ObjectEntries(operations)) {
467471
result[operation] ||= {};
472+
nameMap[operation] ||= new SafeMap();
473+
nameMap[operation].set(StringPrototypeToUpperCase(algorithmName), algorithmName);
468474

469475
// Add experimental warnings for experimental algorithms
470476
if (ArrayPrototypeIncludes(experimentalAlgorithms, algorithmName)) {
@@ -482,12 +488,14 @@ function createSupportedAlgorithms(algorithmDefs) {
482488
}
483489
}
484490

485-
return result;
491+
return { algorithms: result, nameMap };
486492
}
487493

488-
const kSupportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
494+
const { algorithms: kSupportedAlgorithms, nameMap: kAlgorithmNameMap } =
495+
createSupportedAlgorithms(kAlgorithmDefinitions);
489496

490497
const simpleAlgorithmDictionaries = {
498+
__proto__: null,
491499
AesCbcParams: { iv: 'BufferSource' },
492500
AesCtrParams: { counter: 'BufferSource' },
493501
AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
@@ -527,6 +535,12 @@ const simpleAlgorithmDictionaries = {
527535
TurboShakeParams: {},
528536
};
529537

538+
// Pre-compute ObjectKeys() for each dictionary entry at module init
539+
// to avoid allocating a new keys array on every normalizeAlgorithm call.
540+
for (const { 0: name, 1: types } of ObjectEntries(simpleAlgorithmDictionaries)) {
541+
simpleAlgorithmDictionaries[name] = { keys: ObjectKeys(types), types };
542+
}
543+
530544
function validateMaxBufferLength(data, name) {
531545
if (data.byteLength > kMaxBufferLength) {
532546
throw lazyDOMException(
@@ -537,6 +551,12 @@ function validateMaxBufferLength(data, name) {
537551

538552
let webidl;
539553

554+
const kNormalizeAlgorithmOpts = {
555+
__proto__: null,
556+
prefix: 'Failed to normalize algorithm',
557+
context: 'passed algorithm',
558+
};
559+
540560
// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
541561
// adapted for Node.js from Deno's implementation
542562
// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
@@ -549,69 +569,56 @@ function normalizeAlgorithm(algorithm, op) {
549569
// 1.
550570
const registeredAlgorithms = kSupportedAlgorithms[op];
551571
// 2. 3.
552-
const initialAlg = webidl.converters.Algorithm(algorithm, {
553-
prefix: 'Failed to normalize algorithm',
554-
context: 'passed algorithm',
555-
});
572+
const initialAlg = webidl.converters.Algorithm(algorithm,
573+
kNormalizeAlgorithmOpts);
556574
// 4.
557575
let algName = initialAlg.name;
558576

559-
// 5.
560-
let desiredType;
561-
for (const key in registeredAlgorithms) {
562-
if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) {
563-
continue;
564-
}
565-
if (
566-
StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
567-
) {
568-
algName = key;
569-
desiredType = registeredAlgorithms[key];
570-
}
571-
}
572-
if (desiredType === undefined)
577+
// 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
578+
const canonicalName = kAlgorithmNameMap[op]?.get(
579+
StringPrototypeToUpperCase(algName));
580+
if (canonicalName === undefined)
573581
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
574582

583+
algName = canonicalName;
584+
const desiredType = registeredAlgorithms[algName];
585+
575586
// Fast path everything below if the registered dictionary is null
576587
if (desiredType === null)
577588
return { name: algName };
578589

579590
// 6.
580591
const normalizedAlgorithm = webidl.converters[desiredType](
581592
{ __proto__: algorithm, name: algName },
582-
{
583-
prefix: 'Failed to normalize algorithm',
584-
context: 'passed algorithm',
585-
},
593+
kNormalizeAlgorithmOpts,
586594
);
587595
// 7.
588596
normalizedAlgorithm.name = algName;
589597

590-
// 9.
591-
const dict = simpleAlgorithmDictionaries[desiredType];
592-
// 10.
593-
const dictKeys = dict ? ObjectKeys(dict) : [];
594-
for (let i = 0; i < dictKeys.length; i++) {
595-
const member = dictKeys[i];
596-
if (!ObjectPrototypeHasOwnProperty(dict, member))
597-
continue;
598-
const idlType = dict[member];
599-
const idlValue = normalizedAlgorithm[member];
600-
// 3.
601-
if (idlType === 'BufferSource' && idlValue) {
602-
const isView = ArrayBufferIsView(idlValue);
603-
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
604-
new Uint8Array(
605-
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
606-
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
607-
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
608-
),
609-
);
610-
} else if (idlType === 'HashAlgorithmIdentifier') {
611-
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
612-
} else if (idlType === 'AlgorithmIdentifier') {
613-
// This extension point is not used by any supported algorithm (yet?)
614-
throw lazyDOMException('Not implemented.', 'NotSupportedError');
598+
// 9. 10. Pre-computed keys and types from simpleAlgorithmDictionaries.
599+
const dictMeta = simpleAlgorithmDictionaries[desiredType];
600+
if (dictMeta) {
601+
const { keys: dictKeys, types: dictTypes } = dictMeta;
602+
for (let i = 0; i < dictKeys.length; i++) {
603+
const member = dictKeys[i];
604+
const idlType = dictTypes[member];
605+
const idlValue = normalizedAlgorithm[member];
606+
// 3.
607+
if (idlType === 'BufferSource' && idlValue) {
608+
const isView = ArrayBufferIsView(idlValue);
609+
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
610+
new Uint8Array(
611+
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
612+
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
613+
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
614+
),
615+
);
616+
} else if (idlType === 'HashAlgorithmIdentifier') {
617+
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
618+
} else if (idlType === 'AlgorithmIdentifier') {
619+
// This extension point is not used by any supported algorithm (yet?)
620+
throw lazyDOMException('Not implemented.', 'NotSupportedError');
621+
}
615622
}
616623
}
617624

0 commit comments

Comments
 (0)