github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/whisper/whisperv2/api.go (about)

     1  // Copyright 2015 The Spectrum Authors
     2  // This file is part of the Spectrum library.
     3  //
     4  // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package whisperv2
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/SmartMeshFoundation/Spectrum/common"
    26  	"github.com/SmartMeshFoundation/Spectrum/common/hexutil"
    27  	"github.com/SmartMeshFoundation/Spectrum/crypto"
    28  )
    29  
    30  // PublicWhisperAPI provides the whisper RPC service.
    31  type PublicWhisperAPI struct {
    32  	w *Whisper
    33  
    34  	messagesMu sync.RWMutex
    35  	messages   map[hexutil.Uint]*whisperFilter
    36  }
    37  
    38  type whisperOfflineError struct{}
    39  
    40  func (e *whisperOfflineError) Error() string {
    41  	return "whisper is offline"
    42  }
    43  
    44  // whisperOffLineErr is returned when the node doesn't offer the shh service.
    45  var whisperOffLineErr = new(whisperOfflineError)
    46  
    47  // NewPublicWhisperAPI create a new RPC whisper service.
    48  func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
    49  	return &PublicWhisperAPI{w: w, messages: make(map[hexutil.Uint]*whisperFilter)}
    50  }
    51  
    52  // Version returns the Whisper version this node offers.
    53  func (s *PublicWhisperAPI) Version() (hexutil.Uint, error) {
    54  	if s.w == nil {
    55  		return 0, whisperOffLineErr
    56  	}
    57  	return hexutil.Uint(s.w.Version()), nil
    58  }
    59  
    60  // HasIdentity checks if the the whisper node is configured with the private key
    61  // of the specified public pair.
    62  func (s *PublicWhisperAPI) HasIdentity(identity string) (bool, error) {
    63  	if s.w == nil {
    64  		return false, whisperOffLineErr
    65  	}
    66  	return s.w.HasIdentity(crypto.ToECDSAPub(common.FromHex(identity))), nil
    67  }
    68  
    69  // NewIdentity generates a new cryptographic identity for the client, and injects
    70  // it into the known identities for message decryption.
    71  func (s *PublicWhisperAPI) NewIdentity() (string, error) {
    72  	if s.w == nil {
    73  		return "", whisperOffLineErr
    74  	}
    75  
    76  	identity := s.w.NewIdentity()
    77  	return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil
    78  }
    79  
    80  type NewFilterArgs struct {
    81  	To     string
    82  	From   string
    83  	Topics [][][]byte
    84  }
    85  
    86  // NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages.
    87  func (s *PublicWhisperAPI) NewFilter(args NewFilterArgs) (hexutil.Uint, error) {
    88  	if s.w == nil {
    89  		return 0, whisperOffLineErr
    90  	}
    91  
    92  	var id hexutil.Uint
    93  	filter := Filter{
    94  		To:     crypto.ToECDSAPub(common.FromHex(args.To)),
    95  		From:   crypto.ToECDSAPub(common.FromHex(args.From)),
    96  		Topics: NewFilterTopics(args.Topics...),
    97  		Fn: func(message *Message) {
    98  			wmsg := NewWhisperMessage(message)
    99  			s.messagesMu.RLock() // Only read lock to the filter pool
   100  			defer s.messagesMu.RUnlock()
   101  			if s.messages[id] != nil {
   102  				s.messages[id].insert(wmsg)
   103  			}
   104  		},
   105  	}
   106  	id = hexutil.Uint(s.w.Watch(filter))
   107  
   108  	s.messagesMu.Lock()
   109  	s.messages[id] = newWhisperFilter(id, s.w)
   110  	s.messagesMu.Unlock()
   111  
   112  	return id, nil
   113  }
   114  
   115  // GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval.
   116  func (s *PublicWhisperAPI) GetFilterChanges(filterId hexutil.Uint) []WhisperMessage {
   117  	s.messagesMu.RLock()
   118  	defer s.messagesMu.RUnlock()
   119  
   120  	if s.messages[filterId] != nil {
   121  		if changes := s.messages[filterId].retrieve(); changes != nil {
   122  			return changes
   123  		}
   124  	}
   125  	return returnWhisperMessages(nil)
   126  }
   127  
   128  // UninstallFilter disables and removes an existing filter.
   129  func (s *PublicWhisperAPI) UninstallFilter(filterId hexutil.Uint) bool {
   130  	s.messagesMu.Lock()
   131  	defer s.messagesMu.Unlock()
   132  
   133  	if _, ok := s.messages[filterId]; ok {
   134  		delete(s.messages, filterId)
   135  		return true
   136  	}
   137  	return false
   138  }
   139  
   140  // GetMessages retrieves all the known messages that match a specific filter.
   141  func (s *PublicWhisperAPI) GetMessages(filterId hexutil.Uint) []WhisperMessage {
   142  	// Retrieve all the cached messages matching a specific, existing filter
   143  	s.messagesMu.RLock()
   144  	defer s.messagesMu.RUnlock()
   145  
   146  	var messages []*Message
   147  	if s.messages[filterId] != nil {
   148  		messages = s.messages[filterId].messages()
   149  	}
   150  
   151  	return returnWhisperMessages(messages)
   152  }
   153  
   154  // returnWhisperMessages converts aNhisper message to a RPC whisper message.
   155  func returnWhisperMessages(messages []*Message) []WhisperMessage {
   156  	msgs := make([]WhisperMessage, len(messages))
   157  	for i, msg := range messages {
   158  		msgs[i] = NewWhisperMessage(msg)
   159  	}
   160  	return msgs
   161  }
   162  
   163  type PostArgs struct {
   164  	From     string   `json:"from"`
   165  	To       string   `json:"to"`
   166  	Topics   [][]byte `json:"topics"`
   167  	Payload  string   `json:"payload"`
   168  	Priority int64    `json:"priority"`
   169  	TTL      int64    `json:"ttl"`
   170  }
   171  
   172  // Post injects a message into the whisper network for distribution.
   173  func (s *PublicWhisperAPI) Post(args PostArgs) (bool, error) {
   174  	if s.w == nil {
   175  		return false, whisperOffLineErr
   176  	}
   177  
   178  	// construct whisper message with transmission options
   179  	message := NewMessage(common.FromHex(args.Payload))
   180  	options := Options{
   181  		To:     crypto.ToECDSAPub(common.FromHex(args.To)),
   182  		TTL:    time.Duration(args.TTL) * time.Second,
   183  		Topics: NewTopics(args.Topics...),
   184  	}
   185  
   186  	// set sender identity
   187  	if len(args.From) > 0 {
   188  		if key := s.w.GetIdentity(crypto.ToECDSAPub(common.FromHex(args.From))); key != nil {
   189  			options.From = key
   190  		} else {
   191  			return false, fmt.Errorf("unknown identity to send from: %s", args.From)
   192  		}
   193  	}
   194  
   195  	// Wrap and send the message
   196  	pow := time.Duration(args.Priority) * time.Millisecond
   197  	envelope, err := message.Wrap(pow, options)
   198  	if err != nil {
   199  		return false, err
   200  	}
   201  
   202  	return true, s.w.Send(envelope)
   203  }
   204  
   205  // WhisperMessage is the RPC representation of a whisper message.
   206  type WhisperMessage struct {
   207  	ref *Message
   208  
   209  	Payload string `json:"payload"`
   210  	To      string `json:"to"`
   211  	From    string `json:"from"`
   212  	Sent    int64  `json:"sent"`
   213  	TTL     int64  `json:"ttl"`
   214  	Hash    string `json:"hash"`
   215  }
   216  
   217  func (args *PostArgs) UnmarshalJSON(data []byte) (err error) {
   218  	var obj struct {
   219  		From     string         `json:"from"`
   220  		To       string         `json:"to"`
   221  		Topics   []string       `json:"topics"`
   222  		Payload  string         `json:"payload"`
   223  		Priority hexutil.Uint64 `json:"priority"`
   224  		TTL      hexutil.Uint64 `json:"ttl"`
   225  	}
   226  
   227  	if err := json.Unmarshal(data, &obj); err != nil {
   228  		return err
   229  	}
   230  
   231  	args.From = obj.From
   232  	args.To = obj.To
   233  	args.Payload = obj.Payload
   234  	args.Priority = int64(obj.Priority) // TODO(gluk256): handle overflow
   235  	args.TTL = int64(obj.TTL)           // ... here too ...
   236  
   237  	// decode topic strings
   238  	args.Topics = make([][]byte, len(obj.Topics))
   239  	for i, topic := range obj.Topics {
   240  		args.Topics[i] = common.FromHex(topic)
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
   247  // JSON message blob into a WhisperFilterArgs structure.
   248  func (args *NewFilterArgs) UnmarshalJSON(b []byte) (err error) {
   249  	// Unmarshal the JSON message and sanity check
   250  	var obj struct {
   251  		To     interface{} `json:"to"`
   252  		From   interface{} `json:"from"`
   253  		Topics interface{} `json:"topics"`
   254  	}
   255  	if err := json.Unmarshal(b, &obj); err != nil {
   256  		return err
   257  	}
   258  
   259  	// Retrieve the simple data contents of the filter arguments
   260  	if obj.To == nil {
   261  		args.To = ""
   262  	} else {
   263  		argstr, ok := obj.To.(string)
   264  		if !ok {
   265  			return fmt.Errorf("to is not a string")
   266  		}
   267  		args.To = argstr
   268  	}
   269  	if obj.From == nil {
   270  		args.From = ""
   271  	} else {
   272  		argstr, ok := obj.From.(string)
   273  		if !ok {
   274  			return fmt.Errorf("from is not a string")
   275  		}
   276  		args.From = argstr
   277  	}
   278  	// Construct the nested topic array
   279  	if obj.Topics != nil {
   280  		// Make sure we have an actual topic array
   281  		list, ok := obj.Topics.([]interface{})
   282  		if !ok {
   283  			return fmt.Errorf("topics is not an array")
   284  		}
   285  		// Iterate over each topic and handle nil, string or array
   286  		topics := make([][]string, len(list))
   287  		for idx, field := range list {
   288  			switch value := field.(type) {
   289  			case nil:
   290  				topics[idx] = []string{}
   291  
   292  			case string:
   293  				topics[idx] = []string{value}
   294  
   295  			case []interface{}:
   296  				topics[idx] = make([]string, len(value))
   297  				for i, nested := range value {
   298  					switch value := nested.(type) {
   299  					case nil:
   300  						topics[idx][i] = ""
   301  
   302  					case string:
   303  						topics[idx][i] = value
   304  
   305  					default:
   306  						return fmt.Errorf("topic[%d][%d] is not a string", idx, i)
   307  					}
   308  				}
   309  			default:
   310  				return fmt.Errorf("topic[%d] not a string or array", idx)
   311  			}
   312  		}
   313  
   314  		topicsDecoded := make([][][]byte, len(topics))
   315  		for i, condition := range topics {
   316  			topicsDecoded[i] = make([][]byte, len(condition))
   317  			for j, topic := range condition {
   318  				topicsDecoded[i][j] = common.FromHex(topic)
   319  			}
   320  		}
   321  
   322  		args.Topics = topicsDecoded
   323  	}
   324  	return nil
   325  }
   326  
   327  // whisperFilter is the message cache matching a specific filter, accumulating
   328  // inbound messages until the are requested by the client.
   329  type whisperFilter struct {
   330  	id  hexutil.Uint // Filter identifier for old message retrieval
   331  	ref *Whisper     // Whisper reference for old message retrieval
   332  
   333  	cache  []WhisperMessage         // Cache of messages not yet polled
   334  	skip   map[common.Hash]struct{} // List of retrieved messages to avoid duplication
   335  	update time.Time                // Time of the last message query
   336  
   337  	lock sync.RWMutex // Lock protecting the filter internals
   338  }
   339  
   340  // messages retrieves all the cached messages from the entire pool matching the
   341  // filter, resetting the filter's change buffer.
   342  func (w *whisperFilter) messages() []*Message {
   343  	w.lock.Lock()
   344  	defer w.lock.Unlock()
   345  
   346  	w.cache = nil
   347  	w.update = time.Now()
   348  
   349  	w.skip = make(map[common.Hash]struct{})
   350  	messages := w.ref.Messages(int(w.id))
   351  	for _, message := range messages {
   352  		w.skip[message.Hash] = struct{}{}
   353  	}
   354  	return messages
   355  }
   356  
   357  // insert injects a new batch of messages into the filter cache.
   358  func (w *whisperFilter) insert(messages ...WhisperMessage) {
   359  	w.lock.Lock()
   360  	defer w.lock.Unlock()
   361  
   362  	for _, message := range messages {
   363  		if _, ok := w.skip[message.ref.Hash]; !ok {
   364  			w.cache = append(w.cache, messages...)
   365  		}
   366  	}
   367  }
   368  
   369  // retrieve fetches all the cached messages from the filter.
   370  func (w *whisperFilter) retrieve() (messages []WhisperMessage) {
   371  	w.lock.Lock()
   372  	defer w.lock.Unlock()
   373  
   374  	messages, w.cache = w.cache, nil
   375  	w.update = time.Now()
   376  
   377  	return
   378  }
   379  
   380  // newWhisperFilter creates a new serialized, poll based whisper topic filter.
   381  func newWhisperFilter(id hexutil.Uint, ref *Whisper) *whisperFilter {
   382  	return &whisperFilter{
   383  		id:     id,
   384  		ref:    ref,
   385  		update: time.Now(),
   386  		skip:   make(map[common.Hash]struct{}),
   387  	}
   388  }
   389  
   390  // NewWhisperMessage converts an internal message into an API version.
   391  func NewWhisperMessage(message *Message) WhisperMessage {
   392  	return WhisperMessage{
   393  		ref: message,
   394  
   395  		Payload: common.ToHex(message.Payload),
   396  		From:    common.ToHex(crypto.FromECDSAPub(message.Recover())),
   397  		To:      common.ToHex(crypto.FromECDSAPub(message.To)),
   398  		Sent:    message.Sent.Unix(),
   399  		TTL:     int64(message.TTL / time.Second),
   400  		Hash:    common.ToHex(message.Hash.Bytes()),
   401  	}
   402  }