// 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 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('Go accepts a doubled opening bracket, and a CID with one more ' 'character whose bits are zero', () { // The host of Go is strings.Trim(host, "[]"), and isCIDv1 checks only // that the bits left over are zero, not their count: Dart does the // same, as the vectors of Go say. for (final uri in [ 'https://[[2000::]/', 'ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdia', ]) { final c = rows(v, 'uris').firstWhere((c) => c[0] == uri); expect(c[1], 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', () { 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'); 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()), ); checkResolvedIp([8, 8, 8, 8]); expect(() => checkResolvedIp([8, 8, 8, 256]), throwsArgumentError); }); }); }