From 86e624bb7370f8adcd602c69d284689e046d3752 Mon Sep 17 00:00:00 2001 From: Gareth Date: Mon, 21 Jul 2025 21:20:16 -0700 Subject: [PATCH] chore: simplify sync impl by abstracting bidirectional transport (#844) --- gen/go/v1/syncservice.pb.go | 659 +++++++++++-------- gen/go/v1/syncservice_grpc.pb.go | 100 +++ gen/go/v1/v1connect/syncservice.connect.go | 74 +++ internal/api/syncapi/syncapi_test.go | 39 +- internal/api/syncapi/tunnel/connstate.go | 205 ++++++ internal/api/syncapi/tunnel/httpclient.go | 27 + internal/api/syncapi/tunnel/listener.go | 59 ++ internal/api/syncapi/tunnel/streamutil.go | 94 +++ internal/api/syncapi/tunnel/tunnel_test.go | 176 +++++ internal/api/syncapi/tunnel/wrappedstream.go | 290 ++++++++ internal/cryptoutil/identity.go | 1 - internal/testutil/logging.go | 12 +- proto/v1/syncservice.proto | 18 + webui/gen/ts/v1/syncservice_pb.ts | 108 ++- 14 files changed, 1535 insertions(+), 327 deletions(-) create mode 100644 internal/api/syncapi/tunnel/connstate.go create mode 100644 internal/api/syncapi/tunnel/httpclient.go create mode 100644 internal/api/syncapi/tunnel/listener.go create mode 100644 internal/api/syncapi/tunnel/streamutil.go create mode 100644 internal/api/syncapi/tunnel/tunnel_test.go create mode 100644 internal/api/syncapi/tunnel/wrappedstream.go diff --git a/gen/go/v1/syncservice.pb.go b/gen/go/v1/syncservice.pb.go index ceac61a5..506097a2 100644 --- a/gen/go/v1/syncservice.pb.go +++ b/gen/go/v1/syncservice.pb.go @@ -11,6 +11,7 @@ import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" _ "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" @@ -139,7 +140,92 @@ func (x SyncStreamItem_RepoConnectionState) Number() protoreflect.EnumNumber { // Deprecated: Use SyncStreamItem_RepoConnectionState.Descriptor instead. func (SyncStreamItem_RepoConnectionState) EnumDescriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 0} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 0} +} + +type TunnelMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConnId int64 `protobuf:"varint,1,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty"` // The connection ID this request belongs to, enables multiplexing. + Seqno int64 `protobuf:"varint,2,opt,name=seqno,proto3" json:"seqno,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` // The data to send, this is a raw byte stream. + Close bool `protobuf:"varint,4,opt,name=close,proto3" json:"close,omitempty"` // If true, the write end should be closed after this request. + PubkeyEcdhX25519 []byte `protobuf:"bytes,100,opt,name=pubkey_ecdh_x25519,json=pubkeyEcdhX25519,proto3" json:"pubkey_ecdh_x25519,omitempty"` // The public key used for ECDH key exchange, binary encoded. + Any *anypb.Any `protobuf:"bytes,1000,opt,name=any,proto3" json:"any,omitempty"` // Available to allow sending custom data down the tunnel in the initial handshake. +} + +func (x *TunnelMessage) Reset() { + *x = TunnelMessage{} + mi := &file_v1_syncservice_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TunnelMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TunnelMessage) ProtoMessage() {} + +func (x *TunnelMessage) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TunnelMessage.ProtoReflect.Descriptor instead. +func (*TunnelMessage) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{0} +} + +func (x *TunnelMessage) GetConnId() int64 { + if x != nil { + return x.ConnId + } + return 0 +} + +func (x *TunnelMessage) GetSeqno() int64 { + if x != nil { + return x.Seqno + } + return 0 +} + +func (x *TunnelMessage) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *TunnelMessage) GetClose() bool { + if x != nil { + return x.Close + } + return false +} + +func (x *TunnelMessage) GetPubkeyEcdhX25519() []byte { + if x != nil { + return x.PubkeyEcdhX25519 + } + return nil +} + +func (x *TunnelMessage) GetAny() *anypb.Any { + if x != nil { + return x.Any + } + return nil } type SyncStateStreamRequest struct { @@ -152,7 +238,7 @@ type SyncStateStreamRequest struct { func (x *SyncStateStreamRequest) Reset() { *x = SyncStateStreamRequest{} - mi := &file_v1_syncservice_proto_msgTypes[0] + mi := &file_v1_syncservice_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -164,7 +250,7 @@ func (x *SyncStateStreamRequest) String() string { func (*SyncStateStreamRequest) ProtoMessage() {} func (x *SyncStateStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[0] + mi := &file_v1_syncservice_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -177,7 +263,7 @@ func (x *SyncStateStreamRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncStateStreamRequest.ProtoReflect.Descriptor instead. func (*SyncStateStreamRequest) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{0} + return file_v1_syncservice_proto_rawDescGZIP(), []int{1} } func (x *SyncStateStreamRequest) GetSubscribe() bool { @@ -204,7 +290,7 @@ type PeerState struct { func (x *PeerState) Reset() { *x = PeerState{} - mi := &file_v1_syncservice_proto_msgTypes[1] + mi := &file_v1_syncservice_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -216,7 +302,7 @@ func (x *PeerState) String() string { func (*PeerState) ProtoMessage() {} func (x *PeerState) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[1] + mi := &file_v1_syncservice_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -229,7 +315,7 @@ func (x *PeerState) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerState.ProtoReflect.Descriptor instead. func (*PeerState) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{1} + return file_v1_syncservice_proto_rawDescGZIP(), []int{2} } func (x *PeerState) GetPeerInstanceId() string { @@ -309,7 +395,7 @@ type SyncStreamItem struct { func (x *SyncStreamItem) Reset() { *x = SyncStreamItem{} - mi := &file_v1_syncservice_proto_msgTypes[2] + mi := &file_v1_syncservice_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -321,7 +407,7 @@ func (x *SyncStreamItem) String() string { func (*SyncStreamItem) ProtoMessage() {} func (x *SyncStreamItem) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[2] + mi := &file_v1_syncservice_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -334,7 +420,7 @@ func (x *SyncStreamItem) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncStreamItem.ProtoReflect.Descriptor instead. func (*SyncStreamItem) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3} } func (m *SyncStreamItem) GetAction() isSyncStreamItem_Action { @@ -479,7 +565,7 @@ type RemoteConfig struct { func (x *RemoteConfig) Reset() { *x = RemoteConfig{} - mi := &file_v1_syncservice_proto_msgTypes[3] + mi := &file_v1_syncservice_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -491,7 +577,7 @@ func (x *RemoteConfig) String() string { func (*RemoteConfig) ProtoMessage() {} func (x *RemoteConfig) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[3] + mi := &file_v1_syncservice_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -504,7 +590,7 @@ func (x *RemoteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoteConfig.ProtoReflect.Descriptor instead. func (*RemoteConfig) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3} + return file_v1_syncservice_proto_rawDescGZIP(), []int{4} } func (x *RemoteConfig) GetModno() int32 { @@ -547,7 +633,7 @@ type SyncStreamItem_SyncActionHandshake struct { func (x *SyncStreamItem_SyncActionHandshake) Reset() { *x = SyncStreamItem_SyncActionHandshake{} - mi := &file_v1_syncservice_proto_msgTypes[4] + mi := &file_v1_syncservice_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -559,7 +645,7 @@ func (x *SyncStreamItem_SyncActionHandshake) String() string { func (*SyncStreamItem_SyncActionHandshake) ProtoMessage() {} func (x *SyncStreamItem_SyncActionHandshake) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[4] + mi := &file_v1_syncservice_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -572,7 +658,7 @@ func (x *SyncStreamItem_SyncActionHandshake) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionHandshake.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionHandshake) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 0} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 0} } func (x *SyncStreamItem_SyncActionHandshake) GetProtocolVersion() int64 { @@ -605,7 +691,7 @@ type SyncStreamItem_SyncActionHeartbeat struct { func (x *SyncStreamItem_SyncActionHeartbeat) Reset() { *x = SyncStreamItem_SyncActionHeartbeat{} - mi := &file_v1_syncservice_proto_msgTypes[5] + mi := &file_v1_syncservice_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -617,7 +703,7 @@ func (x *SyncStreamItem_SyncActionHeartbeat) String() string { func (*SyncStreamItem_SyncActionHeartbeat) ProtoMessage() {} func (x *SyncStreamItem_SyncActionHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[5] + mi := &file_v1_syncservice_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -630,7 +716,7 @@ func (x *SyncStreamItem_SyncActionHeartbeat) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionHeartbeat.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionHeartbeat) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 1} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 1} } type SyncStreamItem_SyncActionSendConfig struct { @@ -643,7 +729,7 @@ type SyncStreamItem_SyncActionSendConfig struct { func (x *SyncStreamItem_SyncActionSendConfig) Reset() { *x = SyncStreamItem_SyncActionSendConfig{} - mi := &file_v1_syncservice_proto_msgTypes[6] + mi := &file_v1_syncservice_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -655,7 +741,7 @@ func (x *SyncStreamItem_SyncActionSendConfig) String() string { func (*SyncStreamItem_SyncActionSendConfig) ProtoMessage() {} func (x *SyncStreamItem_SyncActionSendConfig) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[6] + mi := &file_v1_syncservice_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -668,7 +754,7 @@ func (x *SyncStreamItem_SyncActionSendConfig) ProtoReflect() protoreflect.Messag // Deprecated: Use SyncStreamItem_SyncActionSendConfig.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionSendConfig) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 2} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 2} } func (x *SyncStreamItem_SyncActionSendConfig) GetConfig() *RemoteConfig { @@ -691,7 +777,7 @@ type SyncStreamItem_SyncActionSetConfig struct { func (x *SyncStreamItem_SyncActionSetConfig) Reset() { *x = SyncStreamItem_SyncActionSetConfig{} - mi := &file_v1_syncservice_proto_msgTypes[7] + mi := &file_v1_syncservice_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -703,7 +789,7 @@ func (x *SyncStreamItem_SyncActionSetConfig) String() string { func (*SyncStreamItem_SyncActionSetConfig) ProtoMessage() {} func (x *SyncStreamItem_SyncActionSetConfig) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[7] + mi := &file_v1_syncservice_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -716,7 +802,7 @@ func (x *SyncStreamItem_SyncActionSetConfig) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionSetConfig.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionSetConfig) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 3} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 3} } func (x *SyncStreamItem_SyncActionSetConfig) GetRepos() []*Repo { @@ -758,7 +844,7 @@ type SyncStreamItem_SyncActionListResources struct { func (x *SyncStreamItem_SyncActionListResources) Reset() { *x = SyncStreamItem_SyncActionListResources{} - mi := &file_v1_syncservice_proto_msgTypes[8] + mi := &file_v1_syncservice_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -770,7 +856,7 @@ func (x *SyncStreamItem_SyncActionListResources) String() string { func (*SyncStreamItem_SyncActionListResources) ProtoMessage() {} func (x *SyncStreamItem_SyncActionListResources) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[8] + mi := &file_v1_syncservice_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -783,7 +869,7 @@ func (x *SyncStreamItem_SyncActionListResources) ProtoReflect() protoreflect.Mes // Deprecated: Use SyncStreamItem_SyncActionListResources.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionListResources) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 4} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 4} } func (x *SyncStreamItem_SyncActionListResources) GetRepoIds() []string { @@ -810,7 +896,7 @@ type SyncStreamItem_SyncActionConnectRepo struct { func (x *SyncStreamItem_SyncActionConnectRepo) Reset() { *x = SyncStreamItem_SyncActionConnectRepo{} - mi := &file_v1_syncservice_proto_msgTypes[9] + mi := &file_v1_syncservice_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -822,7 +908,7 @@ func (x *SyncStreamItem_SyncActionConnectRepo) String() string { func (*SyncStreamItem_SyncActionConnectRepo) ProtoMessage() {} func (x *SyncStreamItem_SyncActionConnectRepo) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[9] + mi := &file_v1_syncservice_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -835,7 +921,7 @@ func (x *SyncStreamItem_SyncActionConnectRepo) ProtoReflect() protoreflect.Messa // Deprecated: Use SyncStreamItem_SyncActionConnectRepo.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionConnectRepo) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 5} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 5} } func (x *SyncStreamItem_SyncActionConnectRepo) GetRepoId() string { @@ -861,7 +947,7 @@ type SyncStreamItem_SyncActionDiffOperations struct { func (x *SyncStreamItem_SyncActionDiffOperations) Reset() { *x = SyncStreamItem_SyncActionDiffOperations{} - mi := &file_v1_syncservice_proto_msgTypes[10] + mi := &file_v1_syncservice_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -873,7 +959,7 @@ func (x *SyncStreamItem_SyncActionDiffOperations) String() string { func (*SyncStreamItem_SyncActionDiffOperations) ProtoMessage() {} func (x *SyncStreamItem_SyncActionDiffOperations) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[10] + mi := &file_v1_syncservice_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -886,7 +972,7 @@ func (x *SyncStreamItem_SyncActionDiffOperations) ProtoReflect() protoreflect.Me // Deprecated: Use SyncStreamItem_SyncActionDiffOperations.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionDiffOperations) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 6} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 6} } func (x *SyncStreamItem_SyncActionDiffOperations) GetHaveOperationsSelector() *OpSelector { @@ -927,7 +1013,7 @@ type SyncStreamItem_SyncActionSendOperations struct { func (x *SyncStreamItem_SyncActionSendOperations) Reset() { *x = SyncStreamItem_SyncActionSendOperations{} - mi := &file_v1_syncservice_proto_msgTypes[11] + mi := &file_v1_syncservice_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -939,7 +1025,7 @@ func (x *SyncStreamItem_SyncActionSendOperations) String() string { func (*SyncStreamItem_SyncActionSendOperations) ProtoMessage() {} func (x *SyncStreamItem_SyncActionSendOperations) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[11] + mi := &file_v1_syncservice_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -952,7 +1038,7 @@ func (x *SyncStreamItem_SyncActionSendOperations) ProtoReflect() protoreflect.Me // Deprecated: Use SyncStreamItem_SyncActionSendOperations.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionSendOperations) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 7} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 7} } func (x *SyncStreamItem_SyncActionSendOperations) GetEvent() *OperationEvent { @@ -972,7 +1058,7 @@ type SyncStreamItem_SyncActionThrottle struct { func (x *SyncStreamItem_SyncActionThrottle) Reset() { *x = SyncStreamItem_SyncActionThrottle{} - mi := &file_v1_syncservice_proto_msgTypes[12] + mi := &file_v1_syncservice_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -984,7 +1070,7 @@ func (x *SyncStreamItem_SyncActionThrottle) String() string { func (*SyncStreamItem_SyncActionThrottle) ProtoMessage() {} func (x *SyncStreamItem_SyncActionThrottle) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[12] + mi := &file_v1_syncservice_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -997,7 +1083,7 @@ func (x *SyncStreamItem_SyncActionThrottle) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionThrottle.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionThrottle) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 8} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 8} } func (x *SyncStreamItem_SyncActionThrottle) GetDelayMs() int64 { @@ -1019,7 +1105,7 @@ type SyncStreamItem_SyncEstablishSharedSecret struct { func (x *SyncStreamItem_SyncEstablishSharedSecret) Reset() { *x = SyncStreamItem_SyncEstablishSharedSecret{} - mi := &file_v1_syncservice_proto_msgTypes[13] + mi := &file_v1_syncservice_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1031,7 +1117,7 @@ func (x *SyncStreamItem_SyncEstablishSharedSecret) String() string { func (*SyncStreamItem_SyncEstablishSharedSecret) ProtoMessage() {} func (x *SyncStreamItem_SyncEstablishSharedSecret) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[13] + mi := &file_v1_syncservice_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1044,7 +1130,7 @@ func (x *SyncStreamItem_SyncEstablishSharedSecret) ProtoReflect() protoreflect.M // Deprecated: Use SyncStreamItem_SyncEstablishSharedSecret.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncEstablishSharedSecret) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2, 9} + return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 9} } func (x *SyncStreamItem_SyncEstablishSharedSecret) GetEd25519() string { @@ -1069,190 +1155,208 @@ var file_v1_syncservice_proto_rawDesc = []byte{ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x16, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, - 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x22, 0xd7, 0x02, 0x0a, 0x09, 0x50, - 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x65, 0x65, 0x72, - 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x69, - 0x64, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, - 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6b, 0x6e, - 0x6f, 0x77, 0x6e, 0x50, 0x6c, 0x61, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, - 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6b, - 0x6e, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x35, 0x0a, 0x0d, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x32, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, - 0x61, 0x74, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x13, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x4d, 0x69, - 0x6c, 0x6c, 0x69, 0x73, 0x22, 0xcf, 0x0e, 0x0a, 0x0e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x3a, 0x0a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x48, 0x00, - 0x52, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x68, - 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, - 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, - 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, - 0x65, 0x61, 0x74, 0x12, 0x56, 0x0a, 0x0f, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, - 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x66, 0x66, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x69, 0x66, - 0x66, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x56, 0x0a, 0x0f, 0x73, - 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x15, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x4a, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, - 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, - 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x47, 0x0a, 0x0a, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x18, 0x20, + 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x01, + 0x0a, 0x0d, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x65, 0x71, 0x6e, + 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x65, 0x71, 0x6e, 0x6f, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x5f, 0x65, 0x63, 0x64, 0x68, 0x5f, 0x78, 0x32, 0x35, 0x35, 0x31, 0x39, 0x18, 0x64, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x45, 0x63, 0x64, 0x68, + 0x58, 0x32, 0x35, 0x35, 0x31, 0x39, 0x12, 0x27, 0x0a, 0x03, 0x61, 0x6e, 0x79, 0x18, 0xe8, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x03, 0x61, 0x6e, 0x79, 0x22, + 0x36, 0x0a, 0x16, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x22, 0xd7, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x69, 0x64, 0x12, 0x2d, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, + 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x6c, + 0x61, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, + 0x50, 0x6c, 0x61, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x72, + 0x65, 0x70, 0x6f, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, + 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x35, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, + 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, + 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, 0x61, + 0x73, 0x74, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, + 0x73, 0x22, 0xcf, 0x0e, 0x0a, 0x0e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x3a, 0x0a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, + 0x00, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x46, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x09, 0x73, - 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x53, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74, - 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0d, - 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x44, 0x0a, - 0x08, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x25, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x08, 0x74, 0x68, 0x72, 0x6f, 0x74, - 0x74, 0x6c, 0x65, 0x1a, 0xa2, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x15, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, - 0x40, 0x0a, 0x14, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x1a, 0xa5, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, - 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, - 0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6c, 0x61, 0x6e, - 0x73, 0x54, 0x6f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x1a, 0x4f, 0x0a, 0x17, 0x53, 0x79, 0x6e, - 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x30, 0x0a, 0x15, 0x53, 0x79, - 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x1a, 0xf5, 0x01, 0x0a, - 0x18, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x66, 0x66, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x18, 0x68, 0x61, 0x76, - 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, - 0x2e, 0x4f, 0x70, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x16, 0x68, 0x61, 0x76, - 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, - 0x10, 0x68, 0x61, 0x76, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x73, 0x12, 0x32, 0x0a, 0x15, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, - 0x52, 0x13, 0x68, 0x61, 0x76, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, - 0x6f, 0x64, 0x6e, 0x6f, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x03, 0x52, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x44, 0x0a, 0x18, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x28, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x2f, 0x0a, 0x12, 0x53, 0x79, - 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, - 0x12, 0x19, 0x0a, 0x08, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x1a, 0x38, 0x0a, 0x19, 0x53, - 0x79, 0x6e, 0x63, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x68, 0x61, 0x72, - 0x65, 0x64, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x65, 0x64, 0x32, 0x35, - 0x35, 0x31, 0x39, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x64, 0x32, 0x35, 0x35, - 0x31, 0x39, 0x70, 0x75, 0x62, 0x22, 0xb4, 0x01, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, - 0x18, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, - 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, - 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, - 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, - 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, - 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, - 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, - 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x04, 0x42, 0x08, 0x0a, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x0c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x52, + 0x6f, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x48, 0x00, 0x52, 0x09, 0x68, + 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, + 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, + 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, + 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x12, 0x56, 0x0a, 0x0f, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, + 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x66, 0x66, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x69, 0x66, 0x66, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x56, 0x0a, 0x0f, 0x73, 0x65, 0x6e, 0x64, + 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, + 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x4a, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, + 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x47, 0x0a, 0x0a, + 0x73, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x09, 0x73, 0x65, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x53, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, + 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x44, 0x0a, 0x08, 0x74, 0x68, + 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, + 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x6f, + 0x74, 0x74, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x08, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, + 0x1a, 0xa2, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x15, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x14, + 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0xa5, + 0x01, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, - 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2a, 0xa0, 0x02, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, - 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, + 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x5f, + 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0d, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x26, + 0x0a, 0x0f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x54, 0x6f, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x1a, 0x4f, 0x0a, 0x17, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x73, 0x12, 0x19, 0x0a, 0x08, + 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x30, 0x0a, 0x15, 0x53, 0x79, 0x6e, 0x63, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x1a, 0xf5, 0x01, 0x0a, 0x18, 0x53, 0x79, + 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x66, 0x66, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x18, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x16, 0x68, 0x61, 0x76, 0x65, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x12, 0x2c, 0x0a, 0x12, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x68, 0x61, + 0x76, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x32, + 0x0a, 0x15, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x13, 0x68, + 0x61, 0x76, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x6e, + 0x6f, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x03, 0x52, 0x11, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x1a, 0x44, 0x0a, 0x18, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x0a, + 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, + 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x2f, 0x0a, 0x12, 0x53, 0x79, 0x6e, 0x63, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x1a, 0x38, 0x0a, 0x19, 0x53, 0x79, 0x6e, 0x63, + 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x70, + 0x75, 0x62, 0x22, 0xb4, 0x01, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x4e, + 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, + 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4e, 0x4f, + 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x04, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x0c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, + 0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c, + 0x61, 0x6e, 0x73, 0x2a, 0xa0, 0x02, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, + 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, + 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, + 0x45, 0x54, 0x52, 0x59, 0x5f, 0x57, 0x41, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x0a, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, - 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, - 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, - 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1f, 0x0a, - 0x1b, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, - 0x45, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x57, 0x41, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1f, - 0x0a, 0x1b, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x0a, 0x12, - 0x23, 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, - 0x4f, 0x4c, 0x10, 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, - 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, - 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x0c, 0x32, 0x4b, 0x0a, 0x13, 0x42, 0x61, 0x63, - 0x6b, 0x72, 0x65, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x34, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, - 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x1a, 0x12, 0x2e, 0x76, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x10, + 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, + 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x0c, 0x32, 0x45, 0x0a, 0x0d, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x12, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x32, 0x4b, 0x0a, + 0x13, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, - 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x32, 0x64, 0x0a, 0x18, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, - 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x48, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x6e, - 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1a, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, - 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, - 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x32, 0x64, 0x0a, 0x18, 0x42, 0x61, + 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, + 0x72, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x12, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x00, 0x30, 0x01, + 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, + 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1268,61 +1372,66 @@ func file_v1_syncservice_proto_rawDescGZIP() []byte { } var file_v1_syncservice_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_v1_syncservice_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_v1_syncservice_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_v1_syncservice_proto_goTypes = []any{ (SyncConnectionState)(0), // 0: v1.SyncConnectionState (SyncStreamItem_RepoConnectionState)(0), // 1: v1.SyncStreamItem.RepoConnectionState - (*SyncStateStreamRequest)(nil), // 2: v1.SyncStateStreamRequest - (*PeerState)(nil), // 3: v1.PeerState - (*SyncStreamItem)(nil), // 4: v1.SyncStreamItem - (*RemoteConfig)(nil), // 5: v1.RemoteConfig - (*SyncStreamItem_SyncActionHandshake)(nil), // 6: v1.SyncStreamItem.SyncActionHandshake - (*SyncStreamItem_SyncActionHeartbeat)(nil), // 7: v1.SyncStreamItem.SyncActionHeartbeat - (*SyncStreamItem_SyncActionSendConfig)(nil), // 8: v1.SyncStreamItem.SyncActionSendConfig - (*SyncStreamItem_SyncActionSetConfig)(nil), // 9: v1.SyncStreamItem.SyncActionSetConfig - (*SyncStreamItem_SyncActionListResources)(nil), // 10: v1.SyncStreamItem.SyncActionListResources - (*SyncStreamItem_SyncActionConnectRepo)(nil), // 11: v1.SyncStreamItem.SyncActionConnectRepo - (*SyncStreamItem_SyncActionDiffOperations)(nil), // 12: v1.SyncStreamItem.SyncActionDiffOperations - (*SyncStreamItem_SyncActionSendOperations)(nil), // 13: v1.SyncStreamItem.SyncActionSendOperations - (*SyncStreamItem_SyncActionThrottle)(nil), // 14: v1.SyncStreamItem.SyncActionThrottle - (*SyncStreamItem_SyncEstablishSharedSecret)(nil), // 15: v1.SyncStreamItem.SyncEstablishSharedSecret - (*SignedMessage)(nil), // 16: v1.SignedMessage - (*Repo)(nil), // 17: v1.Repo - (*Plan)(nil), // 18: v1.Plan - (*PublicKey)(nil), // 19: v1.PublicKey - (*OpSelector)(nil), // 20: v1.OpSelector - (*OperationEvent)(nil), // 21: v1.OperationEvent + (*TunnelMessage)(nil), // 2: v1.TunnelMessage + (*SyncStateStreamRequest)(nil), // 3: v1.SyncStateStreamRequest + (*PeerState)(nil), // 4: v1.PeerState + (*SyncStreamItem)(nil), // 5: v1.SyncStreamItem + (*RemoteConfig)(nil), // 6: v1.RemoteConfig + (*SyncStreamItem_SyncActionHandshake)(nil), // 7: v1.SyncStreamItem.SyncActionHandshake + (*SyncStreamItem_SyncActionHeartbeat)(nil), // 8: v1.SyncStreamItem.SyncActionHeartbeat + (*SyncStreamItem_SyncActionSendConfig)(nil), // 9: v1.SyncStreamItem.SyncActionSendConfig + (*SyncStreamItem_SyncActionSetConfig)(nil), // 10: v1.SyncStreamItem.SyncActionSetConfig + (*SyncStreamItem_SyncActionListResources)(nil), // 11: v1.SyncStreamItem.SyncActionListResources + (*SyncStreamItem_SyncActionConnectRepo)(nil), // 12: v1.SyncStreamItem.SyncActionConnectRepo + (*SyncStreamItem_SyncActionDiffOperations)(nil), // 13: v1.SyncStreamItem.SyncActionDiffOperations + (*SyncStreamItem_SyncActionSendOperations)(nil), // 14: v1.SyncStreamItem.SyncActionSendOperations + (*SyncStreamItem_SyncActionThrottle)(nil), // 15: v1.SyncStreamItem.SyncActionThrottle + (*SyncStreamItem_SyncEstablishSharedSecret)(nil), // 16: v1.SyncStreamItem.SyncEstablishSharedSecret + (*anypb.Any)(nil), // 17: google.protobuf.Any + (*SignedMessage)(nil), // 18: v1.SignedMessage + (*Repo)(nil), // 19: v1.Repo + (*Plan)(nil), // 20: v1.Plan + (*PublicKey)(nil), // 21: v1.PublicKey + (*OpSelector)(nil), // 22: v1.OpSelector + (*OperationEvent)(nil), // 23: v1.OperationEvent } var file_v1_syncservice_proto_depIdxs = []int32{ - 0, // 0: v1.PeerState.state:type_name -> v1.SyncConnectionState - 5, // 1: v1.PeerState.remote_config:type_name -> v1.RemoteConfig - 16, // 2: v1.SyncStreamItem.signed_message:type_name -> v1.SignedMessage - 6, // 3: v1.SyncStreamItem.handshake:type_name -> v1.SyncStreamItem.SyncActionHandshake - 7, // 4: v1.SyncStreamItem.heartbeat:type_name -> v1.SyncStreamItem.SyncActionHeartbeat - 12, // 5: v1.SyncStreamItem.diff_operations:type_name -> v1.SyncStreamItem.SyncActionDiffOperations - 13, // 6: v1.SyncStreamItem.send_operations:type_name -> v1.SyncStreamItem.SyncActionSendOperations - 8, // 7: v1.SyncStreamItem.send_config:type_name -> v1.SyncStreamItem.SyncActionSendConfig - 9, // 8: v1.SyncStreamItem.set_config:type_name -> v1.SyncStreamItem.SyncActionSetConfig - 10, // 9: v1.SyncStreamItem.list_resources:type_name -> v1.SyncStreamItem.SyncActionListResources - 14, // 10: v1.SyncStreamItem.throttle:type_name -> v1.SyncStreamItem.SyncActionThrottle - 17, // 11: v1.RemoteConfig.repos:type_name -> v1.Repo - 18, // 12: v1.RemoteConfig.plans:type_name -> v1.Plan - 19, // 13: v1.SyncStreamItem.SyncActionHandshake.public_key:type_name -> v1.PublicKey - 16, // 14: v1.SyncStreamItem.SyncActionHandshake.instance_id:type_name -> v1.SignedMessage - 5, // 15: v1.SyncStreamItem.SyncActionSendConfig.config:type_name -> v1.RemoteConfig - 17, // 16: v1.SyncStreamItem.SyncActionSetConfig.repos:type_name -> v1.Repo - 18, // 17: v1.SyncStreamItem.SyncActionSetConfig.plans:type_name -> v1.Plan - 20, // 18: v1.SyncStreamItem.SyncActionDiffOperations.have_operations_selector:type_name -> v1.OpSelector - 21, // 19: v1.SyncStreamItem.SyncActionSendOperations.event:type_name -> v1.OperationEvent - 4, // 20: v1.BackrestSyncService.Sync:input_type -> v1.SyncStreamItem - 2, // 21: v1.BackrestSyncStateService.GetPeerSyncStatesStream:input_type -> v1.SyncStateStreamRequest - 4, // 22: v1.BackrestSyncService.Sync:output_type -> v1.SyncStreamItem - 3, // 23: v1.BackrestSyncStateService.GetPeerSyncStatesStream:output_type -> v1.PeerState - 22, // [22:24] is the sub-list for method output_type - 20, // [20:22] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 17, // 0: v1.TunnelMessage.any:type_name -> google.protobuf.Any + 0, // 1: v1.PeerState.state:type_name -> v1.SyncConnectionState + 6, // 2: v1.PeerState.remote_config:type_name -> v1.RemoteConfig + 18, // 3: v1.SyncStreamItem.signed_message:type_name -> v1.SignedMessage + 7, // 4: v1.SyncStreamItem.handshake:type_name -> v1.SyncStreamItem.SyncActionHandshake + 8, // 5: v1.SyncStreamItem.heartbeat:type_name -> v1.SyncStreamItem.SyncActionHeartbeat + 13, // 6: v1.SyncStreamItem.diff_operations:type_name -> v1.SyncStreamItem.SyncActionDiffOperations + 14, // 7: v1.SyncStreamItem.send_operations:type_name -> v1.SyncStreamItem.SyncActionSendOperations + 9, // 8: v1.SyncStreamItem.send_config:type_name -> v1.SyncStreamItem.SyncActionSendConfig + 10, // 9: v1.SyncStreamItem.set_config:type_name -> v1.SyncStreamItem.SyncActionSetConfig + 11, // 10: v1.SyncStreamItem.list_resources:type_name -> v1.SyncStreamItem.SyncActionListResources + 15, // 11: v1.SyncStreamItem.throttle:type_name -> v1.SyncStreamItem.SyncActionThrottle + 19, // 12: v1.RemoteConfig.repos:type_name -> v1.Repo + 20, // 13: v1.RemoteConfig.plans:type_name -> v1.Plan + 21, // 14: v1.SyncStreamItem.SyncActionHandshake.public_key:type_name -> v1.PublicKey + 18, // 15: v1.SyncStreamItem.SyncActionHandshake.instance_id:type_name -> v1.SignedMessage + 6, // 16: v1.SyncStreamItem.SyncActionSendConfig.config:type_name -> v1.RemoteConfig + 19, // 17: v1.SyncStreamItem.SyncActionSetConfig.repos:type_name -> v1.Repo + 20, // 18: v1.SyncStreamItem.SyncActionSetConfig.plans:type_name -> v1.Plan + 22, // 19: v1.SyncStreamItem.SyncActionDiffOperations.have_operations_selector:type_name -> v1.OpSelector + 23, // 20: v1.SyncStreamItem.SyncActionSendOperations.event:type_name -> v1.OperationEvent + 2, // 21: v1.TunnelService.Tunnel:input_type -> v1.TunnelMessage + 5, // 22: v1.BackrestSyncService.Sync:input_type -> v1.SyncStreamItem + 3, // 23: v1.BackrestSyncStateService.GetPeerSyncStatesStream:input_type -> v1.SyncStateStreamRequest + 2, // 24: v1.TunnelService.Tunnel:output_type -> v1.TunnelMessage + 5, // 25: v1.BackrestSyncService.Sync:output_type -> v1.SyncStreamItem + 4, // 26: v1.BackrestSyncStateService.GetPeerSyncStatesStream:output_type -> v1.PeerState + 24, // [24:27] is the sub-list for method output_type + 21, // [21:24] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_v1_syncservice_proto_init() } @@ -1335,7 +1444,7 @@ func file_v1_syncservice_proto_init() { file_v1_restic_proto_init() file_v1_service_proto_init() file_v1_operations_proto_init() - file_v1_syncservice_proto_msgTypes[2].OneofWrappers = []any{ + file_v1_syncservice_proto_msgTypes[3].OneofWrappers = []any{ (*SyncStreamItem_SignedMessage)(nil), (*SyncStreamItem_Handshake)(nil), (*SyncStreamItem_Heartbeat)(nil), @@ -1352,9 +1461,9 @@ func file_v1_syncservice_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_syncservice_proto_rawDesc, NumEnums: 2, - NumMessages: 14, + NumMessages: 15, NumExtensions: 0, - NumServices: 2, + NumServices: 3, }, GoTypes: file_v1_syncservice_proto_goTypes, DependencyIndexes: file_v1_syncservice_proto_depIdxs, diff --git a/gen/go/v1/syncservice_grpc.pb.go b/gen/go/v1/syncservice_grpc.pb.go index a8bd0bc9..eda1055f 100644 --- a/gen/go/v1/syncservice_grpc.pb.go +++ b/gen/go/v1/syncservice_grpc.pb.go @@ -18,6 +18,106 @@ import ( // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 +const ( + TunnelService_Tunnel_FullMethodName = "/v1.TunnelService/Tunnel" +) + +// TunnelServiceClient is the client API for TunnelService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// TunnelService allows inverted communicatons from server to client where the server can initiate requests to the client. +type TunnelServiceClient interface { + Tunnel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TunnelMessage, TunnelMessage], error) +} + +type tunnelServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTunnelServiceClient(cc grpc.ClientConnInterface) TunnelServiceClient { + return &tunnelServiceClient{cc} +} + +func (c *tunnelServiceClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TunnelMessage, TunnelMessage], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &TunnelService_ServiceDesc.Streams[0], TunnelService_Tunnel_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TunnelMessage, TunnelMessage]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type TunnelService_TunnelClient = grpc.BidiStreamingClient[TunnelMessage, TunnelMessage] + +// TunnelServiceServer is the server API for TunnelService service. +// All implementations must embed UnimplementedTunnelServiceServer +// for forward compatibility. +// +// TunnelService allows inverted communicatons from server to client where the server can initiate requests to the client. +type TunnelServiceServer interface { + Tunnel(grpc.BidiStreamingServer[TunnelMessage, TunnelMessage]) error + mustEmbedUnimplementedTunnelServiceServer() +} + +// UnimplementedTunnelServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedTunnelServiceServer struct{} + +func (UnimplementedTunnelServiceServer) Tunnel(grpc.BidiStreamingServer[TunnelMessage, TunnelMessage]) error { + return status.Errorf(codes.Unimplemented, "method Tunnel not implemented") +} +func (UnimplementedTunnelServiceServer) mustEmbedUnimplementedTunnelServiceServer() {} +func (UnimplementedTunnelServiceServer) testEmbeddedByValue() {} + +// UnsafeTunnelServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TunnelServiceServer will +// result in compilation errors. +type UnsafeTunnelServiceServer interface { + mustEmbedUnimplementedTunnelServiceServer() +} + +func RegisterTunnelServiceServer(s grpc.ServiceRegistrar, srv TunnelServiceServer) { + // If the following call pancis, it indicates UnimplementedTunnelServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&TunnelService_ServiceDesc, srv) +} + +func _TunnelService_Tunnel_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TunnelServiceServer).Tunnel(&grpc.GenericServerStream[TunnelMessage, TunnelMessage]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type TunnelService_TunnelServer = grpc.BidiStreamingServer[TunnelMessage, TunnelMessage] + +// TunnelService_ServiceDesc is the grpc.ServiceDesc for TunnelService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TunnelService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "v1.TunnelService", + HandlerType: (*TunnelServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Tunnel", + Handler: _TunnelService_Tunnel_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "v1/syncservice.proto", +} + const ( BackrestSyncService_Sync_FullMethodName = "/v1.BackrestSyncService/Sync" ) diff --git a/gen/go/v1/v1connect/syncservice.connect.go b/gen/go/v1/v1connect/syncservice.connect.go index e32fb0b8..2290eb6d 100644 --- a/gen/go/v1/v1connect/syncservice.connect.go +++ b/gen/go/v1/v1connect/syncservice.connect.go @@ -21,6 +21,8 @@ import ( const _ = connect.IsAtLeastVersion1_13_0 const ( + // TunnelServiceName is the fully-qualified name of the TunnelService service. + TunnelServiceName = "v1.TunnelService" // BackrestSyncServiceName is the fully-qualified name of the BackrestSyncService service. BackrestSyncServiceName = "v1.BackrestSyncService" // BackrestSyncStateServiceName is the fully-qualified name of the BackrestSyncStateService service. @@ -35,6 +37,8 @@ const ( // reflection-formatted method names, remove the leading slash and convert the remaining slash to a // period. const ( + // TunnelServiceTunnelProcedure is the fully-qualified name of the TunnelService's Tunnel RPC. + TunnelServiceTunnelProcedure = "/v1.TunnelService/Tunnel" // BackrestSyncServiceSyncProcedure is the fully-qualified name of the BackrestSyncService's Sync // RPC. BackrestSyncServiceSyncProcedure = "/v1.BackrestSyncService/Sync" @@ -45,12 +49,82 @@ const ( // These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. var ( + tunnelServiceServiceDescriptor = v1.File_v1_syncservice_proto.Services().ByName("TunnelService") + tunnelServiceTunnelMethodDescriptor = tunnelServiceServiceDescriptor.Methods().ByName("Tunnel") backrestSyncServiceServiceDescriptor = v1.File_v1_syncservice_proto.Services().ByName("BackrestSyncService") backrestSyncServiceSyncMethodDescriptor = backrestSyncServiceServiceDescriptor.Methods().ByName("Sync") backrestSyncStateServiceServiceDescriptor = v1.File_v1_syncservice_proto.Services().ByName("BackrestSyncStateService") backrestSyncStateServiceGetPeerSyncStatesStreamMethodDescriptor = backrestSyncStateServiceServiceDescriptor.Methods().ByName("GetPeerSyncStatesStream") ) +// TunnelServiceClient is a client for the v1.TunnelService service. +type TunnelServiceClient interface { + Tunnel(context.Context) *connect.BidiStreamForClient[v1.TunnelMessage, v1.TunnelMessage] +} + +// NewTunnelServiceClient constructs a client for the v1.TunnelService service. By default, it uses +// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends +// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewTunnelServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) TunnelServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &tunnelServiceClient{ + tunnel: connect.NewClient[v1.TunnelMessage, v1.TunnelMessage]( + httpClient, + baseURL+TunnelServiceTunnelProcedure, + connect.WithSchema(tunnelServiceTunnelMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// tunnelServiceClient implements TunnelServiceClient. +type tunnelServiceClient struct { + tunnel *connect.Client[v1.TunnelMessage, v1.TunnelMessage] +} + +// Tunnel calls v1.TunnelService.Tunnel. +func (c *tunnelServiceClient) Tunnel(ctx context.Context) *connect.BidiStreamForClient[v1.TunnelMessage, v1.TunnelMessage] { + return c.tunnel.CallBidiStream(ctx) +} + +// TunnelServiceHandler is an implementation of the v1.TunnelService service. +type TunnelServiceHandler interface { + Tunnel(context.Context, *connect.BidiStream[v1.TunnelMessage, v1.TunnelMessage]) error +} + +// NewTunnelServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewTunnelServiceHandler(svc TunnelServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + tunnelServiceTunnelHandler := connect.NewBidiStreamHandler( + TunnelServiceTunnelProcedure, + svc.Tunnel, + connect.WithSchema(tunnelServiceTunnelMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/v1.TunnelService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case TunnelServiceTunnelProcedure: + tunnelServiceTunnelHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedTunnelServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedTunnelServiceHandler struct{} + +func (UnimplementedTunnelServiceHandler) Tunnel(context.Context, *connect.BidiStream[v1.TunnelMessage, v1.TunnelMessage]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("v1.TunnelService.Tunnel is not implemented")) +} + // BackrestSyncServiceClient is a client for the v1.BackrestSyncService service. type BackrestSyncServiceClient interface { Sync(context.Context) *connect.BidiStreamForClient[v1.SyncStreamItem, v1.SyncStreamItem] diff --git a/internal/api/syncapi/syncapi_test.go b/internal/api/syncapi/syncapi_test.go index d9d94820..a91ec1dd 100644 --- a/internal/api/syncapi/syncapi_test.go +++ b/internal/api/syncapi/syncapi_test.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "net" "net/http" "path/filepath" "slices" @@ -74,8 +73,8 @@ func TestConnectionSucceeds(t *testing.T) { testutil.InstallZapLogger(t) ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) - peerHostAddr := allocBindAddrForTest(t) - peerClientAddr := allocBindAddrForTest(t) + peerHostAddr := testutil.AllocOpenBindAddr(t) + peerClientAddr := testutil.AllocOpenBindAddr(t) peerHostConfig := &v1.Config{ Instance: defaultHostID, @@ -120,8 +119,8 @@ func TestConnectionBadKeyRejected(t *testing.T) { testutil.InstallZapLogger(t) ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) - peerHostAddr := allocBindAddrForTest(t) - peerClientAddr := allocBindAddrForTest(t) + peerHostAddr := testutil.AllocOpenBindAddr(t) + peerClientAddr := testutil.AllocOpenBindAddr(t) // Host has identity1, and authorizes no one. peerHostConfig := &v1.Config{ @@ -162,8 +161,8 @@ func TestSyncConfigChange(t *testing.T) { testutil.InstallZapLogger(t) ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) - peerHostAddr := allocBindAddrForTest(t) - peerClientAddr := allocBindAddrForTest(t) + peerHostAddr := testutil.AllocOpenBindAddr(t) + peerClientAddr := testutil.AllocOpenBindAddr(t) peerHostConfig := &v1.Config{ Instance: defaultHostID, @@ -254,8 +253,8 @@ func TestSimpleOperationSync(t *testing.T) { testutil.InstallZapLogger(t) ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) - peerHostAddr := allocBindAddrForTest(t) - peerClientAddr := allocBindAddrForTest(t) + peerHostAddr := testutil.AllocOpenBindAddr(t) + peerClientAddr := testutil.AllocOpenBindAddr(t) peerHostConfig := &v1.Config{ Instance: defaultHostID, @@ -378,8 +377,8 @@ func TestSyncMutations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - peerHostAddr := allocBindAddrForTest(t) - peerClientAddr := allocBindAddrForTest(t) + peerHostAddr := testutil.AllocOpenBindAddr(t) + peerClientAddr := testutil.AllocOpenBindAddr(t) peerHostConfig := &v1.Config{ Instance: defaultHostID, @@ -658,24 +657,6 @@ func tryConnect(t *testing.T, ctx context.Context, peer *peerUnderTest, hostPeer waitForConnectionState(t, ctx, peer, hostPeer, v1.SyncConnectionState_CONNECTION_STATE_CONNECTED) } -func allocBindAddrForTest(t *testing.T) string { - t.Helper() - - listener, err := net.Listen("tcp", ":0") - if err != nil { - t.Fatalf("failed to listen: %v", err) - } - defer listener.Close() - - // Get the port number from the listener - _, port, err := net.SplitHostPort(listener.Addr().String()) - if err != nil { - t.Fatalf("failed to split host and port: %v", err) - } - - return "127.0.0.1:" + port -} - func runSyncAPIWithCtx(ctx context.Context, peer *peerUnderTest, bindAddr string) { mux := http.NewServeMux() syncHandler := NewBackrestSyncHandler(peer.manager) diff --git a/internal/api/syncapi/tunnel/connstate.go b/internal/api/syncapi/tunnel/connstate.go new file mode 100644 index 00000000..4f9c1df9 --- /dev/null +++ b/internal/api/syncapi/tunnel/connstate.go @@ -0,0 +1,205 @@ +package tunnel + +import ( + "fmt" + "net" + "os" + "sync" + "sync/atomic" + "time" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "go.uber.org/zap" +) + +type connState struct { + connId int64 + nextWriteSeqno int64 + stream stream // stream it's associated with + logger *zap.Logger // can be nil + + closed atomic.Bool + closedCh chan struct{} + + sendMu sync.Mutex + seqno int64 + + readsMu sync.Mutex + reads chan []byte + readsBuf []byte + + readDeadlineMu sync.Mutex + readDeadlineTimer *time.Timer + readDeadlineChan chan struct{} +} + +var _ net.Conn = (*connState)(nil) + +func newConnState(stream stream, connId int64, logger *zap.Logger) *connState { + if logger != nil { + logger = logger.Named("connState").With( + zap.Int64("connId", connId)) + } + return &connState{ + connId: connId, + seqno: 0, + stream: stream, + logger: logger, + + closedCh: make(chan struct{}), + + reads: make(chan []byte, 10), // Buffered channel to hold a few writes worth of messages before blocking + readsBuf: nil, + + readDeadlineChan: make(chan struct{}), + } +} + +// sendOpenPacket is only sent by the end starting the connection, not by the end that's opening it in response to a receive. +func (c *connState) sendOpenPacket() error { + if c.closed.Load() { + return net.ErrClosed + } + + if c.logger != nil { + c.logger.Info("sending open packet") + } + + return c.stream.Send(&v1.TunnelMessage{ + ConnId: c.connId, + Seqno: 0, // Open packet has Seqno 0 + }) +} + +func (c *connState) Write(data []byte) (int, error) { + c.sendMu.Lock() + defer c.sendMu.Unlock() + if c.closed.Load() { + return 0, net.ErrClosed + } + + c.seqno++ + if c.logger != nil { + c.logger.Debug("writing data", zap.Int("dataLength", len(data)), zap.Int64("seqno", c.seqno)) + } + err := c.stream.Send(&v1.TunnelMessage{ + ConnId: c.connId, + Data: data, + Seqno: c.nextWriteSeqno, + }) + if err != nil { + if c.logger != nil { + c.logger.Error("failed to send data", zap.Int64("connId", c.connId), zap.Error(err)) + } + return 0, err + } + return len(data), nil +} + +func (c *connState) Read(b []byte) (int, error) { + c.readsMu.Lock() + defer c.readsMu.Unlock() + + if len(c.readsBuf) > 0 { + n := copy(b, c.readsBuf) + c.readsBuf = c.readsBuf[n:] + return n, nil + } + + c.readDeadlineMu.Lock() + readDeadlineChan := c.readDeadlineChan + c.readDeadlineMu.Unlock() + + select { + case data := <-c.reads: + if len(data) == 0 { + return 0, net.ErrClosed + } + if c.logger != nil { + c.logger.Debug("conn state c.reads received packet", zap.Int("dataLength", len(data))) + } + n := copy(b, data) + if n < len(data) { + c.readsBuf = data[n:] + } + return n, nil + case <-readDeadlineChan: + if c.logger != nil { + c.logger.Info("read deadline reached") + } + return 0, os.ErrDeadlineExceeded + case <-c.closedCh: + if c.logger != nil { + c.logger.Info("connection closed while waiting for read") + } + return 0, net.ErrClosed + } +} + +func (c *connState) Close() error { + if !c.closed.Swap(true) { + if c.logger != nil { + c.logger.Info("closing connection") + } + close(c.closedCh) + if err := c.stream.Send(&v1.TunnelMessage{ + ConnId: c.connId, + Close: true, + }); err != nil { + if c.logger != nil { + c.logger.Error("failed to send close message", zap.Error(err)) + } + return fmt.Errorf("send close message: %w", err) + } + if c.logger != nil { + c.logger.Info("connection closed successfully") + } + } else if c.logger != nil { + c.logger.Warn("close called on already closed connection") + } + return nil +} + +func (c *connState) LocalAddr() net.Addr { + // Return the local address of the tunnel connection + return &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } +} + +func (c *connState) RemoteAddr() net.Addr { + // Return the remote address of the tunnel connection + return &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } +} + +func (c *connState) SetDeadline(t time.Time) error { + c.SetReadDeadline(t) + c.SetWriteDeadline(t) + return nil +} + +func (c *connState) SetReadDeadline(t time.Time) error { + c.readDeadlineMu.Lock() + defer c.readDeadlineMu.Unlock() + if c.readDeadlineTimer != nil { + c.readDeadlineTimer.Stop() + } + if t.IsZero() { + c.readDeadlineTimer = nil + return nil + } + c.readDeadlineTimer = time.AfterFunc(time.Until(t), func() { + close(c.readDeadlineChan) + }) + c.readDeadlineChan = make(chan struct{}) // Reset the channel to ensure it is ready for the next deadline + return nil +} + +func (c *connState) SetWriteDeadline(t time.Time) error { + // TODO: simply does not work right now + return nil +} diff --git a/internal/api/syncapi/tunnel/httpclient.go b/internal/api/syncapi/tunnel/httpclient.go new file mode 100644 index 00000000..fbb3d0c5 --- /dev/null +++ b/internal/api/syncapi/tunnel/httpclient.go @@ -0,0 +1,27 @@ +package tunnel + +import ( + "crypto/tls" + "net" + "net/http" + "time" + + "go.uber.org/zap" + "golang.org/x/net/http2" +) + +// NewInsecureHttpClient creates a new HTTP client that allows insecure connections (HTTP/2 over plain TCP). +// This is useful for testing or when you don't need TLS. +func NewInsecureHttpClient() *http.Client { + return &http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { + zap.L().Sugar().Debugf("Dialing %s on %s", network, addr) + return net.Dial(network, addr) + }, + IdleConnTimeout: 300 * time.Second, + ReadIdleTimeout: 60 * time.Second, + }, + } +} diff --git a/internal/api/syncapi/tunnel/listener.go b/internal/api/syncapi/tunnel/listener.go new file mode 100644 index 00000000..822ec54c --- /dev/null +++ b/internal/api/syncapi/tunnel/listener.go @@ -0,0 +1,59 @@ +package tunnel + +import ( + "net" + "sync" +) + +// tunnelListener is a net.Listener that makes it possible to feed net.Conn instances into it. +type ConnectionProvider struct { + doClose sync.Once + isOpen chan struct{} + nextConn chan net.Conn +} + +var _ net.Listener = (*ConnectionProvider)(nil) + +func NewConnectionProvider(bufferSize int) *ConnectionProvider { + return &ConnectionProvider{ + isOpen: make(chan struct{}), + nextConn: make(chan net.Conn, bufferSize), // Buffered channel to hold incoming connections + } +} + +var _ net.Listener = (*ConnectionProvider)(nil) + +func (l *ConnectionProvider) Accept() (net.Conn, error) { + select { + case <-l.isOpen: + // If the listener is closed, we don't accept new connections. + return nil, &net.OpError{Op: "accept", Net: "tunnel", Err: net.ErrClosed} + case conn := <-l.nextConn: + return conn, nil + } +} + +func (l *ConnectionProvider) Close() error { + l.doClose.Do(func() { + close(l.isOpen) // Close the channel to signal that the listener is closed + }) + return nil +} + +func (l *ConnectionProvider) Addr() net.Addr { + return &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, // Port is not used in this implementation + } +} + +func (l *ConnectionProvider) ProvideConn(conn net.Conn) { + select { + case <-l.isOpen: + // If the listener is closed, we don't accept new connections. + conn.Close() + return + case l.nextConn <- conn: + // Successfully provided the connection + } +} diff --git a/internal/api/syncapi/tunnel/streamutil.go b/internal/api/syncapi/tunnel/streamutil.go new file mode 100644 index 00000000..dda632b8 --- /dev/null +++ b/internal/api/syncapi/tunnel/streamutil.go @@ -0,0 +1,94 @@ +package tunnel + +import ( + "errors" + "sync" + "sync/atomic" + + "connectrpc.com/connect" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/hashicorp/go-multierror" +) + +var ErrStreamClosed = errors.New("stream closed") + +type stream interface { + Send(item *v1.TunnelMessage) error + Receive() (*v1.TunnelMessage, error) + Close() error +} + +type clientStream struct { + sendMu sync.Mutex + receiveMu sync.Mutex + stream *connect.BidiStreamForClient[v1.TunnelMessage, v1.TunnelMessage] + closed atomic.Bool +} + +func (s *clientStream) Send(item *v1.TunnelMessage) error { + s.sendMu.Lock() + defer s.sendMu.Unlock() + if s.closed.Load() { + return connect.NewError(connect.CodeFailedPrecondition, ErrStreamClosed) + } + return s.stream.Send(item) +} + +func (s *clientStream) Receive() (*v1.TunnelMessage, error) { + s.receiveMu.Lock() + defer s.receiveMu.Unlock() + if s.closed.Load() { + return nil, connect.NewError(connect.CodeFailedPrecondition, ErrStreamClosed) + } + return s.stream.Receive() +} + +// Close closes the request side of the stream, allowing the server to finish processing. +// It will block if Receive or Send are in progress. +func (s *clientStream) Close() error { + s.closed.Store(true) + s.receiveMu.Lock() + var err error + if e := s.stream.CloseResponse(); e != nil { + err = multierror.Append(err, e) + } + s.receiveMu.Unlock() + s.sendMu.Lock() + if e := s.stream.CloseRequest(); e != nil { + err = multierror.Append(err, e) + } + s.sendMu.Unlock() + return err +} + +type serverStream struct { + sendMu sync.Mutex + receiveMu sync.Mutex + stream *connect.BidiStream[v1.TunnelMessage, v1.TunnelMessage] + closed atomic.Bool +} + +func (s *serverStream) Send(item *v1.TunnelMessage) error { + s.sendMu.Lock() + defer s.sendMu.Unlock() + if s.closed.Load() { + return connect.NewError(connect.CodeFailedPrecondition, ErrStreamClosed) + } + return s.stream.Send(item) +} + +func (s *serverStream) Receive() (*v1.TunnelMessage, error) { + s.receiveMu.Lock() + defer s.receiveMu.Unlock() + if s.closed.Load() { + return nil, connect.NewError(connect.CodeFailedPrecondition, ErrStreamClosed) + } + return s.stream.Receive() +} + +func (s *serverStream) Close() error { + s.receiveMu.Lock() + s.sendMu.Lock() + s.closed.Store(true) + return nil +} diff --git a/internal/api/syncapi/tunnel/tunnel_test.go b/internal/api/syncapi/tunnel/tunnel_test.go new file mode 100644 index 00000000..1e2712d9 --- /dev/null +++ b/internal/api/syncapi/tunnel/tunnel_test.go @@ -0,0 +1,176 @@ +package tunnel + +import ( + "bufio" + "context" + "io" + "net" + "net/http" + "testing" + "time" + + "connectrpc.com/connect" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/gen/go/v1/v1connect" + "github.com/garethgeorge/backrest/internal/testutil" + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +func newHelloHandler() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello, World!")) + }) + return mux +} + +type sampleHandler struct { + logger *zap.Logger + streams []*WrappedStream + provider *ConnectionProvider +} + +var _ v1connect.TunnelServiceHandler = (*sampleHandler)(nil) + +func (sh *sampleHandler) Tunnel(ctx context.Context, stream *connect.BidiStream[v1.TunnelMessage, v1.TunnelMessage]) error { + wrapped := NewWrappedStream(stream, WithLogger(sh.logger)) + wrapped.ProvideConnectionsTo(sh.provider) + sh.streams = append(sh.streams, wrapped) + return wrapped.HandlePackets(ctx) +} + +func serveForTest(name string, t *testing.T, server *http.Server, listener net.Listener) { + t.Helper() + go func() { + t.Logf("Starting server %s with listener", name) + if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { + t.Errorf("Server %s failed to start: %v", name, err) + } + }() + t.Cleanup(func() { + t.Logf("Shutting down server %s", name) + deadline, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second)) + defer cancel() + if err := server.Shutdown(deadline); err != nil { + t.Errorf("Failed to close server %s: %v", name, err) + } else { + t.Logf("Server %s closed successfully", name) + } + }) +} + +func listenAndServeForTest(name string, t *testing.T, server *http.Server) { + t.Helper() + go func() { + t.Logf("Starting server %s on %s", name, server.Addr) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + t.Errorf("Server %s failed to start: %v", name, err) + } else { + t.Logf("Server %s closed successfully", name) + } + }() + t.Cleanup(func() { + t.Logf("Shutting down server %s on %s", name, server.Addr) + if err := server.Shutdown(context.Background()); err != nil { + t.Errorf("Failed to close server %s: %v", name, err) + } + }) +} + +func waitForConnectionReady(ctx context.Context, t *testing.T, wrapped *WrappedStream) { + for { + select { + case <-ctx.Done(): + t.Fatalf("timed out waiting for connection to be ready: %v", ctx.Err()) + default: + if wrapped.IsReady() { + return + } + time.Sleep(10 * time.Millisecond) + } + } +} + +func TestConnect(t *testing.T) { + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) + defer cancel() + + // Construct the service we want to provide + provider := NewConnectionProvider(10 /* bufferSize */) + sampleHandler := &sampleHandler{ + logger: testutil.NewTestLogger(t).Named("sample-handler"), + provider: provider, + } + + server := &http.Server{ + Handler: newHelloHandler(), + } + serveForTest("HTTPServer", t, server, provider) + + // Serve the sample handler + mux := http.NewServeMux() + mux.Handle(v1connect.NewTunnelServiceHandler(sampleHandler)) + grpcServer := &http.Server{ + Addr: testutil.AllocOpenBindAddr(t), + Handler: h2c.NewHandler(mux, &http2.Server{}), // h2c is HTTP/2 without TLS for grpc-connect support. + } + listenAndServeForTest("gRPCServer", t, grpcServer) + + // Create a client and connect to the server + client := v1connect.NewTunnelServiceClient(NewInsecureHttpClient(), "http://"+grpcServer.Addr) + stream := client.Tunnel(ctx) + wrapped := NewWrappedStreamFromClient(stream, WithLogger(testutil.NewTestLogger(t).Named("client"))) + go func() { + t.Log("Client stream started") + if err := wrapped.HandlePackets(ctx); err != nil { + t.Errorf("Failed to handle packets: %v", err) + } + t.Log("Client stream ended") + }() + + waitForConnectionReady(ctx, t, wrapped) + + // Attempt to connect to the server + conn, err := wrapped.Dial() + if err != nil { + t.Fatalf("Failed to dial server: %v", err) + } + + // Write an HTTP request to the connection + req, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + if err := req.Write(conn); err != nil { + t.Fatalf("Failed to write request: %v", err) + } + + // Read the response + resp, err := http.ReadResponse(bufio.NewReader(conn), req) + if err != nil { + t.Fatalf("Failed to read response: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("Expected status OK, got %s", resp.Status) + } + t.Logf("Received response: %s", resp.Status) + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %v", err) + } + t.Logf("Response body: %s", body) + + t.Logf("Closing connection with ID %d", conn.(*connState).connId) + if err := conn.Close(); err != nil { + t.Fatalf("Failed to close connection: %v", err) + } + t.Logf("Calling wrapped.Shutdown()") + if err := wrapped.Shutdown(); err != nil { + t.Fatalf("Failed to shutdown wrapped stream: %v", err) + } + t.Log("Test completed successfully") +} diff --git a/internal/api/syncapi/tunnel/wrappedstream.go b/internal/api/syncapi/tunnel/wrappedstream.go new file mode 100644 index 00000000..5901b998 --- /dev/null +++ b/internal/api/syncapi/tunnel/wrappedstream.go @@ -0,0 +1,290 @@ +package tunnel + +import ( + "context" + "crypto/ecdh" + "crypto/rand" + "fmt" + "net" + "sync" + "sync/atomic" + "time" + + "connectrpc.com/connect" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "go.uber.org/zap" +) + +type WrappedStreamOptions func(*WrappedStream) + +func WithLogger(logger *zap.Logger) WrappedStreamOptions { + return func(ws *WrappedStream) { + ws.logger = logger + } +} + +func WithHeartbeatInterval(interval time.Duration) WrappedStreamOptions { + return func(ws *WrappedStream) { + ws.heartbeatInterval = interval + } +} + +type WrappedStream struct { + isClient bool + + stream stream + logger *zap.Logger + + provider *ConnectionProvider // is nil until ProvideConnectionsTo is called + heartbeatInterval time.Duration // interval for heartbeat messages, if set + + // pool of connections, every connection is bidirectional so can be initiated from either side. + connsMu sync.Mutex + conns map[int64]*connState + + lastConnID atomic.Int64 + + handlingPackets atomic.Bool + streamStopped atomic.Bool +} + +func newWrappedStreamInternal(stream stream, isClient bool, opts ...WrappedStreamOptions) *WrappedStream { + ws := &WrappedStream{ + isClient: isClient, + stream: stream, + heartbeatInterval: 30 * time.Second, + conns: make(map[int64]*connState), + } + for _, opt := range opts { + opt(ws) + } + if isClient { + ws.lastConnID.Store(1) // Use odd numbered connection IDs for client-initiated connections. + } else { + ws.lastConnID.Store(2) // Use even numbered connection IDs for server-initiated connections. + } + return ws +} + +func NewWrappedStream(stream *connect.BidiStream[v1.TunnelMessage, v1.TunnelMessage], opts ...WrappedStreamOptions) *WrappedStream { + return newWrappedStreamInternal(&serverStream{ + stream: stream, + }, false, opts...) +} + +func NewWrappedStreamFromClient(stream *connect.BidiStreamForClient[v1.TunnelMessage, v1.TunnelMessage], opts ...WrappedStreamOptions) *WrappedStream { + return newWrappedStreamInternal(&clientStream{ + stream: stream, + }, true, opts...) +} + +func (ws *WrappedStream) allocConnID() int64 { + return ws.lastConnID.Add(2) +} + +func (ws *WrappedStream) IsReady() bool { + return ws.handlingPackets.Load() && !ws.streamStopped.Load() +} + +func (ws *WrappedStream) Dial() (net.Conn, error) { + if !ws.handlingPackets.Load() { + return nil, fmt.Errorf("cannot dial before handling packets") + } + + connID := ws.allocConnID() + new := newConnState(ws.stream, connID, ws.logger) + if err := new.sendOpenPacket(); err != nil { + return nil, fmt.Errorf("send open packet: %w", err) + } + ws.connsMu.Lock() + defer ws.connsMu.Unlock() + ws.conns[connID] = new + return new, nil +} + +func (ws *WrappedStream) ProvideConnectionsTo(provider *ConnectionProvider) { + ws.provider = provider +} + +func (ws *WrappedStream) sendHeartbeats(ctx context.Context) { + if ws.heartbeatInterval <= 0 || !ws.isClient { + return + } + + ticker := time.NewTicker(ws.heartbeatInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if ws.logger != nil { + ws.logger.Debug("sending heartbeat") + } + if err := ws.stream.Send(&v1.TunnelMessage{ + ConnId: -1, // handshake packet + }); err != nil && ws.logger != nil { + ws.logger.Error("failed to send heartbeat", zap.Error(err)) + } + } + } +} + +func (ws *WrappedStream) HandlePackets(ctx context.Context) error { + if ws.handlingPackets.Swap(true) { + return fmt.Errorf("already handling packets") + } + defer ws.handlingPackets.Store(false) + if ws.streamStopped.Load() { + return fmt.Errorf("stream already stopped") + } + + // TODO: optimization, it is generally secure and performant have a singleton key that is generated once and reused for all connections. + // the only risk w/this approach is if the key were somehow leaked all related connections would be compromised. The risk is low in this case since + // the key is only kept in memory for the lifetime of the process, and ideally is a second line of defense after TLS. + key, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return fmt.Errorf("generate key for handshake packet: %w", err) + } + + if err := ws.stream.Send(&v1.TunnelMessage{ + ConnId: -100, // hadnshake packet + PubkeyEcdhX25519: key.PublicKey().Bytes(), + }); err != nil { + return fmt.Errorf("send handshake packet: %w", err) + } + + // receive handshake packet + handshake, err := ws.stream.Receive() + if err != nil { + return fmt.Errorf("receive handshake packet: %w", err) + } + if handshake.GetConnId() != -100 { + return fmt.Errorf("expected handshake packet with connId -100, got %d", handshake.GetConnId()) + } + if len(handshake.PubkeyEcdhX25519) == 0 { + return fmt.Errorf("handshake packet does not contain public key") + } + peerKey, err := ecdh.X25519().NewPublicKey(handshake.PubkeyEcdhX25519) + if err != nil { + return fmt.Errorf("parse peer public key: %w", err) + } + + _, err = key.ECDH(peerKey) + if err != nil { + return fmt.Errorf("compute shared key: %w", err) + } + // TODO: use the key for encryption and decryption of messages. + + newConn := func(connId int64) *connState { + if ws.logger != nil { + ws.logger.Info("new tunnel connection", zap.Int64("connId", connId)) + } + new := newConnState(ws.stream, connId, ws.logger) + ws.conns[connId] = new + ws.provider.ProvideConn(new) + return new + } + + headOfLineBlockingTimer := time.NewTimer(0) + defer headOfLineBlockingTimer.Stop() + + // send heartbeats in a separate goroutine if heartbeat interval is set + go ws.sendHeartbeats(ctx) + + for { + msg, err := ws.stream.Receive() + if err != nil { + if ws.handlingPackets.Load() { + return nil + } + return fmt.Errorf("receive message: %w", err) + } + + connId := msg.GetConnId() + if connId <= 0 { + // negative IDs are reserved for healthchecks and control messages, ignored. + continue + } + + if msg.Close { + if ws.logger != nil { + ws.logger.Info("closing connection", zap.Int64("connId", connId)) + } + ws.connsMu.Lock() + if conn, exists := ws.conns[connId]; exists { + if err := conn.Close(); err != nil && ws.logger != nil { + ws.logger.Error("failed to close connection", zap.Int64("connId", connId), zap.Error(err)) + } + delete(ws.conns, connId) + } + ws.connsMu.Unlock() + continue + } + + ws.connsMu.Lock() + conn, exists := ws.conns[connId] + if !exists { + if msg.Seqno != 0 { + if ws.logger != nil { + ws.logger.Warn("received message for unknown connection", zap.Int64("connId", connId), zap.Int64("seqno", msg.Seqno)) + } + continue + } + if ws.provider == nil { + if ws.logger != nil { + ws.logger.Warn("received open packet for unknown connection, but no provider is set, ignoring the connection", zap.Int64("connId", connId)) + } + continue + } + + conn = newConn(connId) + } + ws.connsMu.Unlock() + + if len(msg.Data) == 0 { + continue + } + + headOfLineBlockingTimer.Reset(100 * time.Millisecond) + select { + case <-conn.closedCh: + if ws.logger != nil { + ws.logger.Warn("received message for closed connection", zap.Int64("connId", connId), zap.Int64("seqno", msg.Seqno)) + } + case conn.reads <- msg.Data: + if ws.logger != nil { + ws.logger.Debug("received data on tunnel connection", zap.Int64("connId", connId), zap.Int64("seqno", msg.Seqno), zap.Int("dataLength", len(msg.Data))) + } + case <-headOfLineBlockingTimer.C: + if ws.logger != nil { + ws.logger.Warn("head-of-line blocking detected, no reads available for connection", zap.Int64("connId", connId), zap.Int64("seqno", msg.Seqno)) + } + conn.Close() + case <-ctx.Done(): + // Close all open connections when the context is done. + ws.connsMu.Lock() + if ws.logger != nil { + ws.logger.Info("context done, closing all connections") + } + for _, c := range ws.conns { + if err := c.Close(); err != nil && ws.logger != nil { + ws.logger.Error("failed to close connection on context done", zap.Int64("connId", c.connId), zap.Error(err)) + } + } + ws.connsMu.Unlock() + return nil + } + } +} + +func (ws *WrappedStream) Shutdown() error { + if ws.streamStopped.Swap(true) { + if ws.logger != nil { + ws.logger.Warn("wrapped stream already shutdown") + } + return nil + } + return ws.stream.Close() +} diff --git a/internal/cryptoutil/identity.go b/internal/cryptoutil/identity.go index 24e49bb7..bb5c1fda 100644 --- a/internal/cryptoutil/identity.go +++ b/internal/cryptoutil/identity.go @@ -17,7 +17,6 @@ import ( var ( curve = elliptic.P256() // ed25519 - ) type PublicKey struct { diff --git a/internal/testutil/logging.go b/internal/testutil/logging.go index f6f2a922..47a4e362 100644 --- a/internal/testutil/logging.go +++ b/internal/testutil/logging.go @@ -13,16 +13,22 @@ type testLogger struct { } func (l *testLogger) Write(p []byte) (n int, err error) { - l.t.Log("global log: " + strings.Trim(string(p), "\n")) + l.t.Log(strings.Trim(string(p), "\n")) return len(p), nil } +// InstallZapLogger sets up a global zap logger for testing purposes. func InstallZapLogger(t *testing.T) { t.Helper() - logger := zap.New(zapcore.NewCore( + zap.ReplaceGlobals(NewTestLogger(t)) +} + +// NewTestLogger creates a zap logger for testing that outputs to the test log. +func NewTestLogger(t *testing.T) *zap.Logger { + t.Helper() + return zap.New(zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), zapcore.AddSync(&testLogger{t: t}), zapcore.DebugLevel, )) - zap.ReplaceGlobals(logger) } diff --git a/proto/v1/syncservice.proto b/proto/v1/syncservice.proto index a4d9e4be..ed1dbdff 100644 --- a/proto/v1/syncservice.proto +++ b/proto/v1/syncservice.proto @@ -12,6 +12,24 @@ import "v1/operations.proto"; import "types/value.proto"; import "google/protobuf/empty.proto"; import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; + + +// TunnelService allows inverted communicatons from server to client where the server can initiate requests to the client. +service TunnelService { + rpc Tunnel(stream TunnelMessage) returns (stream TunnelMessage) {} +} + +message TunnelMessage { + int64 conn_id = 1; // The connection ID this request belongs to, enables multiplexing. + int64 seqno = 2; + bytes data = 3; // The data to send, this is a raw byte stream. + bool close = 4; // If true, the write end should be closed after this request. + + bytes pubkey_ecdh_x25519 = 100; // The public key used for ECDH key exchange, binary encoded. + + google.protobuf.Any any = 1000; // Available to allow sending custom data down the tunnel in the initial handshake. +} // BackrestSyncService provides methods to sync data between backrest instances. diff --git a/webui/gen/ts/v1/syncservice_pb.ts b/webui/gen/ts/v1/syncservice_pb.ts index 2dc17755..bf961477 100644 --- a/webui/gen/ts/v1/syncservice_pb.ts +++ b/webui/gen/ts/v1/syncservice_pb.ts @@ -14,7 +14,8 @@ import { file_v1_service } from "./service_pb"; import type { OperationEvent } from "./operations_pb"; import { file_v1_operations } from "./operations_pb"; import { file_types_value } from "../types/value_pb"; -import { file_google_protobuf_empty } from "@bufbuild/protobuf/wkt"; +import type { Any } from "@bufbuild/protobuf/wkt"; +import { file_google_protobuf_any, file_google_protobuf_empty } from "@bufbuild/protobuf/wkt"; import { file_google_api_annotations } from "../google/api/annotations_pb"; import type { Message } from "@bufbuild/protobuf"; @@ -22,7 +23,59 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file v1/syncservice.proto. */ export const file_v1_syncservice: GenFile = /*@__PURE__*/ - fileDesc("ChR2MS9zeW5jc2VydmljZS5wcm90bxICdjEiKwoWU3luY1N0YXRlU3RyZWFtUmVxdWVzdBIRCglzdWJzY3JpYmUYASABKAgi6wEKCVBlZXJTdGF0ZRIYChBwZWVyX2luc3RhbmNlX2lkGAEgASgJEhIKCnBlZXJfa2V5aWQYAiABKAkSJgoFc3RhdGUYAyABKA4yFy52MS5TeW5jQ29ubmVjdGlvblN0YXRlEhYKDnN0YXR1c19tZXNzYWdlGAQgASgJEhMKC2tub3duX3BsYW5zGAUgAygJEhMKC2tub3duX3JlcG9zGAYgAygJEicKDXJlbW90ZV9jb25maWcYByABKAsyEC52MS5SZW1vdGVDb25maWcSHQoVbGFzdF9oZWFydGJlYXRfbWlsbGlzGAggASgDIoAMCg5TeW5jU3RyZWFtSXRlbRIrCg5zaWduZWRfbWVzc2FnZRgBIAEoCzIRLnYxLlNpZ25lZE1lc3NhZ2VIABI7CgloYW5kc2hha2UYAyABKAsyJi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uSGFuZHNoYWtlSAASOwoJaGVhcnRiZWF0GAQgASgLMiYudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkhlYXJ0YmVhdEgAEkYKD2RpZmZfb3BlcmF0aW9ucxgUIAEoCzIrLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25EaWZmT3BlcmF0aW9uc0gAEkYKD3NlbmRfb3BlcmF0aW9ucxgVIAEoCzIrLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25TZW5kT3BlcmF0aW9uc0gAEj4KC3NlbmRfY29uZmlnGBYgASgLMicudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRDb25maWdIABI8CgpzZXRfY29uZmlnGBggASgLMiYudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNldENvbmZpZ0gAEkQKDmxpc3RfcmVzb3VyY2VzGBkgASgLMioudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkxpc3RSZXNvdXJjZXNIABI6Cgh0aHJvdHRsZRjoByABKAsyJS52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uVGhyb3R0bGVIABp6ChNTeW5jQWN0aW9uSGFuZHNoYWtlEhgKEHByb3RvY29sX3ZlcnNpb24YASABKAMSIQoKcHVibGljX2tleRgCIAEoCzINLnYxLlB1YmxpY0tleRImCgtpbnN0YW5jZV9pZBgDIAEoCzIRLnYxLlNpZ25lZE1lc3NhZ2UaFQoTU3luY0FjdGlvbkhlYXJ0YmVhdBo4ChRTeW5jQWN0aW9uU2VuZENvbmZpZxIgCgZjb25maWcYASABKAsyEC52MS5SZW1vdGVDb25maWcaeQoTU3luY0FjdGlvblNldENvbmZpZxIXCgVyZXBvcxgBIAMoCzIILnYxLlJlcG8SFwoFcGxhbnMYAiADKAsyCC52MS5QbGFuEhcKD3JlcG9zX3RvX2RlbGV0ZRgDIAMoCRIXCg9wbGFuc190b19kZWxldGUYBCADKAkaPQoXU3luY0FjdGlvbkxpc3RSZXNvdXJjZXMSEAoIcmVwb19pZHMYASADKAkSEAoIcGxhbl9pZHMYAiADKAkaKAoVU3luY0FjdGlvbkNvbm5lY3RSZXBvEg8KB3JlcG9faWQYASABKAkaowEKGFN5bmNBY3Rpb25EaWZmT3BlcmF0aW9ucxIwChhoYXZlX29wZXJhdGlvbnNfc2VsZWN0b3IYASABKAsyDi52MS5PcFNlbGVjdG9yEhoKEmhhdmVfb3BlcmF0aW9uX2lkcxgCIAMoAxIdChVoYXZlX29wZXJhdGlvbl9tb2Rub3MYAyADKAMSGgoScmVxdWVzdF9vcGVyYXRpb25zGAQgAygDGj0KGFN5bmNBY3Rpb25TZW5kT3BlcmF0aW9ucxIhCgVldmVudBgBIAEoCzISLnYxLk9wZXJhdGlvbkV2ZW50GiYKElN5bmNBY3Rpb25UaHJvdHRsZRIQCghkZWxheV9tcxgBIAEoAxo4ChlTeW5jRXN0YWJsaXNoU2hhcmVkU2VjcmV0EhsKB2VkMjU1MTkYAiABKAlSCmVkMjU1MTlwdWIitAEKE1JlcG9Db25uZWN0aW9uU3RhdGUSHAoYQ09OTkVDVElPTl9TVEFURV9VTktOT1dOEAASHAoYQ09OTkVDVElPTl9TVEFURV9QRU5ESU5HEAESHgoaQ09OTkVDVElPTl9TVEFURV9DT05ORUNURUQQAhIhCh1DT05ORUNUSU9OX1NUQVRFX1VOQVVUSE9SSVpFRBADEh4KGkNPTk5FQ1RJT05fU1RBVEVfTk9UX0ZPVU5EEARCCAoGYWN0aW9uImAKDFJlbW90ZUNvbmZpZxINCgVtb2RubxgBIAEoBRIPCgd2ZXJzaW9uGAIgASgFEhcKBXJlcG9zGAMgAygLMggudjEuUmVwbxIXCgVwbGFucxgEIAMoCzIILnYxLlBsYW4qoAIKE1N5bmNDb25uZWN0aW9uU3RhdGUSHAoYQ09OTkVDVElPTl9TVEFURV9VTktOT1dOEAASHAoYQ09OTkVDVElPTl9TVEFURV9QRU5ESU5HEAESHgoaQ09OTkVDVElPTl9TVEFURV9DT05ORUNURUQQAhIhCh1DT05ORUNUSU9OX1NUQVRFX0RJU0NPTk5FQ1RFRBADEh8KG0NPTk5FQ1RJT05fU1RBVEVfUkVUUllfV0FJVBAEEh8KG0NPTk5FQ1RJT05fU1RBVEVfRVJST1JfQVVUSBAKEiMKH0NPTk5FQ1RJT05fU1RBVEVfRVJST1JfUFJPVE9DT0wQCxIjCh9DT05ORUNUSU9OX1NUQVRFX0VSUk9SX0lOVEVSTkFMEAwySwoTQmFja3Jlc3RTeW5jU2VydmljZRI0CgRTeW5jEhIudjEuU3luY1N0cmVhbUl0ZW0aEi52MS5TeW5jU3RyZWFtSXRlbSIAKAEwATJkChhCYWNrcmVzdFN5bmNTdGF0ZVNlcnZpY2USSAoXR2V0UGVlclN5bmNTdGF0ZXNTdHJlYW0SGi52MS5TeW5jU3RhdGVTdHJlYW1SZXF1ZXN0Gg0udjEuUGVlclN0YXRlIgAwAUIsWipnaXRodWIuY29tL2dhcmV0aGdlb3JnZS9iYWNrcmVzdC9nZW4vZ28vdjFiBnByb3RvMw", [file_v1_config, file_v1_crypto, file_v1_restic, file_v1_service, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations]); + fileDesc("ChR2MS9zeW5jc2VydmljZS5wcm90bxICdjEijAEKDVR1bm5lbE1lc3NhZ2USDwoHY29ubl9pZBgBIAEoAxINCgVzZXFubxgCIAEoAxIMCgRkYXRhGAMgASgMEg0KBWNsb3NlGAQgASgIEhoKEnB1YmtleV9lY2RoX3gyNTUxORhkIAEoDBIiCgNhbnkY6AcgASgLMhQuZ29vZ2xlLnByb3RvYnVmLkFueSIrChZTeW5jU3RhdGVTdHJlYW1SZXF1ZXN0EhEKCXN1YnNjcmliZRgBIAEoCCLrAQoJUGVlclN0YXRlEhgKEHBlZXJfaW5zdGFuY2VfaWQYASABKAkSEgoKcGVlcl9rZXlpZBgCIAEoCRImCgVzdGF0ZRgDIAEoDjIXLnYxLlN5bmNDb25uZWN0aW9uU3RhdGUSFgoOc3RhdHVzX21lc3NhZ2UYBCABKAkSEwoLa25vd25fcGxhbnMYBSADKAkSEwoLa25vd25fcmVwb3MYBiADKAkSJwoNcmVtb3RlX2NvbmZpZxgHIAEoCzIQLnYxLlJlbW90ZUNvbmZpZxIdChVsYXN0X2hlYXJ0YmVhdF9taWxsaXMYCCABKAMigAwKDlN5bmNTdHJlYW1JdGVtEisKDnNpZ25lZF9tZXNzYWdlGAEgASgLMhEudjEuU2lnbmVkTWVzc2FnZUgAEjsKCWhhbmRzaGFrZRgDIAEoCzImLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25IYW5kc2hha2VIABI7CgloZWFydGJlYXQYBCABKAsyJi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uSGVhcnRiZWF0SAASRgoPZGlmZl9vcGVyYXRpb25zGBQgASgLMisudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkRpZmZPcGVyYXRpb25zSAASRgoPc2VuZF9vcGVyYXRpb25zGBUgASgLMisudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRPcGVyYXRpb25zSAASPgoLc2VuZF9jb25maWcYFiABKAsyJy52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uU2VuZENvbmZpZ0gAEjwKCnNldF9jb25maWcYGCABKAsyJi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uU2V0Q29uZmlnSAASRAoObGlzdF9yZXNvdXJjZXMYGSABKAsyKi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uTGlzdFJlc291cmNlc0gAEjoKCHRocm90dGxlGOgHIAEoCzIlLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25UaHJvdHRsZUgAGnoKE1N5bmNBY3Rpb25IYW5kc2hha2USGAoQcHJvdG9jb2xfdmVyc2lvbhgBIAEoAxIhCgpwdWJsaWNfa2V5GAIgASgLMg0udjEuUHVibGljS2V5EiYKC2luc3RhbmNlX2lkGAMgASgLMhEudjEuU2lnbmVkTWVzc2FnZRoVChNTeW5jQWN0aW9uSGVhcnRiZWF0GjgKFFN5bmNBY3Rpb25TZW5kQ29uZmlnEiAKBmNvbmZpZxgBIAEoCzIQLnYxLlJlbW90ZUNvbmZpZxp5ChNTeW5jQWN0aW9uU2V0Q29uZmlnEhcKBXJlcG9zGAEgAygLMggudjEuUmVwbxIXCgVwbGFucxgCIAMoCzIILnYxLlBsYW4SFwoPcmVwb3NfdG9fZGVsZXRlGAMgAygJEhcKD3BsYW5zX3RvX2RlbGV0ZRgEIAMoCRo9ChdTeW5jQWN0aW9uTGlzdFJlc291cmNlcxIQCghyZXBvX2lkcxgBIAMoCRIQCghwbGFuX2lkcxgCIAMoCRooChVTeW5jQWN0aW9uQ29ubmVjdFJlcG8SDwoHcmVwb19pZBgBIAEoCRqjAQoYU3luY0FjdGlvbkRpZmZPcGVyYXRpb25zEjAKGGhhdmVfb3BlcmF0aW9uc19zZWxlY3RvchgBIAEoCzIOLnYxLk9wU2VsZWN0b3ISGgoSaGF2ZV9vcGVyYXRpb25faWRzGAIgAygDEh0KFWhhdmVfb3BlcmF0aW9uX21vZG5vcxgDIAMoAxIaChJyZXF1ZXN0X29wZXJhdGlvbnMYBCADKAMaPQoYU3luY0FjdGlvblNlbmRPcGVyYXRpb25zEiEKBWV2ZW50GAEgASgLMhIudjEuT3BlcmF0aW9uRXZlbnQaJgoSU3luY0FjdGlvblRocm90dGxlEhAKCGRlbGF5X21zGAEgASgDGjgKGVN5bmNFc3RhYmxpc2hTaGFyZWRTZWNyZXQSGwoHZWQyNTUxORgCIAEoCVIKZWQyNTUxOXB1YiK0AQoTUmVwb0Nvbm5lY3Rpb25TdGF0ZRIcChhDT05ORUNUSU9OX1NUQVRFX1VOS05PV04QABIcChhDT05ORUNUSU9OX1NUQVRFX1BFTkRJTkcQARIeChpDT05ORUNUSU9OX1NUQVRFX0NPTk5FQ1RFRBACEiEKHUNPTk5FQ1RJT05fU1RBVEVfVU5BVVRIT1JJWkVEEAMSHgoaQ09OTkVDVElPTl9TVEFURV9OT1RfRk9VTkQQBEIICgZhY3Rpb24iYAoMUmVtb3RlQ29uZmlnEg0KBW1vZG5vGAEgASgFEg8KB3ZlcnNpb24YAiABKAUSFwoFcmVwb3MYAyADKAsyCC52MS5SZXBvEhcKBXBsYW5zGAQgAygLMggudjEuUGxhbiqgAgoTU3luY0Nvbm5lY3Rpb25TdGF0ZRIcChhDT05ORUNUSU9OX1NUQVRFX1VOS05PV04QABIcChhDT05ORUNUSU9OX1NUQVRFX1BFTkRJTkcQARIeChpDT05ORUNUSU9OX1NUQVRFX0NPTk5FQ1RFRBACEiEKHUNPTk5FQ1RJT05fU1RBVEVfRElTQ09OTkVDVEVEEAMSHwobQ09OTkVDVElPTl9TVEFURV9SRVRSWV9XQUlUEAQSHwobQ09OTkVDVElPTl9TVEFURV9FUlJPUl9BVVRIEAoSIwofQ09OTkVDVElPTl9TVEFURV9FUlJPUl9QUk9UT0NPTBALEiMKH0NPTk5FQ1RJT05fU1RBVEVfRVJST1JfSU5URVJOQUwQDDJFCg1UdW5uZWxTZXJ2aWNlEjQKBlR1bm5lbBIRLnYxLlR1bm5lbE1lc3NhZ2UaES52MS5UdW5uZWxNZXNzYWdlIgAoATABMksKE0JhY2tyZXN0U3luY1NlcnZpY2USNAoEU3luYxISLnYxLlN5bmNTdHJlYW1JdGVtGhIudjEuU3luY1N0cmVhbUl0ZW0iACgBMAEyZAoYQmFja3Jlc3RTeW5jU3RhdGVTZXJ2aWNlEkgKF0dldFBlZXJTeW5jU3RhdGVzU3RyZWFtEhoudjEuU3luY1N0YXRlU3RyZWFtUmVxdWVzdBoNLnYxLlBlZXJTdGF0ZSIAMAFCLFoqZ2l0aHViLmNvbS9nYXJldGhnZW9yZ2UvYmFja3Jlc3QvZ2VuL2dvL3YxYgZwcm90bzM", [file_v1_config, file_v1_crypto, file_v1_restic, file_v1_service, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations, file_google_protobuf_any]); + +/** + * @generated from message v1.TunnelMessage + */ +export type TunnelMessage = Message<"v1.TunnelMessage"> & { + /** + * The connection ID this request belongs to, enables multiplexing. + * + * @generated from field: int64 conn_id = 1; + */ + connId: bigint; + + /** + * @generated from field: int64 seqno = 2; + */ + seqno: bigint; + + /** + * The data to send, this is a raw byte stream. + * + * @generated from field: bytes data = 3; + */ + data: Uint8Array; + + /** + * If true, the write end should be closed after this request. + * + * @generated from field: bool close = 4; + */ + close: boolean; + + /** + * The public key used for ECDH key exchange, binary encoded. + * + * @generated from field: bytes pubkey_ecdh_x25519 = 100; + */ + pubkeyEcdhX25519: Uint8Array; + + /** + * Available to allow sending custom data down the tunnel in the initial handshake. + * + * @generated from field: google.protobuf.Any any = 1000; + */ + any?: Any; +}; + +/** + * Describes the message v1.TunnelMessage. + * Use `create(TunnelMessageSchema)` to create a new message. + */ +export const TunnelMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 0); /** * @generated from message v1.SyncStateStreamRequest @@ -41,7 +94,7 @@ export type SyncStateStreamRequest = Message<"v1.SyncStateStreamRequest"> & { * Use `create(SyncStateStreamRequestSchema)` to create a new message. */ export const SyncStateStreamRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 0); + messageDesc(file_v1_syncservice, 1); /** * @generated from message v1.PeerState @@ -101,7 +154,7 @@ export type PeerState = Message<"v1.PeerState"> & { * Use `create(PeerStateSchema)` to create a new message. */ export const PeerStateSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 1); + messageDesc(file_v1_syncservice, 2); /** * @generated from message v1.SyncStreamItem @@ -172,7 +225,7 @@ export type SyncStreamItem = Message<"v1.SyncStreamItem"> & { * Use `create(SyncStreamItemSchema)` to create a new message. */ export const SyncStreamItemSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2); + messageDesc(file_v1_syncservice, 3); /** * @generated from message v1.SyncStreamItem.SyncActionHandshake @@ -199,7 +252,7 @@ export type SyncStreamItem_SyncActionHandshake = Message<"v1.SyncStreamItem.Sync * Use `create(SyncStreamItem_SyncActionHandshakeSchema)` to create a new message. */ export const SyncStreamItem_SyncActionHandshakeSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 0); + messageDesc(file_v1_syncservice, 3, 0); /** * SyncActionHeartbeat is sent periodically to keep the connection alive. @@ -214,7 +267,7 @@ export type SyncStreamItem_SyncActionHeartbeat = Message<"v1.SyncStreamItem.Sync * Use `create(SyncStreamItem_SyncActionHeartbeatSchema)` to create a new message. */ export const SyncStreamItem_SyncActionHeartbeatSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 1); + messageDesc(file_v1_syncservice, 3, 1); /** * @generated from message v1.SyncStreamItem.SyncActionSendConfig @@ -231,7 +284,7 @@ export type SyncStreamItem_SyncActionSendConfig = Message<"v1.SyncStreamItem.Syn * Use `create(SyncStreamItem_SyncActionSendConfigSchema)` to create a new message. */ export const SyncStreamItem_SyncActionSendConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 2); + messageDesc(file_v1_syncservice, 3, 2); /** * @generated from message v1.SyncStreamItem.SyncActionSetConfig @@ -263,7 +316,7 @@ export type SyncStreamItem_SyncActionSetConfig = Message<"v1.SyncStreamItem.Sync * Use `create(SyncStreamItem_SyncActionSetConfigSchema)` to create a new message. */ export const SyncStreamItem_SyncActionSetConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 3); + messageDesc(file_v1_syncservice, 3, 3); /** * @generated from message v1.SyncStreamItem.SyncActionListResources @@ -289,7 +342,7 @@ export type SyncStreamItem_SyncActionListResources = Message<"v1.SyncStreamItem. * Use `create(SyncStreamItem_SyncActionListResourcesSchema)` to create a new message. */ export const SyncStreamItem_SyncActionListResourcesSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 4); + messageDesc(file_v1_syncservice, 3, 4); /** * @generated from message v1.SyncStreamItem.SyncActionConnectRepo @@ -306,7 +359,7 @@ export type SyncStreamItem_SyncActionConnectRepo = Message<"v1.SyncStreamItem.Sy * Use `create(SyncStreamItem_SyncActionConnectRepoSchema)` to create a new message. */ export const SyncStreamItem_SyncActionConnectRepoSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 5); + messageDesc(file_v1_syncservice, 3, 5); /** * @generated from message v1.SyncStreamItem.SyncActionDiffOperations @@ -343,7 +396,7 @@ export type SyncStreamItem_SyncActionDiffOperations = Message<"v1.SyncStreamItem * Use `create(SyncStreamItem_SyncActionDiffOperationsSchema)` to create a new message. */ export const SyncStreamItem_SyncActionDiffOperationsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 6); + messageDesc(file_v1_syncservice, 3, 6); /** * @generated from message v1.SyncStreamItem.SyncActionSendOperations @@ -360,7 +413,7 @@ export type SyncStreamItem_SyncActionSendOperations = Message<"v1.SyncStreamItem * Use `create(SyncStreamItem_SyncActionSendOperationsSchema)` to create a new message. */ export const SyncStreamItem_SyncActionSendOperationsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 7); + messageDesc(file_v1_syncservice, 3, 7); /** * @generated from message v1.SyncStreamItem.SyncActionThrottle @@ -377,7 +430,7 @@ export type SyncStreamItem_SyncActionThrottle = Message<"v1.SyncStreamItem.SyncA * Use `create(SyncStreamItem_SyncActionThrottleSchema)` to create a new message. */ export const SyncStreamItem_SyncActionThrottleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 8); + messageDesc(file_v1_syncservice, 3, 8); /** * @generated from message v1.SyncStreamItem.SyncEstablishSharedSecret @@ -399,7 +452,7 @@ export type SyncStreamItem_SyncEstablishSharedSecret = Message<"v1.SyncStreamIte * Use `create(SyncStreamItem_SyncEstablishSharedSecretSchema)` to create a new message. */ export const SyncStreamItem_SyncEstablishSharedSecretSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2, 9); + messageDesc(file_v1_syncservice, 3, 9); /** * @generated from enum v1.SyncStreamItem.RepoConnectionState @@ -437,7 +490,7 @@ export enum SyncStreamItem_RepoConnectionState { * Describes the enum v1.SyncStreamItem.RepoConnectionState. */ export const SyncStreamItem_RepoConnectionStateSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_syncservice, 2, 0); + enumDesc(file_v1_syncservice, 3, 0); /** * RemoteConfig contains shareable properties from a remote backrest instance. @@ -475,7 +528,7 @@ export type RemoteConfig = Message<"v1.RemoteConfig"> & { * Use `create(RemoteConfigSchema)` to create a new message. */ export const RemoteConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3); + messageDesc(file_v1_syncservice, 4); /** * @generated from enum v1.SyncConnectionState @@ -528,6 +581,23 @@ export enum SyncConnectionState { export const SyncConnectionStateSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_syncservice, 0); +/** + * TunnelService allows inverted communicatons from server to client where the server can initiate requests to the client. + * + * @generated from service v1.TunnelService + */ +export const TunnelService: GenService<{ + /** + * @generated from rpc v1.TunnelService.Tunnel + */ + tunnel: { + methodKind: "bidi_streaming"; + input: typeof TunnelMessageSchema; + output: typeof TunnelMessageSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_v1_syncservice, 0); + /** * BackrestSyncService provides methods to sync data between backrest instances. * This service provides its own authentication and authorization. @@ -544,7 +614,7 @@ export const BackrestSyncService: GenService<{ output: typeof SyncStreamItemSchema; }, }> = /*@__PURE__*/ - serviceDesc(file_v1_syncservice, 0); + serviceDesc(file_v1_syncservice, 1); /** * BackrestSyncStateService provides methods to query the sync state of known hosts and clients. @@ -562,5 +632,5 @@ export const BackrestSyncStateService: GenService<{ output: typeof PeerStateSchema; }, }> = /*@__PURE__*/ - serviceDesc(file_v1_syncservice, 1); + serviceDesc(file_v1_syncservice, 2);