1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
15 // Compute the MD5 digest of a data block (consisting of buf1 + buf2 +
16 // all bytes readable from rdr). If all data is read successfully,
17 // return DiskHashError or CollisionError depending on whether it
18 // matches expectMD5. If an error occurs while reading, return that
21 // "content has expected MD5" is called a collision because this
22 // function is used in cases where we have another block in hand with
23 // the given MD5 but different content.
24 func collisionOrCorrupt(expectMD5 string, buf1, buf2 []byte, rdr io.Reader) error {
25 outcome := make(chan error)
26 data := make(chan []byte, 1)
32 if fmt.Sprintf("%x", h.Sum(nil)) == expectMD5 {
33 outcome <- CollisionError
35 outcome <- DiskHashError
43 for rdr != nil && err == nil {
44 buf := make([]byte, 1<<18)
46 n, err = rdr.Read(buf)
50 if rdr != nil && err != io.EOF {
57 func compareReaderWithBuf(ctx context.Context, rdr io.Reader, expect []byte, hash string) error {
59 if bufLen > len(expect) && len(expect) > 0 {
60 // No need for bufLen to be longer than
61 // expect, except that len(buf)==0 would
62 // prevent us from handling empty readers the
63 // same way as non-empty readers: reading 0
64 // bytes at a time never reaches EOF.
67 buf := make([]byte, bufLen)
70 // Loop invariants: all data read so far matched what
71 // we expected, and the first N bytes of cmp are
72 // expected to equal the next N bytes read from
75 ready := make(chan bool)
79 n, err = rdr.Read(buf)
87 if n > len(cmp) || bytes.Compare(cmp[:n], buf[:n]) != 0 {
88 return collisionOrCorrupt(hash, expect[:len(expect)-len(cmp)], buf[:n], rdr)
93 return collisionOrCorrupt(hash, expect[:len(expect)-len(cmp)], nil, nil)
96 } else if err != nil {