You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
5.1 KiB
141 lines
5.1 KiB
// The addresses of a locator against Go (spec §44.1): checkAddressUri and
|
|
// LocatorAddress.host on the 247 addresses of testdata/vectors/locator.json
|
|
// and on the 2 800 of tool/locator_go_vectors.go, with the texts of
|
|
// locator.CheckURI and Address.Host; the parser of IP addresses against
|
|
// netip.ParseAddr and String; and the classification of publicIP on the
|
|
// bytes of addresses, as checkResolvedIp checks the IP that a name resolves
|
|
// to. Everything here runs on the VM and compiled to JavaScript, from the
|
|
// constant of locator_uris.g.dart.
|
|
library;
|
|
|
|
import 'package:datekeys/src/bytes.dart';
|
|
import 'package:datekeys/src/ipaddr.dart';
|
|
import 'package:datekeys/src/locator.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'locator_support.dart';
|
|
import 'vectors/locator_uris.g.dart';
|
|
import 'wordkey_support.dart' show fromWtf8;
|
|
|
|
void main() {
|
|
final v = decodeJson(locatorUrisJson);
|
|
|
|
void expectUri(List<Object?> c) {
|
|
final uri = c[0]! as String;
|
|
final want = textOf(v, c[1]);
|
|
expect(errorText(() => checkAddressUri(uri)), want, reason: uri);
|
|
final host = LocatorAddress(uri).host;
|
|
expect(host, c[2], reason: uri);
|
|
// As FuzzCheckURI of Go: the host shown is in the address as it is
|
|
// written, with no decoding.
|
|
if (want.isEmpty) expect(uri.contains(host) && host.isNotEmpty, isTrue);
|
|
}
|
|
|
|
group('checkAddressUri', () {
|
|
test('the addresses of locator.json, with the texts of Go', () {
|
|
final cases = rows(v, 'testdata');
|
|
expect(cases, hasLength(247));
|
|
cases.forEach(expectUri);
|
|
});
|
|
|
|
test('the addresses at the edges of §44.1 and drawn from a seed', () {
|
|
final cases = rows(v, 'uris');
|
|
expect(cases, hasLength(2800));
|
|
cases.forEach(expectUri);
|
|
// Every rule rejects some and some pass.
|
|
final texts = {for (final c in cases) c[1]};
|
|
expect(texts.length, greaterThan(20));
|
|
});
|
|
|
|
test('an address that is not ASCII is measured and quoted in UTF-8', () {
|
|
// Go counts the bytes of the string and quotes its first byte that is
|
|
// not of RFC 3986 as a rune, 0xc3 as U+00C3 in quotes.
|
|
final c = rows(
|
|
v,
|
|
'uris',
|
|
).firstWhere((c) => c[0] == 'https://ejemplo.org/\u00e9');
|
|
expectUri(c);
|
|
expect(textOf(v, c[1]), contains("'\u00c3'"));
|
|
// A lone surrogate is three bytes that are not UTF-8, as Go reads
|
|
// those bytes.
|
|
for (final c in rows(v, 'wtf8')) {
|
|
final uri = fromWtf8(fromHex(c[0]! as String))!;
|
|
expect(errorText(() => checkAddressUri(uri)), textOf(v, c[1]));
|
|
expect(LocatorAddress(uri).host, '');
|
|
}
|
|
});
|
|
|
|
test('a doubled opening bracket and a CID with one more character whose '
|
|
'bits are zero are refused, as by Go since the draft v0.13', () {
|
|
// checkHost of Go takes one pair of brackets, and isCIDv1 refuses 5 or
|
|
// more bits left over: neither is the form that spec §44.1 asks for.
|
|
for (final uri in [
|
|
'https://[[2000::]/',
|
|
'ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdia',
|
|
]) {
|
|
final c = rows(v, 'uris').firstWhere((c) => c[0] == uri);
|
|
expect(c[1], isNot(0), reason: uri);
|
|
expectUri(c);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('IP addresses', () {
|
|
test('parseIpAddress accepts what netip.ParseAddr accepts', () {
|
|
final cases = rows(v, 'ips');
|
|
expect(cases.length, greaterThan(1500));
|
|
for (final c in cases) {
|
|
final s = c[0]! as String;
|
|
final a = parseIpAddress(s);
|
|
if (c[1] == false) {
|
|
expect(a, isNull, reason: s);
|
|
continue;
|
|
}
|
|
expect(a, isNotNull, reason: s);
|
|
expect(toHex(a!.bytes), c[2], reason: s);
|
|
expect(a.zone, c[3], reason: s);
|
|
expect('$a', c[4], reason: s);
|
|
expect(isPublicIp(a), c[5], reason: s);
|
|
}
|
|
});
|
|
|
|
test(
|
|
'checkResolvedIp checks the bytes of an address as publicIP, but NAT64',
|
|
() {
|
|
for (final c in rows(v, 'public')) {
|
|
final b = fromHex(c[0]! as String);
|
|
if (c[1] == null) {
|
|
expect(() => checkResolvedIp(b), throwsArgumentError);
|
|
continue;
|
|
}
|
|
final a = IpAddress.fromBytes(b);
|
|
expect('$a', c[2]);
|
|
expect(isPublicIp(a), c[1], reason: '$a');
|
|
// An address of NAT64 counts by the IPv4 address it holds (spec
|
|
// v0.13): resolved_ip_test.dart runs those.
|
|
if (b.length == 16 &&
|
|
toHex(b.sublist(0, 12)) == '0064ff9b0000000000000000') {
|
|
continue;
|
|
}
|
|
expect(
|
|
errorText(() => checkResolvedIp(b)),
|
|
c[1] == true
|
|
? ''
|
|
: 'locator: an https address whose name resolves to ${c[2]}, '
|
|
'an IP address that is not public',
|
|
);
|
|
}
|
|
},
|
|
);
|
|
|
|
test('an IPv4 address mapped in IPv6 is not public', () {
|
|
expect(
|
|
() => checkResolvedIp([...List.filled(10, 0), 0xff, 0xff, 8, 8, 8, 8]),
|
|
throwsA(isA<LocatorException>()),
|
|
);
|
|
checkResolvedIp([8, 8, 8, 8]);
|
|
expect(() => checkResolvedIp([8, 8, 8, 256]), throwsArgumentError);
|
|
});
|
|
});
|
|
}
|