diff --git a/pkg/acl/aclchanges/aclpb/aclchanges.pb.go b/pkg/acl/aclchanges/aclpb/aclchanges.pb.go index 4a892fdf..0a4b6746 100644 --- a/pkg/acl/aclchanges/aclpb/aclchanges.pb.go +++ b/pkg/acl/aclchanges/aclpb/aclchanges.pb.go @@ -1023,6 +1023,106 @@ func (m *ACLChangeUserPermissionChange) GetPermissions() ACLChangeUserPermission return ACLChange_Admin } +type Change struct { + TreeHeadIds []string `protobuf:"bytes,1,rep,name=treeHeadIds,proto3" json:"treeHeadIds,omitempty"` + AclHeadIds []string `protobuf:"bytes,2,rep,name=aclHeadIds,proto3" json:"aclHeadIds,omitempty"` + SnapshotBaseId string `protobuf:"bytes,3,opt,name=snapshotBaseId,proto3" json:"snapshotBaseId,omitempty"` + ChangesData []byte `protobuf:"bytes,4,opt,name=changesData,proto3" json:"changesData,omitempty"` + CurrentReadKeyHash uint64 `protobuf:"varint,5,opt,name=currentReadKeyHash,proto3" json:"currentReadKeyHash,omitempty"` + Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Identity string `protobuf:"bytes,7,opt,name=identity,proto3" json:"identity,omitempty"` + IsSnapshot bool `protobuf:"varint,8,opt,name=isSnapshot,proto3" json:"isSnapshot,omitempty"` +} + +func (m *Change) Reset() { *m = Change{} } +func (m *Change) String() string { return proto.CompactTextString(m) } +func (*Change) ProtoMessage() {} +func (*Change) Descriptor() ([]byte, []int) { + return fileDescriptor_37a022c841a51877, []int{2} +} +func (m *Change) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Change) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Change.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 *Change) XXX_Merge(src proto.Message) { + xxx_messageInfo_Change.Merge(m, src) +} +func (m *Change) XXX_Size() int { + return m.Size() +} +func (m *Change) XXX_DiscardUnknown() { + xxx_messageInfo_Change.DiscardUnknown(m) +} + +var xxx_messageInfo_Change proto.InternalMessageInfo + +func (m *Change) GetTreeHeadIds() []string { + if m != nil { + return m.TreeHeadIds + } + return nil +} + +func (m *Change) GetAclHeadIds() []string { + if m != nil { + return m.AclHeadIds + } + return nil +} + +func (m *Change) GetSnapshotBaseId() string { + if m != nil { + return m.SnapshotBaseId + } + return "" +} + +func (m *Change) GetChangesData() []byte { + if m != nil { + return m.ChangesData + } + return nil +} + +func (m *Change) GetCurrentReadKeyHash() uint64 { + if m != nil { + return m.CurrentReadKeyHash + } + return 0 +} + +func (m *Change) GetTimestamp() int64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +func (m *Change) GetIdentity() string { + if m != nil { + return m.Identity + } + return "" +} + +func (m *Change) GetIsSnapshot() bool { + if m != nil { + return m.IsSnapshot + } + return false +} + func init() { proto.RegisterEnum("acl.ACLChangeUserPermissions", ACLChangeUserPermissions_name, ACLChangeUserPermissions_value) proto.RegisterType((*RawChange)(nil), "acl.RawChange") @@ -1040,6 +1140,7 @@ func init() { proto.RegisterType((*ACLChangeUserRemove)(nil), "acl.ACLChange.UserRemove") proto.RegisterType((*ACLChangeReadKeyReplace)(nil), "acl.ACLChange.ReadKeyReplace") proto.RegisterType((*ACLChangeUserPermissionChange)(nil), "acl.ACLChange.UserPermissionChange") + proto.RegisterType((*Change)(nil), "acl.Change") } func init() { @@ -1047,67 +1148,69 @@ func init() { } var fileDescriptor_37a022c841a51877 = []byte{ - // 948 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0xf7, 0xc4, 0x4d, 0x1d, 0x3f, 0x87, 0x36, 0x0c, 0x2b, 0xd6, 0x58, 0x4b, 0x88, 0xca, 0x0a, - 0x45, 0x08, 0xa5, 0xab, 0xac, 0x90, 0x56, 0x80, 0x2a, 0xda, 0x82, 0x36, 0xa1, 0x1c, 0x56, 0x53, - 0x2d, 0x08, 0x6e, 0x53, 0x7b, 0x68, 0xad, 0x75, 0x6c, 0xe3, 0x99, 0x14, 0xe5, 0x82, 0xc4, 0x89, - 0x2b, 0x67, 0xbe, 0x0a, 0x5f, 0x60, 0x8f, 0x7b, 0xe4, 0x06, 0x6a, 0xef, 0x5c, 0xf8, 0x02, 0x68, - 0xfe, 0xd8, 0x71, 0x12, 0x6f, 0x24, 0xa4, 0x15, 0x12, 0x87, 0x4a, 0x33, 0xbf, 0xf7, 0x7b, 0xd3, - 0xf7, 0xde, 0xef, 0xbd, 0x17, 0xc3, 0x83, 0xfc, 0xd9, 0xe5, 0x21, 0x0d, 0x13, 0xf9, 0x17, 0x5e, - 0xd1, 0xf4, 0x92, 0x71, 0x79, 0xcc, 0x2f, 0x0e, 0xf3, 0x22, 0x13, 0x19, 0xaf, 0xe1, 0x23, 0x85, - 0x60, 0x9b, 0x86, 0xc9, 0xc1, 0x39, 0xb8, 0x84, 0xfe, 0x70, 0xaa, 0x0c, 0xd8, 0x07, 0x27, 0xa7, - 0x8b, 0x24, 0xa3, 0x91, 0x8f, 0x06, 0x68, 0xd8, 0x25, 0xe5, 0x15, 0xdf, 0x03, 0x97, 0xc7, 0x97, - 0x29, 0x15, 0xf3, 0x82, 0xf9, 0x2d, 0x65, 0x5b, 0x02, 0x78, 0x0f, 0x5a, 0x71, 0xe4, 0xdb, 0x03, - 0x34, 0x74, 0x49, 0x2b, 0x8e, 0x0e, 0xfe, 0x7e, 0x03, 0xdc, 0xe3, 0xd3, 0x2f, 0xcd, 0xab, 0x03, - 0xf0, 0x44, 0xc1, 0xd8, 0x84, 0xd1, 0x68, 0x1a, 0x71, 0x1f, 0x0d, 0xec, 0xa1, 0x4b, 0xea, 0x10, - 0xee, 0x03, 0xd0, 0x30, 0x29, 0x09, 0x2d, 0x45, 0xa8, 0x21, 0xf8, 0x3d, 0xd8, 0xe3, 0x29, 0xcd, - 0xf9, 0x55, 0x26, 0x4e, 0x28, 0x67, 0xd3, 0xf2, 0x7f, 0xad, 0xa1, 0xf8, 0x01, 0x38, 0x34, 0x4c, - 0x3e, 0xa3, 0x82, 0xfa, 0x3b, 0x03, 0x34, 0xf4, 0xc6, 0x6f, 0x8e, 0x68, 0x98, 0x8c, 0xaa, 0x50, - 0xe4, 0x49, 0x5a, 0x49, 0x49, 0x93, 0xb1, 0x99, 0xa2, 0x28, 0xaf, 0xb6, 0xca, 0xac, 0x0e, 0xe1, - 0x11, 0xe0, 0x70, 0x5e, 0x14, 0x2c, 0x15, 0x84, 0xd1, 0xe8, 0x8c, 0x2d, 0x26, 0x94, 0x5f, 0xf9, - 0xbb, 0x03, 0x34, 0xdc, 0x21, 0x0d, 0x16, 0x59, 0x29, 0x11, 0xcf, 0x18, 0x17, 0x74, 0x96, 0xfb, - 0xce, 0x00, 0x0d, 0x6d, 0xb2, 0x04, 0x70, 0x00, 0x9d, 0x38, 0x62, 0xa9, 0x88, 0xc5, 0xc2, 0xef, - 0xa8, 0x1c, 0xaa, 0x7b, 0xf0, 0xab, 0x0d, 0xfb, 0x32, 0xd4, 0x2c, 0x15, 0x2c, 0x15, 0x5f, 0xd1, - 0x64, 0xce, 0xf0, 0x18, 0x9c, 0x39, 0x67, 0xc5, 0x71, 0xa4, 0x15, 0xd9, 0xcc, 0xe8, 0xa9, 0xb6, - 0x4e, 0x2c, 0x52, 0x12, 0xf1, 0xc7, 0x00, 0xf2, 0x48, 0xd8, 0x2c, 0xbb, 0xd6, 0x62, 0x79, 0xe3, - 0xb7, 0x1a, 0xdc, 0x34, 0x61, 0x62, 0x91, 0x1a, 0x1d, 0x7f, 0x03, 0x77, 0xe4, 0xed, 0x09, 0x2b, - 0x66, 0x31, 0xe7, 0x71, 0x96, 0x6a, 0x07, 0x55, 0x70, 0x6f, 0xfc, 0x6e, 0xc3, 0x33, 0xeb, 0xd4, - 0x89, 0x45, 0x1a, 0x9f, 0x28, 0xe3, 0x9a, 0xa6, 0xd7, 0xb1, 0x60, 0x46, 0xa0, 0xa6, 0xb8, 0x34, - 0xa1, 0x8c, 0x4b, 0xdf, 0xf0, 0x87, 0xd0, 0x91, 0xb7, 0x2f, 0xb2, 0x38, 0x55, 0x2a, 0x79, 0xe3, - 0xbb, 0x0d, 0xae, 0xd2, 0x3c, 0xb1, 0x48, 0x45, 0xc5, 0x47, 0xe0, 0xc9, 0xf3, 0x69, 0x96, 0x7e, - 0x17, 0x17, 0x33, 0x25, 0x9b, 0x37, 0x0e, 0x1a, 0x3c, 0x0d, 0x63, 0x62, 0x91, 0xba, 0xc3, 0x89, - 0x03, 0xed, 0x6b, 0x29, 0x44, 0xf0, 0x33, 0x02, 0xc7, 0x74, 0x0f, 0xfe, 0x04, 0x3c, 0x1a, 0x26, - 0xe7, 0xa6, 0xf7, 0x8c, 0x30, 0xc1, 0x66, 0xab, 0x95, 0x0c, 0x52, 0xa7, 0xe3, 0x23, 0xd5, 0xec, - 0x46, 0x65, 0xd5, 0xec, 0xde, 0xb8, 0xbf, 0xe9, 0x5c, 0x6f, 0x03, 0x52, 0xf3, 0x08, 0x4e, 0xc0, - 0xab, 0xbd, 0x8d, 0x1f, 0x42, 0x47, 0xbe, 0x2e, 0xa8, 0x60, 0x26, 0x92, 0xbb, 0x0d, 0x91, 0x48, - 0x33, 0xa9, 0x88, 0xc1, 0x4f, 0x2d, 0xe8, 0x94, 0x30, 0xbe, 0x0f, 0xaf, 0x15, 0xcb, 0x06, 0x66, - 0x7a, 0x42, 0x77, 0xc8, 0x2a, 0x88, 0x1f, 0x69, 0xf5, 0x94, 0x0b, 0x37, 0x61, 0xfb, 0x0d, 0x85, - 0xd4, 0xff, 0xaa, 0xc6, 0xc5, 0x47, 0xe0, 0xc4, 0x4a, 0x44, 0xee, 0xdb, 0xca, 0xed, 0xfe, 0x4b, - 0x02, 0x1c, 0x69, 0xad, 0xf9, 0xe7, 0xa9, 0x28, 0x16, 0xa4, 0x74, 0x0a, 0x9e, 0x42, 0xb7, 0x6e, - 0xc0, 0x3d, 0xb0, 0x9f, 0xb1, 0x85, 0x4a, 0xd6, 0x25, 0xf2, 0x88, 0x0f, 0x8d, 0x4a, 0x5b, 0x9a, - 0x5d, 0xbf, 0x40, 0x34, 0xef, 0xa3, 0xd6, 0x23, 0x14, 0xfc, 0x81, 0xc0, 0xad, 0x02, 0x5e, 0x19, - 0x4c, 0xb4, 0x3a, 0x98, 0xb2, 0x40, 0x2c, 0x0d, 0x8b, 0x45, 0x2e, 0xe2, 0x2c, 0x3d, 0x63, 0x0b, - 0xb3, 0x00, 0x57, 0x41, 0xfc, 0x01, 0xbc, 0x6e, 0x00, 0x16, 0x99, 0x85, 0xa0, 0x13, 0xee, 0x92, - 0x4d, 0x03, 0xfe, 0x14, 0xbc, 0xbc, 0x1a, 0x10, 0xae, 0xa6, 0x61, 0x6f, 0xa3, 0x0d, 0x56, 0xc7, - 0x8b, 0x93, 0xba, 0x8b, 0x5c, 0x5d, 0x53, 0x6e, 0xfa, 0x94, 0x45, 0x6a, 0x28, 0x3a, 0xa4, 0x0e, - 0x05, 0xbf, 0x21, 0x70, 0xcc, 0x7e, 0xf8, 0xff, 0xe5, 0x17, 0x3c, 0x06, 0xaf, 0x36, 0x98, 0x5b, - 0x13, 0xb8, 0x07, 0xae, 0x59, 0x7e, 0xd3, 0x48, 0x05, 0xef, 0x92, 0x25, 0x10, 0xfc, 0x85, 0x00, - 0x96, 0x2d, 0x80, 0x87, 0xb0, 0x4f, 0xc3, 0x90, 0xe5, 0xe2, 0xc9, 0xfc, 0x22, 0x89, 0xc3, 0x33, - 0xd3, 0x4a, 0x5d, 0xb2, 0x0e, 0xe3, 0xf7, 0xa1, 0x67, 0x12, 0x5b, 0x52, 0x75, 0x69, 0x36, 0xf0, - 0xff, 0x5c, 0xfd, 0x00, 0x3a, 0x3a, 0x9f, 0xa9, 0x96, 0xde, 0x25, 0xd5, 0x3d, 0x78, 0x8e, 0xa0, - 0x53, 0x6e, 0xc3, 0x57, 0x20, 0x7c, 0x55, 0xb0, 0xf3, 0xea, 0x0b, 0xc0, 0xae, 0x17, 0xac, 0x82, - 0xf1, 0x01, 0x74, 0x97, 0x2b, 0x7b, 0x1a, 0xa9, 0xbc, 0x5c, 0xb2, 0x82, 0x35, 0x17, 0xaa, 0xfd, - 0x92, 0x42, 0x05, 0xdf, 0x6b, 0xe9, 0xcc, 0x8f, 0xd3, 0xb6, 0x5c, 0x1e, 0xc3, 0xbe, 0x59, 0x58, - 0x84, 0xe5, 0x09, 0x0d, 0xab, 0x6d, 0xf3, 0xf6, 0x5a, 0x59, 0xc9, 0x0a, 0x8b, 0xac, 0x7b, 0x05, - 0x3f, 0xc2, 0xde, 0x2a, 0xe5, 0x15, 0x94, 0x70, 0xd9, 0x49, 0x55, 0x6e, 0xa6, 0x86, 0x1b, 0x78, - 0x20, 0xe0, 0x4e, 0xd3, 0xcf, 0xea, 0xd6, 0x28, 0xd6, 0xfa, 0xa9, 0xf5, 0xaf, 0xfb, 0xe9, 0xe0, - 0x18, 0xf6, 0xd7, 0xec, 0xd8, 0x85, 0xf6, 0x71, 0x34, 0x8b, 0xd3, 0x9e, 0x85, 0x01, 0x76, 0xbf, - 0x2e, 0x62, 0xc1, 0x8a, 0x1e, 0x92, 0x67, 0x19, 0x2a, 0x2b, 0x7a, 0x2d, 0xec, 0x81, 0xa3, 0xa5, - 0x89, 0x7a, 0xf6, 0xc9, 0x3b, 0xcf, 0x6f, 0xfa, 0xe8, 0xc5, 0x4d, 0x1f, 0xfd, 0x79, 0xd3, 0x47, - 0xbf, 0xdc, 0xf6, 0xad, 0x17, 0xb7, 0x7d, 0xeb, 0xf7, 0xdb, 0xbe, 0xf5, 0x6d, 0x5b, 0x7d, 0x88, - 0x5e, 0xec, 0xaa, 0xef, 0xce, 0x87, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xe8, 0x23, 0x71, - 0xab, 0x0a, 0x00, 0x00, + // 987 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x4f, 0x8f, 0xdb, 0x44, + 0x14, 0xcf, 0x24, 0x9b, 0x75, 0xfc, 0x1c, 0x76, 0xc3, 0x50, 0x51, 0x63, 0x95, 0x10, 0x85, 0x0a, + 0x45, 0x08, 0x65, 0xab, 0x54, 0x48, 0x15, 0xa0, 0x15, 0xbb, 0x0b, 0x6a, 0xc2, 0x72, 0xa8, 0x66, + 0x55, 0x10, 0xdc, 0x66, 0xed, 0x61, 0xd7, 0xaa, 0x63, 0x1b, 0xcf, 0x64, 0x51, 0x2e, 0x48, 0x9c, + 0xb8, 0x72, 0xe6, 0xc0, 0x17, 0xe1, 0x0b, 0xf4, 0xd8, 0x23, 0x37, 0xd0, 0xee, 0x9d, 0x0b, 0x5f, + 0x00, 0xcd, 0x1f, 0x3b, 0x8e, 0xe3, 0x46, 0x45, 0xaa, 0x2a, 0x71, 0x88, 0x34, 0xf3, 0x7b, 0xbf, + 0x37, 0x79, 0xef, 0xfd, 0xde, 0x3c, 0x0f, 0xdc, 0x4b, 0x9f, 0x5c, 0x1c, 0x50, 0x3f, 0x92, 0x3f, + 0xff, 0x92, 0xc6, 0x17, 0x8c, 0xcb, 0x65, 0x7a, 0x7e, 0x90, 0x66, 0x89, 0x48, 0x78, 0x09, 0x1f, + 0x2b, 0x04, 0xb7, 0xa8, 0x1f, 0x0d, 0xcf, 0xc0, 0x26, 0xf4, 0x87, 0x13, 0x65, 0xc0, 0x2e, 0x58, + 0x29, 0x5d, 0x46, 0x09, 0x0d, 0x5c, 0x34, 0x40, 0xa3, 0x2e, 0xc9, 0xb7, 0xf8, 0x0e, 0xd8, 0x3c, + 0xbc, 0x88, 0xa9, 0x58, 0x64, 0xcc, 0x6d, 0x2a, 0xdb, 0x0a, 0xc0, 0x7b, 0xd0, 0x0c, 0x03, 0xb7, + 0x35, 0x40, 0x23, 0x9b, 0x34, 0xc3, 0x60, 0xf8, 0xcf, 0x1b, 0x60, 0x1f, 0x9d, 0x7c, 0x69, 0x4e, + 0x1d, 0x80, 0x23, 0x32, 0xc6, 0xa6, 0x8c, 0x06, 0xb3, 0x80, 0xbb, 0x68, 0xd0, 0x1a, 0xd9, 0xa4, + 0x0c, 0xe1, 0x3e, 0x00, 0xf5, 0xa3, 0x9c, 0xd0, 0x54, 0x84, 0x12, 0x82, 0xdf, 0x83, 0x3d, 0x1e, + 0xd3, 0x94, 0x5f, 0x26, 0xe2, 0x98, 0x72, 0x36, 0xcb, 0xff, 0xab, 0x82, 0xe2, 0x7b, 0x60, 0x51, + 0x3f, 0xfa, 0x8c, 0x0a, 0xea, 0xee, 0x0c, 0xd0, 0xc8, 0x99, 0xbc, 0x39, 0xa6, 0x7e, 0x34, 0x2e, + 0x42, 0x91, 0x2b, 0x69, 0x25, 0x39, 0x4d, 0xc6, 0x66, 0x8a, 0xa2, 0xbc, 0xda, 0x2a, 0xb3, 0x32, + 0x84, 0xc7, 0x80, 0xfd, 0x45, 0x96, 0xb1, 0x58, 0x10, 0x46, 0x83, 0x53, 0xb6, 0x9c, 0x52, 0x7e, + 0xe9, 0xee, 0x0e, 0xd0, 0x68, 0x87, 0xd4, 0x58, 0x64, 0xa5, 0x44, 0x38, 0x67, 0x5c, 0xd0, 0x79, + 0xea, 0x5a, 0x03, 0x34, 0x6a, 0x91, 0x15, 0x80, 0x3d, 0xe8, 0x84, 0x01, 0x8b, 0x45, 0x28, 0x96, + 0x6e, 0x47, 0xe5, 0x50, 0xec, 0xbd, 0x5f, 0x5b, 0xb0, 0x2f, 0x43, 0x4d, 0x62, 0xc1, 0x62, 0xf1, + 0x15, 0x8d, 0x16, 0x0c, 0x4f, 0xc0, 0x5a, 0x70, 0x96, 0x1d, 0x05, 0x5a, 0x91, 0xcd, 0x8c, 0x1e, + 0x6b, 0xeb, 0xb4, 0x41, 0x72, 0x22, 0xfe, 0x18, 0x40, 0x2e, 0x09, 0x9b, 0x27, 0x57, 0x5a, 0x2c, + 0x67, 0xf2, 0x56, 0x8d, 0x9b, 0x26, 0x4c, 0x1b, 0xa4, 0x44, 0xc7, 0xdf, 0xc0, 0x2d, 0xb9, 0x7b, + 0xc4, 0xb2, 0x79, 0xc8, 0x79, 0x98, 0xc4, 0xda, 0x41, 0x15, 0xdc, 0x99, 0xbc, 0x5b, 0x73, 0x4c, + 0x95, 0x3a, 0x6d, 0x90, 0xda, 0x23, 0xf2, 0xb8, 0x66, 0xf1, 0x55, 0x28, 0x98, 0x11, 0xa8, 0x2e, + 0x2e, 0x4d, 0xc8, 0xe3, 0xd2, 0x3b, 0xfc, 0x21, 0x74, 0xe4, 0xee, 0x8b, 0x24, 0x8c, 0x95, 0x4a, + 0xce, 0xe4, 0x76, 0x8d, 0xab, 0x34, 0x4f, 0x1b, 0xa4, 0xa0, 0xe2, 0x43, 0x70, 0xe4, 0xfa, 0x24, + 0x89, 0xbf, 0x0b, 0xb3, 0xb9, 0x92, 0xcd, 0x99, 0x78, 0x35, 0x9e, 0x86, 0x31, 0x6d, 0x90, 0xb2, + 0xc3, 0xb1, 0x05, 0xed, 0x2b, 0x29, 0x84, 0xf7, 0x33, 0x02, 0xcb, 0x74, 0x0f, 0xfe, 0x04, 0x1c, + 0xea, 0x47, 0x67, 0xa6, 0xf7, 0x8c, 0x30, 0xde, 0x66, 0xab, 0xe5, 0x0c, 0x52, 0xa6, 0xe3, 0x43, + 0xd5, 0xec, 0x46, 0x65, 0xd5, 0xec, 0xce, 0xa4, 0xbf, 0xe9, 0x5c, 0x6e, 0x03, 0x52, 0xf2, 0xf0, + 0x8e, 0xc1, 0x29, 0x9d, 0x8d, 0xef, 0x43, 0x47, 0x9e, 0x2e, 0xa8, 0x60, 0x26, 0x92, 0xdb, 0x35, + 0x91, 0x48, 0x33, 0x29, 0x88, 0xde, 0x4f, 0x4d, 0xe8, 0xe4, 0x30, 0xbe, 0x0b, 0xaf, 0x65, 0xab, + 0x06, 0x66, 0xfa, 0x86, 0xee, 0x90, 0x75, 0x10, 0x3f, 0xd0, 0xea, 0x29, 0x17, 0x6e, 0xc2, 0x76, + 0x6b, 0x0a, 0xa9, 0xff, 0xaa, 0xc4, 0xc5, 0x87, 0x60, 0x85, 0x4a, 0x44, 0xee, 0xb6, 0x94, 0xdb, + 0xdd, 0xe7, 0x04, 0x38, 0xd6, 0x5a, 0xf3, 0xcf, 0x63, 0x91, 0x2d, 0x49, 0xee, 0xe4, 0x3d, 0x86, + 0x6e, 0xd9, 0x80, 0x7b, 0xd0, 0x7a, 0xc2, 0x96, 0x2a, 0x59, 0x9b, 0xc8, 0x25, 0x3e, 0x30, 0x2a, + 0x6d, 0x69, 0x76, 0x7d, 0x02, 0xd1, 0xbc, 0x8f, 0x9a, 0x0f, 0x90, 0xf7, 0x27, 0x02, 0xbb, 0x08, + 0x78, 0xed, 0x62, 0xa2, 0xf5, 0x8b, 0x29, 0x0b, 0xc4, 0x62, 0x3f, 0x5b, 0xa6, 0x22, 0x4c, 0xe2, + 0x53, 0xb6, 0x34, 0x03, 0x70, 0x1d, 0xc4, 0x1f, 0xc0, 0xeb, 0x06, 0x60, 0x81, 0x19, 0x08, 0x3a, + 0xe1, 0x2e, 0xd9, 0x34, 0xe0, 0x4f, 0xc1, 0x49, 0x8b, 0x0b, 0xc2, 0xd5, 0x6d, 0xd8, 0xdb, 0x68, + 0x83, 0xf5, 0xeb, 0xc5, 0x49, 0xd9, 0x45, 0x8e, 0xae, 0x19, 0x37, 0x7d, 0xca, 0x02, 0x75, 0x29, + 0x3a, 0xa4, 0x0c, 0x79, 0xbf, 0x23, 0xb0, 0xcc, 0x7c, 0xf8, 0xff, 0xe5, 0xe7, 0x3d, 0x04, 0xa7, + 0x74, 0x31, 0xb7, 0x26, 0x70, 0x07, 0x6c, 0x33, 0xfc, 0x66, 0x81, 0x0a, 0xde, 0x26, 0x2b, 0xc0, + 0xfb, 0x1b, 0x01, 0xac, 0x5a, 0x00, 0x8f, 0x60, 0x9f, 0xfa, 0x3e, 0x4b, 0xc5, 0xa3, 0xc5, 0x79, + 0x14, 0xfa, 0xa7, 0xa6, 0x95, 0xba, 0xa4, 0x0a, 0xe3, 0xf7, 0xa1, 0x67, 0x12, 0x5b, 0x51, 0x75, + 0x69, 0x36, 0xf0, 0x57, 0xae, 0xbe, 0x07, 0x1d, 0x9d, 0xcf, 0x4c, 0x4b, 0x6f, 0x93, 0x62, 0xef, + 0x3d, 0x45, 0xd0, 0xc9, 0xa7, 0xe1, 0x4b, 0x10, 0xbe, 0x28, 0xd8, 0x59, 0xf1, 0x02, 0x68, 0x95, + 0x0b, 0x56, 0xc0, 0x78, 0x08, 0xdd, 0xd5, 0xc8, 0x9e, 0x05, 0x2a, 0x2f, 0x9b, 0xac, 0x61, 0xf5, + 0x85, 0x6a, 0x3f, 0xa7, 0x50, 0xde, 0xf7, 0x5a, 0x3a, 0xf3, 0x71, 0xda, 0x96, 0xcb, 0x43, 0xd8, + 0x37, 0x03, 0x8b, 0xb0, 0x34, 0xa2, 0x7e, 0x31, 0x6d, 0xde, 0xae, 0x94, 0x95, 0xac, 0xb1, 0x48, + 0xd5, 0xcb, 0xfb, 0x11, 0xf6, 0xd6, 0x29, 0x2f, 0xa1, 0x84, 0xab, 0x4e, 0x2a, 0x72, 0x33, 0x35, + 0xdc, 0xc0, 0x3d, 0x01, 0xb7, 0xea, 0x3e, 0xab, 0x5b, 0xa3, 0xa8, 0xf4, 0x53, 0xf3, 0x3f, 0xf7, + 0xd3, 0xf0, 0x08, 0xf6, 0x2b, 0x76, 0x6c, 0x43, 0xfb, 0x28, 0x98, 0x87, 0x71, 0xaf, 0x81, 0x01, + 0x76, 0xbf, 0xce, 0x42, 0xc1, 0xb2, 0x1e, 0x92, 0x6b, 0x19, 0x2a, 0xcb, 0x7a, 0x4d, 0xec, 0x80, + 0xa5, 0xa5, 0x09, 0x7a, 0xad, 0xe1, 0x6f, 0x4d, 0xd8, 0x7d, 0xe5, 0x4f, 0xbe, 0xca, 0x03, 0x6e, + 0xe7, 0x45, 0x1f, 0x70, 0xed, 0x17, 0x7b, 0xc0, 0xed, 0x6e, 0x7b, 0xc0, 0x59, 0x15, 0x15, 0xfa, + 0x00, 0x21, 0x2f, 0x9e, 0x05, 0x1d, 0x35, 0x90, 0x4b, 0xc8, 0xf1, 0x3b, 0x4f, 0xaf, 0xfb, 0xe8, + 0xd9, 0x75, 0x1f, 0xfd, 0x75, 0xdd, 0x47, 0xbf, 0xdc, 0xf4, 0x1b, 0xcf, 0x6e, 0xfa, 0x8d, 0x3f, + 0x6e, 0xfa, 0x8d, 0x6f, 0xdb, 0xea, 0xa5, 0x7e, 0xbe, 0xab, 0x1e, 0xe6, 0xf7, 0xff, 0x0d, 0x00, + 0x00, 0xff, 0xff, 0xf9, 0xfe, 0x4f, 0x12, 0xcc, 0x0b, 0x00, 0x00, } func (m *RawChange) Marshal() (dAtA []byte, err error) { @@ -1951,6 +2054,88 @@ func (m *ACLChangeUserPermissionChange) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } +func (m *Change) 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 *Change) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Change) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IsSnapshot { + i-- + if m.IsSnapshot { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x40 + } + if len(m.Identity) > 0 { + i -= len(m.Identity) + copy(dAtA[i:], m.Identity) + i = encodeVarintAclchanges(dAtA, i, uint64(len(m.Identity))) + i-- + dAtA[i] = 0x3a + } + if m.Timestamp != 0 { + i = encodeVarintAclchanges(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x30 + } + if m.CurrentReadKeyHash != 0 { + i = encodeVarintAclchanges(dAtA, i, uint64(m.CurrentReadKeyHash)) + i-- + dAtA[i] = 0x28 + } + if len(m.ChangesData) > 0 { + i -= len(m.ChangesData) + copy(dAtA[i:], m.ChangesData) + i = encodeVarintAclchanges(dAtA, i, uint64(len(m.ChangesData))) + i-- + dAtA[i] = 0x22 + } + if len(m.SnapshotBaseId) > 0 { + i -= len(m.SnapshotBaseId) + copy(dAtA[i:], m.SnapshotBaseId) + i = encodeVarintAclchanges(dAtA, i, uint64(len(m.SnapshotBaseId))) + i-- + dAtA[i] = 0x1a + } + if len(m.AclHeadIds) > 0 { + for iNdEx := len(m.AclHeadIds) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AclHeadIds[iNdEx]) + copy(dAtA[i:], m.AclHeadIds[iNdEx]) + i = encodeVarintAclchanges(dAtA, i, uint64(len(m.AclHeadIds[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.TreeHeadIds) > 0 { + for iNdEx := len(m.TreeHeadIds) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.TreeHeadIds[iNdEx]) + copy(dAtA[i:], m.TreeHeadIds[iNdEx]) + i = encodeVarintAclchanges(dAtA, i, uint64(len(m.TreeHeadIds[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintAclchanges(dAtA []byte, offset int, v uint64) int { offset -= sovAclchanges(v) base := offset @@ -2366,6 +2551,48 @@ func (m *ACLChangeUserPermissionChange) Size() (n int) { return n } +func (m *Change) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.TreeHeadIds) > 0 { + for _, s := range m.TreeHeadIds { + l = len(s) + n += 1 + l + sovAclchanges(uint64(l)) + } + } + if len(m.AclHeadIds) > 0 { + for _, s := range m.AclHeadIds { + l = len(s) + n += 1 + l + sovAclchanges(uint64(l)) + } + } + l = len(m.SnapshotBaseId) + if l > 0 { + n += 1 + l + sovAclchanges(uint64(l)) + } + l = len(m.ChangesData) + if l > 0 { + n += 1 + l + sovAclchanges(uint64(l)) + } + if m.CurrentReadKeyHash != 0 { + n += 1 + sovAclchanges(uint64(m.CurrentReadKeyHash)) + } + if m.Timestamp != 0 { + n += 1 + sovAclchanges(uint64(m.Timestamp)) + } + l = len(m.Identity) + if l > 0 { + n += 1 + l + sovAclchanges(uint64(l)) + } + if m.IsSnapshot { + n += 2 + } + return n +} + func sovAclchanges(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -4813,6 +5040,276 @@ func (m *ACLChangeUserPermissionChange) Unmarshal(dAtA []byte) error { } return nil } +func (m *Change) 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 ErrIntOverflowAclchanges + } + 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: Change: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Change: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TreeHeadIds", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + 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 ErrInvalidLengthAclchanges + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAclchanges + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TreeHeadIds = append(m.TreeHeadIds, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AclHeadIds", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + 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 ErrInvalidLengthAclchanges + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAclchanges + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AclHeadIds = append(m.AclHeadIds, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SnapshotBaseId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + 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 ErrInvalidLengthAclchanges + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAclchanges + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SnapshotBaseId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangesData", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAclchanges + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthAclchanges + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangesData = append(m.ChangesData[:0], dAtA[iNdEx:postIndex]...) + if m.ChangesData == nil { + m.ChangesData = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentReadKeyHash", wireType) + } + m.CurrentReadKeyHash = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CurrentReadKeyHash |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + 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 ErrInvalidLengthAclchanges + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAclchanges + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identity = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsSnapshot", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAclchanges + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsSnapshot = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipAclchanges(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAclchanges + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipAclchanges(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/pkg/acl/aclchanges/aclpb/protos/aclchanges.proto b/pkg/acl/aclchanges/aclpb/protos/aclchanges.proto index 1c8ceaf2..bc464cec 100644 --- a/pkg/acl/aclchanges/aclpb/protos/aclchanges.proto +++ b/pkg/acl/aclchanges/aclpb/protos/aclchanges.proto @@ -109,3 +109,14 @@ message ACLChange { Removed = 3; } } + +message Change { + repeated string treeHeadIds = 1; + repeated string aclHeadIds = 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; +} diff --git a/pkg/acl/acltree/doctree.go b/pkg/acl/acltree/doctree.go new file mode 100644 index 00000000..50b92e27 --- /dev/null +++ b/pkg/acl/acltree/doctree.go @@ -0,0 +1,8 @@ +package acltree + +type DocTree interface { +} + +type docTree struct { + tree *Tree +} diff --git a/pkg/acl/tree/aclstate.go b/pkg/acl/tree/aclstate.go new file mode 100644 index 00000000..f2016bb2 --- /dev/null +++ b/pkg/acl/tree/aclstate.go @@ -0,0 +1,367 @@ +package tree + +import ( + "bytes" + "errors" + "fmt" + "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" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric" + "github.com/gogo/protobuf/proto" + "hash/fnv" +) + +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") + +type ACLState struct { + currentReadKeyHash uint64 + userReadKeys map[uint64]*symmetric.Key + userStates map[string]*aclpb.ACLChangeUserState + userInvites map[string]*aclpb.ACLChangeUserInvite + signingPubKeyDecoder signingkey.PubKeyDecoder + encryptionKey encryptionkey.PrivKey + identity string +} + +func newACLStateWithIdentity( + identity string, + encryptionKey encryptionkey.PrivKey, + signingPubKeyDecoder signingkey.PubKeyDecoder) *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, + } +} + +func newACLState() *ACLState { + return &ACLState{ + userReadKeys: make(map[uint64]*symmetric.Key), + userStates: make(map[string]*aclpb.ACLChangeUserState), + userInvites: make(map[string]*aclpb.ACLChangeUserInvite), + } +} + +func (st *ACLState) applyChange(change *aclpb.Change) (err error) { + aclData := &aclpb.ACLChangeACLData{} + err = proto.Unmarshal(change.ChangesData, aclData) + if err != nil { + return + } + + defer func() { + if err != nil { + return + } + st.currentReadKeyHash = change.CurrentReadKeyHash + }() + + // 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(aclData) || (st.currentReadKeyHash == 0 && st.isUserAdd(aclData, change.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 { + err = ErrNoSuchUser + return + } + + if !st.hasPermission(change.Identity, aclpb.ACLChange_Admin) { + err = fmt.Errorf("user %s must have admin permissions", change.Identity) + return + } + } + + for _, ch := range aclData.GetAclContent() { + if err = st.applyChangeContent(ch); err != nil { + log.Infof("error while applying changes: %v; ignore", err) + return err + } + } + + return nil +} + +func (st *ACLState) applyChangeContent(ch *aclpb.ACLChangeACLContentValue) error { + switch { + case ch.GetUserPermissionChange() != nil: + return st.applyUserPermissionChange(ch.GetUserPermissionChange()) + case ch.GetUserAdd() != nil: + return st.applyUserAdd(ch.GetUserAdd()) + case ch.GetUserRemove() != nil: + return st.applyUserRemove(ch.GetUserRemove()) + case ch.GetUserInvite() != nil: + return st.applyUserInvite(ch.GetUserInvite()) + case ch.GetUserJoin() != nil: + return st.applyUserJoin(ch.GetUserJoin()) + case ch.GetUserConfirm() != nil: + return st.applyUserConfirm(ch.GetUserConfirm()) + default: + return fmt.Errorf("unexpected change type: %v", ch) + } +} + +func (st *ACLState) applyUserPermissionChange(ch *aclpb.ACLChangeUserPermissionChange) error { + if _, exists := st.userStates[ch.Identity]; !exists { + return ErrNoSuchUser + } + + st.userStates[ch.Identity].Permissions = ch.Permissions + return nil +} + +func (st *ACLState) applyUserInvite(ch *aclpb.ACLChangeUserInvite) error { + st.userInvites[ch.InviteId] = ch + return nil +} + +func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error { + invite, exists := st.userInvites[ch.UserInviteId] + if !exists { + return fmt.Errorf("no such invite with id %s", ch.UserInviteId) + } + + if _, exists = st.userStates[ch.Identity]; exists { + return ErrUserAlreadyExists + } + + // validating signature + signature := ch.GetAcceptSignature() + verificationKey, err := st.signingPubKeyDecoder.DecodeFromBytes(invite.AcceptPublicKey) + if err != nil { + return fmt.Errorf("public key verifying invite accepts is given in incorrect format: %v", err) + } + + rawSignedId, err := st.signingPubKeyDecoder.DecodeFromStringIntoBytes(ch.Identity) + if err != nil { + return fmt.Errorf("failed to decode signing identity as bytes") + } + + res, err := verificationKey.Verify(rawSignedId, signature) + if err != nil { + return fmt.Errorf("verification returned error: %w", err) + } + if !res { + return fmt.Errorf("signature is invalid") + } + + // if ourselves -> we need to decrypt the read keys + if st.identity == ch.Identity { + for _, key := range ch.EncryptedReadKeys { + key, hash, err := st.decryptReadKeyAndHash(key) + if err != nil { + return ErrFailedToDecrypt + } + + st.userReadKeys[hash] = key + } + } + + // adding user to the list + userState := &aclpb.ACLChangeUserState{ + Identity: ch.Identity, + EncryptionKey: ch.EncryptionKey, + EncryptedReadKeys: ch.EncryptedReadKeys, + Permissions: invite.Permissions, + IsConfirmed: true, + } + st.userStates[ch.Identity] = userState + return nil +} + +func (st *ACLState) applyUserAdd(ch *aclpb.ACLChangeUserAdd) error { + if _, exists := st.userStates[ch.Identity]; exists { + return ErrUserAlreadyExists + } + + st.userStates[ch.Identity] = &aclpb.ACLChangeUserState{ + Identity: ch.Identity, + EncryptionKey: ch.EncryptionKey, + Permissions: ch.Permissions, + EncryptedReadKeys: ch.EncryptedReadKeys, + } + + if ch.Identity == st.identity { + for _, key := range ch.EncryptedReadKeys { + key, hash, err := st.decryptReadKeyAndHash(key) + if err != nil { + return ErrFailedToDecrypt + } + + st.userReadKeys[hash] = key + } + } + + return nil +} + +func (st *ACLState) applyUserRemove(ch *aclpb.ACLChangeUserRemove) error { + if ch.Identity == st.identity { + return ErrDocumentForbidden + } + + if _, exists := st.userStates[ch.Identity]; !exists { + return ErrNoSuchUser + } + + delete(st.userStates, ch.Identity) + + for _, replace := range ch.ReadKeyReplaces { + userState, exists := st.userStates[replace.Identity] + if !exists { + continue + } + + userState.EncryptedReadKeys = append(userState.EncryptedReadKeys, replace.EncryptedReadKey) + // if this is our identity then we have to decrypt the key + if replace.Identity == st.identity { + key, hash, err := st.decryptReadKeyAndHash(replace.EncryptedReadKey) + if err != nil { + return ErrFailedToDecrypt + } + + st.currentReadKeyHash = hash + st.userReadKeys[st.currentReadKeyHash] = key + } + } + return nil +} + +func (st *ACLState) applyUserConfirm(ch *aclpb.ACLChangeUserConfirm) error { + if _, exists := st.userStates[ch.Identity]; !exists { + return ErrNoSuchUser + } + + userState := st.userStates[ch.Identity] + userState.IsConfirmed = true + return nil +} + +func (st *ACLState) decryptReadKeyAndHash(msg []byte) (*symmetric.Key, uint64, error) { + decrypted, err := st.encryptionKey.Decrypt(msg) + if err != nil { + return nil, 0, ErrFailedToDecrypt + } + + key, err := symmetric.FromBytes(decrypted) + if err != nil { + return nil, 0, ErrFailedToDecrypt + } + + hasher := fnv.New64() + hasher.Write(decrypted) + return key, hasher.Sum64(), nil +} + +func (st *ACLState) hasPermission(identity string, permission aclpb.ACLChangeUserPermissions) bool { + state, exists := st.userStates[identity] + if !exists { + return false + } + + return state.Permissions == permission +} + +func (st *ACLState) isUserJoin(data *aclpb.ACLChangeACLData) bool { + // if we have a UserJoin, then it should always be the first one applied + return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil +} + +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 := data.GetAclContent()[0].GetUserAdd() + return data.GetAclContent() != nil && userAdd != nil && userAdd.GetIdentity() == identity +} + +func (st *ACLState) getPermissionDecreasedUsers(ch *aclpb.ACLChange) (identities []*aclpb.ACLChangeUserPermissionChange) { + // this should be called after general checks are completed + if ch.GetAclData().GetAclContent() == nil { + return nil + } + + contents := ch.GetAclData().GetAclContent() + for _, c := range contents { + if c.GetUserPermissionChange() != nil { + content := c.GetUserPermissionChange() + + currentState := st.userStates[content.Identity] + // the comparison works in different direction :-) + if content.Permissions > currentState.Permissions { + identities = append(identities, &aclpb.ACLChangeUserPermissionChange{ + Identity: content.Identity, + Permissions: content.Permissions, + }) + } + } + if c.GetUserRemove() != nil { + content := c.GetUserRemove() + identities = append(identities, &aclpb.ACLChangeUserPermissionChange{ + Identity: content.Identity, + Permissions: aclpb.ACLChange_Removed, + }) + } + } + + return identities +} + +func (st *ACLState) equal(other *ACLState) bool { + if st == nil && other == nil { + return true + } + + if st == nil || other == nil { + return false + } + + if st.currentReadKeyHash != other.currentReadKeyHash { + return false + } + + if st.identity != other.identity { + return false + } + + if len(st.userStates) != len(other.userStates) { + return false + } + + for _, st := range st.userStates { + otherSt, exists := other.userStates[st.Identity] + if !exists { + return false + } + + if st.Permissions != otherSt.Permissions { + return false + } + + if bytes.Compare(st.EncryptionKey, otherSt.EncryptionKey) != 0 { + return false + } + } + + if len(st.userInvites) != len(other.userInvites) { + return false + } + + // TODO: add detailed user invites comparison + compare other stuff + return true +} + +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 == "" +} diff --git a/pkg/acl/tree/change.go b/pkg/acl/tree/change.go new file mode 100644 index 00000000..1d232133 --- /dev/null +++ b/pkg/acl/tree/change.go @@ -0,0 +1,82 @@ +package tree + +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 + DecryptedChange []byte + + Content *aclpb.Change + Sign []byte +} + +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 NewFromRawChange(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) + ch.Sign = rawChange.Signature + return ch, nil +} + +func NewChange(id string, ch *aclpb.Change) *Change { + return &Change{ + Next: nil, + PreviousIds: ch.TreeHeadIds, + Id: id, + Content: ch, + SnapshotId: ch.SnapshotBaseId, + IsSnapshot: ch.IsSnapshot, + } +} + +func (ch *Change) ProtoChange() *aclpb.Change { + return ch.Content +} + +func (ch *Change) DecryptedChangeContent() []byte { + return ch.DecryptedChange +} + +func (ch *Change) Signature() []byte { + return ch.Sign +} + +func (ch *Change) CID() string { + return ch.Id +} diff --git a/pkg/acl/tree/tree.go b/pkg/acl/tree/tree.go new file mode 100644 index 00000000..1c6cd1fe --- /dev/null +++ b/pkg/acl/tree/tree.go @@ -0,0 +1,416 @@ +package tree + +import ( + "bytes" + "crypto/md5" + "fmt" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" + "sort" +) + +type Mode int + +const ( + Append Mode = iota + Rebuild + Nothing +) + +// TODO: consider abstracting into separate package with iterator, remove +type Tree struct { + root *Change + headIds []string + metaHeadIds []string + attached map[string]*Change + unAttached map[string]*Change + // missed id -> list of dependency ids + waitList map[string][]string + invalidChanges map[string]struct{} + + // bufs + iterCompBuf []*Change + iterQueue []*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 + } + return "" +} + +func (t *Tree) Root() *Change { + return t.root +} + +func (t *Tree) AddFast(changes ...*Change) { + for _, c := range changes { + // ignore existing + if _, ok := t.attached[c.Id]; ok { + continue + } else if _, ok := t.unAttached[c.Id]; ok { + continue + } + t.add(c) + } + t.updateHeads() +} + +func (t *Tree) Add(changes ...*Change) (mode Mode) { + var beforeHeadIds = t.headIds + var attached bool + var empty = t.Len() == 0 + for _, c := range changes { + // ignore existing + if _, ok := t.attached[c.Id]; ok { + continue + } else if _, ok := t.unAttached[c.Id]; ok { + continue + } + if t.add(c) { + attached = true + } + } + if !attached { + return Nothing + } + t.updateHeads() + if empty { + return Rebuild + } + for _, hid := range beforeHeadIds { + for _, newCh := range changes { + if _, ok := t.attached[newCh.Id]; ok { + if !t.after(newCh.Id, hid) { + return Rebuild + } + } + } + } + return Append +} + +func (t *Tree) RemoveInvalidChange(id string) { + stack := []string{id} + // removing all children of this id (either next or unattached) + for len(stack) > 0 { + var exists bool + top := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + if _, exists = t.invalidChanges[top]; exists { + continue + } + + var rem *Change + t.invalidChanges[top] = struct{}{} + if rem, exists = t.unAttached[top]; exists { + delete(t.unAttached, top) + } else if rem, exists = t.attached[top]; exists { + // remove from all prev changes + for _, id := range rem.PreviousIds { + prev, exists := t.attached[id] + if !exists { + continue + } + for i, next := range prev.Next { + if next.Id == top { + prev.Next[i] = nil + prev.Next = append(prev.Next[:i], prev.Next[i+1:]...) + break + } + } + } + delete(t.attached, top) + } + for _, el := range rem.Unattached { + stack = append(stack, el.Id) + } + for _, el := range rem.Next { + stack = append(stack, el.Id) + } + } + t.updateHeads() +} + +func (t *Tree) add(c *Change) (attached bool) { + if c == nil { + return false + } + if _, exists := t.invalidChanges[c.Id]; exists { + return false + } + + if t.root == nil { // first element + t.root = c + t.attached = map[string]*Change{ + c.Id: c, + } + t.unAttached = make(map[string]*Change) + t.waitList = make(map[string][]string) + t.invalidChanges = make(map[string]struct{}) + return true + } + if len(c.PreviousIds) > 1 { + sort.Strings(c.PreviousIds) + } + // 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) + continue + } + attached = false + if prev, ok := t.unAttached[pid]; ok { + prev.Unattached = append(prev.Unattached, c) + continue + } + wl := t.waitList[pid] + wl = append(wl, c.Id) + t.waitList[pid] = wl + } + 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 +} + +func (t *Tree) canAttach(c *Change) (attach bool) { + if c == nil { + return false + } + attach = true + for _, id := range c.PreviousIds { + if _, exists := t.attached[id]; !exists { + attach = false + } + } + return +} + +func (t *Tree) attach(c *Change, newEl bool) { + t.attached[c.Id] = c + if !newEl { + delete(t.unAttached, c.Id) + } + + // add next to all prev changes + for _, id := range c.PreviousIds { + // prev id must be attached if we attach this id + 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 + } + } + } + + // clearing wait list + if waitIds, ok := t.waitList[c.Id]; ok { + for _, wid := range waitIds { + next := t.unAttached[wid] + if t.canAttach(next) { + t.attach(next, false) + } + } + 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) { + t.iterate(t.attached[id2], func(c *Change) (isContinue bool) { + if c.Id == id1 { + found = true + return false + } + return true + }) + 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{} + + for len(stack) > 0 { + ch := stack[len(stack)-1] + stack = stack[:len(stack)-1] + if _, exists := uniqMap[ch.Id]; exists { + continue + } + + uniqMap[ch.Id] = ch + + for _, prev := range ch.PreviousIds { + stack = append(stack, t.attached[prev]) + } + } + return uniqMap +} + +func (t *Tree) updateHeads() { + var newHeadIds, newMetaHeadIds []string + t.iterate(t.root, func(c *Change) (isContinue bool) { + if len(c.Next) == 0 { + newHeadIds = append(newHeadIds, c.Id) + } + 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)) { + it := newIterator() + defer freeIterator(it) + it.iterate(start, f) +} + +func (t *Tree) iterateSkip(start *Change, skipBefore *Change, f func(c *Change) (isContinue bool)) { + it := newIterator() + defer freeIterator(it) + it.iterateSkip(start, skipBefore, f) +} + +func (t *Tree) IterateSkip(startId string, skipBeforeId string, f func(c *Change) (isContinue bool)) { + it := newIterator() + defer freeIterator(it) + it.iterateSkip(t.attached[startId], t.attached[skipBeforeId], f) +} + +func (t *Tree) Iterate(startId string, f func(c *Change) (isContinue bool)) { + t.iterate(t.attached[startId], f) +} + +func (t *Tree) IterateBranching(startId string, f func(c *Change, branchLevel int) (isContinue bool)) { + // branchLevel indicates the number of parallel branches + var bc int + t.iterate(t.attached[startId], func(c *Change) (isContinue bool) { + if pl := len(c.PreviousIds); pl > 1 { + bc -= pl - 1 + } + bl := bc + if nl := len(c.Next); nl > 1 { + bc += nl - 1 + } + return f(c, bl) + }) +} + +func (t *Tree) Hash() string { + h := md5.New() + n := 0 + t.iterate(t.root, func(c *Change) (isContinue bool) { + n++ + fmt.Fprintf(h, "-%s", c.Id) + return true + }) + return fmt.Sprintf("%d-%x", n, h.Sum(nil)) +} + +func (t *Tree) GetDuplicateEvents() int { + return t.duplicateEvents +} + +func (t *Tree) ResetDuplicateEvents() { + t.duplicateEvents = 0 +} + +func (t *Tree) Len() int { + return len(t.attached) +} + +func (t *Tree) Heads() []string { + return t.headIds +} + +func (t *Tree) String() string { + var buf = bytes.NewBuffer(nil) + t.Iterate(t.RootId(), func(c *Change) (isContinue bool) { + buf.WriteString(c.Id) + if len(c.Next) > 1 { + buf.WriteString("-<") + } else if len(c.Next) > 0 { + buf.WriteString("->") + } else { + buf.WriteString("-|") + } + return true + }) + return buf.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] +} diff --git a/pkg/acl/tree/treebuilder.go b/pkg/acl/tree/treebuilder.go new file mode 100644 index 00000000..44da7c6d --- /dev/null +++ b/pkg/acl/tree/treebuilder.go @@ -0,0 +1,365 @@ +package tree + +import ( + "context" + "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/pkg/acl/treestorage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" + "github.com/gogo/protobuf/proto" + "time" +) + +var ( + log = logger.NewNamed("acltree").Sugar() + ErrEmpty = errors.New("logs empty") +) + +type treeBuilder struct { + cache map[string]*Change + identityKeys map[string]signingkey.PubKey + signingPubKeyDecoder signingkey.PubKeyDecoder + tree *Tree + treeStorage treestorage.TreeStorage +} + +func newTreeBuilder(t treestorage.TreeStorage, decoder signingkey.PubKeyDecoder) *treeBuilder { + return &treeBuilder{ + signingPubKeyDecoder: decoder, + treeStorage: t, + } +} + +func (tb *treeBuilder) Init() { + tb.cache = make(map[string]*Change) + tb.identityKeys = make(map[string]signingkey.PubKey) + tb.tree = &Tree{} +} + +func (tb *treeBuilder) Build(fromStart bool) (*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...) + + 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) + } + } + + tb.cache = nil + + return tb.tree, nil +} + +func (tb *treeBuilder) 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 *treeBuilder) 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 *treeBuilder) buildTree(heads []string, breakpoint string) (err error) { + ch, err := tb.loadChange(breakpoint) + if err != nil { + return + } + 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) + } + } + return buf, nil +} + +func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) { + if ch, ok := tb.cache[id]; ok { + return ch, nil + } + + // TODO: Add virtual changes logic + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + change, err := tb.treeStorage.GetChange(ctx, id) + if err != nil { + return nil, err + } + + verifiedChange, err := tb.makeVerifiedChange(change) + if err != nil { + return nil, err + } + + tb.cache[id] = NewChange(id, verifiedChange) + + return ch, nil +} + +func (tb *treeBuilder) verify(identity string, payload, signature []byte) (isVerified bool, err error) { + identityKey, exists := tb.identityKeys[identity] + if !exists { + identityKey, err = tb.signingPubKeyDecoder.DecodeFromString(identity) + if err != nil { + return + } + tb.identityKeys[identity] = identityKey + } + return identityKey.Verify(payload, signature) +} + +func (tb *treeBuilder) makeVerifiedChange(change *aclpb.RawChange) (aclChange *aclpb.Change, err error) { + aclChange = new(aclpb.Change) + + // 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 = tb.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 (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.ACLChange, err error) { + aclChange = new(aclpb.ACLChange) + err = proto.Unmarshal(change.Payload, aclChange) + return +} + +func (tb *treeBuilder) findBreakpoint(heads []string) (breakpoint string, err error) { + var ( + ch *Change + snapshotIds []string + ) + for _, head := range heads { + if ch, err = tb.loadChange(head); err != nil { + return + } + shId := ch.SnapshotId + if ch.IsSnapshot { + shId = ch.Id + } + if slice.FindPos(snapshotIds, shId) == -1 { + snapshotIds = append(snapshotIds, shId) + } + } + return tb.findCommonSnapshot(snapshotIds) +} + +func (tb *treeBuilder) findCommonSnapshot(snapshotIds []string) (snapshotId string, err error) { + if len(snapshotIds) == 1 { + return snapshotIds[0], nil + } else if len(snapshotIds) == 0 { + return "", fmt.Errorf("snapshots not found") + } + + for len(snapshotIds) > 1 { + l := len(snapshotIds) + shId, e := tb.findCommonForTwoSnapshots(snapshotIds[l-2], snapshotIds[l-1]) + if e != nil { + return "", e + } + snapshotIds[l-2] = shId + snapshotIds = snapshotIds[:l-1] + } + return snapshotIds[0], nil +} + +func (tb *treeBuilder) findCommonForTwoSnapshots(s1, s2 string) (s string, err error) { + // fast cases + if s1 == s2 { + return s1, nil + } + ch1, err := tb.loadChange(s1) + if err != nil { + return "", err + } + if ch1.SnapshotId == s2 { + return s2, nil + } + ch2, err := tb.loadChange(s2) + if err != nil { + return "", err + } + if ch2.SnapshotId == s1 { + return s1, nil + } + if ch1.SnapshotId == ch2.SnapshotId && ch1.SnapshotId != "" { + return ch1.SnapshotId, nil + } + // traverse + var t1 = make([]string, 0, 5) + var t2 = make([]string, 0, 5) + t1 = append(t1, ch1.Id, ch1.SnapshotId) + t2 = append(t2, ch2.Id, ch2.SnapshotId) + for { + lid1 := t1[len(t1)-1] + if lid1 != "" { + l1, e := tb.loadChange(lid1) + if e != nil { + return "", e + } + if l1.SnapshotId != "" { + if slice.FindPos(t2, l1.SnapshotId) != -1 { + return l1.SnapshotId, nil + } + } + t1 = append(t1, l1.SnapshotId) + } + lid2 := t2[len(t2)-1] + if lid2 != "" { + l2, e := tb.loadChange(t2[len(t2)-1]) + if e != nil { + return "", e + } + if l2.SnapshotId != "" { + if slice.FindPos(t1, l2.SnapshotId) != -1 { + return l2.SnapshotId, nil + } + } + t2 = append(t2, l2.SnapshotId) + } + if lid1 == "" && lid2 == "" { + break + } + } + + log.Warnf("changes build Tree: possible versions split") + + // prefer not first snapshot + if len(ch1.PreviousIds) == 0 && len(ch2.PreviousIds) > 0 { + log.Warnf("changes build Tree: prefer %s(%d prevIds) over %s(%d prevIds)", s2, len(ch2.PreviousIds), s1, len(ch1.PreviousIds)) + return s2, nil + } else if len(ch1.PreviousIds) > 0 && len(ch2.PreviousIds) == 0 { + log.Warnf("changes build Tree: prefer %s(%d prevIds) over %s(%d prevIds)", s1, len(ch1.PreviousIds), s2, len(ch2.PreviousIds)) + return s1, nil + } + + 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) + return s2, nil + } else if isEmptySnapshot(ch2) && !isEmptySnapshot(ch1) { + log.Warnf("changes build Tree: prefer %s(not empty) over %s(empty)", s1, s2) + 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) + return s1, nil + } + log.Warnf("changes build Tree: prefer %s (%s<%s)", s2, s2, s1) + + return s2, nil +}