github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/compressio/nocompressio.go (about)

     1  // Copyright 2023 The gVisor Authors.
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package compressio
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"crypto/hmac"
    21  	"crypto/sha256"
    22  	"encoding/binary"
    23  	"hash"
    24  	"io"
    25  )
    26  
    27  // nocompressio provides data storage that does not use data compression but
    28  // offers optional data integrity via SHA-256 hashing.
    29  //
    30  // The stream format is defined as follows.
    31  //
    32  // /------------------------------------------------------\
    33  // |                  data size (4-bytes)                 |
    34  // +------------------------------------------------------+
    35  // |                  data                                |
    36  // +------------------------------------------------------+
    37  // |       (optional) hash (32-bytes)                     |
    38  // +------------------------------------------------------+
    39  // |                  data size (4-bytes)                 |
    40  // +------------------------------------------------------+
    41  // |                       ......                         |
    42  // \------------------------------------------------------/
    43  //
    44  // where each hash is calculated from the following items in order
    45  //
    46  //	data
    47  //	data size
    48  
    49  // SimpleReader is a reader from uncompressed image.
    50  type SimpleReader struct {
    51  	// in is the source.
    52  	in io.Reader
    53  
    54  	// key is the key used to create hash objects.
    55  	key []byte
    56  
    57  	// h is the hash object.
    58  	h hash.Hash
    59  
    60  	// current data chunk size
    61  	chunkSize uint32
    62  
    63  	// current chunk position
    64  	done uint32
    65  }
    66  
    67  var _ io.Reader = (*SimpleReader)(nil)
    68  
    69  const (
    70  	defaultBufSize = 256 * 1024
    71  )
    72  
    73  // NewSimpleReader returns a new (uncompressed) reader. If key is non-nil, the data stream
    74  // is assumed to contain expected hash values. See package comments for
    75  // details.
    76  func NewSimpleReader(in io.Reader, key []byte) (*SimpleReader, error) {
    77  	r := &SimpleReader{
    78  		in:  bufio.NewReaderSize(in, defaultBufSize),
    79  		key: key,
    80  	}
    81  
    82  	if key != nil {
    83  		r.h = hmac.New(sha256.New, key)
    84  	}
    85  
    86  	return r, nil
    87  }
    88  
    89  // ReadByte implements wire.Reader.ReadByte.
    90  func (r *SimpleReader) ReadByte() (byte, error) {
    91  	var p [1]byte
    92  	n, err := r.Read(p[:])
    93  	if n != 1 {
    94  		return p[0], err
    95  	}
    96  	// Suppress EOF.
    97  	return p[0], nil
    98  }
    99  
   100  // Read implements io.Reader.Read.
   101  func (r *SimpleReader) Read(p []byte) (int, error) {
   102  	var scratch [4]byte
   103  
   104  	if len(p) == 0 {
   105  		return r.in.Read(p)
   106  	}
   107  
   108  	// need next chunk?
   109  	if r.done >= r.chunkSize {
   110  		if _, err := io.ReadFull(r.in, scratch[:]); err != nil {
   111  			return 0, err
   112  		}
   113  
   114  		r.chunkSize = binary.BigEndian.Uint32(scratch[:])
   115  		r.done = 0
   116  		if r.key != nil {
   117  			r.h.Reset()
   118  		}
   119  
   120  		if r.chunkSize == 0 {
   121  			// this must not happen
   122  			return 0, io.ErrNoProgress
   123  		}
   124  	}
   125  
   126  	toRead := uint32(len(p))
   127  	// can't read more than what's left
   128  	if toRead > r.chunkSize-r.done {
   129  		toRead = r.chunkSize - r.done
   130  	}
   131  
   132  	n, err := r.in.Read(p[:toRead])
   133  	if err != nil {
   134  		if err == io.EOF {
   135  			// this only can happen if storage or data size is corrupted,
   136  			// but we have no other means to detect it earlier as we store
   137  			// hash after the data block.
   138  			return n, ErrHashMismatch
   139  		}
   140  		return n, err
   141  	}
   142  
   143  	if r.key != nil {
   144  		_, _ = r.h.Write(p[:n])
   145  	}
   146  
   147  	r.done += uint32(n)
   148  	if r.done >= r.chunkSize {
   149  		if r.key != nil {
   150  			binary.BigEndian.PutUint32(scratch[:], r.chunkSize)
   151  			r.h.Write(scratch[:4])
   152  
   153  			sum := r.h.Sum(nil)
   154  			readerSum := make([]byte, len(sum))
   155  			if _, err := io.ReadFull(r.in, readerSum); err != nil {
   156  				if err == io.EOF {
   157  					return n, io.ErrUnexpectedEOF
   158  				}
   159  				return n, err
   160  			}
   161  
   162  			if !hmac.Equal(readerSum, sum) {
   163  				return n, ErrHashMismatch
   164  			}
   165  		}
   166  
   167  		r.done = 0
   168  		r.chunkSize = 0
   169  	}
   170  
   171  	return n, nil
   172  }
   173  
   174  // SimpleWriter is a writer that does not compress.
   175  type SimpleWriter struct {
   176  	// base is the underlying writer.
   177  	base io.Writer
   178  
   179  	// out is a buffered writer.
   180  	out *bufio.Writer
   181  
   182  	// key is the key used to create hash objects.
   183  	key []byte
   184  
   185  	// closed indicates whether the file has been closed.
   186  	closed bool
   187  }
   188  
   189  var _ io.Writer = (*SimpleWriter)(nil)
   190  var _ io.Closer = (*SimpleWriter)(nil)
   191  
   192  // NewSimpleWriter returns a new non-compressing writer. If key is non-nil, hash values are
   193  // generated and written out for compressed bytes. See package comments for
   194  // details.
   195  func NewSimpleWriter(out io.Writer, key []byte) (*SimpleWriter, error) {
   196  	return &SimpleWriter{
   197  		base: out,
   198  		out:  bufio.NewWriterSize(out, defaultBufSize),
   199  		key:  key,
   200  	}, nil
   201  }
   202  
   203  // WriteByte implements wire.Writer.WriteByte.
   204  //
   205  // Note that this implementation is necessary on the object itself, as an
   206  // interface-based dispatch cannot tell whether the array backing the slice
   207  // escapes, therefore the all bytes written will generate an escape.
   208  func (w *SimpleWriter) WriteByte(b byte) error {
   209  	var p [1]byte
   210  	p[0] = b
   211  	n, err := w.Write(p[:])
   212  	if n != 1 {
   213  		return err
   214  	}
   215  	return nil
   216  }
   217  
   218  // Write implements io.Writer.Write.
   219  func (w *SimpleWriter) Write(p []byte) (int, error) {
   220  	var scratch [4]byte
   221  
   222  	// Did we close already?
   223  	if w.closed {
   224  		return 0, io.ErrUnexpectedEOF
   225  	}
   226  
   227  	l := uint32(len(p))
   228  
   229  	// chunk length
   230  	binary.BigEndian.PutUint32(scratch[:], l)
   231  	if _, err := w.out.Write(scratch[:4]); err != nil {
   232  		return 0, err
   233  	}
   234  
   235  	// Write out to the stream.
   236  	n, err := w.out.Write(p)
   237  	if err != nil {
   238  		return n, err
   239  	}
   240  
   241  	if w.key != nil {
   242  		h := hmac.New(sha256.New, w.key)
   243  
   244  		// chunk data
   245  		_, _ = h.Write(p)
   246  
   247  		// chunk length
   248  		binary.BigEndian.PutUint32(scratch[:], l)
   249  		h.Write(scratch[:4])
   250  
   251  		sum := h.Sum(nil)
   252  		if _, err := io.CopyN(w.out, bytes.NewReader(sum), int64(len(sum))); err != nil {
   253  			return n, err
   254  		}
   255  	}
   256  
   257  	return n, nil
   258  }
   259  
   260  // Close implements io.Closer.Close.
   261  func (w *SimpleWriter) Close() error {
   262  	// Did we already close? After the call to Close, we always mark as
   263  	// closed, regardless of whether the flush is successful.
   264  	if w.closed {
   265  		return io.ErrUnexpectedEOF
   266  	}
   267  	w.closed = true
   268  
   269  	// Flush buffered writer
   270  	if err := w.out.Flush(); err != nil {
   271  		return err
   272  	}
   273  
   274  	// Close the underlying writer (if necessary).
   275  	if closer, ok := w.base.(io.Closer); ok {
   276  		return closer.Close()
   277  	}
   278  
   279  	w.out = nil
   280  	w.base = nil
   281  
   282  	return nil
   283  }