Remove digest package's dependency on external sha implementation
The change relies on a refactor of the upstream resumable sha256/sha512 package that opts to register implementations with the standard library. This allows the resumable support to be detected where it matters, avoiding unnecessary and complex code. It also ensures that consumers of the digest package don't need to depend on the forked sha implementations. We also get an optimization with this change. If the size of data written to a digester is the same as the file size, we check to see if the digest has been verified. This works if the blob is written and committed in a single request. Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
07ff029506
commit
eee6cad2cf
32 changed files with 431 additions and 459 deletions
|
@ -2,6 +2,7 @@ package digest
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
@ -15,8 +16,12 @@ import (
|
|||
const (
|
||||
// DigestTarSumV1EmptyTar is the digest for the empty tar file.
|
||||
DigestTarSumV1EmptyTar = "tarsum.v1+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
|
||||
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
CanonicalAlgorithm = "sha256"
|
||||
CanonicalHash = crypto.SHA256 // main digest algorithm used through distribution
|
||||
)
|
||||
|
||||
// Digest allows simple protection of hex formatted digest strings, prefixed
|
||||
|
@ -73,7 +78,7 @@ func ParseDigest(s string) (Digest, error) {
|
|||
func FromReader(rd io.Reader) (Digest, error) {
|
||||
digester := NewCanonicalDigester()
|
||||
|
||||
if _, err := io.Copy(digester, rd); err != nil {
|
||||
if _, err := io.Copy(digester.Hash(), rd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
|
|
@ -1,54 +1,40 @@
|
|||
package digest
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
)
|
||||
import "hash"
|
||||
|
||||
// Digester calculates the digest of written data. It is functionally
|
||||
// equivalent to hash.Hash but provides methods for returning the Digest type
|
||||
// rather than raw bytes.
|
||||
type Digester struct {
|
||||
alg string
|
||||
hash.Hash
|
||||
// Digester calculates the digest of written data. Writes should go directly
|
||||
// to the return value of Hash, while calling Digest will return the current
|
||||
// value of the digest.
|
||||
type Digester interface {
|
||||
Hash() hash.Hash // provides direct access to underlying hash instance.
|
||||
Digest() Digest
|
||||
}
|
||||
|
||||
// NewDigester create a new Digester with the given hashing algorithm and instance
|
||||
// of that algo's hasher.
|
||||
// NewDigester create a new Digester with the given hashing algorithm and
|
||||
// instance of that algo's hasher.
|
||||
func NewDigester(alg string, h hash.Hash) Digester {
|
||||
return Digester{
|
||||
return &digester{
|
||||
alg: alg,
|
||||
Hash: h,
|
||||
hash: h,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCanonicalDigester is a convenience function to create a new Digester with
|
||||
// our default settings.
|
||||
func NewCanonicalDigester() Digester {
|
||||
return NewDigester("sha256", sha256.New())
|
||||
return NewDigester(CanonicalAlgorithm, CanonicalHash.New())
|
||||
}
|
||||
|
||||
// Digest returns the current digest for this digester.
|
||||
func (d *Digester) Digest() Digest {
|
||||
return NewDigest(d.alg, d.Hash)
|
||||
// digester provides a simple digester definition that embeds a hasher.
|
||||
type digester struct {
|
||||
alg string
|
||||
hash hash.Hash
|
||||
}
|
||||
|
||||
// ResumableHash is the common interface implemented by all resumable hash
|
||||
// functions.
|
||||
type ResumableHash interface {
|
||||
// ResumableHash is a superset of hash.Hash
|
||||
hash.Hash
|
||||
// Len returns the number of bytes written to the Hash so far.
|
||||
Len() uint64
|
||||
// State returns a snapshot of the state of the Hash.
|
||||
State() ([]byte, error)
|
||||
// Restore resets the Hash to the given state.
|
||||
Restore(state []byte) error
|
||||
func (d *digester) Hash() hash.Hash {
|
||||
return d.hash
|
||||
}
|
||||
|
||||
// ResumableDigester is a digester that can export its internal state and be
|
||||
// restored from saved state.
|
||||
type ResumableDigester interface {
|
||||
ResumableHash
|
||||
Digest() Digest
|
||||
func (d *digester) Digest() Digest {
|
||||
return NewDigest(d.alg, d.Hash())
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
// +build !noresumabledigest
|
||||
|
||||
package digest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jlhawn/go-crypto"
|
||||
// For ResumableHash
|
||||
_ "github.com/jlhawn/go-crypto/sha256" // For Resumable SHA256
|
||||
_ "github.com/jlhawn/go-crypto/sha512" // For Resumable SHA384, SHA512
|
||||
)
|
||||
|
||||
// resumableDigester implements ResumableDigester.
|
||||
type resumableDigester struct {
|
||||
alg string
|
||||
crypto.ResumableHash
|
||||
}
|
||||
|
||||
var resumableHashAlgs = map[string]crypto.Hash{
|
||||
"sha256": crypto.SHA256,
|
||||
"sha384": crypto.SHA384,
|
||||
"sha512": crypto.SHA512,
|
||||
}
|
||||
|
||||
// NewResumableDigester creates a new ResumableDigester with the given hashing
|
||||
// algorithm.
|
||||
func NewResumableDigester(alg string) (ResumableDigester, error) {
|
||||
hash, supported := resumableHashAlgs[alg]
|
||||
if !supported {
|
||||
return resumableDigester{}, fmt.Errorf("unsupported resumable hash algorithm: %s", alg)
|
||||
}
|
||||
|
||||
return resumableDigester{
|
||||
alg: alg,
|
||||
ResumableHash: hash.New(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewCanonicalResumableDigester creates a ResumableDigester using the default
|
||||
// digest algorithm.
|
||||
func NewCanonicalResumableDigester() ResumableDigester {
|
||||
return resumableDigester{
|
||||
alg: "sha256",
|
||||
ResumableHash: crypto.SHA256.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Digest returns the current digest for this resumable digester.
|
||||
func (d resumableDigester) Digest() Digest {
|
||||
return NewDigest(d.alg, d.ResumableHash)
|
||||
}
|
21
digest/digester_resumable_test.go
Normal file
21
digest/digester_resumable_test.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// +build !noresumabledigest
|
||||
|
||||
package digest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stevvooe/resumable"
|
||||
_ "github.com/stevvooe/resumable/sha256"
|
||||
)
|
||||
|
||||
// TestResumableDetection just ensures that the resumable capability of a hash
|
||||
// is exposed through the digester type, which is just a hash plus a Digest
|
||||
// method.
|
||||
func TestResumableDetection(t *testing.T) {
|
||||
d := NewCanonicalDigester()
|
||||
|
||||
if _, ok := d.Hash().(resumable.Hash); !ok {
|
||||
t.Fatalf("expected digester to implement resumable: %#v, %v", d, d.Hash())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue