diff --git a/codec.go b/codec.go index bdbeba63..1374aff5 100644 --- a/codec.go +++ b/codec.go @@ -67,31 +67,36 @@ func stringToBytes(s string) ([]byte, error) { return b.Bytes(), nil } -func readComponent(b []byte) (int, Component, error) { +func readComponent(b []byte) (int, *Component, error) { var offset int code, n, err := ReadVarintCode(b) if err != nil { - return 0, Component{}, err + return 0, nil, err } offset += n p := ProtocolWithCode(code) if p.Code == 0 { - return 0, Component{}, fmt.Errorf("no protocol with code %d", code) + return 0, nil, fmt.Errorf("no protocol with code %d", code) } pPtr := protocolPtrByCode[code] if pPtr == nil { - return 0, Component{}, fmt.Errorf("no protocol with code %d", code) + return 0, nil, fmt.Errorf("no protocol with code %d", code) } if p.Size == 0 { - c, err := validateComponent(Component{ + c := &Component{ bytes: string(b[:offset]), valueStartIdx: offset, protocol: pPtr, - }) + } + + err := validateComponent(c) + if err != nil { + return 0, nil, err + } - return offset, c, err + return offset, c, nil } var size int @@ -100,7 +105,7 @@ func readComponent(b []byte) (int, Component, error) { var n int size, n, err = ReadVarintCode(b[offset:]) if err != nil { - return 0, Component{}, err + return 0, nil, err } offset += n } else { @@ -109,14 +114,18 @@ func readComponent(b []byte) (int, Component, error) { } if len(b[offset:]) < size || size <= 0 { - return 0, Component{}, fmt.Errorf("invalid value for size %d", len(b[offset:])) + return 0, nil, fmt.Errorf("invalid value for size %d", len(b[offset:])) } - c, err := validateComponent(Component{ + c := &Component{ bytes: string(b[:offset+size]), protocol: pPtr, valueStartIdx: offset, - }) + } + err = validateComponent(c) + if err != nil { + return 0, nil, err + } return offset + size, c, err } @@ -142,7 +151,7 @@ func readMultiaddr(b []byte) (int, Multiaddr, error) { return bytesRead, nil, fmt.Errorf("unexpected component after path component") } sawPathComponent = c.protocol.Path - res = append(res, c) + res = append(res, *c) } return bytesRead, res, nil } diff --git a/compattest/README.md b/compattest/README.md new file mode 100644 index 00000000..09825c8a --- /dev/null +++ b/compattest/README.md @@ -0,0 +1,10 @@ +# Compatibility Test + +This package is used to test the backwards compatibility of the `go-multiaddr` +package against a previous version. + +To update the previous version, from the root of the repo: +```sh +git subtree pull --prefix=compattest/internal/prev https://github.com/multiformats/go-multiaddr.git --squash +``` + diff --git a/compattest/backwards_compat_test.go b/compattest/backwards_compat_test.go new file mode 100644 index 00000000..7e71b767 --- /dev/null +++ b/compattest/backwards_compat_test.go @@ -0,0 +1,140 @@ +package multiaddr + +import ( + "bytes" + "testing" + + multiaddr "github.com/multiformats/go-multiaddr" + prevmultiaddr "github.com/multiformats/go-multiaddr/compattest/internal/prev" +) + +func FuzzBackwardsCompat(f *testing.F) { + for _, v := range good { + f.Add(v) + } + + f.Fuzz(func(t *testing.T, addrStr string) { + oldAddr, err := prevmultiaddr.NewMultiaddr(addrStr) + if err != nil { + _, err := multiaddr.NewMultiaddr(addrStr) + if err == nil { + t.Fatalf("NewMultiaddr(%s) should fail", addrStr) + } + t.Skip() // Skip inputs that are not valid multiaddrs + } + addr, err := multiaddr.NewMultiaddr(addrStr) + if err != nil { + t.Fatalf("NewMultiaddr(%s) failed: %s", addrStr, err) + } + oldBytes := oldAddr.Bytes() + newBytes := addr.Bytes() + if !bytes.Equal(oldBytes, newBytes) { + t.Fatalf("NewMultiaddr(%s) returned different bytes than ma14.NewMultiaddr(%s)", addrStr, addrStr) + } + + addr, err = multiaddr.NewMultiaddrBytes(oldBytes) + if err != nil { + t.Fatalf("NewMultiaddrBytes(%s) failed: %s", oldBytes, err) + } + newBytes = addr.Bytes() + if !bytes.Equal(oldBytes, newBytes) { + t.Fatalf("NewMultiaddrBytes(%s) returned different bytes than NewMultiaddr(%s)", oldBytes, addrStr) + } + }) +} + +var good = []string{ + "/ip4/1.2.3.4", + "/ip4/0.0.0.0", + "/ip4/192.0.2.0/ipcidr/24", + "/ip6/::1", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic-v1", + "/ip6/2001:db8::/ipcidr/32", + "/ip6zone/x/ip6/fe80::1", + "/ip6zone/x%y/ip6/fe80::1", + "/ip6zone/x%y/ip6/::", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic-v1", + "/onion/timaq4ygg2iegci7:1234", + "/onion/timaq4ygg2iegci7:80/http", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/udp/8080", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwas", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwassw", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/http", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/udp/8080", + "/udp/0", + "/tcp/0", + "/sctp/0", + "/udp/1234", + "/tcp/1234", + "/sctp/1234", + "/udp/65535", + "/tcp/65535", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/bafzbeigvf25ytwc3akrijfecaotc74udrhcxzh2cx3we5qqnw5vgrei4bm", + "/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV", + "/p2p/k51qzi5uqu5dhb6l8spkdx7yxafegfkee5by8h7lmjh2ehc2sgg34z7c15vzqs", + "/p2p/bafzaajaiaejcalj543iwv2d7pkjt7ykvefrkfu7qjfi6sduakhso4lay6abn2d5u", + "/udp/1234/sctp/1234", + "/udp/1234/udt", + "/udp/1234/utp", + "/tcp/1234/http", + "/tcp/1234/tls/http", + "/tcp/1234/https", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/udp/1234", + "/ip4/127.0.0.1/udp/0", + "/ip4/127.0.0.1/tcp/1234", + "/ip4/127.0.0.1/tcp/1234/", + "/ip4/127.0.0.1/udp/1234/quic", + "/ip4/127.0.0.1/udp/1234/quic-v1", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/unix/a/b/c/d/e", + "/unix/stdio", + "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/tls", + "/ip4/127.0.0.1/tcp/127/tls/ws", + "/ip4/127.0.0.1/tcp/127/noise", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/webrtc-direct", + "/ip4/127.0.0.1/tcp/127/webrtc", + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + "/ip4/127.0.0.1/tcp/0/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV/http-path/foo", + "/ip4/127.0.0.1/tcp/443/tls/sni/example.com/http/http-path/foo", + "/memory/4", +} diff --git a/compattest/go.mod b/compattest/go.mod new file mode 100644 index 00000000..000e61a6 --- /dev/null +++ b/compattest/go.mod @@ -0,0 +1,29 @@ +module github.com/multiformats/go-multiaddr/compattest + +go 1.23.4 + +replace github.com/multiformats/go-multiaddr => ../ + +replace github.com/multiformats/go-multiaddr/compattest/internal/prev => ./internal/prev + +require ( + github.com/multiformats/go-multiaddr v0.0.0-00010101000000-000000000000 + github.com/multiformats/go-multiaddr/compattest/internal/prev v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/ipfs/go-cid v0.0.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20230725012225-302865e7556b // indirect + golang.org/x/sys v0.28.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect +) diff --git a/compattest/go.sum b/compattest/go.sum new file mode 100644 index 00000000..659d3e49 --- /dev/null +++ b/compattest/go.sum @@ -0,0 +1,52 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b h1:tK7yjGqVRzYdXsBcfD2MLhFAhHfDgGLm2rY1ub7FA9k= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/compattest/internal/prev/.github/workflows/go-check.yml b/compattest/internal/prev/.github/workflows/go-check.yml new file mode 100644 index 00000000..26f63bc1 --- /dev/null +++ b/compattest/internal/prev/.github/workflows/go-check.yml @@ -0,0 +1,18 @@ +name: Go Checks + +on: + pull_request: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} + cancel-in-progress: true + +jobs: + go-check: + uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0 diff --git a/compattest/internal/prev/.github/workflows/go-test.yml b/compattest/internal/prev/.github/workflows/go-test.yml new file mode 100644 index 00000000..778de6ed --- /dev/null +++ b/compattest/internal/prev/.github/workflows/go-test.yml @@ -0,0 +1,20 @@ +name: Go Test + +on: + pull_request: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} + cancel-in-progress: true + +jobs: + go-test: + uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0 + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/compattest/internal/prev/.github/workflows/release-check.yml b/compattest/internal/prev/.github/workflows/release-check.yml new file mode 100644 index 00000000..0b5ff607 --- /dev/null +++ b/compattest/internal/prev/.github/workflows/release-check.yml @@ -0,0 +1,19 @@ +name: Release Checker + +on: + pull_request_target: + paths: [ 'version.json' ] + types: [ opened, synchronize, reopened, labeled, unlabeled ] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-check: + uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0 diff --git a/compattest/internal/prev/.github/workflows/releaser.yml b/compattest/internal/prev/.github/workflows/releaser.yml new file mode 100644 index 00000000..2ebdbed3 --- /dev/null +++ b/compattest/internal/prev/.github/workflows/releaser.yml @@ -0,0 +1,17 @@ +name: Releaser + +on: + push: + paths: [ 'version.json' ] + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.sha }} + cancel-in-progress: true + +jobs: + releaser: + uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0 diff --git a/compattest/internal/prev/.github/workflows/tagpush.yml b/compattest/internal/prev/.github/workflows/tagpush.yml new file mode 100644 index 00000000..5ef3fb9e --- /dev/null +++ b/compattest/internal/prev/.github/workflows/tagpush.yml @@ -0,0 +1,18 @@ +name: Tag Push Checker + +on: + push: + tags: + - v* + +permissions: + contents: read + issues: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + releaser: + uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0 diff --git a/compattest/internal/prev/.gitignore b/compattest/internal/prev/.gitignore new file mode 100644 index 00000000..699d271b --- /dev/null +++ b/compattest/internal/prev/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +multiaddr/multiaddr +tmp/ diff --git a/compattest/internal/prev/LICENSE b/compattest/internal/prev/LICENSE new file mode 100644 index 00000000..c7386b3c --- /dev/null +++ b/compattest/internal/prev/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Juan Batiz-Benet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/compattest/internal/prev/README.md b/compattest/internal/prev/README.md new file mode 100644 index 00000000..df2766aa --- /dev/null +++ b/compattest/internal/prev/README.md @@ -0,0 +1,117 @@ +# go-multiaddr + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) +[![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) +[![GoDoc](https://godoc.org/github.com/multiformats/go-multiaddr?status.svg)](https://godoc.org/github.com/multiformats/go-multiaddr) +[![Travis CI](https://img.shields.io/travis/multiformats/go-multiaddr.svg?style=flat-square&branch=master)](https://travis-ci.org/multiformats/go-multiaddr) +[![codecov.io](https://img.shields.io/codecov/c/github/multiformats/go-multiaddr.svg?style=flat-square&branch=master)](https://codecov.io/github/multiformats/go-multiaddr?branch=master) + +> [multiaddr](https://github.com/multiformats/multiaddr) implementation in go + +Multiaddr is a standard way to represent addresses that: + +- Support any standard network protocols. +- Self-describe (include protocols). +- Have a binary packed format. +- Have a nice string representation. +- Encapsulate well. + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) + - [Example](#example) + - [Simple](#simple) + - [Protocols](#protocols) + - [En/decapsulate](#endecapsulate) + - [Tunneling](#tunneling) +- [Maintainers](#maintainers) +- [Contribute](#contribute) +- [License](#license) + +## Install + +```sh +go get github.com/multiformats/go-multiaddr +``` + +## Usage + +### Example + +#### Simple + +```go +import ma "github.com/multiformats/go-multiaddr" + +// construct from a string (err signals parse failure) +m1, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") + +// construct from bytes (err signals parse failure) +m2, err := ma.NewMultiaddrBytes(m1.Bytes()) + +// true +strings.Equal(m1.String(), "/ip4/127.0.0.1/udp/1234") +strings.Equal(m1.String(), m2.String()) +bytes.Equal(m1.Bytes(), m2.Bytes()) +m1.Equal(m2) +m2.Equal(m1) +``` + +#### Protocols + +```go +// get the multiaddr protocol description objects +m1.Protocols() +// []Protocol{ +// Protocol{ Code: 4, Name: 'ip4', Size: 32}, +// Protocol{ Code: 17, Name: 'udp', Size: 16}, +// } +``` + +#### En/decapsulate + +```go +import ma "github.com/multiformats/go-multiaddr" + +m, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") +// + +sctpMA, err := ma.NewMultiaddr("/sctp/5678") + +m.Encapsulate(sctpMA) +// + +udpMA, err := ma.NewMultiaddr("/udp/1234") + +m.Decapsulate(udpMA) // up to + inc last occurrence of subaddr +// +``` + +#### Tunneling + +Multiaddr allows expressing tunnels very nicely. + +```js +printer, _ := ma.NewMultiaddr("/ip4/192.168.0.13/tcp/80") +proxy, _ := ma.NewMultiaddr("/ip4/10.20.30.40/tcp/443") +printerOverProxy := proxy.Encapsulate(printer) +// /ip4/10.20.30.40/tcp/443/ip4/192.168.0.13/tcp/80 + +proxyAgain := printerOverProxy.Decapsulate(printer) +// /ip4/10.20.30.40/tcp/443 +``` + +## Contribute + +Contributions welcome. Please check out [the issues](https://github.com/multiformats/go-multiaddr/issues). + +Check out our [contributing document](https://github.com/multiformats/multiformats/blob/master/contributing.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. + +## License + +[MIT](LICENSE) © 2014 Juan Batiz-Benet diff --git a/compattest/internal/prev/codec.go b/compattest/internal/prev/codec.go new file mode 100644 index 00000000..f2951c48 --- /dev/null +++ b/compattest/internal/prev/codec.go @@ -0,0 +1,181 @@ +package multiaddr + +import ( + "bytes" + "fmt" + "strings" + + "github.com/multiformats/go-varint" +) + +func stringToBytes(s string) ([]byte, error) { + // consume trailing slashes + s = strings.TrimRight(s, "/") + + var b bytes.Buffer + sp := strings.Split(s, "/") + + if sp[0] != "" { + return nil, fmt.Errorf("failed to parse multiaddr %q: must begin with /", s) + } + + // consume first empty elem + sp = sp[1:] + + if len(sp) == 0 { + return nil, fmt.Errorf("failed to parse multiaddr %q: empty multiaddr", s) + } + + for len(sp) > 0 { + name := sp[0] + p := ProtocolWithName(name) + if p.Code == 0 { + return nil, fmt.Errorf("failed to parse multiaddr %q: unknown protocol %s", s, sp[0]) + } + _, _ = b.Write(p.VCode) + sp = sp[1:] + + if p.Size == 0 { // no length. + continue + } + + if len(sp) < 1 { + return nil, fmt.Errorf("failed to parse multiaddr %q: unexpected end of multiaddr", s) + } + + if p.Path { + // it's a path protocol (terminal). + // consume the rest of the address as the next component. + sp = []string{"/" + strings.Join(sp, "/")} + } + + a, err := p.Transcoder.StringToBytes(sp[0]) + if err != nil { + return nil, fmt.Errorf("failed to parse multiaddr %q: invalid value %q for protocol %s: %s", s, sp[0], p.Name, err) + } + if p.Size < 0 { // varint size. + _, _ = b.Write(varint.ToUvarint(uint64(len(a)))) + } + b.Write(a) + sp = sp[1:] + } + + return b.Bytes(), nil +} + +func validateBytes(b []byte) (err error) { + if len(b) == 0 { + return fmt.Errorf("empty multiaddr") + } + for len(b) > 0 { + code, n, err := ReadVarintCode(b) + if err != nil { + return err + } + + b = b[n:] + p := ProtocolWithCode(code) + if p.Code == 0 { + return fmt.Errorf("no protocol with code %d", code) + } + + if p.Size == 0 { + continue + } + + n, size, err := sizeForAddr(p, b) + if err != nil { + return err + } + + b = b[n:] + + if len(b) < size || size < 0 { + return fmt.Errorf("invalid value for size %d", len(b)) + } + if p.Path && len(b) != size { + return fmt.Errorf("invalid size of component for path protocol %d: expected %d", size, len(b)) + } + + err = p.Transcoder.ValidateBytes(b[:size]) + if err != nil { + return err + } + + b = b[size:] + } + + return nil +} + +func readComponent(b []byte) (int, Component, error) { + var offset int + code, n, err := ReadVarintCode(b) + if err != nil { + return 0, Component{}, err + } + offset += n + + p := ProtocolWithCode(code) + if p.Code == 0 { + return 0, Component{}, fmt.Errorf("no protocol with code %d", code) + } + + if p.Size == 0 { + return offset, Component{ + bytes: b[:offset], + offset: offset, + protocol: p, + }, nil + } + + n, size, err := sizeForAddr(p, b[offset:]) + if err != nil { + return 0, Component{}, err + } + + offset += n + + if len(b[offset:]) < size || size < 0 { + return 0, Component{}, fmt.Errorf("invalid value for size %d", len(b[offset:])) + } + + return offset + size, Component{ + bytes: b[:offset+size], + protocol: p, + offset: offset, + }, nil +} + +func bytesToString(b []byte) (ret string, err error) { + if len(b) == 0 { + return "", fmt.Errorf("empty multiaddr") + } + var buf strings.Builder + + for len(b) > 0 { + n, c, err := readComponent(b) + if err != nil { + return "", err + } + b = b[n:] + c.writeTo(&buf) + } + + return buf.String(), nil +} + +func sizeForAddr(p Protocol, b []byte) (skip, size int, err error) { + switch { + case p.Size > 0: + return 0, (p.Size / 8), nil + case p.Size == 0: + return 0, 0, nil + default: + size, n, err := ReadVarintCode(b) + if err != nil { + return 0, 0, err + } + return n, size, nil + } +} diff --git a/compattest/internal/prev/codecov.yml b/compattest/internal/prev/codecov.yml new file mode 100644 index 00000000..ca8100ab --- /dev/null +++ b/compattest/internal/prev/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "multiaddr" diff --git a/compattest/internal/prev/component.go b/compattest/internal/prev/component.go new file mode 100644 index 00000000..4ee68092 --- /dev/null +++ b/compattest/internal/prev/component.go @@ -0,0 +1,186 @@ +package multiaddr + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "strings" + + "github.com/multiformats/go-varint" +) + +// Component is a single multiaddr Component. +type Component struct { + bytes []byte + protocol Protocol + offset int +} + +func (c *Component) Bytes() []byte { + return c.bytes +} + +func (c *Component) MarshalBinary() ([]byte, error) { + return c.Bytes(), nil +} + +func (c *Component) UnmarshalBinary(data []byte) error { + _, comp, err := readComponent(data) + if err != nil { + return err + } + *c = comp + return nil +} + +func (c *Component) MarshalText() ([]byte, error) { + return []byte(c.String()), nil +} + +func (c *Component) UnmarshalText(data []byte) error { + bytes, err := stringToBytes(string(data)) + if err != nil { + return err + } + _, comp, err := readComponent(bytes) + if err != nil { + return err + } + *c = comp + return nil +} + +func (c *Component) MarshalJSON() ([]byte, error) { + txt, err := c.MarshalText() + if err != nil { + return nil, err + } + + return json.Marshal(string(txt)) +} + +func (m *Component) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + return m.UnmarshalText([]byte(v)) +} + +func (c *Component) Equal(o Multiaddr) bool { + if o == nil { + return false + } + return bytes.Equal(c.bytes, o.Bytes()) +} + +func (c *Component) Protocols() []Protocol { + return []Protocol{c.protocol} +} + +func (c *Component) Decapsulate(o Multiaddr) Multiaddr { + if c.Equal(o) { + return nil + } + return c +} + +func (c *Component) Encapsulate(o Multiaddr) Multiaddr { + m := &multiaddr{bytes: c.bytes} + return m.Encapsulate(o) +} + +func (c *Component) ValueForProtocol(code int) (string, error) { + if c.protocol.Code != code { + return "", ErrProtocolNotFound + } + return c.Value(), nil +} + +func (c *Component) Protocol() Protocol { + return c.protocol +} + +func (c *Component) RawValue() []byte { + return c.bytes[c.offset:] +} + +func (c *Component) Value() string { + if c.protocol.Transcoder == nil { + return "" + } + value, err := c.protocol.Transcoder.BytesToString(c.bytes[c.offset:]) + if err != nil { + // This Component must have been checked. + panic(err) + } + return value +} + +func (c *Component) String() string { + var b strings.Builder + c.writeTo(&b) + return b.String() +} + +// writeTo is an efficient, private function for string-formatting a multiaddr. +// Trust me, we tend to allocate a lot when doing this. +func (c *Component) writeTo(b *strings.Builder) { + b.WriteByte('/') + b.WriteString(c.protocol.Name) + value := c.Value() + if len(value) == 0 { + return + } + if !(c.protocol.Path && value[0] == '/') { + b.WriteByte('/') + } + b.WriteString(value) +} + +// NewComponent constructs a new multiaddr component +func NewComponent(protocol, value string) (*Component, error) { + p := ProtocolWithName(protocol) + if p.Code == 0 { + return nil, fmt.Errorf("unsupported protocol: %s", protocol) + } + if p.Transcoder != nil { + bts, err := p.Transcoder.StringToBytes(value) + if err != nil { + return nil, err + } + return newComponent(p, bts), nil + } else if value != "" { + return nil, fmt.Errorf("protocol %s doesn't take a value", p.Name) + } + return newComponent(p, nil), nil + // TODO: handle path /? +} + +func newComponent(protocol Protocol, bvalue []byte) *Component { + size := len(bvalue) + size += len(protocol.VCode) + if protocol.Size < 0 { + size += varint.UvarintSize(uint64(len(bvalue))) + } + maddr := make([]byte, size) + var offset int + offset += copy(maddr[offset:], protocol.VCode) + if protocol.Size < 0 { + offset += binary.PutUvarint(maddr[offset:], uint64(len(bvalue))) + } + copy(maddr[offset:], bvalue) + + // For debugging + if len(maddr) != offset+len(bvalue) { + panic("incorrect length") + } + + return &Component{ + bytes: maddr, + protocol: protocol, + offset: offset, + } +} diff --git a/compattest/internal/prev/doc.go b/compattest/internal/prev/doc.go new file mode 100644 index 00000000..b80f3ab2 --- /dev/null +++ b/compattest/internal/prev/doc.go @@ -0,0 +1,35 @@ +/* +Package multiaddr provides an implementation of the Multiaddr network +address format. Multiaddr emphasizes explicitness, self-description, and +portability. It allows applications to treat addresses as opaque tokens, +and to avoid making assumptions about the address representation (e.g. length). +Learn more at https://github.com/multiformats/multiaddr + +Basic Use: + + import ( + "bytes" + "strings" + ma "github.com/multiformats/go-multiaddr" + ) + + // construct from a string (err signals parse failure) + m1, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") + + // construct from bytes (err signals parse failure) + m2, err := ma.NewMultiaddrBytes(m1.Bytes()) + + // true + strings.Equal(m1.String(), "/ip4/127.0.0.1/udp/1234") + strings.Equal(m1.String(), m2.String()) + bytes.Equal(m1.Bytes(), m2.Bytes()) + m1.Equal(m2) + m2.Equal(m1) + + // tunneling (en/decap) + printer, _ := ma.NewMultiaddr("/ip4/192.168.0.13/tcp/80") + proxy, _ := ma.NewMultiaddr("/ip4/10.20.30.40/tcp/443") + printerOverProxy := proxy.Encapsulate(printer) + proxyAgain := printerOverProxy.Decapsulate(printer) +*/ +package multiaddr diff --git a/compattest/internal/prev/filter.go b/compattest/internal/prev/filter.go new file mode 100644 index 00000000..ba915da0 --- /dev/null +++ b/compattest/internal/prev/filter.go @@ -0,0 +1,145 @@ +package multiaddr + +import ( + "net" + "sync" +) + +// Action is an enum modelling all possible filter actions. +type Action int32 + +const ( + ActionNone Action = iota // zero value. + ActionAccept + ActionDeny +) + +type filterEntry struct { + f net.IPNet + action Action +} + +// Filters is a structure representing a collection of accept/deny +// net.IPNet filters, together with the DefaultAction flag, which +// represents the default filter policy. +// +// Note that the last policy added to the Filters is authoritative. +type Filters struct { + DefaultAction Action + + mu sync.RWMutex + filters []*filterEntry +} + +// NewFilters constructs and returns a new set of net.IPNet filters. +// By default, the new filter accepts all addresses. +func NewFilters() *Filters { + return &Filters{ + DefaultAction: ActionAccept, + filters: make([]*filterEntry, 0), + } +} + +func (fs *Filters) find(ipnet net.IPNet) (int, *filterEntry) { + s := ipnet.String() + for idx, ft := range fs.filters { + if ft.f.String() == s { + return idx, ft + } + } + return -1, nil +} + +// AddFilter adds a rule to the Filters set, enforcing the desired action for +// the provided IPNet mask. +func (fs *Filters) AddFilter(ipnet net.IPNet, action Action) { + fs.mu.Lock() + defer fs.mu.Unlock() + + if _, f := fs.find(ipnet); f != nil { + f.action = action + } else { + fs.filters = append(fs.filters, &filterEntry{ipnet, action}) + } +} + +// RemoveLiteral removes the first filter associated with the supplied IPNet, +// returning whether something was removed or not. It makes no distinction +// between whether the rule is an accept or a deny. +func (fs *Filters) RemoveLiteral(ipnet net.IPNet) (removed bool) { + fs.mu.Lock() + defer fs.mu.Unlock() + + if idx, _ := fs.find(ipnet); idx != -1 { + fs.filters = append(fs.filters[:idx], fs.filters[idx+1:]...) + return true + } + return false +} + +// AddrBlocked parses a ma.Multiaddr and, if a valid netip is found, it applies the +// Filter set rules, returning true if the given address should be denied, and false if +// the given address is accepted. +// +// If a parsing error occurs, or no filter matches, the Filters' +// default is returned. +// +// TODO: currently, the last filter to match wins always, but it shouldn't be that way. +// +// Instead, the highest-specific last filter should win; that way more specific filters +// override more general ones. +func (fs *Filters) AddrBlocked(a Multiaddr) (deny bool) { + var ( + netip net.IP + found bool + ) + + ForEach(a, func(c Component) bool { + switch c.Protocol().Code { + case P_IP6ZONE: + return true + case P_IP6, P_IP4: + found = true + netip = net.IP(c.RawValue()) + return false + default: + return false + } + }) + + if !found { + return fs.DefaultAction == ActionDeny + } + + fs.mu.RLock() + defer fs.mu.RUnlock() + + action := fs.DefaultAction + for _, ft := range fs.filters { + if ft.f.Contains(netip) { + action = ft.action + } + } + + return action == ActionDeny +} + +func (fs *Filters) ActionForFilter(ipnet net.IPNet) (action Action, ok bool) { + if _, f := fs.find(ipnet); f != nil { + return f.action, true + } + return ActionNone, false +} + +// FiltersForAction returns the filters associated with the indicated action. +func (fs *Filters) FiltersForAction(action Action) (result []net.IPNet) { + fs.mu.RLock() + defer fs.mu.RUnlock() + + for _, ff := range fs.filters { + if ff.action == action { + result = append(result, ff.f) + } + } + return result +} diff --git a/compattest/internal/prev/filter_test.go b/compattest/internal/prev/filter_test.go new file mode 100644 index 00000000..82b974dd --- /dev/null +++ b/compattest/internal/prev/filter_test.go @@ -0,0 +1,202 @@ +package multiaddr + +import ( + "net" + "testing" +) + +func TestFilterListing(t *testing.T) { + f := NewFilters() + expected := map[string]bool{ + "1.2.3.0/24": true, + "4.3.2.1/32": true, + "fd00::/8": true, + "fc00::1/128": true, + } + for cidr := range expected { + _, ipnet, _ := net.ParseCIDR(cidr) + f.AddFilter(*ipnet, ActionDeny) + } + + for _, filter := range f.FiltersForAction(ActionDeny) { + cidr := filter.String() + if expected[cidr] { + delete(expected, cidr) + } else { + t.Errorf("unexected filter %s", cidr) + } + } + for cidr := range expected { + t.Errorf("expected filter %s", cidr) + } +} + +func TestFilterBlocking(t *testing.T) { + f := NewFilters() + + _, ipnet, _ := net.ParseCIDR("0.1.2.3/24") + f.AddFilter(*ipnet, ActionDeny) + filters := f.FiltersForAction(ActionDeny) + if len(filters) != 1 { + t.Fatal("Expected only 1 filter") + } + + if a, ok := f.ActionForFilter(*ipnet); !ok || a != ActionDeny { + t.Fatal("Expected filter with DENY action") + } + + if !f.RemoveLiteral(filters[0]) { + t.Error("expected true value from RemoveLiteral") + } + + for _, cidr := range []string{ + "1.2.3.0/24", + "4.3.2.1/32", + "fd00::/8", + "fc00::1/128", + } { + _, ipnet, _ := net.ParseCIDR(cidr) + f.AddFilter(*ipnet, ActionDeny) + } + + // These addresses should all be blocked + for _, blocked := range []string{ + "/ip4/1.2.3.4/tcp/123", + "/ip4/4.3.2.1/udp/123", + "/ip6/fd00::2/tcp/321", + "/ip6/fc00::1/udp/321", + } { + maddr, err := NewMultiaddr(blocked) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blocked", blocked) + } + } + + // test that other net intervals are not blocked + for _, addr := range []string{ + "/ip4/1.2.4.1/tcp/123", + "/ip4/4.3.2.2/udp/123", + "/ip6/fe00::1/tcp/321", + "/ip6/fc00::2/udp/321", + } { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if f.AddrBlocked(maddr) { + t.Fatalf("expected %s to not be blocked", addr) + } + } +} + +func TestFilterWhitelisting(t *testing.T) { + f := NewFilters() + + // Add default reject filter + f.DefaultAction = ActionDeny + + // Add a whitelist + _, ipnet, _ := net.ParseCIDR("1.2.3.0/24") + f.AddFilter(*ipnet, ActionAccept) + + if a, ok := f.ActionForFilter(*ipnet); !ok || a != ActionAccept { + t.Fatal("Expected filter with ACCEPT action") + } + + // That /24 should now be allowed + for _, addr := range []string{ + "/ip4/1.2.3.1/tcp/123", + "/ip4/1.2.3.254/tcp/123", + "/ip4/1.2.3.254/udp/321", + } { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be whitelisted", addr) + } + } + + // No policy matches these maddrs, they should be blocked by default + for _, blocked := range []string{ + "/ip4/1.2.4.4/tcp/123", + "/ip4/4.3.2.1/udp/123", + "/ip6/fd00::2/tcp/321", + "/ip6/fc00::1/udp/321", + } { + maddr, err := NewMultiaddr(blocked) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blocked", blocked) + } + } +} + +func TestFiltersRemoveRules(t *testing.T) { + f := NewFilters() + + ips := []string{ + "/ip4/1.2.3.1/tcp/123", + "/ip4/1.2.3.254/tcp/123", + } + + // Add default reject filter + f.DefaultAction = ActionDeny + + // Add whitelisting + _, ipnet, _ := net.ParseCIDR("1.2.3.0/24") + f.AddFilter(*ipnet, ActionAccept) + + if a, ok := f.ActionForFilter(*ipnet); !ok || a != ActionAccept { + t.Fatal("Expected filter with ACCEPT action") + } + + // these are all whitelisted, should be OK + for _, addr := range ips { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be whitelisted", addr) + } + } + + // Test removing the filter. It'll remove multiple, so make a dupe & + // a complement + f.AddFilter(*ipnet, ActionDeny) + + // Show that they all apply, these are now blacklisted & should fail + for _, addr := range ips { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blacklisted", addr) + } + } + + // remove those rules + if !f.RemoveLiteral(*ipnet) { + t.Error("expected true value from RemoveLiteral") + } + + // our default is reject, so the 1.2.3.0/24 should be rejected now, + // along with everything else + for _, addr := range ips { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blocked", addr) + } + } +} diff --git a/compattest/internal/prev/go.mod b/compattest/internal/prev/go.mod new file mode 100644 index 00000000..e3249bfc --- /dev/null +++ b/compattest/internal/prev/go.mod @@ -0,0 +1,27 @@ +module github.com/multiformats/go-multiaddr + +go 1.22 + +require ( + github.com/ipfs/go-cid v0.0.7 + github.com/multiformats/go-multibase v0.2.0 + github.com/multiformats/go-multihash v0.2.3 + github.com/multiformats/go-varint v0.0.7 + github.com/stretchr/testify v1.7.0 + golang.org/x/exp v0.0.0-20230725012225-302865e7556b +) + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect +) diff --git a/compattest/internal/prev/go.sum b/compattest/internal/prev/go.sum new file mode 100644 index 00000000..076449b7 --- /dev/null +++ b/compattest/internal/prev/go.sum @@ -0,0 +1,56 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b h1:tK7yjGqVRzYdXsBcfD2MLhFAhHfDgGLm2rY1ub7FA9k= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/compattest/internal/prev/interface.go b/compattest/internal/prev/interface.go new file mode 100644 index 00000000..699c54d1 --- /dev/null +++ b/compattest/internal/prev/interface.go @@ -0,0 +1,63 @@ +package multiaddr + +import ( + "encoding" + "encoding/json" +) + +/* +Multiaddr is a cross-protocol, cross-platform format for representing +internet addresses. It emphasizes explicitness and self-description. +Learn more here: https://github.com/multiformats/multiaddr + +Multiaddrs have both a binary and string representation. + + import ma "github.com/multiformats/go-multiaddr" + + addr, err := ma.NewMultiaddr("/ip4/1.2.3.4/tcp/80") + // err non-nil when parsing failed. +*/ +type Multiaddr interface { + json.Marshaler + json.Unmarshaler + encoding.TextMarshaler + encoding.TextUnmarshaler + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + + // Equal returns whether two Multiaddrs are exactly equal + Equal(Multiaddr) bool + + // Bytes returns the []byte representation of this Multiaddr + // + // This function may expose immutable, internal state. Do not modify. + Bytes() []byte + + // String returns the string representation of this Multiaddr + // (may panic if internal state is corrupted) + String() string + + // Protocols returns the list of Protocols this Multiaddr includes + // will panic if protocol code incorrect (and bytes accessed incorrectly) + Protocols() []Protocol + + // Encapsulate wraps this Multiaddr around another. For example: + // + // /ip4/1.2.3.4 encapsulate /tcp/80 = /ip4/1.2.3.4/tcp/80 + // + Encapsulate(Multiaddr) Multiaddr + + // Decapsulate removes a Multiaddr wrapping. For example: + // + // /ip4/1.2.3.4/tcp/80 decapsulate /tcp/80 = /ip4/1.2.3.4 + // /ip4/1.2.3.4/tcp/80 decapsulate /udp/80 = /ip4/1.2.3.4/tcp/80 + // /ip4/1.2.3.4/tcp/80 decapsulate /ip4/1.2.3.4 = nil + // + Decapsulate(Multiaddr) Multiaddr + + // ValueForProtocol returns the value (if any) following the specified protocol + // + // Note: protocols can appear multiple times in a single multiaddr. + // Consider using `ForEach` to walk over the addr manually. + ValueForProtocol(code int) (string, error) +} diff --git a/compattest/internal/prev/multiaddr.go b/compattest/internal/prev/multiaddr.go new file mode 100644 index 00000000..5e607808 --- /dev/null +++ b/compattest/internal/prev/multiaddr.go @@ -0,0 +1,261 @@ +package multiaddr + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + + "golang.org/x/exp/slices" +) + +// multiaddr is the data structure representing a Multiaddr +type multiaddr struct { + bytes []byte +} + +// NewMultiaddr parses and validates an input string, returning a *Multiaddr +func NewMultiaddr(s string) (a Multiaddr, err error) { + defer func() { + if e := recover(); e != nil { + log.Printf("Panic in NewMultiaddr on input %q: %s", s, e) + err = fmt.Errorf("%v", e) + } + }() + b, err := stringToBytes(s) + if err != nil { + return nil, err + } + return &multiaddr{bytes: b}, nil +} + +// NewMultiaddrBytes initializes a Multiaddr from a byte representation. +// It validates it as an input string. +func NewMultiaddrBytes(b []byte) (a Multiaddr, err error) { + defer func() { + if e := recover(); e != nil { + log.Printf("Panic in NewMultiaddrBytes on input %q: %s", b, e) + err = fmt.Errorf("%v", e) + } + }() + + if err := validateBytes(b); err != nil { + return nil, err + } + + return &multiaddr{bytes: b}, nil +} + +// Equal tests whether two multiaddrs are equal +func (m *multiaddr) Equal(m2 Multiaddr) bool { + if m2 == nil { + return false + } + return bytes.Equal(m.bytes, m2.Bytes()) +} + +// Bytes returns the []byte representation of this Multiaddr +// +// Do not modify the returned buffer, it may be shared. +func (m *multiaddr) Bytes() []byte { + return m.bytes +} + +// String returns the string representation of a Multiaddr +func (m *multiaddr) String() string { + s, err := bytesToString(m.bytes) + if err != nil { + panic(fmt.Errorf("multiaddr failed to convert back to string. corrupted? %s", err)) + } + return s +} + +func (m *multiaddr) MarshalBinary() ([]byte, error) { + return m.Bytes(), nil +} + +func (m *multiaddr) UnmarshalBinary(data []byte) error { + new, err := NewMultiaddrBytes(data) + if err != nil { + return err + } + *m = *(new.(*multiaddr)) + return nil +} + +func (m *multiaddr) MarshalText() ([]byte, error) { + return []byte(m.String()), nil +} + +func (m *multiaddr) UnmarshalText(data []byte) error { + new, err := NewMultiaddr(string(data)) + if err != nil { + return err + } + *m = *(new.(*multiaddr)) + return nil +} + +func (m *multiaddr) MarshalJSON() ([]byte, error) { + return json.Marshal(m.String()) +} + +func (m *multiaddr) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + new, err := NewMultiaddr(v) + *m = *(new.(*multiaddr)) + return err +} + +// Protocols returns the list of protocols this Multiaddr has. +// will panic in case we access bytes incorrectly. +func (m *multiaddr) Protocols() []Protocol { + ps := make([]Protocol, 0, 8) + b := m.bytes + for len(b) > 0 { + code, n, err := ReadVarintCode(b) + if err != nil { + panic(err) + } + + p := ProtocolWithCode(code) + if p.Code == 0 { + // this is a panic (and not returning err) because this should've been + // caught on constructing the Multiaddr + panic(fmt.Errorf("no protocol with code %d", b[0])) + } + ps = append(ps, p) + b = b[n:] + + n, size, err := sizeForAddr(p, b) + if err != nil { + panic(err) + } + + b = b[n+size:] + } + return ps +} + +// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr +func (m *multiaddr) Encapsulate(o Multiaddr) Multiaddr { + if o == nil { + return m + } + + mb := m.bytes + ob := o.Bytes() + + b := make([]byte, len(mb)+len(ob)) + copy(b, mb) + copy(b[len(mb):], ob) + return &multiaddr{bytes: b} +} + +// Decapsulate unwraps Multiaddr up until the given Multiaddr is found. +func (m *multiaddr) Decapsulate(right Multiaddr) Multiaddr { + if right == nil { + return m + } + + leftParts := Split(m) + rightParts := Split(right) + + lastIndex := -1 + for i := range leftParts { + foundMatch := false + for j, rightC := range rightParts { + if len(leftParts) <= i+j { + foundMatch = false + break + } + + foundMatch = rightC.Equal(leftParts[i+j]) + if !foundMatch { + break + } + } + + if foundMatch { + lastIndex = i + } + } + + if lastIndex == 0 { + return nil + } + + if lastIndex < 0 { + // if multiaddr not contained, returns a copy. + cpy := make([]byte, len(m.bytes)) + copy(cpy, m.bytes) + return &multiaddr{bytes: cpy} + } + + return Join(leftParts[:lastIndex]...) +} + +var ErrProtocolNotFound = fmt.Errorf("protocol not found in multiaddr") + +func (m *multiaddr) ValueForProtocol(code int) (value string, err error) { + err = ErrProtocolNotFound + ForEach(m, func(c Component) bool { + if c.Protocol().Code == code { + value = c.Value() + err = nil + return false + } + return true + }) + return +} + +// FilterAddrs is a filter that removes certain addresses, according to the given filters. +// If all filters return true, the address is kept. +func FilterAddrs(a []Multiaddr, filters ...func(Multiaddr) bool) []Multiaddr { + b := make([]Multiaddr, 0, len(a)) +addrloop: + for _, addr := range a { + for _, filter := range filters { + if !filter(addr) { + continue addrloop + } + } + b = append(b, addr) + } + return b +} + +// Contains reports whether addr is contained in addrs. +func Contains(addrs []Multiaddr, addr Multiaddr) bool { + for _, a := range addrs { + if addr.Equal(a) { + return true + } + } + return false +} + +// Unique deduplicates addresses in place, leave only unique addresses. +// It doesn't allocate. +func Unique(addrs []Multiaddr) []Multiaddr { + if len(addrs) == 0 { + return addrs + } + // Use the new slices package here, as the sort function doesn't allocate (sort.Slice does). + slices.SortFunc(addrs, func(a, b Multiaddr) int { return bytes.Compare(a.Bytes(), b.Bytes()) }) + idx := 1 + for i := 1; i < len(addrs); i++ { + if !addrs[i-1].Equal(addrs[i]) { + addrs[idx] = addrs[i] + idx++ + } + } + for i := idx; i < len(addrs); i++ { + addrs[i] = nil + } + return addrs[:idx] +} diff --git a/compattest/internal/prev/multiaddr/main.go b/compattest/internal/prev/multiaddr/main.go new file mode 100644 index 00000000..6d0aa7b2 --- /dev/null +++ b/compattest/internal/prev/multiaddr/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "os" + "strings" + + maddr "github.com/multiformats/go-multiaddr" +) + +var ( + flagHelp bool +) + +func main() { + flag.Usage = func() { + usage := `usage: %s [options] ADDR + +Print details about the given multiaddr. + +Options: +` + fmt.Fprintf(os.Stderr, usage, os.Args[0]) + flag.PrintDefaults() + } + + flag.BoolVar(&flagHelp, "h", false, "display help message") + flag.Parse() + + if flagHelp || len(flag.Args()) == 0 { + flag.Usage() + os.Exit(0) + } + + addrStr := flag.Args()[0] + var addr maddr.Multiaddr + var merr error + if strings.HasPrefix(addrStr, "0x") { + addrBytes, err := hex.DecodeString(addrStr[2:]) + if err != nil { + fmt.Fprintf(os.Stderr, "parse error: %s\n", err) + os.Exit(1) + } + addr, merr = maddr.NewMultiaddrBytes(addrBytes) + } else { + addr, merr = maddr.NewMultiaddr(addrStr) + } + if merr != nil { + fmt.Fprintf(os.Stderr, "parse error: %s\n", merr) + os.Exit(1) + } + + infoCommand(addr) +} + +func infoCommand(addr maddr.Multiaddr) { + var compsJson []string + maddr.ForEach(addr, func(comp maddr.Component) bool { + lengthPrefix := "" + if comp.Protocol().Size == maddr.LengthPrefixedVarSize { + lengthPrefix = "0x" + hex.EncodeToString(maddr.CodeToVarint(len(comp.RawValue()))) + } + + compsJson = append(compsJson, `{`+ + fmt.Sprintf(`"string": "%s", `, comp.String())+ + fmt.Sprintf(`"stringSize": "%d", `, len(comp.String()))+ + fmt.Sprintf(`"packed": "0x%x", `, comp.Bytes())+ + fmt.Sprintf(`"packedSize": "%d", `, len(comp.Bytes()))+ + fmt.Sprintf(`"value": %#v, `, comp.Value())+ + fmt.Sprintf(`"rawValue": "0x%x", `, comp.RawValue())+ + fmt.Sprintf(`"valueSize": "%d", `, len(comp.RawValue()))+ + fmt.Sprintf(`"protocol": "%s", `, comp.Protocol().Name)+ + fmt.Sprintf(`"codec": "%d", `, comp.Protocol().Code)+ + fmt.Sprintf(`"uvarint": "0x%x", `, comp.Protocol().VCode)+ + fmt.Sprintf(`"lengthPrefix": "%s"`, lengthPrefix)+ + `}`) + return true + }) + + addrJson := `{ + "string": "%[1]s", + "stringSize": "%[2]d", + "packed": "0x%[3]x", + "packedSize": "%[4]d", + "components": [ + %[5]s + ] +}` + fmt.Fprintf(os.Stdout, addrJson+"\n", + addr.String(), len(addr.String()), addr.Bytes(), len(addr.Bytes()), strings.Join(compsJson, ",\n ")) +} diff --git a/compattest/internal/prev/multiaddr_test.go b/compattest/internal/prev/multiaddr_test.go new file mode 100644 index 00000000..2d3b73c9 --- /dev/null +++ b/compattest/internal/prev/multiaddr_test.go @@ -0,0 +1,1063 @@ +package multiaddr + +import ( + "bytes" + "encoding/hex" + "fmt" + "math" + "math/rand" + "testing" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" +) + +func newMultiaddr(t *testing.T, a string) Multiaddr { + m, err := NewMultiaddr(a) + if err != nil { + t.Error(err) + } + return m +} + +func TestConstructFails(t *testing.T) { + cases := []string{ + "/ip4", + "/ip4/::1", + "/ip4/fdpsofodsajfdoisa", + "/ip4/::/ipcidr/256", + "/ip6/::/ipcidr/1026", + "/ip6", + "/ip6zone", + "/ip6zone/", + "/ip6zone//ip6/fe80::1", + "/udp", + "/tcp", + "/sctp", + "/udp/65536", + "/tcp/65536", + "/quic/65536", + "/quic-v1/65536", + "/onion/9imaq4ygg2iegci7:80", + "/onion/aaimaq4ygg2iegci7:80", + "/onion/timaq4ygg2iegci7:0", + "/onion/timaq4ygg2iegci7:-1", + "/onion/timaq4ygg2iegci7", + "/onion/timaq4ygg2iegci@:666", + "/onion3/9ww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd7:80", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:0", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:-1", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyy@:666", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA7:80", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:-1", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA@:666", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA7:80", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:-1", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA@:666", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu77", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu:80", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq:-1", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu@", + "/udp/1234/sctp", + "/udp/1234/udt/1234", + "/udp/1234/utp/1234", + "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/udp", + "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/tcp", + "/ip4/127.0.0.1/quic/1234", + "/ip4/127.0.0.1/quic-v1/1234", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmp", // 1 character missing from certhash + "/ip4/127.0.0.1/ipfs", + "/ip4/127.0.0.1/ipfs/tcp", + "/ip4/127.0.0.1/p2p", + "/ip4/127.0.0.1/p2p/tcp", + "/unix", + "/ip4/1.2.3.4/tcp/80/unix", + "/ip4/1.2.3.4/tcp/-1", + "/ip4/127.0.0.1/tcp/9090/http/p2p-webcrt-direct", + fmt.Sprintf("/memory/%d1", uint64(1<<63)), + "/", + "", + "/p2p/QmxoHT6iViN5xAjoz1VZ553cL31U9F94ht3QvWR1FrEbZY", // sha256 multihash with digest len > 32 + } + + for _, a := range cases { + if _, err := NewMultiaddr(a); err == nil { + t.Errorf("should have failed: %s - %s", a, err) + } + } +} + +func TestEmptyMultiaddr(t *testing.T) { + _, err := NewMultiaddrBytes([]byte{}) + if err == nil { + t.Fatal("should have failed to parse empty multiaddr") + } +} + +var good = []string{ + "/ip4/1.2.3.4", + "/ip4/0.0.0.0", + "/ip4/192.0.2.0/ipcidr/24", + "/ip6/::1", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic-v1", + "/ip6/2001:db8::/ipcidr/32", + "/ip6zone/x/ip6/fe80::1", + "/ip6zone/x%y/ip6/fe80::1", + "/ip6zone/x%y/ip6/::", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic-v1", + "/onion/timaq4ygg2iegci7:1234", + "/onion/timaq4ygg2iegci7:80/http", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/udp/8080", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwas", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwassw", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/http", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/udp/8080", + "/udp/0", + "/tcp/0", + "/sctp/0", + "/udp/1234", + "/tcp/1234", + "/sctp/1234", + "/udp/65535", + "/tcp/65535", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/bafzbeigvf25ytwc3akrijfecaotc74udrhcxzh2cx3we5qqnw5vgrei4bm", + "/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV", + "/p2p/k51qzi5uqu5dhb6l8spkdx7yxafegfkee5by8h7lmjh2ehc2sgg34z7c15vzqs", + "/p2p/bafzaajaiaejcalj543iwv2d7pkjt7ykvefrkfu7qjfi6sduakhso4lay6abn2d5u", + "/udp/1234/sctp/1234", + "/udp/1234/udt", + "/udp/1234/utp", + "/tcp/1234/http", + "/tcp/1234/tls/http", + "/tcp/1234/https", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/udp/1234", + "/ip4/127.0.0.1/udp/0", + "/ip4/127.0.0.1/tcp/1234", + "/ip4/127.0.0.1/tcp/1234/", + "/ip4/127.0.0.1/udp/1234/quic", + "/ip4/127.0.0.1/udp/1234/quic-v1", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/unix/a/b/c/d/e", + "/unix/stdio", + "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/tls", + "/ip4/127.0.0.1/tcp/127/tls/ws", + "/ip4/127.0.0.1/tcp/127/noise", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/webrtc-direct", + "/ip4/127.0.0.1/tcp/127/webrtc", + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + "/ip4/127.0.0.1/tcp/0/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV/http-path/foo", + "/ip4/127.0.0.1/tcp/443/tls/sni/example.com/http/http-path/foo", + "/memory/4", +} + +func TestConstructSucceeds(t *testing.T) { + for _, a := range good { + if _, err := NewMultiaddr(a); err != nil { + t.Errorf("should have succeeded: %s -- %s", a, err) + } + } +} + +func TestEqual(t *testing.T) { + m1 := newMultiaddr(t, "/ip4/127.0.0.1/udp/1234") + m2 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234") + m3 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234") + m4 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234/") + + if m1.Equal(m2) { + t.Error("should not be equal") + } + + if m2.Equal(m1) { + t.Error("should not be equal") + } + + if !m2.Equal(m3) { + t.Error("should be equal") + } + + if !m3.Equal(m2) { + t.Error("should be equal") + } + + if !m1.Equal(m1) { + t.Error("should be equal") + } + + if !m2.Equal(m4) { + t.Error("should be equal") + } + + if !m4.Equal(m3) { + t.Error("should be equal") + } +} + +// TestNilInterface makes sure funcs that accept a multiaddr interface don't +// panic if it's passed a nil interface. +func TestNilInterface(t *testing.T) { + m1 := newMultiaddr(t, "/ip4/127.0.0.1/udp/1234") + var m2 Multiaddr + m1.Equal(m2) + m1.Encapsulate(m2) + m1.Decapsulate(m2) + + // Test components + c, _ := SplitFirst(m1) + c.Equal(m2) + c.Encapsulate(m2) + c.Decapsulate(m2) + + // Util funcs + _ = Split(m2) + _, _ = SplitFirst(m2) + _, _ = SplitLast(m2) + ForEach(m2, func(c Component) bool { return true }) +} + +func TestStringToBytes(t *testing.T) { + + testString := func(s string, h string) { + b1, err := hex.DecodeString(h) + if err != nil { + t.Error("failed to decode hex", h) + } + + // t.Log("196", h, []byte(b1)) + + b2, err := stringToBytes(s) + if err != nil { + t.Error("failed to convert", s, err) + } + + if !bytes.Equal(b1, b2) { + t.Error("failed to convert \n", s, "to\n", hex.EncodeToString(b1), "got\n", hex.EncodeToString(b2)) + } + + if err := validateBytes(b2); err != nil { + t.Error(err, "len:", len(b2)) + } + } + + testString("/ip4/127.0.0.1/udp/1234", "047f000001910204d2") + testString("/ip4/127.0.0.1/tcp/4321", "047f0000010610e1") + testString("/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321", "047f000001910204d2047f0000010610e1") + testString("/onion/aaimaq4ygg2iegci:80", "bc030010c0439831b48218480050") + testString("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", "bd03adadec040be047f9658668b11a504f3155001f231a37f54c4476c07fb4cc139ed7e30304d2") + testString("/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "be0383038d3fc8c976a86ae4e78ba378e75ec41bc9ab1542a9cb422581987e118f5cb0c024f3639d6ad9b3aff613672f07bfbbbfc2f920ef910534ecaa6ff9c03e0fa4872a764d2fce6d4cfc5a5a9800cd95944cc9ef0241f753fe71494a175f334b35682459acadc4076428ab49b5a83a49d2ea2366b06461e4a559b0111fa750e0de0c138a94d1231ed5979572ff53922905636221994bdabc44bd0c17fef11622b16432db3f193400af53cc61aa9bfc0c4c8d874b41a6e18732f0b60f5662ef1a89c80589dd8366c90bb58bb85ead56356aba2a244950ca170abbd01094539014f84bdd383e4a10e00cee63dfc3e809506e2d9b54edbdca1bace6eaa119e68573d30533791fba830f5d80be5c051a77c09415e3b8fe3139400848be5244b8ae96bb0c4a24f819cba0488f34985eac741d3359180bd72cafa1559e4c19f54ea8cedbb6a5afde4319396eb92aab340c60a50cc2284580cb3ad09017e8d9abc60269b3d8d687680bd86ce834412273d4f2e3bf68dd3d6fe87e2426ac658cd5c77fd5c0aa000000") + testString("/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "bf0320efbcd45d0c5dc79781ac6f20ea5055a036afb48d45a52e7d68ec7d4338919e69") + +} + +func TestBytesToString(t *testing.T) { + + testString := func(s1 string, h string) { + t.Helper() + b, err := hex.DecodeString(h) + if err != nil { + t.Error("failed to decode hex", h) + } + + if err := validateBytes(b); err != nil { + t.Error(err) + } + + s2, err := bytesToString(b) + if err != nil { + t.Log("236", s1, ":", string(h), ":", s2) + t.Error("failed to convert", b, err) + } + + if s1 != s2 { + t.Error("failed to convert", b, "to", s1, "got", s2) + } + } + + testString("/ip4/127.0.0.1/udp/1234", "047f000001910204d2") + testString("/ip4/127.0.0.1/tcp/4321", "047f0000010610e1") + testString("/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321", "047f000001910204d2047f0000010610e1") + testString("/onion/aaimaq4ygg2iegci:80", "bc030010c0439831b48218480050") + testString("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", "bd03adadec040be047f9658668b11a504f3155001f231a37f54c4476c07fb4cc139ed7e30304d2") + testString("/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "be0383038d3fc8c976a86ae4e78ba378e75ec41bc9ab1542a9cb422581987e118f5cb0c024f3639d6ad9b3aff613672f07bfbbbfc2f920ef910534ecaa6ff9c03e0fa4872a764d2fce6d4cfc5a5a9800cd95944cc9ef0241f753fe71494a175f334b35682459acadc4076428ab49b5a83a49d2ea2366b06461e4a559b0111fa750e0de0c138a94d1231ed5979572ff53922905636221994bdabc44bd0c17fef11622b16432db3f193400af53cc61aa9bfc0c4c8d874b41a6e18732f0b60f5662ef1a89c80589dd8366c90bb58bb85ead56356aba2a244950ca170abbd01094539014f84bdd383e4a10e00cee63dfc3e809506e2d9b54edbdca1bace6eaa119e68573d30533791fba830f5d80be5c051a77c09415e3b8fe3139400848be5244b8ae96bb0c4a24f819cba0488f34985eac741d3359180bd72cafa1559e4c19f54ea8cedbb6a5afde4319396eb92aab340c60a50cc2284580cb3ad09017e8d9abc60269b3d8d687680bd86ce834412273d4f2e3bf68dd3d6fe87e2426ac658cd5c77fd5c0aa000000") + testString("/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "bf0320efbcd45d0c5dc79781ac6f20ea5055a036afb48d45a52e7d68ec7d4338919e69") +} + +func TestBytesSplitAndJoin(t *testing.T) { + + testString := func(s string, res []string) { + m, err := NewMultiaddr(s) + if err != nil { + t.Fatal("failed to convert", s, err) + } + + split := Split(m) + if len(split) != len(res) { + t.Error("not enough split components", split) + return + } + + for i, a := range split { + if a.String() != res[i] { + t.Errorf("split component failed: %s != %s", a, res[i]) + } + } + + joined := Join(split...) + if !m.Equal(joined) { + t.Errorf("joined components failed: %s != %s", m, joined) + } + + for i, a := range split { + if a.String() != res[i] { + t.Errorf("split component failed: %s != %s", a, res[i]) + } + } + } + + testString("/ip4/1.2.3.4/udp/1234", []string{"/ip4/1.2.3.4", "/udp/1234"}) + testString("/ip4/1.2.3.4/tcp/1/ip4/2.3.4.5/udp/2", + []string{"/ip4/1.2.3.4", "/tcp/1", "/ip4/2.3.4.5", "/udp/2"}) + testString("/ip4/1.2.3.4/utp/ip4/2.3.4.5/udp/2/udt", + []string{"/ip4/1.2.3.4", "/utp", "/ip4/2.3.4.5", "/udp/2", "/udt"}) +} + +func TestProtocols(t *testing.T) { + m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234") + } + + ps := m.Protocols() + if ps[0].Code != ProtocolWithName("ip4").Code { + t.Error(ps[0], ProtocolWithName("ip4")) + t.Error("failed to get ip4 protocol") + } + + if ps[1].Code != ProtocolWithName("udp").Code { + t.Error(ps[1], ProtocolWithName("udp")) + t.Error("failed to get udp protocol") + } + +} + +func TestProtocolsWithString(t *testing.T) { + pwn := ProtocolWithName + good := map[string][]Protocol{ + "/ip4": {pwn("ip4")}, + "/ip4/tcp": {pwn("ip4"), pwn("tcp")}, + "ip4/tcp/udp/ip6": {pwn("ip4"), pwn("tcp"), pwn("udp"), pwn("ip6")}, + "////////ip4/tcp": {pwn("ip4"), pwn("tcp")}, + "ip4/udp/////////": {pwn("ip4"), pwn("udp")}, + "////////ip4/tcp////////": {pwn("ip4"), pwn("tcp")}, + } + + for s, ps1 := range good { + ps2, err := ProtocolsWithString(s) + if err != nil { + t.Errorf("ProtocolsWithString(%s) should have succeeded", s) + } + + for i, ps1p := range ps1 { + ps2p := ps2[i] + if ps1p.Code != ps2p.Code { + t.Errorf("mismatch: %s != %s, %s", ps1p.Name, ps2p.Name, s) + } + } + } + + bad := []string{ + "dsijafd", // bogus proto + "/ip4/tcp/fidosafoidsa", // bogus proto + "////////ip4/tcp/21432141/////////", // bogus proto + "////////ip4///////tcp/////////", // empty protos in between + } + + for _, s := range bad { + if _, err := ProtocolsWithString(s); err == nil { + t.Errorf("ProtocolsWithString(%s) should have failed", s) + } + } + +} + +func TestEncapsulate(t *testing.T) { + m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + t.Error(err) + } + + m2, err := NewMultiaddr("/udp/5678") + if err != nil { + t.Error(err) + } + + b := m.Encapsulate(m2) + if s := b.String(); s != "/ip4/127.0.0.1/udp/1234/udp/5678" { + t.Error("encapsulate /ip4/127.0.0.1/udp/1234/udp/5678 failed.", s) + } + + m3, _ := NewMultiaddr("/udp/5678") + c := b.Decapsulate(m3) + if s := c.String(); s != "/ip4/127.0.0.1/udp/1234" { + t.Error("decapsulate /udp failed.", "/ip4/127.0.0.1/udp/1234", s) + } + + m4, _ := NewMultiaddr("/ip4/127.0.0.1") + d := c.Decapsulate(m4) + if d != nil { + t.Error("decapsulate /ip4 failed: ", d) + } +} + +func TestDecapsulateComment(t *testing.T) { + // shows the behavior from the interface comment + m := StringCast("/ip4/1.2.3.4/tcp/80") + rest := m.Decapsulate(StringCast("/tcp/80")) + if rest.String() != "/ip4/1.2.3.4" { + t.Fatalf("Documented behavior is not correct. Expected %v saw %v", "/ip4/1.2.3.4/", rest.String()) + } + + m = StringCast("/ip4/1.2.3.4/tcp/80") + rest = m.Decapsulate(StringCast("/udp/80")) + if !rest.Equal(m) { + t.Fatalf("Documented behavior is not correct. Expected %v saw %v", "/ip4/1.2.3.4/tcp/80", rest.String()) + } + + m = StringCast("/ip4/1.2.3.4/tcp/80") + rest = m.Decapsulate(StringCast("/ip4/1.2.3.4")) + require.Nil(t, rest, "expected a nil multiaddr if we decapsulate everything") +} + +func TestDecapsulate(t *testing.T) { + t.Run("right is nil", func(t *testing.T) { + left := StringCast("/ip4/1.2.3.4/tcp/1") + var right Multiaddr + left.Decapsulate(right) + }) + + testcases := []struct { + left, right, expected string + }{ + {"/ip4/1.2.3.4/tcp/1234", "/ip4/1.2.3.4", ""}, + {"/ip4/1.2.3.4", "/ip4/1.2.3.4/tcp/1234", "/ip4/1.2.3.4"}, + {"/ip4/1.2.3.5/tcp/1234", "/ip4/5.3.2.1", "/ip4/1.2.3.5/tcp/1234"}, + {"/ip4/1.2.3.5/udp/1234/quic-v1", "/udp/1234", "/ip4/1.2.3.5"}, + {"/ip4/1.2.3.6/udp/1234/quic-v1", "/udp/1234/quic-v1", "/ip4/1.2.3.6"}, + {"/ip4/1.2.3.7/tcp/1234", "/ws", "/ip4/1.2.3.7/tcp/1234"}, + {"/dnsaddr/wss.com/tcp/4001", "/ws", "/dnsaddr/wss.com/tcp/4001"}, + {"/dnsaddr/wss.com/tcp/4001/ws", "/wss", "/dnsaddr/wss.com/tcp/4001/ws"}, + {"/dnsaddr/wss.com/ws", "/wss", "/dnsaddr/wss.com/ws"}, + {"/dnsaddr/wss.com/ws", "/dnsaddr/wss.com", ""}, + {"/dnsaddr/wss.com/tcp/4001/wss", "/wss", "/dnsaddr/wss.com/tcp/4001"}, + } + + for _, tc := range testcases { + t.Run(tc.left, func(t *testing.T) { + left := StringCast(tc.left) + right := StringCast(tc.right) + actualMa := left.Decapsulate(right) + + if tc.expected == "" { + require.Nil(t, actualMa, "expected nil") + return + } + + actual := actualMa.String() + expected := StringCast(tc.expected).String() + require.Equal(t, expected, actual) + }) + } +} + +func assertValueForProto(t *testing.T, a Multiaddr, p int, exp string) { + t.Logf("checking for %s in %s", ProtocolWithCode(p).Name, a) + fv, err := a.ValueForProtocol(p) + if err != nil { + t.Fatal(err) + } + + if fv != exp { + t.Fatalf("expected %q for %d in %s, but got %q instead", exp, p, a, fv) + } +} + +func TestGetValue(t *testing.T) { + a := newMultiaddr(t, "/ip4/127.0.0.1/utp/tcp/5555/udp/1234/tls/utp/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") + assertValueForProto(t, a, P_IP4, "127.0.0.1") + assertValueForProto(t, a, P_UTP, "") + assertValueForProto(t, a, P_TLS, "") + assertValueForProto(t, a, P_TCP, "5555") + assertValueForProto(t, a, P_UDP, "1234") + assertValueForProto(t, a, P_IPFS, "QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") + assertValueForProto(t, a, P_P2P, "QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") + + _, err := a.ValueForProtocol(P_IP6) + switch err { + case ErrProtocolNotFound: + break + case nil: + t.Fatal("expected value lookup to fail") + default: + t.Fatalf("expected ErrProtocolNotFound but got: %s", err) + } + + a = newMultiaddr(t, "/ip4/0.0.0.0") // only one addr + assertValueForProto(t, a, P_IP4, "0.0.0.0") + + a = newMultiaddr(t, "/ip4/0.0.0.0/ip4/0.0.0.0/ip4/0.0.0.0") // same sub-addr + assertValueForProto(t, a, P_IP4, "0.0.0.0") + + a = newMultiaddr(t, "/ip4/0.0.0.0/udp/12345/utp") // ending in a no-value one. + assertValueForProto(t, a, P_IP4, "0.0.0.0") + assertValueForProto(t, a, P_UDP, "12345") + assertValueForProto(t, a, P_UTP, "") + + a = newMultiaddr(t, "/ip4/0.0.0.0/unix/a/b/c/d") // ending in a path one. + assertValueForProto(t, a, P_IP4, "0.0.0.0") + assertValueForProto(t, a, P_UNIX, "/a/b/c/d") +} + +func FuzzNewMultiaddrBytes(f *testing.F) { + for _, v := range good { + ma, err := NewMultiaddr(v) + if err != nil { + f.Fatal(err) + } + f.Add(ma.Bytes()) + } + + f.Fuzz(func(t *testing.T, b []byte) { + // just checking that it doesn't panic + ma, err := NewMultiaddrBytes(b) + if err == nil { + // for any valid multiaddrs, make sure these calls don't panic + ma.Protocols() + roundTripBytes(t, ma) + roundTripString(t, ma) + } + }) +} + +func FuzzNewMultiaddrString(f *testing.F) { + for _, v := range good { + if _, err := NewMultiaddr(v); err != nil { + // Validate maddrs + f.Fatal(err) + } + f.Add(v) + } + f.Fuzz(func(t *testing.T, s string) { + // just checking that it doesn't panic + ma, err := NewMultiaddr(s) + if err == nil { + // for any valid multiaddrs, make sure these calls don't panic + ma.Protocols() + roundTripBytes(t, ma) + roundTripString(t, ma) + } + }) +} + +func roundTripBytes(t *testing.T, orig Multiaddr) { + m2, err := NewMultiaddrBytes(orig.Bytes()) + if err != nil { + t.Fatalf("failed to parse maddr back from ma.Bytes, %v: %v", orig, err) + } + if !m2.Equal(orig) { + t.Fatalf("unequal maddr after roundTripBytes %v %v", orig, m2) + } +} + +func roundTripString(t *testing.T, orig Multiaddr) { + m2, err := NewMultiaddr(orig.String()) + if err != nil { + t.Fatalf("failed to parse maddr back from ma.String, %v: %v", orig, err) + } + if !m2.Equal(orig) { + t.Fatalf("unequal maddr after roundTripString %v %v\n% 02x\n% 02x\n", orig, m2, orig.Bytes(), m2.Bytes()) + } +} + +func TestBinaryRepresentation(t *testing.T) { + expected := []byte{0x4, 0x7f, 0x0, 0x0, 0x1, 0x91, 0x2, 0x4, 0xd2} + ma, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + t.Error(err) + } + + if !bytes.Equal(ma.Bytes(), expected) { + t.Errorf("expected %x, got %x", expected, ma.Bytes()) + } +} + +func TestRoundTrip(t *testing.T) { + for _, s := range []string{ + "/unix/a/b/c/d", + "/ip6/::ffff:127.0.0.1/tcp/111", + "/ip4/127.0.0.1/tcp/123", + "/ip4/127.0.0.1/tcp/123/tls", + "/ip4/127.0.0.1/udp/123", + "/ip4/127.0.0.1/udp/123/ip6/::", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g", + "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP", + "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP/unix/a/b/c", + "/http-path/tmp%2Fbar", + } { + ma, err := NewMultiaddr(s) + if err != nil { + t.Errorf("error when parsing %q: %s", s, err) + continue + } + if ma.String() != s { + t.Errorf("failed to round trip %q", s) + } + } +} + +func TestIPFSvP2P(t *testing.T) { + var ( + p2pAddr = "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP" + ipfsAddr = "/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP" + ) + + for _, s := range []string{p2pAddr, ipfsAddr} { + ma, err := NewMultiaddr(s) + if err != nil { + t.Errorf("error when parsing %q: %s", s, err) + } + if ma.String() != p2pAddr { + t.Errorf("expected %q, got %q", p2pAddr, ma.String()) + } + } +} + +func TestInvalidP2PAddrBytes(t *testing.T) { + badAddr := "a503221221c05877cbae039d70a5e600ea02c6f9f2942439285c9e344e26f8d280c850fad6" + bts, err := hex.DecodeString(badAddr) + if err != nil { + t.Fatal(err) + } + ma, err := NewMultiaddrBytes(bts) + if err == nil { + t.Error("should have failed") + // Check for panic + _ = ma.String() + } +} + +func TestInvalidP2PAddrString(t *testing.T) { + hashedData, err := mh.Sum([]byte("test"), mh.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + // using MD5 since it's not a valid data codec + unknownCodecCID := cid.NewCidV1(mh.MD5, hashedData).String() + + badStringAddrs := []string{ + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl-", // invalid multibase encoding + "/p2p/?unknownmultibase", // invalid multibase encoding + "/p2p/k2jmtxwoe2phm1hbqp0e7nufqf6umvuu2e9qd7ana7h411a0haqj6i2z", // non-libp2p-key codec + "/p2p/" + unknownCodecCID, // impossible codec + } + for _, a := range badStringAddrs { + ma, err := NewMultiaddr(a) + if err == nil { + t.Error("should have failed") + // Check for panic + _ = ma.String() + } + } +} + +func TestZone(t *testing.T) { + ip6String := "/ip6zone/eth0/ip6/::1" + ip6Bytes := []byte{ + 0x2a, 4, + 'e', 't', 'h', '0', + 0x29, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1, + } + + ma, err := NewMultiaddr(ip6String) + if err != nil { + t.Error(err) + } + if !bytes.Equal(ma.Bytes(), ip6Bytes) { + t.Errorf("expected %x, got %x", ip6Bytes, ma.Bytes()) + } + + ma2, err2 := NewMultiaddrBytes(ip6Bytes) + if err2 != nil { + t.Error(err) + } + if ma2.String() != ip6String { + t.Errorf("expected %s, got %s", ip6String, ma2.String()) + } +} + +func TestBinaryMarshaler(t *testing.T) { + addr := newMultiaddr(t, "/ip4/0.0.0.0/tcp/4001/tls") + b, err := addr.MarshalBinary() + if err != nil { + t.Fatal(err) + } + + var addr2 multiaddr + if err = addr2.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + if !addr.Equal(&addr2) { + t.Error("expected equal addresses in circular marshaling test") + } +} + +func TestTextMarshaler(t *testing.T) { + addr := newMultiaddr(t, "/ip4/0.0.0.0/tcp/4001/tls") + b, err := addr.MarshalText() + if err != nil { + t.Fatal(err) + } + + var addr2 multiaddr + if err = addr2.UnmarshalText(b); err != nil { + t.Fatal(err) + } + if !addr.Equal(&addr2) { + t.Error("expected equal addresses in circular marshaling test") + } +} + +func TestJSONMarshaler(t *testing.T) { + addr := newMultiaddr(t, "/ip4/0.0.0.0/tcp/4001/tls") + b, err := addr.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + var addr2 multiaddr + if err = addr2.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + if !addr.Equal(&addr2) { + t.Error("expected equal addresses in circular marshaling test") + } +} + +func TestComponentBinaryMarshaler(t *testing.T) { + comp, err := NewComponent("ip4", "0.0.0.0") + if err != nil { + t.Fatal(err) + } + b, err := comp.MarshalBinary() + if err != nil { + t.Fatal(err) + } + + comp2 := &Component{} + if err = comp2.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + if !comp.Equal(comp2) { + t.Error("expected equal components in circular marshaling test") + } +} + +func TestComponentTextMarshaler(t *testing.T) { + comp, err := NewComponent("ip4", "0.0.0.0") + if err != nil { + t.Fatal(err) + } + b, err := comp.MarshalText() + if err != nil { + t.Fatal(err) + } + + comp2 := &Component{} + if err = comp2.UnmarshalText(b); err != nil { + t.Fatal(err) + } + if !comp.Equal(comp2) { + t.Error("expected equal components in circular marshaling test") + } +} + +func TestComponentJSONMarshaler(t *testing.T) { + comp, err := NewComponent("ip4", "0.0.0.0") + if err != nil { + t.Fatal(err) + } + b, err := comp.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + comp2 := &Component{} + if err = comp2.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + if !comp.Equal(comp2) { + t.Error("expected equal components in circular marshaling test") + } +} + +func TestFilterAddrs(t *testing.T) { + bad := []Multiaddr{ + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), + } + good := []Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/1.1.1.1/tcp/999"), + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), + } + goodAndBad := append(good, bad...) + + filter := func(addr Multiaddr) bool { + return addr.Protocols()[0].Code == P_IP4 + } + + require.Empty(t, FilterAddrs(bad, filter)) + require.ElementsMatch(t, FilterAddrs(good, filter), good) + require.ElementsMatch(t, FilterAddrs(goodAndBad, filter), good) +} + +func TestContains(t *testing.T) { + a1 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234") + a2 := newMultiaddr(t, "/ip4/1.1.1.1/tcp/999") + a3 := newMultiaddr(t, "/ip4/1.2.3.4/udp/443/quic") + a4 := newMultiaddr(t, "/ip4/1.2.3.4/udp/443/quic-v1") + addrs := []Multiaddr{a1, a2, a3, a4} + + require.True(t, Contains(addrs, a1)) + require.True(t, Contains(addrs, a2)) + require.True(t, Contains(addrs, a3)) + require.True(t, Contains(addrs, a4)) + require.False(t, Contains(addrs, newMultiaddr(t, "/ip4/4.3.2.1/udp/1234/utp"))) + require.False(t, Contains(nil, a1)) +} + +func TestUniqueAddrs(t *testing.T) { + tcpAddr := StringCast("/ip4/127.0.0.1/tcp/1234") + quicAddr := StringCast("/ip4/127.0.0.1/udp/1234/quic-v1") + wsAddr := StringCast("/ip4/127.0.0.1/tcp/1234/ws") + + type testcase struct { + in, out []Multiaddr + } + + for i, tc := range []testcase{ + {in: nil, out: nil}, + {in: []Multiaddr{tcpAddr}, out: []Multiaddr{tcpAddr}}, + {in: []Multiaddr{tcpAddr, tcpAddr, tcpAddr}, out: []Multiaddr{tcpAddr}}, + {in: []Multiaddr{tcpAddr, quicAddr, tcpAddr}, out: []Multiaddr{tcpAddr, quicAddr}}, + {in: []Multiaddr{tcpAddr, quicAddr, wsAddr}, out: []Multiaddr{tcpAddr, quicAddr, wsAddr}}, + } { + tc := tc + t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { + deduped := Unique(tc.in) + for _, a := range tc.out { + require.Contains(t, deduped, a) + } + }) + } +} + +func BenchmarkUniqueAddrs(b *testing.B) { + b.ReportAllocs() + var addrs []Multiaddr + r := rand.New(rand.NewSource(1234)) + for i := 0; i < 100; i++ { + tcpAddr := StringCast(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", r.Intn(math.MaxUint16))) + quicAddr := StringCast(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic-v1", r.Intn(math.MaxUint16))) + wsAddr := StringCast(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d/ws", r.Intn(math.MaxUint16))) + addrs = append(addrs, tcpAddr, tcpAddr, quicAddr, quicAddr, wsAddr) + } + for _, sz := range []int{10, 20, 30, 50, 100} { + b.Run(fmt.Sprintf("%d", sz), func(b *testing.B) { + items := make([]Multiaddr, sz) + for i := 0; i < b.N; i++ { + copy(items, addrs[:sz]) + Unique(items) + } + }) + } +} + +func TestDNS(t *testing.T) { + b := []byte("7*000000000000000000000000000000000000000000") + a, err := NewMultiaddrBytes(b) + if err != nil { + t.Fatal(err) + } + aa := StringCast(a.String()) + if !a.Equal(aa) { + t.Fatal("expected equality") + } +} + +func TestHTTPPath(t *testing.T) { + t.Run("bad addr", func(t *testing.T) { + badAddr := "/http-path/thisIsMissingAfullByte%f" + _, err := NewMultiaddr(badAddr) + require.Error(t, err) + }) + + t.Run("only reads the http-path part", func(t *testing.T) { + addr := "/http-path/tmp%2Fbar/p2p-circuit" // The http-path only reference the part immediately after it. It does not include the rest of the multiaddr (like the /path component sometimes does) + m, err := NewMultiaddr(addr) + require.NoError(t, err) + m.ValueForProtocol(P_HTTP_PATH) + v, err := m.ValueForProtocol(P_HTTP_PATH) + require.NoError(t, err) + require.Equal(t, "tmp%2Fbar", v) + }) + + t.Run("round trip", func(t *testing.T) { + cases := []string{ + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + "/ip4/127.0.0.1/tcp/0/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV/http-path/foo", + "/ip4/127.0.0.1/tcp/443/tls/sni/example.com/http/http-path/foo", + } + for _, c := range cases { + m, err := NewMultiaddr(c) + require.NoError(t, err) + require.Equal(t, c, m.String()) + } + }) + + t.Run("value for protocol", func(t *testing.T) { + m := StringCast("/http-path/tmp%2Fbar") + v, err := m.ValueForProtocol(P_HTTP_PATH) + require.NoError(t, err) + // This gives us the url escaped version + require.Equal(t, "tmp%2Fbar", v) + + // If we want the raw unescaped version, we can use the component and read it + _, component := SplitLast(m) + require.Equal(t, "tmp/bar", string(component.RawValue())) + }) +} + +func FuzzSplitRoundtrip(f *testing.F) { + for _, v := range good { + f.Add(v) + } + otherMultiaddr := StringCast("/udp/1337") + + f.Fuzz(func(t *testing.T, addrStr string) { + addr, err := NewMultiaddr(addrStr) + if err != nil { + t.Skip() // Skip inputs that are not valid multiaddrs + } + + // Test SplitFirst + first, rest := SplitFirst(addr) + joined := Join(first, rest) + require.Equal(t, addr, joined, "SplitFirst and Join should round-trip") + + // Test SplitLast + rest, last := SplitLast(addr) + joined = Join(rest, last) + require.Equal(t, addr, joined, "SplitLast and Join should round-trip") + + p := addr.Protocols() + if len(p) == 0 { + t.Skip() + } + + tryPubMethods := func(a Multiaddr) { + if a == nil { + return + } + _ = a.Equal(otherMultiaddr) + _ = a.Bytes() + _ = a.String() + _ = a.Protocols() + _ = a.Encapsulate(otherMultiaddr) + _ = a.Decapsulate(otherMultiaddr) + _, _ = a.ValueForProtocol(P_TCP) + } + + for _, proto := range p { + splitFunc := func(c Component) bool { + return c.Protocol().Code == proto.Code + } + beforeC, after := SplitFirst(addr) + joined = Join(beforeC, after) + require.Equal(t, addr, joined) + tryPubMethods(after) + + before, afterC := SplitLast(addr) + joined = Join(before, afterC) + require.Equal(t, addr, joined) + tryPubMethods(before) + + before, after = SplitFunc(addr, splitFunc) + joined = Join(before, after) + require.Equal(t, addr, joined) + tryPubMethods(before) + tryPubMethods(after) + } + }) +} diff --git a/compattest/internal/prev/net/convert.go b/compattest/internal/prev/net/convert.go new file mode 100644 index 00000000..4603fa22 --- /dev/null +++ b/compattest/internal/prev/net/convert.go @@ -0,0 +1,358 @@ +package manet + +import ( + "errors" + "fmt" + "net" + "path/filepath" + "runtime" + "strings" + + ma "github.com/multiformats/go-multiaddr" +) + +var errIncorrectNetAddr = fmt.Errorf("incorrect network addr conversion") +var errNotIP = fmt.Errorf("multiaddr does not start with an IP address") + +// FromNetAddr converts a net.Addr type to a Multiaddr. +func FromNetAddr(a net.Addr) (ma.Multiaddr, error) { + return defaultCodecs.FromNetAddr(a) +} + +// FromNetAddr converts a net.Addr to Multiaddress. +func (cm *CodecMap) FromNetAddr(a net.Addr) (ma.Multiaddr, error) { + if a == nil { + return nil, fmt.Errorf("nil multiaddr") + } + p, err := cm.getAddrParser(a.Network()) + if err != nil { + return nil, err + } + + return p(a) +} + +// ToNetAddr converts a Multiaddr to a net.Addr +// Must be ThinWaist. acceptable protocol stacks are: +// /ip{4,6}/{tcp, udp} +func ToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { + return defaultCodecs.ToNetAddr(maddr) +} + +// ToNetAddr converts a Multiaddress to a standard net.Addr. +func (cm *CodecMap) ToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { + protos := maddr.Protocols() + final := protos[len(protos)-1] + + p, err := cm.getMaddrParser(final.Name) + if err != nil { + return nil, err + } + + return p(maddr) +} + +// MultiaddrToIPNet converts a multiaddr to an IPNet. Useful for seeing if another IP address is contained within this multiaddr network+mask +func MultiaddrToIPNet(m ma.Multiaddr) (*net.IPNet, error) { + var ipString string + var mask string + + ma.ForEach(m, func(c ma.Component) bool { + if c.Protocol().Code == ma.P_IP4 || c.Protocol().Code == ma.P_IP6 { + ipString = c.Value() + } + if c.Protocol().Code == ma.P_IPCIDR { + mask = c.Value() + } + return ipString == "" || mask == "" + }) + + if ipString == "" { + return nil, errors.New("no ip protocol found") + } + + if mask == "" { + return nil, errors.New("no mask found") + } + + _, ipnet, err := net.ParseCIDR(ipString + "/" + string(mask)) + return ipnet, err +} + +func parseBasicNetMaddr(maddr ma.Multiaddr) (net.Addr, error) { + network, host, err := DialArgs(maddr) + if err != nil { + return nil, err + } + + switch network { + case "tcp", "tcp4", "tcp6": + return net.ResolveTCPAddr(network, host) + case "udp", "udp4", "udp6": + return net.ResolveUDPAddr(network, host) + case "ip", "ip4", "ip6": + return net.ResolveIPAddr(network, host) + case "unix": + return net.ResolveUnixAddr(network, host) + } + + return nil, fmt.Errorf("network not supported: %s", network) +} + +func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) { + switch { + case ip.To4() != nil: + return ma.NewComponent("ip4", ip.String()) + case ip.To16() != nil: + ip6, err := ma.NewComponent("ip6", ip.String()) + if err != nil { + return nil, err + } + if zone == "" { + return ip6, nil + } else { + zone, err := ma.NewComponent("ip6zone", zone) + if err != nil { + return nil, err + } + return zone.Encapsulate(ip6), nil + } + default: + return nil, errIncorrectNetAddr + } +} + +// FromIP converts a net.IP type to a Multiaddr. +func FromIP(ip net.IP) (ma.Multiaddr, error) { + return FromIPAndZone(ip, "") +} + +// ToIP converts a Multiaddr to a net.IP when possible +func ToIP(addr ma.Multiaddr) (net.IP, error) { + var ip net.IP + ma.ForEach(addr, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_IP6ZONE: + // we can't return these anyways. + return true + case ma.P_IP6, ma.P_IP4: + ip = net.IP(c.RawValue()) + return false + } + return false + }) + if ip == nil { + return nil, errNotIP + } + return ip, nil +} + +// DialArgs is a convenience function that returns network and address as +// expected by net.Dial. See https://godoc.org/net#Dial for an overview of +// possible return values (we do not support the unixpacket ones yet). Unix +// addresses do not, at present, compose. +func DialArgs(m ma.Multiaddr) (string, string, error) { + zone, network, ip, port, hostname, err := dialArgComponents(m) + if err != nil { + return "", "", err + } + + // If we have a hostname (dns*), we don't want any fancy ipv6 formatting + // logic (zone, brackets, etc.). + if hostname { + switch network { + case "ip", "ip4", "ip6": + return network, ip, nil + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + return network, ip + ":" + port, nil + } + // Hostname is only true when network is one of the above. + panic("unreachable") + } + + switch network { + case "ip6": + if zone != "" { + ip += "%" + zone + } + fallthrough + case "ip4": + return network, ip, nil + case "tcp4", "udp4": + return network, ip + ":" + port, nil + case "tcp6", "udp6": + if zone != "" { + ip += "%" + zone + } + return network, "[" + ip + "]" + ":" + port, nil + case "unix": + if runtime.GOOS == "windows" { + // convert /c:/... to c:\... + ip = filepath.FromSlash(strings.TrimLeft(ip, "/")) + } + return network, ip, nil + default: + return "", "", fmt.Errorf("%s is not a 'thin waist' address", m) + } +} + +// dialArgComponents extracts the raw pieces used in dialing a Multiaddr +func dialArgComponents(m ma.Multiaddr) (zone, network, ip, port string, hostname bool, err error) { + ma.ForEach(m, func(c ma.Component) bool { + switch network { + case "": + switch c.Protocol().Code { + case ma.P_IP6ZONE: + if zone != "" { + err = fmt.Errorf("%s has multiple zones", m) + return false + } + zone = c.Value() + return true + case ma.P_IP6: + network = "ip6" + ip = c.Value() + return true + case ma.P_IP4: + if zone != "" { + err = fmt.Errorf("%s has ip4 with zone", m) + return false + } + network = "ip4" + ip = c.Value() + return true + case ma.P_DNS: + network = "ip" + hostname = true + ip = c.Value() + return true + case ma.P_DNS4: + network = "ip4" + hostname = true + ip = c.Value() + return true + case ma.P_DNS6: + network = "ip6" + hostname = true + ip = c.Value() + return true + case ma.P_UNIX: + network = "unix" + ip = c.Value() + return false + } + case "ip": + switch c.Protocol().Code { + case ma.P_UDP: + network = "udp" + case ma.P_TCP: + network = "tcp" + default: + return false + } + port = c.Value() + case "ip4": + switch c.Protocol().Code { + case ma.P_UDP: + network = "udp4" + case ma.P_TCP: + network = "tcp4" + default: + return false + } + port = c.Value() + case "ip6": + switch c.Protocol().Code { + case ma.P_UDP: + network = "udp6" + case ma.P_TCP: + network = "tcp6" + default: + return false + } + port = c.Value() + } + // Done. + return false + }) + return +} + +func parseTCPNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.TCPAddr) + if !ok { + return nil, errIncorrectNetAddr + } + + // Get IP Addr + ipm, err := FromIPAndZone(ac.IP, ac.Zone) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Get TCP Addr + tcpm, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", ac.Port)) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Encapsulate + return ipm.Encapsulate(tcpm), nil +} + +func parseUDPNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.UDPAddr) + if !ok { + return nil, errIncorrectNetAddr + } + + // Get IP Addr + ipm, err := FromIPAndZone(ac.IP, ac.Zone) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Get UDP Addr + udpm, err := ma.NewMultiaddr(fmt.Sprintf("/udp/%d", ac.Port)) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Encapsulate + return ipm.Encapsulate(udpm), nil +} + +func parseIPNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.IPAddr) + if !ok { + return nil, errIncorrectNetAddr + } + return FromIPAndZone(ac.IP, ac.Zone) +} + +func parseIPPlusNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.IPNet) + if !ok { + return nil, errIncorrectNetAddr + } + return FromIP(ac.IP) +} + +func parseUnixNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.UnixAddr) + if !ok { + return nil, errIncorrectNetAddr + } + + path := ac.Name + if runtime.GOOS == "windows" { + // Convert c:\foobar\... to c:/foobar/... + path = filepath.ToSlash(path) + } + if len(path) == 0 || path[0] != '/' { + // convert "" and "c:/..." to "/..." + path = "/" + path + } + + return ma.NewComponent("unix", path) +} diff --git a/compattest/internal/prev/net/convert_test.go b/compattest/internal/prev/net/convert_test.go new file mode 100644 index 00000000..a36f5e9e --- /dev/null +++ b/compattest/internal/prev/net/convert_test.go @@ -0,0 +1,266 @@ +package manet + +import ( + "net" + "runtime" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +type GenFunc func() (ma.Multiaddr, error) + +func testConvert(t *testing.T, s string, gen GenFunc) { + m, err := gen() + if err != nil { + t.Fatal("failed to generate.") + } + + if s2 := m.String(); err != nil || s2 != s { + t.Fatal("failed to convert: " + s + " != " + s2) + } +} + +func testToNetAddr(t *testing.T, maddr, ntwk, addr string) { + m, err := ma.NewMultiaddr(maddr) + if err != nil { + t.Fatal("failed to generate.") + } + + naddr, err := ToNetAddr(m) + if addr == "" { // should fail + if err == nil { + t.Fatalf("failed to error: %s", m) + } + return + } + + // shouldn't fail + if err != nil { + t.Fatalf("failed to convert to net addr: %s", m) + } + + if naddr.String() != addr { + t.Fatalf("naddr.Address() == %s != %s", naddr, addr) + } + + if naddr.Network() != ntwk { + t.Fatalf("naddr.Network() == %s != %s", naddr.Network(), ntwk) + } + + // should convert properly + switch ntwk { + case "tcp": + taddr := naddr.(*net.TCPAddr) + if ip, err := ToIP(m); err != nil || !taddr.IP.Equal(ip) { + t.Fatalf("ToIP() and ToNetAddr diverged: %s != %s", taddr, ip) + } + case "udp": + uaddr := naddr.(*net.UDPAddr) + if ip, err := ToIP(m); err != nil || !uaddr.IP.Equal(ip) { + t.Fatalf("ToIP() and ToNetAddr diverged: %s != %s", uaddr, ip) + } + case "ip": + ipaddr := naddr.(*net.IPAddr) + if ip, err := ToIP(m); err != nil || !ipaddr.IP.Equal(ip) { + t.Fatalf("ToIP() and ToNetAddr diverged: %s != %s", ipaddr, ip) + } + } +} + +func TestFromIP4(t *testing.T) { + testConvert(t, "/ip4/10.20.30.40", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.IPAddr{IP: net.ParseIP("10.20.30.40")}) + }) +} + +func TestFromUnix(t *testing.T) { + path := "/C:/foo/bar" + if runtime.GOOS == "windows" { + path = `C:\foo\bar` + } + testConvert(t, "/unix/C:/foo/bar", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.UnixAddr{Name: path, Net: "unix"}) + }) +} + +func TestToUnix(t *testing.T) { + path := "/C:/foo/bar" + if runtime.GOOS == "windows" { + path = `C:\foo\bar` + } + testToNetAddr(t, "/unix/C:/foo/bar", "unix", path) +} + +func TestFromIP6(t *testing.T) { + testConvert(t, "/ip6/2001:4860:0:2001::68", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.IPAddr{IP: net.ParseIP("2001:4860:0:2001::68")}) + }) +} + +func TestFromTCP(t *testing.T) { + testConvert(t, "/ip4/10.20.30.40/tcp/1234", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.TCPAddr{ + IP: net.ParseIP("10.20.30.40"), + Port: 1234, + }) + }) +} + +func TestFromUDP(t *testing.T) { + testConvert(t, "/ip4/10.20.30.40/udp/1234", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.UDPAddr{ + IP: net.ParseIP("10.20.30.40"), + Port: 1234, + }) + }) +} + +func TestThinWaist(t *testing.T) { + addrs := map[string]bool{ + "/ip4/127.0.0.1/udp/1234": true, + "/ip4/127.0.0.1/tcp/1234": true, + "/ip4/127.0.0.1/udp/1234/tcp/1234": true, + "/ip4/127.0.0.1/tcp/12345/ip4/1.2.3.4": true, + "/ip6/::1/tcp/80": true, + "/ip6/::1/udp/80": true, + "/ip6/::1": true, + "/ip6zone/hello/ip6/fe80::1/tcp/80": true, + "/ip6zone/hello/ip6/fe80::1": true, + "/tcp/1234/ip4/1.2.3.4": false, + "/tcp/1234": false, + "/tcp/1234/udp/1234": false, + "/ip4/1.2.3.4/ip4/2.3.4.5": true, + "/ip6/fe80::1/ip4/2.3.4.5": true, + "/ip6zone/hello/ip6/fe80::1/ip4/2.3.4.5": true, + + // Invalid ip6zone usage: + "/ip6zone/hello": false, + "/ip6zone/hello/ip4/1.1.1.1": false, + } + + for a, res := range addrs { + m, err := ma.NewMultiaddr(a) + if err != nil { + t.Fatalf("failed to construct Multiaddr: %s", a) + } + + if IsThinWaist(m) != res { + t.Fatalf("IsThinWaist(%s) != %v", a, res) + } + } +} + +func TestDialArgs(t *testing.T) { + test := func(e_maddr, e_nw, e_host string) { + m, err := ma.NewMultiaddr(e_maddr) + if err != nil { + t.Fatal("failed to construct", e_maddr) + } + + nw, host, err := DialArgs(m) + if err != nil { + t.Fatal("failed to get dial args", e_maddr, m, err) + } + + if nw != e_nw { + t.Error("failed to get udp network Dial Arg", e_nw, nw) + } + + if host != e_host { + t.Error("failed to get host:port Dial Arg", e_host, host) + } + } + + test_error := func(e_maddr string) { + m, err := ma.NewMultiaddr(e_maddr) + if err != nil { + t.Fatal("failed to construct", e_maddr) + } + + _, _, err = DialArgs(m) + if err == nil { + t.Fatal("expected DialArgs to fail on", e_maddr) + } + } + + test("/ip4/127.0.0.1/udp/1234", "udp4", "127.0.0.1:1234") + test("/ip4/127.0.0.1/tcp/4321", "tcp4", "127.0.0.1:4321") + test("/ip6/::1/udp/1234", "udp6", "[::1]:1234") + test("/ip6/::1/tcp/4321", "tcp6", "[::1]:4321") + test("/ip6/::1", "ip6", "::1") // Just an IP + test("/ip4/1.2.3.4", "ip4", "1.2.3.4") // Just an IP + test("/ip6zone/foo/ip6/::1/tcp/4321", "tcp6", "[::1%foo]:4321") // zone + test("/ip6zone/foo/ip6/::1/udp/4321", "udp6", "[::1%foo]:4321") // zone + test("/ip6zone/foo/ip6/::1", "ip6", "::1%foo") // no TCP + test_error("/ip6zone/foo/ip4/127.0.0.1") // IP4 doesn't take zone + test("/ip6zone/foo/ip6/::1/ip6zone/bar", "ip6", "::1%foo") // IP over IP + test_error("/ip6zone/foo/ip6zone/bar/ip6/::1") // Only one zone per IP6 + test("/dns/abc.com/tcp/1234", "tcp", "abc.com:1234") // DNS4:port + test("/dns4/abc.com/tcp/1234", "tcp4", "abc.com:1234") // DNS4:port + test("/dns4/abc.com", "ip4", "abc.com") // Just DNS4 + test("/dns6/abc.com/udp/1234", "udp6", "abc.com:1234") // DNS6:port + test("/dns6/abc.com", "ip6", "abc.com") // Just DNS6 +} + +func TestMultiaddrToIPNet(t *testing.T) { + type testCase struct { + name string + ma string + ips []string + contained []bool + } + + testCases := []testCase{ + { + name: "basic", + ma: "/ip4/1.2.3.0/ipcidr/24", + ips: []string{"1.2.3.4", "1.2.3.9", "2.1.1.1"}, + contained: []bool{true, true, false}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ma := ma.StringCast(tc.ma) + + ipnet, err := MultiaddrToIPNet(ma) + if err != nil { + t.Fatalf("failed to parse multiaddr %v into ipnet", ma) + } + for i, ipString := range tc.ips { + ip := net.ParseIP(ipString) + if ip == nil { + t.Fatalf("failed to parse IP %s", ipString) + } + if ipnet.Contains(ip) != tc.contained[i] { + t.Fatalf("Contains check failed. Expected %v got %v", tc.contained[i], ipnet.Contains(ip)) + } + } + }) + } +} + +func TestFailMultiaddrToIPNet(t *testing.T) { + type testCase struct { + name string + ma string + } + + testCases := []testCase{ + {name: "missing ip addr", ma: "/ipcidr/24"}, + {name: "wrong mask", ma: "/ip4/1.2.3.0/ipcidr/128"}, + {name: "wrong mask", ma: "/ip6/::/ipcidr/255"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ma := ma.StringCast(tc.ma) + + _, err := MultiaddrToIPNet(ma) + if err == nil { + t.Fatalf("Expected error when parsing: %s", tc.ma) + } + }) + } +} diff --git a/compattest/internal/prev/net/doc.go b/compattest/internal/prev/net/doc.go new file mode 100644 index 00000000..040ad3f0 --- /dev/null +++ b/compattest/internal/prev/net/doc.go @@ -0,0 +1,5 @@ +// Package manet provides Multiaddr specific versions of common +// functions in stdlib's net package. This means wrappers of +// standard net symbols like net.Dial and net.Listen, as well +// as conversion to/from net.Addr. +package manet diff --git a/compattest/internal/prev/net/ip.go b/compattest/internal/prev/net/ip.go new file mode 100644 index 00000000..def9321d --- /dev/null +++ b/compattest/internal/prev/net/ip.go @@ -0,0 +1,132 @@ +package manet + +import ( + "net" + + ma "github.com/multiformats/go-multiaddr" +) + +// Loopback Addresses +var ( + // IP4Loopback is the ip4 loopback multiaddr + IP4Loopback = ma.StringCast("/ip4/127.0.0.1") + + // IP6Loopback is the ip6 loopback multiaddr + IP6Loopback = ma.StringCast("/ip6/::1") + + // IP4MappedIP6Loopback is the IPv4 Mapped IPv6 loopback address. + IP4MappedIP6Loopback = ma.StringCast("/ip6/::ffff:127.0.0.1") +) + +// Unspecified Addresses (used for ) +var ( + IP4Unspecified = ma.StringCast("/ip4/0.0.0.0") + IP6Unspecified = ma.StringCast("/ip6/::") +) + +// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols. +// This means: /{IP4, IP6}[/{TCP, UDP}] +func IsThinWaist(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + p := m.Protocols() + + // nothing? not even a waist. + if len(p) == 0 { + return false + } + + if p[0].Code != ma.P_IP4 && p[0].Code != ma.P_IP6 { + return false + } + + // only IP? still counts. + if len(p) == 1 { + return true + } + + switch p[1].Code { + case ma.P_TCP, ma.P_UDP, ma.P_IP4, ma.P_IP6: + return true + default: + return false + } +} + +// IsIPLoopback returns whether a Multiaddr starts with a "Loopback" IP address +// This means either /ip4/127.*.*.*/*, /ip6/::1/*, or /ip6/::ffff:127.*.*.*.*/*, +// or /ip6zone//ip6//* +func IsIPLoopback(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + c, _ := ma.SplitFirst(m) + if c == nil { + return false + } + switch c.Protocol().Code { + case ma.P_IP4, ma.P_IP6: + return net.IP(c.RawValue()).IsLoopback() + } + return false +} + +// IsIP6LinkLocal returns whether a Multiaddr starts with an IPv6 link-local +// multiaddress (with zero or one leading zone). These addresses are non +// routable. +func IsIP6LinkLocal(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + c, _ := ma.SplitFirst(m) + if c == nil || c.Protocol().Code != ma.P_IP6 { + return false + } + ip := net.IP(c.RawValue()) + return ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() +} + +// IsIPUnspecified returns whether a Multiaddr starts with an Unspecified IP address +// This means either /ip4/0.0.0.0/* or /ip6/::/* +func IsIPUnspecified(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + c, _ := ma.SplitFirst(m) + return net.IP(c.RawValue()).IsUnspecified() +} + +// If m matches [zone,ip6,...], return [ip6,...] +// else if m matches [], [zone], or [zone,...], return nil +// else return m +func zoneless(m ma.Multiaddr) ma.Multiaddr { + head, tail := ma.SplitFirst(m) + if head == nil { + return nil + } + if head.Protocol().Code == ma.P_IP6ZONE { + if tail == nil { + return nil + } + tailhead, _ := ma.SplitFirst(tail) + if tailhead.Protocol().Code != ma.P_IP6 { + return nil + } + return tail + } else { + return m + } +} + +// IsNAT64IPv4ConvertedIPv6Addr returns whether addr is a well-known prefix "64:ff9b::/96" addr +// used for NAT64 Translation. See RFC 6052 +func IsNAT64IPv4ConvertedIPv6Addr(addr ma.Multiaddr) bool { + c, _ := ma.SplitFirst(addr) + return c != nil && c.Protocol().Code == ma.P_IP6 && + inAddrRange(c.RawValue(), nat64) +} diff --git a/compattest/internal/prev/net/ip_test.go b/compattest/internal/prev/net/ip_test.go new file mode 100644 index 00000000..4d394b7c --- /dev/null +++ b/compattest/internal/prev/net/ip_test.go @@ -0,0 +1,54 @@ +package manet + +import ( + "fmt" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestIsWellKnownPrefixIPv4ConvertedIPv6Address(t *testing.T) { + cases := []struct { + addr ma.Multiaddr + want bool + failureReason string + }{ + { + addr: ma.StringCast("/ip4/1.2.3.4/tcp/1234"), + want: false, + failureReason: "ip4 addresses should return false", + }, + { + addr: ma.StringCast("/ip6/1::4/tcp/1234"), + want: false, + failureReason: "ip6 addresses doesn't have well-known prefix", + }, + { + addr: ma.StringCast("/ip6/::1/tcp/1234"), + want: false, + failureReason: "localhost addresses should return false", + }, + { + addr: ma.StringCast("/ip6/64:ff9b::192.0.1.2/tcp/1234"), + want: true, + failureReason: "ip6 address begins with well-known prefix", + }, + { + addr: ma.StringCast("/ip6/64:ff9b::1:192.0.1.2/tcp/1234"), + want: false, + failureReason: "64:ff9b::1 is not well-known prefix", + }, + { + addr: ma.StringCast("/ip6/64:ff9b:1::1:192.0.1.2/tcp/1234"), + want: true, + failureReason: "64:ff9b:1::1 is allowed for NAT64 translation", + }, + } + for i, tc := range cases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + if IsNAT64IPv4ConvertedIPv6Addr(tc.addr) != tc.want { + t.Fatalf("%s %s", tc.addr, tc.failureReason) + } + }) + } +} diff --git a/compattest/internal/prev/net/net.go b/compattest/internal/prev/net/net.go new file mode 100644 index 00000000..10fcff70 --- /dev/null +++ b/compattest/internal/prev/net/net.go @@ -0,0 +1,430 @@ +// Package manet provides Multiaddr +// (https://github.com/multiformats/go-multiaddr) specific versions of common +// functions in Go's standard `net` package. This means wrappers of standard +// net symbols like `net.Dial` and `net.Listen`, as well as conversion to +// and from `net.Addr`. +package manet + +import ( + "context" + "fmt" + "net" + + ma "github.com/multiformats/go-multiaddr" +) + +// Conn is the equivalent of a net.Conn object. It is the +// result of calling the Dial or Listen functions in this +// package, with associated local and remote Multiaddrs. +type Conn interface { + net.Conn + + // LocalMultiaddr returns the local Multiaddr associated + // with this connection + LocalMultiaddr() ma.Multiaddr + + // RemoteMultiaddr returns the remote Multiaddr associated + // with this connection + RemoteMultiaddr() ma.Multiaddr +} + +type halfOpen interface { + net.Conn + CloseRead() error + CloseWrite() error +} + +func wrap(nconn net.Conn, laddr, raddr ma.Multiaddr) Conn { + endpts := maEndpoints{ + laddr: laddr, + raddr: raddr, + } + // This sucks. However, it's the only way to reliably expose the + // underlying methods. This way, users that need access to, e.g., + // CloseRead and CloseWrite, can do so via type assertions. + switch nconn := nconn.(type) { + case *net.TCPConn: + return &struct { + *net.TCPConn + maEndpoints + }{nconn, endpts} + case *net.UDPConn: + return &struct { + *net.UDPConn + maEndpoints + }{nconn, endpts} + case *net.IPConn: + return &struct { + *net.IPConn + maEndpoints + }{nconn, endpts} + case *net.UnixConn: + return &struct { + *net.UnixConn + maEndpoints + }{nconn, endpts} + case halfOpen: + return &struct { + halfOpen + maEndpoints + }{nconn, endpts} + default: + return &struct { + net.Conn + maEndpoints + }{nconn, endpts} + } +} + +// WrapNetConn wraps a net.Conn object with a Multiaddr friendly Conn. +// +// This function does it's best to avoid "hiding" methods exposed by the wrapped +// type. Guarantees: +// +// - If the wrapped connection exposes the "half-open" closer methods +// (CloseWrite, CloseRead), these will be available on the wrapped connection +// via type assertions. +// - If the wrapped connection is a UnixConn, IPConn, TCPConn, or UDPConn, all +// methods on these wrapped connections will be available via type assertions. +func WrapNetConn(nconn net.Conn) (Conn, error) { + if nconn == nil { + return nil, fmt.Errorf("failed to convert nconn.LocalAddr: nil") + } + + laddr, err := FromNetAddr(nconn.LocalAddr()) + if err != nil { + return nil, fmt.Errorf("failed to convert nconn.LocalAddr: %s", err) + } + + raddr, err := FromNetAddr(nconn.RemoteAddr()) + if err != nil { + return nil, fmt.Errorf("failed to convert nconn.RemoteAddr: %s", err) + } + + return wrap(nconn, laddr, raddr), nil +} + +type maEndpoints struct { + laddr ma.Multiaddr + raddr ma.Multiaddr +} + +// LocalMultiaddr returns the local address associated with +// this connection +func (c *maEndpoints) LocalMultiaddr() ma.Multiaddr { + return c.laddr +} + +// RemoteMultiaddr returns the remote address associated with +// this connection +func (c *maEndpoints) RemoteMultiaddr() ma.Multiaddr { + return c.raddr +} + +// Dialer contains options for connecting to an address. It +// is effectively the same as net.Dialer, but its LocalAddr +// and RemoteAddr options are Multiaddrs, instead of net.Addrs. +type Dialer struct { + + // Dialer is just an embedded net.Dialer, with all its options. + net.Dialer + + // LocalAddr is the local address to use when dialing an + // address. The address must be of a compatible type for the + // network being dialed. + // If nil, a local address is automatically chosen. + LocalAddr ma.Multiaddr +} + +// Dial connects to a remote address, using the options of the +// Dialer. Dialer uses an underlying net.Dialer to Dial a +// net.Conn, then wraps that in a Conn object (with local and +// remote Multiaddrs). +func (d *Dialer) Dial(remote ma.Multiaddr) (Conn, error) { + return d.DialContext(context.Background(), remote) +} + +// DialContext allows to provide a custom context to Dial(). +func (d *Dialer) DialContext(ctx context.Context, remote ma.Multiaddr) (Conn, error) { + // if a LocalAddr is specified, use it on the embedded dialer. + if d.LocalAddr != nil { + // convert our multiaddr to net.Addr friendly + naddr, err := ToNetAddr(d.LocalAddr) + if err != nil { + return nil, err + } + + // set the dialer's LocalAddr as naddr + d.Dialer.LocalAddr = naddr + } + + // get the net.Dial friendly arguments from the remote addr + rnet, rnaddr, err := DialArgs(remote) + if err != nil { + return nil, err + } + + // ok, Dial! + var nconn net.Conn + switch rnet { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "unix": + nconn, err = d.Dialer.DialContext(ctx, rnet, rnaddr) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unrecognized network: %s", rnet) + } + + // get local address (pre-specified or assigned within net.Conn) + local := d.LocalAddr + // This block helps us avoid parsing addresses in transports (such as unix + // sockets) that don't have local addresses when dialing out. + if local == nil && nconn.LocalAddr().String() != "" { + local, err = FromNetAddr(nconn.LocalAddr()) + if err != nil { + return nil, err + } + } + return wrap(nconn, local, remote), nil +} + +// Dial connects to a remote address. It uses an underlying net.Conn, +// then wraps it in a Conn object (with local and remote Multiaddrs). +func Dial(remote ma.Multiaddr) (Conn, error) { + return (&Dialer{}).Dial(remote) +} + +// A Listener is a generic network listener for stream-oriented protocols. +// it uses an embedded net.Listener, overriding net.Listener.Accept to +// return a Conn and providing Multiaddr. +type Listener interface { + // Accept waits for and returns the next connection to the listener. + // Returns a Multiaddr friendly Conn + Accept() (Conn, error) + + // Close closes the listener. + // Any blocked Accept operations will be unblocked and return errors. + Close() error + + // Multiaddr returns the listener's (local) Multiaddr. + Multiaddr() ma.Multiaddr + + // Addr returns the net.Listener's network address. + Addr() net.Addr +} + +type netListenerAdapter struct { + Listener +} + +func (nla *netListenerAdapter) Accept() (net.Conn, error) { + return nla.Listener.Accept() +} + +// NetListener turns this Listener into a net.Listener. +// +// - Connections returned from Accept implement multiaddr/net Conn. +// - Calling WrapNetListener on the net.Listener returned by this function will +// return the original (underlying) multiaddr/net Listener. +func NetListener(l Listener) net.Listener { + return &netListenerAdapter{l} +} + +// maListener implements Listener +type maListener struct { + net.Listener + laddr ma.Multiaddr +} + +// Accept waits for and returns the next connection to the listener. +// Returns a Multiaddr friendly Conn +func (l *maListener) Accept() (Conn, error) { + nconn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + var raddr ma.Multiaddr + // This block protects us in transports (i.e. unix sockets) that don't have + // remote addresses for inbound connections. + if addr := nconn.RemoteAddr(); addr != nil && addr.String() != "" { + raddr, err = FromNetAddr(addr) + if err != nil { + return nil, fmt.Errorf("failed to convert conn.RemoteAddr: %s", err) + } + } + + var laddr ma.Multiaddr + if addr := nconn.LocalAddr(); addr != nil && addr.String() != "" { + laddr, err = FromNetAddr(addr) + if err != nil { + return nil, fmt.Errorf("failed to convert conn.LocalAddr: %s", err) + } + } + + return wrap(nconn, laddr, raddr), nil +} + +// Multiaddr returns the listener's (local) Multiaddr. +func (l *maListener) Multiaddr() ma.Multiaddr { + return l.laddr +} + +// Addr returns the listener's network address. +func (l *maListener) Addr() net.Addr { + return l.Listener.Addr() +} + +// Listen announces on the local network address laddr. +// The Multiaddr must be a "ThinWaist" stream-oriented network: +// ip4/tcp, ip6/tcp, (TODO: unix, unixpacket) +// See Dial for the syntax of laddr. +func Listen(laddr ma.Multiaddr) (Listener, error) { + + // get the net.Listen friendly arguments from the remote addr + lnet, lnaddr, err := DialArgs(laddr) + if err != nil { + return nil, err + } + + nl, err := net.Listen(lnet, lnaddr) + if err != nil { + return nil, err + } + + // we want to fetch the new multiaddr from the listener, as it may + // have resolved to some other value. WrapNetListener does it for us. + return WrapNetListener(nl) +} + +// WrapNetListener wraps a net.Listener with a manet.Listener. +func WrapNetListener(nl net.Listener) (Listener, error) { + if nla, ok := nl.(*netListenerAdapter); ok { + return nla.Listener, nil + } + + laddr, err := FromNetAddr(nl.Addr()) + if err != nil { + return nil, err + } + + return &maListener{ + Listener: nl, + laddr: laddr, + }, nil +} + +// A PacketConn is a generic packet oriented network connection which uses an +// underlying net.PacketConn, wrapped with the locally bound Multiaddr. +type PacketConn interface { + net.PacketConn + + LocalMultiaddr() ma.Multiaddr + + ReadFromMultiaddr(b []byte) (int, ma.Multiaddr, error) + WriteToMultiaddr(b []byte, maddr ma.Multiaddr) (int, error) +} + +// maPacketConn implements PacketConn +type maPacketConn struct { + net.PacketConn + laddr ma.Multiaddr +} + +var _ PacketConn = (*maPacketConn)(nil) + +// LocalMultiaddr returns the bound local Multiaddr. +func (l *maPacketConn) LocalMultiaddr() ma.Multiaddr { + return l.laddr +} + +func (l *maPacketConn) ReadFromMultiaddr(b []byte) (int, ma.Multiaddr, error) { + n, addr, err := l.ReadFrom(b) + maddr, _ := FromNetAddr(addr) + return n, maddr, err +} + +func (l *maPacketConn) WriteToMultiaddr(b []byte, maddr ma.Multiaddr) (int, error) { + addr, err := ToNetAddr(maddr) + if err != nil { + return 0, err + } + return l.WriteTo(b, addr) +} + +// ListenPacket announces on the local network address laddr. +// The Multiaddr must be a packet driven network, like udp4 or udp6. +// See Dial for the syntax of laddr. +func ListenPacket(laddr ma.Multiaddr) (PacketConn, error) { + lnet, lnaddr, err := DialArgs(laddr) + if err != nil { + return nil, err + } + + pc, err := net.ListenPacket(lnet, lnaddr) + if err != nil { + return nil, err + } + + // We want to fetch the new multiaddr from the listener, as it may + // have resolved to some other value. WrapPacketConn does this. + return WrapPacketConn(pc) +} + +// WrapPacketConn wraps a net.PacketConn with a manet.PacketConn. +func WrapPacketConn(pc net.PacketConn) (PacketConn, error) { + laddr, err := FromNetAddr(pc.LocalAddr()) + if err != nil { + return nil, err + } + + return &maPacketConn{ + PacketConn: pc, + laddr: laddr, + }, nil +} + +// InterfaceMultiaddrs will return the addresses matching net.InterfaceAddrs +func InterfaceMultiaddrs() ([]ma.Multiaddr, error) { + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, err + } + + maddrs := make([]ma.Multiaddr, len(addrs)) + for i, a := range addrs { + maddrs[i], err = FromNetAddr(a) + if err != nil { + return nil, err + } + } + return maddrs, nil +} + +// AddrMatch returns the Multiaddrs that match the protocol stack on addr +func AddrMatch(match ma.Multiaddr, addrs []ma.Multiaddr) []ma.Multiaddr { + + // we should match transports entirely. + p1s := match.Protocols() + + out := make([]ma.Multiaddr, 0, len(addrs)) + for _, a := range addrs { + p2s := a.Protocols() + if len(p1s) != len(p2s) { + continue + } + + match := true + for i, p2 := range p2s { + if p1s[i].Code != p2.Code { + match = false + break + } + } + if match { + out = append(out, a) + } + } + return out +} diff --git a/compattest/internal/prev/net/net_test.go b/compattest/internal/prev/net/net_test.go new file mode 100644 index 00000000..86cfc50c --- /dev/null +++ b/compattest/internal/prev/net/net_test.go @@ -0,0 +1,693 @@ +package manet + +import ( + "bytes" + "fmt" + "net" + "os" + "path/filepath" + "sync" + "testing" + "time" + + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +func newMultiaddr(t *testing.T, m string) ma.Multiaddr { + maddr, err := ma.NewMultiaddr(m) + if err != nil { + t.Fatal("failed to construct multiaddr:", m, err) + } + return maddr +} + +func TestDial(t *testing.T) { + + listener, err := net.Listen("tcp", "127.0.0.1:4321") + if err != nil { + t.Fatal("failed to listen") + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + cB, err := listener.Accept() + if err != nil { + t.Error("failed to accept") + } + + // echo out + buf := make([]byte, 1024) + for { + _, err := cB.Read(buf) + if err != nil { + break + } + cB.Write(buf) + } + + wg.Done() + }() + + maddr := newMultiaddr(t, "/ip4/127.0.0.1/tcp/4321") + cA, err := Dial(maddr) + if err != nil { + t.Fatal("failed to dial") + } + + buf := make([]byte, 1024) + if _, err := cA.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write:", err) + } + + if _, err := cA.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echo:", buf) + } + + maddr2 := cA.RemoteMultiaddr() + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cA.Close() + wg.Wait() +} + +func TestUnixSockets(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), "manettest") + if err != nil { + t.Fatal(err) + } + path := filepath.Join(dir, "listen.sock") + maddr := newMultiaddr(t, "/unix/"+path) + + listener, err := Listen(maddr) + if err != nil { + t.Fatal(err) + } + + payload := []byte("hello") + + // listen + done := make(chan struct{}, 1) + go func() { + conn, err := listener.Accept() + if err != nil { + t.Error(err) + } + defer conn.Close() + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + t.Error(err) + } + if n != len(payload) { + t.Error("failed to read appropriate number of bytes") + } + if !bytes.Equal(buf[0:n], payload) { + t.Error("payload did not match") + } + done <- struct{}{} + }() + + // dial + conn, err := Dial(maddr) + if err != nil { + t.Fatal(err) + } + n, err := conn.Write(payload) + if err != nil { + t.Fatal(err) + } + if n != len(payload) { + t.Fatal("failed to write appropriate number of bytes") + } + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for read") + } +} + +func TestListen(t *testing.T) { + + maddr := newMultiaddr(t, "/ip4/127.0.0.1/tcp/4322") + listener, err := Listen(maddr) + if err != nil { + t.Fatal("failed to listen") + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + cB, err := listener.Accept() + if err != nil { + t.Error("failed to accept") + } + + if !cB.LocalMultiaddr().Equal(maddr) { + t.Error("local multiaddr not equal:", maddr, cB.LocalMultiaddr()) + } + + // echo out + buf := make([]byte, 1024) + for { + _, err := cB.Read(buf) + if err != nil { + break + } + cB.Write(buf) + } + + wg.Done() + }() + + cA, err := net.Dial("tcp", "127.0.0.1:4322") + if err != nil { + t.Fatal("failed to dial") + } + + buf := make([]byte, 1024) + if _, err := cA.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write:", err) + } + + if _, err := cA.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echo:", buf) + } + + maddr2, err := FromNetAddr(cA.RemoteAddr()) + if err != nil { + t.Fatal("failed to convert", err) + } + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cA.Close() + wg.Wait() +} + +func TestListenAddrs(t *testing.T) { + + test := func(addr, resaddr string, succeed bool) { + if resaddr == "" { + resaddr = addr + } + + maddr := newMultiaddr(t, addr) + l, err := Listen(maddr) + if !succeed { + if err == nil { + t.Fatal("succeeded in listening", addr) + } + return + } + if succeed && err != nil { + t.Error("failed to listen", addr, err) + } + if l == nil { + t.Error("failed to listen", addr, succeed, err) + } + if l.Multiaddr().String() != resaddr { + t.Error("listen addr did not resolve properly", l.Multiaddr().String(), resaddr, succeed, err) + } + + if err = l.Close(); err != nil { + t.Fatal("failed to close listener", addr, err) + } + } + + test("/ip4/127.0.0.1/tcp/4324", "", true) + test("/ip4/127.0.0.1/udp/4325", "", false) + test("/ip4/127.0.0.1/udp/4326/udt", "", false) + + if len(os.Getenv("CI")) > 0 { + test("/ip4/0.0.0.0/tcp/4324", "", true) + test("/ip4/0.0.0.0/udp/4325", "", false) + test("/ip4/0.0.0.0/udp/4326/udt", "", false) + + test("/ip6/::1/tcp/4324", "", true) + test("/ip6/::1/udp/4325", "", false) + test("/ip6/::1/udp/4326/udt", "", false) + test("/ip6/::/tcp/4324", "", true) + test("/ip6/::/udp/4325", "", false) + test("/ip6/::/udp/4326/udt", "", false) + + /* "An implementation should also support the concept of a "default" + * zone for each scope. And, when supported, the index value zero + * at each scope SHOULD be reserved to mean "use the default zone"." + * -- rfc4007. So, this _should_ work everywhere(?). */ + test("/ip6zone/0/ip6/::1/tcp/4324", "/ip6/::1/tcp/4324", true) + test("/ip6zone/0/ip6/::1/udp/4324", "", false) + } else { + t.Skip("all tests only run on CI") + } +} + +func TestListenAndDial(t *testing.T) { + + maddr := newMultiaddr(t, "/ip4/127.0.0.1/tcp/4323") + listener, err := Listen(maddr) + if err != nil { + t.Fatal("failed to listen") + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + cB, err := listener.Accept() + if err != nil { + t.Error("failed to accept") + } + + if !cB.LocalMultiaddr().Equal(maddr) { + t.Error("local multiaddr not equal:", maddr, cB.LocalMultiaddr()) + } + + // echo out + buf := make([]byte, 1024) + for { + _, err := cB.Read(buf) + if err != nil { + break + } + cB.Write(buf) + } + + wg.Done() + }() + + cA, err := Dial(newMultiaddr(t, "/ip4/127.0.0.1/tcp/4323")) + if err != nil { + t.Fatal("failed to dial") + } + + buf := make([]byte, 1024) + if _, err := cA.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write:", err) + } + + if _, err := cA.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echo:", buf) + } + + maddr2 := cA.RemoteMultiaddr() + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cA.Close() + wg.Wait() +} + +func TestListenPacketAndDial(t *testing.T) { + maddr := newMultiaddr(t, "/ip4/127.0.0.1/udp/4324") + pc, err := ListenPacket(maddr) + if err != nil { + t.Fatal("failed to listen", err) + } + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + if !pc.LocalMultiaddr().Equal(maddr) { + t.Error("connection multiaddr not equal:", maddr, pc.LocalMultiaddr()) + } + + buffer := make([]byte, 1024) + _, addr, err := pc.ReadFrom(buffer) + if err != nil { + t.Error("failed to read into buffer", err) + } + pc.WriteTo(buffer, addr) + + wg.Done() + }() + + cn, err := Dial(maddr) + if err != nil { + t.Fatal("failed to dial", err) + } + + buf := make([]byte, 1024) + if _, err := cn.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write", err) + } + + if _, err := cn.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echk:", buf) + } + + maddr2 := cn.RemoteMultiaddr() + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cn.Close() + pc.Close() + wg.Wait() +} + +func TestIPLoopback(t *testing.T) { + if IP4Loopback.String() != "/ip4/127.0.0.1" { + t.Error("IP4Loopback incorrect:", IP4Loopback) + } + + if IP6Loopback.String() != "/ip6/::1" { + t.Error("IP6Loopback incorrect:", IP6Loopback) + } + + if IP4MappedIP6Loopback.String() != "/ip6/::ffff:127.0.0.1" { + t.Error("IP4MappedIP6Loopback incorrect:", IP4MappedIP6Loopback) + } + + if !IsIPLoopback(IP4Loopback) { + t.Error("IsIPLoopback failed (IP4Loopback)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip4/127.1.80.9")) { + t.Error("IsIPLoopback failed (/ip4/127.1.80.9)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip4/112.123.11.1")) { + t.Error("IsIPLoopback false positive (/ip4/112.123.11.1)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip4/192.168.0.1/ip6/::1")) { + t.Error("IsIPLoopback false positive (/ip4/192.168.0.1/ip6/::1)") + } + + if !IsIPLoopback(IP6Loopback) { + t.Error("IsIPLoopback failed (IP6Loopback)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6/127.0.0.1")) { + t.Error("IsIPLoopback failed (/ip6/127.0.0.1)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6/127.99.3.2")) { + t.Error("IsIPLoopback failed (/ip6/127.99.3.2)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip6/::fffa:127.99.3.2")) { + t.Error("IsIPLoopback false positive (/ip6/::fffa:127.99.3.2)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/::1")) { + t.Error("IsIPLoopback failed (/ip6zone/0/ip6/::1)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6zone/xxx/ip6/::1")) { + t.Error("IsIPLoopback failed (/ip6zone/xxx/ip6/::1)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/1::1")) { + t.Errorf("IsIPLoopback false positive (/ip6zone/0/ip6/1::1)") + } +} + +func TestIPUnspecified(t *testing.T) { + if IP4Unspecified.String() != "/ip4/0.0.0.0" { + t.Error("IP4Unspecified incorrect:", IP4Unspecified) + } + + if IP6Unspecified.String() != "/ip6/::" { + t.Error("IP6Unspecified incorrect:", IP6Unspecified) + } + + if !IsIPUnspecified(IP4Unspecified) { + t.Error("IsIPUnspecified failed (IP4Unspecified)") + } + + if !IsIPUnspecified(IP6Unspecified) { + t.Error("IsIPUnspecified failed (IP6Unspecified)") + } + + if !IsIPUnspecified(newMultiaddr(t, "/ip6zone/xxx/ip6/::")) { + t.Error("IsIPUnspecified failed (/ip6zone/xxx/ip6/::)") + } +} + +func TestIP6LinkLocal(t *testing.T) { + for a := 0; a < 65536; a++ { + isLinkLocal := a&0xffc0 == 0xfe80 || a&0xff0f == 0xff02 + m := newMultiaddr(t, fmt.Sprintf("/ip6/%x::1", a)) + if IsIP6LinkLocal(m) != isLinkLocal { + t.Errorf("IsIP6LinkLocal failed (%s != %v)", m, isLinkLocal) + } + } + + if !IsIP6LinkLocal(newMultiaddr(t, "/ip6zone/hello/ip6/fe80::9999")) { + t.Error("IsIP6LinkLocal failed (/ip6/fe80::9999)") + } + + bad := []ma.Multiaddr{ + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), // link local + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), // link local + } + good := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), + } + for _, addr := range bad { + require.True(t, IsIP6LinkLocal(addr), "%s is a link local addr", addr) + } + for _, addr := range good { + require.False(t, IsIP6LinkLocal(addr), "%s is not a link local addr", addr) + } +} + +func TestConvertNetAddr(t *testing.T) { + m1 := newMultiaddr(t, "/ip4/1.2.3.4/tcp/4001") + + n1, err := ToNetAddr(m1) + if err != nil { + t.Fatal(err) + } + + m2, err := FromNetAddr(n1) + if err != nil { + t.Fatal(err) + } + + if m1.String() != m2.String() { + t.Fatal("ToNetAddr + FromNetAddr did not work") + } +} + +func TestWrapNetConn(t *testing.T) { + // test WrapNetConn nil + if _, err := WrapNetConn(nil); err == nil { + t.Error("WrapNetConn(nil) should return an error") + } + + checkErr := func(err error, s string) { + if err != nil { + t.Fatal(s, err) + } + } + + listener, err := net.Listen("tcp", "127.0.0.1:0") + checkErr(err, "failed to listen") + + var wg sync.WaitGroup + defer wg.Wait() + wg.Add(1) + go func() { + defer wg.Done() + cB, err := listener.Accept() + checkErr(err, "failed to accept") + _ = cB.(halfOpen) + cB.Close() + }() + + cA, err := net.Dial("tcp", listener.Addr().String()) + checkErr(err, "failed to dial") + defer cA.Close() + _ = cA.(halfOpen) + + lmaddr, err := FromNetAddr(cA.LocalAddr()) + checkErr(err, "failed to get local addr") + rmaddr, err := FromNetAddr(cA.RemoteAddr()) + checkErr(err, "failed to get remote addr") + + mcA, err := WrapNetConn(cA) + checkErr(err, "failed to wrap conn") + + _ = mcA.(halfOpen) + + if mcA.LocalAddr().String() != cA.LocalAddr().String() { + t.Error("wrapped conn local addr differs") + } + if mcA.RemoteAddr().String() != cA.RemoteAddr().String() { + t.Error("wrapped conn remote addr differs") + } + if mcA.LocalMultiaddr().String() != lmaddr.String() { + t.Error("wrapped conn local maddr differs") + } + if mcA.RemoteMultiaddr().String() != rmaddr.String() { + t.Error("wrapped conn remote maddr differs") + } +} + +func TestAddrMatch(t *testing.T) { + + test := func(m ma.Multiaddr, input, expect []ma.Multiaddr) { + actual := AddrMatch(m, input) + testSliceEqual(t, expect, actual) + } + + a := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + } + + test(a[0], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/2345"), + }) + test(a[2], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + }) + test(a[4], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + }) + test(a[6], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + }) + test(a[8], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/2345"), + }) + test(a[10], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + }) + test(a[12], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + }) + test(a[14], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + }) + +} + +func testSliceEqual(t *testing.T, a, b []ma.Multiaddr) { + if len(a) != len(b) { + t.Error("differ", a, b) + } + for i, addrA := range a { + if !addrA.Equal(b[i]) { + t.Error("differ", a, b) + } + } +} + +func TestInterfaceAddressesWorks(t *testing.T) { + _, err := InterfaceMultiaddrs() + if err != nil { + t.Fatal(err) + } +} + +func TestNetListener(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:1234") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + malist, err := WrapNetListener(listener) + if err != nil { + t.Fatal(err) + } + if !malist.Multiaddr().Equal(newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234")) { + t.Fatal("unexpected multiaddr") + } + + go func() { + c, err := Dial(malist.Multiaddr()) + if err != nil { + t.Error("failed to dial") + } + if !c.RemoteMultiaddr().Equal(malist.Multiaddr()) { + t.Error("dialed wrong target") + } + c.Close() + + c, err = Dial(malist.Multiaddr()) + if err != nil { + t.Error("failed to dial") + } + c.Close() + }() + + c, err := malist.Accept() + if err != nil { + t.Fatal(err) + } + c.Close() + netList := NetListener(malist) + malist2, err := WrapNetListener(netList) + if err != nil { + t.Fatal(err) + } + if malist2 != malist { + t.Fatal("expected WrapNetListener(NetListener(malist)) == malist") + } + nc, err := netList.Accept() + if err != nil { + t.Fatal(err) + } + if !nc.(Conn).LocalMultiaddr().Equal(malist.Multiaddr()) { + t.Fatal("wrong multiaddr on conn") + } + nc.Close() +} + +func BenchmarkResolveUnspecifiedAddress(b *testing.B) { + b.ReportAllocs() + a := ma.StringCast("/ip4/0.0.0.0/udp/42/quic-v1") + iaddrs, _ := interfaceAddresses() + for i := 0; i < b.N; i++ { + ResolveUnspecifiedAddress(a, iaddrs) + } +} diff --git a/compattest/internal/prev/net/private.go b/compattest/internal/prev/net/private.go new file mode 100644 index 00000000..af55c2a9 --- /dev/null +++ b/compattest/internal/prev/net/private.go @@ -0,0 +1,208 @@ +package manet + +import ( + "net" + "strings" + + ma "github.com/multiformats/go-multiaddr" +) + +// Private4 and Private6 are well-known private networks +var Private4, Private6 []*net.IPNet +var privateCIDR4 = []string{ + // localhost + "127.0.0.0/8", + // private networks + "10.0.0.0/8", + "100.64.0.0/10", + "172.16.0.0/12", + "192.168.0.0/16", + // link local + "169.254.0.0/16", +} +var privateCIDR6 = []string{ + // localhost + "::1/128", + // ULA reserved + "fc00::/7", + // link local + "fe80::/10", +} + +// Unroutable4 and Unroutable6 are well known unroutable address ranges +var Unroutable4, Unroutable6 []*net.IPNet +var unroutableCIDR4 = []string{ + "0.0.0.0/8", + "192.0.0.0/26", + "192.0.2.0/24", + "192.88.99.0/24", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "224.0.0.0/4", + "240.0.0.0/4", + "255.255.255.255/32", +} +var unroutableCIDR6 = []string{ + "ff00::/8", // multicast + "2001:db8::/32", // documentation +} + +var globalUnicast []*net.IPNet +var globalUnicastCIDR6 = []string{ + "2000::/3", +} + +var nat64CIDRs = []string{ + "64:ff9b:1::/48", // RFC 8215 + "64:ff9b::/96", // RFC 6052 +} + +var nat64 []*net.IPNet + +// unResolvableDomains do not resolve to an IP address. +// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names +var unResolvableDomains = []string{ + // Reverse DNS Lookup + ".in-addr.arpa", + ".ip6.arpa", + + // RFC 6761: Users MAY assume that queries for "invalid" names will always return NXDOMAIN + // responses + ".invalid", +} + +// privateUseDomains are reserved for private use and have no central authority for consistent +// address resolution +// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names +var privateUseDomains = []string{ + // RFC 8375: Reserved for home networks + ".home.arpa", + + // MDNS + ".local", + + // RFC 6761: No central authority for .test names + ".test", +} + +// RFC 6761: Users may assume that IPv4 and IPv6 address queries for localhost names will +// always resolve to the respective IP loopback address +const localHostDomain = ".localhost" + +func init() { + Private4 = parseCIDR(privateCIDR4) + Private6 = parseCIDR(privateCIDR6) + Unroutable4 = parseCIDR(unroutableCIDR4) + Unroutable6 = parseCIDR(unroutableCIDR6) + globalUnicast = parseCIDR(globalUnicastCIDR6) + nat64 = parseCIDR(nat64CIDRs) +} + +func parseCIDR(cidrs []string) []*net.IPNet { + ipnets := make([]*net.IPNet, len(cidrs)) + for i, cidr := range cidrs { + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + panic(err) + } + ipnets[i] = ipnet + } + return ipnets +} + +// IsPublicAddr returns true if the IP part of the multiaddr is a publicly routable address +// or if it's a dns address without a special use domain e.g. .local. +func IsPublicAddr(a ma.Multiaddr) bool { + isPublic := false + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_IP6ZONE: + return true + case ma.P_IP4: + ip := net.IP(c.RawValue()) + isPublic = !inAddrRange(ip, Private4) && !inAddrRange(ip, Unroutable4) + case ma.P_IP6: + ip := net.IP(c.RawValue()) + // IP6 documentation prefix(part of Unroutable6) is a subset of the ip6 + // global unicast allocation so we ensure that it's not a documentation + // prefix by diffing with Unroutable6 + isPublicUnicastAddr := inAddrRange(ip, globalUnicast) && !inAddrRange(ip, Unroutable6) + if isPublicUnicastAddr { + isPublic = true + return false + } + // The WellKnown NAT64 prefix(RFC 6052) can only reference a public IPv4 + // address. + // The Local use NAT64 prefix(RFC 8215) can reference private IPv4 + // addresses. But since the translation from Local use NAT64 prefix to IPv4 + // address is left to the user we have no way of knowing which IPv4 address + // is referenced. We count these as Public addresses because a false + // negative for this method here is generally worse than a false positive. + isPublic = inAddrRange(ip, nat64) + return false + case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR: + dnsAddr := c.Value() + isPublic = true + if isSubdomain(dnsAddr, localHostDomain) { + isPublic = false + return false + } + for _, ud := range unResolvableDomains { + if isSubdomain(dnsAddr, ud) { + isPublic = false + return false + } + } + for _, pd := range privateUseDomains { + if isSubdomain(dnsAddr, pd) { + isPublic = false + break + } + } + } + return false + }) + return isPublic +} + +// isSubdomain checks if child is sub domain of parent. It also returns true if child and parent are +// the same domain. +// Parent must have a "." prefix. +func isSubdomain(child, parent string) bool { + return strings.HasSuffix(child, parent) || child == parent[1:] +} + +// IsPrivateAddr returns true if the IP part of the mutiaddr is in a private network +func IsPrivateAddr(a ma.Multiaddr) bool { + isPrivate := false + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_IP6ZONE: + return true + case ma.P_IP4: + isPrivate = inAddrRange(net.IP(c.RawValue()), Private4) + case ma.P_IP6: + isPrivate = inAddrRange(net.IP(c.RawValue()), Private6) + case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR: + dnsAddr := c.Value() + if isSubdomain(dnsAddr, localHostDomain) { + isPrivate = true + } + // We don't check for privateUseDomains because private use domains can + // resolve to public IP addresses + } + return false + }) + return isPrivate +} + +func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool { + for _, ipnet := range ipnets { + if ipnet.Contains(ip) { + return true + } + } + + return false +} diff --git a/compattest/internal/prev/net/private_test.go b/compattest/internal/prev/net/private_test.go new file mode 100644 index 00000000..c76b5dbf --- /dev/null +++ b/compattest/internal/prev/net/private_test.go @@ -0,0 +1,84 @@ +package manet + +import ( + "fmt" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestIsPublicAddr(t *testing.T) { + tests := []struct { + addr ma.Multiaddr + isPublic bool + isPrivate bool + }{ + { + addr: ma.StringCast("/ip4/192.168.1.1/tcp/80"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/ip4/1.1.1.1/tcp/80"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/tcp/80/ip4/1.1.1.1"), + isPublic: false, + isPrivate: false, + }, + { + addr: ma.StringCast("/dns/node.libp2p.io/udp/1/quic-v1"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/dnsaddr/node.libp2p.io/udp/1/quic-v1"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/dns/node.libp2p.local/udp/1/quic-v1"), + isPublic: false, + isPrivate: false, // You can configure .local domains in local networks to return public addrs + }, + { + addr: ma.StringCast("/dns/localhost/udp/1/quic-v1"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/dns/a.localhost/tcp/1"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/ip6/2400::1/tcp/10"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/ip6/2001:db8::42/tcp/10"), + isPublic: false, + isPrivate: false, + }, + { + addr: ma.StringCast("/ip6/64:ff9b::1.1.1.1/tcp/10"), + isPublic: true, + isPrivate: false, + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + isPublic := IsPublicAddr(tt.addr) + isPrivate := IsPrivateAddr(tt.addr) + if isPublic != tt.isPublic { + t.Errorf("IsPublicAddr check failed for %s: expected %t, got %t", tt.addr, tt.isPublic, isPublic) + } + if isPrivate != tt.isPrivate { + t.Errorf("IsPrivateAddr check failed for %s: expected %t, got %t", tt.addr, tt.isPrivate, isPrivate) + } + }) + } +} diff --git a/compattest/internal/prev/net/registry.go b/compattest/internal/prev/net/registry.go new file mode 100644 index 00000000..7883b958 --- /dev/null +++ b/compattest/internal/prev/net/registry.go @@ -0,0 +1,97 @@ +package manet + +import ( + "fmt" + "net" + "sync" + + ma "github.com/multiformats/go-multiaddr" +) + +// FromNetAddrFunc is a generic function which converts a net.Addr to Multiaddress +type FromNetAddrFunc func(a net.Addr) (ma.Multiaddr, error) + +// ToNetAddrFunc is a generic function which converts a Multiaddress to net.Addr +type ToNetAddrFunc func(ma ma.Multiaddr) (net.Addr, error) + +var defaultCodecs = NewCodecMap() + +func init() { + RegisterFromNetAddr(parseTCPNetAddr, "tcp", "tcp4", "tcp6") + RegisterFromNetAddr(parseUDPNetAddr, "udp", "udp4", "udp6") + RegisterFromNetAddr(parseIPNetAddr, "ip", "ip4", "ip6") + RegisterFromNetAddr(parseIPPlusNetAddr, "ip+net") + RegisterFromNetAddr(parseUnixNetAddr, "unix") + + RegisterToNetAddr(parseBasicNetMaddr, "tcp", "udp", "ip6", "ip4", "unix") +} + +// CodecMap holds a map of NetCodecs indexed by their Protocol ID +// along with parsers for the addresses they use. +// It is used to keep a list of supported network address codecs (protocols +// which addresses can be converted to and from multiaddresses). +type CodecMap struct { + addrParsers map[string]FromNetAddrFunc + maddrParsers map[string]ToNetAddrFunc + lk sync.Mutex +} + +// NewCodecMap initializes and returns a CodecMap object. +func NewCodecMap() *CodecMap { + return &CodecMap{ + addrParsers: make(map[string]FromNetAddrFunc), + maddrParsers: make(map[string]ToNetAddrFunc), + } +} + +// RegisterFromNetAddr registers a conversion from net.Addr instances to multiaddrs. +func RegisterFromNetAddr(from FromNetAddrFunc, networks ...string) { + defaultCodecs.RegisterFromNetAddr(from, networks...) +} + +// RegisterToNetAddr registers a conversion from multiaddrs to net.Addr instances. +func RegisterToNetAddr(to ToNetAddrFunc, protocols ...string) { + defaultCodecs.RegisterToNetAddr(to, protocols...) +} + +// RegisterFromNetAddr registers a conversion from net.Addr instances to multiaddrs +func (cm *CodecMap) RegisterFromNetAddr(from FromNetAddrFunc, networks ...string) { + cm.lk.Lock() + defer cm.lk.Unlock() + + for _, n := range networks { + cm.addrParsers[n] = from + } +} + +// RegisterToNetAddr registers a conversion from multiaddrs to net.Addr instances +func (cm *CodecMap) RegisterToNetAddr(to ToNetAddrFunc, protocols ...string) { + cm.lk.Lock() + defer cm.lk.Unlock() + + for _, p := range protocols { + cm.maddrParsers[p] = to + } +} + +func (cm *CodecMap) getAddrParser(net string) (FromNetAddrFunc, error) { + cm.lk.Lock() + defer cm.lk.Unlock() + + parser, ok := cm.addrParsers[net] + if !ok { + return nil, fmt.Errorf("unknown network %v", net) + } + return parser, nil +} + +func (cm *CodecMap) getMaddrParser(name string) (ToNetAddrFunc, error) { + cm.lk.Lock() + defer cm.lk.Unlock() + p, ok := cm.maddrParsers[name] + if !ok { + return nil, fmt.Errorf("network not supported: %s", name) + } + + return p, nil +} diff --git a/compattest/internal/prev/net/registry_test.go b/compattest/internal/prev/net/registry_test.go new file mode 100644 index 00000000..9aaf4def --- /dev/null +++ b/compattest/internal/prev/net/registry_test.go @@ -0,0 +1,42 @@ +package manet + +import ( + "net" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestRegisterFrom(t *testing.T) { + cm := NewCodecMap() + cm.RegisterFromNetAddr( + func(a net.Addr) (ma.Multiaddr, error) { return nil, nil }, + "test", "iptest", "blahtest", + ) + if _, ok := cm.addrParsers["test"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.addrParsers["iptest"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.addrParsers["blahtest"]; !ok { + t.Fatal("myproto not properly registered") + } +} + +func TestRegisterTo(t *testing.T) { + cm := NewCodecMap() + cm.RegisterToNetAddr( + func(a ma.Multiaddr) (net.Addr, error) { return nil, nil }, + "test", "iptest", "blahtest", + ) + if _, ok := cm.maddrParsers["test"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.maddrParsers["iptest"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.maddrParsers["blahtest"]; !ok { + t.Fatal("myproto not properly registered") + } +} diff --git a/compattest/internal/prev/net/resolve.go b/compattest/internal/prev/net/resolve.go new file mode 100644 index 00000000..44c2ef1f --- /dev/null +++ b/compattest/internal/prev/net/resolve.go @@ -0,0 +1,87 @@ +package manet + +import ( + "fmt" + + ma "github.com/multiformats/go-multiaddr" +) + +// ResolveUnspecifiedAddress expands an unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. If ifaceAddr is nil, we request interface addresses +// from the network stack. (this is so you can provide a cached value if resolving many addrs) +func ResolveUnspecifiedAddress(resolve ma.Multiaddr, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // split address into its components + first, rest := ma.SplitFirst(resolve) + + // if first component (ip) is not unspecified, use it as is. + if !IsIPUnspecified(first) { + return []ma.Multiaddr{resolve}, nil + } + + resolveProto := resolve.Protocols()[0].Code + out := make([]ma.Multiaddr, 0, len(ifaceAddrs)) + for _, ia := range ifaceAddrs { + iafirst, _ := ma.SplitFirst(ia) + // must match the first protocol to be resolve. + if iafirst.Protocol().Code != resolveProto { + continue + } + + joined := ia + if rest != nil { + joined = ma.Join(ia, rest) + } + out = append(out, joined) + } + if len(out) < 1 { + return nil, fmt.Errorf("failed to resolve: %s", resolve) + } + return out, nil +} + +// ResolveUnspecifiedAddresses expands unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. +func ResolveUnspecifiedAddresses(unspecAddrs, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // todo optimize: only fetch these if we have a "any" addr. + if len(ifaceAddrs) < 1 { + var err error + ifaceAddrs, err = interfaceAddresses() + if err != nil { + return nil, err + } + } + + var outputAddrs []ma.Multiaddr + for _, a := range unspecAddrs { + // unspecified? + resolved, err := ResolveUnspecifiedAddress(a, ifaceAddrs) + if err != nil { + continue // optimistic. if we can't resolve anything, we'll know at the bottom. + } + outputAddrs = append(outputAddrs, resolved...) + } + + if len(outputAddrs) < 1 { + return nil, fmt.Errorf("failed to specify addrs: %s", unspecAddrs) + } + return outputAddrs, nil +} + +// interfaceAddresses returns a list of addresses associated with local machine +// Note: we do not return link local addresses. IP loopback is ok, because we +// may be connecting to other nodes in the same machine. +func interfaceAddresses() ([]ma.Multiaddr, error) { + maddrs, err := InterfaceMultiaddrs() + if err != nil { + return nil, err + } + + var out []ma.Multiaddr + for _, a := range maddrs { + if IsIP6LinkLocal(a) { + continue + } + out = append(out, a) + } + return out, nil +} diff --git a/compattest/internal/prev/net/resolve_test.go b/compattest/internal/prev/net/resolve_test.go new file mode 100644 index 00000000..e4af820f --- /dev/null +++ b/compattest/internal/prev/net/resolve_test.go @@ -0,0 +1,60 @@ +package manet + +import ( + "testing" + + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +func TestResolvingAddrs(t *testing.T) { + unspec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/0.0.0.0/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + newMultiaddr(t, "/ip4/0.0.0.0"), + } + + iface := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1"), + newMultiaddr(t, "/ip4/10.20.30.40"), + newMultiaddr(t, "/ip6/::1"), + newMultiaddr(t, "/ip6/::f"), + } + + spec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/10.20.30.40/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::f/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + newMultiaddr(t, "/ip4/127.0.0.1"), + newMultiaddr(t, "/ip4/10.20.30.40"), + } + + actual, err := ResolveUnspecifiedAddresses(unspec, iface) + require.NoError(t, err) + require.Equal(t, actual, spec) + + ip4u := []ma.Multiaddr{newMultiaddr(t, "/ip4/0.0.0.0")} + ip4i := []ma.Multiaddr{newMultiaddr(t, "/ip4/1.2.3.4")} + + ip6u := []ma.Multiaddr{newMultiaddr(t, "/ip6/::")} + ip6i := []ma.Multiaddr{newMultiaddr(t, "/ip6/::1")} + + if _, err := ResolveUnspecifiedAddress(ip4u[0], ip6i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddress(ip6u[0], ip4i); err == nil { + t.Fatal("should have failed") + } + + if _, err := ResolveUnspecifiedAddresses(ip6u, ip4i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddresses(ip4u, ip6i); err == nil { + t.Fatal("should have failed") + } +} diff --git a/compattest/internal/prev/package.json b/compattest/internal/prev/package.json new file mode 100644 index 00000000..c493b27e --- /dev/null +++ b/compattest/internal/prev/package.json @@ -0,0 +1,23 @@ +{ + "author": "multiformats", + "bugs": { + "url": "https://github.com/multiformats/go-multiaddr/issues" + }, + "gx": { + "dvcsimport": "github.com/multiformats/go-multiaddr" + }, + "gxDependencies": [ + { + "hash": "QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW", + "name": "go-multihash", + "version": "1.0.9" + } + ], + "gxVersion": "0.9.0", + "language": "go", + "license": "MIT", + "name": "go-multiaddr", + "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", + "version": "1.4.1" +} + diff --git a/compattest/internal/prev/protocol.go b/compattest/internal/prev/protocol.go new file mode 100644 index 00000000..61a2924c --- /dev/null +++ b/compattest/internal/prev/protocol.go @@ -0,0 +1,102 @@ +package multiaddr + +import ( + "fmt" + "strings" +) + +// These are special sizes +const ( + LengthPrefixedVarSize = -1 +) + +// Protocol is a Multiaddr protocol description structure. +type Protocol struct { + // Name is the string representation of the protocol code. E.g., ip4, + // ip6, tcp, udp, etc. + Name string + + // Code is the protocol's multicodec (a normal, non-varint number). + Code int + + // VCode is a precomputed varint encoded version of Code. + VCode []byte + + // Size is the size of the argument to this protocol. + // + // * Size == 0 means this protocol takes no argument. + // * Size > 0 means this protocol takes a constant sized argument. + // * Size < 0 means this protocol takes a variable length, varint + // prefixed argument. + Size int // a size of -1 indicates a length-prefixed variable size + + // Path indicates a path protocol (e.g., unix). When parsing multiaddr + // strings, path protocols consume the remainder of the address instead + // of stopping at the next forward slash. + // + // Size must be LengthPrefixedVarSize. + Path bool + + // Transcoder converts between the byte representation and the string + // representation of this protocol's argument (if any). + // + // This should only be non-nil if Size != 0 + Transcoder Transcoder +} + +var protocolsByName = map[string]Protocol{} +var protocolsByCode = map[int]Protocol{} + +// Protocols is the list of multiaddr protocols supported by this module. +var Protocols = []Protocol{} + +func AddProtocol(p Protocol) error { + if _, ok := protocolsByName[p.Name]; ok { + return fmt.Errorf("protocol by the name %q already exists", p.Name) + } + + if _, ok := protocolsByCode[p.Code]; ok { + return fmt.Errorf("protocol code %d already taken by %q", p.Code, p.Code) + } + + if p.Size != 0 && p.Transcoder == nil { + return fmt.Errorf("protocols with arguments must define transcoders") + } + if p.Path && p.Size >= 0 { + return fmt.Errorf("path protocols must have variable-length sizes") + } + + Protocols = append(Protocols, p) + protocolsByName[p.Name] = p + protocolsByCode[p.Code] = p + return nil +} + +// ProtocolWithName returns the Protocol description with given string name. +func ProtocolWithName(s string) Protocol { + return protocolsByName[s] +} + +// ProtocolWithCode returns the Protocol description with given protocol code. +func ProtocolWithCode(c int) Protocol { + return protocolsByCode[c] +} + +// ProtocolsWithString returns a slice of protocols matching given string. +func ProtocolsWithString(s string) ([]Protocol, error) { + s = strings.Trim(s, "/") + sp := strings.Split(s, "/") + if len(sp) == 0 { + return nil, nil + } + + t := make([]Protocol, len(sp)) + for i, name := range sp { + p := ProtocolWithName(name) + if p.Code == 0 { + return nil, fmt.Errorf("no protocol with name: %s", name) + } + t[i] = p + } + return t, nil +} diff --git a/compattest/internal/prev/protocols.go b/compattest/internal/prev/protocols.go new file mode 100644 index 00000000..0e74b5a1 --- /dev/null +++ b/compattest/internal/prev/protocols.go @@ -0,0 +1,344 @@ +package multiaddr + +// You **MUST** register your multicodecs with +// https://github.com/multiformats/multicodec before adding them here. +const ( + P_IP4 = 4 + P_TCP = 6 + P_DNS = 53 // 4 or 6 + P_DNS4 = 54 + P_DNS6 = 55 + P_DNSADDR = 56 + P_UDP = 273 + P_DCCP = 33 + P_IP6 = 41 + P_IP6ZONE = 42 + P_IPCIDR = 43 + P_QUIC = 460 + P_QUIC_V1 = 461 + P_WEBTRANSPORT = 465 + P_CERTHASH = 466 + P_SCTP = 132 + P_CIRCUIT = 290 + P_UDT = 301 + P_UTP = 302 + P_UNIX = 400 + P_P2P = 421 + P_IPFS = P_P2P // alias for backwards compatibility + P_HTTP = 480 + P_HTTP_PATH = 481 + P_HTTPS = 443 // deprecated alias for /tls/http + P_ONION = 444 // also for backwards compatibility + P_ONION3 = 445 + P_GARLIC64 = 446 + P_GARLIC32 = 447 + P_P2P_WEBRTC_DIRECT = 276 // Deprecated. use webrtc-direct instead + P_TLS = 448 + P_SNI = 449 + P_NOISE = 454 + P_WS = 477 + P_WSS = 478 // deprecated alias for /tls/ws + P_PLAINTEXTV2 = 7367777 + P_WEBRTC_DIRECT = 280 + P_WEBRTC = 281 + P_MEMORY = 777 +) + +var ( + protoIP4 = Protocol{ + Name: "ip4", + Code: P_IP4, + VCode: CodeToVarint(P_IP4), + Size: 32, + Path: false, + Transcoder: TranscoderIP4, + } + protoTCP = Protocol{ + Name: "tcp", + Code: P_TCP, + VCode: CodeToVarint(P_TCP), + Size: 16, + Path: false, + Transcoder: TranscoderPort, + } + protoDNS = Protocol{ + Code: P_DNS, + Size: LengthPrefixedVarSize, + Name: "dns", + VCode: CodeToVarint(P_DNS), + Transcoder: TranscoderDns, + } + protoDNS4 = Protocol{ + Code: P_DNS4, + Size: LengthPrefixedVarSize, + Name: "dns4", + VCode: CodeToVarint(P_DNS4), + Transcoder: TranscoderDns, + } + protoDNS6 = Protocol{ + Code: P_DNS6, + Size: LengthPrefixedVarSize, + Name: "dns6", + VCode: CodeToVarint(P_DNS6), + Transcoder: TranscoderDns, + } + protoDNSADDR = Protocol{ + Code: P_DNSADDR, + Size: LengthPrefixedVarSize, + Name: "dnsaddr", + VCode: CodeToVarint(P_DNSADDR), + Transcoder: TranscoderDns, + } + protoUDP = Protocol{ + Name: "udp", + Code: P_UDP, + VCode: CodeToVarint(P_UDP), + Size: 16, + Path: false, + Transcoder: TranscoderPort, + } + protoDCCP = Protocol{ + Name: "dccp", + Code: P_DCCP, + VCode: CodeToVarint(P_DCCP), + Size: 16, + Path: false, + Transcoder: TranscoderPort, + } + protoIP6 = Protocol{ + Name: "ip6", + Code: P_IP6, + VCode: CodeToVarint(P_IP6), + Size: 128, + Transcoder: TranscoderIP6, + } + protoIPCIDR = Protocol{ + Name: "ipcidr", + Code: P_IPCIDR, + VCode: CodeToVarint(P_IPCIDR), + Size: 8, + Transcoder: TranscoderIPCIDR, + } + // these require varint + protoIP6ZONE = Protocol{ + Name: "ip6zone", + Code: P_IP6ZONE, + VCode: CodeToVarint(P_IP6ZONE), + Size: LengthPrefixedVarSize, + Path: false, + Transcoder: TranscoderIP6Zone, + } + protoSCTP = Protocol{ + Name: "sctp", + Code: P_SCTP, + VCode: CodeToVarint(P_SCTP), + Size: 16, + Transcoder: TranscoderPort, + } + + protoCIRCUIT = Protocol{ + Code: P_CIRCUIT, + Size: 0, + Name: "p2p-circuit", + VCode: CodeToVarint(P_CIRCUIT), + } + + protoONION2 = Protocol{ + Name: "onion", + Code: P_ONION, + VCode: CodeToVarint(P_ONION), + Size: 96, + Transcoder: TranscoderOnion, + } + protoONION3 = Protocol{ + Name: "onion3", + Code: P_ONION3, + VCode: CodeToVarint(P_ONION3), + Size: 296, + Transcoder: TranscoderOnion3, + } + protoGARLIC64 = Protocol{ + Name: "garlic64", + Code: P_GARLIC64, + VCode: CodeToVarint(P_GARLIC64), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderGarlic64, + } + protoGARLIC32 = Protocol{ + Name: "garlic32", + Code: P_GARLIC32, + VCode: CodeToVarint(P_GARLIC32), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderGarlic32, + } + protoUTP = Protocol{ + Name: "utp", + Code: P_UTP, + VCode: CodeToVarint(P_UTP), + } + protoUDT = Protocol{ + Name: "udt", + Code: P_UDT, + VCode: CodeToVarint(P_UDT), + } + protoQUIC = Protocol{ + Name: "quic", + Code: P_QUIC, + VCode: CodeToVarint(P_QUIC), + } + protoQUICV1 = Protocol{ + Name: "quic-v1", + Code: P_QUIC_V1, + VCode: CodeToVarint(P_QUIC_V1), + } + protoWEBTRANSPORT = Protocol{ + Name: "webtransport", + Code: P_WEBTRANSPORT, + VCode: CodeToVarint(P_WEBTRANSPORT), + } + protoCERTHASH = Protocol{ + Name: "certhash", + Code: P_CERTHASH, + VCode: CodeToVarint(P_CERTHASH), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderCertHash, + } + protoHTTP = Protocol{ + Name: "http", + Code: P_HTTP, + VCode: CodeToVarint(P_HTTP), + } + protoHTTPPath = Protocol{ + Name: "http-path", + Code: P_HTTP_PATH, + VCode: CodeToVarint(P_HTTP_PATH), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderHTTPPath, + } + protoHTTPS = Protocol{ + Name: "https", + Code: P_HTTPS, + VCode: CodeToVarint(P_HTTPS), + } + protoP2P = Protocol{ + Name: "p2p", + Code: P_P2P, + VCode: CodeToVarint(P_P2P), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderP2P, + } + protoUNIX = Protocol{ + Name: "unix", + Code: P_UNIX, + VCode: CodeToVarint(P_UNIX), + Size: LengthPrefixedVarSize, + Path: true, + Transcoder: TranscoderUnix, + } + protoP2P_WEBRTC_DIRECT = Protocol{ + Name: "p2p-webrtc-direct", + Code: P_P2P_WEBRTC_DIRECT, + VCode: CodeToVarint(P_P2P_WEBRTC_DIRECT), + } + protoTLS = Protocol{ + Name: "tls", + Code: P_TLS, + VCode: CodeToVarint(P_TLS), + } + protoSNI = Protocol{ + Name: "sni", + Size: LengthPrefixedVarSize, + Code: P_SNI, + VCode: CodeToVarint(P_SNI), + Transcoder: TranscoderDns, + } + protoNOISE = Protocol{ + Name: "noise", + Code: P_NOISE, + VCode: CodeToVarint(P_NOISE), + } + protoPlaintextV2 = Protocol{ + Name: "plaintextv2", + Code: P_PLAINTEXTV2, + VCode: CodeToVarint(P_PLAINTEXTV2), + } + protoWS = Protocol{ + Name: "ws", + Code: P_WS, + VCode: CodeToVarint(P_WS), + } + protoWSS = Protocol{ + Name: "wss", + Code: P_WSS, + VCode: CodeToVarint(P_WSS), + } + protoWebRTCDirect = Protocol{ + Name: "webrtc-direct", + Code: P_WEBRTC_DIRECT, + VCode: CodeToVarint(P_WEBRTC_DIRECT), + } + protoWebRTC = Protocol{ + Name: "webrtc", + Code: P_WEBRTC, + VCode: CodeToVarint(P_WEBRTC), + } + + protoMemory = Protocol{ + Name: "memory", + Code: P_MEMORY, + VCode: CodeToVarint(P_MEMORY), + Size: 64, + Transcoder: TranscoderMemory, + } +) + +func init() { + for _, p := range []Protocol{ + protoIP4, + protoTCP, + protoDNS, + protoDNS4, + protoDNS6, + protoDNSADDR, + protoUDP, + protoDCCP, + protoIP6, + protoIP6ZONE, + protoIPCIDR, + protoSCTP, + protoCIRCUIT, + protoONION2, + protoONION3, + protoGARLIC64, + protoGARLIC32, + protoUTP, + protoUDT, + protoQUIC, + protoQUICV1, + protoWEBTRANSPORT, + protoCERTHASH, + protoHTTP, + protoHTTPPath, + protoHTTPS, + protoP2P, + protoUNIX, + protoP2P_WEBRTC_DIRECT, + protoTLS, + protoSNI, + protoNOISE, + protoWS, + protoWSS, + protoPlaintextV2, + protoWebRTCDirect, + protoWebRTC, + protoMemory, + } { + if err := AddProtocol(p); err != nil { + panic(err) + } + } + + // explicitly set both of these + protocolsByName["p2p"] = protoP2P + protocolsByName["ipfs"] = protoP2P +} diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 new file mode 100644 index 00000000..013e7dc9 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xa5\x03&\x12$000000000000000000000000000000000000") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 new file mode 100644 index 00000000..02922958 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x90\x03\x06000000") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb new file mode 100644 index 00000000..7fc603cd --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xbd\x0300000000000000000000000000000000000\x00\x00") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 new file mode 100644 index 00000000..77c10c85 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x90\x03\x06000000\x90\x03\x06000000") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 new file mode 100644 index 00000000..17866a99 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("7*000000000000000000000000000000000000000000") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 new file mode 100644 index 00000000..05d670cb --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xd2\x03\x00") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e new file mode 100644 index 00000000..62c184cc --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("7\x00") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e new file mode 100644 index 00000000..7326a783 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xbc\x030000000000\x00\x00") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d new file mode 100644 index 00000000..f4903958 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x84\xe0\xff\x00") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 new file mode 100644 index 00000000..66332457 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x90\x03\x06/0000/") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 new file mode 100644 index 00000000..9a3e0ddd --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xa5\x03\x030\x010") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 new file mode 100644 index 00000000..976f5059 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/garlic32/2222222222222222222222222222222222222222222222222222222") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 new file mode 100644 index 00000000..f627d5d1 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/onion/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\xa0\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7fd\x00-\a\t\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x04\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x00\x01\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\n\xec\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\n\xff\v\v\v\v\v\v\v\v\v\v\v\v\v\x80\x00\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x87نŁ\xe2\xec\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\r\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xf2\n\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x1d\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v2222=:") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 new file mode 100644 index 00000000..df7624a1 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/p2p/BAfZA2jA222222222222222222222222222222222222222222222222222222222") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 new file mode 100644 index 00000000..3b6089e4 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/p2p/BAfZBAjA222222222222222222222222222222222222222222222222222222222") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 new file mode 100644 index 00000000..805c039e --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/garlic64/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\r\r\r\r\r\r\r\r0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 new file mode 100644 index 00000000..6c48422f --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/garlic32/222222222222222222222222222222222222222222222\r\r\r\r\r\r\r") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a new file mode 100644 index 00000000..a542e410 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/p2p/Qm11ps1111111111111111111111111111111") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a new file mode 100644 index 00000000..ec2b8bba --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/sni//sni/0") diff --git a/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae new file mode 100644 index 00000000..829d05f5 --- /dev/null +++ b/compattest/internal/prev/testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/onion/222222222222222=:1") diff --git a/compattest/internal/prev/transcoders.go b/compattest/internal/prev/transcoders.go new file mode 100644 index 00000000..03b39aeb --- /dev/null +++ b/compattest/internal/prev/transcoders.go @@ -0,0 +1,520 @@ +package multiaddr + +import ( + "bytes" + "encoding/base32" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "net" + "net/url" + "strconv" + "strings" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multibase" + mh "github.com/multiformats/go-multihash" +) + +type Transcoder interface { + // Validates and encodes to bytes a multiaddr that's in the string representation. + StringToBytes(string) ([]byte, error) + // Validates and decodes to a string a multiaddr that's in the bytes representation. + BytesToString([]byte) (string, error) + // Validates bytes when parsing a multiaddr that's already in the bytes representation. + ValidateBytes([]byte) error +} + +func NewTranscoderFromFunctions( + s2b func(string) ([]byte, error), + b2s func([]byte) (string, error), + val func([]byte) error, +) Transcoder { + return twrp{s2b, b2s, val} +} + +type twrp struct { + strtobyte func(string) ([]byte, error) + bytetostr func([]byte) (string, error) + validbyte func([]byte) error +} + +func (t twrp) StringToBytes(s string) ([]byte, error) { + return t.strtobyte(s) +} +func (t twrp) BytesToString(b []byte) (string, error) { + return t.bytetostr(b) +} + +func (t twrp) ValidateBytes(b []byte) error { + if t.validbyte == nil { + return nil + } + return t.validbyte(b) +} + +var TranscoderIP4 = NewTranscoderFromFunctions(ip4StB, ip4BtS, nil) +var TranscoderIP6 = NewTranscoderFromFunctions(ip6StB, ip6BtS, nil) +var TranscoderIP6Zone = NewTranscoderFromFunctions(ip6zoneStB, ip6zoneBtS, ip6zoneVal) +var TranscoderIPCIDR = NewTranscoderFromFunctions(ipcidrStB, ipcidrBtS, ipcidrValidate) + +func ipcidrBtS(b []byte) (string, error) { + if err := ipcidrValidate(b); err != nil { + return "", err + } + return strconv.Itoa(int(b[0])), nil +} + +func ipcidrStB(s string) ([]byte, error) { + ipMask, err := strconv.ParseUint(s, 10, 8) + if err != nil { + return nil, err + } + return []byte{byte(uint8(ipMask))}, nil +} + +func ipcidrValidate(b []byte) error { + if len(b) != 1 { + return fmt.Errorf("invalid length (should be == 1)") + } + return nil +} + +func ip4StB(s string) ([]byte, error) { + i := net.ParseIP(s).To4() + if i == nil { + return nil, fmt.Errorf("failed to parse ip4 addr: %s", s) + } + return i, nil +} + +func ip6zoneStB(s string) ([]byte, error) { + if len(s) == 0 { + return nil, fmt.Errorf("empty ip6zone") + } + if strings.Contains(s, "/") { + return nil, fmt.Errorf("IPv6 zone ID contains '/': %s", s) + } + return []byte(s), nil +} + +func ip6zoneBtS(b []byte) (string, error) { + if len(b) == 0 { + return "", fmt.Errorf("invalid length (should be > 0)") + } + return string(b), nil +} + +func ip6zoneVal(b []byte) error { + if len(b) == 0 { + return fmt.Errorf("invalid length (should be > 0)") + } + // Not supported as this would break multiaddrs. + if bytes.IndexByte(b, '/') >= 0 { + return fmt.Errorf("IPv6 zone ID contains '/': %s", string(b)) + } + return nil +} + +func ip6StB(s string) ([]byte, error) { + i := net.ParseIP(s).To16() + if i == nil { + return nil, fmt.Errorf("failed to parse ip6 addr: %s", s) + } + return i, nil +} + +func ip6BtS(b []byte) (string, error) { + ip := net.IP(b) + if ip4 := ip.To4(); ip4 != nil { + // Go fails to prepend the `::ffff:` part. + return "::ffff:" + ip4.String(), nil + } + return ip.String(), nil +} + +func ip4BtS(b []byte) (string, error) { + return net.IP(b).String(), nil +} + +var TranscoderPort = NewTranscoderFromFunctions(portStB, portBtS, nil) + +func portStB(s string) ([]byte, error) { + i, err := strconv.ParseUint(s, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse port addr: %s", err) + } + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, uint16(i)) + return b, nil +} + +func portBtS(b []byte) (string, error) { + i := binary.BigEndian.Uint16(b) + return strconv.FormatUint(uint64(i), 10), nil +} + +var TranscoderOnion = NewTranscoderFromFunctions(onionStB, onionBtS, onionValidate) + +func onionStB(s string) ([]byte, error) { + addr := strings.Split(s, ":") + if len(addr) != 2 { + return nil, fmt.Errorf("failed to parse onion addr: %s does not contain a port number", s) + } + + onionHostBytes, err := base32.StdEncoding.DecodeString(strings.ToUpper(addr[0])) + if err != nil { + return nil, fmt.Errorf("failed to decode base32 onion addr: %s %s", s, err) + } + + // onion address without the ".onion" substring are 10 bytes long + if len(onionHostBytes) != 10 { + return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onion address", s) + } + + // onion port number + i, err := strconv.ParseUint(addr[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse onion addr: %s", err) + } + if i == 0 { + return nil, fmt.Errorf("failed to parse onion addr: %s", "non-zero port") + } + + onionPortBytes := make([]byte, 2) + binary.BigEndian.PutUint16(onionPortBytes, uint16(i)) + bytes := []byte{} + bytes = append(bytes, onionHostBytes...) + bytes = append(bytes, onionPortBytes...) + return bytes, nil +} + +func onionBtS(b []byte) (string, error) { + addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:10])) + port := binary.BigEndian.Uint16(b[10:12]) + if port == 0 { + return "", fmt.Errorf("failed to parse onion addr: %s", "non-zero port") + } + return addr + ":" + strconv.FormatUint(uint64(port), 10), nil +} + +func onionValidate(b []byte) error { + if len(b) != 12 { + return fmt.Errorf("invalid len for onion addr: got %d expected 12", len(b)) + } + port := binary.BigEndian.Uint16(b[10:12]) + if port == 0 { + return fmt.Errorf("invalid port 0 for onion addr") + } + return nil +} + +var TranscoderOnion3 = NewTranscoderFromFunctions(onion3StB, onion3BtS, onion3Validate) + +func onion3StB(s string) ([]byte, error) { + addr := strings.Split(s, ":") + if len(addr) != 2 { + return nil, fmt.Errorf("failed to parse onion addr: %s does not contain a port number", s) + } + + // onion address without the ".onion" substring + if len(addr[0]) != 56 { + return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onionv3 address. len == %d", s, len(addr[0])) + } + onionHostBytes, err := base32.StdEncoding.DecodeString(strings.ToUpper(addr[0])) + if err != nil { + return nil, fmt.Errorf("failed to decode base32 onion addr: %s %s", s, err) + } + + // onion port number + i, err := strconv.ParseUint(addr[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse onion addr: %s", err) + } + if i == 0 { + return nil, fmt.Errorf("failed to parse onion addr: %s", "non-zero port") + } + + onionPortBytes := make([]byte, 2) + binary.BigEndian.PutUint16(onionPortBytes, uint16(i)) + bytes := []byte{} + bytes = append(bytes, onionHostBytes[0:35]...) + bytes = append(bytes, onionPortBytes...) + return bytes, nil +} + +func onion3BtS(b []byte) (string, error) { + addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:35])) + port := binary.BigEndian.Uint16(b[35:37]) + if port < 1 { + return "", fmt.Errorf("failed to parse onion addr: %s", "port less than 1") + } + str := addr + ":" + strconv.FormatUint(uint64(port), 10) + return str, nil +} + +func onion3Validate(b []byte) error { + if len(b) != 37 { + return fmt.Errorf("invalid len for onion addr: got %d expected 37", len(b)) + } + port := binary.BigEndian.Uint16(b[35:37]) + if port == 0 { + return fmt.Errorf("invalid port 0 for onion addr") + } + return nil +} + +var TranscoderGarlic64 = NewTranscoderFromFunctions(garlic64StB, garlic64BtS, garlic64Validate) + +// i2p uses an alternate character set for base64 addresses. This returns an appropriate encoder. +var garlicBase64Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~") + +func garlic64StB(s string) ([]byte, error) { + garlicHostBytes, err := garlicBase64Encoding.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 i2p addr: %s %s", s, err) + } + + if err := garlic64Validate(garlicHostBytes); err != nil { + return nil, err + } + return garlicHostBytes, nil +} + +func garlic64BtS(b []byte) (string, error) { + if err := garlic64Validate(b); err != nil { + return "", err + } + addr := garlicBase64Encoding.EncodeToString(b) + return addr, nil +} + +func garlic64Validate(b []byte) error { + // A garlic64 address will always be greater than 386 bytes long when encoded. + if len(b) < 386 { + return fmt.Errorf("failed to validate garlic addr: %s not an i2p base64 address. len: %d", b, len(b)) + } + return nil +} + +var TranscoderGarlic32 = NewTranscoderFromFunctions(garlic32StB, garlic32BtS, garlic32Validate) + +var garlicBase32Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567") + +func garlic32StB(s string) ([]byte, error) { + for len(s)%8 != 0 { + s += "=" + } + garlicHostBytes, err := garlicBase32Encoding.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to decode base32 garlic addr: %s, err: %v len: %v", s, err, len(s)) + } + + if err := garlic32Validate(garlicHostBytes); err != nil { + return nil, err + } + return garlicHostBytes, nil +} + +func garlic32BtS(b []byte) (string, error) { + if err := garlic32Validate(b); err != nil { + return "", err + } + return strings.TrimRight(garlicBase32Encoding.EncodeToString(b), "="), nil +} + +func garlic32Validate(b []byte) error { + // an i2p address with encrypted leaseset has len >= 35 bytes + // all other addresses will always be exactly 32 bytes + // https://geti2p.net/spec/b32encrypted + if len(b) < 35 && len(b) != 32 { + return fmt.Errorf("failed to validate garlic addr: %s not an i2p base32 address. len: %d", b, len(b)) + } + return nil +} + +var TranscoderP2P = NewTranscoderFromFunctions(p2pStB, p2pBtS, p2pVal) + +// The encoded peer ID can either be a CID of a key or a raw multihash (identity +// or sha256-256). +func p2pStB(s string) ([]byte, error) { + // check if the address is a base58 encoded sha256 or identity multihash + if strings.HasPrefix(s, "Qm") || strings.HasPrefix(s, "1") { + m, err := mh.FromB58String(s) + if err != nil { + return nil, fmt.Errorf("failed to parse p2p addr: %s %s", s, err) + } + if err := p2pVal(m); err != nil { + return nil, err + } + return m, nil + } + + // check if the address is a CID + c, err := cid.Decode(s) + if err != nil { + return nil, fmt.Errorf("failed to parse p2p addr: %s %s", s, err) + } + + if ty := c.Type(); ty == cid.Libp2pKey { + if err := p2pVal(c.Hash()); err != nil { + return nil, err + } + return c.Hash(), nil + } else { + return nil, fmt.Errorf("failed to parse p2p addr: %s has the invalid codec %d", s, ty) + } +} + +func p2pVal(b []byte) error { + h, err := mh.Decode([]byte(b)) + if err != nil { + return fmt.Errorf("invalid multihash: %s", err) + } + // Peer IDs require either sha256 or identity multihash + // https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#peer-ids + if h.Code != mh.SHA2_256 && h.Code != mh.IDENTITY { + return fmt.Errorf("invalid multihash code %d expected sha-256 or identity", h.Code) + } + // This check should ideally be in multihash. sha256 digest lengths MUST be 32 + if h.Code == mh.SHA2_256 && h.Length != 32 { + return fmt.Errorf("invalid digest length %d for sha256 addr: expected 32", h.Length) + } + return nil +} + +func p2pBtS(b []byte) (string, error) { + m, err := mh.Cast(b) + if err != nil { + return "", err + } + return m.B58String(), nil +} + +var TranscoderUnix = NewTranscoderFromFunctions(unixStB, unixBtS, unixValidate) + +func unixStB(s string) ([]byte, error) { + return []byte(s), nil +} + +func unixBtS(b []byte) (string, error) { + return string(b), nil +} + +func unixValidate(b []byte) error { + // The string to bytes parser requires that all Path protocols begin with a '/' + // file://./codec.go#L49 + if len(b) < 2 { + return fmt.Errorf("byte slice too short: %d", len(b)) + } + if b[0] != '/' { + return errors.New("path protocol must begin with '/'") + } + if b[len(b)-1] == '/' { + return errors.New("unix socket path must not end in '/'") + } + return nil +} + +var TranscoderDns = NewTranscoderFromFunctions(dnsStB, dnsBtS, dnsVal) + +func dnsVal(b []byte) error { + if len(b) == 0 { + return fmt.Errorf("empty dns addr") + } + if bytes.IndexByte(b, '/') >= 0 { + return fmt.Errorf("domain name %q contains a slash", string(b)) + } + return nil +} + +func dnsStB(s string) ([]byte, error) { + b := []byte(s) + if err := dnsVal(b); err != nil { + return nil, err + } + return b, nil +} + +func dnsBtS(b []byte) (string, error) { + return string(b), nil +} + +var TranscoderCertHash = NewTranscoderFromFunctions(certHashStB, certHashBtS, validateCertHash) + +func certHashStB(s string) ([]byte, error) { + _, data, err := multibase.Decode(s) + if err != nil { + return nil, err + } + if _, err := mh.Decode(data); err != nil { + return nil, err + } + return data, nil +} + +func certHashBtS(b []byte) (string, error) { + return multibase.Encode(multibase.Base64url, b) +} + +func validateCertHash(b []byte) error { + _, err := mh.Decode(b) + return err +} + +var TranscoderHTTPPath = NewTranscoderFromFunctions(httpPathStB, httpPathBtS, validateHTTPPath) + +func httpPathStB(s string) ([]byte, error) { + unescaped, err := url.QueryUnescape(s) + if err != nil { + return nil, err + } + if len(unescaped) == 0 { + return nil, fmt.Errorf("empty http path is not allowed") + } + return []byte(unescaped), err +} + +func httpPathBtS(b []byte) (string, error) { + if len(b) == 0 { + return "", fmt.Errorf("empty http path is not allowed") + } + return url.QueryEscape(string(b)), nil +} + +func validateHTTPPath(b []byte) error { + if len(b) == 0 { + return fmt.Errorf("empty http path is not allowed") + } + return nil // We can represent any byte slice when we escape it. +} + +var TranscoderMemory = NewTranscoderFromFunctions(memoryStB, memoryBtS, memoryValidate) + +func memoryStB(s string) ([]byte, error) { + z, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, err + } + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, z) + return buf, nil +} + +func memoryBtS(b []byte) (string, error) { + if len(b) != 8 { + return "", fmt.Errorf("expected uint64, only found %d bits", len(b)*8) + } + z := binary.BigEndian.Uint64(b) + return strconv.FormatUint(z, 10), nil +} + +func memoryValidate(b []byte) error { + // Ensure the byte array is exactly 8 bytes long for a uint64 in big-endian format + if len(b) != 8 { + return errors.New("invalid length: must be exactly 8 bytes") + } + + return nil +} diff --git a/compattest/internal/prev/util.go b/compattest/internal/prev/util.go new file mode 100644 index 00000000..16a347c6 --- /dev/null +++ b/compattest/internal/prev/util.go @@ -0,0 +1,204 @@ +package multiaddr + +import "fmt" + +// Split returns the sub-address portions of a multiaddr. +func Split(m Multiaddr) []Multiaddr { + if _, ok := m.(*Component); ok { + return []Multiaddr{m} + } + var addrs []Multiaddr + ForEach(m, func(c Component) bool { + addrs = append(addrs, &c) + return true + }) + return addrs +} + +// Join returns a combination of addresses. +func Join(ms ...Multiaddr) Multiaddr { + switch len(ms) { + case 0: + // empty multiaddr, unfortunately, we have callers that rely on + // this contract. + return &multiaddr{} + case 1: + return ms[0] + } + + length := 0 + for _, m := range ms { + if m == nil { + continue + } + length += len(m.Bytes()) + } + + bidx := 0 + b := make([]byte, length) + if length == 0 { + return nil + } + for _, mb := range ms { + if mb == nil { + continue + } + bidx += copy(b[bidx:], mb.Bytes()) + } + if length == 0 { + return nil + } + return &multiaddr{bytes: b} +} + +// Cast re-casts a byte slice as a multiaddr. will panic if it fails to parse. +func Cast(b []byte) Multiaddr { + m, err := NewMultiaddrBytes(b) + if err != nil { + panic(fmt.Errorf("multiaddr failed to parse: %s", err)) + } + return m +} + +// StringCast like Cast, but parses a string. Will also panic if it fails to parse. +func StringCast(s string) Multiaddr { + m, err := NewMultiaddr(s) + if err != nil { + panic(fmt.Errorf("multiaddr failed to parse: %s", err)) + } + return m +} + +// SplitFirst returns the first component and the rest of the multiaddr. +func SplitFirst(m Multiaddr) (*Component, Multiaddr) { + if m == nil { + return nil, nil + } + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + return c, nil + } + + b := m.Bytes() + if len(b) == 0 { + return nil, nil + } + n, c, err := readComponent(b) + if err != nil { + panic(err) + } + if len(b) == n { + return &c, nil + } + return &c, &multiaddr{b[n:]} +} + +// SplitLast returns the rest of the multiaddr and the last component. +func SplitLast(m Multiaddr) (Multiaddr, *Component) { + if m == nil { + return nil, nil + } + + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + return nil, c + } + + b := m.Bytes() + if len(b) == 0 { + return nil, nil + } + + var ( + c Component + err error + offset int + ) + for { + var n int + n, c, err = readComponent(b[offset:]) + if err != nil { + panic(err) + } + if len(b) == n+offset { + // Reached end + if offset == 0 { + // Only one component + return nil, &c + } + return &multiaddr{b[:offset]}, &c + } + offset += n + } +} + +// SplitFunc splits the multiaddr when the callback first returns true. The +// component on which the callback first returns will be included in the +// *second* multiaddr. +func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { + if m == nil { + return nil, nil + } + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + if cb(*c) { + return nil, m + } + return m, nil + } + b := m.Bytes() + if len(b) == 0 { + return nil, nil + } + var ( + c Component + err error + offset int + ) + for offset < len(b) { + var n int + n, c, err = readComponent(b[offset:]) + if err != nil { + panic(err) + } + if cb(c) { + break + } + offset += n + } + switch offset { + case 0: + return nil, m + case len(b): + return m, nil + default: + return &multiaddr{b[:offset]}, &multiaddr{b[offset:]} + } +} + +// ForEach walks over the multiaddr, component by component. +// +// This function iterates over components *by value* to avoid allocating. +// Return true to continue iteration, false to stop. +func ForEach(m Multiaddr, cb func(c Component) bool) { + if m == nil { + return + } + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + cb(*c) + return + } + + b := m.Bytes() + for len(b) > 0 { + n, c, err := readComponent(b) + if err != nil { + panic(err) + } + if !cb(c) { + return + } + b = b[n:] + } +} diff --git a/compattest/internal/prev/util_test.go b/compattest/internal/prev/util_test.go new file mode 100644 index 00000000..4976d7de --- /dev/null +++ b/compattest/internal/prev/util_test.go @@ -0,0 +1,142 @@ +package multiaddr + +import ( + "strings" + "testing" +) + +func TestSplitFirstLast(t *testing.T) { + ipStr := "/ip4/0.0.0.0" + tcpStr := "/tcp/123" + quicStr := "/quic" + ipfsStr := "/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7" + + for _, x := range [][]string{ + {ipStr, tcpStr, quicStr, ipfsStr}, + {ipStr, tcpStr, ipfsStr}, + {ipStr, tcpStr}, + {ipStr}, + } { + addr := StringCast(strings.Join(x, "")) + head, tail := SplitFirst(addr) + rest, last := SplitLast(addr) + if len(x) == 0 { + if head != nil { + t.Error("expected head to be nil") + } + if tail != nil { + t.Error("expected tail to be nil") + } + if rest != nil { + t.Error("expected rest to be nil") + } + if last != nil { + t.Error("expected last to be nil") + } + continue + } + if !head.Equal(StringCast(x[0])) { + t.Errorf("expected %s to be %s", head, x[0]) + } + if !last.Equal(StringCast(x[len(x)-1])) { + t.Errorf("expected %s to be %s", head, x[len(x)-1]) + } + if len(x) == 1 { + if tail != nil { + t.Error("expected tail to be nil") + } + if rest != nil { + t.Error("expected rest to be nil") + } + continue + } + tailExp := strings.Join(x[1:], "") + if !tail.Equal(StringCast(tailExp)) { + t.Errorf("expected %s to be %s", tail, tailExp) + } + restExp := strings.Join(x[:len(x)-1], "") + if !rest.Equal(StringCast(restExp)) { + t.Errorf("expected %s to be %s", rest, restExp) + } + } + + c, err := NewComponent("ip4", "127.0.0.1") + if err != nil { + t.Fatal(err) + } + + ci, m := SplitFirst(c) + if !ci.Equal(c) || m != nil { + t.Error("split first on component failed") + } + m, ci = SplitLast(c) + if !ci.Equal(c) || m != nil { + t.Error("split last on component failed") + } + cis := Split(c) + if len(cis) != 1 || !cis[0].Equal(c) { + t.Error("split on component failed") + } + m1, m2 := SplitFunc(c, func(c Component) bool { + return true + }) + if m1 != nil || !m2.Equal(c) { + t.Error("split func(true) on component failed") + } + m1, m2 = SplitFunc(c, func(c Component) bool { + return false + }) + if !m1.Equal(c) || m2 != nil { + t.Error("split func(false) on component failed") + } + + i := 0 + ForEach(c, func(ci Component) bool { + if i != 0 { + t.Error("expected exactly one component") + } + i++ + if !ci.Equal(c) { + t.Error("foreach on component failed") + } + return true + }) +} + +func TestSplitFunc(t *testing.T) { + ipStr := "/ip4/0.0.0.0" + tcpStr := "/tcp/123" + quicStr := "/quic" + ipfsStr := "/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7" + + for _, x := range [][]string{ + {ipStr, tcpStr, quicStr, ipfsStr}, + {ipStr, tcpStr, ipfsStr}, + {ipStr, tcpStr}, + {ipStr}, + } { + addr := StringCast(strings.Join(x, "")) + for i, cs := range x { + target := StringCast(cs) + a, b := SplitFunc(addr, func(c Component) bool { + return c.Equal(target) + }) + if i == 0 { + if a != nil { + t.Error("expected nil addr") + } + } else { + if !a.Equal(StringCast(strings.Join(x[:i], ""))) { + t.Error("split failed") + } + if !b.Equal(StringCast(strings.Join(x[i:], ""))) { + t.Error("split failed") + } + } + } + a, b := SplitFunc(addr, func(_ Component) bool { return false }) + if !a.Equal(addr) || b != nil { + t.Error("should not have split") + } + } +} diff --git a/compattest/internal/prev/varint.go b/compattest/internal/prev/varint.go new file mode 100644 index 00000000..d1ea7fc4 --- /dev/null +++ b/compattest/internal/prev/varint.go @@ -0,0 +1,27 @@ +package multiaddr + +import ( + "math" + + "github.com/multiformats/go-varint" +) + +// CodeToVarint converts an integer to a varint-encoded []byte +func CodeToVarint(num int) []byte { + if num < 0 || num > math.MaxInt32 { + panic("invalid code") + } + return varint.ToUvarint(uint64(num)) +} + +func ReadVarintCode(b []byte) (int, int, error) { + code, n, err := varint.FromUvarint(b) + if err != nil { + return 0, 0, err + } + if code > math.MaxInt32 { + // we only allow 32bit codes. + return 0, 0, varint.ErrOverflow + } + return int(code), n, err +} diff --git a/compattest/internal/prev/version.json b/compattest/internal/prev/version.json new file mode 100644 index 00000000..7dd7b578 --- /dev/null +++ b/compattest/internal/prev/version.json @@ -0,0 +1,3 @@ +{ + "version": "v0.14.0" +} diff --git a/component.go b/component.go index 39f5d5ac..6e8a640c 100644 --- a/component.go +++ b/component.go @@ -19,30 +19,32 @@ type Component struct { valueStartIdx int // Index of the first byte of the Component's value in the bytes array } -func (c Component) AsMultiaddr() Multiaddr { - if c.Empty() { +func (c *Component) Multiaddr() Multiaddr { + if c == nil { return nil } - return []Component{c} -} - -func (c Component) Encapsulate(o Multiaddr) Multiaddr { - return c.AsMultiaddr().Encapsulate(o) + return []Component{*c} } -func (c Component) Decapsulate(o Multiaddr) Multiaddr { - return c.AsMultiaddr().Decapsulate(o) +func (c *Component) Encapsulate(o Multiaddrer) Multiaddr { + return c.Multiaddr().Encapsulate(o) } -func (c Component) Empty() bool { - return len(c.bytes) == 0 +func (c *Component) Decapsulate(o Multiaddrer) Multiaddr { + return c.Multiaddr().Decapsulate(o) } -func (c Component) Bytes() []byte { +func (c *Component) Bytes() []byte { + if c == nil { + return nil + } return []byte(c.bytes) } -func (c Component) MarshalBinary() ([]byte, error) { +func (c *Component) MarshalBinary() ([]byte, error) { + if c == nil { + return nil, errNilPtr + } return c.Bytes(), nil } @@ -54,11 +56,14 @@ func (c *Component) UnmarshalBinary(data []byte) error { if err != nil { return err } - *c = comp + *c = *comp return nil } -func (c Component) MarshalText() ([]byte, error) { +func (c *Component) MarshalText() ([]byte, error) { + if c == nil { + return nil, errNilPtr + } return []byte(c.String()), nil } @@ -75,11 +80,14 @@ func (c *Component) UnmarshalText(data []byte) error { if err != nil { return err } - *c = comp + *c = *comp return nil } -func (c Component) MarshalJSON() ([]byte, error) { +func (c *Component) MarshalJSON() ([]byte, error) { + if c == nil { + return nil, errNilPtr + } txt, err := c.MarshalText() if err != nil { return nil, err @@ -101,22 +109,40 @@ func (c *Component) UnmarshalJSON(data []byte) error { return c.UnmarshalText([]byte(v)) } -func (c Component) Equal(o Component) bool { +func (c *Component) Equal(o *Component) bool { + if c == nil || o == nil { + return c == o + } return c.bytes == o.bytes } -func (c Component) Compare(o Component) int { +func (c *Component) Compare(o *Component) int { + if c == nil && o == nil { + return 0 + } + if c == nil { + return -1 + } + if o == nil { + return 1 + } return strings.Compare(c.bytes, o.bytes) } -func (c Component) Protocols() []Protocol { +func (c *Component) Protocols() []Protocol { + if c == nil { + return nil + } if c.protocol == nil { return nil } return []Protocol{*c.protocol} } -func (c Component) ValueForProtocol(code int) (string, error) { +func (c *Component) ValueForProtocol(code int) (string, error) { + if c == nil { + return "", fmt.Errorf("component is nil") + } if c.protocol == nil { return "", fmt.Errorf("component has nil protocol") } @@ -126,19 +152,25 @@ func (c Component) ValueForProtocol(code int) (string, error) { return c.Value(), nil } -func (c Component) Protocol() Protocol { +func (c *Component) Protocol() Protocol { + if c == nil { + return Protocol{} + } if c.protocol == nil { return Protocol{} } return *c.protocol } -func (c Component) RawValue() []byte { +func (c *Component) RawValue() []byte { + if c == nil { + return nil + } return []byte(c.bytes[c.valueStartIdx:]) } -func (c Component) Value() string { - if c.Empty() { +func (c *Component) Value() string { + if c == nil { return "" } // This Component MUST have been checked by validateComponent when created @@ -146,7 +178,10 @@ func (c Component) Value() string { return value } -func (c Component) valueAndErr() (string, error) { +func (c *Component) valueAndErr() (string, error) { + if c == nil { + return "", errNilPtr + } if c.protocol == nil { return "", fmt.Errorf("component has nil protocol") } @@ -160,7 +195,10 @@ func (c Component) valueAndErr() (string, error) { return value, nil } -func (c Component) String() string { +func (c *Component) String() string { + if c == nil { + return "" + } var b strings.Builder c.writeTo(&b) return b.String() @@ -168,7 +206,10 @@ func (c Component) String() string { // writeTo is an efficient, private function for string-formatting a multiaddr. // Trust me, we tend to allocate a lot when doing this. -func (c Component) writeTo(b *strings.Builder) { +func (c *Component) writeTo(b *strings.Builder) { + if c == nil { + return + } if c.protocol == nil { return } @@ -185,24 +226,24 @@ func (c Component) writeTo(b *strings.Builder) { } // NewComponent constructs a new multiaddr component -func NewComponent(protocol, value string) (Component, error) { +func NewComponent(protocol, value string) (*Component, error) { p := ProtocolWithName(protocol) if p.Code == 0 { - return Component{}, fmt.Errorf("unsupported protocol: %s", protocol) + return nil, fmt.Errorf("unsupported protocol: %s", protocol) } if p.Transcoder != nil { bts, err := p.Transcoder.StringToBytes(value) if err != nil { - return Component{}, err + return nil, err } return newComponent(p, bts) } else if value != "" { - return Component{}, fmt.Errorf("protocol %s doesn't take a value", p.Name) + return nil, fmt.Errorf("protocol %s doesn't take a value", p.Name) } return newComponent(p, nil) } -func newComponent(protocol Protocol, bvalue []byte) (Component, error) { +func newComponent(protocol Protocol, bvalue []byte) (*Component, error) { protocolPtr := protocolPtrByCode[protocol.Code] if protocolPtr == nil { protocolPtr = &protocol @@ -223,71 +264,79 @@ func newComponent(protocol Protocol, bvalue []byte) (Component, error) { // Shouldn't happen if len(maddr) != offset+len(bvalue) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(maddr), offset+len(bvalue)) + return nil, fmt.Errorf("component size mismatch: %d != %d", len(maddr), offset+len(bvalue)) } - return validateComponent( - Component{ - bytes: string(maddr), - protocol: protocolPtr, - valueStartIdx: offset, - }) + c := &Component{ + bytes: string(maddr), + protocol: protocolPtr, + valueStartIdx: offset, + } + + err := validateComponent(c) + if err != nil { + return nil, err + } + return c, nil } // validateComponent MUST be called after creating a non-zero Component. // It ensures that we will be able to call all methods on Component without // error. -func validateComponent(c Component) (Component, error) { +func validateComponent(c *Component) error { + if c == nil { + return errNilPtr + } if c.protocol == nil { - return Component{}, fmt.Errorf("component is missing its protocol") + return fmt.Errorf("component is missing its protocol") } if c.valueStartIdx > len(c.bytes) { - return Component{}, fmt.Errorf("component valueStartIdx is greater than the length of the component's bytes") + return fmt.Errorf("component valueStartIdx is greater than the length of the component's bytes") } if len(c.protocol.VCode) == 0 { - return Component{}, fmt.Errorf("Component is missing its protocol's VCode field") + return fmt.Errorf("Component is missing its protocol's VCode field") } if len(c.bytes) < len(c.protocol.VCode) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.bytes), len(c.protocol.VCode)) + return fmt.Errorf("component size mismatch: %d != %d", len(c.bytes), len(c.protocol.VCode)) } if !bytes.Equal([]byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) { - return Component{}, fmt.Errorf("component's VCode field is invalid: %v != %v", []byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) + return fmt.Errorf("component's VCode field is invalid: %v != %v", []byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) } if c.protocol.Size < 0 { size, n, err := ReadVarintCode([]byte(c.bytes[len(c.protocol.VCode):])) if err != nil { - return Component{}, err + return err } if size != len(c.bytes[c.valueStartIdx:]) { - return Component{}, fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) + return fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) } if len(c.protocol.VCode)+n+size != len(c.bytes) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+n+size, len(c.bytes)) + return fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+n+size, len(c.bytes)) } } else { // Fixed size value size := c.protocol.Size / 8 if size != len(c.bytes[c.valueStartIdx:]) { - return Component{}, fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) + return fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) } if len(c.protocol.VCode)+size != len(c.bytes) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+size, len(c.bytes)) + return fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+size, len(c.bytes)) } } _, err := c.valueAndErr() if err != nil { - return Component{}, err + return err } if c.protocol.Transcoder != nil { err = c.protocol.Transcoder.ValidateBytes([]byte(c.bytes[c.valueStartIdx:])) if err != nil { - return Component{}, err + return err } } - return c, nil + return nil } diff --git a/go.mod b/go.mod index 3a48057b..f1457a05 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/multiformats/go-multiaddr -go 1.22 +go 1.23 require ( github.com/ipfs/go-cid v0.0.7 diff --git a/multiaddr.go b/multiaddr.go index 86092ae9..2565f51b 100644 --- a/multiaddr.go +++ b/multiaddr.go @@ -16,16 +16,13 @@ var errNilPtr = errors.New("nil ptr") // Multiaddr is the data structure representing a Multiaddr type Multiaddr []Component -func (m Multiaddr) Empty() bool { - if len(m) == 0 { - return true - } - for _, c := range m { - if !c.Empty() { - return false - } +func (m Multiaddr) copy() Multiaddr { + if m == nil { + return nil } - return true + out := make(Multiaddr, len(m)) + copy(out, m) + return out } // NewMultiaddr parses and validates an input string, returning a *Multiaddr @@ -71,7 +68,7 @@ func (m Multiaddr) Equal(m2 Multiaddr) bool { return false } for i, c := range m { - if !c.Equal(m2[i]) { + if !c.Equal(&m2[i]) { return false } } @@ -80,7 +77,7 @@ func (m Multiaddr) Equal(m2 Multiaddr) bool { func (m Multiaddr) Compare(o Multiaddr) int { for i := 0; i < len(m) && i < len(o); i++ { - if cmp := m[i].Compare(o[i]); cmp != 0 { + if cmp := m[i].Compare(&o[i]); cmp != 0 { return cmp } } @@ -172,23 +169,38 @@ func (m Multiaddr) Protocols() []Protocol { return out } -// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr -func (m Multiaddr) Encapsulate(o Multiaddr) Multiaddr { - return Join(m, o) +type Multiaddrer interface { + // Multiaddr returns the Multiaddr representation + Multiaddr() Multiaddr } -func (m Multiaddr) EncapsulateC(c Component) Multiaddr { - if c.Empty() { - return m +func (m Multiaddr) Multiaddr() Multiaddr { + return m +} + +// AppendComponent is the same as using `append(m, *c)`, but with a safety check +// for a nil Component. +func (m Multiaddr) AppendComponent(cs ...*Component) Multiaddr { + for _, c := range cs { + if c == nil { + continue + } + m = append(m, *c) } - out := make([]Component, 0, len(m)+1) - out = append(out, m...) - out = append(out, c) - return out + return m +} + +// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr +func (m Multiaddr) Encapsulate(other Multiaddrer) Multiaddr { + return Join(m, other) } // Decapsulate unwraps Multiaddr up until the given Multiaddr is found. -func (m Multiaddr) Decapsulate(rightParts Multiaddr) Multiaddr { +func (m Multiaddr) Decapsulate(rightPartsAny Multiaddrer) Multiaddr { + if rightPartsAny == nil { + return m + } + rightParts := rightPartsAny.Multiaddr() leftParts := m lastIndex := -1 @@ -200,7 +212,7 @@ func (m Multiaddr) Decapsulate(rightParts Multiaddr) Multiaddr { break } - foundMatch = rightC.Equal(leftParts[i+j]) + foundMatch = rightC.Equal(&leftParts[i+j]) if !foundMatch { break } diff --git a/multiaddr_test.go b/multiaddr_test.go index 95b57f4e..54fdc4c9 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -28,6 +28,11 @@ func TestReturnsNilOnEmpty(t *testing.T) { a, _ = SplitLast(a) require.Nil(t, a) + a, c := SplitLast(nil) + require.Zero(t, len(a.Protocols())) + require.Nil(t, a) + require.Nil(t, c) + // Test that empty multiaddr from various operations returns nil a = StringCast("/ip4/1.2.3.4/tcp/1234") _, a = SplitFirst(a) @@ -36,6 +41,10 @@ func TestReturnsNilOnEmpty(t *testing.T) { _, a = SplitFirst(a) require.Nil(t, a) + c, a = SplitFirst(nil) + require.Nil(t, a) + require.Nil(t, c) + a = StringCast("/ip4/1.2.3.4/tcp/1234") a = a.Decapsulate(a) require.Nil(t, a) @@ -61,13 +70,24 @@ func TestReturnsNilOnEmpty(t *testing.T) { _, err := NewMultiaddr("") require.Error(t, err) - a = JoinComponents() + var nilMultiaddr Multiaddr + a = nilMultiaddr.AppendComponent() require.Nil(t, a) a = Join() require.Nil(t, a) } +func TestJoinWithComponents(t *testing.T) { + var m Multiaddr + c, err := NewComponent("ip4", "127.0.0.1") + require.NoError(t, err) + + expected := "/ip4/127.0.0.1" + require.Equal(t, expected, Join(m, c).String()) + +} + func TestConstructFails(t *testing.T) { cases := []string{ "/ip4", @@ -302,7 +322,7 @@ func TestNilInterface(t *testing.T) { // Test components c, _ := SplitFirst(m1) - c.AsMultiaddr().Equal(m2) + c.Multiaddr().Equal(m2) c.Encapsulate(m2) c.Decapsulate(m2) @@ -400,18 +420,18 @@ func TestBytesSplitAndJoin(t *testing.T) { for i, a := range split { if a.String() != res[i] { - t.Errorf("split component failed: %s != %s", a, res[i]) + t.Errorf("split component failed: %s != %s", &a, res[i]) } } - joined := JoinComponents(split...) + joined := append(Multiaddr{}, split...) if !m.Equal(joined) { t.Errorf("joined components failed: %s != %s", m, joined) } for i, a := range split { if a.String() != res[i] { - t.Errorf("split component failed: %s != %s", a, res[i]) + t.Errorf("split component failed: %s != %s", &a, res[i]) } } } @@ -509,6 +529,19 @@ func TestEncapsulate(t *testing.T) { if d != nil { t.Error("decapsulate /ip4 failed: ", d) } + + t.Run("Encapsulating with components", func(t *testing.T) { + left, last := SplitLast(m) + joined := left.Encapsulate(last) + require.True(t, joined.Equal(m)) + + first, rest := SplitFirst(m) + joined = first.Encapsulate(rest) + require.True(t, joined.Equal(m)) + // Component type + joined = (*first).Encapsulate(rest) + require.True(t, joined.Equal(m)) + }) } func TestDecapsulateComment(t *testing.T) { @@ -569,6 +602,16 @@ func TestDecapsulate(t *testing.T) { require.Equal(t, expected, actual) }) } + + for _, tc := range testcases { + t.Run("Decapsulating with components"+tc.left, func(t *testing.T) { + left, last := SplitLast(StringCast(tc.left)) + butLast := left.Decapsulate(last) + require.Equal(t, butLast.String(), left.String()) + // Round trip + require.Equal(t, tc.left, butLast.Encapsulate(last).String()) + }) + } } func assertValueForProto(t *testing.T, a Multiaddr, p int, exp string) { @@ -583,6 +626,17 @@ func assertValueForProto(t *testing.T, a Multiaddr, p int, exp string) { } } +func TestAppendComponent(t *testing.T) { + var m Multiaddr + res := m.AppendComponent(nil) + require.Equal(t, m, res) + + c, err := NewComponent("ip4", "127.0.0.1") + require.NoError(t, err) + res = m.AppendComponent(c) + require.Equal(t, "/ip4/127.0.0.1", res.String()) +} + func TestGetValue(t *testing.T) { a := newMultiaddr(t, "/ip4/127.0.0.1/utp/tcp/5555/udp/1234/tls/utp/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") assertValueForProto(t, a, P_IP4, "127.0.0.1") @@ -859,11 +913,11 @@ func TestComponentBinaryMarshaler(t *testing.T) { t.Fatal(err) } - comp2 := Component{} + var comp2 Component if err = comp2.UnmarshalBinary(b); err != nil { t.Fatal(err) } - if !comp.Equal(comp2) { + if !comp.Equal(&comp2) { t.Error("expected equal components in circular marshaling test") } } @@ -878,11 +932,11 @@ func TestComponentTextMarshaler(t *testing.T) { t.Fatal(err) } - comp2 := Component{} + var comp2 Component if err = comp2.UnmarshalText(b); err != nil { t.Fatal(err) } - if !comp.Equal(comp2) { + if !comp.Equal(&comp2) { t.Error("expected equal components in circular marshaling test") } } @@ -897,11 +951,11 @@ func TestComponentJSONMarshaler(t *testing.T) { t.Fatal(err) } - comp2 := Component{} + var comp2 Component if err = comp2.UnmarshalJSON(b); err != nil { t.Fatal(err) } - if !comp.Equal(comp2) { + if !comp.Equal(&comp2) { t.Error("expected equal components in circular marshaling test") } } @@ -914,6 +968,9 @@ func TestUseNil(t *testing.T) { _ = f() var foo Multiaddr = nil + _, right := SplitFirst(foo) + right.Protocols() + foo.Protocols() foo.Bytes() foo.Compare(nil) foo.Decapsulate(nil) @@ -930,6 +987,32 @@ func TestUseNil(t *testing.T) { _, _ = foo.ValueForProtocol(0) } +func TestUseNilComponent(t *testing.T) { + var foo *Component + foo.Multiaddr() + foo.Encapsulate(nil) + foo.Decapsulate(nil) + require.True(t, foo == nil) + foo.Bytes() + foo.MarshalBinary() + foo.MarshalJSON() + foo.MarshalText() + foo.UnmarshalBinary(nil) + foo.UnmarshalJSON(nil) + foo.UnmarshalText(nil) + foo.Equal(nil) + foo.Compare(nil) + foo.Protocols() + foo.ValueForProtocol(0) + foo.Protocol() + foo.RawValue() + foo.Value() + _ = foo.String() + + var m Multiaddr = nil + m.Encapsulate(foo) +} + func TestFilterAddrs(t *testing.T) { bad := []Multiaddr{ newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), @@ -1084,12 +1167,12 @@ func FuzzSplitRoundtrip(f *testing.F) { // Test SplitFirst first, rest := SplitFirst(addr) - joined := Join(first.AsMultiaddr(), rest) + joined := Join(first, rest) require.True(t, addr.Equal(joined), "SplitFirst and Join should round-trip") // Test SplitLast rest, last := SplitLast(addr) - joined = Join(rest, last.AsMultiaddr()) + joined = Join(rest, last) require.True(t, addr.Equal(joined), "SplitLast and Join should round-trip") p := addr.Protocols() @@ -1115,12 +1198,12 @@ func FuzzSplitRoundtrip(f *testing.F) { return c.Protocol().Code == proto.Code } beforeC, after := SplitFirst(addr) - joined = Join(beforeC.AsMultiaddr(), after) + joined = Join(beforeC, after) require.True(t, addr.Equal(joined)) tryPubMethods(after) before, afterC := SplitLast(addr) - joined = Join(before, afterC.AsMultiaddr()) + joined = Join(before, afterC) require.True(t, addr.Equal(joined)) tryPubMethods(before) @@ -1140,7 +1223,7 @@ func BenchmarkComponentValidation(b *testing.B) { } b.ReportAllocs() for i := 0; i < b.N; i++ { - _, err := validateComponent(comp) + err := validateComponent(comp) if err != nil { b.Fatal(err) } diff --git a/net/convert.go b/net/convert.go index be320d3e..80564517 100644 --- a/net/convert.go +++ b/net/convert.go @@ -108,13 +108,13 @@ func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) { if err != nil { return nil, err } - return c.AsMultiaddr(), nil + return c.Multiaddr(), nil case ip.To16() != nil: ip6C, err := ma.NewComponent("ip6", ip.String()) if err != nil { return nil, err } - ip6 := ip6C.AsMultiaddr() + ip6 := ip6C.Multiaddr() if zone == "" { return ip6, nil } else { @@ -362,5 +362,5 @@ func parseUnixNetAddr(a net.Addr) (ma.Multiaddr, error) { if err != nil { return nil, err } - return c.AsMultiaddr(), nil + return c.Multiaddr(), nil } diff --git a/net/ip.go b/net/ip.go index e8acecba..def9321d 100644 --- a/net/ip.go +++ b/net/ip.go @@ -64,7 +64,7 @@ func IsIPLoopback(m ma.Multiaddr) bool { return false } c, _ := ma.SplitFirst(m) - if c.Empty() { + if c == nil { return false } switch c.Protocol().Code { @@ -83,7 +83,7 @@ func IsIP6LinkLocal(m ma.Multiaddr) bool { return false } c, _ := ma.SplitFirst(m) - if c.Empty() || c.Protocol().Code != ma.P_IP6 { + if c == nil || c.Protocol().Code != ma.P_IP6 { return false } ip := net.IP(c.RawValue()) @@ -106,11 +106,11 @@ func IsIPUnspecified(m ma.Multiaddr) bool { // else return m func zoneless(m ma.Multiaddr) ma.Multiaddr { head, tail := ma.SplitFirst(m) - if head.Empty() { + if head == nil { return nil } if head.Protocol().Code == ma.P_IP6ZONE { - if tail.Empty() { + if tail == nil { return nil } tailhead, _ := ma.SplitFirst(tail) @@ -127,6 +127,6 @@ func zoneless(m ma.Multiaddr) ma.Multiaddr { // used for NAT64 Translation. See RFC 6052 func IsNAT64IPv4ConvertedIPv6Addr(addr ma.Multiaddr) bool { c, _ := ma.SplitFirst(addr) - return !c.Empty() && c.Protocol().Code == ma.P_IP6 && + return c != nil && c.Protocol().Code == ma.P_IP6 && inAddrRange(c.RawValue(), nat64) } diff --git a/net/resolve.go b/net/resolve.go index 0b43d081..cd98fd88 100644 --- a/net/resolve.go +++ b/net/resolve.go @@ -14,7 +14,7 @@ func ResolveUnspecifiedAddress(resolve ma.Multiaddr, ifaceAddrs []ma.Multiaddr) first, rest := ma.SplitFirst(resolve) // if first component (ip) is not unspecified, use it as is. - if !IsIPUnspecified(first.AsMultiaddr()) { + if !IsIPUnspecified(first.Multiaddr()) { return []ma.Multiaddr{resolve}, nil } diff --git a/util.go b/util.go index 71fb9bf6..d063e399 100644 --- a/util.go +++ b/util.go @@ -9,23 +9,17 @@ func Split(m Multiaddr) []Component { return m } -func JoinComponents(cs ...Component) Multiaddr { - if len(cs) == 0 { - return nil - } - out := make([]Component, 0, len(cs)) - for _, c := range cs { - if !c.Empty() { - out = append(out, c) - } - } - return out -} - // Join returns a combination of addresses. // Note: This copies all the components from the input Multiaddrs. Depending on // your use case, you may prefer to use `append(leftMA, rightMA...)` instead. -func Join(ms ...Multiaddr) Multiaddr { +func Join(msInterfaces ...Multiaddrer) Multiaddr { + ms := make([]Multiaddr, len(msInterfaces)) + for i, m := range msInterfaces { + if m == nil { + continue + } + ms[i] = m.Multiaddr() + } size := 0 for _, m := range ms { size += len(m) @@ -36,11 +30,7 @@ func Join(ms ...Multiaddr) Multiaddr { out := make([]Component, 0, size) for _, m := range ms { - for _, c := range m { - if !c.Empty() { - out = append(out, c) - } - } + out = append(out, m...) } return out } @@ -64,33 +54,37 @@ func StringCast(s string) Multiaddr { } // SplitFirst returns the first component and the rest of the multiaddr. -func SplitFirst(m Multiaddr) (Component, Multiaddr) { - if m.Empty() { - return Component{}, nil +func SplitFirst(m Multiaddr) (*Component, Multiaddr) { + if len(m) == 0 { + return nil, nil } if len(m) == 1 { - return m[0], nil + return &m[0], nil } - return m[0], m[1:] + // defensive copy. Users can avoid by doing the split themselves. + copyC := m[0] + return ©C, m[1:].copy() } // SplitLast returns the rest of the multiaddr and the last component. -func SplitLast(m Multiaddr) (Multiaddr, Component) { - if m.Empty() { - return nil, Component{} +func SplitLast(m Multiaddr) (Multiaddr, *Component) { + if len(m) == 0 { + return nil, nil } if len(m) == 1 { // We want to explicitly return a nil slice if the prefix is now empty. - return nil, m[0] + return nil, &m[0] } - return m[:len(m)-1], m[len(m)-1] + // defensive copy. Users can avoid by doing the split themselves. + copyC := m[len(m)-1] + return m[:len(m)-1].copy(), ©C } // SplitFunc splits the multiaddr when the callback first returns true. The // component on which the callback first returns will be included in the // *second* multiaddr. func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { - if m.Empty() { + if len(m) == 0 { return nil, nil } @@ -102,13 +96,14 @@ func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { } } pre, post := m[:idx], m[idx:] - if pre.Empty() { + if len(pre) == 0 { pre = nil } - if post.Empty() { + if len(post) == 0 { post = nil } - return pre, post + // defensive copy. Users can avoid by doing the split themselves. + return pre.copy(), post.copy() } // ForEach walks over the multiaddr, component by component. @@ -116,7 +111,7 @@ func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { // This function iterates over components. // Return true to continue iteration, false to stop. // -// Prefer to use a standard for range loop instead +// Prefer a standard for range loop instead // e.g. `for _, c := range m { ... }` func ForEach(m Multiaddr, cb func(c Component) bool) { for _, c := range m { diff --git a/util_test.go b/util_test.go index 3494486f..365ba414 100644 --- a/util_test.go +++ b/util_test.go @@ -21,8 +21,8 @@ func TestSplitFirstLast(t *testing.T) { head, tail := SplitFirst(addr) rest, last := SplitLast(addr) if len(x) == 0 { - if !head.Empty() { - t.Error("expected head to be empty") + if head != nil { + t.Error("expected head to be nil") } if tail != nil { t.Error("expected tail to be nil") @@ -30,15 +30,15 @@ func TestSplitFirstLast(t *testing.T) { if rest != nil { t.Error("expected rest to be nil") } - if !last.Empty() { - t.Error("expected last to be empty") + if last != nil { + t.Error("expected last to be nil") } continue } - if !head.AsMultiaddr().Equal(StringCast(x[0])) { + if !head.Multiaddr().Equal(StringCast(x[0])) { t.Errorf("expected %s to be %s", head, x[0]) } - if !last.AsMultiaddr().Equal(StringCast(x[len(x)-1])) { + if !last.Multiaddr().Equal(StringCast(x[len(x)-1])) { t.Errorf("expected %s to be %s", head, x[len(x)-1]) } if len(x) == 1 { @@ -65,33 +65,33 @@ func TestSplitFirstLast(t *testing.T) { t.Fatal(err) } - ci, m := SplitFirst(c.AsMultiaddr()) + ci, m := SplitFirst(c.Multiaddr()) if !ci.Equal(c) || m != nil { t.Error("split first on component failed") } - m, ci = SplitLast(c.AsMultiaddr()) + m, ci = SplitLast(c.Multiaddr()) if !ci.Equal(c) || m != nil { t.Error("split last on component failed") } - cis := Split(c.AsMultiaddr()) + cis := Split(c.Multiaddr()) if len(cis) != 1 || !cis[0].Equal(c) { t.Error("split on component failed") } - m1, m2 := SplitFunc(c.AsMultiaddr(), func(c Component) bool { + m1, m2 := SplitFunc(c.Multiaddr(), func(c Component) bool { return true }) - if m1 != nil || !m2.Equal(c.AsMultiaddr()) { + if m1 != nil || !m2.Equal(c.Multiaddr()) { t.Error("split func(true) on component failed") } - m1, m2 = SplitFunc(c.AsMultiaddr(), func(c Component) bool { + m1, m2 = SplitFunc(c.Multiaddr(), func(c Component) bool { return false }) - if !m1.Equal(c.AsMultiaddr()) || m2 != nil { + if !m1.Equal(c.Multiaddr()) || m2 != nil { t.Error("split func(false) on component failed") } i := 0 - ForEach(c.AsMultiaddr(), func(ci Component) bool { + ForEach(c.Multiaddr(), func(ci Component) bool { if i != 0 { t.Error("expected exactly one component") } @@ -119,10 +119,10 @@ func TestSplitFunc(t *testing.T) { for i, cs := range x { target := StringCast(cs) a, b := SplitFunc(addr, func(c Component) bool { - return c.AsMultiaddr().Equal(target) + return c.Multiaddr().Equal(target) }) if i == 0 { - if !a.Empty() { + if a != nil { t.Error("expected nil addr") } } else { @@ -135,7 +135,7 @@ func TestSplitFunc(t *testing.T) { } } a, b := SplitFunc(addr, func(_ Component) bool { return false }) - if !a.Equal(addr) || !b.Empty() { + if !a.Equal(addr) || b != nil { t.Error("should not have split") } } diff --git a/v015-MIGRATION.md b/v015-MIGRATION.md index ab75a395..3af7180a 100644 --- a/v015-MIGRATION.md +++ b/v015-MIGRATION.md @@ -2,7 +2,7 @@ - There is no `Multiaddr` interface type. - Multiaddr is now a concrete type. Not an interface. -- Empty Multiaddrs/ should be checked with `.Empty()`, not `== nil`. This is similar to how slices should be checked with `len(s) == 0` rather than `s == nil`. +- Empty Multiaddrs/Component should generally be checked with `.Empty()`, not `== nil`. This is similar to how slices should be checked with `len(s) == 0` rather than `s == nil`. - Components do not implement `Multiaddr` as there is no `Multiaddr` to implement. - `Multiaddr` can no longer be a key in a Map. If you want unique Multiaddrs, use `Multiaddr.String()` as the key, otherwise you can use the pointer value `*Multiaddr`. @@ -12,4 +12,6 @@ ## Migration tips for v0.15 -- If trying to encapsulate a Component to a Multiaddr, use `m.encapsulateC(c)`, instead of the old form of `m.Encapsulate(c)`. `Encapsulate` now only accepts a `Multiaddr`. `EncapsulateC` accepts a `Component`. +- If your use case supports it, prefer `append` to append a Component to a + Multiaddr rather than using `Encapsulate`. It's much faster as it does not do + a defensive copy.