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 }