diff --git a/acltree/acltree.go b/acltree/acltree.go index 0162cd33..59268b34 100644 --- a/acltree/acltree.go +++ b/acltree/acltree.go @@ -1,13 +1,11 @@ package acltree import ( + "sync" + "github.com/anytypeio/go-anytype-infrastructure-experiments/account" - "github.com/anytypeio/go-anytype-infrastructure-experiments/aclchanges/pb" "github.com/anytypeio/go-anytype-infrastructure-experiments/thread" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" - "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" - "github.com/gogo/protobuf/proto" - "sync" ) type AddResultSummary int @@ -25,10 +23,9 @@ type AddResult struct { Summary AddResultSummary } -// TODO: Change add change content to include ACLChangeBuilder type ACLTree interface { ACLState() *ACLState - AddContent(changeContent *ChangeContent) (*Change, error) + AddContent(f func(builder ChangeBuilder)) (*Change, error) AddChanges(changes ...*Change) (AddResult, error) Heads() []string Iterate(func(change *Change) bool) @@ -40,14 +37,15 @@ type aclTree struct { thread thread.Thread accountData *account.AccountData - fullTree *Tree - aclTree *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 + 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.Mutex } @@ -58,6 +56,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) { treeBuilder := newTreeBuilder(t, decoder) snapshotValidator := newSnapshotValidator(decoder, acc) aclStateBuilder := newACLStateBuilder(decoder, acc) + changeBuilder := newChangeBuilder() aclTree := &aclTree{ thread: t, @@ -68,6 +67,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) { aclTreeBuilder: aclTreeBuilder, aclStateBuilder: aclStateBuilder, snapshotValidator: snapshotValidator, + changeBuilder: changeBuilder, } err := aclTree.rebuildFromThread(false) if err != nil { @@ -80,7 +80,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) { // 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.aclTree) + err = a.snapshotValidator.Init(a.aclTreeFromStart) if err != nil { return err } @@ -118,13 +118,13 @@ func (a *aclTree) rebuildFromThread(fromStart bool) error { } // TODO: remove this from context as this is used only to validate snapshot - a.aclTree, err = a.aclTreeBuilder.Build() + a.aclTreeFromStart, err = a.aclTreeBuilder.Build() if err != nil { return err } if !fromStart { - err = a.snapshotValidator.Init(a.aclTree) + err = a.snapshotValidator.Init(a.aclTreeFromStart) if err != nil { return err } @@ -150,98 +150,40 @@ func (a *aclTree) rebuildFromThread(fromStart bool) error { return nil } -// TODO: this should not be the responsibility of ACLTree, move it somewhere else after testing -func (a *aclTree) getACLHeads() []string { - var aclTreeHeads []string - for _, head := range a.fullTree.Heads() { - if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads - continue - } - precedingHeads := a.getPrecedingACLHeads(head) - - for _, aclHead := range precedingHeads { - if slice.FindPos(aclTreeHeads, aclHead) != -1 { - continue - } - aclTreeHeads = append(aclTreeHeads, aclHead) - } - } - return aclTreeHeads -} - -func (a *aclTree) getPrecedingACLHeads(head string) []string { - headChange := a.fullTree.attached[head] - - if headChange.Content.GetAclData() != nil { - return []string{head} - } else { - return headChange.Content.AclHeadIds - } -} - func (a *aclTree) ACLState() *ACLState { return a.aclState } -func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) { +func (a *aclTree) AddContent(build func(builder ChangeBuilder)) (*Change, error) { // TODO: add snapshot creation logic a.Lock() defer a.Unlock() - marshalled, err := changeContent.ChangesData.Marshal() + + a.changeBuilder.Init(a.aclState, a.fullTree, a.accountData) + build(a.changeBuilder) + + ch, marshalled, err := a.changeBuilder.Build() + if err != nil { + return nil, err + } + err = a.aclState.applyChange(ch.Id, ch.Content) if err != nil { return nil, err } - encrypted, err := a.aclState.userReadKeys[a.aclState.currentReadKeyHash]. - Encrypt(marshalled) - if err != nil { - return nil, err - } - - aclChange := &pb.ACLChange{ - TreeHeadIds: a.fullTree.Heads(), - AclHeadIds: a.getACLHeads(), - SnapshotBaseId: a.fullTree.RootId(), - AclData: changeContent.ACLData, - ChangesData: encrypted, - CurrentReadKeyHash: a.aclState.currentReadKeyHash, - Timestamp: 0, - Identity: a.accountData.Identity, - } - - // TODO: add CID creation logic based on content - ch := NewChange(changeContent.Id, aclChange) - ch.DecryptedDocumentChange = marshalled - - fullMarshalledChange, err := proto.Marshal(aclChange) - if err != nil { - return nil, err - } - signature, err := a.accountData.SignKey.Sign(fullMarshalledChange) - if err != nil { - return nil, err - } - - if aclChange.AclData != nil { - // we can apply change right away without going through builder, because - err = a.aclState.applyChange(changeContent.Id, aclChange) - if err != nil { - return nil, err - } - } a.fullTree.AddFast(ch) err = a.thread.AddRawChange(&thread.RawChange{ Payload: marshalled, - Signature: signature, - Id: changeContent.Id, + Signature: ch.Signature(), + Id: ch.Id, }) if err != nil { return nil, err } a.thread.SetHeads([]string{ch.Id}) - return a.fullTree.attached[changeContent.Id], nil + return ch, nil } func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) { diff --git a/acltree/changebuilder.go b/acltree/changebuilder.go new file mode 100644 index 00000000..458bc85b --- /dev/null +++ b/acltree/changebuilder.go @@ -0,0 +1,101 @@ +package acltree + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/account" + "github.com/anytypeio/go-anytype-infrastructure-experiments/aclchanges/pb" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" + "github.com/gogo/protobuf/proto" +) + +type MarshalledChange = []byte + +type ACLChangeBuilder interface { + UserAdd(identity string, encryptionKey keys.EncryptionPubKey) + AddId(id string) // TODO: this is only for testing + SetMakeSnapshot(bool) // TODO: who should decide this? probably ACLTree so we can delete it +} + +type ChangeBuilder interface { + ACLChangeBuilder + AddChangeContent(marshaler proto.Marshaler) // user code should be responsible for making regular snapshots +} + +type changeBuilder struct { + aclState *ACLState + tree *Tree + acc *account.AccountData + + aclData *pb.ACLChangeACLData + changeContent proto.Marshaler + id string + makeSnapshot bool +} + +func newChangeBuilder() *changeBuilder { + return &changeBuilder{} +} + +func (c *changeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) { + c.aclState = state + c.tree = tree + c.acc = acc + + c.aclData = &pb.ACLChangeACLData{} +} + +func (c *changeBuilder) AddId(id string) { + c.id = id +} + +func (c *changeBuilder) SetMakeSnapshot(b bool) { + c.makeSnapshot = b +} + +func (c *changeBuilder) UserAdd(identity string, encryptionKey keys.EncryptionPubKey) { + //TODO implement me + panic("implement me") +} + +func (c *changeBuilder) Build() (*Change, []byte, error) { + 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 := &pb.ACLChange{ + TreeHeadIds: c.tree.Heads(), + AclHeadIds: c.tree.ACLHeads(), + SnapshotBaseId: c.tree.RootId(), // TODO: add logic for ACL snapshot + AclData: c.aclData, + ChangesData: encrypted, + CurrentReadKeyHash: c.aclState.currentReadKeyHash, + Timestamp: 0, + Identity: c.acc.Identity, + } + + // TODO: add CID creation logic based on content + ch := NewChange(c.id, aclChange) + ch.DecryptedDocumentChange = marshalled + + fullMarshalledChange, err := proto.Marshal(aclChange) + if err != nil { + return nil, nil, err + } + signature, err := c.acc.SignKey.Sign(fullMarshalledChange) + if err != nil { + return nil, nil, err + } + ch.Sign = signature + + return ch, fullMarshalledChange, nil +} + +func (c *changeBuilder) AddChangeContent(marshaler proto.Marshaler) { + c.changeContent = marshaler +} diff --git a/acltree/tree.go b/acltree/tree.go index 326ebcad..e47ed212 100644 --- a/acltree/tree.go +++ b/acltree/tree.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/md5" "fmt" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" "sort" ) @@ -288,6 +289,34 @@ func (t *Tree) updateHeads() { 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)) { it := newIterator() defer freeIterator(it)