service: support broken apart signable payload of the requests

In previous implementation service package provided types and functions
that wrapped signing/verification of data with session token.
This allowed us to use these functions for signing / verification of
service requests of other packages. To support the expansion of messages
with additional parts that need to be signed, you must be able to easily
expand the signed data with new parts.

To achieve the described goal, this commit makes the following changes:

  * adds GroupSignedPayloads and GroupVerifyPayloads functions;

  * renames SignedDataWithToken to RequestData, DataWithTokenSignAccumulator
    to RequestSignedData, DataWithTokenSignSource to RequestVerifyData;

  * renames SignDataWithSessionToken/VerifyAccumulatedSignaturesWithToken
    function to SignRequestData/VerifyRequestData and makes it to use
    GroupSignedPayloads/GroupVerifyPayloads internally.
This commit is contained in:
Leonard Lyubich 2020-06-10 20:22:34 +03:00
parent 8dbd65132d
commit 74e917810a
12 changed files with 260 additions and 103 deletions

View file

@ -13,7 +13,7 @@ func TestSignBalanceRequest(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -159,26 +159,26 @@ func TestSignBalanceRequest(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(token) v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
corruption(v) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -56,26 +56,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(token) v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
corruption(v) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -117,26 +117,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(token) v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
corruption(v) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

View file

@ -13,7 +13,7 @@ func TestSignVerifyRequests(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*Token) SetToken(*Token)
@ -164,26 +164,26 @@ func TestSignVerifyRequests(t *testing.T) {
token := new(Token) token := new(Token)
v.SetToken(token) v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
corruption(v) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

View file

@ -36,14 +36,18 @@ const ErrEmptyDataWithSignature = internal.Error("empty data with signature")
// negative length for slice allocation. // negative length for slice allocation.
const ErrNegativeLength = internal.Error("negative slice length") const ErrNegativeLength = internal.Error("negative slice length")
// ErrNilDataWithTokenSignAccumulator is returned by functions that expect // ErrNilRequestSignedData is returned by functions that expect
// a non-nil DataWithTokenSignAccumulator, but received nil. // a non-nil RequestSignedData, but received nil.
const ErrNilDataWithTokenSignAccumulator = internal.Error("signed data with token is nil") const ErrNilRequestSignedData = internal.Error("request signed data is nil")
// ErrNilSignatureKeySourceWithToken is returned by functions that expect // ErrNilRequestVerifyData is returned by functions that expect
// a non-nil SignatureKeySourceWithToken, but received nil. // a non-nil RequestVerifyData, but received nil.
const ErrNilSignatureKeySourceWithToken = internal.Error("key-signature source with token is nil") const ErrNilRequestVerifyData = internal.Error("request verification data is nil")
// ErrNilSignedDataReader is returned by functions that expect // ErrNilSignedDataReader is returned by functions that expect
// a non-nil SignedDataReader, but received nil. // a non-nil SignedDataReader, but received nil.
const ErrNilSignedDataReader = internal.Error("signed data reader is nil") const ErrNilSignedDataReader = internal.Error("signed data reader is nil")
// ErrNilSignKeyPairAccumulator is returned by functions that expect
// a non-nil SignKeyPairAccumulator, but received nil.
const ErrNilSignKeyPairAccumulator = internal.Error("signature-key pair accumulator is nil")

View file

@ -2,9 +2,11 @@ package service
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"io"
"sync" "sync"
crypto "github.com/nspcc-dev/neofs-crypto" crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/pkg/errors"
) )
type keySign struct { type keySign struct {
@ -12,6 +14,20 @@ type keySign struct {
sign []byte sign []byte
} }
type signSourceGroup struct {
SignKeyPairSource
SignKeyPairAccumulator
sources []SignedDataSource
}
type signReadersGroup struct {
SignKeyPairSource
SignKeyPairAccumulator
readers []SignedDataReader
}
var bytesPool = sync.Pool{ var bytesPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return make([]byte, 5<<20) return make([]byte, 5<<20)
@ -176,54 +192,191 @@ func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error {
) )
} }
// SignDataWithSessionToken calculates data with token signature and adds it to accumulator. // SignRequestData calculates request data signature and adds it to accumulator.
// //
// Any change of data or session token info provoke signature breakdown. // Any change of request data provoke signature breakdown.
// //
// If passed private key is nil, crypto.ErrEmptyPrivateKey returns. // If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
// If passed DataWithTokenSignAccumulator is nil, ErrNilDataWithTokenSignAccumulator returns. // If passed RequestSignedData is nil, ErrNilRequestSignedData returns.
func SignDataWithSessionToken(key *ecdsa.PrivateKey, src DataWithTokenSignAccumulator) error { func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error {
if src == nil { if src == nil {
return ErrNilDataWithTokenSignAccumulator return ErrNilRequestSignedData
} else if r, ok := src.(SignedDataReader); ok {
return AddSignatureWithKey(key, &signDataReaderWithToken{
SignedDataSource: src,
SignKeyPairAccumulator: src,
rdr: r,
token: src.GetSessionToken(),
},
)
} }
return AddSignatureWithKey(key, &signAccumWithToken{ sigSrc, err := GroupSignedPayloads(
SignedDataSource: src, src,
SignKeyPairAccumulator: src, src,
NewSignedSessionToken(
src.GetSessionToken(),
),
)
if err != nil {
return err
}
token: src.GetSessionToken(), return AddSignatureWithKey(key, sigSrc)
})
} }
// VerifyAccumulatedSignaturesWithToken checks if accumulated key-signature pairs of data with token are valid. // VerifyRequestData checks if accumulated key-signature pairs of data with token are valid.
// //
// If passed DataWithTokenSignSource is nil, ErrNilSignatureKeySourceWithToken returns. // If passed RequestVerifyData is nil, ErrNilRequestVerifyData returns.
func VerifyAccumulatedSignaturesWithToken(src DataWithTokenSignSource) error { func VerifyRequestData(src RequestVerifyData) error {
if src == nil { if src == nil {
return ErrNilSignatureKeySourceWithToken return ErrNilRequestVerifyData
} else if r, ok := src.(SignedDataReader); ok {
return VerifyAccumulatedSignatures(&signDataReaderWithToken{
SignedDataSource: src,
SignKeyPairSource: src,
rdr: r,
token: src.GetSessionToken(),
})
} }
return VerifyAccumulatedSignatures(&signAccumWithToken{ verSrc, err := GroupVerifyPayloads(
SignedDataSource: src, src,
SignKeyPairSource: src, src,
NewVerifiedSessionToken(
src.GetSessionToken(),
),
)
if err != nil {
return err
}
token: src.GetSessionToken(), return VerifyAccumulatedSignatures(verSrc)
}) }
// SignedData returns payload bytes concatenation from all sources keeping order.
func (s signSourceGroup) SignedData() ([]byte, error) {
chunks := make([][]byte, 0, len(s.sources))
sz := 0
for i := range s.sources {
data, err := s.sources[i].SignedData()
if err != nil {
return nil, errors.Wrapf(err, "could not get signed payload of element #%d", i)
}
chunks = append(chunks, data)
sz += len(data)
}
res := make([]byte, sz)
off := 0
for i := range chunks {
off += copy(res[off:], chunks[i])
}
return res, nil
}
// SignedData returns payload bytes concatenation from all readers.
func (s signReadersGroup) SignedData() ([]byte, error) {
return SignedDataFromReader(s)
}
// SignedDataSize returns the sum of sizes of all readers.
func (s signReadersGroup) SignedDataSize() (sz int) {
for i := range s.readers {
sz += s.readers[i].SignedDataSize()
}
return
}
// ReadSignedData reads data from all readers to passed buffer keeping order.
//
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
func (s signReadersGroup) ReadSignedData(p []byte) (int, error) {
sz := s.SignedDataSize()
if len(p) < sz {
return 0, io.ErrUnexpectedEOF
}
off := 0
for i := range s.readers {
n, err := s.readers[i].ReadSignedData(p[off:])
off += n
if err != nil {
return off, errors.Wrapf(err, "could not read signed payload of element #%d", i)
}
}
return off, nil
}
// GroupSignedPayloads groups SignKeyPairAccumulator and SignedDataSource list to DataWithSignKeyAccumulator.
//
// If passed SignKeyPairAccumulator is nil, ErrNilSignKeyPairAccumulator returns.
//
// Signed payload of the result is a concatenation of payloads of list elements keeping order.
// Nil elements in list are ignored.
//
// If all elements implement SignedDataReader, result implements it too.
func GroupSignedPayloads(acc SignKeyPairAccumulator, sources ...SignedDataSource) (DataWithSignKeyAccumulator, error) {
if acc == nil {
return nil, ErrNilSignKeyPairAccumulator
}
return groupPayloads(acc, nil, sources...), nil
}
// GroupVerifyPayloads groups SignKeyPairSource and SignedDataSource list to DataWithSignKeySource.
//
// If passed SignKeyPairSource is nil, ErrNilSignatureKeySource returns.
//
// Signed payload of the result is a concatenation of payloads of list elements keeping order.
// Nil elements in list are ignored.
//
// If all elements implement SignedDataReader, result implements it too.
func GroupVerifyPayloads(src SignKeyPairSource, sources ...SignedDataSource) (DataWithSignKeySource, error) {
if src == nil {
return nil, ErrNilSignatureKeySource
}
return groupPayloads(nil, src, sources...), nil
}
func groupPayloads(acc SignKeyPairAccumulator, src SignKeyPairSource, sources ...SignedDataSource) interface {
SignedDataSource
SignKeyPairSource
SignKeyPairAccumulator
} {
var allReaders bool
for i := range sources {
if sources[i] == nil {
continue
} else if _, allReaders = sources[i].(SignedDataReader); !allReaders {
break
}
}
if !allReaders {
res := &signSourceGroup{
SignKeyPairSource: src,
SignKeyPairAccumulator: acc,
sources: make([]SignedDataSource, 0, len(sources)),
}
for i := range sources {
if sources[i] != nil {
res.sources = append(res.sources, sources[i])
}
}
return res
}
res := &signReadersGroup{
SignKeyPairSource: src,
SignKeyPairAccumulator: acc,
readers: make([]SignedDataReader, 0, len(sources)),
}
for i := range sources {
if sources[i] != nil {
res.readers = append(res.readers, sources[i].(SignedDataReader))
}
}
return res
} }

View file

@ -257,16 +257,16 @@ func TestVerifySignatureWithKey(t *testing.T) {
} }
func TestSignVerifyDataWithSessionToken(t *testing.T) { func TestSignVerifyDataWithSessionToken(t *testing.T) {
// sign with empty DataWithTokenSignAccumulator // sign with empty RequestSignedData
require.EqualError(t, require.EqualError(t,
SignDataWithSessionToken(nil, nil), SignRequestData(nil, nil),
ErrNilDataWithTokenSignAccumulator.Error(), ErrNilRequestSignedData.Error(),
) )
// verify with empty DataWithTokenSignSource // verify with empty RequestVerifyData
require.EqualError(t, require.EqualError(t,
VerifyAccumulatedSignaturesWithToken(nil), VerifyRequestData(nil),
ErrNilSignatureKeySourceWithToken.Error(), ErrNilRequestVerifyData.Error(),
) )
// create test session token // create test session token
@ -287,16 +287,16 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
// sign with private key // sign with private key
require.NoError(t, SignDataWithSessionToken(sk, src)) require.NoError(t, SignRequestData(sk, src))
// ascertain that verification is passed // ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) require.NoError(t, VerifyRequestData(src))
// break the data // break the data
src.data[0]++ src.data[0]++
// ascertain that verification is failed // ascertain that verification is failed
require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) require.Error(t, VerifyRequestData(src))
// restore the data // restore the data
src.data[0]-- src.data[0]--
@ -305,13 +305,13 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
token.SetVerb(initVerb + 1) token.SetVerb(initVerb + 1)
// ascertain that verification is failed // ascertain that verification is failed
require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) require.Error(t, VerifyRequestData(src))
// restore the token // restore the token
token.SetVerb(initVerb) token.SetVerb(initVerb)
// ascertain that verification is passed // ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) require.NoError(t, VerifyRequestData(src))
// wrap to data reader // wrap to data reader
rdr := &testSignedDataReader{ rdr := &testSignedDataReader{
@ -319,8 +319,8 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
} }
// sign with private key // sign with private key
require.NoError(t, SignDataWithSessionToken(sk, rdr)) require.NoError(t, SignRequestData(sk, rdr))
// ascertain that verification is passed // ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(rdr)) require.NoError(t, VerifyRequestData(rdr))
} }

View file

@ -250,20 +250,20 @@ type DataWithSignKeySource interface {
SignKeyPairSource SignKeyPairSource
} }
// SignedDataWithToken is an interface of data-token pair with read access. // RequestData is an interface of the request information with read access.
type SignedDataWithToken interface { type RequestData interface {
SignedDataSource SignedDataSource
SessionTokenSource SessionTokenSource
} }
// DataWithTokenSignAccumulator is an interface of data-token pair with signature write access. // RequestSignedData is an interface of request information with signature write access.
type DataWithTokenSignAccumulator interface { type RequestSignedData interface {
SignedDataWithToken RequestData
SignKeyPairAccumulator SignKeyPairAccumulator
} }
// DataWithTokenSignSource is an interface of data-token pair with signature read access. // RequestVerifyData is an interface of request information with signature read access.
type DataWithTokenSignSource interface { type RequestVerifyData interface {
SignedDataWithToken RequestData
SignKeyPairSource SignKeyPairSource
} }

View file

@ -69,7 +69,7 @@ func BenchmarkSignDataWithSessionToken(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
require.NoError(b, SignDataWithSessionToken(key, req)) require.NoError(b, SignRequestData(key, req))
} }
} }
@ -91,14 +91,14 @@ func BenchmarkVerifyAccumulatedSignaturesWithToken(b *testing.B) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
key := test.DecodeKey(i) key := test.DecodeKey(i)
require.NoError(b, SignDataWithSessionToken(key, req)) require.NoError(b, SignRequestData(key, req))
} }
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
require.NoError(b, VerifyAccumulatedSignaturesWithToken(req)) require.NoError(b, VerifyRequestData(req))
} }
} }

View file

@ -53,7 +53,7 @@ func (s gRPCCreator) Create(ctx context.Context, p CreateParamsSource) (CreateRe
req.SetExpirationEpoch(p.ExpirationEpoch()) req.SetExpirationEpoch(p.ExpirationEpoch())
// sign with private key // sign with private key
if err := service.SignDataWithSessionToken(s.key, req); err != nil { if err := service.SignRequestData(s.key, req); err != nil {
return nil, err return nil, err
} }

View file

@ -84,7 +84,7 @@ func TestGRPCCreator_Create(t *testing.T) {
require.Equal(t, ownerID, req.GetOwnerID()) require.Equal(t, ownerID, req.GetOwnerID())
require.Equal(t, created, req.CreationEpoch()) require.Equal(t, created, req.CreationEpoch())
require.Equal(t, expired, req.ExpirationEpoch()) require.Equal(t, expired, req.ExpirationEpoch())
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(req)) require.NoError(t, service.VerifyRequestData(req))
}, },
resp: &CreateResponse{ resp: &CreateResponse{
ID: TokenID{1, 2, 3}, ID: TokenID{1, 2, 3},

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -68,26 +68,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(token) v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v)) require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.NoError(t, service.VerifyRequestData(v))
corruption(v) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }