Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DHClient can have a dhstore URL or API #71

Merged
merged 2 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 34 additions & 80 deletions find/client/dhash_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
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
}
}