diff --git a/client/account/service.go b/client/account/service.go new file mode 100644 index 00000000..3181f9bd --- /dev/null +++ b/client/account/service.go @@ -0,0 +1,62 @@ +package account + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + commonaccount "github.com/anytypeio/go-anytype-infrastructure-experiments/common/account" + "github.com/anytypeio/go-anytype-infrastructure-experiments/config" + "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" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" +) + +type service struct { + accountData *account.AccountData + peerId string +} + +func (s *service) Account() *account.AccountData { + return s.accountData +} + +func New() app.Component { + return &service{} +} + +func (s *service) Init(a *app.App) (err error) { + acc := a.MustComponent(config.CName).(commonaccount.ConfigGetter).GetAccount() + + decodedEncryptionKey, err := keys.DecodeKeyFromString( + acc.EncryptionKey, + encryptionkey.NewEncryptionRsaPrivKeyFromBytes, + nil) + if err != nil { + return err + } + + decodedSigningKey, err := keys.DecodeKeyFromString( + acc.SigningKey, + signingkey.NewSigningEd25519PrivKeyFromBytes, + nil) + if err != nil { + return err + } + + identity, err := decodedSigningKey.GetPublic().Raw() + if err != nil { + return err + } + + s.accountData = &account.AccountData{ + Identity: identity, + SignKey: decodedSigningKey, + EncKey: decodedEncryptionKey, + } + s.peerId = acc.PeerId + + return nil +} + +func (s *service) Name() (name string) { + return commonaccount.CName +} diff --git a/client/badgerprovider/helpers.go b/client/badgerprovider/helpers.go new file mode 100644 index 00000000..96ed8acf --- /dev/null +++ b/client/badgerprovider/helpers.go @@ -0,0 +1,76 @@ +package badgerprovider + +import ( + "errors" + "github.com/dgraph-io/badger/v3" +) + +var ErrIncorrectKey = errors.New("the key is incorrect") + +func Has(db *badger.DB, key []byte) (has bool, err error) { + err = db.View(func(txn *badger.Txn) error { + _, err := txn.Get(key) + return err + }) + if err != nil { + return + } + has = true + return +} + +func Put(db *badger.DB, key, value []byte) (err error) { + return db.Update(func(txn *badger.Txn) error { + return txn.Set(key, value) + }) +} + +func Get(db *badger.DB, key []byte) (value []byte, err error) { + err = db.View(func(txn *badger.Txn) error { + item, err := txn.Get(key) + if err != nil { + return err + } + value, err = item.ValueCopy(value) + if err != nil { + return err + } + return err + }) + return +} + +func GetKeySuffix(txn *badger.Txn, keyPrefix []byte) (suffix []byte, err error) { + iter := txn.NewIterator(badger.IteratorOptions{Prefix: keyPrefix}) + iter.Next() + if !iter.Valid() { + err = badger.ErrKeyNotFound + return + } + + suffix = iter.Item().Key() + if len(suffix) <= len(keyPrefix)+1 { + err = ErrIncorrectKey + return + } + suffix = suffix[len(keyPrefix)+1:] + return +} + +func GetKeySuffixAndValue(txn *badger.Txn, keyPrefix []byte) (suffix []byte, value []byte, err error) { + iter := txn.NewIterator(badger.IteratorOptions{Prefix: keyPrefix}) + iter.Next() + if !iter.Valid() { + err = badger.ErrKeyNotFound + return + } + + suffix = iter.Item().Key() + if len(suffix) <= len(keyPrefix)+1 { + err = ErrIncorrectKey + return + } + suffix = suffix[len(keyPrefix)+1:] + value, err = iter.Item().ValueCopy(value) + return +} diff --git a/client/badgerprovider/service.go b/client/badgerprovider/service.go new file mode 100644 index 00000000..2fb8a9c0 --- /dev/null +++ b/client/badgerprovider/service.go @@ -0,0 +1,41 @@ +package badgerprovider + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + "github.com/anytypeio/go-anytype-infrastructure-experiments/config" + "github.com/dgraph-io/badger/v3" +) + +type BadgerProvider interface { + app.ComponentRunnable + Badger() *badger.DB +} + +var CName = "client.badgerprovider" + +type service struct { + db *badger.DB +} + +func (s *service) Init(a *app.App) (err error) { + cfg := a.MustComponent(config.CName).(*config.Config) + s.db, err = badger.Open(badger.DefaultOptions(cfg.Storage.Path)) + return +} + +func (s *service) Name() (name string) { + return CName +} + +func (s *service) Badger() *badger.DB { + return s.db +} + +func (s *service) Run(ctx context.Context) (err error) { + return +} + +func (s *service) Close(ctx context.Context) (err error) { + return s.db.Close() +} diff --git a/client/clientspace/clientcache/treecache.go b/client/clientspace/clientcache/treecache.go new file mode 100644 index 00000000..eee57452 --- /dev/null +++ b/client/clientspace/clientcache/treecache.go @@ -0,0 +1,86 @@ +package clientcache + +import ( + "context" + "errors" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" + "github.com/anytypeio/go-anytype-infrastructure-experiments/client/clientspace" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/node/nodespace" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache" + "time" +) + +var log = logger.NewNamed("treecache") +var ErrCacheObjectWithoutTree = errors.New("cache object contains no tree") + +type ctxKey int + +const spaceKey ctxKey = 0 + +type treeCache struct { + gcttl int + cache ocache.OCache + clientService clientspace.Service +} + +func New(ttl int) cache.TreeCache { + return &treeCache{ + gcttl: ttl, + } +} + +func (c *treeCache) Run(ctx context.Context) (err error) { + return nil +} + +func (c *treeCache) Close(ctx context.Context) (err error) { + return c.cache.Close() +} + +func (c *treeCache) Init(a *app.App) (err error) { + c.clientService = a.MustComponent(nodespace.CName).(nodespace.Service) + c.cache = ocache.New( + func(ctx context.Context, id string) (value ocache.Object, err error) { + spaceId := ctx.Value(spaceKey).(string) + space, err := c.clientService.GetSpace(ctx, spaceId) + if err != nil { + return + } + return space.BuildTree(ctx, id, nil) + }, + ocache.WithLogger(log.Sugar()), + ocache.WithGCPeriod(time.Minute), + ocache.WithTTL(time.Duration(c.gcttl)*time.Second), + ocache.WithRefCounter(false), + ) + return nil +} + +func (c *treeCache) Name() (name string) { + return cache.CName +} + +func (c *treeCache) GetTree(ctx context.Context, spaceId, id string) (res cache.TreeResult, err error) { + var cacheRes ocache.Object + ctx = context.WithValue(ctx, spaceKey, spaceId) + cacheRes, err = c.cache.Get(ctx, id) + if err != nil { + return cache.TreeResult{}, err + } + + treeContainer, ok := cacheRes.(cache.TreeContainer) + if !ok { + err = ErrCacheObjectWithoutTree + return + } + + res = cache.TreeResult{ + Release: func() { + c.cache.Release(id) + }, + TreeContainer: treeContainer, + } + return +} diff --git a/client/clientspace/rpchandler.go b/client/clientspace/rpchandler.go new file mode 100644 index 00000000..3a76454c --- /dev/null +++ b/client/clientspace/rpchandler.go @@ -0,0 +1,58 @@ +package clientspace + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" +) + +type rpcHandler struct { + s *service +} + +func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) { + _, err = r.s.GetSpace(ctx, req.SpaceHeader.Id) + if err == nil { + err = spacesyncproto.ErrSpaceExists + return + } + if err != cache.ErrSpaceNotFound { + err = spacesyncproto.ErrUnexpected + return + } + + payload := storage.SpaceStorageCreatePayload{ + RecWithId: req.AclRoot, + SpaceHeaderWithId: req.SpaceHeader, + } + _, err = r.s.spaceStorageProvider.CreateSpaceStorage(payload) + if err != nil { + err = spacesyncproto.ErrUnexpected + if err == storage.ErrSpaceStorageExists { + err = spacesyncproto.ErrSpaceExists + } + return + } + return +} + +func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error) { + sp, err := r.s.GetSpace(ctx, req.SpaceId) + if err != nil { + return nil, spacesyncproto.ErrSpaceMissing + } + return sp.SpaceSyncRpc().HeadSync(ctx, req) +} + +func (r *rpcHandler) Stream(stream spacesyncproto.DRPCSpace_StreamStream) error { + msg, err := stream.Recv() + if err != nil { + return err + } + sp, err := r.s.GetSpace(stream.Context(), msg.SpaceId) + if err != nil { + return spacesyncproto.ErrSpaceMissing + } + return sp.SpaceSyncRpc().Stream(stream) +} diff --git a/client/clientspace/service.go b/client/clientspace/service.go new file mode 100644 index 00000000..3fcebc1b --- /dev/null +++ b/client/clientspace/service.go @@ -0,0 +1,74 @@ +package clientspace + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/server" + "github.com/anytypeio/go-anytype-infrastructure-experiments/config" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache" + "time" +) + +const CName = "client.clientspace" + +var log = logger.NewNamed(CName) + +func New() Service { + return &service{} +} + +type Service interface { + GetSpace(ctx context.Context, id string) (commonspace.Space, error) + app.ComponentRunnable +} + +type service struct { + conf config.Space + spaceCache ocache.OCache + commonSpace commonspace.Service + spaceStorageProvider storage.SpaceStorageProvider +} + +func (s *service) Init(a *app.App) (err error) { + s.conf = a.MustComponent(config.CName).(*config.Config).Space + s.commonSpace = a.MustComponent(commonspace.CName).(commonspace.Service) + s.spaceStorageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider) + s.spaceCache = ocache.New( + func(ctx context.Context, id string) (value ocache.Object, err error) { + return s.commonSpace.GetSpace(ctx, id) + }, + ocache.WithLogger(log.Sugar()), + ocache.WithGCPeriod(time.Minute), + ocache.WithTTL(time.Duration(s.conf.GCTTL)*time.Second), + ocache.WithRefCounter(false), + ) + return spacesyncproto.DRPCRegisterSpace(a.MustComponent(server.CName).(server.DRPCServer), &rpcHandler{s}) +} + +func (s *service) Name() (name string) { + return CName +} + +func (s *service) Run(ctx context.Context) (err error) { + go func() { + time.Sleep(time.Second * 5) + _, _ = s.GetSpace(ctx, "testDSpace") + }() + return +} + +func (s *service) GetSpace(ctx context.Context, id string) (commonspace.Space, error) { + v, err := s.spaceCache.Get(ctx, id) + if err != nil { + return nil, err + } + return v.(commonspace.Space), nil +} + +func (s *service) Close(ctx context.Context) (err error) { + return s.spaceCache.Close() +} diff --git a/client/storage/keys.go b/client/storage/keys.go new file mode 100644 index 00000000..5ee5958f --- /dev/null +++ b/client/storage/keys.go @@ -0,0 +1,91 @@ +package storage + +import ( + "bytes" +) + +type aclKeys struct { + spaceId string + rootKey []byte + headKey []byte +} + +func newACLKeys(spaceId string) aclKeys { + return aclKeys{ + spaceId: spaceId, + rootKey: joinStringsToBytes("space", spaceId, "a", "rootId"), + headKey: joinStringsToBytes("space", spaceId, "a", "headId"), + } +} + +func (a aclKeys) HeadIdKey() []byte { + return a.headKey +} + +func (a aclKeys) RootIdKey() []byte { + return a.rootKey +} + +func (a aclKeys) RawRecordKey(id string) []byte { + return joinStringsToBytes("space", a.spaceId, "a", id) +} + +type treeKeys struct { + id string + spaceId string + headsKey []byte + rootKey []byte +} + +func newTreeKeys(spaceId, id string) treeKeys { + return treeKeys{ + id: id, + spaceId: spaceId, + headsKey: joinStringsToBytes("space", spaceId, "t", id, "heads"), + rootKey: joinStringsToBytes("space", spaceId, "t", id), + } +} + +func (t treeKeys) HeadsKey() []byte { + return t.headsKey +} + +func (t treeKeys) RootIdKey() []byte { + return t.rootKey +} + +func (t treeKeys) RawChangeKey(id string) []byte { + return joinStringsToBytes("space", t.spaceId, "t", t.id, id) +} + +type spaceKeys struct { + headerKey []byte +} + +func newSpaceKeys(spaceId string) spaceKeys { + return spaceKeys{headerKey: joinStringsToBytes("space", spaceId)} +} + +func (s spaceKeys) HeaderKey() []byte { + return s.headerKey +} + +func joinStringsToBytes(strs ...string) []byte { + var ( + b bytes.Buffer + totalLen int + ) + for _, s := range strs { + totalLen += len(s) + } + // adding separators + totalLen += len(strs) - 1 + b.Grow(totalLen) + for idx, s := range strs { + if idx > 0 { + b.WriteString("/") + } + b.WriteString(s) + } + return b.Bytes() +} diff --git a/client/storage/liststorage.go b/client/storage/liststorage.go new file mode 100644 index 00000000..30910d65 --- /dev/null +++ b/client/storage/liststorage.go @@ -0,0 +1,131 @@ +package storage + +import ( + "context" + "errors" + provider "github.com/anytypeio/go-anytype-infrastructure-experiments/client/badgerprovider" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + "github.com/dgraph-io/badger/v3" +) + +var ErrIncorrectKey = errors.New("key format is incorrect") + +type listStorage struct { + db *badger.DB + keys aclKeys + id string + root *aclrecordproto.RawACLRecordWithId +} + +func newListStorage(spaceId string, db *badger.DB, txn *badger.Txn) (ls storage.ListStorage, err error) { + keys := newACLKeys(spaceId) + rootId, err := provider.GetKeySuffix(txn, keys.RootIdKey()) + if err != nil { + if err == badger.ErrKeyNotFound { + err = storage.ErrUnknownACLId + } + return + } + stringId := string(rootId) + rootItem, err := txn.Get(keys.RawRecordKey(stringId)) + if err != nil { + if err == badger.ErrKeyNotFound { + err = storage.ErrUnknownACLId + } + return + } + var value []byte + value, err = rootItem.ValueCopy(value) + if err != nil { + return + } + + rootWithId := &aclrecordproto.RawACLRecordWithId{ + Payload: value, + Id: stringId, + } + + ls = &listStorage{ + db: db, + keys: aclKeys{}, + id: stringId, + root: rootWithId, + } + return +} + +func createListStorage(spaceId string, db *badger.DB, txn *badger.Txn, root *aclrecordproto.RawACLRecordWithId) (ls storage.ListStorage, err error) { + keys := newACLKeys(spaceId) + _, err = provider.GetKeySuffix(txn, keys.RootIdKey()) + if err != nil && err != badger.ErrKeyNotFound { + return + } + if err == nil { + err = storage.ErrACLExists + return + } + aclRootKey := append(keys.RootIdKey(), '/') + aclRootKey = append(aclRootKey, []byte(root.Id)...) + + err = txn.Set(aclRootKey, nil) + if err != nil { + return + } + + err = txn.Set(keys.HeadIdKey(), []byte(root.Id)) + if err != nil { + return + } + + err = txn.Set(keys.RawRecordKey(root.Id), root.Payload) + if err != nil { + return + } + + ls = &listStorage{ + db: db, + keys: aclKeys{}, + id: root.Id, + root: root, + } + return +} + +func (l *listStorage) ID() (string, error) { + return l.id, nil +} + +func (l *listStorage) Root() (*aclrecordproto.RawACLRecordWithId, error) { + return l.root, nil +} + +func (l *listStorage) Head() (head string, err error) { + bytes, err := provider.Get(l.db, l.keys.HeadIdKey()) + if err != nil { + return + } + head = string(bytes) + return +} + +func (l *listStorage) GetRawRecord(ctx context.Context, id string) (raw *aclrecordproto.RawACLRecordWithId, err error) { + res, err := l.db.Get(l.keys.RawRecordKey(id)) + if err != nil { + return + } + if res == nil { + err = storage.ErrUnknownRecord + return + } + + raw = &aclrecordproto.RawACLRecordWithId{ + Payload: res, + Id: id, + } + return +} + +func (l *listStorage) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error { + return l.db.Put([]byte(rec.Id), rec.Payload) +} diff --git a/client/storage/spacestorage.go b/client/storage/spacestorage.go new file mode 100644 index 00000000..1affdc14 --- /dev/null +++ b/client/storage/spacestorage.go @@ -0,0 +1,159 @@ +package storage + +import ( + "github.com/akrylysov/pogreb" + provider "github.com/anytypeio/go-anytype-infrastructure-experiments/client/badgerprovider" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + "github.com/dgraph-io/badger/v3" + "path" + "sync" + "time" +) + +var defPogrebOptions = &pogreb.Options{ + BackgroundCompactionInterval: time.Minute * 5, +} + +type spaceStorage struct { + objDb *pogreb.DB + keys spaceKeys + aclStorage storage.ListStorage + header *spacesyncproto.RawSpaceHeaderWithId + mx sync.Mutex +} + +func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.SpaceStorage, err error) { + keys := newSpaceKeys(spaceId) + err = objDb.Update(func(txn *badger.Txn) error { + header, err := txn.Get(keys.HeaderKey()) + if err != nil { + return err + } + + }) + has, err := provider.Has(obj) + if err != nil { + return + } + if !has { + err = spacestorage.ErrSpaceStorageMissing + return + } + + header, err := objDb.Get(keys.HeaderKey()) + if err != nil { + return + } + if header == nil { + err = spacestorage.ErrSpaceStorageMissing + return + } + + aclStorage, err := newListStorage(objDb) + if err != nil { + return + } + + store = &spaceStorage{ + objDb: objDb, + keys: keys, + header: &spacesyncproto.RawSpaceHeaderWithId{ + RawHeader: header, + Id: spaceId, + }, + aclStorage: aclStorage, + } + return +} + +func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreatePayload) (store spacestorage.SpaceStorage, err error) { + dbPath := path.Join(rootPath, payload.SpaceHeaderWithId.Id) + db, err := pogreb.Open(dbPath, defPogrebOptions) + if err != nil { + return + } + + defer func() { + if err != nil { + db.Close() + } + }() + + keys := newSpaceKeys(payload.SpaceHeaderWithId.Id) + has, err := db.Has(keys.SpaceIdKey()) + if err != nil { + return + } + if has { + err = spacesyncproto.ErrSpaceExists + return + } + + aclStorage, err := createListStorage(db, payload.RecWithId) + if err != nil { + return + } + + err = db.Put(keys.HeaderKey(), payload.SpaceHeaderWithId.RawHeader) + if err != nil { + return + } + + err = db.Put(keys.SpaceIdKey(), []byte(payload.SpaceHeaderWithId.Id)) + if err != nil { + return + } + + store = &spaceStorage{ + objDb: db, + keys: keys, + aclStorage: aclStorage, + header: payload.SpaceHeaderWithId, + } + return +} + +func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) { + return newTreeStorage(s.objDb, id) +} + +func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) { + // we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation + s.mx.Lock() + defer s.mx.Unlock() + + return createTreeStorage(s.objDb, payload) +} + +func (s *spaceStorage) ACLStorage() (storage.ListStorage, error) { + return s.aclStorage, nil +} + +func (s *spaceStorage) SpaceHeader() (header *spacesyncproto.RawSpaceHeaderWithId, err error) { + return s.header, nil +} + +func (s *spaceStorage) StoredIds() (ids []string, err error) { + index := s.objDb.Items() + + key, val, err := index.Next() + for err == nil { + strKey := string(key) + if isRootIdKey(strKey) { + ids = append(ids, string(val)) + } + key, val, err = index.Next() + } + + if err != pogreb.ErrIterationDone { + return + } + err = nil + return +} + +func (s *spaceStorage) Close() (err error) { + return s.objDb.Close() +} diff --git a/client/storage/storageservice.go b/client/storage/storageservice.go new file mode 100644 index 00000000..ba9de309 --- /dev/null +++ b/client/storage/storageservice.go @@ -0,0 +1,34 @@ +package storage + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + "github.com/anytypeio/go-anytype-infrastructure-experiments/client/badgerprovider" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" + "github.com/dgraph-io/badger/v3" +) + +type storageService struct { + db *badger.DB +} + +func New() storage.SpaceStorageProvider { + return &storageService{} +} + +func (s *storageService) Init(a *app.App) (err error) { + provider := a.MustComponent(badgerprovider.CName).(badgerprovider.BadgerProvider) + s.db = provider.Badger() + return +} + +func (s *storageService) Name() (name string) { + return storage.CName +} + +func (s *storageService) SpaceStorage(id string) (storage.SpaceStorage, error) { + return newSpaceStorage(s.rootPath, id) +} + +func (s *storageService) CreateSpaceStorage(payload storage.SpaceStorageCreatePayload) (storage.SpaceStorage, error) { + return createSpaceStorage(s.rootPath, payload) +} diff --git a/client/storage/treestorage.go b/client/storage/treestorage.go new file mode 100644 index 00000000..6c6c0d57 --- /dev/null +++ b/client/storage/treestorage.go @@ -0,0 +1,181 @@ +package storage + +import ( + "bytes" + "context" + "github.com/akrylysov/pogreb" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + "strings" +) + +type treeStorage struct { + db *pogreb.DB + keys treeKeys + id string + root *treechangeproto.RawTreeChangeWithId +} + +func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err error) { + keys := newTreeKeys(treeId) + has, err := db.Has(keys.RootIdKey()) + if err != nil { + return + } + if !has { + err = storage.ErrUnknownTreeId + return + } + + heads, err := db.Get(keys.HeadsKey()) + if err != nil { + return + } + if heads == nil { + err = storage.ErrUnknownTreeId + return + } + + root, err := db.Get(keys.RawChangeKey(treeId)) + if err != nil { + return + } + if root == nil { + err = storage.ErrUnknownTreeId + return + } + + rootWithId := &treechangeproto.RawTreeChangeWithId{ + RawChange: root, + Id: treeId, + } + if err != nil { + return + } + + ts = &treeStorage{ + db: db, + keys: keys, + id: treeId, + root: rootWithId, + } + return +} + +func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) { + keys := newTreeKeys(payload.TreeId) + has, err := db.Has(keys.RootIdKey()) + if err != nil { + return + } + if has { + err = storage.ErrTreeExists + return + } + + heads := createHeadsPayload(payload.Heads) + + for _, ch := range payload.Changes { + err = db.Put(keys.RawChangeKey(ch.Id), ch.GetRawChange()) + if err != nil { + return + } + } + + err = db.Put(keys.RawChangeKey(payload.RootRawChange.Id), payload.RootRawChange.GetRawChange()) + if err != nil { + return + } + + err = db.Put(keys.HeadsKey(), heads) + if err != nil { + return + } + + err = db.Put(keys.RootIdKey(), []byte(payload.RootRawChange.Id)) + if err != nil { + return + } + + ts = &treeStorage{ + db: db, + keys: keys, + id: payload.RootRawChange.Id, + root: payload.RootRawChange, + } + return +} + +func (t *treeStorage) ID() (string, error) { + return t.id, nil +} + +func (t *treeStorage) Root() (raw *treechangeproto.RawTreeChangeWithId, err error) { + return t.root, nil +} + +func (t *treeStorage) Heads() (heads []string, err error) { + headsBytes, err := t.db.Get(t.keys.HeadsKey()) + if err != nil { + return + } + if heads == nil { + err = storage.ErrUnknownTreeId + return + } + heads = parseHeads(headsBytes) + return +} + +func (t *treeStorage) SetHeads(heads []string) (err error) { + payload := createHeadsPayload(heads) + return t.db.Put(t.keys.HeadsKey(), payload) +} + +func (t *treeStorage) AddRawChange(change *treechangeproto.RawTreeChangeWithId) (err error) { + return t.db.Put([]byte(change.Id), change.RawChange) +} + +func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treechangeproto.RawTreeChangeWithId, err error) { + res, err := t.db.Get(t.keys.RawChangeKey(id)) + if err != nil { + return + } + if res == nil { + err = storage.ErrUnkownChange + } + + raw = &treechangeproto.RawTreeChangeWithId{ + RawChange: res, + Id: id, + } + return +} + +func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) { + return t.db.Has(t.keys.RawChangeKey(id)) +} + +func parseHeads(headsPayload []byte) []string { + return strings.Split(string(headsPayload), "/") +} + +func createHeadsPayload(heads []string) []byte { + var ( + b bytes.Buffer + totalLen int + ) + for _, s := range heads { + totalLen += len(s) + } + // adding separators + totalLen += len(heads) - 1 + b.Grow(totalLen) + for idx, s := range heads { + if idx > 0 { + b.WriteString("/") + } + b.WriteString(s) + } + return b.Bytes() +} diff --git a/go.mod b/go.mod index 49dbf9e1..b234e89d 100644 --- a/go.mod +++ b/go.mod @@ -71,10 +71,13 @@ require ( go.uber.org/multierr v1.8.0 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.5 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 03ccc1fe..8af8e42b 100644 --- a/go.sum +++ b/go.sum @@ -229,6 +229,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -283,6 +284,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/node/storage/keys.go b/node/storage/keys.go index 853cfdd8..7840334e 100644 --- a/node/storage/keys.go +++ b/node/storage/keys.go @@ -26,14 +26,12 @@ func (a aclKeys) RawRecordKey(id string) []byte { type treeKeys struct { id string headsKey []byte - rootKey []byte } func newTreeKeys(id string) treeKeys { return treeKeys{ id: id, headsKey: joinStringsToBytes("t", id, "heads"), - rootKey: joinStringsToBytes("t", id, "rootId"), } } @@ -41,10 +39,6 @@ func (t treeKeys) HeadsKey() []byte { return t.headsKey } -func (t treeKeys) RootIdKey() []byte { - return t.rootKey -} - func (t treeKeys) RawChangeKey(id string) []byte { return joinStringsToBytes("t", t.id, id) } diff --git a/node/storage/liststorage.go b/node/storage/liststorage.go index 989791b6..e4d99add 100644 --- a/node/storage/liststorage.go +++ b/node/storage/liststorage.go @@ -16,15 +16,6 @@ type listStorage struct { func newListStorage(db *pogreb.DB) (ls storage.ListStorage, err error) { keys := aclKeys{} - head, err := db.Get(keys.HeadIdKey()) - if err != nil { - return - } - if head == nil { - err = storage.ErrUnknownACLId - return - } - rootId, err := db.Get(keys.RootIdKey()) if err != nil { return diff --git a/node/storage/treestorage.go b/node/storage/treestorage.go index 6c6c0d57..a1253492 100644 --- a/node/storage/treestorage.go +++ b/node/storage/treestorage.go @@ -18,15 +18,6 @@ type treeStorage struct { func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err error) { keys := newTreeKeys(treeId) - has, err := db.Has(keys.RootIdKey()) - if err != nil { - return - } - if !has { - err = storage.ErrUnknownTreeId - return - } - heads, err := db.Get(keys.HeadsKey()) if err != nil { return @@ -64,7 +55,7 @@ func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err e func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) { keys := newTreeKeys(payload.TreeId) - has, err := db.Has(keys.RootIdKey()) + has, err := db.Has(keys.HeadsKey()) if err != nil { return } @@ -92,11 +83,6 @@ func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) return } - err = db.Put(keys.RootIdKey(), []byte(payload.RootRawChange.Id)) - if err != nil { - return - } - ts = &treeStorage{ db: db, keys: keys, diff --git a/pkg/acl/aclrecordproto/protos/aclrecord.proto b/pkg/acl/aclrecordproto/protos/aclrecord.proto index 4d2a969a..0da3b260 100644 --- a/pkg/acl/aclrecordproto/protos/aclrecord.proto +++ b/pkg/acl/aclrecordproto/protos/aclrecord.proto @@ -63,6 +63,15 @@ message ACLUserAdd { ACLUserPermissions permissions = 4; } +// signing accept key +// rsa encryption key -> read keys + +// accept key, encrypt key, invite id +// GetSpace(id) -> ... (space header + acl root) -> diff +// Join(ACLJoinRecord) -> Ok + +// + message ACLUserInvite { bytes acceptPublicKey = 1; bytes encryptPublicKey = 2;