Skip to content

Commit 9d7fe70

Browse files
authored
s2: Add Index header trim/restore (#638)
* s2: Add Index header trim/restore Add `RemoveIndexHeaders` that will remove 20 header+trailer bytes for cases when storage can be relied upon. `RestoreIndexHeaders` will restore the index header+trailer so it can be loaded.
1 parent 4b3cc06 commit 9d7fe70

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

s2/index.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,66 @@ func (i *Index) JSON() []byte {
533533
b, _ := json.MarshalIndent(x, "", " ")
534534
return b
535535
}
536+
537+
// RemoveIndexHeaders will trim all headers and trailers from a given index.
538+
// This is expected to save 20 bytes.
539+
// These can be restored using RestoreIndexHeaders.
540+
// This removes a layer of security, but is the most compact representation.
541+
// Returns nil if headers contains errors.
542+
// The returned slice references the provided slice.
543+
func RemoveIndexHeaders(b []byte) []byte {
544+
const save = 4 + len(S2IndexHeader) + len(S2IndexTrailer) + 4
545+
if len(b) <= save {
546+
return nil
547+
}
548+
if b[0] != ChunkTypeIndex {
549+
return nil
550+
}
551+
chunkLen := int(b[1]) | int(b[2])<<8 | int(b[3])<<16
552+
b = b[4:]
553+
554+
// Validate we have enough...
555+
if len(b) < chunkLen {
556+
return nil
557+
}
558+
b = b[:chunkLen]
559+
560+
if !bytes.Equal(b[:len(S2IndexHeader)], []byte(S2IndexHeader)) {
561+
return nil
562+
}
563+
b = b[len(S2IndexHeader):]
564+
if !bytes.HasSuffix(b, []byte(S2IndexTrailer)) {
565+
return nil
566+
}
567+
b = bytes.TrimSuffix(b, []byte(S2IndexTrailer))
568+
569+
if len(b) < 4 {
570+
return nil
571+
}
572+
return b[:len(b)-4]
573+
}
574+
575+
// RestoreIndexHeaders will index restore headers removed by RemoveIndexHeaders.
576+
// No error checking is performed on the input.
577+
// If a 0 length slice is sent, it is returned without modification.
578+
func RestoreIndexHeaders(in []byte) []byte {
579+
if len(in) == 0 {
580+
return in
581+
}
582+
b := make([]byte, 0, 4+len(S2IndexHeader)+len(in)+len(S2IndexTrailer)+4)
583+
b = append(b, ChunkTypeIndex, 0, 0, 0)
584+
b = append(b, []byte(S2IndexHeader)...)
585+
b = append(b, in...)
586+
587+
var tmp [4]byte
588+
binary.LittleEndian.PutUint32(tmp[:], uint32(len(b)+4+len(S2IndexTrailer)))
589+
b = append(b, tmp[:4]...)
590+
// Trailer
591+
b = append(b, []byte(S2IndexTrailer)...)
592+
593+
chunkLen := len(b) - skippableFrameHeader
594+
b[1] = uint8(chunkLen >> 0)
595+
b[2] = uint8(chunkLen >> 8)
596+
b[3] = uint8(chunkLen >> 16)
597+
return b
598+
}

s2/index_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package s2_test
22

33
import (
44
"bytes"
5+
"encoding/hex"
56
"fmt"
67
"io"
78
"io/ioutil"
@@ -123,6 +124,17 @@ func TestSeeking(t *testing.T) {
123124
t.Fatal(err)
124125
}
125126

127+
// Test trimming
128+
slim := s2.RemoveIndexHeaders(index)
129+
if slim == nil {
130+
t.Error("Removing headers failed")
131+
}
132+
restored := s2.RestoreIndexHeaders(slim)
133+
if !bytes.Equal(restored, index) {
134+
t.Errorf("want %s, got %s", hex.EncodeToString(index), hex.EncodeToString(restored))
135+
}
136+
t.Logf("Saved %d bytes", len(index)-len(slim))
137+
126138
for _, skip := range testSizes {
127139
t.Run(fmt.Sprintf("noSeekSkip=%d", skip), func(t *testing.T) {
128140
dec := s2.NewReader(io.NopCloser(bytes.NewReader(compressed.Bytes())))

0 commit comments

Comments
 (0)