blob: 079959f227d24eeb5e2fdf89c6fed25f59d3e001 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EndPoint } from "../utils/Config";
import { logger } from "../utils/Logger";
interface CacheEntry {
endpoint: EndPoint;
timestamp: number;
}
/**
* Cache for device-to-endpoint redirect mappings.
* Supports TTL-based expiration and LRU eviction.
*/
export class RedirectCache {
private deviceToEndpoint: Map<string, CacheEntry> = new Map();
private ttl: number;
private maxSize: number;
constructor(ttl: number = 300000, maxSize: number = 10000) {
this.ttl = ttl;
this.maxSize = maxSize;
}
/**
* Get cached endpoint for a device.
* Returns null if not cached or expired.
*/
get(deviceId: string): EndPoint | null {
const entry = this.deviceToEndpoint.get(deviceId);
if (!entry) {
return null;
}
// Check expiration
if (this.ttl > 0 && Date.now() - entry.timestamp > this.ttl) {
this.deviceToEndpoint.delete(deviceId);
logger.debug(`Redirect cache expired for device: ${deviceId}`);
return null;
}
return entry.endpoint;
}
/**
* Cache endpoint for a device.
*/
set(deviceId: string, endpoint: EndPoint): void {
// Check if key already exists - if so, delete it first to maintain LRU order
const exists = this.deviceToEndpoint.has(deviceId);
if (exists) {
this.deviceToEndpoint.delete(deviceId);
}
// Evict oldest entry if cache is full and we're adding a new entry (not updating)
if (!exists && this.deviceToEndpoint.size >= this.maxSize) {
const firstKey = this.deviceToEndpoint.keys().next().value;
if (firstKey) {
this.deviceToEndpoint.delete(firstKey);
logger.debug(`Evicted oldest redirect cache entry: ${firstKey}`);
}
}
// Add the entry (will be at the end of the Map, maintaining LRU order)
this.deviceToEndpoint.set(deviceId, {
endpoint,
timestamp: Date.now(),
});
logger.debug(
`Cached redirect: ${deviceId} -> ${endpoint.host}:${endpoint.port}`,
);
}
/**
* Remove cached endpoint for a device.
*/
remove(deviceId: string): void {
this.deviceToEndpoint.delete(deviceId);
logger.debug(`Removed redirect cache for device: ${deviceId}`);
}
/**
* Clear all cached mappings.
*/
clear(): void {
this.deviceToEndpoint.clear();
logger.debug("Cleared all redirect cache entries");
}
/**
* Get cache statistics.
*/
getStats(): { size: number; maxSize: number; ttl: number } {
return {
size: this.deviceToEndpoint.size,
maxSize: this.maxSize,
ttl: this.ttl,
};
}
}