Merge remote-tracking branch 'origin/master' into 2044-share-button
[arvados.git] / sdk / go / src / arvados.org / keepclient / hashcheck.go
1 // Lightweight implementation of io.ReadCloser that checks the contents read
2 // from the underlying io.Reader a against checksum hash.  To avoid reading the
3 // entire contents into a buffer up front, the hash is updated with each read,
4 // and the actual checksum is not checked until the underlying reader returns
5 // EOF.
6 package keepclient
7
8 import (
9         "errors"
10         "fmt"
11         "hash"
12         "io"
13 )
14
15 var BadChecksum = errors.New("Reader failed checksum")
16
17 type HashCheckingReader struct {
18         // The underlying data source
19         io.Reader
20
21         // The hashing function to use
22         hash.Hash
23
24         // The hash value to check against.  Must be a hex-encoded lowercase string.
25         Check string
26 }
27
28 // Read from the underlying reader, update the hashing function, and pass the
29 // results through.  Will return BadChecksum on the last read instead of EOF if
30 // the checksum doesn't match.
31 func (this HashCheckingReader) Read(p []byte) (n int, err error) {
32         n, err = this.Reader.Read(p)
33         if err == nil {
34                 this.Hash.Write(p[:n])
35         } else if err == io.EOF {
36                 sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
37                 if fmt.Sprintf("%x", sum) != this.Check {
38                         err = BadChecksum
39                 }
40         }
41         return n, err
42 }
43
44 // Write entire contents of this.Reader to 'dest'.  Returns BadChecksum if the
45 // data written to 'dest' doesn't match the hash code of this.Check.
46 func (this HashCheckingReader) WriteTo(dest io.Writer) (written int64, err error) {
47         if writeto, ok := this.Reader.(io.WriterTo); ok {
48                 written, err = writeto.WriteTo(io.MultiWriter(dest, this.Hash))
49         } else {
50                 written, err = io.Copy(io.MultiWriter(dest, this.Hash), this.Reader)
51         }
52
53         sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
54
55         if fmt.Sprintf("%x", sum) != this.Check {
56                 err = BadChecksum
57         }
58
59         return written, err
60 }
61
62 // Close() the underlying Reader if it is castable to io.ReadCloser.  This will
63 // drain the underlying reader of any remaining data and check the checksum.
64 func (this HashCheckingReader) Close() (err error) {
65         _, err = io.Copy(this.Hash, this.Reader)
66
67         if closer, ok := this.Reader.(io.ReadCloser); ok {
68                 err = closer.Close()
69         }
70
71         sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
72         if fmt.Sprintf("%x", sum) != this.Check {
73                 err = BadChecksum
74         }
75
76         return err
77 }