mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-08-25 02:16:38 +00:00
restore old impl and abandon tunneling approach as liable to be fragile
This commit is contained in:
+1062
-70
File diff suppressed because it is too large
Load Diff
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+16
-2
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,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;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user