github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/oracle/neofs/neofs.go (about) 1 package neofs 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "net/url" 10 "strconv" 11 "strings" 12 13 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 14 "github.com/nspcc-dev/neo-go/pkg/util" 15 "github.com/nspcc-dev/neofs-sdk-go/client" 16 cid "github.com/nspcc-dev/neofs-sdk-go/container/id" 17 "github.com/nspcc-dev/neofs-sdk-go/object" 18 oid "github.com/nspcc-dev/neofs-sdk-go/object/id" 19 "github.com/nspcc-dev/neofs-sdk-go/user" 20 ) 21 22 const ( 23 // URIScheme is the name of neofs URI scheme. 24 URIScheme = "neofs" 25 26 // rangeSep is a separator between offset and length. 27 rangeSep = '|' 28 29 rangeCmd = "range" 30 headerCmd = "header" 31 hashCmd = "hash" 32 ) 33 34 // Various validation errors. 35 var ( 36 ErrInvalidScheme = errors.New("invalid URI scheme") 37 ErrMissingObject = errors.New("object ID is missing from URI") 38 ErrInvalidContainer = errors.New("container ID is invalid") 39 ErrInvalidObject = errors.New("object ID is invalid") 40 ErrInvalidRange = errors.New("object range is invalid (expected 'Offset|Length')") 41 ErrInvalidCommand = errors.New("invalid command") 42 ) 43 44 // Get returns a neofs object from the provided url. 45 // URI scheme is "neofs:<Container-ID>/<Object-ID/<Command>/<Params>". 46 // If Command is not provided, full object is requested. 47 func Get(ctx context.Context, priv *keys.PrivateKey, u *url.URL, addr string) (io.ReadCloser, error) { 48 objectAddr, ps, err := parseNeoFSURL(u) 49 if err != nil { 50 return nil, err 51 } 52 53 c, err := client.New(client.PrmInit{}) 54 if err != nil { 55 return nil, fmt.Errorf("failed to create client: %w", err) 56 } 57 58 var ( 59 res = clientCloseWrapper{c: c} 60 prmd client.PrmDial 61 ) 62 prmd.SetServerURI(addr) 63 prmd.SetContext(ctx) 64 err = c.Dial(prmd) //nolint:contextcheck // contextcheck: Function `Dial->Balance->SendUnary->Init->setNeoFSAPIServer` should pass the context parameter 65 if err != nil { 66 return res, err 67 } 68 69 var s = user.NewAutoIDSignerRFC6979(priv.PrivateKey) 70 switch { 71 case len(ps) == 0 || ps[0] == "": // Get request 72 res.ReadCloser, err = getPayload(ctx, s, c, objectAddr) 73 case ps[0] == rangeCmd: 74 res.ReadCloser, err = getRange(ctx, s, c, objectAddr, ps[1:]...) 75 case ps[0] == headerCmd: 76 res.ReadCloser, err = getHeader(ctx, s, c, objectAddr) 77 case ps[0] == hashCmd: 78 res.ReadCloser, err = getHash(ctx, s, c, objectAddr, ps[1:]...) 79 default: 80 err = ErrInvalidCommand 81 } 82 return res, err 83 } 84 85 type clientCloseWrapper struct { 86 io.ReadCloser 87 c *client.Client 88 } 89 90 func (w clientCloseWrapper) Close() error { 91 var res error 92 if w.ReadCloser != nil { 93 res = w.ReadCloser.Close() 94 } 95 w.c.Close() 96 return res 97 } 98 99 // parseNeoFSURL returns parsed neofs address. 100 func parseNeoFSURL(u *url.URL) (*oid.Address, []string, error) { 101 if u.Scheme != URIScheme { 102 return nil, nil, ErrInvalidScheme 103 } 104 105 ps := strings.Split(u.Opaque, "/") 106 if len(ps) < 2 { 107 return nil, nil, ErrMissingObject 108 } 109 110 var containerID cid.ID 111 if err := containerID.DecodeString(ps[0]); err != nil { 112 return nil, nil, fmt.Errorf("%w: %w", ErrInvalidContainer, err) 113 } 114 115 var objectID oid.ID 116 if err := objectID.DecodeString(ps[1]); err != nil { 117 return nil, nil, fmt.Errorf("%w: %w", ErrInvalidObject, err) 118 } 119 var objAddr = new(oid.Address) 120 objAddr.SetContainer(containerID) 121 objAddr.SetObject(objectID) 122 return objAddr, ps[2:], nil 123 } 124 125 func getPayload(ctx context.Context, s user.Signer, c *client.Client, addr *oid.Address) (io.ReadCloser, error) { 126 var iorc io.ReadCloser 127 _, rc, err := c.ObjectGetInit(ctx, addr.Container(), addr.Object(), s, client.PrmObjectGet{}) 128 if rc != nil { 129 iorc = rc 130 } 131 return iorc, err 132 } 133 134 func getRange(ctx context.Context, s user.Signer, c *client.Client, addr *oid.Address, ps ...string) (io.ReadCloser, error) { 135 var iorc io.ReadCloser 136 if len(ps) == 0 { 137 return nil, ErrInvalidRange 138 } 139 r, err := parseRange(ps[0]) 140 if err != nil { 141 return nil, err 142 } 143 144 rc, err := c.ObjectRangeInit(ctx, addr.Container(), addr.Object(), r.GetOffset(), r.GetLength(), s, client.PrmObjectRange{}) 145 if rc != nil { 146 iorc = rc 147 } 148 return iorc, err 149 } 150 151 func getObjHeader(ctx context.Context, s user.Signer, c *client.Client, addr *oid.Address) (*object.Object, error) { 152 return c.ObjectHead(ctx, addr.Container(), addr.Object(), s, client.PrmObjectHead{}) 153 } 154 155 func getHeader(ctx context.Context, s user.Signer, c *client.Client, addr *oid.Address) (io.ReadCloser, error) { 156 obj, err := getObjHeader(ctx, s, c, addr) 157 if err != nil { 158 return nil, err 159 } 160 res, err := obj.MarshalHeaderJSON() 161 if err != nil { 162 return nil, err 163 } 164 return io.NopCloser(bytes.NewReader(res)), nil 165 } 166 167 func getHash(ctx context.Context, s user.Signer, c *client.Client, addr *oid.Address, ps ...string) (io.ReadCloser, error) { 168 if len(ps) == 0 || ps[0] == "" { // hash of the full payload 169 obj, err := getObjHeader(ctx, s, c, addr) 170 if err != nil { 171 return nil, err 172 } 173 sum, flag := obj.PayloadChecksum() 174 if !flag { 175 return nil, errors.New("missing checksum in the reply") 176 } 177 return io.NopCloser(bytes.NewReader(sum.Value())), nil 178 } 179 r, err := parseRange(ps[0]) 180 if err != nil { 181 return nil, err 182 } 183 var hashPrm client.PrmObjectHash 184 hashPrm.SetRangeList(r.GetOffset(), r.GetLength()) 185 186 hashes, err := c.ObjectHash(ctx, addr.Container(), addr.Object(), s, hashPrm) 187 if err != nil { 188 return nil, err 189 } 190 if len(hashes) == 0 { 191 return nil, fmt.Errorf("%w: empty response", ErrInvalidRange) 192 } 193 u256, err := util.Uint256DecodeBytesBE(hashes[0]) 194 if err != nil { 195 return nil, fmt.Errorf("decode Uint256: %w", err) 196 } 197 res, err := u256.MarshalJSON() 198 if err != nil { 199 return nil, err 200 } 201 return io.NopCloser(bytes.NewReader(res)), nil 202 } 203 204 func parseRange(s string) (*object.Range, error) { 205 sepIndex := strings.IndexByte(s, rangeSep) 206 if sepIndex < 0 { 207 return nil, ErrInvalidRange 208 } 209 offset, err := strconv.ParseUint(s[:sepIndex], 10, 64) 210 if err != nil { 211 return nil, fmt.Errorf("%w: invalid offset", ErrInvalidRange) 212 } 213 length, err := strconv.ParseUint(s[sepIndex+1:], 10, 64) 214 if err != nil { 215 return nil, fmt.Errorf("%w: invalid length", ErrInvalidRange) 216 } 217 218 r := object.NewRange() 219 r.SetOffset(offset) 220 r.SetLength(length) 221 return r, nil 222 }