2026-10-15 04:39:03 +02:00
# : project ../ src / MeWeb . Core / MeWeb . Core . csproj
2026-10-15 11:11:24 +02:00
# : project ../ src / MeWeb . Shared / MeWeb . Shared . csproj
using System.Net.Http.Json ;
2026-10-15 04:39:03 +02:00
using System.Security.Cryptography ;
using MeWeb.Core.Knowledge ;
2026-10-15 11:11:24 +02:00
using MeWeb.Shared ;
2026-10-15 04:39:03 +02:00
2026-10-15 11:11:24 +02:00
const string Fallback = "https://iptoasn.com/data/ip2asn-combined.tsv.gz" ;
2026-10-15 04:39:03 +02:00
var root = FindRoot ();
2026-10-15 11:11:24 +02:00
var output = Path . Combine ( root , "artifacts" , "knowledge" );
2026-10-15 04:39:03 +02:00
var cache = Path . Combine ( root , "tools" , "cache" );
2026-10-15 11:11:24 +02:00
Directory . CreateDirectory ( output );
2026-10-15 04:39:03 +02:00
Directory . CreateDirectory ( cache );
2026-10-15 11:11:24 +02:00
var server = Environment . GetEnvironmentVariable ( "MEWEB_KNOWLEDGE_URL" ) ?? Endpoints . Base ;
using var http = new HttpClient { Timeout = TimeSpan . FromMinutes ( 2 ) };
http . DefaultRequestHeaders . UserAgent . ParseAdd ( "me-web-knowledge/2 (+https://git.thepra.dev/Arasaka/MeWeb)" );
if (! args . Contains ( "--local" ))
{
try
{
var manifest = await http . GetFromJsonAsync ( server + Endpoints . Knowledge . GET_Manifest , MeWebJsonContext . Default . ViewKnowledge );
if ( manifest is { Files . Count : > 0 })
{
foreach ( var file in manifest . Files )
{
var bytes = await http . GetByteArrayAsync ( server + Endpoints . Knowledge . GET_M_File ( file . Name ));
var hash = Convert . ToHexStringLower ( SHA256 . HashData ( bytes ));
if ( hash != file . Sha256 ) throw new InvalidDataException ( $"{file.Name}: sha256 {hash}, the manifest says {file.Sha256}" );
File . WriteAllBytes ( Path . Combine ( output , file . Name ), bytes );
Console . WriteLine ( $"knowledge: {file.Name} {bytes.Length / 1024} KiB from {server}" );
}
File . Move ( Path . Combine ( output , "SOURCES.txt" ), Path . Combine ( output , "SOURCE.txt" ), overwrite : true );
Report ();
return 0 ;
}
}
catch ( Exception ex ) when ( ex is HttpRequestException or TaskCanceledException or InvalidDataException or System . Text . Json . JsonException )
{
Console . Error . WriteLine ( $"knowledge: the server's files could not be used ({ex.Message}); building the provider map alone" );
}
}
2026-10-15 04:39:03 +02:00
2026-10-15 11:11:24 +02:00
var download = Path . Combine ( cache , "ip2asn-combined.tsv.gz" );
2026-10-15 04:39:03 +02:00
var sourceDate = File . Exists ( download ) ? new DateTimeOffset ( File . GetLastWriteTimeUtc ( download ), TimeSpan . Zero ) : DateTimeOffset . UtcNow ;
2026-10-15 11:11:24 +02:00
if (! File . Exists ( download ) || File . GetLastWriteTimeUtc ( download ) < DateTime . UtcNow . AddHours (- 12 ) || args . Contains ( "--refresh" ))
2026-10-15 04:39:03 +02:00
{
2026-10-15 11:11:24 +02:00
using var response = await http . GetAsync ( Fallback , HttpCompletionOption . ResponseHeadersRead );
2026-10-15 04:39:03 +02:00
response . EnsureSuccessStatusCode ();
sourceDate = response . Content . Headers . LastModified ?? DateTimeOffset . UtcNow ;
await using ( var file = File . Create ( download + ".tmp" ))
await response . Content . CopyToAsync ( file );
File . Move ( download + ".tmp" , download , overwrite : true );
File . SetLastWriteTimeUtc ( download , sourceDate . UtcDateTime );
}
2026-10-15 11:11:24 +02:00
using ( var input = File . OpenRead ( download ))
using ( var file = File . Create ( Path . Combine ( output , "ip2asn.bin" )))
AsnTable . Write ( file , sourceDate , Sources . IpToAsn ( input ));
2026-10-16 18:10:19 +02:00
foreach ( var stale in new [] { "networks.bin" , "carriers.json" , "registry.bin" }) File . Delete ( Path . Combine ( output , stale ));
2026-10-15 11:11:24 +02:00
string sha ;
using ( var input = File . OpenRead ( download )) sha = Convert . ToHexStringLower ( SHA256 . HashData ( input ));
File . WriteAllText ( Path . Combine ( output , "SOURCE.txt" ), $"iptoasn.com, Public Domain Dedication and License (PDDL) 1.0\n{Fallback}\nlast-modified {sourceDate:O}\nsha256 {sha}\n" );
Report ();
return 0 ;
2026-10-15 04:39:03 +02:00
2026-10-15 11:11:24 +02:00
void Report ()
2026-10-15 04:39:03 +02:00
{
2026-10-15 11:11:24 +02:00
using var stream = File . OpenRead ( Path . Combine ( output , "ip2asn.bin" ));
var table = AsnTable . Read ( stream );
var path = Path . Combine ( output , "networks.bin" );
NetworkTable networks = null ;
if ( File . Exists ( path ))
using ( var input = File . OpenRead ( path )) networks = NetworkTable . Read ( input );
2026-10-16 18:10:19 +02:00
path = Path . Combine ( output , "registry.bin" );
2026-10-16 19:41:56 +02:00
using var registry = File . Exists ( path ) ? RegistryTable . Open ( File . OpenRead ( path )) : null ;
Console . WriteLine ( $"knowledge: {table.Count} networks in the provider map (source {table.SourceDate:yyyy-MM-dd}), {networks?.Count ?? 0} with names or upstreams, {registry?.Count ?? 0} named address blocks" );
2026-10-15 04:39:03 +02:00
}
static string FindRoot ()
{
var dir = new DirectoryInfo ( Directory . GetCurrentDirectory ());
while ( dir is not null && ! File . Exists ( Path . Combine ( dir . FullName , "MeWeb.slnx" ))) dir = dir . Parent ;
return dir ?. FullName ?? throw new InvalidOperationException ( "run from inside the repository (MeWeb.slnx not found)" );
}