diff --git a/find/client/dhash_client.go b/find/client/dhash_client.go index a12e1c5..2226178 100644 --- a/find/client/dhash_client.go +++ b/find/client/dhash_client.go @@ -2,15 +2,11 @@ package client import ( "context" - "encoding/json" - "io" - "net/http" "net/url" "strings" "time" logging "github.com/ipfs/go-log/v2" - "github.com/ipni/go-libipni/apierror" "github.com/ipni/go-libipni/dhash" "github.com/ipni/go-libipni/find/model" b58 "github.com/mr-tron/base58/base58" @@ -25,11 +21,16 @@ const ( var log = logging.Logger("dhash-client") +type DHStoreAPI interface { + // FindMultihash does a dh-multihash lookup and returns a + // model.FindResponse with EncryptedMultihashResults. + FindMultihash(context.Context, multihash.Multihash) ([]model.EncryptedMultihashResult, error) + FindMetadata(context.Context, []byte) ([]byte, error) +} + type DHashClient struct { - c *http.Client - dhFindURL *url.URL - dhMetadataURL *url.URL - pcache *providerCache + dhstoreAPI DHStoreAPI + pcache *providerCache } // NewDHashClient instantiates a new client that uses Reader Privacy API for @@ -48,24 +49,32 @@ func NewDHashClient(stiURL string, options ...Option) (*DHashClient, error) { return nil, err } - dhsURL := sURL - if len(opts.dhstoreURL) > 0 { - dhsURL, err = parseURL(opts.dhstoreURL) - if err != nil { - return nil, err - } - } - pcache, err := newProviderCache(sURL, opts.httpClient) if err != nil { return nil, err } + var dhsAPI DHStoreAPI + if opts.dhstoreAPI != nil { + dhsAPI = opts.dhstoreAPI + } else { + dhsURL := sURL + if len(opts.dhstoreURL) > 0 { + dhsURL, err = parseURL(opts.dhstoreURL) + if err != nil { + return nil, err + } + } + dhsAPI = &dhstoreHTTP{ + c: opts.httpClient, + dhFindURL: dhsURL.JoinPath(findPath), + dhMetadataURL: dhsURL.JoinPath(metadataPath), + } + } + return &DHashClient{ - c: opts.httpClient, - dhFindURL: dhsURL.JoinPath(findPath), - dhMetadataURL: dhsURL.JoinPath(metadataPath), - pcache: pcache, + dhstoreAPI: dhsAPI, + pcache: pcache, }, nil } @@ -100,40 +109,17 @@ func (c *DHashClient) Find(ctx context.Context, mh multihash.Multihash) (*model. func (c *DHashClient) FindAsync(ctx context.Context, mh multihash.Multihash, resChan chan<- model.ProviderResult) error { defer close(resChan) - smh, err := dhash.SecondMultihash(mh) - if err != nil { - return err - } - u := c.dhFindURL.JoinPath(smh.B58String()) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) - if err != nil { - return err - } - req.Header.Add("Accept", "application/json") - - resp, err := c.c.Do(req) - if err != nil { - return err - } - - body, err := io.ReadAll(resp.Body) - defer resp.Body.Close() - + dhmh, err := dhash.SecondMultihash(mh) if err != nil { return err } - if resp.StatusCode != http.StatusOK { - return apierror.FromResponse(resp.StatusCode, body) - } - - encResponse := &model.FindResponse{} - err = json.Unmarshal(body, encResponse) + encryptedMultihashResults, err := c.dhstoreAPI.FindMultihash(ctx, dhmh) if err != nil { return err } - for _, emhrs := range encResponse.EncryptedMultihashResults { + for _, emhrs := range encryptedMultihashResults { for _, evk := range emhrs.EncryptedValueKeys { vk, err := dhash.DecryptValueKey(evk, mh) // skip errors as we don't want to fail the whole query, warn @@ -176,43 +162,11 @@ func (c *DHashClient) FindAsync(ctx context.Context, mh multihash.Multihash, res // fetchMetadata fetches and decrypts metadata from a remote server. func (c *DHashClient) fetchMetadata(ctx context.Context, vk []byte) ([]byte, error) { - u := c.dhMetadataURL.JoinPath(b58.Encode(dhash.SHA256(vk, nil))) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) - if err != nil { - return nil, err - } - req.Header.Add("Accept", "application/json") - - resp, err := c.c.Do(req) - if err != nil { - return nil, err - } - - body, err := io.ReadAll(resp.Body) - defer resp.Body.Close() - + encryptedMetadata, err := c.dhstoreAPI.FindMetadata(ctx, vk) if err != nil { return nil, err } - - if resp.StatusCode != http.StatusOK { - return nil, apierror.FromResponse(resp.StatusCode, body) - } - - type ( - GetMetadataResponse struct { - EncryptedMetadata []byte `json:"EncryptedMetadata"` - } - ) - - findResponse := &GetMetadataResponse{} - err = json.Unmarshal(body, findResponse) - - if err != nil { - return nil, err - } - - return dhash.DecryptMetadata(findResponse.EncryptedMetadata, vk) + return dhash.DecryptMetadata(encryptedMetadata, vk) } func parseURL(su string) (*url.URL, error) { diff --git a/find/client/dhstore_http.go b/find/client/dhstore_http.go new file mode 100644 index 0000000..568a4f2 --- /dev/null +++ b/find/client/dhstore_http.go @@ -0,0 +1,90 @@ +package client + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/ipni/go-libipni/apierror" + "github.com/ipni/go-libipni/dhash" + "github.com/ipni/go-libipni/find/model" + b58 "github.com/mr-tron/base58/base58" + "github.com/multiformats/go-multihash" +) + +type dhstoreHTTP struct { + c *http.Client + dhFindURL *url.URL + dhMetadataURL *url.URL +} + +func (d *dhstoreHTTP) FindMultihash(ctx context.Context, dhmh multihash.Multihash) ([]model.EncryptedMultihashResult, error) { + u := d.dhFindURL.JoinPath(dhmh.B58String()) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + req.Header.Add("Accept", "application/json") + + resp, err := d.c.Do(req) + if err != nil { + return nil, err + } + + body, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, apierror.FromResponse(resp.StatusCode, body) + } + + encResponse := &model.FindResponse{} + err = json.Unmarshal(body, encResponse) + if err != nil { + return nil, err + } + return encResponse.EncryptedMultihashResults, nil +} + +func (d *dhstoreHTTP) FindMetadata(ctx context.Context, vk []byte) ([]byte, error) { + u := d.dhMetadataURL.JoinPath(b58.Encode(dhash.SHA256(vk, nil))) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + req.Header.Add("Accept", "application/json") + + resp, err := d.c.Do(req) + if err != nil { + return nil, err + } + + body, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, apierror.FromResponse(resp.StatusCode, body) + } + + type GetMetadataResponse struct { + EncryptedMetadata []byte `json:"EncryptedMetadata"` + } + + findResponse := &GetMetadataResponse{} + err = json.Unmarshal(body, findResponse) + if err != nil { + return nil, err + } + + return findResponse.EncryptedMetadata, nil +} diff --git a/find/client/option.go b/find/client/option.go index cd01ce7..374f046 100644 --- a/find/client/option.go +++ b/find/client/option.go @@ -8,6 +8,7 @@ import ( type config struct { httpClient *http.Client dhstoreURL string + dhstoreAPI DHStoreAPI } // Option is a function that sets a value in a config. @@ -46,3 +47,13 @@ func WithDHStoreURL(u string) Option { return nil } } + +// WithDHStoreAPI configures an interface to use for doing multihash and +// metadata lookups with dhstore. If this is not configured, then dhstore +// lookups are done using the dhstoreURL. +func WithDHStoreAPI(dhsAPI DHStoreAPI) Option { + return func(cfg *config) error { + cfg.dhstoreAPI = dhsAPI + return nil + } +}