Merge pull request #7 from anytypeio/new-tree-structure
This commit is contained in:
commit
2e47b23827
11
Makefile
11
Makefile
@ -13,25 +13,22 @@ endif
|
||||
export PATH=$(GOPATH)/bin:$(shell echo $$PATH)
|
||||
|
||||
# TODO: folders were changed, so we should update Makefile and protos generation
|
||||
protos-go:
|
||||
proto:
|
||||
@echo 'Generating protobuf packages (Go)...'
|
||||
# Uncomment if needed
|
||||
@$(eval ROOT_PKG := pkg)
|
||||
@$(eval GOGO_START := GOGO_NO_UNDERSCORE=1 GOGO_EXPORT_ONEOF_INTERFACE=1)
|
||||
@$(eval P_TREE_STORAGE_PATH_PB := $(ROOT_PKG)/acl/treestorage/treepb)
|
||||
@$(eval P_ACL_CHANGES_PATH_PB := $(ROOT_PKG)/acl/aclchanges/aclpb)
|
||||
@$(eval P_PLAINTEXT_CHANGES_PATH_PB := $(ROOT_PKG)/acl/testutils/testchanges/testchangepb)
|
||||
@$(eval P_SYNC_CHANGES_PATH_PB := syncproto)
|
||||
@$(eval P_TEST_CHANGES_PATH_PB := $(ROOT_PKG)/acl/testutils/testchanges)
|
||||
@$(eval P_TIMESTAMP := Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types)
|
||||
@$(eval P_STRUCT := Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types)
|
||||
@$(eval P_ACL_CHANGES := M$(P_ACL_CHANGES_PATH_PB)/protos/aclchanges.proto=github.com/anytypeio/go-anytype-infrastructure-experiments/$(P_ACL_CHANGES_PATH_PB))
|
||||
@$(eval P_TREE_CHANGES := M$(P_TREE_STORAGE_PATH_PB)/protos/tree.proto=github.com/anytypeio/go-anytype-infrastructure-experiments/$(P_TREE_STORAGE_PATH_PB))
|
||||
|
||||
# use if needed $(eval PKGMAP := $$(P_TIMESTAMP),$$(P_STRUCT))
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_ACL_CHANGES_PATH_PB)/protos/*.proto; mv $(P_ACL_CHANGES_PATH_PB)/protos/*.go $(P_ACL_CHANGES_PATH_PB)
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_TREE_STORAGE_PATH_PB)/protos/*.proto; mv $(P_TREE_STORAGE_PATH_PB)/protos/*.go $(P_TREE_STORAGE_PATH_PB)
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_PLAINTEXT_CHANGES_PATH_PB)/protos/*.proto; mv $(P_PLAINTEXT_CHANGES_PATH_PB)/protos/*.go $(P_PLAINTEXT_CHANGES_PATH_PB)
|
||||
$(eval PKGMAP := $$(P_ACL_CHANGES),$$(P_TREE_CHANGES))
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_TEST_CHANGES_PATH_PB)/proto/*.proto
|
||||
$(eval PKGMAP := $$(P_ACL_CHANGES))
|
||||
$(GOGO_START) protoc --gogofaster_out=$(PKGMAP):. $(P_SYNC_CHANGES_PATH_PB)/proto/*.proto
|
||||
$(GOGO_START) protoc --gogofaster_out=$(PKGMAP):. service/space/spacesync/protos/*.proto
|
||||
|
||||
|
||||
@ -10,12 +10,13 @@ import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/api"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/configuration"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/document"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/dialer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/rpc/server"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/secure"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/document"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/message"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/requesthandler"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
@ -98,6 +99,7 @@ func Bootstrap(a *app.App) {
|
||||
Register(server.New()).
|
||||
Register(dialer.New()).
|
||||
Register(pool.NewPool()).
|
||||
Register(storage.New()).
|
||||
Register(configuration.New()).
|
||||
Register(document.New()).
|
||||
Register(message.New()).
|
||||
|
||||
28
etc/acl.yml
Normal file
28
etc/acl.yml
Normal file
@ -0,0 +1,28 @@
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
- userAdd:
|
||||
identity: B
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Enc:
|
||||
- name: A
|
||||
value: JgG4CcCbae1qEpe7mKpBzsHjZhXUmDSNVNX2B1gxFZsJyMX4V6kBQUott9zRWyeXaW1ZmpzuxDXnwSQpAnNurhXyGa9iQaAPqzY9A9VWBPD33Yy1eW7TRuVemzToh8jJQKQKnZNbF8ucTWV9qahusKzyvN8uyhrqoW2tAPfA9S3E3ognCuqbLSW6yjE2rBKayvyS1BVwzjSd6FZK4DDyjfU3pbEVjut3wytGEAn9af6sNMmyCnf2MX5vLovWs9rU8av61wD4z7HTsXyGFx4K75N4Go249Hpe9SKAT6HxhRc3yvj63krPLiQV5yMuH2UeMUXBDekUQyNmBEdn9wrur7mLqB67Bc6tcc2PP8XApBCdWJHvHjN4FktSpaG5vbCqoZbLD1oCbk36q2x9s6XM8pydVqD1J9P3nTbfgMb5pJCTFjNtgKeuKv6wjfJeA9jF1VhcJQisfsahgv9MvZ9M8FJpZTq1zKUhYDCRnZxUkraoMS5yNNVdDzaUckKEDthqik7BMWCWT79vq7uVgMwEvGwGi76gtoMg1159bbPMLZ4bdPVfhH2S9QjPrzQfwZSrzB2YeVPjWpaXDeLDity5H8n1NK2oniAQR6gE71n81neSptsuhV6o6QpQ89AU8y57XmEsou4VEryn8vUxBHhULLxrLNUouxyWamCeFiDjk5cSN6koQsf9BYKSNTPFTrwjTKForDokMhcPdMtFktKwjv7u9UEGcY4MKvNzZZkc77gHiP8bqVtdNNoLpTFUC5SZ9i7bKdHvK12HpSy7yzzPeMXJ9UwhLxkok1g81ngTbN1yxRhvYXyHZFtguCR9kvGojDjka91MTBtk551qDw9eCn2xZT9U8jqzBCjdpvSg3mRWKMPnYAGB7m7u1ye165wyGFvzcHAx3vtXjxAqLUeKYZCjv2m6V9D2Y4qH1TQNddWqH14T1JVMis971UCH9Ddpj6a3387oUnufD1P6HZN2ieJCvptrmbGVvxJYYSvmVf1dkwbtqurDRNWD7TJ7gf6iqSP549C9bxP4GpLt3ygjHmMtcuUzstBuztvunJUnQhfnJxqU6LjRdsFzm53wGWgXNxab7ZvQcPyLwsevn1b98FGPnVpS5iY4LjmqW4ugrC6HgrbsjrXiKzR1yZKhLQkCbLzPoaHb8iB5iBnCr7d4yf5CtfpFRqgoqMFdK5LNZYmDX4HzUKN6A7wC3gGiSRFTLcgGZeSMkB5Pa61CZBU7WCQgFxykycE9HRA7PiQa496GWDCV15teToCpFRsAa6jDmR1MGXPeLRqQgve49VXnQN5FL7c1VuEv5SWjeTuCnMB47DJKBaP7eKJNKgLwETALzSCMF3nRiRgeb15kfoS4BbrJ5yupjrvwmbmvNg1AYFFS5sYNWft7K8v87wQvBakRtGP71Kp8NX77XFtu6xdB7sR6jpfC6qJPyB9akWNXgCrWy9kE4ih42gwAZdUugNZ9YtEsgRM3pwb6qJhkAPyEJtrxrja859PCAgqPSQiPQN33PaMkgQ6HJknu8CrjKRiXAycZ16KLUkHV64TNhEjPTcX1a7rqpD131AYMWX8d7CCdc9Ys7RUb6BwguuNSh8rJK3x4AkMDSUsaE8ynKvpC7RXZpJ9Nxfhd
|
||||
- name: B
|
||||
value: JgG4CcCbae1qEpe7mKXzp7m5hNc56SSyZd9DwUaEStKJrq7RToAC2Vgd3i6hKRwa58zCWeN6Wjc3o6qrdKPEPRvcyEPysamajVo5mdQiUgWAmr97pGEsyjuRjQoC2GY2LvLiEQxEgwFgJxKGMHMiaWMtDfxCDUaDEm4bu5RdMhqRZekAWho6c3WoEeruSr14iX1TrocFNfBkBY7CjEw8kcywXCTNgtvhb2Qiwgj5AxEF4wyw4bzaNA9ctXb1hoHPFVMu6C51pkFY7jUD9zwyH3ukgnAewkGAcPNbKmaTAtMosKRVaAN97mAwXh2VRt1hWmRvVk7r76EjnVKhD4vbsKZc56RVcHTVWRVdhU7FGyPsiE5rSQAz1JQGYzxnZpX7EG77CyrmUGyfueVfRHhwY2oq8A4uQCRaQxSaJHYLowjXSxh8DQ2V6MTqyzti32C27utBYdHzLVCJSGkmdzGwrFcHqsq7nLDxmvJVErPvyReixEe8kFmqopJ3e6LLm8WdYw9K6JYBjXnEfwPzm7Von9sf3dcaGDUHYfttMyeke7fAXJkvPRje69hYVyzdQGAauuojzGkkvQWCSMK1KCMNMznRaPDCNvofrQhYrub24WhmwpKhorufdfW8Cb4T6reBDCtaWVsbuinjtL6F6Sui5aYHJFLJ6e4pPewr1P4EuZYRbMBZwN5KvDLhTGLBuBnaTqUUdF6bj2U22NoRYMogiHiftqKqiexKNDXX1Zg9RQEvxgjuVo6SBW42mVEA8agrLhruRqCmiduJxVrfqLNGeYXHXrcmMEgW7uosJbPXvTcfRvdFWS1ov7oSALvj6vhDQ28Yi9D2ETNdNsfVWAFQuwvPpW7CHQGXTitprVbqH8JYxNZuGygcLmr5efbB22Vzu4ntd1HoraQpG12qeDEUA7tXYUpoYyuSdWwKPjSAMtaQcCSfVrhKQHQuKJargrVrez8vjWuwLfvSucV7ZHe7gjqvYgULdE1ubRCRSd7DuLjEN2Vd6obzV2c3MRet7ZSf4Sp88WM5AuTyW7BjArBc4S3gUQ8rYaiZ8Tu7NCxkEzbFwWRaemZkwfvcsX3XxqjyF37tFSGkEqE5kuBvpZW72675LkDffj7kH1zA8yE6dVujJjWsNYVFJWndUtz5Vy2KCdZAbBgq19q4AtsxWPodU2N3yZXzFAFAzTrxS6V4P7Scpdau1avgRvHLcBQPunA37xaYMy8YMifJwtmRY25mnAQwZAk3eANk7tXwZd58SDnciLNvARJvwKzTQBXcshkwyy52SX8XmXDJsPnRLaHmiYBJ63Yzr5XpZuuAtxb9qrWG2NHCNxfomHokWacV1hjZPPd6ZxT1FuRozB6Qt2NLcyqY7bnTcQJb1jPUaTAGXXCR8WVmmmYo2fDQe8CdBmgyPvbzNTEJUyScBz4RdycB5PZap4SurJCWtHbuMyQbQUB6jJgURDstfXS5Akfe4oruNq9rnYcNtnsDJPtrhXHBqzDizmf1BDxR5FB2RCxzCgeAfg8WQ1Ug9PVAGTzob6ZqCrGXzWXEUniZnf1vjr7QhGKBYXEX9SWDoSMUpP4FreVDTnx15ijRZTV3p8xG5fE9e36TnugRVvTyq7XzmyPBjW2r66f1bior
|
||||
Sign:
|
||||
- name: A
|
||||
value: 3id6ddLcoNoe9rDgGM88ET8T6TnvHm5GFqFdN6kBzn7Q8d6VUGgjeT59CNWFiaofdeRnHBvX2A5ZacMXvfwaYEFuCbug
|
||||
- name: B
|
||||
value: 3iiLPj6wMUQpPwTBNZcUgkbXub1jumg4AEV9LfMyFHZVc84GLyAjVbVvH6EAGhcNrxRxL82aW4BimhDZCpLsRCqx5vwj
|
||||
Read:
|
||||
- name: 1
|
||||
value: bamccoi5jdypwnjkiuuogkawvhkbowha4qg756uhnbkecr5vt3h4q
|
||||
15
etc/path.go
Normal file
15
etc/path.go
Normal file
@ -0,0 +1,15 @@
|
||||
package etc
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
_, b, _, _ = runtime.Caller(0)
|
||||
basepath = filepath.Dir(b)
|
||||
)
|
||||
|
||||
func Path() string {
|
||||
return basepath
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
@ -9,5 +10,5 @@ type AccountData struct { // TODO: create a convenient constructor for this
|
||||
Identity string // TODO: this is essentially the same as sign key
|
||||
SignKey signingkey.PrivKey
|
||||
EncKey encryptionkey.PrivKey
|
||||
Decoder signingkey.PubKeyDecoder
|
||||
Decoder keys.Decoder
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,12 @@ message RawChange {
|
||||
string id = 3;
|
||||
}
|
||||
|
||||
message RawRecord {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
string id = 3;
|
||||
}
|
||||
|
||||
// the element of change tree used to store and internal apply smartBlock history
|
||||
message ACLChange {
|
||||
repeated string treeHeadIds = 1;
|
||||
@ -109,3 +115,34 @@ message ACLChange {
|
||||
Removed = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Change {
|
||||
repeated string treeHeadIds = 1;
|
||||
string aclHeadId = 2;
|
||||
string snapshotBaseId = 3; // we will only have one base snapshot for both
|
||||
bytes changesData = 4;
|
||||
uint64 currentReadKeyHash = 5;
|
||||
int64 timestamp = 6;
|
||||
string identity = 7;
|
||||
bool isSnapshot = 8;
|
||||
}
|
||||
|
||||
message Record {
|
||||
string prevId = 1;
|
||||
string identity = 2;
|
||||
bytes data = 3;
|
||||
uint64 currentReadKeyHash = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
message Header {
|
||||
string firstId = 1;
|
||||
string aclListId = 2;
|
||||
string workspaceId = 3;
|
||||
DocType docType = 4;
|
||||
|
||||
enum DocType {
|
||||
ACL = 0;
|
||||
DocTree = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package aclchanges
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type Change interface {
|
||||
ProtoChange() *aclpb.ACLChange
|
||||
ProtoChange() proto.Marshaler
|
||||
DecryptedChangeContent() []byte
|
||||
Signature() []byte
|
||||
CID() string
|
||||
|
||||
@ -1,186 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type aclStateBuilder struct {
|
||||
tree *Tree
|
||||
identity string
|
||||
key encryptionkey.PrivKey
|
||||
decoder signingkey.PubKeyDecoder
|
||||
}
|
||||
|
||||
type decreasedPermissionsParameters struct {
|
||||
users []*aclpb.ACLChangeUserPermissionChange
|
||||
startChange string
|
||||
}
|
||||
|
||||
func newACLStateBuilder(decoder signingkey.PubKeyDecoder, accountData *account.AccountData) *aclStateBuilder {
|
||||
return &aclStateBuilder{
|
||||
decoder: decoder,
|
||||
identity: accountData.Identity,
|
||||
key: accountData.EncKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (sb *aclStateBuilder) Init(tree *Tree) error {
|
||||
sb.tree = tree
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *aclStateBuilder) Build() (*ACLState, error) {
|
||||
state, _, err := sb.BuildBefore("")
|
||||
return state, err
|
||||
}
|
||||
|
||||
// TODO: we can probably have only one state builder, because we can Build both at the same time
|
||||
func (sb *aclStateBuilder) BuildBefore(beforeId string) (*ACLState, bool, error) {
|
||||
var (
|
||||
err error
|
||||
startChange = sb.tree.root
|
||||
foundId bool
|
||||
idSeenMap = make(map[string][]*Change)
|
||||
decreasedPermissions *decreasedPermissionsParameters
|
||||
)
|
||||
root := sb.tree.Root()
|
||||
if !root.IsSnapshot {
|
||||
return nil, false, fmt.Errorf("root should always be a snapshot")
|
||||
}
|
||||
|
||||
state, err := newACLStateFromSnapshotChange(
|
||||
root.Content,
|
||||
sb.identity,
|
||||
sb.key,
|
||||
sb.decoder)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("could not build ACLState from snapshot: %w", err)
|
||||
}
|
||||
|
||||
idSeenMap[startChange.Content.Identity] = append(idSeenMap[startChange.Content.Identity], startChange)
|
||||
|
||||
if startChange.Content.GetChangesData() != nil {
|
||||
key, exists := state.userReadKeys[startChange.Content.CurrentReadKeyHash]
|
||||
if !exists {
|
||||
return nil, false, fmt.Errorf("no first snapshot")
|
||||
}
|
||||
|
||||
err = startChange.DecryptContents(key)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to decrypt contents of first snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
if beforeId == startChange.Id {
|
||||
return state, true, nil
|
||||
}
|
||||
|
||||
for {
|
||||
// TODO: we should optimize this method to just remember last state of iterator and not iterate from the start and skip if nothing was removed from the Tree
|
||||
sb.tree.IterateSkip(sb.tree.root.Id, startChange.Id, func(c *Change) (isContinue bool) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
startChange = c
|
||||
} else if err != ErrDocumentForbidden {
|
||||
//log.Errorf("marking change %s as invalid: %v", c.Id, err)
|
||||
sb.tree.RemoveInvalidChange(c.Id)
|
||||
}
|
||||
}()
|
||||
|
||||
// not applying root change
|
||||
if c.Id == startChange.Id {
|
||||
return true
|
||||
}
|
||||
|
||||
idSeenMap[c.Content.Identity] = append(idSeenMap[c.Content.Identity], c)
|
||||
if c.Content.GetAclData() != nil {
|
||||
err = state.applyChange(c.Content)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// if we have some users who have less permissions now
|
||||
users := state.getPermissionDecreasedUsers(c.Content)
|
||||
if len(users) > 0 {
|
||||
decreasedPermissions = &decreasedPermissionsParameters{
|
||||
users: users,
|
||||
startChange: c.Id,
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// the user can't make changes
|
||||
if !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Writer) && !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Admin) {
|
||||
err = fmt.Errorf("user %s cannot make changes", c.Content.Identity)
|
||||
return false
|
||||
}
|
||||
|
||||
// decrypting contents on the fly
|
||||
if c.Content.GetChangesData() != nil {
|
||||
key, exists := state.userReadKeys[c.Content.CurrentReadKeyHash]
|
||||
if !exists {
|
||||
err = fmt.Errorf("failed to find key with hash: %d", c.Content.CurrentReadKeyHash)
|
||||
return false
|
||||
}
|
||||
|
||||
err = c.DecryptContents(key)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to decrypt contents for hash: %d", c.Content.CurrentReadKeyHash)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if c.Id == beforeId {
|
||||
foundId = true
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// if we have users with decreased permissions
|
||||
if decreasedPermissions != nil {
|
||||
var removed bool
|
||||
validChanges := sb.tree.dfs(decreasedPermissions.startChange)
|
||||
|
||||
for _, permChange := range decreasedPermissions.users {
|
||||
seenChanges := idSeenMap[permChange.Identity]
|
||||
|
||||
for _, seen := range seenChanges {
|
||||
// if we find some invalid changes
|
||||
if _, exists := validChanges[seen.Id]; !exists {
|
||||
// if the user didn't have enough permission to make changes
|
||||
if seen.IsACLChange() || permChange.Permissions > aclpb.ACLChange_Writer {
|
||||
removed = true
|
||||
sb.tree.RemoveInvalidChange(seen.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decreasedPermissions = nil
|
||||
if removed {
|
||||
// starting from the beginning but with updated Tree
|
||||
return sb.BuildBefore(beforeId)
|
||||
}
|
||||
} else if err == nil {
|
||||
// we can finish the acl state building process
|
||||
break
|
||||
}
|
||||
|
||||
// the user is forbidden to access the document
|
||||
if err == ErrDocumentForbidden {
|
||||
return nil, foundId, err
|
||||
}
|
||||
|
||||
// otherwise we have to continue from the change which we had
|
||||
err = nil
|
||||
}
|
||||
|
||||
return state, foundId, err
|
||||
}
|
||||
@ -1,520 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type AddResultSummary int
|
||||
|
||||
const (
|
||||
AddResultSummaryNothing AddResultSummary = iota
|
||||
AddResultSummaryAppend
|
||||
AddResultSummaryRebuild
|
||||
)
|
||||
|
||||
type AddResult struct {
|
||||
OldHeads []string
|
||||
Heads []string
|
||||
Added []*aclpb.RawChange
|
||||
// TODO: add summary for changes
|
||||
Summary AddResultSummary
|
||||
}
|
||||
|
||||
type TreeUpdateListener interface {
|
||||
Update(tree ACLTree)
|
||||
Rebuild(tree ACLTree)
|
||||
}
|
||||
|
||||
type NoOpListener struct{}
|
||||
|
||||
func (n NoOpListener) Update(tree ACLTree) {}
|
||||
|
||||
func (n NoOpListener) Rebuild(tree ACLTree) {}
|
||||
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
RLock()
|
||||
RUnlock()
|
||||
}
|
||||
|
||||
var ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
|
||||
|
||||
type ACLTree interface {
|
||||
RWLocker
|
||||
ID() string
|
||||
Header() *treepb.TreeHeader
|
||||
ACLState() *ACLState
|
||||
AddContent(ctx context.Context, f func(builder ChangeBuilder) error) (*aclpb.RawChange, error)
|
||||
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
||||
Heads() []string
|
||||
Root() *Change
|
||||
Iterate(func(change *Change) bool)
|
||||
IterateFrom(string, func(change *Change) bool)
|
||||
HasChange(string) bool
|
||||
SnapshotPath() []string
|
||||
ChangesAfterCommonSnapshot(snapshotPath []string) ([]*aclpb.RawChange, error)
|
||||
Storage() treestorage.TreeStorage
|
||||
DebugDump() (string, error)
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
type aclTree struct {
|
||||
treeStorage treestorage.TreeStorage
|
||||
accountData *account.AccountData
|
||||
updateListener TreeUpdateListener
|
||||
|
||||
id string
|
||||
header *treepb.TreeHeader
|
||||
fullTree *Tree
|
||||
aclTreeFromStart *Tree // TODO: right now we don't use it, we can probably have only local var for now. This tree is built from start of the document
|
||||
aclState *ACLState
|
||||
|
||||
treeBuilder *treeBuilder
|
||||
aclTreeBuilder *aclTreeBuilder
|
||||
aclStateBuilder *aclStateBuilder
|
||||
snapshotValidator *snapshotValidator
|
||||
changeBuilder *changeBuilder
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func BuildACLTree(
|
||||
t treestorage.TreeStorage,
|
||||
acc *account.AccountData,
|
||||
listener TreeUpdateListener) (ACLTree, error) {
|
||||
aclTreeBuilder := newACLTreeBuilder(t, acc.Decoder)
|
||||
treeBuilder := newTreeBuilder(t, acc.Decoder)
|
||||
snapshotValidator := newSnapshotValidator(acc.Decoder, acc) // TODO: this looks weird, change it
|
||||
aclStateBuilder := newACLStateBuilder(acc.Decoder, acc)
|
||||
changeBuilder := newChangeBuilder()
|
||||
|
||||
aclTree := &aclTree{
|
||||
treeStorage: t,
|
||||
accountData: acc,
|
||||
fullTree: nil,
|
||||
aclState: nil,
|
||||
treeBuilder: treeBuilder,
|
||||
aclTreeBuilder: aclTreeBuilder,
|
||||
aclStateBuilder: aclStateBuilder,
|
||||
snapshotValidator: snapshotValidator,
|
||||
changeBuilder: changeBuilder,
|
||||
updateListener: listener,
|
||||
}
|
||||
err := aclTree.rebuildFromStorage(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = aclTree.removeOrphans()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.SetHeads(aclTree.Heads())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclTree.id, err = t.TreeID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclTree.header, err = t.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listener.Rebuild(aclTree)
|
||||
|
||||
return aclTree, nil
|
||||
}
|
||||
|
||||
// TODO: this is not used for now, in future we should think about not making full tree rebuild
|
||||
//func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) {
|
||||
// if validateSnapshot {
|
||||
// err = a.snapshotValidator.Init(a.aclTreeFromStart)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !valid {
|
||||
// return a.rebuildFromStorage(true)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// err = a.aclStateBuilder.Init(a.fullTree)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// a.aclState, err = a.aclStateBuilder.Build()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func (a *aclTree) removeOrphans() error {
|
||||
// removing attached or invalid orphans
|
||||
var toRemove []string
|
||||
|
||||
orphans, err := a.treeStorage.Orphans()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, orphan := range orphans {
|
||||
if _, exists := a.fullTree.attached[orphan]; exists {
|
||||
toRemove = append(toRemove, orphan)
|
||||
}
|
||||
if _, exists := a.fullTree.invalidChanges[orphan]; exists {
|
||||
toRemove = append(toRemove, orphan)
|
||||
}
|
||||
}
|
||||
return a.treeStorage.RemoveOrphans(toRemove...)
|
||||
}
|
||||
|
||||
func (a *aclTree) rebuildFromStorage(fromStart bool) error {
|
||||
a.treeBuilder.Init()
|
||||
a.aclTreeBuilder.Init()
|
||||
|
||||
var err error
|
||||
a.fullTree, err = a.treeBuilder.Build(fromStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: remove this from context as this is used only to validate snapshot
|
||||
a.aclTreeFromStart, err = a.aclTreeBuilder.Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fromStart {
|
||||
err = a.snapshotValidator.Init(a.aclTreeFromStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid {
|
||||
return a.rebuildFromStorage(true)
|
||||
}
|
||||
}
|
||||
// TODO: there is a question how we can validate not only that the full tree is built correctly
|
||||
// but also that the ACL prev ids are not messed up. I think we should probably compare the resulting
|
||||
// acl state with the acl state which is built in aclTreeFromStart
|
||||
|
||||
err = a.aclStateBuilder.Init(a.fullTree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.aclState, err = a.aclStateBuilder.Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *aclTree) ID() string {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *aclTree) Header() *treepb.TreeHeader {
|
||||
return a.header
|
||||
}
|
||||
|
||||
func (a *aclTree) ACLState() *ACLState {
|
||||
return a.aclState
|
||||
}
|
||||
|
||||
func (a *aclTree) Storage() treestorage.TreeStorage {
|
||||
return a.treeStorage
|
||||
}
|
||||
|
||||
func (a *aclTree) AddContent(ctx context.Context, build func(builder ChangeBuilder) error) (*aclpb.RawChange, error) {
|
||||
// TODO: add snapshot creation logic
|
||||
defer func() {
|
||||
// TODO: should this be called in a separate goroutine to prevent accidental cycles (tree->updater->tree)
|
||||
a.updateListener.Update(a)
|
||||
}()
|
||||
|
||||
a.changeBuilder.Init(a.aclState, a.fullTree, a.accountData)
|
||||
err := build(a.changeBuilder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch, marshalled, err := a.changeBuilder.BuildAndApply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.fullTree.AddFast(ch)
|
||||
rawCh := &aclpb.RawChange{
|
||||
Payload: marshalled,
|
||||
Signature: ch.Signature(),
|
||||
Id: ch.Id,
|
||||
}
|
||||
|
||||
err = a.treeStorage.AddRawChange(rawCh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = a.treeStorage.SetHeads([]string{ch.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rawCh, nil
|
||||
}
|
||||
|
||||
func (a *aclTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (AddResult, error) {
|
||||
// TODO: make proper error handling, because there are a lot of corner cases where this will break
|
||||
var err error
|
||||
var mode Mode
|
||||
|
||||
var changes []*Change // TODO: = addChangesBuf[:0] ...
|
||||
for _, ch := range rawChanges {
|
||||
change, err := NewFromRawChange(ch)
|
||||
// TODO: think what if we will have incorrect signatures on rawChanges, how everything will work
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
changes = append(changes, change)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.removeOrphans()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.treeStorage.SetHeads(a.fullTree.Heads())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case Append:
|
||||
a.updateListener.Update(a)
|
||||
case Rebuild:
|
||||
a.updateListener.Rebuild(a)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}()
|
||||
|
||||
getAddedChanges := func() []*aclpb.RawChange {
|
||||
var added []*aclpb.RawChange
|
||||
for _, ch := range rawChanges {
|
||||
if _, exists := a.fullTree.attached[ch.Id]; exists {
|
||||
added = append(added, ch)
|
||||
}
|
||||
}
|
||||
return added
|
||||
}
|
||||
|
||||
for _, ch := range changes {
|
||||
err = a.treeStorage.AddChange(ch)
|
||||
if err != nil {
|
||||
return AddResult{}, err
|
||||
}
|
||||
err = a.treeStorage.AddOrphans(ch.Id)
|
||||
if err != nil {
|
||||
return AddResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
prevHeads := a.fullTree.Heads()
|
||||
mode = a.fullTree.Add(changes...)
|
||||
switch mode {
|
||||
case Nothing:
|
||||
return AddResult{
|
||||
OldHeads: prevHeads,
|
||||
Heads: prevHeads,
|
||||
Summary: AddResultSummaryNothing,
|
||||
}, nil
|
||||
|
||||
case Rebuild:
|
||||
err = a.rebuildFromStorage(false)
|
||||
if err != nil {
|
||||
return AddResult{}, err
|
||||
}
|
||||
|
||||
return AddResult{
|
||||
OldHeads: prevHeads,
|
||||
Heads: a.fullTree.Heads(),
|
||||
Added: getAddedChanges(),
|
||||
Summary: AddResultSummaryRebuild,
|
||||
}, nil
|
||||
default:
|
||||
// just rebuilding the state from start without reloading everything from tree storage
|
||||
// as an optimization we could've started from current heads, but I didn't implement that
|
||||
a.aclState, err = a.aclStateBuilder.Build()
|
||||
if err != nil {
|
||||
return AddResult{}, err
|
||||
}
|
||||
|
||||
return AddResult{
|
||||
OldHeads: prevHeads,
|
||||
Heads: a.fullTree.Heads(),
|
||||
Added: getAddedChanges(),
|
||||
Summary: AddResultSummaryAppend,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclTree) Iterate(f func(change *Change) bool) {
|
||||
a.fullTree.Iterate(a.fullTree.RootId(), f)
|
||||
}
|
||||
|
||||
func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) {
|
||||
a.fullTree.Iterate(s, f)
|
||||
}
|
||||
|
||||
func (a *aclTree) HasChange(s string) bool {
|
||||
_, attachedExists := a.fullTree.attached[s]
|
||||
_, unattachedExists := a.fullTree.unAttached[s]
|
||||
_, invalidExists := a.fullTree.invalidChanges[s]
|
||||
return attachedExists || unattachedExists || invalidExists
|
||||
}
|
||||
|
||||
func (a *aclTree) Heads() []string {
|
||||
return a.fullTree.Heads()
|
||||
}
|
||||
|
||||
func (a *aclTree) Root() *Change {
|
||||
return a.fullTree.Root()
|
||||
}
|
||||
|
||||
func (a *aclTree) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *aclTree) SnapshotPath() []string {
|
||||
// TODO: think about caching this
|
||||
|
||||
var path []string
|
||||
// TODO: think that the user may have not all of the snapshots locally
|
||||
currentSnapshotId := a.fullTree.RootId()
|
||||
for currentSnapshotId != "" {
|
||||
sn, err := a.treeBuilder.loadChange(currentSnapshotId)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
path = append(path, currentSnapshotId)
|
||||
currentSnapshotId = sn.SnapshotId
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (a *aclTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawChange, error) {
|
||||
// TODO: think about when the clients will have their full acl tree and thus full snapshots
|
||||
// but no changes after some of the snapshots
|
||||
|
||||
var (
|
||||
isNewDocument = len(theirPath) == 0
|
||||
ourPath = a.SnapshotPath()
|
||||
// by default returning everything we have
|
||||
commonSnapshot = ourPath[len(ourPath)-1] // TODO: root snapshot, probably it is better to have a specific method in treestorage
|
||||
err error
|
||||
)
|
||||
|
||||
// if this is non-empty request
|
||||
if !isNewDocument {
|
||||
commonSnapshot, err = a.commonSnapshotForTwoPaths(ourPath, theirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var rawChanges []*aclpb.RawChange
|
||||
// using custom load function to skip verification step and save raw changes
|
||||
load := func(id string) (*Change, error) {
|
||||
raw, err := a.treeStorage.GetChange(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aclChange, err := a.treeBuilder.makeUnverifiedACLChange(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := NewChange(id, aclChange)
|
||||
rawChanges = append(rawChanges, raw)
|
||||
return ch, nil
|
||||
}
|
||||
// we presume that we have everything after the common snapshot, though this may not be the case in case of clients and only ACL tree changes
|
||||
log.With(
|
||||
zap.Strings("heads", a.fullTree.Heads()),
|
||||
zap.String("breakpoint", commonSnapshot),
|
||||
zap.String("id", a.id)).
|
||||
Debug("getting all changes from common snapshot")
|
||||
_, err = a.treeBuilder.dfs(a.fullTree.Heads(), commonSnapshot, load)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNewDocument {
|
||||
// adding snapshot to raw changes
|
||||
_, err = load(commonSnapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
log.With(
|
||||
zap.Int("len(changes)", len(rawChanges)),
|
||||
zap.String("id", a.id)).
|
||||
Debug("returning all changes after common snapshot")
|
||||
|
||||
return rawChanges, nil
|
||||
}
|
||||
|
||||
func (a *aclTree) DebugDump() (string, error) {
|
||||
return a.fullTree.Graph()
|
||||
}
|
||||
|
||||
func (a *aclTree) commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
|
||||
var i int
|
||||
var j int
|
||||
log.With(zap.Strings("our path", ourPath), zap.Strings("their path", theirPath)).
|
||||
Debug("finding common snapshot for two paths")
|
||||
OuterLoop:
|
||||
// find starting point from the right
|
||||
for i = len(ourPath) - 1; i >= 0; i-- {
|
||||
for j = len(theirPath) - 1; j >= 0; j-- {
|
||||
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
|
||||
if ourPath[i] == theirPath[j] {
|
||||
break OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
if i < 0 || j < 0 {
|
||||
return "", ErrNoCommonSnapshot
|
||||
}
|
||||
// find last common element of the sequence moving from right to left
|
||||
for i >= 0 && j >= 0 {
|
||||
if ourPath[i] == theirPath[j] {
|
||||
i--
|
||||
j--
|
||||
}
|
||||
}
|
||||
return ourPath[i+1], nil
|
||||
}
|
||||
@ -1,262 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/treestoragebuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockListener struct{}
|
||||
|
||||
func (m *mockListener) Update(tree ACLTree) {}
|
||||
|
||||
func (m *mockListener) Rebuild(tree ACLTree) {}
|
||||
|
||||
func TestACLTree_UserJoinBuild(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("userjoinexample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
aclState := tree.ACLState()
|
||||
aId := keychain.GeneratedIdentities["A"]
|
||||
bId := keychain.GeneratedIdentities["B"]
|
||||
cId := keychain.GeneratedIdentities["C"]
|
||||
|
||||
assert.Equal(t, aclState.identity, aId)
|
||||
assert.Equal(t, aclState.userStates[aId].Permissions, aclpb.ACLChange_Admin)
|
||||
assert.Equal(t, aclState.userStates[bId].Permissions, aclpb.ACLChange_Writer)
|
||||
assert.Equal(t, aclState.userStates[cId].Permissions, aclpb.ACLChange_Reader)
|
||||
|
||||
var changeIds []string
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, changeIds, []string{"A.1.1", "A.1.2", "B.1.1", "B.1.2"})
|
||||
}
|
||||
|
||||
func TestACLTree_UserJoinUpdate_Append(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("userjoinexampleupdate.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
rawChanges := thr.GetUpdates("append")
|
||||
res, err := tree.AddRawChanges(context.Background(), rawChanges...)
|
||||
assert.Equal(t, res.Summary, AddResultSummaryAppend)
|
||||
|
||||
aclState := tree.ACLState()
|
||||
aId := keychain.GeneratedIdentities["A"]
|
||||
bId := keychain.GeneratedIdentities["B"]
|
||||
cId := keychain.GeneratedIdentities["C"]
|
||||
dId := keychain.GeneratedIdentities["D"]
|
||||
|
||||
assert.Equal(t, aclState.identity, aId)
|
||||
assert.Equal(t, aclState.userStates[aId].Permissions, aclpb.ACLChange_Admin)
|
||||
assert.Equal(t, aclState.userStates[bId].Permissions, aclpb.ACLChange_Writer)
|
||||
assert.Equal(t, aclState.userStates[cId].Permissions, aclpb.ACLChange_Reader)
|
||||
assert.Equal(t, aclState.userStates[dId].Permissions, aclpb.ACLChange_Writer)
|
||||
|
||||
var changeIds []string
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, changeIds, []string{"A.1.1", "A.1.2", "B.1.1", "B.1.2", "B.1.3", "A.1.4"})
|
||||
}
|
||||
|
||||
func TestACLTree_UserJoinUpdate_Rebuild(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("userjoinexampleupdate.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
rawChanges := thr.GetUpdates("rebuild")
|
||||
res, err := tree.AddRawChanges(context.Background(), rawChanges...)
|
||||
assert.Equal(t, res.Summary, AddResultSummaryRebuild)
|
||||
|
||||
aclState := tree.ACLState()
|
||||
aId := keychain.GeneratedIdentities["A"]
|
||||
bId := keychain.GeneratedIdentities["B"]
|
||||
cId := keychain.GeneratedIdentities["C"]
|
||||
dId := keychain.GeneratedIdentities["D"]
|
||||
|
||||
assert.Equal(t, aclState.identity, aId)
|
||||
assert.Equal(t, aclState.userStates[aId].Permissions, aclpb.ACLChange_Admin)
|
||||
assert.Equal(t, aclState.userStates[bId].Permissions, aclpb.ACLChange_Writer)
|
||||
assert.Equal(t, aclState.userStates[cId].Permissions, aclpb.ACLChange_Reader)
|
||||
assert.Equal(t, aclState.userStates[dId].Permissions, aclpb.ACLChange_Writer)
|
||||
|
||||
var changeIds []string
|
||||
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, changeIds, []string{"A.1.1", "A.1.2", "B.1.1", "B.1.2", "A.1.4"})
|
||||
}
|
||||
|
||||
func TestACLTree_UserRemoveBuild(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("userremoveexample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
aclState := tree.ACLState()
|
||||
aId := keychain.GeneratedIdentities["A"]
|
||||
|
||||
assert.Equal(t, aclState.identity, aId)
|
||||
assert.Equal(t, aclState.userStates[aId].Permissions, aclpb.ACLChange_Admin)
|
||||
|
||||
var changeIds []string
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, changeIds, []string{"A.1.1", "A.1.2", "B.1.1", "A.1.3", "A.1.4"})
|
||||
}
|
||||
|
||||
func TestACLTree_UserRemoveBeforeBuild(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("userremovebeforeexample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
aclState := tree.ACLState()
|
||||
for _, s := range []string{"A", "C", "E"} {
|
||||
assert.Equal(t, aclState.userStates[keychain.GetIdentity(s)].Permissions, aclpb.ACLChange_Admin)
|
||||
}
|
||||
assert.Equal(t, aclState.identity, keychain.GetIdentity("A"))
|
||||
assert.Nil(t, aclState.userStates[keychain.GetIdentity("B")])
|
||||
|
||||
var changeIds []string
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, changeIds, []string{"A.1.1", "B.1.1", "A.1.2", "A.1.3"})
|
||||
}
|
||||
|
||||
func TestACLTree_InvalidSnapshotBuild(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("invalidsnapshotexample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
aclState := tree.ACLState()
|
||||
for _, s := range []string{"A", "B", "C", "D", "E", "F"} {
|
||||
assert.Equal(t, aclState.userStates[keychain.GetIdentity(s)].Permissions, aclpb.ACLChange_Admin)
|
||||
}
|
||||
assert.Equal(t, aclState.identity, keychain.GetIdentity("A"))
|
||||
|
||||
var changeIds []string
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, []string{"A.1.1", "B.1.1", "A.1.2", "A.1.3", "B.1.2"}, changeIds)
|
||||
}
|
||||
|
||||
func TestACLTree_ValidSnapshotBuild(t *testing.T) {
|
||||
thr, err := treestoragebuilder.NewTreeStorageBuilderWithTestName("validsnapshotexample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keychain := thr.GetKeychain()
|
||||
accountData := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
listener := &mockListener{}
|
||||
tree, err := BuildACLTree(thr, accountData, listener)
|
||||
if err != nil {
|
||||
t.Fatalf("should Build acl ACLState without err: %v", err)
|
||||
}
|
||||
aclState := tree.ACLState()
|
||||
for _, s := range []string{"A", "B", "C", "D", "E", "F"} {
|
||||
assert.Equal(t, aclState.userStates[keychain.GetIdentity(s)].Permissions, aclpb.ACLChange_Admin)
|
||||
}
|
||||
assert.Equal(t, aclState.identity, keychain.GetIdentity("A"))
|
||||
|
||||
var changeIds []string
|
||||
tree.Iterate(func(c *Change) (isContinue bool) {
|
||||
changeIds = append(changeIds, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, []string{"A.1.2", "A.1.3", "B.1.2"}, changeIds)
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
)
|
||||
|
||||
type aclTreeBuilder struct {
|
||||
cache map[string]*Change
|
||||
identityKeys map[string]signingkey.PubKey
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder
|
||||
tree *Tree
|
||||
treeStorage treestorage.TreeStorage
|
||||
|
||||
*changeLoader
|
||||
}
|
||||
|
||||
func newACLTreeBuilder(t treestorage.TreeStorage, decoder signingkey.PubKeyDecoder) *aclTreeBuilder {
|
||||
return &aclTreeBuilder{
|
||||
signingPubKeyDecoder: decoder,
|
||||
treeStorage: t,
|
||||
changeLoader: newChangeLoader(
|
||||
t,
|
||||
decoder,
|
||||
NewACLChange),
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *aclTreeBuilder) Init() {
|
||||
tb.cache = make(map[string]*Change)
|
||||
tb.identityKeys = make(map[string]signingkey.PubKey)
|
||||
tb.tree = &Tree{}
|
||||
tb.changeLoader.Init(tb.cache, tb.identityKeys)
|
||||
}
|
||||
|
||||
func (tb *aclTreeBuilder) Build() (*Tree, error) {
|
||||
var headsAndOrphans []string
|
||||
orphans, err := tb.treeStorage.Orphans()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
heads, err := tb.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headsAndOrphans = append(headsAndOrphans, orphans...)
|
||||
headsAndOrphans = append(headsAndOrphans, heads...)
|
||||
aclHeads, err := tb.getACLHeads(headsAndOrphans)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tb.buildTreeFromStart(aclHeads); err != nil {
|
||||
return nil, fmt.Errorf("buildTree error: %v", err)
|
||||
}
|
||||
tb.cache = nil
|
||||
|
||||
return tb.tree, nil
|
||||
}
|
||||
|
||||
func (tb *aclTreeBuilder) buildTreeFromStart(heads []string) (err error) {
|
||||
changes, root, err := tb.dfsFromStart(heads)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tb.tree.AddFast(root)
|
||||
tb.tree.AddFast(changes...)
|
||||
return
|
||||
}
|
||||
|
||||
func (tb *aclTreeBuilder) dfsFromStart(heads []string) (buf []*Change, root *Change, err error) {
|
||||
var possibleRoots []*Change
|
||||
stack := make([]string, len(heads), len(heads)*2)
|
||||
copy(stack, heads)
|
||||
|
||||
buf = make([]*Change, 0, len(stack)*2)
|
||||
uniqMap := make(map[string]struct{})
|
||||
for len(stack) > 0 {
|
||||
id := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
if _, exists := uniqMap[id]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
ch, err := tb.loadChange(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
uniqMap[id] = struct{}{}
|
||||
buf = append(buf, ch)
|
||||
|
||||
for _, prev := range ch.PreviousIds {
|
||||
stack = append(stack, prev)
|
||||
}
|
||||
if len(ch.PreviousIds) == 0 {
|
||||
possibleRoots = append(possibleRoots, ch)
|
||||
}
|
||||
}
|
||||
header, err := tb.treeStorage.Header()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, r := range possibleRoots {
|
||||
if r.Id == header.FirstChangeId {
|
||||
return buf, r, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("could not find root change")
|
||||
}
|
||||
|
||||
func (tb *aclTreeBuilder) getACLHeads(heads []string) (aclTreeHeads []string, err error) {
|
||||
for _, head := range heads {
|
||||
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
|
||||
continue
|
||||
}
|
||||
precedingHeads, err := tb.getPrecedingACLHeads(head)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, aclHead := range precedingHeads {
|
||||
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
|
||||
continue
|
||||
}
|
||||
aclTreeHeads = append(aclTreeHeads, aclHead)
|
||||
}
|
||||
}
|
||||
|
||||
if len(aclTreeHeads) == 0 {
|
||||
return nil, fmt.Errorf("no usable ACL heads in tree storage")
|
||||
}
|
||||
return aclTreeHeads, nil
|
||||
}
|
||||
|
||||
func (tb *aclTreeBuilder) getPrecedingACLHeads(head string) ([]string, error) {
|
||||
headChange, err := tb.loadChange(head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if headChange.Content.GetAclData() != nil {
|
||||
return []string{head}, nil
|
||||
} else {
|
||||
return headChange.PreviousIds, nil
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
func CreateNewTreeStorageWithACL(
|
||||
acc *account.AccountData,
|
||||
build func(builder ChangeBuilder) error,
|
||||
create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
|
||||
bld := newChangeBuilder()
|
||||
bld.Init(
|
||||
newACLState(acc.Identity, acc.EncKey, signingkey.NewEd25519PubKeyDecoder()),
|
||||
&Tree{},
|
||||
acc)
|
||||
err := build(bld)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bld.SetMakeSnapshot(true)
|
||||
|
||||
change, payload, err := bld.BuildAndApply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawChange := &aclpb.RawChange{
|
||||
Payload: payload,
|
||||
Signature: change.Signature(),
|
||||
Id: change.CID(),
|
||||
}
|
||||
header, id, err := createTreeHeaderAndId(rawChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
thr, err := create(id, header, []*aclpb.RawChange{rawChange})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = thr.SetHeads([]string{change.CID()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return thr, nil
|
||||
}
|
||||
|
||||
func createTreeHeaderAndId(change *aclpb.RawChange) (*treepb.TreeHeader, string, error) {
|
||||
header := &treepb.TreeHeader{
|
||||
FirstChangeId: change.Id,
|
||||
IsWorkspace: false,
|
||||
}
|
||||
marshalledHeader, err := proto.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
treeId, err := cid.NewCIDFromBytes(marshalledHeader)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return header, treeId, nil
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/treestoragebuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_BuildTreeStorageWithACL(t *testing.T) {
|
||||
keychain := treestoragebuilder.NewKeychain()
|
||||
keychain.AddSigningKey("A")
|
||||
keychain.AddEncryptionKey("A")
|
||||
data := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
}
|
||||
thr, err := CreateNewTreeStorageWithACL(
|
||||
data,
|
||||
func(builder ChangeBuilder) error {
|
||||
return builder.UserAdd(
|
||||
keychain.GetIdentity("A"),
|
||||
keychain.EncryptionKeys["A"].GetPublic(),
|
||||
aclpb.ACLChange_Admin)
|
||||
},
|
||||
treestorage.NewInMemoryTreeStorage)
|
||||
if err != nil {
|
||||
t.Fatalf("build should not return error")
|
||||
}
|
||||
|
||||
heads, err := thr.Heads()
|
||||
if err != nil {
|
||||
t.Fatalf("should return heads: %v", err)
|
||||
}
|
||||
if len(heads) == 0 {
|
||||
t.Fatalf("tree storage should have non-empty heads")
|
||||
}
|
||||
|
||||
header, err := thr.Header()
|
||||
if err != nil {
|
||||
t.Fatalf("tree storage header should return without error: %v", err)
|
||||
}
|
||||
assert.Equal(t, heads[0], header.FirstChangeId)
|
||||
|
||||
treeId, err := thr.TreeID()
|
||||
if err != nil {
|
||||
t.Fatalf("tree id should return without error: %v", err)
|
||||
}
|
||||
assert.NotEmpty(t, treeId)
|
||||
ch, err := thr.GetChange(context.Background(), header.FirstChangeId)
|
||||
if err != nil {
|
||||
t.Fatalf("get change should not return error: %v", err)
|
||||
}
|
||||
|
||||
_, err = NewFromRawChange(ch)
|
||||
if err != nil {
|
||||
t.Fatalf("we should be able to unmarshall change: %v", err)
|
||||
}
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
)
|
||||
|
||||
type ChangeContent struct {
|
||||
ChangesData proto.Marshaler
|
||||
ACLData *aclpb.ACLChangeACLData
|
||||
Id string // TODO: this is just for testing, because id should be created automatically from content
|
||||
}
|
||||
|
||||
// Change is an abstract type for all types of changes
|
||||
type Change struct {
|
||||
Next []*Change
|
||||
Unattached []*Change
|
||||
PreviousIds []string
|
||||
Id string
|
||||
SnapshotId string
|
||||
IsSnapshot bool
|
||||
DecryptedDocumentChange []byte
|
||||
|
||||
Content *aclpb.ACLChange
|
||||
Sign []byte
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptContents(key *symmetric.Key) error {
|
||||
if ch.Content.ChangesData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
decrypted, err := key.Decrypt(ch.Content.ChangesData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt changes data: %w", err)
|
||||
}
|
||||
|
||||
ch.DecryptedDocumentChange = decrypted
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Change) IsACLChange() bool {
|
||||
return ch.Content.GetAclData() != nil
|
||||
}
|
||||
|
||||
func NewFromRawChange(rawChange *aclpb.RawChange) (*Change, error) {
|
||||
unmarshalled := &aclpb.ACLChange{}
|
||||
err := proto.Unmarshal(rawChange.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := NewChange(rawChange.Id, unmarshalled)
|
||||
ch.Sign = rawChange.Signature
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func NewChange(id string, ch *aclpb.ACLChange) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
PreviousIds: ch.TreeHeadIds,
|
||||
Id: id,
|
||||
Content: ch,
|
||||
SnapshotId: ch.SnapshotBaseId,
|
||||
IsSnapshot: ch.GetAclData().GetAclSnapshot() != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func NewACLChange(id string, ch *aclpb.ACLChange) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
PreviousIds: ch.AclHeadIds,
|
||||
Id: id,
|
||||
Content: ch,
|
||||
SnapshotId: ch.SnapshotBaseId,
|
||||
IsSnapshot: ch.GetAclData().GetAclSnapshot() != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Change) ProtoChange() *aclpb.ACLChange {
|
||||
return ch.Content
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptedChangeContent() []byte {
|
||||
return ch.DecryptedDocumentChange
|
||||
}
|
||||
|
||||
func (ch *Change) Signature() []byte {
|
||||
return ch.Sign
|
||||
}
|
||||
|
||||
func (ch *Change) CID() string {
|
||||
return ch.Id
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type changeLoader struct {
|
||||
cache map[string]*Change
|
||||
identityKeys map[string]signingkey.PubKey
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder
|
||||
treeStorage treestorage.TreeStorage
|
||||
changeCreator func(id string, ch *aclpb.ACLChange) *Change
|
||||
}
|
||||
|
||||
func newChangeLoader(
|
||||
treeStorage treestorage.TreeStorage,
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder,
|
||||
changeCreator func(id string, ch *aclpb.ACLChange) *Change) *changeLoader {
|
||||
return &changeLoader{
|
||||
signingPubKeyDecoder: signingPubKeyDecoder,
|
||||
treeStorage: treeStorage,
|
||||
changeCreator: changeCreator,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *changeLoader) Init(cache map[string]*Change,
|
||||
identityKeys map[string]signingkey.PubKey) {
|
||||
c.cache = cache
|
||||
c.identityKeys = identityKeys
|
||||
}
|
||||
|
||||
func (c *changeLoader) loadChange(id string) (ch *Change, err error) {
|
||||
if ch, ok := c.cache[id]; ok {
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// TODO: Add virtual changes logic
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
change, err := c.treeStorage.GetChange(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aclChange, err := c.makeVerifiedACLChange(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch = c.changeCreator(id, aclChange)
|
||||
c.cache[id] = ch
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (c *changeLoader) verify(identity string, payload, signature []byte) (isVerified bool, err error) {
|
||||
identityKey, exists := c.identityKeys[identity]
|
||||
if !exists {
|
||||
identityKey, err = c.signingPubKeyDecoder.DecodeFromString(identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.identityKeys[identity] = identityKey
|
||||
}
|
||||
return identityKey.Verify(payload, signature)
|
||||
}
|
||||
|
||||
func (c *changeLoader) makeVerifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.ACLChange, err error) {
|
||||
aclChange = new(aclpb.ACLChange)
|
||||
|
||||
// TODO: think what should we do with such cases, because this can be used by attacker to break our Tree
|
||||
if err = proto.Unmarshal(change.Payload, aclChange); err != nil {
|
||||
return
|
||||
}
|
||||
var verified bool
|
||||
verified, err = c.verify(aclChange.Identity, change.Payload, change.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !verified {
|
||||
err = fmt.Errorf("the signature of the payload cannot be verified")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeLoader) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.ACLChange, err error) {
|
||||
aclChange = new(aclpb.ACLChange)
|
||||
err = proto.Unmarshal(change.Payload, aclChange)
|
||||
return
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type snapshotValidator struct {
|
||||
aclTree *Tree
|
||||
identity string
|
||||
key encryptionkey.PrivKey
|
||||
decoder signingkey.PubKeyDecoder
|
||||
stateBuilder *aclStateBuilder
|
||||
}
|
||||
|
||||
func newSnapshotValidator(
|
||||
decoder signingkey.PubKeyDecoder,
|
||||
accountData *account.AccountData) *snapshotValidator {
|
||||
return &snapshotValidator{
|
||||
identity: accountData.Identity,
|
||||
key: accountData.EncKey,
|
||||
decoder: decoder,
|
||||
stateBuilder: newACLStateBuilder(decoder, accountData),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *snapshotValidator) Init(aclTree *Tree) error {
|
||||
s.aclTree = aclTree
|
||||
return s.stateBuilder.Init(aclTree)
|
||||
}
|
||||
|
||||
func (s *snapshotValidator) ValidateSnapshot(ch *Change) (bool, error) {
|
||||
st, found, err := s.stateBuilder.BuildBefore(ch.Id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, fmt.Errorf("didn't find snapshot in ACL Tree")
|
||||
}
|
||||
|
||||
otherSt, err := newACLStateFromSnapshotChange(ch.Content, s.identity, s.key, s.decoder)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return st.equal(otherSt), nil
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
package acltree
|
||||
|
||||
//func createTreeFromThread(t thread.Thread, fromStart bool) (*Tree, error) {
|
||||
// treeBuilder := newTreeBuilder(t, keys.NewEd25519Decoder())
|
||||
// treeBuilder.Init()
|
||||
// return treeBuilder.Build(fromStart)
|
||||
//}
|
||||
//
|
||||
//func TestACLTreeBuilder_UserJoinCorrectHeadsAndLen(t *testing.T) {
|
||||
// thread, err := threadbuilder.NewThreadBuilderWithTestName("threadbuilder/userjoinexample.yml")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// res, err := createTreeFromThread(thread)
|
||||
// if err != nil {
|
||||
// t.Fatalf("build Tree should not result in an error: %v", res)
|
||||
// }
|
||||
//
|
||||
// assert.equal(t, res.Heads(), []string{"C.1.1"})
|
||||
// assert.equal(t, res.Len(), 4)
|
||||
//}
|
||||
//
|
||||
//func TestTreeBuilder_UserJoinTestTreeIterate(t *testing.T) {
|
||||
// thread, err := threadbuilder.NewThreadBuilderWithTestName("threadbuilder/userjoinexample.yml")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// res, err := createTreeFromThread(thread)
|
||||
// if err != nil {
|
||||
// t.Fatalf("build Tree should not result in an error: %v", res)
|
||||
// }
|
||||
//
|
||||
// assert.equal(t, res.Heads(), []string{"C.1.1"})
|
||||
// assert.equal(t, res.Len(), 4)
|
||||
// var changeIds []string
|
||||
// res.iterate(res.root, func(c *Change) (isContinue bool) {
|
||||
// changeIds = append(changeIds, c.Id)
|
||||
// return true
|
||||
// })
|
||||
// assert.equal(t, changeIds, []string{"A.1.1", "A.1.2", "B.1.1", "C.1.1"})
|
||||
//}
|
||||
//
|
||||
//func TestTreeBuilder_UserRemoveTestTreeIterate(t *testing.T) {
|
||||
// thread, err := threadbuilder.NewThreadBuilderWithTestName("threadbuilder/userremoveexample.yml")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// res, err := createTreeFromThread(thread)
|
||||
// if err != nil {
|
||||
// t.Fatalf("build Tree should not result in an error: %v", res)
|
||||
// }
|
||||
//
|
||||
// assert.equal(t, res.Heads(), []string{"A.1.3"})
|
||||
// assert.equal(t, res.Len(), 4)
|
||||
// var changeIds []string
|
||||
// res.iterate(res.root, func(c *Change) (isContinue bool) {
|
||||
// changeIds = append(changeIds, c.Id)
|
||||
// return true
|
||||
// })
|
||||
// assert.equal(t, changeIds, []string{"A.1.1", "A.1.2", "B.1.1", "A.1.3"})
|
||||
//}
|
||||
@ -1,158 +0,0 @@
|
||||
package acltree
|
||||
|
||||
import "sync"
|
||||
|
||||
var itPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &iterator{}
|
||||
},
|
||||
}
|
||||
|
||||
func newIterator() *iterator {
|
||||
return itPool.Get().(*iterator)
|
||||
}
|
||||
|
||||
func freeIterator(i *iterator) {
|
||||
itPool.Put(i)
|
||||
}
|
||||
|
||||
type iterator struct {
|
||||
compBuf []*Change
|
||||
queue []*Change
|
||||
doneMap map[*Change]struct{}
|
||||
breakpoint *Change
|
||||
f func(c *Change) bool
|
||||
}
|
||||
|
||||
func (i *iterator) iterateSkip(start *Change, skipBefore *Change, f func(c *Change) (isContinue bool)) {
|
||||
skipping := true
|
||||
i.iterate(start, func(c *Change) (isContinue bool) {
|
||||
if skipping && c != skipBefore {
|
||||
return true
|
||||
}
|
||||
skipping = false
|
||||
return f(c)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *iterator) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||
if start == nil {
|
||||
return
|
||||
}
|
||||
// reset
|
||||
i.queue = i.queue[:0]
|
||||
i.compBuf = i.compBuf[:0]
|
||||
i.doneMap = make(map[*Change]struct{})
|
||||
i.queue = append(i.queue, start)
|
||||
i.breakpoint = nil
|
||||
i.f = f
|
||||
|
||||
for len(i.queue) > 0 {
|
||||
c := i.queue[0]
|
||||
i.queue = i.queue[1:]
|
||||
nl := len(c.Next)
|
||||
if nl == 1 {
|
||||
if !i.iterateLin(c) {
|
||||
return
|
||||
}
|
||||
if i.breakpoint != nil {
|
||||
i.toQueue(i.breakpoint)
|
||||
i.breakpoint = nil
|
||||
}
|
||||
} else {
|
||||
_, done := i.doneMap[c]
|
||||
if !done {
|
||||
if !f(c) {
|
||||
return
|
||||
}
|
||||
i.doneMap[c] = struct{}{}
|
||||
}
|
||||
if nl != 0 {
|
||||
for _, next := range c.Next {
|
||||
i.toQueue(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *iterator) iterateLin(c *Change) bool {
|
||||
for len(c.Next) == 1 {
|
||||
_, done := i.doneMap[c]
|
||||
if !done {
|
||||
if !i.f(c) {
|
||||
return false
|
||||
}
|
||||
i.doneMap[c] = struct{}{}
|
||||
}
|
||||
|
||||
c = c.Next[0]
|
||||
if len(c.PreviousIds) > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(c.Next) == 0 && len(c.PreviousIds) <= 1 {
|
||||
if !i.f(c) {
|
||||
return false
|
||||
}
|
||||
i.doneMap[c] = struct{}{}
|
||||
} else {
|
||||
i.breakpoint = c
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *iterator) comp(c1, c2 *Change) uint8 {
|
||||
if c1.Id == c2.Id {
|
||||
return 0
|
||||
}
|
||||
i.compBuf = i.compBuf[:0]
|
||||
i.compBuf = append(i.compBuf, c1.Next...)
|
||||
var uniq = make(map[*Change]struct{})
|
||||
var appendUniqueToBuf = func(next []*Change) {
|
||||
for _, n := range next {
|
||||
if _, ok := uniq[n]; !ok {
|
||||
i.compBuf = append(i.compBuf, n)
|
||||
uniq[n] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
var used int
|
||||
for len(i.compBuf)-used > 0 {
|
||||
l := len(i.compBuf) - used
|
||||
for _, n := range i.compBuf[used:] {
|
||||
delete(uniq, n)
|
||||
if n.Id == c2.Id {
|
||||
return 1
|
||||
} else {
|
||||
appendUniqueToBuf(n.Next)
|
||||
}
|
||||
}
|
||||
used += l
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i *iterator) toQueue(c *Change) {
|
||||
var pos = -1
|
||||
For:
|
||||
for idx, qc := range i.queue {
|
||||
switch i.comp(c, qc) {
|
||||
// exists
|
||||
case 0:
|
||||
return
|
||||
//
|
||||
case 1:
|
||||
pos = idx
|
||||
break For
|
||||
}
|
||||
}
|
||||
if pos == -1 {
|
||||
i.queue = append(i.queue, c)
|
||||
} else if pos == 0 {
|
||||
i.queue = append([]*Change{c}, i.queue...)
|
||||
} else {
|
||||
i.queue = append(i.queue[:pos], append([]*Change{c}, i.queue[pos:]...)...)
|
||||
}
|
||||
}
|
||||
@ -1,169 +0,0 @@
|
||||
package plaintextdocument
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
aclpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type PlainTextDocument interface {
|
||||
Text() string
|
||||
AddText(ctx context.Context, text string) error
|
||||
}
|
||||
|
||||
// TODO: this struct is not thread-safe, so use it wisely :-)
|
||||
type plainTextDocument struct {
|
||||
heads []string
|
||||
aclTree acltree.ACLTree
|
||||
state *DocumentState
|
||||
}
|
||||
|
||||
func (p *plainTextDocument) Text() string {
|
||||
if p.state != nil {
|
||||
return p.state.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *plainTextDocument) AddText(ctx context.Context, text string) error {
|
||||
_, err := p.aclTree.AddContent(ctx, func(builder acltree.ChangeBuilder) error {
|
||||
builder.AddChangeContent(
|
||||
&testchangepb.PlainTextChangeData{
|
||||
Content: []*testchangepb.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
})
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *plainTextDocument) Update(tree acltree.ACLTree) {
|
||||
p.aclTree = tree
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
fmt.Println("rebuild has returned error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
prevHeads := p.heads
|
||||
p.heads = tree.Heads()
|
||||
startId := prevHeads[0]
|
||||
tree.IterateFrom(startId, func(change *acltree.Change) (isContinue bool) {
|
||||
if change.Id == startId {
|
||||
return true
|
||||
}
|
||||
if change.DecryptedDocumentChange != nil {
|
||||
p.state, err = p.state.ApplyChange(change.DecryptedDocumentChange, change.Id)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (p *plainTextDocument) Rebuild(tree acltree.ACLTree) {
|
||||
p.aclTree = tree
|
||||
p.heads = tree.Heads()
|
||||
var startId string
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
fmt.Println("rebuild has returned error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
rootChange := tree.Root()
|
||||
|
||||
if rootChange.DecryptedDocumentChange == nil {
|
||||
err = fmt.Errorf("root doesn't have decrypted change")
|
||||
return
|
||||
}
|
||||
|
||||
state, err := BuildDocumentStateFromChange(rootChange.DecryptedDocumentChange, rootChange.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
startId = rootChange.Id
|
||||
tree.Iterate(func(change *acltree.Change) (isContinue bool) {
|
||||
if startId == change.Id {
|
||||
return true
|
||||
}
|
||||
if change.DecryptedDocumentChange != nil {
|
||||
state, err = state.ApplyChange(change.DecryptedDocumentChange, change.Id)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.state = state
|
||||
}
|
||||
|
||||
func NewInMemoryPlainTextDocument(acc *account.AccountData, text string) (PlainTextDocument, error) {
|
||||
return NewPlainTextDocument(acc, treestorage.NewInMemoryTreeStorage, text)
|
||||
}
|
||||
|
||||
func NewPlainTextDocument(
|
||||
acc *account.AccountData,
|
||||
create treestorage.CreatorFunc,
|
||||
text string) (PlainTextDocument, error) {
|
||||
changeBuilder := func(builder acltree.ChangeBuilder) error {
|
||||
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
builder.AddChangeContent(createInitialChangeContent(text))
|
||||
return nil
|
||||
}
|
||||
t, err := acltree.CreateNewTreeStorageWithACL(
|
||||
acc,
|
||||
changeBuilder,
|
||||
create)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
doc := &plainTextDocument{
|
||||
heads: nil,
|
||||
aclTree: nil,
|
||||
state: nil,
|
||||
}
|
||||
tree, err := acltree.BuildACLTree(t, acc, doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc.aclTree = tree
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func createInitialChangeContent(text string) proto.Marshaler {
|
||||
return &testchangepb.PlainTextChangeData{
|
||||
Content: []*testchangepb.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
Snapshot: &testchangepb.PlainTextChangeSnapshot{Text: text},
|
||||
}
|
||||
}
|
||||
|
||||
func createAppendTextChangeContent(text string) *testchangepb.PlainTextChangeContent {
|
||||
return &testchangepb.PlainTextChangeContent{
|
||||
Value: &testchangepb.PlainTextChangeContentValueOfTextAppend{
|
||||
TextAppend: &testchangepb.PlainTextChangeTextAppend{
|
||||
Text: text,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package plaintextdocument
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/treestoragebuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDocument_NewPlainTextDocument(t *testing.T) {
|
||||
keychain := treestoragebuilder.NewKeychain()
|
||||
keychain.AddSigningKey("A")
|
||||
keychain.AddEncryptionKey("A")
|
||||
data := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
|
||||
doc, err := NewPlainTextDocument(data, treestorage.NewInMemoryTreeStorage, "Some text")
|
||||
if err != nil {
|
||||
t.Fatalf("should not create document with error: %v", err)
|
||||
}
|
||||
assert.Equal(t, doc.Text(), "Some text")
|
||||
}
|
||||
|
||||
func TestDocument_PlainTextDocument_AddText(t *testing.T) {
|
||||
keychain := treestoragebuilder.NewKeychain()
|
||||
keychain.AddSigningKey("A")
|
||||
keychain.AddEncryptionKey("A")
|
||||
data := &account.AccountData{
|
||||
Identity: keychain.GetIdentity("A"),
|
||||
SignKey: keychain.SigningKeys["A"],
|
||||
EncKey: keychain.EncryptionKeys["A"],
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
}
|
||||
|
||||
doc, err := NewPlainTextDocument(data, treestorage.NewInMemoryTreeStorage, "Some text")
|
||||
if err != nil {
|
||||
t.Fatalf("should not create document with error: %v", err)
|
||||
}
|
||||
|
||||
err = doc.AddText(context.Background(), "Next")
|
||||
if err != nil {
|
||||
t.Fatalf("should be able to add document: %v", err)
|
||||
}
|
||||
assert.Equal(t, doc.Text(), "Some text|Next")
|
||||
|
||||
err = doc.AddText(context.Background(), "Shmext")
|
||||
if err != nil {
|
||||
t.Fatalf("should be able to add document: %v", err)
|
||||
}
|
||||
assert.Equal(t, doc.Text(), "Some text|Next|Shmext")
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package plaintextdocument
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type DocumentState struct {
|
||||
LastChangeId string
|
||||
Text string
|
||||
}
|
||||
|
||||
func NewDocumentState(text string, id string) *DocumentState {
|
||||
return &DocumentState{
|
||||
LastChangeId: id,
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildDocumentStateFromChange(change []byte, id string) (*DocumentState, error) {
|
||||
var changesData testchangepb.PlainTextChangeData
|
||||
err := proto.Unmarshal(change, &changesData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if changesData.GetSnapshot() == nil {
|
||||
return nil, fmt.Errorf("could not create state from empty snapshot")
|
||||
}
|
||||
return NewDocumentState(changesData.GetSnapshot().GetText(), id), nil
|
||||
}
|
||||
|
||||
func (p *DocumentState) ApplyChange(change []byte, id string) (*DocumentState, error) {
|
||||
var changesData testchangepb.PlainTextChangeData
|
||||
err := proto.Unmarshal(change, &changesData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, content := range changesData.GetContent() {
|
||||
err = p.applyChange(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
p.LastChangeId = id
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *DocumentState) applyChange(ch *testchangepb.PlainTextChangeContent) error {
|
||||
switch {
|
||||
case ch.GetTextAppend() != nil:
|
||||
text := ch.GetTextAppend().GetText()
|
||||
p.Text += "|" + text
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,135 +1,181 @@
|
||||
package acltree
|
||||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
"hash/fnv"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed("acllist").Sugar()
|
||||
|
||||
var ErrNoSuchUser = errors.New("no such user")
|
||||
var ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||
var ErrUserRemoved = errors.New("user was removed from the document")
|
||||
var ErrDocumentForbidden = errors.New("your user was forbidden access to the document")
|
||||
var ErrUserAlreadyExists = errors.New("user already exists")
|
||||
var ErrNoSuchRecord = errors.New("no such record")
|
||||
var ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||
var ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||
|
||||
type UserPermissionPair struct {
|
||||
Identity string
|
||||
Permission aclpb.ACLChangeUserPermissions
|
||||
}
|
||||
|
||||
type ACLState struct {
|
||||
currentReadKeyHash uint64
|
||||
userReadKeys map[uint64]*symmetric.Key
|
||||
userStates map[string]*aclpb.ACLChangeUserState
|
||||
userInvites map[string]*aclpb.ACLChangeUserInvite
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder
|
||||
signingPubKeyDecoder keys.Decoder
|
||||
encryptionKey encryptionkey.PrivKey
|
||||
identity string
|
||||
permissionsAtRecord map[string][]UserPermissionPair
|
||||
}
|
||||
|
||||
func newACLState(
|
||||
func newACLStateWithIdentity(
|
||||
identity string,
|
||||
encryptionKey encryptionkey.PrivKey,
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder) *ACLState {
|
||||
decoder keys.Decoder) *ACLState {
|
||||
return &ACLState{
|
||||
identity: identity,
|
||||
encryptionKey: encryptionKey,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclpb.ACLChangeUserState),
|
||||
userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
|
||||
signingPubKeyDecoder: signingPubKeyDecoder,
|
||||
signingPubKeyDecoder: decoder,
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
}
|
||||
}
|
||||
|
||||
func newACLStateFromSnapshotChange(
|
||||
snapshotChange *aclpb.ACLChange,
|
||||
identity string,
|
||||
encryptionKey encryptionkey.PrivKey,
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder) (*ACLState, error) {
|
||||
st := &ACLState{
|
||||
identity: identity,
|
||||
encryptionKey: encryptionKey,
|
||||
func newACLState(decoder keys.Decoder) *ACLState {
|
||||
return &ACLState{
|
||||
signingPubKeyDecoder: decoder,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclpb.ACLChangeUserState),
|
||||
userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
|
||||
signingPubKeyDecoder: signingPubKeyDecoder,
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
}
|
||||
err := st.recreateFromSnapshotChange(snapshotChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (st *ACLState) recreateFromSnapshotChange(snapshotChange *aclpb.ACLChange) error {
|
||||
snapshot := snapshotChange.GetAclData().GetAclSnapshot()
|
||||
if snapshot == nil {
|
||||
return fmt.Errorf("could not create state from snapshot, because it is nil")
|
||||
}
|
||||
state := snapshot.AclState
|
||||
for _, userState := range state.UserStates {
|
||||
st.userStates[userState.Identity] = userState
|
||||
}
|
||||
func (st *ACLState) CurrentReadKeyHash() uint64 {
|
||||
return st.currentReadKeyHash
|
||||
}
|
||||
|
||||
userState, exists := st.userStates[st.identity]
|
||||
func (st *ACLState) CurrentReadKey() (*symmetric.Key, error) {
|
||||
key, exists := st.userReadKeys[st.currentReadKeyHash]
|
||||
if !exists {
|
||||
return ErrNoSuchUser
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
for _, key := range userState.EncryptedReadKeys {
|
||||
key, hash, err := st.decryptReadKeyAndHash(key)
|
||||
if err != nil {
|
||||
return ErrFailedToDecrypt
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (st *ACLState) UserReadKeys() map[uint64]*symmetric.Key {
|
||||
return st.userReadKeys
|
||||
}
|
||||
|
||||
func (st *ACLState) PermissionsAtRecord(id string, identity string) (UserPermissionPair, error) {
|
||||
permissions, ok := st.permissionsAtRecord[id]
|
||||
if !ok {
|
||||
log.Errorf("missing record at id %s", id)
|
||||
return UserPermissionPair{}, ErrNoSuchRecord
|
||||
}
|
||||
|
||||
for _, perm := range permissions {
|
||||
if perm.Identity == identity {
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
st.userReadKeys[hash] = key
|
||||
}
|
||||
st.currentReadKeyHash = snapshotChange.CurrentReadKeyHash
|
||||
if snapshot.GetAclState().GetInvites() != nil {
|
||||
st.userInvites = snapshot.GetAclState().GetInvites()
|
||||
}
|
||||
return nil
|
||||
return UserPermissionPair{}, ErrNoSuchUser
|
||||
}
|
||||
|
||||
func (st *ACLState) makeSnapshot() *aclpb.ACLChangeACLSnapshot {
|
||||
var userStates []*aclpb.ACLChangeUserState
|
||||
for _, st := range st.userStates {
|
||||
userStates = append(userStates, st)
|
||||
func (st *ACLState) applyRecord(record *aclpb.Record) (err error) {
|
||||
// TODO: this should be probably changed
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
|
||||
err = proto.Unmarshal(record.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return &aclpb.ACLChangeACLSnapshot{AclState: &aclpb.ACLChangeACLState{
|
||||
ReadKeyHashes: nil,
|
||||
UserStates: userStates, // TODO: make states and invites in same format
|
||||
Invites: st.userInvites,
|
||||
}}
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChange(change *aclpb.ACLChange) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.currentReadKeyHash = change.CurrentReadKeyHash
|
||||
st.currentReadKeyHash = record.CurrentReadKeyHash
|
||||
}()
|
||||
|
||||
return st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChangeAndUpdate(recordWrapper *Record) (err error) {
|
||||
change := recordWrapper.Content
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
|
||||
if recordWrapper.ParsedModel != nil {
|
||||
aclData = recordWrapper.ParsedModel.(*aclpb.ACLChangeACLData)
|
||||
} else {
|
||||
err = proto.Unmarshal(change.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
recordWrapper.ParsedModel = aclData
|
||||
}
|
||||
|
||||
err = st.applyChangeData(aclData, recordWrapper.Content.CurrentReadKeyHash, recordWrapper.Content.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var permissions []UserPermissionPair
|
||||
for _, state := range st.userStates {
|
||||
permission := UserPermissionPair{
|
||||
Identity: state.Identity,
|
||||
Permission: state.Permissions,
|
||||
}
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
st.permissionsAtRecord[recordWrapper.Id] = permissions
|
||||
log.Infof("adding permissions at record %s", recordWrapper.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uint64, identity string) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.currentReadKeyHash = hash
|
||||
}()
|
||||
|
||||
// we can't check this for the user which is joining, because it will not be in our list
|
||||
// the same is for the first change to be added
|
||||
skipIdentityCheck := st.isUserJoin(change) || (st.currentReadKeyHash == 0 && st.isUserAdd(change))
|
||||
skipIdentityCheck := st.isUserJoin(changeData) || (st.currentReadKeyHash == 0 && st.isUserAdd(changeData, identity))
|
||||
if !skipIdentityCheck {
|
||||
// we check signature when we add this to the Tree, so no need to do it here
|
||||
if _, exists := st.userStates[change.Identity]; !exists {
|
||||
if _, exists := st.userStates[identity]; !exists {
|
||||
err = ErrNoSuchUser
|
||||
return
|
||||
}
|
||||
|
||||
if !st.hasPermission(change.Identity, aclpb.ACLChange_Admin) {
|
||||
err = fmt.Errorf("user %s must have admin permissions", change.Identity)
|
||||
if !st.hasPermission(identity, aclpb.ACLChange_Admin) {
|
||||
err = fmt.Errorf("user %s must have admin permissions", identity)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range change.GetAclData().GetAclContent() {
|
||||
for _, ch := range changeData.GetAclContent() {
|
||||
if err = st.applyChangeContent(ch); err != nil {
|
||||
//log.Infof("error while applying changes: %v; ignore", err)
|
||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -137,7 +183,6 @@ func (st *ACLState) applyChange(change *aclpb.ACLChange) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: remove changeId, because it is not needed
|
||||
func (st *ACLState) applyChangeContent(ch *aclpb.ACLChangeACLContentValue) error {
|
||||
switch {
|
||||
case ch.GetUserPermissionChange() != nil:
|
||||
@ -193,7 +238,7 @@ func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error {
|
||||
return fmt.Errorf("failed to decode signing identity as bytes")
|
||||
}
|
||||
|
||||
res, err := verificationKey.Verify(rawSignedId, signature)
|
||||
res, err := verificationKey.(signingkey.PubKey).Verify(rawSignedId, signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verification returned error: %w", err)
|
||||
}
|
||||
@ -318,15 +363,15 @@ func (st *ACLState) hasPermission(identity string, permission aclpb.ACLChangeUse
|
||||
return state.Permissions == permission
|
||||
}
|
||||
|
||||
func (st *ACLState) isUserJoin(ch *aclpb.ACLChange) bool {
|
||||
func (st *ACLState) isUserJoin(data *aclpb.ACLChangeACLData) bool {
|
||||
// if we have a UserJoin, then it should always be the first one applied
|
||||
return ch.AclData.GetAclContent() != nil && ch.AclData.GetAclContent()[0].GetUserJoin() != nil
|
||||
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
||||
}
|
||||
|
||||
func (st *ACLState) isUserAdd(ch *aclpb.ACLChange) bool {
|
||||
func (st *ACLState) isUserAdd(data *aclpb.ACLChangeACLData, identity string) bool {
|
||||
// if we have a UserAdd, then it should always be the first one applied
|
||||
userAdd := ch.AclData.GetAclContent()[0].GetUserAdd()
|
||||
return ch.AclData.GetAclContent() != nil && userAdd != nil && userAdd.GetIdentity() == ch.Identity
|
||||
userAdd := data.GetAclContent()[0].GetUserAdd()
|
||||
return data.GetAclContent() != nil && userAdd != nil && userAdd.GetIdentity() == identity
|
||||
}
|
||||
|
||||
func (st *ACLState) getPermissionDecreasedUsers(ch *aclpb.ACLChange) (identities []*aclpb.ACLChangeUserPermissionChange) {
|
||||
@ -409,3 +454,7 @@ func (st *ACLState) GetUserStates() map[string]*aclpb.ACLChangeUserState {
|
||||
// TODO: we should provide better API that would not allow to change this map from the outside
|
||||
return st.userStates
|
||||
}
|
||||
|
||||
func (st *ACLState) isNodeIdentity() bool {
|
||||
return st.identity == ""
|
||||
}
|
||||
48
pkg/acl/list/aclstatebuilder.go
Normal file
48
pkg/acl/list/aclstatebuilder.go
Normal file
@ -0,0 +1,48 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
)
|
||||
|
||||
type aclStateBuilder struct {
|
||||
identity string
|
||||
key encryptionkey.PrivKey
|
||||
decoder keys.Decoder
|
||||
}
|
||||
|
||||
func newACLStateBuilderWithIdentity(decoder keys.Decoder, accountData *account.AccountData) *aclStateBuilder {
|
||||
return &aclStateBuilder{
|
||||
decoder: decoder,
|
||||
identity: accountData.Identity,
|
||||
key: accountData.EncKey,
|
||||
}
|
||||
}
|
||||
|
||||
func newACLStateBuilder(decoder keys.Decoder) *aclStateBuilder {
|
||||
return &aclStateBuilder{
|
||||
decoder: decoder,
|
||||
}
|
||||
}
|
||||
|
||||
func (sb *aclStateBuilder) Build(records []*Record) (*ACLState, error) {
|
||||
var (
|
||||
err error
|
||||
state *ACLState
|
||||
)
|
||||
|
||||
if sb.key != nil {
|
||||
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
|
||||
} else {
|
||||
state = newACLState(sb.decoder)
|
||||
}
|
||||
for _, rec := range records {
|
||||
err = state.applyChangeAndUpdate(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return state, err
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package acltree
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
@ -15,35 +15,27 @@ type MarshalledChange = []byte
|
||||
|
||||
type ACLChangeBuilder interface {
|
||||
UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error
|
||||
AddId(id string) // TODO: this is only for testing
|
||||
SetMakeSnapshot(bool) // TODO: who should decide this? probably ACLTree so we can delete it
|
||||
AddId(id string) // TODO: this is only for testing
|
||||
}
|
||||
|
||||
type ChangeBuilder interface {
|
||||
ACLChangeBuilder
|
||||
AddChangeContent(marshaler proto.Marshaler) // user code should be responsible for making regular snapshots
|
||||
}
|
||||
|
||||
type changeBuilder struct {
|
||||
type aclChangeBuilder struct {
|
||||
aclState *ACLState
|
||||
tree *Tree
|
||||
list ACLList
|
||||
acc *account.AccountData
|
||||
|
||||
aclData *aclpb.ACLChangeACLData
|
||||
changeContent proto.Marshaler
|
||||
id string
|
||||
makeSnapshot bool
|
||||
readKey *symmetric.Key
|
||||
readKeyHash uint64
|
||||
aclData *aclpb.ACLChangeACLData
|
||||
id string
|
||||
readKey *symmetric.Key
|
||||
readKeyHash uint64
|
||||
}
|
||||
|
||||
func newChangeBuilder() *changeBuilder {
|
||||
return &changeBuilder{}
|
||||
func newACLChangeBuilder() *aclChangeBuilder {
|
||||
return &aclChangeBuilder{}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) {
|
||||
func (c *aclChangeBuilder) Init(state *ACLState, list ACLList, acc *account.AccountData) {
|
||||
c.aclState = state
|
||||
c.tree = tree
|
||||
c.list = list
|
||||
c.acc = acc
|
||||
|
||||
c.aclData = &aclpb.ACLChangeACLData{}
|
||||
@ -60,15 +52,11 @@ func (c *changeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountDa
|
||||
}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) AddId(id string) {
|
||||
func (c *aclChangeBuilder) AddId(id string) {
|
||||
c.id = id
|
||||
}
|
||||
|
||||
func (c *changeBuilder) SetMakeSnapshot(b bool) {
|
||||
c.makeSnapshot = b
|
||||
}
|
||||
|
||||
func (c *changeBuilder) UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error {
|
||||
func (c *aclChangeBuilder) UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error {
|
||||
var allKeys []*symmetric.Key
|
||||
if c.aclState.currentReadKeyHash != 0 {
|
||||
for _, key := range c.aclState.userReadKeys {
|
||||
@ -105,41 +93,25 @@ func (c *changeBuilder) UserAdd(identity string, encryptionKey encryptionkey.Pub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildAndApply() (*Change, []byte, error) {
|
||||
aclChange := &aclpb.ACLChange{
|
||||
TreeHeadIds: c.tree.Heads(),
|
||||
AclHeadIds: c.tree.ACLHeads(),
|
||||
SnapshotBaseId: c.tree.RootId(),
|
||||
AclData: c.aclData,
|
||||
func (c *aclChangeBuilder) BuildAndApply() (*Record, []byte, error) {
|
||||
aclRecord := &aclpb.Record{
|
||||
PrevId: c.list.Head().Id,
|
||||
CurrentReadKeyHash: c.readKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: c.acc.Identity,
|
||||
}
|
||||
err := c.aclState.applyChange(aclChange)
|
||||
|
||||
marshalledData, err := proto.Marshal(c.aclData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
aclRecord.Data = marshalledData
|
||||
err = c.aclState.applyRecord(aclRecord)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if c.makeSnapshot {
|
||||
c.aclData.AclSnapshot = c.aclState.makeSnapshot()
|
||||
}
|
||||
|
||||
var marshalled []byte
|
||||
if c.changeContent != nil {
|
||||
marshalled, err = c.changeContent.Marshal()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
encrypted, err := c.aclState.userReadKeys[c.aclState.currentReadKeyHash].
|
||||
Encrypt(marshalled)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
aclChange.ChangesData = encrypted
|
||||
}
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||
fullMarshalledChange, err := proto.Marshal(aclRecord)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -151,13 +123,9 @@ func (c *changeBuilder) BuildAndApply() (*Change, []byte, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ch := NewChange(id, aclChange)
|
||||
ch.DecryptedDocumentChange = marshalled
|
||||
ch := NewRecord(id, aclRecord)
|
||||
ch.ParsedModel = c.aclData
|
||||
ch.Sign = signature
|
||||
|
||||
return ch, fullMarshalledChange, nil
|
||||
}
|
||||
|
||||
func (c *changeBuilder) AddChangeContent(marshaler proto.Marshaler) {
|
||||
c.changeContent = marshaler
|
||||
}
|
||||
179
pkg/acl/list/list.go
Normal file
179
pkg/acl/list/list.go
Normal file
@ -0,0 +1,179 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type IterFunc = func(record *Record) (IsContinue bool)
|
||||
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
RLock()
|
||||
RUnlock()
|
||||
}
|
||||
|
||||
type ACLList interface {
|
||||
RWLocker
|
||||
ID() string
|
||||
Header() *aclpb.Header
|
||||
Records() []*Record
|
||||
ACLState() *ACLState
|
||||
IsAfter(first string, second string) (bool, error)
|
||||
Head() *Record
|
||||
Get(id string) (*Record, error)
|
||||
Iterate(iterFunc IterFunc)
|
||||
IterateFrom(startId string, iterFunc IterFunc)
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type aclList struct {
|
||||
header *aclpb.Header
|
||||
records []*Record
|
||||
indexes map[string]int
|
||||
id string
|
||||
|
||||
builder *aclStateBuilder
|
||||
aclState *ACLState
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStorage) (ACLList, error) {
|
||||
builder := newACLStateBuilderWithIdentity(acc.Decoder, acc)
|
||||
return buildWithACLStateBuilder(builder, storage)
|
||||
}
|
||||
|
||||
func BuildACLList(decoder keys.Decoder, storage storage.ListStorage) (ACLList, error) {
|
||||
return buildWithACLStateBuilder(newACLStateBuilder(decoder), storage)
|
||||
}
|
||||
|
||||
func buildWithACLStateBuilder(builder *aclStateBuilder, storage storage.ListStorage) (ACLList, error) {
|
||||
header, err := storage.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := storage.ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawRecord, err := storage.Head()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record, err := NewFromRawRecord(rawRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records := []*Record{record}
|
||||
|
||||
for record.Content.PrevId != "" {
|
||||
rawRecord, err = storage.GetRawRecord(context.Background(), record.Content.PrevId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record, err = NewFromRawRecord(rawRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
indexes := make(map[string]int)
|
||||
for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 {
|
||||
records[i], records[j] = records[j], records[i]
|
||||
indexes[records[i].Id] = i
|
||||
indexes[records[j].Id] = j
|
||||
}
|
||||
// adding missed index if needed
|
||||
if len(records)%2 != 0 {
|
||||
indexes[records[len(records)/2].Id] = len(records) / 2
|
||||
}
|
||||
|
||||
log.With(zap.String("head id", records[len(records)-1].Id), zap.String("list id", id)).
|
||||
Info("building acl tree")
|
||||
state, err := builder.Build(records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &aclList{
|
||||
header: header,
|
||||
records: records,
|
||||
indexes: indexes,
|
||||
builder: builder,
|
||||
aclState: state,
|
||||
id: id,
|
||||
RWMutex: sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *aclList) Records() []*Record {
|
||||
return a.records
|
||||
}
|
||||
|
||||
func (a *aclList) ID() string {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *aclList) Header() *aclpb.Header {
|
||||
return a.header
|
||||
}
|
||||
|
||||
func (a *aclList) ACLState() *ACLState {
|
||||
return a.aclState
|
||||
}
|
||||
|
||||
func (a *aclList) IsAfter(first string, second string) (bool, error) {
|
||||
firstRec, okFirst := a.indexes[first]
|
||||
secondRec, okSecond := a.indexes[second]
|
||||
if !okFirst || !okSecond {
|
||||
return false, fmt.Errorf("not all entries are there: first (%t), second (%t)", okFirst, okSecond)
|
||||
}
|
||||
return firstRec >= secondRec, nil
|
||||
}
|
||||
|
||||
func (a *aclList) Head() *Record {
|
||||
return a.records[len(a.records)-1]
|
||||
}
|
||||
|
||||
func (a *aclList) Get(id string) (*Record, error) {
|
||||
recIdx, ok := a.indexes[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no such record")
|
||||
}
|
||||
return a.records[recIdx], nil
|
||||
}
|
||||
|
||||
func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||
for _, rec := range a.records {
|
||||
if !iterFunc(rec) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||
recIdx, ok := a.indexes[startId]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for i := recIdx; i < len(a.records); i++ {
|
||||
if !iterFunc(a.records[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclList) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
92
pkg/acl/list/list_test.go
Normal file
92
pkg/acl/list/list_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAclList_ACLState_UserInviteAndJoin(t *testing.T) {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
keychain := st.(*acllistbuilder.ACLListStorageBuilder).GetKeychain()
|
||||
|
||||
aclList, err := BuildACLList(signingkey.NewEDPubKeyDecoder(), st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
idA := keychain.GetIdentity("A")
|
||||
idB := keychain.GetIdentity("B")
|
||||
idC := keychain.GetIdentity("C")
|
||||
|
||||
// checking final state
|
||||
assert.Equal(t, aclpb.ACLChange_Admin, aclList.ACLState().GetUserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclpb.ACLChange_Writer, aclList.ACLState().GetUserStates()[idB].Permissions)
|
||||
assert.Equal(t, aclpb.ACLChange_Reader, aclList.ACLState().GetUserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().Content.CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
|
||||
var records []*Record
|
||||
aclList.Iterate(func(record *Record) (IsContinue bool) {
|
||||
records = append(records, record)
|
||||
return true
|
||||
})
|
||||
|
||||
// checking permissions at specific records
|
||||
assert.Equal(t, 3, len(records))
|
||||
|
||||
_, err = aclList.ACLState().PermissionsAtRecord(records[1].Id, idB)
|
||||
assert.Error(t, err, "B should have no permissions at record 1")
|
||||
|
||||
perm, err := aclList.ACLState().PermissionsAtRecord(records[2].Id, idB)
|
||||
assert.NoError(t, err, "should have no error with permissions of B in the record 2")
|
||||
assert.Equal(t, UserPermissionPair{
|
||||
Identity: idB,
|
||||
Permission: aclpb.ACLChange_Writer,
|
||||
}, perm)
|
||||
}
|
||||
|
||||
func TestAclList_ACLState_UserJoinAndRemove(t *testing.T) {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userremoveexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
keychain := st.(*acllistbuilder.ACLListStorageBuilder).GetKeychain()
|
||||
|
||||
aclList, err := BuildACLList(signingkey.NewEDPubKeyDecoder(), st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
idA := keychain.GetIdentity("A")
|
||||
idB := keychain.GetIdentity("B")
|
||||
idC := keychain.GetIdentity("C")
|
||||
|
||||
// checking final state
|
||||
assert.Equal(t, aclpb.ACLChange_Admin, aclList.ACLState().GetUserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclpb.ACLChange_Reader, aclList.ACLState().GetUserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().Content.CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
|
||||
_, exists := aclList.ACLState().GetUserStates()[idB]
|
||||
assert.Equal(t, false, exists)
|
||||
|
||||
var records []*Record
|
||||
aclList.Iterate(func(record *Record) (IsContinue bool) {
|
||||
records = append(records, record)
|
||||
return true
|
||||
})
|
||||
|
||||
// checking permissions at specific records
|
||||
assert.Equal(t, 4, len(records))
|
||||
|
||||
assert.NotEqual(t, records[2].Content.CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
|
||||
perm, err := aclList.ACLState().PermissionsAtRecord(records[2].Id, idB)
|
||||
assert.NoError(t, err, "should have no error with permissions of B in the record 2")
|
||||
assert.Equal(t, UserPermissionPair{
|
||||
Identity: idB,
|
||||
Permission: aclpb.ACLChange_Writer,
|
||||
}, perm)
|
||||
|
||||
_, err = aclList.ACLState().PermissionsAtRecord(records[3].Id, idB)
|
||||
assert.Error(t, err, "B should have no permissions at record 3, because user should be removed")
|
||||
}
|
||||
34
pkg/acl/list/record.go
Normal file
34
pkg/acl/list/record.go
Normal file
@ -0,0 +1,34 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
Id string
|
||||
Content *aclpb.Record
|
||||
ParsedModel interface{}
|
||||
Sign []byte
|
||||
}
|
||||
|
||||
func NewRecord(id string, aclRecord *aclpb.Record) *Record {
|
||||
return &Record{
|
||||
Id: id,
|
||||
Content: aclRecord,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromRawRecord(rawRec *aclpb.RawRecord) (*Record, error) {
|
||||
aclRec := &aclpb.Record{}
|
||||
err := proto.Unmarshal(rawRec.Payload, aclRec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Record{
|
||||
Id: rawRec.Id,
|
||||
Content: aclRec,
|
||||
Sign: rawRec.Signature,
|
||||
}, nil
|
||||
}
|
||||
191
pkg/acl/storage/inmemory.go
Normal file
191
pkg/acl/storage/inmemory.go
Normal file
@ -0,0 +1,191 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type inMemoryACLListStorage struct {
|
||||
header *aclpb.Header
|
||||
records []*aclpb.RawRecord
|
||||
|
||||
id string
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryACLListStorage(
|
||||
id string,
|
||||
header *aclpb.Header,
|
||||
records []*aclpb.RawRecord) (ListStorage, error) {
|
||||
return &inMemoryACLListStorage{
|
||||
id: id,
|
||||
header: header,
|
||||
records: records,
|
||||
RWMutex: sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) Header() (*aclpb.Header, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return i.header, nil
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) Head() (*aclpb.RawRecord, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return i.records[len(i.records)-1], nil
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
for _, rec := range i.records {
|
||||
if rec.Id == id {
|
||||
return rec, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no such record")
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) ID() (string, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return i.id, nil
|
||||
}
|
||||
|
||||
type inMemoryTreeStorage struct {
|
||||
id string
|
||||
header *aclpb.Header
|
||||
heads []string
|
||||
changes map[string]*aclpb.RawChange
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryTreeStorage(
|
||||
treeId string,
|
||||
header *aclpb.Header,
|
||||
heads []string,
|
||||
changes []*aclpb.RawChange) (TreeStorage, error) {
|
||||
allChanges := make(map[string]*aclpb.RawChange)
|
||||
for _, ch := range changes {
|
||||
allChanges[ch.Id] = ch
|
||||
}
|
||||
|
||||
return &inMemoryTreeStorage{
|
||||
id: treeId,
|
||||
header: header,
|
||||
heads: heads,
|
||||
changes: allChanges,
|
||||
RWMutex: sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) ID() (string, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.id, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Header() (*aclpb.Header, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.header, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Heads() ([]string, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.heads, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) SetHeads(heads []string) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.heads = t.heads[:0]
|
||||
|
||||
for _, h := range heads {
|
||||
t.heads = append(t.heads, h)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) AddRawChange(change *aclpb.RawChange) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
// TODO: better to do deep copy
|
||||
t.changes[change.Id] = change
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) GetRawChange(ctx context.Context, changeId string) (*aclpb.RawChange, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if res, exists := t.changes[changeId]; exists {
|
||||
return res, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not get change with id: %s", changeId)
|
||||
}
|
||||
|
||||
type inMemoryStorageProvider struct {
|
||||
objects map[string]Storage
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) AddStorage(id string, st Storage) error {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
if _, exists := i.objects[id]; exists {
|
||||
return fmt.Errorf("storage already exists")
|
||||
}
|
||||
|
||||
i.objects[id] = st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) Storage(id string) (Storage, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
if tree, exists := i.objects[id]; exists {
|
||||
return tree, nil
|
||||
}
|
||||
return nil, ErrUnknownTreeId
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) CreateTreeStorage(payload TreeStorageCreatePayload) (TreeStorage, error) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
res, err := NewInMemoryTreeStorage(payload.TreeId, payload.Header, payload.Heads, payload.Changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.objects[payload.TreeId] = res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) CreateACLListStorage(payload ACLListStorageCreatePayload) (ListStorage, error) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
res, err := NewInMemoryACLListStorage(payload.ListId, payload.Header, payload.Records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.objects[payload.ListId] = res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NewInMemoryTreeStorageProvider() Provider {
|
||||
return &inMemoryStorageProvider{
|
||||
objects: make(map[string]Storage),
|
||||
}
|
||||
}
|
||||
14
pkg/acl/storage/liststorage.go
Normal file
14
pkg/acl/storage/liststorage.go
Normal file
@ -0,0 +1,14 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
)
|
||||
|
||||
type ListStorage interface {
|
||||
Storage
|
||||
Head() (*aclpb.RawRecord, error)
|
||||
|
||||
GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error)
|
||||
AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error
|
||||
}
|
||||
28
pkg/acl/storage/provider.go
Normal file
28
pkg/acl/storage/provider.go
Normal file
@ -0,0 +1,28 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
)
|
||||
|
||||
var ErrUnknownTreeId = errors.New("tree does not exist")
|
||||
|
||||
type TreeStorageCreatePayload struct {
|
||||
TreeId string
|
||||
Header *aclpb.Header
|
||||
Changes []*aclpb.RawChange
|
||||
Heads []string
|
||||
}
|
||||
|
||||
type ACLListStorageCreatePayload struct {
|
||||
ListId string
|
||||
Header *aclpb.Header
|
||||
Records []*aclpb.RawRecord
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Storage(id string) (Storage, error)
|
||||
AddStorage(id string, st Storage) error
|
||||
CreateTreeStorage(payload TreeStorageCreatePayload) (TreeStorage, error)
|
||||
CreateACLListStorage(payload ACLListStorageCreatePayload) (ListStorage, error)
|
||||
}
|
||||
8
pkg/acl/storage/storage.go
Normal file
8
pkg/acl/storage/storage.go
Normal file
@ -0,0 +1,8 @@
|
||||
package storage
|
||||
|
||||
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
|
||||
type Storage interface {
|
||||
ID() (string, error)
|
||||
Header() (*aclpb.Header, error)
|
||||
}
|
||||
17
pkg/acl/storage/treestorage.go
Normal file
17
pkg/acl/storage/treestorage.go
Normal file
@ -0,0 +1,17 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
)
|
||||
|
||||
type TreeStorage interface {
|
||||
Storage
|
||||
Heads() ([]string, error)
|
||||
SetHeads(heads []string) error
|
||||
|
||||
AddRawChange(change *aclpb.RawChange) error
|
||||
GetRawChange(ctx context.Context, recordID string) (*aclpb.RawChange, error)
|
||||
}
|
||||
|
||||
type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error)
|
||||
@ -1,4 +1,4 @@
|
||||
package treestoragebuilder
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
@ -50,69 +50,110 @@ func (k *Keychain) ParseKeys(keys *Keys) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) AddEncryptionKey(name string) {
|
||||
if _, exists := k.EncryptionKeys[name]; exists {
|
||||
func (k *Keychain) AddEncryptionKey(key *Key) {
|
||||
if _, exists := k.EncryptionKeys[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
newPrivKey, _, err := encryptionkey.GenerateRandomRSAKeyPair(2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
var (
|
||||
newPrivKey encryptionkey.PrivKey
|
||||
err error
|
||||
)
|
||||
if key.Value == "generated" {
|
||||
newPrivKey, _, err = encryptionkey.GenerateRandomRSAKeyPair(2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
decoder := encryptionkey.NewRSAPrivKeyDecoder()
|
||||
privKey, err := decoder.DecodeFromString(key.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newPrivKey = privKey.(encryptionkey.PrivKey)
|
||||
}
|
||||
|
||||
k.EncryptionKeys[name] = newPrivKey
|
||||
k.EncryptionKeys[key.Name] = newPrivKey
|
||||
}
|
||||
|
||||
func (k *Keychain) AddSigningKey(name string) {
|
||||
if _, exists := k.SigningKeys[name]; exists {
|
||||
func (k *Keychain) AddSigningKey(key *Key) {
|
||||
if _, exists := k.SigningKeys[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
newPrivKey, pubKey, err := signingkey.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
var (
|
||||
newPrivKey signingkey.PrivKey
|
||||
pubKey signingkey.PubKey
|
||||
err error
|
||||
)
|
||||
if key.Value == "generated" {
|
||||
newPrivKey, pubKey, err = signingkey.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
decoder := signingkey.NewEDPrivKeyDecoder()
|
||||
privKey, err := decoder.DecodeFromString(key.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newPrivKey = privKey.(signingkey.PrivKey)
|
||||
pubKey = newPrivKey.GetPublic()
|
||||
}
|
||||
|
||||
k.SigningKeys[name] = newPrivKey
|
||||
k.SigningKeys[key.Name] = newPrivKey
|
||||
res, err := k.coder.EncodeToString(pubKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
k.SigningKeysByIdentity[res] = newPrivKey
|
||||
k.GeneratedIdentities[name] = res
|
||||
k.GeneratedIdentities[key.Name] = res
|
||||
}
|
||||
|
||||
func (k *Keychain) AddReadKey(name string) {
|
||||
if _, exists := k.ReadKeys[name]; exists {
|
||||
func (k *Keychain) AddReadKey(key *Key) {
|
||||
if _, exists := k.ReadKeys[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
key, _ := symmetric.NewRandom()
|
||||
|
||||
var (
|
||||
rkey *symmetric.Key
|
||||
err error
|
||||
)
|
||||
if key.Value == "generated" {
|
||||
rkey, err = symmetric.NewRandom()
|
||||
if err != nil {
|
||||
panic("should be able to generate symmetric key")
|
||||
}
|
||||
} else {
|
||||
rkey, err = symmetric.FromString(key.Value)
|
||||
if err != nil {
|
||||
panic("should be able to parse symmetric key")
|
||||
}
|
||||
}
|
||||
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(key.Bytes())
|
||||
hasher.Write(rkey.Bytes())
|
||||
|
||||
k.ReadKeys[name] = &SymKey{
|
||||
k.ReadKeys[key.Name] = &SymKey{
|
||||
Hash: hasher.Sum64(),
|
||||
Key: key,
|
||||
Key: rkey,
|
||||
}
|
||||
k.ReadKeysByHash[hasher.Sum64()] = &SymKey{
|
||||
Hash: hasher.Sum64(),
|
||||
Key: key,
|
||||
Key: rkey,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) AddKey(key string) {
|
||||
parts := strings.Split(key, ".")
|
||||
func (k *Keychain) AddKey(key *Key) {
|
||||
parts := strings.Split(key.Name, ".")
|
||||
if len(parts) != 3 {
|
||||
panic("cannot parse a key")
|
||||
}
|
||||
name := parts[2]
|
||||
|
||||
switch parts[1] {
|
||||
case "Sign":
|
||||
k.AddSigningKey(name)
|
||||
k.AddSigningKey(key)
|
||||
case "Enc":
|
||||
k.AddEncryptionKey(name)
|
||||
k.AddEncryptionKey(key)
|
||||
case "Read":
|
||||
k.AddReadKey(name)
|
||||
k.AddReadKey(key)
|
||||
default:
|
||||
panic("incorrect format")
|
||||
}
|
||||
322
pkg/acl/testutils/acllistbuilder/liststoragebuilder.go
Normal file
322
pkg/acl/testutils/acllistbuilder/liststoragebuilder.go
Normal file
@ -0,0 +1,322 @@
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/yamltests"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ACLListStorageBuilder struct {
|
||||
aclList string
|
||||
records []*aclpb.Record
|
||||
rawRecords []*aclpb.RawRecord
|
||||
indexes map[string]int
|
||||
keychain *Keychain
|
||||
header *aclpb.Header
|
||||
id string
|
||||
}
|
||||
|
||||
func NewACLListStorageBuilder(keychain *Keychain) *ACLListStorageBuilder {
|
||||
return &ACLListStorageBuilder{
|
||||
records: make([]*aclpb.Record, 0),
|
||||
indexes: make(map[string]int),
|
||||
keychain: keychain,
|
||||
}
|
||||
}
|
||||
|
||||
func NewListStorageWithTestName(name string) (storage.ListStorage, error) {
|
||||
filePath := path.Join(yamltests.Path(), name)
|
||||
return NewACLListStorageBuilderFromFile(filePath)
|
||||
}
|
||||
|
||||
func NewACLListStorageBuilderFromFile(file string) (*ACLListStorageBuilder, error) {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ymlTree := YMLList{}
|
||||
err = yaml.Unmarshal(content, &ymlTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tb := NewACLListStorageBuilder(NewKeychain())
|
||||
tb.Parse(&ymlTree)
|
||||
|
||||
return tb, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) createRaw(rec *aclpb.Record) *aclpb.RawRecord {
|
||||
aclMarshaled, err := proto.Marshal(rec)
|
||||
if err != nil {
|
||||
panic("should be able to marshal final acl message!")
|
||||
}
|
||||
|
||||
signature, err := t.keychain.SigningKeysByIdentity[rec.Identity].Sign(aclMarshaled)
|
||||
if err != nil {
|
||||
panic("should be able to sign final acl message!")
|
||||
}
|
||||
|
||||
id, _ := cid.NewCIDFromBytes(aclMarshaled)
|
||||
|
||||
return &aclpb.RawRecord{
|
||||
Payload: aclMarshaled,
|
||||
Signature: signature,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) getRecord(idx int) *aclpb.RawRecord {
|
||||
return t.rawRecords[idx]
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) Head() (*aclpb.RawRecord, error) {
|
||||
return t.getRecord(len(t.records) - 1), nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) Header() (*aclpb.Header, error) {
|
||||
return t.header, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) {
|
||||
recIdx, ok := t.indexes[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no such record")
|
||||
}
|
||||
return t.getRecord(recIdx), nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) ID() (string, error) {
|
||||
return t.id, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) GetRawRecords() []*aclpb.RawRecord {
|
||||
return t.rawRecords
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) GetKeychain() *Keychain {
|
||||
return t.keychain
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) Parse(tree *YMLList) {
|
||||
// Just to clarify - we are generating new identities for the ones that
|
||||
// are specified in the yml file, because our identities should be Ed25519
|
||||
// the same thing is happening for the encryption keys
|
||||
t.keychain.ParseKeys(&tree.Keys)
|
||||
prevId := ""
|
||||
for idx, rec := range tree.Records {
|
||||
newRecord := t.parseRecord(rec, prevId)
|
||||
rawRecord := t.createRaw(newRecord)
|
||||
t.records = append(t.records, newRecord)
|
||||
t.rawRecords = append(t.rawRecords, t.createRaw(newRecord))
|
||||
t.indexes[rawRecord.Id] = idx
|
||||
prevId = rawRecord.Id
|
||||
}
|
||||
|
||||
t.createHeaderAndId()
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclpb.Record {
|
||||
k := t.keychain.GetKey(rec.ReadKey).(*SymKey)
|
||||
var aclChangeContents []*aclpb.ACLChangeACLContentValue
|
||||
for _, ch := range rec.AclChanges {
|
||||
aclChangeContent := t.parseACLChange(ch)
|
||||
aclChangeContents = append(aclChangeContents, aclChangeContent)
|
||||
}
|
||||
data := &aclpb.ACLChangeACLData{
|
||||
AclContent: aclChangeContents,
|
||||
}
|
||||
bytes, _ := data.Marshal()
|
||||
|
||||
return &aclpb.Record{
|
||||
PrevId: prevId,
|
||||
Identity: t.keychain.GetIdentity(rec.Identity),
|
||||
Data: bytes,
|
||||
CurrentReadKeyHash: k.Hash,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACLChangeACLContentValue) {
|
||||
switch {
|
||||
case ch.UserAdd != nil:
|
||||
add := ch.UserAdd
|
||||
|
||||
encKey := t.keychain.GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserAdd{
|
||||
UserAdd: &aclpb.ACLChangeUserAdd{
|
||||
Identity: t.keychain.GetIdentity(add.Identity),
|
||||
EncryptionKey: rawKey,
|
||||
EncryptedReadKeys: t.encryptReadKeys(add.EncryptedReadKeys, encKey),
|
||||
Permissions: t.convertPermission(add.Permission),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserJoin != nil:
|
||||
join := ch.UserJoin
|
||||
|
||||
encKey := t.keychain.
|
||||
GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
idKey, _ := t.keychain.SigningKeys[join.Identity].GetPublic().Raw()
|
||||
signKey := t.keychain.GetKey(join.AcceptSignature).(signingkey.PrivKey)
|
||||
signature, err := signKey.Sign(idKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserJoin{
|
||||
UserJoin: &aclpb.ACLChangeUserJoin{
|
||||
Identity: t.keychain.GetIdentity(join.Identity),
|
||||
EncryptionKey: rawKey,
|
||||
AcceptSignature: signature,
|
||||
UserInviteId: join.InviteId,
|
||||
EncryptedReadKeys: t.encryptReadKeys(join.EncryptedReadKeys, encKey),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserInvite != nil:
|
||||
invite := ch.UserInvite
|
||||
rawAcceptKey, _ := t.keychain.GetKey(invite.AcceptKey).(signingkey.PrivKey).GetPublic().Raw()
|
||||
encKey := t.keychain.
|
||||
GetKey(invite.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserInvite{
|
||||
UserInvite: &aclpb.ACLChangeUserInvite{
|
||||
AcceptPublicKey: rawAcceptKey,
|
||||
EncryptPublicKey: rawEncKey,
|
||||
EncryptedReadKeys: t.encryptReadKeys(invite.EncryptedReadKeys, encKey),
|
||||
Permissions: t.convertPermission(invite.Permissions),
|
||||
InviteId: invite.InviteId,
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserConfirm != nil:
|
||||
confirm := ch.UserConfirm
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserConfirm{
|
||||
UserConfirm: &aclpb.ACLChangeUserConfirm{
|
||||
Identity: t.keychain.GetIdentity(confirm.Identity),
|
||||
UserAddId: confirm.UserAddId,
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserPermissionChange != nil:
|
||||
permissionChange := ch.UserPermissionChange
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserPermissionChange{
|
||||
UserPermissionChange: &aclpb.ACLChangeUserPermissionChange{
|
||||
Identity: t.keychain.GetIdentity(permissionChange.Identity),
|
||||
Permissions: t.convertPermission(permissionChange.Permission),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserRemove != nil:
|
||||
remove := ch.UserRemove
|
||||
|
||||
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
|
||||
|
||||
var replaces []*aclpb.ACLChangeReadKeyReplace
|
||||
for _, id := range remove.IdentitiesLeft {
|
||||
identity := t.keychain.GetIdentity(id)
|
||||
encKey := t.keychain.EncryptionKeys[id]
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
replaces = append(replaces, &aclpb.ACLChangeReadKeyReplace{
|
||||
Identity: identity,
|
||||
EncryptionKey: rawEncKey,
|
||||
EncryptedReadKey: encReadKey,
|
||||
})
|
||||
}
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserRemove{
|
||||
UserRemove: &aclpb.ACLChangeUserRemove{
|
||||
Identity: t.keychain.GetIdentity(remove.RemovedIdentity),
|
||||
ReadKeyReplaces: replaces,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if convCh == nil {
|
||||
panic("cannot have empty acl change")
|
||||
}
|
||||
|
||||
return convCh
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) encryptReadKeys(keys []string, encKey encryptionkey.PrivKey) (enc [][]byte) {
|
||||
for _, k := range keys {
|
||||
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
||||
res, err := encKey.GetPublic().Encrypt(realKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enc = append(enc, res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) convertPermission(perm string) aclpb.ACLChangeUserPermissions {
|
||||
switch perm {
|
||||
case "admin":
|
||||
return aclpb.ACLChange_Admin
|
||||
case "writer":
|
||||
return aclpb.ACLChange_Writer
|
||||
case "reader":
|
||||
return aclpb.ACLChange_Reader
|
||||
default:
|
||||
panic(fmt.Sprintf("incorrect permission: %s", perm))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclpb.Record, id string) error) (err error) {
|
||||
for i := len(t.records) - 1; i >= 0; i-- {
|
||||
err = f(t.records[i], t.rawRecords[i].Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) createHeaderAndId() {
|
||||
t.header = &aclpb.Header{
|
||||
FirstId: t.rawRecords[0].Id,
|
||||
AclListId: "",
|
||||
WorkspaceId: "",
|
||||
DocType: aclpb.Header_ACL,
|
||||
}
|
||||
bytes, _ := t.header.Marshal()
|
||||
id, _ := cid.NewCIDFromBytes(bytes)
|
||||
t.id = id
|
||||
}
|
||||
@ -2,10 +2,10 @@
|
||||
// +build !linux,!darwin android ios nographviz
|
||||
// +build !amd64
|
||||
|
||||
package treestoragebuilder
|
||||
package acllistbuilder
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (t *TreeStorageBuilder) Graph() (string, error) {
|
||||
func (t *ACLListStorageBuilder) Graph() (string, error) {
|
||||
return "", fmt.Errorf("building graphs is not supported")
|
||||
}
|
||||
121
pkg/acl/testutils/acllistbuilder/liststoragebuildergraph_nix.go
Normal file
121
pkg/acl/testutils/acllistbuilder/liststoragebuildergraph_nix.go
Normal file
@ -0,0 +1,121 @@
|
||||
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
|
||||
// +build linux darwin
|
||||
// +build !android
|
||||
// +build !ios
|
||||
// +build !nographviz
|
||||
// +build amd64 arm64
|
||||
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/awalterschulze/gographviz"
|
||||
)
|
||||
|
||||
// To quickly look at visualized string you can use https://dreampuf.github.io/GraphvizOnline
|
||||
|
||||
type EdgeParameters struct {
|
||||
style string
|
||||
color string
|
||||
label string
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) Graph() (string, error) {
|
||||
// TODO: check updates on https://github.com/goccy/go-graphviz/issues/52 or make a fix yourself to use better library here
|
||||
graph := gographviz.NewGraph()
|
||||
graph.SetName("G")
|
||||
graph.SetDir(true)
|
||||
var nodes = make(map[string]struct{})
|
||||
|
||||
var addNodes = func(r *aclpb.Record, id string) error {
|
||||
style := "solid"
|
||||
|
||||
var chSymbs []string
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
err := proto.Unmarshal(r.GetData(), aclData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chc := range aclData.AclContent {
|
||||
tp := fmt.Sprintf("%T", chc.Value)
|
||||
tp = strings.Replace(tp, "ACLChangeACLContentValueValueOf", "", 1)
|
||||
res := ""
|
||||
for _, ts := range tp {
|
||||
if unicode.IsUpper(ts) {
|
||||
res += string(ts)
|
||||
}
|
||||
}
|
||||
chSymbs = append(chSymbs, res)
|
||||
}
|
||||
|
||||
shortId := id
|
||||
label := fmt.Sprintf("Id: %s\nChanges: %s\n",
|
||||
shortId,
|
||||
strings.Join(chSymbs, ","),
|
||||
)
|
||||
e := graph.AddNode("G", "\""+id+"\"", map[string]string{
|
||||
"label": "\"" + label + "\"",
|
||||
"style": "\"" + style + "\"",
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
nodes[id] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var createEdge = func(firstId, secondId string, params EdgeParameters) error {
|
||||
_, exists := nodes[firstId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no such node")
|
||||
}
|
||||
_, exists = nodes[secondId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no previous node")
|
||||
}
|
||||
|
||||
err := graph.AddEdge("\""+firstId+"\"", "\""+secondId+"\"", true, map[string]string{
|
||||
"color": params.color,
|
||||
"style": params.style,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var addLinks = func(r *aclpb.Record, id string) error {
|
||||
if r.PrevId == "" {
|
||||
return nil
|
||||
}
|
||||
err := createEdge(id, r.PrevId, EdgeParameters{
|
||||
style: "dashed",
|
||||
color: "red",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := t.traverseFromHead(addNodes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = t.traverseFromHead(addLinks)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return graph.String(), nil
|
||||
}
|
||||
70
pkg/acl/testutils/acllistbuilder/ymlentities.go
Normal file
70
pkg/acl/testutils/acllistbuilder/ymlentities.go
Normal file
@ -0,0 +1,70 @@
|
||||
package acllistbuilder
|
||||
|
||||
type Key struct {
|
||||
Name string `yaml:"name"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Enc []*Key `yaml:"Enc"`
|
||||
Sign []*Key `yaml:"Sign"`
|
||||
Read []*Key `yaml:"Read"`
|
||||
}
|
||||
|
||||
type ACLChange struct {
|
||||
UserAdd *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permission string `yaml:"permission"`
|
||||
} `yaml:"userAdd"`
|
||||
|
||||
UserJoin *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
AcceptSignature string `yaml:"acceptSignature"`
|
||||
InviteId string `yaml:"inviteId"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
} `yaml:"userJoin"`
|
||||
|
||||
UserInvite *struct {
|
||||
AcceptKey string `yaml:"acceptKey"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permissions string `yaml:"permissions"`
|
||||
InviteId string `yaml:"inviteId"`
|
||||
} `yaml:"userInvite"`
|
||||
|
||||
UserConfirm *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
UserAddId string `yaml:"UserAddId"`
|
||||
} `yaml:"userConfirm"`
|
||||
|
||||
UserRemove *struct {
|
||||
RemovedIdentity string `yaml:"removedIdentity"`
|
||||
NewReadKey string `yaml:"newReadKey"`
|
||||
IdentitiesLeft []string `yaml:"identitiesLeft"`
|
||||
} `yaml:"userRemove"`
|
||||
|
||||
UserPermissionChange *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
Permission string `yaml:"permission"`
|
||||
}
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Identity string `yaml:"identity"`
|
||||
AclChanges []*ACLChange `yaml:"aclChanges"`
|
||||
ReadKey string `yaml:"readKey"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
FirstChangeId string `yaml:"firstChangeId"`
|
||||
IsWorkspace bool `yaml:"isWorkspace"`
|
||||
}
|
||||
|
||||
type YMLList struct {
|
||||
Records []*Record `yaml:"records"`
|
||||
|
||||
Keys Keys `yaml:"keys"`
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: pkg/acl/testutils/testchanges/testchangepb/protos/testdocumentchanges.proto
|
||||
// source: pkg/acl/testutils/testchanges/proto/test.proto
|
||||
|
||||
package testchangepb
|
||||
package testchanges
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
@ -29,7 +29,7 @@ func (m *PlainTextChange) Reset() { *m = PlainTextChange{} }
|
||||
func (m *PlainTextChange) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChange) ProtoMessage() {}
|
||||
func (*PlainTextChange) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c07268f9f08f2beb, []int{0}
|
||||
return fileDescriptor_37f33c266ada4318, []int{0}
|
||||
}
|
||||
func (m *PlainTextChange) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -68,7 +68,7 @@ func (m *PlainTextChangeContent) Reset() { *m = PlainTextChangeContent{}
|
||||
func (m *PlainTextChangeContent) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeContent) ProtoMessage() {}
|
||||
func (*PlainTextChangeContent) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c07268f9f08f2beb, []int{0, 0}
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 0}
|
||||
}
|
||||
func (m *PlainTextChangeContent) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -138,7 +138,7 @@ func (m *PlainTextChangeTextAppend) Reset() { *m = PlainTextChangeTextAp
|
||||
func (m *PlainTextChangeTextAppend) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeTextAppend) ProtoMessage() {}
|
||||
func (*PlainTextChangeTextAppend) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c07268f9f08f2beb, []int{0, 1}
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 1}
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -182,7 +182,7 @@ func (m *PlainTextChangeSnapshot) Reset() { *m = PlainTextChangeSnapshot
|
||||
func (m *PlainTextChangeSnapshot) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeSnapshot) ProtoMessage() {}
|
||||
func (*PlainTextChangeSnapshot) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c07268f9f08f2beb, []int{0, 2}
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 2}
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -227,7 +227,7 @@ func (m *PlainTextChangeData) Reset() { *m = PlainTextChangeData{} }
|
||||
func (m *PlainTextChangeData) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeData) ProtoMessage() {}
|
||||
func (*PlainTextChangeData) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c07268f9f08f2beb, []int{0, 3}
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 3}
|
||||
}
|
||||
func (m *PlainTextChangeData) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -279,29 +279,28 @@ func init() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("pkg/acl/testutils/testchanges/testchangepb/protos/testdocumentchanges.proto", fileDescriptor_c07268f9f08f2beb)
|
||||
proto.RegisterFile("pkg/acl/testutils/testchanges/proto/test.proto", fileDescriptor_37f33c266ada4318)
|
||||
}
|
||||
|
||||
var fileDescriptor_c07268f9f08f2beb = []byte{
|
||||
// 278 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xf2, 0x2e, 0xc8, 0x4e, 0xd7,
|
||||
var fileDescriptor_37f33c266ada4318 = []byte{
|
||||
// 266 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2b, 0xc8, 0x4e, 0xd7,
|
||||
0x4f, 0x4c, 0xce, 0xd1, 0x2f, 0x49, 0x2d, 0x2e, 0x29, 0x2d, 0xc9, 0xcc, 0x29, 0x06, 0xb3, 0x92,
|
||||
0x33, 0x12, 0xf3, 0xd2, 0x53, 0x91, 0xd9, 0x05, 0x49, 0xfa, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x10,
|
||||
0xb1, 0x94, 0xfc, 0xe4, 0xd2, 0xdc, 0xd4, 0x3c, 0x98, 0x3a, 0x3d, 0xb0, 0x94, 0x10, 0x7b, 0x62,
|
||||
0x5e, 0x65, 0x49, 0x65, 0x41, 0xaa, 0xd2, 0x26, 0x26, 0x2e, 0xfe, 0x80, 0x9c, 0xc4, 0xcc, 0xbc,
|
||||
0x90, 0xd4, 0x8a, 0x12, 0x67, 0xb0, 0x1a, 0xa9, 0x48, 0x2e, 0x76, 0xe7, 0xfc, 0xbc, 0x92, 0xd4,
|
||||
0xbc, 0x12, 0x21, 0x57, 0x2e, 0xae, 0x92, 0xd4, 0x8a, 0x12, 0xc7, 0x82, 0x82, 0xd4, 0xbc, 0x14,
|
||||
0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x65, 0x3d, 0xa8, 0x66, 0x3d, 0x34, 0x8d, 0x7a, 0x21,
|
||||
0x70, 0xa5, 0x1e, 0x0c, 0x41, 0x48, 0x1a, 0x9d, 0xd8, 0xb9, 0x58, 0xcb, 0x12, 0x73, 0x4a, 0x53,
|
||||
0xa5, 0x14, 0xb8, 0xb8, 0x10, 0x8a, 0x84, 0x84, 0xb8, 0x58, 0x40, 0x8a, 0xc0, 0xe6, 0x72, 0x06,
|
||||
0x81, 0xd9, 0x52, 0x72, 0x5c, 0x1c, 0xc1, 0x79, 0x89, 0x05, 0xc5, 0x19, 0xf9, 0x25, 0x58, 0xe5,
|
||||
0x1b, 0x19, 0xb9, 0x58, 0x5c, 0x12, 0x4b, 0x12, 0x85, 0xac, 0xb8, 0xd8, 0x93, 0x21, 0xae, 0x94,
|
||||
0x60, 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x52, 0xc0, 0xe9, 0x2e, 0xa8, 0x6f, 0x82, 0x60, 0x1a, 0x84,
|
||||
0x6c, 0xb9, 0x38, 0x8a, 0xa1, 0x96, 0x48, 0x30, 0x81, 0x3d, 0xa5, 0x88, 0x53, 0x33, 0xcc, 0x35,
|
||||
0x41, 0x70, 0x2d, 0x4e, 0x6a, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91,
|
||||
0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xc5,
|
||||
0x83, 0x1c, 0x0d, 0x49, 0x6c, 0xe0, 0xc0, 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x73,
|
||||
0xe1, 0xf2, 0xbb, 0x01, 0x00, 0x00,
|
||||
0x33, 0x12, 0xf3, 0xd2, 0x53, 0x8b, 0xf5, 0x0b, 0x8a, 0xf2, 0x4b, 0xf2, 0xc1, 0x22, 0x7a, 0x60,
|
||||
0xa6, 0x10, 0x7b, 0x62, 0x5e, 0x65, 0x49, 0x65, 0x41, 0xaa, 0xd2, 0x26, 0x26, 0x2e, 0xfe, 0x80,
|
||||
0x9c, 0xc4, 0xcc, 0xbc, 0x90, 0xd4, 0x8a, 0x12, 0x67, 0xb0, 0x72, 0xa9, 0x48, 0x2e, 0x76, 0xe7,
|
||||
0xfc, 0xbc, 0x92, 0xd4, 0xbc, 0x12, 0x21, 0x57, 0x2e, 0xae, 0x92, 0xd4, 0x8a, 0x12, 0xc7, 0x82,
|
||||
0x82, 0xd4, 0xbc, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x65, 0x3d, 0xa8, 0x66, 0x3d,
|
||||
0x34, 0x8d, 0x7a, 0x21, 0x70, 0xa5, 0x1e, 0x0c, 0x41, 0x48, 0x1a, 0x9d, 0xd8, 0xb9, 0x58, 0xcb,
|
||||
0x12, 0x73, 0x4a, 0x53, 0xa5, 0x14, 0xb8, 0xb8, 0x10, 0x8a, 0x84, 0x84, 0xb8, 0x58, 0x40, 0x8a,
|
||||
0xc0, 0xe6, 0x72, 0x06, 0x81, 0xd9, 0x52, 0x72, 0x5c, 0x1c, 0xc1, 0x79, 0x89, 0x05, 0xc5, 0x19,
|
||||
0xf9, 0x25, 0x58, 0xe5, 0x1b, 0x19, 0xb9, 0x58, 0x5c, 0x12, 0x4b, 0x12, 0x85, 0xac, 0xb8, 0xd8,
|
||||
0x93, 0x21, 0xae, 0x94, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x52, 0xc0, 0xe9, 0x2e, 0xa8, 0x6f,
|
||||
0x82, 0x60, 0x1a, 0x84, 0x6c, 0xb9, 0x38, 0x8a, 0xa1, 0x96, 0x48, 0x30, 0x81, 0x3d, 0xa5, 0x88,
|
||||
0x53, 0x33, 0xcc, 0x35, 0x41, 0x70, 0x2d, 0x4e, 0xaa, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24,
|
||||
0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78,
|
||||
0x2c, 0xc7, 0x10, 0xc5, 0x8d, 0x14, 0xea, 0x49, 0x6c, 0xe0, 0xb0, 0x36, 0x06, 0x04, 0x00, 0x00,
|
||||
0xff, 0xff, 0xf8, 0x8c, 0x6a, 0x1d, 0x9d, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *PlainTextChange) Marshal() (dAtA []byte, err error) {
|
||||
@ -373,7 +372,7 @@ func (m *PlainTextChangeContentValueOfTextAppend) MarshalToSizedBuffer(dAtA []by
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintTestdocumentchanges(dAtA, i, uint64(size))
|
||||
i = encodeVarintTest(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
@ -403,7 +402,7 @@ func (m *PlainTextChangeTextAppend) MarshalToSizedBuffer(dAtA []byte) (int, erro
|
||||
if len(m.Text) > 0 {
|
||||
i -= len(m.Text)
|
||||
copy(dAtA[i:], m.Text)
|
||||
i = encodeVarintTestdocumentchanges(dAtA, i, uint64(len(m.Text)))
|
||||
i = encodeVarintTest(dAtA, i, uint64(len(m.Text)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
@ -433,7 +432,7 @@ func (m *PlainTextChangeSnapshot) MarshalToSizedBuffer(dAtA []byte) (int, error)
|
||||
if len(m.Text) > 0 {
|
||||
i -= len(m.Text)
|
||||
copy(dAtA[i:], m.Text)
|
||||
i = encodeVarintTestdocumentchanges(dAtA, i, uint64(len(m.Text)))
|
||||
i = encodeVarintTest(dAtA, i, uint64(len(m.Text)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
@ -467,7 +466,7 @@ func (m *PlainTextChangeData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintTestdocumentchanges(dAtA, i, uint64(size))
|
||||
i = encodeVarintTest(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
@ -480,7 +479,7 @@ func (m *PlainTextChangeData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintTestdocumentchanges(dAtA, i, uint64(size))
|
||||
i = encodeVarintTest(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
@ -489,8 +488,8 @@ func (m *PlainTextChangeData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintTestdocumentchanges(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovTestdocumentchanges(v)
|
||||
func encodeVarintTest(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovTest(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
@ -529,7 +528,7 @@ func (m *PlainTextChangeContentValueOfTextAppend) Size() (n int) {
|
||||
_ = l
|
||||
if m.TextAppend != nil {
|
||||
l = m.TextAppend.Size()
|
||||
n += 1 + l + sovTestdocumentchanges(uint64(l))
|
||||
n += 1 + l + sovTest(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
@ -541,7 +540,7 @@ func (m *PlainTextChangeTextAppend) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.Text)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovTestdocumentchanges(uint64(l))
|
||||
n += 1 + l + sovTest(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
@ -554,7 +553,7 @@ func (m *PlainTextChangeSnapshot) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.Text)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovTestdocumentchanges(uint64(l))
|
||||
n += 1 + l + sovTest(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
@ -568,21 +567,21 @@ func (m *PlainTextChangeData) Size() (n int) {
|
||||
if len(m.Content) > 0 {
|
||||
for _, e := range m.Content {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovTestdocumentchanges(uint64(l))
|
||||
n += 1 + l + sovTest(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.Snapshot != nil {
|
||||
l = m.Snapshot.Size()
|
||||
n += 1 + l + sovTestdocumentchanges(uint64(l))
|
||||
n += 1 + l + sovTest(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovTestdocumentchanges(x uint64) (n int) {
|
||||
func sovTest(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozTestdocumentchanges(x uint64) (n int) {
|
||||
return sovTestdocumentchanges(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
func sozTest(x uint64) (n int) {
|
||||
return sovTest(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *PlainTextChange) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
@ -592,7 +591,7 @@ func (m *PlainTextChange) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -615,12 +614,12 @@ func (m *PlainTextChange) Unmarshal(dAtA []byte) error {
|
||||
switch fieldNum {
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTestdocumentchanges(dAtA[iNdEx:])
|
||||
skippy, err := skipTest(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -642,7 +641,7 @@ func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -670,7 +669,7 @@ func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -683,11 +682,11 @@ func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -700,12 +699,12 @@ func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTestdocumentchanges(dAtA[iNdEx:])
|
||||
skippy, err := skipTest(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -727,7 +726,7 @@ func (m *PlainTextChangeTextAppend) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -755,7 +754,7 @@ func (m *PlainTextChangeTextAppend) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -769,11 +768,11 @@ func (m *PlainTextChangeTextAppend) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -782,12 +781,12 @@ func (m *PlainTextChangeTextAppend) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTestdocumentchanges(dAtA[iNdEx:])
|
||||
skippy, err := skipTest(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -809,7 +808,7 @@ func (m *PlainTextChangeSnapshot) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -837,7 +836,7 @@ func (m *PlainTextChangeSnapshot) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -851,11 +850,11 @@ func (m *PlainTextChangeSnapshot) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -864,12 +863,12 @@ func (m *PlainTextChangeSnapshot) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTestdocumentchanges(dAtA[iNdEx:])
|
||||
skippy, err := skipTest(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -891,7 +890,7 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -919,7 +918,7 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -932,11 +931,11 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -953,7 +952,7 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTestdocumentchanges
|
||||
return ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -966,11 +965,11 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -984,12 +983,12 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTestdocumentchanges(dAtA[iNdEx:])
|
||||
skippy, err := skipTest(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTestdocumentchanges
|
||||
return ErrInvalidLengthTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@ -1003,7 +1002,7 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
func skipTest(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
@ -1011,7 +1010,7 @@ func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTestdocumentchanges
|
||||
return 0, ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
@ -1028,7 +1027,7 @@ func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTestdocumentchanges
|
||||
return 0, ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
@ -1044,7 +1043,7 @@ func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTestdocumentchanges
|
||||
return 0, ErrIntOverflowTest
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
@ -1057,14 +1056,14 @@ func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthTestdocumentchanges
|
||||
return 0, ErrInvalidLengthTest
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupTestdocumentchanges
|
||||
return 0, ErrUnexpectedEndOfGroupTest
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
@ -1073,7 +1072,7 @@ func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthTestdocumentchanges
|
||||
return 0, ErrInvalidLengthTest
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
@ -1083,7 +1082,7 @@ func skipTestdocumentchanges(dAtA []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthTestdocumentchanges = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowTestdocumentchanges = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupTestdocumentchanges = fmt.Errorf("proto: unexpected end of group")
|
||||
ErrInvalidLengthTest = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowTest = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupTest = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
@ -1,8 +1,6 @@
|
||||
syntax = "proto3";
|
||||
package anytype;
|
||||
option go_package = "testchangepb";
|
||||
|
||||
// TODO: move to separate package
|
||||
option go_package = "testchanges";
|
||||
|
||||
message PlainTextChange {
|
||||
message Content {
|
||||
@ -23,4 +21,4 @@ message PlainTextChange {
|
||||
repeated Content content = 1;
|
||||
Snapshot snapshot = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,539 +0,0 @@
|
||||
package treestoragebuilder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
testpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/yamltests"
|
||||
storagepb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const plainTextDocType uint16 = 1
|
||||
|
||||
type treeChange struct {
|
||||
*aclpb.ACLChange
|
||||
id string
|
||||
readKey *SymKey
|
||||
signKey signingkey.PrivKey
|
||||
|
||||
changesDataDecrypted []byte
|
||||
}
|
||||
|
||||
type updateUseCase struct {
|
||||
changes map[string]*treeChange
|
||||
}
|
||||
|
||||
type TreeStorageBuilder struct {
|
||||
treeId string
|
||||
allChanges map[string]*treeChange
|
||||
updates map[string]*updateUseCase
|
||||
heads []string
|
||||
orphans []string
|
||||
keychain *Keychain
|
||||
header *storagepb.TreeHeader
|
||||
}
|
||||
|
||||
func NewTreeStorageBuilder(keychain *Keychain) *TreeStorageBuilder {
|
||||
return &TreeStorageBuilder{
|
||||
allChanges: make(map[string]*treeChange),
|
||||
updates: make(map[string]*updateUseCase),
|
||||
keychain: keychain,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTreeStorageBuilderWithTestName(name string) (*TreeStorageBuilder, error) {
|
||||
filePath := path.Join(yamltests.Path(), name)
|
||||
return NewTreeStorageBuilderFromFile(filePath)
|
||||
}
|
||||
|
||||
func NewTreeStorageBuilderFromFile(file string) (*TreeStorageBuilder, error) {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ymlTree := YMLTree{}
|
||||
err = yaml.Unmarshal(content, &ymlTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tb := NewTreeStorageBuilder(NewKeychain())
|
||||
tb.Parse(&ymlTree)
|
||||
|
||||
return tb, nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) TreeID() (string, error) {
|
||||
return t.treeId, nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) GetKeychain() *Keychain {
|
||||
return t.keychain
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) Heads() ([]string, error) {
|
||||
return t.heads, nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) AddRawChange(change *aclpb.RawChange) error {
|
||||
aclChange := new(aclpb.ACLChange)
|
||||
var err error
|
||||
|
||||
if err = proto.Unmarshal(change.Payload, aclChange); err != nil {
|
||||
return fmt.Errorf("could not unmarshall changes")
|
||||
}
|
||||
var changesData []byte
|
||||
|
||||
// get correct readkey
|
||||
readKey := t.keychain.ReadKeysByHash[aclChange.CurrentReadKeyHash]
|
||||
if aclChange.ChangesData != nil {
|
||||
changesData, err = readKey.Key.Decrypt(aclChange.ChangesData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt changes data: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// get correct signing key
|
||||
signKey := t.keychain.SigningKeysByIdentity[aclChange.Identity]
|
||||
|
||||
t.allChanges[change.Id] = &treeChange{
|
||||
ACLChange: aclChange,
|
||||
id: change.Id,
|
||||
readKey: readKey,
|
||||
signKey: signKey,
|
||||
changesDataDecrypted: changesData,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) AddOrphans(orphans ...string) error {
|
||||
t.orphans = append(t.orphans, orphans...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) AddChange(change aclchanges.Change) error {
|
||||
aclChange := change.ProtoChange()
|
||||
var err error
|
||||
var changesData []byte
|
||||
|
||||
// get correct readkey
|
||||
readKey := t.keychain.ReadKeysByHash[aclChange.CurrentReadKeyHash]
|
||||
if aclChange.ChangesData != nil {
|
||||
changesData, err = readKey.Key.Decrypt(aclChange.ChangesData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt changes data: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// get correct signing key
|
||||
signKey := t.keychain.SigningKeysByIdentity[aclChange.Identity]
|
||||
|
||||
t.allChanges[change.CID()] = &treeChange{
|
||||
ACLChange: aclChange,
|
||||
id: change.CID(),
|
||||
readKey: readKey,
|
||||
signKey: signKey,
|
||||
changesDataDecrypted: changesData,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) Orphans() ([]string, error) {
|
||||
return t.orphans, nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) SetHeads(heads []string) error {
|
||||
// we should copy here instead of just setting the value
|
||||
t.heads = heads
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) RemoveOrphans(orphans ...string) error {
|
||||
t.orphans = slice.Difference(t.orphans, orphans)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) GetChange(ctx context.Context, recordID string) (*aclpb.RawChange, error) {
|
||||
return t.getChange(recordID, t.allChanges), nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) GetUpdates(useCase string) []*aclpb.RawChange {
|
||||
var res []*aclpb.RawChange
|
||||
update := t.updates[useCase]
|
||||
for _, ch := range update.changes {
|
||||
rawCh := t.getChange(ch.id, update.changes)
|
||||
res = append(res, rawCh)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) Header() (*storagepb.TreeHeader, error) {
|
||||
return t.header, nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) getChange(changeId string, m map[string]*treeChange) *aclpb.RawChange {
|
||||
rec := m[changeId]
|
||||
|
||||
if rec.changesDataDecrypted != nil {
|
||||
encrypted, err := rec.readKey.Key.Encrypt(rec.changesDataDecrypted)
|
||||
if err != nil {
|
||||
panic("should be able to encrypt data with read key!")
|
||||
}
|
||||
|
||||
rec.ChangesData = encrypted
|
||||
}
|
||||
|
||||
aclMarshaled, err := proto.Marshal(rec.ACLChange)
|
||||
if err != nil {
|
||||
panic("should be able to marshal final acl message!")
|
||||
}
|
||||
|
||||
signature, err := rec.signKey.Sign(aclMarshaled)
|
||||
if err != nil {
|
||||
panic("should be able to sign final acl message!")
|
||||
}
|
||||
|
||||
transformedRec := &aclpb.RawChange{
|
||||
Payload: aclMarshaled,
|
||||
Signature: signature,
|
||||
Id: changeId,
|
||||
}
|
||||
return transformedRec
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) Parse(tree *YMLTree) {
|
||||
// Just to clarify - we are generating new identities for the ones that
|
||||
// are specified in the yml file, because our identities should be Ed25519
|
||||
// the same thing is happening for the encryption keys
|
||||
t.keychain.ParseKeys(&tree.Keys)
|
||||
t.treeId = t.parseTreeId(tree.Description)
|
||||
for _, ch := range tree.Changes {
|
||||
newChange := t.parseChange(ch)
|
||||
t.allChanges[newChange.id] = newChange
|
||||
}
|
||||
|
||||
t.parseGraph(tree)
|
||||
t.parseOrphans(tree)
|
||||
t.parseHeader(tree)
|
||||
t.parseUpdates(tree.Updates)
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseChange(ch *Change) *treeChange {
|
||||
newChange := &treeChange{
|
||||
id: ch.Id,
|
||||
}
|
||||
k := t.keychain.GetKey(ch.ReadKey).(*SymKey)
|
||||
newChange.readKey = k
|
||||
newChange.signKey = t.keychain.SigningKeys[ch.Identity]
|
||||
aclChange := &aclpb.ACLChange{}
|
||||
aclChange.Identity = t.keychain.GetIdentity(ch.Identity)
|
||||
if len(ch.AclChanges) > 0 || ch.AclSnapshot != nil {
|
||||
aclChange.AclData = &aclpb.ACLChangeACLData{}
|
||||
if ch.AclSnapshot != nil {
|
||||
aclChange.AclData.AclSnapshot = t.parseACLSnapshot(ch.AclSnapshot)
|
||||
}
|
||||
if ch.AclChanges != nil {
|
||||
var aclChangeContents []*aclpb.ACLChangeACLContentValue
|
||||
for _, ch := range ch.AclChanges {
|
||||
aclChangeContent := t.parseACLChange(ch)
|
||||
aclChangeContents = append(aclChangeContents, aclChangeContent)
|
||||
}
|
||||
aclChange.AclData.AclContent = aclChangeContents
|
||||
}
|
||||
}
|
||||
if len(ch.Changes) > 0 || ch.Snapshot != nil {
|
||||
changesData := &testpb.PlainTextChangeData{}
|
||||
if ch.Snapshot != nil {
|
||||
changesData.Snapshot = t.parseChangeSnapshot(ch.Snapshot)
|
||||
}
|
||||
if len(ch.Changes) > 0 {
|
||||
var changeContents []*testpb.PlainTextChangeContent
|
||||
for _, ch := range ch.Changes {
|
||||
aclChangeContent := t.parseDocumentChange(ch)
|
||||
changeContents = append(changeContents, aclChangeContent)
|
||||
}
|
||||
changesData.Content = changeContents
|
||||
}
|
||||
m, err := proto.Marshal(changesData)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
newChange.changesDataDecrypted = m
|
||||
}
|
||||
aclChange.CurrentReadKeyHash = k.Hash
|
||||
newChange.ACLChange = aclChange
|
||||
return newChange
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseTreeId(description *TreeDescription) string {
|
||||
if description == nil {
|
||||
panic("no author in tree")
|
||||
}
|
||||
return description.Author + ".tree.id"
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseChangeSnapshot(s *PlainTextSnapshot) *testpb.PlainTextChangeSnapshot {
|
||||
return &testpb.PlainTextChangeSnapshot{
|
||||
Text: s.Text,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseACLSnapshot(s *ACLSnapshot) *aclpb.ACLChangeACLSnapshot {
|
||||
newState := &aclpb.ACLChangeACLState{}
|
||||
for _, state := range s.UserStates {
|
||||
aclUserState := &aclpb.ACLChangeUserState{}
|
||||
aclUserState.Identity = t.keychain.GetIdentity(state.Identity)
|
||||
|
||||
encKey := t.keychain.
|
||||
GetKey(state.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
aclUserState.EncryptionKey = rawKey
|
||||
|
||||
aclUserState.EncryptedReadKeys = t.encryptReadKeys(state.EncryptedReadKeys, encKey)
|
||||
aclUserState.Permissions = t.convertPermission(state.Permissions)
|
||||
newState.UserStates = append(newState.UserStates, aclUserState)
|
||||
}
|
||||
return &aclpb.ACLChangeACLSnapshot{
|
||||
AclState: newState,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseDocumentChange(ch *PlainTextChange) (convCh *testpb.PlainTextChangeContent) {
|
||||
switch {
|
||||
case ch.TextAppend != nil:
|
||||
convCh = &testpb.PlainTextChangeContent{
|
||||
Value: &testpb.PlainTextChangeContentValueOfTextAppend{
|
||||
TextAppend: &testpb.PlainTextChangeTextAppend{
|
||||
Text: ch.TextAppend.Text,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if convCh == nil {
|
||||
panic("cannot have empty document change")
|
||||
}
|
||||
|
||||
return convCh
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACLChangeACLContentValue) {
|
||||
switch {
|
||||
case ch.UserAdd != nil:
|
||||
add := ch.UserAdd
|
||||
|
||||
encKey := t.keychain.
|
||||
GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserAdd{
|
||||
UserAdd: &aclpb.ACLChangeUserAdd{
|
||||
Identity: t.keychain.GetIdentity(add.Identity),
|
||||
EncryptionKey: rawKey,
|
||||
EncryptedReadKeys: t.encryptReadKeys(add.EncryptedReadKeys, encKey),
|
||||
Permissions: t.convertPermission(add.Permission),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserJoin != nil:
|
||||
join := ch.UserJoin
|
||||
|
||||
encKey := t.keychain.
|
||||
GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
idKey, _ := t.keychain.SigningKeys[join.Identity].GetPublic().Raw()
|
||||
signKey := t.keychain.GetKey(join.AcceptSignature).(signingkey.PrivKey)
|
||||
signature, err := signKey.Sign(idKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserJoin{
|
||||
UserJoin: &aclpb.ACLChangeUserJoin{
|
||||
Identity: t.keychain.GetIdentity(join.Identity),
|
||||
EncryptionKey: rawKey,
|
||||
AcceptSignature: signature,
|
||||
UserInviteId: join.InviteId,
|
||||
EncryptedReadKeys: t.encryptReadKeys(join.EncryptedReadKeys, encKey),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserInvite != nil:
|
||||
invite := ch.UserInvite
|
||||
rawAcceptKey, _ := t.keychain.GetKey(invite.AcceptKey).(signingkey.PrivKey).GetPublic().Raw()
|
||||
encKey := t.keychain.
|
||||
GetKey(invite.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserInvite{
|
||||
UserInvite: &aclpb.ACLChangeUserInvite{
|
||||
AcceptPublicKey: rawAcceptKey,
|
||||
EncryptPublicKey: rawEncKey,
|
||||
EncryptedReadKeys: t.encryptReadKeys(invite.EncryptedReadKeys, encKey),
|
||||
Permissions: t.convertPermission(invite.Permissions),
|
||||
InviteId: invite.InviteId,
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserConfirm != nil:
|
||||
confirm := ch.UserConfirm
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserConfirm{
|
||||
UserConfirm: &aclpb.ACLChangeUserConfirm{
|
||||
Identity: t.keychain.GetIdentity(confirm.Identity),
|
||||
UserAddId: confirm.UserAddId,
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserPermissionChange != nil:
|
||||
permissionChange := ch.UserPermissionChange
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserPermissionChange{
|
||||
UserPermissionChange: &aclpb.ACLChangeUserPermissionChange{
|
||||
Identity: t.keychain.GetIdentity(permissionChange.Identity),
|
||||
Permissions: t.convertPermission(permissionChange.Permission),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserRemove != nil:
|
||||
remove := ch.UserRemove
|
||||
|
||||
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
|
||||
|
||||
var replaces []*aclpb.ACLChangeReadKeyReplace
|
||||
for _, id := range remove.IdentitiesLeft {
|
||||
identity := t.keychain.GetIdentity(id)
|
||||
encKey := t.keychain.EncryptionKeys[id]
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
replaces = append(replaces, &aclpb.ACLChangeReadKeyReplace{
|
||||
Identity: identity,
|
||||
EncryptionKey: rawEncKey,
|
||||
EncryptedReadKey: encReadKey,
|
||||
})
|
||||
}
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserRemove{
|
||||
UserRemove: &aclpb.ACLChangeUserRemove{
|
||||
Identity: t.keychain.GetIdentity(remove.RemovedIdentity),
|
||||
ReadKeyReplaces: replaces,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if convCh == nil {
|
||||
panic("cannot have empty acl change")
|
||||
}
|
||||
|
||||
return convCh
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) encryptReadKeys(keys []string, encKey encryptionkey.PrivKey) (enc [][]byte) {
|
||||
for _, k := range keys {
|
||||
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
||||
res, err := encKey.GetPublic().Encrypt(realKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enc = append(enc, res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) convertPermission(perm string) aclpb.ACLChangeUserPermissions {
|
||||
switch perm {
|
||||
case "admin":
|
||||
return aclpb.ACLChange_Admin
|
||||
case "writer":
|
||||
return aclpb.ACLChange_Writer
|
||||
case "reader":
|
||||
return aclpb.ACLChange_Reader
|
||||
default:
|
||||
panic(fmt.Sprintf("incorrect permission: %s", perm))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) traverseFromHeads(f func(t *treeChange) error) error {
|
||||
uniqMap := map[string]struct{}{}
|
||||
stack := make([]string, len(t.orphans), 10)
|
||||
copy(stack, t.orphans)
|
||||
for len(stack) > 0 {
|
||||
id := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
if _, exists := uniqMap[id]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
ch := t.allChanges[id]
|
||||
uniqMap[id] = struct{}{}
|
||||
if err := f(ch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, prev := range ch.ACLChange.TreeHeadIds {
|
||||
stack = append(stack, prev)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseUpdates(updates []*Update) {
|
||||
for _, update := range updates {
|
||||
useCase := &updateUseCase{
|
||||
changes: map[string]*treeChange{},
|
||||
}
|
||||
for _, ch := range update.Changes {
|
||||
newChange := t.parseChange(ch)
|
||||
useCase.changes[newChange.id] = newChange
|
||||
}
|
||||
for _, node := range update.Graph {
|
||||
rec := useCase.changes[node.Id]
|
||||
rec.AclHeadIds = node.ACLHeads
|
||||
rec.TreeHeadIds = node.TreeHeads
|
||||
rec.SnapshotBaseId = node.BaseSnapshot
|
||||
}
|
||||
|
||||
t.updates[update.UseCase] = useCase
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseGraph(tree *YMLTree) {
|
||||
for _, node := range tree.Graph {
|
||||
rec := t.allChanges[node.Id]
|
||||
rec.AclHeadIds = node.ACLHeads
|
||||
rec.TreeHeadIds = node.TreeHeads
|
||||
rec.SnapshotBaseId = node.BaseSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseOrphans(tree *YMLTree) {
|
||||
t.orphans = tree.Orphans
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) parseHeader(tree *YMLTree) {
|
||||
t.header = &storagepb.TreeHeader{
|
||||
FirstChangeId: tree.Header.FirstChangeId,
|
||||
IsWorkspace: tree.Header.IsWorkspace,
|
||||
}
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
|
||||
// +build linux darwin
|
||||
// +build !android
|
||||
// +build !ios
|
||||
// +build !nographviz
|
||||
// +build amd64 arm64
|
||||
|
||||
package treestoragebuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
testpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/awalterschulze/gographviz"
|
||||
)
|
||||
|
||||
// To quickly look at visualized string you can use https://dreampuf.github.io/GraphvizOnline
|
||||
|
||||
type EdgeParameters struct {
|
||||
style string
|
||||
color string
|
||||
label string
|
||||
}
|
||||
|
||||
func (t *TreeStorageBuilder) Graph() (string, error) {
|
||||
// TODO: check updates on https://github.com/goccy/go-graphviz/issues/52 or make a fix yourself to use better library here
|
||||
graph := gographviz.NewGraph()
|
||||
graph.SetName("G")
|
||||
graph.SetDir(true)
|
||||
var nodes = make(map[string]struct{})
|
||||
|
||||
var addNodes = func(r *treeChange) error {
|
||||
// TODO: revisit function after checking
|
||||
|
||||
style := "solid"
|
||||
if r.GetAclData() != nil {
|
||||
style = "filled"
|
||||
} else if r.changesDataDecrypted != nil {
|
||||
style = "dashed"
|
||||
}
|
||||
|
||||
var chSymbs []string
|
||||
if r.changesDataDecrypted != nil {
|
||||
res := &testpb.PlainTextChangeData{}
|
||||
err := proto.Unmarshal(r.changesDataDecrypted, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chc := range res.Content {
|
||||
tp := fmt.Sprintf("%T", chc.Value)
|
||||
tp = strings.Replace(tp, "ChangeContentValueOf", "", 1)
|
||||
res := ""
|
||||
for _, ts := range tp {
|
||||
if unicode.IsUpper(ts) {
|
||||
res += string(ts)
|
||||
}
|
||||
}
|
||||
chSymbs = append(chSymbs, res)
|
||||
}
|
||||
}
|
||||
if r.GetAclData() != nil {
|
||||
for _, chc := range r.GetAclData().AclContent {
|
||||
tp := fmt.Sprintf("%T", chc.Value)
|
||||
tp = strings.Replace(tp, "ACLChangeACLContentValueValueOf", "", 1)
|
||||
res := ""
|
||||
for _, ts := range tp {
|
||||
if unicode.IsUpper(ts) {
|
||||
res += string(ts)
|
||||
}
|
||||
}
|
||||
chSymbs = append(chSymbs, res)
|
||||
}
|
||||
}
|
||||
|
||||
shortId := r.id
|
||||
label := fmt.Sprintf("Id: %s\nChanges: %s\n",
|
||||
shortId,
|
||||
strings.Join(chSymbs, ","),
|
||||
)
|
||||
e := graph.AddNode("G", "\""+r.id+"\"", map[string]string{
|
||||
"label": "\"" + label + "\"",
|
||||
"style": "\"" + style + "\"",
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
nodes[r.id] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var createEdge = func(firstId, secondId string, params EdgeParameters) error {
|
||||
_, exists := nodes[firstId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no such node")
|
||||
}
|
||||
_, exists = nodes[secondId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no previous node")
|
||||
}
|
||||
|
||||
err := graph.AddEdge("\""+firstId+"\"", "\""+secondId+"\"", true, map[string]string{
|
||||
"color": params.color,
|
||||
"style": params.style,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var addLinks = func(t *treeChange) error {
|
||||
for _, prevId := range t.AclHeadIds {
|
||||
err := createEdge(t.id, prevId, EdgeParameters{
|
||||
style: "dashed",
|
||||
color: "red",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, prevId := range t.TreeHeadIds {
|
||||
err := createEdge(t.id, prevId, EdgeParameters{
|
||||
style: "dashed",
|
||||
color: "blue",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if t.SnapshotBaseId != "" {
|
||||
err := createEdge(t.id, t.SnapshotBaseId, EdgeParameters{
|
||||
style: "bold",
|
||||
color: "blue",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := t.traverseFromHeads(addNodes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = t.traverseFromHeads(addLinks)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return graph.String(), nil
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
package treestoragebuilder
|
||||
|
||||
type TreeDescription struct {
|
||||
Author string `yaml:"author"`
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Enc []string `yaml:"Enc"`
|
||||
Sign []string `yaml:"Sign"`
|
||||
Read []string `yaml:"Read"`
|
||||
}
|
||||
|
||||
type ACLSnapshot struct {
|
||||
UserStates []struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permissions string `yaml:"permission"`
|
||||
IsConfirmed bool `yaml:"isConfirmed"`
|
||||
} `yaml:"userStates"`
|
||||
}
|
||||
|
||||
type PlainTextSnapshot struct {
|
||||
Text string `yaml:"text"`
|
||||
}
|
||||
|
||||
type ACLChange struct {
|
||||
UserAdd *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permission string `yaml:"permission"`
|
||||
} `yaml:"userAdd"`
|
||||
|
||||
UserJoin *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
AcceptSignature string `yaml:"acceptSignature"`
|
||||
InviteId string `yaml:"inviteId"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
} `yaml:"userJoin"`
|
||||
|
||||
UserInvite *struct {
|
||||
AcceptKey string `yaml:"acceptKey"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permissions string `yaml:"permissions"`
|
||||
InviteId string `yaml:"inviteId"`
|
||||
} `yaml:"userInvite"`
|
||||
|
||||
UserConfirm *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
UserAddId string `yaml:"UserAddId"`
|
||||
} `yaml:"userConfirm"`
|
||||
|
||||
UserRemove *struct {
|
||||
RemovedIdentity string `yaml:"removedIdentity"`
|
||||
NewReadKey string `yaml:"newReadKey"`
|
||||
IdentitiesLeft []string `yaml:"identitiesLeft"`
|
||||
} `yaml:"userRemove"`
|
||||
|
||||
UserPermissionChange *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
Permission string `yaml:"permission"`
|
||||
}
|
||||
}
|
||||
|
||||
type PlainTextChange struct {
|
||||
TextAppend *struct {
|
||||
Text string `yaml:"text"`
|
||||
} `yaml:"textAppend"`
|
||||
}
|
||||
|
||||
type GraphNode struct {
|
||||
Id string `yaml:"id"`
|
||||
BaseSnapshot string `yaml:"baseSnapshot"`
|
||||
AclSnapshot string `yaml:"aclSnapshot"`
|
||||
ACLHeads []string `yaml:"aclHeads"`
|
||||
TreeHeads []string `yaml:"treeHeads"`
|
||||
}
|
||||
|
||||
type Change struct {
|
||||
Id string `yaml:"id"`
|
||||
Identity string `yaml:"identity"`
|
||||
|
||||
AclSnapshot *ACLSnapshot `yaml:"aclSnapshot"`
|
||||
Snapshot *PlainTextSnapshot `yaml:"snapshot"`
|
||||
AclChanges []*ACLChange `yaml:"aclChanges"`
|
||||
Changes []*PlainTextChange `yaml:"changes"`
|
||||
|
||||
ReadKey string `yaml:"readKey"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
FirstChangeId string `yaml:"firstChangeId"`
|
||||
IsWorkspace bool `yaml:"isWorkspace"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
UseCase string `yaml:"useCase"`
|
||||
Changes []*Change `yaml:"changes"`
|
||||
Graph []*GraphNode `yaml:"graph"`
|
||||
}
|
||||
|
||||
type YMLTree struct {
|
||||
Description *TreeDescription `yaml:"tree"`
|
||||
Changes []*Change `yaml:"changes"`
|
||||
Updates []*Update `yaml:"updates"`
|
||||
|
||||
Keys Keys `yaml:"keys"`
|
||||
|
||||
Graph []*GraphNode `yaml:"graph"`
|
||||
|
||||
Heads []string `yaml:"heads"`
|
||||
Orphans []string `yaml:"orphans"`
|
||||
Header *Header `yaml:"header"`
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package treestoragebuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_YamlParse(t *testing.T) {
|
||||
tb, _ := NewTreeStorageBuilderWithTestName("userjoinexampleupdate.yml")
|
||||
gr, _ := tb.Graph()
|
||||
fmt.Println(gr)
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
tree:
|
||||
author: A
|
||||
changes:
|
||||
- id: A.1.1
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
- userAdd:
|
||||
identity: B
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: A.1.2
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: D
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: D
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: A.1.3
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: E
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.E
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.1
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.2
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: F
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.F
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Enc:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
- F
|
||||
Sign:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
- F
|
||||
Read:
|
||||
- 1
|
||||
- 2
|
||||
graph:
|
||||
- id: A.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
- id: A.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: B.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.1]
|
||||
treeHeads: [A.1.1]
|
||||
- id: B.1.2
|
||||
baseSnapshot: A.1.2
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
- id: A.1.3
|
||||
baseSnapshot: A.1.2
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
header:
|
||||
firstChangeId: A.1.1
|
||||
isWorkspace: false
|
||||
orphans:
|
||||
- A.1.3
|
||||
- B.1.2
|
||||
|
||||
@ -1,28 +1,13 @@
|
||||
tree:
|
||||
author: A
|
||||
changes:
|
||||
- id: A.1.1
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "some text"
|
||||
readKey: key.Read.1
|
||||
- id: A.1.2
|
||||
identity: A
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
acceptKey: key.Sign.Onetime1
|
||||
@ -34,16 +19,9 @@ changes:
|
||||
identity: C
|
||||
permission: reader
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: A.1.3
|
||||
identity: A
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "second"
|
||||
readKey: key.Read.1
|
||||
- id: B.1.1
|
||||
identity: B
|
||||
- identity: B
|
||||
aclChanges:
|
||||
- userJoin:
|
||||
identity: B
|
||||
@ -52,56 +30,25 @@ changes:
|
||||
inviteId: A.1.2
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.2
|
||||
identity: B
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "first"
|
||||
readKey: key.Read.1
|
||||
- id: C.1.1
|
||||
identity: C
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "third"
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Enc:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- Onetime1
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Sign:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- Onetime1
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Read:
|
||||
- 1
|
||||
graph:
|
||||
- id: A.1.1
|
||||
baseSnapshot: A.1.1
|
||||
- id: A.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.1]
|
||||
treeHeads: [A.1.1]
|
||||
- id: B.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
- id: B.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: A.1.3 # this should be invalid, because it is based on one of the invalid changes
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.2, C.1.1]
|
||||
- id: C.1.1 # this should be invalid, because C is a reader
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
header:
|
||||
firstChangeId: A.1.1
|
||||
isWorkspace: false
|
||||
orphans:
|
||||
- "A.1.3"
|
||||
- name: 1
|
||||
value: generated
|
||||
|
||||
@ -1,152 +0,0 @@
|
||||
tree:
|
||||
author: A
|
||||
changes:
|
||||
- id: A.1.1
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "some text"
|
||||
readKey: key.Read.1
|
||||
- id: A.1.2
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
acceptKey: key.Sign.Onetime1
|
||||
encryptionKey: key.Enc.Onetime1
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permissions: writer
|
||||
inviteId: A.1.2
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: reader
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
- id: A.1.3
|
||||
identity: A
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "second"
|
||||
readKey: key.Read.1
|
||||
- id: B.1.1
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userJoin:
|
||||
identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
acceptSignature: key.Sign.Onetime1
|
||||
inviteId: A.1.2
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.2
|
||||
identity: B
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "first"
|
||||
readKey: key.Read.1
|
||||
- id: C.1.1
|
||||
identity: C
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "third"
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Enc:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- Onetime1
|
||||
Sign:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- Onetime1
|
||||
Read:
|
||||
- 1
|
||||
graph:
|
||||
- id: A.1.1
|
||||
baseSnapshot: A.1.1
|
||||
- id: A.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.1]
|
||||
treeHeads: [A.1.1]
|
||||
- id: B.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
- id: B.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: A.1.3 # this should be invalid, because it is based on one of the invalid changes
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.2, C.1.1]
|
||||
- id: C.1.1 # this should be invalid, because C is a reader
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
header:
|
||||
firstChangeId: A.1.1
|
||||
isWorkspace: false
|
||||
orphans:
|
||||
- "A.1.3"
|
||||
updates:
|
||||
- useCase: append
|
||||
changes:
|
||||
- id: B.1.3
|
||||
identity: B
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "second"
|
||||
readKey: key.Read.1
|
||||
- id: A.1.4
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: D
|
||||
permission: writer
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
graph:
|
||||
- id: B.1.3
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [ B.1.1 ]
|
||||
treeHeads: [ B.1.2 ]
|
||||
- id: A.1.4
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [ B.1.1 ]
|
||||
treeHeads: [ B.1.3 ]
|
||||
- useCase: rebuild
|
||||
changes:
|
||||
- id: A.1.4
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: D
|
||||
permission: writer
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
graph:
|
||||
- id: A.1.4
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [ A.1.1 ]
|
||||
treeHeads: [ A.1.1 ]
|
||||
@ -1,109 +0,0 @@
|
||||
tree:
|
||||
author: A
|
||||
changes:
|
||||
- id: A.1.1
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
- userAdd:
|
||||
identity: B
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "some text"
|
||||
readKey: key.Read.1
|
||||
- id: A.1.2
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userRemove:
|
||||
removedIdentity: B
|
||||
newReadKey: key.Read.2
|
||||
identitiesLeft: [A, C]
|
||||
readKey: key.Read.2
|
||||
- id: A.1.3
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: E
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.E
|
||||
encryptedReadKeys: [key.Read.1, key.Read.2]
|
||||
readKey: key.Read.2
|
||||
- id: B.1.1
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.2
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: D
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Enc:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
Sign:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
Read:
|
||||
- 1
|
||||
- 2
|
||||
graph:
|
||||
- id: A.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
- id: A.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: B.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.1]
|
||||
treeHeads: [A.1.1]
|
||||
- id: B.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: A.1.3
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
orphans:
|
||||
- "A.1.3"
|
||||
- "B.1.2"
|
||||
header:
|
||||
firstChangeId: A.1.1
|
||||
isWorkspace: false
|
||||
@ -1,28 +1,13 @@
|
||||
tree:
|
||||
author: A
|
||||
changes:
|
||||
- id: A.1.1
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "some text"
|
||||
readKey: key.Read.1
|
||||
- id: A.1.2
|
||||
identity: A
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
acceptKey: key.Sign.Onetime1
|
||||
@ -30,23 +15,13 @@ changes:
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permissions: writer
|
||||
inviteId: A.1.2
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: reader
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: A.1.3
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userRemove:
|
||||
removedIdentity: B
|
||||
newReadKey: key.Read.2
|
||||
identitiesLeft: [A]
|
||||
readKey: key.Read.2
|
||||
- id: A.1.4
|
||||
identity: A
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "first"
|
||||
readKey: key.Read.2
|
||||
- id: B.1.1
|
||||
identity: B
|
||||
- identity: B
|
||||
aclChanges:
|
||||
- userJoin:
|
||||
identity: B
|
||||
@ -55,56 +30,34 @@ changes:
|
||||
inviteId: A.1.2
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.2
|
||||
identity: B
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "second"
|
||||
readKey: key.Read.1
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userRemove:
|
||||
removedIdentity: B
|
||||
newReadKey: key.Read.2
|
||||
identitiesLeft: [A, C]
|
||||
readKey: key.Read.2
|
||||
keys:
|
||||
Enc:
|
||||
- A
|
||||
- B
|
||||
- Onetime1
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Sign:
|
||||
- A
|
||||
- B
|
||||
- Onetime1
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Read:
|
||||
- 1
|
||||
- 2
|
||||
graph:
|
||||
- id: A.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
- id: A.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
aclHeads: [A.1.1]
|
||||
treeHeads: [A.1.1]
|
||||
- id: B.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
- id: B.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: A.1.3
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: A.1.4
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
aclHeads: [A.1.3]
|
||||
treeHeads: [A.1.3]
|
||||
orphans:
|
||||
- "A.1.4"
|
||||
- "B.1.2"
|
||||
header:
|
||||
firstChangeId: A.1.1
|
||||
isWorkspace: false
|
||||
- name: 1
|
||||
value: generated
|
||||
- name: 2
|
||||
value: generated
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
tree:
|
||||
author: A
|
||||
changes:
|
||||
- id: A.1.1
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
- userAdd:
|
||||
identity: B
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
changes:
|
||||
- textAppend:
|
||||
text: "some text"
|
||||
- id: A.1.2
|
||||
identity: A
|
||||
aclSnapshot:
|
||||
userStates:
|
||||
- identity: A
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
permission: admin
|
||||
- identity: C
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
permission: admin
|
||||
- identity: D
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
permission: admin
|
||||
snapshot:
|
||||
text: "some text"
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: D
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.D
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: A.1.3
|
||||
identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: E
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.E
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.1
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
- id: B.1.2
|
||||
identity: B
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: F
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.F
|
||||
encryptedReadKeys: [ key.Read.1 ]
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Enc:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
- F
|
||||
Sign:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
- F
|
||||
Read:
|
||||
- 1
|
||||
- 2
|
||||
graph:
|
||||
- id: A.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclSnapshot: A.1.1
|
||||
- id: A.1.2
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [B.1.1]
|
||||
treeHeads: [B.1.1]
|
||||
- id: B.1.1
|
||||
baseSnapshot: A.1.1
|
||||
aclHeads: [A.1.1]
|
||||
treeHeads: [A.1.1]
|
||||
- id: B.1.2
|
||||
baseSnapshot: A.1.2
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
- id: A.1.3
|
||||
baseSnapshot: A.1.2
|
||||
aclHeads: [A.1.2]
|
||||
treeHeads: [A.1.2]
|
||||
orphans:
|
||||
- "A.1.3"
|
||||
- "B.1.2"
|
||||
header:
|
||||
firstChangeId: A.1.1
|
||||
isWorkspace: false
|
||||
|
||||
117
pkg/acl/tree/change.go
Normal file
117
pkg/acl/tree/change.go
Normal file
@ -0,0 +1,117 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIncorrectSignature = errors.New("change has incorrect signature")
|
||||
ErrIncorrectCID = errors.New("change has incorrect CID")
|
||||
)
|
||||
|
||||
type ChangeContent struct {
|
||||
ChangesData proto.Marshaler
|
||||
ACLData *aclpb.ACLChangeACLData
|
||||
Id string // TODO: this is just for testing, because id should be created automatically from content
|
||||
}
|
||||
|
||||
// Change is an abstract type for all types of changes
|
||||
type Change struct {
|
||||
Next []*Change
|
||||
PreviousIds []string
|
||||
Id string
|
||||
SnapshotId string
|
||||
IsSnapshot bool
|
||||
DecryptedChange []byte // TODO: check if we need it
|
||||
ParsedModel interface{} // TODO: check if we need it
|
||||
|
||||
// iterator helpers
|
||||
visited bool
|
||||
branchesFinished bool
|
||||
|
||||
Content *aclpb.Change
|
||||
Sign []byte
|
||||
}
|
||||
|
||||
func (ch *Change) ProtoChange() proto.Marshaler {
|
||||
return ch.Content
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptContents(key *symmetric.Key) error {
|
||||
// if the document is already decrypted
|
||||
if ch.Content.CurrentReadKeyHash == 0 {
|
||||
return nil
|
||||
}
|
||||
decrypted, err := key.Decrypt(ch.Content.ChangesData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt changes data: %w", err)
|
||||
}
|
||||
|
||||
ch.DecryptedChange = decrypted
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewChangeFromRaw(rawChange *aclpb.RawChange) (*Change, error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
err := proto.Unmarshal(rawChange.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := NewChange(rawChange.Id, unmarshalled, rawChange.Signature)
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func newVerifiedChangeFromRaw(
|
||||
rawChange *aclpb.RawChange,
|
||||
kch *keychain) (*Change, error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
ch, err := NewChangeFromRaw(rawChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identityKey, err := kch.getOrAdd(unmarshalled.Identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := identityKey.Verify(rawChange.Payload, rawChange.Signature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !res {
|
||||
return nil, ErrIncorrectSignature
|
||||
}
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func NewChange(id string, ch *aclpb.Change, signature []byte) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
PreviousIds: ch.TreeHeadIds,
|
||||
Id: id,
|
||||
Content: ch,
|
||||
SnapshotId: ch.SnapshotBaseId,
|
||||
IsSnapshot: ch.IsSnapshot,
|
||||
Sign: signature,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptedChangeContent() []byte {
|
||||
return ch.DecryptedChange
|
||||
}
|
||||
|
||||
func (ch *Change) Signature() []byte {
|
||||
return ch.Sign
|
||||
}
|
||||
|
||||
func (ch *Change) CID() string {
|
||||
return ch.Id
|
||||
}
|
||||
126
pkg/acl/tree/changebuilder.go
Normal file
126
pkg/acl/tree/changebuilder.go
Normal file
@ -0,0 +1,126 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
const componentBuilder = "tree.changebuilder"
|
||||
|
||||
type BuilderContent struct {
|
||||
treeHeadIds []string
|
||||
aclHeadId string
|
||||
snapshotBaseId string
|
||||
currentReadKeyHash uint64
|
||||
identity string
|
||||
isSnapshot bool
|
||||
signingKey signingkey.PrivKey
|
||||
readKey *symmetric.Key
|
||||
content proto.Marshaler
|
||||
}
|
||||
|
||||
type ChangeBuilder interface {
|
||||
ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error)
|
||||
ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error)
|
||||
BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error)
|
||||
}
|
||||
|
||||
type changeBuilder struct {
|
||||
keys *keychain
|
||||
}
|
||||
|
||||
func newChangeBuilder(keys *keychain) *changeBuilder {
|
||||
return &changeBuilder{keys: keys}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
err = proto.Unmarshal(rawChange.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch = NewChange(rawChange.Id, unmarshalled, rawChange.Signature)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
ch, err = c.ConvertFromRaw(rawChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identityKey, err := c.keys.getOrAdd(unmarshalled.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
res, err := identityKey.Verify(rawChange.Payload, rawChange.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrIncorrectSignature
|
||||
return
|
||||
}
|
||||
|
||||
// verifying ID
|
||||
if !cid.VerifyCID(rawChange.Payload, rawChange.Id) {
|
||||
err = ErrIncorrectCID
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) {
|
||||
aclChange := &aclpb.Change{
|
||||
TreeHeadIds: payload.treeHeadIds,
|
||||
AclHeadId: payload.aclHeadId,
|
||||
SnapshotBaseId: payload.snapshotBaseId,
|
||||
CurrentReadKeyHash: payload.currentReadKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: payload.identity,
|
||||
IsSnapshot: payload.isSnapshot,
|
||||
}
|
||||
marshalledData, err := payload.content.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, err := payload.readKey.Encrypt(marshalledData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclChange.ChangesData = encrypted
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := payload.signingKey.Sign(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch = NewChange(id, aclChange, signature)
|
||||
ch.ParsedModel = payload.content
|
||||
|
||||
raw = &aclpb.RawChange{
|
||||
Payload: fullMarshalledChange,
|
||||
Signature: signature,
|
||||
Id: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
58
pkg/acl/tree/changevalidator.go
Normal file
58
pkg/acl/tree/changevalidator.go
Normal file
@ -0,0 +1,58 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
)
|
||||
|
||||
type ObjectTreeValidator interface {
|
||||
// ValidateTree should always be entered while holding a read lock on ACLList
|
||||
ValidateTree(tree *Tree, aclList list.ACLList) error
|
||||
}
|
||||
|
||||
type objectTreeValidator struct{}
|
||||
|
||||
func newTreeValidator() ObjectTreeValidator {
|
||||
return &objectTreeValidator{}
|
||||
}
|
||||
|
||||
func (v *objectTreeValidator) ValidateTree(tree *Tree, aclList list.ACLList) (err error) {
|
||||
|
||||
var (
|
||||
perm list.UserPermissionPair
|
||||
state = aclList.ACLState()
|
||||
)
|
||||
|
||||
tree.Iterate(tree.RootId(), func(c *Change) (isContinue bool) {
|
||||
// checking if the user could write
|
||||
perm, err = state.PermissionsAtRecord(c.Content.AclHeadId, c.Content.Identity)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if perm.Permission != aclpb.ACLChange_Writer && perm.Permission != aclpb.ACLChange_Admin {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return false
|
||||
}
|
||||
|
||||
// checking if the change refers to later acl heads than its previous ids
|
||||
for _, id := range c.PreviousIds {
|
||||
prevChange := tree.attached[id]
|
||||
if prevChange.Content.AclHeadId == c.Content.AclHeadId {
|
||||
continue
|
||||
}
|
||||
var after bool
|
||||
after, err = aclList.IsAfter(c.Content.AclHeadId, prevChange.Content.AclHeadId)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !after {
|
||||
err = fmt.Errorf("current acl head id (%s) should be after each of the previous ones (%s)", c.Content.AclHeadId, prevChange.Content.AclHeadId)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return err
|
||||
}
|
||||
13
pkg/acl/tree/descriptionparser.go
Normal file
13
pkg/acl/tree/descriptionparser.go
Normal file
@ -0,0 +1,13 @@
|
||||
package tree
|
||||
|
||||
type DescriptionParser interface {
|
||||
ParseChange(*Change) ([]string, error)
|
||||
}
|
||||
|
||||
var NoOpDescriptionParser = noopDescriptionParser{}
|
||||
|
||||
type noopDescriptionParser struct{}
|
||||
|
||||
func (n noopDescriptionParser) ParseChange(change *Change) ([]string, error) {
|
||||
return []string{"DOC"}, nil
|
||||
}
|
||||
31
pkg/acl/tree/keychain.go
Normal file
31
pkg/acl/tree/keychain.go
Normal file
@ -0,0 +1,31 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type keychain struct {
|
||||
decoder keys.Decoder
|
||||
keys map[string]signingkey.PubKey
|
||||
}
|
||||
|
||||
func newKeychain() *keychain {
|
||||
return &keychain{
|
||||
decoder: signingkey.NewEDPubKeyDecoder(),
|
||||
keys: make(map[string]signingkey.PubKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (k *keychain) getOrAdd(identity string) (signingkey.PubKey, error) {
|
||||
if key, exists := k.keys[identity]; exists {
|
||||
return key, nil
|
||||
}
|
||||
res, err := k.decoder.DecodeFromString(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.keys[identity] = res.(signingkey.PubKey)
|
||||
return res.(signingkey.PubKey), nil
|
||||
}
|
||||
571
pkg/acl/tree/objecttree.go
Normal file
571
pkg/acl/tree/objecttree.go
Normal file
@ -0,0 +1,571 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ObjectTreeUpdateListener interface {
|
||||
Update(tree ObjectTree)
|
||||
Rebuild(tree ObjectTree)
|
||||
}
|
||||
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
RLock()
|
||||
RUnlock()
|
||||
}
|
||||
|
||||
var (
|
||||
ErrHasInvalidChanges = errors.New("the change is invalid")
|
||||
ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
|
||||
)
|
||||
|
||||
type AddResultSummary int
|
||||
|
||||
const (
|
||||
AddResultSummaryNothing AddResultSummary = iota
|
||||
AddResultSummaryAppend
|
||||
AddResultSummaryRebuild
|
||||
)
|
||||
|
||||
type AddResult struct {
|
||||
OldHeads []string
|
||||
Heads []string
|
||||
Added []*aclpb.RawChange
|
||||
|
||||
Summary AddResultSummary
|
||||
}
|
||||
|
||||
type ChangeIterateFunc = func(change *Change) bool
|
||||
type ChangeConvertFunc = func(decrypted []byte) (any, error)
|
||||
|
||||
type ObjectTree interface {
|
||||
RWLocker
|
||||
|
||||
ID() string
|
||||
Header() *aclpb.Header
|
||||
Heads() []string
|
||||
Root() *Change
|
||||
HasChange(string) bool
|
||||
|
||||
Iterate(convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
IterateFrom(id string, convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
|
||||
SnapshotPath() []string
|
||||
ChangesAfterCommonSnapshot(snapshotPath, heads []string) ([]*aclpb.RawChange, error)
|
||||
|
||||
Storage() storage.TreeStorage
|
||||
DebugDump() (string, error)
|
||||
|
||||
AddContent(ctx context.Context, content SignableChangeContent) (*aclpb.RawChange, error)
|
||||
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
type objectTree struct {
|
||||
treeStorage storage.TreeStorage
|
||||
changeBuilder ChangeBuilder
|
||||
updateListener ObjectTreeUpdateListener
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
treeBuilder *treeBuilder
|
||||
aclList list.ACLList
|
||||
|
||||
id string
|
||||
header *aclpb.Header
|
||||
tree *Tree
|
||||
|
||||
keys map[uint64]*symmetric.Key
|
||||
|
||||
// buffers
|
||||
difSnapshotBuf []*aclpb.RawChange
|
||||
tmpChangesBuf []*Change
|
||||
newSnapshotsBuf []*Change
|
||||
notSeenIdxBuf []int
|
||||
|
||||
snapshotPath []string
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type objectTreeDeps struct {
|
||||
changeBuilder ChangeBuilder
|
||||
treeBuilder *treeBuilder
|
||||
treeStorage storage.TreeStorage
|
||||
updateListener ObjectTreeUpdateListener
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
aclList list.ACLList
|
||||
}
|
||||
|
||||
func defaultObjectTreeDeps(
|
||||
treeStorage storage.TreeStorage,
|
||||
listener ObjectTreeUpdateListener,
|
||||
aclList list.ACLList) objectTreeDeps {
|
||||
|
||||
keychain := newKeychain()
|
||||
changeBuilder := newChangeBuilder(keychain)
|
||||
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
|
||||
return objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: treeBuilder,
|
||||
treeStorage: treeStorage,
|
||||
updateListener: listener,
|
||||
validator: newTreeValidator(),
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
aclList: aclList,
|
||||
}
|
||||
}
|
||||
|
||||
func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
objTree := &objectTree{
|
||||
treeStorage: deps.treeStorage,
|
||||
updateListener: deps.updateListener,
|
||||
treeBuilder: deps.treeBuilder,
|
||||
validator: deps.validator,
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
tree: nil,
|
||||
keys: make(map[uint64]*symmetric.Key),
|
||||
tmpChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*aclpb.RawChange, 0, 10),
|
||||
notSeenIdxBuf: make([]int, 0, 10),
|
||||
newSnapshotsBuf: make([]*Change, 0, 10),
|
||||
}
|
||||
|
||||
err := objTree.rebuildFromStorage(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageHeads, err := objTree.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// comparing rebuilt heads with heads in storage
|
||||
// in theory it can happen that we didn't set heads because the process has crashed
|
||||
// therefore we want to set them later
|
||||
if !slice.UnsortedEquals(storageHeads, objTree.tree.Heads()) {
|
||||
log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", objTree.tree.Heads())).
|
||||
Errorf("the heads in storage and objTree are different")
|
||||
err = objTree.treeStorage.SetHeads(objTree.tree.Heads())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
objTree.id, err = objTree.treeStorage.ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objTree.header, err = objTree.treeStorage.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if objTree.updateListener != nil {
|
||||
objTree.updateListener.Rebuild(objTree)
|
||||
}
|
||||
|
||||
return objTree, nil
|
||||
}
|
||||
|
||||
func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateListener, aclList list.ACLList) (ObjectTree, error) {
|
||||
deps := defaultObjectTreeDeps(treeStorage, listener, aclList)
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func (ot *objectTree) rebuildFromStorage(newChanges []*Change) (err error) {
|
||||
ot.treeBuilder.Reset()
|
||||
|
||||
ot.tree, err = ot.treeBuilder.Build(newChanges)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// during building the tree we may have marked some changes as possible roots,
|
||||
// but obviously they are not roots, because of the way how we construct the tree
|
||||
ot.tree.clearPossibleRoots()
|
||||
|
||||
return ot.validateTree()
|
||||
}
|
||||
|
||||
func (ot *objectTree) ID() string {
|
||||
return ot.id
|
||||
}
|
||||
|
||||
func (ot *objectTree) Header() *aclpb.Header {
|
||||
return ot.header
|
||||
}
|
||||
|
||||
func (ot *objectTree) Storage() storage.TreeStorage {
|
||||
return ot.treeStorage
|
||||
}
|
||||
|
||||
func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (rawChange *aclpb.RawChange, err error) {
|
||||
defer func() {
|
||||
if err == nil && ot.updateListener != nil {
|
||||
ot.updateListener.Update(ot)
|
||||
}
|
||||
}()
|
||||
|
||||
payload, err := ot.prepareBuilderContent(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
objChange, rawChange, err := ot.changeBuilder.BuildContent(payload)
|
||||
if content.IsSnapshot {
|
||||
// clearing tree, because we already fixed everything in the last snapshot
|
||||
ot.tree = &Tree{}
|
||||
}
|
||||
err = ot.tree.AddMergedHead(objChange)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ot.treeStorage.AddRawChange(rawChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ot.treeStorage.SetHeads([]string{objChange.Id})
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt BuilderContent, err error) {
|
||||
ot.aclList.RLock()
|
||||
defer ot.aclList.RUnlock()
|
||||
|
||||
state := ot.aclList.ACLState() // special method for own keys
|
||||
readKey, err := state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cnt = BuilderContent{
|
||||
treeHeadIds: ot.tree.Heads(),
|
||||
aclHeadId: ot.aclList.Head().Id,
|
||||
snapshotBaseId: ot.tree.RootId(),
|
||||
currentReadKeyHash: state.CurrentReadKeyHash(),
|
||||
identity: content.Identity,
|
||||
isSnapshot: content.IsSnapshot,
|
||||
signingKey: content.Key,
|
||||
readKey: readKey,
|
||||
content: content.Proto,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
|
||||
var mode Mode
|
||||
mode, addResult, err = ot.addRawChanges(ctx, rawChanges...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// reducing tree if we have new roots
|
||||
ot.tree.reduceTree()
|
||||
|
||||
// adding to database all the added changes only after they are good
|
||||
for _, ch := range addResult.Added {
|
||||
err = ot.treeStorage.AddRawChange(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// setting heads
|
||||
err = ot.treeStorage.SetHeads(ot.tree.Heads())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ot.updateListener == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case Append:
|
||||
ot.updateListener.Update(ot)
|
||||
case Rebuild:
|
||||
ot.updateListener.Rebuild(ot)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (mode Mode, addResult AddResult, err error) {
|
||||
// resetting buffers
|
||||
ot.tmpChangesBuf = ot.tmpChangesBuf[:0]
|
||||
ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0]
|
||||
ot.difSnapshotBuf = ot.difSnapshotBuf[:0]
|
||||
ot.newSnapshotsBuf = ot.newSnapshotsBuf[:0]
|
||||
|
||||
headsCopy := func() []string {
|
||||
newHeads := make([]string, 0, len(ot.tree.Heads()))
|
||||
newHeads = append(newHeads, ot.tree.Heads()...)
|
||||
return newHeads
|
||||
}
|
||||
|
||||
// this will be returned to client, so we shouldn't use buffer here
|
||||
prevHeadsCopy := headsCopy()
|
||||
|
||||
// filtering changes, verifying and unmarshalling them
|
||||
for idx, ch := range rawChanges {
|
||||
if ot.HasChange(ch.Id) {
|
||||
continue
|
||||
}
|
||||
|
||||
var change *Change
|
||||
change, err = ot.changeBuilder.ConvertFromRawAndVerify(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if change.IsSnapshot {
|
||||
ot.newSnapshotsBuf = append(ot.newSnapshotsBuf, change)
|
||||
}
|
||||
ot.tmpChangesBuf = append(ot.tmpChangesBuf, change)
|
||||
ot.notSeenIdxBuf = append(ot.notSeenIdxBuf, idx)
|
||||
}
|
||||
|
||||
// if no new changes, then returning
|
||||
if len(ot.notSeenIdxBuf) == 0 {
|
||||
addResult = AddResult{
|
||||
OldHeads: prevHeadsCopy,
|
||||
Heads: prevHeadsCopy,
|
||||
Summary: AddResultSummaryNothing,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// returns changes that we added to the tree
|
||||
getAddedChanges := func() []*aclpb.RawChange {
|
||||
var added []*aclpb.RawChange
|
||||
for _, idx := range ot.notSeenIdxBuf {
|
||||
rawChange := rawChanges[idx]
|
||||
if _, exists := ot.tree.attached[rawChange.Id]; exists {
|
||||
added = append(added, rawChange)
|
||||
}
|
||||
}
|
||||
return added
|
||||
}
|
||||
|
||||
rollback := func() {
|
||||
for _, ch := range ot.tmpChangesBuf {
|
||||
if _, exists := ot.tree.attached[ch.Id]; exists {
|
||||
delete(ot.tree.attached, ch.Id)
|
||||
} else if _, exists := ot.tree.unAttached[ch.Id]; exists {
|
||||
delete(ot.tree.unAttached, ch.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checks if we need to go to database
|
||||
isOldSnapshot := func(ch *Change) bool {
|
||||
if ch.SnapshotId == ot.tree.RootId() {
|
||||
return false
|
||||
}
|
||||
for _, sn := range ot.newSnapshotsBuf {
|
||||
// if change refers to newly received snapshot
|
||||
if ch.SnapshotId == sn.Id {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checking if we have some changes with different snapshot and then rebuilding
|
||||
for _, ch := range ot.tmpChangesBuf {
|
||||
if isOldSnapshot(ch) {
|
||||
err = ot.rebuildFromStorage(ot.tmpChangesBuf)
|
||||
if err != nil {
|
||||
// rebuilding without new changes
|
||||
ot.rebuildFromStorage(nil)
|
||||
return
|
||||
}
|
||||
|
||||
addResult = AddResult{
|
||||
OldHeads: prevHeadsCopy,
|
||||
Heads: headsCopy(),
|
||||
Added: getAddedChanges(),
|
||||
Summary: AddResultSummaryRebuild,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// normal mode of operation, where we don't need to rebuild from database
|
||||
mode = ot.tree.Add(ot.tmpChangesBuf...)
|
||||
switch mode {
|
||||
case Nothing:
|
||||
addResult = AddResult{
|
||||
OldHeads: prevHeadsCopy,
|
||||
Heads: prevHeadsCopy,
|
||||
Summary: AddResultSummaryNothing,
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
// just rebuilding the state from start without reloading everything from tree storage
|
||||
// as an optimization we could've started from current heads, but I didn't implement that
|
||||
err = ot.validateTree()
|
||||
if err != nil {
|
||||
rollback()
|
||||
err = ErrHasInvalidChanges
|
||||
return
|
||||
}
|
||||
|
||||
addResult = AddResult{
|
||||
OldHeads: prevHeadsCopy,
|
||||
Heads: headsCopy(),
|
||||
Added: getAddedChanges(),
|
||||
Summary: AddResultSummaryAppend,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) Iterate(convert ChangeConvertFunc, iterate ChangeIterateFunc) (err error) {
|
||||
return ot.IterateFrom(ot.tree.RootId(), convert, iterate)
|
||||
}
|
||||
|
||||
func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate ChangeIterateFunc) (err error) {
|
||||
if convert == nil {
|
||||
ot.tree.Iterate(id, iterate)
|
||||
return
|
||||
}
|
||||
|
||||
ot.tree.Iterate(ot.tree.RootId(), func(c *Change) (isContinue bool) {
|
||||
var model any
|
||||
if c.ParsedModel != nil {
|
||||
return iterate(c)
|
||||
}
|
||||
readKey, exists := ot.keys[c.Content.CurrentReadKeyHash]
|
||||
if !exists {
|
||||
err = list.ErrNoReadKey
|
||||
return false
|
||||
}
|
||||
|
||||
var decrypted []byte
|
||||
decrypted, err = readKey.Decrypt(c.Content.GetChangesData())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
model, err = convert(decrypted)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
c.ParsedModel = model
|
||||
return iterate(c)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) HasChange(s string) bool {
|
||||
_, attachedExists := ot.tree.attached[s]
|
||||
_, unattachedExists := ot.tree.unAttached[s]
|
||||
return attachedExists || unattachedExists
|
||||
}
|
||||
|
||||
func (ot *objectTree) Heads() []string {
|
||||
return ot.tree.Heads()
|
||||
}
|
||||
|
||||
func (ot *objectTree) Root() *Change {
|
||||
return ot.tree.Root()
|
||||
}
|
||||
|
||||
func (ot *objectTree) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ot *objectTree) SnapshotPath() []string {
|
||||
// TODO: Add error as return parameter
|
||||
if ot.snapshotPathIsActual() {
|
||||
return ot.snapshotPath
|
||||
}
|
||||
|
||||
var path []string
|
||||
// TODO: think that the user may have not all of the snapshots locally
|
||||
currentSnapshotId := ot.tree.RootId()
|
||||
for currentSnapshotId != "" {
|
||||
sn, err := ot.treeBuilder.loadChange(currentSnapshotId)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
path = append(path, currentSnapshotId)
|
||||
currentSnapshotId = sn.SnapshotId
|
||||
}
|
||||
ot.snapshotPath = path
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string) ([]*aclpb.RawChange, error) {
|
||||
var (
|
||||
needFullDocument = len(theirPath) == 0
|
||||
ourPath = ot.SnapshotPath()
|
||||
// by default returning everything we have from start
|
||||
commonSnapshot = ourPath[len(ourPath)-1]
|
||||
err error
|
||||
)
|
||||
|
||||
// if this is non-empty request
|
||||
if !needFullDocument {
|
||||
commonSnapshot, err = commonSnapshotForTwoPaths(ourPath, theirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if commonSnapshot == ot.tree.RootId() {
|
||||
return ot.getChangesFromTree(theirHeads)
|
||||
} else {
|
||||
return ot.getChangesFromDB(commonSnapshot, theirHeads)
|
||||
}
|
||||
}
|
||||
|
||||
func (ot *objectTree) getChangesFromTree(theirHeads []string) (rawChanges []*aclpb.RawChange, err error) {
|
||||
return ot.rawChangeLoader.LoadFromTree(ot.tree, theirHeads)
|
||||
}
|
||||
|
||||
func (ot *objectTree) getChangesFromDB(commonSnapshot string, theirHeads []string) (rawChanges []*aclpb.RawChange, err error) {
|
||||
return ot.rawChangeLoader.LoadFromStorage(commonSnapshot, ot.tree.headIds, theirHeads)
|
||||
}
|
||||
|
||||
func (ot *objectTree) snapshotPathIsActual() bool {
|
||||
return len(ot.snapshotPath) != 0 && ot.snapshotPath[0] == ot.tree.RootId()
|
||||
}
|
||||
|
||||
func (ot *objectTree) validateTree() error {
|
||||
ot.aclList.RLock()
|
||||
defer ot.aclList.RUnlock()
|
||||
state := ot.aclList.ACLState()
|
||||
|
||||
// just not to take lock many times, updating the key map from aclList
|
||||
if len(ot.keys) != len(state.UserReadKeys()) {
|
||||
for key, value := range state.UserReadKeys() {
|
||||
ot.keys[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return ot.validator.ValidateTree(ot.tree, ot.aclList)
|
||||
}
|
||||
|
||||
func (ot *objectTree) DebugDump() (string, error) {
|
||||
return ot.tree.Graph(NoOpDescriptionParser)
|
||||
}
|
||||
476
pkg/acl/tree/objecttree_test.go
Normal file
476
pkg/acl/tree/objecttree_test.go
Normal file
@ -0,0 +1,476 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockChangeCreator struct{}
|
||||
|
||||
func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *aclpb.RawChange {
|
||||
aclChange := &aclpb.Change{
|
||||
TreeHeadIds: prevIds,
|
||||
AclHeadId: aclId,
|
||||
SnapshotBaseId: snapshotId,
|
||||
ChangesData: nil,
|
||||
IsSnapshot: isSnapshot,
|
||||
}
|
||||
res, _ := aclChange.Marshal()
|
||||
return &aclpb.RawChange{
|
||||
Payload: res,
|
||||
Signature: nil,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclListId, aclHeadId, firstChangeId string) storage.TreeStorage {
|
||||
firstChange := c.createRaw(firstChangeId, aclHeadId, "", true)
|
||||
header := &aclpb.Header{
|
||||
FirstId: firstChangeId,
|
||||
AclListId: aclListId,
|
||||
WorkspaceId: "",
|
||||
DocType: aclpb.Header_DocTree,
|
||||
}
|
||||
treeStorage, _ := storage.NewInMemoryTreeStorage(treeId, header, []string{firstChangeId}, []*aclpb.RawChange{firstChange})
|
||||
return treeStorage
|
||||
}
|
||||
|
||||
type mockChangeBuilder struct{}
|
||||
|
||||
func (c *mockChangeBuilder) ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
err = proto.Unmarshal(rawChange.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch = NewChange(rawChange.Id, unmarshalled, rawChange.Signature)
|
||||
return
|
||||
}
|
||||
func (c *mockChangeBuilder) ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
return c.ConvertFromRaw(rawChange)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type mockChangeValidator struct{}
|
||||
|
||||
func (m *mockChangeValidator) ValidateTree(tree *Tree, aclList list.ACLList) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testTreeContext struct {
|
||||
aclList list.ACLList
|
||||
treeStorage storage.TreeStorage
|
||||
changeBuilder *mockChangeBuilder
|
||||
changeCreator *mockChangeCreator
|
||||
objTree ObjectTree
|
||||
}
|
||||
|
||||
func prepareACLList(t *testing.T) list.ACLList {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
aclList, err := list.BuildACLList(signingkey.NewEDPubKeyDecoder(), st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
return aclList
|
||||
}
|
||||
|
||||
func prepareTreeContext(t *testing.T, aclList list.ACLList) testTreeContext {
|
||||
changeCreator := &mockChangeCreator{}
|
||||
treeStorage := changeCreator.createNewTreeStorage("treeId", aclList.ID(), aclList.Head().Id, "0")
|
||||
changeBuilder := &mockChangeBuilder{}
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
updateListener: nil,
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
validator: &mockChangeValidator{},
|
||||
aclList: aclList,
|
||||
}
|
||||
|
||||
// check build
|
||||
objTree, err := buildObjectTree(deps)
|
||||
require.NoError(t, err, "building tree should be without error")
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = objTree.Iterate(nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0"}, iterChangesId)
|
||||
return testTreeContext{
|
||||
aclList: aclList,
|
||||
treeStorage: treeStorage,
|
||||
changeBuilder: changeBuilder,
|
||||
changeCreator: changeCreator,
|
||||
objTree: objTree,
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectTree(t *testing.T) {
|
||||
aclList := prepareACLList(t)
|
||||
|
||||
t.Run("add simple", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
treeStorage := ctx.treeStorage
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
// check result
|
||||
assert.Equal(t, []string{"0"}, res.OldHeads)
|
||||
assert.Equal(t, []string{"2"}, res.Heads)
|
||||
assert.Equal(t, len(rawChanges), len(res.Added))
|
||||
assert.Equal(t, AddResultSummaryAppend, res.Summary)
|
||||
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"2"}, objTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = objTree.Iterate(nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0", "1", "2"}, iterChangesId)
|
||||
|
||||
// check storage
|
||||
heads, _ := treeStorage.Heads()
|
||||
assert.Equal(t, []string{"2"}, heads)
|
||||
|
||||
for _, ch := range rawChanges {
|
||||
raw, err := treeStorage.GetRawChange(context.Background(), ch.Id)
|
||||
assert.NoError(t, err, "storage should have all the changes")
|
||||
assert.Equal(t, ch, raw, "the changes in the storage should be the same")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("add no new changes", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("0", aclList.Head().Id, "", true, ""),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
// check result
|
||||
assert.Equal(t, []string{"0"}, res.OldHeads)
|
||||
assert.Equal(t, []string{"0"}, res.Heads)
|
||||
assert.Equal(t, 0, len(res.Added))
|
||||
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"0"}, objTree.Heads())
|
||||
})
|
||||
|
||||
t.Run("add unattachable changes", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
// check result
|
||||
assert.Equal(t, []string{"0"}, res.OldHeads)
|
||||
assert.Equal(t, []string{"0"}, res.Heads)
|
||||
assert.Equal(t, 0, len(res.Added))
|
||||
assert.Equal(t, AddResultSummaryNothing, res.Summary)
|
||||
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"0"}, objTree.Heads())
|
||||
})
|
||||
|
||||
t.Run("add new snapshot simple", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
treeStorage := ctx.treeStorage
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "3", false, "3"),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
// check result
|
||||
assert.Equal(t, []string{"0"}, res.OldHeads)
|
||||
assert.Equal(t, []string{"4"}, res.Heads)
|
||||
assert.Equal(t, len(rawChanges), len(res.Added))
|
||||
assert.Equal(t, AddResultSummaryAppend, res.Summary)
|
||||
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"4"}, objTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = objTree.Iterate(nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"3", "4"}, iterChangesId)
|
||||
assert.Equal(t, "3", objTree.Root().Id)
|
||||
|
||||
// check storage
|
||||
heads, _ := treeStorage.Heads()
|
||||
assert.Equal(t, []string{"4"}, heads)
|
||||
|
||||
for _, ch := range rawChanges {
|
||||
raw, err := treeStorage.GetRawChange(context.Background(), ch.Id)
|
||||
assert.NoError(t, err, "storage should have all the changes")
|
||||
assert.Equal(t, ch, raw, "the changes in the storage should be the same")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("snapshot path", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
}
|
||||
_, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
snapshotPath := objTree.SnapshotPath()
|
||||
assert.Equal(t, []string{"3", "0"}, snapshotPath)
|
||||
|
||||
assert.Equal(t, true, objTree.(*objectTree).snapshotPathIsActual())
|
||||
})
|
||||
|
||||
t.Run("changes from tree after common snapshot complex", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
|
||||
_, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "0", objTree.Root().Id)
|
||||
|
||||
t.Run("all changes from tree", func(t *testing.T) {
|
||||
changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{})
|
||||
require.NoError(t, err, "changes after common snapshot should be without error")
|
||||
|
||||
changeIds := make(map[string]struct{})
|
||||
for _, ch := range changes {
|
||||
changeIds[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, raw := range rawChanges {
|
||||
_, ok := changeIds[raw.Id]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
_, ok := changeIds["0"]
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
|
||||
t.Run("changes from tree after 1", func(t *testing.T) {
|
||||
changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"1"})
|
||||
require.NoError(t, err, "changes after common snapshot should be without error")
|
||||
|
||||
changeIds := make(map[string]struct{})
|
||||
for _, ch := range changes {
|
||||
changeIds[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, id := range []string{"2", "3", "4", "5", "6"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
for _, id := range []string{"0", "1"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, false, ok)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("changes from tree after 5", func(t *testing.T) {
|
||||
changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"5"})
|
||||
require.NoError(t, err, "changes after common snapshot should be without error")
|
||||
|
||||
changeIds := make(map[string]struct{})
|
||||
for _, ch := range changes {
|
||||
changeIds[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, id := range []string{"2", "3", "4", "6"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
for _, id := range []string{"0", "1", "5"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, false, ok)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("changes after common snapshot db complex", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
// main difference from tree example
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
|
||||
}
|
||||
|
||||
_, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "6", objTree.Root().Id)
|
||||
|
||||
t.Run("all changes from db", func(t *testing.T) {
|
||||
changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{})
|
||||
require.NoError(t, err, "changes after common snapshot should be without error")
|
||||
|
||||
changeIds := make(map[string]struct{})
|
||||
for _, ch := range changes {
|
||||
changeIds[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, raw := range rawChanges {
|
||||
_, ok := changeIds[raw.Id]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
_, ok := changeIds["0"]
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
|
||||
t.Run("changes from tree db 1", func(t *testing.T) {
|
||||
changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"1"})
|
||||
require.NoError(t, err, "changes after common snapshot should be without error")
|
||||
|
||||
changeIds := make(map[string]struct{})
|
||||
for _, ch := range changes {
|
||||
changeIds[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, id := range []string{"2", "3", "4", "5", "6"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
for _, id := range []string{"0", "1"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, false, ok)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("changes from tree db 5", func(t *testing.T) {
|
||||
changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"5"})
|
||||
require.NoError(t, err, "changes after common snapshot should be without error")
|
||||
|
||||
changeIds := make(map[string]struct{})
|
||||
for _, ch := range changes {
|
||||
changeIds[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, id := range []string{"2", "3", "4", "6"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
for _, id := range []string{"0", "1", "5"} {
|
||||
_, ok := changeIds[id]
|
||||
assert.Equal(t, false, ok)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("add new changes related to previous snapshot", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
treeStorage := ctx.treeStorage
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "3", objTree.Root().Id)
|
||||
|
||||
rawChanges = []*aclpb.RawChange{
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
res, err = objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
// check result
|
||||
assert.Equal(t, []string{"3"}, res.OldHeads)
|
||||
assert.Equal(t, []string{"6"}, res.Heads)
|
||||
assert.Equal(t, len(rawChanges), len(res.Added))
|
||||
assert.Equal(t, AddResultSummaryRebuild, res.Summary)
|
||||
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"6"}, objTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = objTree.Iterate(nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId)
|
||||
assert.Equal(t, "0", objTree.Root().Id)
|
||||
|
||||
// check storage
|
||||
heads, _ := treeStorage.Heads()
|
||||
assert.Equal(t, []string{"6"}, heads)
|
||||
|
||||
for _, ch := range rawChanges {
|
||||
raw, err := treeStorage.GetRawChange(context.Background(), ch.Id)
|
||||
assert.NoError(t, err, "storage should have all the changes")
|
||||
assert.Equal(t, ch, raw, "the changes in the storage should be the same")
|
||||
}
|
||||
})
|
||||
}
|
||||
255
pkg/acl/tree/rawloader.go
Normal file
255
pkg/acl/tree/rawloader.go
Normal file
@ -0,0 +1,255 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"time"
|
||||
)
|
||||
|
||||
type rawChangeLoader struct {
|
||||
treeStorage storage.TreeStorage
|
||||
changeBuilder ChangeBuilder
|
||||
|
||||
// buffers
|
||||
idStack []string
|
||||
cache map[string]rawCacheEntry
|
||||
}
|
||||
|
||||
type rawCacheEntry struct {
|
||||
change *Change
|
||||
rawChange *aclpb.RawChange
|
||||
position int
|
||||
}
|
||||
|
||||
func newRawChangeLoader(treeStorage storage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||
return &rawChangeLoader{
|
||||
treeStorage: treeStorage,
|
||||
changeBuilder: changeBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*aclpb.RawChange, error) {
|
||||
var stack []*Change
|
||||
for _, h := range t.headIds {
|
||||
stack = append(stack, t.attached[h])
|
||||
}
|
||||
|
||||
convert := func(chs []*Change) (rawChanges []*aclpb.RawChange, err error) {
|
||||
for _, ch := range chs {
|
||||
var marshalled []byte
|
||||
marshalled, err = ch.Content.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw := &aclpb.RawChange{
|
||||
Payload: marshalled,
|
||||
Signature: ch.Signature(),
|
||||
Id: ch.Id,
|
||||
}
|
||||
rawChanges = append(rawChanges, raw)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getting all changes that we visit
|
||||
var results []*Change
|
||||
rootVisited := false
|
||||
t.dfsPrev(
|
||||
stack,
|
||||
breakpoints,
|
||||
func(ch *Change) bool {
|
||||
results = append(results, ch)
|
||||
return true
|
||||
},
|
||||
func(visited []*Change) {
|
||||
if t.root.visited {
|
||||
rootVisited = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// if we stopped at breakpoints or there are no breakpoints
|
||||
if !rootVisited || len(breakpoints) == 0 {
|
||||
// in this case we will add root if there are no breakpoints
|
||||
return convert(results)
|
||||
}
|
||||
|
||||
// now starting from breakpoints
|
||||
stack = stack[:0]
|
||||
for _, h := range breakpoints {
|
||||
stack = append(stack, t.attached[h])
|
||||
}
|
||||
|
||||
// doing another dfs to get all changes before breakpoints, we need to exclude them from results
|
||||
// if we don't have some breakpoints we will just ignore them
|
||||
t.dfsPrev(
|
||||
stack,
|
||||
[]string{},
|
||||
func(ch *Change) bool {
|
||||
return true
|
||||
},
|
||||
func(visited []*Change) {
|
||||
results = discardFromSlice(results, func(change *Change) bool {
|
||||
return change.visited
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
// otherwise we want to exclude everything that wasn't in breakpoints
|
||||
return convert(results)
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*aclpb.RawChange, error) {
|
||||
// resetting cache
|
||||
r.cache = make(map[string]rawCacheEntry)
|
||||
defer func() {
|
||||
r.cache = nil
|
||||
}()
|
||||
|
||||
existingBreakpoints := make([]string, 0, len(breakpoints))
|
||||
for _, b := range breakpoints {
|
||||
entry, err := r.loadEntry(b)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entry.position = -1
|
||||
r.cache[b] = entry
|
||||
existingBreakpoints = append(existingBreakpoints, b)
|
||||
}
|
||||
r.cache[commonSnapshot] = rawCacheEntry{position: -1}
|
||||
|
||||
dfs := func(
|
||||
commonSnapshot string,
|
||||
heads []string,
|
||||
startCounter int,
|
||||
shouldVisit func(counter int, mapExists bool) bool,
|
||||
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
||||
|
||||
// resetting stack
|
||||
r.idStack = r.idStack[:0]
|
||||
r.idStack = append(r.idStack, heads...)
|
||||
|
||||
commonSnapshotVisited := false
|
||||
var err error
|
||||
for len(r.idStack) > 0 {
|
||||
id := r.idStack[len(r.idStack)-1]
|
||||
r.idStack = r.idStack[:len(r.idStack)-1]
|
||||
|
||||
entry, exists := r.cache[id]
|
||||
if !shouldVisit(entry.position, exists) {
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
entry, err = r.loadEntry(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// setting the counter when we visit
|
||||
r.cache[id] = visit(entry)
|
||||
|
||||
for _, prev := range entry.change.PreviousIds {
|
||||
if prev == commonSnapshot {
|
||||
commonSnapshotVisited = true
|
||||
break
|
||||
}
|
||||
entry, exists = r.cache[prev]
|
||||
if !shouldVisit(entry.position, exists) {
|
||||
continue
|
||||
}
|
||||
r.idStack = append(r.idStack, prev)
|
||||
}
|
||||
}
|
||||
return commonSnapshotVisited
|
||||
}
|
||||
|
||||
// preparing first pass
|
||||
r.idStack = append(r.idStack, heads...)
|
||||
var buffer []*aclpb.RawChange
|
||||
|
||||
rootVisited := dfs(commonSnapshot, heads, 0,
|
||||
func(counter int, mapExists bool) bool {
|
||||
return !mapExists
|
||||
},
|
||||
func(entry rawCacheEntry) rawCacheEntry {
|
||||
buffer = append(buffer, entry.rawChange)
|
||||
entry.position = len(buffer) - 1
|
||||
return entry
|
||||
})
|
||||
|
||||
// checking if we stopped at breakpoints
|
||||
if !rootVisited {
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// if there are no breakpoints then we should load root also
|
||||
if len(breakpoints) == 0 {
|
||||
common, err := r.loadEntry(commonSnapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer = append(buffer, common.rawChange)
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// marking all visited as nil
|
||||
dfs(commonSnapshot, existingBreakpoints, len(buffer),
|
||||
func(counter int, mapExists bool) bool {
|
||||
return !mapExists || counter < len(buffer)
|
||||
},
|
||||
func(entry rawCacheEntry) rawCacheEntry {
|
||||
if entry.position != -1 {
|
||||
buffer[entry.position] = nil
|
||||
}
|
||||
entry.position = len(buffer) + 1
|
||||
return entry
|
||||
})
|
||||
|
||||
// discarding visited
|
||||
buffer = discardFromSlice(buffer, func(change *aclpb.RawChange) bool {
|
||||
return change == nil
|
||||
})
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
rawChange, err := r.treeStorage.GetRawChange(ctx, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
change, err := r.changeBuilder.ConvertFromRaw(rawChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
entry = rawCacheEntry{
|
||||
change: change,
|
||||
rawChange: rawChange,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func discardFromSlice[T any](elements []T, isDiscarded func(T) bool) []T {
|
||||
var (
|
||||
finishedIdx = 0
|
||||
currentIdx = 0
|
||||
)
|
||||
for currentIdx < len(elements) {
|
||||
if !isDiscarded(elements[currentIdx]) {
|
||||
if finishedIdx != currentIdx {
|
||||
elements[finishedIdx] = elements[currentIdx]
|
||||
}
|
||||
finishedIdx++
|
||||
}
|
||||
currentIdx++
|
||||
}
|
||||
elements = elements[:finishedIdx]
|
||||
return elements
|
||||
}
|
||||
13
pkg/acl/tree/signablecontent.go
Normal file
13
pkg/acl/tree/signablecontent.go
Normal file
@ -0,0 +1,13 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type SignableChangeContent struct {
|
||||
Proto proto.Marshaler
|
||||
Key signingkey.PrivKey
|
||||
Identity string
|
||||
IsSnapshot bool
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
package acltree
|
||||
package tree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@ -16,7 +15,6 @@ const (
|
||||
Nothing
|
||||
)
|
||||
|
||||
// TODO: consider abstracting into separate package with iterator, remove
|
||||
type Tree struct {
|
||||
root *Change
|
||||
headIds []string
|
||||
@ -26,18 +24,15 @@ type Tree struct {
|
||||
// missed id -> list of dependency ids
|
||||
waitList map[string][]string
|
||||
invalidChanges map[string]struct{}
|
||||
possibleRoots []*Change
|
||||
|
||||
// bufs
|
||||
iterCompBuf []*Change
|
||||
iterQueue []*Change
|
||||
visitedBuf []*Change
|
||||
stackBuf []*Change
|
||||
|
||||
duplicateEvents int
|
||||
}
|
||||
|
||||
func (t *Tree) GetUnattachedChanges(changes ...*Change) []*Change {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tree) RootId() string {
|
||||
if t.root != nil {
|
||||
return t.root.Id
|
||||
@ -62,6 +57,30 @@ func (t *Tree) AddFast(changes ...*Change) {
|
||||
t.updateHeads()
|
||||
}
|
||||
|
||||
func (t *Tree) AddMergedHead(c *Change) error {
|
||||
// check that it was not inserted previously
|
||||
if _, ok := t.attached[c.Id]; ok {
|
||||
return fmt.Errorf("change already exists") // TODO: named error
|
||||
} else if _, ok := t.unAttached[c.Id]; ok {
|
||||
return fmt.Errorf("change already exists")
|
||||
}
|
||||
|
||||
// check that it was attached after adding
|
||||
if !t.add(c) {
|
||||
return fmt.Errorf("change is not attached")
|
||||
}
|
||||
|
||||
// check that previous heads have new change as next
|
||||
for _, prevHead := range t.headIds {
|
||||
head := t.attached[prevHead]
|
||||
if len(head.Next) != 1 || head.Next[0].Id != c.Id {
|
||||
return fmt.Errorf("this is not a new head")
|
||||
}
|
||||
}
|
||||
t.headIds = []string{c.Id}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tree) Add(changes ...*Change) (mode Mode) {
|
||||
var beforeHeadIds = t.headIds
|
||||
var attached bool
|
||||
@ -96,6 +115,7 @@ func (t *Tree) Add(changes ...*Change) (mode Mode) {
|
||||
return Append
|
||||
}
|
||||
|
||||
// RemoveInvalidChange removes all the changes that are descendants of id
|
||||
func (t *Tree) RemoveInvalidChange(id string) {
|
||||
stack := []string{id}
|
||||
// removing all children of this id (either next or unattached)
|
||||
@ -112,6 +132,7 @@ func (t *Tree) RemoveInvalidChange(id string) {
|
||||
t.invalidChanges[top] = struct{}{}
|
||||
if rem, exists = t.unAttached[top]; exists {
|
||||
delete(t.unAttached, top)
|
||||
// TODO: delete waitlist, this can only help for memory/performance
|
||||
} else if rem, exists = t.attached[top]; exists {
|
||||
// remove from all prev changes
|
||||
for _, id := range rem.PreviousIds {
|
||||
@ -129,9 +150,6 @@ func (t *Tree) RemoveInvalidChange(id string) {
|
||||
}
|
||||
delete(t.attached, top)
|
||||
}
|
||||
for _, el := range rem.Unattached {
|
||||
stack = append(stack, el.Id)
|
||||
}
|
||||
for _, el := range rem.Next {
|
||||
stack = append(stack, el.Id)
|
||||
}
|
||||
@ -155,6 +173,7 @@ func (t *Tree) add(c *Change) (attached bool) {
|
||||
t.unAttached = make(map[string]*Change)
|
||||
t.waitList = make(map[string][]string)
|
||||
t.invalidChanges = make(map[string]struct{})
|
||||
t.possibleRoots = make([]*Change, 0, 10)
|
||||
return true
|
||||
}
|
||||
if len(c.PreviousIds) > 1 {
|
||||
@ -163,15 +182,11 @@ func (t *Tree) add(c *Change) (attached bool) {
|
||||
// attaching only if all prev ids are attached
|
||||
attached = true
|
||||
for _, pid := range c.PreviousIds {
|
||||
if prev, ok := t.attached[pid]; ok {
|
||||
prev.Unattached = append(prev.Unattached, c)
|
||||
if _, ok := t.attached[pid]; ok {
|
||||
continue
|
||||
}
|
||||
attached = false
|
||||
if prev, ok := t.unAttached[pid]; ok {
|
||||
prev.Unattached = append(prev.Unattached, c)
|
||||
continue
|
||||
}
|
||||
// updating wait list for either unseen or unAttached changes
|
||||
wl := t.waitList[pid]
|
||||
wl = append(wl, c.Id)
|
||||
t.waitList[pid] = wl
|
||||
@ -179,11 +194,6 @@ func (t *Tree) add(c *Change) (attached bool) {
|
||||
if attached {
|
||||
t.attach(c, true)
|
||||
} else {
|
||||
// clearing wait list
|
||||
for _, wid := range t.waitList[c.Id] {
|
||||
c.Unattached = append(c.Unattached, t.unAttached[wid])
|
||||
}
|
||||
delete(t.waitList, c.Id)
|
||||
t.unAttached[c.Id] = c
|
||||
}
|
||||
return
|
||||
@ -197,6 +207,7 @@ func (t *Tree) canAttach(c *Change) (attach bool) {
|
||||
for _, id := range c.PreviousIds {
|
||||
if _, exists := t.attached[id]; !exists {
|
||||
attach = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -207,40 +218,46 @@ func (t *Tree) attach(c *Change, newEl bool) {
|
||||
if !newEl {
|
||||
delete(t.unAttached, c.Id)
|
||||
}
|
||||
if c.IsSnapshot {
|
||||
t.possibleRoots = append(t.possibleRoots, c)
|
||||
}
|
||||
|
||||
// add next to all prev changes
|
||||
for _, id := range c.PreviousIds {
|
||||
// prev id must be attached if we attach this id
|
||||
// prev id must already be attached if we attach this id, so we don't need to check if it exists
|
||||
prev := t.attached[id]
|
||||
prev.Next = append(prev.Next, c)
|
||||
if len(prev.Next) > 1 {
|
||||
sort.Sort(sortChanges(prev.Next))
|
||||
}
|
||||
for i, next := range prev.Unattached {
|
||||
if next.Id == c.Id {
|
||||
prev.Unattached[i] = nil
|
||||
prev.Unattached = append(prev.Unattached[:i], prev.Unattached[i+1:]...)
|
||||
break
|
||||
// appending c to next changes of all previous changes
|
||||
if len(prev.Next) == 0 || prev.Next[len(prev.Next)-1].Id <= c.Id {
|
||||
prev.Next = append(prev.Next, c)
|
||||
} else {
|
||||
// inserting in correct position, before the change which is greater or equal
|
||||
insertIdx := 0
|
||||
for idx, el := range prev.Next {
|
||||
if el.Id >= c.Id {
|
||||
insertIdx = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
prev.Next = append(prev.Next[:insertIdx+1], prev.Next[insertIdx:]...)
|
||||
prev.Next[insertIdx] = c
|
||||
}
|
||||
}
|
||||
// TODO: as a future optimization we can actually sort next later after we finished building the tree
|
||||
|
||||
// clearing wait list
|
||||
if waitIds, ok := t.waitList[c.Id]; ok {
|
||||
for _, wid := range waitIds {
|
||||
// next can only be in unAttached, because if next is attached then previous (we) are attached
|
||||
// which is obviously not true, because we are attaching previous only now
|
||||
next := t.unAttached[wid]
|
||||
if t.canAttach(next) {
|
||||
t.attach(next, false)
|
||||
}
|
||||
// if we can't attach next that means that some other change will trigger attachment later,
|
||||
// so we don't care about those changes
|
||||
}
|
||||
delete(t.waitList, c.Id)
|
||||
}
|
||||
|
||||
for _, next := range c.Unattached {
|
||||
if t.canAttach(next) {
|
||||
t.attach(next, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) after(id1, id2 string) (found bool) {
|
||||
@ -254,29 +271,48 @@ func (t *Tree) after(id1, id2 string) (found bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Tree) dfs(startChange string) (uniqMap map[string]*Change) {
|
||||
stack := make([]*Change, 0, 10)
|
||||
stack = append(stack, t.attached[startChange])
|
||||
uniqMap = map[string]*Change{}
|
||||
func (t *Tree) dfsPrev(stack []*Change, breakpoints []string, visit func(ch *Change) (isContinue bool), afterVisit func([]*Change)) {
|
||||
t.visitedBuf = t.visitedBuf[:0]
|
||||
|
||||
// setting breakpoints as visited
|
||||
for _, breakpoint := range breakpoints {
|
||||
if ch, ok := t.attached[breakpoint]; ok {
|
||||
ch.visited = true
|
||||
t.visitedBuf = append(t.visitedBuf, ch)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
afterVisit(t.visitedBuf)
|
||||
for _, ch := range t.visitedBuf {
|
||||
ch.visited = false
|
||||
}
|
||||
}()
|
||||
|
||||
for len(stack) > 0 {
|
||||
ch := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
if _, exists := uniqMap[ch.Id]; exists {
|
||||
if ch.visited {
|
||||
continue
|
||||
}
|
||||
|
||||
uniqMap[ch.Id] = ch
|
||||
ch.visited = true
|
||||
t.visitedBuf = append(t.visitedBuf, ch)
|
||||
|
||||
for _, prev := range ch.PreviousIds {
|
||||
stack = append(stack, t.attached[prev])
|
||||
for _, prevId := range ch.PreviousIds {
|
||||
prevCh := t.attached[prevId]
|
||||
if !prevCh.visited {
|
||||
stack = append(stack, prevCh)
|
||||
}
|
||||
}
|
||||
if !visit(ch) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return uniqMap
|
||||
}
|
||||
|
||||
func (t *Tree) updateHeads() {
|
||||
var newHeadIds, newMetaHeadIds []string
|
||||
var newHeadIds []string
|
||||
t.iterate(t.root, func(c *Change) (isContinue bool) {
|
||||
if len(c.Next) == 0 {
|
||||
newHeadIds = append(newHeadIds, c.Id)
|
||||
@ -284,37 +320,7 @@ func (t *Tree) updateHeads() {
|
||||
return true
|
||||
})
|
||||
t.headIds = newHeadIds
|
||||
t.metaHeadIds = newMetaHeadIds
|
||||
sort.Strings(t.headIds)
|
||||
sort.Strings(t.metaHeadIds)
|
||||
}
|
||||
|
||||
func (t *Tree) ACLHeads() []string {
|
||||
var aclTreeHeads []string
|
||||
for _, head := range t.Heads() {
|
||||
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
|
||||
continue
|
||||
}
|
||||
precedingHeads := t.getPrecedingACLHeads(head)
|
||||
|
||||
for _, aclHead := range precedingHeads {
|
||||
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
|
||||
continue
|
||||
}
|
||||
aclTreeHeads = append(aclTreeHeads, aclHead)
|
||||
}
|
||||
}
|
||||
return aclTreeHeads
|
||||
}
|
||||
|
||||
func (t *Tree) getPrecedingACLHeads(head string) []string {
|
||||
headChange := t.attached[head]
|
||||
|
||||
if headChange.Content.GetAclData() != nil {
|
||||
return []string{head}
|
||||
} else {
|
||||
return headChange.Content.AclHeadIds
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||
@ -381,6 +387,14 @@ func (t *Tree) Heads() []string {
|
||||
return t.headIds
|
||||
}
|
||||
|
||||
func (t *Tree) HeadsChanges() []*Change {
|
||||
var heads []*Change
|
||||
for _, head := range t.headIds {
|
||||
heads = append(heads, t.attached[head])
|
||||
}
|
||||
return heads
|
||||
}
|
||||
|
||||
func (t *Tree) String() string {
|
||||
var buf = bytes.NewBuffer(nil)
|
||||
t.Iterate(t.RootId(), func(c *Change) (isContinue bool) {
|
||||
@ -400,17 +414,3 @@ func (t *Tree) String() string {
|
||||
func (t *Tree) Get(id string) *Change {
|
||||
return t.attached[id]
|
||||
}
|
||||
|
||||
type sortChanges []*Change
|
||||
|
||||
func (s sortChanges) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s sortChanges) Less(i, j int) bool {
|
||||
return s[i].Id < s[j].Id
|
||||
}
|
||||
|
||||
func (s sortChanges) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
325
pkg/acl/tree/tree_test.go
Normal file
325
pkg/acl/tree/tree_test.go
Normal file
@ -0,0 +1,325 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newChange(id string, snapshotId string, prevIds ...string) *Change {
|
||||
return &Change{
|
||||
PreviousIds: prevIds,
|
||||
Id: id,
|
||||
SnapshotId: snapshotId,
|
||||
IsSnapshot: false,
|
||||
}
|
||||
}
|
||||
|
||||
func newSnapshot(id, snapshotId string, prevIds ...string) *Change {
|
||||
return &Change{
|
||||
PreviousIds: prevIds,
|
||||
Id: id,
|
||||
SnapshotId: snapshotId,
|
||||
IsSnapshot: true,
|
||||
}
|
||||
}
|
||||
|
||||
func TestTree_Add(t *testing.T) {
|
||||
t.Run("add first el", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
assert.Equal(t, Rebuild, tr.Add(newSnapshot("root", "")))
|
||||
assert.Equal(t, tr.root.Id, "root")
|
||||
assert.Equal(t, []string{"root"}, tr.Heads())
|
||||
})
|
||||
t.Run("linear add", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
assert.Equal(t, Rebuild, tr.Add(
|
||||
newSnapshot("root", ""),
|
||||
newChange("one", "root", "root"),
|
||||
newChange("two", "root", "one"),
|
||||
))
|
||||
assert.Equal(t, []string{"two"}, tr.Heads())
|
||||
assert.Equal(t, Append, tr.Add(newChange("three", "root", "two")))
|
||||
el := tr.root
|
||||
var ids []string
|
||||
for el != nil {
|
||||
ids = append(ids, el.Id)
|
||||
if len(el.Next) > 0 {
|
||||
el = el.Next[0]
|
||||
} else {
|
||||
el = nil
|
||||
}
|
||||
}
|
||||
assert.Equal(t, []string{"root", "one", "two", "three"}, ids)
|
||||
assert.Equal(t, []string{"three"}, tr.Heads())
|
||||
})
|
||||
t.Run("branch", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
assert.Equal(t, Rebuild, tr.Add(
|
||||
newSnapshot("root", ""),
|
||||
newChange("1", "root", "root"),
|
||||
newChange("2", "root", "1"),
|
||||
))
|
||||
assert.Equal(t, []string{"2"}, tr.Heads())
|
||||
assert.Equal(t, Rebuild, tr.Add(
|
||||
newChange("1.2", "root", "1.1"),
|
||||
newChange("1.3", "root", "1.2"),
|
||||
newChange("1.1", "root", "1"),
|
||||
))
|
||||
assert.Len(t, tr.attached["1"].Next, 2)
|
||||
assert.Len(t, tr.unAttached, 0)
|
||||
assert.Len(t, tr.attached, 6)
|
||||
assert.Equal(t, []string{"1.3", "2"}, tr.Heads())
|
||||
})
|
||||
t.Run("branch union", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
assert.Equal(t, Rebuild, tr.Add(
|
||||
newSnapshot("root", ""),
|
||||
newChange("1", "root", "root"),
|
||||
newChange("2", "root", "1"),
|
||||
newChange("1.2", "root", "1.1"),
|
||||
newChange("1.3", "root", "1.2"),
|
||||
newChange("1.1", "root", "1"),
|
||||
newChange("3", "root", "2", "1.3"),
|
||||
newChange("4", "root", "3"),
|
||||
))
|
||||
assert.Len(t, tr.unAttached, 0)
|
||||
assert.Len(t, tr.attached, 8)
|
||||
assert.Equal(t, []string{"4"}, tr.Heads())
|
||||
})
|
||||
t.Run("big set", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
tr.Add(newSnapshot("root", ""))
|
||||
var changes []*Change
|
||||
for i := 0; i < 10000; i++ {
|
||||
if i == 0 {
|
||||
changes = append(changes, newChange(fmt.Sprint(i), "root", "root"))
|
||||
} else {
|
||||
changes = append(changes, newChange(fmt.Sprint(i), "root", fmt.Sprint(i-1)))
|
||||
}
|
||||
}
|
||||
st := time.Now()
|
||||
tr.AddFast(changes...)
|
||||
t.Log(time.Since(st))
|
||||
assert.Equal(t, []string{"9999"}, tr.Heads())
|
||||
})
|
||||
// TODO: add my tests
|
||||
}
|
||||
|
||||
func TestTree_Hash(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
tr.Add(newSnapshot("root", ""))
|
||||
hash1 := tr.Hash()
|
||||
assert.Equal(t, tr.Hash(), hash1)
|
||||
tr.Add(newChange("1", "root", "root"))
|
||||
assert.NotEqual(t, tr.Hash(), hash1)
|
||||
assert.Equal(t, tr.Hash(), tr.Hash())
|
||||
}
|
||||
|
||||
func TestTree_AddFuzzy(t *testing.T) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
getChanges := func() []*Change {
|
||||
changes := []*Change{
|
||||
newChange("1", "root", "root"),
|
||||
newChange("2", "root", "1"),
|
||||
newChange("1.2", "root", "1.1"),
|
||||
newChange("1.3", "root", "1.2"),
|
||||
newChange("1.1", "root", "1"),
|
||||
newChange("3", "root", "2", "1.3"),
|
||||
}
|
||||
rand.Shuffle(len(changes), func(i, j int) {
|
||||
changes[i], changes[j] = changes[j], changes[i]
|
||||
})
|
||||
return changes
|
||||
}
|
||||
var phash string
|
||||
for i := 0; i < 100; i++ {
|
||||
tr := new(Tree)
|
||||
tr.Add(newSnapshot("root", ""))
|
||||
tr.Add(getChanges()...)
|
||||
assert.Len(t, tr.unAttached, 0)
|
||||
assert.Len(t, tr.attached, 7)
|
||||
hash := tr.Hash()
|
||||
if phash != "" {
|
||||
assert.Equal(t, phash, hash)
|
||||
}
|
||||
phash = hash
|
||||
assert.Equal(t, []string{"3"}, tr.Heads())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTree_CheckRootReduce(t *testing.T) {
|
||||
t.Run("check root once", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
tr.Add(
|
||||
newSnapshot("0", ""),
|
||||
newChange("1", "0", "0"),
|
||||
newChange("1.1", "0", "1"),
|
||||
newChange("1.2", "0", "1"),
|
||||
newChange("1.4", "0", "1.2"),
|
||||
newChange("1.3", "0", "1"),
|
||||
newChange("1.3.1", "0", "1.3"),
|
||||
newChange("1.2+3", "0", "1.4", "1.3.1"),
|
||||
newChange("1.2+3.1", "0", "1.2+3"),
|
||||
newSnapshot("10", "0", "1.2+3.1", "1.1"),
|
||||
newChange("last", "10", "10"),
|
||||
)
|
||||
t.Run("check root", func(t *testing.T) {
|
||||
total := tr.checkRoot(tr.attached["10"])
|
||||
assert.Equal(t, 1, total)
|
||||
})
|
||||
t.Run("reduce", func(t *testing.T) {
|
||||
tr.reduceTree()
|
||||
assert.Equal(t, "10", tr.RootId())
|
||||
var res []string
|
||||
tr.Iterate(tr.RootId(), func(c *Change) (isContinue bool) {
|
||||
res = append(res, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, []string{"10", "last"}, res)
|
||||
})
|
||||
})
|
||||
t.Run("check root many", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
tr.Add(
|
||||
newSnapshot("0", ""),
|
||||
newSnapshot("1", "0", "0"),
|
||||
newChange("1.2", "0", "1"),
|
||||
newChange("1.3", "0", "1"),
|
||||
newChange("1.3.1", "0", "1.3"),
|
||||
newSnapshot("1.2+3", "1", "1.2", "1.3.1"),
|
||||
newChange("1.2+3.1", "1", "1.2+3"),
|
||||
newChange("1.2+3.2", "1", "1.2+3"),
|
||||
newSnapshot("10", "1.2+3", "1.2+3.1", "1.2+3.2"),
|
||||
newChange("last", "10", "10"),
|
||||
)
|
||||
t.Run("check root", func(t *testing.T) {
|
||||
total := tr.checkRoot(tr.attached["10"])
|
||||
assert.Equal(t, 1, total)
|
||||
|
||||
total = tr.checkRoot(tr.attached["1.2+3"])
|
||||
assert.Equal(t, 4, total)
|
||||
|
||||
total = tr.checkRoot(tr.attached["1"])
|
||||
assert.Equal(t, 8, total)
|
||||
})
|
||||
t.Run("reduce", func(t *testing.T) {
|
||||
tr.reduceTree()
|
||||
assert.Equal(t, "10", tr.RootId())
|
||||
var res []string
|
||||
tr.Iterate(tr.RootId(), func(c *Change) (isContinue bool) {
|
||||
res = append(res, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, []string{"10", "last"}, res)
|
||||
})
|
||||
})
|
||||
t.Run("check root incorrect", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
tr.Add(
|
||||
newSnapshot("0", ""),
|
||||
newChange("1", "0", "0"),
|
||||
newChange("1.1", "0", "1"),
|
||||
newChange("1.2", "0", "1"),
|
||||
newChange("1.4", "0", "1.2"),
|
||||
newChange("1.3", "0", "1"),
|
||||
newSnapshot("1.3.1", "0", "1.3"),
|
||||
newChange("1.2+3", "0", "1.4", "1.3.1"),
|
||||
newChange("1.2+3.1", "0", "1.2+3"),
|
||||
newChange("10", "0", "1.2+3.1", "1.1"),
|
||||
newChange("last", "10", "10"),
|
||||
)
|
||||
t.Run("check root", func(t *testing.T) {
|
||||
total := tr.checkRoot(tr.attached["1.3.1"])
|
||||
assert.Equal(t, -1, total)
|
||||
})
|
||||
t.Run("reduce", func(t *testing.T) {
|
||||
tr.reduceTree()
|
||||
assert.Equal(t, "0", tr.RootId())
|
||||
assert.Equal(t, 0, len(tr.possibleRoots))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestTree_Iterate(t *testing.T) {
|
||||
t.Run("complex tree", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
tr.Add(
|
||||
newSnapshot("0", ""),
|
||||
newChange("1", "0", "0"),
|
||||
newChange("1.1", "0", "1"),
|
||||
newChange("1.2", "0", "1"),
|
||||
newChange("1.4", "0", "1.2"),
|
||||
newChange("1.3", "0", "1"),
|
||||
newChange("1.3.1", "0", "1.3"),
|
||||
newChange("1.2+3", "0", "1.4", "1.3.1"),
|
||||
newChange("1.2+3.1", "0", "1.2+3"),
|
||||
newChange("10", "0", "1.2+3.1", "1.1"),
|
||||
newChange("last", "0", "10"),
|
||||
)
|
||||
var res []string
|
||||
tr.Iterate("0", func(c *Change) (isContinue bool) {
|
||||
res = append(res, c.Id)
|
||||
return true
|
||||
})
|
||||
res = res[:0]
|
||||
tr.Iterate("0", func(c *Change) (isContinue bool) {
|
||||
res = append(res, c.Id)
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, []string{"0", "1", "1.1", "1.2", "1.4", "1.3", "1.3.1", "1.2+3", "1.2+3.1", "10", "last"}, res)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTree_Add(b *testing.B) {
|
||||
getChanges := func() []*Change {
|
||||
return []*Change{
|
||||
newChange("1", "root", "root"),
|
||||
newChange("2", "root", "1"),
|
||||
newChange("1.2", "root", "1.1"),
|
||||
newChange("1.3", "root", "1.2"),
|
||||
newChange("1.1", "root", "1"),
|
||||
newChange("3", "root", "2", "1.3"),
|
||||
}
|
||||
}
|
||||
b.Run("by one", func(b *testing.B) {
|
||||
tr := new(Tree)
|
||||
tr.Add(newSnapshot("root", ""))
|
||||
tr.Add(getChanges()...)
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Add(newChange(fmt.Sprint(i+4), "root", fmt.Sprint(i+3)))
|
||||
}
|
||||
})
|
||||
b.Run("add", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr := new(Tree)
|
||||
tr.Add(newSnapshot("root", ""))
|
||||
tr.Add(getChanges()...)
|
||||
}
|
||||
})
|
||||
b.Run("add fast", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr := new(Tree)
|
||||
tr.AddFast(newSnapshot("root", ""))
|
||||
tr.AddFast(getChanges()...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTree_IterateLinear(b *testing.B) {
|
||||
// prepare linear tree
|
||||
tr := new(Tree)
|
||||
tr.AddFast(newSnapshot("0", ""))
|
||||
for j := 0; j < 10000; j++ {
|
||||
tr.Add(newChange(fmt.Sprint(j+1), "0", fmt.Sprint(j)))
|
||||
}
|
||||
b.Run("add linear", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Iterate("0", func(c *Change) (isContinue bool) {
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1,12 +1,14 @@
|
||||
package acltree
|
||||
package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -15,87 +17,83 @@ var (
|
||||
)
|
||||
|
||||
type treeBuilder struct {
|
||||
cache map[string]*Change
|
||||
identityKeys map[string]signingkey.PubKey
|
||||
signingPubKeyDecoder signingkey.PubKeyDecoder
|
||||
tree *Tree
|
||||
treeStorage treestorage.TreeStorage
|
||||
treeStorage storage.TreeStorage
|
||||
builder ChangeBuilder
|
||||
|
||||
*changeLoader
|
||||
cache map[string]*Change
|
||||
tree *Tree
|
||||
|
||||
// buffers
|
||||
idStack []string
|
||||
loadBuffer []*Change
|
||||
}
|
||||
|
||||
func newTreeBuilder(t treestorage.TreeStorage, decoder signingkey.PubKeyDecoder) *treeBuilder {
|
||||
func newTreeBuilder(storage storage.TreeStorage, builder ChangeBuilder) *treeBuilder {
|
||||
return &treeBuilder{
|
||||
signingPubKeyDecoder: decoder,
|
||||
treeStorage: t,
|
||||
changeLoader: newChangeLoader(
|
||||
t,
|
||||
decoder,
|
||||
NewChange),
|
||||
treeStorage: storage,
|
||||
builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) Init() {
|
||||
func (tb *treeBuilder) Reset() {
|
||||
tb.cache = make(map[string]*Change)
|
||||
tb.identityKeys = make(map[string]signingkey.PubKey)
|
||||
tb.tree = &Tree{}
|
||||
tb.changeLoader.Init(tb.cache, tb.identityKeys)
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) Build(fromStart bool) (*Tree, error) {
|
||||
var headsAndOrphans []string
|
||||
orphans, err := tb.treeStorage.Orphans()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (tb *treeBuilder) Build(newChanges []*Change) (*Tree, error) {
|
||||
var headsAndNewChanges []string
|
||||
heads, err := tb.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headsAndOrphans = append(headsAndOrphans, orphans...)
|
||||
headsAndOrphans = append(headsAndOrphans, heads...)
|
||||
|
||||
if fromStart {
|
||||
if err := tb.buildTreeFromStart(headsAndOrphans); err != nil {
|
||||
return nil, fmt.Errorf("buildTree error: %v", err)
|
||||
}
|
||||
} else {
|
||||
breakpoint, err := tb.findBreakpoint(headsAndOrphans)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("findBreakpoint error: %v", err)
|
||||
}
|
||||
|
||||
if err = tb.buildTree(headsAndOrphans, breakpoint); err != nil {
|
||||
return nil, fmt.Errorf("buildTree error: %v", err)
|
||||
}
|
||||
headsAndNewChanges = append(headsAndNewChanges, heads...)
|
||||
tb.cache = make(map[string]*Change)
|
||||
for _, ch := range newChanges {
|
||||
headsAndNewChanges = append(headsAndNewChanges, ch.Id)
|
||||
tb.cache[ch.Id] = ch
|
||||
}
|
||||
|
||||
tb.cache = nil
|
||||
log.With(zap.Strings("heads", heads)).Debug("building tree")
|
||||
breakpoint, err := tb.findBreakpoint(headsAndNewChanges)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("findBreakpoint error: %v", err)
|
||||
}
|
||||
|
||||
if err = tb.buildTree(headsAndNewChanges, breakpoint); err != nil {
|
||||
return nil, fmt.Errorf("buildTree error: %v", err)
|
||||
}
|
||||
|
||||
return tb.tree, nil
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) buildTreeFromStart(heads []string) (err error) {
|
||||
changes, root, err := tb.dfsFromStart(heads)
|
||||
func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error) {
|
||||
ch, err := tb.loadChange(breakpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
tb.tree.AddFast(ch)
|
||||
changes, err := tb.dfs(heads, breakpoint)
|
||||
|
||||
tb.tree.AddFast(root)
|
||||
tb.tree.AddFast(changes...)
|
||||
return
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) dfsFromStart(heads []string) (buf []*Change, root *Change, err error) {
|
||||
var possibleRoots []*Change
|
||||
stack := make([]string, len(heads), len(heads)*2)
|
||||
copy(stack, heads)
|
||||
func (tb *treeBuilder) dfs(heads []string, breakpoint string) (buf []*Change, err error) {
|
||||
// initializing buffers
|
||||
tb.idStack = tb.idStack[:0]
|
||||
tb.loadBuffer = tb.loadBuffer[:0]
|
||||
|
||||
buf = make([]*Change, 0, len(stack)*2)
|
||||
uniqMap := make(map[string]struct{})
|
||||
for len(stack) > 0 {
|
||||
id := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
// updating map
|
||||
uniqMap := map[string]struct{}{breakpoint: {}}
|
||||
|
||||
// preparing dfs
|
||||
tb.idStack = append(tb.idStack, heads...)
|
||||
|
||||
// dfs
|
||||
for len(tb.idStack) > 0 {
|
||||
id := tb.idStack[len(tb.idStack)-1]
|
||||
tb.idStack = tb.idStack[:len(tb.idStack)-1]
|
||||
if _, exists := uniqMap[id]; exists {
|
||||
continue
|
||||
}
|
||||
@ -106,69 +104,38 @@ func (tb *treeBuilder) dfsFromStart(heads []string) (buf []*Change, root *Change
|
||||
}
|
||||
|
||||
uniqMap[id] = struct{}{}
|
||||
buf = append(buf, ch)
|
||||
tb.loadBuffer = append(tb.loadBuffer, ch)
|
||||
|
||||
for _, prev := range ch.PreviousIds {
|
||||
stack = append(stack, prev)
|
||||
}
|
||||
if len(ch.PreviousIds) == 0 {
|
||||
possibleRoots = append(possibleRoots, ch)
|
||||
if _, exists := uniqMap[prev]; exists {
|
||||
continue
|
||||
}
|
||||
tb.idStack = append(tb.idStack, prev)
|
||||
}
|
||||
}
|
||||
header, err := tb.treeStorage.Header()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, r := range possibleRoots {
|
||||
if r.Id == header.FirstChangeId {
|
||||
return buf, r, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("could not find root change")
|
||||
return tb.loadBuffer, nil
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error) {
|
||||
ch, err := tb.loadChange(breakpoint)
|
||||
func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
|
||||
if ch, ok := tb.cache[id]; ok {
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
change, err := tb.treeStorage.GetRawChange(ctx, id)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
tb.tree.AddFast(ch)
|
||||
changes, err := tb.dfs(heads, breakpoint, tb.loadChange)
|
||||
|
||||
tb.tree.AddFast(changes...)
|
||||
return
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) dfs(
|
||||
heads []string,
|
||||
breakpoint string,
|
||||
load func(string) (*Change, error)) (buf []*Change, err error) {
|
||||
stack := make([]string, len(heads), len(heads)*2)
|
||||
copy(stack, heads)
|
||||
|
||||
buf = make([]*Change, 0, len(stack)*2)
|
||||
uniqMap := map[string]struct{}{breakpoint: {}}
|
||||
for len(stack) > 0 {
|
||||
id := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
if _, exists := uniqMap[id]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
ch, err := load(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
uniqMap[id] = struct{}{}
|
||||
buf = append(buf, ch)
|
||||
|
||||
for _, prev := range ch.PreviousIds {
|
||||
stack = append(stack, prev)
|
||||
}
|
||||
ch, err = tb.builder.ConvertFromRawAndVerify(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
|
||||
tb.cache[id] = ch
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) findBreakpoint(heads []string) (breakpoint string, err error) {
|
||||
@ -281,11 +248,9 @@ func (tb *treeBuilder) findCommonForTwoSnapshots(s1, s2 string) (s string, err e
|
||||
}
|
||||
|
||||
isEmptySnapshot := func(ch *Change) bool {
|
||||
// TODO: add more sophisticated checks in Change for snapshots
|
||||
return !ch.IsSnapshot
|
||||
}
|
||||
|
||||
// TODO: can we even have empty snapshots?
|
||||
// prefer not empty snapshot
|
||||
if isEmptySnapshot(ch1) && !isEmptySnapshot(ch2) {
|
||||
log.Warnf("changes build Tree: prefer %s(not empty) over %s(empty)", s2, s1)
|
||||
@ -295,7 +260,6 @@ func (tb *treeBuilder) findCommonForTwoSnapshots(s1, s2 string) (s string, err e
|
||||
return s1, nil
|
||||
}
|
||||
|
||||
// TODO: add virtual change mechanics
|
||||
// unexpected behavior - just return lesser id
|
||||
if s1 < s2 {
|
||||
log.Warnf("changes build Tree: prefer %s (%s<%s)", s1, s1, s2)
|
||||
@ -2,10 +2,10 @@
|
||||
// +build !linux,!darwin android ios nographviz
|
||||
// +build !amd64
|
||||
|
||||
package acltree
|
||||
package tree
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (t *Tree) Graph() (data string, err error) {
|
||||
func (t *Tree) Graph(parser DescriptionParser) (data []string, err error) {
|
||||
return "", fmt.Errorf("not supported")
|
||||
}
|
||||
@ -5,20 +5,18 @@
|
||||
// +build !nographviz
|
||||
// +build amd64 arm64
|
||||
|
||||
package acltree
|
||||
package tree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/goccy/go-graphviz"
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (t *Tree) Graph() (data string, err error) {
|
||||
func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
|
||||
var order = make(map[string]string)
|
||||
var seq = 0
|
||||
t.Iterate(t.RootId(), func(c *Change) (isContinue bool) {
|
||||
@ -46,44 +44,15 @@ func (t *Tree) Graph() (data string, err error) {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if c.Content.GetAclData() != nil {
|
||||
n.SetStyle(cgraph.FilledNodeStyle)
|
||||
} else if c.IsSnapshot {
|
||||
n.SetStyle(cgraph.DashedNodeStyle)
|
||||
}
|
||||
n.SetStyle(cgraph.FilledNodeStyle)
|
||||
nodes[c.Id] = n
|
||||
ord := order[c.Id]
|
||||
if ord == "" {
|
||||
ord = "miss"
|
||||
}
|
||||
var chSymbs []string
|
||||
if c.Content.AclData != nil {
|
||||
for _, chc := range c.Content.AclData.AclContent {
|
||||
tp := fmt.Sprintf("%T", chc.Value)
|
||||
tp = strings.Replace(tp, "ACLChangeACLContentValueValueOf", "", 1)
|
||||
res := ""
|
||||
for _, ts := range tp {
|
||||
if unicode.IsUpper(ts) {
|
||||
res += string(ts)
|
||||
}
|
||||
}
|
||||
chSymbs = append(chSymbs, res)
|
||||
}
|
||||
}
|
||||
if c.DecryptedDocumentChange != nil {
|
||||
// TODO: add some parser to provide custom unmarshalling for the document change
|
||||
//for _, chc := range c.DecryptedDocumentChange.Content {
|
||||
// tp := fmt.Sprintf("%T", chc.Value)
|
||||
// tp = strings.Replace(tp, "ChangeContentValueOf", "", 1)
|
||||
// res := ""
|
||||
// for _, ts := range tp {
|
||||
// if unicode.IsUpper(ts) {
|
||||
// res += string(ts)
|
||||
// }
|
||||
// }
|
||||
// chSymbs = append(chSymbs, res)
|
||||
//}
|
||||
chSymbs = append(chSymbs, "DEC")
|
||||
chSymbs, err := parser.ParseChange(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shortId := c.Id
|
||||
93
pkg/acl/tree/treeiterator.go
Normal file
93
pkg/acl/tree/treeiterator.go
Normal file
@ -0,0 +1,93 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var itPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &iterator{
|
||||
stack: make([]*Change, 0, 100),
|
||||
resBuf: make([]*Change, 0, 100),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func newIterator() *iterator {
|
||||
return itPool.Get().(*iterator)
|
||||
}
|
||||
|
||||
func freeIterator(i *iterator) {
|
||||
itPool.Put(i)
|
||||
}
|
||||
|
||||
type iterator struct {
|
||||
resBuf []*Change
|
||||
stack []*Change
|
||||
f func(c *Change) bool
|
||||
}
|
||||
|
||||
func (i *iterator) iterateSkip(start *Change, skipBefore *Change, f func(c *Change) (isContinue bool)) {
|
||||
skipping := true
|
||||
i.iterate(start, func(c *Change) (isContinue bool) {
|
||||
if skipping && c != skipBefore {
|
||||
return true
|
||||
}
|
||||
skipping = false
|
||||
return f(c)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *iterator) topSort(start *Change) {
|
||||
stack := i.stack
|
||||
stack = append(stack, start)
|
||||
|
||||
for len(stack) > 0 {
|
||||
ch := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
|
||||
// this looks a bit clumsy, but the idea is that we will go through the change again as soon as we finished
|
||||
// going through its branches
|
||||
if ch.branchesFinished {
|
||||
i.resBuf = append(i.resBuf, ch)
|
||||
ch.branchesFinished = false
|
||||
continue
|
||||
}
|
||||
|
||||
// in theory, it may be the case that we add the change two times
|
||||
// but probably due to the way how we build the tree, we won't need it
|
||||
if ch.visited {
|
||||
continue
|
||||
}
|
||||
|
||||
stack = append(stack, ch)
|
||||
ch.visited = true
|
||||
ch.branchesFinished = true
|
||||
|
||||
for j := 0; j < len(ch.Next); j++ {
|
||||
if !ch.Next[j].visited {
|
||||
stack = append(stack, ch.Next[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ch := range i.resBuf {
|
||||
ch.visited = false
|
||||
}
|
||||
}
|
||||
|
||||
func (i *iterator) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||
if start == nil {
|
||||
return
|
||||
}
|
||||
// reset
|
||||
i.resBuf = i.resBuf[:0]
|
||||
i.stack = i.stack[:0]
|
||||
i.f = f
|
||||
|
||||
i.topSort(start)
|
||||
for idx := len(i.resBuf) - 1; idx >= 0; idx-- {
|
||||
if !f(i.resBuf[idx]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
92
pkg/acl/tree/treereduce.go
Normal file
92
pkg/acl/tree/treereduce.go
Normal file
@ -0,0 +1,92 @@
|
||||
package tree
|
||||
|
||||
import "math"
|
||||
|
||||
// clearPossibleRoots force removes any snapshots which can further be deemed as roots
|
||||
func (t *Tree) clearPossibleRoots() {
|
||||
t.possibleRoots = t.possibleRoots[:0]
|
||||
}
|
||||
|
||||
// checkRoot checks if a change can be a new root for the tree
|
||||
// it returns total changes which were discovered during dfsPrev from heads
|
||||
func (t *Tree) checkRoot(change *Change) (total int) {
|
||||
t.stackBuf = t.stackBuf[:0]
|
||||
stack := t.stackBuf
|
||||
|
||||
// starting with heads
|
||||
for _, h := range t.headIds {
|
||||
stack = append(stack, t.attached[h])
|
||||
}
|
||||
|
||||
t.dfsPrev(
|
||||
stack,
|
||||
[]string{change.Id},
|
||||
func(ch *Change) bool {
|
||||
total += 1
|
||||
return true
|
||||
},
|
||||
func(changes []*Change) {
|
||||
if t.root.visited {
|
||||
total = -1
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// makeRootAndRemove removes all changes before start and makes start the root
|
||||
func (t *Tree) makeRootAndRemove(start *Change) {
|
||||
t.stackBuf = t.stackBuf[:0]
|
||||
stack := t.stackBuf
|
||||
for _, prev := range start.PreviousIds {
|
||||
stack = append(stack, t.attached[prev])
|
||||
}
|
||||
|
||||
t.dfsPrev(
|
||||
stack,
|
||||
[]string{},
|
||||
func(ch *Change) bool {
|
||||
return true
|
||||
},
|
||||
func(changes []*Change) {
|
||||
for _, ch := range changes {
|
||||
delete(t.unAttached, ch.Id)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// removing unattached because they may refer to previous root
|
||||
t.unAttached = make(map[string]*Change)
|
||||
t.root = start
|
||||
}
|
||||
|
||||
// reduceTree tries to reduce the tree to one of possible tree roots
|
||||
func (t *Tree) reduceTree() (res bool) {
|
||||
if len(t.possibleRoots) == 0 {
|
||||
return
|
||||
}
|
||||
var (
|
||||
minRoot *Change
|
||||
minTotal = math.MaxInt
|
||||
)
|
||||
|
||||
// checking if we can reduce tree to other root
|
||||
for _, root := range t.possibleRoots {
|
||||
totalChanges := t.checkRoot(root)
|
||||
// we prefer new root with min amount of total changes
|
||||
if totalChanges != -1 && totalChanges < minTotal {
|
||||
minRoot = root
|
||||
minTotal = totalChanges
|
||||
}
|
||||
}
|
||||
|
||||
t.clearPossibleRoots()
|
||||
if minRoot == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.makeRootAndRemove(minRoot)
|
||||
res = true
|
||||
return
|
||||
}
|
||||
91
pkg/acl/tree/treestorage.go
Normal file
91
pkg/acl/tree/treestorage.go
Normal file
@ -0,0 +1,91 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CreateNewTreeStorage(
|
||||
acc *account.AccountData,
|
||||
aclList list.ACLList,
|
||||
content proto.Marshaler,
|
||||
create storage.TreeStorageCreatorFunc) (thr storage.TreeStorage, err error) {
|
||||
|
||||
state := aclList.ACLState()
|
||||
change := &aclpb.Change{
|
||||
AclHeadId: aclList.Head().Id,
|
||||
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: acc.Identity,
|
||||
IsSnapshot: true,
|
||||
}
|
||||
|
||||
marshalledData, err := content.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
readKey, err := state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, err := readKey.Encrypt(marshalledData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
change.ChangesData = encrypted
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := acc.SignKey.Sign(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
changeId, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rawChange := &aclpb.RawChange{
|
||||
Payload: fullMarshalledChange,
|
||||
Signature: signature,
|
||||
Id: changeId,
|
||||
}
|
||||
header, treeId, err := createTreeHeaderAndId(rawChange, aclpb.Header_DocTree, aclList.ID())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return create(storage.TreeStorageCreatePayload{
|
||||
TreeId: treeId,
|
||||
Header: header,
|
||||
Changes: []*aclpb.RawChange{rawChange},
|
||||
Heads: []string{rawChange.Id},
|
||||
})
|
||||
}
|
||||
|
||||
func createTreeHeaderAndId(change *aclpb.RawChange, treeType aclpb.HeaderDocType, aclListId string) (header *aclpb.Header, treeId string, err error) {
|
||||
header = &aclpb.Header{
|
||||
FirstId: change.Id,
|
||||
DocType: treeType,
|
||||
AclListId: aclListId,
|
||||
}
|
||||
marshalledHeader, err := proto.Marshal(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
treeId, err = cid.NewCIDFromBytes(marshalledHeader)
|
||||
return
|
||||
}
|
||||
29
pkg/acl/tree/util.go
Normal file
29
pkg/acl/tree/util.go
Normal file
@ -0,0 +1,29 @@
|
||||
package tree
|
||||
|
||||
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
|
||||
var i int
|
||||
var j int
|
||||
OuterLoop:
|
||||
// find starting point from the right
|
||||
for i = len(ourPath) - 1; i >= 0; i-- {
|
||||
for j = len(theirPath) - 1; j >= 0; j-- {
|
||||
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
|
||||
if ourPath[i] == theirPath[j] {
|
||||
break OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
if i < 0 || j < 0 {
|
||||
return "", ErrNoCommonSnapshot
|
||||
}
|
||||
// find last common element of the sequence moving from right to left
|
||||
for i >= 0 && j >= 0 {
|
||||
if ourPath[i] == theirPath[j] {
|
||||
i--
|
||||
j--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ourPath[i+1], nil
|
||||
}
|
||||
@ -1,163 +0,0 @@
|
||||
package treestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type inMemoryTreeStorage struct {
|
||||
id string
|
||||
header *treepb.TreeHeader
|
||||
heads []string
|
||||
orphans []string
|
||||
changes map[string]*aclpb.RawChange
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type CreatorFunc = func(string, *treepb.TreeHeader, []*aclpb.RawChange) (TreeStorage, error)
|
||||
|
||||
func NewInMemoryTreeStorage(
|
||||
treeId string,
|
||||
header *treepb.TreeHeader,
|
||||
changes []*aclpb.RawChange) (TreeStorage, error) {
|
||||
allChanges := make(map[string]*aclpb.RawChange)
|
||||
var orphans []string
|
||||
for _, ch := range changes {
|
||||
allChanges[ch.Id] = ch
|
||||
orphans = append(orphans, ch.Id)
|
||||
}
|
||||
|
||||
return &inMemoryTreeStorage{
|
||||
id: treeId,
|
||||
header: header,
|
||||
heads: nil,
|
||||
orphans: orphans,
|
||||
changes: allChanges,
|
||||
RWMutex: sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) TreeID() (string, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.id, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Header() (*treepb.TreeHeader, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.header, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Heads() ([]string, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.heads, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Orphans() ([]string, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.orphans, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) SetHeads(heads []string) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.heads = t.heads[:0]
|
||||
|
||||
for _, h := range heads {
|
||||
t.heads = append(t.heads, h)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) RemoveOrphans(orphans ...string) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.orphans = slice.Difference(t.orphans, orphans)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) AddOrphans(orphans ...string) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.orphans = append(t.orphans, orphans...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) AddRawChange(change *aclpb.RawChange) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
// TODO: better to do deep copy
|
||||
t.changes[change.Id] = change
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) AddChange(change aclchanges.Change) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
signature := change.Signature()
|
||||
id := change.CID()
|
||||
aclChange := change.ProtoChange()
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawChange := &aclpb.RawChange{
|
||||
Payload: fullMarshalledChange,
|
||||
Signature: signature,
|
||||
Id: id,
|
||||
}
|
||||
t.changes[id] = rawChange
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) GetChange(ctx context.Context, changeId string) (*aclpb.RawChange, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if res, exists := t.changes[changeId]; exists {
|
||||
return res, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not get change with id: %s", changeId)
|
||||
}
|
||||
|
||||
type inMemoryTreeStorageProvider struct {
|
||||
trees map[string]TreeStorage
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (i *inMemoryTreeStorageProvider) TreeStorage(treeId string) (TreeStorage, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
if tree, exists := i.trees[treeId]; exists {
|
||||
return tree, nil
|
||||
}
|
||||
return nil, ErrUnknownTreeId
|
||||
}
|
||||
|
||||
func (i *inMemoryTreeStorageProvider) CreateTreeStorage(treeId string, header *treepb.TreeHeader, changes []*aclpb.RawChange) (TreeStorage, error) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
res, err := NewInMemoryTreeStorage(treeId, header, changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.trees[treeId] = res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NewInMemoryTreeStorageProvider() Provider {
|
||||
return &inMemoryTreeStorageProvider{
|
||||
trees: make(map[string]TreeStorage),
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package treestorage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
)
|
||||
|
||||
var ErrUnknownTreeId = errors.New("tree does not exist")
|
||||
|
||||
type Provider interface {
|
||||
TreeStorage(treeId string) (TreeStorage, error)
|
||||
CreateTreeStorage(treeId string, header *treepb.TreeHeader, changes []*aclpb.RawChange) (TreeStorage, error)
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package treestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
)
|
||||
|
||||
type TreeStorage interface {
|
||||
TreeID() (string, error)
|
||||
|
||||
Header() (*treepb.TreeHeader, error)
|
||||
Heads() ([]string, error)
|
||||
Orphans() ([]string, error)
|
||||
SetHeads(heads []string) error
|
||||
RemoveOrphans(orphan ...string) error
|
||||
AddOrphans(orphan ...string) error
|
||||
|
||||
AddRawChange(change *aclpb.RawChange) error
|
||||
AddChange(change aclchanges.Change) error
|
||||
|
||||
// TODO: have methods with raw changes also
|
||||
GetChange(ctx context.Context, recordID string) (*aclpb.RawChange, error)
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
syntax = "proto3";
|
||||
package tree;
|
||||
option go_package = "treepb";
|
||||
|
||||
message TreeHeader {
|
||||
string firstChangeId = 1;
|
||||
bool isWorkspace = 2;
|
||||
// TODO: add user identity, signature and nano timestamp
|
||||
}
|
||||
@ -1,358 +0,0 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: pkg/acl/treestorage/treepb/protos/tree.proto
|
||||
|
||||
package treepb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type TreeHeader struct {
|
||||
FirstChangeId string `protobuf:"bytes,1,opt,name=firstChangeId,proto3" json:"firstChangeId,omitempty"`
|
||||
IsWorkspace bool `protobuf:"varint,2,opt,name=isWorkspace,proto3" json:"isWorkspace,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TreeHeader) Reset() { *m = TreeHeader{} }
|
||||
func (m *TreeHeader) String() string { return proto.CompactTextString(m) }
|
||||
func (*TreeHeader) ProtoMessage() {}
|
||||
func (*TreeHeader) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_e7d760b855878644, []int{0}
|
||||
}
|
||||
func (m *TreeHeader) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *TreeHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_TreeHeader.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *TreeHeader) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TreeHeader.Merge(m, src)
|
||||
}
|
||||
func (m *TreeHeader) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *TreeHeader) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_TreeHeader.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_TreeHeader proto.InternalMessageInfo
|
||||
|
||||
func (m *TreeHeader) GetFirstChangeId() string {
|
||||
if m != nil {
|
||||
return m.FirstChangeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TreeHeader) GetIsWorkspace() bool {
|
||||
if m != nil {
|
||||
return m.IsWorkspace
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*TreeHeader)(nil), "tree.TreeHeader")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("pkg/acl/treestorage/treepb/protos/tree.proto", fileDescriptor_e7d760b855878644)
|
||||
}
|
||||
|
||||
var fileDescriptor_e7d760b855878644 = []byte{
|
||||
// 165 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x29, 0xc8, 0x4e, 0xd7,
|
||||
0x4f, 0x4c, 0xce, 0xd1, 0x2f, 0x29, 0x4a, 0x4d, 0x2d, 0x2e, 0xc9, 0x2f, 0x4a, 0x4c, 0x4f, 0x05,
|
||||
0xb3, 0x0b, 0x92, 0xf4, 0x0b, 0x8a, 0xf2, 0x4b, 0xf2, 0x8b, 0xc1, 0x3c, 0x3d, 0x30, 0x5b, 0x88,
|
||||
0x05, 0xc4, 0x56, 0x0a, 0xe1, 0xe2, 0x0a, 0x29, 0x4a, 0x4d, 0xf5, 0x48, 0x4d, 0x4c, 0x49, 0x2d,
|
||||
0x12, 0x52, 0xe1, 0xe2, 0x4d, 0xcb, 0x2c, 0x2a, 0x2e, 0x71, 0xce, 0x48, 0xcc, 0x4b, 0x4f, 0xf5,
|
||||
0x4c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x42, 0x15, 0x14, 0x52, 0xe0, 0xe2, 0xce, 0x2c,
|
||||
0x0e, 0xcf, 0x2f, 0xca, 0x2e, 0x2e, 0x48, 0x4c, 0x4e, 0x95, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x08,
|
||||
0x42, 0x16, 0x72, 0x52, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4,
|
||||
0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x36,
|
||||
0x88, 0x7b, 0x92, 0xd8, 0xc0, 0x8e, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x09, 0x4f, 0xc6,
|
||||
0xec, 0xb4, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *TreeHeader) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *TreeHeader) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *TreeHeader) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.IsWorkspace {
|
||||
i--
|
||||
if m.IsWorkspace {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if len(m.FirstChangeId) > 0 {
|
||||
i -= len(m.FirstChangeId)
|
||||
copy(dAtA[i:], m.FirstChangeId)
|
||||
i = encodeVarintTree(dAtA, i, uint64(len(m.FirstChangeId)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintTree(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovTree(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *TreeHeader) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.FirstChangeId)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovTree(uint64(l))
|
||||
}
|
||||
if m.IsWorkspace {
|
||||
n += 2
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovTree(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozTree(x uint64) (n int) {
|
||||
return sovTree(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *TreeHeader) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTree
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: TreeHeader: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: TreeHeader: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field FirstChangeId", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTree
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthTree
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTree
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.FirstChangeId = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field IsWorkspace", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTree
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.IsWorkspace = bool(v != 0)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTree(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTree
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipTree(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTree
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTree
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTree
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthTree
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupTree
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthTree
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthTree = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowTree = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupTree = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
@ -61,7 +61,7 @@ func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
Identity: identity,
|
||||
SignKey: signKey,
|
||||
EncKey: decodedEncryptionKey.(encryptionkey.PrivKey),
|
||||
Decoder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
Decoder: signingkey.NewEDPubKeyDecoder(),
|
||||
}
|
||||
s.peerId = acc.PeerId
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/document"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/document"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
@ -53,7 +53,7 @@ func (s *service) Run(ctx context.Context) (err error) {
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/treeDump", s.treeDump)
|
||||
mux.HandleFunc("/createDocument", s.createDocument)
|
||||
mux.HandleFunc("/createDocumentTree", s.createDocumentTree)
|
||||
mux.HandleFunc("/appendDocument", s.appendDocument)
|
||||
s.srv.Handler = mux
|
||||
|
||||
@ -79,8 +79,9 @@ func (s *service) treeDump(w http.ResponseWriter, req *http.Request) {
|
||||
dump string
|
||||
err error
|
||||
)
|
||||
err = s.treeCache.Do(context.Background(), treeId, func(tree acltree.ACLTree) error {
|
||||
dump, err = tree.DebugDump()
|
||||
err = s.treeCache.Do(context.Background(), treeId, func(obj interface{}) error {
|
||||
t := obj.(tree.ObjectTree)
|
||||
dump, err = t.DebugDump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -93,13 +94,14 @@ func (s *service) treeDump(w http.ResponseWriter, req *http.Request) {
|
||||
sendText(w, http.StatusOK, dump)
|
||||
}
|
||||
|
||||
func (s *service) createDocument(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *service) createDocumentTree(w http.ResponseWriter, req *http.Request) {
|
||||
var (
|
||||
query = req.URL.Query()
|
||||
text = query.Get("text")
|
||||
query = req.URL.Query()
|
||||
text = query.Get("text")
|
||||
aclListId = query.Get("aclListId")
|
||||
)
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
treeId, err := s.documentService.CreateDocument(timeoutCtx, fmt.Sprintf("created document with id: %s", text))
|
||||
treeId, err := s.documentService.CreateDocumentTree(timeoutCtx, aclListId, text)
|
||||
cancel()
|
||||
if err != nil {
|
||||
sendText(w, http.StatusInternalServerError, err.Error())
|
||||
@ -115,7 +117,7 @@ func (s *service) appendDocument(w http.ResponseWriter, req *http.Request) {
|
||||
treeId = query.Get("treeId")
|
||||
)
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
err := s.documentService.UpdateDocument(timeoutCtx, treeId, text)
|
||||
err := s.documentService.UpdateDocumentTree(timeoutCtx, treeId, text)
|
||||
cancel()
|
||||
if err != nil {
|
||||
sendText(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
@ -2,14 +2,16 @@ package document
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
testchanges "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/proto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/message"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/syncproto"
|
||||
@ -25,13 +27,14 @@ type service struct {
|
||||
messageService message.Service
|
||||
treeCache treecache.Service
|
||||
account account.Service
|
||||
storage storage.Service
|
||||
// to create new documents we need to know all nodes
|
||||
nodes []*node.Node
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
UpdateDocument(ctx context.Context, id, text string) error
|
||||
CreateDocument(ctx context.Context, text string) (string, error)
|
||||
UpdateDocumentTree(ctx context.Context, id, text string) error
|
||||
CreateDocumentTree(ctx context.Context, aclTreeId string, text string) (id string, err error)
|
||||
}
|
||||
|
||||
func New() app.Component {
|
||||
@ -42,6 +45,7 @@ func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
s.account = a.MustComponent(account.CName).(account.Service)
|
||||
s.messageService = a.MustComponent(message.CName).(message.Service)
|
||||
s.treeCache = a.MustComponent(treecache.CName).(treecache.Service)
|
||||
s.storage = a.MustComponent(storage.CName).(storage.Service)
|
||||
|
||||
nodesService := a.MustComponent(node.CName).(node.Service)
|
||||
s.nodes = nodesService.Nodes()
|
||||
@ -54,41 +58,65 @@ func (s *service) Name() (name string) {
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
syncData := s.storage.ImportedACLSyncData()
|
||||
|
||||
// we could have added a timeout or some additional logic,
|
||||
// but let's just use the ACL id of the latest started node :-)
|
||||
return s.messageService.SendToSpaceAsync("", syncproto.WrapACLList(
|
||||
&syncproto.SyncACLList{Records: syncData.Records},
|
||||
syncData.Header,
|
||||
syncData.Id,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *service) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateDocument(ctx context.Context, id, text string) (err error) {
|
||||
func (s *service) UpdateDocumentTree(ctx context.Context, id, text string) (err error) {
|
||||
var (
|
||||
ch *aclpb.RawChange
|
||||
header *treepb.TreeHeader
|
||||
header *aclpb.Header
|
||||
snapshotPath []string
|
||||
heads []string
|
||||
)
|
||||
log.With(zap.String("id", id), zap.String("text", text)).
|
||||
Debug("updating document")
|
||||
|
||||
err = s.treeCache.Do(ctx, id, func(tree acltree.ACLTree) error {
|
||||
ch, err = tree.AddContent(ctx, func(builder acltree.ChangeBuilder) error {
|
||||
builder.AddChangeContent(
|
||||
&testchangepb.PlainTextChangeData{
|
||||
Content: []*testchangepb.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
})
|
||||
err = s.treeCache.Do(ctx, id, func(obj interface{}) error {
|
||||
docTree, ok := obj.(tree.ObjectTree)
|
||||
if !ok {
|
||||
return fmt.Errorf("can't update acl trees with text")
|
||||
}
|
||||
|
||||
docTree.Lock()
|
||||
defer docTree.Unlock()
|
||||
err = s.treeCache.Do(ctx, docTree.Header().AclListId, func(obj interface{}) error {
|
||||
aclTree := obj.(list.ACLList)
|
||||
aclTree.RLock()
|
||||
defer aclTree.RUnlock()
|
||||
|
||||
content := createAppendTextChange(text)
|
||||
signable := tree.SignableChangeContent{
|
||||
Proto: content,
|
||||
Key: s.account.Account().SignKey,
|
||||
Identity: s.account.Account().Identity,
|
||||
IsSnapshot: false,
|
||||
}
|
||||
ch, err = docTree.AddContent(ctx, signable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id = tree.ID()
|
||||
heads = tree.Heads()
|
||||
header = tree.Header()
|
||||
snapshotPath = tree.SnapshotPath()
|
||||
id = docTree.ID()
|
||||
heads = docTree.Heads()
|
||||
header = docTree.Header()
|
||||
snapshotPath = docTree.SnapshotPath()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -103,84 +131,87 @@ func (s *service) UpdateDocument(ctx context.Context, id, text string) (err erro
|
||||
return s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
||||
Heads: heads,
|
||||
Changes: []*aclpb.RawChange{ch},
|
||||
TreeId: id,
|
||||
SnapshotPath: snapshotPath,
|
||||
TreeHeader: header,
|
||||
}))
|
||||
}, header, id))
|
||||
}
|
||||
|
||||
func (s *service) CreateDocument(ctx context.Context, text string) (id string, err error) {
|
||||
func (s *service) CreateDocumentTree(ctx context.Context, aclListId string, text string) (id string, err error) {
|
||||
acc := s.account.Account()
|
||||
var (
|
||||
ch *aclpb.RawChange
|
||||
header *treepb.TreeHeader
|
||||
header *aclpb.Header
|
||||
snapshotPath []string
|
||||
heads []string
|
||||
)
|
||||
err = s.treeCache.Do(ctx, aclListId, func(obj interface{}) error {
|
||||
t := obj.(list.ACLList)
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
err = s.treeCache.Create(ctx, func(builder acltree.ChangeBuilder) error {
|
||||
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
|
||||
content := createInitialTextChange(text)
|
||||
doc, err := tree.CreateNewTreeStorage(acc, t, content, s.storage.CreateTreeStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// adding all predefined nodes to the document as admins
|
||||
for _, n := range s.nodes {
|
||||
err = builder.UserAdd(n.SigningKeyString, n.EncryptionKey, aclpb.ACLChange_Admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
builder.AddChangeContent(createInitialChangeContent(text))
|
||||
return nil
|
||||
}, func(tree acltree.ACLTree) error {
|
||||
id = tree.ID()
|
||||
heads = tree.Heads()
|
||||
header = tree.Header()
|
||||
snapshotPath = tree.SnapshotPath()
|
||||
ch, err = tree.Storage().GetChange(ctx, heads[0])
|
||||
id, err = doc.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.With(
|
||||
zap.String("id", id),
|
||||
zap.Strings("heads", heads),
|
||||
zap.String("header", header.String())).
|
||||
Debug("document created in the database")
|
||||
|
||||
header, err = doc.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
heads = []string{header.FirstId}
|
||||
snapshotPath = []string{header.FirstId}
|
||||
ch, err = doc.GetRawChange(ctx, header.FirstId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.With(zap.String("id", id), zap.String("text", text)).
|
||||
Debug("creating document")
|
||||
|
||||
err = s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
||||
Heads: heads,
|
||||
Changes: []*aclpb.RawChange{ch},
|
||||
TreeId: id,
|
||||
SnapshotPath: snapshotPath,
|
||||
TreeHeader: header,
|
||||
}))
|
||||
}, header, id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func createInitialChangeContent(text string) proto.Marshaler {
|
||||
return &testchangepb.PlainTextChangeData{
|
||||
Content: []*testchangepb.PlainTextChangeContent{
|
||||
func createInitialTextChange(text string) proto.Marshaler {
|
||||
return &testchanges.PlainTextChangeData{
|
||||
Content: []*testchanges.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
Snapshot: &testchangepb.PlainTextChangeSnapshot{Text: text},
|
||||
Snapshot: &testchanges.PlainTextChangeSnapshot{Text: text},
|
||||
}
|
||||
}
|
||||
|
||||
func createAppendTextChangeContent(text string) *testchangepb.PlainTextChangeContent {
|
||||
return &testchangepb.PlainTextChangeContent{
|
||||
Value: &testchangepb.PlainTextChangeContentValueOfTextAppend{
|
||||
TextAppend: &testchangepb.PlainTextChangeTextAppend{
|
||||
func createAppendTextChange(text string) proto.Marshaler {
|
||||
return &testchanges.PlainTextChangeData{
|
||||
Content: []*testchanges.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAppendTextChangeContent(text string) *testchanges.PlainTextChangeContent {
|
||||
return &testchanges.PlainTextChangeContent{
|
||||
Value: &testchanges.PlainTextChangeContentValueOfTextAppend{
|
||||
TextAppend: &testchanges.PlainTextChangeTextAppend{
|
||||
Text: text,
|
||||
},
|
||||
},
|
||||
@ -78,7 +78,8 @@ func (s *service) Handle(ctx context.Context, data []byte) (resp proto.Marshaler
|
||||
return
|
||||
}
|
||||
if spaceReq.SpaceId != "" {
|
||||
sp, err := s.get(ctx, spaceReq.SpaceId)
|
||||
var sp Space
|
||||
sp, err = s.get(ctx, spaceReq.SpaceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -19,8 +19,6 @@ type Space interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type space struct {
|
||||
id string
|
||||
conf configuration.Configuration
|
||||
|
||||
136
service/storage/service.go
Normal file
136
service/storage/service.go
Normal file
@ -0,0 +1,136 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/etc"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
||||
)
|
||||
|
||||
var CName = "storage"
|
||||
|
||||
var log = logger.NewNamed("storage").Sugar()
|
||||
|
||||
type ImportedACLSyncData struct {
|
||||
Id string
|
||||
Header *aclpb.Header
|
||||
Records []*aclpb.RawRecord
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
storage.Provider
|
||||
ImportedACLSyncData() ImportedACLSyncData
|
||||
}
|
||||
|
||||
func New() app.Component {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
storageProvider storage.Provider
|
||||
importedACLSyncData ImportedACLSyncData
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
s.storageProvider = storage.NewInMemoryTreeStorageProvider()
|
||||
// importing hardcoded acl list, check that the keys there are correct
|
||||
return s.importACLList(a)
|
||||
}
|
||||
|
||||
func (s *service) Storage(treeId string) (storage.Storage, error) {
|
||||
return s.storageProvider.Storage(treeId)
|
||||
}
|
||||
|
||||
func (s *service) AddStorage(id string, st storage.Storage) error {
|
||||
return s.storageProvider.AddStorage(id, st)
|
||||
}
|
||||
|
||||
func (s *service) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (storage.TreeStorage, error) {
|
||||
return s.storageProvider.CreateTreeStorage(payload)
|
||||
}
|
||||
|
||||
func (s *service) CreateACLListStorage(payload storage.ACLListStorageCreatePayload) (storage.ListStorage, error) {
|
||||
return s.storageProvider.CreateACLListStorage(payload)
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) ImportedACLSyncData() ImportedACLSyncData {
|
||||
return s.importedACLSyncData
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s service) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) importACLList(a *app.App) (err error) {
|
||||
path := fmt.Sprintf("%s/%s", etc.Path(), "acl.yml")
|
||||
st, err := acllistbuilder.NewACLListStorageBuilderFromFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := st.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := st.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// checking that acl list contains all the needed permissions for all our nodes
|
||||
err = s.checkActualNodesPermissions(st, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.importedACLSyncData = ImportedACLSyncData{
|
||||
Id: id,
|
||||
Header: header,
|
||||
Records: st.GetRawRecords(),
|
||||
}
|
||||
|
||||
log.Infof("imported ACLList with id %s", id)
|
||||
return s.storageProvider.AddStorage(id, st)
|
||||
}
|
||||
|
||||
func (s *service) checkActualNodesPermissions(st *acllistbuilder.ACLListStorageBuilder, a *app.App) error {
|
||||
nodes := a.MustComponent(node.CName).(node.Service)
|
||||
acc := a.MustComponent(account.CName).(account.Service)
|
||||
|
||||
aclList, err := list.BuildACLListWithIdentity(acc.Account(), st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state := aclList.ACLState()
|
||||
|
||||
// checking own state
|
||||
if state.GetUserStates()[acc.Account().Identity].Permissions != aclpb.ACLChange_Admin {
|
||||
return fmt.Errorf("own node with signing key %s should be admin", acc.Account().Identity)
|
||||
}
|
||||
|
||||
// checking other nodes' states
|
||||
for _, n := range nodes.Nodes() {
|
||||
if state.GetUserStates()[n.SigningKeyString].Permissions != aclpb.ACLChange_Admin {
|
||||
return fmt.Errorf("other node with signing key %s should be admin", n.SigningKeyString)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -2,6 +2,7 @@ package message
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/pool"
|
||||
@ -85,7 +86,7 @@ func (s *service) SendMessageAsync(peerId string, msg *syncproto.Sync) (err erro
|
||||
return
|
||||
}
|
||||
|
||||
go s.sendAsync(peerId, msgType(msg), marshalled)
|
||||
go s.sendAsync(peerId, msgInfo(msg), marshalled)
|
||||
return
|
||||
}
|
||||
|
||||
@ -108,15 +109,16 @@ func (s *service) sendAsync(peerId string, msgTypeStr string, marshalled []byte)
|
||||
})
|
||||
}
|
||||
|
||||
func msgType(content *syncproto.Sync) string {
|
||||
func msgInfo(content *syncproto.Sync) (syncMethod string) {
|
||||
msg := content.GetMessage()
|
||||
switch {
|
||||
case msg.GetFullSyncRequest() != nil:
|
||||
return "FullSyncRequest"
|
||||
syncMethod = "FullSyncRequest"
|
||||
case msg.GetFullSyncResponse() != nil:
|
||||
return "FullSyncResponse"
|
||||
syncMethod = "FullSyncResponse"
|
||||
case msg.GetHeadUpdate() != nil:
|
||||
return "HeadUpdate"
|
||||
syncMethod = "HeadUpdate"
|
||||
}
|
||||
return "UnknownMessage"
|
||||
syncMethod = fmt.Sprintf("method: %s, treeType: %s", syncMethod, content.TreeHeader.DocType.String())
|
||||
return
|
||||
}
|
||||
|
||||
@ -5,9 +5,8 @@ import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/syncproto"
|
||||
@ -61,142 +60,147 @@ func (r *requestHandler) HandleSyncMessage(ctx context.Context, senderId string,
|
||||
msg := content.GetMessage()
|
||||
switch {
|
||||
case msg.GetFullSyncRequest() != nil:
|
||||
return r.HandleFullSyncRequest(ctx, senderId, msg.GetFullSyncRequest())
|
||||
return r.HandleFullSyncRequest(ctx, senderId, msg.GetFullSyncRequest(), content.GetTreeHeader(), content.GetTreeId())
|
||||
case msg.GetFullSyncResponse() != nil:
|
||||
return r.HandleFullSyncResponse(ctx, senderId, msg.GetFullSyncResponse())
|
||||
return r.HandleFullSyncResponse(ctx, senderId, msg.GetFullSyncResponse(), content.GetTreeHeader(), content.GetTreeId())
|
||||
case msg.GetHeadUpdate() != nil:
|
||||
return r.HandleHeadUpdate(ctx, senderId, msg.GetHeadUpdate())
|
||||
return r.HandleHeadUpdate(ctx, senderId, msg.GetHeadUpdate(), content.GetTreeHeader(), content.GetTreeId())
|
||||
case msg.GetAclList() != nil:
|
||||
return r.HandleACLList(ctx, senderId, msg.GetAclList(), content.GetTreeHeader(), content.GetTreeId())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestHandler) HandleHeadUpdate(ctx context.Context, senderId string, update *syncproto.SyncHeadUpdate) (err error) {
|
||||
func (r *requestHandler) HandleHeadUpdate(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
update *syncproto.SyncHeadUpdate,
|
||||
header *aclpb.Header,
|
||||
treeId string) (err error) {
|
||||
|
||||
var (
|
||||
fullRequest *syncproto.SyncFullRequest
|
||||
snapshotPath []string
|
||||
result acltree.AddResult
|
||||
result tree.AddResult
|
||||
)
|
||||
log.With(zap.String("peerId", senderId), zap.String("treeId", update.TreeId)).
|
||||
log.With(zap.String("peerId", senderId), zap.String("treeId", treeId)).
|
||||
Debug("processing head update")
|
||||
|
||||
err = r.treeCache.Do(ctx, update.TreeId, func(tree acltree.ACLTree) error {
|
||||
// TODO: check if we already have those changes
|
||||
result, err = tree.AddRawChanges(ctx, update.Changes...)
|
||||
err = r.treeCache.Do(ctx, treeId, func(obj any) error {
|
||||
objTree := obj.(tree.ObjectTree)
|
||||
objTree.Lock()
|
||||
defer objTree.Unlock()
|
||||
|
||||
if slice.UnsortedEquals(update.Heads, objTree.Heads()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err = objTree.AddRawChanges(ctx, update.Changes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.With(zap.Strings("update heads", update.Heads), zap.Strings("tree heads", tree.Heads())).
|
||||
Debug("comparing heads after head update")
|
||||
shouldFullSync := !slice.UnsortedEquals(update.Heads, tree.Heads())
|
||||
snapshotPath = tree.SnapshotPath()
|
||||
|
||||
// if we couldn't add all the changes
|
||||
shouldFullSync := len(update.Changes) != len(result.Added)
|
||||
snapshotPath = objTree.SnapshotPath()
|
||||
if shouldFullSync {
|
||||
fullRequest, err = r.prepareFullSyncRequest(update.TreeId, update.TreeHeader, update.SnapshotPath, tree)
|
||||
fullRequest, err = r.prepareFullSyncRequest(objTree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// if there are no such tree
|
||||
if err == treestorage.ErrUnknownTreeId {
|
||||
// TODO: maybe we can optimize this by sending the header and stuff right away, so when the tree is created we are able to add it on first request
|
||||
fullRequest = &syncproto.SyncFullRequest{
|
||||
TreeId: update.TreeId,
|
||||
TreeHeader: update.TreeHeader,
|
||||
}
|
||||
if err == storage.ErrUnknownTreeId {
|
||||
fullRequest = &syncproto.SyncFullRequest{}
|
||||
}
|
||||
// if we have incompatible heads, or we haven't seen the tree at all
|
||||
if fullRequest != nil {
|
||||
return r.messageService.SendMessageAsync(senderId, syncproto.WrapFullRequest(fullRequest))
|
||||
return r.messageService.SendMessageAsync(senderId, syncproto.WrapFullRequest(fullRequest, header, treeId))
|
||||
}
|
||||
// if error or nothing has changed
|
||||
if err != nil || len(result.Added) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
// otherwise sending heads update message
|
||||
newUpdate := &syncproto.SyncHeadUpdate{
|
||||
Heads: result.Heads,
|
||||
Changes: result.Added,
|
||||
SnapshotPath: snapshotPath,
|
||||
TreeId: update.TreeId,
|
||||
TreeHeader: update.TreeHeader,
|
||||
}
|
||||
return r.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(newUpdate))
|
||||
return r.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(newUpdate, header, treeId))
|
||||
}
|
||||
|
||||
func (r *requestHandler) HandleFullSyncRequest(ctx context.Context, senderId string, request *syncproto.SyncFullRequest) (err error) {
|
||||
var (
|
||||
fullResponse *syncproto.SyncFullResponse
|
||||
snapshotPath []string
|
||||
result acltree.AddResult
|
||||
)
|
||||
log.With(zap.String("peerId", senderId), zap.String("treeId", request.TreeId)).
|
||||
Debug("processing full sync request")
|
||||
func (r *requestHandler) HandleFullSyncRequest(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
request *syncproto.SyncFullRequest,
|
||||
header *aclpb.Header,
|
||||
treeId string) (err error) {
|
||||
|
||||
err = r.treeCache.Do(ctx, request.TreeId, func(tree acltree.ACLTree) error {
|
||||
// TODO: check if we already have those changes
|
||||
// if we have non-empty request
|
||||
if len(request.Heads) != 0 {
|
||||
result, err = tree.AddRawChanges(ctx, request.Changes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
snapshotPath = tree.SnapshotPath()
|
||||
fullResponse, err = r.prepareFullSyncResponse(request.TreeId, request.SnapshotPath, request.Changes, tree)
|
||||
var fullResponse *syncproto.SyncFullResponse
|
||||
err = r.treeCache.Do(ctx, treeId, func(obj any) error {
|
||||
objTree := obj.(tree.ObjectTree)
|
||||
objTree.Lock()
|
||||
defer objTree.Unlock()
|
||||
|
||||
fullResponse, err = r.prepareFullSyncResponse(treeId, request.SnapshotPath, request.Heads, objTree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.messageService.SendMessageAsync(senderId, syncproto.WrapFullResponse(fullResponse))
|
||||
// if error or nothing has changed
|
||||
if err != nil || len(result.Added) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
// otherwise sending heads update message
|
||||
newUpdate := &syncproto.SyncHeadUpdate{
|
||||
Heads: result.Heads,
|
||||
Changes: result.Added,
|
||||
SnapshotPath: snapshotPath,
|
||||
TreeId: request.TreeId,
|
||||
TreeHeader: request.TreeHeader,
|
||||
}
|
||||
return r.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(newUpdate))
|
||||
return r.messageService.SendMessageAsync(senderId, syncproto.WrapFullResponse(fullResponse, header, treeId))
|
||||
}
|
||||
|
||||
func (r *requestHandler) HandleFullSyncResponse(ctx context.Context, senderId string, response *syncproto.SyncFullResponse) (err error) {
|
||||
func (r *requestHandler) HandleFullSyncResponse(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
response *syncproto.SyncFullResponse,
|
||||
header *aclpb.Header,
|
||||
treeId string) (err error) {
|
||||
|
||||
var (
|
||||
snapshotPath []string
|
||||
result acltree.AddResult
|
||||
result tree.AddResult
|
||||
)
|
||||
log.With(zap.String("peerId", senderId), zap.String("treeId", response.TreeId)).
|
||||
Debug("processing full sync response")
|
||||
|
||||
err = r.treeCache.Do(ctx, response.TreeId, func(tree acltree.ACLTree) error {
|
||||
// TODO: check if we already have those changes
|
||||
result, err = tree.AddRawChanges(ctx, response.Changes...)
|
||||
err = r.treeCache.Do(ctx, treeId, func(obj interface{}) error {
|
||||
objTree := obj.(tree.ObjectTree)
|
||||
objTree.Lock()
|
||||
defer objTree.Unlock()
|
||||
|
||||
// if we already have the heads for whatever reason
|
||||
if slice.UnsortedEquals(response.Heads, objTree.Heads()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err = objTree.AddRawChanges(ctx, response.Changes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotPath = tree.SnapshotPath()
|
||||
snapshotPath = objTree.SnapshotPath()
|
||||
return nil
|
||||
})
|
||||
|
||||
// if error or nothing has changed
|
||||
if (err != nil || len(result.Added) == 0) && err != treestorage.ErrUnknownTreeId {
|
||||
if (err != nil || len(result.Added) == 0) && err != storage.ErrUnknownTreeId {
|
||||
return err
|
||||
}
|
||||
// if we have a new tree
|
||||
if err == treestorage.ErrUnknownTreeId {
|
||||
err = r.createTree(ctx, response)
|
||||
if err == storage.ErrUnknownTreeId {
|
||||
err = r.createTree(ctx, response, header, treeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = acltree.AddResult{
|
||||
result = tree.AddResult{
|
||||
OldHeads: []string{},
|
||||
Heads: response.Heads,
|
||||
Added: response.Changes,
|
||||
@ -207,66 +211,83 @@ func (r *requestHandler) HandleFullSyncResponse(ctx context.Context, senderId st
|
||||
Heads: result.Heads,
|
||||
Changes: result.Added,
|
||||
SnapshotPath: snapshotPath,
|
||||
TreeId: response.TreeId,
|
||||
}
|
||||
return r.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(newUpdate))
|
||||
return r.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(newUpdate, header, treeId))
|
||||
}
|
||||
|
||||
func (r *requestHandler) prepareFullSyncRequest(treeId string, header *treepb.TreeHeader, theirPath []string, tree acltree.ACLTree) (*syncproto.SyncFullRequest, error) {
|
||||
ourChanges, err := tree.ChangesAfterCommonSnapshot(theirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (r *requestHandler) HandleACLList(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
req *syncproto.SyncACLList,
|
||||
header *aclpb.Header,
|
||||
id string) (err error) {
|
||||
|
||||
err = r.treeCache.Do(ctx, id, func(obj interface{}) error {
|
||||
return nil
|
||||
})
|
||||
// do nothing if already added
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// if not found then add to storage
|
||||
if err == storage.ErrUnknownTreeId {
|
||||
return r.createACLList(ctx, req, header, id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *requestHandler) prepareFullSyncRequest(t tree.ObjectTree) (*syncproto.SyncFullRequest, error) {
|
||||
return &syncproto.SyncFullRequest{
|
||||
Heads: tree.Heads(),
|
||||
Changes: ourChanges,
|
||||
TreeId: treeId,
|
||||
SnapshotPath: tree.SnapshotPath(),
|
||||
TreeHeader: header,
|
||||
Heads: t.Heads(),
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *requestHandler) prepareFullSyncResponse(
|
||||
treeId string,
|
||||
theirPath []string,
|
||||
theirChanges []*aclpb.RawChange,
|
||||
tree acltree.ACLTree) (*syncproto.SyncFullResponse, error) {
|
||||
// TODO: we can probably use the common snapshot calculated on the request step from previous peer
|
||||
ourChanges, err := tree.ChangesAfterCommonSnapshot(theirPath)
|
||||
theirPath, theirHeads []string,
|
||||
t tree.ObjectTree) (*syncproto.SyncFullResponse, error) {
|
||||
ourChanges, err := t.ChangesAfterCommonSnapshot(theirPath, theirHeads)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
theirMap := make(map[string]struct{})
|
||||
for _, ch := range theirChanges {
|
||||
theirMap[ch.Id] = struct{}{}
|
||||
}
|
||||
|
||||
// filtering our changes, so we will not send the same changes back
|
||||
var final []*aclpb.RawChange
|
||||
for _, ch := range ourChanges {
|
||||
if _, exists := theirMap[ch.Id]; !exists {
|
||||
final = append(final, ch)
|
||||
}
|
||||
}
|
||||
log.With(zap.Int("len(changes)", len(final)), zap.String("id", treeId)).
|
||||
Debug("preparing changes for tree")
|
||||
|
||||
return &syncproto.SyncFullResponse{
|
||||
Heads: tree.Heads(),
|
||||
Changes: final,
|
||||
TreeId: treeId,
|
||||
SnapshotPath: tree.SnapshotPath(),
|
||||
TreeHeader: tree.Header(),
|
||||
Heads: t.Heads(),
|
||||
Changes: ourChanges,
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *requestHandler) createTree(ctx context.Context, response *syncproto.SyncFullResponse) error {
|
||||
func (r *requestHandler) createTree(
|
||||
ctx context.Context,
|
||||
response *syncproto.SyncFullResponse,
|
||||
header *aclpb.Header,
|
||||
treeId string) error {
|
||||
|
||||
return r.treeCache.Add(
|
||||
ctx,
|
||||
response.TreeId,
|
||||
response.TreeHeader,
|
||||
response.Changes,
|
||||
func(tree acltree.ACLTree) error {
|
||||
return nil
|
||||
treeId,
|
||||
storage.TreeStorageCreatePayload{
|
||||
TreeId: treeId,
|
||||
Header: header,
|
||||
Changes: response.Changes,
|
||||
Heads: response.Heads,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *requestHandler) createACLList(
|
||||
ctx context.Context,
|
||||
req *syncproto.SyncACLList,
|
||||
header *aclpb.Header,
|
||||
treeId string) error {
|
||||
|
||||
return r.treeCache.Add(
|
||||
ctx,
|
||||
treeId,
|
||||
storage.ACLListStorageCreatePayload{
|
||||
ListId: treeId,
|
||||
Header: header,
|
||||
Records: req.Records,
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,88 +2,82 @@ package treecache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
aclstorage "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/storage"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const CName = "treecache"
|
||||
|
||||
// TODO: add context
|
||||
type ACLTreeFunc = func(tree acltree.ACLTree) error
|
||||
type ChangeBuildFunc = func(builder acltree.ChangeBuilder) error
|
||||
type ObjFunc = func(obj interface{}) error
|
||||
|
||||
var log = logger.NewNamed("treecache")
|
||||
|
||||
type Service interface {
|
||||
Do(ctx context.Context, treeId string, f ACLTreeFunc) error
|
||||
Add(ctx context.Context, treeId string, header *treepb.TreeHeader, changes []*aclpb.RawChange, f ACLTreeFunc) error
|
||||
Create(ctx context.Context, build ChangeBuildFunc, f ACLTreeFunc) error
|
||||
Do(ctx context.Context, id string, f ObjFunc) error
|
||||
Add(ctx context.Context, id string, payload any) error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
treeProvider treestorage.Provider
|
||||
account account.Service
|
||||
cache ocache.OCache
|
||||
storage storage.Service
|
||||
account account.Service
|
||||
cache ocache.OCache
|
||||
}
|
||||
|
||||
func New() app.ComponentRunnable {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, build ChangeBuildFunc, f ACLTreeFunc) error {
|
||||
acc := s.account.Account()
|
||||
st, err := acltree.CreateNewTreeStorageWithACL(acc, build, s.treeProvider.CreateTreeStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := st.TreeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Do(ctx, id, f)
|
||||
}
|
||||
|
||||
func (s *service) Do(ctx context.Context, treeId string, f ACLTreeFunc) error {
|
||||
func (s *service) Do(ctx context.Context, treeId string, f ObjFunc) error {
|
||||
log.
|
||||
With(zap.String("treeId", treeId)).
|
||||
Debug("requesting tree from cache to perform operation")
|
||||
|
||||
tree, err := s.cache.Get(ctx, treeId)
|
||||
t, err := s.cache.Get(ctx, treeId)
|
||||
defer s.cache.Release(treeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aclTree := tree.(acltree.ACLTree)
|
||||
aclTree.Lock()
|
||||
defer aclTree.Unlock()
|
||||
return f(tree.(acltree.ACLTree))
|
||||
return f(t)
|
||||
}
|
||||
|
||||
func (s *service) Add(ctx context.Context, treeId string, header *treepb.TreeHeader, changes []*aclpb.RawChange, f ACLTreeFunc) error {
|
||||
log.
|
||||
With(zap.String("treeId", treeId), zap.Int("len(changes)", len(changes))).
|
||||
Debug("adding tree with changes")
|
||||
func (s *service) Add(ctx context.Context, treeId string, payload any) error {
|
||||
switch pl := payload.(type) {
|
||||
case aclstorage.TreeStorageCreatePayload:
|
||||
log.
|
||||
With(zap.String("treeId", treeId), zap.Int("len(changes)", len(pl.Changes))).
|
||||
Debug("adding Tree with changes")
|
||||
|
||||
_, err := s.storage.CreateTreeStorage(payload.(aclstorage.TreeStorageCreatePayload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case aclstorage.ACLListStorageCreatePayload:
|
||||
log.
|
||||
With(zap.String("treeId", treeId), zap.Int("len(changes)", len(pl.Records))).
|
||||
Debug("adding ACLList with records")
|
||||
|
||||
_, err := s.storage.CreateACLListStorage(payload.(aclstorage.ACLListStorageCreatePayload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := s.treeProvider.CreateTreeStorage(treeId, header, changes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Do(ctx, treeId, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
s.cache = ocache.New(s.loadTree)
|
||||
s.account = a.MustComponent(account.CName).(account.Service)
|
||||
s.treeProvider = treestorage.NewInMemoryTreeStorageProvider()
|
||||
s.storage = a.MustComponent(storage.CName).(storage.Service)
|
||||
// TODO: for test we should load some predefined keys
|
||||
return nil
|
||||
}
|
||||
@ -101,11 +95,33 @@ func (s *service) Close(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
func (s *service) loadTree(ctx context.Context, id string) (ocache.Object, error) {
|
||||
tree, err := s.treeProvider.TreeStorage(id)
|
||||
t, err := s.storage.Storage(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: should probably accept nil listeners
|
||||
aclTree, err := acltree.BuildACLTree(tree, s.account.Account(), acltree.NoOpListener{})
|
||||
return aclTree, err
|
||||
header, err := t.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch header.DocType { // handler
|
||||
case aclpb.Header_ACL:
|
||||
return list.BuildACLListWithIdentity(s.account.Account(), t.(aclstorage.ListStorage))
|
||||
case aclpb.Header_DocTree:
|
||||
break
|
||||
default:
|
||||
return nil, fmt.Errorf("incorrect type")
|
||||
}
|
||||
log.Info("got header", zap.String("header", header.String()))
|
||||
var objTree tree.ObjectTree
|
||||
err = s.Do(ctx, header.AclListId, func(obj interface{}) error {
|
||||
aclList := obj.(list.ACLList)
|
||||
objTree, err = tree.BuildObjectTree(t.(aclstorage.TreeStorage), nil, aclList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return objTree, err
|
||||
}
|
||||
|
||||
@ -1,19 +1,45 @@
|
||||
package syncproto
|
||||
|
||||
func WrapHeadUpdate(update *SyncHeadUpdate) *Sync {
|
||||
return &Sync{Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfHeadUpdate{HeadUpdate: update},
|
||||
}}
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
)
|
||||
|
||||
func WrapHeadUpdate(update *SyncHeadUpdate, header *aclpb.Header, treeId string) *Sync {
|
||||
return &Sync{
|
||||
Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfHeadUpdate{HeadUpdate: update},
|
||||
},
|
||||
TreeHeader: header,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapFullRequest(request *SyncFullRequest) *Sync {
|
||||
return &Sync{Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfFullSyncRequest{FullSyncRequest: request},
|
||||
}}
|
||||
func WrapFullRequest(request *SyncFullRequest, header *aclpb.Header, treeId string) *Sync {
|
||||
return &Sync{
|
||||
Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfFullSyncRequest{FullSyncRequest: request},
|
||||
},
|
||||
TreeHeader: header,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapFullResponse(response *SyncFullResponse) *Sync {
|
||||
return &Sync{Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfFullSyncResponse{FullSyncResponse: response},
|
||||
}}
|
||||
func WrapFullResponse(response *SyncFullResponse, header *aclpb.Header, treeId string) *Sync {
|
||||
return &Sync{
|
||||
Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfFullSyncResponse{FullSyncResponse: response},
|
||||
},
|
||||
TreeHeader: header,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapACLList(aclList *SyncACLList, header *aclpb.Header, id string) *Sync {
|
||||
return &Sync{
|
||||
Message: &SyncContentValue{
|
||||
Value: &SyncContentValueValueOfAclList{AclList: aclList},
|
||||
},
|
||||
TreeHeader: header,
|
||||
TreeId: id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package anytype;
|
||||
option go_package = "/syncproto";
|
||||
|
||||
import "pkg/acl/aclchanges/aclpb/protos/aclchanges.proto";
|
||||
import "pkg/acl/treestorage/treepb/protos/tree.proto";
|
||||
|
||||
message Message {
|
||||
Header header = 1;
|
||||
@ -52,21 +51,26 @@ message System {
|
||||
message Sync {
|
||||
string spaceId = 1;
|
||||
ContentValue message = 2;
|
||||
acl.Header treeHeader = 3;
|
||||
string treeId = 4;
|
||||
|
||||
message ContentValue {
|
||||
oneof value {
|
||||
HeadUpdate headUpdate = 1;
|
||||
Full.Request fullSyncRequest = 2;
|
||||
Full.Response fullSyncResponse = 3;
|
||||
ACLList aclList = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message ACLList {
|
||||
repeated acl.RawRecord records = 1;
|
||||
}
|
||||
|
||||
message HeadUpdate {
|
||||
repeated string heads = 1;
|
||||
repeated acl.RawChange changes = 2;
|
||||
string treeId = 3;
|
||||
repeated string snapshotPath = 4;
|
||||
tree.TreeHeader treeHeader = 5;
|
||||
repeated string snapshotPath = 3;
|
||||
}
|
||||
|
||||
message Full {
|
||||
@ -74,17 +78,13 @@ message Sync {
|
||||
message Request {
|
||||
repeated string heads = 1;
|
||||
repeated acl.RawChange changes = 2;
|
||||
string treeId = 3;
|
||||
repeated string snapshotPath = 4;
|
||||
tree.TreeHeader treeHeader = 5;
|
||||
repeated string snapshotPath = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
repeated string heads = 1;
|
||||
repeated acl.RawChange changes = 2;
|
||||
string treeId = 3;
|
||||
repeated string snapshotPath = 4;
|
||||
tree.TreeHeader treeHeader = 5;
|
||||
repeated string snapshotPath = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user