From 690f6ea1d74f949d4830d3a38c786510fc43d37c Mon Sep 17 00:00:00 2001 From: gluk256 Date: Tue, 31 Jan 2017 11:16:20 +0100 Subject: [PATCH] cmd/wnode, whisper: add whisper CLI tool and mail server (#3580) --- cmd/wnode/main.go | 537 ++++++++++++++++++++++++++++++ whisper/mailserver/mailserver.go | 170 ++++++++++ whisper/shhapi/api.go | 12 +- whisper/whisperv5/doc.go | 2 +- whisper/whisperv5/message_test.go | 33 ++ whisper/whisperv5/peer.go | 5 + whisper/whisperv5/whisper.go | 34 +- whisper/whisperv5/whisper_test.go | 6 - 8 files changed, 769 insertions(+), 30 deletions(-) create mode 100644 cmd/wnode/main.go create mode 100644 whisper/mailserver/mailserver.go diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go new file mode 100644 index 000000000..cbf093aa7 --- /dev/null +++ b/cmd/wnode/main.go @@ -0,0 +1,537 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +// This is a simple Whisper node. It could be used as a stand-alone bootstrap node. +// Also, could be used for different test and diagnostics purposes. + +package main + +import ( + "bufio" + "crypto/ecdsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "encoding/hex" + "flag" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/whisper/mailserver" + whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" + "golang.org/x/crypto/pbkdf2" +) + +const quitCommand = "~Q" + +// singletons +var ( + server *p2p.Server + shh *whisper.Whisper + done chan struct{} + mailServer mailserver.WMailServer + + input = bufio.NewReader(os.Stdin) +) + +// encryption +var ( + symKey []byte + pub *ecdsa.PublicKey + asymKey *ecdsa.PrivateKey + nodeid *ecdsa.PrivateKey + topic whisper.TopicType + filterID uint32 + msPassword string +) + +// cmd arguments +var ( + echoMode = flag.Bool("e", false, "echo mode: prints some arguments for diagnostics") + bootstrapMode = flag.Bool("b", false, "boostrap node: don't actively connect to peers, wait for incoming connections") + forwarderMode = flag.Bool("f", false, "forwarder mode: only forward messages, neither send nor decrypt messages") + mailServerMode = flag.Bool("s", false, "mail server mode: delivers expired messages on demand") + requestMail = flag.Bool("r", false, "request expired messages from the bootstrap server") + asymmetricMode = flag.Bool("a", false, "use asymmetric encryption") + testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics") + generateKey = flag.Bool("k", false, "generate and show the private key") + + argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") + argWorkTime = flag.Uint("work", 5, "work time in seconds") + argPoW = flag.Float64("pow", whisper.MinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") + argServerPoW = flag.Float64("mspow", whisper.MinimumPoW, "PoW requirement for Mail Server request") + + argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") + argSalt = flag.String("salt", "", "salt (for topic and key derivation)") + argPub = flag.String("pub", "", "public key for asymmetric encryption") + argDBPath = flag.String("dbpath", "", "path to the server's DB directory") + argIDFile = flag.String("idfile", "", "file name with node id (private key)") + argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)") + argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)") +) + +func main() { + processArgs() + initialize() + run() +} + +func processArgs() { + flag.Parse() + + if len(*argIDFile) > 0 { + var err error + nodeid, err = crypto.LoadECDSA(*argIDFile) + if err != nil { + utils.Fatalf("Failed to load file [%s]: %s.", *argIDFile, err) + } + } + + const enodePrefix = "enode://" + if len(*argEnode) > 0 { + if (*argEnode)[:len(enodePrefix)] != enodePrefix { + *argEnode = enodePrefix + *argEnode + } + } + + if len(*argTopic) > 0 { + x, err := hex.DecodeString(*argTopic) + if err != nil { + utils.Fatalf("Failed to parse the topic: %s", err) + } + topic = whisper.BytesToTopic(x) + } + + if *asymmetricMode && len(*argPub) > 0 { + pub = crypto.ToECDSAPub(common.FromHex(*argPub)) + if !isKeyValid(pub) { + utils.Fatalf("invalid public key") + } + } + + if *echoMode { + echo() + } +} + +func echo() { + fmt.Printf("ttl = %d \n", *argTTL) + fmt.Printf("workTime = %d \n", *argWorkTime) + fmt.Printf("pow = %f \n", *argPoW) + fmt.Printf("mspow = %f \n", *argServerPoW) + fmt.Printf("ip = %s \n", *argIP) + fmt.Printf("salt = %s \n", *argSalt) + fmt.Printf("pub = %s \n", common.ToHex(crypto.FromECDSAPub(pub))) + fmt.Printf("idfile = %s \n", *argIDFile) + fmt.Printf("dbpath = %s \n", *argDBPath) + fmt.Printf("boot = %s \n", *argEnode) +} + +func initialize() { + glog.SetV(logger.Warn) + glog.SetToStderr(true) + + done = make(chan struct{}) + var peers []*discover.Node + var err error + + if *generateKey { + key, err := crypto.GenerateKey() + if err != nil { + utils.Fatalf("Failed to generate private key: %s", err) + } + k := hex.EncodeToString(crypto.FromECDSA(key)) + fmt.Printf("Random private key: %s \n", k) + os.Exit(0) + } + + if *testMode { + password := []byte("test password for symmetric encryption") + salt := []byte("test salt for symmetric encryption") + symKey = pbkdf2.Key(password, salt, 64, 32, sha256.New) + topic = whisper.TopicType{0xFF, 0xFF, 0xFF, 0xFF} + msPassword = "mail server test password" + } + + if *bootstrapMode { + if len(*argIP) == 0 { + argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ") + } + } else { + if len(*argEnode) == 0 { + argEnode = scanLineA("Please enter the peer's enode: ") + } + peer := discover.MustParseNode(*argEnode) + peers = append(peers, peer) + } + + if *mailServerMode { + if len(msPassword) == 0 { + msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") + if err != nil { + utils.Fatalf("Failed to read Mail Server password: %s", err) + } + } + shh = whisper.NewWhisper(&mailServer) + mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW) + } else { + shh = whisper.NewWhisper(nil) + } + + asymKey = shh.NewIdentity() + if nodeid == nil { + nodeid = shh.NewIdentity() + } + + server = &p2p.Server{ + Config: p2p.Config{ + PrivateKey: nodeid, + MaxPeers: 128, + Name: common.MakeName("whisper-go", "5.0"), + Protocols: shh.Protocols(), + ListenAddr: *argIP, + NAT: nat.Any(), + BootstrapNodes: peers, + StaticNodes: peers, + TrustedNodes: peers, + }, + } +} + +func startServer() { + err := server.Start() + if err != nil { + utils.Fatalf("Failed to start Whisper peer: %s.", err) + } + + fmt.Printf("my public key: %s \n", common.ToHex(crypto.FromECDSAPub(&asymKey.PublicKey))) + fmt.Println(server.NodeInfo().Enode) + + if *bootstrapMode { + configureNode() + fmt.Println("Bootstrap Whisper node started") + } else { + fmt.Println("Whisper node started") + // first see if we can establish connection, then ask for user input + waitForConnection(true) + configureNode() + } + + if !*forwarderMode { + fmt.Printf("Please type the message. To quit type: '%s'\n", quitCommand) + } +} + +func isKeyValid(k *ecdsa.PublicKey) bool { + return k.X != nil && k.Y != nil +} + +func configureNode() { + var err error + var p2pAccept bool + + if *forwarderMode { + return + } + + if *asymmetricMode { + if len(*argPub) == 0 { + s := scanLine("Please enter the peer's public key: ") + pub = crypto.ToECDSAPub(common.FromHex(s)) + if !isKeyValid(pub) { + utils.Fatalf("Error: invalid public key") + } + } + } + + if *requestMail { + p2pAccept = true + if len(msPassword) == 0 { + msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") + if err != nil { + utils.Fatalf("Failed to read Mail Server password: %s", err) + } + } + } + + if !*asymmetricMode && !*forwarderMode && !*testMode { + pass, err := console.Stdin.PromptPassword("Please enter the password: ") + if err != nil { + utils.Fatalf("Failed to read passphrase: %v", err) + } + + if len(*argSalt) == 0 { + argSalt = scanLineA("Please enter the salt: ") + } + + symKey = pbkdf2.Key([]byte(pass), []byte(*argSalt), 65356, 32, sha256.New) + + if len(*argTopic) == 0 { + generateTopic([]byte(pass), []byte(*argSalt)) + } + } + + if *mailServerMode { + if len(*argDBPath) == 0 { + argDBPath = scanLineA("Please enter the path to DB file: ") + } + } + + filter := whisper.Filter{ + KeySym: symKey, + KeyAsym: asymKey, + Topics: []whisper.TopicType{topic}, + AcceptP2P: p2pAccept, + } + filterID = shh.Watch(&filter) + fmt.Printf("Filter is configured for the topic: %x \n", topic) +} + +func generateTopic(password, salt []byte) { + const rounds = 4000 + const size = 128 + x1 := pbkdf2.Key(password, salt, rounds, size, sha512.New) + x2 := pbkdf2.Key(password, salt, rounds, size, sha1.New) + x3 := pbkdf2.Key(x1, x2, rounds, size, sha256.New) + + for i := 0; i < size; i++ { + topic[i%whisper.TopicLength] ^= x3[i] + } +} + +func waitForConnection(timeout bool) { + var cnt int + var connected bool + for !connected { + time.Sleep(time.Millisecond * 50) + connected = server.PeerCount() > 0 + if timeout { + cnt++ + if cnt > 1000 { + utils.Fatalf("Timeout expired, failed to connect") + } + } + } + + fmt.Println("Connected to peer.") +} + +func run() { + defer mailServer.Close() + startServer() + defer server.Stop() + shh.Start(nil) + defer shh.Stop() + + if !*forwarderMode { + go messageLoop() + } + + if *requestMail { + requestExpiredMessagesLoop() + } else { + sendLoop() + } +} + +func sendLoop() { + for { + s := scanLine("") + if s == quitCommand { + fmt.Println("Quit command received") + close(done) + break + } + sendMsg([]byte(s)) + + if *asymmetricMode { + // print your own message for convenience, + // because in asymmetric mode it is impossible to decrypt it + hour, min, sec := time.Now().Clock() + from := crypto.PubkeyToAddress(asymKey.PublicKey) + fmt.Printf("\n%02d:%02d:%02d <%x>: %s\n", hour, min, sec, from, s) + } + } +} + +func scanLine(prompt string) string { + if len(prompt) > 0 { + fmt.Print(prompt) + } + txt, err := input.ReadString('\n') + if err != nil { + utils.Fatalf("input error: %s", err) + } + txt = strings.TrimRight(txt, "\n\r") + return txt +} + +func scanLineA(prompt string) *string { + s := scanLine(prompt) + return &s +} + +func scanUint(prompt string) uint32 { + s := scanLine(prompt) + i, err := strconv.Atoi(s) + if err != nil { + utils.Fatalf("Fail to parse the lower time limit: %s", err) + } + return uint32(i) +} + +func sendMsg(payload []byte) { + params := whisper.MessageParams{ + Src: asymKey, + Dst: pub, + KeySym: symKey, + Payload: payload, + Topic: topic, + TTL: uint32(*argTTL), + PoW: *argPoW, + WorkTime: uint32(*argWorkTime), + } + + msg := whisper.NewSentMessage(¶ms) + envelope, err := msg.Wrap(¶ms) + if err != nil { + fmt.Printf("failed to seal message: %v \n", err) + return + } + + err = shh.Send(envelope) + if err != nil { + fmt.Printf("failed to send message: %v \n", err) + } +} + +func messageLoop() { + f := shh.GetFilter(filterID) + if f == nil { + utils.Fatalf("filter is not installed") + } + + ticker := time.NewTicker(time.Millisecond * 50) + + for { + select { + case <-ticker.C: + messages := f.Retrieve() + for _, msg := range messages { + printMessageInfo(msg) + } + case <-done: + return + } + } +} + +func printMessageInfo(msg *whisper.ReceivedMessage) { + timestamp := fmt.Sprintf("%d", msg.Sent) // unix timestamp for diagnostics + text := string(msg.Payload) + + var address common.Address + if msg.Src != nil { + address = crypto.PubkeyToAddress(*msg.Src) + } + + if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { + fmt.Printf("\n%s <%x>: %s\n", timestamp, address, text) // message from myself + } else { + fmt.Printf("\n%s [%x]: %s\n", timestamp, address, text) // message from a peer + } +} + +func requestExpiredMessagesLoop() { + var key, peerID []byte + var timeLow, timeUpp uint32 + var t string + var xt, empty whisper.TopicType + + err := shh.AddSymKey(mailserver.MailServerKeyName, []byte(msPassword)) + if err != nil { + utils.Fatalf("Failed to create symmetric key for mail request: %s", err) + } + key = shh.GetSymKey(mailserver.MailServerKeyName) + peerID = extractIdFromEnode(*argEnode) + shh.MarkPeerTrusted(peerID) + + for { + timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") + timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ") + t = scanLine("Please enter the topic (hexadecimal): ") + if len(t) >= whisper.TopicLength*2 { + x, err := hex.DecodeString(t) + if err != nil { + utils.Fatalf("Failed to parse the topic: %s", err) + } + xt = whisper.BytesToTopic(x) + } + if timeUpp == 0 { + timeUpp = 0xFFFFFFFF + } + + data := make([]byte, 8+whisper.TopicLength) + binary.BigEndian.PutUint32(data, timeLow) + binary.BigEndian.PutUint32(data[4:], timeUpp) + copy(data[8:], xt[:]) + if xt == empty { + data = data[:8] + } + + var params whisper.MessageParams + params.PoW = *argServerPoW + params.Payload = data + params.KeySym = key + params.Src = nodeid + params.WorkTime = 5 + + msg := whisper.NewSentMessage(¶ms) + env, err := msg.Wrap(¶ms) + if err != nil { + utils.Fatalf("Wrap failed: %s", err) + } + + err = shh.RequestHistoricMessages(peerID, env) + if err != nil { + utils.Fatalf("Failed to send P2P message: %s", err) + } + + time.Sleep(time.Second * 5) + } +} + +func extractIdFromEnode(s string) []byte { + n, err := discover.ParseNode(s) + if err != nil { + utils.Fatalf("Failed to parse enode: %s", err) + return nil + } + return n.ID[:] +} diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go new file mode 100644 index 000000000..f7d6c3e5c --- /dev/null +++ b/whisper/mailserver/mailserver.go @@ -0,0 +1,170 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package mailserver + +import ( + "bytes" + "encoding/binary" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" + whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/util" +) + +const MailServerKeyName = "958e04ab302fb36ad2616a352cbac79d" + +type WMailServer struct { + db *leveldb.DB + w *whisper.Whisper + pow float64 + key []byte +} + +type DBKey struct { + timestamp uint32 + hash common.Hash + raw []byte +} + +func NewDbKey(t uint32, h common.Hash) *DBKey { + const sz = common.HashLength + 4 + var k DBKey + k.timestamp = t + k.hash = h + k.raw = make([]byte, sz) + binary.BigEndian.PutUint32(k.raw, k.timestamp) + copy(k.raw[4:], k.hash[:]) + return &k +} + +func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) { + var err error + if len(path) == 0 { + utils.Fatalf("DB file is not specified") + } + + if len(password) == 0 { + utils.Fatalf("Password is not specified for MailServer") + } + + s.db, err = leveldb.OpenFile(path, nil) + if err != nil { + utils.Fatalf("Failed to open DB file: %s", err) + } + + s.w = shh + s.pow = pow + + err = s.w.AddSymKey(MailServerKeyName, []byte(password)) + if err != nil { + utils.Fatalf("Failed to create symmetric key for MailServer: %s", err) + } + s.key = s.w.GetSymKey(MailServerKeyName) +} + +func (s *WMailServer) Close() { + if s.db != nil { + s.db.Close() + } +} + +func (s *WMailServer) Archive(env *whisper.Envelope) { + key := NewDbKey(env.Expiry-env.TTL, env.Hash()) + rawEnvelope, err := rlp.EncodeToBytes(env) + if err != nil { + glog.V(logger.Error).Infof("rlp.EncodeToBytes failed: %s", err) + } else { + err = s.db.Put(key.raw, rawEnvelope, nil) + if err != nil { + glog.V(logger.Error).Infof("Writing to DB failed: %s", err) + } + } +} + +func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) { + ok, lower, upper, topic := s.validate(peer, request) + if !ok { + return + } + + var err error + var zero common.Hash + var empty whisper.TopicType + kl := NewDbKey(lower, zero) + ku := NewDbKey(upper, zero) + i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil) + defer i.Release() + + for i.Next() { + var envelope whisper.Envelope + err = rlp.DecodeBytes(i.Value(), &envelope) + if err != nil { + glog.V(logger.Error).Infof("RLP decoding failed: %s", err) + } + + if topic == empty || envelope.Topic == topic { + err = s.w.SendP2PDirect(peer, &envelope) + if err != nil { + glog.V(logger.Error).Infof("Failed to send direct message to peer: %s", err) + return + } + } + } + + err = i.Error() + if err != nil { + glog.V(logger.Error).Infof("Level DB iterator error: %s", err) + } +} + +func (s *WMailServer) validate(peer *whisper.Peer, request *whisper.Envelope) (bool, uint32, uint32, whisper.TopicType) { + var topic whisper.TopicType + if s.pow > 0.0 && request.PoW() < s.pow { + return false, 0, 0, topic + } + + f := whisper.Filter{KeySym: s.key} + decrypted := request.Open(&f) + if decrypted == nil { + glog.V(logger.Warn).Infof("Failed to decrypt p2p request") + return false, 0, 0, topic + } + + if len(decrypted.Payload) < 8 { + glog.V(logger.Warn).Infof("Undersized p2p request") + return false, 0, 0, topic + } + + if bytes.Equal(peer.ID(), decrypted.Signature) { + glog.V(logger.Warn).Infof("Wrong signature of p2p request") + return false, 0, 0, topic + } + + lower := binary.BigEndian.Uint32(decrypted.Payload[:4]) + upper := binary.BigEndian.Uint32(decrypted.Payload[4:8]) + + if len(decrypted.Payload) >= 8+whisper.TopicLength { + topic = whisper.BytesToTopic(decrypted.Payload[8:]) + } + + return true, lower, upper, topic +} diff --git a/whisper/shhapi/api.go b/whisper/shhapi/api.go index 379bb90d3..b273053ec 100644 --- a/whisper/shhapi/api.go +++ b/whisper/shhapi/api.go @@ -93,12 +93,12 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error { // data contains parameters (time frame, payment details, etc.), required // by the remote email-like server. Whisper is not aware about the data format, // it will just forward the raw data to the server. -func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error { - if api.whisper == nil { - return whisperOffLineErr - } - return api.whisper.RequestHistoricMessages(peerID, data) -} +//func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error { +// if api.whisper == nil { +// return whisperOffLineErr +// } +// return api.whisper.RequestHistoricMessages(peerID, data) +//} // HasIdentity checks if the whisper node is configured with the private key // of the specified public pair. diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 8ec81b180..b82a82468 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -83,5 +83,5 @@ func (e unknownVersionError) Error() string { // in order to bypass the expiry checks. type MailServer interface { Archive(env *Envelope) - DeliverMail(whisperPeer *Peer, data []byte) + DeliverMail(whisperPeer *Peer, request *Envelope) } diff --git a/whisper/whisperv5/message_test.go b/whisper/whisperv5/message_test.go index 7c90f59c0..c6f1ca2ca 100644 --- a/whisper/whisperv5/message_test.go +++ b/whisper/whisperv5/message_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" ) func copyFromBuf(dst []byte, src []byte, beg int) int { @@ -311,3 +312,35 @@ func TestEncryptWithZeroKey(t *testing.T) { t.Fatalf("wrapped with nil key, seed: %d.", seed) } } + +func TestRlpEncode(t *testing.T) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg := NewSentMessage(params) + env, err := msg.Wrap(params) + if err != nil { + t.Fatalf("wrapped with zero key, seed: %d.", seed) + } + + raw, err := rlp.EncodeToBytes(env) + if err != nil { + t.Fatalf("RLP encode failed: %s.", err) + } + + var decoded Envelope + rlp.DecodeBytes(raw, &decoded) + if err != nil { + t.Fatalf("RLP decode failed: %s.", err) + } + + he := env.Hash() + hd := decoded.Hash() + + if he != hd { + t.Fatalf("Hashes are not equal: %x vs. %x", he, hd) + } +} diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go index 634045504..42394a0a3 100644 --- a/whisper/whisperv5/peer.go +++ b/whisper/whisperv5/peer.go @@ -175,3 +175,8 @@ func (p *Peer) broadcast() error { glog.V(logger.Detail).Infoln(p.peer, "broadcasted", len(transmit), "message(s)") return nil } + +func (p *Peer) ID() []byte { + id := p.peer.ID() + return id[:] +} diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index b514c022e..ff31aab2d 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/pbkdf2" set "gopkg.in/fatih/set.v0" ) @@ -125,13 +124,13 @@ func (w *Whisper) MarkPeerTrusted(peerID []byte) error { return nil } -func (w *Whisper) RequestHistoricMessages(peerID []byte, data []byte) error { +func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error { p, err := w.getPeer(peerID) if err != nil { return err } p.trusted = true - return p2p.Send(p.ws, p2pRequestCode, data) + return p2p.Send(p.ws, p2pRequestCode, envelope) } func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error { @@ -142,6 +141,10 @@ func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error { return p2p.Send(p.ws, p2pCode, envelope) } +func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error { + return p2p.Send(peer.ws, p2pCode, envelope) +} + // NewIdentity generates a new cryptographic identity for the client, and injects // it into the known identities for message decryption. func (w *Whisper) NewIdentity() *ecdsa.PrivateKey { @@ -347,9 +350,6 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { return fmt.Errorf("invalid envelope") } p.mark(envelope) - if wh.mailServer != nil { - wh.mailServer.Archive(envelope) - } } case p2pCode: // peer-to-peer message, sent directly to peer bypassing PoW checks, etc. @@ -357,25 +357,22 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { // therefore might not satisfy the PoW, expiry and other requirements. // these messages are only accepted from the trusted peer. if p.trusted { - var envelopes []*Envelope - if err := packet.Decode(&envelopes); err != nil { + var envelope Envelope + if err := packet.Decode(&envelope); err != nil { glog.V(logger.Warn).Infof("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err) return fmt.Errorf("garbage received (directMessage)") } - for _, envelope := range envelopes { - wh.postEvent(envelope, true) - } + wh.postEvent(&envelope, true) } case p2pRequestCode: // Must be processed if mail server is implemented. Otherwise ignore. if wh.mailServer != nil { - s := rlp.NewStream(packet.Payload, uint64(packet.Size)) - data, err := s.Bytes() - if err == nil { - wh.mailServer.DeliverMail(p, data) - } else { - glog.V(logger.Error).Infof("%v: bad requestHistoricMessages received: [%v]", p.peer, err) + var request Envelope + if err := packet.Decode(&request); err != nil { + glog.V(logger.Warn).Infof("%v: failed to decode p2p request message: [%v], peer will be disconnected", p.peer, err) + return fmt.Errorf("garbage received (p2p request)") } + wh.mailServer.DeliverMail(p, &request) } default: // New message types might be implemented in the future versions of Whisper. @@ -454,6 +451,9 @@ func (wh *Whisper) add(envelope *Envelope) error { } else { glog.V(logger.Detail).Infof("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope) wh.postEvent(envelope, false) // notify the local node about the new message + if wh.mailServer != nil { + wh.mailServer.Archive(envelope) + } } return nil } diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index b9cd9b1a0..a3ded7b37 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -57,12 +57,6 @@ func TestWhisperBasic(t *testing.T) { if err := w.MarkPeerTrusted(peerID); err == nil { t.Fatalf("failed MarkPeerTrusted.") } - if err := w.RequestHistoricMessages(peerID, peerID); err == nil { - t.Fatalf("failed RequestHistoricMessages.") - } - if err := w.SendP2PMessage(peerID, nil); err == nil { - t.Fatalf("failed SendP2PMessage.") - } exist := w.HasSymKey("non-existing") if exist { t.Fatalf("failed HasSymKey.")