2020-10-16 09:51:43 +00:00
package neofs
import (
2023-06-06 09:19:23 +00:00
"bytes"
2020-10-16 09:51:43 +00:00
"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
)
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-06-06 09:19:23 +00:00
func Get ( ctx context . Context , priv * keys . PrivateKey , u * url . URL , addr string ) ( io . ReadCloser , error ) {
2020-10-16 09:51:43 +00:00
objectAddr , ps , err := parseNeoFSURL ( u )
if err != nil {
return nil , err
}
2023-06-06 08:22:08 +00:00
var (
prmi client . PrmInit
c * client . Client
)
2023-04-27 15:38:06 +00:00
prmi . SetDefaultSigner ( neofsecdsa . Signer ( priv . PrivateKey ) )
2023-06-06 08:22:08 +00:00
c , err = client . New ( prmi )
if err != nil {
return nil , fmt . Errorf ( "failed to create client: %w" , err )
}
2023-04-27 15:38:06 +00:00
2023-06-06 09:19:23 +00:00
var (
res = clientCloseWrapper { c : c }
prmd client . PrmDial
)
2023-04-27 15:38:06 +00:00
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 {
2023-06-06 09:19:23 +00:00
return res , err
2020-10-16 09:51:43 +00:00
}
switch {
case len ( ps ) == 0 || ps [ 0 ] == "" : // Get request
2023-06-06 09:19:23 +00:00
res . ReadCloser , err = getPayload ( ctx , c , objectAddr )
2020-10-16 09:51:43 +00:00
case ps [ 0 ] == rangeCmd :
2023-06-06 09:19:23 +00:00
res . ReadCloser , err = getRange ( ctx , c , objectAddr , ps [ 1 : ] ... )
2020-10-16 09:51:43 +00:00
case ps [ 0 ] == headerCmd :
2023-06-06 09:19:23 +00:00
res . ReadCloser , err = getHeader ( ctx , c , objectAddr )
2020-10-16 09:51:43 +00:00
case ps [ 0 ] == hashCmd :
2023-06-06 09:19:23 +00:00
res . ReadCloser , err = getHash ( ctx , c , objectAddr , ps [ 1 : ] ... )
2020-10-16 09:51:43 +00:00
default :
2023-06-06 09:19:23 +00:00
err = ErrInvalidCommand
}
return res , err
}
type clientCloseWrapper struct {
io . ReadCloser
c * client . Client
}
func ( w clientCloseWrapper ) Close ( ) error {
var res error
if w . ReadCloser != nil {
res = w . ReadCloser . Close ( )
2020-10-16 09:51:43 +00:00
}
2023-06-06 09:19:23 +00:00
w . c . Close ( )
return res
2020-10-16 09:51:43 +00:00
}
// 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-06-06 09:19:23 +00:00
func getPayload ( ctx context . Context , c * client . Client , addr * oid . Address ) ( io . ReadCloser , error ) {
return c . ObjectGetInit ( ctx , addr . Container ( ) , addr . Object ( ) , client . PrmObjectGet { } )
2020-10-16 09:51:43 +00:00
}
2023-06-06 09:19:23 +00:00
func getRange ( ctx context . Context , c * client . Client , addr * oid . Address , ps ... string ) ( io . ReadCloser , 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
2023-06-06 09:19:23 +00:00
return c . ObjectRangeInit ( ctx , addr . Container ( ) , addr . Object ( ) , r . GetOffset ( ) , r . GetLength ( ) , client . PrmObjectRange { } )
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 ) {
2023-06-06 08:22:08 +00:00
res , err := c . ObjectHead ( ctx , addr . Container ( ) , addr . Object ( ) , client . PrmObjectHead { } )
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-06-06 09:19:23 +00:00
func getHeader ( ctx context . Context , c * client . Client , addr * oid . Address ) ( io . ReadCloser , error ) {
2023-04-27 15:38:06 +00:00
obj , err := getObjHeader ( ctx , c , addr )
if err != nil {
return nil , err
}
2023-06-06 09:19:23 +00:00
res , err := obj . MarshalHeaderJSON ( )
if err != nil {
return nil , err
}
return io . NopCloser ( bytes . NewReader ( res ) ) , nil
2023-04-27 15:38:06 +00:00
}
2023-06-06 09:19:23 +00:00
func getHash ( ctx context . Context , c * client . Client , addr * oid . Address , ps ... string ) ( io . ReadCloser , 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" )
}
2023-06-06 09:19:23 +00:00
return io . NopCloser ( bytes . NewReader ( 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 . SetRangeList ( r . GetOffset ( ) , r . GetLength ( ) )
2023-06-06 08:22:08 +00:00
hashes , err := c . ObjectHash ( ctx , addr . Container ( ) , addr . Object ( ) , 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
}
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 )
}
2023-06-06 09:19:23 +00:00
res , err := u256 . MarshalJSON ( )
if err != nil {
return nil , err
}
return io . NopCloser ( bytes . NewReader ( res ) ) , nil
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
}