Compare commits
No commits in common. "main" and "yamux" have entirely different histories.
28
.github/workflows/coverage.yml
vendored
28
.github/workflows/coverage.yml
vendored
@ -17,20 +17,20 @@ jobs:
|
|||||||
- name: git config
|
- name: git config
|
||||||
run: git config --global url.https://${{ secrets.ANYTYPE_PAT }}@github.com/.insteadOf https://github.com/
|
run: git config --global url.https://${{ secrets.ANYTYPE_PAT }}@github.com/.insteadOf https://github.com/
|
||||||
|
|
||||||
# # cache {{
|
# cache {{
|
||||||
# - id: go-cache-paths
|
- id: go-cache-paths
|
||||||
# run: |
|
run: |
|
||||||
# echo "GOCACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
echo "GOCACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
||||||
# echo "GOMODCACHE=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
echo "GOMODCACHE=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
||||||
# - uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
# with:
|
with:
|
||||||
# path: |
|
path: |
|
||||||
# ${{ steps.go-cache-paths.outputs.GOCACHE }}
|
${{ steps.go-cache-paths.outputs.GOCACHE }}
|
||||||
# ${{ steps.go-cache-paths.outputs.GOMODCACHE }}
|
${{ steps.go-cache-paths.outputs.GOMODCACHE }}
|
||||||
# key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
|
||||||
# restore-keys: |
|
restore-keys: |
|
||||||
# ${{ runner.os }}-go-${{ matrix.go-version }}-
|
${{ runner.os }}-go-${{ matrix.go-version }}-
|
||||||
# # }}
|
# }}
|
||||||
|
|
||||||
- name: deps
|
- name: deps
|
||||||
run: make deps
|
run: make deps
|
||||||
|
|||||||
1
Makefile
1
Makefile
@ -20,7 +20,6 @@ proto:
|
|||||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
||||||
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
|
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
|
||||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
|
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
|
||||||
protoc --gogofaster_out=:. --go-drpc_out=protolib=github.com/gogo/protobuf:. consensus/consensusproto/protos/*.proto
|
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
go mod download
|
go mod download
|
||||||
|
|||||||
43
README.md
43
README.md
@ -1,43 +0,0 @@
|
|||||||
# Any-Sync
|
|
||||||
Any-Sync is an open-source protocol designed to create high-performance, local-first, peer-to-peer, end-to-end encrypted applications that facilitate seamless collaboration among multiple users and devices.
|
|
||||||
|
|
||||||
By utilizing this protocol, users can rest assured that they retain complete control over their data and digital experience. They are empowered to freely transition between various service providers, or even opt to self-host the applications.
|
|
||||||
|
|
||||||
This ensures utmost flexibility and autonomy for users in managing their personal information and digital interactions.
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
Most existing information management tools are implemented on centralized client-server architecture or designed for an offline-first single-user usage. Either way there are trade-offs for users: they can face restricted freedoms and privacy violations or compromise on the functionality of tools to avoid this.
|
|
||||||
|
|
||||||
We believe this goes against fundamental digital freedoms and that a new generation of software is needed that will respect these freedoms, while providing best in-class user experience.
|
|
||||||
|
|
||||||
Our goal with `any-sync` is to develop a protocol that will enable the deployment of this software.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Conflict-free data replication across multiple devices and agents
|
|
||||||
- Built-in end-to-end encryption
|
|
||||||
- Cryptographically verifiable history of changes
|
|
||||||
- Adoption to frequent operations (high performance)
|
|
||||||
- Reliable and scalable infrastructure
|
|
||||||
- Simultaneous support of p2p and remote communication
|
|
||||||
|
|
||||||
## Protocol explanation
|
|
||||||
Plese read the [overview](https://tech.anytype.io/any-sync/overview) of protocol entities and design.
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
You can find the various parts of the protocol implemented in Go in the following repositories:
|
|
||||||
- [`any-sync-node`](https://github.com/anyproto/any-sync-node) — implementation of a sync node responsible for storing spaces and objects.
|
|
||||||
- [`any-sync-filenode`](https://github.com/anyproto/any-sync-filenode) — implementation of a file node responsible for storing files.
|
|
||||||
- [`any-sync-coordinator`](https://github.com/anyproto/any-sync-coordinator) — implementation of a coordinator node responsible for network configuration management.
|
|
||||||
|
|
||||||
## Contribution
|
|
||||||
Thank you for your desire to develop Anytype together.
|
|
||||||
|
|
||||||
Currently, we're not ready to accept PRs, but we will in the nearest future.
|
|
||||||
|
|
||||||
Follow us on [Github](https://github.com/anyproto) and join the [Contributors Community](https://github.com/orgs/anyproto/discussions).
|
|
||||||
|
|
||||||
---
|
|
||||||
Made by Any — a Swiss association 🇨🇭
|
|
||||||
|
|
||||||
Licensed under [MIT License](./LICENSE).
|
|
||||||
@ -3,7 +3,7 @@ package mock_accountservice
|
|||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||||
"go.uber.org/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
app "github.com/anyproto/any-sync/app"
|
||||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockService is a mock of Service interface.
|
// MockService is a mock of Service interface.
|
||||||
|
|||||||
64
app/app.go
64
app/app.go
@ -55,13 +55,11 @@ type ComponentStatable interface {
|
|||||||
// App is the central part of the application
|
// App is the central part of the application
|
||||||
// It contains and manages all components
|
// It contains and manages all components
|
||||||
type App struct {
|
type App struct {
|
||||||
parent *App
|
|
||||||
components []Component
|
components []Component
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
startStat Stat
|
startStat Stat
|
||||||
stopStat Stat
|
stopStat Stat
|
||||||
deviceState int
|
deviceState int
|
||||||
versionName string
|
|
||||||
anySyncVersion string
|
anySyncVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,19 +77,6 @@ func (app *App) Version() string {
|
|||||||
return GitSummary
|
return GitSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVersionName sets the custom application version
|
|
||||||
func (app *App) SetVersionName(v string) {
|
|
||||||
app.versionName = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionName returns a string with the settled app version or auto-generated version if it didn't set
|
|
||||||
func (app *App) VersionName() string {
|
|
||||||
if app.versionName != "" {
|
|
||||||
return app.versionName
|
|
||||||
}
|
|
||||||
return AppName + ":" + GitSummary + "/any-sync:" + app.AnySyncVersion()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stat struct {
|
type Stat struct {
|
||||||
SpentMsPerComp map[string]int64
|
SpentMsPerComp map[string]int64
|
||||||
SpentMsTotal int64
|
SpentMsTotal int64
|
||||||
@ -124,16 +109,6 @@ func VersionDescription() string {
|
|||||||
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChildApp creates a child container which has access to parent's components
|
|
||||||
// It doesn't call Start on any of the parent's components
|
|
||||||
func (app *App) ChildApp() *App {
|
|
||||||
return &App{
|
|
||||||
parent: app,
|
|
||||||
deviceState: app.deviceState,
|
|
||||||
anySyncVersion: app.AnySyncVersion(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register adds service to registry
|
// Register adds service to registry
|
||||||
// All components will be started in the order they were registered
|
// All components will be started in the order they were registered
|
||||||
func (app *App) Register(s Component) *App {
|
func (app *App) Register(s Component) *App {
|
||||||
@ -153,14 +128,10 @@ func (app *App) Register(s Component) *App {
|
|||||||
func (app *App) Component(name string) Component {
|
func (app *App) Component(name string) Component {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
current := app
|
for _, s := range app.components {
|
||||||
for current != nil {
|
if s.Name() == name {
|
||||||
for _, s := range current.components {
|
return s
|
||||||
if s.Name() == name {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
current = current.parent
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -178,14 +149,10 @@ func (app *App) MustComponent(name string) Component {
|
|||||||
func MustComponent[i any](app *App) i {
|
func MustComponent[i any](app *App) i {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
current := app
|
for _, s := range app.components {
|
||||||
for current != nil {
|
if v, ok := s.(i); ok {
|
||||||
for _, s := range current.components {
|
return v
|
||||||
if v, ok := s.(i); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
current = current.parent
|
|
||||||
}
|
}
|
||||||
empty := new(i)
|
empty := new(i)
|
||||||
panic(fmt.Errorf("component with interface %T is not found", empty))
|
panic(fmt.Errorf("component with interface %T is not found", empty))
|
||||||
@ -195,13 +162,9 @@ func MustComponent[i any](app *App) i {
|
|||||||
func (app *App) ComponentNames() (names []string) {
|
func (app *App) ComponentNames() (names []string) {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
names = make([]string, 0, len(app.components))
|
names = make([]string, len(app.components))
|
||||||
current := app
|
for i, c := range app.components {
|
||||||
for current != nil {
|
names[i] = c.Name()
|
||||||
for _, c := range current.components {
|
|
||||||
names = append(names, c.Name())
|
|
||||||
}
|
|
||||||
current = current.parent
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -262,15 +225,6 @@ func (app *App) Start(ctx context.Context) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterateComponents iterates over all registered components. It's safe for concurrent use.
|
|
||||||
func (app *App) IterateComponents(fn func(Component)) {
|
|
||||||
app.mu.RLock()
|
|
||||||
defer app.mu.RUnlock()
|
|
||||||
for _, s := range app.components {
|
|
||||||
fn(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackAllGoroutines() []byte {
|
func stackAllGoroutines() []byte {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
|
|||||||
@ -34,40 +34,6 @@ func TestAppServiceRegistry(t *testing.T) {
|
|||||||
names := app.ComponentNames()
|
names := app.ComponentNames()
|
||||||
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
||||||
})
|
})
|
||||||
t.Run("Child MustComponent", func(t *testing.T) {
|
|
||||||
app := app.ChildApp()
|
|
||||||
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
|
||||||
for _, name := range []string{"c1", "r1", "s1", "x1"} {
|
|
||||||
assert.NotPanics(t, func() { app.MustComponent(name) }, name)
|
|
||||||
}
|
|
||||||
assert.Panics(t, func() { app.MustComponent("not-registered") })
|
|
||||||
})
|
|
||||||
t.Run("Child ComponentNames", func(t *testing.T) {
|
|
||||||
app := app.ChildApp()
|
|
||||||
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
|
||||||
names := app.ComponentNames()
|
|
||||||
assert.Equal(t, names, []string{"x1", "c1", "r1", "s1"})
|
|
||||||
})
|
|
||||||
t.Run("Child override", func(t *testing.T) {
|
|
||||||
app := app.ChildApp()
|
|
||||||
app.Register(newTestService(testTypeRunnable, "s1", nil, nil))
|
|
||||||
_ = app.MustComponent("s1").(*testRunnable)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApp_IterateComponents(t *testing.T) {
|
|
||||||
app := new(App)
|
|
||||||
|
|
||||||
app.Register(newTestService(testTypeRunnable, "c1", nil, nil))
|
|
||||||
app.Register(newTestService(testTypeRunnable, "r1", nil, nil))
|
|
||||||
app.Register(newTestService(testTypeComponent, "s1", nil, nil))
|
|
||||||
|
|
||||||
var got []string
|
|
||||||
app.IterateComponents(func(s Component) {
|
|
||||||
got = append(got, s.Name())
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.ElementsMatch(t, []string{"c1", "r1", "s1"}, got)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppStart(t *testing.T) {
|
func TestAppStart(t *testing.T) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
ldiff "github.com/anyproto/any-sync/app/ldiff"
|
ldiff "github.com/anyproto/any-sync/app/ldiff"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockDiff is a mock of Diff interface.
|
// MockDiff is a mock of Diff interface.
|
||||||
|
|||||||
@ -2,10 +2,9 @@ package ocache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type entryState int
|
type entryState int
|
||||||
@ -26,7 +25,6 @@ type entry struct {
|
|||||||
value Object
|
value Object
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
mx sync.Mutex
|
mx sync.Mutex
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEntry(id string, value Object, state entryState) *entry {
|
func newEntry(id string, value Object, state entryState) *entry {
|
||||||
@ -51,20 +49,6 @@ func (e *entry) isClosing() bool {
|
|||||||
return e.state == entryStateClosed || e.state == entryStateClosing
|
return e.state == entryStateClosed || e.state == entryStateClosing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entry) setCancel(cancel context.CancelFunc) {
|
|
||||||
e.mx.Lock()
|
|
||||||
defer e.mx.Unlock()
|
|
||||||
e.cancel = cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) cancelLoad() {
|
|
||||||
e.mx.Lock()
|
|
||||||
defer e.mx.Unlock()
|
|
||||||
if e.cancel != nil {
|
|
||||||
e.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
|
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|||||||
@ -3,11 +3,10 @@ package ocache
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -158,10 +157,7 @@ func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error)
|
|||||||
|
|
||||||
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
||||||
defer close(e.load)
|
defer close(e.load)
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
e.setCancel(cancel)
|
|
||||||
value, err := c.loadFunc(ctx, id)
|
value, err := c.loadFunc(ctx, id)
|
||||||
cancel()
|
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
@ -319,7 +315,6 @@ func (c *oCache) Close() (err error) {
|
|||||||
close(c.closeCh)
|
close(c.closeCh)
|
||||||
var toClose []*entry
|
var toClose []*entry
|
||||||
for _, e := range c.data {
|
for _, e := range c.data {
|
||||||
e.cancelLoad()
|
|
||||||
toClose = append(toClose, e)
|
toClose = append(toClose, e)
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|||||||
@ -386,25 +386,6 @@ func Test_OCache_Remove(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOCacheCancelWhenRemove(t *testing.T) {
|
|
||||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}, WithTTL(time.Millisecond*10))
|
|
||||||
stopLoad := make(chan struct{})
|
|
||||||
var err error
|
|
||||||
go func() {
|
|
||||||
_, err = c.Get(context.TODO(), "id")
|
|
||||||
stopLoad <- struct{}{}
|
|
||||||
}()
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
c.Close()
|
|
||||||
<-stopLoad
|
|
||||||
require.Equal(t, context.Canceled, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOCacheFuzzy(t *testing.T) {
|
func TestOCacheFuzzy(t *testing.T) {
|
||||||
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
||||||
tryCloseIds := make(map[string]bool)
|
tryCloseIds := make(map[string]bool)
|
||||||
|
|||||||
62
commonspace/commongetter.go
Normal file
62
commonspace/commongetter.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package commonspace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commonGetter struct {
|
||||||
|
treemanager.TreeManager
|
||||||
|
spaceId string
|
||||||
|
reservedObjects []syncobjectgetter.SyncObject
|
||||||
|
spaceIsClosed *atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommonGetter(spaceId string, getter treemanager.TreeManager, spaceIsClosed *atomic.Bool) *commonGetter {
|
||||||
|
return &commonGetter{
|
||||||
|
TreeManager: getter,
|
||||||
|
spaceId: spaceId,
|
||||||
|
spaceIsClosed: spaceIsClosed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) AddObject(object syncobjectgetter.SyncObject) {
|
||||||
|
c.reservedObjects = append(c.reservedObjects, object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
||||||
|
if c.spaceIsClosed.Load() {
|
||||||
|
return nil, ErrSpaceClosed
|
||||||
|
}
|
||||||
|
if obj := c.getReservedObject(treeId); obj != nil {
|
||||||
|
return obj.(objecttree.ObjectTree), nil
|
||||||
|
}
|
||||||
|
return c.TreeManager.GetTree(ctx, spaceId, treeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
|
||||||
|
for _, obj := range c.reservedObjects {
|
||||||
|
if obj != nil && obj.Id() == id {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||||
|
if c.spaceIsClosed.Load() {
|
||||||
|
return nil, ErrSpaceClosed
|
||||||
|
}
|
||||||
|
if obj := c.getReservedObject(objectId); obj != nil {
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
t, err := c.TreeManager.GetTree(ctx, c.spaceId, objectId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj = t.(syncobjectgetter.SyncObject)
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package config
|
package commonspace
|
||||||
|
|
||||||
type ConfigGetter interface {
|
type ConfigGetter interface {
|
||||||
GetSpace() Config
|
GetSpace() Config
|
||||||
@ -3,7 +3,6 @@ package credentialprovider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,21 +13,12 @@ func NewNoOp() CredentialProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CredentialProvider interface {
|
type CredentialProvider interface {
|
||||||
app.Component
|
|
||||||
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
|
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type noOpProvider struct {
|
type noOpProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noOpProvider) Init(a *app.App) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n noOpProvider) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
|
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockCredentialProvider is a mock of CredentialProvider interface.
|
// MockCredentialProvider is a mock of CredentialProvider interface.
|
||||||
@ -50,31 +49,3 @@ func (mr *MockCredentialProviderMockRecorder) GetCredential(arg0, arg1 interface
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredential", reflect.TypeOf((*MockCredentialProvider)(nil).GetCredential), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredential", reflect.TypeOf((*MockCredentialProvider)(nil).GetCredential), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockCredentialProvider) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockCredentialProviderMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockCredentialProvider)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockCredentialProvider) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockCredentialProviderMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockCredentialProvider)(nil).Name))
|
|
||||||
}
|
|
||||||
|
|||||||
@ -73,14 +73,13 @@ func TestSpaceDeleteIds(t *testing.T) {
|
|||||||
fx.treeManager.space = spc
|
fx.treeManager.space = spc
|
||||||
err = spc.Init(ctx)
|
err = spc.Init(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
var ids []string
|
var ids []string
|
||||||
for i := 0; i < totalObjs; i++ {
|
for i := 0; i < totalObjs; i++ {
|
||||||
// creating a tree
|
// creating a tree
|
||||||
bytes := make([]byte, 32)
|
bytes := make([]byte, 32)
|
||||||
rand.Read(bytes)
|
rand.Read(bytes)
|
||||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
doc, err := spc.CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
||||||
PrivKey: acc.SignKey,
|
PrivKey: acc.SignKey,
|
||||||
ChangeType: "some",
|
ChangeType: "some",
|
||||||
SpaceId: spc.Id(),
|
SpaceId: spc.Id(),
|
||||||
@ -89,7 +88,7 @@ func TestSpaceDeleteIds(t *testing.T) {
|
|||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
tr, err := spc.PutTree(ctx, doc, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ids = append(ids, tr.Id())
|
ids = append(ids, tr.Id())
|
||||||
tr.Close()
|
tr.Close()
|
||||||
@ -107,7 +106,7 @@ func TestSpaceDeleteIds(t *testing.T) {
|
|||||||
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
|
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
|
||||||
bytes := make([]byte, 32)
|
bytes := make([]byte, 32)
|
||||||
rand.Read(bytes)
|
rand.Read(bytes)
|
||||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
doc, err := spc.CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
||||||
PrivKey: acc.SignKey,
|
PrivKey: acc.SignKey,
|
||||||
ChangeType: "some",
|
ChangeType: "some",
|
||||||
SpaceId: spc.Id(),
|
SpaceId: spc.Id(),
|
||||||
@ -116,7 +115,7 @@ func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.A
|
|||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
tr, err := spc.PutTree(ctx, doc, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tr.Close()
|
tr.Close()
|
||||||
return tr.Id()
|
return tr.Id()
|
||||||
@ -148,10 +147,9 @@ func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
|
|||||||
// adding space to tree manager
|
// adding space to tree manager
|
||||||
fx.treeManager.space = spc
|
fx.treeManager.space = spc
|
||||||
err = spc.Init(ctx)
|
err = spc.Init(ctx)
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
settingsObject := spc.(*space).settingsObject
|
||||||
var ids []string
|
var ids []string
|
||||||
for i := 0; i < totalObjs; i++ {
|
for i := 0; i < totalObjs; i++ {
|
||||||
id := createTree(t, ctx, spc, acc)
|
id := createTree(t, ctx, spc, acc)
|
||||||
@ -185,19 +183,17 @@ func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
|
|||||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, spc)
|
require.NotNil(t, spc)
|
||||||
fx.treeManager.waitLoad = make(chan struct{})
|
|
||||||
fx.treeManager.space = spc
|
fx.treeManager.space = spc
|
||||||
fx.treeManager.deletedIds = nil
|
fx.treeManager.deletedIds = nil
|
||||||
err = spc.Init(ctx)
|
err = spc.Init(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
// waiting until everything is deleted
|
// waiting until everything is deleted
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
||||||
|
|
||||||
// checking that new snapshot will contain all the changes
|
// checking that new snapshot will contain all the changes
|
||||||
settingsObject = spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
settingsObject = spc.(*space).settingsObject
|
||||||
settings.DoSnapshot = func(treeLen int) bool {
|
settings.DoSnapshot = func(treeLen int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -234,9 +230,8 @@ func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
|||||||
fx.treeManager.space = spc
|
fx.treeManager.space = spc
|
||||||
err = spc.Init(ctx)
|
err = spc.Init(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
settingsObject := spc.(*space).settingsObject
|
||||||
var ids []string
|
var ids []string
|
||||||
for i := 0; i < totalObjs; i++ {
|
for i := 0; i < totalObjs; i++ {
|
||||||
id := createTree(t, ctx, spc, acc)
|
id := createTree(t, ctx, spc, acc)
|
||||||
@ -264,12 +259,10 @@ func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, spc)
|
require.NotNil(t, spc)
|
||||||
fx.treeManager.space = spc
|
fx.treeManager.space = spc
|
||||||
fx.treeManager.waitLoad = make(chan struct{})
|
|
||||||
fx.treeManager.deletedIds = nil
|
fx.treeManager.deletedIds = nil
|
||||||
fx.treeManager.markedIds = nil
|
fx.treeManager.markedIds = nil
|
||||||
err = spc.Init(ctx)
|
err = spc.Init(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
// waiting until everything is deleted
|
// waiting until everything is deleted
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
|||||||
@ -1,144 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/deletionstate (interfaces: ObjectDeletionState)
|
|
||||||
|
|
||||||
// Package mock_deletionstate is a generated GoMock package.
|
|
||||||
package mock_deletionstate
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
|
||||||
deletionstate "github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
|
|
||||||
type MockObjectDeletionState struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockObjectDeletionStateMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockObjectDeletionStateMockRecorder is the mock recorder for MockObjectDeletionState.
|
|
||||||
type MockObjectDeletionStateMockRecorder struct {
|
|
||||||
mock *MockObjectDeletionState
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockObjectDeletionState creates a new mock instance.
|
|
||||||
func NewMockObjectDeletionState(ctrl *gomock.Controller) *MockObjectDeletionState {
|
|
||||||
mock := &MockObjectDeletionState{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockObjectDeletionStateMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockObjectDeletionState) EXPECT() *MockObjectDeletionStateMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Add(arg0 map[string]struct{}) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Add", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add indicates an expected call of Add.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockObjectDeletionState)(nil).Add), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddObserver mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) AddObserver(arg0 deletionstate.StateUpdateObserver) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "AddObserver", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddObserver indicates an expected call of AddObserver.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockObjectDeletionState)(nil).AddObserver), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Delete(arg0 string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectDeletionState)(nil).Delete), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Exists(arg0 string) bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Exists", arg0)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists indicates an expected call of Exists.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockObjectDeletionState)(nil).Exists), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Filter(arg0 []string) []string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Filter", arg0)
|
|
||||||
ret0, _ := ret[0].([]string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter indicates an expected call of Filter.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Filter(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockObjectDeletionState)(nil).Filter), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueued mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) GetQueued() []string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetQueued")
|
|
||||||
ret0, _ := ret[0].([]string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueued indicates an expected call of GetQueued.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) GetQueued() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockObjectDeletionState)(nil).GetQueued))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectDeletionState)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectDeletionState)(nil).Name))
|
|
||||||
}
|
|
||||||
@ -3,45 +3,49 @@ package headsync
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anyproto/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiffSyncer interface {
|
type DiffSyncer interface {
|
||||||
Sync(ctx context.Context) error
|
Sync(ctx context.Context) error
|
||||||
RemoveObjects(ids []string)
|
RemoveObjects(ids []string)
|
||||||
UpdateHeads(id string, heads []string)
|
UpdateHeads(id string, heads []string)
|
||||||
Init()
|
Init(deletionState settingsstate.ObjectDeletionState)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDiffSyncer(hs *headSync) DiffSyncer {
|
func newDiffSyncer(
|
||||||
|
spaceId string,
|
||||||
|
diff ldiff.Diff,
|
||||||
|
peerManager peermanager.PeerManager,
|
||||||
|
cache treemanager.TreeManager,
|
||||||
|
storage spacestorage.SpaceStorage,
|
||||||
|
clientFactory spacesyncproto.ClientFactory,
|
||||||
|
syncStatus syncstatus.StatusUpdater,
|
||||||
|
credentialProvider credentialprovider.CredentialProvider,
|
||||||
|
log logger.CtxLogger) DiffSyncer {
|
||||||
return &diffSyncer{
|
return &diffSyncer{
|
||||||
diff: hs.diff,
|
diff: diff,
|
||||||
spaceId: hs.spaceId,
|
spaceId: spaceId,
|
||||||
treeManager: hs.treeManager,
|
treeManager: cache,
|
||||||
storage: hs.storage,
|
storage: storage,
|
||||||
peerManager: hs.peerManager,
|
peerManager: peerManager,
|
||||||
clientFactory: spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient),
|
clientFactory: clientFactory,
|
||||||
credentialProvider: hs.credentialProvider,
|
credentialProvider: credentialProvider,
|
||||||
log: log,
|
log: log,
|
||||||
syncStatus: hs.syncStatus,
|
syncStatus: syncStatus,
|
||||||
deletionState: hs.deletionState,
|
|
||||||
syncAcl: hs.syncAcl,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,14 +57,14 @@ type diffSyncer struct {
|
|||||||
storage spacestorage.SpaceStorage
|
storage spacestorage.SpaceStorage
|
||||||
clientFactory spacesyncproto.ClientFactory
|
clientFactory spacesyncproto.ClientFactory
|
||||||
log logger.CtxLogger
|
log logger.CtxLogger
|
||||||
deletionState deletionstate.ObjectDeletionState
|
deletionState settingsstate.ObjectDeletionState
|
||||||
credentialProvider credentialprovider.CredentialProvider
|
credentialProvider credentialprovider.CredentialProvider
|
||||||
syncStatus syncstatus.StatusUpdater
|
syncStatus syncstatus.StatusUpdater
|
||||||
treeSyncer treemanager.TreeSyncer
|
treeSyncer treemanager.TreeSyncer
|
||||||
syncAcl syncacl.SyncAcl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diffSyncer) Init() {
|
func (d *diffSyncer) Init(deletionState settingsstate.ObjectDeletionState) {
|
||||||
|
d.deletionState = deletionState
|
||||||
d.deletionState.AddObserver(d.RemoveObjects)
|
d.deletionState.AddObserver(d.RemoveObjects)
|
||||||
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
|
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
|
||||||
}
|
}
|
||||||
@ -111,17 +115,10 @@ func (d *diffSyncer) Sync(ctx context.Context) error {
|
|||||||
|
|
||||||
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
||||||
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
|
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
|
||||||
conn, err := p.AcquireDrpcConn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer p.ReleaseDrpcConn(conn)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cl = d.clientFactory.Client(conn)
|
cl = d.clientFactory.Client(p)
|
||||||
rdiff = NewRemoteDiff(d.spaceId, cl)
|
rdiff = NewRemoteDiff(d.spaceId, cl)
|
||||||
stateCounter = d.syncStatus.StateCounter()
|
stateCounter = d.syncStatus.StateCounter()
|
||||||
syncAclId = d.syncAcl.Id()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
||||||
@ -144,29 +141,17 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
|||||||
// not syncing ids which were removed through settings document
|
// not syncing ids which were removed through settings document
|
||||||
missingIds := d.deletionState.Filter(newIds)
|
missingIds := d.deletionState.Filter(newIds)
|
||||||
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
||||||
|
|
||||||
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
||||||
|
|
||||||
prevExistingLen := len(existingIds)
|
|
||||||
existingIds = slice.DiscardFromSlice(existingIds, func(s string) bool {
|
|
||||||
return s == syncAclId
|
|
||||||
})
|
|
||||||
// if we removed acl head from the list
|
|
||||||
if len(existingIds) < prevExistingLen {
|
|
||||||
if syncErr := d.syncAcl.SyncWithPeer(ctx, p.Id()); syncErr != nil {
|
|
||||||
log.Warn("failed to send acl sync message to peer", zap.String("aclId", syncAclId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// treeSyncer should not get acl id, that's why we filter existing ids before
|
|
||||||
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
|
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.log.Info("sync done:",
|
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
|
||||||
zap.Int("newIds", len(newIds)),
|
|
||||||
zap.Int("changedIds", len(changedIds)),
|
zap.Int("changedIds", len(changedIds)),
|
||||||
zap.Int("removedIds", len(removedIds)),
|
zap.Int("removedIds", len(removedIds)),
|
||||||
zap.Int("already deleted ids", totalLen-prevExistingLen-len(missingIds)),
|
zap.Int("already deleted ids", totalLen-len(existingIds)-len(missingIds)),
|
||||||
zap.String("peerId", p.Id()),
|
zap.String("peerId", p.Id()),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -4,19 +4,28 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anyproto/any-sync/app/ldiff"
|
||||||
|
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/credentialprovider/mock_credentialprovider"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
mock_treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/libp2p/go-libp2p/core/sec"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"storj.io/drpc"
|
"storj.io/drpc"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pushSpaceRequestMatcher struct {
|
type pushSpaceRequestMatcher struct {
|
||||||
@ -27,6 +36,60 @@ type pushSpaceRequestMatcher struct {
|
|||||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
||||||
|
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pushSpaceRequestMatcher) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPeer struct{}
|
||||||
|
|
||||||
|
func (m mockPeer) Addr() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||||
|
return true, m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Id() string {
|
||||||
|
return "mockId"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) LastUsage() time.Time {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Secure() sec.SecureConn {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) UpdateLastUsage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Closed() <-chan struct{} {
|
||||||
|
return make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newPushSpaceRequestMatcher(
|
func newPushSpaceRequestMatcher(
|
||||||
spaceId string,
|
spaceId string,
|
||||||
aclRootId string,
|
aclRootId string,
|
||||||
@ -42,159 +105,81 @@ func newPushSpaceRequestMatcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
func TestDiffSyncer_Sync(t *testing.T) {
|
||||||
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
// setup
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p pushSpaceRequestMatcher) String() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockPeer struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) Id() string {
|
|
||||||
return "peerId"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) Context() context.Context {
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) ReleaseDrpcConn(conn drpc.Conn) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) DoDrpc(ctx context.Context, do func(conn drpc.Conn) error) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) IsClosed() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeer) Close() (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *headSyncFixture) initDiffSyncer(t *testing.T) {
|
|
||||||
fx.init(t)
|
|
||||||
fx.diffSyncer = newDiffSyncer(fx.headSync).(*diffSyncer)
|
|
||||||
fx.diffSyncer.clientFactory = spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
|
||||||
return fx.clientMock
|
|
||||||
})
|
|
||||||
fx.deletionStateMock.EXPECT().AddObserver(gomock.Any())
|
|
||||||
fx.treeManagerMock.EXPECT().NewTreeSyncer(fx.spaceState.SpaceId, fx.treeManagerMock).Return(fx.treeSyncerMock)
|
|
||||||
fx.diffSyncer.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiffSyncer(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||||
|
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
||||||
|
cacheMock := mock_treemanager.NewMockTreeManager(ctrl)
|
||||||
|
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
|
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||||
|
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||||
|
return clientMock
|
||||||
|
})
|
||||||
|
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
|
||||||
|
credentialProvider := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
||||||
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
|
spaceId := "spaceId"
|
||||||
|
aclRootId := "aclRootId"
|
||||||
|
l := logger.NewNamed(spaceId)
|
||||||
|
diffSyncer := newDiffSyncer(spaceId, diffMock, peerManagerMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), credentialProvider, l)
|
||||||
|
delState.EXPECT().AddObserver(gomock.Any())
|
||||||
|
cacheMock.EXPECT().NewTreeSyncer(spaceId, gomock.Any()).Return(treeSyncerMock)
|
||||||
|
diffSyncer.Init(delState)
|
||||||
|
|
||||||
t.Run("diff syncer sync", func(t *testing.T) {
|
t.Run("diff syncer sync", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mPeer := mockPeer{}
|
mPeer := mockPeer{}
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
peerManagerMock.EXPECT().
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mPeer}, nil)
|
Return([]peer.Peer{mPeer}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
delState.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).Times(1)
|
delState.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).Times(1)
|
||||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
delState.EXPECT().Filter(nil).Return(nil).Times(1)
|
||||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("diff syncer sync, acl changed", func(t *testing.T) {
|
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mPeer := mockPeer{}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
|
||||||
Return([]peer.Peer{mPeer}, nil)
|
|
||||||
fx.diffMock.EXPECT().
|
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
|
||||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed", "aclId"}).Times(1)
|
|
||||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
|
||||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
|
||||||
fx.aclMock.EXPECT().SyncWithPeer(gomock.Any(), mPeer.Id()).Return(nil)
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
peerManagerMock.EXPECT().
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
ctx := context.Background()
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return(nil, fmt.Errorf("some error"))
|
Return(nil, fmt.Errorf("some error"))
|
||||||
|
|
||||||
require.Error(t, fx.diffSyncer.Sync(ctx))
|
require.Error(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deletion state remove objects", func(t *testing.T) {
|
t.Run("deletion state remove objects", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
deletedId := "id"
|
deletedId := "id"
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
delState.EXPECT().Exists(deletedId).Return(true)
|
||||||
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
|
|
||||||
|
|
||||||
// this should not result in any mock being called
|
// this should not result in any mock being called
|
||||||
fx.diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("update heads updates diff", func(t *testing.T) {
|
t.Run("update heads updates diff", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
newId := "newId"
|
newId := "newId"
|
||||||
newHeads := []string{"h1", "h2"}
|
newHeads := []string{"h1", "h2"}
|
||||||
hash := "hash"
|
hash := "hash"
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
diffMock.EXPECT().Set(ldiff.Element{
|
||||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
|
||||||
Id: newId,
|
Id: newId,
|
||||||
Head: concatStrings(newHeads),
|
Head: concatStrings(newHeads),
|
||||||
})
|
})
|
||||||
fx.diffMock.EXPECT().Hash().Return(hash)
|
diffMock.EXPECT().Hash().Return(hash)
|
||||||
fx.deletionStateMock.EXPECT().Exists(newId).Return(false)
|
delState.EXPECT().Exists(newId).Return(false)
|
||||||
fx.storageMock.EXPECT().WriteSpaceHash(hash)
|
stMock.EXPECT().WriteSpaceHash(hash)
|
||||||
fx.diffSyncer.UpdateHeads(newId, newHeads)
|
diffSyncer.UpdateHeads(newId, newHeads)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
|
||||||
fx.initDiffSyncer(t)
|
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||||
defer fx.stop()
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
|
|
||||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
|
||||||
settingsId := "settingsId"
|
settingsId := "settingsId"
|
||||||
aclRootId := "aclRootId"
|
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
||||||
aclRoot := &consensusproto.RawRecordWithId{
|
|
||||||
Id: aclRootId,
|
Id: aclRootId,
|
||||||
}
|
}
|
||||||
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
||||||
@ -204,63 +189,55 @@ func TestDiffSyncer(t *testing.T) {
|
|||||||
spaceSettingsId := "spaceSettingsId"
|
spaceSettingsId := "spaceSettingsId"
|
||||||
credential := []byte("credential")
|
credential := []byte("credential")
|
||||||
|
|
||||||
fx.peerManagerMock.EXPECT().
|
peerManagerMock.EXPECT().
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mockPeer{}}, nil)
|
Return([]peer.Peer{mockPeer{}}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
||||||
|
|
||||||
fx.storageMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
stMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
||||||
fx.storageMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
||||||
fx.storageMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
||||||
fx.storageMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
||||||
|
|
||||||
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
||||||
aclStorageMock.EXPECT().
|
aclStorageMock.EXPECT().
|
||||||
Root().
|
Root().
|
||||||
Return(aclRoot, nil)
|
Return(aclRoot, nil)
|
||||||
fx.credentialProviderMock.EXPECT().
|
credentialProvider.EXPECT().
|
||||||
GetCredential(gomock.Any(), spaceHeader).
|
GetCredential(gomock.Any(), spaceHeader).
|
||||||
Return(credential, nil)
|
Return(credential, nil)
|
||||||
fx.clientMock.EXPECT().
|
clientMock.EXPECT().
|
||||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(fx.spaceState.SpaceId, aclRootId, settingsId, credential, spaceHeader)).
|
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||||
Return(nil, nil)
|
Return(nil, nil)
|
||||||
fx.peerManagerMock.EXPECT().SendPeer(gomock.Any(), "peerId", gomock.Any())
|
peerManagerMock.EXPECT().SendPeer(gomock.Any(), "mockId", gomock.Any())
|
||||||
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
peerManagerMock.EXPECT().
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mockPeer{}}, nil)
|
Return([]peer.Peer{mockPeer{}}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
||||||
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mPeer := mockPeer{}
|
mPeer := mockPeer{}
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
peerManagerMock.EXPECT().
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mPeer}, nil)
|
Return([]peer.Peer{mPeer}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
|
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
|
||||||
fx.storageMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
stMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
||||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
|
treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
|
||||||
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,150 +3,123 @@ package headsync
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anyproto/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
config2 "github.com/anyproto/any-sync/commonspace/config"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
"github.com/anyproto/any-sync/util/periodicsync"
|
"github.com/anyproto/any-sync/util/periodicsync"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
const CName = "common.commonspace.headsync"
|
|
||||||
|
|
||||||
type TreeHeads struct {
|
type TreeHeads struct {
|
||||||
Id string
|
Id string
|
||||||
Heads []string
|
Heads []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadSync interface {
|
type HeadSync interface {
|
||||||
app.ComponentRunnable
|
Init(objectIds []string, deletionState settingsstate.ObjectDeletionState)
|
||||||
ExternalIds() []string
|
|
||||||
DebugAllHeads() (res []TreeHeads)
|
|
||||||
AllIds() []string
|
|
||||||
UpdateHeads(id string, heads []string)
|
UpdateHeads(id string, heads []string)
|
||||||
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
||||||
RemoveObjects(ids []string)
|
RemoveObjects(ids []string)
|
||||||
|
AllIds() []string
|
||||||
|
DebugAllHeads() (res []TreeHeads)
|
||||||
|
|
||||||
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type headSync struct {
|
type headSync struct {
|
||||||
spaceId string
|
spaceId string
|
||||||
|
periodicSync periodicsync.PeriodicSync
|
||||||
|
storage spacestorage.SpaceStorage
|
||||||
|
diff ldiff.Diff
|
||||||
|
log logger.CtxLogger
|
||||||
|
syncer DiffSyncer
|
||||||
|
configuration nodeconf.NodeConf
|
||||||
spaceIsDeleted *atomic.Bool
|
spaceIsDeleted *atomic.Bool
|
||||||
syncPeriod int
|
|
||||||
|
|
||||||
periodicSync periodicsync.PeriodicSync
|
syncPeriod int
|
||||||
storage spacestorage.SpaceStorage
|
|
||||||
diff ldiff.Diff
|
|
||||||
log logger.CtxLogger
|
|
||||||
syncer DiffSyncer
|
|
||||||
configuration nodeconf.NodeConf
|
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
treeManager treemanager.TreeManager
|
|
||||||
credentialProvider credentialprovider.CredentialProvider
|
|
||||||
syncStatus syncstatus.StatusService
|
|
||||||
deletionState deletionstate.ObjectDeletionState
|
|
||||||
syncAcl syncacl.SyncAcl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() HeadSync {
|
func NewHeadSync(
|
||||||
return &headSync{}
|
spaceId string,
|
||||||
}
|
spaceIsDeleted *atomic.Bool,
|
||||||
|
syncPeriod int,
|
||||||
|
configuration nodeconf.NodeConf,
|
||||||
|
storage spacestorage.SpaceStorage,
|
||||||
|
peerManager peermanager.PeerManager,
|
||||||
|
cache treemanager.TreeManager,
|
||||||
|
syncStatus syncstatus.StatusUpdater,
|
||||||
|
credentialProvider credentialprovider.CredentialProvider,
|
||||||
|
log logger.CtxLogger) HeadSync {
|
||||||
|
|
||||||
var createDiffSyncer = newDiffSyncer
|
diff := ldiff.New(16, 16)
|
||||||
|
l := log.With(zap.String("spaceId", spaceId))
|
||||||
func (h *headSync) Init(a *app.App) (err error) {
|
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
||||||
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
syncer := newDiffSyncer(spaceId, diff, peerManager, cache, storage, factory, syncStatus, credentialProvider, l)
|
||||||
cfg := a.MustComponent("config").(config2.ConfigGetter)
|
|
||||||
h.syncAcl = a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
|
||||||
h.spaceId = shared.SpaceId
|
|
||||||
h.spaceIsDeleted = shared.SpaceIsDeleted
|
|
||||||
h.syncPeriod = cfg.GetSpace().SyncPeriod
|
|
||||||
h.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
|
||||||
h.log = log.With(zap.String("spaceId", h.spaceId))
|
|
||||||
h.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
h.diff = ldiff.New(16, 16)
|
|
||||||
h.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
|
||||||
h.credentialProvider = a.MustComponent(credentialprovider.CName).(credentialprovider.CredentialProvider)
|
|
||||||
h.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
|
||||||
h.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
|
||||||
h.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
|
|
||||||
h.syncer = createDiffSyncer(h)
|
|
||||||
sync := func(ctx context.Context) (err error) {
|
sync := func(ctx context.Context) (err error) {
|
||||||
// for clients cancelling the sync process
|
// for clients cancelling the sync process
|
||||||
if h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
|
if spaceIsDeleted.Load() && !configuration.IsResponsible(spaceId) {
|
||||||
return spacesyncproto.ErrSpaceIsDeleted
|
return spacesyncproto.ErrSpaceIsDeleted
|
||||||
}
|
}
|
||||||
return h.syncer.Sync(ctx)
|
return syncer.Sync(ctx)
|
||||||
}
|
}
|
||||||
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, sync, l)
|
||||||
h.syncAcl.SetHeadUpdater(h)
|
|
||||||
// TODO: move to run?
|
|
||||||
h.syncer.Init()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headSync) Name() (name string) {
|
return &headSync{
|
||||||
return CName
|
spaceId: spaceId,
|
||||||
}
|
storage: storage,
|
||||||
|
syncer: syncer,
|
||||||
func (h *headSync) Run(ctx context.Context) (err error) {
|
periodicSync: periodicSync,
|
||||||
initialIds, err := h.storage.StoredIds()
|
diff: diff,
|
||||||
if err != nil {
|
log: log,
|
||||||
return
|
syncPeriod: syncPeriod,
|
||||||
|
configuration: configuration,
|
||||||
|
spaceIsDeleted: spaceIsDeleted,
|
||||||
}
|
}
|
||||||
h.fillDiff(initialIds)
|
|
||||||
h.periodicSync.Run()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
func (d *headSync) Init(objectIds []string, deletionState settingsstate.ObjectDeletionState) {
|
||||||
if h.spaceIsDeleted.Load() {
|
d.fillDiff(objectIds)
|
||||||
|
d.syncer.Init(deletionState)
|
||||||
|
d.periodicSync.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||||
|
if d.spaceIsDeleted.Load() {
|
||||||
peerId, err := peer.CtxPeerId(ctx)
|
peerId, err := peer.CtxPeerId(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// stop receiving all request for sync from clients
|
// stop receiving all request for sync from clients
|
||||||
if !slices.Contains(h.configuration.NodeIds(h.spaceId), peerId) {
|
if !slices.Contains(d.configuration.NodeIds(d.spaceId), peerId) {
|
||||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return HandleRangeRequest(ctx, h.diff, req)
|
return HandleRangeRequest(ctx, d.diff, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) UpdateHeads(id string, heads []string) {
|
func (d *headSync) UpdateHeads(id string, heads []string) {
|
||||||
h.syncer.UpdateHeads(id, heads)
|
d.syncer.UpdateHeads(id, heads)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) AllIds() []string {
|
func (d *headSync) AllIds() []string {
|
||||||
return h.diff.Ids()
|
return d.diff.Ids()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) ExternalIds() []string {
|
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||||
settingsId := h.storage.SpaceSettingsId()
|
els := d.diff.Elements()
|
||||||
return slice.DiscardFromSlice(h.AllIds(), func(id string) bool {
|
|
||||||
return id == settingsId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
|
||||||
els := h.diff.Elements()
|
|
||||||
for _, el := range els {
|
for _, el := range els {
|
||||||
idHead := TreeHeads{
|
idHead := TreeHeads{
|
||||||
Id: el.Id,
|
Id: el.Id,
|
||||||
@ -157,19 +130,19 @@ func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) RemoveObjects(ids []string) {
|
func (d *headSync) RemoveObjects(ids []string) {
|
||||||
h.syncer.RemoveObjects(ids)
|
d.syncer.RemoveObjects(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) Close(ctx context.Context) (err error) {
|
func (d *headSync) Close() (err error) {
|
||||||
h.periodicSync.Close()
|
d.periodicSync.Close()
|
||||||
return h.syncer.Close()
|
return d.syncer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) fillDiff(objectIds []string) {
|
func (d *headSync) fillDiff(objectIds []string) {
|
||||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||||
for _, id := range objectIds {
|
for _, id := range objectIds {
|
||||||
st, err := h.storage.TreeStorage(id)
|
st, err := d.storage.TreeStorage(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -182,12 +155,32 @@ func (h *headSync) fillDiff(objectIds []string) {
|
|||||||
Head: concatStrings(heads),
|
Head: concatStrings(heads),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
els = append(els, ldiff.Element{
|
d.diff.Set(els...)
|
||||||
Id: h.syncAcl.Id(),
|
if err := d.storage.WriteSpaceHash(d.diff.Hash()); err != nil {
|
||||||
Head: h.syncAcl.Head().Id,
|
d.log.Error("can't write space hash", zap.Error(err))
|
||||||
})
|
|
||||||
h.diff.Set(els...)
|
|
||||||
if err := h.storage.WriteSpaceHash(h.diff.Hash()); err != nil {
|
|
||||||
h.log.Error("can't write space hash", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func concatStrings(strs []string) string {
|
||||||
|
var (
|
||||||
|
b strings.Builder
|
||||||
|
totalLen int
|
||||||
|
)
|
||||||
|
for _, s := range strs {
|
||||||
|
totalLen += len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Grow(totalLen)
|
||||||
|
for _, s := range strs {
|
||||||
|
b.WriteString(s)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitString(str string) (res []string) {
|
||||||
|
const cidLen = 59
|
||||||
|
for i := 0; i < len(str); i += cidLen {
|
||||||
|
res = append(res, str[i:i+cidLen])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@ -1,190 +1,71 @@
|
|||||||
package headsync
|
package headsync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anyproto/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
|
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
|
||||||
"github.com/anyproto/any-sync/commonspace/config"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider/mock_credentialprovider"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync/mock_headsync"
|
"github.com/anyproto/any-sync/commonspace/headsync/mock_headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
"github.com/anyproto/any-sync/util/periodicsync/mock_periodicsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockConfig struct {
|
func TestDiffService(t *testing.T) {
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockConfig) Init(a *app.App) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockConfig) Name() (name string) {
|
|
||||||
return "config"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockConfig) GetSpace() config.Config {
|
|
||||||
return config.Config{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type headSyncFixture struct {
|
|
||||||
spaceState *spacestate.SpaceState
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
app *app.App
|
|
||||||
|
|
||||||
configurationMock *mock_nodeconf.MockService
|
|
||||||
storageMock *mock_spacestorage.MockSpaceStorage
|
|
||||||
peerManagerMock *mock_peermanager.MockPeerManager
|
|
||||||
credentialProviderMock *mock_credentialprovider.MockCredentialProvider
|
|
||||||
syncStatus syncstatus.StatusService
|
|
||||||
treeManagerMock *mock_treemanager.MockTreeManager
|
|
||||||
deletionStateMock *mock_deletionstate.MockObjectDeletionState
|
|
||||||
diffSyncerMock *mock_headsync.MockDiffSyncer
|
|
||||||
treeSyncerMock *mock_treemanager.MockTreeSyncer
|
|
||||||
diffMock *mock_ldiff.MockDiff
|
|
||||||
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
|
|
||||||
aclMock *mock_syncacl.MockSyncAcl
|
|
||||||
headSync *headSync
|
|
||||||
diffSyncer *diffSyncer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHeadSyncFixture(t *testing.T) *headSyncFixture {
|
|
||||||
spaceState := &spacestate.SpaceState{
|
|
||||||
SpaceId: "spaceId",
|
|
||||||
SpaceIsDeleted: &atomic.Bool{},
|
|
||||||
}
|
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
configurationMock := mock_nodeconf.NewMockService(ctrl)
|
defer ctrl.Finish()
|
||||||
configurationMock.EXPECT().Name().AnyTimes().Return(nodeconf.CName)
|
|
||||||
|
spaceId := "spaceId"
|
||||||
|
l := logger.NewNamed("sync")
|
||||||
|
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
|
||||||
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
storageMock.EXPECT().Name().AnyTimes().Return(spacestorage.CName)
|
treeStorageMock := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||||
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
|
||||||
peerManagerMock.EXPECT().Name().AnyTimes().Return(peermanager.CName)
|
|
||||||
credentialProviderMock := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
|
||||||
credentialProviderMock.EXPECT().Name().AnyTimes().Return(credentialprovider.CName)
|
|
||||||
syncStatus := syncstatus.NewNoOpSyncStatus()
|
|
||||||
treeManagerMock := mock_treemanager.NewMockTreeManager(ctrl)
|
|
||||||
treeManagerMock.EXPECT().Name().AnyTimes().Return(treemanager.CName)
|
|
||||||
deletionStateMock := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
|
||||||
deletionStateMock.EXPECT().Name().AnyTimes().Return(deletionstate.CName)
|
|
||||||
diffSyncerMock := mock_headsync.NewMockDiffSyncer(ctrl)
|
|
||||||
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
|
|
||||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
|
||||||
aclMock := mock_syncacl.NewMockSyncAcl(ctrl)
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
aclMock.EXPECT().Name().AnyTimes().Return(syncacl.CName)
|
syncPeriod := 1
|
||||||
aclMock.EXPECT().SetHeadUpdater(gomock.Any()).AnyTimes()
|
initId := "initId"
|
||||||
hs := &headSync{}
|
|
||||||
a := &app.App{}
|
service := &headSync{
|
||||||
a.Register(spaceState).
|
spaceId: spaceId,
|
||||||
Register(aclMock).
|
storage: storageMock,
|
||||||
Register(mockConfig{}).
|
periodicSync: pSyncMock,
|
||||||
Register(configurationMock).
|
syncer: syncer,
|
||||||
Register(storageMock).
|
diff: diffMock,
|
||||||
Register(peerManagerMock).
|
log: l,
|
||||||
Register(credentialProviderMock).
|
syncPeriod: syncPeriod,
|
||||||
Register(syncStatus).
|
|
||||||
Register(treeManagerMock).
|
|
||||||
Register(deletionStateMock).
|
|
||||||
Register(hs)
|
|
||||||
return &headSyncFixture{
|
|
||||||
spaceState: spaceState,
|
|
||||||
ctrl: ctrl,
|
|
||||||
app: a,
|
|
||||||
configurationMock: configurationMock,
|
|
||||||
storageMock: storageMock,
|
|
||||||
peerManagerMock: peerManagerMock,
|
|
||||||
credentialProviderMock: credentialProviderMock,
|
|
||||||
syncStatus: syncStatus,
|
|
||||||
treeManagerMock: treeManagerMock,
|
|
||||||
deletionStateMock: deletionStateMock,
|
|
||||||
headSync: hs,
|
|
||||||
diffSyncerMock: diffSyncerMock,
|
|
||||||
treeSyncerMock: treeSyncerMock,
|
|
||||||
diffMock: diffMock,
|
|
||||||
clientMock: clientMock,
|
|
||||||
aclMock: aclMock,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *headSyncFixture) init(t *testing.T) {
|
t.Run("init", func(t *testing.T) {
|
||||||
createDiffSyncer = func(hs *headSync) DiffSyncer {
|
storageMock.EXPECT().TreeStorage(initId).Return(treeStorageMock, nil)
|
||||||
return fx.diffSyncerMock
|
treeStorageMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
||||||
}
|
syncer.EXPECT().Init(delState)
|
||||||
fx.diffSyncerMock.EXPECT().Init()
|
diffMock.EXPECT().Set(ldiff.Element{
|
||||||
err := fx.headSync.Init(fx.app)
|
Id: initId,
|
||||||
require.NoError(t, err)
|
|
||||||
fx.headSync.diff = fx.diffMock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *headSyncFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadSync(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("run close", func(t *testing.T) {
|
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.init(t)
|
|
||||||
defer fx.stop()
|
|
||||||
|
|
||||||
ids := []string{"id1"}
|
|
||||||
treeMock := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
|
||||||
fx.storageMock.EXPECT().StoredIds().Return(ids, nil)
|
|
||||||
fx.storageMock.EXPECT().TreeStorage(ids[0]).Return(treeMock, nil)
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.aclMock.EXPECT().Head().AnyTimes().Return(&list.AclRecord{Id: "headId"})
|
|
||||||
treeMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
|
||||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
|
||||||
Id: "id1",
|
|
||||||
Head: "h1h2",
|
Head: "h1h2",
|
||||||
})
|
})
|
||||||
fx.diffMock.EXPECT().Hash().Return("hash")
|
hash := "123"
|
||||||
fx.storageMock.EXPECT().WriteSpaceHash("hash").Return(nil)
|
diffMock.EXPECT().Hash().Return(hash)
|
||||||
fx.diffSyncerMock.EXPECT().Sync(gomock.Any()).Return(nil)
|
storageMock.EXPECT().WriteSpaceHash(hash)
|
||||||
fx.diffSyncerMock.EXPECT().Close().Return(nil)
|
pSyncMock.EXPECT().Run()
|
||||||
err := fx.headSync.Run(ctx)
|
service.Init([]string{initId}, delState)
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.headSync.Close(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("update heads", func(t *testing.T) {
|
t.Run("update heads", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
|
||||||
fx.init(t)
|
service.UpdateHeads(initId, []string{"h1", "h2"})
|
||||||
defer fx.stop()
|
|
||||||
|
|
||||||
fx.diffSyncerMock.EXPECT().UpdateHeads("id1", []string{"h1"})
|
|
||||||
fx.headSync.UpdateHeads("id1", []string{"h1"})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("remove objects", func(t *testing.T) {
|
t.Run("remove objects", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
|
||||||
fx.init(t)
|
service.RemoveObjects([]string{"h1", "h2"})
|
||||||
defer fx.stop()
|
})
|
||||||
|
|
||||||
fx.diffSyncerMock.EXPECT().RemoveObjects([]string{"id1"})
|
t.Run("close", func(t *testing.T) {
|
||||||
fx.headSync.RemoveObjects([]string{"id1"})
|
pSyncMock.EXPECT().Close()
|
||||||
|
syncer.EXPECT().Close()
|
||||||
|
service.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockDiffSyncer is a mock of DiffSyncer interface.
|
// MockDiffSyncer is a mock of DiffSyncer interface.
|
||||||
@ -49,15 +50,15 @@ func (mr *MockDiffSyncerMockRecorder) Close() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init mocks base method.
|
// Init mocks base method.
|
||||||
func (m *MockDiffSyncer) Init() {
|
func (m *MockDiffSyncer) Init(arg0 settingsstate.ObjectDeletionState) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Init")
|
m.ctrl.Call(m, "Init", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
// Init indicates an expected call of Init.
|
||||||
func (mr *MockDiffSyncerMockRecorder) Init() *gomock.Call {
|
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveObjects mocks base method.
|
// RemoveObjects mocks base method.
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
package headsync
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func concatStrings(strs []string) string {
|
|
||||||
var (
|
|
||||||
b strings.Builder
|
|
||||||
totalLen int
|
|
||||||
)
|
|
||||||
for _, s := range strs {
|
|
||||||
totalLen += len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Grow(totalLen)
|
|
||||||
for _, s := range strs {
|
|
||||||
b.WriteString(s)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitString(str string) (res []string) {
|
|
||||||
const cidLen = 59
|
|
||||||
for i := 0; i < len(str); i += cidLen {
|
|
||||||
res = append(res, str[i:i+cidLen])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,26 @@ syntax = "proto3";
|
|||||||
package aclrecord;
|
package aclrecord;
|
||||||
option go_package = "commonspace/object/acl/aclrecordproto";
|
option go_package = "commonspace/object/acl/aclrecordproto";
|
||||||
|
|
||||||
// AclRoot is a root of access control list
|
message RawAclRecord {
|
||||||
|
bytes payload = 1;
|
||||||
|
bytes signature = 2;
|
||||||
|
bytes acceptorIdentity = 3;
|
||||||
|
bytes acceptorSignature = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RawAclRecordWithId {
|
||||||
|
bytes payload = 1;
|
||||||
|
string id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclRecord {
|
||||||
|
string prevId = 1;
|
||||||
|
bytes identity = 2;
|
||||||
|
bytes data = 3;
|
||||||
|
string readKeyId = 4;
|
||||||
|
int64 timestamp = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message AclRoot {
|
message AclRoot {
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
bytes masterKey = 2;
|
bytes masterKey = 2;
|
||||||
@ -12,95 +31,82 @@ message AclRoot {
|
|||||||
bytes identitySignature = 6;
|
bytes identitySignature = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
|
|
||||||
message AclAccountInvite {
|
|
||||||
bytes inviteKey = 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
|
|
||||||
message AclAccountRequestJoin {
|
|
||||||
bytes inviteIdentity = 1;
|
|
||||||
string inviteRecordId = 2;
|
|
||||||
bytes inviteIdentitySignature = 3;
|
|
||||||
bytes metadata = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
|
|
||||||
message AclAccountRequestAccept {
|
|
||||||
bytes identity = 1;
|
|
||||||
string requestRecordId = 2;
|
|
||||||
repeated AclReadKeyWithRecord encryptedReadKeys = 3;
|
|
||||||
AclUserPermissions permissions = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestDecline contains the reference to join record
|
|
||||||
message AclAccountRequestDecline {
|
|
||||||
string requestRecordId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountInviteRevoke revokes the invite record
|
|
||||||
message AclAccountInviteRevoke {
|
|
||||||
string inviteRecordId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclReadKeys are a read key with record id
|
|
||||||
message AclReadKeyWithRecord {
|
|
||||||
string recordId = 1;
|
|
||||||
bytes encryptedReadKey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclEncryptedReadKeys are new key for specific identity
|
|
||||||
message AclEncryptedReadKey {
|
|
||||||
bytes identity = 1;
|
|
||||||
bytes encryptedReadKey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountPermissionChange changes permissions of specific account
|
|
||||||
message AclAccountPermissionChange {
|
|
||||||
bytes identity = 1;
|
|
||||||
AclUserPermissions permissions = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclReadKeyChange changes the key for a space
|
|
||||||
message AclReadKeyChange {
|
|
||||||
repeated AclEncryptedReadKey accountKeys = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRemove removes an account and changes read key for space
|
|
||||||
message AclAccountRemove {
|
|
||||||
repeated bytes identities = 1;
|
|
||||||
repeated AclEncryptedReadKey accountKeys = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestRemove adds a request to remove an account
|
|
||||||
message AclAccountRequestRemove {
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclContentValue contains possible values for Acl
|
|
||||||
message AclContentValue {
|
message AclContentValue {
|
||||||
oneof value {
|
oneof value {
|
||||||
AclAccountInvite invite = 1;
|
AclUserAdd userAdd = 1;
|
||||||
AclAccountInviteRevoke inviteRevoke = 2;
|
AclUserRemove userRemove = 2;
|
||||||
AclAccountRequestJoin requestJoin = 3;
|
AclUserPermissionChange userPermissionChange = 3;
|
||||||
AclAccountRequestAccept requestAccept = 4;
|
AclUserInvite userInvite = 4;
|
||||||
AclAccountPermissionChange permissionChange = 5;
|
AclUserJoin userJoin = 5;
|
||||||
AclAccountRemove accountRemove = 6;
|
|
||||||
AclReadKeyChange readKeyChange = 7;
|
|
||||||
AclAccountRequestDecline requestDecline = 8;
|
|
||||||
AclAccountRequestRemove accountRequestRemove = 9;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclData contains different acl content
|
|
||||||
message AclData {
|
message AclData {
|
||||||
repeated AclContentValue aclContent = 1;
|
repeated AclContentValue aclContent = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclUserPermissions contains different possible user roles
|
message AclState {
|
||||||
enum AclUserPermissions {
|
repeated string readKeyIds = 1;
|
||||||
None = 0;
|
repeated AclUserState userStates = 2;
|
||||||
Owner = 1;
|
map<string, AclUserInvite> invites = 3;
|
||||||
Admin = 2;
|
|
||||||
Writer = 3;
|
|
||||||
Reader = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AclUserState {
|
||||||
|
bytes identity = 1;
|
||||||
|
AclUserPermissions permissions = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserAdd {
|
||||||
|
bytes identity = 1;
|
||||||
|
repeated bytes encryptedReadKeys = 2;
|
||||||
|
AclUserPermissions permissions = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserInvite {
|
||||||
|
bytes acceptPublicKey = 1;
|
||||||
|
repeated bytes encryptedReadKeys = 2;
|
||||||
|
AclUserPermissions permissions = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserJoin {
|
||||||
|
bytes identity = 1;
|
||||||
|
bytes acceptSignature = 2;
|
||||||
|
bytes acceptPubKey = 3;
|
||||||
|
repeated bytes encryptedReadKeys = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserRemove {
|
||||||
|
bytes identity = 1;
|
||||||
|
repeated AclReadKeyReplace readKeyReplaces = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclReadKeyReplace {
|
||||||
|
bytes identity = 1;
|
||||||
|
bytes encryptedReadKey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserPermissionChange {
|
||||||
|
bytes identity = 1;
|
||||||
|
AclUserPermissions permissions = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AclUserPermissions {
|
||||||
|
Admin = 0;
|
||||||
|
Writer = 1;
|
||||||
|
Reader = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclSyncMessage {
|
||||||
|
AclSyncContentValue content = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AclSyncContentValue provides different types for acl sync
|
||||||
|
message AclSyncContentValue {
|
||||||
|
oneof value {
|
||||||
|
AclAddRecords addRecords = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclAddRecords {
|
||||||
|
repeated RawAclRecordWithId records = 1;
|
||||||
|
}
|
||||||
@ -1,14 +1,11 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RootContent struct {
|
type RootContent struct {
|
||||||
@ -18,387 +15,26 @@ type RootContent struct {
|
|||||||
EncryptedReadKey []byte
|
EncryptedReadKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestJoinPayload struct {
|
|
||||||
InviteRecordId string
|
|
||||||
InviteKey crypto.PrivKey
|
|
||||||
Metadata []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestAcceptPayload struct {
|
|
||||||
RequestRecordId string
|
|
||||||
Permissions AclPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
type PermissionChangePayload struct {
|
|
||||||
Identity crypto.PubKey
|
|
||||||
Permissions AclPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountRemovePayload struct {
|
|
||||||
Identities []crypto.PubKey
|
|
||||||
ReadKey crypto.SymKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type InviteResult struct {
|
|
||||||
InviteRec *consensusproto.RawRecord
|
|
||||||
InviteKey crypto.PrivKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type AclRecordBuilder interface {
|
type AclRecordBuilder interface {
|
||||||
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
|
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
||||||
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
|
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
|
||||||
|
|
||||||
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
|
|
||||||
BuildInvite() (res InviteResult, err error)
|
|
||||||
BuildInviteRevoke(inviteRecordId string) (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)
|
|
||||||
BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type aclRecordBuilder struct {
|
type aclRecordBuilder struct {
|
||||||
id string
|
id string
|
||||||
keyStorage crypto.KeyStorage
|
keyStorage crypto.KeyStorage
|
||||||
accountKeys *accountdata.AccountKeys
|
|
||||||
verifier AcceptorVerifier
|
|
||||||
state *AclState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
|
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
|
||||||
return &aclRecordBuilder{
|
return &aclRecordBuilder{
|
||||||
id: id,
|
id: id,
|
||||||
keyStorage: keyStorage,
|
keyStorage: keyStorage,
|
||||||
accountKeys: keys,
|
|
||||||
verifier: verifier,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
|
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
||||||
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
|
|
||||||
aclContent,
|
|
||||||
}}
|
|
||||||
marshalledData, err := aclData.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoKey, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rec := &consensusproto.Record{
|
|
||||||
PrevId: a.state.lastRecordId,
|
|
||||||
Identity: protoKey,
|
|
||||||
Data: marshalledData,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
marshalledRec, err := rec.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signature, err := a.accountKeys.SignKey.Sign(marshalledRec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawRec = &consensusproto.RawRecord{
|
|
||||||
Payload: marshalledRec,
|
|
||||||
Signature: signature,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, 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
|
|
||||||
}
|
|
||||||
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
|
||||||
rawRec, err := a.buildRecord(content)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.InviteKey = privKey
|
|
||||||
res.InviteRec = rawRec
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, exists := a.state.inviteKeys[inviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchInvite
|
|
||||||
return
|
|
||||||
}
|
|
||||||
revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
key, exists := a.state.inviteKeys[payload.InviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchInvite
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !payload.InviteKey.GetPublic().Equals(key) {
|
|
||||||
err = ErrIncorrectInviteKey
|
|
||||||
}
|
|
||||||
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signature, err := payload.InviteKey.Sign(rawIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
joinRec := &aclrecordproto.AclAccountRequestJoin{
|
|
||||||
InviteIdentity: protoIdentity,
|
|
||||||
InviteRecordId: payload.InviteRecordId,
|
|
||||||
InviteIdentitySignature: signature,
|
|
||||||
Metadata: payload.Metadata,
|
|
||||||
}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestJoin{RequestJoin: joinRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
request, exists := a.state.requestRecords[payload.RequestRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchRequest
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var encryptedReadKeys []*aclrecordproto.AclReadKeyWithRecord
|
|
||||||
for keyId, key := range a.state.userReadKeys {
|
|
||||||
rawKey, err := key.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enc, err := request.RequestIdentity.Encrypt(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encryptedReadKeys = append(encryptedReadKeys, &aclrecordproto.AclReadKeyWithRecord{
|
|
||||||
RecordId: keyId,
|
|
||||||
EncryptedReadKey: enc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requestIdentityProto, err := request.RequestIdentity.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
acceptRec := &aclrecordproto.AclAccountRequestAccept{
|
|
||||||
Identity: requestIdentityProto,
|
|
||||||
RequestRecordId: payload.RequestRecordId,
|
|
||||||
EncryptedReadKeys: encryptedReadKeys,
|
|
||||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
|
||||||
}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, exists := a.state.requestRecords[requestRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchRequest
|
|
||||||
return
|
|
||||||
}
|
|
||||||
declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
permissions := a.state.Permissions(a.state.pubKey)
|
|
||||||
if !permissions.CanManageAccounts() || payload.Identity.Equals(a.state.pubKey) {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if payload.Permissions.IsOwner() {
|
|
||||||
err = ErrIsOwner
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoIdentity, err := payload.Identity.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
permissionRec := &aclrecordproto.AclAccountPermissionChange{
|
|
||||||
Identity: protoIdentity,
|
|
||||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
|
||||||
}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChange{PermissionChange: permissionRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawKey, err := newKey.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(rawKey) != crypto.KeyBytes {
|
|
||||||
err = ErrIncorrectReadKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
|
||||||
for _, st := range a.state.userStates {
|
|
||||||
protoIdentity, err := st.PubKey.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enc, err := st.PubKey.Encrypt(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
|
||||||
Identity: protoIdentity,
|
|
||||||
EncryptedReadKey: enc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
readRec := &aclrecordproto.AclReadKeyChange{AccountKeys: aclReadKeys}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_ReadKeyChange{ReadKeyChange: readRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
deletedMap := map[string]struct{}{}
|
|
||||||
for _, key := range payload.Identities {
|
|
||||||
permissions := a.state.Permissions(key)
|
|
||||||
if permissions.IsOwner() {
|
|
||||||
return nil, ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
if permissions.NoPermissions() {
|
|
||||||
return nil, ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
deletedMap[mapKeyFromPubKey(key)] = struct{}{}
|
|
||||||
}
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawKey, err := payload.ReadKey.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(rawKey) != crypto.KeyBytes {
|
|
||||||
err = ErrIncorrectReadKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
|
||||||
for _, st := range a.state.userStates {
|
|
||||||
if _, exists := deletedMap[mapKeyFromPubKey(st.PubKey)]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
protoIdentity, err := st.PubKey.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enc, err := st.PubKey.Encrypt(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
|
||||||
Identity: protoIdentity,
|
|
||||||
EncryptedReadKey: enc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var marshalledIdentities [][]byte
|
|
||||||
for _, key := range payload.Identities {
|
|
||||||
protoIdentity, err := key.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
marshalledIdentities = append(marshalledIdentities, protoIdentity)
|
|
||||||
}
|
|
||||||
removeRec := &aclrecordproto.AclAccountRemove{AccountKeys: aclReadKeys, Identities: marshalledIdentities}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
permissions := a.state.Permissions(a.state.pubKey)
|
|
||||||
if permissions.NoPermissions() {
|
|
||||||
err = ErrNoSuchAccount
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if permissions.IsOwner() {
|
|
||||||
err = ErrIsOwner
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeRec := &aclrecordproto.AclAccountRequestRemove{}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRequestRemove{AccountRequestRemove: removeRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) {
|
|
||||||
aclRecord := &consensusproto.Record{}
|
|
||||||
err = proto.Unmarshal(rawRecord.Payload, aclRecord)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pubKey, err := a.keyStorage.PubKeyFromProto(aclRecord.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aclData := &aclrecordproto.AclData{}
|
|
||||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rec = &AclRecord{
|
|
||||||
PrevId: aclRecord.PrevId,
|
|
||||||
Timestamp: aclRecord.Timestamp,
|
|
||||||
Data: aclRecord.Data,
|
|
||||||
Signature: rawRecord.Signature,
|
|
||||||
Identity: pubKey,
|
|
||||||
Model: aclData,
|
|
||||||
}
|
|
||||||
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !res {
|
|
||||||
err = ErrInvalidSignature
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error) {
|
|
||||||
var (
|
var (
|
||||||
rawRec = &consensusproto.RawRecord{}
|
rawRec = &aclrecordproto.RawAclRecord{}
|
||||||
pubKey crypto.PubKey
|
pubKey crypto.PubKey
|
||||||
)
|
)
|
||||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||||
@ -417,17 +53,14 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
|||||||
}
|
}
|
||||||
rec = &AclRecord{
|
rec = &AclRecord{
|
||||||
Id: rawIdRecord.Id,
|
Id: rawIdRecord.Id,
|
||||||
|
ReadKeyId: rawIdRecord.Id,
|
||||||
Timestamp: aclRoot.Timestamp,
|
Timestamp: aclRoot.Timestamp,
|
||||||
Signature: rawRec.Signature,
|
Signature: rawRec.Signature,
|
||||||
Identity: pubKey,
|
Identity: pubKey,
|
||||||
Model: aclRoot,
|
Model: aclRoot,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = a.verifier.VerifyAcceptor(rawRec)
|
aclRecord := &aclrecordproto.AclRecord{}
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aclRecord := &consensusproto.Record{}
|
|
||||||
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -436,19 +69,14 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aclData := &aclrecordproto.AclData{}
|
|
||||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rec = &AclRecord{
|
rec = &AclRecord{
|
||||||
Id: rawIdRecord.Id,
|
Id: rawIdRecord.Id,
|
||||||
PrevId: aclRecord.PrevId,
|
PrevId: aclRecord.PrevId,
|
||||||
|
ReadKeyId: aclRecord.ReadKeyId,
|
||||||
Timestamp: aclRecord.Timestamp,
|
Timestamp: aclRecord.Timestamp,
|
||||||
Data: aclRecord.Data,
|
Data: aclRecord.Data,
|
||||||
Signature: rawRec.Signature,
|
Signature: rawRec.Signature,
|
||||||
Identity: pubKey,
|
Identity: pubKey,
|
||||||
Model: aclData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +84,7 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error) {
|
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
|
||||||
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -490,8 +118,8 @@ func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.R
|
|||||||
|
|
||||||
func verifyRaw(
|
func verifyRaw(
|
||||||
pubKey crypto.PubKey,
|
pubKey crypto.PubKey,
|
||||||
rawRec *consensusproto.RawRecord,
|
rawRec *aclrecordproto.RawAclRecord,
|
||||||
recWithId *consensusproto.RawRecordWithId) (err error) {
|
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||||
// verifying signature
|
// verifying signature
|
||||||
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -509,7 +137,7 @@ func verifyRaw(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *consensusproto.RawRecordWithId, err error) {
|
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||||
marshalledRoot, err := aclRoot.Marshal()
|
marshalledRoot, err := aclRoot.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -518,7 +146,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
raw := &consensusproto.RawRecord{
|
raw := &aclrecordproto.RawAclRecord{
|
||||||
Payload: marshalledRoot,
|
Payload: marshalledRoot,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -530,7 +158,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId = &consensusproto.RawRecordWithId{
|
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
|
|||||||
9
commonspace/object/acl/list/aclrecordbuilder_test.go
Normal file
9
commonspace/object/acl/list/aclrecordbuilder_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
@ -13,24 +13,19 @@ import (
|
|||||||
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoSuchAccount = errors.New("no such account")
|
ErrNoSuchUser = errors.New("no such user")
|
||||||
ErrPendingRequest = errors.New("already exists pending request")
|
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||||
ErrUnexpectedContentType = errors.New("unexpected content type")
|
ErrUserRemoved = errors.New("user was removed from the document")
|
||||||
ErrIncorrectIdentity = errors.New("incorrect identity")
|
ErrDocumentForbidden = errors.New("your user was forbidden access to the document")
|
||||||
ErrIncorrectInviteKey = errors.New("incorrect invite key")
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
ErrNoSuchRecord = errors.New("no such record")
|
||||||
ErrNoSuchRecord = errors.New("no such record")
|
ErrNoSuchInvite = errors.New("no such invite")
|
||||||
ErrNoSuchRequest = errors.New("no such request")
|
ErrOldInvite = errors.New("invite is too old")
|
||||||
ErrNoSuchInvite = errors.New("no such invite")
|
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||||
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||||
ErrIsOwner = errors.New("can't be made by owner")
|
ErrInvalidSignature = errors.New("signature is invalid")
|
||||||
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
|
ErrIncorrectRoot = errors.New("incorrect root")
|
||||||
ErrDuplicateAccounts = errors.New("duplicate accounts")
|
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
||||||
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
|
||||||
ErrIncorrectReadKey = errors.New("incorrect read key")
|
|
||||||
ErrInvalidSignature = errors.New("signature is invalid")
|
|
||||||
ErrIncorrectRoot = errors.New("incorrect root")
|
|
||||||
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserPermissionPair struct {
|
type UserPermissionPair struct {
|
||||||
@ -41,71 +36,37 @@ type UserPermissionPair struct {
|
|||||||
type AclState struct {
|
type AclState struct {
|
||||||
id string
|
id string
|
||||||
currentReadKeyId string
|
currentReadKeyId string
|
||||||
// userReadKeys is a map recordId -> read key which tells us about every read key
|
userReadKeys map[string]crypto.SymKey
|
||||||
userReadKeys map[string]crypto.SymKey
|
userStates map[string]AclUserState
|
||||||
// userStates is a map pubKey -> state which defines current user state
|
statesAtRecord map[string][]AclUserState
|
||||||
userStates map[string]AclUserState
|
key crypto.PrivKey
|
||||||
// statesAtRecord is a map recordId -> state which define user state at particular record
|
pubKey crypto.PubKey
|
||||||
// probably this can grow rather large at some point, so we can maybe optimise later to have:
|
keyStore crypto.KeyStorage
|
||||||
// - map pubKey -> []recordIds (where recordIds is an array where such identity permissions were changed)
|
totalReadKeys int
|
||||||
statesAtRecord map[string][]AclUserState
|
|
||||||
// inviteKeys is a map recordId -> invite
|
|
||||||
inviteKeys map[string]crypto.PubKey
|
|
||||||
// requestRecords is a map recordId -> RequestRecord
|
|
||||||
requestRecords map[string]RequestRecord
|
|
||||||
// pendingRequests is a map pubKey -> recordId
|
|
||||||
pendingRequests map[string]string
|
|
||||||
key crypto.PrivKey
|
|
||||||
pubKey crypto.PubKey
|
|
||||||
keyStore crypto.KeyStorage
|
|
||||||
totalReadKeys int
|
|
||||||
|
|
||||||
lastRecordId string
|
lastRecordId string
|
||||||
contentValidator ContentValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAclStateWithKeys(
|
func newAclStateWithKeys(
|
||||||
id string,
|
id string,
|
||||||
key crypto.PrivKey) (*AclState, error) {
|
key crypto.PrivKey) (*AclState, error) {
|
||||||
st := &AclState{
|
return &AclState{
|
||||||
id: id,
|
id: id,
|
||||||
key: key,
|
key: key,
|
||||||
pubKey: key.GetPublic(),
|
pubKey: key.GetPublic(),
|
||||||
userReadKeys: make(map[string]crypto.SymKey),
|
userReadKeys: make(map[string]crypto.SymKey),
|
||||||
userStates: make(map[string]AclUserState),
|
userStates: make(map[string]AclUserState),
|
||||||
statesAtRecord: make(map[string][]AclUserState),
|
statesAtRecord: make(map[string][]AclUserState),
|
||||||
inviteKeys: make(map[string]crypto.PubKey),
|
}, nil
|
||||||
requestRecords: make(map[string]RequestRecord),
|
|
||||||
pendingRequests: make(map[string]string),
|
|
||||||
keyStore: crypto.NewKeyStorage(),
|
|
||||||
}
|
|
||||||
st.contentValidator = &contentValidator{
|
|
||||||
keyStore: st.keyStore,
|
|
||||||
aclState: st,
|
|
||||||
}
|
|
||||||
return st, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAclState(id string) *AclState {
|
func newAclState(id string) *AclState {
|
||||||
st := &AclState{
|
return &AclState{
|
||||||
id: id,
|
id: id,
|
||||||
userReadKeys: make(map[string]crypto.SymKey),
|
userReadKeys: make(map[string]crypto.SymKey),
|
||||||
userStates: make(map[string]AclUserState),
|
userStates: make(map[string]AclUserState),
|
||||||
statesAtRecord: make(map[string][]AclUserState),
|
statesAtRecord: make(map[string][]AclUserState),
|
||||||
inviteKeys: make(map[string]crypto.PubKey),
|
|
||||||
requestRecords: make(map[string]RequestRecord),
|
|
||||||
pendingRequests: make(map[string]string),
|
|
||||||
keyStore: crypto.NewKeyStorage(),
|
|
||||||
}
|
}
|
||||||
st.contentValidator = &contentValidator{
|
|
||||||
keyStore: st.keyStore,
|
|
||||||
aclState: st,
|
|
||||||
}
|
|
||||||
return st
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) Validator() ContentValidator {
|
|
||||||
return st.contentValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) CurrentReadKeyId() string {
|
func (st *AclState) CurrentReadKeyId() string {
|
||||||
@ -113,7 +74,7 @@ func (st *AclState) CurrentReadKeyId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
||||||
key, exists := st.userReadKeys[st.CurrentReadKeyId()]
|
key, exists := st.userReadKeys[st.currentReadKeyId]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, ErrNoReadKey
|
return nil, ErrNoReadKey
|
||||||
}
|
}
|
||||||
@ -136,7 +97,7 @@ func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState
|
|||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AclUserState{}, ErrNoSuchAccount
|
return AclUserState{}, ErrNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||||
@ -149,18 +110,17 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
|||||||
err = ErrIncorrectRecordSequence
|
err = ErrIncorrectRecordSequence
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if the record is root record
|
|
||||||
if record.Id == st.id {
|
if record.Id == st.id {
|
||||||
err = st.applyRoot(record)
|
err = st.applyRoot(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st.statesAtRecord[record.Id] = []AclUserState{
|
st.statesAtRecord[record.Id] = []AclUserState{
|
||||||
st.userStates[mapKeyFromPubKey(record.Identity)],
|
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if the model is not cached
|
|
||||||
if record.Model == nil {
|
if record.Model == nil {
|
||||||
aclData := &aclrecordproto.AclData{}
|
aclData := &aclrecordproto.AclData{}
|
||||||
err = proto.Unmarshal(record.Data, aclData)
|
err = proto.Unmarshal(record.Data, aclData)
|
||||||
@ -169,16 +129,18 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
|||||||
}
|
}
|
||||||
record.Model = aclData
|
record.Model = aclData
|
||||||
}
|
}
|
||||||
// applying records contents
|
|
||||||
err = st.applyChangeData(record)
|
err = st.applyChangeData(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// getting all states for users at record and saving them
|
|
||||||
|
// getting all states for users at record
|
||||||
var states []AclUserState
|
var states []AclUserState
|
||||||
for _, state := range st.userStates {
|
for _, state := range st.userStates {
|
||||||
states = append(states, state)
|
states = append(states, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
st.statesAtRecord[record.Id] = states
|
st.statesAtRecord[record.Id] = states
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -194,9 +156,9 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
|||||||
// adding user to the list
|
// adding user to the list
|
||||||
userState := AclUserState{
|
userState := AclUserState{
|
||||||
PubKey: record.Identity,
|
PubKey: record.Identity,
|
||||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
|
Permissions: aclrecordproto.AclUserPermissions_Admin,
|
||||||
}
|
}
|
||||||
st.currentReadKeyId = record.Id
|
st.currentReadKeyId = record.ReadKeyId
|
||||||
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
||||||
st.totalReadKeys++
|
st.totalReadKeys++
|
||||||
return
|
return
|
||||||
@ -219,191 +181,92 @@ func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
st.userReadKeys[record.Id] = readKey
|
st.userReadKeys[record.Id] = readKey
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if record.ReadKeyId != st.currentReadKeyId {
|
||||||
|
st.totalReadKeys++
|
||||||
|
st.currentReadKeyId = record.ReadKeyId
|
||||||
|
}
|
||||||
|
}()
|
||||||
model := record.Model.(*aclrecordproto.AclData)
|
model := record.Model.(*aclrecordproto.AclData)
|
||||||
|
if !st.isUserJoin(model) {
|
||||||
|
// we check signature when we add this to the List, so no need to do it here
|
||||||
|
if _, exists := st.userStates[mapKeyFromPubKey(record.Identity)]; !exists {
|
||||||
|
err = ErrNoSuchUser
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// only Admins can do non-user join changes
|
||||||
|
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
|
||||||
|
// TODO: add string encoding
|
||||||
|
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, ch := range model.GetAclContent() {
|
for _, ch := range model.GetAclContent() {
|
||||||
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
|
if err = st.applyChangeContent(ch, record.Id); err != nil {
|
||||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
|
||||||
switch {
|
switch {
|
||||||
case ch.GetPermissionChange() != nil:
|
case ch.GetUserPermissionChange() != nil:
|
||||||
return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity)
|
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
|
||||||
case ch.GetInvite() != nil:
|
case ch.GetUserAdd() != nil:
|
||||||
return st.applyInvite(ch.GetInvite(), recordId, authorIdentity)
|
return st.applyUserAdd(ch.GetUserAdd(), recordId)
|
||||||
case ch.GetInviteRevoke() != nil:
|
case ch.GetUserRemove() != nil:
|
||||||
return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity)
|
return st.applyUserRemove(ch.GetUserRemove(), recordId)
|
||||||
case ch.GetRequestJoin() != nil:
|
case ch.GetUserInvite() != nil:
|
||||||
return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity)
|
return st.applyUserInvite(ch.GetUserInvite(), recordId)
|
||||||
case ch.GetRequestAccept() != nil:
|
case ch.GetUserJoin() != nil:
|
||||||
return st.applyRequestAccept(ch.GetRequestAccept(), recordId, authorIdentity)
|
return st.applyUserJoin(ch.GetUserJoin(), recordId)
|
||||||
case ch.GetRequestDecline() != nil:
|
|
||||||
return st.applyRequestDecline(ch.GetRequestDecline(), recordId, authorIdentity)
|
|
||||||
case ch.GetAccountRemove() != nil:
|
|
||||||
return st.applyAccountRemove(ch.GetAccountRemove(), recordId, authorIdentity)
|
|
||||||
case ch.GetReadKeyChange() != nil:
|
|
||||||
return st.applyReadKeyChange(ch.GetReadKeyChange(), recordId, authorIdentity)
|
|
||||||
case ch.GetAccountRequestRemove() != nil:
|
|
||||||
return st.applyRequestRemove(ch.GetAccountRequestRemove(), recordId, authorIdentity)
|
|
||||||
default:
|
default:
|
||||||
return ErrUnexpectedContentType
|
return fmt.Errorf("unexpected change type: %v", ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
|
||||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
|
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
|
||||||
if err != nil {
|
if !exists {
|
||||||
return err
|
return ErrNoSuchUser
|
||||||
}
|
}
|
||||||
stringKey := mapKeyFromPubKey(chIdentity)
|
|
||||||
state, _ := st.userStates[stringKey]
|
state.Permissions = ch.Permissions
|
||||||
state.Permissions = AclPermissions(ch.Permissions)
|
|
||||||
st.userStates[stringKey] = state
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
|
||||||
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
|
// TODO: check old code and bring it back :-)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = st.contentValidator.ValidateInvite(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.inviteKeys[recordId] = inviteKey
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
|
||||||
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(st.inviteKeys, ch.InviteRecordId)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
|
||||||
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
|
||||||
st.requestRecords[recordId] = RequestRecord{
|
|
||||||
RequestIdentity: authorIdentity,
|
|
||||||
RequestMetadata: ch.Metadata,
|
|
||||||
Type: RequestTypeJoin,
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
|
||||||
err := st.contentValidator.ValidateRequestAccept(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
acceptIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record, _ := st.requestRecords[ch.RequestRecordId]
|
|
||||||
st.userStates[mapKeyFromPubKey(acceptIdentity)] = AclUserState{
|
|
||||||
PubKey: acceptIdentity,
|
|
||||||
Permissions: AclPermissions(ch.Permissions),
|
|
||||||
RequestMetadata: record.RequestMetadata,
|
|
||||||
}
|
|
||||||
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
|
||||||
if !st.pubKey.Equals(acceptIdentity) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, key := range ch.EncryptedReadKeys {
|
|
||||||
decrypted, err := st.key.Decrypt(key.EncryptedReadKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sym, err := crypto.UnmarshallAESKey(decrypted)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.userReadKeys[key.RecordId] = sym
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateRequestDecline(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
|
||||||
delete(st.requestRecords, ch.RequestRecordId)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateRequestRemove(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.requestRecords[recordId] = RequestRecord{
|
|
||||||
RequestIdentity: authorIdentity,
|
|
||||||
Type: RequestTypeRemove,
|
|
||||||
}
|
|
||||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateAccountRemove(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, rawIdentity := range ch.Identities {
|
|
||||||
identity, err := st.keyStore.PubKeyFromProto(rawIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
idKey := mapKeyFromPubKey(identity)
|
|
||||||
delete(st.userStates, idKey)
|
|
||||||
delete(st.pendingRequests, idKey)
|
|
||||||
}
|
|
||||||
return st.updateReadKey(ch.AccountKeys, recordId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateReadKeyChange(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return st.updateReadKey(ch.AccountKeys, recordId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) updateReadKey(keys []*aclrecordproto.AclEncryptedReadKey, recordId string) error {
|
|
||||||
for _, accKey := range keys {
|
|
||||||
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
|
|
||||||
if st.pubKey.Equals(identity) {
|
|
||||||
res, err := st.decryptReadKey(accKey.EncryptedReadKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.userReadKeys[recordId] = res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
st.currentReadKeyId = recordId
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,6 +275,7 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToDecrypt
|
return nil, ErrFailedToDecrypt
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := crypto.UnmarshallAESKey(decrypted)
|
key, err := crypto.UnmarshallAESKey(decrypted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToDecrypt
|
return nil, ErrFailedToDecrypt
|
||||||
@ -419,31 +283,29 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
|
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
|
||||||
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
||||||
if !exists {
|
if !exists {
|
||||||
return AclPermissions(aclrecordproto.AclUserPermissions_None)
|
return false
|
||||||
}
|
}
|
||||||
return state.Permissions
|
|
||||||
|
return state.Permissions == permission
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) JoinRecords() (records []RequestRecord) {
|
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
|
||||||
for _, recId := range st.pendingRequests {
|
// if we have a UserJoin, then it should always be the first one applied
|
||||||
rec := st.requestRecords[recId]
|
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
||||||
if rec.Type == RequestTypeJoin {
|
|
||||||
records = append(records, rec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) RemoveRecords() (records []RequestRecord) {
|
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
|
||||||
for _, recId := range st.pendingRequests {
|
return false
|
||||||
rec := st.requestRecords[recId]
|
}
|
||||||
if rec.Type == RequestTypeRemove {
|
|
||||||
records = append(records, rec)
|
func (st *AclState) UserStates() map[string]AclUserState {
|
||||||
}
|
return st.userStates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,21 +5,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
||||||
|
|
||||||
var (
|
var ErrIncorrectCID = errors.New("incorrect CID")
|
||||||
ErrIncorrectCID = errors.New("incorrect CID")
|
|
||||||
ErrRecordAlreadyExists = errors.New("record already exists")
|
|
||||||
)
|
|
||||||
|
|
||||||
type RWLocker interface {
|
type RWLocker interface {
|
||||||
sync.Locker
|
sync.Locker
|
||||||
@ -27,45 +22,26 @@ type RWLocker interface {
|
|||||||
RUnlock()
|
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 {
|
type AclList interface {
|
||||||
RWLocker
|
RWLocker
|
||||||
Id() string
|
Id() string
|
||||||
Root() *consensusproto.RawRecordWithId
|
Root() *aclrecordproto.RawAclRecordWithId
|
||||||
Records() []*AclRecord
|
Records() []*AclRecord
|
||||||
AclState() *AclState
|
AclState() *AclState
|
||||||
IsAfter(first string, second string) (bool, error)
|
IsAfter(first string, second string) (bool, error)
|
||||||
HasHead(head string) bool
|
|
||||||
Head() *AclRecord
|
Head() *AclRecord
|
||||||
|
|
||||||
RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error)
|
|
||||||
Get(id string) (*AclRecord, error)
|
Get(id string) (*AclRecord, error)
|
||||||
GetIndex(idx int) (*AclRecord, error)
|
|
||||||
Iterate(iterFunc IterFunc)
|
Iterate(iterFunc IterFunc)
|
||||||
IterateFrom(startId string, iterFunc IterFunc)
|
IterateFrom(startId string, iterFunc IterFunc)
|
||||||
|
|
||||||
KeyStorage() crypto.KeyStorage
|
KeyStorage() crypto.KeyStorage
|
||||||
RecordBuilder() AclRecordBuilder
|
|
||||||
|
|
||||||
ValidateRawRecord(record *consensusproto.RawRecord) (err error)
|
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
|
||||||
AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error)
|
|
||||||
AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error)
|
|
||||||
|
|
||||||
Close(ctx context.Context) (err error)
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type aclList struct {
|
type aclList struct {
|
||||||
root *consensusproto.RawRecordWithId
|
root *aclrecordproto.RawAclRecordWithId
|
||||||
records []*AclRecord
|
records []*AclRecord
|
||||||
indexes map[string]int
|
indexes map[string]int
|
||||||
id string
|
id string
|
||||||
@ -79,45 +55,18 @@ type aclList struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type internalDeps struct {
|
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
|
||||||
storage liststorage.ListStorage
|
builder := newAclStateBuilderWithIdentity(acc)
|
||||||
keyStorage crypto.KeyStorage
|
|
||||||
stateBuilder *aclStateBuilder
|
|
||||||
recordBuilder AclRecordBuilder
|
|
||||||
acceptorVerifier AcceptorVerifier
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
deps := internalDeps{
|
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
|
||||||
storage: storage,
|
|
||||||
keyStorage: keyStorage,
|
|
||||||
stateBuilder: newAclStateBuilderWithIdentity(acc),
|
|
||||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, acc, verifier),
|
|
||||||
acceptorVerifier: verifier,
|
|
||||||
}
|
|
||||||
return build(deps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAclList(storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
deps := internalDeps{
|
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
|
||||||
storage: storage,
|
|
||||||
keyStorage: keyStorage,
|
|
||||||
stateBuilder: newAclStateBuilder(),
|
|
||||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, nil, verifier),
|
|
||||||
acceptorVerifier: verifier,
|
|
||||||
}
|
|
||||||
return build(deps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(deps internalDeps) (list AclList, err error) {
|
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
||||||
var (
|
|
||||||
storage = deps.storage
|
|
||||||
id = deps.storage.Id()
|
|
||||||
recBuilder = deps.recordBuilder
|
|
||||||
stateBuilder = deps.stateBuilder
|
|
||||||
)
|
|
||||||
head, err := storage.Head()
|
head, err := storage.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -128,7 +77,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err := recBuilder.UnmarshallWithId(rawRecordWithId)
|
record, err := recBuilder.Unmarshall(rawRecordWithId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -140,7 +89,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err = recBuilder.UnmarshallWithId(rawRecordWithId)
|
record, err = recBuilder.Unmarshall(rawRecordWithId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -170,7 +119,6 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
recBuilder.(*aclRecordBuilder).state = state
|
|
||||||
list = &aclList{
|
list = &aclList{
|
||||||
root: rootWithId,
|
root: rootWithId,
|
||||||
records: records,
|
records: records,
|
||||||
@ -184,37 +132,15 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) RecordBuilder() AclRecordBuilder {
|
|
||||||
return a.recordBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) Records() []*AclRecord {
|
func (a *aclList) Records() []*AclRecord {
|
||||||
return a.records
|
return a.records
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) {
|
func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error) {
|
||||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return a.aclState.Validator().ValidateAclRecordContents(record)
|
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
|
||||||
for _, rec := range rawRecords {
|
|
||||||
err = a.AddRawRecord(rec)
|
|
||||||
if err != nil && err != ErrRecordAlreadyExists {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
|
||||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
|
||||||
return ErrRecordAlreadyExists
|
|
||||||
}
|
|
||||||
record, err := a.recordBuilder.UnmarshallWithId(rawRec)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -229,6 +155,15 @@ func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err erro
|
|||||||
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||||
|
_, err = a.recordBuilder.Unmarshall(rawRec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: change state and add "check" method for records
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +171,7 @@ func (a *aclList) Id() string {
|
|||||||
return a.id
|
return a.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) Root() *consensusproto.RawRecordWithId {
|
func (a *aclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||||
return a.root
|
return a.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,27 +196,14 @@ func (a *aclList) Head() *AclRecord {
|
|||||||
return a.records[len(a.records)-1]
|
return a.records[len(a.records)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) HasHead(head string) bool {
|
|
||||||
_, exists := a.indexes[head]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) Get(id string) (*AclRecord, error) {
|
func (a *aclList) Get(id string) (*AclRecord, error) {
|
||||||
recIdx, ok := a.indexes[id]
|
recIdx, ok := a.indexes[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNoSuchRecord
|
return nil, fmt.Errorf("no such record")
|
||||||
}
|
}
|
||||||
return a.records[recIdx], nil
|
return a.records[recIdx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) GetIndex(idx int) (*AclRecord, error) {
|
|
||||||
// TODO: when we add snapshots we will have to monitor record num in snapshots
|
|
||||||
if idx < 0 || idx >= len(a.records) {
|
|
||||||
return nil, ErrNoSuchRecord
|
|
||||||
}
|
|
||||||
return a.records[idx], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) Iterate(iterFunc IterFunc) {
|
func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||||
for _, rec := range a.records {
|
for _, rec := range a.records {
|
||||||
if !iterFunc(rec) {
|
if !iterFunc(rec) {
|
||||||
@ -290,21 +212,6 @@ func (a *aclList) Iterate(iterFunc IterFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error) {
|
|
||||||
recIdx, ok := a.indexes[id]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrNoSuchRecord
|
|
||||||
}
|
|
||||||
for i := recIdx + 1; i < len(a.records); i++ {
|
|
||||||
rawRec, err := a.storage.GetRawRecord(ctx, a.records[i].Id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
records = append(records, rawRec)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||||
recIdx, ok := a.indexes[startId]
|
recIdx, ok := a.indexes[startId]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -317,21 +224,6 @@ func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) Close(ctx context.Context) (err error) {
|
func (a *aclList) Close() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapAclRecord(rawRec *consensusproto.RawRecord) *consensusproto.RawRecordWithId {
|
|
||||||
payload, err := rawRec.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
id, err := cidutil.NewCidFromBytes(payload)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return &consensusproto.RawRecordWithId{
|
|
||||||
Payload: payload,
|
|
||||||
Id: id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,98 +2,11 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aclFixture struct {
|
|
||||||
ownerKeys *accountdata.AccountKeys
|
|
||||||
accountKeys *accountdata.AccountKeys
|
|
||||||
ownerAcl *aclList
|
|
||||||
accountAcl *aclList
|
|
||||||
spaceId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFixture(t *testing.T) *aclFixture {
|
|
||||||
ownerKeys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
accountKeys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
ownerAcl, err := NewTestDerivedAcl(spaceId, ownerKeys)
|
|
||||||
require.NoError(t, err)
|
|
||||||
accountAcl, err := NewTestAclWithRoot(accountKeys, ownerAcl.Root())
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &aclFixture{
|
|
||||||
ownerKeys: ownerKeys,
|
|
||||||
accountKeys: accountKeys,
|
|
||||||
ownerAcl: ownerAcl.(*aclList),
|
|
||||||
accountAcl: accountAcl.(*aclList),
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) {
|
|
||||||
err := fx.ownerAcl.AddRawRecord(rec)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.accountAcl.AddRawRecord(rec)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
|
|
||||||
var (
|
|
||||||
ownerAcl = fx.ownerAcl
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountAcl = fx.accountAcl
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
// building invite
|
|
||||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
|
||||||
fx.addRec(t, inviteRec)
|
|
||||||
|
|
||||||
// building request join
|
|
||||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
|
||||||
InviteRecordId: inviteRec.Id,
|
|
||||||
InviteKey: inv.InviteKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestJoinRec := WrapAclRecord(requestJoin)
|
|
||||||
fx.addRec(t, requestJoinRec)
|
|
||||||
|
|
||||||
// building request accept
|
|
||||||
requestAccept, err := ownerAcl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
|
|
||||||
RequestRecordId: requestJoinRec.Id,
|
|
||||||
Permissions: perms,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
// validate
|
|
||||||
err = ownerAcl.ValidateRawRecord(requestAccept)
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestAcceptRec := WrapAclRecord(requestAccept)
|
|
||||||
fx.addRec(t, requestAcceptRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey).CanWrite())
|
|
||||||
|
|
||||||
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
|
|
||||||
require.Equal(t, ErrNoSuchAccount, err)
|
|
||||||
stateAtRec, err := ownerState.StateAtRecord(requestAcceptRec.Id, accountState.pubKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, stateAtRec.Permissions == perms)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_BuildRoot(t *testing.T) {
|
func TestAclList_BuildRoot(t *testing.T) {
|
||||||
randomKeys, err := accountdata.NewRandom()
|
randomKeys, err := accountdata.NewRandom()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -101,193 +14,3 @@ func TestAclList_BuildRoot(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println(randomAcl.Id())
|
fmt.Println(randomAcl.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAclList_InvitePipeline(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_InviteRevoke(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
// building invite
|
|
||||||
inv, err := fx.ownerAcl.RecordBuilder().BuildInvite()
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
|
||||||
fx.addRec(t, inviteRec)
|
|
||||||
|
|
||||||
// building invite revoke
|
|
||||||
inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId)
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRevokeRec := WrapAclRecord(inviteRevoke)
|
|
||||||
fx.addRec(t, inviteRevokeRec)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_RequestDecline(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerAcl = fx.ownerAcl
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountAcl = fx.accountAcl
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
// building invite
|
|
||||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
|
||||||
fx.addRec(t, inviteRec)
|
|
||||||
|
|
||||||
// building request join
|
|
||||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
|
||||||
InviteRecordId: inviteRec.Id,
|
|
||||||
InviteKey: inv.InviteKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestJoinRec := WrapAclRecord(requestJoin)
|
|
||||||
fx.addRec(t, requestJoinRec)
|
|
||||||
|
|
||||||
// building request decline
|
|
||||||
requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId)
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestDeclineRec := WrapAclRecord(requestDecline)
|
|
||||||
fx.addRec(t, requestDeclineRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Empty(t, ownerState.pendingRequests)
|
|
||||||
require.Empty(t, accountState.pendingRequests)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_Remove(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
|
|
||||||
newReadKey := crypto.NewAES()
|
|
||||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
|
||||||
Identities: []crypto.PubKey{fx.accountKeys.SignKey.GetPublic()},
|
|
||||||
ReadKey: newReadKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
removeRec := WrapAclRecord(remove)
|
|
||||||
fx.addRec(t, removeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_ReadKeyChange(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
|
||||||
|
|
||||||
newReadKey := crypto.NewAES()
|
|
||||||
readKeyChange, err := fx.ownerAcl.RecordBuilder().BuildReadKeyChange(newReadKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
readKeyRec := WrapAclRecord(readKeyChange)
|
|
||||||
fx.addRec(t, readKeyRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanManageAccounts())
|
|
||||||
require.True(t, ownerState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
|
||||||
require.True(t, accountState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
readKey, err := ownerState.CurrentReadKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, newReadKey.Equals(readKey))
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_PermissionChange(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
|
||||||
|
|
||||||
permissionChange, err := fx.ownerAcl.RecordBuilder().BuildPermissionChange(PermissionChangePayload{
|
|
||||||
Identity: fx.accountKeys.SignKey.GetPublic(),
|
|
||||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Writer),
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
permissionChangeRec := WrapAclRecord(permissionChange)
|
|
||||||
fx.addRec(t, permissionChangeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_RequestRemove(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
|
|
||||||
removeRequest, err := fx.accountAcl.RecordBuilder().BuildRequestRemove()
|
|
||||||
require.NoError(t, err)
|
|
||||||
removeRequestRec := WrapAclRecord(removeRequest)
|
|
||||||
fx.addRec(t, removeRequestRec)
|
|
||||||
|
|
||||||
recs := fx.accountAcl.AclState().RemoveRecords()
|
|
||||||
require.Len(t, recs, 1)
|
|
||||||
require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity))
|
|
||||||
|
|
||||||
newReadKey := crypto.NewAES()
|
|
||||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
|
||||||
Identities: []crypto.PubKey{recs[0].RequestIdentity},
|
|
||||||
ReadKey: newReadKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
removeRec := WrapAclRecord(remove)
|
|
||||||
fx.addRec(t, removeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,13 +2,13 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
||||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
|
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
|
||||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -21,21 +21,11 @@ func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
|
||||||
root,
|
root,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
return BuildAclListWithIdentity(keys, st)
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.RawRecordWithId) (AclList, error) {
|
|
||||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
|
||||||
root,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,12 @@
|
|||||||
package mock_list
|
package mock_list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
|
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAclList is a mock of AclList interface.
|
// MockAclList is a mock of AclList interface.
|
||||||
@ -52,11 +51,12 @@ func (mr *MockAclListMockRecorder) AclState() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
// AddRawRecord mocks base method.
|
||||||
func (m *MockAclList) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
func (m *MockAclList) AddRawRecord(arg0 *aclrecordproto.RawAclRecordWithId) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(bool)
|
||||||
return ret0
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||||
@ -65,32 +65,18 @@ func (mr *MockAclListMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockAclList)(nil).AddRawRecord), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockAclList)(nil).AddRawRecord), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecords mocks base method.
|
|
||||||
func (m *MockAclList) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
|
||||||
func (mr *MockAclListMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockAclList)(nil).AddRawRecords), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close mocks base method.
|
// Close mocks base method.
|
||||||
func (m *MockAclList) Close(arg0 context.Context) error {
|
func (m *MockAclList) Close() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Close", arg0)
|
ret := m.ctrl.Call(m, "Close")
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
// Close indicates an expected call of Close.
|
||||||
func (mr *MockAclListMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
func (mr *MockAclListMockRecorder) Close() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get mocks base method.
|
// Get mocks base method.
|
||||||
@ -108,35 +94,6 @@ func (mr *MockAclListMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIndex mocks base method.
|
|
||||||
func (m *MockAclList) GetIndex(arg0 int) (*list.AclRecord, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndex indicates an expected call of GetIndex.
|
|
||||||
func (mr *MockAclListMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockAclList)(nil).GetIndex), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead mocks base method.
|
|
||||||
func (m *MockAclList) HasHead(arg0 string) bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead indicates an expected call of HasHead.
|
|
||||||
func (mr *MockAclListMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockAclList)(nil).HasHead), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head mocks base method.
|
// Head mocks base method.
|
||||||
func (m *MockAclList) Head() *list.AclRecord {
|
func (m *MockAclList) Head() *list.AclRecord {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -254,20 +211,6 @@ func (mr *MockAclListMockRecorder) RUnlock() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordBuilder mocks base method.
|
|
||||||
func (m *MockAclList) RecordBuilder() list.AclRecordBuilder {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
|
||||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
|
||||||
func (mr *MockAclListMockRecorder) RecordBuilder() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockAclList)(nil).RecordBuilder))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records mocks base method.
|
// Records mocks base method.
|
||||||
func (m *MockAclList) Records() []*list.AclRecord {
|
func (m *MockAclList) Records() []*list.AclRecord {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -282,26 +225,11 @@ func (mr *MockAclListMockRecorder) Records() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockAclList)(nil).Records))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockAclList)(nil).Records))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordsAfter mocks base method.
|
|
||||||
func (m *MockAclList) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
|
||||||
func (mr *MockAclListMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockAclList)(nil).RecordsAfter), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
func (m *MockAclList) Root() *consensusproto.RawRecordWithId {
|
func (m *MockAclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Root")
|
ret := m.ctrl.Call(m, "Root")
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,17 +250,3 @@ func (mr *MockAclListMockRecorder) Unlock() *gomock.Call {
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateRawRecord mocks base method.
|
|
||||||
func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
|
||||||
func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
type AclRecord struct {
|
type AclRecord struct {
|
||||||
Id string
|
Id string
|
||||||
PrevId string
|
PrevId string
|
||||||
|
ReadKeyId string
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
Data []byte
|
Data []byte
|
||||||
Identity crypto.PubKey
|
Identity crypto.PubKey
|
||||||
@ -15,55 +16,7 @@ type AclRecord struct {
|
|||||||
Signature []byte
|
Signature []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestRecord struct {
|
|
||||||
RequestIdentity crypto.PubKey
|
|
||||||
RequestMetadata []byte
|
|
||||||
Type RequestType
|
|
||||||
}
|
|
||||||
|
|
||||||
type AclUserState struct {
|
type AclUserState struct {
|
||||||
PubKey crypto.PubKey
|
PubKey crypto.PubKey
|
||||||
Permissions AclPermissions
|
Permissions aclrecordproto.AclUserPermissions
|
||||||
RequestMetadata []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RequestTypeRemove RequestType = iota
|
|
||||||
RequestTypeJoin
|
|
||||||
)
|
|
||||||
|
|
||||||
type AclPermissions aclrecordproto.AclUserPermissions
|
|
||||||
|
|
||||||
func (p AclPermissions) NoPermissions() bool {
|
|
||||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_None
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p AclPermissions) IsOwner() bool {
|
|
||||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p AclPermissions) CanWrite() bool {
|
|
||||||
switch aclrecordproto.AclUserPermissions(p) {
|
|
||||||
case aclrecordproto.AclUserPermissions_Admin:
|
|
||||||
return true
|
|
||||||
case aclrecordproto.AclUserPermissions_Writer:
|
|
||||||
return true
|
|
||||||
case aclrecordproto.AclUserPermissions_Owner:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p AclPermissions) CanManageAccounts() bool {
|
|
||||||
switch aclrecordproto.AclUserPermissions(p) {
|
|
||||||
case aclrecordproto.AclUserPermissions_Admin:
|
|
||||||
return true
|
|
||||||
case aclrecordproto.AclUserPermissions_Owner:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,218 +0,0 @@
|
|||||||
package list
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContentValidator interface {
|
|
||||||
ValidateAclRecordContents(ch *AclRecord) (err error)
|
|
||||||
ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateInvite(ch *aclrecordproto.AclAccountInvite, 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)
|
|
||||||
ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type contentValidator struct {
|
|
||||||
keyStore crypto.KeyStorage
|
|
||||||
aclState *AclState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
|
|
||||||
if ch.PrevId != c.aclState.lastRecordId {
|
|
||||||
return ErrIncorrectRecordSequence
|
|
||||||
}
|
|
||||||
aclData := ch.Model.(*aclrecordproto.AclData)
|
|
||||||
for _, content := range aclData.AclContent {
|
|
||||||
err = c.validateAclRecordContent(content, ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclContentValue, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
switch {
|
|
||||||
case ch.GetPermissionChange() != nil:
|
|
||||||
return c.ValidatePermissionChange(ch.GetPermissionChange(), authorIdentity)
|
|
||||||
case ch.GetInvite() != nil:
|
|
||||||
return c.ValidateInvite(ch.GetInvite(), authorIdentity)
|
|
||||||
case ch.GetInviteRevoke() != nil:
|
|
||||||
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
|
|
||||||
case ch.GetRequestJoin() != nil:
|
|
||||||
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
|
|
||||||
case ch.GetRequestAccept() != nil:
|
|
||||||
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
|
|
||||||
case ch.GetRequestDecline() != nil:
|
|
||||||
return c.ValidateRequestDecline(ch.GetRequestDecline(), authorIdentity)
|
|
||||||
case ch.GetAccountRemove() != nil:
|
|
||||||
return c.ValidateAccountRemove(ch.GetAccountRemove(), authorIdentity)
|
|
||||||
case ch.GetAccountRequestRemove() != nil:
|
|
||||||
return c.ValidateRequestRemove(ch.GetAccountRequestRemove(), authorIdentity)
|
|
||||||
case ch.GetReadKeyChange() != nil:
|
|
||||||
return c.ValidateReadKeyChange(ch.GetReadKeyChange(), authorIdentity)
|
|
||||||
default:
|
|
||||||
return ErrUnexpectedContentType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
chIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(chIdentity)]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchInvite
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchInvite
|
|
||||||
}
|
|
||||||
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(inviteIdentity)]; exists {
|
|
||||||
return ErrPendingRequest
|
|
||||||
}
|
|
||||||
if !authorIdentity.Equals(inviteIdentity) {
|
|
||||||
return ErrIncorrectIdentity
|
|
||||||
}
|
|
||||||
rawInviteIdentity, err := inviteIdentity.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
|
||||||
if err != nil {
|
|
||||||
return ErrInvalidSignature
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidSignature
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
record, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchRequest
|
|
||||||
}
|
|
||||||
acceptIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !acceptIdentity.Equals(record.RequestIdentity) {
|
|
||||||
return ErrIncorrectIdentity
|
|
||||||
}
|
|
||||||
if ch.Permissions == aclrecordproto.AclUserPermissions_Owner {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchRequest
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
seenIdentities := map[string]struct{}{}
|
|
||||||
for _, rawIdentity := range ch.Identities {
|
|
||||||
identity, err := c.keyStore.PubKeyFromProto(rawIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if identity.Equals(authorIdentity) {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
permissions := c.aclState.Permissions(identity)
|
|
||||||
if permissions.NoPermissions() {
|
|
||||||
return ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
if permissions.IsOwner() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
idKey := mapKeyFromPubKey(identity)
|
|
||||||
if _, exists := seenIdentities[idKey]; exists {
|
|
||||||
return ErrDuplicateAccounts
|
|
||||||
}
|
|
||||||
seenIdentities[mapKeyFromPubKey(identity)] = struct{}{}
|
|
||||||
}
|
|
||||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates)-len(ch.Identities))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if c.aclState.Permissions(authorIdentity).NoPermissions() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(authorIdentity)]; exists {
|
|
||||||
return ErrPendingRequest
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) validateAccountReadKeys(accountKeys []*aclrecordproto.AclEncryptedReadKey, usersNum int) (err error) {
|
|
||||||
if len(accountKeys) != usersNum {
|
|
||||||
return ErrIncorrectNumberOfAccounts
|
|
||||||
}
|
|
||||||
for _, encKeys := range accountKeys {
|
|
||||||
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(identity)]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -3,26 +3,24 @@ package liststorage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inMemoryAclListStorage struct {
|
type inMemoryAclListStorage struct {
|
||||||
id string
|
id string
|
||||||
root *consensusproto.RawRecordWithId
|
root *aclrecordproto.RawAclRecordWithId
|
||||||
head string
|
head string
|
||||||
records map[string]*consensusproto.RawRecordWithId
|
records map[string]*aclrecordproto.RawAclRecordWithId
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInMemoryAclListStorage(
|
func NewInMemoryAclListStorage(
|
||||||
id string,
|
id string,
|
||||||
records []*consensusproto.RawRecordWithId) (ListStorage, error) {
|
records []*aclrecordproto.RawAclRecordWithId) (ListStorage, error) {
|
||||||
|
|
||||||
allRecords := make(map[string]*consensusproto.RawRecordWithId)
|
allRecords := make(map[string]*aclrecordproto.RawAclRecordWithId)
|
||||||
for _, ch := range records {
|
for _, ch := range records {
|
||||||
allRecords[ch.Id] = ch
|
allRecords[ch.Id] = ch
|
||||||
}
|
}
|
||||||
@ -43,7 +41,7 @@ func (t *inMemoryAclListStorage) Id() string {
|
|||||||
return t.id
|
return t.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
func (t *inMemoryAclListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
return t.root, nil
|
return t.root, nil
|
||||||
@ -62,7 +60,7 @@ func (t *inMemoryAclListStorage) SetHead(head string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *consensusproto.RawRecordWithId) error {
|
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawAclRecordWithId) error {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
// TODO: better to do deep copy
|
// TODO: better to do deep copy
|
||||||
@ -70,7 +68,7 @@ func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *conse
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*consensusproto.RawRecordWithId, error) {
|
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
if res, exists := t.records[recordId]; exists {
|
if res, exists := t.records[recordId]; exists {
|
||||||
|
|||||||
@ -4,8 +4,7 @@ package liststorage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -15,15 +14,15 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Exporter interface {
|
type Exporter interface {
|
||||||
ListStorage(root *consensusproto.RawRecordWithId) (ListStorage, error)
|
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListStorage interface {
|
type ListStorage interface {
|
||||||
Id() string
|
Id() string
|
||||||
Root() (*consensusproto.RawRecordWithId, error)
|
Root() (*aclrecordproto.RawAclRecordWithId, error)
|
||||||
Head() (string, error)
|
Head() (string, error)
|
||||||
SetHead(headId string) error
|
SetHead(headId string) error
|
||||||
|
|
||||||
GetRawRecord(ctx context.Context, id string) (*consensusproto.RawRecordWithId, error)
|
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawAclRecordWithId, error)
|
||||||
AddRawRecord(ctx context.Context, rec *consensusproto.RawRecordWithId) error
|
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawAclRecordWithId) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockListStorage is a mock of ListStorage interface.
|
// MockListStorage is a mock of ListStorage interface.
|
||||||
@ -36,7 +36,7 @@ func (m *MockListStorage) EXPECT() *MockListStorageMockRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
// AddRawRecord mocks base method.
|
||||||
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *consensusproto.RawRecordWithId) error {
|
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawAclRecordWithId) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@ -50,10 +50,10 @@ func (mr *MockListStorageMockRecorder) AddRawRecord(arg0, arg1 interface{}) *gom
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRawRecord mocks base method.
|
// GetRawRecord mocks base method.
|
||||||
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*consensusproto.RawRecordWithId, error) {
|
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@ -94,10 +94,10 @@ func (mr *MockListStorageMockRecorder) Id() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
func (m *MockListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
func (m *MockListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Root")
|
ret := m.ctrl.Call(m, "Root")
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,120 +0,0 @@
|
|||||||
//go:generate mockgen -destination mock_syncacl/mock_syncacl.go github.com/anyproto/any-sync/commonspace/object/acl/syncacl SyncAcl,SyncClient,RequestFactory,AclSyncProtocol
|
|
||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AclSyncProtocol interface {
|
|
||||||
HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error)
|
|
||||||
FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error)
|
|
||||||
FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclSyncProtocol struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
aclList list.AclList
|
|
||||||
reqFactory RequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error) {
|
|
||||||
isEmptyUpdate := len(update.Records) == 0
|
|
||||||
log := a.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.String("update head", update.Head),
|
|
||||||
zap.Int("len(update records)", len(update.Records)))
|
|
||||||
log.DebugCtx(ctx, "received acl head update message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "acl head update finished with error", zap.Error(err))
|
|
||||||
} else if request != nil {
|
|
||||||
cnt := request.Content.GetFullSyncRequest()
|
|
||||||
log.DebugCtx(ctx, "returning acl full sync request", zap.String("request head", cnt.Head))
|
|
||||||
} else {
|
|
||||||
if !isEmptyUpdate {
|
|
||||||
log.DebugCtx(ctx, "acl head update finished correctly")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if isEmptyUpdate {
|
|
||||||
headEquals := a.aclList.Head().Id == update.Head
|
|
||||||
log.DebugCtx(ctx, "is empty acl head update", zap.Bool("headEquals", headEquals))
|
|
||||||
if headEquals {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
|
||||||
}
|
|
||||||
if a.aclList.HasHead(update.Head) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = a.aclList.AddRawRecords(update.Records)
|
|
||||||
if err == list.ErrIncorrectRecordSequence {
|
|
||||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error) {
|
|
||||||
log := a.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.String("request head", request.Head),
|
|
||||||
zap.Int("len(request records)", len(request.Records)))
|
|
||||||
log.DebugCtx(ctx, "received acl full sync request message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "acl full sync request finished with error", zap.Error(err))
|
|
||||||
} else if response != nil {
|
|
||||||
cnt := response.Content.GetFullSyncResponse()
|
|
||||||
log.DebugCtx(ctx, "acl full sync response sent", zap.String("response head", cnt.Head), zap.Int("len(response records)", len(cnt.Records)))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if !a.aclList.HasHead(request.Head) {
|
|
||||||
if len(request.Records) > 0 {
|
|
||||||
// in this case we can try to add some records
|
|
||||||
err = a.aclList.AddRawRecords(request.Records)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// here it is impossible for us to do anything, we can't return records after head as defined in request, because we don't have it
|
|
||||||
return nil, list.ErrIncorrectRecordSequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.reqFactory.CreateFullSyncResponse(a.aclList, request.Head)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error) {
|
|
||||||
log := a.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.String("response head", response.Head),
|
|
||||||
zap.Int("len(response records)", len(response.Records)))
|
|
||||||
log.DebugCtx(ctx, "received acl full sync response message")
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "acl full sync response failed", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
log.DebugCtx(ctx, "acl full sync response succeeded")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if a.aclList.HasHead(response.Head) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return a.aclList.AddRawRecords(response.Records)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAclSyncProtocol(spaceId string, aclList list.AclList, reqFactory RequestFactory) *aclSyncProtocol {
|
|
||||||
return &aclSyncProtocol{
|
|
||||||
log: log.With(zap.String("spaceId", spaceId), zap.String("aclId", aclList.Id())),
|
|
||||||
spaceId: spaceId,
|
|
||||||
aclList: aclList,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type aclSyncProtocolFixture struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
aclId string
|
|
||||||
aclMock *mock_list.MockAclList
|
|
||||||
reqFactory *mock_syncacl.MockRequestFactory
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
syncProtocol AclSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyncProtocolFixture(t *testing.T) *aclSyncProtocolFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
aclList := mock_list.NewMockAclList(ctrl)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
reqFactory := mock_syncacl.NewMockRequestFactory(ctrl)
|
|
||||||
aclList.EXPECT().Id().Return("aclId")
|
|
||||||
syncProtocol := newAclSyncProtocol(spaceId, aclList, reqFactory)
|
|
||||||
return &aclSyncProtocolFixture{
|
|
||||||
log: log,
|
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
aclId: "aclId",
|
|
||||||
aclMock: aclList,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
ctrl: ctrl,
|
|
||||||
syncProtocol: syncProtocol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *aclSyncProtocolFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadUpdate(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullRequest := &consensusproto.LogSyncMessage{
|
|
||||||
Content: &consensusproto.LogSyncContentValue{
|
|
||||||
Value: &consensusproto.LogSyncContentValue_FullSyncRequest{
|
|
||||||
FullSyncRequest: &consensusproto.LogFullSyncRequest{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(nil)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Nil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update results in full request", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(list.ErrIncorrectRecordSequence)
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Equal(t, fullRequest, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update old heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Nil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update empty equals", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h1"})
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Nil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update empty results in full request", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h2"})
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Equal(t, fullRequest, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFullSyncRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullResponse := &consensusproto.LogSyncMessage{
|
|
||||||
Content: &consensusproto.LogSyncContentValue{
|
|
||||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
|
||||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
t.Run("full sync request non empty all heads added", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(fullRequest.Records).Return(nil)
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
|
||||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
|
||||||
require.Equal(t, fullResponse, resp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("full sync request non empty head exists", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
|
||||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
|
||||||
require.Equal(t, fullResponse, resp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("full sync request empty head not exists", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
|
||||||
require.Nil(t, resp)
|
|
||||||
require.Error(t, list.ErrIncorrectRecordSequence, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFullSyncResponse(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
t.Run("full sync response no heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(fullResponse.Records).Return(nil)
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("full sync response has heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package headupdater
|
|
||||||
|
|
||||||
type HeadUpdater interface {
|
|
||||||
UpdateHeads(id string, heads []string)
|
|
||||||
}
|
|
||||||
@ -1,694 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/syncacl (interfaces: SyncAcl,SyncClient,RequestFactory,AclSyncProtocol)
|
|
||||||
|
|
||||||
// Package mock_syncacl is a generated GoMock package.
|
|
||||||
package mock_syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
headupdater "github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockSyncAcl is a mock of SyncAcl interface.
|
|
||||||
type MockSyncAcl struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSyncAclMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncAclMockRecorder is the mock recorder for MockSyncAcl.
|
|
||||||
type MockSyncAclMockRecorder struct {
|
|
||||||
mock *MockSyncAcl
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSyncAcl creates a new mock instance.
|
|
||||||
func NewMockSyncAcl(ctrl *gomock.Controller) *MockSyncAcl {
|
|
||||||
mock := &MockSyncAcl{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSyncAclMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSyncAcl) EXPECT() *MockSyncAclMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclState mocks base method.
|
|
||||||
func (m *MockSyncAcl) AclState() *list.AclState {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AclState")
|
|
||||||
ret0, _ := ret[0].(*list.AclState)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclState indicates an expected call of AclState.
|
|
||||||
func (mr *MockSyncAclMockRecorder) AclState() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclState", reflect.TypeOf((*MockSyncAcl)(nil).AclState))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
|
||||||
func (m *MockSyncAcl) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
|
||||||
func (mr *MockSyncAclMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecord), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecords mocks base method.
|
|
||||||
func (m *MockSyncAcl) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
|
||||||
func (mr *MockSyncAclMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecords), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close mocks base method.
|
|
||||||
func (m *MockSyncAcl) Close(arg0 context.Context) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Close", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncAcl)(nil).Close), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get mocks base method.
|
|
||||||
func (m *MockSyncAcl) Get(arg0 string) (*list.AclRecord, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Get", arg0)
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get indicates an expected call of Get.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSyncAcl)(nil).Get), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndex mocks base method.
|
|
||||||
func (m *MockSyncAcl) GetIndex(arg0 int) (*list.AclRecord, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndex indicates an expected call of GetIndex.
|
|
||||||
func (mr *MockSyncAclMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockSyncAcl)(nil).GetIndex), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMessage mocks base method.
|
|
||||||
func (m *MockSyncAcl) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMessage indicates an expected call of HandleMessage.
|
|
||||||
func (mr *MockSyncAclMockRecorder) HandleMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncAcl)(nil).HandleMessage), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequest mocks base method.
|
|
||||||
func (m *MockSyncAcl) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequest indicates an expected call of HandleRequest.
|
|
||||||
func (mr *MockSyncAclMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncAcl)(nil).HandleRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead mocks base method.
|
|
||||||
func (m *MockSyncAcl) HasHead(arg0 string) bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead indicates an expected call of HasHead.
|
|
||||||
func (mr *MockSyncAclMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockSyncAcl)(nil).HasHead), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head mocks base method.
|
|
||||||
func (m *MockSyncAcl) Head() *list.AclRecord {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Head")
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head indicates an expected call of Head.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Head() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockSyncAcl)(nil).Head))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id mocks base method.
|
|
||||||
func (m *MockSyncAcl) Id() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Id")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id indicates an expected call of Id.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Id() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockSyncAcl)(nil).Id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockSyncAcl) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockSyncAcl)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAfter mocks base method.
|
|
||||||
func (m *MockSyncAcl) IsAfter(arg0, arg1 string) (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "IsAfter", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAfter indicates an expected call of IsAfter.
|
|
||||||
func (mr *MockSyncAclMockRecorder) IsAfter(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAfter", reflect.TypeOf((*MockSyncAcl)(nil).IsAfter), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate mocks base method.
|
|
||||||
func (m *MockSyncAcl) Iterate(arg0 func(*list.AclRecord) bool) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Iterate", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate indicates an expected call of Iterate.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Iterate(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockSyncAcl)(nil).Iterate), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateFrom mocks base method.
|
|
||||||
func (m *MockSyncAcl) IterateFrom(arg0 string, arg1 func(*list.AclRecord) bool) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "IterateFrom", arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateFrom indicates an expected call of IterateFrom.
|
|
||||||
func (mr *MockSyncAclMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockSyncAcl)(nil).IterateFrom), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyStorage mocks base method.
|
|
||||||
func (m *MockSyncAcl) KeyStorage() crypto.KeyStorage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "KeyStorage")
|
|
||||||
ret0, _ := ret[0].(crypto.KeyStorage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyStorage indicates an expected call of KeyStorage.
|
|
||||||
func (mr *MockSyncAclMockRecorder) KeyStorage() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyStorage", reflect.TypeOf((*MockSyncAcl)(nil).KeyStorage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock mocks base method.
|
|
||||||
func (m *MockSyncAcl) Lock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Lock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock indicates an expected call of Lock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Lock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncAcl)(nil).Lock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockSyncAcl) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSyncAcl)(nil).Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RLock mocks base method.
|
|
||||||
func (m *MockSyncAcl) RLock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "RLock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RLock indicates an expected call of RLock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RLock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockSyncAcl)(nil).RLock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUnlock mocks base method.
|
|
||||||
func (m *MockSyncAcl) RUnlock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "RUnlock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUnlock indicates an expected call of RUnlock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RUnlock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockSyncAcl)(nil).RUnlock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordBuilder mocks base method.
|
|
||||||
func (m *MockSyncAcl) RecordBuilder() list.AclRecordBuilder {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
|
||||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RecordBuilder() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockSyncAcl)(nil).RecordBuilder))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records mocks base method.
|
|
||||||
func (m *MockSyncAcl) Records() []*list.AclRecord {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Records")
|
|
||||||
ret0, _ := ret[0].([]*list.AclRecord)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records indicates an expected call of Records.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Records() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockSyncAcl)(nil).Records))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordsAfter mocks base method.
|
|
||||||
func (m *MockSyncAcl) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockSyncAcl)(nil).RecordsAfter), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root mocks base method.
|
|
||||||
func (m *MockSyncAcl) Root() *consensusproto.RawRecordWithId {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Root")
|
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root indicates an expected call of Root.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Root() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncAcl)(nil).Root))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run mocks base method.
|
|
||||||
func (m *MockSyncAcl) Run(arg0 context.Context) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Run", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run indicates an expected call of Run.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockSyncAcl)(nil).Run), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeadUpdater mocks base method.
|
|
||||||
func (m *MockSyncAcl) SetHeadUpdater(arg0 headupdater.HeadUpdater) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "SetHeadUpdater", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeadUpdater indicates an expected call of SetHeadUpdater.
|
|
||||||
func (mr *MockSyncAclMockRecorder) SetHeadUpdater(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeadUpdater", reflect.TypeOf((*MockSyncAcl)(nil).SetHeadUpdater), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncWithPeer mocks base method.
|
|
||||||
func (m *MockSyncAcl) SyncWithPeer(arg0 context.Context, arg1 string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncWithPeer indicates an expected call of SyncWithPeer.
|
|
||||||
func (mr *MockSyncAclMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWithPeer", reflect.TypeOf((*MockSyncAcl)(nil).SyncWithPeer), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock mocks base method.
|
|
||||||
func (m *MockSyncAcl) Unlock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Unlock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock indicates an expected call of Unlock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Unlock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockSyncAcl)(nil).Unlock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateRawRecord mocks base method.
|
|
||||||
func (m *MockSyncAcl) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
|
||||||
func (mr *MockSyncAclMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).ValidateRawRecord), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncClient is a mock of SyncClient interface.
|
|
||||||
type MockSyncClient struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSyncClientMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
|
||||||
type MockSyncClientMockRecorder struct {
|
|
||||||
mock *MockSyncClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSyncClient creates a new mock instance.
|
|
||||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
|
||||||
mock := &MockSyncClient{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast mocks base method.
|
|
||||||
func (m *MockSyncClient) Broadcast(arg0 *consensusproto.LogSyncMessage) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Broadcast", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast indicates an expected call of Broadcast.
|
|
||||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) QueueRequest(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest indicates an expected call of QueueRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest indicates an expected call of SendRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) SendUpdate(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate indicates an expected call of SendUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactory is a mock of RequestFactory interface.
|
|
||||||
type MockRequestFactory struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockRequestFactoryMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
|
||||||
type MockRequestFactoryMockRecorder struct {
|
|
||||||
mock *MockRequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRequestFactory creates a new mock instance.
|
|
||||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
|
||||||
mock := &MockRequestFactory{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockAclSyncProtocol is a mock of AclSyncProtocol interface.
|
|
||||||
type MockAclSyncProtocol struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockAclSyncProtocolMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockAclSyncProtocolMockRecorder is the mock recorder for MockAclSyncProtocol.
|
|
||||||
type MockAclSyncProtocolMockRecorder struct {
|
|
||||||
mock *MockAclSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockAclSyncProtocol creates a new mock instance.
|
|
||||||
func NewMockAclSyncProtocol(ctrl *gomock.Controller) *MockAclSyncProtocol {
|
|
||||||
mock := &MockAclSyncProtocol{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockAclSyncProtocolMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockAclSyncProtocol) EXPECT() *MockAclSyncProtocolMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest mocks base method.
|
|
||||||
func (m *MockAclSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncRequest) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
|
||||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse mocks base method.
|
|
||||||
func (m *MockAclSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncResponse) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
|
||||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate mocks base method.
|
|
||||||
func (m *MockAclSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *consensusproto.LogHeadUpdate) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate indicates an expected call of HeadUpdate.
|
|
||||||
func (mr *MockAclSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockAclSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestFactory interface {
|
|
||||||
CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage)
|
|
||||||
CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error)
|
|
||||||
CreateFullSyncResponse(l list.AclList, theirHead string) (*consensusproto.LogSyncMessage, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestFactory struct{}
|
|
||||||
|
|
||||||
func NewRequestFactory() RequestFactory {
|
|
||||||
return &requestFactory{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestFactory) CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage) {
|
|
||||||
return consensusproto.WrapHeadUpdate(&consensusproto.LogHeadUpdate{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
Records: added,
|
|
||||||
}, l.Root())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestFactory) CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error) {
|
|
||||||
if !l.HasHead(theirHead) {
|
|
||||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
}, l.Root()), nil
|
|
||||||
}
|
|
||||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
Records: records,
|
|
||||||
}, l.Root()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestFactory) CreateFullSyncResponse(l list.AclList, theirHead string) (resp *consensusproto.LogSyncMessage, err error) {
|
|
||||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return consensusproto.WrapFullResponse(&consensusproto.LogFullSyncResponse{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
Records: records,
|
|
||||||
}, l.Root()), nil
|
|
||||||
}
|
|
||||||
@ -1,130 +1,21 @@
|
|||||||
package syncacl
|
package syncacl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
|
||||||
"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/list"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"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/consensus/consensusproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.acl.syncacl"
|
type SyncAcl struct {
|
||||||
|
|
||||||
var (
|
|
||||||
log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
ErrSyncAclClosed = errors.New("sync acl is closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
type SyncAcl interface {
|
|
||||||
app.ComponentRunnable
|
|
||||||
list.AclList
|
list.AclList
|
||||||
syncobjectgetter.SyncObject
|
synchandler.SyncHandler
|
||||||
SetHeadUpdater(updater headupdater.HeadUpdater)
|
messagePool objectsync.MessagePool
|
||||||
SyncWithPeer(ctx context.Context, peerId string) (err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() SyncAcl {
|
func NewSyncAcl(aclList list.AclList, messagePool objectsync.MessagePool) *SyncAcl {
|
||||||
return &syncAcl{}
|
return &SyncAcl{
|
||||||
}
|
AclList: aclList,
|
||||||
|
SyncHandler: nil,
|
||||||
type syncAcl struct {
|
messagePool: messagePool,
|
||||||
list.AclList
|
|
||||||
syncClient SyncClient
|
|
||||||
syncHandler synchandler.SyncHandler
|
|
||||||
headUpdater headupdater.HeadUpdater
|
|
||||||
isClosed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Run(ctx context.Context) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
return s.syncHandler.HandleRequest(ctx, senderId, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) SetHeadUpdater(updater headupdater.HeadUpdater) {
|
|
||||||
s.headUpdater = updater
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
return s.syncHandler.HandleMessage(ctx, senderId, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Init(a *app.App) (err error) {
|
|
||||||
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
aclStorage, err := storage.AclStorage()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
|
||||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
spaceId := storage.Id()
|
|
||||||
requestManager := a.MustComponent(requestmanager.CName).(requestmanager.RequestManager)
|
|
||||||
peerManager := a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
|
||||||
syncStatus := a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
|
||||||
s.syncClient = NewSyncClient(spaceId, requestManager, peerManager)
|
|
||||||
s.syncHandler = newSyncAclHandler(storage.Id(), s, s.syncClient, syncStatus)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
|
||||||
if s.isClosed {
|
|
||||||
return ErrSyncAclClosed
|
|
||||||
}
|
|
||||||
err = s.AclList.AddRawRecord(rawRec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, []*consensusproto.RawRecordWithId{rawRec})
|
|
||||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRec.Id})
|
|
||||||
s.syncClient.Broadcast(headUpdate)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
|
||||||
if s.isClosed {
|
|
||||||
return ErrSyncAclClosed
|
|
||||||
}
|
|
||||||
err = s.AclList.AddRawRecords(rawRecords)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, rawRecords)
|
|
||||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRecords[len(rawRecords)-1].Id})
|
|
||||||
s.syncClient.Broadcast(headUpdate)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) SyncWithPeer(ctx context.Context, peerId string) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
|
||||||
return s.syncClient.SendUpdate(peerId, headUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Close(ctx context.Context) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
s.isClosed = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,81 +2,30 @@ package syncacl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrMessageIsRequest = errors.New("message is request")
|
|
||||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncAclHandler struct {
|
type syncAclHandler struct {
|
||||||
aclList list.AclList
|
acl list.AclList
|
||||||
syncClient SyncClient
|
|
||||||
syncProtocol AclSyncProtocol
|
|
||||||
syncStatus syncstatus.StatusUpdater
|
|
||||||
spaceId string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSyncAclHandler(spaceId string, aclList list.AclList, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
return &syncAclHandler{
|
aclMsg := &aclrecordproto.AclSyncMessage{}
|
||||||
aclList: aclList,
|
if err = aclMsg.Unmarshal(req.Payload); err != nil {
|
||||||
syncClient: syncClient,
|
|
||||||
syncProtocol: newAclSyncProtocol(spaceId, aclList, syncClient),
|
|
||||||
syncStatus: syncStatus,
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
|
||||||
err = proto.Unmarshal(message.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := unmarshalled.GetContent()
|
content := aclMsg.GetContent()
|
||||||
head := consensusproto.GetHead(unmarshalled)
|
|
||||||
s.syncStatus.HeadsReceive(senderId, s.aclList.Id(), []string{head})
|
|
||||||
s.aclList.Lock()
|
|
||||||
defer s.aclList.Unlock()
|
|
||||||
switch {
|
switch {
|
||||||
case content.GetHeadUpdate() != nil:
|
case content.GetAddRecords() != nil:
|
||||||
var syncReq *consensusproto.LogSyncMessage
|
return s.handleAddRecords(ctx, senderId, content.GetAddRecords())
|
||||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
default:
|
||||||
if err != nil || syncReq == nil {
|
return fmt.Errorf("unexpected aclSync message: %T", content.Value)
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.syncClient.QueueRequest(senderId, syncReq)
|
|
||||||
case content.GetFullSyncRequest() != nil:
|
|
||||||
return ErrMessageIsRequest
|
|
||||||
case content.GetFullSyncResponse() != nil:
|
|
||||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncAclHandler) handleAddRecords(ctx context.Context, senderId string, addRecord *aclrecordproto.AclAddRecords) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncAclHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
|
||||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
|
||||||
if fullSyncRequest == nil {
|
|
||||||
return nil, ErrMessageIsNotRequest
|
|
||||||
}
|
|
||||||
s.aclList.Lock()
|
|
||||||
defer s.aclList.Unlock()
|
|
||||||
aclResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return spacesyncproto.MarshallSyncMessage(aclResp, s.spaceId, s.aclList.Id())
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,233 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testAclMock struct {
|
|
||||||
*mock_list.MockAclList
|
|
||||||
m sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestAclMock(mockAcl *mock_list.MockAclList) *testAclMock {
|
|
||||||
return &testAclMock{
|
|
||||||
MockAclList: mockAcl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) Lock() {
|
|
||||||
t.m.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) RLock() {
|
|
||||||
t.m.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) Unlock() {
|
|
||||||
t.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) RUnlock() {
|
|
||||||
t.m.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) TryLock() bool {
|
|
||||||
return t.m.TryLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) TryRLock() bool {
|
|
||||||
return t.m.TryRLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncHandlerFixture struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
syncClientMock *mock_syncacl.MockSyncClient
|
|
||||||
aclMock *testAclMock
|
|
||||||
syncProtocolMock *mock_syncacl.MockAclSyncProtocol
|
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
aclId string
|
|
||||||
|
|
||||||
syncHandler *syncAclHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
aclMock := newTestAclMock(mock_list.NewMockAclList(ctrl))
|
|
||||||
syncClientMock := mock_syncacl.NewMockSyncClient(ctrl)
|
|
||||||
syncProtocolMock := mock_syncacl.NewMockAclSyncProtocol(ctrl)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
|
|
||||||
syncHandler := &syncAclHandler{
|
|
||||||
aclList: aclMock,
|
|
||||||
syncClient: syncClientMock,
|
|
||||||
syncProtocol: syncProtocolMock,
|
|
||||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
return &syncHandlerFixture{
|
|
||||||
ctrl: ctrl,
|
|
||||||
syncClientMock: syncClientMock,
|
|
||||||
aclMock: aclMock,
|
|
||||||
syncProtocolMock: syncProtocolMock,
|
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
aclId: "aclId",
|
|
||||||
syncHandler: syncHandler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *syncHandlerFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncAclHandler_HandleMessage(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
t.Run("handle head update, request returned", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
syncReq := &consensusproto.LogSyncMessage{}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
|
||||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, syncReq).Return(nil)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("handle head update, no request", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("handle head update, returned error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
expectedErr := fmt.Errorf("some error")
|
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, expectedErr)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Error(t, expectedErr, err)
|
|
||||||
})
|
|
||||||
t.Run("handle full sync request is forbidden", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Error(t, ErrMessageIsRequest, err)
|
|
||||||
})
|
|
||||||
t.Run("handle full sync response, no error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapFullResponse(fullResponse, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncAclHandler_HandleRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
t.Run("handle full sync request, no error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
fullResp := &consensusproto.LogSyncMessage{
|
|
||||||
Content: &consensusproto.LogSyncContentValue{
|
|
||||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
|
||||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "returnedHead",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(fullResp, nil)
|
|
||||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
|
||||||
err = proto.Unmarshal(res.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.Equal(t, "returnedHead", consensusproto.GetHead(unmarshalled))
|
|
||||||
})
|
|
||||||
t.Run("handle other message returns error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Error(t, ErrMessageIsNotRequest, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SyncClient interface {
|
|
||||||
RequestFactory
|
|
||||||
Broadcast(msg *consensusproto.LogSyncMessage)
|
|
||||||
SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
|
||||||
QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
|
||||||
SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncClient struct {
|
|
||||||
RequestFactory
|
|
||||||
spaceId string
|
|
||||||
requestManager requestmanager.RequestManager
|
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
|
||||||
return &syncClient{
|
|
||||||
RequestFactory: &requestFactory{},
|
|
||||||
spaceId: spaceId,
|
|
||||||
requestManager: requestManager,
|
|
||||||
peerManager: peerManager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) Broadcast(msg *consensusproto.LogSyncMessage) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("broadcast error", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
|
||||||
}
|
|
||||||
@ -15,7 +15,7 @@ type TreeImportParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
||||||
aclList, err := list.BuildAclList(params.ListStorage, list.NoOpAcceptorVerifier{})
|
aclList, err := list.BuildAclList(params.ListStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import (
|
|||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockObjectTree is a mock of ObjectTree interface.
|
// MockObjectTree is a mock of ObjectTree interface.
|
||||||
|
|||||||
@ -4,11 +4,11 @@ package objecttree
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
@ -248,7 +248,9 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
|||||||
pubKey = content.Key.GetPublic()
|
pubKey = content.Key.GetPublic()
|
||||||
readKeyId string
|
readKeyId string
|
||||||
)
|
)
|
||||||
if !state.Permissions(pubKey).CanWrite() {
|
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
|
||||||
|
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
|
||||||
|
if !canWrite {
|
||||||
err = list.ErrInsufficientPermissions
|
err = list.ErrInsufficientPermissions
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,6 @@ package objecttree
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
@ -13,6 +10,8 @@ import (
|
|||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testTreeContext struct {
|
type testTreeContext struct {
|
||||||
@ -124,7 +123,6 @@ func TestObjectTree(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
|
require.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
|
||||||
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
|
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
|
||||||
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
|
|
||||||
})
|
})
|
||||||
t.Run("timestamp is set correctly", func(t *testing.T) {
|
t.Run("timestamp is set correctly", func(t *testing.T) {
|
||||||
someTs := time.Now().Add(time.Hour).Unix()
|
someTs := time.Now().Add(time.Hour).Unix()
|
||||||
@ -141,7 +139,6 @@ func TestObjectTree(t *testing.T) {
|
|||||||
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, ch.Timestamp, someTs)
|
require.Equal(t, ch.Timestamp, someTs)
|
||||||
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package objecttree
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anyproto/any-sync/util/slice"
|
||||||
@ -52,18 +52,20 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
|
|||||||
|
|
||||||
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
||||||
var (
|
var (
|
||||||
userState list.AclUserState
|
perm list.AclUserState
|
||||||
state = aclList.AclState()
|
state = aclList.AclState()
|
||||||
)
|
)
|
||||||
// checking if the user could write
|
// checking if the user could write
|
||||||
userState, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !userState.Permissions.CanWrite() {
|
|
||||||
|
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
|
||||||
err = list.ErrInsufficientPermissions
|
err = list.ErrInsufficientPermissions
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Id == tree.RootId() {
|
if c.Id == tree.RootId() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,10 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anyproto/any-sync/util/slice"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rawChangeLoader struct {
|
type rawChangeLoader struct {
|
||||||
@ -23,7 +22,6 @@ type rawCacheEntry struct {
|
|||||||
change *Change
|
change *Change
|
||||||
rawChange *treechangeproto.RawTreeChangeWithId
|
rawChange *treechangeproto.RawTreeChangeWithId
|
||||||
position int
|
position int
|
||||||
removed bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||||
@ -128,6 +126,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
entry.position = -1
|
||||||
r.cache[b] = entry
|
r.cache[b] = entry
|
||||||
existingBreakpoints = append(existingBreakpoints, b)
|
existingBreakpoints = append(existingBreakpoints, b)
|
||||||
}
|
}
|
||||||
@ -136,7 +135,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
dfs := func(
|
dfs := func(
|
||||||
commonSnapshot string,
|
commonSnapshot string,
|
||||||
heads []string,
|
heads []string,
|
||||||
shouldVisit func(entry rawCacheEntry, mapExists bool) bool,
|
startCounter int,
|
||||||
|
shouldVisit func(counter int, mapExists bool) bool,
|
||||||
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
||||||
|
|
||||||
// resetting stack
|
// resetting stack
|
||||||
@ -150,7 +150,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
r.idStack = r.idStack[:len(r.idStack)-1]
|
r.idStack = r.idStack[:len(r.idStack)-1]
|
||||||
|
|
||||||
entry, exists := r.cache[id]
|
entry, exists := r.cache[id]
|
||||||
if !shouldVisit(entry, exists) {
|
if !shouldVisit(entry.position, exists) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if id == commonSnapshot {
|
if id == commonSnapshot {
|
||||||
@ -159,6 +159,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
entry, err = r.loadEntry(id)
|
entry, err = r.loadEntry(id)
|
||||||
|
entry.position = -1
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -173,7 +174,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
prevEntry, exists := r.cache[prev]
|
prevEntry, exists := r.cache[prev]
|
||||||
if !shouldVisit(prevEntry, exists) {
|
if !shouldVisit(prevEntry.position, exists) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.idStack = append(r.idStack, prev)
|
r.idStack = append(r.idStack, prev)
|
||||||
@ -186,8 +187,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
r.idStack = append(r.idStack, heads...)
|
r.idStack = append(r.idStack, heads...)
|
||||||
var buffer []*treechangeproto.RawTreeChangeWithId
|
var buffer []*treechangeproto.RawTreeChangeWithId
|
||||||
|
|
||||||
rootVisited := dfs(commonSnapshot, heads,
|
rootVisited := dfs(commonSnapshot, heads, 0,
|
||||||
func(_ rawCacheEntry, mapExists bool) bool {
|
func(counter int, mapExists bool) bool {
|
||||||
return !mapExists
|
return !mapExists
|
||||||
},
|
},
|
||||||
func(entry rawCacheEntry) rawCacheEntry {
|
func(entry rawCacheEntry) rawCacheEntry {
|
||||||
@ -212,13 +213,11 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// marking all visited as nil
|
// marking all visited as nil
|
||||||
dfs(commonSnapshot, existingBreakpoints,
|
dfs(commonSnapshot, existingBreakpoints, len(buffer),
|
||||||
func(entry rawCacheEntry, mapExists bool) bool {
|
func(counter int, mapExists bool) bool {
|
||||||
// only going through already loaded changes
|
return !mapExists || counter < len(buffer)
|
||||||
return mapExists && !entry.removed
|
|
||||||
},
|
},
|
||||||
func(entry rawCacheEntry) rawCacheEntry {
|
func(entry rawCacheEntry) rawCacheEntry {
|
||||||
entry.removed = true
|
|
||||||
if entry.position != -1 {
|
if entry.position != -1 {
|
||||||
buffer[entry.position] = nil
|
buffer[entry.position] = nil
|
||||||
}
|
}
|
||||||
@ -249,7 +248,6 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
|
|||||||
entry = rawCacheEntry{
|
entry = rawCacheEntry{
|
||||||
change: change,
|
change: change,
|
||||||
rawChange: rawChange,
|
rawChange: rawChange,
|
||||||
position: -1,
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,6 @@ func (t *Tree) AddMergedHead(c *Change) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.headIds = []string{c.Id}
|
t.headIds = []string{c.Id}
|
||||||
t.lastIteratedHeadId = c.Id
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,10 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newChange(id string, snapshotId string, prevIds ...string) *Change {
|
func newChange(id string, snapshotId string, prevIds ...string) *Change {
|
||||||
@ -28,17 +26,6 @@ func newSnapshot(id, snapshotId string, prevIds ...string) *Change {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTree_AddMergedHead(t *testing.T) {
|
|
||||||
tr := new(Tree)
|
|
||||||
_, _ = tr.Add(
|
|
||||||
newSnapshot("root", ""),
|
|
||||||
newChange("one", "root", "root"),
|
|
||||||
)
|
|
||||||
require.Equal(t, tr.lastIteratedHeadId, "one")
|
|
||||||
tr.AddMergedHead(newChange("two", "root", "one"))
|
|
||||||
require.Equal(t, tr.lastIteratedHeadId, "two")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTree_Add(t *testing.T) {
|
func TestTree_Add(t *testing.T) {
|
||||||
t.Run("add first el", func(t *testing.T) {
|
t.Run("add first el", func(t *testing.T) {
|
||||||
tr := new(Tree)
|
tr := new(Tree)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol)
|
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable)
|
||||||
|
|
||||||
// Package mock_synctree is a generated GoMock package.
|
// Package mock_synctree is a generated GoMock package.
|
||||||
package mock_synctree
|
package mock_synctree
|
||||||
@ -15,7 +15,7 @@ import (
|
|||||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockSyncTree is a mock of SyncTree interface.
|
// MockSyncTree is a mock of SyncTree interface.
|
||||||
@ -186,21 +186,6 @@ func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 interface{})
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleRequest mocks base method.
|
|
||||||
func (m *MockSyncTree) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequest indicates an expected call of HandleRequest.
|
|
||||||
func (mr *MockSyncTreeMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncTree)(nil).HandleRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasChanges mocks base method.
|
// HasChanges mocks base method.
|
||||||
func (m *MockSyncTree) HasChanges(arg0 ...string) bool {
|
func (m *MockSyncTree) HasChanges(arg0 ...string) bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -605,287 +590,3 @@ func (mr *MockHeadNotifiableMockRecorder) UpdateHeads(arg0, arg1 interface{}) *g
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockHeadNotifiable)(nil).UpdateHeads), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockHeadNotifiable)(nil).UpdateHeads), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockSyncClient is a mock of SyncClient interface.
|
|
||||||
type MockSyncClient struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSyncClientMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
|
||||||
type MockSyncClientMockRecorder struct {
|
|
||||||
mock *MockSyncClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSyncClient creates a new mock instance.
|
|
||||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
|
||||||
mock := &MockSyncClient{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast mocks base method.
|
|
||||||
func (m *MockSyncClient) Broadcast(arg0 *treechangeproto.TreeSyncMessage) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Broadcast", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast indicates an expected call of Broadcast.
|
|
||||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) QueueRequest(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest indicates an expected call of QueueRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2, arg3)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest indicates an expected call of SendRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2, arg3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) SendUpdate(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate indicates an expected call of SendUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactory is a mock of RequestFactory interface.
|
|
||||||
type MockRequestFactory struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockRequestFactoryMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
|
||||||
type MockRequestFactoryMockRecorder struct {
|
|
||||||
mock *MockRequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRequestFactory creates a new mock instance.
|
|
||||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
|
||||||
mock := &MockRequestFactory{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateNewTreeRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTreeSyncProtocol is a mock of TreeSyncProtocol interface.
|
|
||||||
type MockTreeSyncProtocol struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockTreeSyncProtocolMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTreeSyncProtocolMockRecorder is the mock recorder for MockTreeSyncProtocol.
|
|
||||||
type MockTreeSyncProtocolMockRecorder struct {
|
|
||||||
mock *MockTreeSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockTreeSyncProtocol creates a new mock instance.
|
|
||||||
func NewMockTreeSyncProtocol(ctrl *gomock.Controller) *MockTreeSyncProtocol {
|
|
||||||
mock := &MockTreeSyncProtocol{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockTreeSyncProtocolMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockTreeSyncProtocol) EXPECT() *MockTreeSyncProtocolMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest mocks base method.
|
|
||||||
func (m *MockTreeSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncRequest) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
|
||||||
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse mocks base method.
|
|
||||||
func (m *MockTreeSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncResponse) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
|
||||||
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate mocks base method.
|
|
||||||
func (m *MockTreeSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeHeadUpdate) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate indicates an expected call of HeadUpdate.
|
|
||||||
func (mr *MockTreeSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockTreeSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SyncClient interface {
|
|
||||||
RequestFactory
|
|
||||||
Broadcast(msg *treechangeproto.TreeSyncMessage)
|
|
||||||
SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
|
||||||
QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
|
||||||
SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncClient struct {
|
|
||||||
RequestFactory
|
|
||||||
spaceId string
|
|
||||||
requestManager requestmanager.RequestManager
|
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
|
||||||
return &syncClient{
|
|
||||||
RequestFactory: &requestFactory{},
|
|
||||||
spaceId: spaceId,
|
|
||||||
requestManager: requestManager,
|
|
||||||
peerManager: peerManager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.RootChange.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("broadcast error", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
|
||||||
}
|
|
||||||
@ -2,10 +2,6 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
@ -14,6 +10,9 @@ import (
|
|||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anyproto/any-sync/util/slice"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
||||||
@ -1,4 +1,4 @@
|
|||||||
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol
|
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable
|
||||||
package synctree
|
package synctree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
@ -43,7 +44,7 @@ type SyncTree interface {
|
|||||||
type syncTree struct {
|
type syncTree struct {
|
||||||
objecttree.ObjectTree
|
objecttree.ObjectTree
|
||||||
synchandler.SyncHandler
|
synchandler.SyncHandler
|
||||||
syncClient SyncClient
|
syncClient objectsync.SyncClient
|
||||||
syncStatus syncstatus.StatusUpdater
|
syncStatus syncstatus.StatusUpdater
|
||||||
notifiable HeadNotifiable
|
notifiable HeadNotifiable
|
||||||
listener updatelistener.UpdateListener
|
listener updatelistener.UpdateListener
|
||||||
@ -59,18 +60,19 @@ type ResponsiblePeersGetter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BuildDeps struct {
|
type BuildDeps struct {
|
||||||
SpaceId string
|
SpaceId string
|
||||||
SyncClient SyncClient
|
SyncClient objectsync.SyncClient
|
||||||
Configuration nodeconf.NodeConf
|
Configuration nodeconf.NodeConf
|
||||||
HeadNotifiable HeadNotifiable
|
HeadNotifiable HeadNotifiable
|
||||||
Listener updatelistener.UpdateListener
|
Listener updatelistener.UpdateListener
|
||||||
AclList list.AclList
|
AclList list.AclList
|
||||||
SpaceStorage spacestorage.SpaceStorage
|
SpaceStorage spacestorage.SpaceStorage
|
||||||
TreeStorage treestorage.TreeStorage
|
TreeStorage treestorage.TreeStorage
|
||||||
OnClose func(id string)
|
OnClose func(id string)
|
||||||
SyncStatus syncstatus.StatusUpdater
|
SyncStatus syncstatus.StatusUpdater
|
||||||
PeerGetter ResponsiblePeersGetter
|
PeerGetter ResponsiblePeersGetter
|
||||||
BuildObjectTree objecttree.BuildObjectTreeFunc
|
BuildObjectTree objecttree.BuildObjectTreeFunc
|
||||||
|
WaitTreeRemoteSync bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
||||||
@ -117,7 +119,7 @@ func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t Sync
|
|||||||
if sendUpdate {
|
if sendUpdate {
|
||||||
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
||||||
// send to everybody, because everybody should know that the node or client got new tree
|
// send to everybody, because everybody should know that the node or client got new tree
|
||||||
syncTree.syncClient.Broadcast(headUpdate)
|
syncTree.syncClient.Broadcast(ctx, headUpdate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -154,7 +156,7 @@ func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableCh
|
|||||||
}
|
}
|
||||||
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||||
s.syncClient.Broadcast(headUpdate)
|
s.syncClient.Broadcast(ctx, headUpdate)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +183,7 @@ func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.
|
|||||||
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
||||||
}
|
}
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||||
s.syncClient.Broadcast(headUpdate)
|
s.syncClient.Broadcast(ctx, headUpdate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -205,27 +207,18 @@ func (s *syncTree) Delete() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
||||||
if !s.TryLock() {
|
return true, s.Close()
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
|
||||||
return true, s.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) Close() (err error) {
|
func (s *syncTree) Close() (err error) {
|
||||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
||||||
s.Lock()
|
|
||||||
return s.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncTree) close() (err error) {
|
|
||||||
defer s.Unlock()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
||||||
}()
|
}()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
if s.isClosed {
|
if s.isClosed {
|
||||||
err = ErrSyncTreeClosed
|
return ErrSyncTreeClosed
|
||||||
return
|
|
||||||
}
|
}
|
||||||
s.onClose(s.Id())
|
s.onClose(s.Id())
|
||||||
s.isClosed = true
|
s.isClosed = true
|
||||||
@ -246,7 +239,7 @@ func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error)
|
|||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||||
return s.syncClient.SendUpdate(peerId, headUpdate.RootChange.Id, headUpdate)
|
return s.syncClient.SendWithReply(ctx, peerId, headUpdate.RootChange.Id, headUpdate, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) afterBuild() {
|
func (s *syncTree) afterBuild() {
|
||||||
|
|||||||
@ -4,21 +4,21 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncTreeMatcher struct {
|
type syncTreeMatcher struct {
|
||||||
objTree objecttree.ObjectTree
|
objTree objecttree.ObjectTree
|
||||||
client SyncClient
|
client objectsync.SyncClient
|
||||||
listener updatelistener.UpdateListener
|
listener updatelistener.UpdateListener
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ func (s syncTreeMatcher) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncClientFuncCreator(client SyncClient) func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
func syncClientFuncCreator(client objectsync.SyncClient) func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
||||||
return func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
return func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
|
|
||||||
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
|
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
|
||||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
|
||||||
objTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
objTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||||
tr := &syncTree{
|
tr := &syncTree{
|
||||||
ObjectTree: objTreeMock,
|
ObjectTree: objTreeMock,
|
||||||
@ -73,7 +73,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
updateListenerMock.EXPECT().Update(tr)
|
updateListenerMock.EXPECT().Update(tr)
|
||||||
|
|
||||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||||
res, err := tr.AddRawChanges(ctx, payload)
|
res, err := tr.AddRawChanges(ctx, payload)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedRes, res)
|
require.Equal(t, expectedRes, res)
|
||||||
@ -95,7 +95,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
updateListenerMock.EXPECT().Rebuild(tr)
|
updateListenerMock.EXPECT().Rebuild(tr)
|
||||||
|
|
||||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||||
res, err := tr.AddRawChanges(ctx, payload)
|
res, err := tr.AddRawChanges(ctx, payload)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedRes, res)
|
require.Equal(t, expectedRes, res)
|
||||||
@ -133,7 +133,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
Return(expectedRes, nil)
|
Return(expectedRes, nil)
|
||||||
|
|
||||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||||
res, err := tr.AddContent(ctx, content)
|
res, err := tr.AddContent(ctx, content)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedRes, res)
|
require.Equal(t, expectedRes, res)
|
||||||
|
|||||||
@ -2,142 +2,233 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anyproto/any-sync/util/slice"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
)
|
"go.uber.org/zap"
|
||||||
|
"sync"
|
||||||
var (
|
|
||||||
ErrMessageIsRequest = errors.New("message is request")
|
|
||||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
|
||||||
ErrMoreThanOneRequest = errors.New("more than one request for same peer")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncTreeHandler struct {
|
type syncTreeHandler struct {
|
||||||
objTree objecttree.ObjectTree
|
objTree objecttree.ObjectTree
|
||||||
syncClient SyncClient
|
syncClient objectsync.SyncClient
|
||||||
syncProtocol TreeSyncProtocol
|
syncStatus syncstatus.StatusUpdater
|
||||||
syncStatus syncstatus.StatusUpdater
|
handlerLock sync.Mutex
|
||||||
spaceId string
|
spaceId string
|
||||||
|
queue ReceiveQueue
|
||||||
handlerLock sync.Mutex
|
|
||||||
pendingRequests map[string]struct{}
|
|
||||||
heads []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxQueueSize = 5
|
const maxQueueSize = 5
|
||||||
|
|
||||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient objectsync.SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||||
return &syncTreeHandler{
|
return &syncTreeHandler{
|
||||||
objTree: objTree,
|
objTree: objTree,
|
||||||
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
|
syncClient: syncClient,
|
||||||
syncClient: syncClient,
|
syncStatus: syncStatus,
|
||||||
syncStatus: syncStatus,
|
spaceId: spaceId,
|
||||||
spaceId: spaceId,
|
queue: newReceiveQueue(maxQueueSize),
|
||||||
pendingRequests: make(map[string]struct{}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTreeHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
|
||||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
|
||||||
if fullSyncRequest == nil {
|
|
||||||
return nil, ErrMessageIsNotRequest
|
|
||||||
}
|
|
||||||
// setting pending requests
|
|
||||||
s.handlerLock.Lock()
|
|
||||||
_, exists := s.pendingRequests[senderId]
|
|
||||||
if exists {
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return nil, ErrMoreThanOneRequest
|
|
||||||
}
|
|
||||||
s.pendingRequests[senderId] = struct{}{}
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
|
|
||||||
response, err = s.handleRequest(ctx, senderId, fullSyncRequest)
|
|
||||||
|
|
||||||
// removing pending requests
|
|
||||||
s.handlerLock.Lock()
|
|
||||||
delete(s.pendingRequests, senderId)
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncTreeHandler) handleRequest(ctx context.Context, senderId string, fullSyncRequest *treechangeproto.TreeFullSyncRequest) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
s.objTree.Lock()
|
|
||||||
defer s.objTree.Unlock()
|
|
||||||
treeResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response, err = spacesyncproto.MarshallSyncMessage(treeResp, s.spaceId, s.objTree.Id())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||||
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
heads := treechangeproto.GetHeads(unmarshalled)
|
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, treechangeproto.GetHeads(unmarshalled))
|
||||||
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, heads)
|
|
||||||
s.handlerLock.Lock()
|
queueFull := s.queue.AddMessage(senderId, unmarshalled, msg.RequestId)
|
||||||
// if the update has same heads then returning not to hang on a lock
|
if queueFull {
|
||||||
if unmarshalled.GetContent().GetHeadUpdate() != nil && slice.UnsortedEquals(heads, s.heads) {
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return s.handleMessage(ctx, unmarshalled, senderId)
|
return s.handleMessage(ctx, senderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeproto.TreeSyncMessage, senderId string) (err error) {
|
func (s *syncTreeHandler) handleMessage(ctx context.Context, senderId string) (err error) {
|
||||||
s.objTree.Lock()
|
s.objTree.Lock()
|
||||||
defer s.objTree.Unlock()
|
defer s.objTree.Unlock()
|
||||||
var (
|
msg, replyId, err := s.queue.GetMessage(senderId)
|
||||||
copyHeads = make([]string, 0, len(s.objTree.Heads()))
|
if err != nil {
|
||||||
treeId = s.objTree.Id()
|
return
|
||||||
content = msg.GetContent()
|
}
|
||||||
)
|
|
||||||
|
defer s.queue.ClearQueue(senderId)
|
||||||
|
|
||||||
|
content := msg.GetContent()
|
||||||
|
switch {
|
||||||
|
case content.GetHeadUpdate() != nil:
|
||||||
|
return s.handleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), replyId)
|
||||||
|
case content.GetFullSyncRequest() != nil:
|
||||||
|
return s.handleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), replyId)
|
||||||
|
case content.GetFullSyncResponse() != nil:
|
||||||
|
return s.handleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) handleHeadUpdate(
|
||||||
|
ctx context.Context,
|
||||||
|
senderId string,
|
||||||
|
update *treechangeproto.TreeHeadUpdate,
|
||||||
|
replyId string) (err error) {
|
||||||
|
var (
|
||||||
|
fullRequest *treechangeproto.TreeSyncMessage
|
||||||
|
isEmptyUpdate = len(update.Changes) == 0
|
||||||
|
objTree = s.objTree
|
||||||
|
treeId = objTree.Id()
|
||||||
|
)
|
||||||
|
log := log.With(
|
||||||
|
zap.Strings("update heads", update.Heads),
|
||||||
|
zap.String("treeId", treeId),
|
||||||
|
zap.String("spaceId", s.spaceId),
|
||||||
|
zap.Int("len(update changes)", len(update.Changes)))
|
||||||
|
log.DebugCtx(ctx, "received head update message")
|
||||||
|
|
||||||
// getting old heads
|
|
||||||
copyHeads = append(copyHeads, s.objTree.Heads()...)
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// checking if something changed
|
if err != nil {
|
||||||
if !slice.UnsortedEquals(copyHeads, s.objTree.Heads()) {
|
log.ErrorCtx(ctx, "head update finished with error", zap.Error(err))
|
||||||
s.handlerLock.Lock()
|
} else if fullRequest != nil {
|
||||||
defer s.handlerLock.Unlock()
|
cnt := fullRequest.Content.GetFullSyncRequest()
|
||||||
s.heads = s.heads[:0]
|
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
||||||
for _, h := range s.objTree.Heads() {
|
log.DebugCtx(ctx, "sending full sync request")
|
||||||
s.heads = append(s.heads, h)
|
} else {
|
||||||
|
if !isEmptyUpdate {
|
||||||
|
log.DebugCtx(ctx, "head update finished correctly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch {
|
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||||
case content.GetHeadUpdate() != nil:
|
if isEmptyUpdate {
|
||||||
var syncReq *treechangeproto.TreeSyncMessage
|
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
|
||||||
if err != nil || syncReq == nil {
|
if headEquals {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return s.syncClient.QueueRequest(senderId, treeId, syncReq)
|
|
||||||
case content.GetFullSyncRequest() != nil:
|
// we need to sync in any case
|
||||||
return ErrMessageIsRequest
|
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||||
case content.GetFullSyncResponse() != nil:
|
if err != nil {
|
||||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: update.Heads,
|
||||||
|
RawChanges: update.Changes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) handleFullSyncRequest(
|
||||||
|
ctx context.Context,
|
||||||
|
senderId string,
|
||||||
|
request *treechangeproto.TreeFullSyncRequest,
|
||||||
|
replyId string) (err error) {
|
||||||
|
var (
|
||||||
|
fullResponse *treechangeproto.TreeSyncMessage
|
||||||
|
header = s.objTree.Header()
|
||||||
|
objTree = s.objTree
|
||||||
|
treeId = s.objTree.Id()
|
||||||
|
)
|
||||||
|
|
||||||
|
log := log.With(zap.String("senderId", senderId),
|
||||||
|
zap.Strings("request heads", request.Heads),
|
||||||
|
zap.String("treeId", treeId),
|
||||||
|
zap.String("replyId", replyId),
|
||||||
|
zap.String("spaceId", s.spaceId),
|
||||||
|
zap.Int("len(request changes)", len(request.Changes)))
|
||||||
|
log.DebugCtx(ctx, "received full sync request message")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
||||||
|
s.syncClient.SendWithReply(ctx, senderId, treeId, treechangeproto.WrapError(treechangeproto.ErrFullSync, header), replyId)
|
||||||
|
return
|
||||||
|
} else if fullResponse != nil {
|
||||||
|
cnt := fullResponse.Content.GetFullSyncResponse()
|
||||||
|
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
||||||
|
log.DebugCtx(ctx, "full sync response sent")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if len(request.Changes) != 0 && !s.alreadyHasHeads(objTree, request.Heads) {
|
||||||
|
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: request.Heads,
|
||||||
|
RawChanges: request.Changes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fullResponse, err = s.syncClient.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullResponse, replyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) handleFullSyncResponse(
|
||||||
|
ctx context.Context,
|
||||||
|
senderId string,
|
||||||
|
response *treechangeproto.TreeFullSyncResponse) (err error) {
|
||||||
|
var (
|
||||||
|
objTree = s.objTree
|
||||||
|
treeId = s.objTree.Id()
|
||||||
|
)
|
||||||
|
log := log.With(
|
||||||
|
zap.Strings("heads", response.Heads),
|
||||||
|
zap.String("treeId", treeId),
|
||||||
|
zap.String("spaceId", s.spaceId),
|
||||||
|
zap.Int("len(changes)", len(response.Changes)))
|
||||||
|
log.DebugCtx(ctx, "received full sync response message")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorCtx(ctx, "full sync response failed", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
log.DebugCtx(ctx, "full sync response succeeded")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if s.alreadyHasHeads(objTree, response.Heads) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: response.Heads,
|
||||||
|
RawChanges: response.Changes,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) alreadyHasHeads(t objecttree.ObjectTree, heads []string) bool {
|
||||||
|
return slice.UnsortedEquals(t.Heads(), heads) || t.HasChanges(heads...)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,16 +2,20 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testObjTreeMock struct {
|
type testObjTreeMock struct {
|
||||||
@ -51,40 +55,31 @@ func (t *testObjTreeMock) TryRLock() bool {
|
|||||||
|
|
||||||
type syncHandlerFixture struct {
|
type syncHandlerFixture struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
syncClientMock *mock_synctree.MockSyncClient
|
syncClientMock *mock_objectsync.MockSyncClient
|
||||||
objectTreeMock *testObjTreeMock
|
objectTreeMock *testObjTreeMock
|
||||||
syncProtocolMock *mock_synctree.MockTreeSyncProtocol
|
receiveQueueMock ReceiveQueue
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
treeId string
|
|
||||||
|
|
||||||
syncHandler *syncTreeHandler
|
syncHandler *syncTreeHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
|
||||||
objectTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
objectTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
receiveQueue := newReceiveQueue(5)
|
||||||
syncProtocolMock := mock_synctree.NewMockTreeSyncProtocol(ctrl)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
|
|
||||||
syncHandler := &syncTreeHandler{
|
syncHandler := &syncTreeHandler{
|
||||||
objTree: objectTreeMock,
|
objTree: objectTreeMock,
|
||||||
syncClient: syncClientMock,
|
syncClient: syncClientMock,
|
||||||
syncProtocol: syncProtocolMock,
|
queue: receiveQueue,
|
||||||
spaceId: spaceId,
|
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
|
||||||
pendingRequests: map[string]struct{}{},
|
|
||||||
}
|
}
|
||||||
return &syncHandlerFixture{
|
return &syncHandlerFixture{
|
||||||
ctrl: ctrl,
|
ctrl: ctrl,
|
||||||
objectTreeMock: objectTreeMock,
|
|
||||||
syncProtocolMock: syncProtocolMock,
|
|
||||||
syncClientMock: syncClientMock,
|
syncClientMock: syncClientMock,
|
||||||
|
objectTreeMock: objectTreeMock,
|
||||||
|
receiveQueueMock: receiveQueue,
|
||||||
syncHandler: syncHandler,
|
syncHandler: syncHandler,
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
treeId: "treeId",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,149 +87,341 @@ func (fx *syncHandlerFixture) stop() {
|
|||||||
fx.ctrl.Finish()
|
fx.ctrl.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||||
|
fullRequest := &treechangeproto.TreeSyncMessage{
|
||||||
|
Content: &treechangeproto.TreeSyncContentValue{
|
||||||
|
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
||||||
|
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("handle head update message, heads not equal, request returned", func(t *testing.T) {
|
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
syncReq := &treechangeproto.TreeSyncMessage{}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
fx.objectTreeMock.EXPECT().
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
NewHeads: []string{"h1"},
|
||||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, syncReq).Return(nil)
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("handle head update message, heads equal", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
treeId := "treeId"
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
}
|
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h1"}
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle head update message, no sync request returned", func(t *testing.T) {
|
t.Run("head update non empty heads not added", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
fx.objectTreeMock.EXPECT().
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullRequest, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle full sync request returns error", func(t *testing.T) {
|
t.Run("head update non empty equal heads", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
fullRequest := &treechangeproto.TreeFullSyncRequest{
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(3).Return([]string{"h2"})
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.Equal(t, err, ErrMessageIsRequest)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle full sync response", func(t *testing.T) {
|
t.Run("head update empty", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: nil,
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullRequest, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("head update empty equal heads", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: nil,
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||||
|
fullResponse := &treechangeproto.TreeSyncMessage{
|
||||||
|
Content: &treechangeproto.TreeSyncContentValue{
|
||||||
|
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
||||||
|
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("full sync request with change", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullResponse, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full sync request with change same heads", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Heads().
|
||||||
|
Return([]string{"h1"}).AnyTimes()
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullResponse, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full sync request without change but with reply id", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
replyId := "replyId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
objectMsg.RequestId = replyId
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullResponse, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(replyId))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full sync request with add raw changes error", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Heads().
|
||||||
|
Return([]string{"h2"})
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
HasChanges(gomock.Eq([]string{"h1"})).
|
||||||
|
Return(false)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, fmt.Errorf(""))
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Any(), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncHandler_HandleFullSyncResponse(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||||
|
|
||||||
|
t.Run("full sync response with change", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
replyId := "replyId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
Heads().
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
Return([]string{"h2"}).AnyTimes()
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
fx.objectTreeMock.EXPECT().
|
||||||
|
HasChanges(gomock.Eq([]string{"h1"})).
|
||||||
|
Return(false)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncTreeHandler_HandleRequest(t *testing.T) {
|
t.Run("full sync response with same heads", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("handle request", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
replyId := "replyId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
Heads: []string{"h1"},
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(syncResp, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("handle other message", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
treeId := "treeId"
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullResponse := &treechangeproto.TreeFullSyncResponse{}
|
|
||||||
responseMsg := treechangeproto.WrapFullResponse(fullResponse, chWithId)
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
|
||||||
headUpdateMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
for _, msg := range []*treechangeproto.TreeSyncMessage{responseMsg, headUpdateMsg} {
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(msg, "spaceId", treeId)
|
|
||||||
|
|
||||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Equal(t, err, ErrMessageIsNotRequest)
|
|
||||||
}
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||||
|
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Heads().
|
||||||
|
Return([]string{"h1"}).AnyTimes()
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,19 +2,16 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
|
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
"time"
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoResponsiblePeers = errors.New("no responsible peers")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type treeRemoteGetter struct {
|
type treeRemoteGetter struct {
|
||||||
@ -39,7 +36,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(respPeers) == 0 {
|
if len(respPeers) == 0 {
|
||||||
err = ErrNoResponsiblePeers
|
err = fmt.Errorf("no responsible peers")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, p := range respPeers {
|
for _, p := range respPeers {
|
||||||
@ -50,7 +47,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
|||||||
|
|
||||||
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||||
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
||||||
resp, err := t.deps.SyncClient.SendRequest(ctx, peerId, t.treeId, newTreeRequest)
|
resp, err := t.deps.SyncClient.SendSync(ctx, peerId, t.treeId, newTreeRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -60,13 +57,37 @@ func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context) (msg *treechangeproto.TreeSyncMessage, err error) {
|
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context, wait bool) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||||
availablePeers, err := t.getPeers(ctx)
|
peerIdx := 0
|
||||||
if err != nil {
|
Loop:
|
||||||
return
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, fmt.Errorf("waiting for object %s interrupted, context closed", t.treeId)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
availablePeers, err := t.getPeers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if !wait {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
// wait for peers to connect
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
continue Loop
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, fmt.Errorf("waiting for object %s interrupted, context closed", t.treeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIdx = peerIdx % len(availablePeers)
|
||||||
|
msg, err = t.treeRequest(ctx, availablePeers[peerIdx])
|
||||||
|
if err == nil || !wait {
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
peerIdx++
|
||||||
}
|
}
|
||||||
// in future we will try to load from different peers
|
|
||||||
return t.treeRequest(ctx, availablePeers[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, isRemote bool, err error) {
|
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, isRemote bool, err error) {
|
||||||
@ -88,15 +109,22 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
|||||||
}
|
}
|
||||||
|
|
||||||
isRemote = true
|
isRemote = true
|
||||||
resp, err := t.treeRequestLoop(ctx)
|
resp, err := t.treeRequestLoop(ctx, t.deps.WaitTreeRemoteSync)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
switch {
|
||||||
if fullSyncResp == nil {
|
case resp.GetContent().GetErrorResponse() != nil:
|
||||||
|
errResp := resp.GetContent().GetErrorResponse()
|
||||||
|
err = rpcerr.Err(errResp.ErrCode)
|
||||||
|
return
|
||||||
|
case resp.GetContent().GetFullSyncResponse() == nil:
|
||||||
err = treechangeproto.ErrUnexpected
|
err = treechangeproto.ErrUnexpected
|
||||||
return
|
return
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
||||||
|
|
||||||
payload := treestorage.TreeStorageCreatePayload{
|
payload := treestorage.TreeStorageCreatePayload{
|
||||||
RootRawChange: resp.RootChange,
|
RootRawChange: resp.RootChange,
|
||||||
|
|||||||
@ -1,87 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/net/peer/mock_peer"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
type treeRemoteGetterFixture struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
|
|
||||||
treeGetter treeRemoteGetter
|
|
||||||
syncClientMock *mock_synctree.MockSyncClient
|
|
||||||
peerGetterMock *mock_peermanager.MockPeerManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTreeRemoteGetterFixture(t *testing.T) *treeRemoteGetterFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
|
||||||
peerGetterMock := mock_peermanager.NewMockPeerManager(ctrl)
|
|
||||||
treeGetter := treeRemoteGetter{
|
|
||||||
deps: BuildDeps{
|
|
||||||
SyncClient: syncClientMock,
|
|
||||||
PeerGetter: peerGetterMock,
|
|
||||||
},
|
|
||||||
treeId: "treeId",
|
|
||||||
}
|
|
||||||
return &treeRemoteGetterFixture{
|
|
||||||
ctrl: ctrl,
|
|
||||||
treeGetter: treeGetter,
|
|
||||||
syncClientMock: syncClientMock,
|
|
||||||
peerGetterMock: peerGetterMock,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *treeRemoteGetterFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeRemoteGetter(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
peerId := "peerId"
|
|
||||||
treeRequest := &treechangeproto.TreeSyncMessage{}
|
|
||||||
treeResponse := &treechangeproto.TreeSyncMessage{
|
|
||||||
RootChange: &treechangeproto.RawTreeChangeWithId{Id: "id"},
|
|
||||||
}
|
|
||||||
marshalled, _ := proto.Marshal(treeResponse)
|
|
||||||
objectResponse := &spacesyncproto.ObjectSyncMessage{
|
|
||||||
Payload: marshalled,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("request works", func(t *testing.T) {
|
|
||||||
fx := newTreeRemoteGetterFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mockPeer := mock_peer.NewMockPeer(fx.ctrl)
|
|
||||||
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
|
|
||||||
fx.peerGetterMock.EXPECT().GetResponsiblePeers(ctx).Return([]peer.Peer{mockPeer}, nil)
|
|
||||||
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
|
|
||||||
fx.syncClientMock.EXPECT().SendRequest(ctx, peerId, fx.treeGetter.treeId, treeRequest).Return(objectResponse, nil)
|
|
||||||
resp, err := fx.treeGetter.treeRequestLoop(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "id", resp.RootChange.Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("request fails", func(t *testing.T) {
|
|
||||||
fx := newTreeRemoteGetterFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
treeRequest := &treechangeproto.TreeSyncMessage{}
|
|
||||||
mockPeer := mock_peer.NewMockPeer(fx.ctrl)
|
|
||||||
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
|
|
||||||
fx.peerGetterMock.EXPECT().GetResponsiblePeers(ctx).Return([]peer.Peer{mockPeer}, nil)
|
|
||||||
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
|
|
||||||
fx.syncClientMock.EXPECT().SendRequest(ctx, peerId, fx.treeGetter.treeId, treeRequest).AnyTimes().Return(nil, fmt.Errorf("some"))
|
|
||||||
_, err := fx.treeGetter.treeRequestLoop(ctx)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TreeSyncProtocol interface {
|
|
||||||
HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (request *treechangeproto.TreeSyncMessage, err error)
|
|
||||||
FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (response *treechangeproto.TreeSyncMessage, err error)
|
|
||||||
FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type treeSyncProtocol struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
objTree objecttree.ObjectTree
|
|
||||||
reqFactory RequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTreeSyncProtocol(spaceId string, objTree objecttree.ObjectTree, reqFactory RequestFactory) *treeSyncProtocol {
|
|
||||||
return &treeSyncProtocol{
|
|
||||||
log: log.With(zap.String("spaceId", spaceId), zap.String("treeId", objTree.Id())),
|
|
||||||
spaceId: spaceId,
|
|
||||||
objTree: objTree,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (fullRequest *treechangeproto.TreeSyncMessage, err error) {
|
|
||||||
var (
|
|
||||||
isEmptyUpdate = len(update.Changes) == 0
|
|
||||||
objTree = t.objTree
|
|
||||||
)
|
|
||||||
log := t.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.Strings("update heads", update.Heads),
|
|
||||||
zap.Int("len(update changes)", len(update.Changes)))
|
|
||||||
log.DebugCtx(ctx, "received head update message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "head update finished with error", zap.Error(err))
|
|
||||||
} else if fullRequest != nil {
|
|
||||||
cnt := fullRequest.Content.GetFullSyncRequest()
|
|
||||||
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
|
||||||
log.DebugCtx(ctx, "returning full sync request")
|
|
||||||
} else {
|
|
||||||
if !isEmptyUpdate {
|
|
||||||
log.DebugCtx(ctx, "head update finished correctly")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
|
||||||
if isEmptyUpdate {
|
|
||||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
|
||||||
log.DebugCtx(ctx, "is empty update", zap.Bool("headEquals", headEquals))
|
|
||||||
if headEquals {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to sync in any case
|
|
||||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.hasHeads(objTree, update.Heads) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: update.Heads,
|
|
||||||
RawChanges: update.Changes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.hasHeads(objTree, update.Heads) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (fullResponse *treechangeproto.TreeSyncMessage, err error) {
|
|
||||||
var (
|
|
||||||
objTree = t.objTree
|
|
||||||
)
|
|
||||||
log := t.log.With(zap.String("senderId", senderId),
|
|
||||||
zap.Strings("request heads", request.Heads),
|
|
||||||
zap.Int("len(request changes)", len(request.Changes)))
|
|
||||||
log.DebugCtx(ctx, "received full sync request message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
|
||||||
} else if fullResponse != nil {
|
|
||||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
|
||||||
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
|
||||||
log.DebugCtx(ctx, "full sync response sent")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if len(request.Changes) != 0 && !t.hasHeads(objTree, request.Heads) {
|
|
||||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: request.Heads,
|
|
||||||
RawChanges: request.Changes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fullResponse, err = t.reqFactory.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error) {
|
|
||||||
var (
|
|
||||||
objTree = t.objTree
|
|
||||||
)
|
|
||||||
log := log.With(
|
|
||||||
zap.Strings("heads", response.Heads),
|
|
||||||
zap.Int("len(changes)", len(response.Changes)))
|
|
||||||
log.DebugCtx(ctx, "received full sync response message")
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "full sync response failed", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
log.DebugCtx(ctx, "full sync response succeeded")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if t.hasHeads(objTree, response.Heads) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: response.Heads,
|
|
||||||
RawChanges: response.Changes,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) hasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
|
||||||
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
|
|
||||||
}
|
|
||||||
@ -1,293 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type treeSyncProtocolFixture struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
treeId string
|
|
||||||
objectTreeMock *testObjTreeMock
|
|
||||||
reqFactory *mock_synctree.MockRequestFactory
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
syncProtocol TreeSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyncProtocolFixture(t *testing.T) *treeSyncProtocolFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
objTree := &testObjTreeMock{
|
|
||||||
MockObjectTree: mock_objecttree.NewMockObjectTree(ctrl),
|
|
||||||
}
|
|
||||||
spaceId := "spaceId"
|
|
||||||
reqFactory := mock_synctree.NewMockRequestFactory(ctrl)
|
|
||||||
objTree.EXPECT().Id().Return("treeId")
|
|
||||||
syncProtocol := newTreeSyncProtocol(spaceId, objTree, reqFactory)
|
|
||||||
return &treeSyncProtocolFixture{
|
|
||||||
log: log,
|
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
treeId: "treeId",
|
|
||||||
objectTreeMock: objTree,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
ctrl: ctrl,
|
|
||||||
syncProtocol: syncProtocol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *treeSyncProtocolFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullRequest := &treechangeproto.TreeSyncMessage{
|
|
||||||
Content: &treechangeproto.TreeSyncContentValue{
|
|
||||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
|
||||||
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
|
||||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{"h1"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, nil)
|
|
||||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("head update non empty equal heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("head update empty", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: nil,
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
|
||||||
Return(fullRequest, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullRequest, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("head update empty equal heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: nil,
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullResponse := &treechangeproto.TreeSyncMessage{
|
|
||||||
Content: &treechangeproto.TreeSyncContentValue{
|
|
||||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
|
||||||
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("full sync request with change", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
|
||||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{"h1"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, nil)
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
|
||||||
Return(fullResponse, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullResponse, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync request with change same heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
Heads().
|
|
||||||
Return([]string{"h1"}).AnyTimes()
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
|
||||||
Return(fullResponse, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullResponse, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync request without changes", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
|
||||||
Return(fullResponse, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullResponse, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync request with change, raw changes error", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
|
||||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{"h1"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, fmt.Errorf("addRawChanges error"))
|
|
||||||
|
|
||||||
_, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeSyncProtocol_FullSyncResponse(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("full sync response with change", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
Heads().
|
|
||||||
Return([]string{"h2"}).AnyTimes()
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
HasChanges(gomock.Eq([]string{"h1"})).
|
|
||||||
Return(false)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{"h1"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, nil)
|
|
||||||
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync response with same heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
Heads().
|
|
||||||
Return([]string{"h1"}).AnyTimes()
|
|
||||||
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -8,7 +8,7 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockUpdateListener is a mock of UpdateListener interface.
|
// MockUpdateListener is a mock of UpdateListener interface.
|
||||||
|
|||||||
@ -3,11 +3,11 @@ package synctree
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
@ -82,124 +82,51 @@ func (m *messageLog) addMessage(msg protocolMsg) {
|
|||||||
m.batcher.Add(context.Background(), msg)
|
m.batcher.Add(context.Background(), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestPeerManager struct {
|
|
||||||
peerId string
|
|
||||||
handlers map[string]*testSyncHandler
|
|
||||||
log *messageLog
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRequestPeerManager(peerId string, log *messageLog) *requestPeerManager {
|
|
||||||
return &requestPeerManager{
|
|
||||||
peerId: peerId,
|
|
||||||
handlers: map[string]*testSyncHandler{},
|
|
||||||
log: log,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) addHandler(peerId string, handler *testSyncHandler) {
|
|
||||||
r.handlers[peerId] = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) Run(ctx context.Context) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) Close(ctx context.Context) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) SendRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
panic("should not be called")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) QueueRequest(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
pMsg := protocolMsg{
|
|
||||||
msg: msg,
|
|
||||||
senderId: r.peerId,
|
|
||||||
receiverId: peerId,
|
|
||||||
}
|
|
||||||
r.log.addMessage(pMsg)
|
|
||||||
return r.handlers[peerId].send(context.Background(), pMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) Init(a *app.App) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) Name() (name string) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
pMsg := protocolMsg{
|
|
||||||
msg: msg,
|
|
||||||
senderId: r.peerId,
|
|
||||||
receiverId: peerId,
|
|
||||||
}
|
|
||||||
r.log.addMessage(pMsg)
|
|
||||||
return r.handlers[peerId].send(context.Background(), pMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
for _, handler := range r.handlers {
|
|
||||||
pMsg := protocolMsg{
|
|
||||||
msg: msg,
|
|
||||||
senderId: r.peerId,
|
|
||||||
receiverId: handler.peerId,
|
|
||||||
}
|
|
||||||
r.log.addMessage(pMsg)
|
|
||||||
handler.send(context.Background(), pMsg)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestPeerManager) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testSyncHandler is the wrapper around individual tree to test sync protocol
|
// testSyncHandler is the wrapper around individual tree to test sync protocol
|
||||||
type testSyncHandler struct {
|
type testSyncHandler struct {
|
||||||
synchandler.SyncHandler
|
synchandler.SyncHandler
|
||||||
batcher *mb.MB[protocolMsg]
|
batcher *mb.MB[protocolMsg]
|
||||||
peerId string
|
peerId string
|
||||||
aclList list.AclList
|
aclList list.AclList
|
||||||
log *messageLog
|
log *messageLog
|
||||||
syncClient SyncClient
|
syncClient objectsync.SyncClient
|
||||||
builder objecttree.BuildObjectTreeFunc
|
builder objecttree.BuildObjectTreeFunc
|
||||||
peerManager *requestPeerManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSyncHandler creates a sync handler when a tree is already created
|
// createSyncHandler creates a sync handler when a tree is already created
|
||||||
func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, log *messageLog) *testSyncHandler {
|
func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, log *messageLog) *testSyncHandler {
|
||||||
peerManager := newRequestPeerManager(peerId, log)
|
factory := objectsync.NewRequestFactory()
|
||||||
syncClient := NewSyncClient(spaceId, peerManager, peerManager)
|
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||||
netTree := &broadcastTree{
|
netTree := &broadcastTree{
|
||||||
ObjectTree: objTree,
|
ObjectTree: objTree,
|
||||||
SyncClient: syncClient,
|
SyncClient: syncClient,
|
||||||
}
|
}
|
||||||
handler := newSyncTreeHandler(spaceId, netTree, syncClient, syncstatus.NewNoOpSyncStatus())
|
handler := newSyncTreeHandler(spaceId, netTree, syncClient, syncstatus.NewNoOpSyncStatus())
|
||||||
return &testSyncHandler{
|
return newTestSyncHandler(peerId, handler)
|
||||||
SyncHandler: handler,
|
|
||||||
batcher: mb.New[protocolMsg](0),
|
|
||||||
peerId: peerId,
|
|
||||||
peerManager: peerManager,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createEmptySyncHandler creates a sync handler when the tree will be provided later (this emulates the situation when we have no tree)
|
// createEmptySyncHandler creates a sync handler when the tree will be provided later (this emulates the situation when we have no tree)
|
||||||
func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObjectTreeFunc, aclList list.AclList, log *messageLog) *testSyncHandler {
|
func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObjectTreeFunc, aclList list.AclList, log *messageLog) *testSyncHandler {
|
||||||
peerManager := newRequestPeerManager(peerId, log)
|
factory := objectsync.NewRequestFactory()
|
||||||
syncClient := NewSyncClient(spaceId, peerManager, peerManager)
|
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||||
|
|
||||||
batcher := mb.New[protocolMsg](0)
|
batcher := mb.New[protocolMsg](0)
|
||||||
return &testSyncHandler{
|
return &testSyncHandler{
|
||||||
|
batcher: batcher,
|
||||||
|
peerId: peerId,
|
||||||
|
aclList: aclList,
|
||||||
|
log: log,
|
||||||
|
syncClient: syncClient,
|
||||||
|
builder: builder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestSyncHandler(peerId string, syncHandler synchandler.SyncHandler) *testSyncHandler {
|
||||||
|
batcher := mb.New[protocolMsg](0)
|
||||||
|
return &testSyncHandler{
|
||||||
|
SyncHandler: syncHandler,
|
||||||
batcher: batcher,
|
batcher: batcher,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
aclList: aclList,
|
|
||||||
log: log,
|
|
||||||
syncClient: syncClient,
|
|
||||||
builder: builder,
|
|
||||||
peerManager: peerManager,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,8 +140,13 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if unmarshalled.Content.GetFullSyncResponse() == nil {
|
if unmarshalled.Content.GetFullSyncResponse() == nil {
|
||||||
newTreeRequest := NewRequestFactory().CreateNewTreeRequest()
|
newTreeRequest := objectsync.NewRequestFactory().CreateNewTreeRequest()
|
||||||
return h.syncClient.QueueRequest(senderId, request.ObjectId, newTreeRequest)
|
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||||
|
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return h.manager().SendPeer(context.Background(), senderId, objMsg)
|
||||||
}
|
}
|
||||||
fullSyncResponse := unmarshalled.Content.GetFullSyncResponse()
|
fullSyncResponse := unmarshalled.Content.GetFullSyncResponse()
|
||||||
treeStorage, _ := treestorage.NewInMemoryTreeStorage(unmarshalled.RootChange, []string{unmarshalled.RootChange.Id}, nil)
|
treeStorage, _ := treestorage.NewInMemoryTreeStorage(unmarshalled.RootChange, []string{unmarshalled.RootChange.Id}, nil)
|
||||||
@ -234,13 +166,20 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.SyncHandler = newSyncTreeHandler(request.SpaceId, netTree, h.syncClient, syncstatus.NewNoOpSyncStatus())
|
h.SyncHandler = newSyncTreeHandler(request.SpaceId, netTree, h.syncClient, syncstatus.NewNoOpSyncStatus())
|
||||||
headUpdate := NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||||
h.syncClient.Broadcast(headUpdate)
|
newTreeRequest := objectsync.NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
||||||
return nil
|
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return h.manager().Broadcast(context.Background(), objMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *testSyncHandler) manager() *requestPeerManager {
|
func (h *testSyncHandler) manager() *testMessagePool {
|
||||||
return h.peerManager
|
if h.SyncHandler != nil {
|
||||||
|
return h.SyncHandler.(*syncTreeHandler).syncClient.MessagePool().(*testMessagePool)
|
||||||
|
}
|
||||||
|
return h.syncClient.MessagePool().(*testMessagePool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *testSyncHandler) tree() *broadcastTree {
|
func (h *testSyncHandler) tree() *broadcastTree {
|
||||||
@ -272,28 +211,74 @@ func (h *testSyncHandler) run(ctx context.Context, t *testing.T, wg *sync.WaitGr
|
|||||||
h.tree().Unlock()
|
h.tree().Unlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if res.description().name == "FullSyncRequest" {
|
err = h.HandleMessage(ctx, res.senderId, res.msg)
|
||||||
resp, err := h.HandleRequest(ctx, res.senderId, res.msg)
|
if err != nil {
|
||||||
if err != nil {
|
fmt.Println("error handling message", err.Error())
|
||||||
fmt.Println("error handling request", err.Error())
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
h.peerManager.SendPeer(ctx, res.senderId, resp)
|
|
||||||
} else {
|
|
||||||
err = h.HandleMessage(ctx, res.senderId, res.msg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error handling message", err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testMessagePool captures all other handlers and sends messages to them
|
||||||
|
type testMessagePool struct {
|
||||||
|
peerId string
|
||||||
|
handlers map[string]*testSyncHandler
|
||||||
|
log *messageLog
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestMessagePool(peerId string, log *messageLog) *testMessagePool {
|
||||||
|
return &testMessagePool{handlers: map[string]*testSyncHandler{}, peerId: peerId, log: log}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) addHandler(peerId string, handler *testSyncHandler) {
|
||||||
|
m.handlers[peerId] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
pMsg := protocolMsg{
|
||||||
|
msg: msg,
|
||||||
|
senderId: m.peerId,
|
||||||
|
receiverId: peerId,
|
||||||
|
}
|
||||||
|
m.log.addMessage(pMsg)
|
||||||
|
return m.handlers[peerId].send(context.Background(), pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
for _, handler := range m.handlers {
|
||||||
|
pMsg := protocolMsg{
|
||||||
|
msg: msg,
|
||||||
|
senderId: m.peerId,
|
||||||
|
receiverId: handler.peerId,
|
||||||
|
}
|
||||||
|
m.log.addMessage(pMsg)
|
||||||
|
handler.send(context.Background(), pMsg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) LastUsage() time.Time {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessagePool) SendSync(ctx context.Context, peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
// broadcastTree is the tree that broadcasts changes to everyone when changes are added
|
// broadcastTree is the tree that broadcasts changes to everyone when changes are added
|
||||||
// it is a simplified version of SyncTree which is easier to use in the test environment
|
// it is a simplified version of SyncTree which is easier to use in the test environment
|
||||||
type broadcastTree struct {
|
type broadcastTree struct {
|
||||||
objecttree.ObjectTree
|
objecttree.ObjectTree
|
||||||
SyncClient
|
objectsync.SyncClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
|
func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
|
||||||
@ -302,7 +287,7 @@ func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.Ra
|
|||||||
return objecttree.AddResult{}, err
|
return objecttree.AddResult{}, err
|
||||||
}
|
}
|
||||||
upd := b.SyncClient.CreateHeadUpdate(b.ObjectTree, res.Added)
|
upd := b.SyncClient.CreateHeadUpdate(b.ObjectTree, res.Added)
|
||||||
b.SyncClient.Broadcast(upd)
|
b.SyncClient.Broadcast(ctx, upd)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockTreeStorage is a mock of TreeStorage interface.
|
// MockTreeStorage is a mock of TreeStorage interface.
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import (
|
|||||||
app "github.com/anyproto/any-sync/app"
|
app "github.com/anyproto/any-sync/app"
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
treemanager "github.com/anyproto/any-sync/commonspace/object/treemanager"
|
treemanager "github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockTreeManager is a mock of TreeManager interface.
|
// MockTreeManager is a mock of TreeManager interface.
|
||||||
|
|||||||
@ -1,98 +0,0 @@
|
|||||||
package objectmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/settings"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrSpaceClosed = errors.New("space is closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ObjectManager interface {
|
|
||||||
treemanager.TreeManager
|
|
||||||
AddObject(object syncobjectgetter.SyncObject)
|
|
||||||
GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type objectManager struct {
|
|
||||||
treemanager.TreeManager
|
|
||||||
spaceId string
|
|
||||||
reservedObjects []syncobjectgetter.SyncObject
|
|
||||||
spaceIsClosed *atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(manager treemanager.TreeManager) ObjectManager {
|
|
||||||
return &objectManager{
|
|
||||||
TreeManager: manager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) Init(a *app.App) (err error) {
|
|
||||||
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
|
||||||
o.spaceId = state.SpaceId
|
|
||||||
o.spaceIsClosed = state.SpaceIsClosed
|
|
||||||
settingsObject := a.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
|
||||||
acl := a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
|
||||||
o.AddObject(settingsObject)
|
|
||||||
o.AddObject(acl)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) Run(ctx context.Context) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) Close(ctx context.Context) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) AddObject(object syncobjectgetter.SyncObject) {
|
|
||||||
o.reservedObjects = append(o.reservedObjects, object)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) Name() string {
|
|
||||||
return treemanager.CName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
|
||||||
if o.spaceIsClosed.Load() {
|
|
||||||
return nil, ErrSpaceClosed
|
|
||||||
}
|
|
||||||
if obj := o.getReservedObject(treeId); obj != nil {
|
|
||||||
return obj.(objecttree.ObjectTree), nil
|
|
||||||
}
|
|
||||||
return o.TreeManager.GetTree(ctx, spaceId, treeId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) getReservedObject(id string) syncobjectgetter.SyncObject {
|
|
||||||
for _, obj := range o.reservedObjects {
|
|
||||||
if obj != nil && obj.Id() == id {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *objectManager) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
|
||||||
if o.spaceIsClosed.Load() {
|
|
||||||
return nil, ErrSpaceClosed
|
|
||||||
}
|
|
||||||
if obj := o.getReservedObject(objectId); obj != nil {
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
t, err := o.TreeManager.GetTree(ctx, o.spaceId, objectId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
obj = t.(syncobjectgetter.SyncObject)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/objectsync (interfaces: ObjectSync)
|
// Source: github.com/anyproto/any-sync/commonspace/objectsync (interfaces: SyncClient)
|
||||||
|
|
||||||
// Package mock_objectsync is a generated GoMock package.
|
// Package mock_objectsync is a generated GoMock package.
|
||||||
package mock_objectsync
|
package mock_objectsync
|
||||||
@ -7,146 +7,146 @@ package mock_objectsync
|
|||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
|
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
objectsync "github.com/anyproto/any-sync/commonspace/objectsync"
|
objectsync "github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockObjectSync is a mock of ObjectSync interface.
|
// MockSyncClient is a mock of SyncClient interface.
|
||||||
type MockObjectSync struct {
|
type MockSyncClient struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockObjectSyncMockRecorder
|
recorder *MockSyncClientMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockObjectSyncMockRecorder is the mock recorder for MockObjectSync.
|
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
||||||
type MockObjectSyncMockRecorder struct {
|
type MockSyncClientMockRecorder struct {
|
||||||
mock *MockObjectSync
|
mock *MockSyncClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockObjectSync creates a new mock instance.
|
// NewMockSyncClient creates a new mock instance.
|
||||||
func NewMockObjectSync(ctrl *gomock.Controller) *MockObjectSync {
|
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
||||||
mock := &MockObjectSync{ctrl: ctrl}
|
mock := &MockSyncClient{ctrl: ctrl}
|
||||||
mock.recorder = &MockObjectSyncMockRecorder{mock}
|
mock.recorder = &MockSyncClientMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockObjectSync) EXPECT() *MockObjectSyncMockRecorder {
|
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close mocks base method.
|
// Broadcast mocks base method.
|
||||||
func (m *MockObjectSync) Close(arg0 context.Context) error {
|
func (m *MockSyncClient) Broadcast(arg0 context.Context, arg1 *treechangeproto.TreeSyncMessage) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Close", arg0)
|
m.ctrl.Call(m, "Broadcast", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
}
|
||||||
|
|
||||||
|
// Broadcast indicates an expected call of Broadcast.
|
||||||
|
func (mr *MockSyncClientMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncRequest mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||||
|
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncResponse mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||||
|
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHeadUpdate mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||||
func (mr *MockObjectSyncMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockObjectSync)(nil).Close), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseThread mocks base method.
|
// CreateNewTreeRequest mocks base method.
|
||||||
func (m *MockObjectSync) CloseThread(arg0 string) error {
|
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CloseThread", arg0)
|
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseThread indicates an expected call of CloseThread.
|
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
||||||
func (mr *MockObjectSyncMockRecorder) CloseThread(arg0 interface{}) *gomock.Call {
|
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseThread", reflect.TypeOf((*MockObjectSync)(nil).CloseThread), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleMessage mocks base method.
|
// MessagePool mocks base method.
|
||||||
func (m *MockObjectSync) HandleMessage(arg0 context.Context, arg1 objectsync.HandleMessage) error {
|
func (m *MockSyncClient) MessagePool() objectsync.MessagePool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1)
|
ret := m.ctrl.Call(m, "MessagePool")
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(objectsync.MessagePool)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleMessage indicates an expected call of HandleMessage.
|
// MessagePool indicates an expected call of MessagePool.
|
||||||
func (mr *MockObjectSyncMockRecorder) HandleMessage(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockSyncClientMockRecorder) MessagePool() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockObjectSync)(nil).HandleMessage), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessagePool", reflect.TypeOf((*MockSyncClient)(nil).MessagePool))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleRequest mocks base method.
|
// SendSync mocks base method.
|
||||||
func (m *MockObjectSync) HandleRequest(arg0 context.Context, arg1 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
func (m *MockSyncClient) SendSync(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1)
|
ret := m.ctrl.Call(m, "SendSync", arg0, arg1, arg2, arg3)
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleRequest indicates an expected call of HandleRequest.
|
// SendSync indicates an expected call of SendSync.
|
||||||
func (mr *MockObjectSyncMockRecorder) HandleRequest(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockSyncClientMockRecorder) SendSync(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockObjectSync)(nil).HandleRequest), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSync", reflect.TypeOf((*MockSyncClient)(nil).SendSync), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init mocks base method.
|
// SendWithReply mocks base method.
|
||||||
func (m *MockObjectSync) Init(arg0 *app.App) error {
|
func (m *MockSyncClient) SendWithReply(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage, arg4 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
ret := m.ctrl.Call(m, "SendWithReply", arg0, arg1, arg2, arg3, arg4)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
// SendWithReply indicates an expected call of SendWithReply.
|
||||||
func (mr *MockObjectSyncMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
func (mr *MockSyncClientMockRecorder) SendWithReply(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectSync)(nil).Init), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendWithReply", reflect.TypeOf((*MockSyncClient)(nil).SendWithReply), arg0, arg1, arg2, arg3, arg4)
|
||||||
}
|
|
||||||
|
|
||||||
// LastUsage mocks base method.
|
|
||||||
func (m *MockObjectSync) LastUsage() time.Time {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LastUsage")
|
|
||||||
ret0, _ := ret[0].(time.Time)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastUsage indicates an expected call of LastUsage.
|
|
||||||
func (mr *MockObjectSyncMockRecorder) LastUsage() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastUsage", reflect.TypeOf((*MockObjectSync)(nil).LastUsage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockObjectSync) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockObjectSyncMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectSync)(nil).Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run mocks base method.
|
|
||||||
func (m *MockObjectSync) Run(arg0 context.Context) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Run", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run indicates an expected call of Run.
|
|
||||||
func (mr *MockObjectSyncMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockObjectSync)(nil).Run), arg0)
|
|
||||||
}
|
}
|
||||||
|
|||||||
142
commonspace/objectsync/msgpool.go
Normal file
142
commonspace/objectsync/msgpool.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package objectsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LastUsage interface {
|
||||||
|
LastUsage() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessagePool can be made generic to work with different streams
|
||||||
|
type MessagePool interface {
|
||||||
|
LastUsage
|
||||||
|
synchandler.SyncHandler
|
||||||
|
peermanager.PeerManager
|
||||||
|
SendSync(ctx context.Context, peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageHandler func(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
||||||
|
|
||||||
|
type responseWaiter struct {
|
||||||
|
ch chan *spacesyncproto.ObjectSyncMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type messagePool struct {
|
||||||
|
sync.Mutex
|
||||||
|
peermanager.PeerManager
|
||||||
|
messageHandler MessageHandler
|
||||||
|
waiters map[string]responseWaiter
|
||||||
|
waitersMx sync.Mutex
|
||||||
|
counter atomic.Uint64
|
||||||
|
lastUsage atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMessagePool(peerManager peermanager.PeerManager, messageHandler MessageHandler) MessagePool {
|
||||||
|
s := &messagePool{
|
||||||
|
PeerManager: peerManager,
|
||||||
|
messageHandler: messageHandler,
|
||||||
|
waiters: make(map[string]responseWaiter),
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) SendSync(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
s.updateLastUsage()
|
||||||
|
if _, ok := ctx.Deadline(); !ok {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
newCounter := s.counter.Add(1)
|
||||||
|
msg.RequestId = genReplyKey(peerId, msg.ObjectId, newCounter)
|
||||||
|
log.InfoCtx(ctx, "mpool sendSync", zap.String("requestId", msg.RequestId))
|
||||||
|
s.waitersMx.Lock()
|
||||||
|
waiter := responseWaiter{
|
||||||
|
ch: make(chan *spacesyncproto.ObjectSyncMessage, 1),
|
||||||
|
}
|
||||||
|
s.waiters[msg.RequestId] = waiter
|
||||||
|
s.waitersMx.Unlock()
|
||||||
|
|
||||||
|
err = s.SendPeer(ctx, peerId, msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.waitersMx.Lock()
|
||||||
|
delete(s.waiters, msg.RequestId)
|
||||||
|
s.waitersMx.Unlock()
|
||||||
|
|
||||||
|
log.With(zap.String("requestId", msg.RequestId)).DebugCtx(ctx, "time elapsed when waiting")
|
||||||
|
err = fmt.Errorf("sendSync context error: %v", ctx.Err())
|
||||||
|
case reply = <-waiter.ch:
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
s.updateLastUsage()
|
||||||
|
return s.PeerManager.SendPeer(ctx, peerId, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
s.updateLastUsage()
|
||||||
|
return s.PeerManager.Broadcast(ctx, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
s.updateLastUsage()
|
||||||
|
if msg.ReplyId != "" {
|
||||||
|
log.InfoCtx(ctx, "mpool receive reply", zap.String("replyId", msg.ReplyId))
|
||||||
|
// we got reply, send it to waiter
|
||||||
|
if s.stopWaiter(msg) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.WarnCtx(ctx, "reply id does not exist", zap.String("replyId", msg.ReplyId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.messageHandler(ctx, senderId, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) LastUsage() time.Time {
|
||||||
|
return time.Unix(s.lastUsage.Load(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) updateLastUsage() {
|
||||||
|
s.lastUsage.Store(time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *messagePool) stopWaiter(msg *spacesyncproto.ObjectSyncMessage) bool {
|
||||||
|
s.waitersMx.Lock()
|
||||||
|
waiter, exists := s.waiters[msg.ReplyId]
|
||||||
|
if exists {
|
||||||
|
delete(s.waiters, msg.ReplyId)
|
||||||
|
s.waitersMx.Unlock()
|
||||||
|
waiter.ch <- msg
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s.waitersMx.Unlock()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func genReplyKey(peerId, treeId string, counter uint64) string {
|
||||||
|
b := &strings.Builder{}
|
||||||
|
b.WriteString(peerId)
|
||||||
|
b.WriteString(".")
|
||||||
|
b.WriteString(treeId)
|
||||||
|
b.WriteString(".")
|
||||||
|
b.WriteString(strconv.FormatUint(counter, 36))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
@ -1,24 +1,18 @@
|
|||||||
//go:generate mockgen -destination mock_objectsync/mock_objectsync.go github.com/anyproto/any-sync/commonspace/objectsync ObjectSync
|
//go:generate mockgen -destination mock_objectsync/mock_objectsync.go github.com/anyproto/any-sync/commonspace/objectsync SyncClient
|
||||||
package objectsync
|
package objectsync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/metric"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/util/multiqueue"
|
|
||||||
"github.com/cheggaaa/mb/v3"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
@ -26,208 +20,138 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace.objectsync"
|
var log = logger.NewNamed("common.commonspace.objectsync")
|
||||||
|
|
||||||
var log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
type ObjectSync interface {
|
type ObjectSync interface {
|
||||||
LastUsage() time.Time
|
LastUsage
|
||||||
HandleMessage(ctx context.Context, hm HandleMessage) (err error)
|
synchandler.SyncHandler
|
||||||
HandleRequest(ctx context.Context, req *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error)
|
SyncClient() SyncClient
|
||||||
CloseThread(id string) (err error)
|
|
||||||
app.ComponentRunnable
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandleMessage struct {
|
Close() (err error)
|
||||||
Id uint64
|
|
||||||
ReceiveTime time.Time
|
|
||||||
StartHandlingTime time.Time
|
|
||||||
Deadline time.Time
|
|
||||||
SenderId string
|
|
||||||
Message *spacesyncproto.ObjectSyncMessage
|
|
||||||
PeerCtx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m HandleMessage) LogFields(fields ...zap.Field) []zap.Field {
|
|
||||||
return append(fields,
|
|
||||||
metric.SpaceId(m.Message.SpaceId),
|
|
||||||
metric.ObjectId(m.Message.ObjectId),
|
|
||||||
metric.QueueDur(m.StartHandlingTime.Sub(m.ReceiveTime)),
|
|
||||||
metric.TotalDur(time.Since(m.ReceiveTime)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectSync struct {
|
type objectSync struct {
|
||||||
spaceId string
|
spaceId string
|
||||||
|
|
||||||
|
messagePool MessagePool
|
||||||
|
syncClient SyncClient
|
||||||
objectGetter syncobjectgetter.SyncObjectGetter
|
objectGetter syncobjectgetter.SyncObjectGetter
|
||||||
configuration nodeconf.NodeConf
|
configuration nodeconf.NodeConf
|
||||||
spaceStorage spacestorage.SpaceStorage
|
spaceStorage spacestorage.SpaceStorage
|
||||||
metric metric.Metric
|
|
||||||
|
|
||||||
|
syncCtx context.Context
|
||||||
|
cancelSync context.CancelFunc
|
||||||
spaceIsDeleted *atomic.Bool
|
spaceIsDeleted *atomic.Bool
|
||||||
handleQueue multiqueue.MultiQueue[HandleMessage]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) Init(a *app.App) (err error) {
|
func NewObjectSync(
|
||||||
s.spaceStorage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
spaceId string,
|
||||||
s.objectGetter = a.MustComponent(treemanager.CName).(syncobjectgetter.SyncObjectGetter)
|
spaceIsDeleted *atomic.Bool,
|
||||||
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
configuration nodeconf.NodeConf,
|
||||||
sharedData := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
peerManager peermanager.PeerManager,
|
||||||
mc := a.Component(metric.CName)
|
objectGetter syncobjectgetter.SyncObjectGetter,
|
||||||
if mc != nil {
|
storage spacestorage.SpaceStorage) ObjectSync {
|
||||||
s.metric = mc.(metric.Metric)
|
syncCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
os := &objectSync{
|
||||||
|
objectGetter: objectGetter,
|
||||||
|
spaceStorage: storage,
|
||||||
|
spaceId: spaceId,
|
||||||
|
syncCtx: syncCtx,
|
||||||
|
cancelSync: cancel,
|
||||||
|
spaceIsDeleted: spaceIsDeleted,
|
||||||
|
configuration: configuration,
|
||||||
}
|
}
|
||||||
s.spaceIsDeleted = sharedData.SpaceIsDeleted
|
os.messagePool = newMessagePool(peerManager, os.handleMessage)
|
||||||
s.spaceId = sharedData.SpaceId
|
os.syncClient = NewSyncClient(spaceId, os.messagePool, NewRequestFactory())
|
||||||
s.handleQueue = multiqueue.New[HandleMessage](s.processHandleMessage, 30)
|
return os
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) Name() (name string) {
|
func (s *objectSync) Close() (err error) {
|
||||||
return CName
|
s.cancelSync()
|
||||||
}
|
return
|
||||||
|
|
||||||
func (s *objectSync) Run(ctx context.Context) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) Close(ctx context.Context) (err error) {
|
|
||||||
return s.handleQueue.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() ObjectSync {
|
|
||||||
return &objectSync{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) LastUsage() time.Time {
|
func (s *objectSync) LastUsage() time.Time {
|
||||||
// TODO: add time
|
return s.messagePool.LastUsage()
|
||||||
return time.Time{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) HandleRequest(ctx context.Context, req *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error) {
|
func (s *objectSync) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
peerId, err := peer.CtxPeerId(ctx)
|
return s.messagePool.HandleMessage(ctx, senderId, message)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s.handleRequest(ctx, peerId, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) HandleMessage(ctx context.Context, hm HandleMessage) (err error) {
|
|
||||||
threadId := hm.Message.ObjectId
|
|
||||||
hm.ReceiveTime = time.Now()
|
|
||||||
if hm.PeerCtx == nil {
|
|
||||||
hm.PeerCtx = ctx
|
|
||||||
}
|
|
||||||
err = s.handleQueue.Add(ctx, threadId, hm)
|
|
||||||
if err == mb.ErrOverflowed {
|
|
||||||
log.InfoCtx(ctx, "queue overflowed", zap.String("spaceId", s.spaceId), zap.String("objectId", threadId))
|
|
||||||
// skip overflowed error
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) processHandleMessage(msg HandleMessage) {
|
|
||||||
var err error
|
|
||||||
msg.StartHandlingTime = time.Now()
|
|
||||||
ctx := peer.CtxWithPeerId(context.Background(), msg.SenderId)
|
|
||||||
ctx = logger.CtxWithFields(ctx, zap.Uint64("msgId", msg.Id), zap.String("senderId", msg.SenderId))
|
|
||||||
defer func() {
|
|
||||||
if s.metric == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.metric.RequestLog(msg.PeerCtx, "space.streamOp", msg.LogFields(
|
|
||||||
zap.Error(err),
|
|
||||||
)...)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if !msg.Deadline.IsZero() {
|
|
||||||
now := time.Now()
|
|
||||||
if now.After(msg.Deadline) {
|
|
||||||
log.InfoCtx(ctx, "skip message: deadline exceed")
|
|
||||||
err = context.DeadlineExceeded
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
|
||||||
if msg.Message.ObjectId != "" {
|
|
||||||
// cleanup thread on error
|
|
||||||
_ = s.handleQueue.CloseThread(msg.Message.ObjectId)
|
|
||||||
}
|
|
||||||
log.InfoCtx(ctx, "handleMessage error", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) handleRequest(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
log := log.With(zap.String("objectId", msg.ObjectId))
|
|
||||||
if s.spaceIsDeleted.Load() {
|
|
||||||
log = log.With(zap.Bool("isDeleted", true))
|
|
||||||
// preventing sync with other clients if they are not just syncing the settings tree
|
|
||||||
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
|
||||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = s.checkEmptyFullSync(log, msg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, treechangeproto.ErrGetTree
|
|
||||||
}
|
|
||||||
return obj.HandleRequest(ctx, senderId, msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
log := log.With(zap.String("objectId", msg.ObjectId))
|
log := log.With(
|
||||||
|
zap.String("objectId", msg.ObjectId),
|
||||||
|
zap.String("requestId", msg.RequestId),
|
||||||
|
zap.String("replyId", msg.ReplyId))
|
||||||
if s.spaceIsDeleted.Load() {
|
if s.spaceIsDeleted.Load() {
|
||||||
log = log.With(zap.Bool("isDeleted", true))
|
log = log.With(zap.Bool("isDeleted", true))
|
||||||
// preventing sync with other clients if they are not just syncing the settings tree
|
// preventing sync with other clients if they are not just syncing the settings tree
|
||||||
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
||||||
return spacesyncproto.ErrSpaceIsDeleted
|
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
||||||
|
return fmt.Errorf("can't perform operation with object, space is deleted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = s.checkEmptyFullSync(log, msg)
|
log.DebugCtx(ctx, "handling message")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get object from cache: %w", err)
|
|
||||||
}
|
|
||||||
err = obj.HandleMessage(ctx, senderId, msg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to handle message: %w", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) CloseThread(id string) (err error) {
|
|
||||||
return s.handleQueue.CloseThread(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) checkEmptyFullSync(log logger.CtxLogger, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
hasTree, err := s.spaceStorage.HasTree(msg.ObjectId)
|
hasTree, err := s.spaceStorage.HasTree(msg.ObjectId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("failed to execute get operation on storage has tree", zap.Error(err))
|
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
||||||
return spacesyncproto.ErrUnexpected
|
return fmt.Errorf("falied to execute get operation on storage has tree: %w", err)
|
||||||
}
|
}
|
||||||
// in this case we will try to get it from remote, unless the sender also sent us the same request :-)
|
// in this case we will try to get it from remote, unless the sender also sent us the same request :-)
|
||||||
if !hasTree {
|
if !hasTree {
|
||||||
treeMsg := &treechangeproto.TreeSyncMessage{}
|
treeMsg := &treechangeproto.TreeSyncMessage{}
|
||||||
err = proto.Unmarshal(msg.Payload, treeMsg)
|
err = proto.Unmarshal(msg.Payload, treeMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
s.sendError(ctx, nil, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId, msg.RequestId)
|
||||||
|
return fmt.Errorf("failed to unmarshall tree sync message: %w", err)
|
||||||
}
|
}
|
||||||
// this means that we don't have the tree locally and therefore can't return it
|
// this means that we don't have the tree locally and therefore can't return it
|
||||||
if s.isEmptyFullSyncRequest(treeMsg) {
|
if s.isEmptyFullSyncRequest(treeMsg) {
|
||||||
return treechangeproto.ErrGetTree
|
err = treechangeproto.ErrGetTree
|
||||||
|
s.sendError(ctx, nil, treechangeproto.ErrGetTree, senderId, msg.ObjectId, msg.RequestId)
|
||||||
|
return fmt.Errorf("failed to get tree from storage on full sync: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: write tests for object sync https://linear.app/anytype/issue/GO-1299/write-tests-for-commonspaceobjectsync
|
||||||
|
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
||||||
|
return fmt.Errorf("failed to get object from cache: %w", err)
|
||||||
|
}
|
||||||
|
// TODO: unmarshall earlier
|
||||||
|
err = obj.HandleMessage(ctx, senderId, msg)
|
||||||
|
if err != nil {
|
||||||
|
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
||||||
|
return fmt.Errorf("failed to handle message: %w", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *objectSync) SyncClient() SyncClient {
|
||||||
|
return s.syncClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *objectSync) unmarshallSendError(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage, respErr error, senderId, objectId string) {
|
||||||
|
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||||
|
err := proto.Unmarshal(msg.Payload, unmarshalled)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.sendError(ctx, unmarshalled.RootChange, respErr, senderId, objectId, msg.RequestId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *objectSync) sendError(ctx context.Context, root *treechangeproto.RawTreeChangeWithId, respErr error, senderId, objectId, replyId string) {
|
||||||
|
// we don't send errors if have no reply id, this can lead to bugs and also nobody needs this error
|
||||||
|
if replyId == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := treechangeproto.WrapError(respErr, root)
|
||||||
|
if err := s.syncClient.SendWithReply(ctx, senderId, objectId, resp, replyId); err != nil {
|
||||||
|
log.InfoCtx(ctx, "failed to send error to client")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *objectSync) isEmptyFullSyncRequest(msg *treechangeproto.TreeSyncMessage) bool {
|
func (s *objectSync) isEmptyFullSyncRequest(msg *treechangeproto.TreeSyncMessage) bool {
|
||||||
return msg.GetContent().GetFullSyncRequest() != nil && len(msg.GetContent().GetFullSyncRequest().GetHeads()) == 0
|
return msg.GetContent().GetFullSyncRequest() != nil && len(msg.GetContent().GetFullSyncRequest().GetHeads()) == 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package synctree
|
package objectsync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
78
commonspace/objectsync/syncclient.go
Normal file
78
commonspace/objectsync/syncclient.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package objectsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SyncClient interface {
|
||||||
|
RequestFactory
|
||||||
|
Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage)
|
||||||
|
SendWithReply(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error)
|
||||||
|
SendSync(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||||
|
MessagePool() MessagePool
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncClient struct {
|
||||||
|
RequestFactory
|
||||||
|
spaceId string
|
||||||
|
messagePool MessagePool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSyncClient(
|
||||||
|
spaceId string,
|
||||||
|
messagePool MessagePool,
|
||||||
|
factory RequestFactory) SyncClient {
|
||||||
|
return &syncClient{
|
||||||
|
messagePool: messagePool,
|
||||||
|
RequestFactory: factory,
|
||||||
|
spaceId: spaceId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.messagePool.Broadcast(ctx, objMsg)
|
||||||
|
if err != nil {
|
||||||
|
log.DebugCtx(ctx, "broadcast error", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) SendSync(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.messagePool.SendSync(ctx, peerId, objMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) SendWithReply(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, replyId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.messagePool.SendPeer(ctx, peerId, objMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) MessagePool() MessagePool {
|
||||||
|
return s.messagePool
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
payload, err := message.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
objMsg = &spacesyncproto.ObjectSyncMessage{
|
||||||
|
ReplyId: replyId,
|
||||||
|
Payload: payload,
|
||||||
|
ObjectId: objectId,
|
||||||
|
SpaceId: spaceId,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -6,6 +6,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SyncHandler interface {
|
type SyncHandler interface {
|
||||||
HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error)
|
||||||
HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/objecttreebuilder (interfaces: TreeBuilder)
|
|
||||||
|
|
||||||
// Package mock_objecttreebuilder is a generated GoMock package.
|
|
||||||
package mock_objecttreebuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
|
||||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
objecttreebuilder "github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockTreeBuilder is a mock of TreeBuilder interface.
|
|
||||||
type MockTreeBuilder struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockTreeBuilderMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTreeBuilderMockRecorder is the mock recorder for MockTreeBuilder.
|
|
||||||
type MockTreeBuilderMockRecorder struct {
|
|
||||||
mock *MockTreeBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockTreeBuilder creates a new mock instance.
|
|
||||||
func NewMockTreeBuilder(ctrl *gomock.Controller) *MockTreeBuilder {
|
|
||||||
mock := &MockTreeBuilder{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockTreeBuilderMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockTreeBuilder) EXPECT() *MockTreeBuilderMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildHistoryTree mocks base method.
|
|
||||||
func (m *MockTreeBuilder) BuildHistoryTree(arg0 context.Context, arg1 string, arg2 objecttreebuilder.HistoryTreeOpts) (objecttree.HistoryTree, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "BuildHistoryTree", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(objecttree.HistoryTree)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildHistoryTree indicates an expected call of BuildHistoryTree.
|
|
||||||
func (mr *MockTreeBuilderMockRecorder) BuildHistoryTree(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildHistoryTree", reflect.TypeOf((*MockTreeBuilder)(nil).BuildHistoryTree), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildTree mocks base method.
|
|
||||||
func (m *MockTreeBuilder) BuildTree(arg0 context.Context, arg1 string, arg2 objecttreebuilder.BuildTreeOpts) (objecttree.ObjectTree, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "BuildTree", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(objecttree.ObjectTree)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildTree indicates an expected call of BuildTree.
|
|
||||||
func (mr *MockTreeBuilderMockRecorder) BuildTree(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTree", reflect.TypeOf((*MockTreeBuilder)(nil).BuildTree), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTree mocks base method.
|
|
||||||
func (m *MockTreeBuilder) CreateTree(arg0 context.Context, arg1 objecttree.ObjectTreeCreatePayload) (treestorage.TreeStorageCreatePayload, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateTree", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(treestorage.TreeStorageCreatePayload)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTree indicates an expected call of CreateTree.
|
|
||||||
func (mr *MockTreeBuilderMockRecorder) CreateTree(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTree", reflect.TypeOf((*MockTreeBuilder)(nil).CreateTree), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutTree mocks base method.
|
|
||||||
func (m *MockTreeBuilder) PutTree(arg0 context.Context, arg1 treestorage.TreeStorageCreatePayload, arg2 updatelistener.UpdateListener) (objecttree.ObjectTree, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "PutTree", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(objecttree.ObjectTree)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutTree indicates an expected call of PutTree.
|
|
||||||
func (mr *MockTreeBuilderMockRecorder) PutTree(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutTree", reflect.TypeOf((*MockTreeBuilder)(nil).PutTree), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
@ -1,204 +0,0 @@
|
|||||||
//go:generate mockgen -destination mock_objecttreebuilder/mock_objecttreebuilder.go github.com/anyproto/any-sync/commonspace/objecttreebuilder TreeBuilder
|
|
||||||
package objecttreebuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
|
||||||
"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/tree/synctree/updatelistener"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildTreeOpts struct {
|
|
||||||
Listener updatelistener.UpdateListener
|
|
||||||
TreeBuilder objecttree.BuildObjectTreeFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
const CName = "common.commonspace.objecttreebuilder"
|
|
||||||
|
|
||||||
var log = logger.NewNamed(CName)
|
|
||||||
var ErrSpaceClosed = errors.New("space is closed")
|
|
||||||
|
|
||||||
type HistoryTreeOpts struct {
|
|
||||||
BeforeId string
|
|
||||||
Include bool
|
|
||||||
BuildFullTree bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type TreeBuilder interface {
|
|
||||||
BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error)
|
|
||||||
BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error)
|
|
||||||
CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error)
|
|
||||||
PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TreeBuilderComponent interface {
|
|
||||||
app.Component
|
|
||||||
TreeBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() TreeBuilderComponent {
|
|
||||||
return &treeBuilder{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type treeBuilder struct {
|
|
||||||
syncClient synctree.SyncClient
|
|
||||||
configuration nodeconf.NodeConf
|
|
||||||
headsNotifiable synctree.HeadNotifiable
|
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
requestManager requestmanager.RequestManager
|
|
||||||
spaceStorage spacestorage.SpaceStorage
|
|
||||||
syncStatus syncstatus.StatusUpdater
|
|
||||||
objectSync objectsync.ObjectSync
|
|
||||||
|
|
||||||
log logger.CtxLogger
|
|
||||||
builder objecttree.BuildObjectTreeFunc
|
|
||||||
spaceId string
|
|
||||||
aclList list.AclList
|
|
||||||
treesUsed *atomic.Int32
|
|
||||||
isClosed *atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) Init(a *app.App) (err error) {
|
|
||||||
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
|
||||||
t.spaceId = state.SpaceId
|
|
||||||
t.isClosed = state.SpaceIsClosed
|
|
||||||
t.treesUsed = state.TreesUsed
|
|
||||||
t.builder = state.TreeBuilderFunc
|
|
||||||
t.aclList = a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
|
||||||
t.spaceStorage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
t.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
|
||||||
t.headsNotifiable = a.MustComponent(headsync.CName).(headsync.HeadSync)
|
|
||||||
t.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusUpdater)
|
|
||||||
t.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
|
||||||
t.requestManager = a.MustComponent(requestmanager.CName).(requestmanager.RequestManager)
|
|
||||||
t.objectSync = a.MustComponent(objectsync.CName).(objectsync.ObjectSync)
|
|
||||||
t.log = log.With(zap.String("spaceId", t.spaceId))
|
|
||||||
t.syncClient = synctree.NewSyncClient(t.spaceId, t.requestManager, t.peerManager)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (ot objecttree.ObjectTree, err error) {
|
|
||||||
if t.isClosed.Load() {
|
|
||||||
// TODO: change to real error
|
|
||||||
err = ErrSpaceClosed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
treeBuilder := opts.TreeBuilder
|
|
||||||
if treeBuilder == nil {
|
|
||||||
treeBuilder = t.builder
|
|
||||||
}
|
|
||||||
deps := synctree.BuildDeps{
|
|
||||||
SpaceId: t.spaceId,
|
|
||||||
SyncClient: t.syncClient,
|
|
||||||
Configuration: t.configuration,
|
|
||||||
HeadNotifiable: t.headsNotifiable,
|
|
||||||
Listener: opts.Listener,
|
|
||||||
AclList: t.aclList,
|
|
||||||
SpaceStorage: t.spaceStorage,
|
|
||||||
OnClose: t.onClose,
|
|
||||||
SyncStatus: t.syncStatus,
|
|
||||||
PeerGetter: t.peerManager,
|
|
||||||
BuildObjectTree: treeBuilder,
|
|
||||||
}
|
|
||||||
t.treesUsed.Add(1)
|
|
||||||
t.log.Debug("incrementing counter", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()))
|
|
||||||
if ot, err = synctree.BuildSyncTreeOrGetRemote(ctx, id, deps); err != nil {
|
|
||||||
t.treesUsed.Add(-1)
|
|
||||||
t.log.Debug("decrementing counter, load failed", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()), zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (ot objecttree.HistoryTree, err error) {
|
|
||||||
if t.isClosed.Load() {
|
|
||||||
// TODO: change to real error
|
|
||||||
err = ErrSpaceClosed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := objecttree.HistoryTreeParams{
|
|
||||||
AclList: t.aclList,
|
|
||||||
BeforeId: opts.BeforeId,
|
|
||||||
IncludeBeforeId: opts.Include,
|
|
||||||
BuildFullTree: opts.BuildFullTree,
|
|
||||||
}
|
|
||||||
params.TreeStorage, err = t.spaceStorage.TreeStorage(id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return objecttree.BuildHistoryTree(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) {
|
|
||||||
if t.isClosed.Load() {
|
|
||||||
err = ErrSpaceClosed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
root, err := objecttree.CreateObjectTreeRoot(payload, t.aclList)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res = treestorage.TreeStorageCreatePayload{
|
|
||||||
RootRawChange: root,
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{root},
|
|
||||||
Heads: []string{root.Id},
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (ot objecttree.ObjectTree, err error) {
|
|
||||||
if t.isClosed.Load() {
|
|
||||||
err = ErrSpaceClosed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
deps := synctree.BuildDeps{
|
|
||||||
SpaceId: t.spaceId,
|
|
||||||
SyncClient: t.syncClient,
|
|
||||||
Configuration: t.configuration,
|
|
||||||
HeadNotifiable: t.headsNotifiable,
|
|
||||||
Listener: listener,
|
|
||||||
AclList: t.aclList,
|
|
||||||
SpaceStorage: t.spaceStorage,
|
|
||||||
OnClose: t.onClose,
|
|
||||||
SyncStatus: t.syncStatus,
|
|
||||||
PeerGetter: t.peerManager,
|
|
||||||
BuildObjectTree: t.builder,
|
|
||||||
}
|
|
||||||
ot, err = synctree.PutSyncTree(ctx, payload, deps)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.treesUsed.Add(1)
|
|
||||||
t.log.Debug("incrementing counter", zap.String("id", payload.RootRawChange.Id), zap.Int32("trees", t.treesUsed.Load()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeBuilder) onClose(id string) {
|
|
||||||
t.treesUsed.Add(-1)
|
|
||||||
log.Debug("decrementing counter", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()), zap.String("spaceId", t.spaceId))
|
|
||||||
_ = t.objectSync.CloseThread(id)
|
|
||||||
}
|
|
||||||
@ -2,22 +2,20 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"hash/fnv"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"hash/fnv"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,7 +71,7 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload sp
|
|||||||
|
|
||||||
// building acl root
|
// building acl root
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
aclBuilder := list.NewAclRecordBuilder("", keyStorage)
|
||||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||||
PrivKey: payload.SigningKey,
|
PrivKey: payload.SigningKey,
|
||||||
MasterKey: payload.MasterKey,
|
MasterKey: payload.MasterKey,
|
||||||
@ -160,7 +158,7 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload sp
|
|||||||
|
|
||||||
// building acl root
|
// building acl root
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
aclBuilder := list.NewAclRecordBuilder("", keyStorage)
|
||||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||||
PrivKey: payload.SigningKey,
|
PrivKey: payload.SigningKey,
|
||||||
MasterKey: payload.MasterKey,
|
MasterKey: payload.MasterKey,
|
||||||
@ -216,15 +214,14 @@ func validateSpaceStorageCreatePayload(payload spacestorage.SpaceStorageCreatePa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, identity crypto.PubKey) (err error) {
|
func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, identity crypto.PubKey) (err error) {
|
||||||
if rawHeaderWithId == nil {
|
|
||||||
return spacestorage.ErrIncorrectSpaceHeader
|
|
||||||
}
|
|
||||||
sepIdx := strings.Index(rawHeaderWithId.Id, ".")
|
sepIdx := strings.Index(rawHeaderWithId.Id, ".")
|
||||||
if sepIdx == -1 {
|
if sepIdx == -1 {
|
||||||
return spacestorage.ErrIncorrectSpaceHeader
|
err = spacestorage.ErrIncorrectSpaceHeader
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if !cidutil.VerifyCid(rawHeaderWithId.RawHeader, rawHeaderWithId.Id[:sepIdx]) {
|
if !cidutil.VerifyCid(rawHeaderWithId.RawHeader, rawHeaderWithId.Id[:sepIdx]) {
|
||||||
return objecttree.ErrIncorrectCid
|
err = objecttree.ErrIncorrectCid
|
||||||
|
return
|
||||||
}
|
}
|
||||||
var rawSpaceHeader spacesyncproto.RawSpaceHeader
|
var rawSpaceHeader spacesyncproto.RawSpaceHeader
|
||||||
err = proto.Unmarshal(rawHeaderWithId.RawHeader, &rawSpaceHeader)
|
err = proto.Unmarshal(rawHeaderWithId.RawHeader, &rawSpaceHeader)
|
||||||
@ -242,26 +239,29 @@ func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, i
|
|||||||
}
|
}
|
||||||
res, err := payloadIdentity.Verify(rawSpaceHeader.SpaceHeader, rawSpaceHeader.Signature)
|
res, err := payloadIdentity.Verify(rawSpaceHeader.SpaceHeader, rawSpaceHeader.Signature)
|
||||||
if err != nil || !res {
|
if err != nil || !res {
|
||||||
return spacestorage.ErrIncorrectSpaceHeader
|
err = spacestorage.ErrIncorrectSpaceHeader
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if rawHeaderWithId.Id[sepIdx+1:] != strconv.FormatUint(header.ReplicationKey, 36) {
|
if rawHeaderWithId.Id[sepIdx+1:] != strconv.FormatUint(header.ReplicationKey, 36) {
|
||||||
return spacestorage.ErrIncorrectSpaceHeader
|
err = spacestorage.ErrIncorrectSpaceHeader
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if identity == nil {
|
if identity == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !payloadIdentity.Equals(identity) {
|
if !payloadIdentity.Equals(identity) {
|
||||||
return ErrIncorrectIdentity
|
err = ErrIncorrectIdentity
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateSpaceAclPayload(rawWithId *consensusproto.RawRecordWithId) (spaceId string, err error) {
|
func validateCreateSpaceAclPayload(rawWithId *aclrecordproto.RawAclRecordWithId) (spaceId string, err error) {
|
||||||
if !cidutil.VerifyCid(rawWithId.Payload, rawWithId.Id) {
|
if !cidutil.VerifyCid(rawWithId.Payload, rawWithId.Id) {
|
||||||
err = objecttree.ErrIncorrectCid
|
err = objecttree.ErrIncorrectCid
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var rawAcl consensusproto.RawRecord
|
var rawAcl aclrecordproto.RawAclRecord
|
||||||
err = proto.Unmarshal(rawWithId.Payload, &rawAcl)
|
err = proto.Unmarshal(rawWithId.Payload, &rawAcl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -2,22 +2,20 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSuccessHeaderPayloadForSpaceCreate(t *testing.T) {
|
func TestSuccessHeaderPayloadForSpaceCreate(t *testing.T) {
|
||||||
@ -190,14 +188,14 @@ func TestFailAclPayloadSpace_IncorrectCid(t *testing.T) {
|
|||||||
marshalled, err := aclRoot.Marshal()
|
marshalled, err := aclRoot.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||||
rawAclRecord := &consensusproto.RawRecord{
|
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
marshalledRaw, err := rawAclRecord.Marshal()
|
marshalledRaw, err := rawAclRecord.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
aclHeadId := "rand"
|
aclHeadId := "rand"
|
||||||
rawWithId := &consensusproto.RawRecordWithId{
|
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
@ -232,7 +230,7 @@ func TestFailedAclPayloadSpace_IncorrectSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
marshalled, err := aclRoot.Marshal()
|
marshalled, err := aclRoot.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
rawAclRecord := &consensusproto.RawRecord{
|
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: marshalled,
|
Signature: marshalled,
|
||||||
}
|
}
|
||||||
@ -240,7 +238,7 @@ func TestFailedAclPayloadSpace_IncorrectSignature(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
|
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
rawWithId := &consensusproto.RawRecordWithId{
|
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
@ -288,7 +286,7 @@ func TestFailedAclPayloadSpace_IncorrectIdentitySignature(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||||
rawAclRecord := &consensusproto.RawRecord{
|
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -300,7 +298,7 @@ func TestFailedAclPayloadSpace_IncorrectIdentitySignature(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId := &consensusproto.RawRecordWithId{
|
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
@ -542,7 +540,7 @@ func rawSettingsPayload(accountKeys *accountdata.AccountKeys, spaceId, aclHeadId
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHeadId string, rawWithId *consensusproto.RawRecordWithId, err error) {
|
func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHeadId string, rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||||
// TODO: use same storage creation methods as we use in spaces
|
// TODO: use same storage creation methods as we use in spaces
|
||||||
readKeyBytes := make([]byte, 32)
|
readKeyBytes := make([]byte, 32)
|
||||||
_, err = rand.Read(readKeyBytes)
|
_, err = rand.Read(readKeyBytes)
|
||||||
@ -584,7 +582,7 @@ func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHead
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||||
rawAclRecord := &consensusproto.RawRecord{
|
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -596,7 +594,7 @@ func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHead
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId = &consensusproto.RawRecordWithId{
|
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,9 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
peer "github.com/anyproto/any-sync/net/peer"
|
peer "github.com/anyproto/any-sync/net/peer"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockPeerManager is a mock of PeerManager interface.
|
// MockPeerManager is a mock of PeerManager interface.
|
||||||
@ -66,34 +65,6 @@ func (mr *MockPeerManagerMockRecorder) GetResponsiblePeers(arg0 interface{}) *go
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResponsiblePeers", reflect.TypeOf((*MockPeerManager)(nil).GetResponsiblePeers), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResponsiblePeers", reflect.TypeOf((*MockPeerManager)(nil).GetResponsiblePeers), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockPeerManager) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockPeerManagerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockPeerManager)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockPeerManager) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockPeerManagerMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockPeerManager)(nil).Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendPeer mocks base method.
|
// SendPeer mocks base method.
|
||||||
func (m *MockPeerManager) SendPeer(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
func (m *MockPeerManager) SendPeer(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@ -8,12 +8,9 @@ import (
|
|||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const CName = "common.commonspace.peermanager"
|
||||||
CName = "common.commonspace.peermanager"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PeerManager interface {
|
type PeerManager interface {
|
||||||
app.Component
|
|
||||||
// SendPeer sends a message to a stream by peerId
|
// SendPeer sends a message to a stream by peerId
|
||||||
SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error)
|
SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error)
|
||||||
// Broadcast sends a message to all subscribed peers
|
// Broadcast sends a message to all subscribed peers
|
||||||
|
|||||||
@ -1,129 +0,0 @@
|
|||||||
package requestmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/net/pool"
|
|
||||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
|
||||||
"github.com/anyproto/any-sync/net/streampool"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"storj.io/drpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const CName = "common.commonspace.requestmanager"
|
|
||||||
|
|
||||||
var log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
type RequestManager interface {
|
|
||||||
app.ComponentRunnable
|
|
||||||
SendRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
QueueRequest(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() RequestManager {
|
|
||||||
return &requestManager{
|
|
||||||
workers: 10,
|
|
||||||
queueSize: 300,
|
|
||||||
pools: map[string]*streampool.ExecPool{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageHandler interface {
|
|
||||||
HandleMessage(ctx context.Context, hm objectsync.HandleMessage) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestManager struct {
|
|
||||||
sync.Mutex
|
|
||||||
pools map[string]*streampool.ExecPool
|
|
||||||
peerPool pool.Pool
|
|
||||||
workers int
|
|
||||||
queueSize int
|
|
||||||
handler MessageHandler
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
clientFactory spacesyncproto.ClientFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) Init(a *app.App) (err error) {
|
|
||||||
r.ctx, r.cancel = context.WithCancel(context.Background())
|
|
||||||
r.handler = a.MustComponent(objectsync.CName).(MessageHandler)
|
|
||||||
r.peerPool = a.MustComponent(pool.CName).(pool.Pool)
|
|
||||||
r.clientFactory = spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) Run(ctx context.Context) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) Close(ctx context.Context) (err error) {
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
r.cancel()
|
|
||||||
for _, p := range r.pools {
|
|
||||||
_ = p.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) SendRequest(ctx context.Context, peerId string, req *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
// TODO: limit concurrent sends?
|
|
||||||
return r.doRequest(ctx, peerId, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) QueueRequest(peerId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
pl, exists := r.pools[peerId]
|
|
||||||
if !exists {
|
|
||||||
pl = streampool.NewExecPool(r.workers, r.queueSize)
|
|
||||||
r.pools[peerId] = pl
|
|
||||||
pl.Run()
|
|
||||||
}
|
|
||||||
// TODO: for later think when many clients are there,
|
|
||||||
// we need to close pools for inactive clients
|
|
||||||
return pl.TryAdd(func() {
|
|
||||||
doRequestAndHandle(r, peerId, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var doRequestAndHandle = (*requestManager).requestAndHandle
|
|
||||||
|
|
||||||
func (r *requestManager) requestAndHandle(peerId string, req *spacesyncproto.ObjectSyncMessage) {
|
|
||||||
ctx := r.ctx
|
|
||||||
resp, err := r.doRequest(ctx, peerId, req)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("failed to send request", zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx = peer.CtxWithPeerId(ctx, peerId)
|
|
||||||
_ = r.handler.HandleMessage(ctx, objectsync.HandleMessage{
|
|
||||||
SenderId: peerId,
|
|
||||||
Message: resp,
|
|
||||||
PeerCtx: ctx,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestManager) doRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
pr, err := r.peerPool.Get(ctx, peerId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = pr.DoDrpc(ctx, func(conn drpc.Conn) error {
|
|
||||||
cl := r.clientFactory.Client(conn)
|
|
||||||
resp, err = cl.ObjectSync(ctx, msg)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
err = rpcerr.Unwrap(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -1,186 +0,0 @@
|
|||||||
package requestmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/net/peer/mock_peer"
|
|
||||||
"github.com/anyproto/any-sync/net/pool/mock_pool"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"storj.io/drpc"
|
|
||||||
"storj.io/drpc/drpcconn"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fixture struct {
|
|
||||||
requestManager *requestManager
|
|
||||||
messageHandlerMock *mock_objectsync.MockObjectSync
|
|
||||||
peerPoolMock *mock_pool.MockPool
|
|
||||||
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFixture(t *testing.T) *fixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
manager := New().(*requestManager)
|
|
||||||
peerPoolMock := mock_pool.NewMockPool(ctrl)
|
|
||||||
messageHandlerMock := mock_objectsync.NewMockObjectSync(ctrl)
|
|
||||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
|
||||||
manager.peerPool = peerPoolMock
|
|
||||||
manager.handler = messageHandlerMock
|
|
||||||
manager.clientFactory = spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
|
||||||
return clientMock
|
|
||||||
})
|
|
||||||
manager.ctx, manager.cancel = context.WithCancel(context.Background())
|
|
||||||
return &fixture{
|
|
||||||
requestManager: manager,
|
|
||||||
messageHandlerMock: messageHandlerMock,
|
|
||||||
peerPoolMock: peerPoolMock,
|
|
||||||
clientMock: clientMock,
|
|
||||||
ctrl: ctrl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *fixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestManager_SyncRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("send request", func(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
|
|
||||||
peerId := "peerId"
|
|
||||||
peerMock := mock_peer.NewMockPeer(fx.ctrl)
|
|
||||||
conn := &drpcconn.Conn{}
|
|
||||||
msg := &spacesyncproto.ObjectSyncMessage{}
|
|
||||||
resp := &spacesyncproto.ObjectSyncMessage{}
|
|
||||||
fx.peerPoolMock.EXPECT().Get(ctx, peerId).Return(peerMock, nil)
|
|
||||||
fx.clientMock.EXPECT().ObjectSync(ctx, msg).Return(resp, nil)
|
|
||||||
peerMock.EXPECT().DoDrpc(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, drpcHandler func(conn drpc.Conn) error) {
|
|
||||||
drpcHandler(conn)
|
|
||||||
}).Return(nil)
|
|
||||||
res, err := fx.requestManager.SendRequest(ctx, peerId, msg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, resp, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("request and handle", func(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
ctx = fx.requestManager.ctx
|
|
||||||
|
|
||||||
peerId := "peerId"
|
|
||||||
peerMock := mock_peer.NewMockPeer(fx.ctrl)
|
|
||||||
conn := &drpcconn.Conn{}
|
|
||||||
msg := &spacesyncproto.ObjectSyncMessage{}
|
|
||||||
resp := &spacesyncproto.ObjectSyncMessage{}
|
|
||||||
fx.peerPoolMock.EXPECT().Get(ctx, peerId).Return(peerMock, nil)
|
|
||||||
fx.clientMock.EXPECT().ObjectSync(ctx, msg).Return(resp, nil)
|
|
||||||
peerMock.EXPECT().DoDrpc(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, drpcHandler func(conn drpc.Conn) error) {
|
|
||||||
drpcHandler(conn)
|
|
||||||
}).Return(nil)
|
|
||||||
fx.messageHandlerMock.EXPECT().HandleMessage(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, msg objectsync.HandleMessage) {
|
|
||||||
require.Equal(t, peerId, msg.SenderId)
|
|
||||||
require.Equal(t, resp, msg.Message)
|
|
||||||
pId, _ := peer.CtxPeerId(msg.PeerCtx)
|
|
||||||
require.Equal(t, peerId, pId)
|
|
||||||
}).Return(nil)
|
|
||||||
fx.requestManager.requestAndHandle(peerId, msg)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestManager_QueueRequest(t *testing.T) {
|
|
||||||
t.Run("max concurrent reqs for peer, independent reqs for other peer", func(t *testing.T) {
|
|
||||||
// testing 2 concurrent requests to one peer and simultaneous to another peer
|
|
||||||
fx := newFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fx.requestManager.workers = 2
|
|
||||||
msgRelease := make(chan struct{})
|
|
||||||
msgWait := make(chan struct{})
|
|
||||||
msgs := sync.Map{}
|
|
||||||
doRequestAndHandle = func(manager *requestManager, peerId string, req *spacesyncproto.ObjectSyncMessage) {
|
|
||||||
msgs.Store(req.ObjectId, struct{}{})
|
|
||||||
<-msgWait
|
|
||||||
<-msgRelease
|
|
||||||
}
|
|
||||||
otherPeer := "otherPeer"
|
|
||||||
msg1 := &spacesyncproto.ObjectSyncMessage{ObjectId: "id1"}
|
|
||||||
msg2 := &spacesyncproto.ObjectSyncMessage{ObjectId: "id2"}
|
|
||||||
msg3 := &spacesyncproto.ObjectSyncMessage{ObjectId: "id3"}
|
|
||||||
otherMsg1 := &spacesyncproto.ObjectSyncMessage{ObjectId: "otherId1"}
|
|
||||||
|
|
||||||
// sending requests to first peer
|
|
||||||
peerId := "peerId"
|
|
||||||
err := fx.requestManager.QueueRequest(peerId, msg1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.requestManager.QueueRequest(peerId, msg2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.requestManager.QueueRequest(peerId, msg3)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// waiting until all the messages are loaded
|
|
||||||
msgWait <- struct{}{}
|
|
||||||
msgWait <- struct{}{}
|
|
||||||
_, ok := msgs.Load("id1")
|
|
||||||
require.True(t, ok)
|
|
||||||
_, ok = msgs.Load("id2")
|
|
||||||
require.True(t, ok)
|
|
||||||
// third message should not be read
|
|
||||||
_, ok = msgs.Load("id3")
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
// request for other peer should pass
|
|
||||||
err = fx.requestManager.QueueRequest(otherPeer, otherMsg1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
msgWait <- struct{}{}
|
|
||||||
|
|
||||||
_, ok = msgs.Load("otherId1")
|
|
||||||
require.True(t, ok)
|
|
||||||
close(msgRelease)
|
|
||||||
fx.requestManager.Close(context.Background())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("no requests after close", func(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fx.requestManager.workers = 1
|
|
||||||
msgRelease := make(chan struct{})
|
|
||||||
msgWait := make(chan struct{})
|
|
||||||
msgs := sync.Map{}
|
|
||||||
doRequestAndHandle = func(manager *requestManager, peerId string, req *spacesyncproto.ObjectSyncMessage) {
|
|
||||||
msgs.Store(req.ObjectId, struct{}{})
|
|
||||||
<-msgWait
|
|
||||||
<-msgRelease
|
|
||||||
}
|
|
||||||
msg1 := &spacesyncproto.ObjectSyncMessage{ObjectId: "id1"}
|
|
||||||
msg2 := &spacesyncproto.ObjectSyncMessage{ObjectId: "id2"}
|
|
||||||
|
|
||||||
// sending requests to first peer
|
|
||||||
peerId := "peerId"
|
|
||||||
err := fx.requestManager.QueueRequest(peerId, msg1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.requestManager.QueueRequest(peerId, msg2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// waiting until all the message is loaded
|
|
||||||
msgWait <- struct{}{}
|
|
||||||
_, ok := msgs.Load("id1")
|
|
||||||
require.True(t, ok)
|
|
||||||
_, ok = msgs.Load("id2")
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
fx.requestManager.Close(context.Background())
|
|
||||||
close(msgRelease)
|
|
||||||
_, ok = msgs.Load("id2")
|
|
||||||
require.False(t, ok)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -2,9 +2,9 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -15,11 +15,11 @@ type Deleter interface {
|
|||||||
|
|
||||||
type deleter struct {
|
type deleter struct {
|
||||||
st spacestorage.SpaceStorage
|
st spacestorage.SpaceStorage
|
||||||
state deletionstate.ObjectDeletionState
|
state settingsstate.ObjectDeletionState
|
||||||
getter treemanager.TreeManager
|
getter treemanager.TreeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDeleter(st spacestorage.SpaceStorage, state deletionstate.ObjectDeletionState, getter treemanager.TreeManager) Deleter {
|
func newDeleter(st spacestorage.SpaceStorage, state settingsstate.ObjectDeletionState, getter treemanager.TreeManager) Deleter {
|
||||||
return &deleter{st, state, getter}
|
return &deleter{st, state, getter}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,11 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
"go.uber.org/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ func TestDeleter_Delete(t *testing.T) {
|
|||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
||||||
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
|
|
||||||
deleter := newDeleter(st, delState, treeManager)
|
deleter := newDeleter(st, delState, treeManager)
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -21,7 +20,7 @@ func newDeletionManager(
|
|||||||
settingsId string,
|
settingsId string,
|
||||||
isResponsible bool,
|
isResponsible bool,
|
||||||
treeManager treemanager.TreeManager,
|
treeManager treemanager.TreeManager,
|
||||||
deletionState deletionstate.ObjectDeletionState,
|
deletionState settingsstate.ObjectDeletionState,
|
||||||
provider SpaceIdsProvider,
|
provider SpaceIdsProvider,
|
||||||
onSpaceDelete func()) DeletionManager {
|
onSpaceDelete func()) DeletionManager {
|
||||||
return &deletionManager{
|
return &deletionManager{
|
||||||
@ -36,7 +35,7 @@ func newDeletionManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type deletionManager struct {
|
type deletionManager struct {
|
||||||
deletionState deletionstate.ObjectDeletionState
|
deletionState settingsstate.ObjectDeletionState
|
||||||
provider SpaceIdsProvider
|
provider SpaceIdsProvider
|
||||||
treeManager treemanager.TreeManager
|
treeManager treemanager.TreeManager
|
||||||
spaceId string
|
spaceId string
|
||||||
|
|||||||
@ -2,12 +2,12 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/mock_settings"
|
"github.com/anyproto/any-sync/commonspace/settings/mock_settings"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ func TestDeletionManager_UpdateState_NotResponsible(t *testing.T) {
|
|||||||
onDeleted := func() {
|
onDeleted := func() {
|
||||||
deleted = true
|
deleted = true
|
||||||
}
|
}
|
||||||
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
||||||
|
|
||||||
delState.EXPECT().Add(state.DeletedIds)
|
delState.EXPECT().Add(state.DeletedIds)
|
||||||
@ -58,7 +58,7 @@ func TestDeletionManager_UpdateState_Responsible(t *testing.T) {
|
|||||||
onDeleted := func() {
|
onDeleted := func() {
|
||||||
deleted = true
|
deleted = true
|
||||||
}
|
}
|
||||||
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
||||||
provider := mock_settings.NewMockSpaceIdsProvider(ctrl)
|
provider := mock_settings.NewMockSpaceIdsProvider(ctrl)
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockDeletionManager is a mock of DeletionManager interface.
|
// MockDeletionManager is a mock of DeletionManager interface.
|
||||||
|
|||||||
@ -1,122 +1,328 @@
|
|||||||
|
//go:generate mockgen -destination mock_settings/mock_settings.go github.com/anyproto/any-sync/commonspace/settings DeletionManager,Deleter,SpaceIdsProvider
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync/atomic"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"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/tree/synctree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace.settings"
|
var log = logger.NewNamed("common.commonspace.settings")
|
||||||
|
|
||||||
type Settings interface {
|
type SettingsObject interface {
|
||||||
DeleteTree(ctx context.Context, id string) (err error)
|
synctree.SyncTree
|
||||||
SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error)
|
Init(ctx context.Context) (err error)
|
||||||
DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error)
|
DeleteObject(id string) (err error)
|
||||||
SettingsObject() SettingsObject
|
DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error)
|
||||||
app.ComponentRunnable
|
SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() Settings {
|
var (
|
||||||
return &settings{}
|
ErrDeleteSelf = errors.New("cannot delete self")
|
||||||
}
|
ErrAlreadyDeleted = errors.New("the object is already deleted")
|
||||||
|
ErrObjDoesNotExist = errors.New("the object does not exist")
|
||||||
|
ErrCantDeleteSpace = errors.New("not able to delete space")
|
||||||
|
)
|
||||||
|
|
||||||
type settings struct {
|
var (
|
||||||
account accountservice.Service
|
DoSnapshot = objecttree.DoSnapshot
|
||||||
treeManager treemanager.TreeManager
|
buildHistoryTree = func(objTree objecttree.ObjectTree) (objecttree.ReadableObjectTree, error) {
|
||||||
storage spacestorage.SpaceStorage
|
return objecttree.BuildHistoryTree(objecttree.HistoryTreeParams{
|
||||||
configuration nodeconf.NodeConf
|
TreeStorage: objTree.Storage(),
|
||||||
deletionState deletionstate.ObjectDeletionState
|
AclList: objTree.AclList(),
|
||||||
headsync headsync.HeadSync
|
BuildFullTree: true,
|
||||||
treeBuilder objecttreebuilder.TreeBuilderComponent
|
})
|
||||||
spaceIsDeleted *atomic.Bool
|
|
||||||
|
|
||||||
settingsObject SettingsObject
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settings) Init(a *app.App) (err error) {
|
|
||||||
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
|
|
||||||
s.treeManager = app.MustComponent[treemanager.TreeManager](a)
|
|
||||||
s.headsync = a.MustComponent(headsync.CName).(headsync.HeadSync)
|
|
||||||
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
|
||||||
s.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
|
|
||||||
s.treeBuilder = a.MustComponent(objecttreebuilder.CName).(objecttreebuilder.TreeBuilderComponent)
|
|
||||||
|
|
||||||
sharedState := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
|
||||||
s.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
s.spaceIsDeleted = sharedState.SpaceIsDeleted
|
|
||||||
|
|
||||||
deps := Deps{
|
|
||||||
BuildFunc: func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error) {
|
|
||||||
res, err := s.treeBuilder.BuildTree(ctx, id, objecttreebuilder.BuildTreeOpts{
|
|
||||||
Listener: listener,
|
|
||||||
// space settings document should not have empty data
|
|
||||||
TreeBuilder: objecttree.BuildObjectTree,
|
|
||||||
})
|
|
||||||
log.Debug("building settings tree", zap.String("id", id), zap.String("spaceId", sharedState.SpaceId))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t = res.(synctree.SyncTree)
|
|
||||||
return
|
|
||||||
},
|
|
||||||
Account: s.account,
|
|
||||||
TreeManager: s.treeManager,
|
|
||||||
Store: s.storage,
|
|
||||||
Configuration: s.configuration,
|
|
||||||
DeletionState: s.deletionState,
|
|
||||||
Provider: s.headsync,
|
|
||||||
OnSpaceDelete: s.onSpaceDelete,
|
|
||||||
}
|
}
|
||||||
s.settingsObject = NewSettingsObject(deps, sharedState.SpaceId)
|
)
|
||||||
return nil
|
|
||||||
|
type BuildTreeFunc func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
BuildFunc BuildTreeFunc
|
||||||
|
Account accountservice.Service
|
||||||
|
TreeManager treemanager.TreeManager
|
||||||
|
Store spacestorage.SpaceStorage
|
||||||
|
Configuration nodeconf.NodeConf
|
||||||
|
DeletionState settingsstate.ObjectDeletionState
|
||||||
|
Provider SpaceIdsProvider
|
||||||
|
OnSpaceDelete func()
|
||||||
|
// testing dependencies
|
||||||
|
builder settingsstate.StateBuilder
|
||||||
|
del Deleter
|
||||||
|
delManager DeletionManager
|
||||||
|
changeFactory settingsstate.ChangeFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settings) Name() (name string) {
|
type settingsObject struct {
|
||||||
return CName
|
synctree.SyncTree
|
||||||
|
account accountservice.Service
|
||||||
|
spaceId string
|
||||||
|
treeManager treemanager.TreeManager
|
||||||
|
store spacestorage.SpaceStorage
|
||||||
|
builder settingsstate.StateBuilder
|
||||||
|
buildFunc BuildTreeFunc
|
||||||
|
loop *deleteLoop
|
||||||
|
|
||||||
|
state *settingsstate.State
|
||||||
|
deletionState settingsstate.ObjectDeletionState
|
||||||
|
deletionManager DeletionManager
|
||||||
|
changeFactory settingsstate.ChangeFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settings) Run(ctx context.Context) (err error) {
|
func NewSettingsObject(deps Deps, spaceId string) (obj SettingsObject) {
|
||||||
return s.settingsObject.Init(ctx)
|
var (
|
||||||
|
deleter Deleter
|
||||||
|
deletionManager DeletionManager
|
||||||
|
builder settingsstate.StateBuilder
|
||||||
|
changeFactory settingsstate.ChangeFactory
|
||||||
|
)
|
||||||
|
if deps.del == nil {
|
||||||
|
deleter = newDeleter(deps.Store, deps.DeletionState, deps.TreeManager)
|
||||||
|
} else {
|
||||||
|
deleter = deps.del
|
||||||
|
}
|
||||||
|
if deps.delManager == nil {
|
||||||
|
deletionManager = newDeletionManager(
|
||||||
|
spaceId,
|
||||||
|
deps.Store.SpaceSettingsId(),
|
||||||
|
deps.Configuration.IsResponsible(spaceId),
|
||||||
|
deps.TreeManager,
|
||||||
|
deps.DeletionState,
|
||||||
|
deps.Provider,
|
||||||
|
deps.OnSpaceDelete)
|
||||||
|
} else {
|
||||||
|
deletionManager = deps.delManager
|
||||||
|
}
|
||||||
|
if deps.builder == nil {
|
||||||
|
builder = settingsstate.NewStateBuilder()
|
||||||
|
} else {
|
||||||
|
builder = deps.builder
|
||||||
|
}
|
||||||
|
if deps.changeFactory == nil {
|
||||||
|
changeFactory = settingsstate.NewChangeFactory()
|
||||||
|
} else {
|
||||||
|
changeFactory = deps.changeFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := newDeleteLoop(func() {
|
||||||
|
deleter.Delete()
|
||||||
|
})
|
||||||
|
deps.DeletionState.AddObserver(func(ids []string) {
|
||||||
|
loop.notify()
|
||||||
|
})
|
||||||
|
|
||||||
|
s := &settingsObject{
|
||||||
|
loop: loop,
|
||||||
|
spaceId: spaceId,
|
||||||
|
account: deps.Account,
|
||||||
|
deletionState: deps.DeletionState,
|
||||||
|
treeManager: deps.TreeManager,
|
||||||
|
store: deps.Store,
|
||||||
|
buildFunc: deps.BuildFunc,
|
||||||
|
builder: builder,
|
||||||
|
deletionManager: deletionManager,
|
||||||
|
changeFactory: changeFactory,
|
||||||
|
}
|
||||||
|
obj = s
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settings) Close(ctx context.Context) (err error) {
|
func (s *settingsObject) updateIds(tr objecttree.ObjectTree) {
|
||||||
return s.settingsObject.Close()
|
var err error
|
||||||
}
|
s.state, err = s.builder.Build(tr, s.state)
|
||||||
|
|
||||||
func (s *settings) DeleteTree(ctx context.Context, id string) (err error) {
|
|
||||||
return s.settingsObject.DeleteObject(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settings) SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
|
||||||
return s.settingsObject.SpaceDeleteRawChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settings) DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error) {
|
|
||||||
return s.settingsObject.DeleteSpace(ctx, deleteChange)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settings) onSpaceDelete() {
|
|
||||||
err := s.storage.SetSpaceDeleted()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("failed to set space deleted")
|
log.Error("failed to build state", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug("updating object state", zap.String("deleted by", s.state.DeleterId))
|
||||||
|
if err = s.deletionManager.UpdateState(context.Background(), s.state); err != nil {
|
||||||
|
log.Error("failed to update state", zap.Error(err))
|
||||||
}
|
}
|
||||||
s.spaceIsDeleted.Swap(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settings) SettingsObject() SettingsObject {
|
// Update is called as part of UpdateListener interface
|
||||||
return s.settingsObject
|
func (s *settingsObject) Update(tr objecttree.ObjectTree) {
|
||||||
|
s.updateIds(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild is called as part of UpdateListener interface (including when the object is built for the first time, e.g. on Init call)
|
||||||
|
func (s *settingsObject) Rebuild(tr objecttree.ObjectTree) {
|
||||||
|
// at initial build "s" may not contain the object tree, so it is safer to provide it from the function parameter
|
||||||
|
s.state = nil
|
||||||
|
s.updateIds(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) Init(ctx context.Context) (err error) {
|
||||||
|
settingsId := s.store.SpaceSettingsId()
|
||||||
|
log.Debug("space settings id", zap.String("id", settingsId))
|
||||||
|
s.SyncTree, err = s.buildFunc(ctx, settingsId, s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: remove this check when everybody updates
|
||||||
|
if err = s.checkHistoryState(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.loop.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) checkHistoryState(ctx context.Context) (err error) {
|
||||||
|
historyTree, err := buildHistoryTree(s.SyncTree)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fullState, err := s.builder.Build(historyTree, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(fullState.DeletedIds) != len(s.state.DeletedIds) {
|
||||||
|
log.WarnCtx(ctx, "state does not have all deleted ids",
|
||||||
|
zap.Int("fullstate ids", len(fullState.DeletedIds)),
|
||||||
|
zap.Int("state ids", len(fullState.DeletedIds)))
|
||||||
|
s.state = fullState
|
||||||
|
err = s.deletionManager.UpdateState(context.Background(), s.state)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) Close() error {
|
||||||
|
s.loop.Close()
|
||||||
|
return s.SyncTree.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
defer func() {
|
||||||
|
log.Debug("finished adding delete change", zap.Error(err))
|
||||||
|
}()
|
||||||
|
err = s.verifyDeleteSpace(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := s.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{raw.Id},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{raw},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !slices.Contains(res.Heads, raw.Id) {
|
||||||
|
err = ErrCantDeleteSpace
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
|
accountData := s.account.Account()
|
||||||
|
data, err := s.changeFactory.CreateSpaceDeleteChange(accountData.PeerId, s.state, false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.PrepareChange(objecttree.SignableChangeContent{
|
||||||
|
Data: data,
|
||||||
|
Key: accountData.SignKey,
|
||||||
|
IsSnapshot: false,
|
||||||
|
IsEncrypted: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) DeleteObject(id string) (err error) {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
if s.Id() == id {
|
||||||
|
err = ErrDeleteSelf
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.state.Exists(id) {
|
||||||
|
err = ErrAlreadyDeleted
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = s.store.TreeStorage(id)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrObjDoesNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isSnapshot := DoSnapshot(s.Len())
|
||||||
|
res, err := s.changeFactory.CreateObjectDeleteChange(id, s.state, isSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.addContent(res, isSnapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) verifyDeleteSpace(raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
||||||
|
data, err := s.UnpackChange(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return verifyDeleteContent(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) addContent(data []byte, isSnapshot bool) (err error) {
|
||||||
|
accountData := s.account.Account()
|
||||||
|
res, err := s.AddContent(context.Background(), objecttree.SignableChangeContent{
|
||||||
|
Data: data,
|
||||||
|
Key: accountData.SignKey,
|
||||||
|
IsSnapshot: isSnapshot,
|
||||||
|
IsEncrypted: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.Mode == objecttree.Rebuild {
|
||||||
|
s.Rebuild(s)
|
||||||
|
} else {
|
||||||
|
s.Update(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyDeleteChange(raw *treechangeproto.RawTreeChangeWithId, identity crypto.PubKey, peerId string) (err error) {
|
||||||
|
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), nil)
|
||||||
|
res, err := changeBuilder.Unmarshall(raw, true)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !res.Identity.Equals(identity) {
|
||||||
|
return fmt.Errorf("incorrect identity")
|
||||||
|
}
|
||||||
|
return verifyDeleteContent(res.Data, peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyDeleteContent(data []byte, peerId string) (err error) {
|
||||||
|
content := &spacesyncproto.SettingsData{}
|
||||||
|
err = proto.Unmarshal(data, content)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(content.GetContent()) != 1 ||
|
||||||
|
content.GetContent()[0].GetSpaceDelete() == nil ||
|
||||||
|
(peerId == "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() == "") ||
|
||||||
|
(peerId != "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() != peerId) {
|
||||||
|
return fmt.Errorf("incorrect delete change payload")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/accountservice/mock_accountservice"
|
"github.com/anyproto/any-sync/accountservice/mock_accountservice"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||||
@ -16,8 +15,8 @@ import (
|
|||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -55,7 +54,7 @@ type settingsFixture struct {
|
|||||||
deleter *mock_settings.MockDeleter
|
deleter *mock_settings.MockDeleter
|
||||||
syncTree *mock_synctree.MockSyncTree
|
syncTree *mock_synctree.MockSyncTree
|
||||||
historyTree *mock_objecttree.MockObjectTree
|
historyTree *mock_objecttree.MockObjectTree
|
||||||
delState *mock_deletionstate.MockObjectDeletionState
|
delState *mock_settingsstate.MockObjectDeletionState
|
||||||
account *mock_accountservice.MockService
|
account *mock_accountservice.MockService
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +66,7 @@ func newSettingsFixture(t *testing.T) *settingsFixture {
|
|||||||
acc := mock_accountservice.NewMockService(ctrl)
|
acc := mock_accountservice.NewMockService(ctrl)
|
||||||
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
|
||||||
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
delManager := mock_settings.NewMockDeletionManager(ctrl)
|
delManager := mock_settings.NewMockDeletionManager(ctrl)
|
||||||
stateBuilder := mock_settingsstate.NewMockStateBuilder(ctrl)
|
stateBuilder := mock_settingsstate.NewMockStateBuilder(ctrl)
|
||||||
changeFactory := mock_settingsstate.NewMockChangeFactory(ctrl)
|
changeFactory := mock_settingsstate.NewMockChangeFactory(ctrl)
|
||||||
@ -1,329 +0,0 @@
|
|||||||
//go:generate mockgen -destination mock_settings/mock_settings.go github.com/anyproto/any-sync/commonspace/settings DeletionManager,Deleter,SpaceIdsProvider
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"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/tree/synctree/updatelistener"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logger.NewNamed("common.commonspace.settings")
|
|
||||||
|
|
||||||
type SettingsObject interface {
|
|
||||||
synctree.SyncTree
|
|
||||||
Init(ctx context.Context) (err error)
|
|
||||||
DeleteObject(id string) (err error)
|
|
||||||
DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error)
|
|
||||||
SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrDeleteSelf = errors.New("cannot delete self")
|
|
||||||
ErrAlreadyDeleted = errors.New("the object is already deleted")
|
|
||||||
ErrObjDoesNotExist = errors.New("the object does not exist")
|
|
||||||
ErrCantDeleteSpace = errors.New("not able to delete space")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DoSnapshot = objecttree.DoSnapshot
|
|
||||||
buildHistoryTree = func(objTree objecttree.ObjectTree) (objecttree.ReadableObjectTree, error) {
|
|
||||||
return objecttree.BuildHistoryTree(objecttree.HistoryTreeParams{
|
|
||||||
TreeStorage: objTree.Storage(),
|
|
||||||
AclList: objTree.AclList(),
|
|
||||||
BuildFullTree: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildTreeFunc func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error)
|
|
||||||
|
|
||||||
type Deps struct {
|
|
||||||
BuildFunc BuildTreeFunc
|
|
||||||
Account accountservice.Service
|
|
||||||
TreeManager treemanager.TreeManager
|
|
||||||
Store spacestorage.SpaceStorage
|
|
||||||
Configuration nodeconf.NodeConf
|
|
||||||
DeletionState deletionstate.ObjectDeletionState
|
|
||||||
Provider SpaceIdsProvider
|
|
||||||
OnSpaceDelete func()
|
|
||||||
// testing dependencies
|
|
||||||
builder settingsstate.StateBuilder
|
|
||||||
del Deleter
|
|
||||||
delManager DeletionManager
|
|
||||||
changeFactory settingsstate.ChangeFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
type settingsObject struct {
|
|
||||||
synctree.SyncTree
|
|
||||||
account accountservice.Service
|
|
||||||
spaceId string
|
|
||||||
treeManager treemanager.TreeManager
|
|
||||||
store spacestorage.SpaceStorage
|
|
||||||
builder settingsstate.StateBuilder
|
|
||||||
buildFunc BuildTreeFunc
|
|
||||||
loop *deleteLoop
|
|
||||||
|
|
||||||
state *settingsstate.State
|
|
||||||
deletionState deletionstate.ObjectDeletionState
|
|
||||||
deletionManager DeletionManager
|
|
||||||
changeFactory settingsstate.ChangeFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSettingsObject(deps Deps, spaceId string) (obj SettingsObject) {
|
|
||||||
var (
|
|
||||||
deleter Deleter
|
|
||||||
deletionManager DeletionManager
|
|
||||||
builder settingsstate.StateBuilder
|
|
||||||
changeFactory settingsstate.ChangeFactory
|
|
||||||
)
|
|
||||||
if deps.del == nil {
|
|
||||||
deleter = newDeleter(deps.Store, deps.DeletionState, deps.TreeManager)
|
|
||||||
} else {
|
|
||||||
deleter = deps.del
|
|
||||||
}
|
|
||||||
if deps.delManager == nil {
|
|
||||||
deletionManager = newDeletionManager(
|
|
||||||
spaceId,
|
|
||||||
deps.Store.SpaceSettingsId(),
|
|
||||||
deps.Configuration.IsResponsible(spaceId),
|
|
||||||
deps.TreeManager,
|
|
||||||
deps.DeletionState,
|
|
||||||
deps.Provider,
|
|
||||||
deps.OnSpaceDelete)
|
|
||||||
} else {
|
|
||||||
deletionManager = deps.delManager
|
|
||||||
}
|
|
||||||
if deps.builder == nil {
|
|
||||||
builder = settingsstate.NewStateBuilder()
|
|
||||||
} else {
|
|
||||||
builder = deps.builder
|
|
||||||
}
|
|
||||||
if deps.changeFactory == nil {
|
|
||||||
changeFactory = settingsstate.NewChangeFactory()
|
|
||||||
} else {
|
|
||||||
changeFactory = deps.changeFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
loop := newDeleteLoop(func() {
|
|
||||||
deleter.Delete()
|
|
||||||
})
|
|
||||||
deps.DeletionState.AddObserver(func(ids []string) {
|
|
||||||
loop.notify()
|
|
||||||
})
|
|
||||||
|
|
||||||
s := &settingsObject{
|
|
||||||
loop: loop,
|
|
||||||
spaceId: spaceId,
|
|
||||||
account: deps.Account,
|
|
||||||
deletionState: deps.DeletionState,
|
|
||||||
treeManager: deps.TreeManager,
|
|
||||||
store: deps.Store,
|
|
||||||
buildFunc: deps.BuildFunc,
|
|
||||||
builder: builder,
|
|
||||||
deletionManager: deletionManager,
|
|
||||||
changeFactory: changeFactory,
|
|
||||||
}
|
|
||||||
obj = s
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) updateIds(tr objecttree.ObjectTree) {
|
|
||||||
var err error
|
|
||||||
s.state, err = s.builder.Build(tr, s.state)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to build state", zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("updating object state", zap.String("deleted by", s.state.DeleterId))
|
|
||||||
if err = s.deletionManager.UpdateState(context.Background(), s.state); err != nil {
|
|
||||||
log.Error("failed to update state", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is called as part of UpdateListener interface
|
|
||||||
func (s *settingsObject) Update(tr objecttree.ObjectTree) {
|
|
||||||
s.updateIds(tr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild is called as part of UpdateListener interface (including when the object is built for the first time, e.g. on Init call)
|
|
||||||
func (s *settingsObject) Rebuild(tr objecttree.ObjectTree) {
|
|
||||||
// at initial build "s" may not contain the object tree, so it is safer to provide it from the function parameter
|
|
||||||
s.state = nil
|
|
||||||
s.updateIds(tr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) Init(ctx context.Context) (err error) {
|
|
||||||
settingsId := s.store.SpaceSettingsId()
|
|
||||||
log.Debug("space settings id", zap.String("id", settingsId))
|
|
||||||
s.SyncTree, err = s.buildFunc(ctx, settingsId, s)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: remove this check when everybody updates
|
|
||||||
if err = s.checkHistoryState(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.loop.Run()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) checkHistoryState(ctx context.Context) (err error) {
|
|
||||||
historyTree, err := buildHistoryTree(s.SyncTree)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullState, err := s.builder.Build(historyTree, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(fullState.DeletedIds) != len(s.state.DeletedIds) {
|
|
||||||
log.WarnCtx(ctx, "state does not have all deleted ids",
|
|
||||||
zap.Int("fullstate ids", len(fullState.DeletedIds)),
|
|
||||||
zap.Int("state ids", len(fullState.DeletedIds)))
|
|
||||||
s.state = fullState
|
|
||||||
err = s.deletionManager.UpdateState(context.Background(), s.state)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) Close() error {
|
|
||||||
s.loop.Close()
|
|
||||||
return s.SyncTree.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
defer func() {
|
|
||||||
log.Debug("finished adding delete change", zap.Error(err))
|
|
||||||
}()
|
|
||||||
err = s.verifyDeleteSpace(raw)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res, err := s.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{raw.Id},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{raw},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !slices.Contains(res.Heads, raw.Id) {
|
|
||||||
err = ErrCantDeleteSpace
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
|
||||||
accountData := s.account.Account()
|
|
||||||
data, err := s.changeFactory.CreateSpaceDeleteChange(accountData.PeerId, s.state, false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.PrepareChange(objecttree.SignableChangeContent{
|
|
||||||
Data: data,
|
|
||||||
Key: accountData.SignKey,
|
|
||||||
IsSnapshot: false,
|
|
||||||
IsEncrypted: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) DeleteObject(id string) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
if s.Id() == id {
|
|
||||||
err = ErrDeleteSelf
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.state.Exists(id) {
|
|
||||||
err = ErrAlreadyDeleted
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = s.store.TreeStorage(id)
|
|
||||||
if err != nil {
|
|
||||||
err = ErrObjDoesNotExist
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isSnapshot := DoSnapshot(s.Len())
|
|
||||||
res, err := s.changeFactory.CreateObjectDeleteChange(id, s.state, isSnapshot)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.addContent(res, isSnapshot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) verifyDeleteSpace(raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
|
||||||
data, err := s.UnpackChange(raw)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return verifyDeleteContent(data, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) addContent(data []byte, isSnapshot bool) (err error) {
|
|
||||||
accountData := s.account.Account()
|
|
||||||
res, err := s.AddContent(context.Background(), objecttree.SignableChangeContent{
|
|
||||||
Data: data,
|
|
||||||
Key: accountData.SignKey,
|
|
||||||
IsSnapshot: isSnapshot,
|
|
||||||
IsEncrypted: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if res.Mode == objecttree.Rebuild {
|
|
||||||
s.Rebuild(s)
|
|
||||||
} else {
|
|
||||||
s.Update(s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerifyDeleteChange(raw *treechangeproto.RawTreeChangeWithId, identity crypto.PubKey, peerId string) (err error) {
|
|
||||||
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), nil)
|
|
||||||
res, err := changeBuilder.Unmarshall(raw, true)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !res.Identity.Equals(identity) {
|
|
||||||
return fmt.Errorf("incorrect identity")
|
|
||||||
}
|
|
||||||
return verifyDeleteContent(res.Data, peerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyDeleteContent(data []byte, peerId string) (err error) {
|
|
||||||
content := &spacesyncproto.SettingsData{}
|
|
||||||
err = proto.Unmarshal(data, content)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(content.GetContent()) != 1 ||
|
|
||||||
content.GetContent()[0].GetSpaceDelete() == nil ||
|
|
||||||
(peerId == "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() == "") ||
|
|
||||||
(peerId != "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() != peerId) {
|
|
||||||
return fmt.Errorf("incorrect delete change payload")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -1,22 +1,16 @@
|
|||||||
//go:generate mockgen -destination mock_deletionstate/mock_deletionstate.go github.com/anyproto/any-sync/commonspace/deletionstate ObjectDeletionState
|
//go:generate mockgen -destination mock_settingsstate/mock_settingsstate.go github.com/anyproto/any-sync/commonspace/settings/settingsstate ObjectDeletionState,StateBuilder,ChangeFactory
|
||||||
package deletionstate
|
package settingsstate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
const CName = "common.commonspace.deletionstate"
|
|
||||||
|
|
||||||
type StateUpdateObserver func(ids []string)
|
type StateUpdateObserver func(ids []string)
|
||||||
|
|
||||||
type ObjectDeletionState interface {
|
type ObjectDeletionState interface {
|
||||||
app.Component
|
|
||||||
AddObserver(observer StateUpdateObserver)
|
AddObserver(observer StateUpdateObserver)
|
||||||
Add(ids map[string]struct{})
|
Add(ids map[string]struct{})
|
||||||
GetQueued() (ids []string)
|
GetQueued() (ids []string)
|
||||||
@ -34,20 +28,12 @@ type objectDeletionState struct {
|
|||||||
storage spacestorage.SpaceStorage
|
storage spacestorage.SpaceStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *objectDeletionState) Init(a *app.App) (err error) {
|
func NewObjectDeletionState(log logger.CtxLogger, storage spacestorage.SpaceStorage) ObjectDeletionState {
|
||||||
st.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *objectDeletionState) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() ObjectDeletionState {
|
|
||||||
return &objectDeletionState{
|
return &objectDeletionState{
|
||||||
log: log,
|
log: log,
|
||||||
queued: map[string]struct{}{},
|
queued: map[string]struct{}{},
|
||||||
deleted: map[string]struct{}{},
|
deleted: map[string]struct{}{},
|
||||||
|
storage: storage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
package deletionstate
|
package settingsstate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -18,8 +19,7 @@ type fixture struct {
|
|||||||
func newFixture(t *testing.T) *fixture {
|
func newFixture(t *testing.T) *fixture {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
spaceStorage := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
spaceStorage := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
delState := New().(*objectDeletionState)
|
delState := NewObjectDeletionState(logger.NewNamed("test"), spaceStorage).(*objectDeletionState)
|
||||||
delState.storage = spaceStorage
|
|
||||||
return &fixture{
|
return &fixture{
|
||||||
ctrl: ctrl,
|
ctrl: ctrl,
|
||||||
delState: delState,
|
delState: delState,
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/settings/settingsstate (interfaces: StateBuilder,ChangeFactory)
|
// Source: github.com/anyproto/any-sync/commonspace/settings/settingsstate (interfaces: ObjectDeletionState,StateBuilder,ChangeFactory)
|
||||||
|
|
||||||
// Package mock_settingsstate is a generated GoMock package.
|
// Package mock_settingsstate is a generated GoMock package.
|
||||||
package mock_settingsstate
|
package mock_settingsstate
|
||||||
@ -9,9 +9,112 @@ import (
|
|||||||
|
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
|
||||||
|
type MockObjectDeletionState struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockObjectDeletionStateMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockObjectDeletionStateMockRecorder is the mock recorder for MockObjectDeletionState.
|
||||||
|
type MockObjectDeletionStateMockRecorder struct {
|
||||||
|
mock *MockObjectDeletionState
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockObjectDeletionState creates a new mock instance.
|
||||||
|
func NewMockObjectDeletionState(ctrl *gomock.Controller) *MockObjectDeletionState {
|
||||||
|
mock := &MockObjectDeletionState{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockObjectDeletionStateMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockObjectDeletionState) EXPECT() *MockObjectDeletionStateMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add mocks base method.
|
||||||
|
func (m *MockObjectDeletionState) Add(arg0 map[string]struct{}) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Add", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add indicates an expected call of Add.
|
||||||
|
func (mr *MockObjectDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockObjectDeletionState)(nil).Add), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddObserver mocks base method.
|
||||||
|
func (m *MockObjectDeletionState) AddObserver(arg0 settingsstate.StateUpdateObserver) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddObserver", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddObserver indicates an expected call of AddObserver.
|
||||||
|
func (mr *MockObjectDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockObjectDeletionState)(nil).AddObserver), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method.
|
||||||
|
func (m *MockObjectDeletionState) Delete(arg0 string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete.
|
||||||
|
func (mr *MockObjectDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectDeletionState)(nil).Delete), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists mocks base method.
|
||||||
|
func (m *MockObjectDeletionState) Exists(arg0 string) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Exists", arg0)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists indicates an expected call of Exists.
|
||||||
|
func (mr *MockObjectDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockObjectDeletionState)(nil).Exists), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter mocks base method.
|
||||||
|
func (m *MockObjectDeletionState) Filter(arg0 []string) []string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Filter", arg0)
|
||||||
|
ret0, _ := ret[0].([]string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter indicates an expected call of Filter.
|
||||||
|
func (mr *MockObjectDeletionStateMockRecorder) Filter(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockObjectDeletionState)(nil).Filter), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueued mocks base method.
|
||||||
|
func (m *MockObjectDeletionState) GetQueued() []string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetQueued")
|
||||||
|
ret0, _ := ret[0].([]string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueued indicates an expected call of GetQueued.
|
||||||
|
func (mr *MockObjectDeletionStateMockRecorder) GetQueued() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockObjectDeletionState)(nil).GetQueued))
|
||||||
|
}
|
||||||
|
|
||||||
// MockStateBuilder is a mock of StateBuilder interface.
|
// MockStateBuilder is a mock of StateBuilder interface.
|
||||||
type MockStateBuilder struct {
|
type MockStateBuilder struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
//go:generate mockgen -destination mock_settingsstate/mock_settingsstate.go github.com/anyproto/any-sync/commonspace/settings/settingsstate StateBuilder,ChangeFactory
|
|
||||||
package settingsstate
|
package settingsstate
|
||||||
|
|
||||||
import "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
import "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import (
|
|||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,26 +2,44 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/app"
|
"errors"
|
||||||
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||||
|
"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/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings"
|
"github.com/anyproto/any-sync/commonspace/settings"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
"github.com/anyproto/any-sync/metric"
|
||||||
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
"github.com/anyproto/any-sync/util/multiqueue"
|
||||||
|
"github.com/anyproto/any-sync/util/slice"
|
||||||
|
"github.com/cheggaaa/mb/v3"
|
||||||
|
"github.com/zeebo/errs"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSpaceClosed = errors.New("space is closed")
|
||||||
|
)
|
||||||
|
|
||||||
type SpaceCreatePayload struct {
|
type SpaceCreatePayload struct {
|
||||||
// SigningKey is the signing key of the owner
|
// SigningKey is the signing key of the owner
|
||||||
SigningKey crypto.PrivKey
|
SigningKey crypto.PrivKey
|
||||||
@ -37,6 +55,25 @@ type SpaceCreatePayload struct {
|
|||||||
MasterKey crypto.PrivKey
|
MasterKey crypto.PrivKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HandleMessage struct {
|
||||||
|
Id uint64
|
||||||
|
ReceiveTime time.Time
|
||||||
|
StartHandlingTime time.Time
|
||||||
|
Deadline time.Time
|
||||||
|
SenderId string
|
||||||
|
Message *spacesyncproto.ObjectSyncMessage
|
||||||
|
PeerCtx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m HandleMessage) LogFields(fields ...zap.Field) []zap.Field {
|
||||||
|
return append(fields,
|
||||||
|
metric.SpaceId(m.Message.SpaceId),
|
||||||
|
metric.ObjectId(m.Message.ObjectId),
|
||||||
|
metric.QueueDur(m.StartHandlingTime.Sub(m.ReceiveTime)),
|
||||||
|
metric.TotalDur(time.Since(m.ReceiveTime)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type SpaceDerivePayload struct {
|
type SpaceDerivePayload struct {
|
||||||
SigningKey crypto.PrivKey
|
SigningKey crypto.PrivKey
|
||||||
MasterKey crypto.PrivKey
|
MasterKey crypto.PrivKey
|
||||||
@ -59,42 +96,58 @@ func NewSpaceId(id string, repKey uint64) string {
|
|||||||
type Space interface {
|
type Space interface {
|
||||||
Id() string
|
Id() string
|
||||||
Init(ctx context.Context) error
|
Init(ctx context.Context) error
|
||||||
Acl() list.AclList
|
|
||||||
|
|
||||||
StoredIds() []string
|
StoredIds() []string
|
||||||
DebugAllHeads() []headsync.TreeHeads
|
DebugAllHeads() []headsync.TreeHeads
|
||||||
Description() (desc SpaceDescription, err error)
|
Description() (SpaceDescription, error)
|
||||||
|
|
||||||
TreeBuilder() objecttreebuilder.TreeBuilder
|
|
||||||
SyncStatus() syncstatus.StatusUpdater
|
|
||||||
Storage() spacestorage.SpaceStorage
|
|
||||||
|
|
||||||
|
CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error)
|
||||||
|
PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error)
|
||||||
|
BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error)
|
||||||
DeleteTree(ctx context.Context, id string) (err error)
|
DeleteTree(ctx context.Context, id string) (err error)
|
||||||
|
BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error)
|
||||||
|
|
||||||
SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error)
|
SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||||
DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error)
|
DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error)
|
||||||
|
|
||||||
HandleMessage(ctx context.Context, msg objectsync.HandleMessage) (err error)
|
HeadSync() headsync.HeadSync
|
||||||
HandleSyncRequest(ctx context.Context, req *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error)
|
ObjectSync() objectsync.ObjectSync
|
||||||
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
SyncStatus() syncstatus.StatusUpdater
|
||||||
|
Storage() spacestorage.SpaceStorage
|
||||||
|
|
||||||
|
HandleMessage(ctx context.Context, msg HandleMessage) (err error)
|
||||||
|
|
||||||
TryClose(objectTTL time.Duration) (close bool, err error)
|
TryClose(objectTTL time.Duration) (close bool, err error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type space struct {
|
type space struct {
|
||||||
|
id string
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
header *spacesyncproto.RawSpaceHeaderWithId
|
header *spacesyncproto.RawSpaceHeaderWithId
|
||||||
|
|
||||||
state *spacestate.SpaceState
|
objectSync objectsync.ObjectSync
|
||||||
app *app.App
|
headSync headsync.HeadSync
|
||||||
|
syncStatus syncstatus.StatusUpdater
|
||||||
|
storage spacestorage.SpaceStorage
|
||||||
|
treeManager *commonGetter
|
||||||
|
account accountservice.Service
|
||||||
|
aclList *syncacl.SyncAcl
|
||||||
|
configuration nodeconf.NodeConf
|
||||||
|
settingsObject settings.SettingsObject
|
||||||
|
peerManager peermanager.PeerManager
|
||||||
|
treeBuilder objecttree.BuildObjectTreeFunc
|
||||||
|
metric metric.Metric
|
||||||
|
|
||||||
treeBuilder objecttreebuilder.TreeBuilderComponent
|
handleQueue multiqueue.MultiQueue[HandleMessage]
|
||||||
headSync headsync.HeadSync
|
|
||||||
objectSync objectsync.ObjectSync
|
isClosed *atomic.Bool
|
||||||
syncStatus syncstatus.StatusService
|
isDeleted *atomic.Bool
|
||||||
settings settings.Settings
|
treesUsed *atomic.Int32
|
||||||
storage spacestorage.SpaceStorage
|
}
|
||||||
aclList list.AclList
|
|
||||||
|
func (s *space) Id() string {
|
||||||
|
return s.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *space) Description() (desc SpaceDescription, err error) {
|
func (s *space) Description() (desc SpaceDescription, err error) {
|
||||||
@ -118,64 +171,72 @@ func (s *space) Description() (desc SpaceDescription, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *space) StoredIds() []string {
|
|
||||||
return s.headSync.ExternalIds()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) DebugAllHeads() []headsync.TreeHeads {
|
|
||||||
return s.headSync.DebugAllHeads()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) DeleteTree(ctx context.Context, id string) (err error) {
|
|
||||||
return s.settings.DeleteTree(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
|
||||||
return s.settings.SpaceDeleteRawChange(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error) {
|
|
||||||
return s.settings.DeleteSpace(ctx, deleteChange)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) HandleMessage(ctx context.Context, msg objectsync.HandleMessage) (err error) {
|
|
||||||
return s.objectSync.HandleMessage(ctx, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) HandleSyncRequest(ctx context.Context, req *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
return s.objectSync.HandleRequest(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
|
||||||
return s.headSync.HandleRangeRequest(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) TreeBuilder() objecttreebuilder.TreeBuilder {
|
|
||||||
return s.treeBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) Acl() list.AclList {
|
|
||||||
return s.aclList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) Id() string {
|
|
||||||
return s.state.SpaceId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *space) Init(ctx context.Context) (err error) {
|
func (s *space) Init(ctx context.Context) (err error) {
|
||||||
err = s.app.Start(ctx)
|
log.With(zap.String("spaceId", s.id)).Debug("initializing space")
|
||||||
|
s.storage = newCommonStorage(s.storage)
|
||||||
|
|
||||||
|
header, err := s.storage.SpaceHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.treeBuilder = s.app.MustComponent(objecttreebuilder.CName).(objecttreebuilder.TreeBuilderComponent)
|
s.header = header
|
||||||
s.headSync = s.app.MustComponent(headsync.CName).(headsync.HeadSync)
|
initialIds, err := s.storage.StoredIds()
|
||||||
s.syncStatus = s.app.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
if err != nil {
|
||||||
s.settings = s.app.MustComponent(settings.CName).(settings.Settings)
|
return
|
||||||
s.objectSync = s.app.MustComponent(objectsync.CName).(objectsync.ObjectSync)
|
}
|
||||||
s.storage = s.app.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
aclStorage, err := s.storage.AclStorage()
|
||||||
s.aclList = s.app.MustComponent(syncacl.CName).(list.AclList)
|
if err != nil {
|
||||||
s.header, err = s.storage.SpaceHeader()
|
return
|
||||||
return
|
}
|
||||||
|
aclList, err := list.BuildAclListWithIdentity(s.account.Account(), aclStorage)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.aclList = syncacl.NewSyncAcl(aclList, s.objectSync.SyncClient().MessagePool())
|
||||||
|
s.treeManager.AddObject(s.aclList)
|
||||||
|
|
||||||
|
deletionState := settingsstate.NewObjectDeletionState(log, s.storage)
|
||||||
|
deps := settings.Deps{
|
||||||
|
BuildFunc: func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error) {
|
||||||
|
res, err := s.BuildTree(ctx, id, BuildTreeOpts{
|
||||||
|
Listener: listener,
|
||||||
|
WaitTreeRemoteSync: false,
|
||||||
|
// space settings document should not have empty data
|
||||||
|
treeBuilder: objecttree.BuildObjectTree,
|
||||||
|
})
|
||||||
|
log.Debug("building settings tree", zap.String("id", id), zap.String("spaceId", s.id))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t = res.(synctree.SyncTree)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
Account: s.account,
|
||||||
|
TreeManager: s.treeManager,
|
||||||
|
Store: s.storage,
|
||||||
|
DeletionState: deletionState,
|
||||||
|
Provider: s.headSync,
|
||||||
|
Configuration: s.configuration,
|
||||||
|
OnSpaceDelete: s.onSpaceDelete,
|
||||||
|
}
|
||||||
|
s.settingsObject = settings.NewSettingsObject(deps, s.id)
|
||||||
|
s.headSync.Init(initialIds, deletionState)
|
||||||
|
err = s.settingsObject.Init(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.treeManager.AddObject(s.settingsObject)
|
||||||
|
s.syncStatus.Run()
|
||||||
|
s.handleQueue = multiqueue.New[HandleMessage](s.handleMessage, 100)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) ObjectSync() objectsync.ObjectSync {
|
||||||
|
return s.objectSync
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) HeadSync() headsync.HeadSync {
|
||||||
|
return s.headSync
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *space) SyncStatus() syncstatus.StatusUpdater {
|
func (s *space) SyncStatus() syncstatus.StatusUpdater {
|
||||||
@ -186,25 +247,246 @@ func (s *space) Storage() spacestorage.SpaceStorage {
|
|||||||
return s.storage
|
return s.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *space) Close() error {
|
func (s *space) StoredIds() []string {
|
||||||
if s.state.SpaceIsClosed.Swap(true) {
|
return slice.DiscardFromSlice(s.headSync.AllIds(), func(id string) bool {
|
||||||
log.Warn("call space.Close on closed space", zap.String("id", s.state.SpaceId))
|
return id == s.settingsObject.Id()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) DebugAllHeads() []headsync.TreeHeads {
|
||||||
|
return s.headSync.DebugAllHeads()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) {
|
||||||
|
if s.isClosed.Load() {
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root, err := objecttree.CreateObjectTreeRoot(payload, s.aclList)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = treestorage.TreeStorageCreatePayload{
|
||||||
|
RootRawChange: root,
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{root},
|
||||||
|
Heads: []string{root.Id},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error) {
|
||||||
|
if s.isClosed.Load() {
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deps := synctree.BuildDeps{
|
||||||
|
SpaceId: s.id,
|
||||||
|
SyncClient: s.objectSync.SyncClient(),
|
||||||
|
Configuration: s.configuration,
|
||||||
|
HeadNotifiable: s.headSync,
|
||||||
|
Listener: listener,
|
||||||
|
AclList: s.aclList,
|
||||||
|
SpaceStorage: s.storage,
|
||||||
|
OnClose: s.onObjectClose,
|
||||||
|
SyncStatus: s.syncStatus,
|
||||||
|
PeerGetter: s.peerManager,
|
||||||
|
BuildObjectTree: s.treeBuilder,
|
||||||
|
}
|
||||||
|
t, err = synctree.PutSyncTree(ctx, payload, deps)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.treesUsed.Add(1)
|
||||||
|
log.Debug("incrementing counter", zap.String("id", payload.RootRawChange.Id), zap.Int32("trees", s.treesUsed.Load()), zap.String("spaceId", s.id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuildTreeOpts struct {
|
||||||
|
Listener updatelistener.UpdateListener
|
||||||
|
WaitTreeRemoteSync bool
|
||||||
|
treeBuilder objecttree.BuildObjectTreeFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryTreeOpts struct {
|
||||||
|
BeforeId string
|
||||||
|
Include bool
|
||||||
|
BuildFullTree bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error) {
|
||||||
|
if s.isClosed.Load() {
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
treeBuilder := opts.treeBuilder
|
||||||
|
if treeBuilder == nil {
|
||||||
|
treeBuilder = s.treeBuilder
|
||||||
|
}
|
||||||
|
deps := synctree.BuildDeps{
|
||||||
|
SpaceId: s.id,
|
||||||
|
SyncClient: s.objectSync.SyncClient(),
|
||||||
|
Configuration: s.configuration,
|
||||||
|
HeadNotifiable: s.headSync,
|
||||||
|
Listener: opts.Listener,
|
||||||
|
AclList: s.aclList,
|
||||||
|
SpaceStorage: s.storage,
|
||||||
|
OnClose: s.onObjectClose,
|
||||||
|
SyncStatus: s.syncStatus,
|
||||||
|
WaitTreeRemoteSync: opts.WaitTreeRemoteSync,
|
||||||
|
PeerGetter: s.peerManager,
|
||||||
|
BuildObjectTree: treeBuilder,
|
||||||
|
}
|
||||||
|
s.treesUsed.Add(1)
|
||||||
|
log.Debug("incrementing counter", zap.String("id", id), zap.Int32("trees", s.treesUsed.Load()), zap.String("spaceId", s.id))
|
||||||
|
if t, err = synctree.BuildSyncTreeOrGetRemote(ctx, id, deps); err != nil {
|
||||||
|
s.treesUsed.Add(-1)
|
||||||
|
log.Debug("decrementing counter, load failed", zap.String("id", id), zap.Int32("trees", s.treesUsed.Load()), zap.String("spaceId", s.id), zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error) {
|
||||||
|
if s.isClosed.Load() {
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := objecttree.HistoryTreeParams{
|
||||||
|
AclList: s.aclList,
|
||||||
|
BeforeId: opts.BeforeId,
|
||||||
|
IncludeBeforeId: opts.Include,
|
||||||
|
BuildFullTree: opts.BuildFullTree,
|
||||||
|
}
|
||||||
|
params.TreeStorage, err = s.storage.TreeStorage(id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return objecttree.BuildHistoryTree(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) DeleteTree(ctx context.Context, id string) (err error) {
|
||||||
|
return s.settingsObject.DeleteObject(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
|
return s.settingsObject.SpaceDeleteRawChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error) {
|
||||||
|
return s.settingsObject.DeleteSpace(ctx, deleteChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) HandleMessage(ctx context.Context, hm HandleMessage) (err error) {
|
||||||
|
threadId := hm.Message.ObjectId
|
||||||
|
hm.ReceiveTime = time.Now()
|
||||||
|
if hm.Message.ReplyId != "" {
|
||||||
|
threadId += hm.Message.ReplyId
|
||||||
|
defer func() {
|
||||||
|
_ = s.handleQueue.CloseThread(threadId)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if hm.PeerCtx == nil {
|
||||||
|
hm.PeerCtx = ctx
|
||||||
|
}
|
||||||
|
err = s.handleQueue.Add(ctx, threadId, hm)
|
||||||
|
if err == mb.ErrOverflowed {
|
||||||
|
log.InfoCtx(ctx, "queue overflowed", zap.String("spaceId", s.id), zap.String("objectId", threadId))
|
||||||
|
// skip overflowed error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log := log.With(zap.String("spaceId", s.state.SpaceId))
|
return
|
||||||
log.Debug("space is closing")
|
}
|
||||||
|
|
||||||
err := s.app.Close(context.Background())
|
func (s *space) handleMessage(msg HandleMessage) {
|
||||||
log.Debug("space closed")
|
var err error
|
||||||
return err
|
msg.StartHandlingTime = time.Now()
|
||||||
|
ctx := peer.CtxWithPeerId(context.Background(), msg.SenderId)
|
||||||
|
ctx = logger.CtxWithFields(ctx, zap.Uint64("msgId", msg.Id), zap.String("senderId", msg.SenderId))
|
||||||
|
defer func() {
|
||||||
|
if s.metric == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.metric.RequestLog(msg.PeerCtx, "space.streamOp", msg.LogFields(
|
||||||
|
zap.Error(err),
|
||||||
|
)...)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !msg.Deadline.IsZero() {
|
||||||
|
now := time.Now()
|
||||||
|
if now.After(msg.Deadline) {
|
||||||
|
log.InfoCtx(ctx, "skip message: deadline exceed")
|
||||||
|
err = context.DeadlineExceeded
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithDeadline(ctx, msg.Deadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.objectSync.HandleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
||||||
|
if msg.Message.ObjectId != "" {
|
||||||
|
// cleanup thread on error
|
||||||
|
_ = s.handleQueue.CloseThread(msg.Message.ObjectId)
|
||||||
|
}
|
||||||
|
log.InfoCtx(ctx, "handleMessage error", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) onObjectClose(id string) {
|
||||||
|
s.treesUsed.Add(-1)
|
||||||
|
log.Debug("decrementing counter", zap.String("id", id), zap.Int32("trees", s.treesUsed.Load()), zap.String("spaceId", s.id))
|
||||||
|
_ = s.handleQueue.CloseThread(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) onSpaceDelete() {
|
||||||
|
err := s.storage.SetSpaceDeleted()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("failed to set space deleted")
|
||||||
|
}
|
||||||
|
s.isDeleted.Swap(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *space) Close() error {
|
||||||
|
if s.isClosed.Swap(true) {
|
||||||
|
log.Warn("call space.Close on closed space", zap.String("id", s.id))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.With(zap.String("id", s.id)).Debug("space is closing")
|
||||||
|
|
||||||
|
var mError errs.Group
|
||||||
|
if err := s.handleQueue.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
if err := s.headSync.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
if err := s.objectSync.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
if err := s.settingsObject.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
if err := s.aclList.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
if err := s.storage.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
if err := s.syncStatus.Close(); err != nil {
|
||||||
|
mError.Add(err)
|
||||||
|
}
|
||||||
|
log.With(zap.String("id", s.id)).Debug("space closed")
|
||||||
|
return mError.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *space) TryClose(objectTTL time.Duration) (close bool, err error) {
|
func (s *space) TryClose(objectTTL time.Duration) (close bool, err error) {
|
||||||
if time.Now().Sub(s.objectSync.LastUsage()) < objectTTL {
|
if time.Now().Sub(s.objectSync.LastUsage()) < objectTTL {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
locked := s.state.TreesUsed.Load() > 1
|
locked := s.treesUsed.Load() > 1
|
||||||
log.With(zap.Int32("trees used", s.state.TreesUsed.Load()), zap.Bool("locked", locked), zap.String("spaceId", s.state.SpaceId)).Debug("space lock status check")
|
log.With(zap.Int32("trees used", s.treesUsed.Load()), zap.Bool("locked", locked), zap.String("spaceId", s.id)).Debug("space lock status check")
|
||||||
if locked {
|
if locked {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,36 +2,26 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/config"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/settings"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/metric"
|
"github.com/anyproto/any-sync/metric"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/net/pool"
|
"github.com/anyproto/any-sync/net/pool"
|
||||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
"storj.io/drpc"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace"
|
const CName = "common.commonspace"
|
||||||
@ -55,30 +45,32 @@ type SpaceService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type spaceService struct {
|
type spaceService struct {
|
||||||
config config.Config
|
config Config
|
||||||
account accountservice.Service
|
account accountservice.Service
|
||||||
configurationService nodeconf.Service
|
configurationService nodeconf.Service
|
||||||
storageProvider spacestorage.SpaceStorageProvider
|
storageProvider spacestorage.SpaceStorageProvider
|
||||||
peerManagerProvider peermanager.PeerManagerProvider
|
peermanagerProvider peermanager.PeerManagerProvider
|
||||||
credentialProvider credentialprovider.CredentialProvider
|
credentialProvider credentialprovider.CredentialProvider
|
||||||
statusServiceProvider syncstatus.StatusServiceProvider
|
treeManager treemanager.TreeManager
|
||||||
treeManager treemanager.TreeManager
|
pool pool.Pool
|
||||||
pool pool.Pool
|
metric metric.Metric
|
||||||
metric metric.Metric
|
|
||||||
app *app.App
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceService) Init(a *app.App) (err error) {
|
func (s *spaceService) Init(a *app.App) (err error) {
|
||||||
s.config = a.MustComponent("config").(config.ConfigGetter).GetSpace()
|
s.config = a.MustComponent("config").(ConfigGetter).GetSpace()
|
||||||
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
|
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||||
s.storageProvider = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorageProvider)
|
s.storageProvider = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorageProvider)
|
||||||
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
||||||
s.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
s.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
||||||
s.peerManagerProvider = a.MustComponent(peermanager.CName).(peermanager.PeerManagerProvider)
|
s.peermanagerProvider = a.MustComponent(peermanager.CName).(peermanager.PeerManagerProvider)
|
||||||
s.statusServiceProvider = a.MustComponent(syncstatus.CName).(syncstatus.StatusServiceProvider)
|
credProvider := a.Component(credentialprovider.CName)
|
||||||
|
if credProvider != nil {
|
||||||
|
s.credentialProvider = credProvider.(credentialprovider.CredentialProvider)
|
||||||
|
} else {
|
||||||
|
s.credentialProvider = credentialprovider.NewNoOp()
|
||||||
|
}
|
||||||
s.pool = a.MustComponent(pool.CName).(pool.Pool)
|
s.pool = a.MustComponent(pool.CName).(pool.Pool)
|
||||||
s.metric, _ = a.Component(metric.CName).(metric.Metric)
|
s.metric, _ = a.Component(metric.CName).(metric.Metric)
|
||||||
s.app = a
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +138,8 @@ func (s *spaceService) NewSpace(ctx context.Context, id string) (Space, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastConfiguration := s.configurationService
|
||||||
var (
|
var (
|
||||||
spaceIsClosed = &atomic.Bool{}
|
spaceIsClosed = &atomic.Bool{}
|
||||||
spaceIsDeleted = &atomic.Bool{}
|
spaceIsDeleted = &atomic.Bool{}
|
||||||
@ -155,46 +149,49 @@ func (s *spaceService) NewSpace(ctx context.Context, id string) (Space, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
spaceIsDeleted.Swap(isDeleted)
|
spaceIsDeleted.Swap(isDeleted)
|
||||||
state := &spacestate.SpaceState{
|
getter := newCommonGetter(st.Id(), s.treeManager, spaceIsClosed)
|
||||||
SpaceId: st.Id(),
|
syncStatus := syncstatus.NewNoOpSyncStatus()
|
||||||
SpaceIsDeleted: spaceIsDeleted,
|
// this will work only for clients, not the best solution, but...
|
||||||
SpaceIsClosed: spaceIsClosed,
|
if !lastConfiguration.IsResponsible(st.Id()) {
|
||||||
TreesUsed: &atomic.Int32{},
|
// TODO: move it to the client package and add possibility to inject StatusProvider from the client
|
||||||
|
syncStatus = syncstatus.NewSyncStatusProvider(st.Id(), syncstatus.DefaultDeps(lastConfiguration, st))
|
||||||
}
|
}
|
||||||
|
var builder objecttree.BuildObjectTreeFunc
|
||||||
if s.config.KeepTreeDataInMemory {
|
if s.config.KeepTreeDataInMemory {
|
||||||
state.TreeBuilderFunc = objecttree.BuildObjectTree
|
builder = objecttree.BuildObjectTree
|
||||||
} else {
|
} else {
|
||||||
state.TreeBuilderFunc = objecttree.BuildEmptyDataObjectTree
|
builder = objecttree.BuildEmptyDataObjectTree
|
||||||
}
|
}
|
||||||
peerManager, err := s.peerManagerProvider.NewPeerManager(ctx, id)
|
|
||||||
|
peerManager, err := s.peermanagerProvider.NewPeerManager(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
statusService := s.statusServiceProvider.NewStatusService()
|
|
||||||
spaceApp := s.app.ChildApp()
|
|
||||||
spaceApp.Register(state).
|
|
||||||
Register(peerManager).
|
|
||||||
Register(newCommonStorage(st)).
|
|
||||||
Register(statusService).
|
|
||||||
Register(syncacl.New()).
|
|
||||||
Register(requestmanager.New()).
|
|
||||||
Register(deletionstate.New()).
|
|
||||||
Register(settings.New()).
|
|
||||||
Register(objectmanager.New(s.treeManager)).
|
|
||||||
Register(objecttreebuilder.New()).
|
|
||||||
Register(objectsync.New()).
|
|
||||||
Register(headsync.New())
|
|
||||||
|
|
||||||
|
headSync := headsync.NewHeadSync(id, spaceIsDeleted, s.config.SyncPeriod, lastConfiguration, st, peerManager, getter, syncStatus, s.credentialProvider, log)
|
||||||
|
objectSync := objectsync.NewObjectSync(id, spaceIsDeleted, lastConfiguration, peerManager, getter, st)
|
||||||
sp := &space{
|
sp := &space{
|
||||||
state: state,
|
id: id,
|
||||||
app: spaceApp,
|
objectSync: objectSync,
|
||||||
|
headSync: headSync,
|
||||||
|
syncStatus: syncStatus,
|
||||||
|
treeManager: getter,
|
||||||
|
account: s.account,
|
||||||
|
configuration: lastConfiguration,
|
||||||
|
peerManager: peerManager,
|
||||||
|
storage: st,
|
||||||
|
treesUsed: &atomic.Int32{},
|
||||||
|
treeBuilder: builder,
|
||||||
|
isClosed: spaceIsClosed,
|
||||||
|
isDeleted: spaceIsDeleted,
|
||||||
|
metric: s.metric,
|
||||||
}
|
}
|
||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceService) addSpaceStorage(ctx context.Context, spaceDescription SpaceDescription) (st spacestorage.SpaceStorage, err error) {
|
func (s *spaceService) addSpaceStorage(ctx context.Context, spaceDescription SpaceDescription) (st spacestorage.SpaceStorage, err error) {
|
||||||
payload := spacestorage.SpaceStorageCreatePayload{
|
payload := spacestorage.SpaceStorageCreatePayload{
|
||||||
AclWithId: &consensusproto.RawRecordWithId{
|
AclWithId: &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: spaceDescription.AclPayload,
|
Payload: spaceDescription.AclPayload,
|
||||||
Id: spaceDescription.AclId,
|
Id: spaceDescription.AclId,
|
||||||
},
|
},
|
||||||
@ -229,19 +226,15 @@ func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var res *spacesyncproto.SpacePullResponse
|
cl := spacesyncproto.NewDRPCSpaceSyncClient(p)
|
||||||
err = p.DoDrpc(ctx, func(conn drpc.Conn) error {
|
res, err := cl.SpacePull(ctx, &spacesyncproto.SpacePullRequest{Id: id})
|
||||||
cl := spacesyncproto.NewDRPCSpaceSyncClient(conn)
|
|
||||||
res, err = cl.SpacePull(ctx, &spacesyncproto.SpacePullRequest{Id: id})
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = rpcerr.Unwrap(err)
|
err = rpcerr.Unwrap(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err = s.createSpaceStorage(spacestorage.SpaceStorageCreatePayload{
|
st, err = s.createSpaceStorage(spacestorage.SpaceStorageCreatePayload{
|
||||||
AclWithId: &consensusproto.RawRecordWithId{
|
AclWithId: &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: res.Payload.AclPayload,
|
Payload: res.Payload.AclPayload,
|
||||||
Id: res.Payload.AclPayloadId,
|
Id: res.Payload.AclPayloadId,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
package spacestate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
const CName = "common.commonspace.spacestate"
|
|
||||||
|
|
||||||
type SpaceState struct {
|
|
||||||
SpaceId string
|
|
||||||
SpaceIsDeleted *atomic.Bool
|
|
||||||
SpaceIsClosed *atomic.Bool
|
|
||||||
TreesUsed *atomic.Int32
|
|
||||||
TreeBuilderFunc objecttree.BuildObjectTreeFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SpaceState) Init(a *app.App) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SpaceState) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user