package blossomsub

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"math/rand"
	"slices"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"google.golang.org/protobuf/proto"
	pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"

	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p/core/host"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/core/peerstore"
	"github.com/libp2p/go-libp2p/core/record"
	"github.com/libp2p/go-msgio"
)

func assertPeerLists(t *testing.T, bitmask []byte, hosts []host.Host, ps *PubSub, has ...int) {
	peers := ps.ListPeers(bitmask)
	set := make(map[peer.ID]struct{})
	for _, p := range peers {
		set[p] = struct{}{}
	}

	for _, h := range has {
		if _, ok := set[hosts[h].ID()]; !ok {
			t.Fatal("expected to have connection to peer: ", h)
		}
	}
}

func checkMessageRouting(t *testing.T, ctx context.Context, bitmasks []*Bitmask, subs []*Subscription) {
	for _, p := range bitmasks {
		data := make([]byte, 16)
		rand.Read(data)
		err := p.Publish(ctx, p.bitmask, data)
		if err != nil {
			t.Fatal(err)
		}

		for _, s := range subs {
			assertReceive(t, s, data)
		}
	}
}

func getDefaultHosts(t *testing.T, n int) []host.Host {
	var out []host.Host

	for i := 0; i < n; i++ {
		h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
		if err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { h.Close() })
		out = append(out, h)
	}

	return out
}

func connect(t *testing.T, a, b host.Host) {
	pinfo := a.Peerstore().PeerInfo(a.ID())
	err := b.Connect(context.Background(), pinfo)
	if err != nil {
		t.Fatal(err)
	}
}

func sparseConnect(t *testing.T, hosts []host.Host) {
	connectSome(t, hosts, 3)
}

func denseConnect(t *testing.T, hosts []host.Host) {
	connectSome(t, hosts, 10)
}

func connectSome(t *testing.T, hosts []host.Host, d int) {
	for i, a := range hosts {
		for j := 0; j < d; j++ {
			n := rand.Intn(len(hosts))
			if n == i {
				j--
				continue
			}

			b := hosts[n]

			connect(t, a, b)
		}
	}
}

func connectAll(t *testing.T, hosts []host.Host) {
	for i, a := range hosts {
		for j, b := range hosts {
			if i == j {
				continue
			}

			connect(t, a, b)
		}
	}
}

func assertReceive(t *testing.T, ch *Subscription, exp []byte) {
	select {
	case msg := <-ch.ch:
		if !bytes.Equal(msg.GetData(), exp) {
			t.Fatalf("got wrong message, expected %s but got %s", string(exp), string(msg.GetData()))
		}
	case <-time.After(time.Second * 5):
		t.Logf("%#v\n", ch)
		t.Fatal("timed out waiting for message of: ", string(exp))
	}
}

func assertNeverReceives(t *testing.T, ch *Subscription, timeout time.Duration) {
	select {
	case msg := <-ch.ch:
		t.Logf("%#v\n", ch)
		t.Fatal("got unexpected message: ", string(msg.GetData()))
	case <-time.After(timeout):
	}
}

func getBlossomSub(ctx context.Context, h host.Host, opts ...Option) *PubSub {
	ps, err := NewBlossomSub(ctx, h, opts...)
	if err != nil {
		panic(err)
	}
	return ps
}

func getBlossomSubs(ctx context.Context, hs []host.Host, opts ...Option) []*PubSub {
	var psubs []*PubSub
	for _, h := range hs {
		psubs = append(psubs, getBlossomSub(ctx, h, opts...))
	}
	return psubs
}

func TestSparseBlossomSub(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)

		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	sparseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestDenseBlossomSub(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubFanout(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs[1:] {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	b, err := psubs[0].Join([]byte{0x00, 0x01})
	if err != nil {
		t.Fatal(err)
	}

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood2 %d", i, i))

		b[0].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}

	// subscribe the owner
	subch, err := psubs[0].Subscribe([]byte{0x00, 0x01})
	if err != nil {
		t.Fatal(err)
	}
	msgs = append(msgs, subch...)

	// wait for a heartbeat
	time.Sleep(time.Second * 1)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		b[0].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubFanoutMaintenance(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs[1:] {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	b, err := psubs[0].Join([]byte{0x00, 0x01})
	if err != nil {
		t.Fatal(err)
	}

	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		b[0].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}

	// unsubscribe all peers to exercise fanout maintenance
	for _, sub := range msgs {
		sub.Cancel()
	}
	msgs = nil

	// wait for heartbeats
	time.Sleep(time.Second * 2)

	// resubscribe and repeat
	for _, ps := range psubs[1:] {
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	time.Sleep(time.Second * 2)

	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood2 %d", i, i))

		b[0].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubFanoutExpiry(t *testing.T) {
	BlossomSubFanoutTTL = 1 * time.Second
	defer func() {
		BlossomSubFanoutTTL = 60 * time.Second
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 10)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs[1:] {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	b, err := psubs[0].Join([]byte{0x00, 0x01})
	if err != nil {
		t.Fatal(err)
	}

	for i := 0; i < 5; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		b[0].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}

	psubs[0].eval <- func() {
		if len(psubs[0].rt.(*BlossomSubRouter).fanout) == 0 {
			t.Fatal("owner has no fanout")
		}
	}

	// wait for TTL to expire fanout peers in owner
	time.Sleep(time.Second * 2)

	psubs[0].eval <- func() {
		if len(psubs[0].rt.(*BlossomSubRouter).fanout) > 0 {
			t.Fatal("fanout hasn't expired")
		}
	}

	// wait for it to run in the event loop
	time.Sleep(10 * time.Millisecond)
}

func TestBlossomSubGossip(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}

		// wait a bit to have some gossip interleaved
		time.Sleep(time.Millisecond * 100)
	}

	// and wait for some gossip flushing
	time.Sleep(time.Second * 2)
}

func TestBlossomSubGossipPropagation(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 40)
	psubs := getBlossomSubs(ctx, hosts)

	hosts1 := hosts[:BlossomSubD+1]
	hosts2 := append(hosts[BlossomSubD+1:], hosts[0])

	denseConnect(t, hosts1)
	denseConnect(t, hosts2)

	var msgs1 []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs[1 : BlossomSubD+1] {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs1 = append(msgs1, subch...)
	}

	b, err := psubs[0].Join([]byte{0x00, 0x01})
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Second * 1)

	for i := 0; i < 10; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		b[0].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs1 {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}

	time.Sleep(time.Millisecond * 100)

	var msgs2 []*Subscription
	for _, ps := range psubs[BlossomSubD+1:] {
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs2 = append(msgs2, subch...)
	}

	var collect [][]byte
	for i := 0; i < 10; i++ {
		for _, sub := range msgs2 {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			collect = append(collect, got.Data)
		}
	}

	for i := 0; i < 10; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))
		gotit := false
		for j := 0; j < len(collect); j++ {
			if bytes.Equal(msg, collect[j]) {
				gotit = true
				break
			}
		}
		if !gotit {
			t.Fatalf("Didn't get message %s", string(msg))
		}
	}
}

func TestBlossomSubPrune(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	// disconnect some peers from the mesh to get some PRUNEs
	for _, sub := range msgs[:5] {
		sub.Cancel()
	}

	// wait a bit to take effect
	time.Sleep(time.Millisecond * 100)

	for i := 0; i < 10; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs[5:] {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubPruneBackoffTime(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 10)

	// App specific score that we'll change later.
	currentScoreForHost0 := int32(0)

	params := DefaultBlossomSubParams()
	params.HeartbeatInitialDelay = time.Millisecond * 10
	params.HeartbeatInterval = time.Millisecond * 100

	psubs := getBlossomSubs(ctx, hosts, WithBlossomSubParams(params), WithPeerScore(
		&PeerScoreParams{
			AppSpecificScore: func(p peer.ID) float64 {
				if p == hosts[0].ID() {
					return float64(atomic.LoadInt32(&currentScoreForHost0))
				} else {
					return 0
				}
			},
			AppSpecificWeight: 1,
			DecayInterval:     time.Second,
			DecayToZero:       0.01,
		},
		&PeerScoreThresholds{
			GossipThreshold:   -1,
			PublishThreshold:  -1,
			GraylistThreshold: -1,
		}))

	var msgs []*Subscription

	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	connectAll(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second)

	pruneTime := time.Now()
	// Flip the score. Host 0 should be pruned from everyone
	atomic.StoreInt32(&currentScoreForHost0, -1000)

	// wait for heartbeats to run and prune
	time.Sleep(time.Second)

	wg := sync.WaitGroup{}
	var missingBackoffs uint32 = 0
	for i := 1; i < 10; i++ {
		wg.Add(1)
		// Copy i so this func keeps the correct value in the closure.
		var idx = i
		// Run this check in the eval thunk so that we don't step over the heartbeat goroutine and trigger a race.
		psubs[idx].rt.(*BlossomSubRouter).p.eval <- func() {
			defer wg.Done()
			backoff, ok := psubs[idx].rt.(*BlossomSubRouter).backoff[string([]byte{0x00, 0x01})][hosts[0].ID()]
			if !ok {
				atomic.AddUint32(&missingBackoffs, 1)
			}
			if ok && backoff.Sub(pruneTime)-params.PruneBackoff > time.Second {
				t.Errorf("backoff time should be equal to prune backoff (with some slack) was %v", backoff.Sub(pruneTime)-params.PruneBackoff)
			}
		}
	}
	wg.Wait()

	// Sometimes not all the peers will have updated their backoffs by this point. If the majority haven't we'll fail this test.
	if missingBackoffs >= 5 {
		t.Errorf("missing too many backoffs: %v", missingBackoffs)
	}

	for i := 0; i < 10; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		// Don't publish from host 0, since everyone should have pruned it.
		owner := rand.Intn(len(psubs)-1) + 1

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs[1:] {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubGraft(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	sparseConnect(t, hosts)

	time.Sleep(time.Second * 1)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)

		// wait for announce to propagate
		time.Sleep(time.Millisecond * 100)
	}

	time.Sleep(time.Second * 1)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubRemovePeer(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)

	psubs := getBlossomSubs(ctx, hosts)

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x01})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	denseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	// disconnect some peers to exercise RemovePeer paths
	for _, host := range hosts[:5] {
		host.Close()
	}

	// wait a heartbeat
	time.Sleep(time.Second * 1)

	for i := 0; i < 10; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := 5 + rand.Intn(len(psubs)-5)

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs[5:] {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubGraftPruneRetry(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 10)
	psubs := getBlossomSubs(ctx, hosts)
	denseConnect(t, hosts)

	var msgs [][]*Subscription
	var bitmasks [][]*Bitmask
	for i := 0; i < 35; i++ {
		bitmask := bytes.Repeat([]byte{0x00}, i+1)
		var subs []*Subscription
		var masks []*Bitmask
		for _, ps := range psubs {
			b, err := ps.Join(bitmask)
			if err != nil {
				t.Fatal(err)
			}

			masks = append(masks, b...)
			subch, err := ps.Subscribe(bitmask)
			if err != nil {
				t.Fatal(err)
			}

			subs = append(subs, subch...)
		}
		bitmasks = append(bitmasks, masks)
		msgs = append(msgs, subs)
	}

	// wait for heartbeats to build meshes
	time.Sleep(time.Second * 5)

	for i, bitmask := range bitmasks {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmask[owner].Publish(ctx, bitmask[owner].bitmask, msg)

		for _, sub := range msgs[i] {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubControlPiggyback(t *testing.T) {
	t.Skip("travis regularly fails on this test")

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 10)
	psubs := getBlossomSubs(ctx, hosts)
	denseConnect(t, hosts)

	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x00, 0x08})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x00, 0x00, 0x08})
		if err != nil {
			t.Fatal(err)
		}
		go func(sub *Subscription) {
			for {
				_, err := sub.Next(ctx)
				if err != nil {
					break
				}
			}
		}(subch[0])
	}

	time.Sleep(time.Second * 1)

	// create a background flood of messages that overloads the queues
	done := make(chan struct{})
	go func() {
		owner := rand.Intn(len(psubs))
		for i := 0; i < 10000; i++ {
			msg := []byte("background flooooood")
			bitmasks[owner].Publish(ctx, []byte{0x00, 0x01}, msg)
		}
		done <- struct{}{}
	}()

	time.Sleep(time.Millisecond * 20)

	// and subscribe to a bunch of bitmasks in the meantime -- this should
	// result in some dropped control messages, with subsequent piggybacking
	// in the background flood
	var otherBitmasks [][]*Bitmask
	var msgs [][]*Subscription
	for i := 0; i < 5; i++ {
		bitmask := make([]byte, i)
		var masks []*Bitmask
		var subs []*Subscription
		for _, ps := range psubs {
			b, err := ps.Join(bitmask)
			if err != nil {
				t.Fatal(err)
			}
			masks = append(masks, b...)

			subch, err := ps.Subscribe(bitmask)
			if err != nil {
				t.Fatal(err)
			}

			subs = append(subs, subch...)
		}

		otherBitmasks = append(otherBitmasks, masks)
		msgs = append(msgs, subs)
	}

	// wait for the flood to stop
	<-done

	// and test that we have functional overlays
	for i, bitmask := range otherBitmasks {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		bitmask[owner].Publish(ctx, []byte{0x00, 0x01}, msg)

		for _, sub := range msgs[i] {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestMixedBlossomSub(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 30)

	bsubs := getBlossomSubs(ctx, hosts[:20])

	var msgs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range bsubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		subch, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch...)
	}

	sparseConnect(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 4)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(bsubs))

		bitmasks[owner].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range msgs {
			got, err := sub.Next(ctx)
			if err != nil {
				t.Fatal(sub.err)
			}
			if !bytes.Equal(msg, got.Data) {
				t.Fatal("got wrong message!")
			}
		}
	}
}

func TestBlossomSubMultihops(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 6)

	psubs := getBlossomSubs(ctx, hosts)

	connect(t, hosts[0], hosts[1])
	connect(t, hosts[1], hosts[2])
	connect(t, hosts[2], hosts[3])
	connect(t, hosts[3], hosts[4])
	connect(t, hosts[4], hosts[5])

	var subs []*Subscription
	var bitmasks []*Bitmask
	for i := 1; i < 6; i++ {
		b, err := psubs[i].Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		ch, err := psubs[i].Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, ch...)
	}

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	msg := []byte("i like cats")
	err := bitmasks[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
	if err != nil {
		t.Fatal(err)
	}

	// last node in the chain should get the message
	select {
	case out := <-subs[4].ch:
		if !bytes.Equal(out.GetData(), msg) {
			t.Fatal("got wrong data")
		}
	case <-time.After(time.Second * 5):
		t.Fatal("timed out waiting for message")
	}
}

func TestBlossomSubTreeTopology(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 10)
	psubs := getBlossomSubs(ctx, hosts)

	connect(t, hosts[0], hosts[1])
	connect(t, hosts[1], hosts[2])
	connect(t, hosts[1], hosts[4])
	connect(t, hosts[2], hosts[3])
	connect(t, hosts[0], hosts[5])
	connect(t, hosts[5], hosts[6])
	connect(t, hosts[5], hosts[8])
	connect(t, hosts[6], hosts[7])
	connect(t, hosts[8], hosts[9])

	/*
		[0] -> [1] -> [2] -> [3]
		 |      L->[4]
		 v
		[5] -> [6] -> [7]
		 |
		 v
		[8] -> [9]
	*/

	var chs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		ch, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		chs = append(chs, ch...)
	}

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	assertPeerLists(t, []byte{0x00, 0x00, 0x80, 0x00}, hosts, psubs[0], 1, 5)
	assertPeerLists(t, []byte{0x00, 0x00, 0x80, 0x00}, hosts, psubs[1], 0, 2, 4)
	assertPeerLists(t, []byte{0x00, 0x00, 0x80, 0x00}, hosts, psubs[2], 1, 3)

	checkMessageRouting(t, ctx, []*Bitmask{bitmasks[9], bitmasks[3]}, chs)
}

// this tests overlay bootstrapping through px in BlossomSub v1.2
// we start with a star topology and rely on px through prune to build the mesh
func TestBlossomSubStarTopology(t *testing.T) {
	originalBlossomSubD := BlossomSubD
	BlossomSubD = 4
	originalBlossomSubDhi := BlossomSubDhi
	BlossomSubDhi = BlossomSubD + 1
	originalBlossomSubDlo := BlossomSubDlo
	BlossomSubDlo = BlossomSubD - 1
	originalBlossomSubDscore := BlossomSubDscore
	BlossomSubDscore = BlossomSubDlo
	defer func() {
		BlossomSubD = originalBlossomSubD
		BlossomSubDhi = originalBlossomSubDhi
		BlossomSubDlo = originalBlossomSubDlo
		BlossomSubDscore = originalBlossomSubDscore
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 20)
	psubs := getBlossomSubs(ctx, hosts, WithPeerExchange(true), WithFloodPublish(true))

	// configure the center of the star with a very low D
	psubs[0].eval <- func() {
		gs := psubs[0].rt.(*BlossomSubRouter)
		gs.params.D = 0
		gs.params.Dlo = 0
		gs.params.Dhi = 0
		gs.params.Dscore = 0
	}

	// add all peer addresses to the peerstores
	// this is necessary because we can't have signed address records witout identify
	// pushing them
	for i := range hosts {
		for j := range hosts {
			if i == j {
				continue
			}
			hosts[i].Peerstore().AddAddrs(hosts[j].ID(), hosts[j].Addrs(), peerstore.PermanentAddrTTL)
		}
	}

	// build the star
	for i := 1; i < 20; i++ {
		connect(t, hosts[0], hosts[i])
	}

	time.Sleep(time.Second)

	// build the mesh
	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	// wait a bit for the mesh to build
	time.Sleep(2 * time.Second)

	// check that all peers have > 1 connection
	for i, h := range hosts {
		if len(h.Network().Conns()) == 1 {
			t.Errorf("peer %d has ony a single connection", i)
		}
	}

	// send a message from each peer and assert it was propagated
	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[i].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range subs {
			assertReceive(t, sub, msg)
		}
	}
}

// this tests overlay bootstrapping through px in BlossomSub v1.2, with addresses
// exchanged in signed peer records.
// we start with a star topology and rely on px through prune to build the mesh
func TestBlossomSubStarTopologyWithSignedPeerRecords(t *testing.T) {
	originalBlossomSubD := BlossomSubD
	BlossomSubD = 4
	originalBlossomSubDhi := BlossomSubDhi
	BlossomSubDhi = BlossomSubD + 1
	originalBlossomSubDlo := BlossomSubDlo
	BlossomSubDlo = BlossomSubD - 1
	originalBlossomSubDscore := BlossomSubDscore
	BlossomSubDscore = BlossomSubDlo
	originalBlossomSubPruneBackoff := BlossomSubPruneBackoff
	BlossomSubPruneBackoff = 2 * time.Second
	defer func() {
		BlossomSubD = originalBlossomSubD
		BlossomSubDhi = originalBlossomSubDhi
		BlossomSubDlo = originalBlossomSubDlo
		BlossomSubDscore = originalBlossomSubDscore
		BlossomSubPruneBackoff = originalBlossomSubPruneBackoff
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 20)
	psubs := getBlossomSubs(ctx, hosts, WithPeerExchange(true), WithFloodPublish(true))

	// configure the center of the star with a very low D
	psubs[0].eval <- func() {
		gs := psubs[0].rt.(*BlossomSubRouter)
		gs.params.D = 0
		gs.params.Dlo = 0
		gs.params.Dhi = 0
		gs.params.Dscore = 0
	}

	// manually create signed peer records for each host and add them to the
	// peerstore of the center of the star, which is doing the bootstrapping
	for i := range hosts[1:] {
		privKey := hosts[i].Peerstore().PrivKey(hosts[i].ID())
		if privKey == nil {
			t.Fatalf("unable to get private key for host %s", hosts[i].ID().String())
		}
		ai := host.InfoFromHost(hosts[i])
		rec := peer.PeerRecordFromAddrInfo(*ai)
		signedRec, err := record.Seal(rec, privKey)
		if err != nil {
			t.Fatalf("error creating signed peer record: %s", err)
		}

		cab, ok := peerstore.GetCertifiedAddrBook(hosts[0].Peerstore())
		if !ok {
			t.Fatal("peerstore does not implement CertifiedAddrBook")
		}
		_, err = cab.ConsumePeerRecord(signedRec, peerstore.PermanentAddrTTL)
		if err != nil {
			t.Fatalf("error adding signed peer record: %s", err)
		}
	}

	// build the star
	for i := 1; i < 20; i++ {
		connect(t, hosts[0], hosts[i])
	}

	time.Sleep(time.Second)

	// build the mesh
	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	// wait a bit for the mesh to build
	time.Sleep(10 * time.Second)

	// check that all peers have > 1 connection
	for i, h := range hosts {
		if len(h.Network().Conns()) == 1 {
			t.Errorf("peer %d has only a single connection", i)
		}
	}

	// send a message from each peer and assert it was propagated
	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[i].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range subs {
			assertReceive(t, sub, msg)
		}
	}
}

func TestBlossomSubDirectPeers(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	h := getDefaultHosts(t, 3)
	psubs := []*PubSub{
		getBlossomSub(ctx, h[0], WithDirectConnectTicks(2)),
		getBlossomSub(ctx, h[1], WithDirectPeers([]peer.AddrInfo{{ID: h[2].ID(), Addrs: h[2].Addrs()}}), WithDirectConnectTicks(2)),
		getBlossomSub(ctx, h[2], WithDirectPeers([]peer.AddrInfo{{ID: h[1].ID(), Addrs: h[1].Addrs()}}), WithDirectConnectTicks(2)),
	}

	connect(t, h[0], h[1])
	connect(t, h[0], h[2])

	// verify that the direct peers connected
	time.Sleep(2 * time.Second)
	if len(h[1].Network().ConnsToPeer(h[2].ID())) == 0 {
		t.Fatal("expected a connection between direct peers")
	}

	// build the mesh
	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(time.Second)

	// publish some messages
	for i := 0; i < 3; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[i].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range subs {
			assertReceive(t, sub, msg)
		}
	}

	// disconnect the direct peers to test reconnection
	for _, c := range h[1].Network().ConnsToPeer(h[2].ID()) {
		c.Close()
	}

	time.Sleep(5 * time.Second)

	if len(h[1].Network().ConnsToPeer(h[2].ID())) == 0 {
		t.Fatal("expected a connection between direct peers")
	}

	// publish some messages
	for i := 0; i < 3; i++ {
		msg := []byte(fmt.Sprintf("message %d", i+3))
		bitmasks[i].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range subs {
			assertReceive(t, sub, msg)
		}
	}
}

func TestBlossomSubPeerFilter(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	h := getDefaultHosts(t, 3)
	psubs := []*PubSub{
		getBlossomSub(ctx, h[0], WithPeerFilter(func(pid peer.ID, bitmask []byte) bool {
			return pid == h[1].ID()
		})),
		getBlossomSub(ctx, h[1], WithPeerFilter(func(pid peer.ID, bitmask []byte) bool {
			return pid == h[0].ID()
		})),
		getBlossomSub(ctx, h[2]),
	}

	connect(t, h[0], h[1])
	connect(t, h[0], h[2])

	// Join all peers
	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(time.Second)

	msg := []byte("message")

	bitmasks[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
	assertReceive(t, subs[0], msg)
	assertReceive(t, subs[1], msg)
	assertNeverReceives(t, subs[2], time.Second)

	msg = []byte("message2")

	bitmasks[1].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
	assertReceive(t, subs[0], msg)
	assertReceive(t, subs[1], msg)
	assertNeverReceives(t, subs[2], time.Second)
}

func TestBlossomSubDirectPeersFanout(t *testing.T) {
	// regression test for #371
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	h := getDefaultHosts(t, 3)
	psubs := []*PubSub{
		getBlossomSub(ctx, h[0]),
		getBlossomSub(ctx, h[1], WithDirectPeers([]peer.AddrInfo{{ID: h[2].ID(), Addrs: h[2].Addrs()}})),
		getBlossomSub(ctx, h[2], WithDirectPeers([]peer.AddrInfo{{ID: h[1].ID(), Addrs: h[1].Addrs()}})),
	}

	connect(t, h[0], h[1])
	connect(t, h[0], h[2])

	// Join all peers except h2
	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs[:2] {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(time.Second)

	b, err := psubs[2].Join([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	// h2 publishes some messages to build a fanout
	for i := 0; i < 3; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		b[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range subs {
			assertReceive(t, sub, msg)
		}
	}

	// verify that h0 is in the fanout of h2, but not h1 who is a direct peer
	result := make(chan bool, 2)
	psubs[2].eval <- func() {
		rt := psubs[2].rt.(*BlossomSubRouter)
		fanout := rt.fanout[string([]byte{0x00, 0x00, 0x80, 0x00})]
		_, ok := fanout[h[0].ID()]
		result <- ok
		_, ok = fanout[h[1].ID()]
		result <- ok
	}

	inFanout := <-result
	if !inFanout {
		t.Fatal("expected peer 0 to be in fanout")
	}

	inFanout = <-result
	if inFanout {
		t.Fatal("expected peer 1 to not be in fanout")
	}

	// now subscribe h2 too and verify tht h0 is in the mesh but not h1
	_, err = psubs[2].Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(2 * time.Second)

	psubs[2].eval <- func() {
		rt := psubs[2].rt.(*BlossomSubRouter)
		mesh := rt.mesh[string([]byte{0x00, 0x00, 0x80, 0x00})]
		_, ok := mesh[h[0].ID()]
		result <- ok
		_, ok = mesh[h[1].ID()]
		result <- ok
	}

	inMesh := <-result
	if !inMesh {
		t.Fatal("expected peer 0 to be in mesh")
	}

	inMesh = <-result
	if inMesh {
		t.Fatal("expected peer 1 to not be in mesh")
	}
}

func TestBlossomSubFloodPublish(t *testing.T) {
	// uses a star topology without PX and publishes from the star to verify that all
	// messages get received
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 20)
	psubs := getBlossomSubs(ctx, hosts, WithFloodPublish(true))

	// build the star
	for i := 1; i < 20; i++ {
		connect(t, hosts[0], hosts[i])
	}

	// build the (partial, unstable) mesh
	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(time.Second)

	// send a message from the star and assert it was received
	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)

		for _, sub := range subs {
			assertReceive(t, sub, msg)
		}
	}
}

func TestBlossomSubEnoughPeers(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 20)
	psubs := getBlossomSubs(ctx, hosts)

	for _, ps := range psubs {
		_, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
	}

	// at this point we have no connections and no mesh, so EnoughPeers should return false
	res := make(chan bool, 1)
	psubs[0].eval <- func() {
		res <- psubs[0].rt.EnoughPeers([]byte{0x00, 0x00, 0x80, 0x00}, 0)
	}
	enough := <-res
	if enough {
		t.Fatal("should not have enough peers")
	}

	// connect them densly to build up the mesh
	denseConnect(t, hosts)

	time.Sleep(3 * time.Second)

	psubs[0].eval <- func() {
		res <- psubs[0].rt.EnoughPeers([]byte{0x00, 0x00, 0x80, 0x00}, 0)
	}
	enough = <-res
	if !enough {
		t.Fatal("should have enough peers")
	}
}

func TestBlossomSubCustomParams(t *testing.T) {
	// in this test we score sinkhole a peer to exercise code paths relative to negative scores
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	params := DefaultBlossomSubParams()

	wantedFollowTime := 1 * time.Second
	params.IWantFollowupTime = wantedFollowTime

	wantedMaxPendingConns := 23
	params.MaxPendingConnections = wantedMaxPendingConns
	hosts := getDefaultHosts(t, 1)
	psubs := getBlossomSubs(ctx, hosts,
		WithBlossomSubParams(params))

	if len(psubs) != 1 {
		t.Fatalf("incorrect number of pusbub objects received: wanted %d but got %d", 1, len(psubs))
	}

	rt, ok := psubs[0].rt.(*BlossomSubRouter)
	if !ok {
		t.Fatal("Did not get gossip sub router from pub sub object")
	}

	if rt.params.IWantFollowupTime != wantedFollowTime {
		t.Errorf("Wanted %d of param BlossomSubIWantFollowupTime but got %d", wantedFollowTime, rt.params.IWantFollowupTime)
	}
	if rt.params.MaxPendingConnections != wantedMaxPendingConns {
		t.Errorf("Wanted %d of param BlossomSubMaxPendingConnections but got %d", wantedMaxPendingConns, rt.params.MaxPendingConnections)
	}
}

func TestBlossomSubNegativeScore(t *testing.T) {
	// in this test we score sinkhole a peer to exercise code paths relative to negative scores
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 20)
	psubs := getBlossomSubs(ctx, hosts,
		WithPeerScore(
			&PeerScoreParams{
				AppSpecificScore: func(p peer.ID) float64 {
					if p == hosts[0].ID() {
						return -1000
					} else {
						return 0
					}
				},
				AppSpecificWeight: 1,
				DecayInterval:     time.Second,
				DecayToZero:       0.01,
			},
			&PeerScoreThresholds{
				GossipThreshold:   -10,
				PublishThreshold:  -100,
				GraylistThreshold: -10000,
			}))

	denseConnect(t, hosts)

	var subs []*Subscription
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(3 * time.Second)

	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[i%20].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
		time.Sleep(20 * time.Millisecond)
	}

	// let the sinkholed peer try to emit gossip as well
	time.Sleep(2 * time.Second)

	// checks:
	// 1. peer 0 should only receive its own message
	// 2. peers 1-20 should not receive a message from peer 0, because it's not part of the mesh
	//    and its gossip is rejected
	collectAll := func(sub *Subscription) []*Message {
		var res []*Message
		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
		defer cancel()

		for {
			msg, err := sub.Next(ctx)
			if err != nil {
				break
			}

			res = append(res, msg)
		}

		return res
	}

	count := len(collectAll(subs[0]))
	if count != 1 {
		t.Fatalf("expected 1 message but got %d instead", count)
	}

	for _, sub := range subs[1:] {
		all := collectAll(sub)
		for _, m := range all {
			if m.ReceivedFrom == hosts[0].ID() {
				t.Fatal("received message from sinkholed peer")
			}
		}
	}
}

func TestBlossomSubScoreValidatorEx(t *testing.T) {
	// this is a test that of the two message drop responses from a validator
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 3)
	psubs := getBlossomSubs(ctx, hosts,
		WithPeerScore(
			&PeerScoreParams{
				AppSpecificScore: func(p peer.ID) float64 { return 0 },
				DecayInterval:    time.Second,
				DecayToZero:      0.01,
				Bitmasks: map[string]*BitmaskScoreParams{
					string([]byte{0x00, 0x00, 0x80, 0x00}): {
						BitmaskWeight:                  1,
						TimeInMeshQuantum:              time.Second,
						InvalidMessageDeliveriesWeight: -1,
						InvalidMessageDeliveriesDecay:  0.9999,
					},
				},
			},
			&PeerScoreThresholds{
				GossipThreshold:   -10,
				PublishThreshold:  -100,
				GraylistThreshold: -10000,
			}))

	connectAll(t, hosts)

	err := psubs[0].RegisterBitmaskValidator([]byte{0x00, 0x00, 0x80, 0x00}, func(ctx context.Context, p peer.ID, msg *Message) ValidationResult {
		// we ignore host1 and reject host2
		if p == hosts[1].ID() {
			return ValidationIgnore
		}
		if p == hosts[2].ID() {
			return ValidationReject
		}

		return ValidationAccept
	})
	if err != nil {
		t.Fatal(err)
	}

	sub, err := psubs[0].Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	b1, err := psubs[1].Join([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	b2, err := psubs[2].Join([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(100 * time.Millisecond)

	expectNoMessage := func(sub *Subscription) {
		ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
		defer cancel()

		m, err := sub.Next(ctx)
		if err == nil {
			t.Fatal("expected no message, but got ", string(m.Data))
		}
	}

	b1[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, []byte("i am not a walrus"))
	b2[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, []byte("i am not a walrus either"))

	// assert no messages
	expectNoMessage(sub[0])

	// assert that peer1's score is still 0 (its message was ignored) while peer2 should have
	// a negative score (its message got rejected)
	res := make(chan float64, 1)
	psubs[0].eval <- func() {
		res <- psubs[0].rt.(*BlossomSubRouter).score.Score(hosts[1].ID())
	}
	score := <-res
	if score != 0 {
		t.Fatalf("expected 0 score for peer1, but got %f", score)
	}

	psubs[0].eval <- func() {
		res <- psubs[0].rt.(*BlossomSubRouter).score.Score(hosts[2].ID())
	}
	score = <-res
	if score >= 0 {
		t.Fatalf("expected negative score for peer2, but got %f", score)
	}
}

func TestBlossomSubPiggybackControl(t *testing.T) {
	// this is a direct test of the piggybackControl function as we can't reliably
	// trigger it on travis
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	h := getDefaultHosts(t, 1)[0]
	ps := getBlossomSub(ctx, h)

	blah := peer.ID("bogotr0n")

	res := make(chan *RPC, 1)
	ps.eval <- func() {
		gs := ps.rt.(*BlossomSubRouter)
		test1 := []byte{0x00, 0x80, 0x00, 0x00}
		test2 := []byte{0x00, 0x20, 0x00, 0x00}
		test3 := []byte{0x00, 0x00, 0x02, 0x00}
		gs.mesh[string(test1)] = make(map[peer.ID]struct{})
		gs.mesh[string(test2)] = make(map[peer.ID]struct{})
		gs.mesh[string(test1)][blah] = struct{}{}

		rpc := &RPC{RPC: pb.RPC{}}
		gs.piggybackControl(blah, rpc, &pb.ControlMessage{
			Graft: []*pb.ControlGraft{{Bitmask: test1}, {Bitmask: test2}, {Bitmask: test3}},
			Prune: []*pb.ControlPrune{{Bitmask: test1}, {Bitmask: test2}, {Bitmask: test3}},
		})
		res <- rpc
	}

	rpc := <-res
	if rpc.Control == nil {
		t.Fatal("expected non-nil control message")
	}
	if len(rpc.Control.Graft) != 1 {
		t.Fatal("expected 1 GRAFT")
	}
	if !bytes.Equal(rpc.Control.Graft[0].GetBitmask(), []byte{0x00, 0x80, 0x00, 0x00}) {
		t.Fatal("expected test1 as graft bitmask ID")
	}
	if len(rpc.Control.Prune) != 2 {
		t.Fatal("expected 2 PRUNEs")
	}
	if !bytes.Equal(rpc.Control.Prune[0].GetBitmask(), []byte{0x00, 0x20, 0x00, 0x00}) {
		t.Fatal("expected test2 as prune bitmask ID")
	}
	if !bytes.Equal(rpc.Control.Prune[1].GetBitmask(), []byte{0x00, 0x00, 0x02, 0x00}) {
		t.Fatal("expected test3 as prune bitmask ID")
	}
}

func TestBlossomSubMultipleGraftBitmasks(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 2)
	psubs := getBlossomSubs(ctx, hosts)
	sparseConnect(t, hosts)

	time.Sleep(time.Second * 1)

	firstBitmask := []byte{0x00, 0x80, 0x00, 0x00}
	secondBitmask := []byte{0x00, 0x20, 0x00, 0x00}
	thirdBitmask := []byte{0x00, 0x00, 0x02, 0x00}

	firstPeer := hosts[0].ID()
	secondPeer := hosts[1].ID()

	p2Sub := psubs[1]
	p1Router := psubs[0].rt.(*BlossomSubRouter)
	p2Router := psubs[1].rt.(*BlossomSubRouter)

	finChan := make(chan struct{})

	p2Sub.eval <- func() {
		// Add bitmasks to second peer
		p2Router.mesh[string(firstBitmask)] = map[peer.ID]struct{}{}
		p2Router.mesh[string(secondBitmask)] = map[peer.ID]struct{}{}
		p2Router.mesh[string(thirdBitmask)] = map[peer.ID]struct{}{}

		finChan <- struct{}{}
	}
	<-finChan

	// Send multiple GRAFT messages to second peer from
	// 1st peer
	p1Router.sendGraftPrune(map[peer.ID][][]byte{
		secondPeer: {firstBitmask, secondBitmask, thirdBitmask},
	}, map[peer.ID][][]byte{}, map[peer.ID]bool{})

	time.Sleep(time.Second * 1)

	p2Sub.eval <- func() {
		if _, ok := p2Router.mesh[string(firstBitmask)][firstPeer]; !ok {
			t.Errorf("First peer wasnt added to mesh of the second peer for the bitmask %s", firstBitmask)
		}
		if _, ok := p2Router.mesh[string(secondBitmask)][firstPeer]; !ok {
			t.Errorf("First peer wasnt added to mesh of the second peer for the bitmask %s", secondBitmask)
		}
		if _, ok := p2Router.mesh[string(thirdBitmask)][firstPeer]; !ok {
			t.Errorf("First peer wasnt added to mesh of the second peer for the bitmask %s", thirdBitmask)
		}
		finChan <- struct{}{}
	}
	<-finChan
}

func TestBlossomSubOpportunisticGrafting(t *testing.T) {
	originalBlossomSubPruneBackoff := BlossomSubPruneBackoff
	BlossomSubPruneBackoff = 500 * time.Millisecond
	originalBlossomSubGraftFloodThreshold := BlossomSubGraftFloodThreshold
	BlossomSubGraftFloodThreshold = 100 * time.Millisecond
	originalBlossomSubOpportunisticGraftTicks := BlossomSubOpportunisticGraftTicks
	BlossomSubOpportunisticGraftTicks = 2
	defer func() {
		BlossomSubPruneBackoff = originalBlossomSubPruneBackoff
		BlossomSubGraftFloodThreshold = originalBlossomSubGraftFloodThreshold
		BlossomSubOpportunisticGraftTicks = originalBlossomSubOpportunisticGraftTicks
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 50)
	// pubsubs for the first 10 hosts
	psubs := getBlossomSubs(ctx, hosts[:10],
		WithFloodPublish(true),
		WithPeerScore(
			&PeerScoreParams{
				AppSpecificScore:  func(peer.ID) float64 { return 0 },
				AppSpecificWeight: 0,
				DecayInterval:     time.Second,
				DecayToZero:       0.01,
				Bitmasks: map[string]*BitmaskScoreParams{
					string([]byte{0x00, 0x00, 0x80, 0x00}): {
						BitmaskWeight:                 1,
						TimeInMeshWeight:              0.0002777,
						TimeInMeshQuantum:             time.Second,
						TimeInMeshCap:                 3600,
						FirstMessageDeliveriesWeight:  1,
						FirstMessageDeliveriesDecay:   0.9997,
						FirstMessageDeliveriesCap:     100,
						InvalidMessageDeliveriesDecay: 0.99997,
					},
				},
			},
			&PeerScoreThresholds{
				GossipThreshold:             -10,
				PublishThreshold:            -100,
				GraylistThreshold:           -10000,
				OpportunisticGraftThreshold: 1,
			}))

	// connect the real hosts with degree 5
	connectSome(t, hosts[:10], 5)

	// sybil squatters for the remaining 40 hosts
	for _, h := range hosts[10:] {
		squatter := &sybilSquatter{h: h}
		h.SetStreamHandler(BlossomSubID_v2, squatter.handleStream)
	}

	// connect all squatters to every real host
	for _, squatter := range hosts[10:] {
		for _, real := range hosts[:10] {
			connect(t, squatter, real)
		}
	}

	// wait a bit for the connections to propagate events to the pubsubs
	time.Sleep(time.Second)

	// ask the real pubsus to join the bitmask
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		// consume the messages
		go func(sub *Subscription) {
			for {
				_, err := sub.Next(ctx)
				if err != nil {
					return
				}
			}
		}(sub[0])
	}

	// publish a bunch of messages from the real hosts
	for i := 0; i < 1000; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[i%10].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
		time.Sleep(20 * time.Millisecond)
	}

	// now wait a few of oppgraft cycles
	time.Sleep(7 * time.Second)

	// check the honest peer meshes, they should have at least 3 honest peers each
	res := make(chan int, 1)
	for _, ps := range psubs {
		ps.eval <- func() {
			gs := ps.rt.(*BlossomSubRouter)
			count := 0
			for _, h := range hosts[:10] {
				_, ok := gs.mesh[string([]byte{0x00, 0x00, 0x80, 0x00})][h.ID()]
				if ok {
					count++
				}
			}
			res <- count
		}

		count := <-res
		if count < 3 {
			t.Fatalf("expected at least 3 honest peers, got %d", count)
		}
	}
}
func TestBlossomSubLeaveBitmask(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	h := getDefaultHosts(t, 2)
	psubs := []*PubSub{
		getBlossomSub(ctx, h[0]),
		getBlossomSub(ctx, h[1]),
	}

	connect(t, h[0], h[1])

	// Join all peers
	var subs []*Subscription
	for _, ps := range psubs {
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(time.Second)

	leaveTime := time.Now()
	done := make(chan struct{})

	psubs[0].rt.(*BlossomSubRouter).p.eval <- func() {
		defer close(done)
		psubs[0].rt.Leave([]byte{0x00, 0x00, 0x80, 0x00})
		time.Sleep(time.Second)
		peerMap := psubs[0].rt.(*BlossomSubRouter).backoff[string([]byte{0x00, 0x00, 0x80, 0x00})]
		if len(peerMap) != 1 {
			t.Fatalf("No peer is populated in the backoff map for peer 0")
		}
		_, ok := peerMap[h[1].ID()]
		if !ok {
			t.Errorf("Expected peer does not exist in the backoff map")
		}

		backoffTime := peerMap[h[1].ID()].Sub(leaveTime)
		// Check that the backoff time is roughly the unsubscribebackoff time (with a slack of 1s)
		if backoffTime-BlossomSubUnsubscribeBackoff > time.Second {
			t.Error("Backoff time should be set to BlossomSubUnsubscribeBackoff.")
		}
	}
	<-done

	done = make(chan struct{})
	// Ensure that remote peer 1 also applies the backoff appropriately
	// for peer 0.
	psubs[1].rt.(*BlossomSubRouter).p.eval <- func() {
		defer close(done)
		peerMap2 := psubs[1].rt.(*BlossomSubRouter).backoff[string([]byte{0x00, 0x00, 0x80, 0x00})]
		if len(peerMap2) != 1 {
			t.Fatalf("No peer is populated in the backoff map for peer 1")
		}
		_, ok := peerMap2[h[0].ID()]
		if !ok {
			t.Errorf("Expected peer does not exist in the backoff map")
		}

		backoffTime := peerMap2[h[0].ID()].Sub(leaveTime)
		// Check that the backoff time is roughly the unsubscribebackoff time (with a slack of 1s)
		if backoffTime-BlossomSubUnsubscribeBackoff > time.Second {
			t.Error("Backoff time should be set to BlossomSubUnsubscribeBackoff.")
		}
	}
	<-done
}

func TestBlossomSubJoinBitmask(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	h := getDefaultHosts(t, 3)
	psubs := []*PubSub{
		getBlossomSub(ctx, h[0]),
		getBlossomSub(ctx, h[1]),
		getBlossomSub(ctx, h[2]),
	}

	connect(t, h[0], h[1])
	connect(t, h[0], h[2])

	router0 := psubs[0].rt.(*BlossomSubRouter)

	// Add in backoff for peer.
	peerMap := make(map[peer.ID]time.Time)
	peerMap[h[1].ID()] = time.Now().Add(router0.params.UnsubscribeBackoff)

	router0.backoff[string([]byte{0x00, 0x00, 0x80, 0x00})] = peerMap

	// Join all peers
	var subs []*Subscription
	for _, ps := range psubs {
		sub, err := ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
		subs = append(subs, sub...)
	}

	time.Sleep(time.Second)

	meshMap := router0.mesh[string([]byte{0x00, 0x00, 0x80, 0x00})]
	if len(meshMap) != 1 {
		t.Fatalf("Unexpect peer included in the mesh")
	}

	_, ok := meshMap[h[1].ID()]
	if ok {
		t.Fatalf("Peer that was to be backed off is included in the mesh")
	}
}

type sybilSquatter struct {
	h host.Host
}

func (sq *sybilSquatter) handleStream(s network.Stream) {
	defer s.Close()

	os, err := sq.h.NewStream(context.Background(), s.Conn().RemotePeer(), BlossomSubID_v2)
	if err != nil {
		panic(err)
	}

	// send a subscription for test in the output stream to become candidate for GRAFT
	// and then just read and ignore the incoming RPCs
	r := msgio.NewVarintReaderSize(s, DefaultMaxMessageSize)
	w := msgio.NewVarintWriter(os)
	truth := true
	bitmask := []byte{0x00, 0x00, 0x80, 0x00}
	msg := &pb.RPC{Subscriptions: []*pb.RPC_SubOpts{{Subscribe: truth, Bitmask: bitmask}}}
	out, err := proto.Marshal(msg)
	if err != nil {
		panic(err)
	}

	err = w.WriteMsg(out)
	if err != nil {
		panic(err)
	}

	var rpc pb.RPC
	for {
		rpc.Reset()
		v, err := r.ReadMsg()
		if err != nil {
			break
		}

		err = proto.Unmarshal(v, &rpc)
		if err != nil {
			break
		}
	}
}

func TestBlossomSubPeerScoreInspect(t *testing.T) {
	// this test exercises the code path sof peer score inspection
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 2)

	inspector := &mockPeerScoreInspector{}
	psub1 := getBlossomSub(ctx, hosts[0],
		WithPeerScore(
			&PeerScoreParams{
				Bitmasks: map[string]*BitmaskScoreParams{
					string([]byte{0x00, 0x00, 0x80, 0x00}): {
						BitmaskWeight:                  1,
						TimeInMeshQuantum:              time.Second,
						FirstMessageDeliveriesWeight:   1,
						FirstMessageDeliveriesDecay:    0.999,
						FirstMessageDeliveriesCap:      100,
						InvalidMessageDeliveriesWeight: -1,
						InvalidMessageDeliveriesDecay:  0.9999,
					},
				},
				AppSpecificScore: func(peer.ID) float64 { return 0 },
				DecayInterval:    time.Second,
				DecayToZero:      0.01,
			},
			&PeerScoreThresholds{
				GossipThreshold:   -1,
				PublishThreshold:  -10,
				GraylistThreshold: -1000,
			}),
		WithPeerScoreInspect(inspector.inspect, time.Second))
	psub2 := getBlossomSub(ctx, hosts[1])
	psubs := []*PubSub{psub1, psub2}

	connect(t, hosts[0], hosts[1])
	var bitmasks []*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b...)
		_, err = ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
		if err != nil {
			t.Fatal(err)
		}
	}

	time.Sleep(time.Second)

	for i := 0; i < 20; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		bitmasks[i%2].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
		time.Sleep(20 * time.Millisecond)
	}

	time.Sleep(time.Second + 200*time.Millisecond)

	score2 := inspector.score(hosts[1].ID())
	if score2 < 9 {
		t.Fatalf("expected score to be at least 9, instead got %f", score2)
	}
}

func TestBlossomSubPeerScoreResetBitmaskParams(t *testing.T) {
	// this test exercises the code path sof peer score inspection
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 1)

	ps := getBlossomSub(ctx, hosts[0],
		WithPeerScore(
			&PeerScoreParams{
				Bitmasks: map[string]*BitmaskScoreParams{
					string([]byte{0x00, 0x00, 0x80, 0x00}): {
						BitmaskWeight:                  1,
						TimeInMeshQuantum:              time.Second,
						FirstMessageDeliveriesWeight:   1,
						FirstMessageDeliveriesDecay:    0.999,
						FirstMessageDeliveriesCap:      100,
						InvalidMessageDeliveriesWeight: -1,
						InvalidMessageDeliveriesDecay:  0.9999,
					},
				},
				AppSpecificScore: func(peer.ID) float64 { return 0 },
				DecayInterval:    time.Second,
				DecayToZero:      0.01,
			},
			&PeerScoreThresholds{
				GossipThreshold:   -1,
				PublishThreshold:  -10,
				GraylistThreshold: -1000,
			}))

	bitmask, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	err = bitmask[0].SetScoreParams(
		&BitmaskScoreParams{
			BitmaskWeight:                  1,
			TimeInMeshQuantum:              time.Second,
			FirstMessageDeliveriesWeight:   1,
			FirstMessageDeliveriesDecay:    0.999,
			FirstMessageDeliveriesCap:      200,
			InvalidMessageDeliveriesWeight: -1,
			InvalidMessageDeliveriesDecay:  0.9999,
		})
	if err != nil {
		t.Fatal(err)
	}
}

type mockPeerScoreInspector struct {
	mx     sync.Mutex
	scores map[peer.ID]float64
}

func (ps *mockPeerScoreInspector) inspect(scores map[peer.ID]float64) {
	ps.mx.Lock()
	defer ps.mx.Unlock()
	ps.scores = scores
}

func (ps *mockPeerScoreInspector) score(p peer.ID) float64 {
	ps.mx.Lock()
	defer ps.mx.Unlock()
	return ps.scores[p]
}

func TestBlossomSubRPCFragmentation(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 2)
	ps := getBlossomSub(ctx, hosts[0])

	// make a fake peer that requests everything through IWANT gossip
	iwe := iwantEverything{h: hosts[1]}
	iwe.h.SetStreamHandler(BlossomSubID_v2, iwe.handleStream)

	connect(t, hosts[0], hosts[1])

	// have the real pubsub join the test bitmask
	b, err := ps.Join([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}
	_, err = ps.Subscribe([]byte{0x00, 0x00, 0x80, 0x00})
	if err != nil {
		t.Fatal(err)
	}

	// wait for the real pubsub to connect and try to graft to the faker
	time.Sleep(time.Second)

	// publish a bunch of fairly large messages from the real host
	nMessages := 1000
	msgSize := 20000
	for i := 0; i < nMessages; i++ {
		msg := make([]byte, msgSize)
		rand.Read(msg)
		b[0].Publish(ctx, []byte{0x00, 0x00, 0x80, 0x00}, msg)
		time.Sleep(20 * time.Millisecond)
	}

	// wait a bit for them to be received via gossip by the fake peer
	time.Sleep(5 * time.Second)
	iwe.lk.Lock()
	defer iwe.lk.Unlock()

	// we should have received all the messages
	if iwe.msgsReceived != nMessages {
		t.Fatalf("expected fake BlossomSub peer to receive all messages, got %d / %d", iwe.msgsReceived, nMessages)
	}

	// and we should have seen an IHAVE message for each of them
	if iwe.ihavesReceived != nMessages {
		t.Fatalf("expected to get IHAVEs for every message, got %d / %d", iwe.ihavesReceived, nMessages)
	}

	// If everything were fragmented with maximum efficiency, we would expect to get
	// (nMessages * msgSize) / ps.maxMessageSize total RPCs containing the messages we sent IWANTs for.
	// The actual number will probably be larger, since there's some overhead for the RPC itself, and
	// we probably aren't packing each RPC to it's maximum size
	minExpectedRPCS := (nMessages * msgSize) / ps.maxMessageSize
	if iwe.rpcsWithMessages < minExpectedRPCS {
		t.Fatalf("expected to receive at least %d RPCs containing messages, got %d", minExpectedRPCS, iwe.rpcsWithMessages)
	}
}

// iwantEverything is a simple BlossomSub client that never grafts onto a mesh,
// instead requesting everything through IWANT gossip messages. It is used to
// test that large responses to IWANT requests are fragmented into multiple RPCs.
type iwantEverything struct {
	h                host.Host
	lk               sync.Mutex
	rpcsWithMessages int
	msgsReceived     int
	ihavesReceived   int
}

func (iwe *iwantEverything) handleStream(s network.Stream) {
	defer s.Close()

	os, err := iwe.h.NewStream(context.Background(), s.Conn().RemotePeer(), BlossomSubID_v2)
	if err != nil {
		panic(err)
	}

	msgIdsReceived := make(map[string]struct{})
	gossipMsgIdsReceived := make(map[string]struct{})

	// send a subscription for test in the output stream to become candidate for gossip
	r := msgio.NewVarintReaderSize(s, DefaultMaxMessageSize)
	w := msgio.NewVarintWriter(os)
	truth := true
	bitmask := []byte{0x00, 0x00, 0x80, 0x00}
	msg := &pb.RPC{Subscriptions: []*pb.RPC_SubOpts{{Subscribe: truth, Bitmask: bitmask}}}
	out, err := proto.Marshal(msg)

	if err != nil {
		panic(err)
	}

	err = w.WriteMsg(out)
	if err != nil {
		panic(err)
	}

	var rpc pb.RPC
	for {
		rpc.Reset()
		v, err := r.ReadMsg()
		if err != nil {
			break
		}

		err = proto.Unmarshal(v, &rpc)
		if err != nil {
			break
		}

		iwe.lk.Lock()
		if len(rpc.Publish) != 0 {
			iwe.rpcsWithMessages++
		}
		// keep track of unique message ids received
		for _, msg := range rpc.Publish {
			id := string(msg.Seqno)
			if _, seen := msgIdsReceived[id]; !seen {
				iwe.msgsReceived++
			}
			msgIdsReceived[id] = struct{}{}
		}

		if rpc.Control != nil {
			// send a PRUNE for all grafts, so we don't get direct message deliveries
			var prunes []*pb.ControlPrune
			for _, graft := range rpc.Control.Graft {
				prunes = append(prunes, &pb.ControlPrune{Bitmask: graft.Bitmask})
			}

			var iwants []*pb.ControlIWant
			for _, ihave := range rpc.Control.Ihave {
				iwants = append(iwants, &pb.ControlIWant{MessageIDs: ihave.MessageIDs})
				for _, msgId := range ihave.MessageIDs {
					if _, seen := gossipMsgIdsReceived[string(msgId)]; !seen {
						iwe.ihavesReceived++
					}
					gossipMsgIdsReceived[string(msgId)] = struct{}{}
				}
			}

			msg := rpcWithControl(nil, nil, iwants, nil, prunes)
			out, err := proto.Marshal(msg)

			if err != nil {
				panic(err)
			}

			err = w.WriteMsg(out)
			if err != nil {
				panic(err)
			}
		}
		iwe.lk.Unlock()
	}
}

func TestFragmentRPCFunction(t *testing.T) {
	p := peer.ID("some-peer")
	bitmask := []byte{0x00, 0x00, 0x80, 0x00}
	rpc := &RPC{from: p}
	limit := 1024

	mkMsg := func(size int) *pb.Message {
		msg := &pb.Message{}
		msg.Data = make([]byte, size-4) // subtract the protobuf overhead, so msg.Size() returns requested size
		rand.Read(msg.Data)
		return msg
	}

	ensureBelowLimit := func(rpcs []*RPC) {
		for _, r := range rpcs {
			if r.Size() > limit {
				t.Fatalf("expected fragmented RPC to be below %d bytes, was %d", limit, r.Size())
			}
		}
	}

	// it should not fragment if everything fits in one RPC
	rpc.Publish = []*pb.Message{}
	rpc.Publish = []*pb.Message{mkMsg(10), mkMsg(10)}
	results := appendOrMergeRPC([]*RPC{}, limit, *rpc)
	if len(results) != 1 {
		t.Fatalf("expected single RPC if input is < limit, got %d", len(results))
	}

	// if there's a message larger than the limit, we should fail
	rpc.Publish = []*pb.Message{mkMsg(10), mkMsg(limit * 2)}
	results = appendOrMergeRPC([]*RPC{}, limit, *rpc)

	// if the individual messages are below the limit, but the RPC as a whole is larger, we should fragment
	nMessages := 100
	msgSize := 200
	truth := true
	rpc.Subscriptions = []*pb.RPC_SubOpts{
		{
			Subscribe: truth,
			Bitmask:   bitmask,
		},
	}
	rpc.Publish = make([]*pb.Message, nMessages)
	for i := 0; i < nMessages; i++ {
		rpc.Publish[i] = mkMsg(msgSize)
	}
	results = appendOrMergeRPC([]*RPC{}, limit, *rpc)
	ensureBelowLimit(results)
	msgsPerRPC := limit / msgSize
	expectedRPCs := nMessages / msgsPerRPC
	if len(results) != expectedRPCs {
		t.Fatalf("expected %d RPC messages in output, got %d", expectedRPCs, len(results))
	}
	var nMessagesFragmented int
	var nSubscriptions int
	for _, r := range results {
		nMessagesFragmented += len(r.Publish)
		nSubscriptions += len(r.Subscriptions)
	}
	if nMessagesFragmented != nMessages {
		t.Fatalf("expected fragemented RPCs to contain same number of messages as input, got %d / %d", nMessagesFragmented, nMessages)
	}
	if nSubscriptions != 1 {
		t.Fatal("expected subscription to be present in one of the fragmented messages, but not found")
	}

	// if we're fragmenting, and the input RPC has control messages,
	// the control messages should be in a separate RPC at the end
	// reuse RPC from prev test, but add a control message
	rpc.Control = &pb.ControlMessage{
		Graft: []*pb.ControlGraft{{Bitmask: bitmask}},
		Prune: []*pb.ControlPrune{{Bitmask: bitmask}},
		Ihave: []*pb.ControlIHave{{MessageIDs: [][]byte{[]byte("foo")}}},
		Iwant: []*pb.ControlIWant{{MessageIDs: [][]byte{[]byte("bar")}}},
	}
	results = appendOrMergeRPC([]*RPC{}, limit, *rpc)
	ensureBelowLimit(results)
	// we expect one more RPC than last time, with the final one containing the control messages
	expectedCtrl := 1
	expectedRPCs = (nMessages / msgsPerRPC) + expectedCtrl
	if len(results) != expectedRPCs {
		t.Fatalf("expected %d RPC messages in output, got %d", expectedRPCs, len(results))
	}
	ctl := results[len(results)-1].Control
	if ctl == nil {
		t.Fatal("expected final fragmented RPC to contain control messages, but .Control was nil")
	}
	// since it was not altered, the original control message should be identical to the output control message
	originalBytes, err := rpc.Control.Marshal()
	if err != nil {
		t.Fatal(err)
	}
	receivedBytes, err := ctl.Marshal()
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(originalBytes, receivedBytes) {
		t.Fatal("expected control message to be unaltered if it fits within one RPC message")
	}

	// if the control message is too large to fit into a single RPC, it should be split into multiple RPCs
	nBitmasks := 5 // pretend we're subscribed to multiple bitmasks and sending IHAVE / IWANTs for each
	messageIdSize := 32
	msgsPerBitmask := 100 // enough that a single IHAVE or IWANT will exceed the limit
	rpc.Control.Ihave = make([]*pb.ControlIHave, nBitmasks)
	rpc.Control.Iwant = make([]*pb.ControlIWant, nBitmasks)
	for i := 0; i < nBitmasks; i++ {
		messageIds := make([][]byte, msgsPerBitmask)
		for m := 0; m < msgsPerBitmask; m++ {
			mid := make([]byte, messageIdSize)
			rand.Read(mid)
			messageIds[m] = mid
		}
		rpc.Control.Ihave[i] = &pb.ControlIHave{MessageIDs: messageIds}
		rpc.Control.Iwant[i] = &pb.ControlIWant{MessageIDs: messageIds}
	}
	results = appendOrMergeRPC([]*RPC{}, limit, *rpc)
	ensureBelowLimit(results)
	minExpectedCtl := rpc.Control.Size() / limit
	minExpectedRPCs := (nMessages / msgsPerRPC) + minExpectedCtl
	if len(results) < minExpectedRPCs {
		t.Fatalf("expected at least %d total RPCs (at least %d with control messages), got %d total", expectedRPCs, expectedCtrl, len(results))
	}

	// Test the pathological case where a single gossip message ID exceeds the limit.
	rpc.Reset()
	giantIdBytes := make([]byte, limit*2)
	rand.Read(giantIdBytes)
	rpc.Control = &pb.ControlMessage{
		Iwant: []*pb.ControlIWant{
			{MessageIDs: [][]byte{[]byte("hello"), giantIdBytes}},
		},
	}
	results = appendOrMergeRPC([]*RPC{}, limit, *rpc)
	if len(results) != 2 {
		t.Fatalf("expected 2 RPC, got %d", len(results))
	}
	if len(results[0].Control.Iwant) != 1 {
		t.Fatalf("expected 1 IWANT, got %d", len(results[0].Control.Iwant))
	}
	if len(results[1].Control.Iwant) != 1 {
		t.Fatalf("expected 1 IWANT, got %d", len(results[1].Control.Iwant))
	}
	if !bytes.Equal(results[0].Control.Iwant[0].MessageIDs[0], []byte("hello")) {
		t.Fatalf("expected small message ID to be included unaltered, got %s instead",
			results[0].Control.Iwant[0].MessageIDs[0])
	}
	if !bytes.Equal(results[1].Control.Iwant[0].MessageIDs[0], giantIdBytes) {
		t.Fatalf("expected giant message ID to be included unaltered, got %s instead",
			results[1].Control.Iwant[0].MessageIDs[0])
	}
}

func TestBloomRouting(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	hosts := getDefaultHosts(t, 20)
	psubs := getBlossomSubs(ctx, hosts)

	var msgs [][]*Subscription
	var bitmasks [][]*Bitmask
	targetSets := [][]byte{
		{0x00, 0x01},
		{0x00, 0x10},
		{0x01, 0x00},
		{0x01, 0x01},
		{0x01, 0x11},
	}

	expectedGroups := [][]int{
		{0, 3, 4},
		{1, 4},
		{2, 3, 4},
		{3, 4},
		{4},
	}

	for i, ps := range psubs {
		b, err := ps.Join(targetSets[i%5])
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b)

		subch, err := ps.Subscribe(targetSets[i%5])
		if err != nil {
			t.Fatal(err)
		}

		msgs = append(msgs, subch)
	}

	connectAll(t, hosts)

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	for i := 0; i < 100; i++ {
		msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i))

		owner := rand.Intn(len(psubs))

		psubs[owner].Publish(ctx, targetSets[owner%5], msg)

		for i, sub := range msgs {
			if !slices.Contains(expectedGroups[owner%5], i%5) {
				continue
			}

			// Normally the expectation is that any subscription will do when using a bloom bitmask
			// But we need to verify one gets it.
			g := sync.WaitGroup{}
			g.Add(len(sub))
			errch := make(chan error)
			var errs []error
			for _, s := range sub {
				s := s
				go func() {
					defer g.Done()
					nctx, _ := context.WithDeadline(ctx, time.Now().Add(10*time.Millisecond))
					got, err := s.Next(nctx)
					if err != nil {
						errch <- err
						return
					}
					if !bytes.Equal(msg, got.Data) {
						errch <- errors.New("got wrong message!")
						return
					}
					errch <- nil
				}()
			}

			go func() {
				for i := 0; i < len(sub); i++ {
					select {
					case err := <-errch:
						if err != nil {
							errs = append(errs, err)
						}
					}
				}
			}()
			g.Wait()
			if len(errs) == len(sub) {
				t.Fatal(errors.Join(errs...))
			}
		}
	}
}

func TestBloomPropagationOverSubTreeTopology(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 10)
	psubs := getBlossomSubs(ctx, hosts)

	connect(t, hosts[0], hosts[1])
	connect(t, hosts[1], hosts[2])
	connect(t, hosts[1], hosts[4])
	connect(t, hosts[2], hosts[3])
	connect(t, hosts[0], hosts[5])
	connect(t, hosts[5], hosts[6])
	connect(t, hosts[5], hosts[8])
	connect(t, hosts[6], hosts[7])
	connect(t, hosts[8], hosts[9])

	/*
		[0] -> [1] -> [2] -> [3]
		 |      L->[4]
		 v
		[5] -> [6] -> [7]
		 |
		 v
		[8] -> [9]
	*/

	var chs [][]*Subscription
	var bitmasks [][]*Bitmask
	for _, ps := range psubs {
		b, err := ps.Join([]byte{0x10, 0x10, 0x10, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		bitmasks = append(bitmasks, b)
		ch, err := ps.Subscribe([]byte{0x10, 0x10, 0x10, 0x00})
		if err != nil {
			t.Fatal(err)
		}

		chs = append(chs, ch)
	}

	// wait for heartbeats to build mesh
	time.Sleep(time.Second * 2)

	assertPeerLists(t, []byte{0x10, 0x10, 0x10, 0x00}, hosts, psubs[0], 1, 5)
	assertPeerLists(t, []byte{0x10, 0x10, 0x10, 0x00}, hosts, psubs[1], 0, 2, 4)
	assertPeerLists(t, []byte{0x10, 0x10, 0x10, 0x00}, hosts, psubs[2], 1, 3)

	for _, p := range bitmasks {
		data := make([]byte, 32)
		rand.Read(data)
		err := p[0].Publish(ctx, []byte{0x10, 0x10, 0x10, 0x00}, data)
		if err != nil {
			t.Fatal(err)
		}

		for _, subs := range chs {
			g := sync.WaitGroup{}
			g.Add(len(subs))
			nctx, cancel := context.WithCancel(ctx)
			msgch := make(chan struct{})
			for _, s := range subs {
				s := s
				go func() {
					nctx, _ := context.WithDeadline(nctx, time.Now().Add(10*time.Millisecond))
					got, err := s.Next(nctx)
					if err != nil {
						g.Done()
						return
					}

					if !bytes.Equal(data, got.Data) {
						g.Done()
						return
					}
					msgch <- struct{}{}
					g.Done()
				}()
			}

			var msg *struct{} = nil
			go func() {
				for i := 0; i < len(subs); i++ {
					select {
					case m := <-msgch:
						msg = &m
						cancel()
					}
				}
			}()
			g.Wait()
			if msg == nil {
				t.Fatal("didn't get message")
			}
		}
	}
}

func TestBlossomSubBloomStarTopology(t *testing.T) {
	originalBlossomSubD := BlossomSubD
	BlossomSubD = 4
	originalBlossomSubDhi := BlossomSubDhi
	BlossomSubDhi = BlossomSubD + 1
	originalBlossomSubDlo := BlossomSubDlo
	BlossomSubDlo = BlossomSubD - 1
	originalBlossomSubDscore := BlossomSubDscore
	BlossomSubDscore = BlossomSubDlo
	defer func() {
		BlossomSubD = originalBlossomSubD
		BlossomSubDhi = originalBlossomSubDhi
		BlossomSubDlo = originalBlossomSubDlo
		BlossomSubDscore = originalBlossomSubDscore
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getDefaultHosts(t, 200)
	psubs := []*PubSub{}
	// Core bootstrapper:
	psubs = append(psubs, getBlossomSubs(ctx, hosts[:1], WithPeerExchange(true), WithFloodPublish(true))...)
	// Everyone else:
	psubs = append(psubs, getBlossomSubs(ctx, hosts[1:])...)

	// configure the center of the star with a very low D
	psubs[0].eval <- func() {
		gs := psubs[0].rt.(*BlossomSubRouter)
		gs.params.D = 0
		gs.params.Dlo = 0
		gs.params.Dhi = 0
		gs.params.Dscore = 0
	}

	// add all peer addresses to the peerstores
	// this is necessary because we can't have signed address records witout identify
	// pushing them
	for i := range hosts {
		for j := range hosts {
			if i == j {
				continue
			}
			hosts[i].Peerstore().AddAddrs(hosts[j].ID(), hosts[j].Addrs(), peerstore.PermanentAddrTTL)
		}
	}

	// build the star
	for i := 1; i < 200; i++ {
		connect(t, hosts[0], hosts[i])
	}

	fullBitmask := []byte{
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	}
	slices := [][]byte{}
	for i := 0; i < 63; i++ {
		if i%2 == 0 {
			slices = append(
				slices,
				append(
					append(
						bytes.Repeat([]byte{0x00}, i/2),
						0xff,
					),
					bytes.Repeat([]byte{0x00}, 31-i/2)...,
				),
			)
		} else {
			slices = append(
				slices,
				append(
					append(
						bytes.Repeat([]byte{0x00}, i/2),
						0x0f,
						0xf0,
					),
					bytes.Repeat([]byte{0x00}, 30-i/2)...,
				),
			)
		}
	}
	time.Sleep(time.Second)

	// build the mesh
	var subs [][]*Subscription
	var bitmasks [][]*Bitmask
	for i, ps := range psubs {
		if i == 0 {
			b, err := ps.Join(fullBitmask)
			if err != nil {
				t.Fatal(err)
			}

			bitmasks = append(bitmasks, b)
			sub, err := ps.Subscribe(fullBitmask)
			if err != nil {
				t.Fatal(err)
			}
			subs = append(subs, sub)
		} else {
			b, err := ps.Join(slices[i%len(slices)])
			if err != nil {
				t.Fatal(err)
			}

			bitmasks = append(bitmasks, b)
			sub, err := ps.Subscribe(slices[i%len(slices)])
			if err != nil {
				t.Fatal(err)
			}
			subs = append(subs, sub)
		}
	}

	// wait a bit for the mesh to build
	time.Sleep(2 * time.Second)

	// check that all peers have > 1 connection
	for i, h := range hosts {
		if len(h.Network().Conns()) == 1 {
			t.Errorf("peer %d has ony a single connection", i)
		}
	}

	// send a message from each peer and assert it was propagated
	for i := 0; i < 600; i++ {
		msg := []byte(fmt.Sprintf("message %d", i))
		if i == 0 {
			for j := 0; j < 256; j++ {
				msg = []byte(fmt.Sprintf("message %d-sub-%d", i, j))
				bitmasks[i%200][j].Publish(ctx, bitmasks[i%200][j].bitmask, msg)

				subgroup := [][]*Subscription{}
				for _, group := range subs {
					group := group
					for _, s := range group {
						if containsBitmask(bitmasks[i%200][j].bitmask, s.bitmask) {
							subgroup = append(subgroup, group)
							break
						}
					}
				}
				assertReceivedBitmaskSubgroup(t, ctx, subgroup, msg)
			}
		} else {
			psubs[i%200].Publish(ctx, slices[(i%200)%len(slices)], msg)

			subgroup := [][]*Subscription{}
			for _, group := range subs[1:] {
				group := group
				in := true
				for _, s := range group {
					if !containsBitmask(slices[(i%200)%len(slices)], s.bitmask) {
						in = false
						break
					}
				}
				if in {
					subgroup = append(subgroup, group)
				}
			}
			assertReceivedBitmaskSubgroup(t, ctx, subgroup, msg)
		}
	}
}

func containsBitmask(bitmask []byte, slice []byte) bool {
	out := make([]byte, len(slice))
	for i, b := range bitmask {
		out[i] = b & slice[i]
	}

	return bytes.Equal(out, slice)
}

func assertReceivedBitmaskSubgroup(t *testing.T, ctx context.Context, subs [][]*Subscription, msg []byte) {
	for i, subs := range subs {
		g := sync.WaitGroup{}
		g.Add(len(subs))
		nctx, cancel := context.WithCancel(ctx)
		msgch := make(chan struct{})
		for _, s := range subs {
			s := s
			go func() {
				nctx, _ := context.WithDeadline(nctx, time.Now().Add(10*time.Millisecond))
				got, err := s.Next(nctx)
				if err != nil {
					g.Done()
					return
				}

				if !bytes.Equal(msg, got.Data) {
					g.Done()
					return
				}
				msgch <- struct{}{}
				g.Done()
			}()
		}

		var msg *struct{} = nil
		go func() {
			for i := 0; i < len(subs); i++ {
				select {
				case m := <-msgch:
					msg = &m
					cancel()
				}
			}
		}()
		g.Wait()
		if msg == nil {
			t.Fatalf("%d didn't get message", i)
		}
	}
}
