diff --git a/bindings/dummy.go b/bindings/dummy.go new file mode 100644 index 0000000000000000000000000000000000000000..8cbaa84dc6a26f8b28765b5334b5be1bd6fe5df3 --- /dev/null +++ b/bindings/dummy.go @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "gitlab.com/elixxir/client/dummy" + "time" +) + +// StartDummyTraffic starts sending dummy traffic. The maxNumMessages is the +// upper bound of the random number of messages sent each send. avgSendDeltaMS +// is the average duration, in milliseconds, to wait between sends. Sends occur +// every avgSendDeltaMS +/- a random duration with an upper bound of +// randomRangeMS. +func StartDummyTraffic(client *Client, maxNumMessages, avgSendDeltaMS, + randomRangeMS int) error { + avgSendDelta := time.Duration(avgSendDeltaMS) * time.Millisecond + randomRange := time.Duration(randomRangeMS) * time.Millisecond + + m := dummy.NewManager( + maxNumMessages, avgSendDelta, randomRange, &client.api) + + return client.api.AddService(m.StartDummyTraffic) +} diff --git a/dummy/manager.go b/dummy/manager.go new file mode 100644 index 0000000000000000000000000000000000000000..edf0b3ac3d1ece2e8fda7ab81376d9b162241a8d --- /dev/null +++ b/dummy/manager.go @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +// Package dummy allows for the sending of dummy messages to dummy recipients +// via SendCmix at randomly generated intervals. + +package dummy + +import ( + "gitlab.com/elixxir/client/api" + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/crypto/fastRNG" + "time" +) + +const ( + dummyTrafficStoppableName = "DummyTraffic" +) + +// Manager manages the sending of dummy messages. +type Manager struct { + // The maximum number of messages to send each send + maxNumMessages int + + // Average duration to wait between message sends + avgSendDelta time.Duration + + // Upper limit for random duration that modified avgSendDelta + randomRange time.Duration + + // Client interfaces + client *api.Client + store *storage.Session + net interfaces.NetworkManager + rng *fastRNG.StreamGenerator +} + +// NewManager creates a new dummy Manager with the specified average send delta +// and the range used for generating random durations. +func NewManager(maxNumMessages int, avgSendDelta, randomRange time.Duration, + client *api.Client) *Manager { + return newManager(maxNumMessages, avgSendDelta, randomRange, client, + client.GetStorage(), client.GetNetworkInterface(), client.GetRng()) +} + +// newManager builds a new dummy Manager from fields explicitly passed in. This +// function is a helper function for NewManager to make it easier to test. +func newManager(maxNumMessages int, avgSendDelta, randomRange time.Duration, + client *api.Client, store *storage.Session, net interfaces.NetworkManager, + rng *fastRNG.StreamGenerator) *Manager { + return &Manager{ + maxNumMessages: maxNumMessages, + avgSendDelta: avgSendDelta, + randomRange: randomRange, + client: client, + store: store, + net: net, + rng: rng, + } +} + +// StartDummyTraffic starts the process of sending dummy traffic. This function +// matches the api.Service type. +func (m *Manager) StartDummyTraffic() (stoppable.Stoppable, error) { + stop := stoppable.NewSingle(dummyTrafficStoppableName) + go m.sendThread(stop) + + return stop, nil +} diff --git a/dummy/manager_test.go b/dummy/manager_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7f8ec99d037dd14576951237abb6113518d89ab9 --- /dev/null +++ b/dummy/manager_test.go @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "reflect" + "testing" + "time" +) + +// Tests that newManager returns the expected Manager. +func Test_newManager(t *testing.T) { + expected := &Manager{ + maxNumMessages: 10, + avgSendDelta: time.Minute, + randomRange: time.Second, + } + + received := newManager(expected.maxNumMessages, expected.avgSendDelta, + expected.randomRange, nil, nil, nil, nil) + + if !reflect.DeepEqual(expected, received) { + t.Errorf("New manager does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, received) + } +} + +// Tests that Manager.StartDummyTraffic sends dummy messages and that it stops +// when the stoppable is closed. +func TestManager_StartDummyTraffic(t *testing.T) { + m := newTestManager(10, 50*time.Millisecond, 10*time.Millisecond, false, t) + + stop, err := m.StartDummyTraffic() + if err != nil { + t.Errorf("StartDummyTraffic returned an error: %+v", err) + } + + msgChan := make(chan bool) + go func() { + for m.net.(*testNetworkManager).GetMsgListLen() == 0 { + time.Sleep(5 * time.Millisecond) + } + msgChan <- true + }() + + var numReceived int + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + t.Errorf("Timed out after %s waiting for messages to be sent.", + 3*m.avgSendDelta) + case <-msgChan: + numReceived += m.net.(*testNetworkManager).GetMsgListLen() + } + + err = stop.Close() + if err != nil { + t.Errorf("Failed to close stoppable: %+v", err) + } + + time.Sleep(10 * time.Millisecond) + if !stop.IsStopped() { + t.Error("Stoppable never stopped.") + } + + msgChan = make(chan bool) + go func() { + for m.net.(*testNetworkManager).GetMsgListLen() == numReceived { + time.Sleep(5 * time.Millisecond) + } + msgChan <- true + }() + + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + + case <-msgChan: + t.Error("Received new messages after stoppable was stopped.") + } +} diff --git a/dummy/random.go b/dummy/random.go new file mode 100644 index 0000000000000000000000000000000000000000..2327ddf6c7d9b978ecf6e07e34f53b733155c87e --- /dev/null +++ b/dummy/random.go @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "encoding/binary" + "github.com/pkg/errors" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/csprng" + "time" +) // Error messages. +const ( + payloadSizeRngErr = "failed to generate random payload size: %+v" +) + +// intRng returns, as an int, a non-negative, non-zero random number in [1, n) +// from the csprng.Source. +func intRng(n int, rng csprng.Source) (int, error) { + v, err := csprng.Generate(8, rng) + if err != nil { + return 0, err + } + + return int(binary.LittleEndian.Uint64(v)%uint64(n-1)) + 1, nil +} + +// durationRng returns a duration that is the base duration plus or minus a +// random duration of max randomRange. +func durationRng(base, randomRange time.Duration, rng csprng.Source) ( + time.Duration, error) { + delta, err := intRng(int(2*randomRange), rng) + if err != nil { + return 0, err + } + + return base + randomRange - time.Duration(delta), nil +} + +// newRandomPayload generates a random payload of a random length. +func newRandomPayload(maxPayloadSize int, rng csprng.Source) ([]byte, error) { + // Generate random payload size + randomPayloadSize, err := intRng(maxPayloadSize, rng) + if err != nil { + return nil, errors.Errorf(payloadSizeRngErr, err) + } + + randomMsg, err := csprng.Generate(randomPayloadSize, rng) + if err != nil { + return nil, err + } + + return randomMsg, nil +} + +// newRandomFingerprint generates a random format.Fingerprint. +func newRandomFingerprint(rng csprng.Source) (format.Fingerprint, error) { + fingerprintBytes, err := csprng.Generate(format.KeyFPLen, rng) + if err != nil { + return format.Fingerprint{}, err + } + + // Create new fingerprint from bytes + fingerprint := format.NewFingerprint(fingerprintBytes) + + // Set the first bit to be 0 to comply with the cMix group + fingerprint[0] &= 0x7F + + return fingerprint, nil +} + +// newRandomMAC generates a random MAC. +func newRandomMAC(rng csprng.Source) ([]byte, error) { + mac, err := csprng.Generate(format.MacLen, rng) + if err != nil { + return nil, err + } + + // Set the first bit to be 0 to comply with the cMix group + mac[0] &= 0x7F + + return mac, nil +} diff --git a/dummy/random_test.go b/dummy/random_test.go new file mode 100644 index 0000000000000000000000000000000000000000..661986a0416993e211209d009a023c451dd3ff60 --- /dev/null +++ b/dummy/random_test.go @@ -0,0 +1,188 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "encoding/base64" + "testing" + "time" +) + +// Consistency test: tests that intRng returns the expected int when using a +// PRNG and that the result is not larger than the max. +func Test_intRng_Consistency(t *testing.T) { + expectedInts := []int{15, 1, 35, 13, 42, 52, 57, 3, 48} + + prng := NewPrng(42) + max := 64 + + for i, expected := range expectedInts { + v, err := intRng(max, prng) + if err != nil { + t.Errorf("intRng returned an error (%d): %+v", i, err) + } + + if v != expected { + t.Errorf("New int #%d does not match expected."+ + "\nexpected: %d\nreceived: %d", i, expected, v) + } + + // Ensure that the int is in range + if v > max || v < 1 { + t.Errorf("Int #%d not within range."+ + "\nexpected: %d < d < %d\nreceived: %d", i, 0, max, v) + } + } +} + +// Consistency test: tests that durationRng returns the expected int when using +// a PRNG and that the result is within the allowed range. +func Test_durationRng_Consistency(t *testing.T) { + expectedDurations := []time.Duration{ + 61460632462, 69300060600, 46066982720, 68493307162, 45820762465, + 56472560211, 68610237306, 45503877311, 63543617747, + } + + prng := NewPrng(42) + base, randomRange := time.Minute, 15*time.Second + + for i, expected := range expectedDurations { + v, err := durationRng(base, randomRange, prng) + if err != nil { + t.Errorf("durationRng returned an error (%d): %+v", i, err) + } + + if v != expected { + t.Errorf("New duration #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, v) + } + + // Ensure that the duration is within range + if v > base+randomRange || v < base-randomRange { + t.Errorf("Duration #%d is not in range."+ + "\nexpected: %s < d < %s\nreceived: %s", i, base-randomRange, + base+randomRange, v) + } + } +} + +// Consistency test: tests that newRandomPayload returns the expected payload +// when using a PRNG and that the result is not larger than the max payload. +func Test_newRandomPayload_Consistency(t *testing.T) { + expectedPayloads := []string{ + "l7ufS7Ry6J9bFITyUgnJ", + "Ut/Xm012Qpthegyfnw07pVsMwNYUTIiFNQ==", + "CD9h", + "GSnh", + "joE=", + "uoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44bC6+uiBuCpw==", + "qkNGWnhiBhaXiu0M48bE8657w+BJW1cS/v2+DBAoh+EA2s0tiF9pLLYH2gChHBxwcec=", + "suEpcF4nPwXJIyaCjisFbg==", + "R/3zREEO1MEWAj+o41drb+0n/4l0usDK/ZrQVpKxNhnnOJZN/ceejVNDc2Yc/WbXTw==", + "bkt1IQ==", + } + + prng := NewPrng(42) + maxPayloadSize := 64 + + for i, expected := range expectedPayloads { + payload, err := newRandomPayload(maxPayloadSize, prng) + if err != nil { + t.Errorf("newRandomPayload returned an error (%d): %+v", i, err) + } + + payloadString := base64.StdEncoding.EncodeToString(payload) + + if payloadString != expected { + t.Errorf("New payload #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, payloadString) + } + + // Ensure that the payload is not larger than the max size + if len(payload) > maxPayloadSize { + t.Errorf("Length of payload #%d longer than max allowed."+ + "\nexpected: <%d\nreceived: %d", i, maxPayloadSize, len(payload)) + } + } +} + +// Consistency test: tests that newRandomFingerprint returns the expected +// fingerprints when using a PRNG. Also tests that the first bit is zero. +func Test_newRandomFingerprint_Consistency(t *testing.T) { + expectedFingerprints := []string{ + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=", + "X9ebTXZCm2F6DJ+fDTulWwzA1hRMiIU1hBrL4HCbB1g=", + "CD9h03W8ArQd9PkZKeGP2p5vguVOdI6B555LvW/jTNw=", + "OoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44=", + "GwuvrogbgqdREIpC7TyQPKpDRlp4YgYWl4rtDOPGxPM=", + "LnvD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHA=", + "ceeWotwtwlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGE=", + "SYlH/fNEQQ7UwRYCP6jjV2tv7Sf/iXS6wMr9mtBWkrE=", + "NhnnOJZN/ceejVNDc2Yc/WbXT+weG4lJGrcjbkt1IWI=", + "EM8r60LDyicyhWDxqsBnzqbov0bUqytGgEAsX7KCDog=", + } + + prng := NewPrng(42) + + for i, expected := range expectedFingerprints { + fp, err := newRandomFingerprint(prng) + if err != nil { + t.Errorf("newRandomFingerprint returned an error (%d): %+v", i, err) + } + + if fp.String() != expected { + t.Errorf("New fingerprint #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, fp) + } + + // Ensure that the first bit is zero + if fp[0]>>7 != 0 { + t.Errorf("First bit of fingerprint #%d is not 0."+ + "\nexpected: %d\nreceived: %d", i, 0, fp[0]>>7) + } + } +} + +// Consistency test: tests that newRandomMAC returns the expected MAC when using +// a PRNG. Also tests that the first bit is zero. +func Test_newRandomMAC_Consistency(t *testing.T) { + expectedMACs := []string{ + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=", + "X9ebTXZCm2F6DJ+fDTulWwzA1hRMiIU1hBrL4HCbB1g=", + "CD9h03W8ArQd9PkZKeGP2p5vguVOdI6B555LvW/jTNw=", + "OoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44=", + "GwuvrogbgqdREIpC7TyQPKpDRlp4YgYWl4rtDOPGxPM=", + "LnvD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHA=", + "ceeWotwtwlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGE=", + "SYlH/fNEQQ7UwRYCP6jjV2tv7Sf/iXS6wMr9mtBWkrE=", + "NhnnOJZN/ceejVNDc2Yc/WbXT+weG4lJGrcjbkt1IWI=", + "EM8r60LDyicyhWDxqsBnzqbov0bUqytGgEAsX7KCDog=", + } + + prng := NewPrng(42) + + for i, expected := range expectedMACs { + mac, err := newRandomMAC(prng) + if err != nil { + t.Errorf("newRandomMAC returned an error (%d): %+v", i, err) + } + + macString := base64.StdEncoding.EncodeToString(mac) + + if macString != expected { + t.Errorf("New MAC #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, macString) + } + + // Ensure that the first bit is zero + if mac[0]>>7 != 0 { + t.Errorf("First bit of MAC #%d is not 0."+ + "\nexpected: %d\nreceived: %d", i, 0, mac[0]>>7) + } + } +} diff --git a/dummy/send.go b/dummy/send.go new file mode 100644 index 0000000000000000000000000000000000000000..6cde639e97f776b485d599d5a2659771751e487f --- /dev/null +++ b/dummy/send.go @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "sync" + "sync/atomic" + "time" +) + +// Error messages. +const ( + numMsgsRngErr = "failed to generate random number of messages to send: %+v" + payloadRngErr = "failed to generate random payload: %+v" + recipientRngErr = "failed to generate random recipient: %+v" + fingerprintRngErr = "failed to generate random fingerprint: %+v" + macRngErr = "failed to generate random MAC: %+v" +) + +// sendThread is a thread that sends the dummy messages at random intervals. +func (m *Manager) sendThread(stop *stoppable.Single) { + jww.DEBUG.Print("Starting dummy traffic sending thread.") + + timer := m.randomTimer() + + for { + select { + case <-stop.Quit(): + jww.DEBUG.Print("Stopping dummy traffic sending thread: stoppable " + + "triggered") + stop.ToStopped() + return + case <-timer.C: + timer = m.randomTimer() + + // Get list of random messages and recipients + rng := m.rng.GetStream() + msgs, err := m.newRandomMessages(rng) + if err != nil { + jww.FATAL.Panicf("Failed to generate dummy messages: %+v", err) + } + rng.Close() + + err = m.sendMessages(msgs) + if err != nil { + jww.FATAL.Panicf("Failed to send dummy messages: %+v", err) + } + } + } +} + +// sendMessages generates and sends random messages. +func (m *Manager) sendMessages(msgs map[id.ID]format.Message) error { + var sent, i int64 + var wg sync.WaitGroup + + for recipient, msg := range msgs { + wg.Add(1) + + go func(i int64, recipient id.ID, msg format.Message) { + _, _, err := m.net.SendCMIX(msg, &recipient, params.GetDefaultCMIX()) + if err != nil { + jww.WARN.Printf("failed to send dummy message %d/%d: %+v", + i, len(msgs), err) + } else { + atomic.AddInt64(&sent, 1) + } + + wg.Done() + }(i, recipient, msg) + + i++ + } + + wg.Wait() + + jww.INFO.Printf("Sent %d/%d dummy messages.", sent, len(msgs)) + + return nil +} + +// newRandomMessages returns a map of a random recipients and random messages of +// a randomly generated length in [1, Manager.maxNumMessages]. +func (m *Manager) newRandomMessages(rng csprng.Source) ( + map[id.ID]format.Message, error) { + numMessages, err := intRng(m.maxNumMessages+1, rng) + if err != nil { + return nil, errors.Errorf(numMsgsRngErr, err) + } + + msgs := make(map[id.ID]format.Message, numMessages) + + for i := 0; i < numMessages; i++ { + // Generate random recipient + recipient, err := id.NewRandomID(rng, id.User) + if err != nil { + return nil, errors.Errorf(recipientRngErr, err) + } + + msgs[*recipient], err = m.newRandomCmixMessage(rng) + if err != nil { + return nil, err + } + } + + return msgs, nil +} + +// newRandomCmixMessage returns a new cMix message filled with a randomly +// generated payload, fingerprint, and MAC. +func (m *Manager) newRandomCmixMessage(rng csprng.Source) (format.Message, error) { + // Create new empty cMix message + cMixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) + + // Generate random message + randomMsg, err := newRandomPayload(cMixMsg.ContentsSize(), rng) + if err != nil { + return format.Message{}, errors.Errorf(payloadRngErr, err) + } + + // Generate random fingerprint + fingerprint, err := newRandomFingerprint(rng) + if err != nil { + return format.Message{}, errors.Errorf(fingerprintRngErr, err) + } + + // Generate random MAC + mac, err := newRandomMAC(rng) + if err != nil { + return format.Message{}, errors.Errorf(macRngErr, err) + } + + // Set contents, fingerprint, and MAC, of the cMix message + cMixMsg.SetContents(randomMsg) + cMixMsg.SetKeyFP(fingerprint) + cMixMsg.SetMac(mac) + + return cMixMsg, nil +} + +// randomTimer generates a timer that will trigger after a random duration. +func (m *Manager) randomTimer() *time.Timer { + rng := m.rng.GetStream() + + duration, err := durationRng(m.avgSendDelta, m.randomRange, rng) + if err != nil { + jww.FATAL.Panicf("Failed to generate random duration to wait to send "+ + "dummy messages: %+v", err) + } + + return time.NewTimer(duration) +} diff --git a/dummy/send_test.go b/dummy/send_test.go new file mode 100644 index 0000000000000000000000000000000000000000..acf08cb558ba02fbf711537ce67a696afa455836 --- /dev/null +++ b/dummy/send_test.go @@ -0,0 +1,169 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "bytes" + "encoding/base64" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" + "time" +) + +// Tests that Manager.sendThread sends multiple sets of messages. +func TestManager_sendThread(t *testing.T) { + m := newTestManager(10, 50*time.Millisecond, 10*time.Millisecond, false, t) + + stop := stoppable.NewSingle("sendThreadTest") + go m.sendThread(stop) + + msgChan := make(chan bool, 10) + go func() { + var numReceived int + for i := 0; i < 2; i++ { + for m.net.(*testNetworkManager).GetMsgListLen() == numReceived { + time.Sleep(5 * time.Millisecond) + } + numReceived = m.net.(*testNetworkManager).GetMsgListLen() + msgChan <- true + } + }() + + var numReceived int + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + t.Errorf("Timed out after %s waiting for messages to be sent.", + 3*m.avgSendDelta) + case <-msgChan: + numReceived += m.net.(*testNetworkManager).GetMsgListLen() + } + + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + t.Errorf("Timed out after %s waiting for messages to be sent.", + 3*m.avgSendDelta) + case <-msgChan: + if m.net.(*testNetworkManager).GetMsgListLen() <= numReceived { + t.Errorf("Failed to receive second send."+ + "\nmessages on last receive: %d\nmessages on this receive: %d", + numReceived, m.net.(*testNetworkManager).GetMsgListLen()) + } + } + + err := stop.Close() + if err != nil { + t.Errorf("Failed to close stoppable: %+v", err) + } + + time.Sleep(10 * time.Millisecond) + if !stop.IsStopped() { + t.Error("Stoppable never stopped.") + } +} + +// Tests that Manager.sendMessages sends all the messages with the correct +// recipient. +func TestManager_sendMessages(t *testing.T) { + m := newTestManager(100, 0, 0, false, t) + prng := NewPrng(42) + + // Generate map of recipients and messages + msgs := make(map[id.ID]format.Message, m.maxNumMessages) + for i := 0; i < m.maxNumMessages; i++ { + recipient, err := id.NewRandomID(prng, id.User) + if err != nil { + t.Errorf("Failed to generate random recipient ID (%d): %+v", i, err) + } + + msg, err := m.newRandomCmixMessage(prng) + if err != nil { + t.Errorf("Failed to generate random cMix message (%d): %+v", i, err) + } + + msgs[*recipient] = msg + } + + // Send the messages + err := m.sendMessages(msgs) + if err != nil { + t.Errorf("sendMessages returned an error: %+v", err) + } + + // Get sent messages + receivedMsgs := m.net.(*testNetworkManager).GetMsgList() + + // Test that all messages were received + if len(receivedMsgs) != len(msgs) { + t.Errorf("Failed to received all sent messages."+ + "\nexpected: %d\nreceived: %d", len(msgs), len(receivedMsgs)) + } + + // Test that all messages were received for the correct recipient + for recipient, msg := range msgs { + receivedMsg, exists := receivedMsgs[recipient] + if !exists { + t.Errorf("Failed to receive message from %s: %+v", &recipient, msg) + } else if !reflect.DeepEqual(msg, receivedMsg) { + t.Errorf("Received unexpected message for recipient %s."+ + "\nexpected: %+v\nreceived: %+v", &recipient, msg, receivedMsg) + } + } +} + +// Tests that Manager.newRandomMessages creates a non-empty map of messages and +// that each message is unique. +func TestManager_newRandomMessages(t *testing.T) { + m := newTestManager(10, 0, 0, false, t) + prng := NewPrng(42) + + msgMap, err := m.newRandomMessages(prng) + if err != nil { + t.Errorf("newRandomMessages returned an error: %+v", err) + } + + if len(msgMap) == 0 { + t.Error("Message map is empty.") + } + + marshalledMsgs := make(map[string]format.Message, len(msgMap)) + for _, msg := range msgMap { + msgString := base64.StdEncoding.EncodeToString(msg.Marshal()) + if _, exists := marshalledMsgs[msgString]; exists { + t.Errorf("Message not unique.") + } else { + marshalledMsgs[msgString] = msg + } + } +} + +// Tests that Manager.newRandomCmixMessage generates a cMix message with +// populated contents, fingerprint, and MAC. +func TestManager_newRandomCmixMessage(t *testing.T) { + m := newTestManager(0, 0, 0, false, t) + prng := NewPrng(42) + + cMixMsg, err := m.newRandomCmixMessage(prng) + if err != nil { + t.Errorf("newRandomCmixMessage returned an error: %+v", err) + } + + if bytes.Equal(cMixMsg.GetContents(), make([]byte, len(cMixMsg.GetContents()))) { + t.Error("cMix message contents not set.") + } + + if cMixMsg.GetKeyFP() == (format.Fingerprint{}) { + t.Error("cMix message fingerprint not set.") + } + + if bytes.Equal(cMixMsg.GetMac(), make([]byte, format.MacLen)) { + t.Error("cMix message MAC not set.") + } +} diff --git a/dummy/utils_test.go b/dummy/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7312effd4d1d7b9d138e467d764f08fd5ddbd401 --- /dev/null +++ b/dummy/utils_test.go @@ -0,0 +1,208 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/e2e" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" + "io" + "math/rand" + "sync" + "testing" + "time" +) + +//////////////////////////////////////////////////////////////////////////////// +// PRNG // +//////////////////////////////////////////////////////////////////////////////// + +// Prng is a PRNG that satisfies the csprng.Source interface. +type Prng struct{ prng io.Reader } + +func NewPrng(seed int64) csprng.Source { return &Prng{rand.New(rand.NewSource(seed))} } +func (s *Prng) Read(b []byte) (int, error) { return s.prng.Read(b) } +func (s *Prng) SetSeed([]byte) error { return nil } + +//////////////////////////////////////////////////////////////////////////////// +// Test Managers // +//////////////////////////////////////////////////////////////////////////////// + +// newTestManager creates a new Manager that has groups stored for testing. One +// of the groups in the list is also returned. +func newTestManager(maxNumMessages int, avgSendDelta, randomRange time.Duration, + sendErr bool, t *testing.T) *Manager { + m := &Manager{ + maxNumMessages: maxNumMessages, + avgSendDelta: avgSendDelta, + randomRange: randomRange, + store: storage.InitTestingSession(t), + net: newTestNetworkManager(sendErr, t), + rng: fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG), + } + + return m +} + +//////////////////////////////////////////////////////////////////////////////// +// Test Network Manager // +//////////////////////////////////////////////////////////////////////////////// + +// testNetworkManager is a test implementation of NetworkManager interface. +type testNetworkManager struct { + instance *network.Instance + messages map[id.ID]format.Message + sendErr bool + sync.RWMutex +} + +func newTestNetworkManager(sendErr bool, t *testing.T) interfaces.NetworkManager { + instanceComms := &connect.ProtoComms{ + Manager: connect.NewManagerTesting(t), + } + + thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), + getNDF(), nil, nil, t) + if err != nil { + t.Fatalf("Failed to create new test instance: %v", err) + } + + return &testNetworkManager{ + instance: thisInstance, + messages: make(map[id.ID]format.Message), + sendErr: sendErr, + } +} + +func (tnm *testNetworkManager) GetMsgListLen() int { + tnm.RLock() + defer tnm.RUnlock() + return len(tnm.messages) +} + +func (tnm *testNetworkManager) GetMsgList() map[id.ID]format.Message { + tnm.RLock() + defer tnm.RUnlock() + return tnm.messages +} + +func (tnm *testNetworkManager) GetMsg(recipient id.ID) format.Message { + tnm.RLock() + defer tnm.RUnlock() + return tnm.messages[recipient] +} + +func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Single) ( + []id.Round, e2e.MessageID, time.Time, error) { + return nil, e2e.MessageID{}, time.Time{}, nil +} + +func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Round, error) { + return []id.Round{}, nil +} + +func (tnm *testNetworkManager) SendCMIX(message format.Message, + recipient *id.ID, _ params.CMIX) (id.Round, ephemeral.Id, error) { + tnm.Lock() + defer tnm.Unlock() + + if tnm.sendErr { + return 0, ephemeral.Id{}, errors.New("SendCMIX error") + } + + tnm.messages[*recipient] = message + + return 0, ephemeral.Id{}, nil +} + +func (tnm *testNetworkManager) SendManyCMIX(map[id.ID]format.Message, params.CMIX) ( + id.Round, []ephemeral.Id, error) { + return 0, nil, nil +} + +type dummyEventMgr struct{} + +func (d *dummyEventMgr) Report(int, string, string, string) {} +func (tnm *testNetworkManager) GetEventManager() interfaces.EventManager { + return &dummyEventMgr{} +} + +func (tnm *testNetworkManager) GetInstance() *network.Instance { return tnm.instance } +func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { return nil } +func (tnm *testNetworkManager) Follow(interfaces.ClientErrorReport) (stoppable.Stoppable, error) { + return nil, nil +} +func (tnm *testNetworkManager) CheckGarbledMessages() {} +func (tnm *testNetworkManager) InProgressRegistrations() int { return 0 } +func (tnm *testNetworkManager) GetSender() *gateway.Sender { return nil } +func (tnm *testNetworkManager) GetAddressSize() uint8 { return 0 } +func (tnm *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) { + return nil, nil +} +func (tnm *testNetworkManager) UnregisterAddressSizeNotification(string) {} +func (tnm *testNetworkManager) SetPoolFilter(gateway.Filter) {} +func (tnm *testNetworkManager) GetVerboseRounds() string { return "" } + +//////////////////////////////////////////////////////////////////////////////// +// NDF Primes // +//////////////////////////////////////////////////////////////////////////////// + +func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + E2E: ndf.Group{ + Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" + + "8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" + + "D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" + + "75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" + + "6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" + + "4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" + + "6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" + + "448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" + + "198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" + + "DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" + + "631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" + + "3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" + + "19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" + + "5873847AEF49F66E43873", + Generator: "2", + }, + CMIX: ndf.Group{ + Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" + + "F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" + + "264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" + + "9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" + + "B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" + + "0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" + + "92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" + + "2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" + + "995FAD5AABBCFBE3EDA2741E375404AE25B", + Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" + + "9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" + + "1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" + + "8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" + + "C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" + + "5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" + + "59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" + + "2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" + + "B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + }, + } +}