Skip to content

Commit 6e666f7

Browse files
committed
added fallback sha1 function
1 parent 266b48d commit 6e666f7

File tree

2 files changed

+135
-8
lines changed

2 files changed

+135
-8
lines changed

src/streaming.test.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,19 @@ jest.mock('@grafana/runtime', () => ({
1414

1515
// Mock crypto.subtle for consistent testing
1616
const mockDigest = jest.fn();
17-
Object.defineProperty(global, 'crypto', {
18-
value: {
19-
subtle: {
20-
digest: mockDigest,
17+
18+
function setupDigestMock() {
19+
Object.defineProperty(global, 'crypto', {
20+
value: {
21+
subtle: {
22+
digest: mockDigest,
23+
},
2124
},
22-
},
23-
writable: true,
24-
});
25+
writable: true,
26+
});
27+
}
28+
29+
setupDigestMock();
2530

2631
describe('getLiveStreamKey', () => {
2732
beforeEach(() => {
@@ -191,4 +196,29 @@ describe('getLiveStreamKey', () => {
191196
// Should pad single digit hex values with leading zeros
192197
expect(key).toBe('mqtt-datasource-uid/0102030a0b0c0d0e/1');
193198
});
199+
200+
describe('removing definition of crypto.subtle', () => {
201+
beforeAll(() => {
202+
Object.defineProperty(global, 'crypto', {
203+
value: {
204+
subtle: undefined,
205+
},
206+
writable: true,
207+
});
208+
});
209+
210+
afterAll(() => {
211+
setupDigestMock();
212+
});
213+
214+
it('should work with crypto.subtle undefined', async () => {
215+
const datasourceUid = 'mqtt-datasource-uid';
216+
const topic = 'sensor/temperature';
217+
218+
const key = await getLiveStreamKey(datasourceUid, topic);
219+
220+
// 8885fa14e6baa4b6 are the first 8 bytes of the hash of '{"topic":"sensor/temperature"}'
221+
expect(key).toBe('mqtt-datasource-uid/8885fa14e6baa4b6/1');
222+
});
223+
});
194224
});

src/streaming.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,104 @@ export async function getLiveStreamKey(datasourceUid: string, topic?: string): P
1010

1111
const orgId = config.bootData.user.orgId;
1212
const msgUint8 = new TextEncoder().encode(str); // encode as (utf-8) Uint8Array
13-
const hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8); // hash the message
13+
let hashBuffer;
14+
if (crypto.subtle === undefined) {
15+
// Fall back to our own sha1 if we don't have crypto.subtle (e.g. not on localhost or over https)
16+
hashBuffer = sha1(msgUint8);
17+
}
18+
else {
19+
hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8); // hash the message
20+
}
1421
const hashArray = Array.from(new Uint8Array(hashBuffer.slice(0, 8))); // first 8 bytes
1522
return `${datasourceUid}/${hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')}/${orgId}`;
1623
}
24+
25+
function sha1(message: Uint8Array): ArrayBuffer {
26+
let h0 = 0x67452301;
27+
let h1 = 0xEFCDAB89;
28+
let h2 = 0x98BADCFE;
29+
let h3 = 0x10325476;
30+
let h4 = 0xC3D2E1F0;
31+
32+
const message_length = message.length * 8;
33+
34+
// We need to pad with 0x80, zeroes, and then the message length as a 64-bit integer, to take us up
35+
// to a multiple of 64 bytes.
36+
let buf = new Uint8Array(64 * Math.ceil((message.length + 9) / 64));
37+
buf.set(message);
38+
buf[message.length] = 0x80;
39+
40+
41+
// Bitwise operators truncate to 32 bits, we need to explicitly take the high bits
42+
const message_length_high = Math.floor(message_length / 0x100000000);
43+
buf[buf.length - 8] = (message_length_high & 0xff000000) >>> 24;
44+
buf[buf.length - 7] = (message_length_high & 0x00ff0000) >>> 16;
45+
buf[buf.length - 6] = (message_length_high & 0x0000ff00) >>> 8;
46+
buf[buf.length - 5] = (message_length_high & 0x000000ff);
47+
48+
buf[buf.length - 4] = (message_length & 0xff000000) >>> 24;
49+
buf[buf.length - 3] = (message_length & 0x00ff0000) >>> 16;
50+
buf[buf.length - 2] = (message_length & 0x0000ff00) >>> 8;
51+
buf[buf.length - 1] = (message_length & 0x000000ff);
52+
53+
for (let chunkIdx = 0; chunkIdx < buf.length; chunkIdx += 64) {
54+
let words = []
55+
for (let wordIdx = 0; wordIdx < 80; wordIdx += 1) {
56+
if (wordIdx < 16) {
57+
words[wordIdx] = buf[chunkIdx + (wordIdx * 4)] << 24 |
58+
buf[chunkIdx + (wordIdx * 4) + 1] << 16 |
59+
buf[chunkIdx + (wordIdx * 4) + 2] << 8 |
60+
buf[chunkIdx + (wordIdx * 4) + 3];
61+
} else {
62+
const withoutRotation: number = words[wordIdx - 3] ^ words[wordIdx - 8] ^ words[wordIdx - 14] ^ words[wordIdx - 16];
63+
words[wordIdx] = (withoutRotation << 1) | (withoutRotation >>> 31);
64+
}
65+
}
66+
67+
let a = h0;
68+
let b = h1;
69+
let c = h2;
70+
let d = h3;
71+
let e = h4;
72+
73+
for (let i = 0; i < 80; i += 1) {
74+
let f;
75+
let k;
76+
if (i < 20) {
77+
f = (b & c) | ((~b) & d);
78+
k = 0x5A827999;
79+
} else if (i < 40) {
80+
f = b ^ c ^ d;
81+
k = 0x6ED9EBA1;
82+
} else if (i < 60) {
83+
f = (b & c) | (b & d) | (c & d);
84+
k = 0x8F1BBCDC;
85+
} else {
86+
f = b ^ c ^ d;
87+
k = 0xCA62C1D6;
88+
}
89+
90+
const temp = ((a << 5) | (a >>> 27)) + f + e + k + words[i];
91+
e = d;
92+
d = c;
93+
c = (b << 30) | (b >>> 2);
94+
b = a;
95+
a = temp;
96+
}
97+
98+
h0 += a
99+
h1 += b
100+
h2 += c
101+
h3 += d
102+
h4 += e
103+
}
104+
105+
const retBuffer = new ArrayBuffer(20);
106+
const view = new DataView(retBuffer);
107+
view.setUint32(0, h0, false);
108+
view.setUint32(4, h1, false);
109+
view.setUint32(8, h2, false);
110+
view.setUint32(12, h3, false);
111+
view.setUint32(16, h4, false);
112+
return retBuffer;
113+
}

0 commit comments

Comments
 (0)