Skip to content

Commit e95f6d0

Browse files
committed
feat(rum): use xxHash instead of SHA-256 for debug ID generation
SHA-256 was chosen for FIPS compliance, which isn't a requirement here. Debug IDs only need to be deterministic and cheap to compute, so xxhash-wasm replaces the cryptographic hash while keeping the same UUID-v4 output shape.
1 parent a6302ed commit e95f6d0

12 files changed

Lines changed: 90 additions & 10 deletions

File tree

31.4 KB
Binary file not shown.

LICENSES-3rdparty.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,7 @@ wide-align,npm,ISC,Rebecca Turner (http://re-becca.org/)
821821
wrap-ansi,npm,MIT,Sindre Sorhus (sindresorhus.com)
822822
wrappy,npm,ISC,Isaac Z. Schlueter (https://github.com/npm/wrappy)
823823
write-file-atomic,npm,ISC,GitHub Inc. (https://github.com/npm/write-file-atomic)
824+
xxhash-wasm,npm,MIT,Michael Jungo (https://www.npmjs.com/package/xxhash-wasm)
824825
y18n,npm,ISC,Ben Coe (https://github.com/yargs/y18n)
825826
yallist,npm,ISC,Isaac Z. Schlueter (http://blog.izs.me/)
826827
yaml,npm,ISC,Eemeli Aro (https://eemeli.org/yaml/)

packages/plugins/rum/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"dependencies": {
4040
"@datadog/js-instrumentation-wasm": "1.0.8",
4141
"@dd/core": "workspace:*",
42-
"chalk": "2.3.1"
42+
"chalk": "2.3.1",
43+
"xxhash-wasm": "1.1.0"
4344
},
4445
"devDependencies": {
4546
"@datadog/browser-rum": "6.26.0",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
import { initDebugIdHasher, stringToUUID } from '@dd/rum-plugin/debugId';
6+
7+
describe('RUM Plugin - debugId', () => {
8+
describe('stringToUUID', () => {
9+
test('Should throw if the hasher is not initialized.', () => {
10+
expect(() => stringToUUID('some-input')).toThrow(
11+
'[stringToUUID] Hasher not initialized: call `initDebugIdHasher()` first.',
12+
);
13+
});
14+
15+
describe('once initialized', () => {
16+
beforeAll(async () => {
17+
await initDebugIdHasher();
18+
});
19+
20+
test('Should produce a UUID-v4-shaped identifier.', () => {
21+
const uuid = stringToUUID('some-input');
22+
expect(uuid).toMatch(
23+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
24+
);
25+
});
26+
27+
test('Should be deterministic for the same input.', () => {
28+
expect(stringToUUID('some-input')).toBe(stringToUUID('some-input'));
29+
});
30+
31+
test('Should differ for different inputs.', () => {
32+
expect(stringToUUID('some-input')).not.toBe(stringToUUID('other-input'));
33+
});
34+
});
35+
});
36+
});

packages/plugins/rum/src/debugId.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2019-Present Datadog, Inc.
44

5-
import { createHash } from 'crypto';
5+
import xxhash from 'xxhash-wasm';
6+
import type { XXHashAPI } from 'xxhash-wasm';
67

78
const VARIANT_CHARS = ['8', '9', 'a', 'b'] as const;
9+
// Arbitrary fixed seed so the second half is independent from the first (which uses the default seed).
10+
const SECOND_HALF_SEED = BigInt('0x9e3779b97f4a7c15');
811

9-
// SHA-256(input) truncated to 128 bits → deterministic UUID-v4-shaped identifier.
10-
// SHA-256 is used instead of MD5 for FIPS 140-2/3 compliance.
12+
let hasher: XXHashAPI | undefined;
13+
14+
// Must be awaited (e.g. during a build's `buildStart`) before any synchronous `stringToUUID` call.
15+
export const initDebugIdHasher = async (): Promise<void> => {
16+
hasher = hasher ?? (await xxhash());
17+
};
18+
19+
// xxHash64(input) || xxHash64(input, seed) → 128 bits, reshaped into a deterministic UUID-v4-shaped identifier.
20+
// xxHash instead of a cryptographic hash: debug IDs only need to be deterministic and cheap to compute.
1121
export const stringToUUID = (input: string): string => {
12-
const hash = createHash('sha256').update(input).digest('hex').slice(0, 32);
22+
if (!hasher) {
23+
throw new Error('[stringToUUID] Hasher not initialized: call `initDebugIdHasher()` first.');
24+
}
25+
const firstHalf = hasher.h64(input).toString(16).padStart(16, '0');
26+
const secondHalf = hasher.h64(input, SECOND_HALF_SEED).toString(16).padStart(16, '0');
27+
const hash = `${firstHalf}${secondHalf}`;
1328
const withVersion = `${hash.slice(0, 12)}4${hash.slice(13)}`;
1429
const variantIndex = withVersion.charCodeAt(16) % 4;
1530
const withVariant = `${withVersion.slice(0, 16)}${VARIANT_CHARS[variantIndex]}${withVersion.slice(17)}`;

packages/plugins/rum/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import path from 'path';
88

99
import type { RumInitConfiguration, RumPublicApi } from './browserSdkTypes';
1010
import { CONFIG_KEY, PLUGIN_NAME } from './constants';
11+
import { initDebugIdHasher } from './debugId';
1112
import { getSourceCodeContextSnippet } from './getSourceCodeContextSnippet';
1213
import { getPrivacyPlugin } from './privacy';
1314
import { getInjectionValue } from './sdk';
@@ -33,6 +34,14 @@ export const getPlugins: GetPlugins = ({ options, context }) => {
3334
const sourceCodeContext = validatedOptions.sourceCodeContext;
3435

3536
if (sourceCodeContext) {
37+
if (sourceCodeContext.debugId) {
38+
// The hasher backing `stringToUUID` needs to be ready before any chunk is processed.
39+
plugins.push({
40+
name: 'datadog-rum-debug-id-hasher-plugin',
41+
buildStart: initDebugIdHasher,
42+
});
43+
}
44+
3645
context.inject({
3746
type: 'code',
3847
position: InjectPosition.BEFORE,

packages/published/esbuild-plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
"p-queue": "6.6.2",
6565
"pretty-bytes": "5.6.0",
6666
"simple-git": "3.36.0",
67-
"unplugin": "2.3.11"
67+
"unplugin": "2.3.11",
68+
"xxhash-wasm": "1.1.0"
6869
},
6970
"devDependencies": {
7071
"@babel/core": "7.24.5",

packages/published/rollup-plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"p-queue": "6.6.2",
6868
"pretty-bytes": "5.6.0",
6969
"simple-git": "3.36.0",
70-
"unplugin": "2.3.11"
70+
"unplugin": "2.3.11",
71+
"xxhash-wasm": "1.1.0"
7172
},
7273
"devDependencies": {
7374
"@babel/core": "7.24.5",

packages/published/rspack-plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
"p-queue": "6.6.2",
6565
"pretty-bytes": "5.6.0",
6666
"simple-git": "3.36.0",
67-
"unplugin": "2.3.11"
67+
"unplugin": "2.3.11",
68+
"xxhash-wasm": "1.1.0"
6869
},
6970
"devDependencies": {
7071
"@babel/core": "7.24.5",

packages/published/vite-plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
"p-queue": "6.6.2",
6565
"pretty-bytes": "5.6.0",
6666
"simple-git": "3.36.0",
67-
"unplugin": "2.3.11"
67+
"unplugin": "2.3.11",
68+
"xxhash-wasm": "1.1.0"
6869
},
6970
"devDependencies": {
7071
"@babel/core": "7.24.5",

0 commit comments

Comments
 (0)