github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/whisper/api.go (about)

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