blob: 9b464e59500e8388aed9e0b01eadbf8095a9726f [file] [log] [blame]
// 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 provides access to RAC (Random Access Compression) files.
//
// RAC is just a container format, and this package ("rac") is a relatively
// low-level. Users will typically want to process a particular compression
// format wrapped in RAC, such as (RAC + Zlib). For that, look at e.g. the
// sibling "raczlib" package.
//
// The RAC specification is at
// https://github.com/google/wuffs/blob/master/doc/spec/rac-spec.md
package rac
import (
"errors"
)
const (
// MaxSize is the maximum RAC file size (in both CSpace and DSpace).
MaxSize = (1 << 48) - 1
// invalidCOffsetCLength is ((1 << 64) - 1).
invalidCOffsetCLength = 0xFFFFFFFFFFFFFFFF
)
// Codec is the compression codec for the RAC file.
//
// For leaf nodes, there are two categories of valid Codecs: Short and Long. A
// Short Codec's uint64 value's high 2 bits and low 56 bits must be zero. A
// Long Codec's uint64 value's high 8 bits must be one and then 7 zeroes.
// Symbolically, Short and Long match:
// - 0b00??????_00000000_00000000_00000000_00000000_00000000_00000000_00000000
// - 0b10000000_????????_????????_????????_????????_????????_????????_????????
//
// In terms of the RAC file format, a Short Codec fits in a single byte: the
// Codec Byte in the middle of a branch node. A Long Codec uses that Codec Byte
// to locate 7 other bytes, a location which would otherwise form a "CPtr|CLen"
// value. When converting from the 7 bytes in the wire format to this Go type
// (a uint64 value), they are read little-endian: the "CPtr" bytes are the low
// 6 bytes, the "CLen" is the second-highest byte and the highest byte is
// hard-coded to 0x80.
//
// For example, the 7 bytes "mdo2\x00\x00\x00" would correspond to a Codec
// value of 0x8000_0000_326F_646D.
//
// The Mix Bit is not part of the uint64 representation. Neither is a Long
// Codec's 'c64' index. This package's exported API deals with leaf nodes.
// Branch nodes' wire formats are internal implementation details.
//
// See the RAC specification for further discussion.
type Codec uint64
func (c Codec) isLong() bool { return int64(c) < 0 }
func (c Codec) isShort() bool { return int64(c) >= 0 }
// Valid returns whether c matches the Short or Long pattern.
func (c Codec) Valid() bool {
if (c >> 63) == 0 {
return ((c << 8) == 0) && ((c >> 62) == 0)
}
return (c >> 56) == 0x80
}
func (c Codec) name() string {
if c.isShort() && c.Valid() {
switch c >> 56 {
case 0:
return "Zeroes"
case 1:
return "Zlib"
case 2:
return "LZ4"
case 3:
return "Zstandard"
}
}
return ""
}
func parentChildCodecsValid(parent Codec, child Codec, parentHasMixBit bool) bool {
return (parent == child) || parentHasMixBit
}
const (
CodecZeroes = Codec(0x00 << 56)
CodecZlib = Codec(0x01 << 56)
CodecLZ4 = Codec(0x02 << 56)
CodecZstandard = Codec(0x03 << 56)
codecMixBit = Codec(1 << 62)
codecLongZeroes = Codec(1 << 63)
CodecInvalid = Codec((1 << 64) - 1)
)
// IndexLocation is whether the index is at the start or end of the RAC file.
//
// See the RAC specification for further discussion.
type IndexLocation uint8
const magic = "\x72\xC3\x63"
const (
IndexLocationAtEnd = IndexLocation(0)
IndexLocationAtStart = IndexLocation(1)
)
var indexLocationAtEndMagic = []byte("\x72\xC3\x63\x00")
var (
ErrCodecWriterDoesNotSupportCChunkSize = errors.New("rac: CodecWriter does not support CChunkSize")
errAlreadyClosed = errors.New("rac: already closed")
errCChunkSizeIsTooSmall = errors.New("rac: CChunkSize is too small")
errILAEndTempFile = errors.New("rac: IndexLocationAtEnd requires a nil TempFile")
errILAStartTempFile = errors.New("rac: IndexLocationAtStart requires a non-nil TempFile")
errInconsistentCompressedSize = errors.New("rac: inconsistent compressed size")
errInvalidCPageSize = errors.New("rac: invalid CPageSize")
errInvalidChunk = errors.New("rac: invalid chunk")
errInvalidChunkTooLarge = errors.New("rac: invalid chunk (too large)")
errInvalidChunkTruncated = errors.New("rac: invalid chunk (truncated)")
errInvalidCodec = errors.New("rac: invalid Codec")
errInvalidCodecWriter = errors.New("rac: invalid CodecWriter")
errInvalidCompressedSize = errors.New("rac: invalid CompressedSize")
errInvalidIndexNode = errors.New("rac: invalid index node")
errInvalidInputMissingMagicBytes = errors.New("rac: invalid input: missing magic bytes")
errInvalidInputMissingRootNode = errors.New("rac: invalid input: missing root node")
errInvalidReadSeeker = errors.New("rac: invalid ReadSeeker")
errInvalidWriter = errors.New("rac: invalid Writer")
errSeekToInvalidWhence = errors.New("rac: seek to invalid whence")
errSeekToNegativePosition = errors.New("rac: seek to negative position")
errSeekToNegativeRange = errors.New("rac: seek to negative range")
errTooManyChunks = errors.New("rac: too many chunks")
errTooManyResources = errors.New("rac: too many resources")
errTooMuchInput = errors.New("rac: too much input")
errUnsupportedRACFileVersion = errors.New("rac: unsupported RAC file version")
errInternalArityIsTooLarge = errors.New("rac: internal error: arity is too large")
errInternalEmptyDRange = errors.New("rac: internal error: empty DRange")
errInternalInconsistentArity = errors.New("rac: internal error: inconsistent arity")
errInternalInconsistentPosition = errors.New("rac: internal error: inconsistent position")
errInternalShortCSize = errors.New("rac: internal error: short CSize")
)