/** Клиентские хелперы IPsec (зеркало чистых функций бэкенда). */ function parseIpv4(s: string): number | null { const parts = s.trim().split(".") if (parts.length !== 4) return null const octets = parts.map((p) => Number(p)) if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) return null return (((octets[0]! << 24) | (octets[1]! << 16) | (octets[2]! << 8) | octets[3]!) >>> 0) } function intToIp(n: number): string { return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join(".") } /** Первый свободный IP диапазона пула («a.b.c.d-a.b.c.e»), исключая занятые. */ export function findFreePoolIp(range: string, taken: Iterable): string | null { const takenSet = new Set( Array.from(taken, (t) => t.replace(/\/\d+$/, "").trim()), ) const first = range.split(",").map((s) => s.trim()).filter(Boolean)[0] ?? "" const [fromRaw, toRaw] = first.split("-") const from = parseIpv4(fromRaw ?? "") const to = parseIpv4(toRaw ?? fromRaw ?? "") if (from == null) return null const last = to ?? from if (last < from) return null const cap = Math.min(last, from + 65_534) for (let n = from; n <= cap; n++) { const ip = intToIp(n) if (!takenSet.has(ip)) return ip } return null }