aboutsummaryrefslogblamecommitdiffstats
path: root/swarm/pss/pss_test.go
blob: ea7a591b1e3c719241f575c4f1ac9167316b1e59 (plain) (tree)







































                                                                                  

                                              
                                                   




                                                                  
                                                   
                                                     
                                                                   


     










                                                                                              












                                                     

                                                                                    
                                                                          
























































































                                                                                                           
                       









































































































                                                                                                                  
                               





                                                                                    
                                                      







                                                                                                    
                                                       







                                                                                                     
                                                       

                                                                                                     
 

 



































































































                                                                                                   
                                                                          















                                                                      
                                                                         

































































                                                                                                                                                        

                                                                         
                       
                                

                                











































































































































                                                                                                                        










                                                                                                    
                       

                              





                                                                    
         
                                                                    















                                                                                                    
                                                                    





















                                                                                         
                                                                    





















                                                                                         
                                                                    


















                                                                         
                                                                           








                                                                        
                                                  




























                                                                              
                       





                                                                                               

                                                                          




                                                                                       
                                                                     





















                                                                                                   

                                                                                     


         
                                                                                    






                                            
                       


















































                                                                                                                           
                                                                          
                                               






                                            
                         









                                                                             
                             



                                                                                                                                 






                                                
                            



                                                                                                                           


                                                  
                                          
                            
                                       








                                                                    
                       






                                                                                                  










                                                                                                                                                              
                       

























































                                                                                              



























                                                                                                     
                                                                   










































                                                                                     
                                                                                            




                                                                                  
                                                                                            




                                     
                                                                                         











                                                                                                        
                                                                                         








































































                                                                                                            
                                                                                             




                                                                                  
                                                                                             











































































































                                                                                                                                       
                                                                                             




                                                                                  
                                                                                             











































                                                                                                            

                         

 
                                                                                                                




                                                                                                             



                                           



                                                                


                                                                                    






                                           


                                                                                    






                                           


                                                                                    





                                           







                                                                                                        



                                                         


                                            
                                                         
 
                                      






































                                                                                       
                                                                                                              


                                                                                       
                                                                                                 










































































































































































                                                                                                                                                  
                                                                                             


























































                                                                                                       
                       




                                                
                                                                 






                                                              
                                                    





























                                                                                                       
                       




                                                
                                                          











































                                                                                                            
                       



                                                        
                                                                     























                                                                                

                                             







                                                  
                                                                                                          










































                                                                                                            
                       


                                                          
                                                                          

























                                                                        

                                     





                                              
                                                                        



















































                                                                                         

                                                         


                                               
                                                
                                            




                                              
                                                                  






























                                                                                                                          





                                                         

















                                                                                      
                                                                  











                                                                                                         
                                                                                            
                                                     
                                                         
                       
                                            
                                        
                                                     






                                                                    
                                  
















                                     


                                                                                                                                                           


                                       
                                                                                              








                                                         
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package pss

import (
    "bytes"
    "context"
    "crypto/ecdsa"
    "encoding/binary"
    "encoding/hex"
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "sync"
    "testing"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/log"
    "github.com/ethereum/go-ethereum/node"
    "github.com/ethereum/go-ethereum/p2p"
    "github.com/ethereum/go-ethereum/p2p/enode"
    "github.com/ethereum/go-ethereum/p2p/protocols"
    "github.com/ethereum/go-ethereum/p2p/simulations"
    "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    "github.com/ethereum/go-ethereum/rpc"
    "github.com/ethereum/go-ethereum/swarm/network"
    "github.com/ethereum/go-ethereum/swarm/pot"
    "github.com/ethereum/go-ethereum/swarm/state"
    whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
)

var (
    initOnce        = sync.Once{}
    loglevel        = flag.Int("loglevel", 2, "logging verbosity")
    longrunning     = flag.Bool("longrunning", false, "do run long-running tests")
    w               *whisper.Whisper
    wapi            *whisper.PublicWhisperAPI
    psslogmain      log.Logger
    pssprotocols    map[string]*protoCtrl
    useHandshake    bool
    noopHandlerFunc = func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error {
        return nil
    }
)

func init() {
    flag.Parse()
    rand.Seed(time.Now().Unix())

    adapters.RegisterServices(newServices(false))
    initTest()
}

func initTest() {
    initOnce.Do(
        func() {
            psslogmain = log.New("psslog", "*")
            hs := log.StreamHandler(os.Stderr, log.TerminalFormat(true))
            hf := log.LvlFilterHandler(log.Lvl(*loglevel), hs)
            h := log.CallerFileHandler(hf)
            log.Root().SetHandler(h)

            w = whisper.New(&whisper.DefaultConfig)
            wapi = whisper.NewPublicWhisperAPI(w)

            pssprotocols = make(map[string]*protoCtrl)
        },
    )
}

// test that topic conversion functions give predictable results
func TestTopic(t *testing.T) {

    api := &API{}

    topicstr := strings.Join([]string{PingProtocol.Name, strconv.Itoa(int(PingProtocol.Version))}, ":")

    // bytestotopic is the authoritative topic conversion source
    topicobj := BytesToTopic([]byte(topicstr))

    // string to topic and bytes to topic must match
    topicapiobj, _ := api.StringToTopic(topicstr)
    if topicobj != topicapiobj {
        t.Fatalf("bytes and string topic conversion mismatch; %s != %s", topicobj, topicapiobj)
    }

    // string representation of topichex
    topichex := topicobj.String()

    // protocoltopic wrapper on pingtopic should be same as topicstring
    // check that it matches
    pingtopichex := PingTopic.String()
    if topichex != pingtopichex {
        t.Fatalf("protocol topic conversion mismatch; %s != %s", topichex, pingtopichex)
    }

    // json marshal of topic
    topicjsonout, err := topicobj.MarshalJSON()
    if err != nil {
        t.Fatal(err)
    }
    if string(topicjsonout)[1:len(topicjsonout)-1] != topichex {
        t.Fatalf("topic json marshal mismatch; %s != \"%s\"", topicjsonout, topichex)
    }

    // json unmarshal of topic
    var topicjsonin Topic
    topicjsonin.UnmarshalJSON(topicjsonout)
    if topicjsonin != topicobj {
        t.Fatalf("topic json unmarshal mismatch: %x != %x", topicjsonin, topicobj)
    }
}

// test bit packing of message control flags
func TestMsgParams(t *testing.T) {
    var ctrl byte
    ctrl |= pssControlRaw
    p := newMsgParamsFromBytes([]byte{ctrl})
    m := newPssMsg(p)
    if !m.isRaw() || m.isSym() {
        t.Fatal("expected raw=true and sym=false")
    }
    ctrl |= pssControlSym
    p = newMsgParamsFromBytes([]byte{ctrl})
    m = newPssMsg(p)
    if !m.isRaw() || !m.isSym() {
        t.Fatal("expected raw=true and sym=true")
    }
    ctrl &= 0xff &^ pssControlRaw
    p = newMsgParamsFromBytes([]byte{ctrl})
    m = newPssMsg(p)
    if m.isRaw() || !m.isSym() {
        t.Fatal("expected raw=false and sym=true")
    }
}

// test if we can insert into cache, match items with cache and cache expiry
func TestCache(t *testing.T) {
    var err error
    to, _ := hex.DecodeString("08090a0b0c0d0e0f1011121314150001020304050607161718191a1b1c1d1e1f")
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    keys, err := wapi.NewKeyPair(ctx)
    privkey, err := w.GetPrivateKey(keys)
    if err != nil {
        t.Fatal(err)
    }
    ps := newTestPss(privkey, nil, nil)
    defer ps.Stop()
    pp := NewPssParams().WithPrivateKey(privkey)
    data := []byte("foo")
    datatwo := []byte("bar")
    datathree := []byte("baz")
    wparams := &whisper.MessageParams{
        TTL:      defaultWhisperTTL,
        Src:      privkey,
        Dst:      &privkey.PublicKey,
        Topic:    whisper.TopicType(PingTopic),
        WorkTime: defaultWhisperWorkTime,
        PoW:      defaultWhisperPoW,
        Payload:  data,
    }
    woutmsg, err := whisper.NewSentMessage(wparams)
    env, err := woutmsg.Wrap(wparams)
    msg := &PssMsg{
        Payload: env,
        To:      to,
    }
    wparams.Payload = datatwo
    woutmsg, err = whisper.NewSentMessage(wparams)
    envtwo, err := woutmsg.Wrap(wparams)
    msgtwo := &PssMsg{
        Payload: envtwo,
        To:      to,
    }
    wparams.Payload = datathree
    woutmsg, err = whisper.NewSentMessage(wparams)
    envthree, err := woutmsg.Wrap(wparams)
    msgthree := &PssMsg{
        Payload: envthree,
        To:      to,
    }

    digest := ps.digest(msg)
    if err != nil {
        t.Fatalf("could not store cache msgone: %v", err)
    }
    digesttwo := ps.digest(msgtwo)
    if err != nil {
        t.Fatalf("could not store cache msgtwo: %v", err)
    }
    digestthree := ps.digest(msgthree)
    if err != nil {
        t.Fatalf("could not store cache msgthree: %v", err)
    }

    if digest == digesttwo {
        t.Fatalf("different msgs return same hash: %d", digesttwo)
    }

    // check the cache
    err = ps.addFwdCache(msg)
    if err != nil {
        t.Fatalf("write to pss expire cache failed: %v", err)
    }

    if !ps.checkFwdCache(msg) {
        t.Fatalf("message %v should have EXPIRE record in cache but checkCache returned false", msg)
    }

    if ps.checkFwdCache(msgtwo) {
        t.Fatalf("message %v should NOT have EXPIRE record in cache but checkCache returned true", msgtwo)
    }

    time.Sleep(pp.CacheTTL + 1*time.Second)
    err = ps.addFwdCache(msgthree)
    if err != nil {
        t.Fatalf("write to pss expire cache failed: %v", err)
    }

    if ps.checkFwdCache(msg) {
        t.Fatalf("message %v should have expired from cache but checkCache returned true", msg)
    }

    if _, ok := ps.fwdCache[digestthree]; !ok {
        t.Fatalf("unexpired message should be in the cache: %v", digestthree)
    }

    if _, ok := ps.fwdCache[digesttwo]; ok {
        t.Fatalf("expired message should have been cleared from the cache: %v", digesttwo)
    }
}

// matching of address hints; whether a message could be or is for the node
func TestAddressMatch(t *testing.T) {

    localaddr := network.RandomAddr().Over()
    copy(localaddr[:8], []byte("deadbeef"))
    remoteaddr := []byte("feedbeef")
    kadparams := network.NewKadParams()
    kad := network.NewKademlia(localaddr, kadparams)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    keys, err := wapi.NewKeyPair(ctx)
    if err != nil {
        t.Fatalf("Could not generate private key: %v", err)
    }
    privkey, err := w.GetPrivateKey(keys)
    pssp := NewPssParams().WithPrivateKey(privkey)
    ps, err := NewPss(kad, pssp)
    if err != nil {
        t.Fatal(err.Error())
    }

    pssmsg := &PssMsg{
        To: remoteaddr,
    }

    // differ from first byte
    if ps.isSelfRecipient(pssmsg) {
        t.Fatalf("isSelfRecipient true but %x != %x", remoteaddr, localaddr)
    }
    if ps.isSelfPossibleRecipient(pssmsg, false) {
        t.Fatalf("isSelfPossibleRecipient true but %x != %x", remoteaddr[:8], localaddr[:8])
    }

    // 8 first bytes same
    copy(remoteaddr[:4], localaddr[:4])
    if ps.isSelfRecipient(pssmsg) {
        t.Fatalf("isSelfRecipient true but %x != %x", remoteaddr, localaddr)
    }
    if !ps.isSelfPossibleRecipient(pssmsg, false) {
        t.Fatalf("isSelfPossibleRecipient false but %x == %x", remoteaddr[:8], localaddr[:8])
    }

    // all bytes same
    pssmsg.To = localaddr
    if !ps.isSelfRecipient(pssmsg) {
        t.Fatalf("isSelfRecipient false but %x == %x", remoteaddr, localaddr)
    }
    if !ps.isSelfPossibleRecipient(pssmsg, false) {
        t.Fatalf("isSelfPossibleRecipient false but %x == %x", remoteaddr[:8], localaddr[:8])
    }

}

// test that message is handled by sender if a prox handler exists and sender is in prox of message
func TestProxShortCircuit(t *testing.T) {

    // sender node address
    localAddr := network.RandomAddr().Over()
    localPotAddr := pot.NewAddressFromBytes(localAddr)

    // set up kademlia
    kadParams := network.NewKadParams()
    kad := network.NewKademlia(localAddr, kadParams)
    peerCount := kad.MinBinSize + 1

    // set up pss
    privKey, err := crypto.GenerateKey()
    pssp := NewPssParams().WithPrivateKey(privKey)
    ps, err := NewPss(kad, pssp)
    if err != nil {
        t.Fatal(err.Error())
    }

    // create kademlia peers, so we have peers both inside and outside minproxlimit
    var peers []*network.Peer
    proxMessageAddress := pot.RandomAddressAt(localPotAddr, peerCount).Bytes()
    distantMessageAddress := pot.RandomAddressAt(localPotAddr, 0).Bytes()

    for i := 0; i < peerCount; i++ {
        rw := &p2p.MsgPipeRW{}
        ptpPeer := p2p.NewPeer(enode.ID{}, "wanna be with me? [ ] yes [ ] no", []p2p.Cap{})
        protoPeer := protocols.NewPeer(ptpPeer, rw, &protocols.Spec{})
        peerAddr := pot.RandomAddressAt(localPotAddr, i)
        bzzPeer := &network.BzzPeer{
            Peer: protoPeer,
            BzzAddr: &network.BzzAddr{
                OAddr: peerAddr.Bytes(),
                UAddr: []byte(fmt.Sprintf("%x", peerAddr[:])),
            },
        }
        peer := network.NewPeer(bzzPeer, kad)
        kad.On(peer)
        peers = append(peers, peer)
    }

    // register it marking prox capability
    delivered := make(chan struct{})
    rawHandlerFunc := func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error {
        log.Trace("in allowraw handler")
        delivered <- struct{}{}
        return nil
    }
    topic := BytesToTopic([]byte{0x2a})
    hndlrProxDereg := ps.Register(&topic, &handler{
        f: rawHandlerFunc,
        caps: &handlerCaps{
            raw:  true,
            prox: true,
        },
    })
    defer hndlrProxDereg()

    // send message too far away for sender to be in prox
    // reception of this message should time out
    errC := make(chan error)
    go func() {
        err := ps.SendRaw(distantMessageAddress, topic, []byte("foo"))
        if err != nil {
            errC <- err
        }
    }()

    ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
    defer cancel()
    select {
    case <-delivered:
        t.Fatal("raw distant message delivered")
    case err := <-errC:
        t.Fatal(err)
    case <-ctx.Done():
    }

    // send message that should be within sender prox
    // this message should be delivered
    go func() {
        err := ps.SendRaw(proxMessageAddress, topic, []byte("bar"))
        if err != nil {
            errC <- err
        }
    }()

    ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
    defer cancel()
    select {
    case <-delivered:
    case err := <-errC:
        t.Fatal(err)
    case <-ctx.Done():
        t.Fatal("raw timeout")
    }

    // try the same prox message with sym and asym send
    proxAddrPss := PssAddress(proxMessageAddress)
    symKeyId, err := ps.GenerateSymmetricKey(topic, proxAddrPss, true)
    go func() {
        err := ps.SendSym(symKeyId, topic, []byte("baz"))
        if err != nil {
            errC <- err
        }
    }()
    ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
    defer cancel()
    select {
    case <-delivered:
    case err := <-errC:
        t.Fatal(err)
    case <-ctx.Done():
        t.Fatal("sym timeout")
    }

    err = ps.SetPeerPublicKey(&privKey.PublicKey, topic, proxAddrPss)
    if err != nil {
        t.Fatal(err)
    }
    pubKeyId := hexutil.Encode(crypto.FromECDSAPub(&privKey.PublicKey))
    go func() {
        err := ps.SendAsym(pubKeyId, topic, []byte("xyzzy"))
        if err != nil {
            errC <- err
        }
    }()
    ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
    defer cancel()
    select {
    case <-delivered:
    case err := <-errC:
        t.Fatal(err)
    case <-ctx.Done():
        t.Fatal("asym timeout")
    }
}

// verify that node can be set as recipient regardless of explicit message address match if minimum one handler of a topic is explicitly set to allow it
// note that in these tests we use the raw capability on handlers for convenience
func TestAddressMatchProx(t *testing.T) {

    // recipient node address
    localAddr := network.RandomAddr().Over()
    localPotAddr := pot.NewAddressFromBytes(localAddr)

    // set up kademlia
    kadparams := network.NewKadParams()
    kad := network.NewKademlia(localAddr, kadparams)
    nnPeerCount := kad.MinBinSize
    peerCount := nnPeerCount + 2

    // set up pss
    privKey, err := crypto.GenerateKey()
    pssp := NewPssParams().WithPrivateKey(privKey)
    ps, err := NewPss(kad, pssp)
    if err != nil {
        t.Fatal(err.Error())
    }

    // create kademlia peers, so we have peers both inside and outside minproxlimit
    var peers []*network.Peer
    for i := 0; i < peerCount; i++ {
        rw := &p2p.MsgPipeRW{}
        ptpPeer := p2p.NewPeer(enode.ID{}, "362436 call me anytime", []p2p.Cap{})
        protoPeer := protocols.NewPeer(ptpPeer, rw, &protocols.Spec{})
        peerAddr := pot.RandomAddressAt(localPotAddr, i)
        bzzPeer := &network.BzzPeer{
            Peer: protoPeer,
            BzzAddr: &network.BzzAddr{
                OAddr: peerAddr.Bytes(),
                UAddr: []byte(fmt.Sprintf("%x", peerAddr[:])),
            },
        }
        peer := network.NewPeer(bzzPeer, kad)
        kad.On(peer)
        peers = append(peers, peer)
    }

    // TODO: create a test in the network package to make a table with n peers where n-m are proxpeers
    // meanwhile test regression for kademlia since we are compiling the test parameters from different packages
    var proxes int
    var conns int
    depth := kad.NeighbourhoodDepth()
    kad.EachConn(nil, peerCount, func(p *network.Peer, po int) bool {
        conns++
        if po >= depth {
            proxes++
        }
        return true
    })
    if proxes != nnPeerCount {
        t.Fatalf("expected %d proxpeers, have %d", nnPeerCount, proxes)
    } else if conns != peerCount {
        t.Fatalf("expected %d peers total, have %d", peerCount, proxes)
    }

    // remote address distances from localAddr to try and the expected outcomes if we use prox handler
    remoteDistances := []int{
        255,
        nnPeerCount + 1,
        nnPeerCount,
        nnPeerCount - 1,
        0,
    }
    expects := []bool{
        true,
        true,
        true,
        false,
        false,
    }

    // first the unit test on the method that calculates possible receipient using prox
    for i, distance := range remoteDistances {
        pssMsg := newPssMsg(&msgParams{})
        pssMsg.To = make([]byte, len(localAddr))
        copy(pssMsg.To, localAddr)
        var byteIdx = distance / 8
        pssMsg.To[byteIdx] ^= 1 << uint(7-(distance%8))
        log.Trace(fmt.Sprintf("addrmatch %v", bytes.Equal(pssMsg.To, localAddr)))
        if ps.isSelfPossibleRecipient(pssMsg, true) != expects[i] {
            t.Fatalf("expected distance %d to be %v", distance, expects[i])
        }
    }

    // we move up to higher level and test the actual message handler
    // for each distance check if we are possible recipient when prox variant is used is set

    // this handler will increment a counter for every message that gets passed to the handler
    var receives int
    rawHandlerFunc := func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error {
        log.Trace("in allowraw handler")
        receives++
        return nil
    }

    // register it marking prox capability
    topic := BytesToTopic([]byte{0x2a})
    hndlrProxDereg := ps.Register(&topic, &handler{
        f: rawHandlerFunc,
        caps: &handlerCaps{
            raw:  true,
            prox: true,
        },
    })

    // test the distances
    var prevReceive int
    for i, distance := range remoteDistances {
        remotePotAddr := pot.RandomAddressAt(localPotAddr, distance)
        remoteAddr := remotePotAddr.Bytes()

        var data [32]byte
        rand.Read(data[:])
        pssMsg := newPssMsg(&msgParams{raw: true})
        pssMsg.To = remoteAddr
        pssMsg.Expire = uint32(time.Now().Unix() + 4200)
        pssMsg.Payload = &whisper.Envelope{
            Topic: whisper.TopicType(topic),
            Data:  data[:],
        }

        log.Trace("withprox addrs", "local", localAddr, "remote", remoteAddr)
        ps.handlePssMsg(context.TODO(), pssMsg)
        if (!expects[i] && prevReceive != receives) || (expects[i] && prevReceive == receives) {
            t.Fatalf("expected distance %d recipient %v when prox is set for handler", distance, expects[i])
        }
        prevReceive = receives
    }

    // now add a non prox-capable handler and test
    ps.Register(&topic, &handler{
        f: rawHandlerFunc,
        caps: &handlerCaps{
            raw: true,
        },
    })
    receives = 0
    prevReceive = 0
    for i, distance := range remoteDistances {
        remotePotAddr := pot.RandomAddressAt(localPotAddr, distance)
        remoteAddr := remotePotAddr.Bytes()

        var data [32]byte
        rand.Read(data[:])
        pssMsg := newPssMsg(&msgParams{raw: true})
        pssMsg.To = remoteAddr
        pssMsg.Expire = uint32(time.Now().Unix() + 4200)
        pssMsg.Payload = &whisper.Envelope{
            Topic: whisper.TopicType(topic),
            Data:  data[:],
        }

        log.Trace("withprox addrs", "local", localAddr, "remote", remoteAddr)
        ps.handlePssMsg(context.TODO(), pssMsg)
        if (!expects[i] && prevReceive != receives) || (expects[i] && prevReceive == receives) {
            t.Fatalf("expected distance %d recipient %v when prox is set for handler", distance, expects[i])
        }
        prevReceive = receives
    }

    // now deregister the prox capable handler, now none of the messages will be handled
    hndlrProxDereg()
    receives = 0

    for _, distance := range remoteDistances {
        remotePotAddr := pot.RandomAddressAt(localPotAddr, distance)
        remoteAddr := remotePotAddr.Bytes()

        pssMsg := newPssMsg(&msgParams{raw: true})
        pssMsg.To = remoteAddr
        pssMsg.Expire = uint32(time.Now().Unix() + 4200)
        pssMsg.Payload = &whisper.Envelope{
            Topic: whisper.TopicType(topic),
            Data:  []byte(remotePotAddr.String()),
        }

        log.Trace("noprox addrs", "local", localAddr, "remote", remoteAddr)
        ps.handlePssMsg(context.TODO(), pssMsg)
        if receives != 0 {
            t.Fatalf("expected distance %d to not be recipient when prox is not set for handler", distance)
        }

    }
}

// verify that message queueing happens when it should, and that expired and corrupt messages are dropped
func TestMessageProcessing(t *testing.T) {

    t.Skip("Disabled due to probable faulty logic for outbox expectations")
    // setup
    privkey, err := crypto.GenerateKey()
    if err != nil {
        t.Fatal(err.Error())
    }

    addr := make([]byte, 32)
    addr[0] = 0x01
    ps := newTestPss(privkey, network.NewKademlia(addr, network.NewKadParams()), NewPssParams())
    defer ps.Stop()

    // message should pass
    msg := newPssMsg(&msgParams{})
    msg.To = addr
    msg.Expire = uint32(time.Now().Add(time.Second * 60).Unix())
    msg.Payload = &whisper.Envelope{
        Topic: [4]byte{},
        Data:  []byte{0x66, 0x6f, 0x6f},
    }
    if err := ps.handlePssMsg(context.TODO(), msg); err != nil {
        t.Fatal(err.Error())
    }
    tmr := time.NewTimer(time.Millisecond * 100)
    var outmsg *PssMsg
    select {
    case outmsg = <-ps.outbox:
    case <-tmr.C:
    default:
    }
    if outmsg != nil {
        t.Fatalf("expected outbox empty after full address on msg, but had message %s", msg)
    }

    // message should pass and queue due to partial length
    msg.To = addr[0:1]
    msg.Payload.Data = []byte{0x78, 0x79, 0x80, 0x80, 0x79}
    if err := ps.handlePssMsg(context.TODO(), msg); err != nil {
        t.Fatal(err.Error())
    }
    tmr.Reset(time.Millisecond * 100)
    outmsg = nil
    select {
    case outmsg = <-ps.outbox:
    case <-tmr.C:
    }
    if outmsg == nil {
        t.Fatal("expected message in outbox on encrypt fail, but empty")
    }
    outmsg = nil
    select {
    case outmsg = <-ps.outbox:
    default:
    }
    if outmsg != nil {
        t.Fatalf("expected only one queued message but also had message %v", msg)
    }

    // full address mismatch should put message in queue
    msg.To[0] = 0xff
    if err := ps.handlePssMsg(context.TODO(), msg); err != nil {
        t.Fatal(err.Error())
    }
    tmr.Reset(time.Millisecond * 10)
    outmsg = nil
    select {
    case outmsg = <-ps.outbox:
    case <-tmr.C:
    }
    if outmsg == nil {
        t.Fatal("expected message in outbox on address mismatch, but empty")
    }
    outmsg = nil
    select {
    case outmsg = <-ps.outbox:
    default:
    }
    if outmsg != nil {
        t.Fatalf("expected only one queued message but also had message %v", msg)
    }

    // expired message should be dropped
    msg.Expire = uint32(time.Now().Add(-time.Second).Unix())
    if err := ps.handlePssMsg(context.TODO(), msg); err != nil {
        t.Fatal(err.Error())
    }
    tmr.Reset(time.Millisecond * 10)
    outmsg = nil
    select {
    case outmsg = <-ps.outbox:
    case <-tmr.C:
    default:
    }
    if outmsg != nil {
        t.Fatalf("expected empty queue but have message %v", msg)
    }

    // invalid message should return error
    fckedupmsg := &struct {
        pssMsg *PssMsg
    }{
        pssMsg: &PssMsg{},
    }
    if err := ps.handlePssMsg(context.TODO(), fckedupmsg); err == nil {
        t.Fatalf("expected error from processMsg but error nil")
    }

    // outbox full should return error
    msg.Expire = uint32(time.Now().Add(time.Second * 60).Unix())
    for i := 0; i < defaultOutboxCapacity; i++ {
        ps.outbox <- msg
    }
    msg.Payload.Data = []byte{0x62, 0x61, 0x72}
    err = ps.handlePssMsg(context.TODO(), msg)
    if err == nil {
        t.Fatal("expected error when mailbox full, but was nil")
    }
}

// set and generate pubkeys and symkeys
func TestKeys(t *testing.T) {
    // make our key and init pss with it
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    ourkeys, err := wapi.NewKeyPair(ctx)
    if err != nil {
        t.Fatalf("create 'our' key fail")
    }
    ctx, cancel2 := context.WithTimeout(context.Background(), time.Second)
    defer cancel2()
    theirkeys, err := wapi.NewKeyPair(ctx)
    if err != nil {
        t.Fatalf("create 'their' key fail")
    }
    ourprivkey, err := w.GetPrivateKey(ourkeys)
    if err != nil {
        t.Fatalf("failed to retrieve 'our' private key")
    }
    theirprivkey, err := w.GetPrivateKey(theirkeys)
    if err != nil {
        t.Fatalf("failed to retrieve 'their' private key")
    }
    ps := newTestPss(ourprivkey, nil, nil)
    defer ps.Stop()

    // set up peer with mock address, mapped to mocked publicaddress and with mocked symkey
    addr := make(PssAddress, 32)
    copy(addr, network.RandomAddr().Over())
    outkey := network.RandomAddr().Over()
    topicobj := BytesToTopic([]byte("foo:42"))
    ps.SetPeerPublicKey(&theirprivkey.PublicKey, topicobj, addr)
    outkeyid, err := ps.SetSymmetricKey(outkey, topicobj, addr, false)
    if err != nil {
        t.Fatalf("failed to set 'our' outgoing symmetric key")
    }

    // make a symmetric key that we will send to peer for encrypting messages to us
    inkeyid, err := ps.GenerateSymmetricKey(topicobj, addr, true)
    if err != nil {
        t.Fatalf("failed to set 'our' incoming symmetric key")
    }

    // get the key back from whisper, check that it's still the same
    outkeyback, err := ps.w.GetSymKey(outkeyid)
    if err != nil {
        t.Fatalf(err.Error())
    }
    inkey, err := ps.w.GetSymKey(inkeyid)
    if err != nil {
        t.Fatalf(err.Error())
    }
    if !bytes.Equal(outkeyback, outkey) {
        t.Fatalf("passed outgoing symkey doesnt equal stored: %x / %x", outkey, outkeyback)
    }

    t.Logf("symout: %v", outkeyback)
    t.Logf("symin: %v", inkey)

    // check that the key is stored in the peerpool
    psp := ps.symKeyPool[inkeyid][topicobj]
    if !bytes.Equal(psp.address, addr) {
        t.Fatalf("inkey address does not match; %p != %p", psp.address, addr)
    }
}

// check that we can retrieve previously added public key entires per topic and peer
func TestGetPublickeyEntries(t *testing.T) {

    privkey, err := crypto.GenerateKey()
    if err != nil {
        t.Fatal(err)
    }
    ps := newTestPss(privkey, nil, nil)
    defer ps.Stop()

    peeraddr := network.RandomAddr().Over()
    topicaddr := make(map[Topic]PssAddress)
    topicaddr[Topic{0x13}] = peeraddr
    topicaddr[Topic{0x2a}] = peeraddr[:16]
    topicaddr[Topic{0x02, 0x9a}] = []byte{}

    remoteprivkey, err := crypto.GenerateKey()
    if err != nil {
        t.Fatal(err)
    }
    remotepubkeybytes := crypto.FromECDSAPub(&remoteprivkey.PublicKey)
    remotepubkeyhex := common.ToHex(remotepubkeybytes)

    pssapi := NewAPI(ps)

    for to, a := range topicaddr {
        err = pssapi.SetPeerPublicKey(remotepubkeybytes, to, a)
        if err != nil {
            t.Fatal(err)
        }
    }

    intopic, err := pssapi.GetPeerTopics(remotepubkeyhex)
    if err != nil {
        t.Fatal(err)
    }

OUTER:
    for _, tnew := range intopic {
        for torig, addr := range topicaddr {
            if bytes.Equal(torig[:], tnew[:]) {
                inaddr, err := pssapi.GetPeerAddress(remotepubkeyhex, torig)
                if err != nil {
                    t.Fatal(err)
                }
                if !bytes.Equal(addr, inaddr) {
                    t.Fatalf("Address mismatch for topic %x; got %x, expected %x", torig, inaddr, addr)
                }
                delete(topicaddr, torig)
                continue OUTER
            }
        }
        t.Fatalf("received topic %x did not match any existing topics", tnew)
    }

    if len(topicaddr) != 0 {
        t.Fatalf("%d topics were not matched", len(topicaddr))
    }
}

// forwarding should skip peers that do not have matching pss capabilities
func TestPeerCapabilityMismatch(t *testing.T) {

    // create privkey for forwarder node
    privkey, err := crypto.GenerateKey()
    if err != nil {
        t.Fatal(err)
    }

    // initialize kad
    baseaddr := network.RandomAddr()
    kad := network.NewKademlia((baseaddr).Over(), network.NewKadParams())
    rw := &p2p.MsgPipeRW{}

    // one peer has a mismatching version of pss
    wrongpssaddr := network.RandomAddr()
    wrongpsscap := p2p.Cap{
        Name:    pssProtocolName,
        Version: 0,
    }
    nid := enode.ID{0x01}
    wrongpsspeer := network.NewPeer(&network.BzzPeer{
        Peer:    protocols.NewPeer(p2p.NewPeer(nid, common.ToHex(wrongpssaddr.Over()), []p2p.Cap{wrongpsscap}), rw, nil),
        BzzAddr: &network.BzzAddr{OAddr: wrongpssaddr.Over(), UAddr: nil},
    }, kad)

    // one peer doesn't even have pss (boo!)
    nopssaddr := network.RandomAddr()
    nopsscap := p2p.Cap{
        Name:    "nopss",
        Version: 1,
    }
    nid = enode.ID{0x02}
    nopsspeer := network.NewPeer(&network.BzzPeer{
        Peer:    protocols.NewPeer(p2p.NewPeer(nid, common.ToHex(nopssaddr.Over()), []p2p.Cap{nopsscap}), rw, nil),
        BzzAddr: &network.BzzAddr{OAddr: nopssaddr.Over(), UAddr: nil},
    }, kad)

    // add peers to kademlia and activate them
    // it's safe so don't check errors
    kad.Register(wrongpsspeer.BzzAddr)
    kad.On(wrongpsspeer)
    kad.Register(nopsspeer.BzzAddr)
    kad.On(nopsspeer)

    // create pss
    pssmsg := &PssMsg{
        To:      []byte{},
        Expire:  uint32(time.Now().Add(time.Second).Unix()),
        Payload: &whisper.Envelope{},
    }
    ps := newTestPss(privkey, kad, nil)
    defer ps.Stop()

    // run the forward
    // it is enough that it completes; trying to send to incapable peers would create segfault
    ps.forward(pssmsg)

}

// verifies that message handlers for raw messages only are invoked when minimum one handler for the topic exists in which raw messages are explicitly allowed
func TestRawAllow(t *testing.T) {

    // set up pss like so many times before
    privKey, err := crypto.GenerateKey()
    if err != nil {
        t.Fatal(err)
    }
    baseAddr := network.RandomAddr()
    kad := network.NewKademlia((baseAddr).Over(), network.NewKadParams())
    ps := newTestPss(privKey, kad, nil)
    defer ps.Stop()
    topic := BytesToTopic([]byte{0x2a})

    // create handler innards that increments every time a message hits it
    var receives int
    rawHandlerFunc := func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error {
        log.Trace("in allowraw handler")
        receives++
        return nil
    }

    // wrap this handler function with a handler without raw capability and register it
    hndlrNoRaw := &handler{
        f: rawHandlerFunc,
    }
    ps.Register(&topic, hndlrNoRaw)

    // test it with a raw message, should be poo-poo
    pssMsg := newPssMsg(&msgParams{
        raw: true,
    })
    pssMsg.To = baseAddr.OAddr
    pssMsg.Expire = uint32(time.Now().Unix() + 4200)
    pssMsg.Payload = &whisper.Envelope{
        Topic: whisper.TopicType(topic),
    }
    ps.handlePssMsg(context.TODO(), pssMsg)
    if receives > 0 {
        t.Fatalf("Expected handler not to be executed with raw cap off")
    }

    // now wrap the same handler function with raw capabilities and register it
    hndlrRaw := &handler{
        f: rawHandlerFunc,
        caps: &handlerCaps{
            raw: true,
        },
    }
    deregRawHandler := ps.Register(&topic, hndlrRaw)

    // should work now
    pssMsg.Payload.Data = []byte("Raw Deal")
    ps.handlePssMsg(context.TODO(), pssMsg)
    if receives == 0 {
        t.Fatalf("Expected handler to be executed with raw cap on")
    }

    // now deregister the raw capable handler
    prevReceives := receives
    deregRawHandler()

    // check that raw messages fail again
    pssMsg.Payload.Data = []byte("Raw Trump")
    ps.handlePssMsg(context.TODO(), pssMsg)
    if receives != prevReceives {
        t.Fatalf("Expected handler not to be executed when raw handler is retracted")
    }
}

// BELOW HERE ARE TESTS USING THE SIMULATION FRAMEWORK

// tests that the API layer can handle edge case values
func TestApi(t *testing.T) {
    clients, err := setupNetwork(2, true)
    if err != nil {
        t.Fatal(err)
    }

    topic := "0xdeadbeef"

    err = clients[0].Call(nil, "pss_sendRaw", "0x", topic, "0x666f6f")
    if err != nil {
        t.Fatal(err)
    }

    err = clients[0].Call(nil, "pss_sendRaw", "0xabcdef", topic, "0x")
    if err == nil {
        t.Fatal("expected error on empty msg")
    }

    overflowAddr := [33]byte{}
    err = clients[0].Call(nil, "pss_sendRaw", hexutil.Encode(overflowAddr[:]), topic, "0x666f6f")
    if err == nil {
        t.Fatal("expected error on send too big address")
    }
}

// verifies that nodes can send and receive raw (verbatim) messages
func TestSendRaw(t *testing.T) {
    t.Run("32", testSendRaw)
    t.Run("8", testSendRaw)
    t.Run("0", testSendRaw)
}

func testSendRaw(t *testing.T) {

    var addrsize int64
    var err error

    paramstring := strings.Split(t.Name(), "/")

    addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0)
    log.Info("raw send test", "addrsize", addrsize)

    clients, err := setupNetwork(2, true)
    if err != nil {
        t.Fatal(err)
    }

    topic := "0xdeadbeef"

    var loaddrhex string
    err = clients[0].Call(&loaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 1 baseaddr fail: %v", err)
    }
    loaddrhex = loaddrhex[:2+(addrsize*2)]
    var roaddrhex string
    err = clients[1].Call(&roaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 2 baseaddr fail: %v", err)
    }
    roaddrhex = roaddrhex[:2+(addrsize*2)]

    time.Sleep(time.Millisecond * 500)

    // at this point we've verified that symkeys are saved and match on each peer
    // now try sending symmetrically encrypted message, both directions
    lmsgC := make(chan APIMsg)
    lctx, lcancel := context.WithTimeout(context.Background(), time.Second*10)
    defer lcancel()
    lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, true, false)
    log.Trace("lsub", "id", lsub)
    defer lsub.Unsubscribe()
    rmsgC := make(chan APIMsg)
    rctx, rcancel := context.WithTimeout(context.Background(), time.Second*10)
    defer rcancel()
    rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, true, false)
    log.Trace("rsub", "id", rsub)
    defer rsub.Unsubscribe()

    // send and verify delivery
    lmsg := []byte("plugh")
    err = clients[1].Call(nil, "pss_sendRaw", loaddrhex, topic, hexutil.Encode(lmsg))
    if err != nil {
        t.Fatal(err)
    }
    select {
    case recvmsg := <-lmsgC:
        if !bytes.Equal(recvmsg.Msg, lmsg) {
            t.Fatalf("node 1 received payload mismatch: expected %v, got %v", lmsg, recvmsg)
        }
    case cerr := <-lctx.Done():
        t.Fatalf("test message (left) timed out: %v", cerr)
    }
    rmsg := []byte("xyzzy")
    err = clients[0].Call(nil, "pss_sendRaw", roaddrhex, topic, hexutil.Encode(rmsg))
    if err != nil {
        t.Fatal(err)
    }
    select {
    case recvmsg := <-rmsgC:
        if !bytes.Equal(recvmsg.Msg, rmsg) {
            t.Fatalf("node 2 received payload mismatch: expected %x, got %v", rmsg, recvmsg.Msg)
        }
    case cerr := <-rctx.Done():
        t.Fatalf("test message (right) timed out: %v", cerr)
    }
}

// send symmetrically encrypted message between two directly connected peers
func TestSendSym(t *testing.T) {
    t.Run("32", testSendSym)
    t.Run("8", testSendSym)
    t.Run("0", testSendSym)
}

func testSendSym(t *testing.T) {

    // address hint size
    var addrsize int64
    var err error
    paramstring := strings.Split(t.Name(), "/")
    addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0)
    log.Info("sym send test", "addrsize", addrsize)

    clients, err := setupNetwork(2, false)
    if err != nil {
        t.Fatal(err)
    }

    var topic string
    err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42")
    if err != nil {
        t.Fatal(err)
    }

    var loaddrhex string
    err = clients[0].Call(&loaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 1 baseaddr fail: %v", err)
    }
    loaddrhex = loaddrhex[:2+(addrsize*2)]
    var roaddrhex string
    err = clients[1].Call(&roaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 2 baseaddr fail: %v", err)
    }
    roaddrhex = roaddrhex[:2+(addrsize*2)]

    // retrieve public key from pss instance
    // set this public key reciprocally
    var lpubkeyhex string
    err = clients[0].Call(&lpubkeyhex, "pss_getPublicKey")
    if err != nil {
        t.Fatalf("rpc get node 1 pubkey fail: %v", err)
    }
    var rpubkeyhex string
    err = clients[1].Call(&rpubkeyhex, "pss_getPublicKey")
    if err != nil {
        t.Fatalf("rpc get node 2 pubkey fail: %v", err)
    }

    time.Sleep(time.Millisecond * 500)

    // at this point we've verified that symkeys are saved and match on each peer
    // now try sending symmetrically encrypted message, both directions
    lmsgC := make(chan APIMsg)
    lctx, lcancel := context.WithTimeout(context.Background(), time.Second*10)
    defer lcancel()
    lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, false, false)
    log.Trace("lsub", "id", lsub)
    defer lsub.Unsubscribe()
    rmsgC := make(chan APIMsg)
    rctx, rcancel := context.WithTimeout(context.Background(), time.Second*10)
    defer rcancel()
    rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false)
    log.Trace("rsub", "id", rsub)
    defer rsub.Unsubscribe()

    lrecvkey := network.RandomAddr().Over()
    rrecvkey := network.RandomAddr().Over()

    var lkeyids [2]string
    var rkeyids [2]string

    // manually set reciprocal symkeys
    err = clients[0].Call(&lkeyids, "psstest_setSymKeys", rpubkeyhex, lrecvkey, rrecvkey, defaultSymKeySendLimit, topic, roaddrhex)
    if err != nil {
        t.Fatal(err)
    }
    err = clients[1].Call(&rkeyids, "psstest_setSymKeys", lpubkeyhex, rrecvkey, lrecvkey, defaultSymKeySendLimit, topic, loaddrhex)
    if err != nil {
        t.Fatal(err)
    }

    // send and verify delivery
    lmsg := []byte("plugh")
    err = clients[1].Call(nil, "pss_sendSym", rkeyids[1], topic, hexutil.Encode(lmsg))
    if err != nil {
        t.Fatal(err)
    }
    select {
    case recvmsg := <-lmsgC:
        if !bytes.Equal(recvmsg.Msg, lmsg) {
            t.Fatalf("node 1 received payload mismatch: expected %v, got %v", lmsg, recvmsg)
        }
    case cerr := <-lctx.Done():
        t.Fatalf("test message timed out: %v", cerr)
    }
    rmsg := []byte("xyzzy")
    err = clients[0].Call(nil, "pss_sendSym", lkeyids[1], topic, hexutil.Encode(rmsg))
    if err != nil {
        t.Fatal(err)
    }
    select {
    case recvmsg := <-rmsgC:
        if !bytes.Equal(recvmsg.Msg, rmsg) {
            t.Fatalf("node 2 received payload mismatch: expected %x, got %v", rmsg, recvmsg.Msg)
        }
    case cerr := <-rctx.Done():
        t.Fatalf("test message timed out: %v", cerr)
    }
}

// send asymmetrically encrypted message between two directly connected peers
func TestSendAsym(t *testing.T) {
    t.Run("32", testSendAsym)
    t.Run("8", testSendAsym)
    t.Run("0", testSendAsym)
}

func testSendAsym(t *testing.T) {

    // address hint size
    var addrsize int64
    var err error
    paramstring := strings.Split(t.Name(), "/")
    addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0)
    log.Info("asym send test", "addrsize", addrsize)

    clients, err := setupNetwork(2, false)
    if err != nil {
        t.Fatal(err)
    }

    var topic string
    err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42")
    if err != nil {
        t.Fatal(err)
    }

    time.Sleep(time.Millisecond * 250)

    var loaddrhex string
    err = clients[0].Call(&loaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 1 baseaddr fail: %v", err)
    }
    loaddrhex = loaddrhex[:2+(addrsize*2)]
    var roaddrhex string
    err = clients[1].Call(&roaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 2 baseaddr fail: %v", err)
    }
    roaddrhex = roaddrhex[:2+(addrsize*2)]

    // retrieve public key from pss instance
    // set this public key reciprocally
    var lpubkey string
    err = clients[0].Call(&lpubkey, "pss_getPublicKey")
    if err != nil {
        t.Fatalf("rpc get node 1 pubkey fail: %v", err)
    }
    var rpubkey string
    err = clients[1].Call(&rpubkey, "pss_getPublicKey")
    if err != nil {
        t.Fatalf("rpc get node 2 pubkey fail: %v", err)
    }

    time.Sleep(time.Millisecond * 500) // replace with hive healthy code

    lmsgC := make(chan APIMsg)
    lctx, lcancel := context.WithTimeout(context.Background(), time.Second*10)
    defer lcancel()
    lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, false, false)
    log.Trace("lsub", "id", lsub)
    defer lsub.Unsubscribe()
    rmsgC := make(chan APIMsg)
    rctx, rcancel := context.WithTimeout(context.Background(), time.Second*10)
    defer rcancel()
    rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false)
    log.Trace("rsub", "id", rsub)
    defer rsub.Unsubscribe()

    // store reciprocal public keys
    err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, roaddrhex)
    if err != nil {
        t.Fatal(err)
    }
    err = clients[1].Call(nil, "pss_setPeerPublicKey", lpubkey, topic, loaddrhex)
    if err != nil {
        t.Fatal(err)
    }

    // send and verify delivery
    rmsg := []byte("xyzzy")
    err = clients[0].Call(nil, "pss_sendAsym", rpubkey, topic, hexutil.Encode(rmsg))
    if err != nil {
        t.Fatal(err)
    }
    select {
    case recvmsg := <-rmsgC:
        if !bytes.Equal(recvmsg.Msg, rmsg) {
            t.Fatalf("node 2 received payload mismatch: expected %v, got %v", rmsg, recvmsg.Msg)
        }
    case cerr := <-rctx.Done():
        t.Fatalf("test message timed out: %v", cerr)
    }
    lmsg := []byte("plugh")
    err = clients[1].Call(nil, "pss_sendAsym", lpubkey, topic, hexutil.Encode(lmsg))
    if err != nil {
        t.Fatal(err)
    }
    select {
    case recvmsg := <-lmsgC:
        if !bytes.Equal(recvmsg.Msg, lmsg) {
            t.Fatalf("node 1 received payload mismatch: expected %v, got %v", lmsg, recvmsg.Msg)
        }
    case cerr := <-lctx.Done():
        t.Fatalf("test message timed out: %v", cerr)
    }
}

type Job struct {
    Msg      []byte
    SendNode enode.ID
    RecvNode enode.ID
}

func worker(id int, jobs <-chan Job, rpcs map[enode.ID]*rpc.Client, pubkeys map[enode.ID]string, topic string) {
    for j := range jobs {
        rpcs[j.SendNode].Call(nil, "pss_sendAsym", pubkeys[j.RecvNode], topic, hexutil.Encode(j.Msg))
    }
}

func TestNetwork(t *testing.T) {
    t.Run("16/1000/4/sim", testNetwork)
}

// params in run name:
// nodes/msgs/addrbytes/adaptertype
// if adaptertype is exec uses execadapter, simadapter otherwise
func TestNetwork2000(t *testing.T) {
    if !*longrunning {
        t.Skip("run with --longrunning flag to run extensive network tests")
    }
    t.Run("3/2000/4/sim", testNetwork)
    t.Run("4/2000/4/sim", testNetwork)
    t.Run("8/2000/4/sim", testNetwork)
    t.Run("16/2000/4/sim", testNetwork)
}

func TestNetwork5000(t *testing.T) {
    if !*longrunning {
        t.Skip("run with --longrunning flag to run extensive network tests")
    }
    t.Run("3/5000/4/sim", testNetwork)
    t.Run("4/5000/4/sim", testNetwork)
    t.Run("8/5000/4/sim", testNetwork)
    t.Run("16/5000/4/sim", testNetwork)
}

func TestNetwork10000(t *testing.T) {
    if !*longrunning {
        t.Skip("run with --longrunning flag to run extensive network tests")
    }
    t.Run("3/10000/4/sim", testNetwork)
    t.Run("4/10000/4/sim", testNetwork)
    t.Run("8/10000/4/sim", testNetwork)
}

func testNetwork(t *testing.T) {
    paramstring := strings.Split(t.Name(), "/")
    nodecount, _ := strconv.ParseInt(paramstring[1], 10, 0)
    msgcount, _ := strconv.ParseInt(paramstring[2], 10, 0)
    addrsize, _ := strconv.ParseInt(paramstring[3], 10, 0)
    adapter := paramstring[4]

    log.Info("network test", "nodecount", nodecount, "msgcount", msgcount, "addrhintsize", addrsize)

    nodes := make([]enode.ID, nodecount)
    bzzaddrs := make(map[enode.ID]string, nodecount)
    rpcs := make(map[enode.ID]*rpc.Client, nodecount)
    pubkeys := make(map[enode.ID]string, nodecount)

    sentmsgs := make([][]byte, msgcount)
    recvmsgs := make([]bool, msgcount)
    nodemsgcount := make(map[enode.ID]int, nodecount)

    trigger := make(chan enode.ID)

    var a adapters.NodeAdapter
    if adapter == "exec" {
        dirname, err := ioutil.TempDir(".", "")
        if err != nil {
            t.Fatal(err)
        }
        a = adapters.NewExecAdapter(dirname)
    } else if adapter == "tcp" {
        a = adapters.NewTCPAdapter(newServices(false))
    } else if adapter == "sim" {
        a = adapters.NewSimAdapter(newServices(false))
    }
    net := simulations.NewNetwork(a, &simulations.NetworkConfig{
        ID: "0",
    })
    defer net.Shutdown()

    f, err := os.Open(fmt.Sprintf("testdata/snapshot_%d.json", nodecount))
    if err != nil {
        t.Fatal(err)
    }
    jsonbyte, err := ioutil.ReadAll(f)
    if err != nil {
        t.Fatal(err)
    }
    var snap simulations.Snapshot
    err = json.Unmarshal(jsonbyte, &snap)
    if err != nil {
        t.Fatal(err)
    }
    err = net.Load(&snap)
    if err != nil {
        //TODO: Fix p2p simulation framework to not crash when loading 32-nodes
        //t.Fatal(err)
    }

    time.Sleep(1 * time.Second)

    triggerChecks := func(trigger chan enode.ID, id enode.ID, rpcclient *rpc.Client, topic string) error {
        msgC := make(chan APIMsg)
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        sub, err := rpcclient.Subscribe(ctx, "pss", msgC, "receive", topic, false, false)
        if err != nil {
            t.Fatal(err)
        }
        go func() {
            defer sub.Unsubscribe()
            for {
                select {
                case recvmsg := <-msgC:
                    idx, _ := binary.Uvarint(recvmsg.Msg)
                    if !recvmsgs[idx] {
                        log.Debug("msg recv", "idx", idx, "id", id)
                        recvmsgs[idx] = true
                        trigger <- id
                    }
                case <-sub.Err():
                    return
                }
            }
        }()
        return nil
    }

    var topic string
    for i, nod := range net.GetNodes() {
        nodes[i] = nod.ID()
        rpcs[nodes[i]], err = nod.Client()
        if err != nil {
            t.Fatal(err)
        }
        if topic == "" {
            err = rpcs[nodes[i]].Call(&topic, "pss_stringToTopic", "foo:42")
            if err != nil {
                t.Fatal(err)
            }
        }
        var pubkey string
        err = rpcs[nodes[i]].Call(&pubkey, "pss_getPublicKey")
        if err != nil {
            t.Fatal(err)
        }
        pubkeys[nod.ID()] = pubkey
        var addrhex string
        err = rpcs[nodes[i]].Call(&addrhex, "pss_baseAddr")
        if err != nil {
            t.Fatal(err)
        }
        bzzaddrs[nodes[i]] = addrhex
        err = triggerChecks(trigger, nodes[i], rpcs[nodes[i]], topic)
        if err != nil {
            t.Fatal(err)
        }
    }

    time.Sleep(1 * time.Second)

    // setup workers
    jobs := make(chan Job, 10)
    for w := 1; w <= 10; w++ {
        go worker(w, jobs, rpcs, pubkeys, topic)
    }

    time.Sleep(1 * time.Second)

    for i := 0; i < int(msgcount); i++ {
        sendnodeidx := rand.Intn(int(nodecount))
        recvnodeidx := rand.Intn(int(nodecount - 1))
        if recvnodeidx >= sendnodeidx {
            recvnodeidx++
        }
        nodemsgcount[nodes[recvnodeidx]]++
        sentmsgs[i] = make([]byte, 8)
        c := binary.PutUvarint(sentmsgs[i], uint64(i))
        if c == 0 {
            t.Fatal("0 byte message")
        }
        if err != nil {
            t.Fatal(err)
        }
        err = rpcs[nodes[sendnodeidx]].Call(nil, "pss_setPeerPublicKey", pubkeys[nodes[recvnodeidx]], topic, bzzaddrs[nodes[recvnodeidx]])
        if err != nil {
            t.Fatal(err)
        }

        jobs <- Job{
            Msg:      sentmsgs[i],
            SendNode: nodes[sendnodeidx],
            RecvNode: nodes[recvnodeidx],
        }
    }

    finalmsgcount := 0
    ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
    defer cancel()
outer:
    for i := 0; i < int(msgcount); i++ {
        select {
        case id := <-trigger:
            nodemsgcount[id]--
            finalmsgcount++
        case <-ctx.Done():
            log.Warn("timeout")
            break outer
        }
    }

    for i, msg := range recvmsgs {
        if !msg {
            log.Debug("missing message", "idx", i)
        }
    }
    t.Logf("%d of %d messages received", finalmsgcount, msgcount)

    if finalmsgcount != int(msgcount) {
        t.Fatalf("%d messages were not received", int(msgcount)-finalmsgcount)
    }

}

// check that in a network of a -> b -> c -> a
// a doesn't receive a sent message twice
func TestDeduplication(t *testing.T) {
    var err error

    clients, err := setupNetwork(3, false)
    if err != nil {
        t.Fatal(err)
    }

    var addrsize = 32
    var loaddrhex string
    err = clients[0].Call(&loaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 1 baseaddr fail: %v", err)
    }
    loaddrhex = loaddrhex[:2+(addrsize*2)]
    var roaddrhex string
    err = clients[1].Call(&roaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 2 baseaddr fail: %v", err)
    }
    roaddrhex = roaddrhex[:2+(addrsize*2)]
    var xoaddrhex string
    err = clients[2].Call(&xoaddrhex, "pss_baseAddr")
    if err != nil {
        t.Fatalf("rpc get node 3 baseaddr fail: %v", err)
    }
    xoaddrhex = xoaddrhex[:2+(addrsize*2)]

    log.Info("peer", "l", loaddrhex, "r", roaddrhex, "x", xoaddrhex)

    var topic string
    err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42")
    if err != nil {
        t.Fatal(err)
    }

    time.Sleep(time.Millisecond * 250)

    // retrieve public key from pss instance
    // set this public key reciprocally
    var rpubkey string
    err = clients[1].Call(&rpubkey, "pss_getPublicKey")
    if err != nil {
        t.Fatalf("rpc get receivenode pubkey fail: %v", err)
    }

    time.Sleep(time.Millisecond * 500) // replace with hive healthy code

    rmsgC := make(chan APIMsg)
    rctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
    defer cancel()
    rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false)
    log.Trace("rsub", "id", rsub)
    defer rsub.Unsubscribe()

    // store public key for recipient
    // zero-length address means forward to all
    // we have just two peers, they will be in proxbin, and will both receive
    err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, "0x")
    if err != nil {
        t.Fatal(err)
    }

    // send and verify delivery
    rmsg := []byte("xyzzy")
    err = clients[0].Call(nil, "pss_sendAsym", rpubkey, topic, hexutil.Encode(rmsg))
    if err != nil {
        t.Fatal(err)
    }

    var receivedok bool
OUTER:
    for {
        select {
        case <-rmsgC:
            if receivedok {
                t.Fatalf("duplicate message received")
            }
            receivedok = true
        case <-rctx.Done():
            break OUTER
        }
    }
    if !receivedok {
        t.Fatalf("message did not arrive")
    }
}

// symmetric send performance with varying message sizes
func BenchmarkSymkeySend(b *testing.B) {
    b.Run(fmt.Sprintf("%d", 256), benchmarkSymKeySend)
    b.Run(fmt.Sprintf("%d", 1024), benchmarkSymKeySend)
    b.Run(fmt.Sprintf("%d", 1024*1024), benchmarkSymKeySend)
    b.Run(fmt.Sprintf("%d", 1024*1024*10), benchmarkSymKeySend)
    b.Run(fmt.Sprintf("%d", 1024*1024*100), benchmarkSymKeySend)
}

func benchmarkSymKeySend(b *testing.B) {
    msgsizestring := strings.Split(b.Name(), "/")
    if len(msgsizestring) != 2 {
        b.Fatalf("benchmark called without msgsize param")
    }
    msgsize, err := strconv.ParseInt(msgsizestring[1], 10, 0)
    if err != nil {
        b.Fatalf("benchmark called with invalid msgsize param '%s': %v", msgsizestring[1], err)
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    keys, err := wapi.NewKeyPair(ctx)
    privkey, err := w.GetPrivateKey(keys)
    ps := newTestPss(privkey, nil, nil)
    defer ps.Stop()
    msg := make([]byte, msgsize)
    rand.Read(msg)
    topic := BytesToTopic([]byte("foo"))
    to := make(PssAddress, 32)
    copy(to[:], network.RandomAddr().Over())
    symkeyid, err := ps.GenerateSymmetricKey(topic, to, true)
    if err != nil {
        b.Fatalf("could not generate symkey: %v", err)
    }
    symkey, err := ps.w.GetSymKey(symkeyid)
    if err != nil {
        b.Fatalf("could not retrieve symkey: %v", err)
    }
    ps.SetSymmetricKey(symkey, topic, to, false)

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        ps.SendSym(symkeyid, topic, msg)
    }
}

// asymmetric send performance with varying message sizes
func BenchmarkAsymkeySend(b *testing.B) {
    b.Run(fmt.Sprintf("%d", 256), benchmarkAsymKeySend)
    b.Run(fmt.Sprintf("%d", 1024), benchmarkAsymKeySend)
    b.Run(fmt.Sprintf("%d", 1024*1024), benchmarkAsymKeySend)
    b.Run(fmt.Sprintf("%d", 1024*1024*10), benchmarkAsymKeySend)
    b.Run(fmt.Sprintf("%d", 1024*1024*100), benchmarkAsymKeySend)
}

func benchmarkAsymKeySend(b *testing.B) {
    msgsizestring := strings.Split(b.Name(), "/")
    if len(msgsizestring) != 2 {
        b.Fatalf("benchmark called without msgsize param")
    }
    msgsize, err := strconv.ParseInt(msgsizestring[1], 10, 0)
    if err != nil {
        b.Fatalf("benchmark called with invalid msgsize param '%s': %v", msgsizestring[1], err)
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    keys, err := wapi.NewKeyPair(ctx)
    privkey, err := w.GetPrivateKey(keys)
    ps := newTestPss(privkey, nil, nil)
    defer ps.Stop()
    msg := make([]byte, msgsize)
    rand.Read(msg)
    topic := BytesToTopic([]byte("foo"))
    to := make(PssAddress, 32)
    copy(to[:], network.RandomAddr().Over())
    ps.SetPeerPublicKey(&privkey.PublicKey, topic, to)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        ps.SendAsym(common.ToHex(crypto.FromECDSAPub(&privkey.PublicKey)), topic, msg)
    }
}
func BenchmarkSymkeyBruteforceChangeaddr(b *testing.B) {
    for i := 100; i < 100000; i = i * 10 {
        for j := 32; j < 10000; j = j * 8 {
            b.Run(fmt.Sprintf("%d/%d", i, j), benchmarkSymkeyBruteforceChangeaddr)
        }
        //b.Run(fmt.Sprintf("%d", i), benchmarkSymkeyBruteforceChangeaddr)
    }
}

// decrypt performance using symkey cache, worst case
// (decrypt key always last in cache)
func benchmarkSymkeyBruteforceChangeaddr(b *testing.B) {
    keycountstring := strings.Split(b.Name(), "/")
    cachesize := int64(0)
    var ps *Pss
    if len(keycountstring) < 2 {
        b.Fatalf("benchmark called without count param")
    }
    keycount, err := strconv.ParseInt(keycountstring[1], 10, 0)
    if err != nil {
        b.Fatalf("benchmark called with invalid count param '%s': %v", keycountstring[1], err)
    }
    if len(keycountstring) == 3 {
        cachesize, err = strconv.ParseInt(keycountstring[2], 10, 0)
        if err != nil {
            b.Fatalf("benchmark called with invalid cachesize '%s': %v", keycountstring[2], err)
        }
    }
    pssmsgs := make([]*PssMsg, 0, keycount)
    var keyid string
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    keys, err := wapi.NewKeyPair(ctx)
    privkey, err := w.GetPrivateKey(keys)
    if cachesize > 0 {
        ps = newTestPss(privkey, nil, &PssParams{SymKeyCacheCapacity: int(cachesize)})
    } else {
        ps = newTestPss(privkey, nil, nil)
    }
    defer ps.Stop()
    topic := BytesToTopic([]byte("foo"))
    for i := 0; i < int(keycount); i++ {
        to := make(PssAddress, 32)
        copy(to[:], network.RandomAddr().Over())
        keyid, err = ps.GenerateSymmetricKey(topic, to, true)
        if err != nil {
            b.Fatalf("cant generate symkey #%d: %v", i, err)
        }
        symkey, err := ps.w.GetSymKey(keyid)
        if err != nil {
            b.Fatalf("could not retrieve symkey %s: %v", keyid, err)
        }
        wparams := &whisper.MessageParams{
            TTL:      defaultWhisperTTL,
            KeySym:   symkey,
            Topic:    whisper.TopicType(topic),
            WorkTime: defaultWhisperWorkTime,
            PoW:      defaultWhisperPoW,
            Payload:  []byte("xyzzy"),
            Padding:  []byte("1234567890abcdef"),
        }
        woutmsg, err := whisper.NewSentMessage(wparams)
        if err != nil {
            b.Fatalf("could not create whisper message: %v", err)
        }
        env, err := woutmsg.Wrap(wparams)
        if err != nil {
            b.Fatalf("could not generate whisper envelope: %v", err)
        }
        ps.Register(&topic, &handler{
            f: noopHandlerFunc,
        })
        pssmsgs = append(pssmsgs, &PssMsg{
            To:      to,
            Payload: env,
        })
    }
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        if err := ps.process(pssmsgs[len(pssmsgs)-(i%len(pssmsgs))-1], false, false); err != nil {
            b.Fatalf("pss processing failed: %v", err)
        }
    }
}

func BenchmarkSymkeyBruteforceSameaddr(b *testing.B) {
    for i := 100; i < 100000; i = i * 10 {
        for j := 32; j < 10000; j = j * 8 {
            b.Run(fmt.Sprintf("%d/%d", i, j), benchmarkSymkeyBruteforceSameaddr)
        }
    }
}

// decrypt performance using symkey cache, best case
// (decrypt key always first in cache)
func benchmarkSymkeyBruteforceSameaddr(b *testing.B) {
    var keyid string
    var ps *Pss
    cachesize := int64(0)
    keycountstring := strings.Split(b.Name(), "/")
    if len(keycountstring) < 2 {
        b.Fatalf("benchmark called without count param")
    }
    keycount, err := strconv.ParseInt(keycountstring[1], 10, 0)
    if err != nil {
        b.Fatalf("benchmark called with invalid count param '%s': %v", keycountstring[1], err)
    }
    if len(keycountstring) == 3 {
        cachesize, err = strconv.ParseInt(keycountstring[2], 10, 0)
        if err != nil {
            b.Fatalf("benchmark called with invalid cachesize '%s': %v", keycountstring[2], err)
        }
    }
    addr := make([]PssAddress, keycount)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    keys, err := wapi.NewKeyPair(ctx)
    privkey, err := w.GetPrivateKey(keys)
    if cachesize > 0 {
        ps = newTestPss(privkey, nil, &PssParams{SymKeyCacheCapacity: int(cachesize)})
    } else {
        ps = newTestPss(privkey, nil, nil)
    }
    defer ps.Stop()
    topic := BytesToTopic([]byte("foo"))
    for i := 0; i < int(keycount); i++ {
        copy(addr[i], network.RandomAddr().Over())
        keyid, err = ps.GenerateSymmetricKey(topic, addr[i], true)
        if err != nil {
            b.Fatalf("cant generate symkey #%d: %v", i, err)
        }

    }
    symkey, err := ps.w.GetSymKey(keyid)
    if err != nil {
        b.Fatalf("could not retrieve symkey %s: %v", keyid, err)
    }
    wparams := &whisper.MessageParams{
        TTL:      defaultWhisperTTL,
        KeySym:   symkey,
        Topic:    whisper.TopicType(topic),
        WorkTime: defaultWhisperWorkTime,
        PoW:      defaultWhisperPoW,
        Payload:  []byte("xyzzy"),
        Padding:  []byte("1234567890abcdef"),
    }
    woutmsg, err := whisper.NewSentMessage(wparams)
    if err != nil {
        b.Fatalf("could not create whisper message: %v", err)
    }
    env, err := woutmsg.Wrap(wparams)
    if err != nil {
        b.Fatalf("could not generate whisper envelope: %v", err)
    }
    ps.Register(&topic, &handler{
        f: noopHandlerFunc,
    })
    pssmsg := &PssMsg{
        To:      addr[len(addr)-1][:],
        Payload: env,
    }
    for i := 0; i < b.N; i++ {
        if err := ps.process(pssmsg, false, false); err != nil {
            b.Fatalf("pss processing failed: %v", err)
        }
    }
}

// setup simulated network with bzz/discovery and pss services.
// connects nodes in a circle
// if allowRaw is set, omission of builtin pss encryption is enabled (see PssParams)
func setupNetwork(numnodes int, allowRaw bool) (clients []*rpc.Client, err error) {
    nodes := make([]*simulations.Node, numnodes)
    clients = make([]*rpc.Client, numnodes)
    if numnodes < 2 {
        return nil, fmt.Errorf("Minimum two nodes in network")
    }
    adapter := adapters.NewSimAdapter(newServices(allowRaw))
    net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
        ID:             "0",
        DefaultService: "bzz",
    })
    for i := 0; i < numnodes; i++ {
        nodeconf := adapters.RandomNodeConfig()
        nodeconf.Services = []string{"bzz", pssProtocolName}
        nodes[i], err = net.NewNodeWithConfig(nodeconf)
        if err != nil {
            return nil, fmt.Errorf("error creating node 1: %v", err)
        }
        err = net.Start(nodes[i].ID())
        if err != nil {
            return nil, fmt.Errorf("error starting node 1: %v", err)
        }
        if i > 0 {
            err = net.Connect(nodes[i].ID(), nodes[i-1].ID())
            if err != nil {
                return nil, fmt.Errorf("error connecting nodes: %v", err)
            }
        }
        clients[i], err = nodes[i].Client()
        if err != nil {
            return nil, fmt.Errorf("create node 1 rpc client fail: %v", err)
        }
    }
    if numnodes > 2 {
        err = net.Connect(nodes[0].ID(), nodes[len(nodes)-1].ID())
        if err != nil {
            return nil, fmt.Errorf("error connecting first and last nodes")
        }
    }
    return clients, nil
}

func newServices(allowRaw bool) adapters.Services {
    stateStore := state.NewInmemoryStore()
    kademlias := make(map[enode.ID]*network.Kademlia)
    kademlia := func(id enode.ID) *network.Kademlia {
        if k, ok := kademlias[id]; ok {
            return k
        }
        params := network.NewKadParams()
        params.NeighbourhoodSize = 2
        params.MaxBinSize = 3
        params.MinBinSize = 1
        params.MaxRetries = 1000
        params.RetryExponent = 2
        params.RetryInterval = 1000000
        kademlias[id] = network.NewKademlia(id[:], params)
        return kademlias[id]
    }
    return adapters.Services{
        pssProtocolName: func(ctx *adapters.ServiceContext) (node.Service, error) {
            // execadapter does not exec init()
            initTest()

            ctxlocal, cancel := context.WithTimeout(context.Background(), time.Second)
            defer cancel()
            keys, err := wapi.NewKeyPair(ctxlocal)
            privkey, err := w.GetPrivateKey(keys)
            pssp := NewPssParams().WithPrivateKey(privkey)
            pssp.AllowRaw = allowRaw
            pskad := kademlia(ctx.Config.ID)
            ps, err := NewPss(pskad, pssp)
            if err != nil {
                return nil, err
            }

            ping := &Ping{
                OutC: make(chan bool),
                Pong: true,
            }
            p2pp := NewPingProtocol(ping)
            pp, err := RegisterProtocol(ps, &PingTopic, PingProtocol, p2pp, &ProtocolParams{Asymmetric: true})
            if err != nil {
                return nil, err
            }
            if useHandshake {
                SetHandshakeController(ps, NewHandshakeParams())
            }
            ps.Register(&PingTopic, &handler{
                f: pp.Handle,
                caps: &handlerCaps{
                    raw: true,
                },
            })
            ps.addAPI(rpc.API{
                Namespace: "psstest",
                Version:   "0.3",
                Service:   NewAPITest(ps),
                Public:    false,
            })
            if err != nil {
                log.Error("Couldnt register pss protocol", "err", err)
                os.Exit(1)
            }
            pssprotocols[ctx.Config.ID.String()] = &protoCtrl{
                C:        ping.OutC,
                protocol: pp,
                run:      p2pp.Run,
            }
            return ps, nil
        },
        "bzz": func(ctx *adapters.ServiceContext) (node.Service, error) {
            addr := network.NewAddr(ctx.Config.Node())
            hp := network.NewHiveParams()
            hp.Discovery = false
            config := &network.BzzConfig{
                OverlayAddr:  addr.Over(),
                UnderlayAddr: addr.Under(),
                HiveParams:   hp,
            }
            return network.NewBzz(config, kademlia(ctx.Config.ID), stateStore, nil, nil), nil
        },
    }
}

func newTestPss(privkey *ecdsa.PrivateKey, kad *network.Kademlia, ppextra *PssParams) *Pss {
    nid := enode.PubkeyToIDV4(&privkey.PublicKey)
    // set up routing if kademlia is not passed to us
    if kad == nil {
        kp := network.NewKadParams()
        kp.NeighbourhoodSize = 3
        kad = network.NewKademlia(nid[:], kp)
    }

    // create pss
    pp := NewPssParams().WithPrivateKey(privkey)
    if ppextra != nil {
        pp.SymKeyCacheCapacity = ppextra.SymKeyCacheCapacity
    }
    ps, err := NewPss(kad, pp)
    if err != nil {
        return nil
    }
    ps.Start(nil)

    return ps
}

// API calls for test/development use
type APITest struct {
    *Pss
}

func NewAPITest(ps *Pss) *APITest {
    return &APITest{Pss: ps}
}

func (apitest *APITest) SetSymKeys(pubkeyid string, recvsymkey []byte, sendsymkey []byte, limit uint16, topic Topic, to hexutil.Bytes) ([2]string, error) {

    recvsymkeyid, err := apitest.SetSymmetricKey(recvsymkey, topic, PssAddress(to), true)
    if err != nil {
        return [2]string{}, err
    }
    sendsymkeyid, err := apitest.SetSymmetricKey(sendsymkey, topic, PssAddress(to), false)
    if err != nil {
        return [2]string{}, err
    }
    return [2]string{recvsymkeyid, sendsymkeyid}, nil
}

func (apitest *APITest) Clean() (int, error) {
    return apitest.Pss.cleanKeys(), nil
}