Просмотр исходного кода

protoapi: add CompressGZIP helper function

The current generated API relies on GZIP since the Descriptor and
EnumDescriptor methods return the file descriptor as the GZIP-compressed
form of the raw encoded bytes.

It turns out that it was a mistake to compress the descriptor since they
hardly contribute any significant size to the total binary bloat and
instead forces us to have a dependency on gzip in the public API.

The CompressGZIP function added is hand-tailored to be as simple and fast
as possible to reduce runtime cost as well.

Change-Id: I32a62f8ba39a0f2dd12fb31800f270b672facf5f
Reviewed-on: https://go-review.googlesource.com/c/164637
Reviewed-by: Damien Neil <dneil@google.com>
Joe Tsai 7 лет назад
Родитель
Сommit
22c36ed954
2 измененных файлов с 79 добавлено и 0 удалено
  1. 38 0
      protoapi/impl.go
  2. 41 0
      protoapi/impl_test.go

+ 38 - 0
protoapi/impl.go

@@ -5,8 +5,11 @@
 package protoapi
 
 import (
+	"encoding/binary"
 	"encoding/json"
 	"fmt"
+	"hash/crc32"
+	"math"
 	"strconv"
 )
 
@@ -49,5 +52,40 @@ func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32,
 	return val, nil
 }
 
+// CompressGZIP compresses the input as a GZIP-encoded file.
+// The current implementation does no compression.
+func CompressGZIP(in []byte) (out []byte) {
+	// RFC 1952, section 2.3.1.
+	var gzipHeader = [10]byte{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}
+
+	// RFC 1951, section 3.2.4.
+	var blockHeader [5]byte
+	const maxBlockSize = math.MaxUint16
+	numBlocks := 1 + len(in)/maxBlockSize
+
+	// RFC 1952, section 2.3.1.
+	var gzipFooter [8]byte
+	binary.LittleEndian.PutUint32(gzipFooter[0:4], crc32.ChecksumIEEE(in))
+	binary.LittleEndian.PutUint32(gzipFooter[4:8], uint32(len(in)))
+
+	// Encode the input without compression using raw DEFLATE blocks.
+	out = make([]byte, 0, len(gzipHeader)+len(blockHeader)*numBlocks+len(in)+len(gzipFooter))
+	out = append(out, gzipHeader[:]...)
+	for blockHeader[0] == 0 {
+		blockSize := maxBlockSize
+		if blockSize > len(in) {
+			blockHeader[0] = 0x01 // final bit per RFC 1951, section 3.2.3.
+			blockSize = len(in)
+		}
+		binary.LittleEndian.PutUint16(blockHeader[1:3], uint16(blockSize)^0x0000)
+		binary.LittleEndian.PutUint16(blockHeader[3:5], uint16(blockSize)^0xffff)
+		out = append(out, blockHeader[:]...)
+		out = append(out, in[:blockSize]...)
+		in = in[blockSize:]
+	}
+	out = append(out, gzipFooter[:]...)
+	return out
+}
+
 // TODO: Remove this when v2 textpb is available.
 var CompactTextString func(Message) string

+ 41 - 0
protoapi/impl_test.go

@@ -0,0 +1,41 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package protoapi
+
+import (
+	"bytes"
+	"compress/gzip"
+	"io/ioutil"
+	"math"
+	"strings"
+	"testing"
+)
+
+func TestCompressGZIP(t *testing.T) {
+	tests := []string{
+		"",
+		"a",
+		"ab",
+		"abc",
+		strings.Repeat("a", math.MaxUint16-1),
+		strings.Repeat("b", math.MaxUint16),
+		strings.Repeat("c", math.MaxUint16+1),
+		strings.Repeat("abcdefghijklmnopqrstuvwxyz", math.MaxUint16-13),
+	}
+	for _, want := range tests {
+		rb := bytes.NewReader(CompressGZIP([]byte(want)))
+		zr, err := gzip.NewReader(rb)
+		if err != nil {
+			t.Errorf("unexpected gzip.NewReader error: %v", err)
+		}
+		b, err := ioutil.ReadAll(zr)
+		if err != nil {
+			t.Errorf("unexpected ioutil.ReadAll error: %v", err)
+		}
+		if got := string(b); got != want {
+			t.Errorf("output mismatch: got %q, want %q", got, want)
+		}
+	}
+}