| // Copyright 2019 The Wuffs Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package rac | |
| import ( | |
| "fmt" | |
| "io" | |
| ) | |
| // zeroesReader is an io.Reader that serves up a finite number of '\x00' bytes. | |
| type zeroesReader int64 | |
| // Read implements io.Reader. | |
| func (z *zeroesReader) Read(p []byte) (int, error) { | |
| if int64(len(p)) > int64(*z) { | |
| p = p[:*z] | |
| } | |
| for i := range p { | |
| p[i] = 0 | |
| } | |
| *z -= zeroesReader(len(p)) | |
| if *z == 0 { | |
| return len(p), io.EOF | |
| } | |
| return len(p), nil | |
| } | |
| // CodecReader specializes a Reader to decode a specific compression codec. | |
| // | |
| // Instances are not required to support concurrent use. | |
| type CodecReader interface { | |
| // Close tells the CodecReader that no further calls will be made. | |
| Close() error | |
| // Accepts returns whether this CodecReader can decode a Codec. | |
| Accepts(c Codec) bool | |
| // Clone duplicates this. The clone and the original can run concurrently. | |
| Clone() CodecReader | |
| // MakeDecompressor returns the Codec-specific io.Reader for a chunk. | |
| // | |
| // The returned io.Reader may optionally implement the io.Closer interface, | |
| // in which case this Reader will call Close when has finished the chunk. | |
| MakeDecompressor(racFile io.ReadSeeker, c Chunk) (io.Reader, error) | |
| } | |
| // Reader reads a RAC file. | |
| // | |
| // Do not modify its exported fields after calling any of its methods. | |
| // | |
| // Reader implements the io.ReadSeeker and io.Closer interfaces. | |
| type Reader struct { | |
| // ReadSeeker is where the RAC-encoded data is read from. | |
| // | |
| // It may optionally implement io.ReaderAt, in which case its ReadAt method | |
| // will be preferred and its Read and Seek methods will never be called. | |
| // The ReadAt method is safe to use concurrently, so that multiple | |
| // rac.Reader's can concurrently use the same source provided that the | |
| // source (this field, nominally an io.ReadSeeker) implements io.ReaderAt. | |
| // | |
| // In particular, if the source is a bytes.Reader or an os.File, multiple | |
| // rac.Reader's can work in parallel, which can speed up decoding. | |
| // | |
| // This type itself only implements io.ReadSeeker, not io.ReaderAt, as it | |
| // is not safe for concurrent use. | |
| // | |
| // Nil is an invalid value. | |
| ReadSeeker io.ReadSeeker | |
| // CompressedSize is the size of the RAC file in CSpace. | |
| // | |
| // Zero is an invalid value. The smallest valid RAC file is 32 bytes long. | |
| CompressedSize int64 | |
| // CodecReaders are the compression codecs that this Reader can decompress. | |
| // | |
| // For example, use a raczlib.CodecReader from the sibilng "raczlib" | |
| // package. | |
| CodecReaders []CodecReader | |
| // Concurrency is how many worker goroutines are used to decode RAC chunks. | |
| // Bigger values often lead to faster throughput, up to a | |
| // hardware-dependent point, but also larger memory requirements. | |
| // | |
| // If positive, then the ReadSeeker must also be an io.ReaderAt. | |
| // | |
| // Non-positive values (including zero) mean a non-concurrent | |
| // (single-goroutine) reader. | |
| Concurrency int | |
| // err is the first error encountered. It is sticky: once a non-nil error | |
| // occurs, all public methods will return that error. | |
| err error | |
| // chunkReader is the low-level RAC chunk reader. | |
| chunkReader ChunkReader | |
| // These two fields combine for a 3-state state machine: | |
| // | |
| // - "State A" (both fields are zero): no RAC chunk is loaded. | |
| // | |
| // - "State B" (decompressor is non-zero, inImplicitZeroes is zero): a RAC | |
| // chunk is loaded, but not fully exhausted: decompressing the e.g. zlib | |
| // stream has not seen io.EOF yet. | |
| // | |
| // - "State C" (decompressor is zero, inImplicitZeroes is non-zero): a RAC | |
| // chunk was exhausted, and we now serve the implicit NUL bytes after a | |
| // chunk's explicitly encoded data. The number of NUL bytes can be (and | |
| // often is) zero. | |
| // | |
| // Calling Read may trigger state transitions (which form a cycle): "State | |
| // A" -> "State B" -> "State C" -> "State A" -> "State B" -> etc. | |
| // | |
| // Calling Seek may reset the state machine to "State A". | |
| // | |
| // The initial state is "State A". | |
| decompressor io.Reader | |
| inImplicitZeroes bool | |
| // closed is whether this Reader is closed. | |
| closed bool | |
| // pos is the current position, in DSpace. It is the base value when Seek | |
| // is called with io.SeekCurrent. | |
| pos int64 | |
| // posLimit is an upper limit on pos. pos can go higher than it (e.g. | |
| // seeking past the end of the file in DSpace), but after doing so, Read | |
| // will always return (0, io.EOF). | |
| posLimit int64 | |
| // dRange is, in "State B" and "State C", what part (in DSpace) of the | |
| // current chunk has not yet been passed on (via this type's Read method). | |
| // | |
| // Within those states, dRange[0] increases over time, as parts of the | |
| // chunk are decompressed and passed on, but dRange[1] does not change. | |
| // | |
| // An invariant is that ((dRange[0] <= pos) && (pos <= dRange[1])). | |
| // | |
| // If the first inequality is strict (i.e. dRange[0] < pos) then we have | |
| // Seek'ed to a pos that is not a chunk boundary, and satisfying the Read | |
| // method will first require decompressing and discarding some of the chunk | |
| // data, until dRange[0] reaches pos. | |
| // | |
| // If the second inequality is strict (i.e. pos < dRange[1]) and we are in | |
| // "State C" then we have a non-zero number of implicit NUL bytes left. | |
| // | |
| // In "State A", the dRange is empty and unused, other than trivially | |
| // maintaining the invariant. | |
| dRange Range | |
| // zeroes serves the Zeroes Codec. | |
| zeroes zeroesReader | |
| // concReader decodes the RAC-compressed data concurrently. | |
| concReader concReader | |
| } | |
| func (r *Reader) initialize() error { | |
| if r.err != nil { | |
| return r.err | |
| } | |
| if r.chunkReader.initialized { | |
| return nil | |
| } | |
| r.chunkReader.ReadSeeker = r.ReadSeeker | |
| r.chunkReader.CompressedSize = r.CompressedSize | |
| if r.Concurrency > 0 { | |
| if _, ok := r.ReadSeeker.(io.ReaderAt); !ok { | |
| r.err = fmt.Errorf("rac: Concurrency > 0 requires the ReadSeeker to be an io.ReaderAt") | |
| return r.err | |
| } | |
| } | |
| if err := r.chunkReader.initialize(); err != nil { | |
| r.err = err | |
| return r.err | |
| } | |
| r.posLimit = r.chunkReader.decompressedSize | |
| r.concReader.initialize(r) | |
| return nil | |
| } | |
| func (r *Reader) clone() *Reader { | |
| c := &Reader{ | |
| ReadSeeker: r.ReadSeeker, | |
| CompressedSize: r.CompressedSize, | |
| CodecReaders: make([]CodecReader, len(r.CodecReaders)), | |
| Concurrency: r.Concurrency, | |
| } | |
| for i := range c.CodecReaders { | |
| c.CodecReaders[i] = r.CodecReaders[i].Clone() | |
| } | |
| return c | |
| } | |
| // Read implements io.Reader. | |
| func (r *Reader) Read(p []byte) (int, error) { | |
| if err := r.initialize(); err != nil { | |
| return 0, err | |
| } | |
| if r.concReader.ready() { | |
| n, err := r.concReader.Read(p) | |
| r.err = err | |
| return n, err | |
| } | |
| if r.pos >= r.posLimit { | |
| return 0, io.EOF | |
| } | |
| if n := r.posLimit - r.pos; int64(len(p)) > n { | |
| p = p[:n] | |
| } | |
| for numRead := 0; ; { | |
| if r.pos >= r.posLimit { | |
| return numRead, io.EOF | |
| } | |
| if len(p) == 0 { | |
| return numRead, nil | |
| } | |
| if (r.pos < r.dRange[0]) || (r.dRange[1] < r.pos) { | |
| r.err = errInternalInconsistentPosition | |
| return numRead, r.err | |
| } | |
| readFunc := (func(*Reader, []byte) (int, error))(nil) | |
| switch { | |
| default: // "State A". | |
| if err := r.nextChunk(); err != nil { | |
| return numRead, err | |
| } | |
| continue | |
| case r.decompressor != nil: // "State B". | |
| readFunc = (*Reader).readExplicitData | |
| case r.inImplicitZeroes: // "State C". | |
| readFunc = (*Reader).readImplicitZeroes | |
| } | |
| n, err := readFunc(r, p) | |
| numRead += n | |
| p = p[n:] | |
| if err != nil { | |
| return numRead, err | |
| } | |
| } | |
| } | |
| // readExplicitData serves the compressed data in a chunk. | |
| func (r *Reader) readExplicitData(p []byte) (int, error) { | |
| // If the chunk started before r.pos, discard the opening bytes of the | |
| // chunk's decompressed data. | |
| for r.pos > r.dRange[0] { | |
| discardBuffer := p | |
| discardBufferLen := r.pos - r.dRange[0] | |
| if int64(len(discardBuffer)) > discardBufferLen { | |
| discardBuffer = discardBuffer[:discardBufferLen] | |
| } | |
| n, err := r.decompressor.Read(discardBuffer) | |
| r.dRange[0] += int64(n) | |
| if err == io.EOF { | |
| return 0, r.transitionFromStateBToStateC() | |
| } | |
| if err != nil { | |
| r.err = err | |
| return 0, r.err | |
| } | |
| } | |
| // Delegate to the decompressor. | |
| n, err := r.decompressor.Read(p) | |
| if size := r.dRange.Size(); int64(n) > size { | |
| n = int(size) | |
| err = errInvalidChunkTooLarge | |
| } | |
| r.pos += int64(n) | |
| r.dRange[0] += int64(n) | |
| if err == io.EOF { | |
| return n, r.transitionFromStateBToStateC() | |
| } else if err == io.ErrUnexpectedEOF { | |
| err = errInvalidChunkTruncated | |
| } | |
| if err != nil { | |
| r.err = err | |
| } | |
| return n, err | |
| } | |
| func (r *Reader) transitionFromStateBToStateC() error { | |
| if c, ok := r.decompressor.(io.Closer); ok { | |
| if err := c.Close(); err != nil { | |
| if err == io.EOF { | |
| err = io.ErrUnexpectedEOF | |
| } | |
| r.err = err | |
| return r.err | |
| } | |
| } | |
| r.decompressor = nil | |
| r.inImplicitZeroes = true | |
| return nil | |
| } | |
| // readImplicitZeroes serves the implicit NUL bytes after a chunk's explicit | |
| // data. As | |
| // https://github.com/google/wuffs/blob/main/doc/spec/rac-spec.md#decompressing-a-leaf-node | |
| // says, "The Codec may produce fewer bytes than the DRange size. In that case, | |
| // the remaining bytes (in DSpace) are set to NUL (memset to zero)." | |
| func (r *Reader) readImplicitZeroes(p []byte) (int, error) { | |
| // If the chunk's explicit data finished before r.pos, discard some of the | |
| // implicit NULs. | |
| if r.dRange[0] < r.pos { | |
| r.dRange[0] = r.pos | |
| } | |
| // The next r.dRange.Size() bytes are all implicitly zero. | |
| n := r.dRange.Size() | |
| if int64(len(p)) > n { | |
| p = p[:n] | |
| } | |
| for i := range p { | |
| p[i] = 0 | |
| } | |
| // Update the cursors, check for exhaustion and return. | |
| r.pos += int64(len(p)) | |
| r.dRange[0] += int64(len(p)) | |
| if r.dRange.Empty() { | |
| // Transition from "State C" to "State A". | |
| r.inImplicitZeroes = false | |
| } | |
| return len(p), nil | |
| } | |
| // nextChunk loads the next independently compressed chunk. It transitions from | |
| // "State A" to "State B". | |
| // | |
| // It may return io.EOF, in which case the Reader stays in "State A", and the | |
| // r.err "sticky error" field stays nil. | |
| func (r *Reader) nextChunk() error { | |
| chunk, err := r.chunkReader.NextChunk() | |
| if err != nil { | |
| if err == io.EOF { | |
| return io.EOF | |
| } | |
| r.err = err | |
| return r.err | |
| } | |
| if chunk.DRange.Empty() { | |
| r.err = errInvalidChunk | |
| return r.err | |
| } | |
| if (chunk.Codec == CodecZeroes) || (chunk.Codec == codecLongZeroes) { | |
| r.dRange = chunk.DRange | |
| r.zeroes = zeroesReader(r.dRange.Size()) | |
| r.decompressor = &r.zeroes | |
| return nil | |
| } | |
| codecReader := CodecReader(nil) | |
| for _, cr := range r.CodecReaders { | |
| if cr.Accepts(chunk.Codec) { | |
| codecReader = cr | |
| break | |
| } | |
| } | |
| if codecReader == nil { | |
| name0, name1, name2 := "", chunk.Codec.name(), "" | |
| if name1 != "" { | |
| name0, name2 = " (", ")" | |
| } | |
| r.err = fmt.Errorf("rac: no matching CodecReader for Codec 0x%X%s%s%s", | |
| chunk.Codec, name0, name1, name2) | |
| return r.err | |
| } | |
| decompressor, err := codecReader.MakeDecompressor(r.chunkReader.readSeeker, chunk) | |
| if err != nil { | |
| if err == io.EOF { | |
| err = io.ErrUnexpectedEOF | |
| } | |
| r.err = err | |
| return r.err | |
| } | |
| r.decompressor = decompressor | |
| r.dRange = chunk.DRange | |
| return nil | |
| } | |
| // Seek implements io.Seeker. | |
| func (r *Reader) Seek(offset int64, whence int) (int64, error) { | |
| if err := r.initialize(); err != nil { | |
| return 0, err | |
| } | |
| const maxInt64 = (1 << 63) - 1 | |
| return r.seek(offset, whence, maxInt64) | |
| } | |
| // SeekRange restricts r to the half-open range [low, high). | |
| // | |
| // It is more efficient than but conceptually equivalent to calling Seek(low, | |
| // io.SeekStart) and wrapping r in an io.LimitedReader whose N is (high - low). | |
| // | |
| // Multiple SeekRange calls apply the most recent high limit, not the minimum | |
| // of the high limits. | |
| // | |
| // Any Seek call, such as Seek(0, io.SeekCurrent), will remove the high limit. | |
| // | |
| // It returns an error if low > high. | |
| func (r *Reader) SeekRange(low int64, high int64) error { | |
| if err := r.initialize(); err != nil { | |
| return err | |
| } | |
| if low > high { | |
| r.err = errSeekToNegativeRange | |
| return r.err | |
| } | |
| _, err := r.seek(low, io.SeekStart, high) | |
| return err | |
| } | |
| func (r *Reader) seek(offset int64, whence int, limit int64) (int64, error) { | |
| if r.concReader.ready() { | |
| n, err := r.concReader.seek(offset, whence, limit) | |
| r.err = err | |
| return n, err | |
| } | |
| pos := r.pos | |
| switch whence { | |
| case io.SeekStart: | |
| pos = offset | |
| case io.SeekCurrent: | |
| pos += offset | |
| case io.SeekEnd: | |
| pos = r.chunkReader.decompressedSize + offset | |
| default: | |
| return 0, errSeekToInvalidWhence | |
| } | |
| if r.pos != pos { | |
| if pos < 0 { | |
| r.err = errSeekToNegativePosition | |
| return 0, r.err | |
| } | |
| if err := r.chunkReader.SeekToChunkContaining(pos); err != nil { | |
| r.err = err | |
| return 0, r.err | |
| } | |
| r.pos = pos | |
| // Maintain the dRange/pos invariant. | |
| r.dRange[0] = pos | |
| r.dRange[1] = pos | |
| // Reset to "State A". | |
| r.decompressor = nil | |
| r.inImplicitZeroes = false | |
| } | |
| if limit > r.chunkReader.decompressedSize { | |
| limit = r.chunkReader.decompressedSize | |
| } | |
| r.posLimit = limit | |
| return r.pos, nil | |
| } | |
| // Close implements io.Closer. | |
| // | |
| // Calling Close will call Close on all of r's CodecReaders. | |
| // | |
| // r.ReadSeeker will not be accessed after Close returns. If r.Concurrency is | |
| // non-zero, this may involve waiting for various goroutines to shut down, | |
| // which may take some time. If the caller does not care about waiting until it | |
| // is safe to close or otherwise release the r.ReadSeeker's resources, call | |
| // CloseWithoutWaiting instead. | |
| // | |
| // It is not safe to call Close from a separate goroutine while another method | |
| // call like Read or Seek is in progress. | |
| func (r *Reader) Close() error { | |
| return r.close(false) | |
| } | |
| // CloseWithoutWaiting is like Close but does not wait until it is safe to | |
| // close or otherwise release the r.ReadSeeker's resources. | |
| func (r *Reader) CloseWithoutWaiting() error { | |
| return r.close(true) | |
| } | |
| func (r *Reader) close(withoutWaiting bool) error { | |
| if r.closed { | |
| return r.err | |
| } | |
| r.closed = true | |
| if err := r.initialize(); r.err == nil { | |
| r.err = err | |
| } | |
| for _, c := range r.CodecReaders { | |
| if err := c.Close(); r.err == nil { | |
| r.err = err | |
| } | |
| } | |
| if withoutWaiting { | |
| if err := r.concReader.CloseWithoutWaiting(); r.err == nil { | |
| r.err = err | |
| } | |
| } else { | |
| if err := r.concReader.Close(); r.err == nil { | |
| r.err = err | |
| } | |
| } | |
| if r.err == nil { | |
| r.err = errAlreadyClosed | |
| return nil | |
| } | |
| return r.err | |
| } |