github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/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 whats 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 }