47 lines
4.2 KiB
TypeScript
47 lines
4.2 KiB
TypeScript
import { isIP } from "node:net"
|
|
import { observedInteger, observedBoolean } from "./ospf-observed-values.js"
|
|
import { isManagedRecursiveRoute, userRecursiveComment, type MappedRecursiveRoute } from "./config-apply-plan.js"
|
|
import { isOwnedRecursiveComment } from "../managed-markers.js"
|
|
|
|
function rawRows(payload: unknown): Array<Record<string, unknown>> {
|
|
if (!Array.isArray(payload) || payload.some(row => row === null || typeof row !== "object" || Array.isArray(row))) throw new Error("Некорректный ответ маршрутов RouterOS")
|
|
const rows = payload as Array<Record<string, unknown>>
|
|
if (rows.some(row => row.comment !== undefined && typeof row.comment !== "string")) throw new Error("Некорректные метки владения маршрутов")
|
|
const ids = rows.map(row => row[".id"])
|
|
if (ids.some(id => typeof id !== "string" || !id.trim()) || new Set(ids).size !== ids.length) throw new Error("Неоднозначные ID маршрутов RouterOS")
|
|
return rows
|
|
}
|
|
function rosText(value: unknown): string | undefined { return typeof value === "string" ? value : undefined }
|
|
function text(value: unknown): string | null { return typeof value === "string" && value.trim() ? value.trim() : null }
|
|
export function parseRecursiveCatalog(payload: unknown): MappedRecursiveRoute[] {
|
|
return rawRows(payload).filter(row => typeof row.comment === "string" && isOwnedRecursiveComment(row.comment)).map(row => {
|
|
const id = String(row[".id"])
|
|
// The editor replaces the entire managed set. Never silently omit an owned row.
|
|
const destination = text(row["dst-address"]), gateway = text(row.gateway)
|
|
const [ip, prefix] = destination?.split("/") ?? []
|
|
const gatewayIp = gateway?.split("%")[0]
|
|
if (!destination || !gateway || isIP(ip ?? "") !== 4 || prefix !== undefined && (!/^\d+$/.test(prefix) || Number(prefix) > 32) || isIP(gatewayIp ?? "") !== 4 || !isManagedRecursiveRoute(row) || observedBoolean(rosText(row.static)) !== true) throw new Error("Управляемый маршрут " + id + " не подтверждён как поддерживаемый статический маршрут")
|
|
const distance = observedInteger(rosText(row.distance), 0, 255), disabled = observedBoolean(rosText(row.disabled))
|
|
const routingTable = text(row["routing-table"]), checkGateway = text(row["check-gateway"])
|
|
if (distance === null || disabled === null || routingTable === null || !checkGateway || !["none", "arp", "ping", "bfd"].includes(checkGateway)) throw new Error("Не подтверждены параметры управляемого маршрута " + id)
|
|
const scope = row.scope === undefined || row.scope === "" ? null : observedInteger(rosText(row.scope), 0, 255)
|
|
const targetScope = row["target-scope"] === undefined || row["target-scope"] === "" ? null : observedInteger(rosText(row["target-scope"]), 0, 255)
|
|
if (row.scope !== undefined && row.scope !== "" && scope === null || row["target-scope"] !== undefined && row["target-scope"] !== "" && targetScope === null) throw new Error("Некорректный scope управляемого маршрута " + id)
|
|
return { id, dstAddress: String(row["dst-address"]), gateway: String(row.gateway), distance, disabled, routingTable, checkGateway, scope, targetScope, country: "", comment: userRecursiveComment(String(row.comment)) }
|
|
})
|
|
}
|
|
export type RecursiveGatewayObservation = { id: string; ip: string; name: string; status: "active" | "inactive" | "unknown" }
|
|
export function parseRecursiveGatewayCatalog(payload: unknown): RecursiveGatewayObservation[] {
|
|
const result = new Map<string, RecursiveGatewayObservation>()
|
|
for (const row of rawRows(payload)) {
|
|
if (typeof row.gateway !== "string") continue
|
|
const [ip, name = ip] = row.gateway.trim().split("%")
|
|
if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(ip) || ip.split(".").some(part => Number(part) > 255) || !name) continue
|
|
const id = ip + "%" + name, active = observedBoolean(rosText(row.active))
|
|
const status = active === true ? "active" : active === false ? "inactive" : "unknown"
|
|
const previous = result.get(id)
|
|
result.set(id, { id, ip, name, status: previous?.status === "active" || status === "active" ? "active" : previous?.status === "unknown" || status === "unknown" ? "unknown" : "inactive" })
|
|
}
|
|
return [...result.values()]
|
|
}
|