github.com/beyonderyue/gochain@v2.2.26+incompatible/whisper/whisperv6/api.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package whisperv6
    18  
    19  import (
    20  	"context"
    21  	"crypto/ecdsa"
    22  	"errors"
    23  	"fmt"
    24  	"sync"
    25  	"time"
    26  
    27  	"go.opencensus.io/trace"
    28  
    29  	"github.com/gochain-io/gochain/common"
    30  	"github.com/gochain-io/gochain/common/hexutil"
    31  	"github.com/gochain-io/gochain/crypto"
    32  	"github.com/gochain-io/gochain/log"
    33  	"github.com/gochain-io/gochain/p2p/discover"
    34  	"github.com/gochain-io/gochain/rpc"
    35  )
    36  
    37  const (
    38  	filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds
    39  )
    40  
    41  // List of errors
    42  var (
    43  	ErrSymAsym              = errors.New("specify either a symmetric or an asymmetric key")
    44  	ErrInvalidSymmetricKey  = errors.New("invalid symmetric key")
    45  	ErrInvalidPublicKey     = errors.New("invalid public key")
    46  	ErrInvalidSigningPubKey = errors.New("invalid signing public key")
    47  	ErrTooLowPoW            = errors.New("message rejected, PoW too low")
    48  	ErrNoTopics             = errors.New("missing topic(s)")
    49  )
    50  
    51  // PublicWhisperAPI provides the whisper RPC service that can be
    52  // use publicly without security implications.
    53  type PublicWhisperAPI struct {
    54  	w *Whisper
    55  
    56  	mu       sync.Mutex
    57  	lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
    58  }
    59  
    60  // NewPublicWhisperAPI create a new RPC whisper service.
    61  func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
    62  	api := &PublicWhisperAPI{
    63  		w:        w,
    64  		lastUsed: make(map[string]time.Time),
    65  	}
    66  
    67  	go api.run()
    68  	return api
    69  }
    70  
    71  // run the api event loop.
    72  // this loop deletes filter that have not been used within filterTimeout
    73  func (api *PublicWhisperAPI) run() {
    74  	timeout := time.NewTicker(2 * time.Minute)
    75  	for {
    76  		<-timeout.C
    77  
    78  		api.mu.Lock()
    79  		for id, lastUsed := range api.lastUsed {
    80  			if time.Since(lastUsed).Seconds() >= filterTimeout {
    81  				delete(api.lastUsed, id)
    82  				if err := api.w.Unsubscribe(id); err != nil {
    83  					log.Error("could not unsubscribe whisper filter", "error", err)
    84  				}
    85  				log.Debug("delete whisper filter (timeout)", "id", id)
    86  			}
    87  		}
    88  		api.mu.Unlock()
    89  	}
    90  }
    91  
    92  // Version returns the Whisper sub-protocol version.
    93  func (api *PublicWhisperAPI) Version(ctx context.Context) string {
    94  	return ProtocolVersionStr
    95  }
    96  
    97  // Info contains diagnostic information.
    98  type Info struct {
    99  	Memory         int     `json:"memory"`         // Memory size of the floating messages in bytes.
   100  	Messages       int     `json:"messages"`       // Number of floating messages.
   101  	MinPow         float64 `json:"minPow"`         // Minimal accepted PoW
   102  	MaxMessageSize uint32  `json:"maxMessageSize"` // Maximum accepted message size
   103  }
   104  
   105  // Info returns diagnostic information about the whisper node.
   106  func (api *PublicWhisperAPI) Info(ctx context.Context) Info {
   107  	stats := api.w.Stats()
   108  	return Info{
   109  		Memory:         stats.memoryUsed,
   110  		Messages:       len(api.w.messageQueue) + len(api.w.p2pMsgQueue),
   111  		MinPow:         api.w.MinPow(),
   112  		MaxMessageSize: api.w.MaxMessageSize(),
   113  	}
   114  }
   115  
   116  // SetMaxMessageSize sets the maximum message size that is accepted.
   117  // Upper limit is defined by MaxMessageSize.
   118  func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) {
   119  	return true, api.w.SetMaxMessageSize(size)
   120  }
   121  
   122  // SetMinPoW sets the minimum PoW, and notifies the peers.
   123  func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
   124  	return true, api.w.SetMinimumPoW(ctx, pow)
   125  }
   126  
   127  // SetBloomFilter sets the new value of bloom filter, and notifies the peers.
   128  func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) {
   129  	ctx, span := trace.StartSpan(ctx, "PublicWhisperAPI.SetBloomFilter")
   130  	defer span.End()
   131  	return true, api.w.SetBloomFilter(ctx, bloom)
   132  }
   133  
   134  // MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages.
   135  // Note: This function is not adding new nodes, the node needs to exists as a peer.
   136  func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) {
   137  	n, err := discover.ParseNode(enode)
   138  	if err != nil {
   139  		return false, err
   140  	}
   141  	return true, api.w.AllowP2PMessagesFromPeer(n.ID[:])
   142  }
   143  
   144  // NewKeyPair generates a new public and private key pair for message decryption and encryption.
   145  // It returns an ID that can be used to refer to the keypair.
   146  func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) {
   147  	return api.w.NewKeyPair()
   148  }
   149  
   150  // AddPrivateKey imports the given private key.
   151  func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) {
   152  	key, err := crypto.ToECDSA(privateKey)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  	return api.w.AddKeyPair(key)
   157  }
   158  
   159  // DeleteKeyPair removes the key with the given key if it exists.
   160  func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
   161  	if ok := api.w.DeleteKeyPair(key); ok {
   162  		return true, nil
   163  	}
   164  	return false, fmt.Errorf("key pair %s not found", key)
   165  }
   166  
   167  // HasKeyPair returns an indication if the node has a key pair that is associated with the given id.
   168  func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool {
   169  	return api.w.HasKeyPair(id)
   170  }
   171  
   172  // GetPublicKey returns the public key associated with the given key. The key is the hex
   173  // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
   174  func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) {
   175  	key, err := api.w.GetPrivateKey(id)
   176  	if err != nil {
   177  		return hexutil.Bytes{}, err
   178  	}
   179  	return crypto.FromECDSAPub(&key.PublicKey), nil
   180  }
   181  
   182  // GetPrivateKey returns the private key associated with the given key. The key is the hex
   183  // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
   184  func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) {
   185  	key, err := api.w.GetPrivateKey(id)
   186  	if err != nil {
   187  		return hexutil.Bytes{}, err
   188  	}
   189  	return crypto.FromECDSA(key), nil
   190  }
   191  
   192  // NewSymKey generate a random symmetric key.
   193  // It returns an ID that can be used to refer to the key.
   194  // Can be used encrypting and decrypting messages where the key is known to both parties.
   195  func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) {
   196  	return api.w.GenerateSymKey()
   197  }
   198  
   199  // AddSymKey import a symmetric key.
   200  // It returns an ID that can be used to refer to the key.
   201  // Can be used encrypting and decrypting messages where the key is known to both parties.
   202  func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) {
   203  	return api.w.AddSymKeyDirect([]byte(key))
   204  }
   205  
   206  // GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID.
   207  func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
   208  	return api.w.AddSymKeyFromPassword(passwd)
   209  }
   210  
   211  // HasSymKey returns an indication if the node has a symmetric key associated with the given key.
   212  func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool {
   213  	return api.w.HasSymKey(id)
   214  }
   215  
   216  // GetSymKey returns the symmetric key associated with the given id.
   217  func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) {
   218  	return api.w.GetSymKey(id)
   219  }
   220  
   221  // DeleteSymKey deletes the symmetric key that is associated with the given id.
   222  func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool {
   223  	return api.w.DeleteSymKey(id)
   224  }
   225  
   226  //go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go
   227  
   228  // NewMessage represents a new whisper message that is posted through the RPC.
   229  type NewMessage struct {
   230  	SymKeyID   string    `json:"symKeyID"`
   231  	PublicKey  []byte    `json:"pubKey"`
   232  	Sig        string    `json:"sig"`
   233  	TTL        uint32    `json:"ttl"`
   234  	Topic      TopicType `json:"topic"`
   235  	Payload    []byte    `json:"payload"`
   236  	Padding    []byte    `json:"padding"`
   237  	PowTime    uint32    `json:"powTime"`
   238  	PowTarget  float64   `json:"powTarget"`
   239  	TargetPeer string    `json:"targetPeer"`
   240  }
   241  
   242  type newMessageOverride struct {
   243  	PublicKey hexutil.Bytes
   244  	Payload   hexutil.Bytes
   245  	Padding   hexutil.Bytes
   246  }
   247  
   248  // Post a message on the Whisper network.
   249  func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) {
   250  	ctx, span := trace.StartSpan(ctx, "PublicWhisperApi.Post")
   251  	defer span.End()
   252  
   253  	var (
   254  		symKeyGiven = len(req.SymKeyID) > 0
   255  		pubKeyGiven = len(req.PublicKey) > 0
   256  		err         error
   257  	)
   258  
   259  	// user must specify either a symmetric or an asymmetric key
   260  	if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
   261  		return false, ErrSymAsym
   262  	}
   263  
   264  	params := &MessageParams{
   265  		TTL:      req.TTL,
   266  		Payload:  req.Payload,
   267  		Padding:  req.Padding,
   268  		WorkTime: req.PowTime,
   269  		PoW:      req.PowTarget,
   270  		Topic:    req.Topic,
   271  	}
   272  
   273  	// Set key that is used to sign the message
   274  	if len(req.Sig) > 0 {
   275  		if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil {
   276  			return false, err
   277  		}
   278  	}
   279  
   280  	// Set symmetric key that is used to encrypt the message
   281  	if symKeyGiven {
   282  		if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption
   283  			return false, ErrNoTopics
   284  		}
   285  		if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
   286  			return false, err
   287  		}
   288  		if !validateDataIntegrity(params.KeySym, aesKeyLength) {
   289  			return false, ErrInvalidSymmetricKey
   290  		}
   291  	}
   292  
   293  	// Set asymmetric key that is used to encrypt the message
   294  	if pubKeyGiven {
   295  		params.Dst = crypto.ToECDSAPub(req.PublicKey)
   296  		if !ValidatePublicKey(params.Dst) {
   297  			return false, ErrInvalidPublicKey
   298  		}
   299  	}
   300  
   301  	// encrypt and sent message
   302  	whisperMsg, err := NewSentMessage(params)
   303  	if err != nil {
   304  		return false, err
   305  	}
   306  
   307  	env, err := whisperMsg.Wrap(params)
   308  	if err != nil {
   309  		return false, err
   310  	}
   311  
   312  	// send to specific node (skip PoW check)
   313  	if len(req.TargetPeer) > 0 {
   314  		n, err := discover.ParseNode(req.TargetPeer)
   315  		if err != nil {
   316  			return false, fmt.Errorf("failed to parse target peer: %s", err)
   317  		}
   318  		return true, api.w.SendP2PMessage(ctx, n.ID[:], env)
   319  	}
   320  
   321  	// ensure that the message PoW meets the node's minimum accepted PoW
   322  	if req.PowTarget < api.w.MinPow() {
   323  		return false, ErrTooLowPoW
   324  	}
   325  
   326  	return true, api.w.Send(env)
   327  }
   328  
   329  //go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go
   330  
   331  // Criteria holds various filter options for inbound messages.
   332  type Criteria struct {
   333  	SymKeyID     string      `json:"symKeyID"`
   334  	PrivateKeyID string      `json:"privateKeyID"`
   335  	Sig          []byte      `json:"sig"`
   336  	MinPow       float64     `json:"minPow"`
   337  	Topics       []TopicType `json:"topics"`
   338  	AllowP2P     bool        `json:"allowP2P"`
   339  }
   340  
   341  type criteriaOverride struct {
   342  	Sig hexutil.Bytes
   343  }
   344  
   345  // Messages set up a subscription that fires events when messages arrive that match
   346  // the given set of criteria.
   347  func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) {
   348  	var (
   349  		symKeyGiven = len(crit.SymKeyID) > 0
   350  		pubKeyGiven = len(crit.PrivateKeyID) > 0
   351  		err         error
   352  	)
   353  
   354  	// ensure that the RPC connection supports subscriptions
   355  	notifier, supported := rpc.NotifierFromContext(ctx)
   356  	if !supported {
   357  		return nil, rpc.ErrNotificationsUnsupported
   358  	}
   359  
   360  	// user must specify either a symmetric or an asymmetric key
   361  	if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
   362  		return nil, ErrSymAsym
   363  	}
   364  
   365  	filter := Filter{
   366  		PoW:      crit.MinPow,
   367  		Messages: make(map[common.Hash]*ReceivedMessage),
   368  		AllowP2P: crit.AllowP2P,
   369  	}
   370  
   371  	if len(crit.Sig) > 0 {
   372  		filter.Src = crypto.ToECDSAPub(crit.Sig)
   373  		if !ValidatePublicKey(filter.Src) {
   374  			return nil, ErrInvalidSigningPubKey
   375  		}
   376  	}
   377  
   378  	for i, bt := range crit.Topics {
   379  		if len(bt) == 0 || len(bt) > 4 {
   380  			return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt))
   381  		}
   382  		filter.Topics = append(filter.Topics, bt[:])
   383  	}
   384  
   385  	// listen for message that are encrypted with the given symmetric key
   386  	if symKeyGiven {
   387  		if len(filter.Topics) == 0 {
   388  			return nil, ErrNoTopics
   389  		}
   390  		key, err := api.w.GetSymKey(crit.SymKeyID)
   391  		if err != nil {
   392  			return nil, err
   393  		}
   394  		if !validateDataIntegrity(key, aesKeyLength) {
   395  			return nil, ErrInvalidSymmetricKey
   396  		}
   397  		filter.KeySym = key
   398  		filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
   399  	}
   400  
   401  	// listen for messages that are encrypted with the given public key
   402  	if pubKeyGiven {
   403  		filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID)
   404  		if err != nil || filter.KeyAsym == nil {
   405  			return nil, ErrInvalidPublicKey
   406  		}
   407  	}
   408  
   409  	id, err := api.w.Subscribe(&filter)
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  
   414  	// create subscription and start waiting for message events
   415  	rpcSub := notifier.CreateSubscription()
   416  	go func() {
   417  		// for now poll internally, refactor whisper internal for channel support
   418  		ticker := time.NewTicker(250 * time.Millisecond)
   419  		defer ticker.Stop()
   420  
   421  		for {
   422  			select {
   423  			case <-ticker.C:
   424  				if filter := api.w.GetFilter(id); filter != nil {
   425  					for _, rpcMessage := range toMessage(filter.Retrieve()) {
   426  						if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil {
   427  							log.Error("Failed to send notification", "err", err)
   428  						}
   429  					}
   430  				}
   431  			case <-rpcSub.Err():
   432  				api.w.Unsubscribe(id)
   433  				return
   434  			case <-notifier.Closed():
   435  				api.w.Unsubscribe(id)
   436  				return
   437  			}
   438  		}
   439  	}()
   440  
   441  	return rpcSub, nil
   442  }
   443  
   444  //go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go
   445  
   446  // Message is the RPC representation of a whisper message.
   447  type Message struct {
   448  	Sig       []byte    `json:"sig,omitempty"`
   449  	TTL       uint32    `json:"ttl"`
   450  	Timestamp uint32    `json:"timestamp"`
   451  	Topic     TopicType `json:"topic"`
   452  	Payload   []byte    `json:"payload"`
   453  	Padding   []byte    `json:"padding"`
   454  	PoW       float64   `json:"pow"`
   455  	Hash      []byte    `json:"hash"`
   456  	Dst       []byte    `json:"recipientPublicKey,omitempty"`
   457  }
   458  
   459  type messageOverride struct {
   460  	Sig     hexutil.Bytes
   461  	Payload hexutil.Bytes
   462  	Padding hexutil.Bytes
   463  	Hash    hexutil.Bytes
   464  	Dst     hexutil.Bytes
   465  }
   466  
   467  // ToWhisperMessage converts an internal message into an API version.
   468  func ToWhisperMessage(message *ReceivedMessage) *Message {
   469  	msg := Message{
   470  		Payload:   message.Payload,
   471  		Padding:   message.Padding,
   472  		Timestamp: message.Sent,
   473  		TTL:       message.TTL,
   474  		PoW:       message.PoW,
   475  		Hash:      message.EnvelopeHash.Bytes(),
   476  		Topic:     message.Topic,
   477  	}
   478  
   479  	if message.Dst != nil {
   480  		b := crypto.FromECDSAPub(message.Dst)
   481  		if b != nil {
   482  			msg.Dst = b
   483  		}
   484  	}
   485  
   486  	if isMessageSigned(message.Raw[0]) {
   487  		b := crypto.FromECDSAPub(message.SigToPubKey())
   488  		if b != nil {
   489  			msg.Sig = b
   490  		}
   491  	}
   492  
   493  	return &msg
   494  }
   495  
   496  // toMessage converts a set of messages to its RPC representation.
   497  func toMessage(messages []*ReceivedMessage) []*Message {
   498  	msgs := make([]*Message, len(messages))
   499  	for i, msg := range messages {
   500  		msgs[i] = ToWhisperMessage(msg)
   501  	}
   502  	return msgs
   503  }
   504  
   505  // GetFilterMessages returns the messages that match the filter criteria and
   506  // are received between the last poll and now.
   507  func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) {
   508  	api.mu.Lock()
   509  	f := api.w.GetFilter(id)
   510  	if f == nil {
   511  		api.mu.Unlock()
   512  		return nil, fmt.Errorf("filter not found")
   513  	}
   514  	api.lastUsed[id] = time.Now()
   515  	api.mu.Unlock()
   516  
   517  	receivedMessages := f.Retrieve()
   518  	messages := make([]*Message, 0, len(receivedMessages))
   519  	for _, msg := range receivedMessages {
   520  		messages = append(messages, ToWhisperMessage(msg))
   521  	}
   522  
   523  	return messages, nil
   524  }
   525  
   526  // DeleteMessageFilter deletes a filter.
   527  func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) {
   528  	api.mu.Lock()
   529  	defer api.mu.Unlock()
   530  
   531  	delete(api.lastUsed, id)
   532  	return true, api.w.Unsubscribe(id)
   533  }
   534  
   535  // NewMessageFilter creates a new filter that can be used to poll for
   536  // (new) messages that satisfy the given criteria.
   537  func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) {
   538  	var (
   539  		src     *ecdsa.PublicKey
   540  		keySym  []byte
   541  		keyAsym *ecdsa.PrivateKey
   542  		topics  [][]byte
   543  
   544  		symKeyGiven  = len(req.SymKeyID) > 0
   545  		asymKeyGiven = len(req.PrivateKeyID) > 0
   546  
   547  		err error
   548  	)
   549  
   550  	// user must specify either a symmetric or an asymmetric key
   551  	if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) {
   552  		return "", ErrSymAsym
   553  	}
   554  
   555  	if len(req.Sig) > 0 {
   556  		src = crypto.ToECDSAPub(req.Sig)
   557  		if !ValidatePublicKey(src) {
   558  			return "", ErrInvalidSigningPubKey
   559  		}
   560  	}
   561  
   562  	if symKeyGiven {
   563  		if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
   564  			return "", err
   565  		}
   566  		if !validateDataIntegrity(keySym, aesKeyLength) {
   567  			return "", ErrInvalidSymmetricKey
   568  		}
   569  	}
   570  
   571  	if asymKeyGiven {
   572  		if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil {
   573  			return "", err
   574  		}
   575  	}
   576  
   577  	if len(req.Topics) > 0 {
   578  		topics = make([][]byte, 0, len(req.Topics))
   579  		for _, topic := range req.Topics {
   580  			topics = append(topics, topic[:])
   581  		}
   582  	}
   583  
   584  	f := &Filter{
   585  		Src:      src,
   586  		KeySym:   keySym,
   587  		KeyAsym:  keyAsym,
   588  		PoW:      req.MinPow,
   589  		AllowP2P: req.AllowP2P,
   590  		Topics:   topics,
   591  		Messages: make(map[common.Hash]*ReceivedMessage),
   592  	}
   593  
   594  	id, err := api.w.Subscribe(f)
   595  	if err != nil {
   596  		return "", err
   597  	}
   598  
   599  	api.mu.Lock()
   600  	api.lastUsed[id] = time.Now()
   601  	api.mu.Unlock()
   602  
   603  	return id, nil
   604  }