61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
import { test } from '@japa/runner'
|
|
import { createHash } from 'node:crypto'
|
|
import { isPar2, parsePar2 } from '#lib/par2/index'
|
|
|
|
const md5 = (data: Buffer) => createHash('md5').update(data).digest()
|
|
|
|
/** Builds one PAR2 packet with a valid checksum. */
|
|
function packet(setId: Buffer, type: string, body: Buffer) {
|
|
const padded = Buffer.concat([body, Buffer.alloc((4 - (body.length % 4)) % 4)])
|
|
const length = Buffer.alloc(8)
|
|
length.writeBigUInt64LE(BigInt(64 + padded.length))
|
|
const typeBuf = Buffer.alloc(16)
|
|
typeBuf.write(type, 'latin1')
|
|
const hashed = Buffer.concat([setId, typeBuf, padded])
|
|
return Buffer.concat([Buffer.from('PAR2\0PKT', 'latin1'), length, md5(hashed), hashed])
|
|
}
|
|
|
|
function fileDesc(setId: Buffer, name: string, size: number) {
|
|
const len = Buffer.alloc(8)
|
|
len.writeBigUInt64LE(BigInt(size))
|
|
const md5_16k = md5(Buffer.from(`${name}-16k`))
|
|
const fileId = md5(Buffer.concat([md5_16k, len, Buffer.from(name)]))
|
|
const body = Buffer.concat([fileId, md5(Buffer.from(name)), md5_16k, len, Buffer.from(name, 'utf8')])
|
|
return packet(setId, 'PAR 2.0\0FileDesc', body)
|
|
}
|
|
|
|
test.group('par2 / parsePar2', () => {
|
|
const setId = Buffer.alloc(16, 7)
|
|
|
|
test('reads file names and sizes from FileDesc packets', ({ assert }) => {
|
|
const data = Buffer.concat([
|
|
packet(setId, 'PAR 2.0\0Main', Buffer.alloc(12)),
|
|
fileDesc(setId, 'Some.Movie.2024.part01.rar', 104857600),
|
|
fileDesc(setId, 'Some.Movie.2024.part02.rar', 52428800),
|
|
fileDesc(setId, 'Some.Movie.2024.part01.rar', 104857600), // duplicates are common
|
|
])
|
|
assert.isTrue(isPar2(data))
|
|
const set = parsePar2(data)!
|
|
assert.equal(set.recoverySetId, setId.toString('hex'))
|
|
assert.deepEqual(
|
|
set.files.map((f) => [f.name, f.length]),
|
|
[
|
|
['Some.Movie.2024.part01.rar', 104857600],
|
|
['Some.Movie.2024.part02.rar', 52428800],
|
|
]
|
|
)
|
|
})
|
|
|
|
test('skips corrupted packets and resumes at the next one', ({ assert }) => {
|
|
const bad = fileDesc(setId, 'bad.bin', 1)
|
|
bad[100] ^= 0xff
|
|
const data = Buffer.concat([bad, fileDesc(setId, 'good.bin', 12345)])
|
|
assert.deepEqual(parsePar2(data)!.files.map((f) => f.name), ['good.bin'])
|
|
})
|
|
|
|
test('returns null for non-par2 data', ({ assert }) => {
|
|
assert.isFalse(isPar2(Buffer.from('Rar!\x1a\x07\x00', 'latin1')))
|
|
assert.isNull(parsePar2(Buffer.from('hello world')))
|
|
})
|
|
})
|