package http3 import ( "crypto/tls" "crypto/x509" "net/http" "strings" "sync" "time" "github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/stream" "github.com/lbryio/reflector.go/shared" "github.com/lbryio/reflector.go/store" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/http3" ) // Store is a blob store that gets blobs from a peer. // It satisfies the store.BlobStore interface but cannot put or delete blobs. type Store struct { opts StoreOpts NotFoundCache *sync.Map } // StoreOpts allows to set options for a new Store. type StoreOpts struct { Address string Timeout time.Duration } // NewStore makes a new peer store. func NewStore(opts StoreOpts) *Store { return &Store{opts: opts, NotFoundCache: &sync.Map{}} } func (p *Store) getClient() (*Client, error) { var qconf quic.Config window500M := 500 * 1 << 20 qconf.MaxStreamReceiveWindow = uint64(window500M) qconf.MaxConnectionReceiveWindow = uint64(window500M) qconf.EnableDatagrams = true qconf.HandshakeIdleTimeout = 4 * time.Second qconf.MaxIdleTimeout = 20 * time.Second pool, err := x509.SystemCertPool() if err != nil { return nil, err } roundTripper := &http3.RoundTripper{ TLSClientConfig: &tls.Config{ RootCAs: pool, InsecureSkipVerify: true, }, QuicConfig: &qconf, } connection := &http.Client{ Transport: roundTripper, } c := &Client{ conn: connection, roundTripper: roundTripper, ServerAddr: p.opts.Address, } return c, errors.Prefix("connection error", err) } func (p *Store) Name() string { return "http3" } // Has asks the peer if they have a hash func (p *Store) Has(hash string) (bool, error) { c, err := p.getClient() if err != nil { return false, err } defer c.Close() return c.HasBlob(hash) } // Get downloads the blob from the peer func (p *Store) Get(hash string) (stream.Blob, shared.BlobTrace, error) { start := time.Now() if lastChecked, ok := p.NotFoundCache.Load(hash); ok { if lastChecked.(time.Time).After(time.Now().Add(-5 * time.Minute)) { return nil, shared.NewBlobTrace(time.Since(start), p.Name()+"-notfoundcache"), store.ErrBlobNotFound } } c, err := p.getClient() if err != nil && strings.Contains(err.Error(), "blob not found") { p.NotFoundCache.Store(hash, time.Now()) } if err != nil { return nil, shared.NewBlobTrace(time.Since(start), p.Name()), err } defer c.Close() return c.GetBlob(hash) } // Put is not supported func (p *Store) Put(hash string, blob stream.Blob) error { return errors.Err(shared.ErrNotImplemented) } // PutSD is not supported func (p *Store) PutSD(hash string, blob stream.Blob) error { return errors.Err(shared.ErrNotImplemented) } // Delete is not supported func (p *Store) Delete(hash string) error { return errors.Err(shared.ErrNotImplemented) } // Delete is not supported func (p *Store) Shutdown() { return }