Skip to content

Commit

Permalink
DHClient can have a dhstore URL or API
Browse files Browse the repository at this point in the history
DHashClient can be given a dhstore API interface when the client within dhstore, This is so that dhstore can give it a set of internal functions to call to do dh multihash and metadata lookups. Otherwise, a client that is not in dhstore uses the dhstore URL to perfrom remote dhstore lookups.
  • Loading branch information
gammazero committed Jun 28, 2023
1 parent a389fe7 commit 0182c73
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 79 deletions.
114 changes: 35 additions & 79 deletions find/client/dhash_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ 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"
Expand All @@ -25,11 +22,17 @@ 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
c *http.Client

Check failure on line 33 in find/client/dhash_client.go

View workflow job for this annotation

GitHub Actions / All

field c is unused (U1000)
dhstoreAPI DHStoreAPI
pcache *providerCache
}

// NewDHashClient instantiates a new client that uses Reader Privacy API for
Expand All @@ -48,24 +51,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
}

Expand Down Expand Up @@ -100,40 +111,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
Expand Down Expand Up @@ -176,43 +164,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) {
Expand Down
90 changes: 90 additions & 0 deletions find/client/dhstore_http.go
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions find/client/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
}

0 comments on commit 0182c73

Please sign in to comment.