Skip to content

Commit 203cbf2

Browse files
committed
Add hashclone method and implement its interface
1 parent fa74f14 commit 203cbf2

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

cng/hash.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ type cloneHash interface {
160160
}
161161

162162
var _ hash.Hash = (*hashX)(nil)
163-
var _ cloneHash = (*hashX)(nil)
163+
var _ HashCloner = (*hashX)(nil)
164164

165165
// hashX implements [hash.Hash].
166166
type hashX struct {
@@ -201,14 +201,14 @@ func (h *hashX) init() {
201201
runtime.SetFinalizer(h, (*hashX).finalize)
202202
}
203203

204-
func (h *hashX) Clone() hash.Hash {
204+
func (h *hashX) Clone() (HashCloner, error) {
205205
defer runtime.KeepAlive(h)
206206
h2 := &hashX{alg: h.alg, key: bytes.Clone(h.key)}
207207
if h.ctx != 0 {
208208
hashClone(h.ctx, &h2.ctx)
209209
runtime.SetFinalizer(h2, (*hashX).finalize)
210210
}
211-
return h2
211+
return h2, nil
212212
}
213213

214214
func (h *hashX) Reset() {

cng/hash_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,55 @@ func TestHash(t *testing.T) {
111111
}
112112
}
113113

114+
func TestHash_Clone(t *testing.T) {
115+
msg := []byte("testing")
116+
for _, tt := range hashes {
117+
t.Run(tt.String(), func(t *testing.T) {
118+
if !cng.SupportsHash(tt) {
119+
t.Skip("skipping: not supported")
120+
}
121+
h := cryptoToHash(tt)().(cng.HashCloner)
122+
123+
_, err := h.Write(msg)
124+
if err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
h3, err := h.Clone()
129+
if err != nil {
130+
t.Fatalf("Clone failed: %v", err)
131+
}
132+
prefix := []byte("tmp")
133+
writeToHash(t, h, prefix)
134+
h2, err := h.Clone()
135+
if err != nil {
136+
t.Fatalf("Clone failed: %v", err)
137+
}
138+
prefixSum := h.Sum(nil)
139+
if !bytes.Equal(prefixSum, h2.Sum(nil)) {
140+
t.Fatalf("%T Clone results are inconsistent", h)
141+
}
142+
suffix := []byte("tmp2")
143+
writeToHash(t, h, suffix)
144+
writeToHash(t, h3, append(prefix, suffix...))
145+
compositeSum := h3.Sum(nil)
146+
if !bytes.Equal(h.Sum(nil), compositeSum) {
147+
t.Fatalf("%T Clone results are inconsistent", h)
148+
}
149+
if !bytes.Equal(h2.Sum(nil), prefixSum) {
150+
t.Fatalf("%T Clone results are inconsistent", h)
151+
}
152+
writeToHash(t, h2, suffix)
153+
if !bytes.Equal(h.Sum(nil), compositeSum) {
154+
t.Fatalf("%T Clone results are inconsistent", h)
155+
}
156+
if !bytes.Equal(h2.Sum(nil), compositeSum) {
157+
t.Fatalf("%T Clone results are inconsistent", h)
158+
}
159+
})
160+
}
161+
}
162+
114163
func TestHash_Interface(t *testing.T) {
115164
for _, tt := range hashes {
116165
t.Run(tt.String(), func(t *testing.T) {
@@ -264,3 +313,20 @@ func BenchmarkSHA256_OneShot(b *testing.B) {
264313
cng.SHA256(buf)
265314
}
266315
}
316+
317+
// Helper function for writing. Verifies that Write does not error.Add commentMore actions
318+
func writeToHash(t *testing.T, h hash.Hash, p []byte) {
319+
t.Helper()
320+
321+
before := make([]byte, len(p))
322+
copy(before, p)
323+
324+
n, err := h.Write(p)
325+
if err != nil || n != len(p) {
326+
t.Errorf("Write returned error; got (%v, %v), want (nil, %v)", err, n, len(p))
327+
}
328+
329+
if !bytes.Equal(p, before) {
330+
t.Errorf("Write modified input slice; got %x, want %x", p, before)
331+
}
332+
}

cng/hashclone.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
//go:build windows
5+
// +build windows
6+
7+
package cng
8+
9+
import (
10+
"hash"
11+
)
12+
13+
// HashCloner is an interface that defines a Clone method.
14+
type HashCloner interface {
15+
hash.Hash
16+
// Clone returns a separate Hash instance with the same state as h.
17+
Clone() (HashCloner, error)
18+
}

cng/hashclone_go125.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
//go:build go1.25 && windows
5+
// +build go1.25,windows
6+
7+
package cng
8+
9+
import (
10+
"hash"
11+
)
12+
13+
type HashCloner = hash.Cloner

0 commit comments

Comments
 (0)