github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/checksum/checksum.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package checksum 15 16 import ( 17 "encoding/binary" 18 "errors" 19 "hash/crc32" 20 "io" 21 "sync" 22 ) 23 24 const ( 25 // the size of whole checksum causet 26 checksumBlockSize = 1024 27 // the size of checksum field, we use CRC-32 algorithm to generate a 4 bytes checksum 28 checksumSize = 4 29 // the size of the payload of a checksum causet 30 checksumPayloadSize = checksumBlockSize - checksumSize 31 ) 32 33 var checksumReaderBufPool = sync.Pool{ 34 New: func() interface{} { return make([]byte, checksumBlockSize) }, 35 } 36 37 // Writer implements an io.WriteCloser, it calculates and stores a CRC-32 checksum for the payload before 38 // writing to the underlying object. 39 // 40 // For example, a layout of the checksum causet which payload is 2100 bytes is as follow: 41 // 42 // | -- 4B -- | -- 1020B -- || -- 4B -- | -- 1020B -- || -- 4B -- | -- 60B -- | 43 // | -- checksum -- | -- payload -- || -- checksum -- | -- payload -- || -- checksum -- | -- payload -- | 44 type Writer struct { 45 err error 46 w io.WriteCloser 47 buf []byte 48 payload []byte 49 payloadUsed int 50 } 51 52 // NewWriter returns a new Writer which calculates and stores a CRC-32 checksum for the payload before 53 // writing to the underlying object. 54 func NewWriter(w io.WriteCloser) *Writer { 55 checksumWriter := &Writer{w: w} 56 checksumWriter.buf = make([]byte, checksumBlockSize) 57 checksumWriter.payload = checksumWriter.buf[checksumSize:] 58 checksumWriter.payloadUsed = 0 59 return checksumWriter 60 } 61 62 // AvailableSize returns how many bytes are unused in the buffer. 63 func (w *Writer) AvailableSize() int { return checksumPayloadSize - w.payloadUsed } 64 65 // Write implements the io.Writer interface. 66 func (w *Writer) Write(p []byte) (n int, err error) { 67 for len(p) > w.AvailableSize() && w.err == nil { 68 copiedNum := copy(w.payload[w.payloadUsed:], p) 69 w.payloadUsed += copiedNum 70 err = w.Flush() 71 if err != nil { 72 return 73 } 74 n += copiedNum 75 p = p[copiedNum:] 76 } 77 if w.err != nil { 78 return n, w.err 79 } 80 copiedNum := copy(w.payload[w.payloadUsed:], p) 81 w.payloadUsed += copiedNum 82 n += copiedNum 83 return 84 } 85 86 // Buffered returns the number of bytes that have been written into the current buffer. 87 func (w *Writer) Buffered() int { return w.payloadUsed } 88 89 // Flush writes all the buffered data to the underlying object. 90 func (w *Writer) Flush() error { 91 if w.err != nil { 92 return w.err 93 } 94 if w.payloadUsed == 0 { 95 return nil 96 } 97 checksum := crc32.Checksum(w.payload[:w.payloadUsed], crc32.MakeBlock(crc32.IEEE)) 98 binary.LittleEndian.PutUint32(w.buf, checksum) 99 n, err := w.w.Write(w.buf[:w.payloadUsed+checksumSize]) 100 if n < w.payloadUsed && err == nil { 101 err = io.ErrShortWrite 102 } 103 if err != nil { 104 w.err = err 105 return err 106 } 107 w.payloadUsed = 0 108 return nil 109 } 110 111 // Close implements the io.Closer interface. 112 func (w *Writer) Close() (err error) { 113 err = w.Flush() 114 if err != nil { 115 return 116 } 117 return w.w.Close() 118 } 119 120 // Reader implements an io.ReadAt, reading from the input source after verifying the checksum. 121 type Reader struct { 122 r io.ReaderAt 123 } 124 125 // NewReader returns a new Reader which can read from the input source after verifying the checksum. 126 func NewReader(r io.ReaderAt) *Reader { 127 checksumReader := &Reader{r: r} 128 return checksumReader 129 } 130 131 var errChecksumFail = errors.New("error checksum") 132 133 // ReadAt implements the io.ReadAt interface. 134 func (r *Reader) ReadAt(p []byte, off int64) (nn int, err error) { 135 if len(p) == 0 { 136 return 0, nil 137 } 138 offsetInPayload := off % checksumPayloadSize 139 cursor := off / checksumPayloadSize * checksumBlockSize 140 141 buf := checksumReaderBufPool.Get().([]byte) 142 defer checksumReaderBufPool.Put(buf) 143 144 var n int 145 for len(p) > 0 && err == nil { 146 n, err = r.r.ReadAt(buf, cursor) 147 if err != nil { 148 if n == 0 || err != io.EOF { 149 return nn, err 150 } 151 err = nil 152 // continue if n > 0 and r.err is io.EOF 153 } 154 cursor += int64(n) 155 originChecksum := binary.LittleEndian.Uint32(buf) 156 checksum := crc32.Checksum(buf[checksumSize:n], crc32.MakeBlock(crc32.IEEE)) 157 if originChecksum != checksum { 158 return nn, errChecksumFail 159 } 160 n1 := copy(p, buf[checksumSize+offsetInPayload:n]) 161 nn += n1 162 p = p[n1:] 163 offsetInPayload = 0 164 } 165 return nn, err 166 }