git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/container/container.go (about)

     1  package container
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"crypto/sha256"
     6  	"errors"
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
    13  	v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
    14  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
    15  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
    16  	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
    17  	frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
    18  	frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
    19  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
    20  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
    21  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
    22  	"github.com/google/uuid"
    23  )
    24  
    25  // Container represents descriptor of the FrostFS container. Container logically
    26  // stores FrostFS objects. Container is one of the basic and at the same time
    27  // necessary data storage units in the FrostFS. Container includes data about the
    28  // owner, rules for placing objects and other information necessary for the
    29  // system functioning.
    30  //
    31  // Container type instances can represent different container states in the
    32  // system, depending on the context. To create new container in FrostFS zero
    33  // instance SHOULD be declared, initialized using Init method and filled using
    34  // dedicated methods. Once container is saved in the FrostFS network, it can't be
    35  // changed: containers stored in the system are immutable, and FrostFS is a CAS
    36  // of containers that are identified by a fixed length value (see cid.ID type).
    37  // Instances for existing containers can be initialized using decoding methods
    38  // (e.g Unmarshal).
    39  //
    40  // Container is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container.Container
    41  // message. See ReadFromV2 / WriteToV2 methods.
    42  type Container struct {
    43  	v2 container.Container
    44  }
    45  
    46  const (
    47  	attributeName      = "Name"
    48  	attributeTimestamp = "Timestamp"
    49  )
    50  
    51  // reads Container from the container.Container message. If checkFieldPresence is set,
    52  // returns an error on absence of any protocol-required field.
    53  func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) error {
    54  	var err error
    55  
    56  	ownerV2 := m.GetOwnerID()
    57  	if ownerV2 != nil {
    58  		var owner user.ID
    59  
    60  		err = owner.ReadFromV2(*ownerV2)
    61  		if err != nil {
    62  			return fmt.Errorf("invalid owner: %w", err)
    63  		}
    64  	} else if checkFieldPresence {
    65  		return errors.New("missing owner")
    66  	}
    67  
    68  	binNonce := m.GetNonce()
    69  	if len(binNonce) > 0 {
    70  		var nonce uuid.UUID
    71  
    72  		err = nonce.UnmarshalBinary(binNonce)
    73  		if err != nil {
    74  			return fmt.Errorf("invalid nonce: %w", err)
    75  		} else if ver := nonce.Version(); ver != 4 {
    76  			return fmt.Errorf("invalid nonce UUID version %d", ver)
    77  		}
    78  	} else if checkFieldPresence {
    79  		return errors.New("missing nonce")
    80  	}
    81  
    82  	ver := m.GetVersion()
    83  	if checkFieldPresence && ver == nil {
    84  		return errors.New("missing version")
    85  	}
    86  
    87  	policyV2 := m.GetPlacementPolicy()
    88  	if policyV2 != nil {
    89  		var policy netmap.PlacementPolicy
    90  
    91  		err = policy.ReadFromV2(*policyV2)
    92  		if err != nil {
    93  			return fmt.Errorf("invalid placement policy: %w", err)
    94  		}
    95  	} else if checkFieldPresence {
    96  		return errors.New("missing placement policy")
    97  	}
    98  
    99  	if err := checkAttributes(m); err != nil {
   100  		return err
   101  	}
   102  
   103  	x.v2 = m
   104  
   105  	return nil
   106  }
   107  
   108  func checkAttributes(m container.Container) error {
   109  	attrs := m.GetAttributes()
   110  	mAttr := make(map[string]struct{}, len(attrs))
   111  	var key, val string
   112  	var was bool
   113  
   114  	for i := range attrs {
   115  		key = attrs[i].GetKey()
   116  		if key == "" {
   117  			return errors.New("empty attribute key")
   118  		}
   119  
   120  		_, was = mAttr[key]
   121  		if was {
   122  			return fmt.Errorf("duplicated attribute %s", key)
   123  		}
   124  
   125  		val = attrs[i].GetValue()
   126  		if val == "" {
   127  			return fmt.Errorf("empty attribute value %s", key)
   128  		}
   129  
   130  		var err error
   131  		if key == attributeTimestamp {
   132  			_, err = strconv.ParseInt(val, 10, 64)
   133  		}
   134  
   135  		if err != nil {
   136  			return fmt.Errorf("invalid attribute value %s: %s (%w)", key, val, err)
   137  		}
   138  
   139  		mAttr[key] = struct{}{}
   140  	}
   141  	return nil
   142  }
   143  
   144  // ReadFromV2 reads Container from the container.Container message. Checks if the
   145  // message conforms to FrostFS API V2 protocol.
   146  //
   147  // See also WriteToV2.
   148  func (x *Container) ReadFromV2(m container.Container) error {
   149  	return x.readFromV2(m, true)
   150  }
   151  
   152  // WriteToV2 writes Container into the container.Container message.
   153  // The message MUST NOT be nil.
   154  //
   155  // See also ReadFromV2.
   156  func (x Container) WriteToV2(m *container.Container) {
   157  	*m = x.v2
   158  }
   159  
   160  // Marshal encodes Container into a binary format of the FrostFS API protocol
   161  // (Protocol Buffers with direct field order).
   162  //
   163  // See also Unmarshal.
   164  func (x Container) Marshal() []byte {
   165  	return x.v2.StableMarshal(nil)
   166  }
   167  
   168  // Unmarshal decodes FrostFS API protocol binary format into the Container
   169  // (Protocol Buffers with direct field order). Returns an error describing
   170  // a format violation.
   171  //
   172  // See also Marshal.
   173  func (x *Container) Unmarshal(data []byte) error {
   174  	var m container.Container
   175  
   176  	err := m.Unmarshal(data)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	return x.readFromV2(m, false)
   182  }
   183  
   184  // MarshalJSON encodes Container into a JSON format of the FrostFS API protocol
   185  // (Protocol Buffers JSON).
   186  //
   187  // See also UnmarshalJSON.
   188  func (x Container) MarshalJSON() ([]byte, error) {
   189  	return x.v2.MarshalJSON()
   190  }
   191  
   192  // UnmarshalJSON decodes FrostFS API protocol JSON format into the Container
   193  // (Protocol Buffers JSON). Returns an error describing a format violation.
   194  //
   195  // See also MarshalJSON.
   196  func (x *Container) UnmarshalJSON(data []byte) error {
   197  	return x.v2.UnmarshalJSON(data)
   198  }
   199  
   200  // Init initializes all internal data of the Container required by FrostFS API
   201  // protocol. Init MUST be called when creating a new container. Init SHOULD NOT
   202  // be called multiple times. Init SHOULD NOT be called if the Container instance
   203  // is used for decoding only.
   204  func (x *Container) Init() {
   205  	var ver refs.Version
   206  	version.Current().WriteToV2(&ver)
   207  
   208  	x.v2.SetVersion(&ver)
   209  
   210  	nonce, err := uuid.New().MarshalBinary()
   211  	if err != nil {
   212  		panic(fmt.Sprintf("unexpected error from UUID.MarshalBinary: %v", err))
   213  	}
   214  
   215  	x.v2.SetNonce(nonce)
   216  }
   217  
   218  // SetOwner specifies the owner of the Container. Each Container has exactly
   219  // one owner, so SetOwner MUST be called for instances to be saved in the
   220  // FrostFS.
   221  //
   222  // See also Owner.
   223  func (x *Container) SetOwner(owner user.ID) {
   224  	var m refs.OwnerID
   225  	owner.WriteToV2(&m)
   226  
   227  	x.v2.SetOwnerID(&m)
   228  }
   229  
   230  // Owner returns owner of the Container set using SetOwner.
   231  //
   232  // Zero Container has no owner which is incorrect according to FrostFS API
   233  // protocol.
   234  func (x Container) Owner() (res user.ID) {
   235  	m := x.v2.GetOwnerID()
   236  	if m != nil {
   237  		err := res.ReadFromV2(*m)
   238  		if err != nil {
   239  			panic(fmt.Sprintf("unexpected error from user.ID.ReadFromV2: %v", err))
   240  		}
   241  	}
   242  
   243  	return
   244  }
   245  
   246  // SetBasicACL specifies basic part of the Container ACL. Basic ACL is used
   247  // to control access inside container storage.
   248  //
   249  // See also BasicACL.
   250  func (x *Container) SetBasicACL(basicACL acl.Basic) {
   251  	x.v2.SetBasicACL(basicACL.Bits())
   252  }
   253  
   254  // BasicACL returns basic ACL set using SetBasicACL.
   255  //
   256  // Zero Container has zero basic ACL which structurally correct but doesn't
   257  // make sense since it denies any access to any party.
   258  func (x Container) BasicACL() (res acl.Basic) {
   259  	res.FromBits(x.v2.GetBasicACL())
   260  	return
   261  }
   262  
   263  // SetPlacementPolicy sets placement policy for the objects within the Container.
   264  // FrostFS storage layer strives to follow the specified policy.
   265  //
   266  // See also PlacementPolicy.
   267  func (x *Container) SetPlacementPolicy(policy netmap.PlacementPolicy) {
   268  	var m v2netmap.PlacementPolicy
   269  	policy.WriteToV2(&m)
   270  
   271  	x.v2.SetPlacementPolicy(&m)
   272  }
   273  
   274  // PlacementPolicy returns placement policy set using SetPlacementPolicy.
   275  //
   276  // Zero Container has no placement policy which is incorrect according to
   277  // FrostFS API protocol.
   278  func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) {
   279  	m := x.v2.GetPlacementPolicy()
   280  	if m != nil {
   281  		err := res.ReadFromV2(*m)
   282  		if err != nil {
   283  			panic(fmt.Sprintf("unexpected error from PlacementPolicy.ReadFromV2: %v", err))
   284  		}
   285  	}
   286  
   287  	return
   288  }
   289  
   290  // SetAttribute sets Container attribute value by key. Both key and value
   291  // MUST NOT be empty. Attributes set by the creator (owner) are most commonly
   292  // ignored by the FrostFS system and used for application layer. Some attributes
   293  // are so-called system or well-known attributes: they are reserved for system
   294  // needs. System attributes SHOULD NOT be modified using SetAttribute, use
   295  // corresponding methods/functions. List of the reserved keys is documented
   296  // in the particular protocol version.
   297  //
   298  // SetAttribute overwrites existing attribute value.
   299  //
   300  // See also Attribute, IterateAttributes, IterateUserAttributes.
   301  func (x *Container) SetAttribute(key, value string) {
   302  	if key == "" {
   303  		panic("empty attribute key")
   304  	} else if value == "" {
   305  		panic("empty attribute value")
   306  	}
   307  
   308  	attrs := x.v2.GetAttributes()
   309  	ln := len(attrs)
   310  
   311  	for i := range ln {
   312  		if attrs[i].GetKey() == key {
   313  			attrs[i].SetValue(value)
   314  			return
   315  		}
   316  	}
   317  
   318  	attrs = append(attrs, container.Attribute{})
   319  	attrs[ln].SetKey(key)
   320  	attrs[ln].SetValue(value)
   321  
   322  	x.v2.SetAttributes(attrs)
   323  }
   324  
   325  // Attribute reads value of the Container attribute by key. Empty result means
   326  // attribute absence.
   327  //
   328  // See also SetAttribute, IterateAttributes, IterateUserAttributes.
   329  func (x Container) Attribute(key string) string {
   330  	attrs := x.v2.GetAttributes()
   331  	for i := range attrs {
   332  		if attrs[i].GetKey() == key {
   333  			return attrs[i].GetValue()
   334  		}
   335  	}
   336  
   337  	return ""
   338  }
   339  
   340  // IterateAttributes iterates over all Container attributes and passes them
   341  // into f. The handler MUST NOT be nil.
   342  //
   343  // See also SetAttribute, Attribute.
   344  func (x Container) IterateAttributes(f func(key, val string)) {
   345  	attrs := x.v2.GetAttributes()
   346  	for i := range attrs {
   347  		f(attrs[i].GetKey(), attrs[i].GetValue())
   348  	}
   349  }
   350  
   351  // IterateUserAttributes iterates over user Container attributes and passes them
   352  // into f. The handler MUST NOT be nil.
   353  //
   354  // See also SetAttribute, Attribute.
   355  func (x Container) IterateUserAttributes(f func(key, val string)) {
   356  	attrs := x.v2.GetAttributes()
   357  	for _, attr := range attrs {
   358  		key := attr.GetKey()
   359  		if !strings.HasPrefix(key, container.SysAttributePrefix) {
   360  			f(key, attr.GetValue())
   361  		}
   362  	}
   363  }
   364  
   365  // SetName sets human-readable name of the Container. Name MUST NOT be empty.
   366  //
   367  // See also Name.
   368  func SetName(cnr *Container, name string) {
   369  	cnr.SetAttribute(attributeName, name)
   370  }
   371  
   372  // Name returns container name set using SetName.
   373  //
   374  // Zero Container has no name.
   375  func Name(cnr Container) string {
   376  	return cnr.Attribute(attributeName)
   377  }
   378  
   379  // SetCreationTime writes container's creation time in Unix Timestamp format.
   380  //
   381  // See also CreatedAt.
   382  func SetCreationTime(cnr *Container, t time.Time) {
   383  	cnr.SetAttribute(attributeTimestamp, strconv.FormatInt(t.Unix(), 10))
   384  }
   385  
   386  // CreatedAt returns container's creation time set using SetCreationTime.
   387  //
   388  // Zero Container has zero timestamp (in seconds).
   389  func CreatedAt(cnr Container) time.Time {
   390  	var sec int64
   391  
   392  	attr := cnr.Attribute(attributeTimestamp)
   393  	if attr != "" {
   394  		var err error
   395  
   396  		sec, err = strconv.ParseInt(cnr.Attribute(attributeTimestamp), 10, 64)
   397  		if err != nil {
   398  			panic(fmt.Sprintf("parse container timestamp: %v", err))
   399  		}
   400  	}
   401  
   402  	return time.Unix(sec, 0)
   403  }
   404  
   405  const attributeHomoHashEnabled = "true"
   406  
   407  // DisableHomomorphicHashing sets flag to disable homomorphic hashing of the
   408  // Container data.
   409  //
   410  // See also IsHomomorphicHashingDisabled.
   411  func DisableHomomorphicHashing(cnr *Container) {
   412  	cnr.SetAttribute(container.SysAttributeHomomorphicHashing, attributeHomoHashEnabled)
   413  }
   414  
   415  // IsHomomorphicHashingDisabled checks if DisableHomomorphicHashing was called.
   416  //
   417  // Zero Container has enabled hashing.
   418  func IsHomomorphicHashingDisabled(cnr Container) bool {
   419  	return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled
   420  }
   421  
   422  // Domain represents information about container domain registered in the NNS
   423  // contract deployed in the FrostFS network.
   424  type Domain struct {
   425  	name, zone string
   426  }
   427  
   428  // SetName sets human-friendly container domain name.
   429  func (x *Domain) SetName(name string) {
   430  	x.name = name
   431  }
   432  
   433  // Name returns name set using SetName.
   434  //
   435  // Zero Domain has zero name.
   436  func (x Domain) Name() string {
   437  	return x.name
   438  }
   439  
   440  // SetZone sets zone which is used as a TLD of a domain name in NNS contract.
   441  func (x *Domain) SetZone(zone string) {
   442  	x.zone = zone
   443  }
   444  
   445  // Zone returns domain zone set using SetZone.
   446  //
   447  // Zero Domain has "container" zone.
   448  func (x Domain) Zone() string {
   449  	if x.zone != "" {
   450  		return x.zone
   451  	}
   452  
   453  	return "container"
   454  }
   455  
   456  // WriteDomain writes Domain into the Container. Name MUST NOT be empty.
   457  func WriteDomain(cnr *Container, domain Domain) {
   458  	cnr.SetAttribute(container.SysAttributeName, domain.Name())
   459  	cnr.SetAttribute(container.SysAttributeZone, domain.Zone())
   460  }
   461  
   462  // ReadDomain reads Domain from the Container. Returns value with empty name
   463  // if domain is not specified.
   464  func ReadDomain(cnr Container) (res Domain) {
   465  	if name := cnr.Attribute(container.SysAttributeName); name != "" {
   466  		res.SetName(name)
   467  		res.SetZone(cnr.Attribute(container.SysAttributeZone))
   468  	}
   469  
   470  	return
   471  }
   472  
   473  // CalculateSignature calculates signature of the Container using provided signer
   474  // and writes it into dst. Signature instance MUST NOT be nil. CalculateSignature
   475  // is expected to be called after all the Container data is filled and before
   476  // saving the Container in the FrostFS network. Note that мany subsequent change
   477  // will most likely break the signature.
   478  //
   479  // See also VerifySignature.
   480  func CalculateSignature(dst *frostfscrypto.Signature, cnr Container, signer ecdsa.PrivateKey) error {
   481  	return dst.Calculate(frostfsecdsa.SignerRFC6979(signer), cnr.Marshal())
   482  }
   483  
   484  // VerifySignature verifies Container signature calculated using CalculateSignature.
   485  // Result means signature correctness.
   486  func VerifySignature(sig frostfscrypto.Signature, cnr Container) bool {
   487  	return sig.Verify(cnr.Marshal())
   488  }
   489  
   490  // CalculateIDFromBinary calculates identifier of the binary-encoded container
   491  // in CAS of the FrostFS containers and writes it into dst. ID instance MUST NOT
   492  // be nil.
   493  //
   494  // See also CalculateID, AssertID.
   495  func CalculateIDFromBinary(dst *cid.ID, cnr []byte) {
   496  	dst.SetSHA256(sha256.Sum256(cnr))
   497  }
   498  
   499  // CalculateID encodes the given Container and passes the result into
   500  // CalculateIDFromBinary.
   501  //
   502  // See also Container.Marshal, AssertID.
   503  func CalculateID(dst *cid.ID, cnr Container) {
   504  	CalculateIDFromBinary(dst, cnr.Marshal())
   505  }
   506  
   507  // AssertID checks if the given Container matches its identifier in CAS of the
   508  // FrostFS containers.
   509  //
   510  // See also CalculateID.
   511  func AssertID(id cid.ID, cnr Container) bool {
   512  	var id2 cid.ID
   513  	CalculateID(&id2, cnr)
   514  
   515  	return id2.Equals(id)
   516  }