diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 9c258cbaeaaa..c6448c22f75b 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -22,16 +22,26 @@ import ( type mockNamesys map[string]path.Path -func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, err error) { - return m.ResolveN(ctx, name, namesys.DefaultDepthLimit) +func (m mockNamesys) Resolve(ctx context.Context, name string) (path.Path, error) { + res, err := m.ResolveFull(ctx, name) + return res.Path, err } -func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) { +func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + res, err := m.ResolveNFull(ctx, name, depth) + return res.Path, err +} + +func (m mockNamesys) ResolveFull(ctx context.Context, name string) (namesys.ResolveResult, error) { + return m.ResolveNFull(ctx, name, namesys.DefaultDepthLimit) +} + +func (m mockNamesys) ResolveNFull(ctx context.Context, name string, depth int) (namesys.ResolveResult, error) { p, ok := m[name] if !ok { - return "", namesys.ErrResolveFailed + return namesys.ResolveResult{}, namesys.ErrResolveFailed } - return p, nil + return namesys.ResolveResult{p, 0}, nil } func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { diff --git a/core/pathresolver.go b/core/pathresolver.go index 153f560bc6e5..184512ddaa12 100644 --- a/core/pathresolver.go +++ b/core/pathresolver.go @@ -3,10 +3,12 @@ package core import ( "errors" "strings" + "time" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" merkledag "github.com/ipfs/go-ipfs/merkledag" + namesys "github.com/ipfs/go-ipfs/namesys" path "github.com/ipfs/go-ipfs/path" ) @@ -20,38 +22,56 @@ var ErrNoNamesys = errors.New( // entries and returning the final merkledag node. Effectively // enables /ipns/, /dns/, etc. in commands. func Resolve(ctx context.Context, n *IpfsNode, p path.Path) (*merkledag.Node, error) { + res, err := ResolveFull(ctx, n, p) + return res.Node, err +} + +// ResolveFull is like Resolve but also returns a time-to-live value which +// indicates the maximum amount of time the result may be cached. +func ResolveFull(ctx context.Context, n *IpfsNode, p path.Path) (ResolveResult, error) { + ttl := namesys.ImmutableTTL + if strings.HasPrefix(p.String(), "/ipns/") { // resolve ipns paths // TODO(cryptix): we sould be able to query the local cache for the path if n.Namesys == nil { - return nil, ErrNoNamesys + return ResolveResult{TTL: 0}, ErrNoNamesys } seg := p.Segments() if len(seg) < 2 || seg[1] == "" { // just "/" without further segments - return nil, path.ErrNoComponents + return ResolveResult{TTL: namesys.ImmutableTTL}, path.ErrNoComponents } extensions := seg[2:] resolvable, err := path.FromSegments("/", seg[0], seg[1]) if err != nil { - return nil, err + return ResolveResult{TTL: namesys.ImmutableTTL}, err } - respath, err := n.Namesys.Resolve(ctx, resolvable.String()) + res, err := n.Namesys.ResolveFull(ctx, resolvable.String()) + ttl = res.TTL if err != nil { - return nil, err + return ResolveResult{TTL: ttl}, err } - segments := append(respath.Segments(), extensions...) + segments := append(res.Path.Segments(), extensions...) p, err = path.FromSegments("/", segments...) if err != nil { - return nil, err + return ResolveResult{TTL: ttl}, err } } // ok, we have an ipfs path now (or what we'll treat as one) - return n.Resolver.ResolvePath(ctx, p) + node, err := n.Resolver.ResolvePath(ctx, p) + return ResolveResult{node, ttl}, err +} + +// ResolveResult is returned by ResolveFull and holds a time-to-live value in +// addition to the merkledag node. +type ResolveResult struct { + Node *merkledag.Node + TTL time.Duration } diff --git a/namesys/base.go b/namesys/base.go index e552fce464a1..223a70c53ed3 100644 --- a/namesys/base.go +++ b/namesys/base.go @@ -4,47 +4,54 @@ import ( "strings" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" - - path "github.com/ipfs/go-ipfs/path" ) type resolver interface { - // resolveOnce looks up a name once (without recursion). - resolveOnce(ctx context.Context, name string) (value path.Path, err error) + // resolveOnce looks up a name once (without recursion). It also + // returns a time-to-live value which indicates the maximum amount of + // time the result may be cached. + resolveOnce(ctx context.Context, name string) (res ResolveResult, err error) } -// resolve is a helper for implementing Resolver.ResolveN using resolveOnce. -func resolve(ctx context.Context, r resolver, name string, depth int, prefixes ...string) (path.Path, error) { +// resolve is a helper for implementing Resolver.ResolveNFull using resolveOnce. +func resolve(ctx context.Context, r resolver, name string, depth int, prefixes ...string) (ResolveResult, error) { + // Start with a long TTL. + ttl := ImmutableTTL + for { - p, err := r.resolveOnce(ctx, name) + res, err := r.resolveOnce(ctx, name) + // Use the lowest TTL reported by the resolveOnce invocations. + if res.TTL < ttl { + ttl = res.TTL + } if err != nil { log.Warningf("Could not resolve %s", name) - return "", err + return ResolveResult{TTL: ttl}, err } - log.Debugf("Resolved %s to %s", name, p.String()) + log.Debugf("Resolved %s to %s (TTL %v -> %v)", name, res.Path.String(), res.TTL, ttl) - if strings.HasPrefix(p.String(), "/ipfs/") { + if strings.HasPrefix(res.Path.String(), "/ipfs/") { // we've bottomed out with an IPFS path - return p, nil + return ResolveResult{res.Path, ttl}, nil } if depth == 1 { - return p, ErrResolveRecursion + return ResolveResult{res.Path, ttl}, ErrResolveRecursion } matched := false for _, prefix := range prefixes { - if strings.HasPrefix(p.String(), prefix) { + if strings.HasPrefix(res.Path.String(), prefix) { matched = true if len(prefixes) == 1 { - name = strings.TrimPrefix(p.String(), prefix) + name = strings.TrimPrefix(res.Path.String(), prefix) } break } } if !matched { - return p, nil + return ResolveResult{res.Path, ttl}, nil } if depth > 1 { diff --git a/namesys/dns.go b/namesys/dns.go index 3703bd8d01de..131665ba869b 100644 --- a/namesys/dns.go +++ b/namesys/dns.go @@ -33,36 +33,53 @@ func newDNSResolver() resolver { // Resolve implements Resolver. func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - return r.ResolveN(ctx, name, DefaultDepthLimit) + res, err := r.ResolveFull(ctx, name) + return res.Path, err } // ResolveN implements Resolver. func (r *DNSResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + res, err := r.ResolveNFull(ctx, name, depth) + return res.Path, err +} + +// ResolveFull implements Resolver. +func (r *DNSResolver) ResolveFull(ctx context.Context, name string) (ResolveResult, error) { + return r.ResolveNFull(ctx, name, DefaultDepthLimit) +} + +// ResolveNFull implements Resolver. +func (r *DNSResolver) ResolveNFull(ctx context.Context, name string, depth int) (ResolveResult, error) { return resolve(ctx, r, name, depth, "/ipns/") } // resolveOnce implements resolver. // TXT records for a given domain name should contain a b58 // encoded multihash. -func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { +func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (ResolveResult, error) { if !isd.IsDomain(name) { - return "", errors.New("not a valid domain name") + return ResolveResult{TTL: UnknownTTL}, errors.New("not a valid domain name") } log.Infof("DNSResolver resolving %s", name) + + // XXX: net.LookupTXT does not provide TTL information, use UnknownTTL + // for now. + ttl := UnknownTTL + txt, err := r.lookupTXT(name) if err != nil { - return "", err + return ResolveResult{TTL: ttl}, err } for _, t := range txt { p, err := parseEntry(t) if err == nil { - return p, nil + return ResolveResult{p, ttl}, nil } } - return "", ErrResolveFailed + return ResolveResult{TTL: ttl}, ErrResolveFailed } func parseEntry(txt string) (path.Path, error) { diff --git a/namesys/dns_test.go b/namesys/dns_test.go index 40bf702c35c2..59e9dee7ca13 100644 --- a/namesys/dns_test.go +++ b/namesys/dns_test.go @@ -93,20 +93,20 @@ func newMockDNS() *mockDNS { func TestDNSResolution(t *testing.T) { mock := newMockDNS() r := &DNSResolver{lookupTXT: mock.lookupTXT} - testResolution(t, r, "multihash.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) - testResolution(t, r, "ipfs.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) - testResolution(t, r, "dns1.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) - testResolution(t, r, "dns1.example.com", 1, "/ipns/ipfs.example.com", ErrResolveRecursion) - testResolution(t, r, "dns2.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) - testResolution(t, r, "dns2.example.com", 1, "/ipns/dns1.example.com", ErrResolveRecursion) - testResolution(t, r, "dns2.example.com", 2, "/ipns/ipfs.example.com", ErrResolveRecursion) - testResolution(t, r, "multi.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) - testResolution(t, r, "multi.example.com", 1, "/ipns/dns1.example.com", ErrResolveRecursion) - testResolution(t, r, "multi.example.com", 2, "/ipns/ipfs.example.com", ErrResolveRecursion) - testResolution(t, r, "equals.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", nil) - testResolution(t, r, "loop1.example.com", 1, "/ipns/loop2.example.com", ErrResolveRecursion) - testResolution(t, r, "loop1.example.com", 2, "/ipns/loop1.example.com", ErrResolveRecursion) - testResolution(t, r, "loop1.example.com", 3, "/ipns/loop2.example.com", ErrResolveRecursion) - testResolution(t, r, "loop1.example.com", DefaultDepthLimit, "/ipns/loop1.example.com", ErrResolveRecursion) - testResolution(t, r, "bad.example.com", DefaultDepthLimit, "", ErrResolveFailed) + testResolution(t, r, "multihash.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil) + testResolution(t, r, "ipfs.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil) + testResolution(t, r, "dns1.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil) + testResolution(t, r, "dns1.example.com", 1, "/ipns/ipfs.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "dns2.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil) + testResolution(t, r, "dns2.example.com", 1, "/ipns/dns1.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "dns2.example.com", 2, "/ipns/ipfs.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "multi.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil) + testResolution(t, r, "multi.example.com", 1, "/ipns/dns1.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "multi.example.com", 2, "/ipns/ipfs.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "equals.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", UnknownTTL, nil) + testResolution(t, r, "loop1.example.com", 1, "/ipns/loop2.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "loop1.example.com", 2, "/ipns/loop1.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "loop1.example.com", 3, "/ipns/loop2.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "loop1.example.com", DefaultDepthLimit, "/ipns/loop1.example.com", UnknownTTL, ErrResolveRecursion) + testResolution(t, r, "bad.example.com", DefaultDepthLimit, "", UnknownTTL, ErrResolveFailed) } diff --git a/namesys/interface.go b/namesys/interface.go index 09c296c23af5..524017db77b7 100644 --- a/namesys/interface.go +++ b/namesys/interface.go @@ -47,6 +47,14 @@ const ( // trust resolution to eventually complete and can't put an upper // limit on how many steps it will take. UnlimitedDepth = 0 + + // ImmutableTTL is the time-to-live value to be reported for immutable + // paths. + ImmutableTTL = 10 * 365 * 24 * time.Hour + + // UnknownTTL is the time-to-live value to be reported for mutable + // paths when accurate information is not available. + UnknownTTL = time.Minute ) // ErrResolveFailed signals an error when attempting to resolve. @@ -98,6 +106,21 @@ type Resolver interface { // Most users should use Resolve, since the default limit works well // in most real-world situations. ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) + + // ResolveFull is like Resolve but also returns a time-to-live value + // which indicates the maximum amount of time the result may be cached. + ResolveFull(ctx context.Context, name string) (value ResolveResult, err error) + + // ResolveNFull is like ResolveN but also returns a time-to-live value + // which indicates the maximum amount of time the result may be cached. + ResolveNFull(ctx context.Context, name string, depth int) (value ResolveResult, err error) +} + +// ResolveResult is returned by ResolveFull and ResolveNFull and holds a +// time-to-live value in addition to the dereferenced path. +type ResolveResult struct { + Path path.Path + TTL time.Duration } // Publisher is an object capable of publishing particular names. diff --git a/namesys/namesys.go b/namesys/namesys.go index c61d3496bde1..e5dd477d4a9f 100644 --- a/namesys/namesys.go +++ b/namesys/namesys.go @@ -39,46 +39,54 @@ func NewNameSystem(r routing.IpfsRouting, ds ds.Datastore, cachesize int) NameSy } } -const DefaultResolverCacheTTL = time.Minute - // Resolve implements Resolver. func (ns *mpns) Resolve(ctx context.Context, name string) (path.Path, error) { - return ns.ResolveN(ctx, name, DefaultDepthLimit) + res, err := ns.ResolveFull(ctx, name) + return res.Path, err } // ResolveN implements Resolver. func (ns *mpns) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { - if strings.HasPrefix(name, "/ipfs/") { - return path.ParsePath(name) - } + res, err := ns.ResolveNFull(ctx, name, depth) + return res.Path, err +} + +// ResolveFull implements Resolver. +func (ns *mpns) ResolveFull(ctx context.Context, name string) (ResolveResult, error) { + return ns.ResolveNFull(ctx, name, DefaultDepthLimit) +} - if !strings.HasPrefix(name, "/") { - return path.ParsePath("/ipfs/" + name) +// ResolveNFull implements Resolver. +func (ns *mpns) ResolveNFull(ctx context.Context, name string, depth int) (ResolveResult, error) { + if strings.HasPrefix(name, "/ipfs/") || !strings.HasPrefix(name, "/") { + // ParsePath also handles paths without a / prefix. + path, err := path.ParsePath(name) + return ResolveResult{path, ImmutableTTL}, err } return resolve(ctx, ns, name, depth, "/ipns/") } // resolveOnce implements resolver. -func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) { +func (ns *mpns) resolveOnce(ctx context.Context, name string) (ResolveResult, error) { if !strings.HasPrefix(name, "/ipns/") { name = "/ipns/" + name } segments := strings.SplitN(name, "/", 3) if len(segments) < 3 || segments[0] != "" { log.Warningf("Invalid name syntax for %s", name) - return "", ErrResolveFailed + return ResolveResult{TTL: ImmutableTTL}, ErrResolveFailed } for protocol, resolver := range ns.resolvers { log.Debugf("Attempting to resolve %s with %s", name, protocol) - p, err := resolver.resolveOnce(ctx, segments[2]) + res, err := resolver.resolveOnce(ctx, segments[2]) if err == nil { - return p, err + return res, err } } log.Warningf("No resolver found for %s", name) - return "", ErrResolveFailed + return ResolveResult{TTL: UnknownTTL}, ErrResolveFailed } // Publish implements Publisher diff --git a/namesys/namesys_test.go b/namesys/namesys_test.go index 256228c3e655..945cbaaec18c 100644 --- a/namesys/namesys_test.go +++ b/namesys/namesys_test.go @@ -3,6 +3,7 @@ package namesys import ( "fmt" "testing" + "time" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" @@ -10,41 +11,53 @@ import ( ) type mockResolver struct { - entries map[string]string + entries map[string]mockEntry } -func testResolution(t *testing.T, resolver Resolver, name string, depth int, expected string, expError error) { - p, err := resolver.ResolveN(context.Background(), name, depth) +type mockEntry struct { + path string + ttl time.Duration +} + +func testResolution(t *testing.T, resolver Resolver, name string, depth int, expected string, expTtl time.Duration, expError error) { + res, err := resolver.ResolveNFull(context.Background(), name, depth) if err != expError { t.Fatal(fmt.Errorf( "Expected %s with a depth of %d to have a '%s' error, but got '%s'", name, depth, expError, err)) } - if p.String() != expected { + if res.Path.String() != expected { t.Fatal(fmt.Errorf( "%s with depth %d resolved to %s != %s", - name, depth, p.String(), expected)) + name, depth, res.Path.String(), expected)) + } + if res.TTL != expTtl { + t.Fatal(fmt.Errorf( + "%s with depth %s had TTL %v != %v", + name, depth, res.TTL, expTtl)) } } -func (r *mockResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { - return path.ParsePath(r.entries[name]) +func (r *mockResolver) resolveOnce(ctx context.Context, name string) (ResolveResult, error) { + entry := r.entries[name] + path, err := path.ParsePath(entry.path) + return ResolveResult{path, entry.ttl}, err } func mockResolverOne() *mockResolver { return &mockResolver{ - entries: map[string]string{ - "QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy": "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", - "QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n": "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", - "QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD": "/ipns/ipfs.io", + entries: map[string]mockEntry{ + "QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy": mockEntry{"/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", ImmutableTTL}, + "QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n": mockEntry{"/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", 10 * time.Minute}, + "QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD": mockEntry{"/ipns/ipfs.io", 5 * time.Minute}, }, } } func mockResolverTwo() *mockResolver { return &mockResolver{ - entries: map[string]string{ - "ipfs.io": "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", + entries: map[string]mockEntry{ + "ipfs.io": mockEntry{"/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1 * time.Minute}, }, } } @@ -57,15 +70,15 @@ func TestNamesysResolution(t *testing.T) { }, } - testResolution(t, r, "Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) - testResolution(t, r, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) - testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) - testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) - testResolution(t, r, "/ipns/ipfs.io", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) - testResolution(t, r, "/ipns/ipfs.io", 1, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", ErrResolveRecursion) - testResolution(t, r, "/ipns/ipfs.io", 2, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) - testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) - testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 1, "/ipns/ipfs.io", ErrResolveRecursion) - testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 2, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", ErrResolveRecursion) - testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 3, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) + testResolution(t, r, "Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", ImmutableTTL, nil) + testResolution(t, r, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", ImmutableTTL, nil) + testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", 10*time.Minute, nil) + testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", 10*time.Minute, ErrResolveRecursion) + testResolution(t, r, "/ipns/ipfs.io", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", 1*time.Minute, nil) + testResolution(t, r, "/ipns/ipfs.io", 1, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1*time.Minute, ErrResolveRecursion) + testResolution(t, r, "/ipns/ipfs.io", 2, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", 1*time.Minute, ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", 1*time.Minute, nil) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 1, "/ipns/ipfs.io", 5*time.Minute, ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 2, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1*time.Minute, ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 3, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", 1*time.Minute, ErrResolveRecursion) } diff --git a/namesys/proquint.go b/namesys/proquint.go index 2ad3275a4ad3..b4fcb3bef292 100644 --- a/namesys/proquint.go +++ b/namesys/proquint.go @@ -12,19 +12,31 @@ type ProquintResolver struct{} // Resolve implements Resolver. func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - return r.ResolveN(ctx, name, DefaultDepthLimit) + res, err := r.ResolveFull(ctx, name) + return res.Path, err } // ResolveN implements Resolver. func (r *ProquintResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + res, err := r.ResolveNFull(ctx, name, depth) + return res.Path, err +} + +// ResolveFull implements Resolver. +func (r *ProquintResolver) ResolveFull(ctx context.Context, name string) (ResolveResult, error) { + return r.ResolveNFull(ctx, name, DefaultDepthLimit) +} + +// ResolveNFull implements Resolver. +func (r *ProquintResolver) ResolveNFull(ctx context.Context, name string, depth int) (ResolveResult, error) { return resolve(ctx, r, name, depth, "/ipns/") } // resolveOnce implements resolver. Decodes the proquint string. -func (r *ProquintResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { +func (r *ProquintResolver) resolveOnce(ctx context.Context, name string) (ResolveResult, error) { ok, err := proquint.IsProquint(name) if err != nil || !ok { - return "", errors.New("not a valid proquint string") + return ResolveResult{TTL: ImmutableTTL}, errors.New("not a valid proquint string") } - return path.FromString(string(proquint.Decode(name))), nil + return ResolveResult{path.FromString(string(proquint.Decode(name))), ImmutableTTL}, nil } diff --git a/namesys/routing.go b/namesys/routing.go index a65ce5123ce5..054589c6b43b 100644 --- a/namesys/routing.go +++ b/namesys/routing.go @@ -26,14 +26,14 @@ type routingResolver struct { cache *lru.Cache } -func (r *routingResolver) cacheGet(name string) (path.Path, bool) { +func (r *routingResolver) cacheGet(name string) (ResolveResult, bool) { if r.cache == nil { - return "", false + return ResolveResult{}, false } ientry, ok := r.cache.Get(name) if !ok { - return "", false + return ResolveResult{}, false } entry, ok := ientry.(cacheEntry) @@ -42,13 +42,14 @@ func (r *routingResolver) cacheGet(name string) (path.Path, bool) { log.Panicf("unexpected type %T in cache for %q.", ientry, name) } - if time.Now().Before(entry.eol) { - return entry.val, true + now := time.Now() + if now.Before(entry.eol) { + return ResolveResult{entry.val, entry.eol.Sub(now)}, true } r.cache.Remove(name) - return "", false + return ResolveResult{}, false } func (r *routingResolver) cacheSet(name string, val path.Path, eol time.Time, rec *pb.IpnsEntry) { @@ -89,18 +90,30 @@ func NewRoutingResolver(route routing.IpfsRouting, cachesize int) *routingResolv // Resolve implements Resolver. func (r *routingResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - return r.ResolveN(ctx, name, DefaultDepthLimit) + res, err := r.ResolveFull(ctx, name) + return res.Path, err } // ResolveN implements Resolver. func (r *routingResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + res, err := r.ResolveNFull(ctx, name, depth) + return res.Path, err +} + +// ResolveFull implements Resolver. +func (r *routingResolver) ResolveFull(ctx context.Context, name string) (ResolveResult, error) { + return r.ResolveNFull(ctx, name, DefaultDepthLimit) +} + +// ResolveNFull implements Resolver. +func (r *routingResolver) ResolveNFull(ctx context.Context, name string, depth int) (ResolveResult, error) { return resolve(ctx, r, name, depth, "/ipns/") } // resolveOnce implements resolver. Uses the IPFS routing system to // resolve SFS-like names. -func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { - log.Debugf("RoutingResolve: '%s'", name) +func (r *routingResolver) resolveOnce(ctx context.Context, name string) (ResolveResult, error) { + log.Debugf("RoutingResolve: %q", name) cached, ok := r.cacheGet(name) if ok { return cached, nil @@ -108,8 +121,8 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa hash, err := mh.FromB58String(name) if err != nil { - log.Warning("RoutingResolve: bad input hash: [%s]\n", name) - return "", err + log.Warningf("RoutingResolve: bad input hash: %q", name) + return ResolveResult{TTL: ImmutableTTL}, err } // name should be a multihash. if it isn't, error out here. @@ -121,19 +134,19 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa val, err := r.routing.GetValue(ctx, ipnsKey) if err != nil { log.Warning("RoutingResolve get failed.") - return "", err + return ResolveResult{TTL: 0}, err } entry := new(pb.IpnsEntry) err = proto.Unmarshal(val, entry) if err != nil { - return "", err + return ResolveResult{TTL: 0}, err } // name should be a public key retrievable from ipfs pubkey, err := routing.GetPublicKey(r.routing, ctx, hash) if err != nil { - return "", err + return ResolveResult{TTL: 0}, err } hsh, _ := pubkey.Hash() @@ -141,14 +154,14 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa // check sig with pk if ok, err := pubkey.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok { - return "", fmt.Errorf("Invalid value. Not signed by PrivateKey corresponding to %v", pubkey) + return ResolveResult{TTL: 0}, fmt.Errorf("Invalid value. Not signed by PrivateKey corresponding to %v", pubkey) } // ok sig checks out. this is a valid name. p, err := entryPath(entry) if err != nil { - return "", err + return ResolveResult{TTL: 0}, err } eol, ttl := entryEOL(entry) @@ -156,7 +169,7 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa r.cacheSet(name, p, eol, entry) } - return p, nil + return ResolveResult{p, ttl}, nil } // entryPath computes the path an IPNS entry points to. @@ -181,7 +194,7 @@ func entryPath(e *pb.IpnsEntry) (path.Path, error) { // and the EOL into account. func entryEOL(e *pb.IpnsEntry) (time.Time, time.Duration) { // if completely unspecified, just use one minute - ttl := DefaultResolverCacheTTL + ttl := UnknownTTL if e.Ttl != nil { recttl := time.Duration(e.GetTtl()) if recttl >= 0 {