github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/digest/reader.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package digest 22 23 import ( 24 "bufio" 25 "errors" 26 "hash" 27 "hash/adler32" 28 "io" 29 "os" 30 ) 31 32 var ( 33 // errReadFewerThanExpectedBytes returned when number of bytes read is fewer than expected 34 errReadFewerThanExpectedBytes = errors.New("number of bytes read is fewer than expected") 35 36 // errChecksumMismatch returned when the calculated checksum doesn't match the stored checksum 37 errChecksumMismatch = errors.New("calculated checksum doesn't match stored checksum") 38 39 // errBufferSizeMismatch returned when ReadAllAndValidate called without well sized buffer 40 errBufferSizeMismatch = errors.New("buffer passed is not an exact fit for contents") 41 ) 42 43 // FdWithDigestReader provides a buffered reader for reading from the underlying file. 44 type FdWithDigestReader interface { 45 FdWithDigest 46 io.Reader 47 48 // ReadAllAndValidate reads everything in the underlying file and validates 49 // it against the expected digest, returning an error if they don't match. 50 // Note: the buffer "b" must be an exact match for how long the contents being 51 // read is, the signature is structured this way to allow for buffer reuse. 52 ReadAllAndValidate(b []byte, expectedDigest uint32) (int, error) 53 54 // Validate compares the current digest against the expected digest and returns 55 // an error if they don't match. 56 Validate(expectedDigest uint32) error 57 } 58 59 type fdWithDigestReader struct { 60 fd *os.File 61 bufReader *bufio.Reader 62 readerWithDigest ReaderWithDigest 63 single [1]byte 64 } 65 66 // NewFdWithDigestReader creates a new FdWithDigestReader. 67 func NewFdWithDigestReader(bufferSize int) FdWithDigestReader { 68 bufReader := bufio.NewReaderSize(nil, bufferSize) 69 return &fdWithDigestReader{ 70 bufReader: bufReader, 71 readerWithDigest: NewReaderWithDigest(bufReader), 72 } 73 } 74 75 // Reset resets the underlying file descriptor and the buffered reader. 76 func (r *fdWithDigestReader) Reset(fd *os.File) { 77 r.fd = fd 78 r.bufReader.Reset(fd) 79 r.readerWithDigest.Reset(r.bufReader) 80 } 81 82 func (r *fdWithDigestReader) Read(b []byte) (int, error) { 83 return r.readerWithDigest.Read(b) 84 } 85 86 func (r *fdWithDigestReader) Fd() *os.File { 87 return r.fd 88 } 89 90 func (r *fdWithDigestReader) Digest() hash.Hash32 { 91 return r.readerWithDigest.Digest() 92 } 93 94 func (r *fdWithDigestReader) ReadAllAndValidate(b []byte, expectedDigest uint32) (int, error) { 95 n, err := r.Read(b) 96 if err != nil { 97 return n, err 98 } 99 // NB(r): Attempt next read to prove that the size of the buffer b 100 // was sized correctly to fit all contents into it and that we are 101 // correctly now at the end of input. 102 _, err = r.Read(r.single[:]) 103 if err != io.EOF { 104 return 0, errBufferSizeMismatch 105 } 106 if err := r.Validate(expectedDigest); err != nil { 107 return n, err 108 } 109 return n, nil 110 } 111 112 func (r *fdWithDigestReader) Validate(expectedDigest uint32) error { 113 return r.readerWithDigest.Validate(expectedDigest) 114 } 115 116 func (r *fdWithDigestReader) Close() error { 117 if r.fd == nil { 118 return nil 119 } 120 err := r.fd.Close() 121 r.fd = nil 122 return err 123 } 124 125 // FdWithDigestContentsReader provides additional functionality of reading a digest from the underlying file. 126 type FdWithDigestContentsReader interface { 127 FdWithDigestReader 128 129 // ReadDigest reads a digest from the underlying file. 130 ReadDigest() (uint32, error) 131 } 132 133 type fdWithDigestContentsReader struct { 134 FdWithDigestReader 135 136 digestBuf Buffer 137 } 138 139 // NewFdWithDigestContentsReader creates a new FdWithDigestContentsReader. 140 func NewFdWithDigestContentsReader(bufferSize int) FdWithDigestContentsReader { 141 return &fdWithDigestContentsReader{ 142 FdWithDigestReader: NewFdWithDigestReader(bufferSize), 143 digestBuf: NewBuffer(), 144 } 145 } 146 147 func (r *fdWithDigestContentsReader) ReadDigest() (uint32, error) { 148 n, err := r.Read(r.digestBuf) 149 if err != nil { 150 return 0, err 151 } 152 if n < len(r.digestBuf) { 153 return 0, errReadFewerThanExpectedBytes 154 } 155 return r.digestBuf.ReadDigest(), nil 156 } 157 158 // ReaderWithDigest is a reader that that calculates a digest 159 // as it is read. 160 type ReaderWithDigest interface { 161 io.Reader 162 163 // Reset resets the reader for use with a new reader. 164 Reset(reader io.Reader) 165 166 // Digest returns the digest. 167 Digest() hash.Hash32 168 169 // Validate compares the current digest against the expected digest and returns 170 // an error if they don't match. 171 Validate(expectedDigest uint32) error 172 } 173 174 type readerWithDigest struct { 175 reader io.Reader 176 digest hash.Hash32 177 } 178 179 // NewReaderWithDigest creates a new reader that calculates a digest as it 180 // reads an input. 181 func NewReaderWithDigest(reader io.Reader) ReaderWithDigest { 182 return &readerWithDigest{ 183 reader: reader, 184 digest: adler32.New(), 185 } 186 } 187 188 func (r *readerWithDigest) Reset(reader io.Reader) { 189 r.reader = reader 190 r.digest.Reset() 191 } 192 193 func (r *readerWithDigest) Digest() hash.Hash32 { 194 return r.digest 195 } 196 197 func (r *readerWithDigest) readBytes(b []byte) (int, error) { 198 n, err := r.reader.Read(b) 199 if err != nil { 200 return 0, err 201 } 202 // In case the buffered reader only returns what's remaining in 203 // the buffer, recursively read what's left in the underlying reader. 204 if n < len(b) { 205 b = b[n:] 206 remainder, err := r.readBytes(b) 207 return n + remainder, err 208 } 209 return n, err 210 } 211 212 func (r *readerWithDigest) Read(b []byte) (int, error) { 213 n, err := r.readBytes(b) 214 if err != nil && err != io.EOF { 215 return n, err 216 } 217 // If we encountered an EOF error and didn't read any bytes 218 // given a non-empty slice, we return an EOF error. 219 if err == io.EOF && n == 0 && len(b) > 0 { 220 return 0, err 221 } 222 if _, err := r.digest.Write(b[:n]); err != nil { 223 return 0, err 224 } 225 return n, nil 226 } 227 228 func (r *readerWithDigest) Validate(expectedDigest uint32) error { 229 if r.digest.Sum32() != expectedDigest { 230 return errChecksumMismatch 231 } 232 return nil 233 }