import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
assertNativeBinaryTarget,
detectNativeBinaryTarget,
} from '../../scripts/native-binary-target.mjs'
const temporaryRoots: string[] = []
afterEach(async () => {
await Promise.all(
temporaryRoots.splice(0).map((root) => rm(root, { recursive: true }))
)
})
function thinMach(arch: 'x64' | 'arm64', littleEndian = true): Buffer {
const header = Buffer.alloc(8)
header.set(littleEndian ? [0xcf, 0xfa, 0xed, 0xfe] : [0xfe, 0xed, 0xfa, 0xcf])
const cpu = arch === 'arm64' ? 0x0100000c : 0x01000007
if (littleEndian) header.writeUInt32LE(cpu, 4)
else header.writeUInt32BE(cpu, 4)
return header
}
function fatMach(arches: Array<'x64' | 'arm64'>, is64 = false): Buffer {
const entrySize = is64 ? 32 : 20
const header = Buffer.alloc(8 + arches.length * entrySize)
header.set(is64 ? [0xca, 0xfe, 0xba, 0xbf] : [0xca, 0xfe, 0xba, 0xbe])
header.writeUInt32BE(arches.length, 4)
arches.forEach((arch, index) => {
header.writeUInt32BE(
arch === 'arm64' ? 0x0100000c : 0x01000007,
8 + index * entrySize
)
})
return header
}
function elf(arch: 'x64' | 'arm64', bigEndian = false): Buffer {
const header = Buffer.alloc(20)
header.set([0x7f, 0x45, 0x4c, 0x46])
header[4] = 2
header[5] = bigEndian ? 2 : 1
const machine = arch === 'arm64' ? 183 : 62
if (bigEndian) header.writeUInt16BE(machine, 18)
else header.writeUInt16LE(machine, 18)
return header
}
function pe(arch: 'x64' | 'arm64'): Buffer {
const header = Buffer.alloc(72)
header.write('MZ')
header.writeUInt32LE(64, 0x3c)
header.set([0x50, 0x45, 0, 0], 64)
header.writeUInt16LE(arch === 'arm64' ? 0xaa64 : 0x8664, 68)
return header
}
describe('native binary target detection', () => {
it.each([
[thinMach('x64'), { format: 'mach-o', arches: ['x64'] }],
[thinMach('arm64', false), { format: 'mach-o', arches: ['arm64'] }],
[fatMach(['x64', 'arm64']), { format: 'mach-o', arches: ['arm64', 'x64'] }],
[fatMach(['arm64'], true), { format: 'mach-o', arches: ['arm64'] }],
[elf('x64'), { format: 'elf', arches: ['x64'] }],
[elf('arm64', true), { format: 'elf', arches: ['arm64'] }],
[pe('x64'), { format: 'pe', arches: ['x64'] }],
[pe('arm64'), { format: 'pe', arches: ['arm64'] }],
])('detects supported headers', (header, expected) => {
expect(detectNativeBinaryTarget(header)).toEqual(expected)
})
it.each([Buffer.alloc(0), Buffer.from('MZ'), fatMach([]), Buffer.alloc(20)])(
'rejects truncated or invalid headers',
(header) => {
expect(detectNativeBinaryTarget(header)).toBeUndefined()
}
)
it('requires an explicit universal Mach-O allowance', async () => {
const root = await mkdtemp(path.join(os.tmpdir(), 'motrix-native-test-'))
temporaryRoots.push(root)
const binary = path.join(root, 'universal.node')
await writeFile(binary, fatMach(['x64', 'arm64']))
await expect(
assertNativeBinaryTarget(binary, 'darwin', 'arm64')
).rejects.toThrow('universal Mach-O is not allowed')
await expect(
assertNativeBinaryTarget(binary, 'darwin', 'arm64', {
allowUniversal: true,
})
).resolves.toEqual({ format: 'mach-o', arches: ['arm64', 'x64'] })
await expect(
assertNativeBinaryTarget(binary, 'linux', 'arm64', {
allowUniversal: true,
})
).rejects.toThrow('expected linux-arm64')
})
})