restore old impl and abandon tunneling approach as liable to be fragile

This commit is contained in:
Gareth
2025-10-31 18:43:29 -07:00
parent c75aaaf7ad
commit b7cd874cbf
24 changed files with 1805 additions and 2273 deletions
File diff suppressed because it is too large Load Diff
+102 -338
View File
@@ -8,12 +8,9 @@ package v1sync
import (
context "context"
types "github.com/garethgeorge/backrest/gen/go/types"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
@@ -21,6 +18,108 @@ import (
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
BackrestSyncService_Sync_FullMethodName = "/v1sync.BackrestSyncService/Sync"
)
// BackrestSyncServiceClient is the client API for BackrestSyncService 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.
//
// BackrestSyncService provides methods to sync data between backrest instances.
// This service provides its own authentication and authorization.
type BackrestSyncServiceClient interface {
Sync(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SyncStreamItem, SyncStreamItem], error)
}
type backrestSyncServiceClient struct {
cc grpc.ClientConnInterface
}
func NewBackrestSyncServiceClient(cc grpc.ClientConnInterface) BackrestSyncServiceClient {
return &backrestSyncServiceClient{cc}
}
func (c *backrestSyncServiceClient) Sync(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SyncStreamItem, SyncStreamItem], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BackrestSyncService_ServiceDesc.Streams[0], BackrestSyncService_Sync_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SyncStreamItem, SyncStreamItem]{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 BackrestSyncService_SyncClient = grpc.BidiStreamingClient[SyncStreamItem, SyncStreamItem]
// BackrestSyncServiceServer is the server API for BackrestSyncService service.
// All implementations must embed UnimplementedBackrestSyncServiceServer
// for forward compatibility.
//
// BackrestSyncService provides methods to sync data between backrest instances.
// This service provides its own authentication and authorization.
type BackrestSyncServiceServer interface {
Sync(grpc.BidiStreamingServer[SyncStreamItem, SyncStreamItem]) error
mustEmbedUnimplementedBackrestSyncServiceServer()
}
// UnimplementedBackrestSyncServiceServer 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 UnimplementedBackrestSyncServiceServer struct{}
func (UnimplementedBackrestSyncServiceServer) Sync(grpc.BidiStreamingServer[SyncStreamItem, SyncStreamItem]) error {
return status.Errorf(codes.Unimplemented, "method Sync not implemented")
}
func (UnimplementedBackrestSyncServiceServer) mustEmbedUnimplementedBackrestSyncServiceServer() {}
func (UnimplementedBackrestSyncServiceServer) testEmbeddedByValue() {}
// UnsafeBackrestSyncServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BackrestSyncServiceServer will
// result in compilation errors.
type UnsafeBackrestSyncServiceServer interface {
mustEmbedUnimplementedBackrestSyncServiceServer()
}
func RegisterBackrestSyncServiceServer(s grpc.ServiceRegistrar, srv BackrestSyncServiceServer) {
// If the following call pancis, it indicates UnimplementedBackrestSyncServiceServer 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(&BackrestSyncService_ServiceDesc, srv)
}
func _BackrestSyncService_Sync_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BackrestSyncServiceServer).Sync(&grpc.GenericServerStream[SyncStreamItem, SyncStreamItem]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BackrestSyncService_SyncServer = grpc.BidiStreamingServer[SyncStreamItem, SyncStreamItem]
// BackrestSyncService_ServiceDesc is the grpc.ServiceDesc for BackrestSyncService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BackrestSyncService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "v1sync.BackrestSyncService",
HandlerType: (*BackrestSyncServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Sync",
Handler: _BackrestSyncService_Sync_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "v1sync/syncservice.proto",
}
const (
BackrestSyncStateService_GetPeerSyncStatesStream_FullMethodName = "/v1sync.BackrestSyncStateService/GetPeerSyncStatesStream"
)
@@ -132,338 +231,3 @@ var BackrestSyncStateService_ServiceDesc = grpc.ServiceDesc{
},
Metadata: "v1sync/syncservice.proto",
}
const (
SyncPeerService_Authenticate_FullMethodName = "/v1sync.SyncPeerService/Authenticate"
SyncPeerService_GetOperationMetadata_FullMethodName = "/v1sync.SyncPeerService/GetOperationMetadata"
SyncPeerService_SendOperations_FullMethodName = "/v1sync.SyncPeerService/SendOperations"
SyncPeerService_GetLog_FullMethodName = "/v1sync.SyncPeerService/GetLog"
SyncPeerService_SetAvailableResources_FullMethodName = "/v1sync.SyncPeerService/SetAvailableResources"
SyncPeerService_SetConfig_FullMethodName = "/v1sync.SyncPeerService/SetConfig"
SyncPeerService_GetConfig_FullMethodName = "/v1sync.SyncPeerService/GetConfig"
)
// SyncPeerServiceClient is the client API for SyncPeerService 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.
type SyncPeerServiceClient interface {
// Authenticate authenticates the peer with the sync service, must be called before any other methods.
Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// GetOperationMetadata returns a stream of sync items from the peer.
GetOperationMetadata(ctx context.Context, in *v1.OpSelector, opts ...grpc.CallOption) (*GetOperationMetadataResponse, error)
SendOperations(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[v1.Operation, emptypb.Empty], error)
GetLog(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogDataEntry], error)
// Called everytime the set of resources available to the peer changes.
SetAvailableResources(ctx context.Context, in *SetAvailableResourcesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Implements semantics for updating the remote config of the peer.
SetConfig(ctx context.Context, in *SetConfigRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*RemoteConfig, error)
}
type syncPeerServiceClient struct {
cc grpc.ClientConnInterface
}
func NewSyncPeerServiceClient(cc grpc.ClientConnInterface) SyncPeerServiceClient {
return &syncPeerServiceClient{cc}
}
func (c *syncPeerServiceClient) Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, SyncPeerService_Authenticate_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *syncPeerServiceClient) GetOperationMetadata(ctx context.Context, in *v1.OpSelector, opts ...grpc.CallOption) (*GetOperationMetadataResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetOperationMetadataResponse)
err := c.cc.Invoke(ctx, SyncPeerService_GetOperationMetadata_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *syncPeerServiceClient) SendOperations(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[v1.Operation, emptypb.Empty], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &SyncPeerService_ServiceDesc.Streams[0], SyncPeerService_SendOperations_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[v1.Operation, emptypb.Empty]{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 SyncPeerService_SendOperationsClient = grpc.ClientStreamingClient[v1.Operation, emptypb.Empty]
func (c *syncPeerServiceClient) GetLog(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogDataEntry], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &SyncPeerService_ServiceDesc.Streams[1], SyncPeerService_GetLog_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[types.StringValue, LogDataEntry]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
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 SyncPeerService_GetLogClient = grpc.ServerStreamingClient[LogDataEntry]
func (c *syncPeerServiceClient) SetAvailableResources(ctx context.Context, in *SetAvailableResourcesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, SyncPeerService_SetAvailableResources_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *syncPeerServiceClient) SetConfig(ctx context.Context, in *SetConfigRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, SyncPeerService_SetConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *syncPeerServiceClient) GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*RemoteConfig, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RemoteConfig)
err := c.cc.Invoke(ctx, SyncPeerService_GetConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// SyncPeerServiceServer is the server API for SyncPeerService service.
// All implementations must embed UnimplementedSyncPeerServiceServer
// for forward compatibility.
type SyncPeerServiceServer interface {
// Authenticate authenticates the peer with the sync service, must be called before any other methods.
Authenticate(context.Context, *AuthenticateRequest) (*emptypb.Empty, error)
// GetOperationMetadata returns a stream of sync items from the peer.
GetOperationMetadata(context.Context, *v1.OpSelector) (*GetOperationMetadataResponse, error)
SendOperations(grpc.ClientStreamingServer[v1.Operation, emptypb.Empty]) error
GetLog(*types.StringValue, grpc.ServerStreamingServer[LogDataEntry]) error
// Called everytime the set of resources available to the peer changes.
SetAvailableResources(context.Context, *SetAvailableResourcesRequest) (*emptypb.Empty, error)
// Implements semantics for updating the remote config of the peer.
SetConfig(context.Context, *SetConfigRequest) (*emptypb.Empty, error)
GetConfig(context.Context, *emptypb.Empty) (*RemoteConfig, error)
mustEmbedUnimplementedSyncPeerServiceServer()
}
// UnimplementedSyncPeerServiceServer 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 UnimplementedSyncPeerServiceServer struct{}
func (UnimplementedSyncPeerServiceServer) Authenticate(context.Context, *AuthenticateRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Authenticate not implemented")
}
func (UnimplementedSyncPeerServiceServer) GetOperationMetadata(context.Context, *v1.OpSelector) (*GetOperationMetadataResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOperationMetadata not implemented")
}
func (UnimplementedSyncPeerServiceServer) SendOperations(grpc.ClientStreamingServer[v1.Operation, emptypb.Empty]) error {
return status.Errorf(codes.Unimplemented, "method SendOperations not implemented")
}
func (UnimplementedSyncPeerServiceServer) GetLog(*types.StringValue, grpc.ServerStreamingServer[LogDataEntry]) error {
return status.Errorf(codes.Unimplemented, "method GetLog not implemented")
}
func (UnimplementedSyncPeerServiceServer) SetAvailableResources(context.Context, *SetAvailableResourcesRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAvailableResources not implemented")
}
func (UnimplementedSyncPeerServiceServer) SetConfig(context.Context, *SetConfigRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetConfig not implemented")
}
func (UnimplementedSyncPeerServiceServer) GetConfig(context.Context, *emptypb.Empty) (*RemoteConfig, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented")
}
func (UnimplementedSyncPeerServiceServer) mustEmbedUnimplementedSyncPeerServiceServer() {}
func (UnimplementedSyncPeerServiceServer) testEmbeddedByValue() {}
// UnsafeSyncPeerServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to SyncPeerServiceServer will
// result in compilation errors.
type UnsafeSyncPeerServiceServer interface {
mustEmbedUnimplementedSyncPeerServiceServer()
}
func RegisterSyncPeerServiceServer(s grpc.ServiceRegistrar, srv SyncPeerServiceServer) {
// If the following call pancis, it indicates UnimplementedSyncPeerServiceServer 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(&SyncPeerService_ServiceDesc, srv)
}
func _SyncPeerService_Authenticate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuthenticateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SyncPeerServiceServer).Authenticate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SyncPeerService_Authenticate_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SyncPeerServiceServer).Authenticate(ctx, req.(*AuthenticateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SyncPeerService_GetOperationMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(v1.OpSelector)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SyncPeerServiceServer).GetOperationMetadata(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SyncPeerService_GetOperationMetadata_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SyncPeerServiceServer).GetOperationMetadata(ctx, req.(*v1.OpSelector))
}
return interceptor(ctx, in, info, handler)
}
func _SyncPeerService_SendOperations_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(SyncPeerServiceServer).SendOperations(&grpc.GenericServerStream[v1.Operation, emptypb.Empty]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type SyncPeerService_SendOperationsServer = grpc.ClientStreamingServer[v1.Operation, emptypb.Empty]
func _SyncPeerService_GetLog_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(types.StringValue)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(SyncPeerServiceServer).GetLog(m, &grpc.GenericServerStream[types.StringValue, LogDataEntry]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type SyncPeerService_GetLogServer = grpc.ServerStreamingServer[LogDataEntry]
func _SyncPeerService_SetAvailableResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetAvailableResourcesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SyncPeerServiceServer).SetAvailableResources(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SyncPeerService_SetAvailableResources_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SyncPeerServiceServer).SetAvailableResources(ctx, req.(*SetAvailableResourcesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SyncPeerService_SetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SyncPeerServiceServer).SetConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SyncPeerService_SetConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SyncPeerServiceServer).SetConfig(ctx, req.(*SetConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SyncPeerService_GetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SyncPeerServiceServer).GetConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SyncPeerService_GetConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SyncPeerServiceServer).GetConfig(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
// SyncPeerService_ServiceDesc is the grpc.ServiceDesc for SyncPeerService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var SyncPeerService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "v1sync.SyncPeerService",
HandlerType: (*SyncPeerServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Authenticate",
Handler: _SyncPeerService_Authenticate_Handler,
},
{
MethodName: "GetOperationMetadata",
Handler: _SyncPeerService_GetOperationMetadata_Handler,
},
{
MethodName: "SetAvailableResources",
Handler: _SyncPeerService_SetAvailableResources_Handler,
},
{
MethodName: "SetConfig",
Handler: _SyncPeerService_SetConfig_Handler,
},
{
MethodName: "GetConfig",
Handler: _SyncPeerService_GetConfig_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SendOperations",
Handler: _SyncPeerService_SendOperations_Handler,
ClientStreams: true,
},
{
StreamName: "GetLog",
Handler: _SyncPeerService_GetLog_Handler,
ServerStreams: true,
},
},
Metadata: "v1sync/syncservice.proto",
}
@@ -8,10 +8,7 @@ import (
connect "connectrpc.com/connect"
context "context"
errors "errors"
types "github.com/garethgeorge/backrest/gen/go/types"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
v1sync "github.com/garethgeorge/backrest/gen/go/v1sync"
emptypb "google.golang.org/protobuf/types/known/emptypb"
http "net/http"
strings "strings"
)
@@ -24,10 +21,10 @@ import (
const _ = connect.IsAtLeastVersion1_13_0
const (
// BackrestSyncServiceName is the fully-qualified name of the BackrestSyncService service.
BackrestSyncServiceName = "v1sync.BackrestSyncService"
// BackrestSyncStateServiceName is the fully-qualified name of the BackrestSyncStateService service.
BackrestSyncStateServiceName = "v1sync.BackrestSyncStateService"
// SyncPeerServiceName is the fully-qualified name of the SyncPeerService service.
SyncPeerServiceName = "v1sync.SyncPeerService"
)
// These constants are the fully-qualified names of the RPCs defined in this package. They're
@@ -38,31 +35,84 @@ const (
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// BackrestSyncServiceSyncProcedure is the fully-qualified name of the BackrestSyncService's Sync
// RPC.
BackrestSyncServiceSyncProcedure = "/v1sync.BackrestSyncService/Sync"
// BackrestSyncStateServiceGetPeerSyncStatesStreamProcedure is the fully-qualified name of the
// BackrestSyncStateService's GetPeerSyncStatesStream RPC.
BackrestSyncStateServiceGetPeerSyncStatesStreamProcedure = "/v1sync.BackrestSyncStateService/GetPeerSyncStatesStream"
// SyncPeerServiceAuthenticateProcedure is the fully-qualified name of the SyncPeerService's
// Authenticate RPC.
SyncPeerServiceAuthenticateProcedure = "/v1sync.SyncPeerService/Authenticate"
// SyncPeerServiceGetOperationMetadataProcedure is the fully-qualified name of the SyncPeerService's
// GetOperationMetadata RPC.
SyncPeerServiceGetOperationMetadataProcedure = "/v1sync.SyncPeerService/GetOperationMetadata"
// SyncPeerServiceSendOperationsProcedure is the fully-qualified name of the SyncPeerService's
// SendOperations RPC.
SyncPeerServiceSendOperationsProcedure = "/v1sync.SyncPeerService/SendOperations"
// SyncPeerServiceGetLogProcedure is the fully-qualified name of the SyncPeerService's GetLog RPC.
SyncPeerServiceGetLogProcedure = "/v1sync.SyncPeerService/GetLog"
// SyncPeerServiceSetAvailableResourcesProcedure is the fully-qualified name of the
// SyncPeerService's SetAvailableResources RPC.
SyncPeerServiceSetAvailableResourcesProcedure = "/v1sync.SyncPeerService/SetAvailableResources"
// SyncPeerServiceSetConfigProcedure is the fully-qualified name of the SyncPeerService's SetConfig
// RPC.
SyncPeerServiceSetConfigProcedure = "/v1sync.SyncPeerService/SetConfig"
// SyncPeerServiceGetConfigProcedure is the fully-qualified name of the SyncPeerService's GetConfig
// RPC.
SyncPeerServiceGetConfigProcedure = "/v1sync.SyncPeerService/GetConfig"
)
// BackrestSyncServiceClient is a client for the v1sync.BackrestSyncService service.
type BackrestSyncServiceClient interface {
Sync(context.Context) *connect.BidiStreamForClient[v1sync.SyncStreamItem, v1sync.SyncStreamItem]
}
// NewBackrestSyncServiceClient constructs a client for the v1sync.BackrestSyncService 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 NewBackrestSyncServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) BackrestSyncServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
backrestSyncServiceMethods := v1sync.File_v1sync_syncservice_proto.Services().ByName("BackrestSyncService").Methods()
return &backrestSyncServiceClient{
sync: connect.NewClient[v1sync.SyncStreamItem, v1sync.SyncStreamItem](
httpClient,
baseURL+BackrestSyncServiceSyncProcedure,
connect.WithSchema(backrestSyncServiceMethods.ByName("Sync")),
connect.WithClientOptions(opts...),
),
}
}
// backrestSyncServiceClient implements BackrestSyncServiceClient.
type backrestSyncServiceClient struct {
sync *connect.Client[v1sync.SyncStreamItem, v1sync.SyncStreamItem]
}
// Sync calls v1sync.BackrestSyncService.Sync.
func (c *backrestSyncServiceClient) Sync(ctx context.Context) *connect.BidiStreamForClient[v1sync.SyncStreamItem, v1sync.SyncStreamItem] {
return c.sync.CallBidiStream(ctx)
}
// BackrestSyncServiceHandler is an implementation of the v1sync.BackrestSyncService service.
type BackrestSyncServiceHandler interface {
Sync(context.Context, *connect.BidiStream[v1sync.SyncStreamItem, v1sync.SyncStreamItem]) error
}
// NewBackrestSyncServiceHandler 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 NewBackrestSyncServiceHandler(svc BackrestSyncServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
backrestSyncServiceMethods := v1sync.File_v1sync_syncservice_proto.Services().ByName("BackrestSyncService").Methods()
backrestSyncServiceSyncHandler := connect.NewBidiStreamHandler(
BackrestSyncServiceSyncProcedure,
svc.Sync,
connect.WithSchema(backrestSyncServiceMethods.ByName("Sync")),
connect.WithHandlerOptions(opts...),
)
return "/v1sync.BackrestSyncService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case BackrestSyncServiceSyncProcedure:
backrestSyncServiceSyncHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedBackrestSyncServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedBackrestSyncServiceHandler struct{}
func (UnimplementedBackrestSyncServiceHandler) Sync(context.Context, *connect.BidiStream[v1sync.SyncStreamItem, v1sync.SyncStreamItem]) error {
return connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.BackrestSyncService.Sync is not implemented"))
}
// BackrestSyncStateServiceClient is a client for the v1sync.BackrestSyncStateService service.
type BackrestSyncStateServiceClient interface {
GetPeerSyncStatesStream(context.Context, *connect.Request[v1sync.SyncStateStreamRequest]) (*connect.ServerStreamForClient[v1sync.PeerState], error)
@@ -133,237 +183,3 @@ type UnimplementedBackrestSyncStateServiceHandler struct{}
func (UnimplementedBackrestSyncStateServiceHandler) GetPeerSyncStatesStream(context.Context, *connect.Request[v1sync.SyncStateStreamRequest], *connect.ServerStream[v1sync.PeerState]) error {
return connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.BackrestSyncStateService.GetPeerSyncStatesStream is not implemented"))
}
// SyncPeerServiceClient is a client for the v1sync.SyncPeerService service.
type SyncPeerServiceClient interface {
// Authenticate authenticates the peer with the sync service, must be called before any other methods.
Authenticate(context.Context, *connect.Request[v1sync.AuthenticateRequest]) (*connect.Response[emptypb.Empty], error)
// GetOperationMetadata returns a stream of sync items from the peer.
GetOperationMetadata(context.Context, *connect.Request[v1.OpSelector]) (*connect.Response[v1sync.GetOperationMetadataResponse], error)
SendOperations(context.Context) *connect.ClientStreamForClient[v1.Operation, emptypb.Empty]
GetLog(context.Context, *connect.Request[types.StringValue]) (*connect.ServerStreamForClient[v1sync.LogDataEntry], error)
// Called everytime the set of resources available to the peer changes.
SetAvailableResources(context.Context, *connect.Request[v1sync.SetAvailableResourcesRequest]) (*connect.Response[emptypb.Empty], error)
// Implements semantics for updating the remote config of the peer.
SetConfig(context.Context, *connect.Request[v1sync.SetConfigRequest]) (*connect.Response[emptypb.Empty], error)
GetConfig(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1sync.RemoteConfig], error)
}
// NewSyncPeerServiceClient constructs a client for the v1sync.SyncPeerService 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 NewSyncPeerServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SyncPeerServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
syncPeerServiceMethods := v1sync.File_v1sync_syncservice_proto.Services().ByName("SyncPeerService").Methods()
return &syncPeerServiceClient{
authenticate: connect.NewClient[v1sync.AuthenticateRequest, emptypb.Empty](
httpClient,
baseURL+SyncPeerServiceAuthenticateProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("Authenticate")),
connect.WithClientOptions(opts...),
),
getOperationMetadata: connect.NewClient[v1.OpSelector, v1sync.GetOperationMetadataResponse](
httpClient,
baseURL+SyncPeerServiceGetOperationMetadataProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("GetOperationMetadata")),
connect.WithClientOptions(opts...),
),
sendOperations: connect.NewClient[v1.Operation, emptypb.Empty](
httpClient,
baseURL+SyncPeerServiceSendOperationsProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("SendOperations")),
connect.WithClientOptions(opts...),
),
getLog: connect.NewClient[types.StringValue, v1sync.LogDataEntry](
httpClient,
baseURL+SyncPeerServiceGetLogProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("GetLog")),
connect.WithClientOptions(opts...),
),
setAvailableResources: connect.NewClient[v1sync.SetAvailableResourcesRequest, emptypb.Empty](
httpClient,
baseURL+SyncPeerServiceSetAvailableResourcesProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("SetAvailableResources")),
connect.WithClientOptions(opts...),
),
setConfig: connect.NewClient[v1sync.SetConfigRequest, emptypb.Empty](
httpClient,
baseURL+SyncPeerServiceSetConfigProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("SetConfig")),
connect.WithClientOptions(opts...),
),
getConfig: connect.NewClient[emptypb.Empty, v1sync.RemoteConfig](
httpClient,
baseURL+SyncPeerServiceGetConfigProcedure,
connect.WithSchema(syncPeerServiceMethods.ByName("GetConfig")),
connect.WithClientOptions(opts...),
),
}
}
// syncPeerServiceClient implements SyncPeerServiceClient.
type syncPeerServiceClient struct {
authenticate *connect.Client[v1sync.AuthenticateRequest, emptypb.Empty]
getOperationMetadata *connect.Client[v1.OpSelector, v1sync.GetOperationMetadataResponse]
sendOperations *connect.Client[v1.Operation, emptypb.Empty]
getLog *connect.Client[types.StringValue, v1sync.LogDataEntry]
setAvailableResources *connect.Client[v1sync.SetAvailableResourcesRequest, emptypb.Empty]
setConfig *connect.Client[v1sync.SetConfigRequest, emptypb.Empty]
getConfig *connect.Client[emptypb.Empty, v1sync.RemoteConfig]
}
// Authenticate calls v1sync.SyncPeerService.Authenticate.
func (c *syncPeerServiceClient) Authenticate(ctx context.Context, req *connect.Request[v1sync.AuthenticateRequest]) (*connect.Response[emptypb.Empty], error) {
return c.authenticate.CallUnary(ctx, req)
}
// GetOperationMetadata calls v1sync.SyncPeerService.GetOperationMetadata.
func (c *syncPeerServiceClient) GetOperationMetadata(ctx context.Context, req *connect.Request[v1.OpSelector]) (*connect.Response[v1sync.GetOperationMetadataResponse], error) {
return c.getOperationMetadata.CallUnary(ctx, req)
}
// SendOperations calls v1sync.SyncPeerService.SendOperations.
func (c *syncPeerServiceClient) SendOperations(ctx context.Context) *connect.ClientStreamForClient[v1.Operation, emptypb.Empty] {
return c.sendOperations.CallClientStream(ctx)
}
// GetLog calls v1sync.SyncPeerService.GetLog.
func (c *syncPeerServiceClient) GetLog(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.ServerStreamForClient[v1sync.LogDataEntry], error) {
return c.getLog.CallServerStream(ctx, req)
}
// SetAvailableResources calls v1sync.SyncPeerService.SetAvailableResources.
func (c *syncPeerServiceClient) SetAvailableResources(ctx context.Context, req *connect.Request[v1sync.SetAvailableResourcesRequest]) (*connect.Response[emptypb.Empty], error) {
return c.setAvailableResources.CallUnary(ctx, req)
}
// SetConfig calls v1sync.SyncPeerService.SetConfig.
func (c *syncPeerServiceClient) SetConfig(ctx context.Context, req *connect.Request[v1sync.SetConfigRequest]) (*connect.Response[emptypb.Empty], error) {
return c.setConfig.CallUnary(ctx, req)
}
// GetConfig calls v1sync.SyncPeerService.GetConfig.
func (c *syncPeerServiceClient) GetConfig(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1sync.RemoteConfig], error) {
return c.getConfig.CallUnary(ctx, req)
}
// SyncPeerServiceHandler is an implementation of the v1sync.SyncPeerService service.
type SyncPeerServiceHandler interface {
// Authenticate authenticates the peer with the sync service, must be called before any other methods.
Authenticate(context.Context, *connect.Request[v1sync.AuthenticateRequest]) (*connect.Response[emptypb.Empty], error)
// GetOperationMetadata returns a stream of sync items from the peer.
GetOperationMetadata(context.Context, *connect.Request[v1.OpSelector]) (*connect.Response[v1sync.GetOperationMetadataResponse], error)
SendOperations(context.Context, *connect.ClientStream[v1.Operation]) (*connect.Response[emptypb.Empty], error)
GetLog(context.Context, *connect.Request[types.StringValue], *connect.ServerStream[v1sync.LogDataEntry]) error
// Called everytime the set of resources available to the peer changes.
SetAvailableResources(context.Context, *connect.Request[v1sync.SetAvailableResourcesRequest]) (*connect.Response[emptypb.Empty], error)
// Implements semantics for updating the remote config of the peer.
SetConfig(context.Context, *connect.Request[v1sync.SetConfigRequest]) (*connect.Response[emptypb.Empty], error)
GetConfig(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1sync.RemoteConfig], error)
}
// NewSyncPeerServiceHandler 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 NewSyncPeerServiceHandler(svc SyncPeerServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
syncPeerServiceMethods := v1sync.File_v1sync_syncservice_proto.Services().ByName("SyncPeerService").Methods()
syncPeerServiceAuthenticateHandler := connect.NewUnaryHandler(
SyncPeerServiceAuthenticateProcedure,
svc.Authenticate,
connect.WithSchema(syncPeerServiceMethods.ByName("Authenticate")),
connect.WithHandlerOptions(opts...),
)
syncPeerServiceGetOperationMetadataHandler := connect.NewUnaryHandler(
SyncPeerServiceGetOperationMetadataProcedure,
svc.GetOperationMetadata,
connect.WithSchema(syncPeerServiceMethods.ByName("GetOperationMetadata")),
connect.WithHandlerOptions(opts...),
)
syncPeerServiceSendOperationsHandler := connect.NewClientStreamHandler(
SyncPeerServiceSendOperationsProcedure,
svc.SendOperations,
connect.WithSchema(syncPeerServiceMethods.ByName("SendOperations")),
connect.WithHandlerOptions(opts...),
)
syncPeerServiceGetLogHandler := connect.NewServerStreamHandler(
SyncPeerServiceGetLogProcedure,
svc.GetLog,
connect.WithSchema(syncPeerServiceMethods.ByName("GetLog")),
connect.WithHandlerOptions(opts...),
)
syncPeerServiceSetAvailableResourcesHandler := connect.NewUnaryHandler(
SyncPeerServiceSetAvailableResourcesProcedure,
svc.SetAvailableResources,
connect.WithSchema(syncPeerServiceMethods.ByName("SetAvailableResources")),
connect.WithHandlerOptions(opts...),
)
syncPeerServiceSetConfigHandler := connect.NewUnaryHandler(
SyncPeerServiceSetConfigProcedure,
svc.SetConfig,
connect.WithSchema(syncPeerServiceMethods.ByName("SetConfig")),
connect.WithHandlerOptions(opts...),
)
syncPeerServiceGetConfigHandler := connect.NewUnaryHandler(
SyncPeerServiceGetConfigProcedure,
svc.GetConfig,
connect.WithSchema(syncPeerServiceMethods.ByName("GetConfig")),
connect.WithHandlerOptions(opts...),
)
return "/v1sync.SyncPeerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case SyncPeerServiceAuthenticateProcedure:
syncPeerServiceAuthenticateHandler.ServeHTTP(w, r)
case SyncPeerServiceGetOperationMetadataProcedure:
syncPeerServiceGetOperationMetadataHandler.ServeHTTP(w, r)
case SyncPeerServiceSendOperationsProcedure:
syncPeerServiceSendOperationsHandler.ServeHTTP(w, r)
case SyncPeerServiceGetLogProcedure:
syncPeerServiceGetLogHandler.ServeHTTP(w, r)
case SyncPeerServiceSetAvailableResourcesProcedure:
syncPeerServiceSetAvailableResourcesHandler.ServeHTTP(w, r)
case SyncPeerServiceSetConfigProcedure:
syncPeerServiceSetConfigHandler.ServeHTTP(w, r)
case SyncPeerServiceGetConfigProcedure:
syncPeerServiceGetConfigHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedSyncPeerServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedSyncPeerServiceHandler struct{}
func (UnimplementedSyncPeerServiceHandler) Authenticate(context.Context, *connect.Request[v1sync.AuthenticateRequest]) (*connect.Response[emptypb.Empty], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.Authenticate is not implemented"))
}
func (UnimplementedSyncPeerServiceHandler) GetOperationMetadata(context.Context, *connect.Request[v1.OpSelector]) (*connect.Response[v1sync.GetOperationMetadataResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.GetOperationMetadata is not implemented"))
}
func (UnimplementedSyncPeerServiceHandler) SendOperations(context.Context, *connect.ClientStream[v1.Operation]) (*connect.Response[emptypb.Empty], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.SendOperations is not implemented"))
}
func (UnimplementedSyncPeerServiceHandler) GetLog(context.Context, *connect.Request[types.StringValue], *connect.ServerStream[v1sync.LogDataEntry]) error {
return connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.GetLog is not implemented"))
}
func (UnimplementedSyncPeerServiceHandler) SetAvailableResources(context.Context, *connect.Request[v1sync.SetAvailableResourcesRequest]) (*connect.Response[emptypb.Empty], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.SetAvailableResources is not implemented"))
}
func (UnimplementedSyncPeerServiceHandler) SetConfig(context.Context, *connect.Request[v1sync.SetConfigRequest]) (*connect.Response[emptypb.Empty], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.SetConfig is not implemented"))
}
func (UnimplementedSyncPeerServiceHandler) GetConfig(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1sync.RemoteConfig], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1sync.SyncPeerService.GetConfig is not implemented"))
}
-18
View File
@@ -1,18 +0,0 @@
package syncapi
import (
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/gen/go/v1sync/v1syncconnect"
)
type syncClient struct {
client *v1syncconnect.SyncPeerServiceClient
peer *v1.Multihost_Peer
}
func newSyncClient(client *v1syncconnect.SyncPeerServiceClient, peer *v1.Multihost_Peer) *syncClient {
return &syncClient{
client: client,
peer: peer,
}
}
+111
View File
@@ -0,0 +1,111 @@
package syncapi
import (
"context"
"errors"
"fmt"
"io"
"time"
"connectrpc.com/connect"
"github.com/garethgeorge/backrest/gen/go/v1sync"
)
type syncCommandStreamTrait interface {
Send(item *v1sync.SyncStreamItem) error
Receive() (*v1sync.SyncStreamItem, error)
}
var _ syncCommandStreamTrait = (*connect.BidiStream[v1sync.SyncStreamItem, v1sync.SyncStreamItem])(nil) // Ensure that connect.BidiStream implements syncCommandStreamTrait
var _ syncCommandStreamTrait = (*connect.BidiStreamForClient[v1sync.SyncStreamItem, v1sync.SyncStreamItem])(nil) // Ensure that connect.BidiStreamForClient implements syncCommandStreamTrait
type bidiSyncCommandStream struct {
sendChan chan *v1sync.SyncStreamItem
recvChan chan *v1sync.SyncStreamItem
terminateWithErrChan chan error
}
func newBidiSyncCommandStream() *bidiSyncCommandStream {
return &bidiSyncCommandStream{
sendChan: make(chan *v1sync.SyncStreamItem, 64), // Buffered channel to allow sending items without blocking
recvChan: make(chan *v1sync.SyncStreamItem, 1),
terminateWithErrChan: make(chan error, 1),
}
}
func (s *bidiSyncCommandStream) Send(item *v1sync.SyncStreamItem) {
select {
case s.sendChan <- item:
default:
// Try again with a timeout, if it fails, send an error to terminate the stream
select {
case s.sendChan <- item:
case <-time.After(100 * time.Millisecond):
s.SendErrorAndTerminate(NewSyncErrorDisconnected(errors.New("send channel is full, cannot send item")))
}
}
}
// SendErrorAndTerminate sends an error to the termination channel.
// If the error is nil, it terminates only.
func (s *bidiSyncCommandStream) SendErrorAndTerminate(err error) {
if err == nil {
return
}
select {
case s.terminateWithErrChan <- err:
default:
// If the channel is full, we can't send the error, so we just ignore it.
// This is a best-effort termination.
}
}
func (s *bidiSyncCommandStream) ReadChannel() chan *v1sync.SyncStreamItem {
return s.recvChan
}
func (s *bidiSyncCommandStream) ReceiveWithinDuration(d time.Duration) *v1sync.SyncStreamItem {
select {
case item := <-s.recvChan:
return item
case <-time.After(d):
return nil // Return nil if no item is received within the duration
}
}
func (s *bidiSyncCommandStream) ConnectStream(ctx context.Context, stream syncCommandStreamTrait) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
for ctx.Err() == nil {
if val, err := stream.Receive(); err != nil {
s.SendErrorAndTerminate(NewSyncErrorDisconnected(fmt.Errorf("receiving item: %w", err)))
break
} else {
s.recvChan <- val
}
}
close(s.recvChan)
}()
for {
select {
case item := <-s.sendChan:
if item == nil {
continue
}
if err := stream.Send(item); err != nil {
if errors.Is(err, io.EOF) {
err = fmt.Errorf("connection failed or dropped: %w", err)
}
s.SendErrorAndTerminate(err)
return err
}
case err := <-s.terminateWithErrChan:
return err // Terminate the stream with the error or nil if no error was sent
case <-ctx.Done():
// Context is done, we should stop processing.
return ctx.Err()
}
}
}
-92
View File
@@ -1,92 +0,0 @@
package syncapi
import (
"context"
"net/http"
"testing"
"time"
"connectrpc.com/connect"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/gen/go/v1sync/v1syncconnect"
"github.com/garethgeorge/backrest/internal/api/syncapi/tunnel"
"github.com/garethgeorge/backrest/internal/logstore"
"github.com/garethgeorge/backrest/internal/oplog"
"github.com/garethgeorge/backrest/internal/oplog/memstore"
"github.com/garethgeorge/backrest/internal/testutil"
"github.com/stretchr/testify/assert"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/protobuf/proto"
)
func TestHandler(t *testing.T) {
ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background())
defer cancel()
handler := newHandlerForTest(t)
handler.SetLogger(testutil.NewTestLogger(t))
mux := http.NewServeMux()
mux.Handle(v1syncconnect.NewSyncPeerServiceHandler(handler))
// Construct the service we want to provide
provider := tunnel.NewConnectionProvider(10 /* bufferSize */)
server := &http.Server{
Handler: h2c.NewHandler(mux, &http2.Server{}), // h2c is HTTP/2 without TLS for grpc-connect support.
}
testutil.ServeForTest("HTTPServer", t, server, provider)
// Serve the tunnel service which is proxying to the provided service.
tunnelServiceMux := http.NewServeMux()
tunnelHandler := tunnel.NewTunnelHandler(provider).SetLogger(testutil.NewTestLogger(t))
tunnelServiceMux.Handle(v1syncconnect.NewTunnelServiceHandler(tunnelHandler))
grpcServer := &http.Server{
Addr: testutil.AllocOpenBindAddr(t),
Handler: h2c.NewHandler(tunnelServiceMux, &http2.Server{}), // h2c is HTTP/2 without TLS for grpc-connect support.
}
testutil.ListenAndServeForTest("gRPCTunnelServer", t, grpcServer)
// With that "simple" setup out of the way, we can try establishing a connection.
// TODO: this really could be made more ergonomic
tunnelClient := v1syncconnect.NewTunnelServiceClient(tunnel.NewInsecureHttpClient(), "http://"+grpcServer.Addr)
stream := tunnelClient.Tunnel(ctx)
wrapped := tunnel.NewWrappedStreamFromClient(stream, tunnel.WithLogger(testutil.NewTestLogger(t).Named("tunnel-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")
}()
// Spin until the connection is ready.
for !wrapped.IsReady() {
time.Sleep(10 * time.Millisecond)
}
t.Log("Connection is ready")
client := v1syncconnect.NewSyncPeerServiceClient(tunnel.NewWrappedStreamClient(wrapped), "https://localhost:80")
resp, err := client.GetOperationMetadata(ctx, connect.NewRequest(&v1.OpSelector{
OriginalInstanceKeyid: proto.String("test-instance-key-id"),
}))
if err != nil {
t.Fatalf("Failed to get operation metadata: %v", err)
}
t.Logf("Received response: %v", resp.Msg)
}
func newHandlerForTest(t *testing.T) *syncHandler {
t.Helper()
// Create a new oplog
opstore := memstore.NewMemStore()
oplog, err := oplog.NewOpLog(opstore)
assert.NoError(t, err, "Failed to create oplog")
// Create a new log store
logStore, err := logstore.NewLogStore(t.TempDir())
assert.NoError(t, err, "Failed to create log store")
// Create the sync handler
return NewSyncHandler(oplog, logStore)
}
@@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/garethgeorge/backrest/gen/go/v1sync"
"github.com/garethgeorge/backrest/internal/kvstore"
"github.com/google/go-cmp/cmp"
)
@@ -33,8 +34,21 @@ func TestPeerStateManager_GetSet(t *testing.T) {
InstanceID: "testInstance",
KeyID: keyID,
LastHeartbeat: time.Now().Round(time.Millisecond),
KnownRepos: map[string]struct{}{"repo1": {}, "repo2": {}},
KnownPlans: map[string]struct{}{"plan1": {}, "plan2": {}},
KnownRepos: map[string]*v1sync.RepoMetadata{
"repo1": {
Id: "repo1",
Guid: "guid1",
},
"repo2": {
Id: "repo2",
Guid: "guid2",
},
},
KnownPlans: map[string]*v1sync.PlanMetadata{
"plan1": {
Id: "plan1",
},
},
}
psm.SetPeerState(keyID, state)
gotState := psm.GetPeerState(keyID)
-107
View File
@@ -1,107 +0,0 @@
package tunnel
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"sync"
)
type crypt struct {
secret []byte
block cipher.Block
once sync.Once
err error
}
func newCrypt(secret []byte) *crypt {
return &crypt{secret: secret}
}
func (c *crypt) init() {
c.block, c.err = aes.NewCipher(c.secret)
}
// pkcs5Padding adds PKCS#5 padding to the data
func pkcs5Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := make([]byte, padding)
for i := range padtext {
padtext[i] = byte(padding)
}
return append(data, padtext...)
}
// pkcs5Unpadding removes PKCS#5 padding from the data
func pkcs5Unpadding(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, errors.New("data is empty")
}
padding := int(data[len(data)-1])
if padding > len(data) || padding == 0 {
return nil, errors.New("invalid padding")
}
// Check that all padding bytes are correct
for i := len(data) - padding; i < len(data); i++ {
if data[i] != byte(padding) {
return nil, errors.New("invalid padding")
}
}
return data[:len(data)-padding], nil
}
func (c *crypt) Encrypt(data []byte) ([]byte, error) {
c.once.Do(c.init)
if c.err != nil {
return nil, c.err
}
// Add PKCS#5 padding
paddedData := pkcs5Padding(data, aes.BlockSize)
// Create a random IV
ciphertext := make([]byte, aes.BlockSize+len(paddedData))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
// Encrypt the data
mode := cipher.NewCBCEncrypter(c.block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], paddedData)
return ciphertext, nil
}
func (c *crypt) Decrypt(data []byte) ([]byte, error) {
if len(data) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
c.once.Do(c.init)
if c.err != nil {
return nil, c.err
}
// Extract IV and ciphertext
iv := data[:aes.BlockSize]
ciphertext := data[aes.BlockSize:]
// Check that ciphertext is a multiple of block size
if len(ciphertext)%aes.BlockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
// Decrypt the data
mode := cipher.NewCBCDecrypter(c.block, iv)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
// Remove PKCS#5 padding
return pkcs5Unpadding(plaintext)
}
-338
View File
@@ -1,338 +0,0 @@
package tunnel
import (
"bytes"
"crypto/rand"
"testing"
)
func TestNewCrypt(t *testing.T) {
secret := []byte("test-secret-key-")
c := newCrypt(secret)
if c == nil {
t.Fatal("newCrypt returned nil")
}
if !bytes.Equal(c.secret, secret) {
t.Errorf("expected secret %v, got %v", secret, c.secret)
}
}
func TestPKCS5Padding(t *testing.T) {
tests := []struct {
name string
data []byte
blockSize int
expected int // expected length after padding
}{
{
name: "empty data",
data: []byte{},
blockSize: 16,
expected: 16,
},
{
name: "data length equals block size",
data: make([]byte, 16),
blockSize: 16,
expected: 32,
},
{
name: "data length less than block size",
data: []byte("hello"),
blockSize: 16,
expected: 16,
},
{
name: "data length greater than block size",
data: make([]byte, 20),
blockSize: 16,
expected: 32,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
padded := pkcs5Padding(tt.data, tt.blockSize)
if len(padded) != tt.expected {
t.Errorf("expected length %d, got %d", tt.expected, len(padded))
}
// Check that padding is correct
paddingLen := tt.blockSize - (len(tt.data) % tt.blockSize)
for i := len(tt.data); i < len(padded); i++ {
if padded[i] != byte(paddingLen) {
t.Errorf("invalid padding byte at position %d: expected %d, got %d", i, paddingLen, padded[i])
}
}
})
}
}
func TestPKCS5Unpadding(t *testing.T) {
tests := []struct {
name string
data []byte
expectErr bool
expected []byte
}{
{
name: "valid padding",
data: []byte{1, 2, 3, 4, 5, 5, 5, 5, 5},
expectErr: false,
expected: []byte{1, 2, 3, 4},
},
{
name: "full block padding",
data: []byte{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16},
expectErr: false,
expected: []byte{},
},
{
name: "empty data",
data: []byte{},
expectErr: true,
expected: nil,
},
{
name: "zero padding",
data: []byte{1, 2, 3, 0},
expectErr: true,
expected: nil,
},
{
name: "invalid padding length",
data: []byte{1, 2, 3, 10},
expectErr: true,
expected: nil,
},
{
name: "inconsistent padding",
data: []byte{1, 2, 3, 3, 2},
expectErr: true,
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := pkcs5Unpadding(tt.data)
if tt.expectErr {
if err == nil {
t.Error("expected error but got none")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !bytes.Equal(result, tt.expected) {
t.Errorf("expected %v, got %v", tt.expected, result)
}
}
})
}
}
func TestEncryptDecrypt(t *testing.T) {
secret := []byte("my-secret-key...")
c := newCrypt(secret)
tests := []struct {
name string
data []byte
}{
{
name: "empty data",
data: []byte{},
},
{
name: "short data",
data: []byte("hello"),
},
{
name: "data equal to block size",
data: make([]byte, 16),
},
{
name: "data larger than block size",
data: []byte("this is a longer message that spans multiple blocks"),
},
{
name: "binary data",
data: []byte{0, 1, 2, 3, 255, 254, 253, 252},
},
{
name: "large data",
data: make([]byte, 1024),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Fill large data with random bytes
if len(tt.data) == 1024 {
rand.Read(tt.data)
}
// Encrypt
encrypted, err := c.Encrypt(tt.data)
if err != nil {
t.Fatalf("encryption failed: %v", err)
}
// Verify encrypted data is different from original (unless original is empty)
if len(tt.data) > 0 && bytes.Equal(encrypted, tt.data) {
t.Error("encrypted data is same as original")
}
// Verify encrypted data includes IV (at least block size)
if len(encrypted) < 16 {
t.Errorf("encrypted data too short: %d bytes", len(encrypted))
}
// Decrypt
decrypted, err := c.Decrypt(encrypted)
if err != nil {
t.Fatalf("decryption failed: %v", err)
}
// Verify decrypted matches original
if !bytes.Equal(decrypted, tt.data) {
t.Errorf("decrypted data doesn't match original.\nOriginal: %v\nDecrypted: %v", tt.data, decrypted)
}
})
}
}
func TestEncryptionIsRandomized(t *testing.T) {
secret := []byte("test-secret.....")
c := newCrypt(secret)
data := []byte("same data every time")
// Encrypt the same data multiple times
encrypted1, err := c.Encrypt(data)
if err != nil {
t.Fatalf("encryption 1 failed: %v", err)
}
encrypted2, err := c.Encrypt(data)
if err != nil {
t.Fatalf("encryption 2 failed: %v", err)
}
// Encrypted results should be different due to random IV
if bytes.Equal(encrypted1, encrypted2) {
t.Error("encryption is not randomized - same input produced same output")
}
// But both should decrypt to the same original data
decrypted1, err := c.Decrypt(encrypted1)
if err != nil {
t.Fatalf("decryption 1 failed: %v", err)
}
decrypted2, err := c.Decrypt(encrypted2)
if err != nil {
t.Fatalf("decryption 2 failed: %v", err)
}
if !bytes.Equal(decrypted1, data) || !bytes.Equal(decrypted2, data) {
t.Error("decryption failed to recover original data")
}
}
func TestDecryptInvalidData(t *testing.T) {
secret := []byte("test-secret.....")
c := newCrypt(secret)
tests := []struct {
name string
data []byte
}{
{
name: "too short",
data: []byte("short"),
},
{
name: "not multiple of block size",
data: make([]byte, 17), // 16 + 1
},
{
name: "corrupted padding",
data: func() []byte {
// Create valid encrypted data then corrupt it
original := []byte("test data")
encrypted, _ := c.Encrypt(original)
// Corrupt the last byte (padding)
encrypted[len(encrypted)-1] = 255
return encrypted
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := c.Decrypt(tt.data)
if err == nil {
t.Error("expected decryption to fail but it succeeded")
}
})
}
}
func TestDifferentSecretsCannotDecrypt(t *testing.T) {
data := []byte("secret message")
c1 := newCrypt([]byte("secret1........."))
c2 := newCrypt([]byte("secret2........."))
// Encrypt with first secret
encrypted, err := c1.Encrypt(data)
if err != nil {
t.Fatalf("encryption failed: %v", err)
}
// Try to decrypt with second secret
_, err = c2.Decrypt(encrypted)
if err == nil {
t.Error("decryption with wrong secret should fail")
}
}
func BenchmarkEncrypt(b *testing.B) {
secret := []byte("benchmark-secret")
c := newCrypt(secret)
data := make([]byte, 1024)
rand.Read(data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := c.Encrypt(data)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecrypt(b *testing.B) {
secret := []byte("benchmark-secret")
c := newCrypt(secret)
data := make([]byte, 1024)
rand.Read(data)
encrypted, err := c.Encrypt(data)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := c.Decrypt(encrypted)
if err != nil {
b.Fatal(err)
}
}
}
-211
View File
@@ -1,211 +0,0 @@
package tunnel
import (
"bytes"
"fmt"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/garethgeorge/backrest/gen/go/v1sync"
"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(streamm stream, connId int64, secret []byte, logger *zap.Logger) *connState {
if logger != nil {
logger = logger.Named("connState").With(
zap.Int64("connId", connId))
}
var s stream = streamm
// if len(secret) > 0 {
// s = newCryptedStream(streamm, secret)
// }
return &connState{
connId: connId,
seqno: 0,
stream: s,
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(&v1sync.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(&v1sync.TunnelMessage{
ConnId: c.connId,
Data: bytes.Clone(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(&v1sync.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.Before(time.Now()) {
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
}
-38
View File
@@ -1,38 +0,0 @@
package tunnel
import (
"crypto/tls"
"net"
"net/http"
"time"
"connectrpc.com/connect"
"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() connect.HTTPClient {
return &http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
IdleConnTimeout: 300 * time.Second,
ReadIdleTimeout: 60 * time.Second,
},
}
}
func NewWrappedStreamClient(stream *WrappedStream) connect.HTTPClient {
return &http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
// Note that the addr doesn't matter at all when using a wrapped stream.
return stream.Dial()
},
},
}
}
-59
View File
@@ -1,59 +0,0 @@
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
}
}
-146
View File
@@ -1,146 +0,0 @@
package tunnel
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"connectrpc.com/connect"
"github.com/garethgeorge/backrest/gen/go/v1sync"
"github.com/hashicorp/go-multierror"
"google.golang.org/protobuf/proto"
)
var ErrStreamClosed = errors.New("stream closed")
type stream interface {
Send(item *v1sync.TunnelMessage) error
Receive() (*v1sync.TunnelMessage, error)
Close() error
}
type cryptedStream struct {
stream
crypt
}
func newCryptedStream(s stream, secret []byte) *cryptedStream {
return &cryptedStream{
stream: s,
crypt: crypt{
secret: secret,
},
}
}
func (cs *cryptedStream) Send(item *v1sync.TunnelMessage) error {
if item.Data == nil {
return cs.stream.Send(item)
}
bytes, err := proto.Marshal(item)
if err != nil {
return err
}
enc, err := cs.Encrypt(bytes)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
return cs.stream.Send(&v1sync.TunnelMessage{
Encrypted: enc,
})
}
func (cs *cryptedStream) Receive() (*v1sync.TunnelMessage, error) {
msg, err := cs.stream.Receive()
if err != nil {
return nil, err
}
dec, err := cs.Decrypt(msg.Encrypted)
if err != nil {
return nil, fmt.Errorf("decrypt: %w", err)
}
var tm v1sync.TunnelMessage
if err := proto.Unmarshal(dec, &tm); err != nil {
return nil, fmt.Errorf("unmarshal: %w", err)
}
if len(tm.Encrypted) != 0 {
return nil, fmt.Errorf("unexpected encrypted field in decrypted message")
}
return &tm, nil
}
type clientStream struct {
sendMu sync.Mutex
receiveMu sync.Mutex
stream *connect.BidiStreamForClient[v1sync.TunnelMessage, v1sync.TunnelMessage]
closed atomic.Bool
}
func (s *clientStream) Send(item *v1sync.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() (*v1sync.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[v1sync.TunnelMessage, v1sync.TunnelMessage]
closed atomic.Bool
}
func (s *serverStream) Send(item *v1sync.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() (*v1sync.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
}
-117
View File
@@ -1,117 +0,0 @@
package tunnel
import (
"bufio"
"context"
"io"
"net/http"
"testing"
"time"
"github.com/garethgeorge/backrest/gen/go/v1sync/v1syncconnect"
"github.com/garethgeorge/backrest/internal/testutil"
"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
}
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) {
t.Skip("skipping syncapi tests")
ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background())
defer cancel()
// Construct the service we want to provide
provider := NewConnectionProvider(10 /* bufferSize */)
sampleHandler := NewTunnelHandler(provider).SetLogger(testutil.NewTestLogger(t))
server := &http.Server{
Handler: newHelloHandler(),
}
testutil.ServeForTest("HTTPServer", t, server, provider)
// Serve the sample handler
mux := http.NewServeMux()
mux.Handle(v1syncconnect.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.
}
testutil.ListenAndServeForTest("gRPCServer", t, grpcServer)
// Create a client and connect to the server
client := v1syncconnect.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")
}
@@ -1,41 +0,0 @@
package tunnel
import (
"context"
"connectrpc.com/connect"
"github.com/garethgeorge/backrest/gen/go/v1sync"
"github.com/garethgeorge/backrest/gen/go/v1sync/v1syncconnect"
"go.uber.org/zap"
)
type TunnelHandler struct {
logger *zap.Logger
streams []*WrappedStream
provider *ConnectionProvider
}
var _ v1syncconnect.TunnelServiceHandler = (*TunnelHandler)(nil)
func NewTunnelHandler(provider *ConnectionProvider) *TunnelHandler {
return &TunnelHandler{
logger: zap.NewNop(),
streams: make([]*WrappedStream, 0),
provider: provider,
}
}
func (th *TunnelHandler) SetLogger(logger *zap.Logger) *TunnelHandler {
if logger == nil {
logger = zap.NewNop()
}
th.logger = logger.Named("tunnel-handler")
return th
}
func (th *TunnelHandler) Tunnel(ctx context.Context, stream *connect.BidiStream[v1sync.TunnelMessage, v1sync.TunnelMessage]) error {
wrapped := NewWrappedStream(stream, WithLogger(th.logger))
wrapped.ProvideConnectionsTo(th.provider)
th.streams = append(th.streams, wrapped)
return wrapped.HandlePackets(ctx)
}
@@ -1,326 +0,0 @@
package tunnel
import (
"context"
"crypto/ecdh"
"crypto/rand"
"fmt"
"net"
"sync"
"sync/atomic"
"time"
"connectrpc.com/connect"
"github.com/garethgeorge/backrest/gen/go/v1sync"
"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
sharedSecretCh chan struct{}
sharedSecret []byte
}
func newWrappedStreamInternal(stream stream, isClient bool, opts ...WrappedStreamOptions) *WrappedStream {
ws := &WrappedStream{
isClient: isClient,
stream: stream,
heartbeatInterval: 30 * time.Second,
conns: make(map[int64]*connState),
sharedSecretCh: make(chan struct{}),
}
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[v1sync.TunnelMessage, v1sync.TunnelMessage], opts ...WrappedStreamOptions) *WrappedStream {
return newWrappedStreamInternal(&serverStream{
stream: stream,
}, false, opts...)
}
func NewWrappedStreamFromClient(stream *connect.BidiStreamForClient[v1sync.TunnelMessage, v1sync.TunnelMessage], opts ...WrappedStreamOptions) *WrappedStream {
return newWrappedStreamInternal(&clientStream{
stream: stream,
}, true, opts...)
}
func (ws *WrappedStream) allocConnID() int64 {
return ws.lastConnID.Add(2)
}
func (ws *WrappedStream) getSharedSecret() ([]byte, error) {
<-ws.sharedSecretCh
if ws.sharedSecret == nil {
return nil, fmt.Errorf("shared secret not available")
}
return ws.sharedSecret, nil
}
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")
}
secret, err := ws.getSharedSecret()
if err != nil {
return nil, fmt.Errorf("get shared secret: %w", err)
}
connID := ws.allocConnID()
new := newConnState(ws.stream, connID, secret, 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, stream stream) {
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 := stream.Send(&v1sync.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(&v1sync.TunnelMessage{
ConnId: -100, // handshake packet
PubkeyEcdhX25519: key.PublicKey().Bytes(),
}); err != nil {
return fmt.Errorf("send handshake packet: %w", err)
}
go func() {
timeoutTimer := time.NewTimer(5 * time.Second)
defer timeoutTimer.Stop()
select {
case <-timeoutTimer.C:
if ws.logger != nil {
ws.logger.Warn("timeout waiting for handshake response")
}
ws.Shutdown()
case <-ws.sharedSecretCh:
if ws.logger != nil {
ws.logger.Info("handshake response received, shared secret established")
}
}
}()
// 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)
}
sharedSecret, err := key.ECDH(peerKey)
if err != nil {
return fmt.Errorf("compute shared key: %w", err)
}
ws.sharedSecret = sharedSecret
close(ws.sharedSecretCh)
// cryptedStream := newCryptedStream(ws.stream, ws.sharedSecret)
cryptedStream := ws.stream
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.sharedSecret, 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, cryptedStream)
for {
msg, err := cryptedStream.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()
}
+15 -35
View File
@@ -15,6 +15,13 @@ import "google/api/annotations.proto";
import "google/protobuf/any.proto";
// BackrestSyncService provides methods to sync data between backrest instances.
// This service provides its own authentication and authorization.
service BackrestSyncService {
rpc Sync(stream SyncStreamItem) returns (stream SyncStreamItem) {}
}
// BackrestSyncStateService provides methods to query the sync state of known hosts and clients.
// This service should be served behind authentication and authorization.
service BackrestSyncStateService {
@@ -39,23 +46,6 @@ message PeerState {
int64 last_heartbeat_millis = 8; // The last time the peer sent a heartbeat, in milliseconds since epoch.
}
service SyncPeerService {
// Authenticate authenticates the peer with the sync service, must be called before any other methods.
rpc Authenticate(AuthenticateRequest) returns (google.protobuf.Empty) {}
// GetOperationMetadata returns a stream of sync items from the peer.
rpc GetOperationMetadata(v1.OpSelector) returns (GetOperationMetadataResponse) {}
rpc SendOperations(stream v1.Operation) returns (google.protobuf.Empty) {}
rpc GetLog(types.StringValue) returns (stream LogDataEntry) {}
// Called everytime the set of resources available to the peer changes.
rpc SetAvailableResources(SetAvailableResourcesRequest) returns (google.protobuf.Empty) {}
// Implements semantics for updating the remote config of the peer.
rpc SetConfig(SetConfigRequest) returns (google.protobuf.Empty) {}
rpc GetConfig(google.protobuf.Empty) returns (RemoteConfig) {}
}
message AuthenticateRequest {
v1.SignedMessage instance_id = 1; // The ID of the peer instance.
}
@@ -116,7 +106,6 @@ message AuthorizationToken {
v1.SignedMessage instance_id = 2; // The ID of the peer instance.
}
/*SyncStreamItem
message SyncStreamItem {
oneof action {
v1.SignedMessage signed_message = 1;
@@ -136,8 +125,8 @@ message SyncStreamItem {
message SyncActionHandshake {
int64 protocol_version = 1;
PublicKey public_key = 2;
SignedMessage instance_id = 3;
v1.PublicKey public_key = 2;
v1.SignedMessage instance_id = 3;
}
// SyncActionHeartbeat is sent periodically to keep the connection alive.
@@ -148,15 +137,15 @@ message SyncStreamItem {
}
message SyncActionSetConfig {
repeated Repo repos = 1;
repeated Plan plans = 2;
repeated v1.Repo repos = 1;
repeated v1.Plan plans = 2;
repeated string repos_to_delete = 3;
repeated string plans_to_delete = 4;
}
message SyncActionListResources {
repeated SyncRepoMetadata repos = 1;
repeated SyncPlanMetadata plans = 2;
repeated RepoMetadata repos = 1;
repeated PlanMetadata plans = 2;
}
message SyncActionConnectRepo {
@@ -174,7 +163,7 @@ message SyncStreamItem {
message SyncActionDiffOperations {
// Client connects and sends a list of "have_operations" that exist in its log.
// have_operation_ids and have_operation_modnos are the operation IDs and modnos that the client has when zip'd pairwise.
OpSelector have_operations_selector = 1;
v1.OpSelector have_operations_selector = 1;
repeated int64 have_operation_ids = 2;
repeated int64 have_operation_modnos = 3;
// Server sends a list of "request_operations" for any operations that it doesn't have.
@@ -182,7 +171,7 @@ message SyncStreamItem {
}
message SyncActionSendOperations {
OperationEvent event = 1;
v1.OperationEvent event = 1;
}
message SyncActionGetLog {
@@ -210,12 +199,3 @@ message SyncStreamItem {
string ed25519 = 2 [json_name="ed25519pub"]; // base64 encoded public key
}
}
// RemoteConfig contains shareable properties from a remote backrest instance.
message RemoteConfig {
int32 modno = 1; // The modno of the config.
int32 version = 2; // The storage version of the config.
repeated Repo repos = 3;
repeated Plan plans = 4;
}
*/
+424 -76
View File
@@ -9,13 +9,11 @@ import { file_v1_config } from "../v1/config_pb";
import type { PublicKey, SignedMessage } from "../v1/crypto_pb";
import { file_v1_crypto } from "../v1/crypto_pb";
import { file_v1_restic } from "../v1/restic_pb";
import type { OpSelectorSchema } from "../v1/service_pb";
import type { OpSelector } from "../v1/service_pb";
import { file_v1_service } from "../v1/service_pb";
import type { OperationSchema } from "../v1/operations_pb";
import type { OperationEvent } from "../v1/operations_pb";
import { file_v1_operations } from "../v1/operations_pb";
import type { StringValueSchema } from "../types/value_pb";
import { file_types_value } from "../types/value_pb";
import type { EmptySchema } 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";
@@ -24,7 +22,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file v1sync/syncservice.proto.
*/
export const file_v1sync_syncservice: GenFile = /*@__PURE__*/
fileDesc("Chh2MXN5bmMvc3luY3NlcnZpY2UucHJvdG8SBnYxc3luYyIrChZTeW5jU3RhdGVTdHJlYW1SZXF1ZXN0EhEKCXN1YnNjcmliZRgBIAEoCCKbAgoJUGVlclN0YXRlEhgKEHBlZXJfaW5zdGFuY2VfaWQYASABKAkSEgoKcGVlcl9rZXlpZBgCIAEoCRImCgVzdGF0ZRgDIAEoDjIXLnYxc3luYy5Db25uZWN0aW9uU3RhdGUSFgoOc3RhdHVzX21lc3NhZ2UYBCABKAkSKQoLa25vd25fcGxhbnMYBSADKAsyFC52MXN5bmMuUGxhbk1ldGFkYXRhEikKC2tub3duX3JlcG9zGAYgAygLMhQudjFzeW5jLlJlcG9NZXRhZGF0YRIrCg1yZW1vdGVfY29uZmlnGAcgASgLMhQudjFzeW5jLlJlbW90ZUNvbmZpZxIdChVsYXN0X2hlYXJ0YmVhdF9taWxsaXMYCCABKAMiPQoTQXV0aGVudGljYXRlUmVxdWVzdBImCgtpbnN0YW5jZV9pZBgBIAEoCzIRLnYxLlNpZ25lZE1lc3NhZ2UiPgocR2V0T3BlcmF0aW9uTWV0YWRhdGFSZXNwb25zZRIOCgZvcF9pZHMYASADKAMSDgoGbW9kbm9zGAIgAygDIl0KDExvZ0RhdGFFbnRyeRIOCgZsb2dfaWQYASABKAkSEgoKb3duZXJfb3BpZBgCIAEoAxIaChJleHBpcmF0aW9uX3RzX3VuaXgYAyABKAMSDQoFY2h1bmsYBCABKAwiaAocU2V0QXZhaWxhYmxlUmVzb3VyY2VzUmVxdWVzdBIjCgVyZXBvcxgBIAMoCzIULnYxc3luYy5QbGFuTWV0YWRhdGESIwoFcGxhbnMYAiADKAsyFC52MXN5bmMuUmVwb01ldGFkYXRhIigKDFJlcG9NZXRhZGF0YRIKCgJpZBgBIAEoCRIMCgRndWlkGAIgASgJIhoKDFBsYW5NZXRhZGF0YRIKCgJpZBgBIAEoCSJ2ChBTZXRDb25maWdSZXF1ZXN0EhcKBXBsYW5zGAEgAygLMggudjEuUGxhbhIXCgVyZXBvcxgCIAMoCzIILnYxLlJlcG8SFwoPcmVwb3NfdG9fZGVsZXRlGAMgAygJEhcKD3BsYW5zX3RvX2RlbGV0ZRgEIAMoCSJgCgxSZW1vdGVDb25maWcSDQoFbW9kbm8YASABKAUSDwoHdmVyc2lvbhgCIAEoBRIXCgVyZXBvcxgDIAMoCzIILnYxLlJlcG8SFwoFcGxhbnMYBCADKAsyCC52MS5QbGFuIl8KEkF1dGhvcml6YXRpb25Ub2tlbhIhCgpwdWJsaWNfa2V5GAEgASgLMg0udjEuUHVibGljS2V5EiYKC2luc3RhbmNlX2lkGAIgASgLMhEudjEuU2lnbmVkTWVzc2FnZSqcAgoPQ29ubmVjdGlvblN0YXRlEhwKGENPTk5FQ1RJT05fU1RBVEVfVU5LTk9XThAAEhwKGENPTk5FQ1RJT05fU1RBVEVfUEVORElORxABEh4KGkNPTk5FQ1RJT05fU1RBVEVfQ09OTkVDVEVEEAISIQodQ09OTkVDVElPTl9TVEFURV9ESVNDT05ORUNURUQQAxIfChtDT05ORUNUSU9OX1NUQVRFX1JFVFJZX1dBSVQQBBIfChtDT05ORUNUSU9OX1NUQVRFX0VSUk9SX0FVVEgQChIjCh9DT05ORUNUSU9OX1NUQVRFX0VSUk9SX1BST1RPQ09MEAsSIwofQ09OTkVDVElPTl9TVEFURV9FUlJPUl9JTlRFUk5BTBAMMmwKGEJhY2tyZXN0U3luY1N0YXRlU2VydmljZRJQChdHZXRQZWVyU3luY1N0YXRlc1N0cmVhbRIeLnYxc3luYy5TeW5jU3RhdGVTdHJlYW1SZXF1ZXN0GhEudjFzeW5jLlBlZXJTdGF0ZSIAMAEy9AMKD1N5bmNQZWVyU2VydmljZRJFCgxBdXRoZW50aWNhdGUSGy52MXN5bmMuQXV0aGVudGljYXRlUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIAEk4KFEdldE9wZXJhdGlvbk1ldGFkYXRhEg4udjEuT3BTZWxlY3RvchokLnYxc3luYy5HZXRPcGVyYXRpb25NZXRhZGF0YVJlc3BvbnNlIgASOwoOU2VuZE9wZXJhdGlvbnMSDS52MS5PcGVyYXRpb24aFi5nb29nbGUucHJvdG9idWYuRW1wdHkiACgBEjYKBkdldExvZxISLnR5cGVzLlN0cmluZ1ZhbHVlGhQudjFzeW5jLkxvZ0RhdGFFbnRyeSIAMAESVwoVU2V0QXZhaWxhYmxlUmVzb3VyY2VzEiQudjFzeW5jLlNldEF2YWlsYWJsZVJlc291cmNlc1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiABI/CglTZXRDb25maWcSGC52MXN5bmMuU2V0Q29uZmlnUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIAEjsKCUdldENvbmZpZxIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRoULnYxc3luYy5SZW1vdGVDb25maWciAEIwWi5naXRodWIuY29tL2dhcmV0aGdlb3JnZS9iYWNrcmVzdC9nZW4vZ28vdjFzeW5jYgZwcm90bzM", [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]);
fileDesc("Chh2MXN5bmMvc3luY3NlcnZpY2UucHJvdG8SBnYxc3luYyIrChZTeW5jU3RhdGVTdHJlYW1SZXF1ZXN0EhEKCXN1YnNjcmliZRgBIAEoCCKbAgoJUGVlclN0YXRlEhgKEHBlZXJfaW5zdGFuY2VfaWQYASABKAkSEgoKcGVlcl9rZXlpZBgCIAEoCRImCgVzdGF0ZRgDIAEoDjIXLnYxc3luYy5Db25uZWN0aW9uU3RhdGUSFgoOc3RhdHVzX21lc3NhZ2UYBCABKAkSKQoLa25vd25fcGxhbnMYBSADKAsyFC52MXN5bmMuUGxhbk1ldGFkYXRhEikKC2tub3duX3JlcG9zGAYgAygLMhQudjFzeW5jLlJlcG9NZXRhZGF0YRIrCg1yZW1vdGVfY29uZmlnGAcgASgLMhQudjFzeW5jLlJlbW90ZUNvbmZpZxIdChVsYXN0X2hlYXJ0YmVhdF9taWxsaXMYCCABKAMiPQoTQXV0aGVudGljYXRlUmVxdWVzdBImCgtpbnN0YW5jZV9pZBgBIAEoCzIRLnYxLlNpZ25lZE1lc3NhZ2UiPgocR2V0T3BlcmF0aW9uTWV0YWRhdGFSZXNwb25zZRIOCgZvcF9pZHMYASADKAMSDgoGbW9kbm9zGAIgAygDIl0KDExvZ0RhdGFFbnRyeRIOCgZsb2dfaWQYASABKAkSEgoKb3duZXJfb3BpZBgCIAEoAxIaChJleHBpcmF0aW9uX3RzX3VuaXgYAyABKAMSDQoFY2h1bmsYBCABKAwiaAocU2V0QXZhaWxhYmxlUmVzb3VyY2VzUmVxdWVzdBIjCgVyZXBvcxgBIAMoCzIULnYxc3luYy5QbGFuTWV0YWRhdGESIwoFcGxhbnMYAiADKAsyFC52MXN5bmMuUmVwb01ldGFkYXRhIigKDFJlcG9NZXRhZGF0YRIKCgJpZBgBIAEoCRIMCgRndWlkGAIgASgJIhoKDFBsYW5NZXRhZGF0YRIKCgJpZBgBIAEoCSJ2ChBTZXRDb25maWdSZXF1ZXN0EhcKBXBsYW5zGAEgAygLMggudjEuUGxhbhIXCgVyZXBvcxgCIAMoCzIILnYxLlJlcG8SFwoPcmVwb3NfdG9fZGVsZXRlGAMgAygJEhcKD3BsYW5zX3RvX2RlbGV0ZRgEIAMoCSJgCgxSZW1vdGVDb25maWcSDQoFbW9kbm8YASABKAUSDwoHdmVyc2lvbhgCIAEoBRIXCgVyZXBvcxgDIAMoCzIILnYxLlJlcG8SFwoFcGxhbnMYBCADKAsyCC52MS5QbGFuIl8KEkF1dGhvcml6YXRpb25Ub2tlbhIhCgpwdWJsaWNfa2V5GAEgASgLMg0udjEuUHVibGljS2V5EiYKC2luc3RhbmNlX2lkGAIgASgLMhEudjEuU2lnbmVkTWVzc2FnZSLZDgoOU3luY1N0cmVhbUl0ZW0SKwoOc2lnbmVkX21lc3NhZ2UYASABKAsyES52MS5TaWduZWRNZXNzYWdlSAASPwoJaGFuZHNoYWtlGAMgASgLMioudjFzeW5jLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25IYW5kc2hha2VIABI/CgloZWFydGJlYXQYBCABKAsyKi52MXN5bmMuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkhlYXJ0YmVhdEgAEkoKD2RpZmZfb3BlcmF0aW9ucxgUIAEoCzIvLnYxc3luYy5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uRGlmZk9wZXJhdGlvbnNIABJKCg9zZW5kX29wZXJhdGlvbnMYFSABKAsyLy52MXN5bmMuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRPcGVyYXRpb25zSAASQgoLc2VuZF9jb25maWcYFiABKAsyKy52MXN5bmMuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRDb25maWdIABJACgpzZXRfY29uZmlnGBggASgLMioudjFzeW5jLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25TZXRDb25maWdIABJICg5saXN0X3Jlc291cmNlcxgZIAEoCzIuLnYxc3luYy5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uTGlzdFJlc291cmNlc0gAEjoKB2dldF9sb2cYGiABKAsyJy52MXN5bmMuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkdldExvZ0gAEkUKDXNlbmRfbG9nX2RhdGEYGyABKAsyLC52MXN5bmMuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRMb2dEYXRhSAASPgoIdGhyb3R0bGUY6AcgASgLMikudjFzeW5jLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25UaHJvdHRsZUgAGnoKE1N5bmNBY3Rpb25IYW5kc2hha2USGAoQcHJvdG9jb2xfdmVyc2lvbhgBIAEoAxIhCgpwdWJsaWNfa2V5GAIgASgLMg0udjEuUHVibGljS2V5EiYKC2luc3RhbmNlX2lkGAMgASgLMhEudjEuU2lnbmVkTWVzc2FnZRoVChNTeW5jQWN0aW9uSGVhcnRiZWF0GjwKFFN5bmNBY3Rpb25TZW5kQ29uZmlnEiQKBmNvbmZpZxgBIAEoCzIULnYxc3luYy5SZW1vdGVDb25maWcaeQoTU3luY0FjdGlvblNldENvbmZpZxIXCgVyZXBvcxgBIAMoCzIILnYxLlJlcG8SFwoFcGxhbnMYAiADKAsyCC52MS5QbGFuEhcKD3JlcG9zX3RvX2RlbGV0ZRgDIAMoCRIXCg9wbGFuc190b19kZWxldGUYBCADKAkaYwoXU3luY0FjdGlvbkxpc3RSZXNvdXJjZXMSIwoFcmVwb3MYASADKAsyFC52MXN5bmMuUmVwb01ldGFkYXRhEiMKBXBsYW5zGAIgAygLMhQudjFzeW5jLlBsYW5NZXRhZGF0YRooChVTeW5jQWN0aW9uQ29ubmVjdFJlcG8SDwoHcmVwb19pZBgBIAEoCRqjAQoYU3luY0FjdGlvbkRpZmZPcGVyYXRpb25zEjAKGGhhdmVfb3BlcmF0aW9uc19zZWxlY3RvchgBIAEoCzIOLnYxLk9wU2VsZWN0b3ISGgoSaGF2ZV9vcGVyYXRpb25faWRzGAIgAygDEh0KFWhhdmVfb3BlcmF0aW9uX21vZG5vcxgDIAMoAxIaChJyZXF1ZXN0X29wZXJhdGlvbnMYBCADKAMaPQoYU3luY0FjdGlvblNlbmRPcGVyYXRpb25zEiEKBWV2ZW50GAEgASgLMhIudjEuT3BlcmF0aW9uRXZlbnQaIgoQU3luY0FjdGlvbkdldExvZxIOCgZsb2dfaWQYASABKAkaZgoVU3luY0FjdGlvblNlbmRMb2dEYXRhEg4KBmxvZ19pZBgBIAEoCRISCgpvd25lcl9vcGlkGAIgASgDEhoKEmV4cGlyYXRpb25fdHNfdW5peBgDIAEoAxINCgVjaHVuaxgEIAEoDBomChJTeW5jQWN0aW9uVGhyb3R0bGUSEAoIZGVsYXlfbXMYASABKAMaOAoZU3luY0VzdGFibGlzaFNoYXJlZFNlY3JldBIbCgdlZDI1NTE5GAIgASgJUgplZDI1NTE5cHViIrQBChNSZXBvQ29ubmVjdGlvblN0YXRlEhwKGENPTk5FQ1RJT05fU1RBVEVfVU5LTk9XThAAEhwKGENPTk5FQ1RJT05fU1RBVEVfUEVORElORxABEh4KGkNPTk5FQ1RJT05fU1RBVEVfQ09OTkVDVEVEEAISIQodQ09OTkVDVElPTl9TVEFURV9VTkFVVEhPUklaRUQQAxIeChpDT05ORUNUSU9OX1NUQVRFX05PVF9GT1VORBAEQggKBmFjdGlvbiqcAgoPQ29ubmVjdGlvblN0YXRlEhwKGENPTk5FQ1RJT05fU1RBVEVfVU5LTk9XThAAEhwKGENPTk5FQ1RJT05fU1RBVEVfUEVORElORxABEh4KGkNPTk5FQ1RJT05fU1RBVEVfQ09OTkVDVEVEEAISIQodQ09OTkVDVElPTl9TVEFURV9ESVNDT05ORUNURUQQAxIfChtDT05ORUNUSU9OX1NUQVRFX1JFVFJZX1dBSVQQBBIfChtDT05ORUNUSU9OX1NUQVRFX0VSUk9SX0FVVEgQChIjCh9DT05ORUNUSU9OX1NUQVRFX0VSUk9SX1BST1RPQ09MEAsSIwofQ09OTkVDVElPTl9TVEFURV9FUlJPUl9JTlRFUk5BTBAMMlMKE0JhY2tyZXN0U3luY1NlcnZpY2USPAoEU3luYxIWLnYxc3luYy5TeW5jU3RyZWFtSXRlbRoWLnYxc3luYy5TeW5jU3RyZWFtSXRlbSIAKAEwATJsChhCYWNrcmVzdFN5bmNTdGF0ZVNlcnZpY2USUAoXR2V0UGVlclN5bmNTdGF0ZXNTdHJlYW0SHi52MXN5bmMuU3luY1N0YXRlU3RyZWFtUmVxdWVzdBoRLnYxc3luYy5QZWVyU3RhdGUiADABQjBaLmdpdGh1Yi5jb20vZ2FyZXRoZ2VvcmdlL2JhY2tyZXN0L2dlbi9nby92MXN5bmNiBnByb3RvMw", [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 v1sync.SyncStateStreamRequest
@@ -355,6 +353,409 @@ export type AuthorizationToken = Message<"v1sync.AuthorizationToken"> & {
export const AuthorizationTokenSchema: GenMessage<AuthorizationToken> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 10);
/**
* @generated from message v1sync.SyncStreamItem
*/
export type SyncStreamItem = Message<"v1sync.SyncStreamItem"> & {
/**
* @generated from oneof v1sync.SyncStreamItem.action
*/
action: {
/**
* @generated from field: v1.SignedMessage signed_message = 1;
*/
value: SignedMessage;
case: "signedMessage";
} | {
/**
* note: mostly deprecated, sent through headers rather than stream.
*
* @generated from field: v1sync.SyncStreamItem.SyncActionHandshake handshake = 3;
*/
value: SyncStreamItem_SyncActionHandshake;
case: "handshake";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionHeartbeat heartbeat = 4;
*/
value: SyncStreamItem_SyncActionHeartbeat;
case: "heartbeat";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionDiffOperations diff_operations = 20;
*/
value: SyncStreamItem_SyncActionDiffOperations;
case: "diffOperations";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionSendOperations send_operations = 21;
*/
value: SyncStreamItem_SyncActionSendOperations;
case: "sendOperations";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionSendConfig send_config = 22;
*/
value: SyncStreamItem_SyncActionSendConfig;
case: "sendConfig";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionSetConfig set_config = 24;
*/
value: SyncStreamItem_SyncActionSetConfig;
case: "setConfig";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionListResources list_resources = 25;
*/
value: SyncStreamItem_SyncActionListResources;
case: "listResources";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionGetLog get_log = 26;
*/
value: SyncStreamItem_SyncActionGetLog;
case: "getLog";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionSendLogData send_log_data = 27;
*/
value: SyncStreamItem_SyncActionSendLogData;
case: "sendLogData";
} | {
/**
* @generated from field: v1sync.SyncStreamItem.SyncActionThrottle throttle = 1000;
*/
value: SyncStreamItem_SyncActionThrottle;
case: "throttle";
} | { case: undefined; value?: undefined };
};
/**
* Describes the message v1sync.SyncStreamItem.
* Use `create(SyncStreamItemSchema)` to create a new message.
*/
export const SyncStreamItemSchema: GenMessage<SyncStreamItem> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionHandshake
*/
export type SyncStreamItem_SyncActionHandshake = Message<"v1sync.SyncStreamItem.SyncActionHandshake"> & {
/**
* @generated from field: int64 protocol_version = 1;
*/
protocolVersion: bigint;
/**
* @generated from field: v1.PublicKey public_key = 2;
*/
publicKey?: PublicKey;
/**
* @generated from field: v1.SignedMessage instance_id = 3;
*/
instanceId?: SignedMessage;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionHandshake.
* Use `create(SyncStreamItem_SyncActionHandshakeSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionHandshakeSchema: GenMessage<SyncStreamItem_SyncActionHandshake> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 0);
/**
* SyncActionHeartbeat is sent periodically to keep the connection alive.
*
* @generated from message v1sync.SyncStreamItem.SyncActionHeartbeat
*/
export type SyncStreamItem_SyncActionHeartbeat = Message<"v1sync.SyncStreamItem.SyncActionHeartbeat"> & {
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionHeartbeat.
* Use `create(SyncStreamItem_SyncActionHeartbeatSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionHeartbeatSchema: GenMessage<SyncStreamItem_SyncActionHeartbeat> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 1);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionSendConfig
*/
export type SyncStreamItem_SyncActionSendConfig = Message<"v1sync.SyncStreamItem.SyncActionSendConfig"> & {
/**
* @generated from field: v1sync.RemoteConfig config = 1;
*/
config?: RemoteConfig;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionSendConfig.
* Use `create(SyncStreamItem_SyncActionSendConfigSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionSendConfigSchema: GenMessage<SyncStreamItem_SyncActionSendConfig> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 2);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionSetConfig
*/
export type SyncStreamItem_SyncActionSetConfig = Message<"v1sync.SyncStreamItem.SyncActionSetConfig"> & {
/**
* @generated from field: repeated v1.Repo repos = 1;
*/
repos: Repo[];
/**
* @generated from field: repeated v1.Plan plans = 2;
*/
plans: Plan[];
/**
* @generated from field: repeated string repos_to_delete = 3;
*/
reposToDelete: string[];
/**
* @generated from field: repeated string plans_to_delete = 4;
*/
plansToDelete: string[];
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionSetConfig.
* Use `create(SyncStreamItem_SyncActionSetConfigSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionSetConfigSchema: GenMessage<SyncStreamItem_SyncActionSetConfig> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 3);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionListResources
*/
export type SyncStreamItem_SyncActionListResources = Message<"v1sync.SyncStreamItem.SyncActionListResources"> & {
/**
* @generated from field: repeated v1sync.RepoMetadata repos = 1;
*/
repos: RepoMetadata[];
/**
* @generated from field: repeated v1sync.PlanMetadata plans = 2;
*/
plans: PlanMetadata[];
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionListResources.
* Use `create(SyncStreamItem_SyncActionListResourcesSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionListResourcesSchema: GenMessage<SyncStreamItem_SyncActionListResources> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 4);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionConnectRepo
*/
export type SyncStreamItem_SyncActionConnectRepo = Message<"v1sync.SyncStreamItem.SyncActionConnectRepo"> & {
/**
* @generated from field: string repo_id = 1;
*/
repoId: string;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionConnectRepo.
* Use `create(SyncStreamItem_SyncActionConnectRepoSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionConnectRepoSchema: GenMessage<SyncStreamItem_SyncActionConnectRepo> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 5);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionDiffOperations
*/
export type SyncStreamItem_SyncActionDiffOperations = Message<"v1sync.SyncStreamItem.SyncActionDiffOperations"> & {
/**
* Client connects and sends a list of "have_operations" that exist in its log.
* have_operation_ids and have_operation_modnos are the operation IDs and modnos that the client has when zip'd pairwise.
*
* @generated from field: v1.OpSelector have_operations_selector = 1;
*/
haveOperationsSelector?: OpSelector;
/**
* @generated from field: repeated int64 have_operation_ids = 2;
*/
haveOperationIds: bigint[];
/**
* @generated from field: repeated int64 have_operation_modnos = 3;
*/
haveOperationModnos: bigint[];
/**
* Server sends a list of "request_operations" for any operations that it doesn't have.
*
* @generated from field: repeated int64 request_operations = 4;
*/
requestOperations: bigint[];
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionDiffOperations.
* Use `create(SyncStreamItem_SyncActionDiffOperationsSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionDiffOperationsSchema: GenMessage<SyncStreamItem_SyncActionDiffOperations> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 6);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionSendOperations
*/
export type SyncStreamItem_SyncActionSendOperations = Message<"v1sync.SyncStreamItem.SyncActionSendOperations"> & {
/**
* @generated from field: v1.OperationEvent event = 1;
*/
event?: OperationEvent;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionSendOperations.
* Use `create(SyncStreamItem_SyncActionSendOperationsSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionSendOperationsSchema: GenMessage<SyncStreamItem_SyncActionSendOperations> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 7);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionGetLog
*/
export type SyncStreamItem_SyncActionGetLog = Message<"v1sync.SyncStreamItem.SyncActionGetLog"> & {
/**
* @generated from field: string log_id = 1;
*/
logId: string;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionGetLog.
* Use `create(SyncStreamItem_SyncActionGetLogSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionGetLogSchema: GenMessage<SyncStreamItem_SyncActionGetLog> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 8);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionSendLogData
*/
export type SyncStreamItem_SyncActionSendLogData = Message<"v1sync.SyncStreamItem.SyncActionSendLogData"> & {
/**
* @generated from field: string log_id = 1;
*/
logId: string;
/**
* Required only for first message in a log data stream.
*
* The operation ID of the operation that owns this log data.
*
* @generated from field: int64 owner_opid = 2;
*/
ownerOpid: bigint;
/**
* Unix timestamp in seconds when the log data expires.
*
* @generated from field: int64 expiration_ts_unix = 3;
*/
expirationTsUnix: bigint;
/**
* Can be sent repeatedly, must be terminated by a packet with size = 0.
*
* @generated from field: bytes chunk = 4;
*/
chunk: Uint8Array;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionSendLogData.
* Use `create(SyncStreamItem_SyncActionSendLogDataSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionSendLogDataSchema: GenMessage<SyncStreamItem_SyncActionSendLogData> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 9);
/**
* @generated from message v1sync.SyncStreamItem.SyncActionThrottle
*/
export type SyncStreamItem_SyncActionThrottle = Message<"v1sync.SyncStreamItem.SyncActionThrottle"> & {
/**
* @generated from field: int64 delay_ms = 1;
*/
delayMs: bigint;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncActionThrottle.
* Use `create(SyncStreamItem_SyncActionThrottleSchema)` to create a new message.
*/
export const SyncStreamItem_SyncActionThrottleSchema: GenMessage<SyncStreamItem_SyncActionThrottle> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 10);
/**
* @generated from message v1sync.SyncStreamItem.SyncEstablishSharedSecret
*/
export type SyncStreamItem_SyncEstablishSharedSecret = Message<"v1sync.SyncStreamItem.SyncEstablishSharedSecret"> & {
/**
* a one-time-use ed25519 public key with a matching unshared private key. Used to perform a key exchange.
* See https://pkg.go.dev/crypto/ecdh#PrivateKey.ECDH .
*
* base64 encoded public key
*
* @generated from field: string ed25519 = 2 [json_name = "ed25519pub"];
*/
ed25519: string;
};
/**
* Describes the message v1sync.SyncStreamItem.SyncEstablishSharedSecret.
* Use `create(SyncStreamItem_SyncEstablishSharedSecretSchema)` to create a new message.
*/
export const SyncStreamItem_SyncEstablishSharedSecretSchema: GenMessage<SyncStreamItem_SyncEstablishSharedSecret> = /*@__PURE__*/
messageDesc(file_v1sync_syncservice, 11, 11);
/**
* @generated from enum v1sync.SyncStreamItem.RepoConnectionState
*/
export enum SyncStreamItem_RepoConnectionState {
/**
* @generated from enum value: CONNECTION_STATE_UNKNOWN = 0;
*/
CONNECTION_STATE_UNKNOWN = 0,
/**
* queried, response not yet received.
*
* @generated from enum value: CONNECTION_STATE_PENDING = 1;
*/
CONNECTION_STATE_PENDING = 1,
/**
* @generated from enum value: CONNECTION_STATE_CONNECTED = 2;
*/
CONNECTION_STATE_CONNECTED = 2,
/**
* @generated from enum value: CONNECTION_STATE_UNAUTHORIZED = 3;
*/
CONNECTION_STATE_UNAUTHORIZED = 3,
/**
* @generated from enum value: CONNECTION_STATE_NOT_FOUND = 4;
*/
CONNECTION_STATE_NOT_FOUND = 4,
}
/**
* Describes the enum v1sync.SyncStreamItem.RepoConnectionState.
*/
export const SyncStreamItem_RepoConnectionStateSchema: GenEnum<SyncStreamItem_RepoConnectionState> = /*@__PURE__*/
enumDesc(file_v1sync_syncservice, 11, 0);
/**
* @generated from enum v1sync.ConnectionState
*/
@@ -406,6 +807,24 @@ export enum ConnectionState {
export const ConnectionStateSchema: GenEnum<ConnectionState> = /*@__PURE__*/
enumDesc(file_v1sync_syncservice, 0);
/**
* BackrestSyncService provides methods to sync data between backrest instances.
* This service provides its own authentication and authorization.
*
* @generated from service v1sync.BackrestSyncService
*/
export const BackrestSyncService: GenService<{
/**
* @generated from rpc v1sync.BackrestSyncService.Sync
*/
sync: {
methodKind: "bidi_streaming";
input: typeof SyncStreamItemSchema;
output: typeof SyncStreamItemSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_v1sync_syncservice, 0);
/**
* BackrestSyncStateService provides methods to query the sync state of known hosts and clients.
* This service should be served behind authentication and authorization.
@@ -421,77 +840,6 @@ export const BackrestSyncStateService: GenService<{
input: typeof SyncStateStreamRequestSchema;
output: typeof PeerStateSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_v1sync_syncservice, 0);
/**
* @generated from service v1sync.SyncPeerService
*/
export const SyncPeerService: GenService<{
/**
* Authenticate authenticates the peer with the sync service, must be called before any other methods.
*
* @generated from rpc v1sync.SyncPeerService.Authenticate
*/
authenticate: {
methodKind: "unary";
input: typeof AuthenticateRequestSchema;
output: typeof EmptySchema;
},
/**
* GetOperationMetadata returns a stream of sync items from the peer.
*
* @generated from rpc v1sync.SyncPeerService.GetOperationMetadata
*/
getOperationMetadata: {
methodKind: "unary";
input: typeof OpSelectorSchema;
output: typeof GetOperationMetadataResponseSchema;
},
/**
* @generated from rpc v1sync.SyncPeerService.SendOperations
*/
sendOperations: {
methodKind: "client_streaming";
input: typeof OperationSchema;
output: typeof EmptySchema;
},
/**
* @generated from rpc v1sync.SyncPeerService.GetLog
*/
getLog: {
methodKind: "server_streaming";
input: typeof StringValueSchema;
output: typeof LogDataEntrySchema;
},
/**
* Called everytime the set of resources available to the peer changes.
*
* @generated from rpc v1sync.SyncPeerService.SetAvailableResources
*/
setAvailableResources: {
methodKind: "unary";
input: typeof SetAvailableResourcesRequestSchema;
output: typeof EmptySchema;
},
/**
* Implements semantics for updating the remote config of the peer.
*
* @generated from rpc v1sync.SyncPeerService.SetConfig
*/
setConfig: {
methodKind: "unary";
input: typeof SetConfigRequestSchema;
output: typeof EmptySchema;
},
/**
* @generated from rpc v1sync.SyncPeerService.GetConfig
*/
getConfig: {
methodKind: "unary";
input: typeof EmptySchema;
output: typeof RemoteConfigSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_v1sync_syncservice, 1);