import type { IAcmeOptions, ISmartProxyOptions } from '../models/interfaces.js'; import type { IRouteAction, IRouteConfig, IRouteMatch, IRouteTarget, ITargetMatch } from '../models/route-types.js'; import type { IRustAcmeOptions, IRustDefaultConfig, IRustProxyOptions, IRustRouteAction, IRustRouteConfig, IRustRouteMatch, IRustRouteTarget, IRustTargetMatch, IRustRouteUdp, TRustHeaderMatchers, } from '../models/rust-types.js'; const SUPPORTED_REGEX_FLAGS = new Set(['i', 'm', 's', 'u', 'g']); export function serializeHeaderMatchValue(value: string | RegExp): string { if (typeof value === 'string') { return value; } const unsupportedFlags = Array.from(new Set(value.flags)).filter((flag) => !SUPPORTED_REGEX_FLAGS.has(flag)); if (unsupportedFlags.length > 0) { throw new Error( `Header RegExp uses unsupported flags for Rust serialization: ${unsupportedFlags.join(', ')}` ); } return `/${value.source}/${value.flags}`; } export function serializeHeaderMatchers(headers?: Record): TRustHeaderMatchers | undefined { if (!headers) { return undefined; } return Object.fromEntries( Object.entries(headers).map(([key, value]) => [key, serializeHeaderMatchValue(value)]) ); } export function serializeTargetMatchForRust(match?: ITargetMatch): IRustTargetMatch | undefined { if (!match) { return undefined; } return { ...match, headers: serializeHeaderMatchers(match.headers), }; } export function serializeRouteMatchForRust(match: IRouteMatch): IRustRouteMatch { return { ...match, headers: serializeHeaderMatchers(match.headers), }; } export function serializeRouteTargetForRust(target: IRouteTarget): IRustRouteTarget { if (typeof target.host !== 'string' && !Array.isArray(target.host)) { throw new Error('Route target host must be serialized before sending to Rust'); } if (typeof target.port !== 'number' && target.port !== 'preserve') { throw new Error('Route target port must be serialized before sending to Rust'); } return { ...target, host: target.host, port: target.port, match: serializeTargetMatchForRust(target.match), }; } function serializeUdpForRust(udp?: IRouteAction['udp']): IRustRouteUdp | undefined { if (!udp) { return undefined; } const { maxSessionsPerIP, ...rest } = udp; return { ...rest, maxSessionsPerIp: maxSessionsPerIP, }; } export function serializeRouteActionForRust(action: IRouteAction): IRustRouteAction { const { socketHandler: _socketHandler, datagramHandler: _datagramHandler, forwardingEngine: _forwardingEngine, nftables: _nftables, targets, udp, ...rest } = action; return { ...rest, targets: targets?.map((target) => serializeRouteTargetForRust(target)), udp: serializeUdpForRust(udp), }; } export function serializeRouteForRust(route: IRouteConfig): IRustRouteConfig { return { ...route, match: serializeRouteMatchForRust(route.match), action: serializeRouteActionForRust(route.action), }; } function serializeAcmeForRust(acme?: IAcmeOptions): IRustAcmeOptions | undefined { if (!acme) { return undefined; } return { enabled: acme.enabled, email: acme.email, environment: acme.environment, accountEmail: acme.accountEmail, port: acme.port, useProduction: acme.useProduction, renewThresholdDays: acme.renewThresholdDays, autoRenew: acme.autoRenew, skipConfiguredCerts: acme.skipConfiguredCerts, renewCheckIntervalHours: acme.renewCheckIntervalHours, }; } function serializeDefaultsForRust(defaults?: ISmartProxyOptions['defaults']): IRustDefaultConfig | undefined { if (!defaults) { return undefined; } const { preserveSourceIP, ...rest } = defaults; return { ...rest, preserveSourceIp: preserveSourceIP, }; } export function buildRustProxyOptions( settings: ISmartProxyOptions, routes: IRustRouteConfig[], acmeOverride?: IAcmeOptions, ): IRustProxyOptions { const acme = acmeOverride !== undefined ? acmeOverride : settings.acme; return { routes, preserveSourceIp: settings.preserveSourceIP, proxyIps: settings.proxyIPs, acceptProxyProtocol: settings.acceptProxyProtocol, sendProxyProtocol: settings.sendProxyProtocol, defaults: serializeDefaultsForRust(settings.defaults), connectionTimeout: settings.connectionTimeout, initialDataTimeout: settings.initialDataTimeout, socketTimeout: settings.socketTimeout, inactivityCheckInterval: settings.inactivityCheckInterval, maxConnectionLifetime: settings.maxConnectionLifetime, inactivityTimeout: settings.inactivityTimeout, gracefulShutdownTimeout: settings.gracefulShutdownTimeout, noDelay: settings.noDelay, keepAlive: settings.keepAlive, keepAliveInitialDelay: settings.keepAliveInitialDelay, maxPendingDataSize: settings.maxPendingDataSize, disableInactivityCheck: settings.disableInactivityCheck, enableKeepAliveProbes: settings.enableKeepAliveProbes, enableDetailedLogging: settings.enableDetailedLogging, enableTlsDebugLogging: settings.enableTlsDebugLogging, enableRandomizedTimeouts: settings.enableRandomizedTimeouts, maxConnectionsPerIp: settings.maxConnectionsPerIP, connectionRateLimitPerMinute: settings.connectionRateLimitPerMinute, keepAliveTreatment: settings.keepAliveTreatment, keepAliveInactivityMultiplier: settings.keepAliveInactivityMultiplier, extendedKeepAliveLifetime: settings.extendedKeepAliveLifetime, metrics: settings.metrics, acme: serializeAcmeForRust(acme), }; }