]>
Commit | Line | Data |
---|---|---|
1 | import { wait } from '@root-helpers/utils' | |
2 | import { Segment } from 'p2p-media-loader-core' | |
3 | import { basename } from 'path' | |
4 | ||
5 | type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } } | |
6 | ||
7 | const maxRetries = 3 | |
8 | ||
9 | function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) { | |
10 | let segmentsJSON = fetchSha256Segments(segmentsSha256Url) | |
11 | const regex = /bytes=(\d+)-(\d+)/ | |
12 | ||
13 | return async function segmentValidator (segment: Segment, _method: string, _peerId: string, retry = 1) { | |
14 | // Wait for hash generation from the server | |
15 | if (isLive) await wait(1000) | |
16 | ||
17 | const filename = basename(segment.url) | |
18 | ||
19 | const segmentValue = (await segmentsJSON)[filename] | |
20 | ||
21 | if (!segmentValue && retry > maxRetries) { | |
22 | throw new Error(`Unknown segment name ${filename} in segment validator`) | |
23 | } | |
24 | ||
25 | if (!segmentValue) { | |
26 | console.log('Refetching sha segments for %s.', filename) | |
27 | ||
28 | await wait(1000) | |
29 | ||
30 | segmentsJSON = fetchSha256Segments(segmentsSha256Url) | |
31 | await segmentValidator(segment, _method, _peerId, retry + 1) | |
32 | ||
33 | return | |
34 | } | |
35 | ||
36 | let hashShouldBe: string | |
37 | let range = '' | |
38 | ||
39 | if (typeof segmentValue === 'string') { | |
40 | hashShouldBe = segmentValue | |
41 | } else { | |
42 | const captured = regex.exec(segment.range) | |
43 | range = captured[1] + '-' + captured[2] | |
44 | ||
45 | hashShouldBe = segmentValue[range] | |
46 | } | |
47 | ||
48 | if (hashShouldBe === undefined) { | |
49 | throw new Error(`Unknown segment name ${filename}/${range} in segment validator`) | |
50 | } | |
51 | ||
52 | const calculatedSha = await sha256Hex(segment.data) | |
53 | if (calculatedSha !== hashShouldBe) { | |
54 | throw new Error( | |
55 | `Hashes does not correspond for segment ${filename}/${range}` + | |
56 | `(expected: ${hashShouldBe} instead of ${calculatedSha})` | |
57 | ) | |
58 | } | |
59 | } | |
60 | } | |
61 | ||
62 | // --------------------------------------------------------------------------- | |
63 | ||
64 | export { | |
65 | segmentValidatorFactory | |
66 | } | |
67 | ||
68 | // --------------------------------------------------------------------------- | |
69 | ||
70 | function fetchSha256Segments (url: string) { | |
71 | return fetch(url) | |
72 | .then(res => res.json() as Promise<SegmentsJSON>) | |
73 | .catch(err => { | |
74 | console.error('Cannot get sha256 segments', err) | |
75 | return {} | |
76 | }) | |
77 | } | |
78 | ||
79 | async function sha256Hex (data?: ArrayBuffer) { | |
80 | if (!data) return undefined | |
81 | ||
82 | if (window.crypto.subtle) { | |
83 | return window.crypto.subtle.digest('SHA-256', data) | |
84 | .then(data => bufferToHex(data)) | |
85 | } | |
86 | ||
87 | // Fallback for non HTTPS context | |
88 | const shaModule = await import('sha.js') | |
89 | return new shaModule.sha256().update(Buffer.from(data)).digest('hex') | |
90 | } | |
91 | ||
92 | // Thanks: https://stackoverflow.com/a/53307879 | |
93 | function bufferToHex (buffer?: ArrayBuffer) { | |
94 | if (!buffer) return '' | |
95 | ||
96 | let s = '' | |
97 | const h = '0123456789abcdef' | |
98 | const o = new Uint8Array(buffer) | |
99 | ||
100 | o.forEach((v: any) => s += h[ v >> 4 ] + h[ v & 15 ]) | |
101 | ||
102 | return s | |
103 | } |