Add "RAC + Zeroes" codec
diff --git a/doc/spec/rac-spec.md b/doc/spec/rac-spec.md
index 828f9c3..4f4d47b 100644
--- a/doc/spec/rac-spec.md
+++ b/doc/spec/rac-spec.md
@@ -346,7 +346,7 @@
not tied to any particular compression codec. For the `Codec` byte in a `Branch
Node`:
- - `0x00` is reserved.
+ - `0x00` means "RAC + Zeroes".
- `0x01` means "RAC + Zlib".
- `0x02` means "RAC + Brotli".
- `0x04` means "RAC + ZStandard".
@@ -515,6 +515,12 @@
# Specific Codecs
+## RAC + Zeroes
+
+The `CRange`s are ignored. The `DRange` is filled with NUL bytes (`memset` to
+zero).
+
+
## RAC + Zlib
The `CFile` data in the `Leaf Node`'s `Primary CRange` is decompressed as Zlib
@@ -567,8 +573,8 @@
up to 3.75 TiB, which is more than the maximum `uint32_t` value. The
`Dictionary Length` field here is therefore 8 bytes, not 4. However, the RFC
acknowledges that "a decoder is allowed to reject a compressed frame that
-requests a memory size beyond decoder's authorized range", and likewise, a RAC
-+ Zstandard decoder is allowed to reject a `Dictionary Length` that it
+requests a memory size beyond decoder's authorized range", and likewise, a
+RAC + Zstandard decoder is allowed to reject a `Dictionary Length` that it
considers too large. The RFC goes on to say "it's recommended for decoders to
support values of Window Size up to 8 MiB and for encoders not to generate
frames requiring a Window Size larger than 8 MiB", and that recommendation
diff --git a/lib/rac/chunk_writer.go b/lib/rac/chunk_writer.go
index 8e43c50..e852a5d 100644
--- a/lib/rac/chunk_writer.go
+++ b/lib/rac/chunk_writer.go
@@ -357,10 +357,6 @@
w.err = errTooManyChunks
return w.err
}
- if codec == 0 {
- w.err = errInvalidCodec
- return w.err
- }
if err := w.initialize(); err != nil {
return err
@@ -383,8 +379,8 @@
}
var emptyRACFile = [32]byte{
- 0x72, 0xC3, 0x63, 0x01, 0xE8, 0xB4, 0x00, 0xFF,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x72, 0xC3, 0x63, 0x01, 0x0D, 0xF8, 0x00, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
}
diff --git a/lib/rac/parser.go b/lib/rac/parser.go
index 8625d63..2994274 100644
--- a/lib/rac/parser.go
+++ b/lib/rac/parser.go
@@ -194,11 +194,6 @@
return false
}
- // Check that the Codec is non-zero.
- if b[(8*arity)+7] == 0 {
- return false
- }
-
// Check that the DPtr values are non-decreasing. The first DPtr value is
// implicitly zero.
prev := u48LE(b[8*1:])
diff --git a/lib/rac/rac.go b/lib/rac/rac.go
index cb7ed47..0bdbc80 100644
--- a/lib/rac/rac.go
+++ b/lib/rac/rac.go
@@ -43,6 +43,7 @@
type Codec uint8
const (
+ CodecZeroes = Codec(0x00)
CodecZlib = Codec(0x01)
CodecBrotli = Codec(0x02)
CodecZstandard = Codec(0x04)
diff --git a/lib/rac/rac_test.go b/lib/rac/rac_test.go
index 265f781..a68d681 100644
--- a/lib/rac/rac_test.go
+++ b/lib/rac/rac_test.go
@@ -60,7 +60,7 @@
}
const writerWantEmpty = "" +
- "00000000 72 c3 63 01 e8 b4 00 ff 00 00 00 00 00 00 00 01 |r.c.............|\n" +
+ "00000000 72 c3 63 01 0d f8 00 ff 00 00 00 00 00 00 00 00 |r.c.............|\n" +
"00000010 20 00 00 00 00 00 01 ff 20 00 00 00 00 00 01 01 | ....... .......|\n"
const writerWantILAEnd = "" +
@@ -548,3 +548,39 @@
}
}
}
+
+func TestReaderEmpty(t *testing.T) {
+ got, err := ioutil.ReadAll(&Reader{
+ ReadSeeker: bytes.NewReader(undoHexDump(writerWantEmpty)),
+ })
+ if err != nil {
+ t.Fatalf("ReadAll: %v", err)
+ }
+ if len(got) != 0 {
+ t.Fatalf("got %q, want %q", got, []byte(nil))
+ }
+}
+
+func TestReaderZeroes(t *testing.T) {
+ const dSize = 7
+ buf := &bytes.Buffer{}
+ w := &ChunkWriter{
+ Writer: buf,
+ }
+ if err := w.AddChunk(dSize, CodecZeroes, nil, 0, 0); err != nil {
+ t.Fatalf("AddChunk: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+ got, err := ioutil.ReadAll(&Reader{
+ ReadSeeker: bytes.NewReader(buf.Bytes()),
+ })
+ if err != nil {
+ t.Fatalf("ReadAll: %v", err)
+ }
+ want := make([]byte, dSize)
+ if !bytes.Equal(got, want) {
+ t.Fatalf("got %q, want %q", got, want)
+ }
+}
diff --git a/lib/rac/reader.go b/lib/rac/reader.go
index 2f9672a..c6c512f 100644
--- a/lib/rac/reader.go
+++ b/lib/rac/reader.go
@@ -19,6 +19,24 @@
"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
+}
+
// ReaderContext contains the decoded Codec-specific metadata (non-primary
// data) associated with a RAC chunk.
type ReaderContext struct {
@@ -125,6 +143,9 @@
// In "State A", the dRange is empty and unused, other than trivially
// maintaining the invariant.
dRange Range
+
+ // zeroes serves the Zeroes Codec.
+ zeroes zeroesReader
}
func (r *Reader) initialize() error {
@@ -299,6 +320,14 @@
return r.err
}
+ if chunk.Codec == 0 {
+ r.currChunk.N = 0
+ 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) {