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

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
    11  	v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
    12  	v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
    13  	rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
    14  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
    15  	v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
    16  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
    17  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
    18  	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
    19  	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
    20  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
    21  	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
    22  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
    23  )
    24  
    25  // PrmObjectGet groups parameters of ObjectGetInit operation.
    26  type PrmObjectGet struct {
    27  	XHeaders []string
    28  
    29  	BearerToken *bearer.Token
    30  
    31  	Session *session.Object
    32  
    33  	Raw bool
    34  
    35  	Local bool
    36  
    37  	ContainerID *cid.ID
    38  
    39  	ObjectID *oid.ID
    40  
    41  	Key *ecdsa.PrivateKey
    42  }
    43  
    44  func (prm *PrmObjectGet) buildRequest(c *Client) (*v2object.GetRequest, error) {
    45  	if prm.ContainerID == nil {
    46  		return nil, errorMissingContainer
    47  	}
    48  
    49  	if prm.ObjectID == nil {
    50  		return nil, errorMissingObject
    51  	}
    52  
    53  	if len(prm.XHeaders)%2 != 0 {
    54  		return nil, errorInvalidXHeaders
    55  	}
    56  
    57  	meta := new(v2session.RequestMetaHeader)
    58  	writeXHeadersToMeta(prm.XHeaders, meta)
    59  
    60  	if prm.BearerToken != nil {
    61  		v2BearerToken := new(acl.BearerToken)
    62  		prm.BearerToken.WriteToV2(v2BearerToken)
    63  		meta.SetBearerToken(v2BearerToken)
    64  	}
    65  
    66  	if prm.Session != nil {
    67  		v2SessionToken := new(v2session.Token)
    68  		prm.Session.WriteToV2(v2SessionToken)
    69  		meta.SetSessionToken(v2SessionToken)
    70  	}
    71  
    72  	if prm.Local {
    73  		meta.SetTTL(1)
    74  	}
    75  
    76  	addr := new(v2refs.Address)
    77  
    78  	cnrV2 := new(v2refs.ContainerID)
    79  	prm.ContainerID.WriteToV2(cnrV2)
    80  	addr.SetContainerID(cnrV2)
    81  
    82  	objV2 := new(v2refs.ObjectID)
    83  	prm.ObjectID.WriteToV2(objV2)
    84  	addr.SetObjectID(objV2)
    85  
    86  	body := new(v2object.GetRequestBody)
    87  	body.SetRaw(prm.Raw)
    88  	body.SetAddress(addr)
    89  
    90  	req := new(v2object.GetRequest)
    91  	req.SetBody(body)
    92  	c.prepareRequest(req, meta)
    93  
    94  	return req, nil
    95  }
    96  
    97  // ResObjectGet groups the final result values of ObjectGetInit operation.
    98  type ResObjectGet struct {
    99  	statusRes
   100  }
   101  
   102  // ObjectReader is designed to read one object from FrostFS system.
   103  //
   104  // Must be initialized using Client.ObjectGetInit, any other
   105  // usage is unsafe.
   106  type ObjectReader struct {
   107  	cancelCtxStream context.CancelFunc
   108  
   109  	client *Client
   110  	stream interface {
   111  		Read(resp *v2object.GetResponse) error
   112  	}
   113  
   114  	res ResObjectGet
   115  	err error
   116  
   117  	tailPayload []byte
   118  
   119  	remainingPayloadLen int
   120  }
   121  
   122  // UseKey specifies private key to sign the requests.
   123  // If key is not provided, then Client default key is used.
   124  //
   125  // Deprecated: Use PrmObjectGet.Key instead.
   126  func (prm *PrmObjectGet) UseKey(key ecdsa.PrivateKey) {
   127  	prm.Key = &key
   128  }
   129  
   130  // ReadHeader reads header of the object. Result means success.
   131  // Failure reason can be received via Close.
   132  func (x *ObjectReader) ReadHeader(dst *object.Object) bool {
   133  	var resp v2object.GetResponse
   134  	x.err = x.stream.Read(&resp)
   135  	if x.err != nil {
   136  		return false
   137  	}
   138  
   139  	x.res.st, x.err = x.client.processResponse(&resp)
   140  	if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
   141  		return false
   142  	}
   143  
   144  	var partInit *v2object.GetObjectPartInit
   145  
   146  	switch v := resp.GetBody().GetObjectPart().(type) {
   147  	default:
   148  		x.err = fmt.Errorf("unexpected message instead of heading part: %T", v)
   149  		return false
   150  	case *v2object.SplitInfo:
   151  		x.err = object.NewSplitInfoError(object.NewSplitInfoFromV2(v))
   152  		return false
   153  	case *v2object.ECInfo:
   154  		x.err = object.NewECInfoError(object.NewECInfoFromV2(v))
   155  		return false
   156  	case *v2object.GetObjectPartInit:
   157  		partInit = v
   158  	}
   159  
   160  	var objv2 v2object.Object
   161  
   162  	objv2.SetObjectID(partInit.GetObjectID())
   163  	objv2.SetHeader(partInit.GetHeader())
   164  	objv2.SetSignature(partInit.GetSignature())
   165  
   166  	x.remainingPayloadLen = int(objv2.GetHeader().GetPayloadLength())
   167  
   168  	*dst = *object.NewFromV2(&objv2) // need smth better
   169  
   170  	return true
   171  }
   172  
   173  func (x *ObjectReader) readChunk(buf []byte) (int, bool) {
   174  	var read int
   175  
   176  	// read remaining tail
   177  	read = copy(buf, x.tailPayload)
   178  
   179  	x.tailPayload = x.tailPayload[read:]
   180  
   181  	if len(buf) == read {
   182  		return read, true
   183  	}
   184  
   185  	var chunk []byte
   186  	var lastRead int
   187  
   188  	for {
   189  		var resp v2object.GetResponse
   190  		x.err = x.stream.Read(&resp)
   191  		if x.err != nil {
   192  			return read, false
   193  		}
   194  
   195  		x.res.st, x.err = x.client.processResponse(&resp)
   196  		if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
   197  			return read, false
   198  		}
   199  
   200  		part := resp.GetBody().GetObjectPart()
   201  		partChunk, ok := part.(*v2object.GetObjectPartChunk)
   202  		if !ok {
   203  			x.err = fmt.Errorf("unexpected message instead of chunk part: %T", part)
   204  			return read, false
   205  		}
   206  
   207  		// read new chunk
   208  		chunk = partChunk.GetChunk()
   209  		if len(chunk) == 0 {
   210  			// just skip empty chunks since they are not prohibited by protocol
   211  			continue
   212  		}
   213  
   214  		lastRead = copy(buf[read:], chunk)
   215  
   216  		read += lastRead
   217  
   218  		if read == len(buf) {
   219  			// save the tail
   220  			x.tailPayload = append(x.tailPayload, chunk[lastRead:]...)
   221  
   222  			return read, true
   223  		}
   224  	}
   225  }
   226  
   227  // ReadChunk reads another chunk of the object payload. Works similar to
   228  // io.Reader.Read but returns success flag instead of error.
   229  //
   230  // Failure reason can be received via Close.
   231  func (x *ObjectReader) ReadChunk(buf []byte) (int, bool) {
   232  	return x.readChunk(buf)
   233  }
   234  
   235  func (x *ObjectReader) close(ignoreEOF bool) (*ResObjectGet, error) {
   236  	defer x.cancelCtxStream()
   237  
   238  	if x.err != nil {
   239  		if !errors.Is(x.err, io.EOF) {
   240  			return nil, x.err
   241  		} else if !ignoreEOF {
   242  			if x.remainingPayloadLen > 0 {
   243  				return nil, io.ErrUnexpectedEOF
   244  			}
   245  
   246  			return nil, io.EOF
   247  		}
   248  	}
   249  
   250  	return &x.res, nil
   251  }
   252  
   253  // Close ends reading the object and returns the result of the operation
   254  // along with the final results. Must be called after using the ObjectReader.
   255  //
   256  // Exactly one return value is non-nil. By default, server status is returned in res structure.
   257  // Any client's internal or transport errors are returned as Go built-in error.
   258  // If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
   259  // codes are returned as error.
   260  //
   261  // Return errors:
   262  //
   263  //	*object.SplitInfoError (returned on virtual objects with PrmObjectGet.MakeRaw).
   264  //	*object.ECInfoError (returned on erasure-coded objects with PrmObjectGet.MakeRaw).
   265  //
   266  // Return statuses:
   267  //   - global (see Client docs);
   268  //   - *apistatus.ContainerNotFound;
   269  //   - *apistatus.ObjectNotFound;
   270  //   - *apistatus.ObjectAccessDenied;
   271  //   - *apistatus.ObjectAlreadyRemoved;
   272  //   - *apistatus.SessionTokenExpired.
   273  func (x *ObjectReader) Close() (*ResObjectGet, error) {
   274  	return x.close(true)
   275  }
   276  
   277  // Read implements io.Reader of the object payload.
   278  func (x *ObjectReader) Read(p []byte) (int, error) {
   279  	n, ok := x.readChunk(p)
   280  
   281  	x.remainingPayloadLen -= n
   282  
   283  	if !ok {
   284  		res, err := x.close(false)
   285  		if err != nil {
   286  			return n, err
   287  		}
   288  
   289  		return n, apistatus.ErrFromStatus(res.Status())
   290  	}
   291  
   292  	if x.remainingPayloadLen < 0 {
   293  		return n, errors.New("payload size overflow")
   294  	}
   295  
   296  	return n, nil
   297  }
   298  
   299  // ObjectGetInit initiates reading an object through a remote server using FrostFS API protocol.
   300  //
   301  // The call only opens the transmission channel, explicit fetching is done using the ObjectReader.
   302  // Exactly one return value is non-nil. Resulting reader must be finally closed.
   303  //
   304  // Returns an error if parameters are set incorrectly (see PrmObjectGet docs).
   305  // Context is required and must not be nil. It is used for network communication.
   306  func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectReader, error) {
   307  	req, err := prm.buildRequest(c)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	key := prm.Key
   313  	if key == nil {
   314  		key = &c.prm.Key
   315  	}
   316  
   317  	err = signature.SignServiceMessage(key, req)
   318  	if err != nil {
   319  		return nil, fmt.Errorf("sign request: %w", err)
   320  	}
   321  
   322  	ctx, cancel := context.WithCancel(ctx)
   323  
   324  	stream, err := rpcapi.GetObject(&c.c, req, client.WithContext(ctx))
   325  	if err != nil {
   326  		cancel()
   327  		return nil, fmt.Errorf("open stream: %w", err)
   328  	}
   329  
   330  	var r ObjectReader
   331  	r.cancelCtxStream = cancel
   332  	r.stream = stream
   333  	r.client = c
   334  
   335  	return &r, nil
   336  }
   337  
   338  // PrmObjectHead groups parameters of ObjectHead operation.
   339  type PrmObjectHead struct {
   340  	XHeaders []string
   341  
   342  	BearerToken *bearer.Token
   343  
   344  	Session *session.Object
   345  
   346  	Raw bool
   347  
   348  	Local bool
   349  
   350  	ContainerID *cid.ID
   351  
   352  	ObjectID *oid.ID
   353  
   354  	Key *ecdsa.PrivateKey
   355  }
   356  
   357  // UseKey specifies private key to sign the requests.
   358  // If key is not provided, then Client default key is used.
   359  //
   360  // Deprecated: Use PrmObjectHead.Key instead.
   361  func (prm *PrmObjectHead) UseKey(key ecdsa.PrivateKey) {
   362  	prm.Key = &key
   363  }
   364  
   365  // ResObjectHead groups resulting values of ObjectHead operation.
   366  type ResObjectHead struct {
   367  	statusRes
   368  
   369  	// requested object (response doesn't carry the ID)
   370  	idObj oid.ID
   371  
   372  	hdr *v2object.HeaderWithSignature
   373  }
   374  
   375  // ReadHeader reads header of the requested object.
   376  // Returns false if header is missing in the response (not read).
   377  func (x *ResObjectHead) ReadHeader(dst *object.Object) bool {
   378  	if x.hdr == nil {
   379  		return false
   380  	}
   381  
   382  	var objv2 v2object.Object
   383  
   384  	objv2.SetHeader(x.hdr.GetHeader())
   385  	objv2.SetSignature(x.hdr.GetSignature())
   386  
   387  	obj := object.NewFromV2(&objv2)
   388  	obj.SetID(x.idObj)
   389  
   390  	*dst = *obj
   391  
   392  	return true
   393  }
   394  
   395  func (prm *PrmObjectHead) buildRequest(c *Client) (*v2object.HeadRequest, error) {
   396  	if prm.ContainerID == nil {
   397  		return nil, errorMissingContainer
   398  	}
   399  
   400  	if prm.ObjectID == nil {
   401  		return nil, errorMissingObject
   402  	}
   403  
   404  	if len(prm.XHeaders)%2 != 0 {
   405  		return nil, errorInvalidXHeaders
   406  	}
   407  
   408  	meta := new(v2session.RequestMetaHeader)
   409  	writeXHeadersToMeta(prm.XHeaders, meta)
   410  
   411  	if prm.BearerToken != nil {
   412  		v2BearerToken := new(acl.BearerToken)
   413  		prm.BearerToken.WriteToV2(v2BearerToken)
   414  		meta.SetBearerToken(v2BearerToken)
   415  	}
   416  
   417  	if prm.Session != nil {
   418  		v2SessionToken := new(v2session.Token)
   419  		prm.Session.WriteToV2(v2SessionToken)
   420  		meta.SetSessionToken(v2SessionToken)
   421  	}
   422  
   423  	if prm.Local {
   424  		meta.SetTTL(1)
   425  	}
   426  
   427  	addr := new(v2refs.Address)
   428  
   429  	cnrV2 := new(v2refs.ContainerID)
   430  	prm.ContainerID.WriteToV2(cnrV2)
   431  	addr.SetContainerID(cnrV2)
   432  
   433  	objV2 := new(v2refs.ObjectID)
   434  	prm.ObjectID.WriteToV2(objV2)
   435  	addr.SetObjectID(objV2)
   436  	body := new(v2object.HeadRequestBody)
   437  	body.SetRaw(prm.Raw)
   438  	body.SetAddress(addr)
   439  
   440  	req := new(v2object.HeadRequest)
   441  	req.SetBody(body)
   442  	c.prepareRequest(req, meta)
   443  
   444  	return req, nil
   445  }
   446  
   447  // ObjectHead reads object header through a remote server using FrostFS API protocol.
   448  //
   449  // Exactly one return value is non-nil. By default, server status is returned in res structure.
   450  // Any client's internal or transport errors are returned as `error`,
   451  // If PrmInit.DisableFrostFSFailuresResolution has been called, unsuccessful
   452  // FrostFS status codes are included in the returned result structure,
   453  // otherwise, are also returned as `error`.
   454  //
   455  // Returns an error if parameters are set incorrectly (see PrmObjectHead docs).
   456  // Context is required and must not be nil. It is used for network communication.
   457  //
   458  // Return errors:
   459  //
   460  //	*object.SplitInfoError (returned on virtual objects with PrmObjectHead.MakeRaw).
   461  //	*object.ECInfoError (returned on erasure-coded objects with PrmObjectHead.MakeRaw).
   462  //
   463  // Return statuses:
   464  //   - global (see Client docs);
   465  //   - *apistatus.ContainerNotFound;
   466  //   - *apistatus.ObjectNotFound;
   467  //   - *apistatus.ObjectAccessDenied;
   468  //   - *apistatus.ObjectAlreadyRemoved;
   469  //   - *apistatus.SessionTokenExpired.
   470  func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) {
   471  	req, err := prm.buildRequest(c)
   472  	if err != nil {
   473  		return nil, err
   474  	}
   475  
   476  	key := c.prm.Key
   477  	if prm.Key != nil {
   478  		key = *prm.Key
   479  	}
   480  
   481  	// sign the request
   482  
   483  	err = signature.SignServiceMessage(&key, req)
   484  	if err != nil {
   485  		return nil, fmt.Errorf("sign request: %w", err)
   486  	}
   487  
   488  	resp, err := rpcapi.HeadObject(&c.c, req, client.WithContext(ctx))
   489  	if err != nil {
   490  		return nil, fmt.Errorf("write request: %w", err)
   491  	}
   492  
   493  	var res ResObjectHead
   494  	res.st, err = c.processResponse(resp)
   495  	if err != nil || !apistatus.IsSuccessful(res.st) {
   496  		return &res, err
   497  	}
   498  
   499  	res.idObj = *prm.ObjectID
   500  
   501  	switch v := resp.GetBody().GetHeaderPart().(type) {
   502  	default:
   503  		return nil, fmt.Errorf("unexpected header type %T", v)
   504  	case *v2object.SplitInfo:
   505  		return nil, object.NewSplitInfoError(object.NewSplitInfoFromV2(v))
   506  	case *v2object.ECInfo:
   507  		return nil, object.NewECInfoError(object.NewECInfoFromV2(v))
   508  	case *v2object.HeaderWithSignature:
   509  		res.hdr = v
   510  	}
   511  
   512  	return &res, nil
   513  }
   514  
   515  // PrmObjectRange groups parameters of ObjectRange operation.
   516  type PrmObjectRange struct {
   517  	XHeaders []string
   518  
   519  	BearerToken *bearer.Token
   520  
   521  	Session *session.Object
   522  
   523  	Raw bool
   524  
   525  	Local bool
   526  
   527  	ContainerID *cid.ID
   528  
   529  	ObjectID *oid.ID
   530  
   531  	Key *ecdsa.PrivateKey
   532  
   533  	Offset uint64
   534  
   535  	Length uint64
   536  }
   537  
   538  func (prm *PrmObjectRange) buildRequest(c *Client) (*v2object.GetRangeRequest, error) {
   539  	if prm.Length == 0 {
   540  		return nil, errorZeroRangeLength
   541  	}
   542  
   543  	if prm.ContainerID == nil {
   544  		return nil, errorMissingContainer
   545  	}
   546  
   547  	if prm.ObjectID == nil {
   548  		return nil, errorMissingObject
   549  	}
   550  
   551  	if len(prm.XHeaders)%2 != 0 {
   552  		return nil, errorInvalidXHeaders
   553  	}
   554  
   555  	meta := new(v2session.RequestMetaHeader)
   556  	writeXHeadersToMeta(prm.XHeaders, meta)
   557  
   558  	if prm.BearerToken != nil {
   559  		v2BearerToken := new(acl.BearerToken)
   560  		prm.BearerToken.WriteToV2(v2BearerToken)
   561  		meta.SetBearerToken(v2BearerToken)
   562  	}
   563  
   564  	if prm.Session != nil {
   565  		v2SessionToken := new(v2session.Token)
   566  		prm.Session.WriteToV2(v2SessionToken)
   567  		meta.SetSessionToken(v2SessionToken)
   568  	}
   569  
   570  	if prm.Local {
   571  		meta.SetTTL(1)
   572  	}
   573  
   574  	addr := new(v2refs.Address)
   575  
   576  	cnrV2 := new(v2refs.ContainerID)
   577  	prm.ContainerID.WriteToV2(cnrV2)
   578  	addr.SetContainerID(cnrV2)
   579  
   580  	objV2 := new(v2refs.ObjectID)
   581  	prm.ObjectID.WriteToV2(objV2)
   582  	addr.SetObjectID(objV2)
   583  
   584  	rng := new(v2object.Range)
   585  	rng.SetLength(prm.Length)
   586  	rng.SetOffset(prm.Offset)
   587  
   588  	body := new(v2object.GetRangeRequestBody)
   589  	body.SetRaw(prm.Raw)
   590  	body.SetAddress(addr)
   591  	body.SetRange(rng)
   592  
   593  	req := new(v2object.GetRangeRequest)
   594  	req.SetBody(body)
   595  	c.prepareRequest(req, meta)
   596  
   597  	return req, nil
   598  }
   599  
   600  // UseKey specifies private key to sign the requests.
   601  // If key is not provided, then Client default key is used.
   602  //
   603  // Deprecated: Use PrmObjectRange.Key instead.
   604  func (prm *PrmObjectRange) UseKey(key ecdsa.PrivateKey) {
   605  	prm.Key = &key
   606  }
   607  
   608  // ResObjectRange groups the final result values of ObjectRange operation.
   609  type ResObjectRange struct {
   610  	statusRes
   611  }
   612  
   613  // ObjectRangeReader is designed to read payload range of one object
   614  // from FrostFS system.
   615  //
   616  // Must be initialized using Client.ObjectRangeInit, any other
   617  // usage is unsafe.
   618  type ObjectRangeReader struct {
   619  	cancelCtxStream context.CancelFunc
   620  
   621  	client *Client
   622  
   623  	res ResObjectRange
   624  	err error
   625  
   626  	stream interface {
   627  		Read(resp *v2object.GetRangeResponse) error
   628  	}
   629  
   630  	tailPayload []byte
   631  
   632  	remainingPayloadLen int
   633  }
   634  
   635  func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) {
   636  	var read int
   637  
   638  	// read remaining tail
   639  	read = copy(buf, x.tailPayload)
   640  
   641  	x.tailPayload = x.tailPayload[read:]
   642  
   643  	if len(buf) == read {
   644  		return read, true
   645  	}
   646  
   647  	var partChunk *v2object.GetRangePartChunk
   648  	var chunk []byte
   649  	var lastRead int
   650  
   651  	for {
   652  		var resp v2object.GetRangeResponse
   653  		x.err = x.stream.Read(&resp)
   654  		if x.err != nil {
   655  			return read, false
   656  		}
   657  
   658  		x.res.st, x.err = x.client.processResponse(&resp)
   659  		if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
   660  			return read, false
   661  		}
   662  
   663  		// get chunk message
   664  		switch v := resp.GetBody().GetRangePart().(type) {
   665  		default:
   666  			x.err = fmt.Errorf("unexpected message received: %T", v)
   667  			return read, false
   668  		case *v2object.SplitInfo:
   669  			x.err = object.NewSplitInfoError(object.NewSplitInfoFromV2(v))
   670  			return read, false
   671  		case *v2object.ECInfo:
   672  			x.err = object.NewECInfoError(object.NewECInfoFromV2(v))
   673  			return read, false
   674  		case *v2object.GetRangePartChunk:
   675  			partChunk = v
   676  		}
   677  
   678  		chunk = partChunk.GetChunk()
   679  		if len(chunk) == 0 {
   680  			// just skip empty chunks since they are not prohibited by protocol
   681  			continue
   682  		}
   683  
   684  		lastRead = copy(buf[read:], chunk)
   685  
   686  		read += lastRead
   687  
   688  		if read == len(buf) {
   689  			// save the tail
   690  			x.tailPayload = append(x.tailPayload, chunk[lastRead:]...)
   691  
   692  			return read, true
   693  		}
   694  	}
   695  }
   696  
   697  // ReadChunk reads another chunk of the object payload range.
   698  // Works similar to io.Reader.Read but returns success flag instead of error.
   699  //
   700  // Failure reason can be received via Close.
   701  func (x *ObjectRangeReader) ReadChunk(buf []byte) (int, bool) {
   702  	return x.readChunk(buf)
   703  }
   704  
   705  func (x *ObjectRangeReader) close(ignoreEOF bool) (*ResObjectRange, error) {
   706  	defer x.cancelCtxStream()
   707  
   708  	if x.err != nil {
   709  		if !errors.Is(x.err, io.EOF) {
   710  			return nil, x.err
   711  		} else if !ignoreEOF {
   712  			if x.remainingPayloadLen > 0 {
   713  				return nil, io.ErrUnexpectedEOF
   714  			}
   715  
   716  			return nil, io.EOF
   717  		}
   718  	}
   719  
   720  	return &x.res, nil
   721  }
   722  
   723  // Close ends reading the payload range and returns the result of the operation
   724  // along with the final results. Must be called after using the ObjectRangeReader.
   725  //
   726  // Exactly one return value is non-nil. By default, server status is returned in res structure.
   727  // Any client's internal or transport errors are returned as Go built-in error.
   728  // If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
   729  // codes are returned as error.
   730  //
   731  // Return errors:
   732  //
   733  //	*object.SplitInfoError (returned on virtual objects with PrmObjectRange.MakeRaw).
   734  //	*object.ECInfoError (returned on erasure-coded objects with PrmObjectRange.MakeRaw).
   735  //
   736  // Return statuses:
   737  //   - global (see Client docs);
   738  //   - *apistatus.ContainerNotFound;
   739  //   - *apistatus.ObjectNotFound;
   740  //   - *apistatus.ObjectAccessDenied;
   741  //   - *apistatus.ObjectAlreadyRemoved;
   742  //   - *apistatus.ObjectOutOfRange;
   743  //   - *apistatus.SessionTokenExpired.
   744  func (x *ObjectRangeReader) Close() (*ResObjectRange, error) {
   745  	return x.close(true)
   746  }
   747  
   748  // Read implements io.Reader of the object payload.
   749  func (x *ObjectRangeReader) Read(p []byte) (int, error) {
   750  	n, ok := x.readChunk(p)
   751  
   752  	x.remainingPayloadLen -= n
   753  
   754  	if !ok {
   755  		res, err := x.close(false)
   756  		if err != nil {
   757  			return n, err
   758  		}
   759  
   760  		return n, apistatus.ErrFromStatus(res.Status())
   761  	}
   762  
   763  	if x.remainingPayloadLen < 0 {
   764  		return n, errors.New("payload range size overflow")
   765  	}
   766  
   767  	return n, nil
   768  }
   769  
   770  // ObjectRangeInit initiates reading an object's payload range through a remote
   771  // server using FrostFS API protocol.
   772  //
   773  // The call only opens the transmission channel, explicit fetching is done using the ObjectRangeReader.
   774  // Exactly one return value is non-nil. Resulting reader must be finally closed.
   775  //
   776  // Returns an error if parameters are set incorrectly (see PrmObjectRange docs).
   777  // Context is required and must not be nil. It is used for network communication.
   778  func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*ObjectRangeReader, error) {
   779  	req, err := prm.buildRequest(c)
   780  	if err != nil {
   781  		return nil, err
   782  	}
   783  
   784  	key := prm.Key
   785  	if key == nil {
   786  		key = &c.prm.Key
   787  	}
   788  
   789  	err = signature.SignServiceMessage(key, req)
   790  	if err != nil {
   791  		return nil, fmt.Errorf("sign request: %w", err)
   792  	}
   793  
   794  	ctx, cancel := context.WithCancel(ctx)
   795  
   796  	stream, err := rpcapi.GetObjectRange(&c.c, req, client.WithContext(ctx))
   797  	if err != nil {
   798  		cancel()
   799  		return nil, fmt.Errorf("open stream: %w", err)
   800  	}
   801  
   802  	var r ObjectRangeReader
   803  	r.remainingPayloadLen = int(prm.Length)
   804  	r.cancelCtxStream = cancel
   805  	r.stream = stream
   806  	r.client = c
   807  
   808  	return &r, nil
   809  }