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  }