mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
MAC addresses are canonicalized to the uppercase colon form a real STB
sends and validated at the input boundary, with a hint when they fall
outside Infomir's OUI — which the stock server's default filter refuses
with a bare {status: 1} no user could diagnose. Normalization applies
only to a value the user actually edits: rewriting stored bytes would
move the session fingerprint for every existing playlist with no user
action, and the MAC is the account key.
Device IDs can optionally be derived from the MAC the way StbEmu and
stalker-to-m3u do — SHA256(MAC) and SHA256(MAC + "stalker"), which a
real box never reports as equal. The portal pins the first non-empty
device_id/device_id2 it sees to the MAC permanently, refuses a different
one, and treats a later empty value as an unrecoverable lockout, so
derived values are written into the visible fields and persisted as
literal strings, never recomputed at request time. The option is offered
at import only; the edit dialog warns instead once an ID has actually
reached the portal.
get_profile now reports one coherent MAG250 (ver, stb_type — previously
empty —, hw_version, image_version, client_type), and a device conflict
gets its own StalkerPortalError kind so the UI can explain it instead of
relaying the portal's "Your STB is damaged".
Closes the identity-fields cluster: #927, #860.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
import { deriveStalkerDeviceIdsFromMac } from './stalker-identity.utils';
|
|
|
|
describe('deriveStalkerDeviceIdsFromMac', () => {
|
|
// Uppercase hex SHA-256 of the canonical MAC, and of that MAC plus the
|
|
// `stalker` salt — the values StbEmu and stalker-to-m3u pin server-side.
|
|
// Asserted literally: matching those clients byte for byte is the entire
|
|
// point of the option, so recomputing them with the implementation's own
|
|
// algorithm would assert nothing.
|
|
const EXPECTED = {
|
|
deviceId1:
|
|
'A446559A63A6A489959198534E649760C6A9A6474DEE7C20314C2F1903B36422',
|
|
deviceId2:
|
|
'BBD059367A90B0166654E6D4F9E09786CE64EB97674D1AF8D1EE2AE335D7205B',
|
|
};
|
|
|
|
it('derives the reference SHA-256 pair', async () => {
|
|
await expect(
|
|
deriveStalkerDeviceIdsFromMac('00:1A:79:AB:CD:EF')
|
|
).resolves.toEqual(EXPECTED);
|
|
});
|
|
|
|
it('never produces an identical pair', async () => {
|
|
// A real box reports device_id and device_id2 from two separate
|
|
// firmware calls and they are never equal, so an identical pair is a
|
|
// fingerprint no STB produces — and the portal pins the first value it
|
|
// sees permanently, so it cannot be corrected afterwards.
|
|
const derived = await deriveStalkerDeviceIdsFromMac(
|
|
'00:1A:79:00:00:01'
|
|
);
|
|
|
|
expect(derived?.deviceId1).not.toBe(derived?.deviceId2);
|
|
});
|
|
|
|
it('hashes the canonical form, so input formatting cannot change the ids', async () => {
|
|
// A user who types the MAC with hyphens must not end up bound to
|
|
// different device ids than one who types colons — the portal pins the
|
|
// first values it sees, permanently.
|
|
await expect(
|
|
deriveStalkerDeviceIdsFromMac('00-1a-79-ab-cd-ef')
|
|
).resolves.toEqual(EXPECTED);
|
|
await expect(
|
|
deriveStalkerDeviceIdsFromMac('001A79ABCDEF')
|
|
).resolves.toEqual(EXPECTED);
|
|
});
|
|
|
|
it('produces 64 uppercase hex characters for both', async () => {
|
|
const derived = await deriveStalkerDeviceIdsFromMac('00:1A:79:00:00:01');
|
|
|
|
expect(derived?.deviceId1).toMatch(/^[0-9A-F]{64}$/);
|
|
expect(derived?.deviceId2).toMatch(/^[0-9A-F]{64}$/);
|
|
});
|
|
|
|
it('refuses to hash something that is not a MAC', async () => {
|
|
// Hashing a typo would pin the account to it forever.
|
|
await expect(
|
|
deriveStalkerDeviceIdsFromMac('00:1A:79')
|
|
).resolves.toBeNull();
|
|
await expect(deriveStalkerDeviceIdsFromMac('')).resolves.toBeNull();
|
|
await expect(
|
|
deriveStalkerDeviceIdsFromMac(undefined)
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('derives for a MAC outside the Infomir range', async () => {
|
|
// The OUI is a portal-side policy, not a precondition for hashing.
|
|
await expect(
|
|
deriveStalkerDeviceIdsFromMac('AA:BB:CC:DD:EE:01')
|
|
).resolves.toEqual({
|
|
deviceId1: expect.stringMatching(/^[0-9A-F]{64}$/),
|
|
deviceId2: expect.stringMatching(/^[0-9A-F]{64}$/),
|
|
});
|
|
});
|
|
});
|