using MongoDB.Entities; using PrivaPub.Models.Jobs; using System.Net; namespace PrivaPub.Infrastructure.Geo { public interface ICdnRanges { // the CDN whose published ranges hold the address, or default CdnProvider Of(IPAddress address); void Replace(CdnRangeSet set); Task Load(CancellationToken token); // per CDN key: how many ranges are known and when they were downloaded IReadOnlyDictionary Status { get; } } // The published CDN ranges in memory, rebuilt from the stored CdnRangeSets at start and replaced set by set as // CdnUpdater downloads them. A few thousand networks: a linear scan per describe is cheap, and describes are weekly. public class CdnRanges : ICdnRanges { readonly object _gate = new(); Dictionary _sets = new(StringComparer.OrdinalIgnoreCase); public IReadOnlyDictionary Status { get { lock (_gate) return _sets.ToDictionary(s => s.Key, s => (s.Value.Networks.Length, s.Value.FetchedAt), StringComparer.OrdinalIgnoreCase); } } public CdnProvider Of(IPAddress address) { if (address == default) return default; if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4(); Dictionary sets; lock (_gate) sets = _sets; foreach (var (provider, networks, _) in sets.Values) foreach (var network in networks) if (network.BaseAddress.AddressFamily == address.AddressFamily && network.Contains(address)) return provider; return default; } public void Replace(CdnRangeSet set) { var provider = CdnCatalog.ByKey(set.Key) ?? new CdnProvider(set.Name, set.Key, Array.Empty(), Array.Empty(), _ => false); var networks = set.Ranges.Select(r => IPNetwork.TryParse(r, out var n) ? n : (IPNetwork?)null).Where(n => n != default).Select(n => n!.Value).ToArray(); lock (_gate) _sets = new(_sets, StringComparer.OrdinalIgnoreCase) { [set.Key] = (provider, networks, set.FetchedAt) }; } public async Task Load(CancellationToken token) { foreach (var set in await DB.Default.Find().ExecuteAsync(token)) Replace(set); } } }