diff --git a/Makefile b/Makefile
index 09faf4765..1bc158842 100644
--- a/Makefile
+++ b/Makefile
@@ -153,7 +153,10 @@ endif
ci-tests: lint vet test
-ci-integration-tests: integration-tests robustness-tool-tests
+ci-integration-tests:
+ $(MAKE) integration-tests
+ $(MAKE) integration-tests-index-v2
+ $(MAKE) robustness-tool-tests
$(MAKE) stress-test
ci-publish-coverage:
@@ -220,6 +223,9 @@ integration-tests: build-integration-test-binary $(gotestsum) $(TESTING_ACTION_E
$(GO_TEST) $(TEST_FLAGS) -count=$(REPEAT_TEST) -parallel $(PARALLEL) -timeout 3600s github.com/kopia/kopia/tests/end_to_end_test
-$(gotestsum) tool slowest --jsonfile .tmp.integration-tests.json --threshold 1000ms
+integration-tests-index-v2:
+ KOPIA_CREATE_INDEX_VERSION=2 KOPIA_RUN_ALL_INTEGRATION_TESTS=true $(MAKE) integration-tests
+
endurance-tests: export KOPIA_EXE ?= $(KOPIA_INTEGRATION_EXE)
endurance-tests: export KOPIA_LOGS_DIR=$(CURDIR)/.logs
endurance-tests: build-integration-test-binary $(gotestsum)
diff --git a/cli/command_content_list.go b/cli/command_content_list.go
index 90077d830..3ddb75489 100644
--- a/cli/command_content_list.go
+++ b/cli/command_content_list.go
@@ -2,11 +2,13 @@
import (
"context"
+ "fmt"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/stats"
"github.com/kopia/kopia/repo"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
)
@@ -16,6 +18,7 @@ type commandContentList struct {
deletedOnly bool
summary bool
human bool
+ compression bool
contentRange contentRangeFlags
jo jsonOutput
@@ -25,6 +28,7 @@ type commandContentList struct {
func (c *commandContentList) setup(svc appServices, parent commandParent) {
cmd := parent.Command("list", "List contents").Alias("ls")
cmd.Flag("long", "Long output").Short('l').BoolVar(&c.long)
+ cmd.Flag("compression", "Compression").Short('c').BoolVar(&c.compression)
cmd.Flag("deleted", "Include deleted content").BoolVar(&c.includeDeleted)
cmd.Flag("deleted-only", "Only show deleted content").BoolVar(&c.deletedOnly)
cmd.Flag("summary", "Summarize the list").Short('s').BoolVar(&c.summary)
@@ -56,24 +60,14 @@ func(b content.Info) error {
totalSize.Add(int64(b.GetPackedLength()))
- if c.jo.jsonOutput {
+ switch {
+ case c.jo.jsonOutput:
jl.emit(b)
- return nil
- }
-
- if c.long {
- optionalDeleted := ""
- if b.GetDeleted() {
- optionalDeleted = " (deleted)"
- }
- c.out.printStdout("%v %v %v %v+%v%v\n",
- b.GetContentID(),
- formatTimestamp(b.Timestamp()),
- b.GetPackBlobID(),
- b.GetPackOffset(),
- maybeHumanReadableBytes(c.human, int64(b.GetPackedLength())),
- optionalDeleted)
- } else {
+ case c.compression:
+ c.outputCompressed(b)
+ case c.long:
+ c.outputLong(b)
+ default:
c.out.printStdout("%v\n", b.GetContentID())
}
@@ -92,3 +86,52 @@ func(b content.Info) error {
return nil
}
+
+func (c *commandContentList) outputLong(b content.Info) {
+ c.out.printStdout("%v %v %v %v %v+%v%v %v\n",
+ b.GetContentID(),
+ b.GetOriginalLength(),
+ formatTimestamp(b.Timestamp()),
+ b.GetPackBlobID(),
+ b.GetPackOffset(),
+ maybeHumanReadableBytes(c.human, int64(b.GetPackedLength())),
+ c.deletedInfoString(b),
+ c.compressionInfoStringString(b),
+ )
+}
+
+func (c *commandContentList) outputCompressed(b content.Info) {
+ c.out.printStdout("%v length %v packed %v %v %v\n",
+ b.GetContentID(),
+ maybeHumanReadableBytes(c.human, int64(b.GetOriginalLength())),
+ maybeHumanReadableBytes(c.human, int64(b.GetPackedLength())),
+ c.compressionInfoStringString(b),
+ c.deletedInfoString(b),
+ )
+}
+
+func (*commandContentList) deletedInfoString(b content.Info) string {
+ if b.GetDeleted() {
+ return " (deleted)"
+ }
+
+ return ""
+}
+
+func (*commandContentList) compressionInfoStringString(b content.Info) string {
+ h := b.GetCompressionHeaderID()
+ if h == content.NoCompression {
+ return "-"
+ }
+
+ s := string(compression.HeaderIDToName[h])
+ if s == "" {
+ s = fmt.Sprintf("compression-%x", h)
+ }
+
+ if b.GetOriginalLength() > 0 {
+ s += " " + formatCompressionPercentage(int64(b.GetOriginalLength()), int64(b.GetPackedLength()))
+ }
+
+ return s
+}
diff --git a/cli/command_content_stats.go b/cli/command_content_stats.go
index 1b64cf0d3..28cda4aca 100644
--- a/cli/command_content_stats.go
+++ b/cli/command_content_stats.go
@@ -8,6 +8,7 @@
"github.com/kopia/kopia/internal/units"
"github.com/kopia/kopia/repo"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
)
@@ -25,39 +26,24 @@ func (c *commandContentStats) setup(svc appServices, parent commandParent) {
cmd.Action(svc.directRepositoryReadAction(c.run))
}
+type contentStatsTotals struct {
+ originalSize, packedSize, count int64
+}
+
func (c *commandContentStats) run(ctx context.Context, rep repo.DirectRepository) error {
- var sizeThreshold uint32 = 10
-
- countMap := map[uint32]int{}
- totalSizeOfContentsUnder := map[uint32]int64{}
-
- var sizeThresholds []uint32
+ var (
+ sizeThreshold uint32 = 10
+ sizeBuckets []uint32
+ )
for i := 0; i < 8; i++ {
- sizeThresholds = append(sizeThresholds, sizeThreshold)
- countMap[sizeThreshold] = 0
+ sizeBuckets = append(sizeBuckets, sizeThreshold)
sizeThreshold *= 10
}
- var totalSize, count int64
-
- if err := rep.ContentReader().IterateContents(
- ctx,
- content.IterateOptions{
- Range: c.contentRange.contentIDRange(),
- },
- func(b content.Info) error {
- totalSize += int64(b.GetPackedLength())
- count++
- for s := range countMap {
- if b.GetPackedLength() < s {
- countMap[s]++
- totalSizeOfContentsUnder[s] += int64(b.GetPackedLength())
- }
- }
- return nil
- }); err != nil {
- return errors.Wrap(err, "error iterating contents")
+ grandTotal, byCompressionTotal, countMap, totalSizeOfContentsUnder, err := c.calculateStats(ctx, rep, sizeBuckets)
+ if err != nil {
+ return errors.Wrap(err, "error calculating totals")
}
sizeToString := units.BytesStringBase10
@@ -65,20 +51,47 @@ func(b content.Info) error {
sizeToString = func(l int64) string { return strconv.FormatInt(l, 10) }
}
- c.out.printStdout("Count: %v\n", count)
- c.out.printStdout("Total: %v\n", sizeToString(totalSize))
+ c.out.printStdout("Count: %v\n", grandTotal.count)
+ c.out.printStdout("Total Bytes: %v\n", sizeToString(grandTotal.originalSize))
- if count == 0 {
+ if grandTotal.packedSize < grandTotal.originalSize {
+ c.out.printStdout(
+ "Total Packed: %v (compression %v)\n",
+ sizeToString(grandTotal.packedSize),
+ formatCompressionPercentage(grandTotal.originalSize, grandTotal.packedSize))
+ }
+
+ if len(byCompressionTotal) > 1 {
+ c.out.printStdout("By Method:\n")
+
+ if bct := byCompressionTotal[content.NoCompression]; bct != nil {
+ c.out.printStdout(" %-22v count: %v size: %v\n", "(uncompressed)", bct.count, sizeToString(bct.originalSize))
+ }
+
+ for hdrID, bct := range byCompressionTotal {
+ cname := compression.HeaderIDToName[hdrID]
+ if cname == "" {
+ continue
+ }
+
+ c.out.printStdout(" %-22v count: %v size: %v packed: %v compression: %v\n",
+ cname, bct.count,
+ sizeToString(bct.originalSize),
+ sizeToString(bct.packedSize),
+ formatCompressionPercentage(bct.originalSize, bct.packedSize))
+ }
+ }
+
+ if grandTotal.count == 0 {
return nil
}
- c.out.printStdout("Average: %v\n", sizeToString(totalSize/count))
-
+ c.out.printStdout("Average: %v\n", sizeToString(grandTotal.originalSize/grandTotal.count))
c.out.printStdout("Histogram:\n\n")
var lastSize uint32
- for _, size := range sizeThresholds {
+ for _, size := range sizeBuckets {
c.out.printStdout("%9v between %v and %v (total %v)\n",
countMap[size]-countMap[lastSize],
sizeToString(int64(lastSize)),
@@ -91,3 +104,51 @@ func(b content.Info) error {
return nil
}
+
+func (c *commandContentStats) calculateStats(ctx context.Context, rep repo.DirectRepository, sizeBuckets []uint32) (
+ grandTotal contentStatsTotals,
+ byCompressionTotal map[compression.HeaderID]*contentStatsTotals,
+ countMap map[uint32]int,
+ totalSizeOfContentsUnder map[uint32]int64,
+ err error,
+) {
+ byCompressionTotal = make(map[compression.HeaderID]*contentStatsTotals)
+ totalSizeOfContentsUnder = make(map[uint32]int64)
+ countMap = make(map[uint32]int)
+
+ for _, s := range sizeBuckets {
+ countMap[s] = 0
+ }
+
+ err = rep.ContentReader().IterateContents(
+ ctx,
+ content.IterateOptions{
+ Range: c.contentRange.contentIDRange(),
+ },
+ func(b content.Info) error {
+ grandTotal.packedSize += int64(b.GetPackedLength())
+ grandTotal.originalSize += int64(b.GetOriginalLength())
+ grandTotal.count++
+
+ bct := byCompressionTotal[b.GetCompressionHeaderID()]
+ if bct == nil {
+ bct = &contentStatsTotals{}
+ byCompressionTotal[b.GetCompressionHeaderID()] = bct
+ }
+
+ bct.packedSize += int64(b.GetPackedLength())
+ bct.originalSize += int64(b.GetOriginalLength())
+ bct.count++
+
+ for s := range countMap {
+ if b.GetPackedLength() < s {
+ countMap[s]++
+ totalSizeOfContentsUnder[s] += int64(b.GetPackedLength())
+ }
+ }
+ return nil
+ })
+
+ // nolint:wrapcheck
+ return grandTotal, byCompressionTotal, countMap, totalSizeOfContentsUnder, err
+}
diff --git a/cli/command_repository_create.go b/cli/command_repository_create.go
index 6853a1cac..9034f909a 100644
--- a/cli/command_repository_create.go
+++ b/cli/command_repository_create.go
@@ -21,6 +21,7 @@ type commandRepositoryCreate struct {
createBlockEncryptionFormat string
createSplitter string
createOnly bool
+ createIndexVersion int
co connectOptions
svc advancedAppServices
@@ -34,6 +35,7 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP
cmd.Flag("encryption", "Content encryption algorithm.").PlaceHolder("ALGO").Default(encryption.DefaultAlgorithm).EnumVar(&c.createBlockEncryptionFormat, encryption.SupportedAlgorithms(false)...)
cmd.Flag("object-splitter", "The splitter to use for new objects in the repository").Default(splitter.DefaultAlgorithm).EnumVar(&c.createSplitter, splitter.SupportedAlgorithms()...)
cmd.Flag("create-only", "Create repository, but don't connect to it.").Short('c').BoolVar(&c.createOnly)
+ cmd.Flag("index-version", "Force particular index version").Hidden().Envar("KOPIA_CREATE_INDEX_VERSION").IntVar(&c.createIndexVersion)
c.co.setup(cmd)
c.svc = svc
@@ -63,8 +65,9 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP
func (c *commandRepositoryCreate) newRepositoryOptionsFromFlags() *repo.NewRepositoryOptions {
return &repo.NewRepositoryOptions{
BlockFormat: content.FormattingOptions{
- Hash: c.createBlockHashFormat,
- Encryption: c.createBlockEncryptionFormat,
+ Hash: c.createBlockHashFormat,
+ Encryption: c.createBlockEncryptionFormat,
+ IndexVersion: c.createIndexVersion,
},
ObjectFormat: object.Format{
diff --git a/cli/command_repository_status.go b/cli/command_repository_status.go
index 35229f8c4..670dd4aea 100644
--- a/cli/command_repository_status.go
+++ b/cli/command_repository_status.go
@@ -60,6 +60,7 @@ func (c *commandRepositoryStatus) run(ctx context.Context, rep repo.Repository)
c.out.printStdout("Encryption: %v\n", dr.ContentReader().ContentFormat().Encryption)
c.out.printStdout("Splitter: %v\n", dr.ObjectFormat().Splitter)
c.out.printStdout("Format version: %v\n", dr.ContentReader().ContentFormat().Version)
+ c.out.printStdout("Content compression: %v\n", dr.ContentReader().SupportsContentCompression())
c.out.printStdout("Max pack length: %v\n", units.BytesStringBase2(int64(dr.ContentReader().ContentFormat().MaxPackSize)))
if !c.statusReconnectToken {
diff --git a/cli/show_utils.go b/cli/show_utils.go
index 86c133b78..8d2219a39 100644
--- a/cli/show_utils.go
+++ b/cli/show_utils.go
@@ -15,6 +15,8 @@
"github.com/kopia/kopia/internal/units"
)
+const oneHundredPercent = 100.0
+
// TODO - remove this global.
var timeZone = "local"
@@ -90,3 +92,15 @@ func convertTimezone(ts time.Time) time.Time {
return ts
}
}
+
+func formatCompressionPercentage(original, compressed int64) string {
+ if compressed >= original {
+ return "0%"
+ }
+
+ if original == 0 {
+ return "0%"
+ }
+
+ return fmt.Sprintf("%.1f%%", oneHundredPercent*(1-float64(compressed)/float64(original)))
+}
diff --git a/htmlui/src/RepoStatus.js b/htmlui/src/RepoStatus.js
index 73c30a740..b8ab901d7 100644
--- a/htmlui/src/RepoStatus.js
+++ b/htmlui/src/RepoStatus.js
@@ -159,6 +159,10 @@ export class RepoStatus extends Component {
Splitter Algorithm
+
+ Supports Content Compression
+
+
>}
diff --git a/htmlui/src/SetupRepository.js b/htmlui/src/SetupRepository.js
index 882649b68..4d817263f 100644
--- a/htmlui/src/SetupRepository.js
+++ b/htmlui/src/SetupRepository.js
@@ -61,6 +61,7 @@ export class SetupRepository extends Component {
hash: result.data.defaultHash,
encryption: result.data.defaultEncryption,
splitter: result.data.defaultSplitter,
+ indexVersion: "",
});
});
axios.get('/api/v1/current-user').then(result => {
@@ -107,7 +108,7 @@ export class SetupRepository extends Component {
return;
}
- const request = {
+ let request = {
storage: {
type: this.state.provider,
config: this.state.providerSettings,
@@ -124,6 +125,10 @@ export class SetupRepository extends Component {
},
};
+ if (this.state.indexVersion) {
+ request.options.blockFormat.indexVersion = parseInt(this.state.indexVersion)
+ }
+
request.clientOptions = this.clientOptions();
axios.post('/api/v1/repo/create', request).then(result => {
@@ -360,6 +365,18 @@ export class SetupRepository extends Component {
{this.state.algorithms.splitter.map(x => )}
+
+ Index Format
+
+
+
+
+
+
{this.overrideUsernameHostnameRow()}
diff --git a/internal/grpcapi/repository_server.pb.go b/internal/grpcapi/repository_server.pb.go
index 02d3328cb..74b0eafd7 100644
--- a/internal/grpcapi/repository_server.pb.go
+++ b/internal/grpcapi/repository_server.pb.go
@@ -319,9 +319,10 @@ type RepositoryParameters struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- HashFunction string `protobuf:"bytes,1,opt,name=hash_function,json=hashFunction,proto3" json:"hash_function,omitempty"`
- HmacSecret []byte `protobuf:"bytes,2,opt,name=hmac_secret,json=hmacSecret,proto3" json:"hmac_secret,omitempty"`
- Splitter string `protobuf:"bytes,3,opt,name=splitter,proto3" json:"splitter,omitempty"`
+ HashFunction string `protobuf:"bytes,1,opt,name=hash_function,json=hashFunction,proto3" json:"hash_function,omitempty"`
+ HmacSecret []byte `protobuf:"bytes,2,opt,name=hmac_secret,json=hmacSecret,proto3" json:"hmac_secret,omitempty"`
+ Splitter string `protobuf:"bytes,3,opt,name=splitter,proto3" json:"splitter,omitempty"`
+ SupportsContentCompression bool `protobuf:"varint,4,opt,name=supports_content_compression,json=supportsContentCompression,proto3" json:"supports_content_compression,omitempty"`
}
func (x *RepositoryParameters) Reset() {
@@ -377,6 +378,13 @@ func (x *RepositoryParameters) GetSplitter() string {
return ""
}
+func (x *RepositoryParameters) GetSupportsContentCompression() bool {
+ if x != nil {
+ return x.SupportsContentCompression
+ }
+ return false
+}
+
// InitializeSessionRequest must be sent by the client as the first request in a session.
type InitializeSessionRequest struct {
state protoimpl.MessageState
@@ -750,8 +758,9 @@ type WriteContentRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"`
- Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+ Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"`
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+ Compression uint32 `protobuf:"varint,3,opt,name=compression,proto3" json:"compression,omitempty"`
}
func (x *WriteContentRequest) Reset() {
@@ -800,6 +809,13 @@ func (x *WriteContentRequest) GetData() []byte {
return nil
}
+func (x *WriteContentRequest) GetCompression() uint32 {
+ if x != nil {
+ return x.Compression
+ }
+ return 0
+}
+
type WriteContentResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1674,206 +1690,212 @@ func (*SessionResponse_DeleteManifest) isSessionResponse_Response() {}
0x10, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e,
0x44, 0x10, 0x04, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x44, 0x45,
0x4e, 0x49, 0x45, 0x44, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d,
- 0x5f, 0x42, 0x52, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x06, 0x22, 0x78, 0x0a, 0x14, 0x52, 0x65, 0x70,
- 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
- 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
- 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x46, 0x75,
- 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x6d, 0x61, 0x63, 0x5f, 0x73,
- 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6d, 0x61,
- 0x63, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x70, 0x6c, 0x69, 0x74,
- 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x6c, 0x69, 0x74,
- 0x74, 0x65, 0x72, 0x22, 0x51, 0x0a, 0x18, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a,
- 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
- 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61,
- 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65,
- 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x63, 0x0a, 0x19, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61,
- 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
- 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
- 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
- 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x73,
- 0x69, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52,
- 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x22, 0x36, 0x0a, 0x15, 0x47,
- 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f,
- 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
- 0x74, 0x49, 0x64, 0x22, 0x4b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
- 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a,
- 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x6f,
- 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x43,
- 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f,
- 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65,
- 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
- 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65,
- 0x6e, 0x74, 0x49, 0x64, 0x22, 0x28, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65,
- 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61,
- 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x0e,
- 0x0a, 0x0c, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0f,
- 0x0a, 0x0d, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
- 0x41, 0x0a, 0x13, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52,
- 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
- 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x12,
- 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61,
- 0x74, 0x61, 0x22, 0x35, 0x0a, 0x14, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65,
- 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f,
- 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
- 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x35, 0x0a, 0x12, 0x47, 0x65, 0x74,
- 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
- 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x49, 0x64,
- 0x22, 0x77, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f,
- 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e,
- 0x44, 0x61, 0x74, 0x61, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72,
- 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
- 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
- 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb6, 0x01, 0x0a, 0x12, 0x50, 0x75,
- 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
- 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x48, 0x0a,
- 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e,
- 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
- 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
- 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c,
- 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
- 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
- 0x38, 0x01, 0x22, 0x36, 0x0a, 0x13, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
- 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x6e,
- 0x69, 0x66, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
- 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15, 0x44, 0x65,
- 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x5f,
- 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65,
- 0x73, 0x74, 0x49, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61,
- 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9d,
- 0x01, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73,
- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c,
- 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
- 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d,
- 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
- 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62,
- 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74,
- 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5c,
- 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
- 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69,
- 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x6e,
- 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
- 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf5, 0x05, 0x0a,
- 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
- 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x5b,
- 0x0a, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73,
- 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6b, 0x6f, 0x70,
- 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6e,
- 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52,
- 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61,
- 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x53, 0x0a, 0x10, 0x67,
- 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18,
- 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
- 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74,
- 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
- 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f,
- 0x12, 0x36, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32,
- 0x1e, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
- 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
- 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x4c, 0x0a, 0x0d, 0x77, 0x72, 0x69, 0x74,
- 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32,
- 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
- 0x72, 0x79, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52,
- 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x43,
- 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x46, 0x0a, 0x0b, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f,
- 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x6f,
- 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47,
- 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
- 0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x49,
- 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x0f,
- 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
- 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66,
- 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x65,
- 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x70, 0x75, 0x74,
- 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32,
- 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
- 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65,
- 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69,
- 0x66, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e,
- 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b,
+ 0x5f, 0x42, 0x52, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x06, 0x22, 0xba, 0x01, 0x0a, 0x14, 0x52, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
+ 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74,
+ 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x46,
+ 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x6d, 0x61, 0x63, 0x5f,
+ 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6d,
+ 0x61, 0x63, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x70, 0x6c, 0x69,
+ 0x74, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x6c, 0x69,
+ 0x74, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x1c, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x73,
+ 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73,
+ 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x73, 0x75, 0x70, 0x70,
+ 0x6f, 0x72, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x72,
+ 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x51, 0x0a, 0x18, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61,
+ 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09,
+ 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x08, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x63, 0x0a, 0x19, 0x49, 0x6e, 0x69,
+ 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
+ 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70,
+ 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x52, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
+ 0x72, 0x73, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x22, 0x36,
+ 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65,
+ 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e,
+ 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x4b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
+ 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+ 0x12, 0x31, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,
+ 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69,
+ 0x6e, 0x66, 0x6f, 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+ 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f,
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x28, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x6f,
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a,
+ 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74,
+ 0x61, 0x22, 0x0e, 0x0a, 0x0c, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x22, 0x0f, 0x0a, 0x0d, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x22, 0x63, 0x0a, 0x13, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65,
+ 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65,
+ 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69,
+ 0x78, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
+ 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73,
+ 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70,
+ 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x35, 0x0a, 0x14, 0x57, 0x72, 0x69, 0x74, 0x65,
+ 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+ 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x35,
+ 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
+ 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x6e, 0x69, 0x66,
+ 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x77, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69,
+ 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09,
+ 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
+ 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74,
+ 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f,
+ 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4d,
+ 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x61,
+ 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb6,
+ 0x01, 0x0a, 0x12, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61,
+ 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61,
+ 0x74, 0x61, 0x12, 0x48, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03,
+ 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+ 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
+ 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45,
+ 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b,
+ 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
+ 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
+ 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
+ 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x36, 0x0a, 0x13, 0x50, 0x75, 0x74, 0x4d, 0x61,
+ 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
+ 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22,
+ 0x38, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
+ 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x69,
+ 0x66, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d,
+ 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c,
+ 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69,
+ 0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4a, 0x0a, 0x06,
+ 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x66, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69,
- 0x66, 0x65, 0x73, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f,
- 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
+ 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
+ 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65,
+ 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
+ 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
+ 0x02, 0x38, 0x01, 0x22, 0x5c, 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66,
+ 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08,
+ 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
- 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74,
- 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x22, 0xb9, 0x06, 0x0a, 0x0f, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
- 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65,
- 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72,
- 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
- 0x12, 0x5c, 0x0a, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x73,
- 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6b,
- 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
- 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
- 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x11, 0x69, 0x6e, 0x69,
- 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x54,
- 0x0a, 0x10, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e,
- 0x66, 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61,
- 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43,
- 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
- 0x73, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
- 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x0c, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
- 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70,
- 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x4d, 0x0a,
+ 0x79, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4d,
+ 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+ 0x61, 0x22, 0xf5, 0x05, 0x0a, 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f,
+ 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x49, 0x64, 0x12, 0x5b, 0x0a, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a,
+ 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x2a, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73,
+ 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x69,
+ 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+ 0x12, 0x53, 0x0a, 0x10, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f,
+ 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70,
+ 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65,
+ 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+ 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x36, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x0c,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x4c, 0x0a,
0x0d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0d,
- 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e,
- 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c,
- 0x77, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0b,
- 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28,
- 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
- 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43, 0x6f,
- 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x6e,
- 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f,
- 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47,
- 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
- 0x73, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
- 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
- 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
- 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61,
- 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00,
- 0x52, 0x0b, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a,
- 0x0e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18,
- 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
- 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e,
- 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00,
- 0x52, 0x0d, 0x66, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x12,
- 0x53, 0x0a, 0x0f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65,
- 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61,
- 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65,
- 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
- 0x73, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69,
- 0x66, 0x65, 0x73, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
- 0x32, 0x65, 0x0a, 0x0f, 0x4b, 0x6f, 0x70, 0x69, 0x61, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
- 0x6f, 0x72, 0x79, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20,
- 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
- 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
- 0x1a, 0x21, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
- 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
- 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75,
- 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x2f, 0x6b, 0x6f, 0x70, 0x69,
- 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x61,
- 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x77,
+ 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x46, 0x0a, 0x0b, 0x67,
+ 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x23, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66,
+ 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69,
+ 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74,
+ 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
+ 0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x49,
+ 0x0a, 0x0c, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x10,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66,
+ 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x75,
+ 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0e, 0x66, 0x69, 0x6e,
+ 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
+ 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
+ 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x66, 0x69, 0x6e,
+ 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
+ 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e,
+ 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e,
+ 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x42, 0x09,
+ 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb9, 0x06, 0x0a, 0x0f, 0x53, 0x65,
+ 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a,
+ 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x03, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x05,
+ 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f,
+ 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45,
+ 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05,
+ 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c,
+ 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x2b, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
+ 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53,
+ 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00,
+ 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73,
+ 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x10, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65,
+ 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e,
+ 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
+ 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f,
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x6c, 0x75,
+ 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61,
+ 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73,
+ 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75,
+ 0x73, 0x68, 0x12, 0x4d, 0x0a, 0x0d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69,
+ 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x57, 0x72, 0x69,
+ 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+ 0x74, 0x12, 0x47, 0x0a, 0x0b, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
+ 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72,
+ 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
+ 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0a,
+ 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x67, 0x65,
+ 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x4d, 0x61,
+ 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x61,
+ 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b,
+ 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
+ 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
+ 0x73, 0x74, 0x12, 0x50, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66,
+ 0x65, 0x73, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70,
+ 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69,
+ 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x66, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66,
+ 0x65, 0x73, 0x74, 0x73, 0x12, 0x53, 0x0a, 0x0f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6d,
+ 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e,
+ 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
+ 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74,
+ 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x65, 0x0a, 0x0f, 0x4b, 0x6f, 0x70, 0x69, 0x61, 0x52, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73,
+ 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
+ 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27,
+ 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x6f, 0x70, 0x69, 0x61,
+ 0x2f, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
+ 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/internal/grpcapi/repository_server.proto b/internal/grpcapi/repository_server.proto
index 6c9e8ff48..119f37415 100644
--- a/internal/grpcapi/repository_server.proto
+++ b/internal/grpcapi/repository_server.proto
@@ -45,6 +45,7 @@ message RepositoryParameters {
string hash_function = 1;
bytes hmac_secret = 2;
string splitter = 3;
+ bool supports_content_compression = 4;
}
// InitializeSessionRequest must be sent by the client as the first request in a session.
@@ -83,6 +84,7 @@ message FlushResponse {
message WriteContentRequest {
string prefix = 1;
bytes data = 2;
+ uint32 compression = 3;
}
message WriteContentResponse {
diff --git a/internal/remoterepoapi/remoterepoapi.go b/internal/remoterepoapi/remoterepoapi.go
index 8121ee36c..9ecb84251 100644
--- a/internal/remoterepoapi/remoterepoapi.go
+++ b/internal/remoterepoapi/remoterepoapi.go
@@ -11,8 +11,9 @@
// Parameters encapsulates all parameters for repository.
// returned by /api/v1/repo/parameters.
type Parameters struct {
- HashFunction string `json:"hash"`
- HMACSecret []byte `json:"hmacSecret"`
+ HashFunction string `json:"hash"`
+ HMACSecret []byte `json:"hmacSecret"`
+ SupportsContentCompression bool `json:"supportsContentCompression"`
object.Format
}
diff --git a/internal/repotesting/repotesting_test.go b/internal/repotesting/repotesting_test.go
index d12a53d4e..5c47cb321 100644
--- a/internal/repotesting/repotesting_test.go
+++ b/internal/repotesting/repotesting_test.go
@@ -7,6 +7,7 @@
"github.com/kopia/kopia/internal/faketime"
"github.com/kopia/kopia/internal/mockfs"
"github.com/kopia/kopia/repo"
+ "github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
"github.com/kopia/kopia/snapshot/snapshotfs"
@@ -42,7 +43,7 @@ func TestTimeFuncWiring(t *testing.T) {
// verify wiring for the content layer
nt := ft.Advance(20 * time.Second)
- cid, err := env.RepositoryWriter.ContentManager().WriteContent(ctx, []byte("foo"), "")
+ cid, err := env.RepositoryWriter.ContentManager().WriteContent(ctx, []byte("foo"), "", content.NoCompression)
if err != nil {
t.Fatal("failed to write content:", err)
}
diff --git a/internal/server/api_content.go b/internal/server/api_content.go
index 60b584178..6d2256939 100644
--- a/internal/server/api_content.go
+++ b/internal/server/api_content.go
@@ -4,12 +4,14 @@
"context"
"errors"
"net/http"
+ "strconv"
"strings"
"github.com/gorilla/mux"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/repo"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/manifest"
)
@@ -66,7 +68,21 @@ func (s *Server) handleContentPut(ctx context.Context, r *http.Request, data []b
return nil, accessDeniedError()
}
- actualCID, err := dr.ContentManager().WriteContent(ctx, data, prefix)
+ var comp compression.HeaderID
+
+ if c := r.URL.Query().Get("compression"); c != "" {
+ v, err := strconv.ParseInt(c, 16, 32)
+ if err != nil {
+ return nil, requestError(serverapi.ErrorMalformedRequest, "malformed compression ID")
+ }
+
+ comp = compression.HeaderID(v)
+ if _, ok := compression.ByHeaderID[comp]; !ok {
+ return nil, requestError(serverapi.ErrorMalformedRequest, "invalid compression ID")
+ }
+ }
+
+ actualCID, err := dr.ContentManager().WriteContent(ctx, data, prefix, comp)
if err != nil {
return nil, internalServerError(err)
}
diff --git a/internal/server/api_repo.go b/internal/server/api_repo.go
index 4614d1372..f8f58ac66 100644
--- a/internal/server/api_repo.go
+++ b/internal/server/api_repo.go
@@ -30,9 +30,10 @@ func (s *Server) handleRepoParameters(ctx context.Context, r *http.Request, body
}
rp := &remoterepoapi.Parameters{
- HashFunction: dr.ContentReader().ContentFormat().Hash,
- HMACSecret: dr.ContentReader().ContentFormat().HMACSecret,
- Format: dr.ObjectFormat(),
+ HashFunction: dr.ContentReader().ContentFormat().Hash,
+ HMACSecret: dr.ContentReader().ContentFormat().HMACSecret,
+ Format: dr.ObjectFormat(),
+ SupportsContentCompression: dr.ContentReader().SupportsContentCompression(),
}
return rp, nil
@@ -48,19 +49,21 @@ func (s *Server) handleRepoStatus(ctx context.Context, r *http.Request, body []b
dr, ok := s.rep.(repo.DirectRepository)
if ok {
return &serverapi.StatusResponse{
- Connected: true,
- ConfigFile: dr.ConfigFilename(),
- Hash: dr.ContentReader().ContentFormat().Hash,
- Encryption: dr.ContentReader().ContentFormat().Encryption,
- MaxPackSize: dr.ContentReader().ContentFormat().MaxPackSize,
- Splitter: dr.ObjectFormat().Splitter,
- Storage: dr.BlobReader().ConnectionInfo().Type,
- ClientOptions: dr.ClientOptions(),
+ Connected: true,
+ ConfigFile: dr.ConfigFilename(),
+ Hash: dr.ContentReader().ContentFormat().Hash,
+ Encryption: dr.ContentReader().ContentFormat().Encryption,
+ MaxPackSize: dr.ContentReader().ContentFormat().MaxPackSize,
+ Splitter: dr.ObjectFormat().Splitter,
+ Storage: dr.BlobReader().ConnectionInfo().Type,
+ ClientOptions: dr.ClientOptions(),
+ SupportsContentCompression: dr.ContentReader().SupportsContentCompression(),
}, nil
}
type remoteRepository interface {
APIServerURL() string
+ SupportsContentCompression() bool
}
result := &serverapi.StatusResponse{
@@ -70,6 +73,7 @@ type remoteRepository interface {
if rr, ok := s.rep.(remoteRepository); ok {
result.APIServerURL = rr.APIServerURL()
+ result.SupportsContentCompression = rr.SupportsContentCompression()
}
return result, nil
diff --git a/internal/server/grpc_session.go b/internal/server/grpc_session.go
index 1c40ad032..66435a47c 100644
--- a/internal/server/grpc_session.go
+++ b/internal/server/grpc_session.go
@@ -19,6 +19,7 @@
"github.com/kopia/kopia/internal/auth"
"github.com/kopia/kopia/internal/grpcapi"
"github.com/kopia/kopia/repo"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/repo/object"
@@ -230,7 +231,7 @@ func handleWriteContentRequest(ctx context.Context, dw repo.DirectRepositoryWrit
return accessDeniedResponse()
}
- contentID, err := dw.ContentManager().WriteContent(ctx, req.GetData(), content.ID(req.GetPrefix()))
+ contentID, err := dw.ContentManager().WriteContent(ctx, req.GetData(), content.ID(req.GetPrefix()), compression.HeaderID(req.GetCompression()))
if err != nil {
return errorResponse(err)
}
@@ -420,9 +421,10 @@ func (s *Server) handleInitialSessionHandshake(srv grpcapi.KopiaRepository_Sessi
Response: &grpcapi.SessionResponse_InitializeSession{
InitializeSession: &grpcapi.InitializeSessionResponse{
Parameters: &grpcapi.RepositoryParameters{
- HashFunction: dr.ContentReader().ContentFormat().Hash,
- HmacSecret: dr.ContentReader().ContentFormat().HMACSecret,
- Splitter: dr.ObjectFormat().Splitter,
+ HashFunction: dr.ContentReader().ContentFormat().Hash,
+ HmacSecret: dr.ContentReader().ContentFormat().HMACSecret,
+ Splitter: dr.ObjectFormat().Splitter,
+ SupportsContentCompression: dr.ContentReader().SupportsContentCompression(),
},
},
},
diff --git a/internal/serverapi/serverapi.go b/internal/serverapi/serverapi.go
index 21bc8e8ef..ae08c6cbf 100644
--- a/internal/serverapi/serverapi.go
+++ b/internal/serverapi/serverapi.go
@@ -18,14 +18,15 @@
// StatusResponse is the response of 'status' HTTP API command.
type StatusResponse struct {
- Connected bool `json:"connected"`
- ConfigFile string `json:"configFile,omitempty"`
- Hash string `json:"hash,omitempty"`
- Encryption string `json:"encryption,omitempty"`
- Splitter string `json:"splitter,omitempty"`
- MaxPackSize int `json:"maxPackSize,omitempty"`
- Storage string `json:"storage,omitempty"`
- APIServerURL string `json:"apiServerURL,omitempty"`
+ Connected bool `json:"connected"`
+ ConfigFile string `json:"configFile,omitempty"`
+ Hash string `json:"hash,omitempty"`
+ Encryption string `json:"encryption,omitempty"`
+ Splitter string `json:"splitter,omitempty"`
+ MaxPackSize int `json:"maxPackSize,omitempty"`
+ Storage string `json:"storage,omitempty"`
+ APIServerURL string `json:"apiServerURL,omitempty"`
+ SupportsContentCompression bool `json:"supportsContentCompression"`
repo.ClientOptions
}
diff --git a/repo/api_server_repository.go b/repo/api_server_repository.go
index db90a4608..8c54a8081 100644
--- a/repo/api_server_repository.go
+++ b/repo/api_server_repository.go
@@ -14,6 +14,7 @@
"github.com/kopia/kopia/internal/cache"
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/internal/remoterepoapi"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/hashing"
"github.com/kopia/kopia/repo/manifest"
@@ -30,12 +31,13 @@ type APIServerInfo struct {
// remoteRepository is an implementation of Repository that connects to an instance of
// API server hosted by `kopia server`, instead of directly manipulating files in the BLOB storage.
type apiServerRepository struct {
- cli *apiclient.KopiaAPIClient
- h hashing.HashFunc
- objectFormat object.Format
- cliOpts ClientOptions
- omgr *object.Manager
- wso WriteSessionOptions
+ cli *apiclient.KopiaAPIClient
+ h hashing.HashFunc
+ objectFormat object.Format
+ serverSupportsContentCompression bool
+ cliOpts ClientOptions
+ omgr *object.Manager
+ wso WriteSessionOptions
isSharedReadOnlySession bool
contentCache *cache.PersistentCache
@@ -136,6 +138,10 @@ func (r *apiServerRepository) Flush(ctx context.Context) error {
return errors.Wrap(r.cli.Post(ctx, "flush", nil, nil), "Flush")
}
+func (r *apiServerRepository) SupportsContentCompression() bool {
+ return r.serverSupportsContentCompression
+}
+
func (r *apiServerRepository) NewWriter(ctx context.Context, opt WriteSessionOptions) (RepositoryWriter, error) {
// apiServerRepository is stateless except object manager.
r2 := *r
@@ -177,7 +183,7 @@ func (r *apiServerRepository) GetContent(ctx context.Context, contentID content.
})
}
-func (r *apiServerRepository) WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error) {
+func (r *apiServerRepository) WriteContent(ctx context.Context, data []byte, prefix content.ID, comp compression.HeaderID) (content.ID, error) {
if err := content.ValidatePrefix(prefix); err != nil {
return "", errors.Wrap(err, "invalid prefix")
}
@@ -194,7 +200,12 @@ func (r *apiServerRepository) WriteContent(ctx context.Context, data []byte, pre
r.wso.OnUpload(int64(len(data)))
- if err := r.cli.Put(ctx, "contents/"+string(contentID), data, nil); err != nil {
+ maybeCompression := ""
+ if comp != content.NoCompression {
+ maybeCompression = fmt.Sprintf("?compression=%x", comp)
+ }
+
+ if err := r.cli.Put(ctx, "contents/"+string(contentID)+maybeCompression, data, nil); err != nil {
return "", errors.Wrapf(err, "error writing content %v", contentID)
}
@@ -266,6 +277,7 @@ func openRestAPIRepository(ctx context.Context, si *APIServerInfo, cliOpts Clien
rr.h = hf
rr.objectFormat = p.Format
+ rr.serverSupportsContentCompression = p.SupportsContentCompression
// create object manager using rr as contentManager implementation.
omgr, err := object.NewObjectManager(ctx, rr, rr.objectFormat)
diff --git a/repo/content/committed_read_manager.go b/repo/content/committed_read_manager.go
index 68014c8ca..0a3fb44d6 100644
--- a/repo/content/committed_read_manager.go
+++ b/repo/content/committed_read_manager.go
@@ -1,6 +1,7 @@
package content
import (
+ "bytes"
"context"
"os"
"sync"
@@ -13,6 +14,7 @@
"github.com/kopia/kopia/internal/cache"
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/repo/blob"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/encryption"
"github.com/kopia/kopia/repo/hashing"
)
@@ -218,11 +220,31 @@ func (sm *SharedManager) decryptContentAndVerify(payload []byte, bi Info) ([]byt
return nil, err
}
+ // reserved for future use
+ if k := bi.GetEncryptionKeyID(); k != 0 {
+ return nil, errors.Errorf("unsupported encryption key ID: %v", k)
+ }
+
decrypted, err := sm.decryptAndVerify(payload, iv)
if err != nil {
return nil, errors.Wrapf(err, "invalid checksum at %v offset %v length %v", bi.GetPackBlobID(), bi.GetPackOffset(), len(payload))
}
+ if h := bi.GetCompressionHeaderID(); h != 0 {
+ c := compression.ByHeaderID[h]
+ if c == nil {
+ return nil, errors.Errorf("unsupported compressor %x", h)
+ }
+
+ out := bytes.NewBuffer(nil)
+
+ if err := c.Decompress(out, decrypted); err != nil {
+ return nil, errors.Wrap(err, "error decompressing")
+ }
+
+ return out.Bytes(), nil
+ }
+
return decrypted, nil
}
@@ -361,6 +383,15 @@ func NewSharedManager(ctx context.Context, st blob.Storage, f *FormattingOptions
return nil, err
}
+ actualIndexVersion := f.IndexVersion
+ if actualIndexVersion == 0 {
+ actualIndexVersion = DefaultIndexVersion
+ }
+
+ if actualIndexVersion < v1IndexVersion || actualIndexVersion > v2IndexVersion {
+ return nil, errors.Errorf("index version %v is not supported", actualIndexVersion)
+ }
+
sm := &SharedManager{
st: st,
encryptor: encryptor,
@@ -375,8 +406,8 @@ func NewSharedManager(ctx context.Context, st blob.Storage, f *FormattingOptions
repositoryFormatBytes: opts.RepositoryFormatBytes,
checkInvariantsOnUnlock: os.Getenv("KOPIA_VERIFY_INVARIANTS") != "",
writeFormatVersion: int32(f.Version),
- encryptionBufferPool: buf.NewPool(ctx, defaultEncryptionBufferPoolSegmentSize+encryptor.Overhead(), "content-manager-encryption"),
- indexVersion: v1IndexVersion,
+ encryptionBufferPool: buf.NewPool(ctx, defaultEncryptionBufferPoolSegmentSize+encryptor.Overhead()+maxCompressionOverheadPerContent, "content-manager-encryption"),
+ indexVersion: actualIndexVersion,
}
caching = caching.CloneOrDefault()
diff --git a/repo/content/content_formatter_test.go b/repo/content/content_formatter_test.go
index 1e2003973..e0c23f694 100644
--- a/repo/content/content_formatter_test.go
+++ b/repo/content/content_formatter_test.go
@@ -121,7 +121,7 @@ func verifyEndToEndFormatter(ctx context.Context, t *testing.T, hashAlgo, encryp
}
for _, b := range cases {
- contentID, err := bm.WriteContent(ctx, b, "")
+ contentID, err := bm.WriteContent(ctx, b, "", NoCompression)
if err != nil {
t.Errorf("err: %v", err)
}
diff --git a/repo/content/content_formatting_options.go b/repo/content/content_formatting_options.go
index 8e0607bd3..2852c4ecb 100644
--- a/repo/content/content_formatting_options.go
+++ b/repo/content/content_formatting_options.go
@@ -2,12 +2,13 @@
// FormattingOptions describes the rules for formatting contents in repository.
type FormattingOptions struct {
- Version int `json:"version,omitempty"` // version number, must be "1"
- Hash string `json:"hash,omitempty"` // identifier of the hash algorithm used
- Encryption string `json:"encryption,omitempty"` // identifier of the encryption algorithm used
- HMACSecret []byte `json:"secret,omitempty"` // HMAC secret used to generate encryption keys
- MasterKey []byte `json:"masterKey,omitempty"` // master encryption key (SIV-mode encryption only)
- MaxPackSize int `json:"maxPackSize,omitempty"` // maximum size of a pack object
+ Version int `json:"version,omitempty"` // version number, must be "1"
+ Hash string `json:"hash,omitempty"` // identifier of the hash algorithm used
+ Encryption string `json:"encryption,omitempty"` // identifier of the encryption algorithm used
+ HMACSecret []byte `json:"secret,omitempty"` // HMAC secret used to generate encryption keys
+ MasterKey []byte `json:"masterKey,omitempty"` // master encryption key (SIV-mode encryption only)
+ MaxPackSize int `json:"maxPackSize,omitempty"` // maximum size of a pack object
+ IndexVersion int `json:"indexVersion,omitempty"` // force particular index format version (1,2,..)
}
// GetEncryptionAlgorithm implements encryption.Parameters.
diff --git a/repo/content/content_manager.go b/repo/content/content_manager.go
index 6be02e1cb..4e6fc67e0 100644
--- a/repo/content/content_manager.go
+++ b/repo/content/content_manager.go
@@ -18,6 +18,7 @@
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/internal/gather"
"github.com/kopia/kopia/repo/blob"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/logging"
)
@@ -31,10 +32,14 @@
PackBlobIDPrefixRegular blob.ID = "p"
PackBlobIDPrefixSpecial blob.ID = "q"
+ NoCompression compression.HeaderID = 0
+
FormatLogModule = "kopia/format"
maxHashSize = 64
defaultEncryptionBufferPoolSegmentSize = 8 << 20 // 8 MB
+
+ DefaultIndexVersion = 1
)
// PackBlobIDPrefixes contains all possible prefixes for pack blobs.
@@ -208,7 +213,7 @@ func (bm *WriteManager) maybeRetryWritingFailedPacksUnlocked(ctx context.Context
return nil
}
-func (bm *WriteManager) addToPackUnlocked(ctx context.Context, contentID ID, data []byte, isDeleted bool) error {
+func (bm *WriteManager) addToPackUnlocked(ctx context.Context, contentID ID, data []byte, isDeleted bool, comp compression.HeaderID) error {
// see if the current index is old enough to cause automatic flush.
if err := bm.maybeFlushBasedOnTimeUnlocked(ctx); err != nil {
return errors.Wrap(err, "unable to flush old pending writes")
@@ -257,10 +262,12 @@ func (bm *WriteManager) addToPackUnlocked(ctx context.Context, contentID ID, dat
OriginalLength: uint32(len(data)),
}
- if err := bm.maybeEncryptContentDataForPacking(pp.currentPackData, data, contentID); err != nil {
+ actualComp, err := bm.maybeCompressAndEncryptDataForPacking(pp.currentPackData, data, contentID, comp)
+ if err != nil {
return errors.Wrapf(err, "unable to encrypt %q", contentID)
}
+ info.CompressionHeaderID = actualComp
info.PackedLength = uint32(pp.currentPackData.Length()) - info.PackOffset
pp.currentPackItems[contentID] = info
@@ -528,6 +535,8 @@ func (bm *WriteManager) Flush(ctx context.Context) error {
}
// RewriteContent causes reads and re-writes a given content using the most recent format.
+// TODO(jkowalski): this will currently always re-encrypt and re-compress data, perhaps consider a
+// pass-through mode that preserves encrypted/compressed bits.
func (bm *WriteManager) RewriteContent(ctx context.Context, contentID ID) error {
formatLog(ctx).Debugf("rewrite-content %v", contentID)
@@ -541,7 +550,7 @@ func (bm *WriteManager) RewriteContent(ctx context.Context, contentID ID) error
return err
}
- return bm.addToPackUnlocked(ctx, contentID, data, bi.GetDeleted())
+ return bm.addToPackUnlocked(ctx, contentID, data, bi.GetDeleted(), bi.GetCompressionHeaderID())
}
// UndeleteContent rewrites the content with the given ID if the content exists
@@ -564,7 +573,7 @@ func (bm *WriteManager) UndeleteContent(ctx context.Context, contentID ID) error
return err
}
- return bm.addToPackUnlocked(ctx, contentID, data, false)
+ return bm.addToPackUnlocked(ctx, contentID, data, false, bi.GetCompressionHeaderID())
}
func packPrefixForContentID(contentID ID) blob.ID {
@@ -609,9 +618,14 @@ func (bm *WriteManager) getOrCreatePendingPackInfoLocked(ctx context.Context, pr
return bm.pendingPacks[prefix], nil
}
+// SupportsContentCompression returns true if content manager supports content-compression.
+func (bm *WriteManager) SupportsContentCompression() bool {
+ return bm.format.IndexVersion >= v2IndexVersion
+}
+
// WriteContent saves a given content of data to a pack group with a provided name and returns a contentID
// that's based on the contents of data written.
-func (bm *WriteManager) WriteContent(ctx context.Context, data []byte, prefix ID) (ID, error) {
+func (bm *WriteManager) WriteContent(ctx context.Context, data []byte, prefix ID, comp compression.HeaderID) (ID, error) {
if err := bm.maybeRetryWritingFailedPacksUnlocked(ctx); err != nil {
return "", err
}
@@ -639,7 +653,7 @@ func (bm *WriteManager) WriteContent(ctx context.Context, data []byte, prefix ID
formatLog(ctx).Debugf("write-content %v new", contentID)
}
- err := bm.addToPackUnlocked(ctx, contentID, data, false)
+ err := bm.addToPackUnlocked(ctx, contentID, data, false, comp)
return contentID, err
}
diff --git a/repo/content/content_manager_lock_free.go b/repo/content/content_manager_lock_free.go
index 1b62691d7..4ea448fc0 100644
--- a/repo/content/content_manager_lock_free.go
+++ b/repo/content/content_manager_lock_free.go
@@ -1,6 +1,7 @@
package content
import (
+ "bytes"
"context"
"crypto/aes"
cryptorand "crypto/rand"
@@ -11,18 +12,52 @@
"github.com/kopia/kopia/internal/gather"
"github.com/kopia/kopia/repo/blob"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/encryption"
"github.com/kopia/kopia/repo/hashing"
)
+// maxCompressionOverheadPerContent is the maximum amount of overhead any compressor
+// would need for non-compressible data of maximum size.
+// The consequences of getting this wrong are not fatal - just an unnecessary memory allocation.
+const maxCompressionOverheadPerContent = 16384
+
const indexBlobCompactionWarningThreshold = 1000
-func (sm *SharedManager) maybeEncryptContentDataForPacking(output *gather.WriteBuffer, data []byte, contentID ID) error {
+func (sm *SharedManager) maybeCompressAndEncryptDataForPacking(output *gather.WriteBuffer, data []byte, contentID ID, comp compression.HeaderID) (compression.HeaderID, error) {
var hashOutput [maxHashSize]byte
iv, err := getPackedContentIV(hashOutput[:], contentID)
if err != nil {
- return errors.Wrapf(err, "unable to get packed content IV for %q", contentID)
+ return NoCompression, errors.Wrapf(err, "unable to get packed content IV for %q", contentID)
+ }
+
+ // nolint:nestif
+ if comp != NoCompression {
+ if sm.format.IndexVersion < v2IndexVersion {
+ return NoCompression, errors.Errorf("compression is not enabled for this repository.")
+ }
+
+ // allocate temporary buffer to hold the compressed bytes.
+ tmp := sm.encryptionBufferPool.Allocate(len(data) + maxCompressionOverheadPerContent)
+ defer tmp.Release()
+
+ c := compression.ByHeaderID[comp]
+ if c == nil {
+ return NoCompression, errors.Errorf("unsupported compressor %x", comp)
+ }
+
+ cbuf := bytes.NewBuffer(tmp.Data[:0])
+ if err = c.Compress(cbuf, data); err != nil {
+ return NoCompression, errors.Wrap(err, "compression error")
+ }
+
+ if cd := cbuf.Bytes(); len(cd) >= len(data) {
+ // data was not compressible enough.
+ comp = NoCompression
+ } else {
+ data = cd
+ }
}
b := sm.encryptionBufferPool.Allocate(len(data) + sm.encryptor.Overhead())
@@ -30,14 +65,14 @@ func (sm *SharedManager) maybeEncryptContentDataForPacking(output *gather.WriteB
cipherText, err := sm.encryptor.Encrypt(b.Data[:0], data, iv)
if err != nil {
- return errors.Wrap(err, "unable to encrypt")
+ return NoCompression, errors.Wrap(err, "unable to encrypt")
}
sm.Stats.encrypted(len(data))
output.Append(cipherText)
- return nil
+ return comp, nil
}
func writeRandomBytesToBuffer(b *gather.WriteBuffer, count int) error {
diff --git a/repo/content/content_manager_test.go b/repo/content/content_manager_test.go
index b336a8bc0..2b88d4f08 100644
--- a/repo/content/content_manager_test.go
+++ b/repo/content/content_manager_test.go
@@ -26,6 +26,7 @@
"github.com/kopia/kopia/internal/testutil"
"github.com/kopia/kopia/repo/blob"
"github.com/kopia/kopia/repo/blob/logging"
+ "github.com/kopia/kopia/repo/compression"
)
const (
@@ -253,7 +254,7 @@ func TestContentManagerWriteMultiple(t *testing.T) {
for i := 0; i < repeatCount; i++ {
b := seededRandomData(i, i%113)
- blkID, err := bm.WriteContent(ctx, b, "")
+ blkID, err := bm.WriteContent(ctx, b, "", NoCompression)
if err != nil {
t.Errorf("err: %v", err)
}
@@ -324,12 +325,12 @@ func TestContentManagerFailedToWritePack(t *testing.T) {
},
}
- _, err = bm.WriteContent(ctx, seededRandomData(1, 10), "")
+ _, err = bm.WriteContent(ctx, seededRandomData(1, 10), "", NoCompression)
if !errors.Is(err, sessionPutErr) {
t.Fatalf("can't create first content: %v", err)
}
- b1, err := bm.WriteContent(ctx, seededRandomData(1, 10), "")
+ b1, err := bm.WriteContent(ctx, seededRandomData(1, 10), "", NoCompression)
if err != nil {
t.Fatalf("can't create content: %v", err)
}
@@ -337,7 +338,7 @@ func TestContentManagerFailedToWritePack(t *testing.T) {
// advance time enough to cause auto-flush, which will fail (firstPutErr)
ta.Advance(1 * time.Hour)
- if _, err := bm.WriteContent(ctx, seededRandomData(2, 10), ""); !errors.Is(err, firstPutErr) {
+ if _, err := bm.WriteContent(ctx, seededRandomData(2, 10), "", NoCompression); !errors.Is(err, firstPutErr) {
t.Fatalf("can't create 2nd content: %v", err)
}
@@ -1771,7 +1772,7 @@ func verifyVersionCompat(t *testing.T, writeVersion int) {
data := make([]byte, i)
cryptorand.Read(data)
- cid, err := mgr.WriteContent(ctx, data, "")
+ cid, err := mgr.WriteContent(ctx, data, "", NoCompression)
if err != nil {
t.Fatalf("unable to write %v bytes: %v", len(data), err)
}
@@ -1921,6 +1922,94 @@ func verifyContentManagerDataSet(ctx context.Context, t *testing.T, mgr *WriteMa
}
}
+func TestCompression_Disabled(t *testing.T) {
+ data := blobtesting.DataMap{}
+ st := blobtesting.NewMapStorage(data, nil, nil)
+ bm := newTestContentManagerWithTweaks(t, st, &contentManagerTestTweaks{
+ indexVersion: v1IndexVersion,
+ })
+
+ require.False(t, bm.SupportsContentCompression())
+ ctx := testlogging.Context(t)
+ compressibleData := bytes.Repeat([]byte{1, 2, 3, 4}, 1000)
+
+ // with index v1 the compression is disabled
+ _, err := bm.WriteContent(ctx, compressibleData, "", compression.ByName["pgzip"].HeaderID())
+ require.Error(t, err)
+}
+
+func TestCompression_CompressibleData(t *testing.T) {
+ data := blobtesting.DataMap{}
+ st := blobtesting.NewMapStorage(data, nil, nil)
+ bm := newTestContentManagerWithTweaks(t, st, &contentManagerTestTweaks{
+ indexVersion: v2IndexVersion,
+ })
+
+ require.True(t, bm.SupportsContentCompression())
+
+ ctx := testlogging.Context(t)
+ compressibleData := bytes.Repeat([]byte{1, 2, 3, 4}, 1000)
+ headerID := compression.ByName["gzip"].HeaderID()
+
+ cid, err := bm.WriteContent(ctx, compressibleData, "", headerID)
+ require.NoError(t, err)
+
+ ci, err := bm.ContentInfo(ctx, cid)
+ require.NoError(t, err)
+
+ // gzip-compressed length
+ require.Equal(t, uint32(79), ci.GetPackedLength())
+ require.Equal(t, uint32(len(compressibleData)), ci.GetOriginalLength())
+ require.Equal(t, headerID, ci.GetCompressionHeaderID())
+
+ verifyContent(ctx, t, bm, cid, compressibleData)
+
+ require.NoError(t, bm.Flush(ctx))
+ verifyContent(ctx, t, bm, cid, compressibleData)
+
+ bm2 := newTestContentManagerWithTweaks(t, st, &contentManagerTestTweaks{
+ indexVersion: v2IndexVersion,
+ })
+ verifyContent(ctx, t, bm2, cid, compressibleData)
+}
+
+func TestCompression_NonCompressibleData(t *testing.T) {
+ data := blobtesting.DataMap{}
+ st := blobtesting.NewMapStorage(data, nil, nil)
+ bm := newTestContentManagerWithTweaks(t, st, &contentManagerTestTweaks{
+ indexVersion: v2IndexVersion,
+ })
+
+ require.True(t, bm.SupportsContentCompression())
+
+ ctx := testlogging.Context(t)
+ nonCompressibleData := make([]byte, 65000)
+ headerID := compression.ByName["pgzip"].HeaderID()
+
+ rand.Read(nonCompressibleData)
+
+ cid, err := bm.WriteContent(ctx, nonCompressibleData, "", headerID)
+ require.NoError(t, err)
+
+ verifyContent(ctx, t, bm, cid, nonCompressibleData)
+
+ ci, err := bm.ContentInfo(ctx, cid)
+ require.NoError(t, err)
+
+ // verify compression did not occur
+ require.True(t, ci.GetPackedLength() > ci.GetOriginalLength())
+ require.Equal(t, uint32(len(nonCompressibleData)), ci.GetOriginalLength())
+ require.Equal(t, NoCompression, ci.GetCompressionHeaderID())
+
+ require.NoError(t, bm.Flush(ctx))
+ verifyContent(ctx, t, bm, cid, nonCompressibleData)
+
+ bm2 := newTestContentManagerWithTweaks(t, st, &contentManagerTestTweaks{
+ indexVersion: v2IndexVersion,
+ })
+ verifyContent(ctx, t, bm2, cid, nonCompressibleData)
+}
+
func newTestContentManager(t *testing.T, data blobtesting.DataMap) *WriteManager {
t.Helper()
@@ -1944,6 +2033,8 @@ func newTestContentManagerWithCustomTime(t *testing.T, data blobtesting.DataMap,
type contentManagerTestTweaks struct {
CachingOptions
ManagerOptions
+
+ indexVersion int
}
func newTestContentManagerWithTweaks(t *testing.T, st blob.Storage, tweaks *contentManagerTestTweaks) *WriteManager {
@@ -1964,6 +2055,8 @@ func newTestContentManagerWithTweaks(t *testing.T, st blob.Storage, tweaks *cont
HMACSecret: hmacSecret,
MaxPackSize: maxPackSize,
Version: 1,
+
+ IndexVersion: tweaks.indexVersion,
}
bm, err := NewManager(ctx, st, fo, &tweaks.CachingOptions, &tweaks.ManagerOptions)
@@ -2023,7 +2116,7 @@ func verifyContent(ctx context.Context, t *testing.T, bm *WriteManager, contentI
func writeContentAndVerify(ctx context.Context, t *testing.T, bm *WriteManager, b []byte) ID {
t.Helper()
- contentID, err := bm.WriteContent(ctx, b, "")
+ contentID, err := bm.WriteContent(ctx, b, "", NoCompression)
if err != nil {
t.Errorf("err: %v", err)
}
@@ -2061,13 +2154,13 @@ func writeContentWithRetriesAndVerify(ctx context.Context, t *testing.T, bm *Wri
log(ctx).Infof("*** starting writeContentWithRetriesAndVerify")
- contentID, err := bm.WriteContent(ctx, b, "")
+ contentID, err := bm.WriteContent(ctx, b, "", NoCompression)
for i := 0; err != nil && i < maxRetries; i++ {
retryCount++
log(ctx).Infof("*** try %v", retryCount)
- contentID, err = bm.WriteContent(ctx, b, "")
+ contentID, err = bm.WriteContent(ctx, b, "", NoCompression)
}
if err != nil {
diff --git a/repo/content/content_reader.go b/repo/content/content_reader.go
index 406bc6b0f..334e6ed71 100644
--- a/repo/content/content_reader.go
+++ b/repo/content/content_reader.go
@@ -6,6 +6,7 @@
// Reader defines content read API.
type Reader interface {
+ SupportsContentCompression() bool
ContentFormat() FormattingOptions
GetContent(ctx context.Context, id ID) ([]byte, error)
ContentInfo(ctx context.Context, id ID) (Info, error)
diff --git a/repo/grpc_repository_client.go b/repo/grpc_repository_client.go
index a9a3d60f8..dab928e76 100644
--- a/repo/grpc_repository_client.go
+++ b/repo/grpc_repository_client.go
@@ -22,6 +22,7 @@
"github.com/kopia/kopia/internal/retry"
"github.com/kopia/kopia/internal/tlsutil"
"github.com/kopia/kopia/repo/blob"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/hashing"
"github.com/kopia/kopia/repo/manifest"
@@ -55,10 +56,11 @@ type grpcRepositoryClient struct {
// how many times we tried to establish inner session
innerSessionAttemptCount int
- h hashing.HashFunc
- objectFormat object.Format
- cliOpts ClientOptions
- omgr *object.Manager
+ h hashing.HashFunc
+ objectFormat object.Format
+ serverSupportsContentCompression bool
+ cliOpts ClientOptions
+ omgr *object.Manager
contentCache *cache.PersistentCache
}
@@ -533,7 +535,11 @@ func (r *grpcInnerSession) GetContent(ctx context.Context, contentID content.ID)
return nil, errNoSessionResponse()
}
-func (r *grpcRepositoryClient) WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error) {
+func (r *grpcRepositoryClient) SupportsContentCompression() bool {
+ return r.serverSupportsContentCompression
+}
+
+func (r *grpcRepositoryClient) WriteContent(ctx context.Context, data []byte, prefix content.ID, comp compression.HeaderID) (content.ID, error) {
if err := content.ValidatePrefix(prefix); err != nil {
return "", errors.Wrap(err, "invalid prefix")
}
@@ -551,7 +557,7 @@ func (r *grpcRepositoryClient) WriteContent(ctx context.Context, data []byte, pr
r.opt.OnUpload(int64(len(data)))
v, err := r.inSessionWithoutRetry(ctx, func(ctx context.Context, sess *grpcInnerSession) (interface{}, error) {
- return sess.WriteContent(ctx, data, prefix)
+ return sess.WriteContent(ctx, data, prefix, comp)
})
if err != nil {
return "", err
@@ -565,7 +571,7 @@ func (r *grpcRepositoryClient) WriteContent(ctx context.Context, data []byte, pr
return v.(content.ID), nil
}
-func (r *grpcInnerSession) WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error) {
+func (r *grpcInnerSession) WriteContent(ctx context.Context, data []byte, prefix content.ID, comp compression.HeaderID) (content.ID, error) {
if err := content.ValidatePrefix(prefix); err != nil {
return "", errors.Wrap(err, "invalid prefix")
}
@@ -573,8 +579,9 @@ func (r *grpcInnerSession) WriteContent(ctx context.Context, data []byte, prefix
for resp := range r.sendRequest(ctx, &apipb.SessionRequest{
Request: &apipb.SessionRequest_WriteContent{
WriteContent: &apipb.WriteContentRequest{
- Data: data,
- Prefix: string(prefix),
+ Data: data,
+ Prefix: string(prefix),
+ Compression: uint32(comp),
},
},
}) {
@@ -773,6 +780,8 @@ func newGRPCAPIRepositoryForConnection(ctx context.Context, conn *grpc.ClientCon
Splitter: p.Splitter,
}
+ rr.serverSupportsContentCompression = p.SupportsContentCompression
+
rr.omgr, err = object.NewObjectManager(ctx, rr, rr.objectFormat)
if err != nil {
return nil, errors.Wrap(err, "unable to initialize object manager")
diff --git a/repo/initialize.go b/repo/initialize.go
index cf17808d2..7bae01f1b 100644
--- a/repo/initialize.go
+++ b/repo/initialize.go
@@ -89,12 +89,13 @@ func formatBlobFromOptions(opt *NewRepositoryOptions) *formatBlob {
func repositoryObjectFormatFromOptions(opt *NewRepositoryOptions) *repositoryObjectFormat {
f := &repositoryObjectFormat{
FormattingOptions: content.FormattingOptions{
- Version: 1,
- Hash: applyDefaultString(opt.BlockFormat.Hash, hashing.DefaultAlgorithm),
- Encryption: applyDefaultString(opt.BlockFormat.Encryption, encryption.DefaultAlgorithm),
- HMACSecret: applyDefaultRandomBytes(opt.BlockFormat.HMACSecret, hmacSecretLength),
- MasterKey: applyDefaultRandomBytes(opt.BlockFormat.MasterKey, masterKeyLength),
- MaxPackSize: applyDefaultInt(opt.BlockFormat.MaxPackSize, 20<<20), //nolint:gomnd
+ Version: 1,
+ Hash: applyDefaultString(opt.BlockFormat.Hash, hashing.DefaultAlgorithm),
+ Encryption: applyDefaultString(opt.BlockFormat.Encryption, encryption.DefaultAlgorithm),
+ HMACSecret: applyDefaultRandomBytes(opt.BlockFormat.HMACSecret, hmacSecretLength),
+ MasterKey: applyDefaultRandomBytes(opt.BlockFormat.MasterKey, masterKeyLength),
+ MaxPackSize: applyDefaultInt(opt.BlockFormat.MaxPackSize, 20<<20), //nolint:gomnd
+ IndexVersion: applyDefaultInt(opt.BlockFormat.IndexVersion, content.DefaultIndexVersion),
},
Format: object.Format{
Splitter: applyDefaultString(opt.ObjectFormat.Splitter, splitter.DefaultAlgorithm),
diff --git a/repo/manifest/committed_manifest_manager.go b/repo/manifest/committed_manifest_manager.go
index 4ca4ea424..c99d3fa3d 100644
--- a/repo/manifest/committed_manifest_manager.go
+++ b/repo/manifest/committed_manifest_manager.go
@@ -76,7 +76,7 @@ func (m *committedManifestManager) writeEntriesLocked(ctx context.Context, entri
mustSucceed(gz.Flush())
mustSucceed(gz.Close())
- contentID, err := m.b.WriteContent(ctx, buf.Bytes(), ContentPrefix)
+ contentID, err := m.b.WriteContent(ctx, buf.Bytes(), ContentPrefix, content.NoCompression)
if err != nil {
return nil, errors.Wrap(err, "unable to write content")
}
diff --git a/repo/manifest/manifest_manager.go b/repo/manifest/manifest_manager.go
index 4a849d4e5..3c45c6afb 100644
--- a/repo/manifest/manifest_manager.go
+++ b/repo/manifest/manifest_manager.go
@@ -13,6 +13,7 @@
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/clock"
+ "github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/logging"
)
@@ -36,7 +37,7 @@
type contentManager interface {
Revision() int64
GetContent(ctx context.Context, contentID content.ID) ([]byte, error)
- WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error)
+ WriteContent(ctx context.Context, data []byte, prefix content.ID, comp compression.HeaderID) (content.ID, error)
DeleteContent(ctx context.Context, contentID content.ID) error
IterateContents(ctx context.Context, options content.IterateOptions, callback content.IterateCallback) error
DisableIndexFlush(ctx context.Context)
diff --git a/repo/object/object_manager.go b/repo/object/object_manager.go
index 380b533c7..5d6793ed2 100644
--- a/repo/object/object_manager.go
+++ b/repo/object/object_manager.go
@@ -34,7 +34,8 @@ type contentReader interface {
type contentManager interface {
contentReader
- WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error)
+ SupportsContentCompression() bool
+ WriteContent(ctx context.Context, data []byte, prefix content.ID, comp compression.HeaderID) (content.ID, error)
}
// Format describes the format of objects in a repository.
diff --git a/repo/object/object_manager_test.go b/repo/object/object_manager_test.go
index 0253f243e..9f7bfa4ed 100644
--- a/repo/object/object_manager_test.go
+++ b/repo/object/object_manager_test.go
@@ -17,6 +17,7 @@
"testing"
"github.com/pkg/errors"
+ "github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
"github.com/kopia/kopia/internal/clock"
@@ -29,8 +30,10 @@
)
type fakeContentManager struct {
- mu sync.Mutex
- data map[content.ID][]byte
+ mu sync.Mutex
+ data map[content.ID][]byte
+ compresionIDs map[content.ID]compression.HeaderID
+ supportsContentCompression bool
}
func (f *fakeContentManager) GetContent(ctx context.Context, contentID content.ID) ([]byte, error) {
@@ -44,7 +47,7 @@ func (f *fakeContentManager) GetContent(ctx context.Context, contentID content.I
return nil, content.ErrContentNotFound
}
-func (f *fakeContentManager) WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error) {
+func (f *fakeContentManager) WriteContent(ctx context.Context, data []byte, prefix content.ID, comp compression.HeaderID) (content.ID, error) {
h := sha256.New()
h.Write(data)
contentID := prefix + content.ID(hex.EncodeToString(h.Sum(nil)))
@@ -53,10 +56,17 @@ func (f *fakeContentManager) WriteContent(ctx context.Context, data []byte, pref
defer f.mu.Unlock()
f.data[contentID] = append([]byte(nil), data...)
+ if f.compresionIDs != nil {
+ f.compresionIDs[contentID] = comp
+ }
return contentID, nil
}
+func (f *fakeContentManager) SupportsContentCompression() bool {
+ return f.supportsContentCompression
+}
+
func (f *fakeContentManager) ContentInfo(ctx context.Context, contentID content.ID) (content.Info, error) {
f.mu.Lock()
defer f.mu.Unlock()
@@ -72,16 +82,16 @@ func (f *fakeContentManager) Flush(ctx context.Context) error {
return nil
}
-func setupTest(t *testing.T) (map[content.ID][]byte, *Manager) {
+func setupTest(t *testing.T, compressionHeaderID map[content.ID]compression.HeaderID) (map[content.ID][]byte, *Manager) {
t.Helper()
- return setupTestWithData(t, map[content.ID][]byte{})
-}
+ data := map[content.ID][]byte{}
-func setupTestWithData(t *testing.T, data map[content.ID][]byte) (map[content.ID][]byte, *Manager) {
- t.Helper()
-
- r, err := NewObjectManager(testlogging.Context(t), &fakeContentManager{data: data}, Format{
+ r, err := NewObjectManager(testlogging.Context(t), &fakeContentManager{
+ data: data,
+ supportsContentCompression: compressionHeaderID != nil,
+ compresionIDs: compressionHeaderID,
+ }, Format{
Splitter: "FIXED-1M",
})
if err != nil {
@@ -109,7 +119,7 @@ func TestWriters(t *testing.T) {
}
for _, c := range cases {
- data, om := setupTest(t)
+ data, om := setupTest(t, nil)
writer := om.NewWriter(ctx, WriterOptions{})
@@ -144,9 +154,46 @@ func objectIDsEqual(o1, o2 ID) bool {
return o1 == o2
}
+func TestCompression_ContentCompressionEnabled(t *testing.T) {
+ ctx := testlogging.Context(t)
+
+ cmap := map[content.ID]compression.HeaderID{}
+ _, om := setupTest(t, cmap)
+
+ w := om.NewWriter(ctx, WriterOptions{
+ Compressor: "gzip",
+ })
+ w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1000))
+ oid, err := w.Result()
+ require.NoError(t, err)
+
+ cid, isCompressed, ok := oid.ContentID()
+ require.True(t, ok)
+ require.False(t, isCompressed) // oid will not indicate compression
+ require.Equal(t, compression.ByName["gzip"].HeaderID(), cmap[cid])
+}
+
+func TestCompression_ContentCompressionDisabled(t *testing.T) {
+ ctx := testlogging.Context(t)
+
+ // this disables content compression
+ _, om := setupTest(t, nil)
+
+ w := om.NewWriter(ctx, WriterOptions{
+ Compressor: "gzip",
+ })
+ w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1000))
+ oid, err := w.Result()
+ require.NoError(t, err)
+
+ _, isCompressed, ok := oid.ContentID()
+ require.True(t, ok)
+ require.True(t, isCompressed) // oid will indicate compression
+}
+
func TestWriterCompleteChunkInTwoWrites(t *testing.T) {
ctx := testlogging.Context(t)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
b := make([]byte, 100)
writer := om.NewWriter(ctx, WriterOptions{})
@@ -161,7 +208,7 @@ func TestWriterCompleteChunkInTwoWrites(t *testing.T) {
func TestCheckpointing(t *testing.T) {
ctx := testlogging.Context(t)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
writer := om.NewWriter(ctx, WriterOptions{})
@@ -348,7 +395,7 @@ func TestIndirection(t *testing.T) {
}
for _, c := range cases {
- data, om := setupTest(t)
+ data, om := setupTest(t, nil)
contentBytes := make([]byte, c.dataLength)
@@ -400,7 +447,7 @@ func TestHMAC(t *testing.T) {
ctx := testlogging.Context(t)
c := bytes.Repeat([]byte{0xcd}, 50)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
w := om.NewWriter(ctx, WriterOptions{})
w.Write(c)
@@ -414,7 +461,7 @@ func TestHMAC(t *testing.T) {
// nolint:gocyclo
func TestConcatenate(t *testing.T) {
ctx := testlogging.Context(t)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
phrase := []byte("hello world\n")
phraseLength := len(phrase)
@@ -549,7 +596,7 @@ func mustWriteObject(t *testing.T, om *Manager, data []byte, compressor compress
func TestReader(t *testing.T) {
ctx := testlogging.Context(t)
- data, om := setupTest(t)
+ data, om := setupTest(t, nil)
storedPayload := []byte("foo\nbar")
data["a76999788386641a3ec798554f1fe7e6"] = storedPayload
@@ -589,7 +636,7 @@ func TestReader(t *testing.T) {
func TestReaderStoredBlockNotFound(t *testing.T) {
ctx := testlogging.Context(t)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
objectID, err := ParseID("deadbeef")
if err != nil {
@@ -610,7 +657,7 @@ func TestEndToEndReadAndSeek(t *testing.T) {
t.Parallel()
ctx := testlogging.Context(t)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
for _, size := range []int{1, 199, 200, 201, 9999, 512434, 5012434} {
// Create some random data sample of the specified size.
@@ -663,7 +710,7 @@ func TestEndToEndReadAndSeekWithCompression(t *testing.T) {
totalBytesWritten := 0
- data, om := setupTest(t)
+ data, om := setupTest(t, nil)
for _, size := range sizes {
var inputData []byte
@@ -762,7 +809,7 @@ func verify(ctx context.Context, t *testing.T, cr contentReader, objectID ID, ex
// nolint:gocyclo
func TestSeek(t *testing.T) {
ctx := testlogging.Context(t)
- _, om := setupTest(t)
+ _, om := setupTest(t, nil)
for _, size := range []int{0, 1, 500000, 15000000} {
randomData := make([]byte, size)
diff --git a/repo/object/object_writer.go b/repo/object/object_writer.go
index b95f341a8..192e1ea38 100644
--- a/repo/object/object_writer.go
+++ b/repo/object/object_writer.go
@@ -189,13 +189,22 @@ func (w *objectWriter) prepareAndWriteContentChunk(chunkID int, data []byte) err
b := w.om.bufferPool.Allocate(len(data) + maxCompressionOverheadPerSegment)
defer b.Release()
+ comp := content.NoCompression
+ objectComp := w.compressor
+
+ // do not compress in this layer, instead pass comp to the content manager.
+ if w.om.contentMgr.SupportsContentCompression() && w.compressor != nil {
+ comp = w.compressor.HeaderID()
+ objectComp = nil
+ }
+
// contentBytes is what we're going to write to the content manager, it potentially uses bytes from b
- contentBytes, isCompressed, err := maybeCompressedContentBytes(w.compressor, bytes.NewBuffer(b.Data[:0]), data)
+ contentBytes, isCompressed, err := maybeCompressedContentBytes(objectComp, bytes.NewBuffer(b.Data[:0]), data)
if err != nil {
return errors.Wrap(err, "unable to prepare content bytes")
}
- contentID, err := w.om.contentMgr.WriteContent(w.ctx, contentBytes, w.prefix)
+ contentID, err := w.om.contentMgr.WriteContent(w.ctx, contentBytes, w.prefix, comp)
if err != nil {
return errors.Wrapf(err, "unable to write content chunk %v of %v: %v", chunkID, w.description, err)
}
diff --git a/tests/end_to_end_test/compression_test.go b/tests/end_to_end_test/compression_test.go
index 9d06298f0..323274a52 100644
--- a/tests/end_to_end_test/compression_test.go
+++ b/tests/end_to_end_test/compression_test.go
@@ -56,11 +56,43 @@ func TestCompression(t *testing.T) {
oid := sources[0].Snapshots[0].ObjectID
entries := clitestutil.ListDirectory(t, e, oid)
- if !strings.HasPrefix(entries[0].ObjectID, "Z") {
- t.Errorf("expected compressed object, got %v", entries[0].ObjectID)
+ supportsContentLevelCompression := containsLineStartingWith(
+ e.RunAndExpectSuccess(t, "repo", "status"),
+ "Content compression: true",
+ )
+
+ // without content-level compression, we'll do it at object level and object ID will be prefixed with 'Z'
+ if !supportsContentLevelCompression {
+ if !strings.HasPrefix(entries[0].ObjectID, "Z") {
+ t.Errorf("expected compressed object, got %v", entries[0].ObjectID)
+ }
+ } else {
+ // with content-level compression we're looking for a content with compression.
+ lines := e.RunAndExpectSuccess(t, "content", "ls", "-c")
+ found := false
+
+ for _, l := range lines {
+ if strings.HasPrefix(l, entries[0].ObjectID) {
+ require.Contains(t, l, "pgzip")
+ found = true
+ break
+ }
+ }
+
+ require.True(t, found)
}
if lines := e.RunAndExpectSuccess(t, "show", entries[0].ObjectID); !reflect.DeepEqual(dataLines, lines) {
t.Errorf("invalid object contents")
}
}
+
+func containsLineStartingWith(lines []string, prefix string) bool {
+ for _, l := range lines {
+ if strings.HasPrefix(l, prefix) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/tests/end_to_end_test/content_info_test.go b/tests/end_to_end_test/content_info_test.go
new file mode 100644
index 000000000..45ba5925b
--- /dev/null
+++ b/tests/end_to_end_test/content_info_test.go
@@ -0,0 +1,68 @@
+package endtoend_test
+
+import (
+ "bytes"
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/kopia/kopia/internal/testutil"
+ "github.com/kopia/kopia/snapshot"
+ "github.com/kopia/kopia/tests/testenv"
+)
+
+func TestContentListAndStats_Indexv1(t *testing.T) {
+ t.Parallel()
+ testContentListAndStats(t, "1")
+}
+
+func TestContentListAndStats_Indexv2(t *testing.T) {
+ t.Parallel()
+ testContentListAndStats(t, "2")
+}
+
+// nolint:thelper
+func testContentListAndStats(t *testing.T, indexVersion string) {
+ runner := testenv.NewInProcRunner(t)
+ e := testenv.NewCLITest(t, runner)
+
+ e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir, "--index-version", indexVersion)
+
+ require.Empty(t, e.RunAndExpectSuccess(t, "content", "list", "--deleted-only"))
+ e.RunAndExpectSuccess(t, "policy", "set", "--global", "--compression", "pgzip")
+
+ srcDir := testutil.TempDirectory(t)
+ ioutil.WriteFile(filepath.Join(srcDir, "compressible.txt"),
+ bytes.Repeat([]byte{1, 2, 3, 4}, 1000),
+ 0o600,
+ )
+
+ var man snapshot.Manifest
+
+ testutil.MustParseJSONLines(t, e.RunAndExpectSuccess(t, "snapshot", "create", srcDir, "--json"), &man)
+ contentID := string(man.RootObjectID())
+
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list"), contentID))
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "-l"), contentID))
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "-c"), contentID))
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "--summary"), "Total: "))
+
+ e.RunAndExpectSuccess(t, "content", "stats")
+
+ // sleep a bit to ensure at least one second passes, otherwise delete may end up happen on the same
+ // second as create, in which case creation will prevail.
+ time.Sleep(time.Second)
+
+ e.RunAndExpectSuccess(t, "content", "delete", contentID)
+
+ require.False(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list"), contentID))
+ require.False(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "-l"), contentID))
+ require.False(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "-c"), contentID))
+
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "--deleted"), contentID))
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "--deleted", "-l"), contentID))
+ require.True(t, containsLineStartingWith(e.RunAndExpectSuccess(t, "content", "list", "--deleted", "-c"), contentID))
+}
diff --git a/tests/repository_stress_test/repository_stress_test.go b/tests/repository_stress_test/repository_stress_test.go
index 04f0cc4bf..82bf5194f 100644
--- a/tests/repository_stress_test/repository_stress_test.go
+++ b/tests/repository_stress_test/repository_stress_test.go
@@ -233,7 +233,7 @@ func writeRandomBlock(ctx context.Context, t *testing.T, r repo.DirectRepository
data := make([]byte, 1000)
cryptorand.Read(data)
- contentID, err := r.ContentManager().WriteContent(ctx, data, "")
+ contentID, err := r.ContentManager().WriteContent(ctx, data, "", content.NoCompression)
if err == nil {
knownBlocksMutex.Lock()
if len(knownBlocks) >= 1000 {
diff --git a/tests/stress_test/stress_test.go b/tests/stress_test/stress_test.go
index 5511c5949..65709dbd0 100644
--- a/tests/stress_test/stress_test.go
+++ b/tests/stress_test/stress_test.go
@@ -95,7 +95,7 @@ type writtenBlock struct {
dataCopy := append([]byte{}, data...)
- contentID, err := bm.WriteContent(ctx, data, "")
+ contentID, err := bm.WriteContent(ctx, data, "", content.NoCompression)
if err != nil {
t.Errorf("err: %v", err)
return
diff --git a/tests/testenv/cli_inproc_runner.go b/tests/testenv/cli_inproc_runner.go
index b122a6c50..d922a5708 100644
--- a/tests/testenv/cli_inproc_runner.go
+++ b/tests/testenv/cli_inproc_runner.go
@@ -31,7 +31,7 @@ func (e *CLIInProcRunner) Start(t *testing.T, args []string) (stdout, stderr io.
func NewInProcRunner(t *testing.T) *CLIInProcRunner {
t.Helper()
- if os.Getenv("KOPIA_EXE") != "" {
+ if os.Getenv("KOPIA_EXE") != "" && os.Getenv("KOPIA_RUN_ALL_INTEGRATION_TESTS") == "" {
t.Skip("not running test since it's also included in the unit tests")
}