2020-10-16 09:51:43 +00:00
package neofs
import (
"context"
"errors"
"fmt"
2023-04-27 15:38:06 +00:00
"io"
2020-10-16 09:51:43 +00:00
"net/url"
"strconv"
"strings"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
2021-03-12 11:19:04 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2021-11-23 16:36:59 +00:00
"github.com/nspcc-dev/neofs-sdk-go/client"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
2023-04-27 15:38:06 +00:00
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
2021-11-23 16:36:59 +00:00
"github.com/nspcc-dev/neofs-sdk-go/object"
2023-04-27 15:38:06 +00:00
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
2020-10-16 09:51:43 +00:00
)
2023-04-27 15:38:06 +00:00
// ResultReader is a function that reads required amount of data and
// checks it.
type ResultReader func ( io . Reader ) ( [ ] byte , error )
2020-10-16 09:51:43 +00:00
const (
// URIScheme is the name of neofs URI scheme.
URIScheme = "neofs"
// rangeSep is a separator between offset and length.
rangeSep = '|'
rangeCmd = "range"
headerCmd = "header"
hashCmd = "hash"
)
// Various validation errors.
var (
ErrInvalidScheme = errors . New ( "invalid URI scheme" )
ErrMissingObject = errors . New ( "object ID is missing from URI" )
ErrInvalidContainer = errors . New ( "container ID is invalid" )
ErrInvalidObject = errors . New ( "object ID is invalid" )
ErrInvalidRange = errors . New ( "object range is invalid (expected 'Offset|Length')" )
ErrInvalidCommand = errors . New ( "invalid command" )
)
2022-04-20 18:30:09 +00:00
// Get returns a neofs object from the provided url.
2021-04-06 13:56:19 +00:00
// URI scheme is "neofs:<Container-ID>/<Object-ID/<Command>/<Params>".
2020-10-16 09:51:43 +00:00
// If Command is not provided, full object is requested.
2023-04-27 15:38:06 +00:00
func Get ( ctx context . Context , priv * keys . PrivateKey , u * url . URL , addr string , resReader ResultReader ) ( [ ] byte , error ) {
2020-10-16 09:51:43 +00:00
objectAddr , ps , err := parseNeoFSURL ( u )
if err != nil {
return nil , err
}
2023-04-27 15:38:06 +00:00
var c = new ( client . Client )
var prmi client . PrmInit
prmi . ResolveNeoFSFailures ( )
prmi . SetDefaultSigner ( neofsecdsa . Signer ( priv . PrivateKey ) )
c . Init ( prmi )
var prmd client . PrmDial
prmd . SetServerURI ( addr )
prmd . SetContext ( ctx )
err = c . Dial ( prmd ) //nolint:contextcheck // contextcheck: Function `Dial->Balance->SendUnary->Init->setNeoFSAPIServer` should pass the context parameter
2020-10-16 09:51:43 +00:00
if err != nil {
return nil , err
}
2023-04-27 15:38:06 +00:00
defer c . Close ( )
2020-10-16 09:51:43 +00:00
switch {
case len ( ps ) == 0 || ps [ 0 ] == "" : // Get request
2023-04-27 15:38:06 +00:00
return getPayload ( ctx , c , objectAddr , resReader )
2020-10-16 09:51:43 +00:00
case ps [ 0 ] == rangeCmd :
2023-04-27 15:38:06 +00:00
return getRange ( ctx , c , objectAddr , resReader , ps [ 1 : ] ... )
2020-10-16 09:51:43 +00:00
case ps [ 0 ] == headerCmd :
return getHeader ( ctx , c , objectAddr )
case ps [ 0 ] == hashCmd :
return getHash ( ctx , c , objectAddr , ps [ 1 : ] ... )
default :
return nil , ErrInvalidCommand
}
}
// parseNeoFSURL returns parsed neofs address.
2023-04-27 15:38:06 +00:00
func parseNeoFSURL ( u * url . URL ) ( * oid . Address , [ ] string , error ) {
2020-10-16 09:51:43 +00:00
if u . Scheme != URIScheme {
return nil , nil , ErrInvalidScheme
}
2021-04-06 13:56:19 +00:00
ps := strings . Split ( u . Opaque , "/" )
if len ( ps ) < 2 {
2020-10-16 09:51:43 +00:00
return nil , nil , ErrMissingObject
}
2023-04-27 15:38:06 +00:00
var containerID cid . ID
if err := containerID . DecodeString ( ps [ 0 ] ) ; err != nil {
2023-03-15 12:47:38 +00:00
return nil , nil , fmt . Errorf ( "%w: %v" , ErrInvalidContainer , err ) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
var objectID oid . ID
if err := objectID . DecodeString ( ps [ 1 ] ) ; err != nil {
2023-03-15 12:47:38 +00:00
return nil , nil , fmt . Errorf ( "%w: %v" , ErrInvalidObject , err ) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
var objAddr = new ( oid . Address )
objAddr . SetContainer ( containerID )
objAddr . SetObject ( objectID )
return objAddr , ps [ 2 : ] , nil
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
func getPayload ( ctx context . Context , c * client . Client , addr * oid . Address , resReader ResultReader ) ( [ ] byte , error ) {
var getPrm client . PrmObjectGet
getPrm . FromContainer ( addr . Container ( ) )
getPrm . ByID ( addr . Object ( ) )
objR , err := c . ObjectGetInit ( ctx , getPrm )
if err != nil {
return nil , err
}
resp , err := resReader ( objR )
if err != nil {
return nil , err
}
_ , err = objR . Close ( ) // Using ResolveNeoFSFailures.
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
return resp , nil
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
func getRange ( ctx context . Context , c * client . Client , addr * oid . Address , resReader ResultReader , ps ... string ) ( [ ] byte , error ) {
2020-10-16 09:51:43 +00:00
if len ( ps ) == 0 {
return nil , ErrInvalidRange
}
r , err := parseRange ( ps [ 0 ] )
if err != nil {
return nil , err
}
2023-04-27 15:38:06 +00:00
var rangePrm client . PrmObjectRange
rangePrm . FromContainer ( addr . Container ( ) )
rangePrm . ByID ( addr . Object ( ) )
rangePrm . SetLength ( r . GetLength ( ) )
rangePrm . SetOffset ( r . GetOffset ( ) )
rangeR , err := c . ObjectRangeInit ( ctx , rangePrm )
if err != nil {
return nil , err
}
resp , err := resReader ( rangeR )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2021-03-12 11:19:04 +00:00
}
2023-04-27 15:38:06 +00:00
_ , err = rangeR . Close ( ) // Using ResolveNeoFSFailures.
if err != nil {
return nil , err
}
return resp , nil
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
func getObjHeader ( ctx context . Context , c * client . Client , addr * oid . Address ) ( * object . Object , error ) {
var headPrm client . PrmObjectHead
headPrm . FromContainer ( addr . Container ( ) )
headPrm . ByID ( addr . Object ( ) )
res , err := c . ObjectHead ( ctx , headPrm )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
var obj = object . New ( )
if ! res . ReadHeader ( obj ) {
return nil , errors . New ( "missing header in the reply" )
}
return obj , nil
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
func getHeader ( ctx context . Context , c * client . Client , addr * oid . Address ) ( [ ] byte , error ) {
obj , err := getObjHeader ( ctx , c , addr )
if err != nil {
return nil , err
}
return obj . MarshalHeaderJSON ( )
}
func getHash ( ctx context . Context , c * client . Client , addr * oid . Address , ps ... string ) ( [ ] byte , error ) {
2020-10-16 09:51:43 +00:00
if len ( ps ) == 0 || ps [ 0 ] == "" { // hash of the full payload
2023-04-27 15:38:06 +00:00
obj , err := getObjHeader ( ctx , c , addr )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
sum , flag := obj . PayloadChecksum ( )
if ! flag {
return nil , errors . New ( "missing checksum in the reply" )
}
return sum . Value ( ) , nil
2020-10-16 09:51:43 +00:00
}
r , err := parseRange ( ps [ 0 ] )
if err != nil {
return nil , err
}
2023-04-27 15:38:06 +00:00
var hashPrm client . PrmObjectHash
hashPrm . FromContainer ( addr . Container ( ) )
hashPrm . ByID ( addr . Object ( ) )
hashPrm . SetRangeList ( r . GetOffset ( ) , r . GetLength ( ) )
res , err := c . ObjectHash ( ctx , hashPrm )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2023-04-27 15:38:06 +00:00
hashes := res . Checksums ( ) // Using ResolveNeoFSFailures.
2020-10-16 09:51:43 +00:00
if len ( hashes ) == 0 {
return nil , fmt . Errorf ( "%w: empty response" , ErrInvalidRange )
}
2021-11-23 16:36:59 +00:00
u256 , err := util . Uint256DecodeBytesBE ( hashes [ 0 ] )
if err != nil {
return nil , fmt . Errorf ( "decode Uint256: %w" , err )
}
return u256 . MarshalJSON ( )
2020-10-16 09:51:43 +00:00
}
func parseRange ( s string ) ( * object . Range , error ) {
sepIndex := strings . IndexByte ( s , rangeSep )
if sepIndex < 0 {
return nil , ErrInvalidRange
}
offset , err := strconv . ParseUint ( s [ : sepIndex ] , 10 , 64 )
if err != nil {
return nil , fmt . Errorf ( "%w: invalid offset" , ErrInvalidRange )
}
length , err := strconv . ParseUint ( s [ sepIndex + 1 : ] , 10 , 64 )
if err != nil {
return nil , fmt . Errorf ( "%w: invalid length" , ErrInvalidRange )
}
r := object . NewRange ( )
r . SetOffset ( offset )
r . SetLength ( length )
return r , nil
}