1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00

Merge branch 'main' of ssh://github.com/anyproto/any-sync into go-5646-live-tech-metrics-ui

This commit is contained in:
Roman Khafizianov 2025-06-05 20:07:21 +02:00
commit a3a3045426
No known key found for this signature in database
GPG key ID: F07A7D55A2684852
51 changed files with 3467 additions and 673 deletions

View file

@ -10,6 +10,7 @@ import (
"go.uber.org/zap"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
)
@ -58,7 +59,7 @@ func (a *aclObject) AddConsensusRecords(recs []*consensusproto.RawRecordWithId)
if a.store, a.consErr = list.NewInMemoryStorage(a.id, recs); a.consErr != nil {
return
}
if a.AclList, a.consErr = list.BuildAclListWithIdentity(a.aclService.accountService.Account(), a.store, list.NoOpAcceptorVerifier{}); a.consErr != nil {
if a.AclList, a.consErr = list.BuildAclListWithIdentity(a.aclService.accountService.Account(), a.store, recordverifier.New()); a.consErr != nil {
return
}
} else {

View file

@ -381,17 +381,24 @@ func (d *diff) compareResults(dctx *diffCtx, r Range, myRes, otherRes RangeResul
}
func (d *diff) compareElementsEqual(dctx *diffCtx, my, other []Element) {
find := func(list []Element, targetEl Element) (has, eq bool) {
for _, el := range list {
if el.Id == targetEl.Id {
return true, el.Head == targetEl.Head
}
find := func(list map[string]string, targetEl Element) (has, eq bool) {
if head, ok := list[targetEl.Id]; ok {
return true, head == targetEl.Head
}
return false, false
}
toMap := func(els []Element) map[string]string {
result := make(map[string]string, len(els))
for _, el := range els {
result[el.Id] = el.Head
}
return result
}
otherMap := toMap(other)
for _, el := range my {
has, eq := find(other, el)
has, eq := find(otherMap, el)
if !has {
dctx.removedIds = append(dctx.removedIds, el.Id)
continue
@ -402,8 +409,9 @@ func (d *diff) compareElementsEqual(dctx *diffCtx, my, other []Element) {
}
}
myMap := toMap(my)
for _, el := range other {
if has, _ := find(my, el); !has {
if has, _ := find(myMap, el); !has {
dctx.newIds = append(dctx.newIds, el.Id)
}
}

View file

@ -9,6 +9,7 @@ import (
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/node/nodeclient"
)
@ -20,6 +21,7 @@ type AclJoiningClient interface {
AclGetRecords(ctx context.Context, spaceId, aclHead string) ([]*consensusproto.RawRecordWithId, error)
RequestJoin(ctx context.Context, spaceId string, payload list.RequestJoinPayload) (aclHeadId string, err error)
CancelJoin(ctx context.Context, spaceId string) (err error)
InviteJoin(ctx context.Context, spaceId string, payload list.InviteJoinPayload) (aclHeadId string, err error)
CancelRemoveSelf(ctx context.Context, spaceId string) (err error)
}
@ -59,7 +61,7 @@ func (c *aclJoiningClient) getAcl(ctx context.Context, spaceId string) (l list.A
if err != nil {
return
}
return list.BuildAclListWithIdentity(c.keys, storage, list.NoOpAcceptorVerifier{})
return list.BuildAclListWithIdentity(c.keys, storage, recordverifier.New())
}
func (c *aclJoiningClient) CancelJoin(ctx context.Context, spaceId string) (err error) {
@ -106,6 +108,23 @@ func (c *aclJoiningClient) RequestJoin(ctx context.Context, spaceId string, payl
return
}
func (c *aclJoiningClient) InviteJoin(ctx context.Context, spaceId string, payload list.InviteJoinPayload) (aclHeadId string, err error) {
acl, err := c.getAcl(ctx, spaceId)
if err != nil {
return
}
rec, err := acl.RecordBuilder().BuildInviteJoin(payload)
if err != nil {
return
}
recWithId, err := c.nodeClient.AclAddRecord(ctx, spaceId, rec)
if err != nil {
return
}
aclHeadId = recWithId.Id
return
}
func (c *aclJoiningClient) CancelRemoveSelf(ctx context.Context, spaceId string) (err error) {
acl, err := c.getAcl(ctx, spaceId)
if err != nil {

View file

@ -5,6 +5,7 @@ import (
"errors"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
"github.com/anyproto/any-sync/commonspace/spacestate"
@ -18,6 +19,10 @@ type InviteResponse struct {
InviteKey crypto.PrivKey
}
type InviteChange struct {
Perms list.AclPermissions
}
type GetRecordsResponse struct {
Records []*consensusproto.RawRecordWithId
}
@ -26,7 +31,8 @@ type InviteSaveFunc func()
type AclSpaceClient interface {
app.Component
GenerateInvite() (list.InviteResult, error)
GenerateInvite(shouldRevokeAll, isRequestToJoin bool, permissions list.AclPermissions) (list.InviteResult, error)
ChangeInvite(ctx context.Context, inviteId string, permissions list.AclPermissions) error
StopSharing(ctx context.Context, readKeyChange list.ReadKeyChangePayload) (err error)
AddRecord(ctx context.Context, consRec *consensusproto.RawRecord) error
RemoveAccounts(ctx context.Context, payload list.AccountRemovePayload) error
@ -127,7 +133,7 @@ func (c *aclSpaceClient) RevokeAllInvites(ctx context.Context) (err error) {
return
}
c.acl.Unlock()
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
return c.sendRecordAndUpdate(ctx, c.spaceId, res.Rec)
}
func (c *aclSpaceClient) StopSharing(ctx context.Context, readKeyChange list.ReadKeyChangePayload) (err error) {
@ -164,7 +170,7 @@ func (c *aclSpaceClient) StopSharing(ctx context.Context, readKeyChange list.Rea
return
}
c.acl.Unlock()
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
return c.sendRecordAndUpdate(ctx, c.spaceId, res.Rec)
}
func (c *aclSpaceClient) DeclineRequest(ctx context.Context, identity crypto.PubKey) (err error) {
@ -210,10 +216,54 @@ func (c *aclSpaceClient) AcceptRequest(ctx context.Context, payload list.Request
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
}
func (c *aclSpaceClient) GenerateInvite() (resp list.InviteResult, err error) {
c.acl.RLock()
defer c.acl.RUnlock()
return c.acl.RecordBuilder().BuildInvite()
func (c *aclSpaceClient) ChangeInvite(ctx context.Context, inviteId string, permissions list.AclPermissions) error {
c.acl.Lock()
res, err := c.acl.RecordBuilder().BuildInviteChange(list.InviteChangePayload{
IniviteRecordId: inviteId,
Permissions: permissions,
})
if err != nil {
c.acl.Unlock()
return err
}
c.acl.Unlock()
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
}
func (c *aclSpaceClient) GenerateInvite(isRevoke, isRequestToJoin bool, permissions list.AclPermissions) (list.InviteResult, error) {
c.acl.Lock()
defer c.acl.Unlock()
var inviteIds []string
if isRevoke {
for _, invite := range c.acl.AclState().Invites() {
if isRequestToJoin && invite.Type == aclrecordproto.AclInviteType_RequestToJoin {
return list.InviteResult{}, list.ErrDuplicateInvites
} else if invite.Permissions == permissions {
return list.InviteResult{}, list.ErrDuplicateInvites
}
}
inviteIds = c.acl.AclState().InviteIds()
}
var payload list.BatchRequestPayload
if isRequestToJoin {
payload = list.BatchRequestPayload{
InviteRevokes: inviteIds,
NewInvites: []list.AclPermissions{list.AclPermissionsNone},
}
} else {
payload = list.BatchRequestPayload{
InviteRevokes: inviteIds,
NewInvites: []list.AclPermissions{permissions},
}
}
res, err := c.acl.RecordBuilder().BuildBatchRequest(payload)
if err != nil {
return list.InviteResult{}, err
}
return list.InviteResult{
InviteRec: res.Rec,
InviteKey: res.Invites[0],
}, nil
}
func (c *aclSpaceClient) AddRecord(ctx context.Context, consRec *consensusproto.RawRecord) (err error) {

View file

@ -99,6 +99,21 @@ func (mr *MockAclJoiningClientMockRecorder) Init(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockAclJoiningClient)(nil).Init), arg0)
}
// InviteJoin mocks base method.
func (m *MockAclJoiningClient) InviteJoin(arg0 context.Context, arg1 string, arg2 list.InviteJoinPayload) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InviteJoin", arg0, arg1, arg2)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InviteJoin indicates an expected call of InviteJoin.
func (mr *MockAclJoiningClientMockRecorder) InviteJoin(arg0, arg1, arg2 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InviteJoin", reflect.TypeOf((*MockAclJoiningClient)(nil).InviteJoin), arg0, arg1, arg2)
}
// Name mocks base method.
func (m *MockAclJoiningClient) Name() string {
m.ctrl.T.Helper()
@ -207,6 +222,20 @@ func (mr *MockAclSpaceClientMockRecorder) CancelRequest(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelRequest", reflect.TypeOf((*MockAclSpaceClient)(nil).CancelRequest), arg0)
}
// ChangeInvite mocks base method.
func (m *MockAclSpaceClient) ChangeInvite(arg0 context.Context, arg1 string, arg2 list.AclPermissions) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ChangeInvite", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// ChangeInvite indicates an expected call of ChangeInvite.
func (mr *MockAclSpaceClientMockRecorder) ChangeInvite(arg0, arg1, arg2 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeInvite", reflect.TypeOf((*MockAclSpaceClient)(nil).ChangeInvite), arg0, arg1, arg2)
}
// ChangePermissions mocks base method.
func (m *MockAclSpaceClient) ChangePermissions(arg0 context.Context, arg1 list.PermissionChangesPayload) error {
m.ctrl.T.Helper()
@ -236,18 +265,18 @@ func (mr *MockAclSpaceClientMockRecorder) DeclineRequest(arg0, arg1 any) *gomock
}
// GenerateInvite mocks base method.
func (m *MockAclSpaceClient) GenerateInvite() (list.InviteResult, error) {
func (m *MockAclSpaceClient) GenerateInvite(arg0, arg1 bool, arg2 list.AclPermissions) (list.InviteResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GenerateInvite")
ret := m.ctrl.Call(m, "GenerateInvite", arg0, arg1, arg2)
ret0, _ := ret[0].(list.InviteResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GenerateInvite indicates an expected call of GenerateInvite.
func (mr *MockAclSpaceClientMockRecorder) GenerateInvite() *gomock.Call {
func (mr *MockAclSpaceClientMockRecorder) GenerateInvite(arg0, arg1, arg2 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateInvite", reflect.TypeOf((*MockAclSpaceClient)(nil).GenerateInvite))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateInvite", reflect.TypeOf((*MockAclSpaceClient)(nil).GenerateInvite), arg0, arg1, arg2)
}
// Init mocks base method.

View file

@ -14,6 +14,7 @@ import (
"github.com/anyproto/any-sync/commonspace/acl/aclclient"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/util/periodicsync"
)
@ -82,7 +83,7 @@ func (a *aclWaiter) loop(ctx context.Context) error {
if err != nil {
return err
}
acl, err := list.BuildAclListWithIdentity(a.keys, storage, list.NoOpAcceptorVerifier{})
acl, err := list.BuildAclListWithIdentity(a.keys, storage, recordverifier.New())
if err != nil {
return err
}

View file

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/spacepayloads"
"github.com/anyproto/any-sync/commonspace/spacestorage"
@ -17,6 +18,14 @@ import (
"github.com/anyproto/any-sync/util/crypto"
)
func mockDeps() Deps {
return Deps{
TreeSyncer: mockTreeSyncer{},
SyncStatus: syncstatus.NewNoOpSyncStatus(),
recordVerifier: recordverifier.NewValidateFull(),
}
}
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
bytes := make([]byte, 32)
rand.Read(bytes)
@ -60,7 +69,7 @@ func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
spc, err := fx.spaceService.NewSpace(ctx, sp, mockDeps())
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
@ -109,7 +118,7 @@ func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
time.Sleep(100 * time.Millisecond)
storeSetter := fx.storageProvider.(storeSetter)
storeSetter.SetStore(sp, newStore)
spc, err = fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
spc, err = fx.spaceService.NewSpace(ctx, sp, mockDeps())
require.NoError(t, err)
require.NotNil(t, spc)
waitTest := make(chan struct{})
@ -153,7 +162,7 @@ func TestSpaceDeleteIds(t *testing.T) {
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
spc, err := fx.spaceService.NewSpace(ctx, sp, mockDeps())
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
@ -202,7 +211,7 @@ func TestSpaceDeleteIds(t *testing.T) {
time.Sleep(100 * time.Millisecond)
storeSetter := fx.storageProvider.(storeSetter)
storeSetter.SetStore(sp, newStore)
spc, err = fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
spc, err = fx.spaceService.NewSpace(ctx, sp, mockDeps())
require.NoError(t, err)
require.NotNil(t, spc)
waitTest := make(chan struct{})

View file

@ -8,6 +8,7 @@ import (
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/spacestorage"
@ -43,7 +44,7 @@ func (d *deleter) Delete(ctx context.Context) {
}
} else {
err = d.getter.DeleteTree(ctx, spaceId, id)
if err != nil && !errors.Is(err, spacestorage.ErrTreeStorageAlreadyDeleted) {
if err != nil && !errors.Is(err, spacestorage.ErrTreeStorageAlreadyDeleted) && !errors.Is(err, synctree.ErrSyncTreeDeleted) {
log.Error("failed to delete object", zap.Error(err))
continue
}

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,19 @@ message AclRoot {
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
message AclAccountInvite {
bytes inviteKey = 1;
AclInviteType inviteType = 2;
AclUserPermissions permissions = 3;
bytes encryptedReadKey = 4;
}
message AclAccountInviteChange {
string inviteRecordId = 1;
AclUserPermissions permissions = 2;
}
enum AclInviteType {
RequestToJoin = 0;
AnyoneCanJoin = 1;
}
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
@ -29,6 +42,17 @@ message AclAccountRequestJoin {
bytes metadata = 4;
}
// AclInviteJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
// The person must encrypt the key with its own public key
message AclAccountInviteJoin {
bytes identity = 1;
string inviteRecordId = 2;
bytes inviteIdentitySignature = 3;
// Metadata is encrypted with metadata key of the space
bytes metadata = 4;
bytes encryptedReadKey = 5;
}
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
message AclAccountRequestAccept {
bytes identity = 1;
@ -90,6 +114,7 @@ message AclReadKeyChange {
bytes encryptedMetadataPrivKey = 3;
// EncryptedOldReadKey is encrypted with new read key
bytes encryptedOldReadKey = 4;
repeated AclEncryptedReadKey inviteKeys = 5;
}
// AclAccountRemove removes an account and changes read key for space
@ -118,6 +143,8 @@ message AclContentValue {
AclAccountPermissionChanges permissionChanges = 10;
AclAccountsAdd accountsAdd = 11;
AclAccountRequestCancel requestCancel = 12;
AclAccountInviteJoin inviteJoin = 13;
AclAccountInviteChange inviteChange = 14;
}
}

View file

@ -8,6 +8,7 @@ import (
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
@ -26,6 +27,11 @@ type RequestJoinPayload struct {
Metadata []byte
}
type InviteJoinPayload struct {
InviteKey crypto.PrivKey
Metadata []byte
}
type ReadKeyChangePayload struct {
MetadataKey crypto.PrivKey
ReadKey crypto.SymKey
@ -41,6 +47,11 @@ type PermissionChangePayload struct {
Permissions AclPermissions
}
type InviteChangePayload struct {
IniviteRecordId string
Permissions AclPermissions
}
type PermissionChangesPayload struct {
Changes []PermissionChangePayload
}
@ -55,6 +66,10 @@ type AccountAdd struct {
Metadata []byte
}
type NewInvites struct {
Permissions AclPermissions
}
type BatchRequestPayload struct {
Additions []AccountAdd
Changes []PermissionChangePayload
@ -62,6 +77,8 @@ type BatchRequestPayload struct {
Approvals []RequestAcceptPayload
Declines []string
InviteRevokes []string
InviteChanges []InviteChangePayload
NewInvites []AclPermissions
}
type AccountRemovePayload struct {
@ -74,14 +91,22 @@ type InviteResult struct {
InviteKey crypto.PrivKey
}
type BatchResult struct {
Rec *consensusproto.RawRecord
Invites []crypto.PrivKey
}
type AclRecordBuilder interface {
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
BuildBatchRequest(payload BatchRequestPayload) (rawRecord *consensusproto.RawRecord, err error)
BuildBatchRequest(payload BatchRequestPayload) (batchResult BatchResult, err error)
BuildInvite() (res InviteResult, err error)
BuildInviteAnyone(permissions AclPermissions) (res InviteResult, err error)
BuildInviteChange(inviteChange InviteChangePayload) (rawRecord *consensusproto.RawRecord, err error)
BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error)
BuildInviteJoin(payload InviteJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error)
@ -98,11 +123,11 @@ type aclRecordBuilder struct {
id string
keyStorage crypto.KeyStorage
accountKeys *accountdata.AccountKeys
verifier AcceptorVerifier
verifier recordverifier.AcceptorVerifier
state *AclState
}
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier recordverifier.AcceptorVerifier) AclRecordBuilder {
return &aclRecordBuilder{
id: id,
keyStorage: keyStorage,
@ -111,51 +136,84 @@ func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountd
}
}
func (a *aclRecordBuilder) BuildBatchRequest(payload BatchRequestPayload) (rawRec *consensusproto.RawRecord, err error) {
var aclContent []*aclrecordproto.AclContentValue
if len(payload.Additions) > 0 {
content, err := a.buildAccountsAdd(AccountsAddPayload{Additions: payload.Additions})
func (a *aclRecordBuilder) BuildBatchRequest(payload BatchRequestPayload) (batchResult BatchResult, err error) {
var (
contentList []*aclrecordproto.AclContentValue
content *aclrecordproto.AclContentValue
)
if len(payload.Removals.Identities) > 0 {
content, err = a.buildAccountRemove(payload.Removals)
if err != nil {
return nil, err
return
}
aclContent = append(aclContent, content)
contentList = append(contentList, content)
}
if len(payload.Additions) > 0 {
content, err = a.buildAccountsAdd(AccountsAddPayload{Additions: payload.Additions}, payload.Removals.Change.MetadataKey.GetPublic(), payload.Removals.Change.ReadKey)
if err != nil {
return
}
contentList = append(contentList, content)
}
if len(payload.Changes) > 0 {
content, err := a.buildPermissionChanges(PermissionChangesPayload{Changes: payload.Changes})
content, err = a.buildPermissionChanges(PermissionChangesPayload{Changes: payload.Changes})
if err != nil {
return nil, err
return
}
aclContent = append(aclContent, content)
contentList = append(contentList, content)
}
for _, acc := range payload.Approvals {
content, err := a.buildRequestAccept(acc)
content, err = a.buildRequestAccept(acc, payload.Removals.Change.ReadKey)
if err != nil {
return nil, err
return
}
aclContent = append(aclContent, content)
contentList = append(contentList, content)
}
for _, id := range payload.Declines {
content, err := a.buildRequestDecline(id)
content, err = a.buildRequestDecline(id)
if err != nil {
return nil, err
return
}
aclContent = append(aclContent, content)
contentList = append(contentList, content)
}
for _, id := range payload.InviteRevokes {
content, err := a.buildInviteRevoke(id)
content, err = a.buildInviteRevoke(id)
if err != nil {
return nil, err
return
}
aclContent = append(aclContent, content)
contentList = append(contentList, content)
}
if len(payload.Removals.Identities) > 0 {
content, err := a.buildAccountRemove(payload.Removals)
for _, invite := range payload.InviteChanges {
content, err = a.buildInviteChange(invite)
if err != nil {
return nil, err
return
}
aclContent = append(aclContent, content)
contentList = append(contentList, content)
}
return a.buildRecords(aclContent)
for _, perms := range payload.NewInvites {
var privKey crypto.PrivKey
if perms.NoPermissions() {
privKey, content, err = a.buildInvite()
if err != nil {
return
}
contentList = append(contentList, content)
batchResult.Invites = append(batchResult.Invites, privKey)
} else {
privKey, content, err = a.buildInviteAnyone(perms)
if err != nil {
return
}
contentList = append(contentList, content)
batchResult.Invites = append(batchResult.Invites, privKey)
}
}
res, err := a.buildRecords(contentList)
if err != nil {
return
}
batchResult.Rec = res
return
}
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
@ -190,9 +248,20 @@ func (a *aclRecordBuilder) buildRecords(aclContent []*aclrecordproto.AclContentV
Payload: marshalledRec,
Signature: signature,
}
err = a.preflightCheck(rawRec)
return
}
func (a *aclRecordBuilder) preflightCheck(rawRecord *consensusproto.RawRecord) (err error) {
aclRec, err := a.Unmarshall(rawRecord)
if err != nil {
return
}
cp := a.state.Copy()
cp.contentValidator.(*contentValidator).verifier = recordverifier.NewValidateFull()
return cp.ApplyRecord(aclRec)
}
func (a *aclRecordBuilder) BuildPermissionChanges(payload PermissionChangesPayload) (rawRecord *consensusproto.RawRecord, err error) {
content, err := a.buildPermissionChanges(payload)
if err != nil {
@ -231,14 +300,14 @@ func (a *aclRecordBuilder) buildPermissionChanges(payload PermissionChangesPaylo
}
func (a *aclRecordBuilder) BuildAccountsAdd(payload AccountsAddPayload) (rawRecord *consensusproto.RawRecord, err error) {
content, err := a.buildAccountsAdd(payload)
content, err := a.buildAccountsAdd(payload, nil, nil)
if err != nil {
return
}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *aclrecordproto.AclContentValue, err error) {
func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload, mkKey crypto.PubKey, readKey crypto.SymKey) (value *aclrecordproto.AclContentValue, err error) {
var accs []*aclrecordproto.AclAccountAdd
for _, acc := range payload.Additions {
if !a.state.Permissions(acc.Identity).NoPermissions() {
@ -247,9 +316,11 @@ func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *
if acc.Permissions.IsOwner() {
return nil, ErrIsOwner
}
mkKey, err := a.state.CurrentMetadataKey()
if err != nil {
return nil, err
if mkKey == nil {
mkKey, err = a.state.CurrentMetadataKey()
if err != nil {
return nil, err
}
}
encMeta, err := mkKey.Encrypt(acc.Metadata)
if err != nil {
@ -258,9 +329,11 @@ func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *
if len(encMeta) > MaxMetadataLen {
return nil, ErrMetadataTooLarge
}
readKey, err := a.state.CurrentReadKey()
if err != nil {
return nil, ErrNoReadKey
if readKey == nil {
readKey, err = a.state.CurrentReadKey()
if err != nil {
return nil, ErrNoReadKey
}
}
protoKey, err := readKey.Marshall()
if err != nil {
@ -287,6 +360,20 @@ func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *
}
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
privKey, content, err := a.buildInvite()
if err != nil {
return
}
rawRec, err := a.buildRecord(content)
if err != nil {
return
}
res.InviteKey = privKey
res.InviteRec = rawRec
return
}
func (a *aclRecordBuilder) buildInvite() (invKey crypto.PrivKey, content *aclrecordproto.AclContentValue, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
@ -300,7 +387,37 @@ func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
return
}
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
content = &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
invKey = privKey
return
}
func (a *aclRecordBuilder) BuildInviteChange(inviteChange InviteChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
content, err := a.buildInviteChange(inviteChange)
if err != nil {
return
}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) buildInviteChange(inviteChange InviteChangePayload) (content *aclrecordproto.AclContentValue, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
inviteRec := &aclrecordproto.AclAccountInviteChange{
InviteRecordId: inviteChange.IniviteRecordId,
Permissions: aclrecordproto.AclUserPermissions(inviteChange.Permissions),
}
content = &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteChange{InviteChange: inviteRec}}
return
}
func (a *aclRecordBuilder) BuildInviteAnyone(permissions AclPermissions) (res InviteResult, err error) {
privKey, content, err := a.buildInviteAnyone(permissions)
if err != nil {
return
}
rawRec, err := a.buildRecord(content)
if err != nil {
return
@ -310,6 +427,42 @@ func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
return
}
func (a *aclRecordBuilder) buildInviteAnyone(permissions AclPermissions) (invKey crypto.PrivKey, content *aclrecordproto.AclContentValue, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
privKey, pubKey, err := crypto.GenerateRandomEd25519KeyPair()
if err != nil {
return
}
invitePubKey, err := pubKey.Marshall()
if err != nil {
return
}
curReadKey, err := a.state.CurrentReadKey()
if err != nil {
return
}
raw, err := curReadKey.Marshall()
if err != nil {
return
}
encReadKey, err := pubKey.Encrypt(raw)
if err != nil {
return
}
inviteRec := &aclrecordproto.AclAccountInvite{
InviteKey: invitePubKey,
InviteType: aclrecordproto.AclInviteType_AnyoneCanJoin,
Permissions: aclrecordproto.AclUserPermissions(permissions),
EncryptedReadKey: encReadKey,
}
content = &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
invKey = privKey
return
}
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
content, err := a.buildInviteRevoke(inviteRecordId)
if err != nil {
@ -323,7 +476,7 @@ func (a *aclRecordBuilder) buildInviteRevoke(inviteRecordId string) (value *aclr
err = ErrInsufficientPermissions
return
}
_, exists := a.state.inviteKeys[inviteRecordId]
_, exists := a.state.invites[inviteRecordId]
if !exists {
err = ErrNoSuchInvite
return
@ -334,17 +487,17 @@ func (a *aclRecordBuilder) buildInviteRevoke(inviteRecordId string) (value *aclr
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
var inviteId string
for id, key := range a.state.inviteKeys {
if key.Equals(payload.InviteKey.GetPublic()) {
for id, inv := range a.state.invites {
if inv.Key.Equals(payload.InviteKey.GetPublic()) {
inviteId = id
}
}
key, exists := a.state.inviteKeys[inviteId]
invite, exists := a.state.invites[inviteId]
if !exists {
err = ErrNoSuchInvite
return
}
if !payload.InviteKey.GetPublic().Equals(key) {
if !payload.InviteKey.GetPublic().Equals(invite.Key) {
err = ErrIncorrectInviteKey
return
}
@ -385,15 +538,81 @@ func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawReco
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildInviteJoin(payload InviteJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
var inviteId string
for id, inv := range a.state.invites {
if inv.Key.Equals(payload.InviteKey.GetPublic()) && inv.Type == aclrecordproto.AclInviteType_AnyoneCanJoin {
inviteId = id
}
}
invite, exists := a.state.invites[inviteId]
if !exists {
err = ErrNoSuchInvite
return
}
if !payload.InviteKey.GetPublic().Equals(invite.Key) {
err = ErrIncorrectInviteKey
return
}
if !a.state.Permissions(a.accountKeys.SignKey.GetPublic()).NoPermissions() {
err = ErrInsufficientPermissions
return
}
mkKey, err := a.state.CurrentMetadataKey()
if err != nil {
return nil, err
}
encMeta, err := mkKey.Encrypt(payload.Metadata)
if err != nil {
return nil, err
}
if len(encMeta) > MaxMetadataLen {
return nil, ErrMetadataTooLarge
}
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
if err != nil {
return
}
signature, err := payload.InviteKey.Sign(rawIdentity)
if err != nil {
return
}
key, err := a.state.DecryptInvite(payload.InviteKey)
if err != nil {
return
}
readKey, err := key.Marshall()
if err != nil {
return
}
encReadKey, err := a.accountKeys.SignKey.GetPublic().Encrypt(readKey)
if err != nil {
return
}
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
if err != nil {
return
}
joinRec := &aclrecordproto.AclAccountInviteJoin{
Identity: protoIdentity,
InviteRecordId: inviteId,
InviteIdentitySignature: signature,
Metadata: encMeta,
EncryptedReadKey: encReadKey,
}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteJoin{InviteJoin: joinRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
content, err := a.buildRequestAccept(payload)
content, err := a.buildRequestAccept(payload, nil)
if err != nil {
return
}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload) (value *aclrecordproto.AclContentValue, err error) {
func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload, readKey crypto.SymKey) (value *aclrecordproto.AclContentValue, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
@ -403,9 +622,11 @@ func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload) (val
err = ErrNoSuchRequest
return
}
readKey, err := a.state.CurrentReadKey()
if err != nil {
return nil, ErrNoReadKey
if readKey == nil {
readKey, err = a.state.CurrentReadKey()
if err != nil {
return nil, ErrNoReadKey
}
}
protoKey, err := readKey.Marshall()
if err != nil {
@ -506,13 +727,19 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo
if err != nil {
return nil, err
}
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
var (
aclReadKeys []*aclrecordproto.AclEncryptedReadKey
invites []*aclrecordproto.AclEncryptedReadKey
)
for identity, st := range a.state.accountStates {
if removedIdentities != nil {
if _, exists := removedIdentities[identity]; exists {
continue
}
}
if st.Permissions.NoPermissions() {
continue
}
protoIdentity, err := st.PubKey.Marshall()
if err != nil {
return nil, err
@ -526,6 +753,23 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo
EncryptedReadKey: enc,
})
}
for _, invite := range a.state.invites {
if invite.Type != aclrecordproto.AclInviteType_AnyoneCanJoin {
continue
}
protoIdentity, err := invite.Key.Marshall()
if err != nil {
return nil, err
}
enc, err := invite.Key.Encrypt(protoKey)
if err != nil {
return nil, err
}
invites = append(invites, &aclrecordproto.AclEncryptedReadKey{
Identity: protoIdentity,
EncryptedReadKey: enc,
})
}
// encrypting metadata key with new read key
mkPubKey, err := payload.MetadataKey.GetPublic().Marshall()
if err != nil {
@ -557,6 +801,7 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo
MetadataPubKey: mkPubKey,
EncryptedMetadataPrivKey: encPrivKey,
EncryptedOldReadKey: encOldKey,
InviteKeys: invites,
}
return readRec, nil
}

View file

@ -5,9 +5,11 @@ import (
"github.com/anyproto/protobuf/proto"
"go.uber.org/zap"
"golang.org/x/exp/slices"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/util/crypto"
)
@ -25,6 +27,7 @@ var (
ErrNoSuchRequest = errors.New("no such request")
ErrNoSuchInvite = errors.New("no such invite")
ErrInsufficientPermissions = errors.New("insufficient permissions")
ErrDuplicateInvites = errors.New("duplicate invites")
ErrIsOwner = errors.New("can't be made by owner")
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
ErrDuplicateAccounts = errors.New("duplicate accounts")
@ -48,6 +51,17 @@ type AclKeys struct {
ReadKey crypto.SymKey
MetadataPrivKey crypto.PrivKey
MetadataPubKey crypto.PubKey
oldEncryptedReadKey []byte
encMetadatKey []byte
}
type Invite struct {
Key crypto.PubKey
Type aclrecordproto.AclInviteType
Permissions AclPermissions
Id string
encryptedKey []byte
}
type AclState struct {
@ -57,7 +71,7 @@ type AclState struct {
// accountStates is a map pubKey -> state which defines current account state
accountStates map[string]AccountState
// inviteKeys is a map recordId -> invite
inviteKeys map[string]crypto.PubKey
invites map[string]Invite
// requestRecords is a map recordId -> RequestRecord
requestRecords map[string]RequestRecord
// pendingRequests is a map pubKey -> recordId
@ -75,22 +89,20 @@ type AclState struct {
func newAclStateWithKeys(
rootRecord *AclRecord,
key crypto.PrivKey) (st *AclState, err error) {
key crypto.PrivKey,
verifier recordverifier.AcceptorVerifier) (st *AclState, err error) {
st = &AclState{
id: rootRecord.Id,
key: key,
pubKey: key.GetPublic(),
keys: make(map[string]AclKeys),
accountStates: make(map[string]AccountState),
inviteKeys: make(map[string]crypto.PubKey),
invites: make(map[string]Invite),
requestRecords: make(map[string]RequestRecord),
pendingRequests: make(map[string]string),
keyStore: crypto.NewKeyStorage(),
}
st.contentValidator = &contentValidator{
keyStore: st.keyStore,
aclState: st,
}
st.contentValidator = newContentValidator(st.keyStore, st, verifier)
err = st.applyRoot(rootRecord)
if err != nil {
return
@ -98,20 +110,17 @@ func newAclStateWithKeys(
return st, nil
}
func newAclState(rootRecord *AclRecord) (st *AclState, err error) {
func newAclState(rootRecord *AclRecord, verifier recordverifier.AcceptorVerifier) (st *AclState, err error) {
st = &AclState{
id: rootRecord.Id,
keys: make(map[string]AclKeys),
accountStates: make(map[string]AccountState),
inviteKeys: make(map[string]crypto.PubKey),
invites: make(map[string]Invite),
requestRecords: make(map[string]RequestRecord),
pendingRequests: make(map[string]string),
keyStore: crypto.NewKeyStorage(),
}
st.contentValidator = &contentValidator{
keyStore: st.keyStore,
aclState: st,
}
st.contentValidator = newContentValidator(st.keyStore, st, verifier)
err = st.applyRoot(rootRecord)
if err != nil {
return
@ -209,9 +218,12 @@ func (st *AclState) HadReadPermissions(identity crypto.PubKey) (had bool) {
return false
}
func (st *AclState) Invites() []crypto.PubKey {
var invites []crypto.PubKey
for _, inv := range st.inviteKeys {
func (st *AclState) Invites(inviteType ...aclrecordproto.AclInviteType) []Invite {
var invites []Invite
for _, inv := range st.invites {
if len(inviteType) > 0 && !slices.Contains(inviteType, inv.Type) {
continue
}
invites = append(invites, inv)
}
return invites
@ -223,12 +235,36 @@ func (st *AclState) Key() crypto.PrivKey {
func (st *AclState) InviteIds() []string {
var invites []string
for invId := range st.inviteKeys {
for invId := range st.invites {
invites = append(invites, invId)
}
return invites
}
func (st *AclState) RequestIds() []string {
var requests []string
for reqId := range st.requestRecords {
requests = append(requests, reqId)
}
return requests
}
func (st *AclState) DecryptInvite(invitePk crypto.PrivKey) (key crypto.SymKey, err error) {
if invitePk == nil {
return nil, ErrNoReadKey
}
for _, invite := range st.invites {
if invite.Key.Equals(invitePk.GetPublic()) {
res, err := st.unmarshallDecryptReadKey(invite.encryptedKey, invitePk.Decrypt)
if err != nil {
return nil, err
}
return res, nil
}
}
return nil, ErrNoSuchInvite
}
func (st *AclState) ApplyRecord(record *AclRecord) (err error) {
if st.lastRecordId != record.PrevId {
err = ErrIncorrectRecordSequence
@ -277,7 +313,10 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
if err != nil {
return err
}
st.keys[record.Id] = AclKeys{MetadataPubKey: mkPubKey}
st.keys[record.Id] = AclKeys{
MetadataPubKey: mkPubKey,
encMetadatKey: root.EncryptedMetadataPrivKey,
}
} else {
// this should be a derived acl
st.keys[record.Id] = AclKeys{}
@ -350,7 +389,7 @@ func (st *AclState) Copy() *AclState {
pubKey: st.key.GetPublic(),
keys: make(map[string]AclKeys),
accountStates: make(map[string]AccountState),
inviteKeys: make(map[string]crypto.PubKey),
invites: make(map[string]Invite),
requestRecords: make(map[string]RequestRecord),
pendingRequests: make(map[string]string),
keyStore: st.keyStore,
@ -365,8 +404,8 @@ func (st *AclState) Copy() *AclState {
accState.PermissionChanges = permChanges
newSt.accountStates[k] = accState
}
for k, v := range st.inviteKeys {
newSt.inviteKeys[k] = v
for k, v := range st.invites {
newSt.invites[k] = v
}
for k, v := range st.requestRecords {
newSt.requestRecords[k] = v
@ -377,12 +416,16 @@ func (st *AclState) Copy() *AclState {
newSt.readKeyChanges = append(newSt.readKeyChanges, st.readKeyChanges...)
newSt.list = st.list
newSt.lastRecordId = st.lastRecordId
newSt.contentValidator = newContentValidator(newSt.keyStore, newSt)
newSt.contentValidator = newContentValidator(newSt.keyStore, newSt, st.list.verifier)
return newSt
}
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, record *AclRecord) error {
switch {
case ch.GetInviteChange() != nil:
return st.applyInviteChange(ch.GetInviteChange(), record)
case ch.GetInviteJoin() != nil:
return st.applyInviteJoin(ch.GetInviteJoin(), record)
case ch.GetPermissionChange() != nil:
return st.applyPermissionChange(ch.GetPermissionChange(), record)
case ch.GetInvite() != nil:
@ -423,6 +466,17 @@ func (st *AclState) applyPermissionChanges(ch *aclrecordproto.AclAccountPermissi
return nil
}
func (st *AclState) applyInviteChange(ch *aclrecordproto.AclAccountInviteChange, record *AclRecord) (err error) {
err = st.contentValidator.ValidateInviteChange(ch, record.Identity)
if err != nil {
return err
}
invite := st.invites[ch.InviteRecordId]
invite.Permissions = AclPermissions(ch.Permissions)
st.invites[ch.InviteRecordId] = invite
return nil
}
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, record *AclRecord) error {
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
@ -452,7 +506,13 @@ func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, record *Acl
if err != nil {
return err
}
st.inviteKeys[record.Id] = inviteKey
st.invites[record.Id] = Invite{
Key: inviteKey,
Id: record.Id,
Type: ch.InviteType,
Permissions: AclPermissions(ch.Permissions),
encryptedKey: ch.EncryptedReadKey,
}
return nil
}
@ -461,7 +521,7 @@ func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke,
if err != nil {
return err
}
delete(st.inviteKeys, ch.InviteRecordId)
delete(st.invites, ch.InviteRecordId)
return nil
}
@ -582,6 +642,58 @@ func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccep
return st.unpackAllKeys(ch.EncryptedReadKey)
}
func (st *AclState) applyInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, record *AclRecord) error {
err := st.contentValidator.ValidateInviteJoin(ch, record.Identity)
if err != nil {
return err
}
identity, err := st.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return err
}
inviteRecord, _ := st.invites[ch.InviteRecordId]
pKeyString := mapKeyFromPubKey(identity)
state, exists := st.accountStates[pKeyString]
if !exists {
st.accountStates[pKeyString] = AccountState{
PubKey: identity,
Permissions: inviteRecord.Permissions,
RequestMetadata: ch.Metadata,
KeyRecordId: st.CurrentReadKeyId(),
Status: StatusActive,
PermissionChanges: []PermissionChange{
{
Permission: inviteRecord.Permissions,
RecordId: record.Id,
},
},
}
} else {
st.accountStates[pKeyString] = AccountState{
PubKey: identity,
Permissions: inviteRecord.Permissions,
RequestMetadata: ch.Metadata,
KeyRecordId: st.CurrentReadKeyId(),
Status: StatusActive,
PermissionChanges: append(state.PermissionChanges, PermissionChange{
Permission: inviteRecord.Permissions,
RecordId: record.Id,
}),
}
}
for _, rec := range st.requestRecords {
if rec.RequestIdentity.Equals(identity) {
delete(st.pendingRequests, mapKeyFromPubKey(rec.RequestIdentity))
delete(st.requestRecords, rec.RecordId)
break
}
}
if st.pubKey.Equals(identity) {
return st.unpackAllKeys(ch.EncryptedReadKey)
}
return nil
}
func (st *AclState) unpackAllKeys(rk []byte) error {
iterReadKey, err := st.unmarshallDecryptReadKey(rk, st.key.Decrypt)
if err != nil {
@ -589,42 +701,8 @@ func (st *AclState) unpackAllKeys(rk []byte) error {
}
for idx := len(st.readKeyChanges) - 1; idx >= 0; idx-- {
recId := st.readKeyChanges[idx]
rec, err := st.list.Get(recId)
if err != nil {
return err
}
// if this is a first key change
if recId == st.id {
ch := rec.Model.(*aclrecordproto.AclRoot)
metadataKey, err := st.unmarshallDecryptPrivKey(ch.EncryptedMetadataPrivKey, iterReadKey.Decrypt)
if err != nil {
return err
}
aclKeys := st.keys[recId]
aclKeys.ReadKey = iterReadKey
aclKeys.MetadataPrivKey = metadataKey
st.keys[recId] = aclKeys
break
}
model := rec.Model.(*aclrecordproto.AclData)
content := model.GetAclContent()
var readKeyChange *aclrecordproto.AclReadKeyChange
for _, ch := range content {
switch {
case ch.GetReadKeyChange() != nil:
readKeyChange = ch.GetReadKeyChange()
case ch.GetAccountRemove() != nil:
readKeyChange = ch.GetAccountRemove().GetReadKeyChange()
}
}
if readKeyChange == nil {
return ErrIncorrectReadKey
}
oldReadKey, err := st.unmarshallDecryptReadKey(readKeyChange.EncryptedOldReadKey, iterReadKey.Decrypt)
if err != nil {
return err
}
metadataKey, err := st.unmarshallDecryptPrivKey(readKeyChange.EncryptedMetadataPrivKey, iterReadKey.Decrypt)
keys := st.keys[recId]
metadataKey, err := st.unmarshallDecryptPrivKey(keys.encMetadatKey, iterReadKey.Decrypt)
if err != nil {
return err
}
@ -632,7 +710,16 @@ func (st *AclState) unpackAllKeys(rk []byte) error {
aclKeys.ReadKey = iterReadKey
aclKeys.MetadataPrivKey = metadataKey
st.keys[recId] = aclKeys
iterReadKey = oldReadKey
if idx != 0 {
if keys.oldEncryptedReadKey == nil {
return ErrIncorrectReadKey
}
oldReadKey, err := st.unmarshallDecryptReadKey(keys.oldEncryptedReadKey, iterReadKey.Decrypt)
if err != nil {
return err
}
iterReadKey = oldReadKey
}
}
return nil
}
@ -744,7 +831,9 @@ func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, reco
return err
}
aclKeys := AclKeys{
MetadataPubKey: mkPubKey,
MetadataPubKey: mkPubKey,
oldEncryptedReadKey: ch.EncryptedOldReadKey,
encMetadatKey: ch.EncryptedMetadataPrivKey,
}
for _, accKey := range ch.AccountKeys {
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
@ -756,6 +845,19 @@ func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, reco
aclKeys.ReadKey = res
}
}
for _, encKey := range ch.InviteKeys {
invKey, err := st.keyStore.PubKeyFromProto(encKey.Identity)
if err != nil {
return err
}
for key, invite := range st.invites {
if invite.Key.Equals(invKey) {
invite.encryptedKey = encKey.EncryptedReadKey
st.invites[key] = invite
break
}
}
}
if aclKeys.ReadKey != nil {
metadataKey, err := st.unmarshallDecryptPrivKey(ch.EncryptedMetadataPrivKey, aclKeys.ReadKey.Decrypt)
if err != nil {
@ -792,8 +894,8 @@ func (st *AclState) unmarshallDecryptPrivKey(msg []byte, decryptor func(msg []by
}
func (st *AclState) GetInviteIdByPrivKey(inviteKey crypto.PrivKey) (recId string, err error) {
for id, inv := range st.inviteKeys {
if inv.Equals(inviteKey.GetPublic()) {
for id, inv := range st.invites {
if inv.Key.Equals(inviteKey.GetPublic()) {
return id, nil
}
}

View file

@ -29,12 +29,12 @@ func (sb *aclStateBuilder) Build(records []*AclRecord, list *aclList) (state *Ac
return nil, ErrIncorrectRecordSequence
}
if sb.privKey != nil {
state, err = newAclStateWithKeys(records[0], sb.privKey)
state, err = newAclStateWithKeys(records[0], sb.privKey, list.verifier)
if err != nil {
return
}
} else {
state, err = newAclState(records[0])
state, err = newAclState(records[0], list.verifier)
if err != nil {
return
}

View file

@ -7,6 +7,7 @@ import (
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/crypto"
)
@ -66,6 +67,7 @@ var (
func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm func(perm string) AclPermissions, addRec func(rec *consensusproto.RawRecordWithId) error) (afterAll []func(), err error) {
// remove:a,b,c;add:d,rw,m1|e,r,m2;changes:f,rw|g,r;revoke:inv1id;decline:g,h;
batchPayload := BatchRequestPayload{}
var inviteIds []string
for _, arg := range args {
parts := strings.Split(arg, ":")
if len(parts) != 2 {
@ -86,7 +88,7 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
return nil, err
}
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, NoOpAcceptorVerifier{})
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, recordverifier.NewValidateFull())
if err != nil {
return nil, err
}
@ -181,6 +183,24 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
afterAll = append(afterAll, func() {
a.expectedAccounts[id].status = StatusDeclined
})
case "invite":
inviteParts := strings.Split(commandArgs[0], ",")
id := inviteParts[0]
inviteIds = append(inviteIds, id)
batchPayload.NewInvites = append(batchPayload.NewInvites, AclPermissionsNone)
case "invite_anyone":
inviteParts := strings.Split(commandArgs[0], ",")
id := inviteParts[0]
var permissions AclPermissions
if inviteParts[1] == "r" {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Reader)
} else if inviteParts[1] == "rw" {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Writer)
} else {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Admin)
}
inviteIds = append(inviteIds, id)
batchPayload.NewInvites = append(batchPayload.NewInvites, permissions)
case "approve":
recs, err := acl.AclState().JoinRecords(false)
if err != nil {
@ -198,7 +218,7 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
}
}
if recId == "" {
return nil, fmt.Errorf("no join records for approve")
return nil, fmt.Errorf("no join records to approve")
}
perms := getPerm(argParts[1])
afterAll = append(afterAll, func() {
@ -211,12 +231,14 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
})
}
}
res, err := acl.RecordBuilder().BuildBatchRequest(batchPayload)
if err != nil {
return nil, err
}
return afterAll, addRec(WrapAclRecord(res))
for i, id := range inviteIds {
a.invites[id] = res.Invites[i]
}
return afterAll, addRec(WrapAclRecord(res.Rec))
}
func (a *AclTestExecutor) Execute(cmd string) (err error) {
@ -273,7 +295,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
} else {
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
copyStorage := ownerAcl.storage.(*inMemoryStorage).Copy()
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, NoOpAcceptorVerifier{})
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, recordverifier.NewValidateFull())
if err != nil {
return err
}
@ -291,7 +313,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
keys := a.actualAccounts[account].Keys
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
copyStorage := ownerAcl.storage.(*inMemoryStorage).Copy()
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, NoOpAcceptorVerifier{})
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, recordverifier.NewValidateFull())
if err != nil {
return err
}
@ -372,6 +394,51 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
if err != nil {
return err
}
case "invite_change":
var permissions AclPermissions
inviteParts := strings.Split(args[0], ",")
if inviteParts[1] == "r" {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Reader)
} else if inviteParts[1] == "rw" {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Writer)
} else {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Admin)
}
invite := a.invites[inviteParts[0]]
invId, err := acl.AclState().GetInviteIdByPrivKey(invite)
if err != nil {
return err
}
res, err := acl.RecordBuilder().BuildInviteChange(InviteChangePayload{
IniviteRecordId: invId,
Permissions: permissions,
})
if err != nil {
return err
}
err = addRec(WrapAclRecord(res))
if err != nil {
return err
}
case "invite_anyone":
var permissions AclPermissions
inviteParts := strings.Split(args[0], ",")
if inviteParts[1] == "r" {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Reader)
} else if inviteParts[1] == "rw" {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Writer)
} else {
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Admin)
}
res, err := acl.RecordBuilder().BuildInviteAnyone(permissions)
if err != nil {
return err
}
a.invites[inviteParts[0]] = res.InviteKey
err = addRec(WrapAclRecord(res.InviteRec))
if err != nil {
return err
}
case "batch":
afterAll, err = a.buildBatchRequest(args, acl, getPerm, addRec)
if err != nil {
@ -394,7 +461,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
}
}
if recId == "" {
return fmt.Errorf("no join records for approve")
return fmt.Errorf("no join records to approve")
}
perms := getPerm(argParts[1])
res, err := acl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
@ -448,7 +515,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
return err
}
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, NoOpAcceptorVerifier{})
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, recordverifier.NewValidateFull())
if err != nil {
return err
}
@ -488,6 +555,25 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
if err != nil {
return err
}
case "invite_join":
invite := a.invites[args[0]]
inviteJoin, err := acl.RecordBuilder().BuildInviteJoin(InviteJoinPayload{
InviteKey: invite,
Metadata: []byte(account),
})
if err != nil {
return err
}
invId, err := acl.AclState().GetInviteIdByPrivKey(invite)
if err != nil {
return err
}
err = addRec(WrapAclRecord(inviteJoin))
if err != nil {
return err
}
a.expectedAccounts[account].status = StatusActive
a.expectedAccounts[account].perms = acl.AclState().invites[invId].Permissions
case "remove":
identities := strings.Split(args[0], ",")
var pubKeys []crypto.PubKey

View file

@ -53,6 +53,7 @@ func TestAclExecutor(t *testing.T) {
{"a.init::a", nil},
// creating an invite
{"a.invite::invId", nil},
{"a.invite_anyone::oldInvId,r", nil},
// cannot self join
{"a.join::invId", ErrInsufficientPermissions},
// now b can join
@ -89,7 +90,7 @@ func TestAclExecutor(t *testing.T) {
{"g.join::inv1Id", nil},
{"g.cancel::g", nil},
// e cannot approve cancelled request
{"e.approve::g,rw", fmt.Errorf("no join records for approve")},
{"e.approve::g,rw", fmt.Errorf("no join records to approve")},
{"g.join::inv1Id", nil},
{"e.decline::g", nil},
// g cannot cancel declined request
@ -128,6 +129,27 @@ func TestAclExecutor(t *testing.T) {
{"a.changes::guest,none", ErrInsufficientPermissions},
// can't change permission of existing user to guest, should be only possible to create it with add
{"a.changes::r,g", ErrInsufficientPermissions},
{"a.invite_anyone::invAnyoneId,rw", nil},
{"new.invite_join::invAnyoneId", nil},
// invite keys persist after user removal
{"a.remove::new", nil},
{"new1.invite_join::invAnyoneId", nil},
{"a.revoke::invAnyoneId", nil},
{"new2.invite_join::invAnyoneId", ErrNoSuchInvite},
{"a.invite_change::oldInvId,a", nil},
{"new2.invite_join::oldInvId", nil},
{"new2.add::new3,r,new3m", nil},
{"a.batch::revoke:oldInvId;invite_anyone:someId,a", nil},
{"new4.invite_join::someId", nil},
{"new4.add::super,r,superm", nil},
// check that users can't join using request to join for anyone can join links
{"new5.join::someId", ErrNoSuchInvite},
{"a.invite::requestJoinId", nil},
{"joiner.join::requestJoinId", nil},
// check that users can join under a different link even after they created a request to join
{"joiner.invite_join::someId", nil},
// check that they can't be approved after they joined under a different link
{"a.approve::joiner,rw", fmt.Errorf("no join records to approve")},
}
for _, cmd := range cmds {
err := a.Execute(cmd.cmd)

View file

@ -8,6 +8,7 @@ import (
"sync"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
@ -26,17 +27,6 @@ type RWLocker interface {
RUnlock()
}
type AcceptorVerifier interface {
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
}
type NoOpAcceptorVerifier struct {
}
func (n NoOpAcceptorVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
return nil
}
type AclList interface {
RWLocker
Id() string
@ -75,6 +65,7 @@ type aclList struct {
keyStorage crypto.KeyStorage
aclState *AclState
storage Storage
verifier recordverifier.AcceptorVerifier
sync.RWMutex
}
@ -84,10 +75,10 @@ type internalDeps struct {
keyStorage crypto.KeyStorage
stateBuilder *aclStateBuilder
recordBuilder AclRecordBuilder
acceptorVerifier AcceptorVerifier
acceptorVerifier recordverifier.AcceptorVerifier
}
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage Storage, verifier AcceptorVerifier) (AclList, error) {
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage Storage, verifier recordverifier.AcceptorVerifier) (AclList, error) {
keyStorage := crypto.NewKeyStorage()
deps := internalDeps{
storage: storage,
@ -159,6 +150,7 @@ func build(deps internalDeps) (list AclList, err error) {
stateBuilder: stateBuilder,
recordBuilder: recBuilder,
storage: storage,
verifier: deps.acceptorVerifier,
id: id,
}
stateBuilder.Init(id)
@ -186,6 +178,7 @@ func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord, afterValid
return
}
stateCopy := a.aclState.Copy()
stateCopy.contentValidator = newContentValidator(stateCopy.keyStore, stateCopy, recordverifier.NewValidateFull())
err = stateCopy.ApplyRecord(record)
if err != nil || afterValid == nil {
return

View file

@ -13,6 +13,7 @@ import (
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/crypto"
)
@ -186,8 +187,8 @@ func TestAclList_InviteRevoke(t *testing.T) {
// checking acl state
require.True(t, ownerState().Permissions(ownerState().pubKey).IsOwner())
require.True(t, ownerState().Permissions(accountState().pubKey).NoPermissions())
require.Empty(t, ownerState().inviteKeys)
require.Empty(t, accountState().inviteKeys)
require.Empty(t, ownerState().invites)
require.Empty(t, accountState().invites)
}
func TestAclList_RequestDecline(t *testing.T) {
@ -279,7 +280,7 @@ func TestAclList_FixAcceptPanic(t *testing.T) {
fx := newFixture(t)
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
_, err := BuildAclListWithIdentity(fx.accountKeys, fx.ownerAcl.storage, NoOpAcceptorVerifier{})
_, err := BuildAclListWithIdentity(fx.accountKeys, fx.ownerAcl.storage, recordverifier.NewValidateFull())
require.NoError(t, err)
}
@ -309,6 +310,27 @@ func TestAclList_MetadataDecrypt(t *testing.T) {
require.NotEqual(t, mockMetadata, meta)
}
func TestAclList_ValidateUsesCorrectVerifier(t *testing.T) {
fx := newFixture(t)
var ownerAcl = fx.ownerAcl
ownerAcl.aclState.contentValidator.(*contentValidator).verifier = recordverifier.New()
ownerAcl.verifier = recordverifier.New()
// building invite
inv, err := ownerAcl.RecordBuilder().BuildInvite()
require.NoError(t, err)
isCalled := false
ok := ownerAcl.aclState.contentValidator.(*contentValidator).verifier.ShouldValidate()
require.False(t, ok)
err = fx.ownerAcl.ValidateRawRecord(inv.InviteRec, func(state *AclState) error {
isCalled = true
// check that we change the validator to the verifying one
require.True(t, state.contentValidator.(*contentValidator).verifier.ShouldValidate())
return nil
})
require.NoError(t, err)
require.True(t, isCalled)
}
func TestAclList_ReadKeyChange(t *testing.T) {
fx := newFixture(t)
var (

View file

@ -1,7 +1,10 @@
package list
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/crypto"
)
@ -17,7 +20,7 @@ func newAclWithStoreProvider(root *consensusproto.RawRecordWithId, keys *account
if err != nil {
return nil, err
}
return BuildAclListWithIdentity(keys, storage, NoOpAcceptorVerifier{})
return BuildAclListWithIdentity(keys, storage, recordverifier.NewValidateFull())
}
func newDerivedAclWithStoreProvider(spaceId string, keys *accountdata.AccountKeys, metadata []byte, storeProvider StorageProvider) (AclList, error) {
@ -43,11 +46,11 @@ func newInMemoryAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.
if err != nil {
return nil, err
}
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
return BuildAclListWithIdentity(keys, st, recordverifier.NewValidateFull())
}
func buildDerivedRoot(spaceId string, keys *accountdata.AccountKeys, metadata []byte) (root *consensusproto.RawRecordWithId, err error) {
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, recordverifier.NewValidateFull())
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
if err != nil {
return nil, err
@ -68,3 +71,30 @@ func buildDerivedRoot(spaceId string, keys *accountdata.AccountKeys, metadata []
Metadata: metadata,
})
}
func NewTestAclStateWithUsers(numWriters, numReaders, numInvites int) *AclState {
st := &AclState{
keys: make(map[string]AclKeys),
accountStates: make(map[string]AccountState),
invites: make(map[string]Invite),
requestRecords: make(map[string]RequestRecord),
pendingRequests: make(map[string]string),
keyStore: crypto.NewKeyStorage(),
}
for i := 0; i < numWriters; i++ {
st.accountStates[fmt.Sprint("w", i)] = AccountState{
Permissions: AclPermissionsWriter,
Status: StatusActive,
}
}
for i := 0; i < numReaders; i++ {
st.accountStates[fmt.Sprint("r", i)] = AccountState{
Permissions: AclPermissionsReader,
Status: StatusActive,
}
}
for i := 0; i < numInvites; i++ {
st.invites[fmt.Sprint("r", i)] = Invite{}
}
return st
}

View file

@ -24,6 +24,8 @@ type AclRecord struct {
AcceptorTimestamp int64
Data []byte
Identity crypto.PubKey
AcceptorIdentity crypto.PubKey
AcceptorSignature []byte
Model interface{}
Signature []byte
}
@ -83,6 +85,10 @@ func (p AclPermissions) IsOwner() bool {
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
}
func (p AclPermissions) IsGuest() bool {
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Guest
}
func (p AclPermissions) CanWrite() bool {
switch aclrecordproto.AclUserPermissions(p) {
case aclrecordproto.AclUserPermissions_Admin:

View file

@ -1,7 +1,10 @@
package list
import (
"golang.org/x/exp/slices"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/util/crypto"
)
@ -11,6 +14,8 @@ type ContentValidator interface {
ValidatePermissionChanges(ch *aclrecordproto.AclAccountPermissionChanges, authorIdentity crypto.PubKey) (err error)
ValidateAccountsAdd(ch *aclrecordproto.AclAccountsAdd, authorIdentity crypto.PubKey) (err error)
ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error)
ValidateInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, authorIdentity crypto.PubKey) (err error)
ValidateInviteChange(ch *aclrecordproto.AclAccountInviteChange, authorIdentity crypto.PubKey) (err error)
ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error)
ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error)
ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error)
@ -24,16 +29,21 @@ type ContentValidator interface {
type contentValidator struct {
keyStore crypto.KeyStorage
aclState *AclState
verifier recordverifier.AcceptorVerifier
}
func newContentValidator(keyStore crypto.KeyStorage, aclState *AclState) ContentValidator {
func newContentValidator(keyStore crypto.KeyStorage, aclState *AclState, verifier recordverifier.AcceptorVerifier) ContentValidator {
return &contentValidator{
keyStore: keyStore,
aclState: aclState,
verifier: verifier,
}
}
func (c *contentValidator) ValidatePermissionChanges(ch *aclrecordproto.AclAccountPermissionChanges, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
for _, ch := range ch.Changes {
err := c.ValidatePermissionChange(ch, authorIdentity)
if err != nil {
@ -44,6 +54,9 @@ func (c *contentValidator) ValidatePermissionChanges(ch *aclrecordproto.AclAccou
}
func (c *contentValidator) ValidateAccountsAdd(ch *aclrecordproto.AclAccountsAdd, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
@ -66,7 +79,54 @@ func (c *contentValidator) ValidateAccountsAdd(ch *aclrecordproto.AclAccountsAdd
return nil
}
func (c *contentValidator) ValidateInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).NoPermissions() {
return ErrInsufficientPermissions
}
invite, exists := c.aclState.invites[ch.InviteRecordId]
if !exists {
return ErrNoSuchInvite
}
if invite.Type != aclrecordproto.AclInviteType_AnyoneCanJoin {
return ErrNoSuchInvite
}
if !c.aclState.Permissions(authorIdentity).NoPermissions() {
return ErrInsufficientPermissions
}
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return
}
if !authorIdentity.Equals(inviteIdentity) {
return ErrIncorrectIdentity
}
rawInviteIdentity, err := inviteIdentity.Raw()
if err != nil {
return err
}
ok, err := invite.Key.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
if err != nil {
return ErrInvalidSignature
}
if !ok {
return ErrInvalidSignature
}
if len(ch.Metadata) > MaxMetadataLen {
return ErrMetadataTooLarge
}
if ch.EncryptedReadKey == nil {
return ErrIncorrectReadKey
}
return nil
}
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if ch.PrevId != c.aclState.lastRecordId {
return ErrIncorrectRecordSequence
}
@ -90,6 +150,8 @@ func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclConten
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
case ch.GetRequestJoin() != nil:
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
case ch.GetInviteJoin() != nil:
return c.ValidateInviteJoin(ch.GetInviteJoin(), authorIdentity)
case ch.GetRequestAccept() != nil:
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
case ch.GetRequestDecline() != nil:
@ -110,6 +172,9 @@ func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclConten
}
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
@ -149,18 +214,57 @@ func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccoun
}
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
permissions := AclPermissions(ch.Permissions)
if ch.InviteType == aclrecordproto.AclInviteType_AnyoneCanJoin {
if permissions.IsOwner() || permissions.NoPermissions() || permissions.IsGuest() {
return ErrInsufficientPermissions
}
if ch.EncryptedReadKey == nil {
return ErrIncorrectReadKey
}
}
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
return
}
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
func (c *contentValidator) ValidateInviteChange(ch *aclrecordproto.AclAccountInviteChange, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
invite, exists := c.aclState.invites[ch.InviteRecordId]
if !exists {
return ErrNoSuchInvite
}
permissions := AclPermissions(ch.Permissions)
if invite.Type != aclrecordproto.AclInviteType_AnyoneCanJoin {
return ErrNoSuchInvite
}
if invite.Permissions == permissions {
return ErrInsufficientPermissions
}
if permissions.IsOwner() || permissions.NoPermissions() || permissions.IsGuest() {
return ErrInsufficientPermissions
}
return
}
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
_, exists := c.aclState.invites[ch.InviteRecordId]
if !exists {
return ErrNoSuchInvite
}
@ -168,13 +272,19 @@ func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInv
}
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
if !c.verifier.ShouldValidate() {
return nil
}
invite, exists := c.aclState.invites[ch.InviteRecordId]
if !exists {
return ErrNoSuchInvite
}
if !c.aclState.Permissions(authorIdentity).NoPermissions() {
return ErrInsufficientPermissions
}
if invite.Type != aclrecordproto.AclInviteType_RequestToJoin {
return ErrNoSuchInvite
}
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
if err != nil {
return
@ -189,7 +299,7 @@ func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequ
if err != nil {
return err
}
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
ok, err := invite.Key.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
if err != nil {
return ErrInvalidSignature
}
@ -203,6 +313,9 @@ func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequ
}
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
@ -224,6 +337,9 @@ func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRe
}
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
@ -235,6 +351,9 @@ func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountR
}
func (c *contentValidator) ValidateRequestCancel(ch *aclrecordproto.AclAccountRequestCancel, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
rec, exists := c.aclState.requestRecords[ch.RecordId]
if !exists {
return ErrNoSuchRequest
@ -246,6 +365,9 @@ func (c *contentValidator) ValidateRequestCancel(ch *aclrecordproto.AclAccountRe
}
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
@ -275,6 +397,9 @@ func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRe
}
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
if c.aclState.Permissions(authorIdentity).NoPermissions() {
return ErrInsufficientPermissions
}
@ -288,10 +413,16 @@ func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRe
}
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
return c.validateReadKeyChange(ch, nil)
}
func (c *contentValidator) validateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, removedUsers map[string]struct{}) (err error) {
if !c.verifier.ShouldValidate() {
return nil
}
_, err = c.keyStore.PubKeyFromProto(ch.MetadataPubKey)
if err != nil {
return ErrNoMetadataKey
@ -299,23 +430,55 @@ func (c *contentValidator) validateReadKeyChange(ch *aclrecordproto.AclReadKeyCh
if ch.EncryptedMetadataPrivKey == nil || ch.EncryptedOldReadKey == nil {
return ErrIncorrectReadKey
}
var (
activeUsers []string
updatedInvites []string
activeInvites []string
updatedUsers []string
)
for _, accState := range c.aclState.accountStates {
if accState.Permissions.NoPermissions() {
continue
}
pubKey := mapKeyFromPubKey(accState.PubKey)
if _, exists := removedUsers[pubKey]; exists {
continue
}
activeUsers = append(activeUsers, pubKey)
}
for _, invite := range c.aclState.invites {
if invite.Type == aclrecordproto.AclInviteType_AnyoneCanJoin {
activeInvites = append(activeInvites, mapKeyFromPubKey(invite.Key))
}
}
for _, encKeys := range ch.AccountKeys {
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
if err != nil {
return err
}
idKey := mapKeyFromPubKey(identity)
_, exists := c.aclState.accountStates[idKey]
if !exists {
return ErrNoSuchAccount
}
if removedUsers == nil {
continue
}
_, exists = removedUsers[idKey]
if exists {
return ErrIncorrectNumberOfAccounts
updatedUsers = append(updatedUsers, idKey)
}
for _, invKeys := range ch.InviteKeys {
identity, err := c.keyStore.PubKeyFromProto(invKeys.Identity)
if err != nil {
return err
}
idKey := mapKeyFromPubKey(identity)
updatedInvites = append(updatedInvites, idKey)
}
if len(activeUsers) != len(updatedUsers) {
return ErrIncorrectNumberOfAccounts
}
if len(activeInvites) != len(updatedInvites) {
return ErrIncorrectNumberOfAccounts
}
slices.Sort(updatedUsers)
slices.Sort(updatedInvites)
slices.Sort(activeUsers)
slices.Sort(activeInvites)
if !slices.Equal(activeUsers, updatedUsers) || !slices.Equal(activeInvites, updatedInvites) {
return ErrIncorrectNumberOfAccounts
}
return
}

View file

@ -0,0 +1,58 @@
package recordverifier
import (
"fmt"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/crypto"
)
const CName = "common.acl.recordverifier"
type AcceptorVerifier interface {
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
ShouldValidate() bool
}
type RecordVerifier interface {
app.Component
AcceptorVerifier
}
func New() RecordVerifier {
return &recordVerifier{
store: crypto.NewKeyStorage(),
}
}
type recordVerifier struct {
store crypto.KeyStorage
}
func (r *recordVerifier) Init(a *app.App) (err error) {
return
}
func (r *recordVerifier) Name() (name string) {
return CName
}
func (r *recordVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
identity, err := r.store.PubKeyFromProto(rec.AcceptorIdentity)
if err != nil {
identity, err = crypto.UnmarshalEd25519PublicKey(rec.AcceptorIdentity)
if err != nil {
return fmt.Errorf("failed to get acceptor identity: %w", err)
}
}
verified, err := identity.Verify(rec.Payload, rec.AcceptorSignature)
if !verified || err != nil {
return fmt.Errorf("failed to verify acceptor: %w", err)
}
return nil
}
func (r *recordVerifier) ShouldValidate() bool {
return false
}

View file

@ -0,0 +1,106 @@
package recordverifier
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/testutil/accounttest"
"github.com/anyproto/any-sync/util/crypto"
)
type fixture struct {
*recordVerifier
app *app.App
networkPrivKey crypto.PrivKey
}
func newFixture(t *testing.T) *fixture {
accService := &accounttest.AccountTestService{}
a := &app.App{}
verifier := New().(*recordVerifier)
a.Register(accService).
Register(verifier)
require.NoError(t, a.Start(context.Background()))
return &fixture{
recordVerifier: verifier,
app: a,
networkPrivKey: accService.Account().SignKey,
}
}
func TestRecordVerifier_VerifyAcceptor(t *testing.T) {
fx := newFixture(t)
identity, err := fx.networkPrivKey.GetPublic().Marshall()
require.NoError(t, err)
testPayload := []byte("test payload")
acceptorSignature, err := fx.networkPrivKey.Sign(testPayload)
require.NoError(t, err)
rawRecord := &consensusproto.RawRecord{
AcceptorIdentity: identity,
Payload: testPayload,
AcceptorSignature: acceptorSignature,
}
err = fx.VerifyAcceptor(rawRecord)
require.NoError(t, err)
}
func TestRecordVerifier_VerifyAcceptor_InvalidSignature(t *testing.T) {
fx := newFixture(t)
identity, err := fx.networkPrivKey.GetPublic().Marshall()
require.NoError(t, err)
testPayload := []byte("test payload")
rawRecord := &consensusproto.RawRecord{
AcceptorIdentity: identity,
Payload: testPayload,
AcceptorSignature: []byte("invalid signature"),
}
err = fx.VerifyAcceptor(rawRecord)
require.Error(t, err)
}
func TestRecordVerifier_VerifyAcceptor_ModifiedPayload(t *testing.T) {
fx := newFixture(t)
identity, err := fx.networkPrivKey.GetPublic().Marshall()
require.NoError(t, err)
testPayload := []byte("test payload")
acceptorSignature, err := fx.networkPrivKey.Sign(testPayload)
require.NoError(t, err)
rawRecord := &consensusproto.RawRecord{
AcceptorIdentity: identity,
Payload: []byte("modified payload"),
AcceptorSignature: acceptorSignature,
}
err = fx.VerifyAcceptor(rawRecord)
require.Error(t, err)
}
func TestRecordVerifier_VerifyAcceptor_InvalidIdentity(t *testing.T) {
fx := newFixture(t)
testPayload := []byte("test payload")
acceptorSignature, err := fx.networkPrivKey.Sign(testPayload)
require.NoError(t, err)
rawRecord := &consensusproto.RawRecord{
AcceptorIdentity: []byte("invalid identity"),
Payload: testPayload,
AcceptorSignature: acceptorSignature,
}
err = fx.VerifyAcceptor(rawRecord)
require.Error(t, err)
}
func TestRecordVerifier_VerifyAcceptor_EmptySignature(t *testing.T) {
fx := newFixture(t)
identity, err := fx.networkPrivKey.GetPublic().Marshall()
require.NoError(t, err)
rawRecord := &consensusproto.RawRecord{
AcceptorIdentity: identity,
Payload: []byte("test payload"),
AcceptorSignature: nil,
}
err = fx.VerifyAcceptor(rawRecord)
require.Error(t, err)
}

View file

@ -0,0 +1,28 @@
package recordverifier
import (
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/consensus/consensusproto"
)
type ValidateFull struct{}
func NewValidateFull() RecordVerifier {
return &ValidateFull{}
}
func (a *ValidateFull) Init(_ *app.App) error {
return nil
}
func (a *ValidateFull) Name() string {
return CName
}
func (a *ValidateFull) VerifyAcceptor(_ *consensusproto.RawRecord) error {
return nil
}
func (a *ValidateFull) ShouldValidate() bool {
return true
}

View file

@ -11,6 +11,7 @@ import (
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/sync"
@ -67,7 +68,8 @@ func (s *syncAcl) Init(a *app.App) (err error) {
return err
}
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
verifier := a.MustComponent(recordverifier.CName).(recordverifier.RecordVerifier)
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, verifier)
if err != nil {
return
}

View file

@ -17,6 +17,7 @@ import (
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage"
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage/innerstorage"
"github.com/anyproto/any-sync/commonspace/spacepayloads"
@ -280,7 +281,7 @@ func newFixture(t *testing.T, keys *accountdata.AccountKeys, spacePayload spaces
require.NoError(t, err)
aclStorage, err := storage.AclStorage()
require.NoError(t, err)
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, list.NoOpAcceptorVerifier{})
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, recordverifier.NewValidateFull())
require.NoError(t, err)
storageId := "kv.storage"
rpcHandler := rpctest.NewTestServer()

View file

@ -36,6 +36,9 @@ type Change struct {
OrderId string
SnapshotCounter int
// using this on build stage
rawChange *treechangeproto.RawTreeChangeWithId
// iterator helpers
visited bool
branchesFinished bool

View file

@ -130,14 +130,13 @@ type objectTree struct {
difSnapshotBuf []*treechangeproto.RawTreeChangeWithId
newChangesBuf []*Change
newSnapshotsBuf []*Change
notSeenIdxBuf []int
snapshotPath []string
sync.Mutex
}
func (ot *objectTree) rebuildFromStorage(theirHeads, theirSnapshotPath []string, newChanges []*Change) (err error) {
func (ot *objectTree) rebuildFromStorage(theirHeads, theirSnapshotPath []string, newChanges []*Change) (added []*Change, err error) {
var (
ourPath []string
oldTree = ot.tree
@ -146,10 +145,10 @@ func (ot *objectTree) rebuildFromStorage(theirHeads, theirSnapshotPath []string,
// TODO: add error handling
ourPath, err = ot.SnapshotPath()
if err != nil {
return fmt.Errorf("rebuild from storage: %w", err)
return nil, fmt.Errorf("rebuild from storage: %w", err)
}
}
ot.tree, err = ot.treeBuilder.Build(treeBuilderOpts{
ot.tree, added, err = ot.treeBuilder.buildWithAdded(treeBuilderOpts{
theirHeads: theirHeads,
ourSnapshotPath: ourPath,
theirSnapshotPath: theirSnapshotPath,
@ -178,7 +177,7 @@ func (ot *objectTree) rebuildFromStorage(theirHeads, theirSnapshotPath []string,
// it is a good question whether we need to validate everything
// because maybe we can trust the stuff that is already in the storage
return ot.validateTree(nil)
return added, ot.validateTree(nil)
}
func (ot *objectTree) Id() string {
@ -406,7 +405,7 @@ func (ot *objectTree) AddRawChangesWithUpdater(ctx context.Context, changes RawC
}
rollback := func() {
rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
_, rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
if rebuildErr != nil {
log.Error("failed to rebuild after adding to storage", zap.Strings("heads", ot.Heads()), zap.Error(rebuildErr))
}
@ -436,7 +435,6 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawChangesPayload) (addResult AddResult, err error) {
// resetting buffers
ot.newChangesBuf = ot.newChangesBuf[:0]
ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0]
ot.difSnapshotBuf = ot.difSnapshotBuf[:0]
ot.newSnapshotsBuf = ot.newSnapshotsBuf[:0]
@ -453,7 +451,7 @@ func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawCh
)
// filtering changes, verifying and unmarshalling them
for idx, ch := range changesPayload.RawChanges {
for _, ch := range changesPayload.RawChanges {
// not unmarshalling the changes if they were already added either as unattached or attached
if _, exists := ot.tree.attached[ch.Id]; exists {
continue
@ -468,16 +466,16 @@ func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawCh
return
}
}
change.rawChange = ch
if change.IsSnapshot {
ot.newSnapshotsBuf = append(ot.newSnapshotsBuf, change)
}
ot.newChangesBuf = append(ot.newChangesBuf, change)
ot.notSeenIdxBuf = append(ot.notSeenIdxBuf, idx)
}
// if no new changes, then returning
if len(ot.notSeenIdxBuf) == 0 {
if len(ot.newChangesBuf) == 0 {
addResult = AddResult{
OldHeads: prevHeadsCopy,
Heads: prevHeadsCopy,
@ -491,7 +489,7 @@ func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawCh
headsToUse = changesPayload.NewHeads
)
// if our validator provides filtering mechanism then we use it
filteredHeads, ot.newChangesBuf, ot.newSnapshotsBuf, ot.notSeenIdxBuf = ot.validator.FilterChanges(ot.aclList, ot.newChangesBuf, ot.newSnapshotsBuf, ot.notSeenIdxBuf)
filteredHeads, ot.newChangesBuf, ot.newSnapshotsBuf = ot.validator.FilterChanges(ot.aclList, ot.newChangesBuf, ot.newSnapshotsBuf)
if filteredHeads {
// if we filtered some of the heads, then we don't know which heads to use
headsToUse = []string{}
@ -553,22 +551,23 @@ func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawCh
}
log := log.With(zap.String("treeId", ot.id))
if shouldRebuildFromStorage {
err = ot.rebuildFromStorage(headsToUse, changesPayload.SnapshotPath, ot.newChangesBuf)
var added []*Change
added, err = ot.rebuildFromStorage(headsToUse, changesPayload.SnapshotPath, ot.newChangesBuf)
if err != nil {
log.Error("failed to rebuild with new heads", zap.Strings("headsToUse", headsToUse), zap.Error(err))
// rebuilding without new changes
rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
_, rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
if rebuildErr != nil {
log.Error("failed to rebuild from storage", zap.Strings("heads", ot.Heads()), zap.Error(rebuildErr))
}
return
}
addResult, err = ot.createAddResult(prevHeadsCopy, Rebuild, changesPayload.RawChanges)
addResult, err = ot.createAddResult(prevHeadsCopy, Rebuild, added)
if err != nil {
log.Error("failed to create add result", zap.Strings("headsToUse", headsToUse), zap.Error(err))
// that means that some unattached changes were somehow corrupted in memory
// this shouldn't happen but if that happens, then rebuilding from storage
rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
_, rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
if rebuildErr != nil {
log.Error("failed to rebuild after add result", zap.Strings("heads", ot.Heads()), zap.Error(rebuildErr))
}
@ -595,12 +594,12 @@ func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawCh
err = fmt.Errorf("%w: %w", ErrHasInvalidChanges, err)
return
}
addResult, err = ot.createAddResult(prevHeadsCopy, mode, changesPayload.RawChanges)
addResult, err = ot.createAddResult(prevHeadsCopy, mode, treeChangesAdded)
if err != nil {
// that means that some unattached changes were somehow corrupted in memory
// this shouldn't happen but if that happens, then rebuilding from storage
rollback(treeChangesAdded)
rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
_, rebuildErr := ot.rebuildFromStorage(nil, nil, nil)
if rebuildErr != nil {
log.Error("failed to rebuild after add result (add to tree)", zap.Strings("heads", ot.Heads()), zap.Error(rebuildErr))
}
@ -610,43 +609,27 @@ func (ot *objectTree) addChangesToTree(ctx context.Context, changesPayload RawCh
}
}
func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, rawChanges []*treechangeproto.RawTreeChangeWithId) (addResult AddResult, err error) {
headsCopy := func() []string {
newHeads := make([]string, 0, len(ot.tree.Heads()))
newHeads = append(newHeads, ot.tree.Heads()...)
return newHeads
}
// returns changes that we added to the tree as attached this round
// they can include not only the changes that were added now,
// but also the changes that were previously in the tree
getAddedChanges := func() (added []StorageChange, err error) {
for _, idx := range ot.notSeenIdxBuf {
rawChange := rawChanges[idx]
if ch, exists := ot.tree.attached[rawChange.Id]; exists {
ot.flusher.MarkNewChange(ch)
added = append(added, StorageChange{
RawChange: rawChange.RawChange,
PrevIds: ch.PreviousIds,
Id: ch.Id,
SnapshotCounter: ch.SnapshotCounter,
SnapshotId: ch.SnapshotId,
OrderId: ch.OrderId,
ChangeSize: len(rawChange.RawChange),
})
}
}
return
}
func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, changes []*Change) (addResult AddResult, err error) {
newHeads := make([]string, 0, len(ot.tree.Heads()))
newHeads = append(newHeads, ot.tree.Heads()...)
var added []StorageChange
added, err = getAddedChanges()
if err != nil {
return
for _, ch := range changes {
rawChange := ch.rawChange
ot.flusher.MarkNewChange(ch)
added = append(added, StorageChange{
RawChange: rawChange.RawChange,
PrevIds: ch.PreviousIds,
Id: ch.Id,
SnapshotCounter: ch.SnapshotCounter,
SnapshotId: ch.SnapshotId,
OrderId: ch.OrderId,
ChangeSize: len(rawChange.RawChange),
})
ch.rawChange = nil
}
addResult = AddResult{
OldHeads: oldHeads,
Heads: headsCopy(),
Heads: newHeads,
Added: added,
Mode: mode,
}

View file

@ -19,6 +19,7 @@ import (
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
)
@ -390,7 +391,7 @@ func TestObjectTree(t *testing.T) {
require.NoError(t, err)
prevId = rec.Id
}
beforeAcl, err := list.BuildAclListWithIdentity(account.Keys, beforeStorage, list.NoOpAcceptorVerifier{})
beforeAcl, err := list.BuildAclListWithIdentity(account.Keys, beforeStorage, recordverifier.NewValidateFull())
require.NoError(t, err)
err = exec.Execute("a.invite::invId")
require.NoError(t, err)
@ -462,7 +463,7 @@ func TestObjectTree(t *testing.T) {
require.NoError(t, err)
storage, err := list.NewInMemoryStorage(prevAclRecs[0].Id, prevAclRecs)
require.NoError(t, err)
acl, err := list.BuildAclListWithIdentity(bAccount.Keys, storage, list.NoOpAcceptorVerifier{})
acl, err := list.BuildAclListWithIdentity(bAccount.Keys, storage, recordverifier.NewValidateFull())
require.NoError(t, err)
// creating tree with old storage which doesn't have a new invite record
bTree, err := BuildKeyFilterableObjectTree(bStore, acl)
@ -487,10 +488,8 @@ func TestObjectTree(t *testing.T) {
})
require.NoError(t, err)
bObjTree := bTree.(*objectTree)
// this is just a random slice, so the func works
indexes := []int{1, 2, 3, 4, 5}
// checking that we filter the changes
filtered, filteredChanges, _, _ := bObjTree.validator.FilterChanges(bObjTree.aclList, collectedChanges, nil, indexes)
filtered, filteredChanges, _ := bObjTree.validator.FilterChanges(bObjTree.aclList, collectedChanges, nil)
require.True(t, filtered)
for _, ch := range filteredChanges {
require.NotEqual(t, unexpectedId, ch.Id)
@ -929,6 +928,86 @@ func TestObjectTree(t *testing.T) {
}
})
t.Run("test fix incorrect changes added", func(t *testing.T) {
treeCtx := prepareTreeContext(t, aclList)
treeStorage := treeCtx.treeStorage
changeCreator := treeCtx.changeCreator
objTree := treeCtx.objTree
rawChangesFirst := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRoot("0", aclList.Head().Id),
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
}
rawChangesSecond := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRoot("0", aclList.Head().Id),
changeCreator.CreateRaw("2", aclList.Head().Id, "0", true, "0"),
}
payloadFirst := RawChangesPayload{
NewHeads: []string{"1"},
RawChanges: rawChangesFirst,
}
payloadSecond := RawChangesPayload{
NewHeads: []string{"2"},
RawChanges: rawChangesSecond,
}
res, err := objTree.AddRawChanges(context.Background(), payloadFirst)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, []string{"1"}, res.Heads)
res, err = objTree.AddRawChanges(context.Background(), payloadSecond)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, []string{"1", "2"}, res.Heads)
for _, ch := range append(rawChangesFirst, rawChangesSecond...) {
raw, err := treeStorage.Get(context.Background(), ch.Id)
assert.NoError(t, err, "storage should have all the changes")
assert.Equal(t, ch.Id, raw.RawTreeChangeWithId().Id, "the changes in the storage should be the same")
assert.Equal(t, ch.RawChange, raw.RawTreeChangeWithId().RawChange, "the changes in the storage should be the same")
}
})
t.Run("test fix incorrect changes added with snapshot path", func(t *testing.T) {
treeCtx := prepareTreeContext(t, aclList)
treeStorage := treeCtx.treeStorage
changeCreator := treeCtx.changeCreator
objTree := treeCtx.objTree
rawChangesFirst := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRoot("0", aclList.Head().Id),
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
}
rawChangesSecond := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRoot("0", aclList.Head().Id),
changeCreator.CreateRaw("2", aclList.Head().Id, "0", true, "0"),
}
payloadFirst := RawChangesPayload{
NewHeads: []string{"1"},
RawChanges: rawChangesFirst,
SnapshotPath: []string{"0", "1"},
}
payloadSecond := RawChangesPayload{
NewHeads: []string{"2"},
RawChanges: rawChangesSecond,
SnapshotPath: []string{"0", "2"},
}
res, err := objTree.AddRawChanges(context.Background(), payloadFirst)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, []string{"1"}, res.Heads)
res, err = objTree.AddRawChanges(context.Background(), payloadSecond)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, []string{"1", "2"}, res.Heads)
for _, ch := range append(rawChangesFirst, rawChangesSecond...) {
raw, err := treeStorage.Get(context.Background(), ch.Id)
assert.NoError(t, err, "storage should have all the changes")
assert.Equal(t, ch.Id, raw.RawTreeChangeWithId().Id, "the changes in the storage should be the same")
assert.Equal(t, ch.RawChange, raw.RawTreeChangeWithId().RawChange, "the changes in the storage should be the same")
}
})
t.Run("add with rollback", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator

View file

@ -250,12 +250,11 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
keys: make(map[string]crypto.SymKey),
newChangesBuf: make([]*Change, 0, 10),
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
notSeenIdxBuf: make([]int, 0, 10),
newSnapshotsBuf: make([]*Change, 0, 10),
flusher: deps.flusher,
}
err := objTree.rebuildFromStorage(nil, nil, nil)
_, err := objTree.rebuildFromStorage(nil, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to rebuild from storage: %w", err)
}
@ -288,7 +287,6 @@ func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht History
keys: make(map[string]crypto.SymKey),
newChangesBuf: make([]*Change, 0, 10),
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
notSeenIdxBuf: make([]int, 0, 10),
newSnapshotsBuf: make([]*Change, 0, 10),
flusher: deps.flusher,
}

View file

@ -35,7 +35,7 @@ type ObjectTreeValidator interface {
ValidateFullTree(tree *Tree, aclList list.AclList) error
// ValidateNewChanges should always be entered while holding a read lock on AclList
ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error
FilterChanges(aclList list.AclList, changes []*Change, snapshots []*Change, indexes []int) (filteredHeads bool, filtered, filteredSnapshots []*Change, newIndexes []int)
FilterChanges(aclList list.AclList, changes []*Change, snapshots []*Change) (filteredHeads bool, filtered, filteredSnapshots []*Change)
}
type noOpTreeValidator struct {
@ -57,14 +57,13 @@ func (n *noOpTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclList,
return nil
}
func (n *noOpTreeValidator) FilterChanges(aclList list.AclList, changes []*Change, snapshots []*Change, indexes []int) (filteredHeads bool, filtered, filteredSnapshots []*Change, newIndexes []int) {
func (n *noOpTreeValidator) FilterChanges(aclList list.AclList, changes []*Change, snapshots []*Change) (filteredHeads bool, filtered, filteredSnapshots []*Change) {
if n.filterFunc == nil {
return false, changes, snapshots, indexes
return false, changes, snapshots
}
for idx, c := range changes {
for _, c := range changes {
// only taking changes which we can read
if n.filterFunc(c) {
newIndexes = append(newIndexes, indexes[idx])
filtered = append(filtered, c)
if c.IsSnapshot {
filteredSnapshots = append(filteredSnapshots, c)
@ -106,24 +105,22 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
return
}
func (v *objectTreeValidator) FilterChanges(aclList list.AclList, changes []*Change, snapshots []*Change, indexes []int) (filteredHeads bool, filtered, filteredSnapshots []*Change, newIndexes []int) {
func (v *objectTreeValidator) FilterChanges(aclList list.AclList, changes []*Change, snapshots []*Change) (filteredHeads bool, filtered, filteredSnapshots []*Change) {
if !v.shouldFilter {
return false, changes, snapshots, indexes
return false, changes, snapshots
}
aclList.RLock()
defer aclList.RUnlock()
state := aclList.AclState()
for idx, c := range changes {
for _, c := range changes {
// this has to be a root
if c.PreviousIds == nil {
newIndexes = append(newIndexes, indexes[idx])
filtered = append(filtered, c)
filteredSnapshots = append(filteredSnapshots, c)
continue
}
// only taking changes which we can read and for which we have acl heads
if keys, exists := state.Keys()[c.ReadKeyId]; aclList.HasHead(c.AclHeadId) && exists && keys.ReadKey != nil {
newIndexes = append(newIndexes, indexes[idx])
filtered = append(filtered, c)
if c.IsSnapshot {
filteredSnapshots = append(filteredSnapshots, c)

View file

@ -53,6 +53,9 @@ func (t *Tree) Root() *Change {
}
func (t *Tree) AddFast(changes ...*Change) []*Change {
if len(changes) == 0 {
return nil
}
defer t.clearUnattached()
t.addedBuf = t.addedBuf[:0]
for _, c := range changes {

View file

@ -49,6 +49,10 @@ func TestTree_Add(t *testing.T) {
assert.Equal(t, tr.root.Id, "root")
assert.Equal(t, []string{"root"}, tr.Heads())
})
t.Run("empty tree add should not panic", func(t *testing.T) {
tr := &Tree{}
tr.AddFast()
})
t.Run("linear add", func(t *testing.T) {
tr := new(Tree)
res, _ := tr.Add(

View file

@ -17,7 +17,7 @@ import (
var (
log = logger.NewNamedSugared("common.commonspace.objecttree")
ErrEmpty = errors.New("logs empty")
ErrEmpty = errors.New("database is empty")
)
type treeBuilder struct {
@ -47,10 +47,6 @@ func newTreeBuilder(storage Storage, builder ChangeBuilder) *treeBuilder {
}
}
func (tb *treeBuilder) Build(opts treeBuilderOpts) (*Tree, error) {
return tb.build(opts)
}
func (tb *treeBuilder) BuildFull() (*Tree, error) {
return tb.build(treeBuilderOpts{full: true})
}
@ -61,7 +57,7 @@ var (
totalLowest atomic.Int32
)
func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
func (tb *treeBuilder) buildWithAdded(opts treeBuilderOpts) (*Tree, []*Change, error) {
cache := make(map[string]*Change)
tb.ctx = context.Background()
for _, ch := range opts.newChanges {
@ -74,12 +70,12 @@ func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
if opts.useHeadsSnapshot {
maxOrder, lowest, err := tb.lowestSnapshots(nil, opts.ourHeads, "")
if err != nil {
return nil, err
return nil, nil, err
}
if len(lowest) != 1 {
snapshot, err = tb.commonSnapshot(lowest)
if err != nil {
return nil, err
return nil, nil, err
}
} else {
snapshot = lowest[0]
@ -94,28 +90,29 @@ func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
if len(opts.ourSnapshotPath) == 0 {
common, err := tb.storage.CommonSnapshot(tb.ctx)
if err != nil {
return nil, err
return nil, nil, err
}
snapshot = common
} else {
our := opts.ourSnapshotPath[0]
_, lowest, err := tb.lowestSnapshots(cache, opts.theirHeads, our)
if err != nil {
return nil, err
return nil, nil, err
}
if len(lowest) != 1 {
snapshot, err = tb.commonSnapshot(lowest)
if err != nil {
return nil, err
return nil, nil, err
}
} else {
snapshot = lowest[0]
}
}
} else {
var err error
snapshot, err = commonSnapshotForTwoPaths(opts.ourSnapshotPath, opts.theirSnapshotPath)
if err != nil {
return nil, err
return nil, nil, err
}
}
} else {
@ -124,10 +121,13 @@ func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
totalSnapshots.Store(totalSnapshots.Load() + 1)
snapshotCh, err := tb.storage.Get(tb.ctx, snapshot)
if err != nil {
return nil, fmt.Errorf("failed to get common snapshot %s: %w", snapshot, err)
return nil, nil, fmt.Errorf("failed to get common snapshot %s: %w", snapshot, err)
}
rawChange := &treechangeproto.RawTreeChangeWithId{}
var changes []*Change
var (
changes = make([]*Change, 0, 10)
newChanges = make([]*Change, 0, 10)
)
err = tb.storage.GetAfterOrder(tb.ctx, snapshotCh.OrderId, func(ctx context.Context, storageChange StorageChange) (shouldContinue bool, err error) {
if order != "" && storageChange.OrderId > order {
return false, nil
@ -141,18 +141,44 @@ func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
ch.OrderId = storageChange.OrderId
ch.SnapshotCounter = storageChange.SnapshotCounter
changes = append(changes, ch)
if _, contains := cache[ch.Id]; contains {
delete(cache, ch.Id)
}
return true, nil
})
if err != nil {
return nil, fmt.Errorf("failed to get changes after order: %w", err)
return nil, nil, fmt.Errorf("failed to get changes after order: %w", err)
}
tr = &Tree{}
changes = append(changes, opts.newChanges...)
tr.AddFast(changes...)
// getting the filtered new changes, we know that we don't have them in storage
for _, change := range cache {
newChanges = append(newChanges, change)
}
if len(changes) == 0 {
return nil, nil, ErrEmpty
}
tr := &Tree{}
changes = append(changes, newChanges...)
added := tr.AddFast(changes...)
if opts.useHeadsSnapshot {
// this is important for history, because by default we get everything after the snapshot
tr.LeaveOnlyBefore(opts.ourHeads)
}
return tr, nil
if len(newChanges) > 0 {
newChanges = newChanges[:0]
for _, change := range added {
// only those that are both added and are new we deem newChanges
if _, contains := cache[change.Id]; contains {
newChanges = append(newChanges, change)
}
}
return tr, newChanges, nil
}
return tr, nil, nil
}
func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
tr, _, err = tb.buildWithAdded(opts)
return
}
func (tb *treeBuilder) lowestSnapshots(cache map[string]*Change, heads []string, ourSnapshot string) (maxOrder string, snapshots []string, err error) {
@ -199,7 +225,7 @@ func (tb *treeBuilder) lowestSnapshots(cache map[string]*Change, heads []string,
current = append(current, next...)
next = next[:0]
for _, id := range current {
if ch, ok := cache[id]; ok {
if ch, ok := cache[id]; ok && ch.SnapshotId != "" {
if ch.visited {
continue
}

View file

@ -25,9 +25,13 @@ func (t *TreeStatsCollector) Register(tree *syncTree) {
func (t *TreeStatsCollector) Collect() []TreeStats {
t.mutex.Lock()
defer t.mutex.Unlock()
stats := make([]TreeStats, 0, len(t.trees))
trees := make([]*syncTree, 0, len(t.trees))
for _, tree := range t.trees {
trees = append(trees, tree)
}
t.mutex.Unlock()
stats := make([]TreeStats, 0, len(t.trees))
for _, tree := range trees {
tree.Lock()
stats = append(stats, TreeStats{
TreeLen: tree.Len(),

View file

@ -37,6 +37,7 @@ type SpaceDescription struct {
AclPayload []byte
SpaceSettingsId string
SpaceSettingsPayload []byte
AclRecords []*spacesyncproto.AclRecord
}
func NewSpaceId(id string, repKey uint64) string {
@ -95,6 +96,19 @@ func (s *space) Description(ctx context.Context) (desc SpaceDescription, err err
if err != nil {
return
}
s.aclList.RLock()
defer s.aclList.RUnlock()
recs, err := s.aclList.RecordsAfter(ctx, "")
if err != nil {
return
}
aclRecs := make([]*spacesyncproto.AclRecord, 0, len(recs))
for _, rec := range recs {
aclRecs = append(aclRecs, &spacesyncproto.AclRecord{
Id: rec.Id,
AclPayload: rec.Payload,
})
}
root := s.aclList.Root()
settingsStorage, err := s.storage.TreeStorage(ctx, state.SettingsId)
if err != nil {
@ -114,6 +128,7 @@ func (s *space) Description(ctx context.Context) (desc SpaceDescription, err err
AclPayload: root.Payload,
SpaceSettingsId: settingsRoot.Id,
SpaceSettingsPayload: settingsRoot.RawChange,
AclRecords: aclRecs,
}
return
}

View file

@ -12,6 +12,7 @@ import (
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/spacestorage"
@ -96,7 +97,7 @@ func StoragePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload sp
// building acl root
keyStorage := crypto.NewKeyStorage()
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, recordverifier.NewValidateFull())
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
PrivKey: payload.SigningKey,
MasterKey: payload.MasterKey,
@ -187,7 +188,7 @@ func StoragePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload sp
// building acl root
keyStorage := crypto.NewKeyStorage()
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, recordverifier.NewValidateFull())
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
PrivKey: payload.SigningKey,
MasterKey: payload.MasterKey,

View file

@ -0,0 +1,296 @@
package commonspace
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/config"
"github.com/anyproto/any-sync/commonspace/credentialprovider"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/spacepayloads"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/rpc/rpctest"
"github.com/anyproto/any-sync/net/streampool"
"github.com/anyproto/any-sync/nodeconf/testconf"
"github.com/anyproto/any-sync/testutil/accounttest"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anyproto/any-sync/util/syncqueues"
)
func TestSpaceService_SpacePull(t *testing.T) {
var makeClientServer = func(t *testing.T) (fxC, fxS *spacePullFixture, peerId string) {
fxC = newSpacePullFixture(t)
fxS = newSpacePullFixture(t)
peerId = "peer"
mcS, mcC := rpctest.MultiConnPair(peerId, peerId+"client")
pS, err := peer.NewPeer(mcS, fxC.ts)
require.NoError(t, err)
fxC.tp.AddPeer(ctx, pS)
_, err = peer.NewPeer(mcC, fxS.ts)
fxC.managerProvider.peer = pS
require.NoError(t, err)
return
}
t.Run("successful space pull", func(t *testing.T) {
fxC, fxS, _ := makeClientServer(t)
defer fxC.Finish(t)
defer fxS.Finish(t)
spaceId, payload := fxS.createTestSpace(t)
space, err := fxC.spaceService.NewSpace(ctx, spaceId, Deps{
SyncStatus: syncstatus.NewNoOpSyncStatus(),
TreeSyncer: &mockTreeSyncer{},
AccountService: fxC.account,
})
require.NoError(t, err)
require.NoError(t, space.Init(ctx))
require.NotNil(t, space)
require.Equal(t, spaceId, space.Id())
storage := space.Storage()
state, err := storage.StateStorage().GetState(ctx)
require.NoError(t, err)
require.Equal(t, spaceId, state.SpaceId)
require.Equal(t, payload.SpaceHeaderWithId.Id, state.SpaceId)
})
t.Run("space pull with acl records", func(t *testing.T) {
fxC, fxS, _ := makeClientServer(t)
defer fxC.Finish(t)
defer fxS.Finish(t)
spaceId, recLen, payload := fxS.createTestSpaceWithAclRecords(t)
space, err := fxC.spaceService.NewSpace(ctx, spaceId, Deps{
SyncStatus: syncstatus.NewNoOpSyncStatus(),
TreeSyncer: &mockTreeSyncer{},
AccountService: fxC.account,
recordVerifier: recordverifier.NewValidateFull(),
})
require.NoError(t, err)
require.NotNil(t, space)
require.NoError(t, space.Init(ctx))
records, err := space.Acl().RecordsAfter(ctx, "")
require.NoError(t, err)
require.Equal(t, recLen, len(records))
storage := space.Storage()
state, err := storage.StateStorage().GetState(ctx)
require.NoError(t, err)
require.Equal(t, spaceId, state.SpaceId)
require.Equal(t, payload.SpaceHeaderWithId.Id, state.SpaceId)
})
}
type spacePullFixture struct {
*spaceService
app *app.App
ctrl *gomock.Controller
ts *rpctest.TestServer
tp *rpctest.TestPool
tmpDir string
account *accounttest.AccountTestService
configService *testconf.StubConf
storage spacestorage.SpaceStorageProvider
managerProvider *mockPeerManagerProvider
}
func newSpacePullFixture(t *testing.T) (fx *spacePullFixture) {
ts := rpctest.NewTestServer()
fx = &spacePullFixture{
spaceService: New().(*spaceService),
ctrl: gomock.NewController(t),
app: new(app.App),
ts: ts,
tp: rpctest.NewTestPool(),
tmpDir: t.TempDir(),
account: &accounttest.AccountTestService{},
configService: &testconf.StubConf{},
storage: &spaceStorageProvider{rootPath: t.TempDir()},
managerProvider: &mockPeerManagerProvider{},
}
configGetter := &mockSpaceConfigGetter{}
fx.app.Register(configGetter).
Register(mockCoordinatorClient{}).
Register(mockNodeClient{}).
Register(credentialprovider.NewNoOp()).
Register(streampool.New()).
Register(newStreamOpener("spaceId")).
Register(fx.account).
Register(fx.storage).
Register(fx.managerProvider).
Register(syncqueues.New()).
Register(&mockTreeManager{}).
Register(fx.spaceService).
Register(fx.tp).
Register(fx.ts).
Register(fx.configService)
require.NoError(t, spacesyncproto.DRPCRegisterSpaceSync(ts, &testSpaceSyncServer{spaceService: fx.spaceService}))
require.NoError(t, fx.app.Start(ctx))
return fx
}
func (fx *spacePullFixture) Finish(t *testing.T) {
fx.ctrl.Finish()
}
func (fx *spacePullFixture) createTestSpace(t *testing.T) (string, spacestorage.SpaceStorageCreatePayload) {
keys := fx.account.Account()
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
require.NoError(t, err)
metaKey, _, err := crypto.GenerateRandomEd25519KeyPair()
require.NoError(t, err)
readKey := crypto.NewAES()
metadata := []byte("metadata")
payload := spacepayloads.SpaceCreatePayload{
SigningKey: keys.SignKey,
SpaceType: "test",
ReplicationKey: 1,
SpacePayload: nil,
MasterKey: masterKey,
ReadKey: readKey,
MetadataKey: metaKey,
Metadata: metadata,
}
createPayload, err := spacepayloads.StoragePayloadForSpaceCreate(payload)
require.NoError(t, err)
storage, err := fx.storage.CreateSpaceStorage(ctx, createPayload)
require.NoError(t, err)
require.NoError(t, storage.Close(ctx))
return createPayload.SpaceHeaderWithId.Id, createPayload
}
func (fx *spacePullFixture) createTestSpaceWithAclRecords(t *testing.T) (string, int, spacestorage.SpaceStorageCreatePayload) {
spaceId, payload := fx.createTestSpace(t)
keys := fx.account.Account()
executor := list.NewExternalKeysAclExecutor(spaceId, keys, []byte("metadata"), payload.AclWithId)
cmds := []string{
"0.init::0",
"0.invite::invId1",
"0.invite::invId2",
}
for _, cmd := range cmds {
err := executor.Execute(cmd)
require.NoError(t, err)
}
allRecords, err := executor.ActualAccounts()["0"].Acl.RecordsAfter(ctx, "")
require.NoError(t, err)
storage, err := fx.storage.WaitSpaceStorage(ctx, spaceId)
require.NoError(t, err)
defer storage.Close(ctx)
aclStorage, err := storage.AclStorage()
require.NoError(t, err)
for i, rec := range allRecords[1:] { // Skip first record as it's already there
err := aclStorage.AddAll(ctx, []list.StorageRecord{
{
RawRecord: rec.Payload,
Id: rec.Id,
PrevId: allRecords[i].Id,
Order: i + 2,
ChangeSize: len(rec.Payload),
},
})
require.NoError(t, err)
}
return spaceId, len(allRecords), payload
}
type testSpaceSyncServer struct {
spacesyncproto.DRPCSpaceSyncUnimplementedServer
spaceService SpaceService
}
func (t *testSpaceSyncServer) SpacePull(ctx context.Context, req *spacesyncproto.SpacePullRequest) (*spacesyncproto.SpacePullResponse, error) {
sp, err := t.spaceService.NewSpace(ctx, req.Id, Deps{
SyncStatus: syncstatus.NewNoOpSyncStatus(),
TreeSyncer: mockTreeSyncer{},
recordVerifier: recordverifier.NewValidateFull(),
})
if err != nil {
return nil, err
}
err = sp.Init(ctx)
if err != nil {
return nil, err
}
spaceDesc, err := sp.Description(ctx)
if err != nil {
return nil, err
}
return &spacesyncproto.SpacePullResponse{
Payload: &spacesyncproto.SpacePayload{
SpaceHeader: spaceDesc.SpaceHeader,
AclPayloadId: spaceDesc.AclId,
AclPayload: spaceDesc.AclPayload,
SpaceSettingsPayload: spaceDesc.SpaceSettingsPayload,
SpaceSettingsPayloadId: spaceDesc.SpaceSettingsId,
},
AclRecords: spaceDesc.AclRecords,
}, nil
}
type mockSpaceConfigGetter struct{}
func (m *mockSpaceConfigGetter) GetStreamConfig() streampool.StreamConfig {
return streampool.StreamConfig{}
}
func (m *mockSpaceConfigGetter) Init(a *app.App) error { return nil }
func (m *mockSpaceConfigGetter) Name() string { return "config" }
func (m *mockSpaceConfigGetter) GetSpace() config.Config {
return config.Config{
GCTTL: 60,
SyncPeriod: 5,
KeepTreeDataInMemory: true,
}
}
type mockTreeManager struct{}
func (m *mockTreeManager) Init(a *app.App) error { return nil }
func (m *mockTreeManager) Name() string { return treemanager.CName }
func (m *mockTreeManager) Run(ctx context.Context) error { return nil }
func (m *mockTreeManager) Close(ctx context.Context) error { return nil }
func (m *mockTreeManager) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
return nil, nil
}
func (m *mockTreeManager) CreateTree(ctx context.Context, spaceId string) (objecttree.ObjectTree, error) {
return nil, nil
}
func (m *mockTreeManager) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload) (objecttree.ObjectTree, error) {
return nil, nil
}
func (m *mockTreeManager) DeleteTree(ctx context.Context, spaceId, treeId string) error { return nil }
func (m *mockTreeManager) MarkTreeDeleted(ctx context.Context, spaceId, treeId string) error {
return nil
}
func (m *mockTreeManager) ValidateAndPutTree(ctx context.Context, spaceId string, payload treestorage.TreeStorageCreatePayload) error {
return nil
}

View file

@ -10,6 +10,7 @@ import (
"github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
@ -127,8 +128,9 @@ func (r *RpcServer) getSpace(ctx context.Context, spaceId string) (sp Space, err
sp, ok := r.spaces[spaceId]
if !ok {
sp, err = r.spaceService.NewSpace(ctx, spaceId, Deps{
TreeSyncer: NewTreeSyncer(spaceId),
SyncStatus: syncstatus.NewNoOpSyncStatus(),
TreeSyncer: NewTreeSyncer(spaceId),
SyncStatus: syncstatus.NewNoOpSyncStatus(),
recordVerifier: recordverifier.NewValidateFull(),
})
if err != nil {
return nil, err

View file

@ -13,6 +13,8 @@ import (
"github.com/anyproto/any-sync/commonspace/acl/aclclient"
"github.com/anyproto/any-sync/commonspace/deletionmanager"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/object/keyvalue"
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage"
"github.com/anyproto/any-sync/commonspace/object/treesyncer"
@ -72,6 +74,7 @@ type Deps struct {
SyncStatus syncstatus.StatusUpdater
TreeSyncer treesyncer.TreeSyncer
AccountService accountservice.Service
recordVerifier recordverifier.RecordVerifier
Indexer keyvaluestorage.Indexer
}
@ -159,7 +162,7 @@ func (s *spaceService) NewSpace(ctx context.Context, id string, deps Deps) (Spac
return nil, err
}
} else {
st, err = s.getSpaceStorageFromRemote(ctx, id)
st, err = s.getSpaceStorageFromRemote(ctx, id, deps)
if err != nil {
return nil, err
}
@ -188,8 +191,13 @@ func (s *spaceService) NewSpace(ctx context.Context, id string, deps Deps) (Spac
if deps.Indexer != nil {
keyValueIndexer = deps.Indexer
}
recordVerifier := recordverifier.New()
if deps.recordVerifier != nil {
recordVerifier = deps.recordVerifier
}
spaceApp.Register(state).
Register(deps.SyncStatus).
Register(recordVerifier).
Register(peerManager).
Register(st).
Register(keyValueIndexer).
@ -236,7 +244,7 @@ func (s *spaceService) addSpaceStorage(ctx context.Context, spaceDescription Spa
return
}
func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string) (st spacestorage.SpaceStorage, err error) {
func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string, deps Deps) (st spacestorage.SpaceStorage, err error) {
// we can't connect to client if it is a node
if s.configurationService.IsResponsible(id) {
err = spacesyncproto.ErrSpaceMissing
@ -275,7 +283,7 @@ func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string)
}
}
for i, p := range peers {
if st, err = s.spacePullWithPeer(ctx, p, id); err != nil {
if st, err = s.spacePullWithPeer(ctx, p, id, deps); err != nil {
if i+1 == len(peers) {
return
} else {
@ -288,7 +296,7 @@ func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string)
return nil, net.ErrUnableToConnect
}
func (s *spaceService) spacePullWithPeer(ctx context.Context, p peer.Peer, id string) (st spacestorage.SpaceStorage, err error) {
func (s *spaceService) spacePullWithPeer(ctx context.Context, p peer.Peer, id string, deps Deps) (st spacestorage.SpaceStorage, err error) {
var res *spacesyncproto.SpacePullResponse
err = p.DoDrpc(ctx, func(conn drpc.Conn) error {
cl := spacesyncproto.NewDRPCSpaceSyncClient(conn)
@ -300,7 +308,7 @@ func (s *spaceService) spacePullWithPeer(ctx context.Context, p peer.Peer, id st
return
}
return s.createSpaceStorage(ctx, spacestorage.SpaceStorageCreatePayload{
st, err = s.createSpaceStorage(ctx, spacestorage.SpaceStorageCreatePayload{
AclWithId: &consensusproto.RawRecordWithId{
Payload: res.Payload.AclPayload,
Id: res.Payload.AclPayloadId,
@ -311,6 +319,35 @@ func (s *spaceService) spacePullWithPeer(ctx context.Context, p peer.Peer, id st
},
SpaceHeaderWithId: res.Payload.SpaceHeader,
})
if err != nil {
return
}
if res.AclRecords != nil {
aclSt, err := st.AclStorage()
if err != nil {
return nil, err
}
recordVerifier := recordverifier.New()
if deps.recordVerifier != nil {
recordVerifier = deps.recordVerifier
}
acl, err := list.BuildAclListWithIdentity(s.account.Account(), aclSt, recordVerifier)
if err != nil {
return nil, err
}
consRecs := make([]*consensusproto.RawRecordWithId, 0, len(res.AclRecords))
for _, rec := range res.AclRecords {
consRecs = append(consRecs, &consensusproto.RawRecordWithId{
Id: rec.Id,
Payload: rec.AclPayload,
})
}
err = acl.AddRawRecords(consRecs)
if err != nil {
return nil, err
}
}
return st, nil
}
func (s *spaceService) createSpaceStorage(ctx context.Context, payload spacestorage.SpaceStorageCreatePayload) (spacestorage.SpaceStorage, error) {

View file

@ -10,6 +10,7 @@ import (
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
"github.com/anyproto/any-sync/commonspace/spacestorage/oldstorage"
"github.com/anyproto/any-sync/consensus/consensusproto"
)
@ -31,7 +32,7 @@ func migrateAclList(ctx context.Context, oldStorage oldstorage.ListStorage, head
if err != nil {
return nil, fmt.Errorf("migration: failed to generate keys: %w", err)
}
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, &list.NoOpAcceptorVerifier{})
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, recordverifier.New())
if err != nil {
return nil, fmt.Errorf("migration: failed to build acl list: %w", err)
}

View file

@ -103,6 +103,12 @@ message SpacePullRequest {
// SpacePullResponse is a response with header and acl root
message SpacePullResponse {
SpacePayload payload = 1;
repeated AclRecord aclRecords = 2;
}
message AclRecord {
bytes aclPayload = 1;
string id = 2;
}
// SpacePayload is a payload for pushing a space

View file

@ -749,7 +749,8 @@ func (m *SpacePullRequest) GetId() string {
// SpacePullResponse is a response with header and acl root
type SpacePullResponse struct {
Payload *SpacePayload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
Payload *SpacePayload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
AclRecords []*AclRecord `protobuf:"bytes,2,rep,name=aclRecords,proto3" json:"aclRecords,omitempty"`
}
func (m *SpacePullResponse) Reset() { *m = SpacePullResponse{} }
@ -800,6 +801,73 @@ func (m *SpacePullResponse) GetPayload() *SpacePayload {
return nil
}
func (m *SpacePullResponse) GetAclRecords() []*AclRecord {
if m != nil {
return m.AclRecords
}
return nil
}
type AclRecord struct {
AclPayload []byte `protobuf:"bytes,1,opt,name=aclPayload,proto3" json:"aclPayload,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
}
func (m *AclRecord) Reset() { *m = AclRecord{} }
func (m *AclRecord) String() string { return proto.CompactTextString(m) }
func (*AclRecord) ProtoMessage() {}
func (*AclRecord) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{10}
}
func (m *AclRecord) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *AclRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_AclRecord.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *AclRecord) XXX_MarshalAppend(b []byte, newLen int) ([]byte, error) {
b = b[:newLen]
_, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b, nil
}
func (m *AclRecord) XXX_Merge(src proto.Message) {
xxx_messageInfo_AclRecord.Merge(m, src)
}
func (m *AclRecord) XXX_Size() int {
return m.Size()
}
func (m *AclRecord) XXX_DiscardUnknown() {
xxx_messageInfo_AclRecord.DiscardUnknown(m)
}
var xxx_messageInfo_AclRecord proto.InternalMessageInfo
func (m *AclRecord) GetAclPayload() []byte {
if m != nil {
return m.AclPayload
}
return nil
}
func (m *AclRecord) GetId() string {
if m != nil {
return m.Id
}
return ""
}
// SpacePayload is a payload for pushing a space
type SpacePayload struct {
SpaceHeader *RawSpaceHeaderWithId `protobuf:"bytes,1,opt,name=spaceHeader,proto3" json:"spaceHeader,omitempty"`
@ -813,7 +881,7 @@ func (m *SpacePayload) Reset() { *m = SpacePayload{} }
func (m *SpacePayload) String() string { return proto.CompactTextString(m) }
func (*SpacePayload) ProtoMessage() {}
func (*SpacePayload) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{10}
return fileDescriptor_80e49f1f4ac27799, []int{11}
}
func (m *SpacePayload) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -899,7 +967,7 @@ func (m *SpaceHeader) Reset() { *m = SpaceHeader{} }
func (m *SpaceHeader) String() string { return proto.CompactTextString(m) }
func (*SpaceHeader) ProtoMessage() {}
func (*SpaceHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{11}
return fileDescriptor_80e49f1f4ac27799, []int{12}
}
func (m *SpaceHeader) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -988,7 +1056,7 @@ func (m *RawSpaceHeader) Reset() { *m = RawSpaceHeader{} }
func (m *RawSpaceHeader) String() string { return proto.CompactTextString(m) }
func (*RawSpaceHeader) ProtoMessage() {}
func (*RawSpaceHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{12}
return fileDescriptor_80e49f1f4ac27799, []int{13}
}
func (m *RawSpaceHeader) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1049,7 +1117,7 @@ func (m *RawSpaceHeaderWithId) Reset() { *m = RawSpaceHeaderWithId{} }
func (m *RawSpaceHeaderWithId) String() string { return proto.CompactTextString(m) }
func (*RawSpaceHeaderWithId) ProtoMessage() {}
func (*RawSpaceHeaderWithId) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{13}
return fileDescriptor_80e49f1f4ac27799, []int{14}
}
func (m *RawSpaceHeaderWithId) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1113,7 +1181,7 @@ func (m *SpaceSettingsContent) Reset() { *m = SpaceSettingsContent{} }
func (m *SpaceSettingsContent) String() string { return proto.CompactTextString(m) }
func (*SpaceSettingsContent) ProtoMessage() {}
func (*SpaceSettingsContent) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{14}
return fileDescriptor_80e49f1f4ac27799, []int{15}
}
func (m *SpaceSettingsContent) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1204,7 +1272,7 @@ func (m *ObjectDelete) Reset() { *m = ObjectDelete{} }
func (m *ObjectDelete) String() string { return proto.CompactTextString(m) }
func (*ObjectDelete) ProtoMessage() {}
func (*ObjectDelete) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{15}
return fileDescriptor_80e49f1f4ac27799, []int{16}
}
func (m *ObjectDelete) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1258,7 +1326,7 @@ func (m *StoreHeader) Reset() { *m = StoreHeader{} }
func (m *StoreHeader) String() string { return proto.CompactTextString(m) }
func (*StoreHeader) ProtoMessage() {}
func (*StoreHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{16}
return fileDescriptor_80e49f1f4ac27799, []int{17}
}
func (m *StoreHeader) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1318,7 +1386,7 @@ func (m *SpaceDelete) Reset() { *m = SpaceDelete{} }
func (m *SpaceDelete) String() string { return proto.CompactTextString(m) }
func (*SpaceDelete) ProtoMessage() {}
func (*SpaceDelete) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{17}
return fileDescriptor_80e49f1f4ac27799, []int{18}
}
func (m *SpaceDelete) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1372,7 +1440,7 @@ func (m *SpaceSettingsSnapshot) Reset() { *m = SpaceSettingsSnapshot{} }
func (m *SpaceSettingsSnapshot) String() string { return proto.CompactTextString(m) }
func (*SpaceSettingsSnapshot) ProtoMessage() {}
func (*SpaceSettingsSnapshot) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{18}
return fileDescriptor_80e49f1f4ac27799, []int{19}
}
func (m *SpaceSettingsSnapshot) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1433,7 +1501,7 @@ func (m *SettingsData) Reset() { *m = SettingsData{} }
func (m *SettingsData) String() string { return proto.CompactTextString(m) }
func (*SettingsData) ProtoMessage() {}
func (*SettingsData) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{19}
return fileDescriptor_80e49f1f4ac27799, []int{20}
}
func (m *SettingsData) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1493,7 +1561,7 @@ func (m *SpaceSubscription) Reset() { *m = SpaceSubscription{} }
func (m *SpaceSubscription) String() string { return proto.CompactTextString(m) }
func (*SpaceSubscription) ProtoMessage() {}
func (*SpaceSubscription) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{20}
return fileDescriptor_80e49f1f4ac27799, []int{21}
}
func (m *SpaceSubscription) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1554,7 +1622,7 @@ func (m *AclAddRecordRequest) Reset() { *m = AclAddRecordRequest{} }
func (m *AclAddRecordRequest) String() string { return proto.CompactTextString(m) }
func (*AclAddRecordRequest) ProtoMessage() {}
func (*AclAddRecordRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{21}
return fileDescriptor_80e49f1f4ac27799, []int{22}
}
func (m *AclAddRecordRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1615,7 +1683,7 @@ func (m *AclAddRecordResponse) Reset() { *m = AclAddRecordResponse{} }
func (m *AclAddRecordResponse) String() string { return proto.CompactTextString(m) }
func (*AclAddRecordResponse) ProtoMessage() {}
func (*AclAddRecordResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{22}
return fileDescriptor_80e49f1f4ac27799, []int{23}
}
func (m *AclAddRecordResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1677,7 +1745,7 @@ func (m *AclGetRecordsRequest) Reset() { *m = AclGetRecordsRequest{} }
func (m *AclGetRecordsRequest) String() string { return proto.CompactTextString(m) }
func (*AclGetRecordsRequest) ProtoMessage() {}
func (*AclGetRecordsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{23}
return fileDescriptor_80e49f1f4ac27799, []int{24}
}
func (m *AclGetRecordsRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1737,7 +1805,7 @@ func (m *AclGetRecordsResponse) Reset() { *m = AclGetRecordsResponse{} }
func (m *AclGetRecordsResponse) String() string { return proto.CompactTextString(m) }
func (*AclGetRecordsResponse) ProtoMessage() {}
func (*AclGetRecordsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{24}
return fileDescriptor_80e49f1f4ac27799, []int{25}
}
func (m *AclGetRecordsResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1790,7 +1858,7 @@ func (m *StoreDiffRequest) Reset() { *m = StoreDiffRequest{} }
func (m *StoreDiffRequest) String() string { return proto.CompactTextString(m) }
func (*StoreDiffRequest) ProtoMessage() {}
func (*StoreDiffRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{25}
return fileDescriptor_80e49f1f4ac27799, []int{26}
}
func (m *StoreDiffRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1849,7 +1917,7 @@ func (m *StoreDiffResponse) Reset() { *m = StoreDiffResponse{} }
func (m *StoreDiffResponse) String() string { return proto.CompactTextString(m) }
func (*StoreDiffResponse) ProtoMessage() {}
func (*StoreDiffResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{26}
return fileDescriptor_80e49f1f4ac27799, []int{27}
}
func (m *StoreDiffResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1905,7 +1973,7 @@ func (m *StoreKeyValue) Reset() { *m = StoreKeyValue{} }
func (m *StoreKeyValue) String() string { return proto.CompactTextString(m) }
func (*StoreKeyValue) ProtoMessage() {}
func (*StoreKeyValue) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{27}
return fileDescriptor_80e49f1f4ac27799, []int{28}
}
func (m *StoreKeyValue) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1985,7 +2053,7 @@ func (m *StoreKeyValues) Reset() { *m = StoreKeyValues{} }
func (m *StoreKeyValues) String() string { return proto.CompactTextString(m) }
func (*StoreKeyValues) ProtoMessage() {}
func (*StoreKeyValues) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{28}
return fileDescriptor_80e49f1f4ac27799, []int{29}
}
func (m *StoreKeyValues) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -2042,7 +2110,7 @@ func (m *StoreKeyInner) Reset() { *m = StoreKeyInner{} }
func (m *StoreKeyInner) String() string { return proto.CompactTextString(m) }
func (*StoreKeyInner) ProtoMessage() {}
func (*StoreKeyInner) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{29}
return fileDescriptor_80e49f1f4ac27799, []int{30}
}
func (m *StoreKeyInner) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -2130,7 +2198,7 @@ func (m *StorageHeader) Reset() { *m = StorageHeader{} }
func (m *StorageHeader) String() string { return proto.CompactTextString(m) }
func (*StorageHeader) ProtoMessage() {}
func (*StorageHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_80e49f1f4ac27799, []int{30}
return fileDescriptor_80e49f1f4ac27799, []int{31}
}
func (m *StorageHeader) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -2196,6 +2264,7 @@ func init() {
proto.RegisterType((*SpacePushResponse)(nil), "spacesync.SpacePushResponse")
proto.RegisterType((*SpacePullRequest)(nil), "spacesync.SpacePullRequest")
proto.RegisterType((*SpacePullResponse)(nil), "spacesync.SpacePullResponse")
proto.RegisterType((*AclRecord)(nil), "spacesync.AclRecord")
proto.RegisterType((*SpacePayload)(nil), "spacesync.SpacePayload")
proto.RegisterType((*SpaceHeader)(nil), "spacesync.SpaceHeader")
proto.RegisterType((*RawSpaceHeader)(nil), "spacesync.RawSpaceHeader")
@ -2224,106 +2293,108 @@ func init() {
}
var fileDescriptor_80e49f1f4ac27799 = []byte{
// 1572 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x18, 0x4d, 0x6f, 0x1b, 0x45,
0x3b, 0xbb, 0x4e, 0x1c, 0xfb, 0x89, 0xe3, 0x6e, 0x26, 0x49, 0xe3, 0xd7, 0xad, 0x5c, 0x6b, 0xf4,
0xaa, 0x6f, 0x14, 0xbd, 0xb4, 0x4d, 0x0a, 0x95, 0x5a, 0xe0, 0x90, 0x26, 0x69, 0x63, 0x4a, 0x9a,
0x68, 0xdc, 0x0f, 0x09, 0x09, 0xa4, 0xcd, 0xee, 0x24, 0x59, 0xba, 0xde, 0x35, 0x3b, 0xe3, 0x36,
0x3e, 0x72, 0xe2, 0x04, 0xe2, 0xcc, 0x7f, 0xe0, 0xc0, 0xbf, 0xe0, 0x58, 0x38, 0x71, 0x44, 0xed,
0x9d, 0xdf, 0x80, 0x66, 0x76, 0x76, 0x76, 0xd6, 0x1f, 0xa5, 0xa8, 0x70, 0x89, 0xe7, 0x79, 0xe6,
0xf9, 0xfe, 0x9c, 0x0d, 0x6c, 0x7a, 0x71, 0xaf, 0x17, 0x47, 0xac, 0xef, 0x7a, 0xf4, 0xba, 0xfc,
0xcb, 0x86, 0x91, 0xd7, 0x4f, 0x62, 0x1e, 0x5f, 0x97, 0x7f, 0x59, 0x8e, 0xbd, 0x26, 0x11, 0xa8,
0xaa, 0x11, 0x98, 0xc2, 0xe2, 0x3e, 0x75, 0xfd, 0xee, 0x30, 0xf2, 0x88, 0x1b, 0x9d, 0x52, 0x84,
0x60, 0xf6, 0x24, 0x89, 0x7b, 0x0d, 0xab, 0x6d, 0xad, 0xcf, 0x12, 0x79, 0x46, 0x75, 0xb0, 0x79,
0xdc, 0xb0, 0x25, 0xc6, 0xe6, 0x31, 0x5a, 0x81, 0xb9, 0x30, 0xe8, 0x05, 0xbc, 0x51, 0x6a, 0x5b,
0xeb, 0x8b, 0x24, 0x05, 0x50, 0x13, 0x2a, 0x34, 0xa4, 0x3d, 0x1a, 0x71, 0xd6, 0x98, 0x6d, 0x5b,
0xeb, 0x15, 0xa2, 0x61, 0x7c, 0x0e, 0x75, 0xad, 0x86, 0xb2, 0x41, 0xc8, 0x85, 0x9e, 0x33, 0x97,
0x9d, 0x49, 0x3d, 0x35, 0x22, 0xcf, 0xe8, 0x23, 0x43, 0x82, 0xdd, 0x2e, 0xad, 0x2f, 0x6c, 0xb5,
0xaf, 0xe5, 0xb6, 0x17, 0x05, 0xec, 0xa5, 0x84, 0xb9, 0x0e, 0x61, 0x95, 0x17, 0x0f, 0x22, 0x6d,
0x95, 0x04, 0xf0, 0x87, 0xb0, 0x3a, 0x91, 0x51, 0x38, 0x15, 0xf8, 0x52, 0x7d, 0x95, 0xd8, 0x81,
0x2f, 0x0d, 0xa2, 0xae, 0x2f, 0xdd, 0xac, 0x12, 0x79, 0xc6, 0xdf, 0x59, 0x70, 0x21, 0xe7, 0xfe,
0x6a, 0x40, 0x19, 0x47, 0x0d, 0x98, 0x97, 0x36, 0x75, 0x32, 0xe6, 0x0c, 0x44, 0x37, 0xa0, 0x9c,
0x88, 0x18, 0x66, 0xc6, 0x37, 0x26, 0x19, 0x2f, 0x08, 0x88, 0xa2, 0x43, 0xd7, 0xa1, 0xe2, 0x07,
0x27, 0x27, 0x8f, 0x86, 0x7d, 0x2a, 0xad, 0xae, 0x6f, 0x2d, 0x1b, 0x3c, 0xbb, 0xea, 0x8a, 0x68,
0x22, 0x7c, 0x0e, 0x8e, 0xe1, 0x4d, 0x3f, 0x8e, 0x18, 0x45, 0x37, 0x61, 0x3e, 0x91, 0x9e, 0xb1,
0x86, 0x25, 0xf5, 0xfe, 0x67, 0x6a, 0xd0, 0x48, 0x46, 0x59, 0xd0, 0x6c, 0xbf, 0x8d, 0xe6, 0x5f,
0x2d, 0x58, 0x3a, 0x3c, 0xfe, 0x92, 0x7a, 0x5c, 0x88, 0x3b, 0xa0, 0x8c, 0xb9, 0xa7, 0xf4, 0x0d,
0xc1, 0xb8, 0x0c, 0xd5, 0x24, 0x8d, 0x58, 0x27, 0x8b, 0x69, 0x8e, 0x10, 0x7c, 0x09, 0xed, 0x87,
0xc3, 0x8e, 0x2f, 0xfd, 0xae, 0x92, 0x0c, 0x14, 0x37, 0x7d, 0x77, 0x18, 0xc6, 0xae, 0x2f, 0x8b,
0xa8, 0x46, 0x32, 0x50, 0xd4, 0x57, 0x2c, 0x0d, 0xe8, 0xf8, 0x8d, 0x39, 0xc9, 0xa4, 0x61, 0xf4,
0x01, 0x40, 0x7a, 0x96, 0x0e, 0x95, 0xa5, 0x43, 0xab, 0x86, 0x43, 0x87, 0xfa, 0x92, 0x18, 0x84,
0x98, 0x82, 0xd3, 0x15, 0x34, 0x47, 0x03, 0x76, 0x96, 0xe5, 0x77, 0x33, 0x37, 0x40, 0xb8, 0xb4,
0xb0, 0xb5, 0x66, 0xc8, 0x49, 0xa9, 0xd3, 0xeb, 0xdc, 0xb2, 0x16, 0xc0, 0x4e, 0x42, 0x7d, 0x1a,
0xf1, 0xc0, 0x0d, 0xa5, 0xb3, 0x35, 0x62, 0x60, 0xf0, 0x32, 0x2c, 0x19, 0x6a, 0xd2, 0xb4, 0x61,
0xac, 0x75, 0x87, 0x61, 0xa6, 0x7b, 0xa4, 0x26, 0xf1, 0x3d, 0xcd, 0x28, 0x68, 0x54, 0xbe, 0xff,
0xbe, 0x81, 0xf8, 0x6b, 0x1b, 0x6a, 0xe6, 0x0d, 0xda, 0x86, 0x05, 0xc9, 0x23, 0xca, 0x83, 0x26,
0x4a, 0xce, 0x15, 0x43, 0x0e, 0x71, 0x5f, 0x74, 0x73, 0x82, 0xa7, 0x01, 0x3f, 0xeb, 0xf8, 0xc4,
0xe4, 0x11, 0x4e, 0xbb, 0x5e, 0xa8, 0x04, 0x66, 0x4e, 0xe7, 0x18, 0x84, 0xa1, 0x96, 0x43, 0x3a,
0xcf, 0x05, 0x1c, 0xda, 0x82, 0x15, 0x29, 0xb2, 0x4b, 0x39, 0x0f, 0xa2, 0x53, 0x76, 0x54, 0xc8,
0xfc, 0xc4, 0x3b, 0x74, 0x0b, 0x2e, 0x4e, 0xc2, 0xeb, 0xa2, 0x98, 0x72, 0x8b, 0x7f, 0xb1, 0x60,
0xc1, 0x70, 0x49, 0x94, 0x53, 0x20, 0x13, 0xc4, 0x87, 0x6a, 0x08, 0x69, 0x58, 0x14, 0x2f, 0x0f,
0x7a, 0x94, 0x71, 0xb7, 0xd7, 0x97, 0xae, 0x95, 0x48, 0x8e, 0x10, 0xb7, 0x52, 0x87, 0x6e, 0xdb,
0x2a, 0xc9, 0x11, 0xe8, 0x2a, 0xd4, 0x45, 0x2d, 0x07, 0x9e, 0xcb, 0x83, 0x38, 0x7a, 0x40, 0x87,
0xd2, 0x9b, 0x59, 0x32, 0x82, 0x15, 0xf3, 0x86, 0x51, 0x9a, 0x5a, 0x5d, 0x23, 0xf2, 0x8c, 0xae,
0x01, 0x32, 0x42, 0x9c, 0x45, 0xa3, 0x2c, 0x29, 0x26, 0xdc, 0xe0, 0x23, 0xa8, 0x17, 0x13, 0x85,
0xda, 0xe3, 0x89, 0xad, 0x15, 0xf3, 0x26, 0xac, 0x0f, 0x4e, 0x23, 0x97, 0x0f, 0x12, 0xaa, 0xd2,
0x96, 0x23, 0xf0, 0x2e, 0xac, 0x4c, 0x4a, 0xbd, 0x6c, 0x67, 0xf7, 0x45, 0x41, 0x6a, 0x8e, 0x50,
0x75, 0x6b, 0xeb, 0xba, 0xfd, 0xc1, 0x82, 0x95, 0xae, 0x99, 0x86, 0x9d, 0x38, 0xe2, 0x62, 0xe8,
0x7e, 0x0c, 0xb5, 0xb4, 0xfd, 0x76, 0x69, 0x48, 0x39, 0x9d, 0x50, 0xc0, 0x87, 0xc6, 0xf5, 0xfe,
0x0c, 0x29, 0x90, 0xa3, 0x3b, 0xca, 0x3b, 0xc5, 0x6d, 0x4b, 0xee, 0x8b, 0xa3, 0xe5, 0xaf, 0x99,
0x4d, 0xe2, 0xbb, 0xf3, 0x30, 0xf7, 0xdc, 0x0d, 0x07, 0x14, 0xb7, 0xa0, 0x66, 0x2a, 0x19, 0x6b,
0xba, 0x0e, 0x2c, 0x74, 0x79, 0x9c, 0x64, 0xf1, 0x9a, 0x3e, 0xe2, 0x44, 0xac, 0x79, 0x9c, 0xb8,
0xa7, 0xf4, 0xa1, 0xdb, 0xa3, 0xca, 0x7d, 0x13, 0x85, 0x6f, 0xaa, 0x92, 0x53, 0x9a, 0xfe, 0x0b,
0x8b, 0xbe, 0x3c, 0x25, 0x47, 0x94, 0x26, 0x5a, 0x60, 0x11, 0x89, 0x3f, 0x87, 0xd5, 0x42, 0xec,
0xba, 0x91, 0xdb, 0x67, 0x67, 0x31, 0x17, 0x1d, 0x97, 0x52, 0xfa, 0x1d, 0x3f, 0x9d, 0xf5, 0x55,
0x62, 0x60, 0xc6, 0xc5, 0xdb, 0x93, 0xc4, 0x7f, 0x63, 0x41, 0x2d, 0x13, 0xbd, 0xeb, 0x72, 0x17,
0xdd, 0x86, 0x79, 0x2f, 0x4d, 0x8f, 0xda, 0x1f, 0x57, 0x46, 0x03, 0x3a, 0x92, 0x45, 0x92, 0xd1,
0x8b, 0x85, 0xcd, 0x94, 0x75, 0x2a, 0x19, 0xed, 0x69, 0xbc, 0x99, 0x17, 0x44, 0x73, 0xe0, 0x67,
0x6a, 0xba, 0x75, 0x07, 0xc7, 0xcc, 0x4b, 0x82, 0xbe, 0xe8, 0x0c, 0xd1, 0x96, 0x2a, 0xbe, 0x99,
0x8b, 0x1a, 0x46, 0x77, 0xa0, 0xec, 0x7a, 0x82, 0x4a, 0xad, 0x2c, 0x3c, 0xa6, 0xcc, 0x90, 0xb4,
0x2d, 0x29, 0x89, 0xe2, 0xc0, 0x1d, 0x58, 0xde, 0xf6, 0xc2, 0x6d, 0xdf, 0x27, 0xd4, 0x8b, 0x13,
0xff, 0xaf, 0xb7, 0xb9, 0xb1, 0x88, 0xec, 0xc2, 0x22, 0xc2, 0x9f, 0xc2, 0x4a, 0x51, 0x94, 0x1a,
0xcc, 0x4d, 0xa8, 0x24, 0x12, 0xa3, 0x85, 0x69, 0xf8, 0x0d, 0xd2, 0x3e, 0x91, 0xd2, 0xee, 0x53,
0x9e, 0x4a, 0x63, 0x6f, 0x65, 0x99, 0xeb, 0x85, 0xfb, 0xf9, 0x63, 0x25, 0x03, 0xf1, 0x26, 0xac,
0x8e, 0xc8, 0x52, 0xa6, 0xc9, 0x7d, 0x2b, 0x51, 0x32, 0xa8, 0x35, 0x92, 0x81, 0xf8, 0x0b, 0x70,
0x64, 0xb5, 0x8b, 0x95, 0xff, 0x2f, 0x3c, 0x71, 0xf0, 0x3e, 0x2c, 0x19, 0xf2, 0xdf, 0xe1, 0xc9,
0x82, 0x7f, 0xb2, 0x60, 0x51, 0x8a, 0x7a, 0x40, 0x87, 0x4f, 0x44, 0x27, 0x8b, 0xa1, 0xf4, 0x8c,
0x0e, 0x0b, 0xbd, 0x94, 0x23, 0xc4, 0x7b, 0x50, 0x36, 0xbc, 0x0a, 0x78, 0x0a, 0xa0, 0xff, 0xc3,
0x52, 0x36, 0xe6, 0xbb, 0x7a, 0x0c, 0x96, 0x24, 0xc5, 0xf8, 0x85, 0x68, 0xa9, 0x3e, 0xa5, 0x49,
0x4e, 0x99, 0x6e, 0xa6, 0x22, 0xd2, 0x8c, 0xd7, 0x5c, 0x21, 0x5e, 0x78, 0x1f, 0xea, 0x05, 0x93,
0x19, 0xba, 0x25, 0x6d, 0x4e, 0x01, 0xe5, 0xbc, 0x19, 0xc4, 0x02, 0x35, 0xc9, 0x49, 0xf1, 0x8f,
0x86, 0xf7, 0x9d, 0x28, 0xa2, 0x89, 0x58, 0x20, 0xc2, 0x8c, 0xec, 0x05, 0x2d, 0xce, 0x85, 0xa5,
0x66, 0x8f, 0x2c, 0x35, 0x1d, 0x8f, 0x92, 0x19, 0x8f, 0xab, 0x50, 0xd7, 0x9b, 0xed, 0x20, 0xf0,
0x92, 0x58, 0xba, 0x58, 0x22, 0x23, 0x58, 0x11, 0x6b, 0x55, 0x65, 0xda, 0xcb, 0x1c, 0x81, 0x1c,
0x28, 0x3d, 0xa3, 0x43, 0xb9, 0xa9, 0xaa, 0x44, 0x1c, 0xf1, 0x83, 0xd4, 0x5c, 0xf7, 0xf4, 0x1f,
0x98, 0xa3, 0x1b, 0x7f, 0x58, 0x50, 0xd9, 0x4b, 0x92, 0x9d, 0xd8, 0xa7, 0x0c, 0xd5, 0x01, 0x1e,
0x47, 0xf4, 0xbc, 0x4f, 0x3d, 0x4e, 0x7d, 0x67, 0x06, 0x39, 0xea, 0x6d, 0x73, 0x10, 0x30, 0x16,
0x44, 0xa7, 0x8e, 0x85, 0x2e, 0xa8, 0xb1, 0xbb, 0x77, 0x1e, 0x30, 0xce, 0x1c, 0x1b, 0x2d, 0xc3,
0x05, 0x89, 0x78, 0x18, 0xf3, 0x4e, 0xb4, 0xe3, 0x7a, 0x67, 0xd4, 0x29, 0x21, 0x04, 0x75, 0x89,
0xec, 0xb0, 0x74, 0x3c, 0xfb, 0xce, 0x2c, 0x6a, 0xc0, 0x8a, 0xac, 0x1e, 0xf6, 0x30, 0xe6, 0xaa,
0x5a, 0x83, 0xe3, 0x90, 0x3a, 0x73, 0x68, 0x05, 0x1c, 0x42, 0x3d, 0x1a, 0xf4, 0x79, 0x87, 0x75,
0xa2, 0xe7, 0x6e, 0x18, 0xf8, 0x4e, 0x59, 0xc8, 0x50, 0x80, 0x5a, 0xc9, 0xce, 0xbc, 0xa0, 0xdc,
0x1d, 0xa4, 0xab, 0x9e, 0xaa, 0x8e, 0x72, 0x2a, 0xe8, 0x12, 0xac, 0x3d, 0x8a, 0xe3, 0x03, 0x37,
0x1a, 0x2a, 0x1c, 0xbb, 0x97, 0xc4, 0x3d, 0xa1, 0xcc, 0xa9, 0x0a, 0x83, 0xf7, 0x92, 0x24, 0x4e,
0x0e, 0x4f, 0x4e, 0x18, 0xe5, 0x8e, 0xbf, 0x71, 0x1b, 0xd6, 0xa6, 0x0c, 0x34, 0xb4, 0x08, 0x55,
0x85, 0x3d, 0xa6, 0xce, 0x8c, 0x60, 0x7d, 0x1c, 0x31, 0x8d, 0xb0, 0x36, 0xfe, 0x07, 0x95, 0xec,
0xf9, 0x8e, 0x16, 0x60, 0xbe, 0x13, 0x05, 0xe2, 0x0d, 0xea, 0xcc, 0xa0, 0x32, 0xd8, 0x4f, 0x36,
0x1d, 0x4b, 0xfe, 0x6e, 0x39, 0xf6, 0xc6, 0x7b, 0x00, 0xf9, 0xb3, 0x18, 0x55, 0x60, 0xf6, 0x51,
0x42, 0x85, 0xc4, 0x79, 0x28, 0x6d, 0x7b, 0xa1, 0x63, 0xa1, 0x1a, 0x54, 0xb2, 0x4a, 0x74, 0xec,
0xad, 0x6f, 0xcb, 0x50, 0x4d, 0x6d, 0x1a, 0x46, 0x1e, 0xda, 0x81, 0x4a, 0xd6, 0xa7, 0xa8, 0x39,
0xb1, 0x79, 0xa5, 0x93, 0xcd, 0x4b, 0x93, 0x1b, 0x3b, 0x1d, 0x03, 0xf7, 0xa0, 0xaa, 0x67, 0x03,
0xba, 0x34, 0xda, 0x05, 0xc6, 0x44, 0x6a, 0x5e, 0x9e, 0x7c, 0xa9, 0xe4, 0xdc, 0x57, 0xad, 0xb1,
0x97, 0x7d, 0x0a, 0x4e, 0xed, 0xa8, 0xe6, 0xd4, 0x9b, 0x75, 0xeb, 0x86, 0x25, 0x0d, 0xca, 0x1e,
0xea, 0x45, 0x83, 0x46, 0xbe, 0x12, 0x8a, 0x06, 0x8d, 0xbe, 0xed, 0x0d, 0x39, 0x61, 0x38, 0x49,
0x8e, 0x7e, 0xf1, 0x4f, 0x92, 0x63, 0x3c, 0xf5, 0x09, 0x38, 0xf9, 0x37, 0x57, 0x97, 0x27, 0xd4,
0xed, 0xa1, 0xcb, 0x63, 0x8f, 0x25, 0xe3, 0x83, 0xac, 0xf9, 0xc6, 0x5b, 0xe9, 0xe3, 0x7e, 0x96,
0x76, 0x99, 0xbb, 0x77, 0x90, 0x86, 0x9e, 0xc2, 0x5a, 0x8e, 0x54, 0x0e, 0xbd, 0xbb, 0x91, 0x37,
0x2c, 0x74, 0x08, 0x35, 0x73, 0xc1, 0xa2, 0x96, 0x41, 0x3f, 0x61, 0x89, 0x37, 0xaf, 0x4c, 0xbd,
0xd7, 0x71, 0x5c, 0x2c, 0xec, 0x45, 0x34, 0xc2, 0x31, 0xb6, 0x7d, 0x9b, 0xed, 0xe9, 0x04, 0xa9,
0xcc, 0xbb, 0xef, 0xff, 0xfc, 0xaa, 0x65, 0xbd, 0x7c, 0xd5, 0xb2, 0x7e, 0x7f, 0xd5, 0xb2, 0xbe,
0x7f, 0xdd, 0x9a, 0x79, 0xf9, 0xba, 0x35, 0xf3, 0xdb, 0xeb, 0xd6, 0xcc, 0x67, 0xcd, 0xe9, 0xff,
0x91, 0x39, 0x2e, 0xcb, 0x9f, 0x9b, 0x7f, 0x06, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x8f, 0x50, 0x36,
0xb6, 0x11, 0x00, 0x00,
// 1609 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0xc5,
0x17, 0xcf, 0xae, 0x13, 0xc7, 0x7e, 0x71, 0xdc, 0xcd, 0xc4, 0x69, 0xfc, 0x75, 0x2b, 0xd7, 0x5a,
0x7d, 0x55, 0xa2, 0x08, 0xda, 0x26, 0x2d, 0x95, 0xda, 0xc2, 0x21, 0x4d, 0xd2, 0xc6, 0x94, 0x34,
0xd1, 0xb8, 0x3f, 0x24, 0x24, 0x90, 0x36, 0xbb, 0x93, 0x64, 0xe9, 0x7a, 0xd7, 0xec, 0x8c, 0xdb,
0x58, 0xe2, 0xc2, 0x89, 0x13, 0x88, 0x33, 0xff, 0x03, 0x07, 0xfe, 0x0b, 0x8e, 0x85, 0x13, 0x47,
0xd4, 0xde, 0xf9, 0x1b, 0xd0, 0xcc, 0xce, 0xce, 0xce, 0xae, 0xed, 0x52, 0x51, 0xb8, 0xc4, 0x3b,
0x6f, 0xde, 0x8f, 0xcf, 0x7b, 0xf3, 0x7e, 0xcc, 0x04, 0x36, 0xdc, 0xa8, 0xdf, 0x8f, 0x42, 0x3a,
0x70, 0x5c, 0x72, 0x55, 0xfc, 0xa5, 0xa3, 0xd0, 0x1d, 0xc4, 0x11, 0x8b, 0xae, 0x8a, 0xbf, 0x34,
0xa3, 0x5e, 0x11, 0x04, 0x54, 0x55, 0x04, 0x9b, 0xc0, 0xe2, 0x1e, 0x71, 0xbc, 0xde, 0x28, 0x74,
0xb1, 0x13, 0x9e, 0x10, 0x84, 0x60, 0xf6, 0x38, 0x8e, 0xfa, 0x4d, 0xa3, 0x63, 0xac, 0xcd, 0x62,
0xf1, 0x8d, 0xea, 0x60, 0xb2, 0xa8, 0x69, 0x0a, 0x8a, 0xc9, 0x22, 0xd4, 0x80, 0xb9, 0xc0, 0xef,
0xfb, 0xac, 0x59, 0xea, 0x18, 0x6b, 0x8b, 0x38, 0x59, 0xa0, 0x16, 0x54, 0x48, 0x40, 0xfa, 0x24,
0x64, 0xb4, 0x39, 0xdb, 0x31, 0xd6, 0x2a, 0x58, 0xad, 0xed, 0x33, 0xa8, 0x2b, 0x33, 0x84, 0x0e,
0x03, 0xc6, 0xed, 0x9c, 0x3a, 0xf4, 0x54, 0xd8, 0xa9, 0x61, 0xf1, 0x8d, 0x3e, 0xd2, 0x34, 0x98,
0x9d, 0xd2, 0xda, 0xc2, 0x66, 0xe7, 0x4a, 0x86, 0x3d, 0xaf, 0x60, 0x37, 0x61, 0xcc, 0x6c, 0x70,
0x54, 0x6e, 0x34, 0x0c, 0x15, 0x2a, 0xb1, 0xb0, 0xef, 0xc0, 0xca, 0x44, 0x41, 0xee, 0x94, 0xef,
0x09, 0xf3, 0x55, 0x6c, 0xfa, 0x9e, 0x00, 0x44, 0x1c, 0x4f, 0xb8, 0x59, 0xc5, 0xe2, 0xdb, 0xfe,
0xde, 0x80, 0x73, 0x99, 0xf4, 0x57, 0x43, 0x42, 0x19, 0x6a, 0xc2, 0xbc, 0xc0, 0xd4, 0x4d, 0x85,
0xd3, 0x25, 0xba, 0x06, 0xe5, 0x98, 0xc7, 0x30, 0x05, 0xdf, 0x9c, 0x04, 0x9e, 0x33, 0x60, 0xc9,
0x87, 0xae, 0x42, 0xc5, 0xf3, 0x8f, 0x8f, 0x1f, 0x8d, 0x06, 0x44, 0xa0, 0xae, 0x6f, 0x2e, 0x6b,
0x32, 0x3b, 0x72, 0x0b, 0x2b, 0x26, 0xfb, 0x0c, 0x2c, 0xcd, 0x9b, 0x41, 0x14, 0x52, 0x82, 0xae,
0xc3, 0x7c, 0x2c, 0x3c, 0xa3, 0x4d, 0x43, 0xd8, 0xfd, 0xdf, 0xd4, 0xa0, 0xe1, 0x94, 0x33, 0x67,
0xd9, 0x7c, 0x1b, 0xcb, 0xbf, 0x19, 0xb0, 0x74, 0x70, 0xf4, 0x25, 0x71, 0x19, 0x57, 0xb7, 0x4f,
0x28, 0x75, 0x4e, 0xc8, 0x1b, 0x82, 0x71, 0x11, 0xaa, 0x71, 0x12, 0xb1, 0x6e, 0x1a, 0xd3, 0x8c,
0xc0, 0xe5, 0x62, 0x32, 0x08, 0x46, 0x5d, 0x4f, 0xf8, 0x5d, 0xc5, 0xe9, 0x92, 0xef, 0x0c, 0x9c,
0x51, 0x10, 0x39, 0x9e, 0x48, 0xa2, 0x1a, 0x4e, 0x97, 0x3c, 0xbf, 0x22, 0x01, 0xa0, 0xeb, 0x35,
0xe7, 0x84, 0x90, 0x5a, 0xa3, 0x0f, 0x01, 0x92, 0x6f, 0xe1, 0x50, 0x59, 0x38, 0xb4, 0xa2, 0x39,
0x74, 0xa0, 0x36, 0xb1, 0xc6, 0x68, 0x13, 0xb0, 0x7a, 0x9c, 0xe7, 0x70, 0x48, 0x4f, 0xd3, 0xf3,
0xdd, 0xc8, 0x00, 0x70, 0x97, 0x16, 0x36, 0x57, 0x35, 0x3d, 0x09, 0x77, 0xb2, 0x9d, 0x21, 0x6b,
0x03, 0x6c, 0xc7, 0xc4, 0x23, 0x21, 0xf3, 0x9d, 0x40, 0x38, 0x5b, 0xc3, 0x1a, 0xc5, 0x5e, 0x86,
0x25, 0xcd, 0x4c, 0x72, 0x6c, 0xb6, 0xad, 0x6c, 0x07, 0x41, 0x6a, 0xbb, 0x90, 0x93, 0xf6, 0xd7,
0x4a, 0x90, 0xf3, 0xc8, 0xf3, 0xfe, 0x07, 0x00, 0x6f, 0x00, 0x38, 0x6e, 0x80, 0x89, 0x1b, 0xc5,
0x5e, 0x9a, 0x9d, 0x0d, 0x4d, 0x6a, 0x2b, 0xdd, 0xc4, 0x1a, 0x9f, 0x7d, 0x07, 0xaa, 0x6a, 0x83,
0xfb, 0xe8, 0xb8, 0xc1, 0xa1, 0x66, 0xb8, 0x86, 0x35, 0x8a, 0x84, 0x6e, 0x2a, 0xe8, 0xdf, 0x98,
0x50, 0xd3, 0xc1, 0xa0, 0x2d, 0x58, 0x10, 0x06, 0x79, 0x46, 0x92, 0x58, 0x42, 0xbf, 0xa4, 0x81,
0xc0, 0xce, 0x8b, 0x5e, 0xc6, 0xf0, 0xd4, 0x67, 0xa7, 0x5d, 0x0f, 0xeb, 0x32, 0x05, 0x0c, 0xe6,
0x18, 0x06, 0x1b, 0x6a, 0xd9, 0x4a, 0xa5, 0x56, 0x8e, 0x86, 0x36, 0xa1, 0x21, 0x54, 0xf6, 0x08,
0x63, 0x7e, 0x78, 0x42, 0x0f, 0x73, 0xc9, 0x36, 0x71, 0x0f, 0xdd, 0x84, 0xf3, 0x93, 0xe8, 0x2a,
0x0f, 0xa7, 0xec, 0xda, 0xbf, 0x1a, 0xb0, 0xa0, 0xb9, 0xc4, 0x33, 0xd8, 0x17, 0x39, 0xc1, 0x46,
0x32, 0x82, 0x6a, 0xcd, 0xeb, 0x85, 0xf9, 0x7d, 0x42, 0x99, 0xd3, 0x1f, 0x08, 0xd7, 0x4a, 0x38,
0x23, 0xf0, 0x5d, 0x61, 0x43, 0x75, 0x8a, 0x2a, 0xce, 0x08, 0xe8, 0x32, 0xd4, 0x79, 0xf9, 0xf8,
0xae, 0xc3, 0xfc, 0x28, 0x7c, 0x40, 0x46, 0xc2, 0x9b, 0x59, 0x5c, 0xa0, 0xf2, 0x16, 0x47, 0x09,
0x49, 0x50, 0xd7, 0xb0, 0xf8, 0x46, 0x57, 0x00, 0x69, 0x21, 0x4e, 0xa3, 0x51, 0x16, 0x1c, 0x13,
0x76, 0xec, 0x43, 0xa8, 0xe7, 0x0f, 0x0a, 0x75, 0xc6, 0x0f, 0xb6, 0x96, 0x3f, 0x37, 0x8e, 0xde,
0x3f, 0x09, 0x1d, 0x36, 0x8c, 0x89, 0x3c, 0xb6, 0x8c, 0x60, 0xef, 0x40, 0x63, 0xd2, 0xd1, 0x8b,
0x0e, 0xe2, 0xbc, 0xc8, 0x69, 0xcd, 0x08, 0x63, 0xf9, 0xf6, 0xa3, 0x01, 0x8d, 0x9e, 0x7e, 0x0c,
0xdb, 0x51, 0xc8, 0x78, 0x9f, 0xff, 0x18, 0x6a, 0x49, 0xc5, 0xef, 0x90, 0x80, 0x30, 0x32, 0xa1,
0x66, 0x0e, 0xb4, 0xed, 0xbd, 0x19, 0x9c, 0x63, 0x47, 0xb7, 0xa5, 0x77, 0x52, 0xda, 0x14, 0xd2,
0xe7, 0x8b, 0x15, 0xa7, 0x84, 0x75, 0xe6, 0xbb, 0xf3, 0x30, 0xf7, 0xdc, 0x09, 0x86, 0xc4, 0x6e,
0x43, 0x4d, 0x37, 0x32, 0x56, 0xe7, 0x5d, 0x58, 0xe8, 0xb1, 0x28, 0x4e, 0xe3, 0x35, 0xbd, 0xab,
0xf2, 0x58, 0xb3, 0x28, 0x76, 0x4e, 0xc8, 0x43, 0xa7, 0x4f, 0xa4, 0xfb, 0x3a, 0xc9, 0xbe, 0x2e,
0x53, 0x4e, 0x5a, 0xfa, 0x3f, 0x2c, 0x7a, 0xe2, 0x2b, 0x3e, 0x24, 0x24, 0x56, 0x0a, 0xf3, 0x44,
0xfb, 0x73, 0x58, 0xc9, 0xc5, 0xae, 0x17, 0x3a, 0x03, 0x7a, 0x1a, 0x31, 0x5e, 0x71, 0x09, 0xa7,
0xd7, 0xf5, 0x92, 0xf1, 0x52, 0xc5, 0x1a, 0x65, 0x5c, 0xbd, 0x39, 0x49, 0xfd, 0xb7, 0x06, 0xd4,
0x52, 0xd5, 0x3b, 0x0e, 0x73, 0xd0, 0x2d, 0x98, 0x77, 0x93, 0xe3, 0x91, 0x23, 0xeb, 0x52, 0x31,
0xa0, 0x85, 0x53, 0xc4, 0x29, 0x3f, 0xbf, 0x23, 0x50, 0x89, 0x4e, 0x1e, 0x46, 0x67, 0x9a, 0x6c,
0xea, 0x05, 0x56, 0x12, 0xf6, 0x33, 0xd9, 0x50, 0x7b, 0xc3, 0x23, 0xea, 0xc6, 0xfe, 0x80, 0x57,
0x06, 0x2f, 0x4b, 0x19, 0xdf, 0xd4, 0x45, 0xb5, 0x46, 0xb7, 0xa1, 0xec, 0xb8, 0x9c, 0x4b, 0x4e,
0x49, 0x7b, 0xcc, 0x98, 0xa6, 0x69, 0x4b, 0x70, 0x62, 0x29, 0x61, 0x77, 0x61, 0x79, 0xcb, 0x0d,
0xb6, 0x3c, 0x4f, 0xf6, 0xd6, 0xbf, 0xbd, 0x40, 0x68, 0xb3, 0xcf, 0xcc, 0xcd, 0x3e, 0xfb, 0x53,
0x68, 0xe4, 0x55, 0xc9, 0x59, 0xd0, 0x82, 0x4a, 0x2c, 0x28, 0x4a, 0x99, 0x5a, 0xbf, 0x41, 0xdb,
0x27, 0x42, 0xdb, 0x7d, 0xc2, 0x64, 0xa7, 0x7f, 0x2b, 0x64, 0x8e, 0x1b, 0xec, 0x65, 0xf7, 0xa3,
0x74, 0x69, 0x6f, 0xc0, 0x4a, 0x41, 0x97, 0x84, 0x26, 0x46, 0x7c, 0x32, 0x70, 0x78, 0x50, 0x6b,
0x38, 0x5d, 0xda, 0x5f, 0x80, 0x25, 0xb2, 0x9d, 0xdf, 0x32, 0xfe, 0x83, 0x5b, 0x95, 0xbd, 0x07,
0x4b, 0x9a, 0xfe, 0x77, 0xb8, 0x25, 0xd9, 0x3f, 0x1b, 0xb0, 0x28, 0x54, 0x3d, 0x20, 0xa3, 0x27,
0xbc, 0x92, 0x79, 0x53, 0x7a, 0x46, 0x46, 0xb9, 0x5a, 0xca, 0x08, 0xfc, 0x0a, 0x2a, 0x0a, 0x5e,
0x06, 0x3c, 0x59, 0xa0, 0xf7, 0x61, 0x29, 0x6d, 0xf3, 0x3d, 0xd5, 0x06, 0x4b, 0x82, 0x63, 0x7c,
0x83, 0x97, 0xd4, 0x80, 0x90, 0x38, 0xe3, 0x4c, 0x26, 0x53, 0x9e, 0xa8, 0xc7, 0x6b, 0x2e, 0x17,
0x2f, 0x7b, 0x0f, 0xea, 0x39, 0xc8, 0x14, 0xdd, 0x14, 0x98, 0x93, 0x85, 0x74, 0x5e, 0x0f, 0x62,
0x8e, 0x1b, 0x67, 0xac, 0xf6, 0x4f, 0x9a, 0xf7, 0xdd, 0x30, 0x24, 0x31, 0x1f, 0x20, 0x1c, 0x46,
0x7a, 0x69, 0xe7, 0xdf, 0xb9, 0xa1, 0x66, 0x16, 0x86, 0x9a, 0x8a, 0x47, 0x49, 0x8f, 0xc7, 0x65,
0xa8, 0xab, 0xc9, 0xb6, 0xef, 0xbb, 0x71, 0x24, 0x5c, 0x2c, 0xe1, 0x02, 0x95, 0xc7, 0x5a, 0x66,
0x99, 0xf2, 0x32, 0x23, 0x20, 0x0b, 0x4a, 0xcf, 0xc8, 0x48, 0x4c, 0xaa, 0x2a, 0xe6, 0x9f, 0xf6,
0x83, 0x04, 0xae, 0x73, 0xf2, 0x2f, 0xf4, 0xd1, 0xf5, 0x3f, 0x0d, 0xa8, 0xec, 0xc6, 0xf1, 0x76,
0xe4, 0x11, 0x8a, 0xea, 0x00, 0x8f, 0x43, 0x72, 0x36, 0x20, 0x2e, 0x23, 0x9e, 0x35, 0x83, 0x2c,
0x79, 0xb7, 0xd9, 0xf7, 0x29, 0xf5, 0xc3, 0x13, 0xcb, 0x40, 0xe7, 0x64, 0xdb, 0xdd, 0x3d, 0xf3,
0x29, 0xa3, 0x96, 0x89, 0x96, 0xe1, 0x9c, 0x20, 0x3c, 0x8c, 0x58, 0x37, 0xdc, 0x76, 0xdc, 0x53,
0x62, 0x95, 0x10, 0x82, 0xba, 0x20, 0x76, 0x69, 0xd2, 0x9e, 0x3d, 0x6b, 0x16, 0x35, 0xa1, 0x21,
0xb2, 0x87, 0x3e, 0x8c, 0x98, 0xcc, 0x56, 0xff, 0x28, 0x20, 0xd6, 0x1c, 0x6a, 0x80, 0x85, 0x89,
0x4b, 0xfc, 0x01, 0xeb, 0xd2, 0x6e, 0xf8, 0xdc, 0x09, 0x7c, 0xcf, 0x2a, 0x73, 0x1d, 0x72, 0x21,
0x47, 0xb2, 0x35, 0xcf, 0x39, 0x77, 0x86, 0xc9, 0xa8, 0x27, 0xb2, 0xa2, 0xac, 0x0a, 0xba, 0x00,
0xab, 0x8f, 0xa2, 0x68, 0xdf, 0x09, 0x47, 0x92, 0x46, 0xef, 0xc5, 0x51, 0x9f, 0x1b, 0xb3, 0xaa,
0x1c, 0xf0, 0x6e, 0x1c, 0x47, 0xf1, 0xc1, 0xf1, 0x31, 0x25, 0xcc, 0xf2, 0xd6, 0x6f, 0xc1, 0xea,
0x94, 0x86, 0x86, 0x16, 0xa1, 0x2a, 0xa9, 0x47, 0xc4, 0x9a, 0xe1, 0xa2, 0x8f, 0x43, 0xaa, 0x08,
0xc6, 0xfa, 0x7b, 0x50, 0x49, 0x5f, 0x0c, 0x68, 0x01, 0xe6, 0xbb, 0xa1, 0xcf, 0xaf, 0xbd, 0xd6,
0x0c, 0x2a, 0x83, 0xf9, 0x64, 0xc3, 0x32, 0xc4, 0xef, 0xa6, 0x65, 0xae, 0x7f, 0x00, 0x90, 0xdd,
0xc4, 0x51, 0x05, 0x66, 0x1f, 0xc5, 0x84, 0x6b, 0x9c, 0x87, 0xd2, 0x96, 0x1b, 0x58, 0x06, 0xaa,
0x41, 0x25, 0xcd, 0x44, 0xcb, 0xdc, 0xfc, 0xae, 0x0c, 0xd5, 0x04, 0xd3, 0x28, 0x74, 0xd1, 0x36,
0x54, 0xd2, 0x3a, 0x45, 0xad, 0x89, 0xc5, 0x2b, 0x9c, 0x6c, 0x5d, 0x98, 0x5c, 0xd8, 0x49, 0x1b,
0xb8, 0x07, 0x55, 0xd5, 0x1b, 0xd0, 0x85, 0x62, 0x15, 0x68, 0x1d, 0xa9, 0x75, 0x71, 0xf2, 0xa6,
0xd4, 0x73, 0x5f, 0x96, 0xc6, 0x6e, 0xfa, 0xfa, 0x9c, 0x5a, 0x51, 0xad, 0xa9, 0x3b, 0x6b, 0xc6,
0x35, 0x43, 0x00, 0x4a, 0xdf, 0x06, 0x79, 0x40, 0x85, 0x87, 0x49, 0x1e, 0x50, 0xf1, 0x39, 0xa1,
0xe9, 0x09, 0x82, 0x49, 0x7a, 0xd4, 0x23, 0x63, 0x92, 0x1e, 0xed, 0x75, 0x81, 0xc1, 0xca, 0x9e,
0x79, 0x3d, 0x16, 0x13, 0xa7, 0x8f, 0x2e, 0x8e, 0x5d, 0x96, 0xb4, 0x37, 0x60, 0xeb, 0x8d, 0xbb,
0xc2, 0xc7, 0xbd, 0xf4, 0xd8, 0xc5, 0xd9, 0xbd, 0x83, 0x36, 0xf4, 0x14, 0x56, 0x33, 0xa2, 0x74,
0xe8, 0xdd, 0x41, 0x5e, 0x33, 0xd0, 0x01, 0xd4, 0xf4, 0x01, 0x8b, 0xda, 0xf9, 0xd7, 0x51, 0x71,
0x88, 0xb7, 0x2e, 0x4d, 0xdd, 0x57, 0x71, 0x5c, 0xcc, 0xcd, 0x45, 0x54, 0x90, 0x18, 0x9b, 0xbe,
0xad, 0xce, 0x74, 0x86, 0x44, 0xe7, 0xdd, 0x1b, 0xbf, 0xbc, 0x6a, 0x1b, 0x2f, 0x5f, 0xb5, 0x8d,
0x3f, 0x5e, 0xb5, 0x8d, 0x1f, 0x5e, 0xb7, 0x67, 0x5e, 0xbe, 0x6e, 0xcf, 0xfc, 0xfe, 0xba, 0x3d,
0xf3, 0x59, 0x6b, 0xfa, 0x3f, 0x81, 0x8e, 0xca, 0xe2, 0xe7, 0xfa, 0x5f, 0x01, 0x00, 0x00, 0xff,
0xff, 0x9d, 0x2c, 0x6b, 0x14, 0x29, 0x12, 0x00, 0x00,
}
func (m *HeadSyncRange) Marshal() (dAtA []byte, err error) {
@ -2729,6 +2800,20 @@ func (m *SpacePullResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.AclRecords) > 0 {
for iNdEx := len(m.AclRecords) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.AclRecords[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintSpacesync(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x12
}
}
if m.Payload != nil {
{
size, err := m.Payload.MarshalToSizedBuffer(dAtA[:i])
@ -2744,6 +2829,43 @@ func (m *SpacePullResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *AclRecord) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *AclRecord) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *AclRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Id) > 0 {
i -= len(m.Id)
copy(dAtA[i:], m.Id)
i = encodeVarintSpacesync(dAtA, i, uint64(len(m.Id)))
i--
dAtA[i] = 0x12
}
if len(m.AclPayload) > 0 {
i -= len(m.AclPayload)
copy(dAtA[i:], m.AclPayload)
i = encodeVarintSpacesync(dAtA, i, uint64(len(m.AclPayload)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *SpacePayload) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@ -3849,6 +3971,29 @@ func (m *SpacePullResponse) Size() (n int) {
l = m.Payload.Size()
n += 1 + l + sovSpacesync(uint64(l))
}
if len(m.AclRecords) > 0 {
for _, e := range m.AclRecords {
l = e.Size()
n += 1 + l + sovSpacesync(uint64(l))
}
}
return n
}
func (m *AclRecord) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.AclPayload)
if l > 0 {
n += 1 + l + sovSpacesync(uint64(l))
}
l = len(m.Id)
if l > 0 {
n += 1 + l + sovSpacesync(uint64(l))
}
return n
}
@ -5444,6 +5589,156 @@ func (m *SpacePullResponse) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AclRecords", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpacesync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthSpacesync
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthSpacesync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AclRecords = append(m.AclRecords, &AclRecord{})
if err := m.AclRecords[len(m.AclRecords)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSpacesync(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSpacesync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *AclRecord) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpacesync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: AclRecord: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: AclRecord: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AclPayload", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpacesync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSpacesync
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSpacesync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AclPayload = append(m.AclPayload[:0], dAtA[iNdEx:postIndex]...)
if m.AclPayload == nil {
m.AclPayload = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpacesync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSpacesync
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSpacesync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Id = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSpacesync(dAtA[iNdEx:])

View file

@ -10,7 +10,6 @@ import (
"time"
anystore "github.com/anyproto/any-store"
"github.com/anyproto/go-chash"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"storj.io/drpc"
@ -44,114 +43,12 @@ import (
"github.com/anyproto/any-sync/net/streampool/streamhandler"
"github.com/anyproto/any-sync/node/nodeclient"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anyproto/any-sync/nodeconf/testconf"
"github.com/anyproto/any-sync/testutil/accounttest"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anyproto/any-sync/util/syncqueues"
)
type mockConf struct {
id string
networkId string
configuration nodeconf.Configuration
}
func (m *mockConf) NetworkCompatibilityStatus() nodeconf.NetworkCompatibilityStatus {
return nodeconf.NetworkCompatibilityStatusOk
}
func (m *mockConf) Init(a *app.App) (err error) {
accountKeys := a.MustComponent(accountService.CName).(accountService.Service).Account()
networkId := accountKeys.SignKey.GetPublic().Network()
node := nodeconf.Node{
PeerId: accountKeys.PeerId,
Addresses: []string{"127.0.0.1:4430"},
Types: []nodeconf.NodeType{nodeconf.NodeTypeTree},
}
m.id = networkId
m.networkId = networkId
m.configuration = nodeconf.Configuration{
Id: networkId,
NetworkId: networkId,
Nodes: []nodeconf.Node{node},
CreationTime: time.Now(),
}
return nil
}
func (m *mockConf) Name() (name string) {
return nodeconf.CName
}
func (m *mockConf) Run(ctx context.Context) (err error) {
return nil
}
func (m *mockConf) Close(ctx context.Context) (err error) {
return nil
}
func (m *mockConf) Id() string {
return m.id
}
func (m *mockConf) Configuration() nodeconf.Configuration {
return m.configuration
}
func (m *mockConf) NodeIds(spaceId string) []string {
var nodeIds []string
for _, node := range m.configuration.Nodes {
nodeIds = append(nodeIds, node.PeerId)
}
return nodeIds
}
func (m *mockConf) IsResponsible(spaceId string) bool {
return true
}
func (m *mockConf) FilePeers() []string {
return nil
}
func (m *mockConf) ConsensusPeers() []string {
return nil
}
func (m *mockConf) CoordinatorPeers() []string {
return nil
}
func (m *mockConf) NamingNodePeers() []string {
return nil
}
func (m *mockConf) PaymentProcessingNodePeers() []string {
return nil
}
func (m *mockConf) PeerAddresses(peerId string) (addrs []string, ok bool) {
if peerId == m.configuration.Nodes[0].PeerId {
return m.configuration.Nodes[0].Addresses, true
}
return nil, false
}
func (m *mockConf) CHash() chash.CHash {
return nil
}
func (m *mockConf) Partition(spaceId string) (part int) {
return 0
}
func (m *mockConf) NodeTypes(nodeId string) []nodeconf.NodeType {
if nodeId == m.configuration.Nodes[0].PeerId {
return m.configuration.Nodes[0].Types
}
return nil
}
var _ nodeclient.NodeClient = (*mockNodeClient)(nil)
type mockNodeClient struct {
@ -174,6 +71,7 @@ func (m mockNodeClient) AclAddRecord(ctx context.Context, spaceId string, rec *c
}
type mockPeerManager struct {
peer peer.Peer
}
func (p *mockPeerManager) BroadcastMessage(ctx context.Context, msg drpc.Message) error {
@ -193,6 +91,9 @@ func (p *mockPeerManager) Name() (name string) {
}
func (p *mockPeerManager) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
if p.peer != nil {
return []peer.Peer{p.peer}, nil
}
return nil, nil
}
@ -218,6 +119,7 @@ func (m *testPeerManagerProvider) NewPeerManager(ctx context.Context, spaceId st
}
type mockPeerManagerProvider struct {
peer peer.Peer
}
func (m *mockPeerManagerProvider) Init(a *app.App) (err error) {
@ -229,7 +131,7 @@ func (m *mockPeerManagerProvider) Name() (name string) {
}
func (m *mockPeerManagerProvider) NewPeerManager(ctx context.Context, spaceId string) (sm peermanager.PeerManager, err error) {
return &mockPeerManager{}, nil
return &mockPeerManager{m.peer}, nil
}
type mockPool struct {
@ -594,7 +496,10 @@ func (s *streamOpener) NewReadMessage() drpc.Message {
}
func (s *streamOpener) Init(a *app.App) (err error) {
s.spaceGetter = a.MustComponent(RpcName).(*RpcServer)
sp := a.Component(RpcName)
if sp != nil {
s.spaceGetter = sp.(*RpcServer)
}
s.streamPool = a.MustComponent(streampool.CName).(streampool.StreamPool)
return nil
}
@ -654,7 +559,7 @@ func newFixture(t *testing.T) *spaceFixture {
app: &app.App{},
config: &mockConfig{},
account: &accounttest.AccountTestService{},
configurationService: &mockConf{},
configurationService: &testconf.StubConf{},
streamOpener: newStreamOpener("spaceId"),
peerManagerProvider: &testPeerManagerProvider{},
storageProvider: &spaceStorageProvider{rootPath: t.TempDir()},
@ -699,7 +604,7 @@ func newPeerFixture(t *testing.T, spaceId string, keys *accountdata.AccountKeys,
app: &app.App{},
config: &mockConfig{},
account: accounttest.NewWithAcc(keys),
configurationService: &mockConf{},
configurationService: &testconf.StubConf{},
storageProvider: provider,
streamOpener: newStreamOpener(spaceId),
peerManagerProvider: &testPeerManagerProvider{},

View file

@ -43,7 +43,7 @@ func (s *spaceStorageProvider) WaitSpaceStorage(ctx context.Context, id string)
}
dbPath := path.Join(s.rootPath, id)
if _, err := os.Stat(dbPath); err != nil {
return nil, err
return nil, spacestorage.ErrSpaceStorageMissing
}
db, err := anystore.Open(ctx, dbPath, nil)
if err != nil {

14
go.mod
View file

@ -6,7 +6,7 @@ toolchain go1.24.1
require (
filippo.io/edwards25519 v1.1.0
github.com/anyproto/any-store v0.1.11
github.com/anyproto/any-store v0.2.2
github.com/anyproto/go-chash v0.1.0
github.com/anyproto/go-slip10 v1.0.0
github.com/anyproto/go-slip21 v1.0.0
@ -30,7 +30,7 @@ require (
github.com/multiformats/go-multibase v0.2.0
github.com/multiformats/go-multihash v0.2.3
github.com/prometheus/client_golang v1.22.0
github.com/quic-go/quic-go v0.51.0
github.com/quic-go/quic-go v0.52.0
github.com/stretchr/testify v1.10.0
github.com/tyler-smith/go-bip39 v1.1.0
github.com/zeebo/blake3 v0.2.4
@ -38,7 +38,7 @@ require (
go.uber.org/mock v0.5.2
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/net v0.40.0
golang.org/x/sys v0.33.0
golang.org/x/time v0.11.0
@ -65,7 +65,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/ipfs/bbloom v0.0.4 // indirect
github.com/ipfs/go-bitfield v1.1.0 // indirect
@ -112,9 +112,9 @@ require (
golang.org/x/text v0.25.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
lukechampine.com/blake3 v1.4.0 // indirect
modernc.org/libc v1.61.13 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.37.0 // indirect
zombiezen.com/go/sqlite v1.4.0 // indirect
)

44
go.sum
View file

@ -6,8 +6,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/anyproto/any-store v0.1.11 h1:xoaDVF8FJEI6V37fMw/R3ptBCLHj0kYiImwWxC1Ryu8=
github.com/anyproto/any-store v0.1.11/go.mod h1:X3UkQ2zLATYNED3gFhY2VcdfDOeJvpEQ0PmDO90A9Yo=
github.com/anyproto/any-store v0.2.2 h1:/sN0oS6dX+oYO6qgW3qrx43hmKa5U7zLiJZHcesh5KE=
github.com/anyproto/any-store v0.2.2/go.mod h1:9o+4sliO3+Lw2HGwyk9oBmoR83c8mwQ7TK7yfyGptPs=
github.com/anyproto/go-chash v0.1.0 h1:I9meTPjXFRfXZHRJzjOHC/XF7Q5vzysKkiT/grsogXY=
github.com/anyproto/go-chash v0.1.0/go.mod h1:0UjNQi3PDazP0fINpFYu6VKhuna+W/V+1vpXHAfNgLY=
github.com/anyproto/go-slip10 v1.0.0 h1:uAEtSuudR3jJBOfkOXf3bErxVoxbuKwdoJN55M1i6IA=
@ -100,8 +100,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -293,8 +293,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc=
github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@ -379,8 +379,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
@ -457,26 +457,26 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View file

@ -31,13 +31,14 @@ var (
// ProtoVersion 4 - new sync compatible version
// ProtoVersion 5 - sync with no entry space
// ProtoVersion 6 - sync with key value messages
CompatibleVersion = uint32(4)
ProtoVersion = uint32(5)
KeyValueVersion = uint32(6)
// ProtoVersion 7 - sync with new invites
CompatibleVersion = uint32(5)
ProtoVersion = uint32(6)
NewInvitesVersion = uint32(7)
)
var (
compatibleVersions = []uint32{CompatibleVersion, ProtoVersion, KeyValueVersion}
compatibleVersions = []uint32{CompatibleVersion, ProtoVersion, NewInvitesVersion}
)
func New() SecureService {

View file

@ -0,0 +1,115 @@
package testconf
import (
"context"
"time"
"github.com/anyproto/go-chash"
accountService "github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/nodeconf"
)
type StubConf struct {
id string
networkId string
configuration nodeconf.Configuration
}
func (m *StubConf) NetworkCompatibilityStatus() nodeconf.NetworkCompatibilityStatus {
return nodeconf.NetworkCompatibilityStatusOk
}
func (m *StubConf) Init(a *app.App) (err error) {
accountKeys := a.MustComponent(accountService.CName).(accountService.Service).Account()
networkId := accountKeys.SignKey.GetPublic().Network()
node := nodeconf.Node{
PeerId: accountKeys.PeerId,
Addresses: []string{"127.0.0.1:4430"},
Types: []nodeconf.NodeType{nodeconf.NodeTypeTree},
}
m.id = networkId
m.networkId = networkId
m.configuration = nodeconf.Configuration{
Id: networkId,
NetworkId: networkId,
Nodes: []nodeconf.Node{node},
CreationTime: time.Now(),
}
return nil
}
func (m *StubConf) Name() (name string) {
return nodeconf.CName
}
func (m *StubConf) Run(ctx context.Context) (err error) {
return nil
}
func (m *StubConf) Close(ctx context.Context) (err error) {
return nil
}
func (m *StubConf) Id() string {
return m.id
}
func (m *StubConf) Configuration() nodeconf.Configuration {
return m.configuration
}
func (m *StubConf) NodeIds(spaceId string) []string {
var nodeIds []string
for _, node := range m.configuration.Nodes {
nodeIds = append(nodeIds, node.PeerId)
}
return nodeIds
}
func (m *StubConf) IsResponsible(spaceId string) bool {
return false
}
func (m *StubConf) FilePeers() []string {
return nil
}
func (m *StubConf) ConsensusPeers() []string {
return nil
}
func (m *StubConf) CoordinatorPeers() []string {
return nil
}
func (m *StubConf) NamingNodePeers() []string {
return nil
}
func (m *StubConf) PaymentProcessingNodePeers() []string {
return nil
}
func (m *StubConf) PeerAddresses(peerId string) (addrs []string, ok bool) {
if peerId == m.configuration.Nodes[0].PeerId {
return m.configuration.Nodes[0].Addresses, true
}
return nil, false
}
func (m *StubConf) CHash() chash.CHash {
return nil
}
func (m *StubConf) Partition(spaceId string) (part int) {
return 0
}
func (m *StubConf) NodeTypes(nodeId string) []nodeconf.NodeType {
if nodeId == m.configuration.Nodes[0].PeerId {
return m.configuration.Nodes[0].Types
}
return nil
}

View file

@ -5,7 +5,6 @@
//
// mockgen -destination=mock/mock_paymentserviceclient.go -package=mock_paymentserviceclient github.com/anyproto/any-sync/paymentservice/paymentserviceclient AnyPpClientService
//
// Package mock_paymentserviceclient is a generated GoMock package.
package mock_paymentserviceclient
@ -22,7 +21,6 @@ import (
type MockAnyPpClientService struct {
ctrl *gomock.Controller
recorder *MockAnyPpClientServiceMockRecorder
isgomock struct{}
}
// MockAnyPpClientServiceMockRecorder is the mock recorder for MockAnyPpClientService.
@ -43,122 +41,122 @@ func (m *MockAnyPpClientService) EXPECT() *MockAnyPpClientServiceMockRecorder {
}
// BuySubscription mocks base method.
func (m *MockAnyPpClientService) BuySubscription(ctx context.Context, in *paymentserviceproto.BuySubscriptionRequestSigned) (*paymentserviceproto.BuySubscriptionResponse, error) {
func (m *MockAnyPpClientService) BuySubscription(arg0 context.Context, arg1 *paymentserviceproto.BuySubscriptionRequestSigned) (*paymentserviceproto.BuySubscriptionResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BuySubscription", ctx, in)
ret := m.ctrl.Call(m, "BuySubscription", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.BuySubscriptionResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BuySubscription indicates an expected call of BuySubscription.
func (mr *MockAnyPpClientServiceMockRecorder) BuySubscription(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) BuySubscription(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuySubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).BuySubscription), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuySubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).BuySubscription), arg0, arg1)
}
// FinalizeSubscription mocks base method.
func (m *MockAnyPpClientService) FinalizeSubscription(ctx context.Context, in *paymentserviceproto.FinalizeSubscriptionRequestSigned) (*paymentserviceproto.FinalizeSubscriptionResponse, error) {
func (m *MockAnyPpClientService) FinalizeSubscription(arg0 context.Context, arg1 *paymentserviceproto.FinalizeSubscriptionRequestSigned) (*paymentserviceproto.FinalizeSubscriptionResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FinalizeSubscription", ctx, in)
ret := m.ctrl.Call(m, "FinalizeSubscription", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.FinalizeSubscriptionResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FinalizeSubscription indicates an expected call of FinalizeSubscription.
func (mr *MockAnyPpClientServiceMockRecorder) FinalizeSubscription(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) FinalizeSubscription(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeSubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).FinalizeSubscription), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeSubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).FinalizeSubscription), arg0, arg1)
}
// GetAllTiers mocks base method.
func (m *MockAnyPpClientService) GetAllTiers(ctx context.Context, in *paymentserviceproto.GetTiersRequestSigned) (*paymentserviceproto.GetTiersResponse, error) {
func (m *MockAnyPpClientService) GetAllTiers(arg0 context.Context, arg1 *paymentserviceproto.GetTiersRequestSigned) (*paymentserviceproto.GetTiersResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAllTiers", ctx, in)
ret := m.ctrl.Call(m, "GetAllTiers", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.GetTiersResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAllTiers indicates an expected call of GetAllTiers.
func (mr *MockAnyPpClientServiceMockRecorder) GetAllTiers(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) GetAllTiers(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllTiers", reflect.TypeOf((*MockAnyPpClientService)(nil).GetAllTiers), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllTiers", reflect.TypeOf((*MockAnyPpClientService)(nil).GetAllTiers), arg0, arg1)
}
// GetSubscriptionPortalLink mocks base method.
func (m *MockAnyPpClientService) GetSubscriptionPortalLink(ctx context.Context, in *paymentserviceproto.GetSubscriptionPortalLinkRequestSigned) (*paymentserviceproto.GetSubscriptionPortalLinkResponse, error) {
func (m *MockAnyPpClientService) GetSubscriptionPortalLink(arg0 context.Context, arg1 *paymentserviceproto.GetSubscriptionPortalLinkRequestSigned) (*paymentserviceproto.GetSubscriptionPortalLinkResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubscriptionPortalLink", ctx, in)
ret := m.ctrl.Call(m, "GetSubscriptionPortalLink", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.GetSubscriptionPortalLinkResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSubscriptionPortalLink indicates an expected call of GetSubscriptionPortalLink.
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionPortalLink(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionPortalLink(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionPortalLink", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionPortalLink), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionPortalLink", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionPortalLink), arg0, arg1)
}
// GetSubscriptionStatus mocks base method.
func (m *MockAnyPpClientService) GetSubscriptionStatus(ctx context.Context, in *paymentserviceproto.GetSubscriptionRequestSigned) (*paymentserviceproto.GetSubscriptionResponse, error) {
func (m *MockAnyPpClientService) GetSubscriptionStatus(arg0 context.Context, arg1 *paymentserviceproto.GetSubscriptionRequestSigned) (*paymentserviceproto.GetSubscriptionResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubscriptionStatus", ctx, in)
ret := m.ctrl.Call(m, "GetSubscriptionStatus", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.GetSubscriptionResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSubscriptionStatus indicates an expected call of GetSubscriptionStatus.
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionStatus(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionStatus(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionStatus", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionStatus), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionStatus", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionStatus), arg0, arg1)
}
// GetVerificationEmail mocks base method.
func (m *MockAnyPpClientService) GetVerificationEmail(ctx context.Context, in *paymentserviceproto.GetVerificationEmailRequestSigned) (*paymentserviceproto.GetVerificationEmailResponse, error) {
func (m *MockAnyPpClientService) GetVerificationEmail(arg0 context.Context, arg1 *paymentserviceproto.GetVerificationEmailRequestSigned) (*paymentserviceproto.GetVerificationEmailResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVerificationEmail", ctx, in)
ret := m.ctrl.Call(m, "GetVerificationEmail", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.GetVerificationEmailResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetVerificationEmail indicates an expected call of GetVerificationEmail.
func (mr *MockAnyPpClientServiceMockRecorder) GetVerificationEmail(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) GetVerificationEmail(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerificationEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).GetVerificationEmail), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerificationEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).GetVerificationEmail), arg0, arg1)
}
// Init mocks base method.
func (m *MockAnyPpClientService) Init(a *app.App) error {
func (m *MockAnyPpClientService) Init(arg0 *app.App) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Init", a)
ret := m.ctrl.Call(m, "Init", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Init indicates an expected call of Init.
func (mr *MockAnyPpClientServiceMockRecorder) Init(a any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) Init(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockAnyPpClientService)(nil).Init), a)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockAnyPpClientService)(nil).Init), arg0)
}
// IsNameValid mocks base method.
func (m *MockAnyPpClientService) IsNameValid(ctx context.Context, in *paymentserviceproto.IsNameValidRequest) (*paymentserviceproto.IsNameValidResponse, error) {
func (m *MockAnyPpClientService) IsNameValid(arg0 context.Context, arg1 *paymentserviceproto.IsNameValidRequest) (*paymentserviceproto.IsNameValidResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsNameValid", ctx, in)
ret := m.ctrl.Call(m, "IsNameValid", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.IsNameValidResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// IsNameValid indicates an expected call of IsNameValid.
func (mr *MockAnyPpClientServiceMockRecorder) IsNameValid(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) IsNameValid(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNameValid", reflect.TypeOf((*MockAnyPpClientService)(nil).IsNameValid), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNameValid", reflect.TypeOf((*MockAnyPpClientService)(nil).IsNameValid), arg0, arg1)
}
// Name mocks base method.
@ -176,31 +174,31 @@ func (mr *MockAnyPpClientServiceMockRecorder) Name() *gomock.Call {
}
// VerifyAppStoreReceipt mocks base method.
func (m *MockAnyPpClientService) VerifyAppStoreReceipt(ctx context.Context, in *paymentserviceproto.VerifyAppStoreReceiptRequestSigned) (*paymentserviceproto.VerifyAppStoreReceiptResponse, error) {
func (m *MockAnyPpClientService) VerifyAppStoreReceipt(arg0 context.Context, arg1 *paymentserviceproto.VerifyAppStoreReceiptRequestSigned) (*paymentserviceproto.VerifyAppStoreReceiptResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VerifyAppStoreReceipt", ctx, in)
ret := m.ctrl.Call(m, "VerifyAppStoreReceipt", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.VerifyAppStoreReceiptResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// VerifyAppStoreReceipt indicates an expected call of VerifyAppStoreReceipt.
func (mr *MockAnyPpClientServiceMockRecorder) VerifyAppStoreReceipt(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) VerifyAppStoreReceipt(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAppStoreReceipt", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyAppStoreReceipt), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAppStoreReceipt", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyAppStoreReceipt), arg0, arg1)
}
// VerifyEmail mocks base method.
func (m *MockAnyPpClientService) VerifyEmail(ctx context.Context, in *paymentserviceproto.VerifyEmailRequestSigned) (*paymentserviceproto.VerifyEmailResponse, error) {
func (m *MockAnyPpClientService) VerifyEmail(arg0 context.Context, arg1 *paymentserviceproto.VerifyEmailRequestSigned) (*paymentserviceproto.VerifyEmailResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VerifyEmail", ctx, in)
ret := m.ctrl.Call(m, "VerifyEmail", arg0, arg1)
ret0, _ := ret[0].(*paymentserviceproto.VerifyEmailResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// VerifyEmail indicates an expected call of VerifyEmail.
func (mr *MockAnyPpClientServiceMockRecorder) VerifyEmail(ctx, in any) *gomock.Call {
func (mr *MockAnyPpClientServiceMockRecorder) VerifyEmail(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyEmail), ctx, in)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyEmail), arg0, arg1)
}

View file

@ -2,8 +2,10 @@ package crypto
import (
"encoding/base64"
"github.com/anyproto/any-sync/util/strkey"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/anyproto/any-sync/util/strkey"
)
func EncodeKeyToString[T Key](key T) (str string, err error) {
@ -55,3 +57,11 @@ func DecodePeerId(peerId string) (PubKey, error) {
}
return UnmarshalEd25519PublicKey(raw)
}
func DecodeNetworkId(networkId string) (PubKey, error) {
pubKeyRaw, err := strkey.Decode(strkey.NetworkAddressVersionByte, networkId)
if err != nil {
return nil, err
}
return UnmarshalEd25519PublicKey(pubKeyRaw)
}

View file

@ -0,0 +1,18 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestDecodeNetworkId(t *testing.T) {
_, pubKey, err := GenerateRandomEd25519KeyPair()
require.NoError(t, err)
networkId := pubKey.Network()
require.Equal(t, uint8('N'), networkId[0])
decodedKey, err := DecodeNetworkId(networkId)
require.NoError(t, err)
require.Equal(t, pubKey, decodedKey)
}