diff --git a/bindings/client.go b/bindings/client.go index f07598c9386ddcb7d038224be15a9a525960cc20..a160886728f0d7f0975be56956cca85171825883 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -14,6 +14,7 @@ import ( "errors" "fmt" "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/single/old" "runtime/pprof" "strings" "sync" @@ -23,7 +24,6 @@ import ( "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/primitives/states" @@ -47,7 +47,7 @@ func init() { // to support the gomobile Client interface type Client struct { api api.Client - single *single.Manager + single *old.Manager singleMux sync.Mutex } @@ -582,12 +582,12 @@ func (b *BindingsClient) Search(data, separator string, // getSingle is a function which returns the single mananger if it // exists or creates a new one, checking appropriate constraints // (that the network follower is running) if it needs to make one -func (c *Client) getSingle() (*single.Manager, error) { +func (c *Client) getSingle() (*old.Manager, error) { c.singleMux.Lock() defer c.singleMux.Unlock() if c.single == nil { apiClient := &c.api - c.single = single.NewManager(apiClient) + c.single = old.NewManager(apiClient) err := apiClient.AddService(c.single.StartProcesses) if err != nil { return nil, err diff --git a/cmd/single.go b/cmd/single.go index bacc818dbeab9591c4d6b0e6a582352de94b7b3b..42eb4d844c3a847e14caadc84840f1e8b7222237 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -15,7 +15,7 @@ import ( jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/client/single/old" "gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/crypto/contact" "gitlab.com/xx_network/primitives/utils" @@ -73,14 +73,14 @@ var singleCmd = &cobra.Command{ waitUntilConnected(connected) // Make single-use manager and start receiving process - singleMng := single.NewManager(client) + singleMng := old.NewManager(client) // get the tag tag := viper.GetString("tag") // Register the callback callbackChan := make(chan responseCallbackChan) - callback := func(payload []byte, c single.Contact) { + callback := func(payload []byte, c old.Contact) { callbackChan <- responseCallbackChan{payload, c} } singleMng.RegisterCallback(tag, callback) @@ -149,7 +149,7 @@ func init() { } // sendSingleUse sends a single use message. -func sendSingleUse(m *single.Manager, partner contact.Contact, payload []byte, +func sendSingleUse(m *old.Manager, partner contact.Contact, payload []byte, maxMessages uint8, timeout time.Duration, tag string) { // Construct callback callbackChan := make(chan struct { @@ -193,7 +193,7 @@ func sendSingleUse(m *single.Manager, partner contact.Contact, payload []byte, // replySingleUse responds to any single-use message it receives by replying\ // with the same payload. -func replySingleUse(m *single.Manager, timeout time.Duration, callbackChan chan responseCallbackChan) { +func replySingleUse(m *old.Manager, timeout time.Duration, callbackChan chan responseCallbackChan) { // Wait to receive a message or stop after timeout occurs fmt.Println("Waiting for single-use message.") @@ -229,13 +229,13 @@ func replySingleUse(m *single.Manager, timeout time.Duration, callbackChan chan // response callback. type responseCallbackChan struct { payload []byte - c single.Contact + c old.Contact } // makeResponsePayload generates a new payload that will span the max number of // message parts in the contact. Each resulting message payload will contain a // copy of the supplied payload with spaces taking up any remaining data. -func makeResponsePayload(m *single.Manager, payload []byte, maxParts uint8) []byte { +func makeResponsePayload(m *old.Manager, payload []byte, maxParts uint8) []byte { payloads := make([][]byte, maxParts) payloadPart := makeResponsePayloadPart(m, payload) for i := range payloads { @@ -247,7 +247,7 @@ func makeResponsePayload(m *single.Manager, payload []byte, maxParts uint8) []by // makeResponsePayloadPart creates a single response payload by coping the given // payload and filling the rest with spaces. -func makeResponsePayloadPart(m *single.Manager, payload []byte) []byte { +func makeResponsePayloadPart(m *old.Manager, payload []byte) []byte { payloadPart := make([]byte, m.GetMaxResponsePayloadSize()) for i := range payloadPart { payloadPart[i] = ' ' diff --git a/cmd/ud.go b/cmd/ud.go index 39d5a8aa764c7c3509b76bf474ebd5de11b11562..10271f53b445935df01fa654451eb2e93723a435 100644 --- a/cmd/ud.go +++ b/cmd/ud.go @@ -10,13 +10,13 @@ package cmd import ( "fmt" + "gitlab.com/elixxir/client/single/old" "time" "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/client/ud" "gitlab.com/elixxir/client/xxmutils" @@ -76,7 +76,7 @@ var udCmd = &cobra.Command{ waitUntilConnected(connected) // Make single-use manager and start receiving process - singleMng := single.NewManager(client) + singleMng := old.NewManager(client) err = client.AddService(singleMng.StartProcesses) if err != nil { jww.FATAL.Panicf("Failed to add single use process: %+v", err) diff --git a/cmix/message/fingerprints.go b/cmix/message/fingerprints.go index 884475364c1049ae58344630a76bb037ef41e28b..e396530db3abd5179502824bb39ad1f11841f99b 100644 --- a/cmix/message/fingerprints.go +++ b/cmix/message/fingerprints.go @@ -8,6 +8,8 @@ package message import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/xx_network/crypto/csprng" "sync" "github.com/pkg/errors" @@ -115,3 +117,13 @@ func (f *FingerprintsManager) DeleteClientFingerprints(clientID *id.ID) { defer f.Unlock() delete(f.fpMap, *clientID) } + +func RandomFingerprint(rng csprng.Source) format.Fingerprint { + fp := format.Fingerprint{} + fpBuf := make([]byte, len(fp[:])) + if _, err := rng.Read(fpBuf); err != nil { + jww.FATAL.Panicf("Failed to generate fingerprint: %+v", err) + } + copy(fp[:], fpBuf) + return fp +} diff --git a/cmix/params.go b/cmix/params.go index 23ce735e8df136fe064ea05fae43114bbdbf9a73..bfd0cd1a5011fabd45c06d971da6ddd7112f7527 100644 --- a/cmix/params.go +++ b/cmix/params.go @@ -103,6 +103,8 @@ func GetParameters(params string) (Params, error) { type NodeMap map[id.ID]bool +const DefaultDebugTag = "External" + type CMIXParams struct { // RoundTries is the maximum number of rounds to try to send on RoundTries uint @@ -140,7 +142,7 @@ func GetDefaultCMIXParams() CMIXParams { Timeout: 25 * time.Second, RetryDelay: 1 * time.Second, SendTimeout: 3 * time.Second, - DebugTag: "External", + DebugTag: DefaultDebugTag, // Unused stoppable so components that require one have a channel to // wait on Stop: stoppable.NewSingle("cmixParamsDefault"), diff --git a/single/callbackMap.go b/single/callbackMap.go deleted file mode 100644 index 39bd3cc166d96e8c3a712a39726b0c5d1cdf820a..0000000000000000000000000000000000000000 --- a/single/callbackMap.go +++ /dev/null @@ -1,60 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "github.com/pkg/errors" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "sync" -) - -type ReceiveComm func(payload []byte, c Contact) - -// callbackMap stores a list of possible callbacks that can be called when a -// message is received. To receive a transmission, each transmitted message must -// use the same tag as the registered callback. The tag fingerprint is a hash of -// a tag string that is used to identify the module that the transmission -// message belongs to. The tag can be anything, but should be long enough so -// that it is unique. -type callbackMap struct { - callbacks map[singleUse.TagFP]ReceiveComm - sync.RWMutex -} - -// newCallbackMap initialises a new map. -func newCallbackMap() *callbackMap { - return &callbackMap{ - callbacks: map[singleUse.TagFP]ReceiveComm{}, - } -} - -// registerCallback adds a callback function to the map that associates it with -// its tag. The tag should be a unique string identifying the module using the -// callback. -func (cbm *callbackMap) registerCallback(tag string, callback ReceiveComm) { - cbm.Lock() - defer cbm.Unlock() - - tagFP := singleUse.NewTagFP(tag) - cbm.callbacks[tagFP] = callback -} - -// getCallback returns the callback registered with the given tag fingerprint. -// An error is returned if no associated callback exists. -func (cbm *callbackMap) getCallback(tagFP singleUse.TagFP) (ReceiveComm, error) { - cbm.RLock() - defer cbm.RUnlock() - - cb, exists := cbm.callbacks[tagFP] - if !exists { - return nil, errors.Errorf("no callback registered for the tag "+ - "fingerprint %s.", tagFP) - } - - return cb, nil -} diff --git a/single/callbackMap_test.go b/single/callbackMap_test.go deleted file mode 100644 index 5af02c8cac5b5b64f372a391ecb031b515f52298..0000000000000000000000000000000000000000 --- a/single/callbackMap_test.go +++ /dev/null @@ -1,82 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "gitlab.com/elixxir/crypto/e2e/singleUse" - "testing" -) - -// Happy path. -func Test_callbackMap_registerCallback(t *testing.T) { - m := newTestManager(0, false, t) - callbackChan := make(chan int) - testCallbacks := []struct { - tag string - cb ReceiveComm - }{ - {"tag1", func([]byte, Contact) { callbackChan <- 0 }}, - {"tag2", func([]byte, Contact) { callbackChan <- 1 }}, - {"tag3", func([]byte, Contact) { callbackChan <- 2 }}, - } - - for _, val := range testCallbacks { - m.callbackMap.registerCallback(val.tag, val.cb) - } - - for i, val := range testCallbacks { - go m.callbackMap.callbacks[singleUse.NewTagFP(val.tag)](nil, Contact{}) - result := <-callbackChan - if result != i { - t.Errorf("getCallback() did not return the expected callback."+ - "\nexpected: %d\nreceived: %d", i, result) - } - } -} - -// Happy path. -func Test_callbackMap_getCallback(t *testing.T) { - m := newTestManager(0, false, t) - callbackChan := make(chan int) - testCallbacks := []struct { - tagFP singleUse.TagFP - cb ReceiveComm - }{ - {singleUse.UnmarshalTagFP([]byte("tag1")), func([]byte, Contact) { callbackChan <- 0 }}, - {singleUse.UnmarshalTagFP([]byte("tag2")), func([]byte, Contact) { callbackChan <- 1 }}, - {singleUse.UnmarshalTagFP([]byte("tsg3")), func([]byte, Contact) { callbackChan <- 2 }}, - } - - for _, val := range testCallbacks { - m.callbackMap.callbacks[val.tagFP] = val.cb - } - - cb, err := m.callbackMap.getCallback(testCallbacks[1].tagFP) - if err != nil { - t.Errorf("getCallback() returned an error: %+v", err) - } - - go cb(nil, Contact{}) - - result := <-callbackChan - if result != 1 { - t.Errorf("getCallback() did not return the expected callback."+ - "\nexpected: %d\nreceived: %d", 1, result) - } -} - -// Error path: no callback exists for the given tag fingerprint. -func Test_callbackMap_getCallback_NoCallbackError(t *testing.T) { - m := newTestManager(0, false, t) - - _, err := m.callbackMap.getCallback(singleUse.UnmarshalTagFP([]byte("tag1"))) - if err == nil { - t.Error("getCallback() failed to return an error for a callback that " + - "does not exist.") - } -} diff --git a/single/contact.go b/single/contact.go deleted file mode 100644 index bf0599899609261f38475e0ee5eed245898709ae..0000000000000000000000000000000000000000 --- a/single/contact.go +++ /dev/null @@ -1,67 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "fmt" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/xx_network/primitives/id" -) - -// Contact contains the information to respond to a single-use contact. -type Contact struct { - partner *id.ID // ID of the person to respond to - partnerPubKey *cyclic.Int // Public key of the partner - dhKey *cyclic.Int // DH key - tagFP singleUse.TagFP // Identifies which callback to use - maxParts uint8 // Max number of messages allowed in reply - used *int32 // Atomic variable -} - -// NewContact initialises a new Contact with the specified fields. -func NewContact(partner *id.ID, partnerPubKey, dhKey *cyclic.Int, - tagFP singleUse.TagFP, maxParts uint8) Contact { - used := int32(0) - return Contact{ - partner: partner, - partnerPubKey: partnerPubKey, - dhKey: dhKey, - tagFP: tagFP, - maxParts: maxParts, - used: &used, - } -} - -// GetMaxParts returns the maximum number of message parts that can be sent in a -// reply. -func (c Contact) GetMaxParts() uint8 { - return c.maxParts -} - -// GetPartner returns a copy of the partner ID. -func (c Contact) GetPartner() *id.ID { - return c.partner.DeepCopy() -} - -// String returns a string of the Contact structure. -func (c Contact) String() string { - format := "Contact{partner:%s partnerPubKey:%s dhKey:%s tagFP:%s maxParts:%d used:%d}" - return fmt.Sprintf(format, c.partner, c.partnerPubKey.Text(10), - c.dhKey.Text(10), c.tagFP, c.maxParts, *c.used) -} - -// Equal determines if c and b have equal field values. -func (c Contact) Equal(b Contact) bool { - return c.partner.Cmp(b.partner) && - c.partnerPubKey.Cmp(b.partnerPubKey) == 0 && - c.dhKey.Cmp(b.dhKey) == 0 && - c.tagFP == b.tagFP && - c.maxParts == b.maxParts && - *c.used == *b.used -} diff --git a/single/contact_test.go b/single/contact_test.go deleted file mode 100644 index 533e5600f935f1feaf36aad48735e47917466357..0000000000000000000000000000000000000000 --- a/single/contact_test.go +++ /dev/null @@ -1,102 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/xx_network/primitives/id" - "math/rand" - "testing" -) - -// Happy path. -func TestNewContact(t *testing.T) { - grp := getGroup() - used := int32(0) - expected := Contact{ - partner: id.NewIdFromString("sender ID", id.User, t), - partnerPubKey: grp.NewInt(99), - dhKey: grp.NewInt(42), - tagFP: singleUse.UnmarshalTagFP([]byte("test tagFP")), - maxParts: uint8(rand.Uint64()), - used: &used, - } - - testC := NewContact(expected.partner, expected.partnerPubKey, - expected.dhKey, expected.tagFP, expected.maxParts) - - if !expected.Equal(testC) { - t.Errorf("NewContact() did not return the expected Contact."+ - "\nexpected: %s\nrecieved: %s", expected, testC) - } -} - -// Happy path. -func TestContact_GetMaxParts(t *testing.T) { - grp := getGroup() - maxParts := uint8(rand.Uint64()) - c := NewContact(id.NewIdFromString("sender ID", id.User, t), grp.NewInt(99), - grp.NewInt(42), singleUse.TagFP{}, maxParts) - - if maxParts != c.GetMaxParts() { - t.Errorf("GetMaxParts() failed to return the expected maxParts."+ - "\nexpected %d\nreceived: %d", maxParts, c.GetMaxParts()) - } -} - -// Happy path. -func TestContact_GetPartner(t *testing.T) { - grp := getGroup() - senderID := id.NewIdFromString("sender ID", id.User, t) - c := NewContact(senderID, grp.NewInt(99), grp.NewInt(42), singleUse.TagFP{}, - uint8(rand.Uint64())) - - if !senderID.Cmp(c.GetPartner()) { - t.Errorf("GetPartner() failed to return the expected sender ID."+ - "\nexpected %s\nreceived: %s", senderID, c.GetPartner()) - } - -} - -// Happy path. -func TestContact_String(t *testing.T) { - grp := getGroup() - a := NewContact(id.NewIdFromString("sender ID 1", id.User, t), grp.NewInt(99), - grp.NewInt(42), singleUse.UnmarshalTagFP([]byte("test tagFP 1")), uint8(rand.Uint64())) - b := NewContact(id.NewIdFromString("sender ID 2", id.User, t), - grp.NewInt(98), grp.NewInt(43), singleUse.UnmarshalTagFP([]byte("test tagFP 2")), uint8(rand.Uint64())) - c := NewContact(a.GetPartner(), a.partnerPubKey.DeepCopy(), a.dhKey.DeepCopy(), a.tagFP, a.maxParts) - - if a.String() == b.String() { - t.Errorf("String() did not return the expected string."+ - "\na: %s\nb: %s", a, b) - } - if a.String() != c.String() { - t.Errorf("String() did not return the expected string."+ - "\na: %s\nc: %s", a, c) - } -} - -// Happy path. -func TestContact_Equal(t *testing.T) { - grp := getGroup() - a := NewContact(id.NewIdFromString("sender ID 1", id.User, t), - grp.NewInt(99), grp.NewInt(42), singleUse.UnmarshalTagFP([]byte("test tagFP 1")), uint8(rand.Uint64())) - b := NewContact(id.NewIdFromString("sender ID 2", id.User, t), - grp.NewInt(98), grp.NewInt(43), singleUse.UnmarshalTagFP([]byte("test tagFP 2")), uint8(rand.Uint64())) - c := NewContact(a.GetPartner(), a.partnerPubKey.DeepCopy(), a.dhKey.DeepCopy(), a.tagFP, a.maxParts) - - if a.Equal(b) { - t.Errorf("Equal() found two different Contacts as equal."+ - "\na: %s\nb: %s", a, b) - } - if !a.Equal(c) { - t.Errorf("Equal() found two equal Contacts as not equal."+ - "\na: %s\nc: %s", a, c) - } -} diff --git a/single/cypher.go b/single/cypher.go new file mode 100644 index 0000000000000000000000000000000000000000..f4365ddc92d28d5035db88691707671f6a352a00 --- /dev/null +++ b/single/cypher.go @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/crypto/cyclic" + cAuth "gitlab.com/elixxir/crypto/e2e/auth" + "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/elixxir/primitives/format" +) + +// makeCyphers generates all fingerprints for a given number of messages. +func makeCyphers(dhKey *cyclic.Int, messageCount uint8) []cypher { + + cylist := make([]cypher, messageCount) + + for i := uint8(0); i < messageCount; i++ { + cylist[i] = cypher{ + dhkey: dhKey, + num: i, + } + } + + return cylist +} + +type cypher struct { + dhkey *cyclic.Int + num uint8 +} + +func (rk *cypher) getKey() []byte { + return singleUse.NewResponseKey(rk.dhkey, uint64(rk.num)) +} + +func (rk *cypher) GetFingerprint() format.Fingerprint { + return singleUse.NewResponseFingerprint(rk.dhkey, uint64(rk.num)) +} + +func (rk *cypher) Encrypt(rp message.ResponsePart) ( + fp format.Fingerprint, encryptedPayload, mac []byte) { + fp = rk.GetFingerprint() + key := rk.getKey() + //fixme: encryption is identical to what is used by e2e.Crypt, lets make + //them the same codepath + encryptedPayload = cAuth.Crypt(key, fp[:24], rp.Marshal()) + mac = singleUse.MakeMAC(key, encryptedPayload) + return fp, encryptedPayload, mac +} + +func (rk *cypher) Decrypt(contents, mac []byte) ([]byte, error) { + + fp := rk.GetFingerprint() + key := rk.getKey() + + // Verify the CMIX message MAC + if !singleUse.VerifyMAC(key, contents, mac) { + return nil, errors.New("failed to verify the single use mac") + } + + //decrypt the payload + return cAuth.Crypt(key, fp[:24], contents), nil +} diff --git a/single/fingerprintMap.go b/single/fingerprintMap.go deleted file mode 100644 index 27f7b54f1bb9b990097cc6cd4b36cb2ecd16f658..0000000000000000000000000000000000000000 --- a/single/fingerprintMap.go +++ /dev/null @@ -1,55 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "sync" -) - -// fingerprintMap stores a map of fingerprint to key numbers. -type fingerprintMap struct { - fps map[format.Fingerprint]uint64 - sync.Mutex -} - -// newFingerprintMap returns a map of fingerprints generated from the provided -// key that is messageCount long. -func newFingerprintMap(dhKey *cyclic.Int, messageCount uint64) *fingerprintMap { - fpm := &fingerprintMap{ - fps: make(map[format.Fingerprint]uint64, messageCount), - } - - for i := uint64(0); i < messageCount; i++ { - fp := singleUse.NewResponseFingerprint(dhKey, i) - fpm.fps[fp] = i - } - - return fpm -} - -// getKey returns true and the corresponding key of the fingerprint exists in -// the map and returns false otherwise. If the fingerprint exists, then it is -// deleted prior to returning the key. -func (fpm *fingerprintMap) getKey(dhKey *cyclic.Int, fp format.Fingerprint) ([]byte, bool) { - fpm.Lock() - defer fpm.Unlock() - - num, exists := fpm.fps[fp] - if !exists { - return nil, false - } - - // delete found fingerprint - delete(fpm.fps, fp) - - // Generate and return the key - return singleUse.NewResponseKey(dhKey, num), true -} diff --git a/single/fingerprintMap_test.go b/single/fingerprintMap_test.go deleted file mode 100644 index 2be4e3f8d447beaa6385770bedfca87864a5ca97..0000000000000000000000000000000000000000 --- a/single/fingerprintMap_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package single - -import ( - "bytes" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "reflect" - "testing" -) - -// Happy path. -func Test_newFingerprintMap(t *testing.T) { - baseKey := getGroup().NewInt(42) - messageCount := 3 - expected := &fingerprintMap{ - fps: map[format.Fingerprint]uint64{ - singleUse.NewResponseFingerprint(baseKey, 0): 0, - singleUse.NewResponseFingerprint(baseKey, 1): 1, - singleUse.NewResponseFingerprint(baseKey, 2): 2, - }, - } - - fpm := newFingerprintMap(baseKey, uint64(messageCount)) - - if !reflect.DeepEqual(expected, fpm) { - t.Errorf("newFingerprintMap() did not generate the expected map."+ - "\nexpected: %+v\nreceived: %+v", expected, fpm) - } -} - -// Happy path. -func TestFingerprintMap_getKey(t *testing.T) { - baseKey := getGroup().NewInt(42) - fpm := newFingerprintMap(baseKey, 5) - fp := singleUse.NewResponseFingerprint(baseKey, 3) - expectedKey := singleUse.NewResponseKey(baseKey, 3) - - testKey, exists := fpm.getKey(baseKey, fp) - if !exists { - t.Errorf("getKey() failed to find key that exists in map."+ - "\nfingerprint: %+v", fp) - } - - if !bytes.Equal(expectedKey, testKey) { - t.Errorf("getKey() returned the wrong key.\nexpected: %+v\nreceived: %+v", - expectedKey, testKey) - } - - testFP, exists := fpm.fps[fp] - if exists { - t.Errorf("getKey() failed to delete the found fingerprint."+ - "\nfingerprint: %+v", testFP) - } -} - -// Error path: fingerprint does not exist in map. -func TestFingerprintMap_getKey_FingerprintNotInMap(t *testing.T) { - baseKey := getGroup().NewInt(42) - fpm := newFingerprintMap(baseKey, 5) - fp := singleUse.NewResponseFingerprint(baseKey, 30) - - key, exists := fpm.getKey(baseKey, fp) - if exists { - t.Errorf("getKey() found a fingerprint in the map that should not exist."+ - "\nfingerprint: %+v\nkey: %+v", fp, key) - } - - // Ensure no fingerprints were deleted - if len(fpm.fps) != 5 { - t.Errorf("getKey() deleted fingerprint."+ - "\nexpected size: %d\nreceived size: %d", 5, len(fpm.fps)) - } -} diff --git a/single/listener.go b/single/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..cf49e7868711c205a40fbdcc92be0b2507e30165 --- /dev/null +++ b/single/listener.go @@ -0,0 +1,118 @@ +package single + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + cmixMsg "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/crypto/cyclic" + cAuth "gitlab.com/elixxir/crypto/e2e/auth" + "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/primitives/id" +) + +type Receiver interface { + Callback(*Request, receptionID.EphemeralIdentity, rounds.Round) +} + +type Listener interface { + // Stop unregisters the listener + Stop() +} + +type listener struct { + myId *id.ID + myPrivkey *cyclic.Int + tag string + grp *cyclic.Group + cb Receiver + net cmix.Client +} + +func Listen(tag string, myId *id.ID, privkey *cyclic.Int, net cmix.Client, + e2eGrp *cyclic.Group, cb Receiver) Listener { + + l := &listener{ + myId: myId, + myPrivkey: privkey, + tag: tag, + grp: e2eGrp, + cb: cb, + net: net, + } + + svc := cmixMsg.Service{ + Identifier: myId[:], + Tag: tag, + Metadata: myId[:], + } + + net.AddService(myId, svc, l) + + return l +} + +func (l *listener) Process(ecrMsg format.Message, + receptionID receptionID.EphemeralIdentity, + round rounds.Round) { + + // Unmarshal the CMIX message contents to a transmission message + transmitMsg, err := message.UnmarshalRequest(ecrMsg.GetContents(), + l.grp.GetP().ByteLen()) + if err != nil { + jww.WARN.Printf("failed to unmarshal contents on single use "+ + "request to %s on tag %s: %+v", l.myId, l.tag, err) + return + } + + // Generate DH key and symmetric key + senderPubkey := transmitMsg.GetPubKey(l.grp) + dhKey := l.grp.Exp(senderPubkey, l.myPrivkey, + l.grp.NewInt(1)) + key := singleUse.NewTransmitKey(dhKey) + + // Verify the MAC + if !singleUse.VerifyMAC(key, transmitMsg.GetPayload(), ecrMsg.GetMac()) { + jww.WARN.Printf("mac check failed on single use request to %s "+ + "on tag %s", l.myId, l.tag) + return + } + + // Decrypt the transmission message payload + fp := ecrMsg.GetKeyFP() + decryptedPayload := cAuth.Crypt(key, fp[:24], transmitMsg.GetPayload()) + + // Unmarshal payload + payload, err := message.UnmarshalRequestPayload(decryptedPayload) + if err != nil { + jww.WARN.Printf("failed to unmarshal decrypted payload on "+ + "single use request to %s on tag %s: %+v", l.myId, l.tag, err) + return + } + + used := uint32(0) + + r := Request{ + sender: payload.GetRID(transmitMsg.GetPubKey(l.grp)), + senderPubKey: senderPubkey, + dhKey: dhKey, + tag: l.tag, + maxParts: 0, + used: &used, + requestPayload: payload.GetContents(), + net: l.net, + } + + go l.cb.Callback(&r, receptionID, round) +} + +func (l *listener) Stop() { + svc := cmixMsg.Service{ + Identifier: l.myId[:], + Tag: l.tag, + } + l.net.DeleteService(l.myId, svc, l) +} diff --git a/single/manager.go b/single/manager.go deleted file mode 100644 index c86bb3ed82b94e7a60973f0209bb2c3efbbd25c2..0000000000000000000000000000000000000000 --- a/single/manager.go +++ /dev/null @@ -1,97 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/api" - "gitlab.com/elixxir/client/cmix/identity/receptionID" - "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/crypto/fastRNG" - "gitlab.com/xx_network/primitives/id" -) - -const ( - rawMessageBuffSize = 100 - singleUseTransmission = "SingleUseTransmission" - singleUseReceiveTransmission = "SingleUseReceiveTransmission" - singleUseResponse = "SingleUseResponse" - singleUseReceiveResponse = "SingleUseReceiveResponse" - singleUseStop = "SingleUse" -) - -// Manager handles the transmission and reception of single-use communication. -type Manager struct { - // Client and its field - client *api.Client - store *storage.Session - reception *receptionID.Store - swb interfaces.Switchboard - net interfaces.NetworkManager - rng *fastRNG.StreamGenerator - - // Holds the information needed to manage each pending communication. A - // state is created when a transmission is started and is removed on - // response or timeout. - p *pending - - // List of callbacks that can be called when a transmission is received. For - // an entity to receive a message, it must register a callback in this map - // with the same tag used to send the message. - callbackMap *callbackMap -} - -// NewManager creates a new single-use communication manager. -func NewManager(client *api.Client) *Manager { - return newManager(client, client.GetStorage().Reception()) -} - -func newManager(client *api.Client, reception *receptionID.Store) *Manager { - return &Manager{ - client: client, - store: client.GetStorage(), - reception: reception, - swb: client.GetSwitchboard(), - net: client.GetNetworkInterface(), - rng: client.GetRng(), - p: newPending(), - callbackMap: newCallbackMap(), - } -} - -// StartProcesses starts the process of receiving single-use transmissions and -// replies. -func (m *Manager) StartProcesses() (stoppable.Stoppable, error) { - // Start waiting for single-use transmission - transmissionStop := stoppable.NewSingle(singleUseTransmission) - transmissionChan := make(chan message.Receive, rawMessageBuffSize) - m.swb.RegisterChannel(singleUseReceiveTransmission, &id.ID{}, message.Raw, transmissionChan) - go m.receiveTransmissionHandler(transmissionChan, transmissionStop) - - // Start waiting for single-use response - responseStop := stoppable.NewSingle(singleUseResponse) - responseChan := make(chan message.Receive, rawMessageBuffSize) - m.swb.RegisterChannel(singleUseReceiveResponse, &id.ID{}, message.Raw, responseChan) - go m.receiveResponseHandler(responseChan, responseStop) - - // Create a multi stoppable - singleUseMulti := stoppable.NewMulti(singleUseStop) - singleUseMulti.Add(transmissionStop) - singleUseMulti.Add(responseStop) - - return singleUseMulti, nil -} - -// RegisterCallback registers a callback for received messages. -func (m *Manager) RegisterCallback(tag string, callback ReceiveComm) { - jww.DEBUG.Printf("Registering single-use callback with tag %s.", tag) - m.callbackMap.registerCallback(tag, callback) -} diff --git a/single/manager_test.go b/single/manager_test.go deleted file mode 100644 index a7de399c871667d1b765e965e044f50e7b1043cb..0000000000000000000000000000000000000000 --- a/single/manager_test.go +++ /dev/null @@ -1,411 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "bytes" - "errors" - "gitlab.com/elixxir/client/api" - "gitlab.com/elixxir/client/cmix/gateway" - ephemeral2 "gitlab.com/elixxir/client/cmix/identity/receptionID" - "gitlab.com/elixxir/client/event" - "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/client/switchboard" - "gitlab.com/elixxir/comms/network" - contact2 "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/e2e" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/crypto/fastRNG" - "gitlab.com/elixxir/ekv" - "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" - "gitlab.com/xx_network/primitives/netTime" - "math/rand" - "reflect" - "sync" - "testing" - "time" -) - -// Happy path. -func Test_newManager(t *testing.T) { - client := &api.Client{} - e := &Manager{ - client: client, - p: newPending(), - } - m := newManager(client, &ephemeral2.Store{}) - - if e.client != m.client || e.store != m.store || e.net != m.net || - e.rng != m.rng || !reflect.DeepEqual(e.p, m.p) { - t.Errorf("NewHandler() did not return the expected new Manager."+ - "\nexpected: %+v\nreceived: %+v", e, m) - } -} - -// Happy path. -func TestManager_StartProcesses(t *testing.T) { - m := newTestManager(0, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReceiveComm() - - transmitMsg, _, rid, _, err := m.makeTransmitCmixMessage(partner, payload, - tag, 8, 32, 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create tranmission CMIX message: %+v", err) - } - - _, sender, err := m.processTransmission(transmitMsg, singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey())) - - replyMsgs, err := m.makeReplyCmixMessages(sender, payload) - if err != nil { - t.Fatalf("Failed to generate reply CMIX messages: %+v", err) - } - - receiveMsg := message.Receive{ - Payload: transmitMsg.Marshal(), - MessageType: message.Raw, - Sender: rid, - RecipientID: partner.ID, - } - - m.callbackMap.registerCallback(tag, callback) - - _, _ = m.StartProcesses() - m.swb.(*switchboard.Switchboard).Speak(receiveMsg) - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - if !bytes.Equal(results.payload, payload) { - t.Errorf("Callback received wrong payload."+ - "\nexpected: %+v\nreceived: %+v", payload, results.payload) - } - case <-timer.C: - t.Errorf("Callback failed to be called.") - } - - callbackFunc, callbackFuncChan := createReplyComm() - m.p.Lock() - m.p.singleUse[*rid] = newState(sender.dhKey, sender.maxParts, callbackFunc) - m.p.Unlock() - eid, _, _, _ := ephemeral.GetId(partner.ID, id.ArrIDLen, netTime.Now().UnixNano()) - replyMsg := message.Receive{ - Payload: replyMsgs[0].Marshal(), - MessageType: message.Raw, - Sender: partner.ID, - RecipientID: rid, - EphemeralID: eid, - } - - go func() { - timer := time.NewTimer(50 * time.Millisecond) - select { - case <-timer.C: - t.Errorf("quitChan never set.") - case <-m.p.singleUse[*rid].quitChan: - } - }() - - m.swb.(*switchboard.Switchboard).Speak(replyMsg) - - timer = time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackFuncChan: - if !bytes.Equal(results.payload, payload) { - t.Errorf("Callback received wrong payload."+ - "\nexpected: %+v\nreceived: %+v", payload, results.payload) - } - case <-timer.C: - t.Errorf("Callback failed to be called.") - } -} - -// Happy path: tests that the stoppable stops both routines. -func TestManager_StartProcesses_Stop(t *testing.T) { - m := newTestManager(0, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReceiveComm() - - transmitMsg, _, rid, _, err := m.makeTransmitCmixMessage(partner, payload, - tag, 8, 32, 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create tranmission CMIX message: %+v", err) - } - - _, sender, err := m.processTransmission(transmitMsg, singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey())) - - replyMsgs, err := m.makeReplyCmixMessages(sender, payload) - if err != nil { - t.Fatalf("Failed to generate reply CMIX messages: %+v", err) - } - - receiveMsg := message.Receive{ - Payload: transmitMsg.Marshal(), - MessageType: message.Raw, - Sender: rid, - RecipientID: partner.ID, - } - - m.callbackMap.registerCallback(tag, callback) - - stop, _ := m.StartProcesses() - if !stop.IsRunning() { - t.Error("Stoppable is not running.") - } - - err = stop.Close() - if err != nil { - t.Errorf("Failed to close: %+v", err) - } - - // Wait for the stoppable to close - for !stop.IsStopped() { - time.Sleep(10 * time.Millisecond) - } - - m.swb.(*switchboard.Switchboard).Speak(receiveMsg) - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when the thread should have stopped."+ - "\npayload: %+v\ncontact: %+v", results.payload, results.c) - case <-timer.C: - } - - callbackFunc, callbackFuncChan := createReplyComm() - m.p.Lock() - m.p.singleUse[*rid] = newState(sender.dhKey, sender.maxParts, callbackFunc) - m.p.Unlock() - eid, _, _, _ := ephemeral.GetId(partner.ID, id.ArrIDLen, netTime.Now().UnixNano()) - replyMsg := message.Receive{ - Payload: replyMsgs[0].Marshal(), - MessageType: message.Raw, - Sender: partner.ID, - RecipientID: rid, - EphemeralID: eid, - } - - m.swb.(*switchboard.Switchboard).Speak(replyMsg) - - timer = time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackFuncChan: - t.Errorf("Callback called when the thread should have stopped."+ - "\npayload: %+v\nerror: %+v", results.payload, results.err) - case <-timer.C: - } -} - -type receiveCommData struct { - payload []byte - c Contact -} - -func createReceiveComm() (ReceiveComm, chan receiveCommData) { - callbackChan := make(chan receiveCommData) - - callback := func(payload []byte, c Contact) { - callbackChan <- receiveCommData{payload: payload, c: c} - } - return callback, callbackChan -} - -func newTestManager(timeout time.Duration, cmixErr bool, t *testing.T) *Manager { - return &Manager{ - client: nil, - store: storage.InitTestingSession(t), - reception: ephemeral2.NewStore(versioned.NewKV(make(ekv.Memstore))), - swb: switchboard.New(), - net: newTestNetworkManager(timeout, cmixErr, t), - rng: fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), - p: newPending(), - callbackMap: newCallbackMap(), - } -} - -func newTestNetworkManager(timeout time.Duration, cmixErr 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, - msgs: []format.Message{}, - cmixTimeout: timeout, - cmixErr: cmixErr, - } -} - -// testNetworkManager is a test implementation of NetworkManager interface. -type testNetworkManager struct { - instance *network.Instance - msgs []format.Message - cmixTimeout time.Duration - cmixErr bool - sync.RWMutex -} - -func (tnm *testNetworkManager) GetMsg(i int) format.Message { - tnm.RLock() - defer tnm.RUnlock() - return tnm.msgs[i] -} - -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) GetVerboseRounds() string { - return "" -} - -func (tnm *testNetworkManager) SendUnsafe(_ message.Send, _ params.Unsafe) ([]id.Round, error) { - return []id.Round{}, nil -} - -func (tnm *testNetworkManager) SendCMIX(msg format.Message, _ *id.ID, _ params.CMIX) (id.Round, ephemeral.Id, error) { - if tnm.cmixTimeout != 0 { - time.Sleep(tnm.cmixTimeout) - } else if tnm.cmixErr { - return 0, ephemeral.Id{}, errors.New("sendCMIX error") - } - - tnm.Lock() - defer tnm.Unlock() - - tnm.msgs = append(tnm.msgs, msg) - - return id.Round(rand.Uint64()), ephemeral.Id{}, nil -} - -func (tnm *testNetworkManager) SendManyCMIX(messages []message.TargetedCmixMessage, p params.CMIX) (id.Round, []ephemeral.Id, error) { - if tnm.cmixTimeout != 0 { - time.Sleep(tnm.cmixTimeout) - } else if tnm.cmixErr { - return 0, []ephemeral.Id{}, errors.New("sendCMIX error") - } - - tnm.Lock() - defer tnm.Unlock() - - for _, msg := range messages { - tnm.msgs = append(tnm.msgs, msg.Message) - } - - return id.Round(rand.Uint64()), []ephemeral.Id{}, nil -} - -func (tnm *testNetworkManager) GetInstance() *network.Instance { - return tnm.instance -} - -type dummyEventMgr struct{} - -func (d *dummyEventMgr) Report(p int, a, b, c string) {} -func (t *testNetworkManager) GetEventManager() event.Manager { - return &dummyEventMgr{} -} - -func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { - return nil -} - -func (tnm *testNetworkManager) Follow(report 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 16 } - -func (tnm *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) { - return nil, nil -} - -func (tnm *testNetworkManager) UnregisterAddressSizeNotification(string) {} -func (tnm *testNetworkManager) SetPoolFilter(gateway.Filter) {} - -func getNDF() *ndf.NetworkDefinition { - return &ndf.NetworkDefinition{ - E2E: ndf.Group{ - Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" + - "7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" + - "DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" + - "8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" + - "023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" + - "3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" + - "6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" + - "A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" + - "37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" + - "78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" + - "015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" + - "6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" + - "847AEF49F66E43873", - Generator: "2", - }, - CMIX: ndf.Group{ - Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" + - "C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" + - "FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" + - "B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" + - "35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" + - "F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" + - "92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" + - "3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", - Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" + - "D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" + - "6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" + - "085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" + - "AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" + - "3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" + - "BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" + - "DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", - }, - } -} diff --git a/single/collator.go b/single/message/collator.go similarity index 72% rename from single/collator.go rename to single/message/collator.go index 2ffe34cd656b0684857e3af21da80ad9682d1afb..842d404ed8e223f4f25ec72a5b187b0cec1ee3cc 100644 --- a/single/collator.go +++ b/single/message/collator.go @@ -1,4 +1,4 @@ -package single +package message import ( "bytes" @@ -6,30 +6,30 @@ import ( "sync" ) -// Initial value of the collator maxNum that indicates it has yet to be set +// Initial value of the Collator maxNum that indicates it has yet to be set const unsetCollatorMax = -1 -// collator stores the list of payloads in the correct order. -type collator struct { +// Collator stores the list of payloads in the correct order. +type Collator struct { payloads [][]byte // List of payloads, in order maxNum int // Max number of messages that can be received count int // Current number of messages received sync.Mutex } -// newCollator generates an empty list of payloads to fit the max number of +// NewCollator generates an empty list of payloads to fit the max number of // possible messages. maxNum is set to indicate that it is not yet set. -func newCollator(messageCount uint64) *collator { - return &collator{ +func NewCollator(messageCount uint8) *Collator { + return &Collator{ payloads: make([][]byte, messageCount), maxNum: unsetCollatorMax, } } -// collate collects message payload parts. Once all parts are received, the full +// Collate collects message payload parts. Once all parts are received, the full // collated payload is returned along with true. Otherwise returns false. -func (c *collator) collate(payloadBytes []byte) ([]byte, bool, error) { - payload, err := unmarshalResponseMessage(payloadBytes) +func (c *Collator) Collate(payloadBytes []byte) ([]byte, bool, error) { + payload, err := UnmarshalResponse(payloadBytes) if err != nil { return nil, false, errors.Errorf("Failed to unmarshal response "+ "payload: %+v", err) @@ -41,12 +41,12 @@ func (c *collator) collate(payloadBytes []byte) ([]byte, bool, error) { // If this is the first message received, then set the max number of // messages expected to be received off its max number of parts. if c.maxNum == unsetCollatorMax { - if int(payload.GetMaxParts()) > len(c.payloads) { + if int(payload.GetNumParts()) > len(c.payloads) { return nil, false, errors.Errorf("Max number of parts reported by "+ - "payload %d is larger than collator expected (%d).", - payload.GetMaxParts(), len(c.payloads)) + "payload %d is larger than Collator expected (%d).", + payload.GetNumParts(), len(c.payloads)) } - c.maxNum = int(payload.GetMaxParts()) + c.maxNum = int(payload.GetNumParts()) } // Make sure that the part number is within the expected number of parts diff --git a/single/collator_test.go b/single/message/collator_test.go similarity index 65% rename from single/collator_test.go rename to single/message/collator_test.go index b940f0c7498b7d7d3ff67b92e367a6621ecc2266..a51721188e403d863825191cfc4638df2812bda8 100644 --- a/single/collator_test.go +++ b/single/message/collator_test.go @@ -1,4 +1,4 @@ -package single +package message import ( "bytes" @@ -10,15 +10,15 @@ import ( // Happy path func Test_newCollator(t *testing.T) { messageCount := uint64(10) - expected := &collator{ + expected := &Collator{ payloads: make([][]byte, messageCount), maxNum: unsetCollatorMax, count: 0, } - c := newCollator(messageCount) + c := NewCollator(messageCount) if !reflect.DeepEqual(expected, c) { - t.Errorf("newCollator() failed to generated the expected collator."+ + t.Errorf("NewCollator() failed to generated the expected Collator."+ "\nexepcted: %+v\nreceived: %+v", expected, c) } } @@ -27,19 +27,19 @@ func Test_newCollator(t *testing.T) { func TestCollator_collate(t *testing.T) { messageCount := 16 msgPayloadSize := 2 - msgParts := map[int]responseMessagePart{} + msgParts := map[int]ResponsePart{} expectedData := make([]byte, messageCount*msgPayloadSize) copy(expectedData, "This is the expected final data.") buff := bytes.NewBuffer(expectedData) for i := 0; i < messageCount; i++ { - msgParts[i] = newResponseMessagePart(msgPayloadSize + 5) - msgParts[i].SetMaxParts(uint8(messageCount)) + msgParts[i] = NewResponsePart(msgPayloadSize + 5) + msgParts[i].SetNumParts(uint8(messageCount)) msgParts[i].SetPartNum(uint8(i)) msgParts[i].SetContents(buff.Next(msgPayloadSize)) } - c := newCollator(uint64(messageCount)) + c := NewCollator(uint64(messageCount)) i := 0 var fullPayload []byte @@ -49,22 +49,22 @@ func TestCollator_collate(t *testing.T) { var err error var collated bool - fullPayload, collated, err = c.collate(part.Marshal()) + fullPayload, collated, err = c.Collate(part.Marshal()) if err != nil { - t.Errorf("collate() returned an error for part #%d: %+v", j, err) + t.Errorf("Collate() returned an error for part #%d: %+v", j, err) } if i == messageCount && (!collated || fullPayload == nil) { - t.Errorf("collate() failed to collate a completed payload."+ + t.Errorf("Collate() failed to Collate a completed payload."+ "\ncollated: %v\nfullPayload: %+v", collated, fullPayload) } else if i < messageCount && (collated || fullPayload != nil) { - t.Errorf("collate() signaled it collated an unfinished payload."+ + t.Errorf("Collate() signaled it collated an unfinished payload."+ "\ncollated: %v\nfullPayload: %+v", collated, fullPayload) } } if !bytes.Equal(expectedData, fullPayload) { - t.Errorf("collate() failed to return the correct collated data."+ + t.Errorf("Collate() failed to return the correct collated data."+ "\nexpected: %s\nreceived: %s", expectedData, fullPayload) } } @@ -72,33 +72,33 @@ func TestCollator_collate(t *testing.T) { // Error path: the byte slice cannot be unmarshaled. func TestCollator_collate_UnmarshalError(t *testing.T) { payloadBytes := []byte{1} - c := newCollator(1) - payload, collated, err := c.collate(payloadBytes) + c := NewCollator(1) + payload, collated, err := c.Collate(payloadBytes) if err == nil || !strings.Contains(err.Error(), "unmarshal") { - t.Errorf("collate() failed to return an error for failing to "+ + t.Errorf("Collate() failed to return an error for failing to "+ "unmarshal the payload.\nerror: %+v", err) } if payload != nil || collated { - t.Errorf("collate() signaled the payload was collated on error."+ + t.Errorf("Collate() signaled the payload was collated on error."+ "\npayload: %+v\ncollated: %+v", payload, collated) } } -// Error path: max reported parts by payload larger then set in collator +// Error path: max reported parts by payload larger then set in Collator func TestCollator_collate_MaxPartsError(t *testing.T) { payloadBytes := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - c := newCollator(1) - payload, collated, err := c.collate(payloadBytes) + c := NewCollator(1) + payload, collated, err := c.Collate(payloadBytes) if err == nil || !strings.Contains(err.Error(), "Max number of parts") { - t.Errorf("collate() failed to return an error when the max number of "+ + t.Errorf("Collate() failed to return an error when the max number of "+ "parts is larger than the payload size.\nerror: %+v", err) } if payload != nil || collated { - t.Errorf("collate() signaled the payload was collated on error."+ + t.Errorf("Collate() signaled the payload was collated on error."+ "\npayload: %+v\ncollated: %+v", payload, collated) } } @@ -106,16 +106,16 @@ func TestCollator_collate_MaxPartsError(t *testing.T) { // Error path: the message part number is greater than the max number of parts. func TestCollator_collate_PartNumTooLargeError(t *testing.T) { payloadBytes := []byte{25, 5, 5, 5, 5} - c := newCollator(5) - payload, collated, err := c.collate(payloadBytes) + c := NewCollator(5) + payload, collated, err := c.Collate(payloadBytes) if err == nil || !strings.Contains(err.Error(), "greater than max number of expected parts") { - t.Errorf("collate() failed to return an error when the part number is "+ + t.Errorf("Collate() failed to return an error when the part number is "+ "greater than the max number of parts.\nerror: %+v", err) } if payload != nil || collated { - t.Errorf("collate() signaled the payload was collated on error."+ + t.Errorf("Collate() signaled the payload was collated on error."+ "\npayload: %+v\ncollated: %+v", payload, collated) } } @@ -123,20 +123,20 @@ func TestCollator_collate_PartNumTooLargeError(t *testing.T) { // Error path: a message with the part number already exists. func TestCollator_collate_PartExistsError(t *testing.T) { payloadBytes := []byte{0, 1, 5, 0, 1, 20} - c := newCollator(5) - payload, collated, err := c.collate(payloadBytes) + c := NewCollator(5) + payload, collated, err := c.Collate(payloadBytes) if err != nil { - t.Fatalf("collate() returned an error: %+v", err) + t.Fatalf("Collate() returned an error: %+v", err) } - payload, collated, err = c.collate(payloadBytes) + payload, collated, err = c.Collate(payloadBytes) if err == nil || !strings.Contains(err.Error(), "A payload for the part number") { - t.Errorf("collate() failed to return an error when the part number "+ + t.Errorf("Collate() failed to return an error when the part number "+ "already exists.\nerror: %+v", err) } if payload != nil || collated { - t.Errorf("collate() signaled the payload was collated on error."+ + t.Errorf("Collate() signaled the payload was collated on error."+ "\npayload: %+v\ncollated: %+v", payload, collated) } } diff --git a/single/transmitMessage.go b/single/message/request.go similarity index 54% rename from single/transmitMessage.go rename to single/message/request.go index 17ce73acd2a6a5183c5c173f31eb23bd950f41c7..f4397053ad9ed771c9908611ad422ccd9c4a66a3 100644 --- a/single/transmitMessage.go +++ b/single/message/request.go @@ -5,7 +5,7 @@ // LICENSE file // /////////////////////////////////////////////////////////////////////////////// -package single +package message import ( "encoding/binary" @@ -22,10 +22,10 @@ import ( +------------------------------------------------------------------------------+ | CMIX Message Contents | +------------+-------------------------------------------- --------+ -| Version | pubKey | payload (transmitMessagePayload) | +| Version | pubKey | payload (RequestPayload) | | 1 byte | pubKeySize | externalPayloadSize - pubKeySize | +------------+------------+----------+---------+----------+---------+----------+ - | Tag FP | nonce | maxParts | size | contents | + | Tag FP | nonce | maxResponseParts | size | contents | | 16 bytes | 8 bytes | 1 byte | 2 bytes | variable | +----------+---------+----------+---------+----------+ */ @@ -33,32 +33,32 @@ import ( const transmitMessageVersion = 0 const transmitMessageVersionSize = 1 -type transmitMessage struct { +type Request struct { data []byte // Serial of all contents version []byte pubKey []byte payload []byte // The encrypted payload containing reception ID and contents } -// newTransmitMessage generates a new empty message for transmission that is the +// NewRequest generates a new empty message for transmission that is the // size of the specified external payload. -func newTransmitMessage(externalPayloadSize, pubKeySize int) transmitMessage { +func NewRequest(externalPayloadSize, pubKeySize int) Request { if externalPayloadSize < pubKeySize { jww.FATAL.Panicf("Payload size of single-use transmission message "+ "(%d) too small to contain the public key (%d).", externalPayloadSize, pubKeySize) } - tm := mapTransmitMessage(make([]byte, externalPayloadSize), pubKeySize) + tm := mapRequest(make([]byte, externalPayloadSize), pubKeySize) tm.version[0] = transmitMessageVersion return tm } -// mapTransmitMessage builds a message mapped to the passed in data. It is +// mapRequest builds a message mapped to the passed in data. It is // mapped by reference; a copy is not made. -func mapTransmitMessage(data []byte, pubKeySize int) transmitMessage { - return transmitMessage{ +func mapRequest(data []byte, pubKeySize int) Request { + return Request{ data: data, version: data[:transmitMessageVersionSize], pubKey: data[transmitMessageVersionSize : transmitMessageVersionSize+pubKeySize], @@ -66,55 +66,55 @@ func mapTransmitMessage(data []byte, pubKeySize int) transmitMessage { } } -// unmarshalTransmitMessage unmarshalls a byte slice into a transmitMessage. An +// UnmarshalRequest unmarshalls a byte slice into a Request. An // error is returned if the slice is not large enough for the public key size. -func unmarshalTransmitMessage(b []byte, pubKeySize int) (transmitMessage, error) { +func UnmarshalRequest(b []byte, pubKeySize int) (Request, error) { if len(b) < pubKeySize { - return transmitMessage{}, errors.Errorf("Length of marshaled bytes "+ + return Request{}, errors.Errorf("Length of marshaled bytes "+ "(%d) too small to contain public key (%d).", len(b), pubKeySize) } - return mapTransmitMessage(b, pubKeySize), nil + return mapRequest(b, pubKeySize), nil } -// Marshal returns the serialised data of a transmitMessage. -func (m transmitMessage) Marshal() []byte { +// Marshal returns the serialised data of a Request. +func (m Request) Marshal() []byte { return m.data } // GetPubKey returns the public key that is part of the given group. -func (m transmitMessage) GetPubKey(grp *cyclic.Group) *cyclic.Int { +func (m Request) GetPubKey(grp *cyclic.Group) *cyclic.Int { return grp.NewIntFromBytes(m.pubKey) } // Version returns the version of the message. -func (m transmitMessage) Version() uint8 { +func (m Request) Version() uint8 { return m.version[0] } // GetPubKeySize returns the length of the public key. -func (m transmitMessage) GetPubKeySize() int { +func (m Request) GetPubKeySize() int { return len(m.pubKey) } // SetPubKey saves the public key to the message as bytes. -func (m transmitMessage) SetPubKey(pubKey *cyclic.Int) { +func (m Request) SetPubKey(pubKey *cyclic.Int) { copy(m.pubKey, pubKey.LeftpadBytes(uint64(len(m.pubKey)))) } // GetPayload returns the encrypted payload of the message. -func (m transmitMessage) GetPayload() []byte { +func (m Request) GetPayload() []byte { return m.payload } // GetPayloadSize returns the length of the encrypted payload. -func (m transmitMessage) GetPayloadSize() int { +func (m Request) GetPayloadSize() int { return len(m.payload) } // SetPayload saves the supplied bytes as the payload of the message, if the // size is correct. -func (m transmitMessage) SetPayload(b []byte) { +func (m Request) SetPayload(b []byte) { if len(b) != len(m.payload) { jww.FATAL.Panicf("Size of payload of single-use transmission message "+ "(%d) is not the same as the size of the supplied payload (%d).", @@ -125,27 +125,26 @@ func (m transmitMessage) SetPayload(b []byte) { } const ( - tagFPSize = singleUse.TagFpSize nonceSize = 8 maxPartsSize = 1 sizeSize = 2 - transmitPlMinSize = tagFPSize + nonceSize + maxPartsSize + sizeSize + transmitPlMinSize = nonceSize + maxPartsSize + sizeSize ) -// transmitMessagePayload is the structure of transmitMessage's payload. -type transmitMessagePayload struct { - data []byte // Serial of all contents - tagFP []byte // Tag fingerprint identifies the type of message - nonce []byte - maxParts []byte // Max number of messages expected in response - size []byte // Size of the contents - contents []byte +// RequestPayload is the structure of Request's payload. +type RequestPayload struct { + data []byte // Serial of all contents e + nonce []byte + nunRequestParts []byte + maxResponseParts []byte // Max number of messages expected in response + size []byte // Size of the contents + contents []byte } -// newTransmitMessage generates a new empty message for transmission that is the +// NewRequestPayload generates a new empty message for transmission that is the // size of the specified payload, which should match the size of the payload in -// the corresponding transmitMessage. -func newTransmitMessagePayload(payloadSize int) transmitMessagePayload { +// the corresponding Request. +func NewRequestPayload(payloadSize int, payload []byte, maxMsgs uint8) RequestPayload { if payloadSize < transmitPlMinSize { jww.FATAL.Panicf("Size of single-use transmission message payload "+ "(%d) too small to contain the necessary data (%d).", @@ -153,67 +152,58 @@ func newTransmitMessagePayload(payloadSize int) transmitMessagePayload { } // Map fields to data - mp := mapTransmitMessagePayload(make([]byte, payloadSize)) + mp := mapRequestPayload(make([]byte, payloadSize)) + mp.SetMaxResponseParts(maxMsgs) + mp.SetContents(payload) return mp } -// mapTransmitMessagePayload builds a message payload mapped to the passed in +// mapRequestPayload builds a message payload mapped to the passed in // data. It is mapped by reference; a copy is not made. -func mapTransmitMessagePayload(data []byte) transmitMessagePayload { - mp := transmitMessagePayload{ - data: data, - tagFP: data[:tagFPSize], - nonce: data[tagFPSize : tagFPSize+nonceSize], - maxParts: data[tagFPSize+nonceSize : tagFPSize+nonceSize+maxPartsSize], - size: data[tagFPSize+nonceSize+maxPartsSize : transmitPlMinSize], - contents: data[transmitPlMinSize:], +func mapRequestPayload(data []byte) RequestPayload { + mp := RequestPayload{ + data: data, + nonce: data[:nonceSize], + maxResponseParts: data[nonceSize : nonceSize+maxPartsSize], + size: data[nonceSize+maxPartsSize : transmitPlMinSize], + contents: data[transmitPlMinSize:], } return mp } -// unmarshalTransmitMessagePayload unmarshalls a byte slice into a -// transmitMessagePayload. An error is returned if the slice is not large enough +// UnmarshalRequestPayload unmarshalls a byte slice into a +// RequestPayload. An error is returned if the slice is not large enough // for the reception ID and message count. -func unmarshalTransmitMessagePayload(b []byte) (transmitMessagePayload, error) { +func UnmarshalRequestPayload(b []byte) (RequestPayload, error) { if len(b) < transmitPlMinSize { - return transmitMessagePayload{}, errors.Errorf("Length of marshaled "+ + return RequestPayload{}, errors.Errorf("Length of marshaled "+ "bytes(%d) too small to contain the necessary data (%d).", len(b), transmitPlMinSize) } - return mapTransmitMessagePayload(b), nil + return mapRequestPayload(b), nil } -// Marshal returns the serialised data of a transmitMessagePayload. -func (mp transmitMessagePayload) Marshal() []byte { +// Marshal returns the serialised data of a RequestPayload. +func (mp RequestPayload) Marshal() []byte { return mp.data } // GetRID generates the reception ID from the bytes of the payload. -func (mp transmitMessagePayload) GetRID(pubKey *cyclic.Int) *id.ID { +func (mp RequestPayload) GetRID(pubKey *cyclic.Int) *id.ID { return singleUse.NewRecipientID(pubKey, mp.Marshal()) } -// GetTagFP returns the tag fingerprint. -func (mp transmitMessagePayload) GetTagFP() singleUse.TagFP { - return singleUse.UnmarshalTagFP(mp.tagFP) -} - -// SetTagFP sets the tag fingerprint. -func (mp transmitMessagePayload) SetTagFP(tagFP singleUse.TagFP) { - copy(mp.tagFP, tagFP.Bytes()) -} - // GetNonce returns the nonce as a uint64. -func (mp transmitMessagePayload) GetNonce() uint64 { +func (mp RequestPayload) GetNonce() uint64 { return binary.BigEndian.Uint64(mp.nonce) } // SetNonce generates a random nonce from the RNG. An error is returned if the // reader fails. -func (mp transmitMessagePayload) SetNonce(rng io.Reader) error { +func (mp RequestPayload) SetNonce(rng io.Reader) error { if _, err := rng.Read(mp.nonce); err != nil { return errors.Errorf("failed to generate nonce: %+v", err) } @@ -221,34 +211,34 @@ func (mp transmitMessagePayload) SetNonce(rng io.Reader) error { return nil } -// GetMaxParts returns the number of messages expected in response. -func (mp transmitMessagePayload) GetMaxParts() uint8 { - return mp.maxParts[0] +// GetMaxResponseParts returns the number of messages expected in response. +func (mp RequestPayload) GetMaxResponseParts() uint8 { + return mp.maxResponseParts[0] } -// SetMaxParts sets the number of expected messages. -func (mp transmitMessagePayload) SetMaxParts(num uint8) { - copy(mp.maxParts, []byte{num}) +// SetMaxResponseParts sets the number of expected messages. +func (mp RequestPayload) SetMaxResponseParts(num uint8) { + copy(mp.maxResponseParts, []byte{num}) } // GetContents returns the payload's contents. -func (mp transmitMessagePayload) GetContents() []byte { +func (mp RequestPayload) GetContents() []byte { return mp.contents[:binary.BigEndian.Uint16(mp.size)] } // GetContentsSize returns the length of payload's contents. -func (mp transmitMessagePayload) GetContentsSize() int { +func (mp RequestPayload) GetContentsSize() int { return int(binary.BigEndian.Uint16(mp.size)) } // GetMaxContentsSize returns the max capacity of the contents. -func (mp transmitMessagePayload) GetMaxContentsSize() int { +func (mp RequestPayload) GetMaxContentsSize() int { return len(mp.contents) } // SetContents saves the contents to the payload, if the size is correct. Does // not zero out previous content. -func (mp transmitMessagePayload) SetContents(contents []byte) { +func (mp RequestPayload) SetContents(contents []byte) { if len(contents) > len(mp.contents) { jww.FATAL.Panicf("Failed to set contents of single-use transmission "+ "message: max size of message content (%d) is smaller than the "+ @@ -262,9 +252,9 @@ func (mp transmitMessagePayload) SetContents(contents []byte) { } // String returns the contents for printing adhering to the stringer interface. -func (mp transmitMessagePayload) String() string { - return fmt.Sprintf("Data: %x [tagFP: %x, nonce: %x, "+ - "maxParts: %x, size: %x, content: %x]", mp.data, mp.tagFP, - mp.nonce, mp.maxParts, mp.size, mp.contents) +func (mp RequestPayload) String() string { + return fmt.Sprintf("Data: %x [nonce: %x, "+ + "maxResponseParts: %x, size: %x, content: %x]", mp.data, + mp.nonce, mp.maxResponseParts, mp.size, mp.contents) } diff --git a/single/transmitMessage_test.go b/single/message/request_test.go similarity index 77% rename from single/transmitMessage_test.go rename to single/message/request_test.go index b6c62d25b71f507d54d1f69a70c0a35e932af628..8b5d2f6e6e6c1175bf75905fcf79631c8a7cbec3 100644 --- a/single/transmitMessage_test.go +++ b/single/message/request_test.go @@ -5,11 +5,12 @@ // LICENSE file // /////////////////////////////////////////////////////////////////////////////// -package single +package message import ( "bytes" "encoding/binary" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/e2e/singleUse" "gitlab.com/elixxir/primitives/format" @@ -25,17 +26,17 @@ func Test_newTransmitMessage(t *testing.T) { prng := rand.New(rand.NewSource(42)) externalPayloadSize := prng.Intn(2000) pubKeySize := prng.Intn(externalPayloadSize) - expected := transmitMessage{ + expected := Request{ data: make([]byte, externalPayloadSize), version: make([]byte, transmitMessageVersionSize), pubKey: make([]byte, pubKeySize), payload: make([]byte, externalPayloadSize-pubKeySize-transmitMessageVersionSize), } - m := newTransmitMessage(externalPayloadSize, pubKeySize) + m := NewRequest(externalPayloadSize, pubKeySize) if !reflect.DeepEqual(expected, m) { - t.Errorf("newTransmitMessage() did not produce the expected transmitMessage."+ + t.Errorf("NewRequest() did not produce the expected Request."+ "\nexpected: %#v\nreceived: %#v", expected, m) } } @@ -44,12 +45,12 @@ func Test_newTransmitMessage(t *testing.T) { func Test_newTransmitMessage_PubKeySizeError(t *testing.T) { defer func() { if r := recover(); r == nil || !strings.Contains(r.(string), "Payload size") { - t.Error("newTransmitMessage() did not panic when the size of " + + t.Error("NewRequest() did not panic when the size of " + "the payload is smaller than the size of the public key.") } }() - _ = newTransmitMessage(5, 10) + _ = NewRequest(5, 10) } // Happy path. @@ -66,20 +67,20 @@ func Test_mapTransmitMessage(t *testing.T) { data = append(data, version...) data = append(data, pubKey...) data = append(data, payload...) - m := mapTransmitMessage(data, pubKeySize) + m := mapRequest(data, pubKeySize) if !bytes.Equal(data, m.data) { - t.Errorf("mapTransmitMessage() failed to map the correct bytes for data."+ + t.Errorf("mapRequest() failed to map the correct bytes for data."+ "\nexpected: %+v\nreceived: %+v", data, m.data) } if !bytes.Equal(pubKey, m.pubKey) { - t.Errorf("mapTransmitMessage() failed to map the correct bytes for pubKey."+ + t.Errorf("mapRequest() failed to map the correct bytes for pubKey."+ "\nexpected: %+v\nreceived: %+v", pubKey, m.pubKey) } if !bytes.Equal(payload, m.payload) { - t.Errorf("mapTransmitMessage() failed to map the correct bytes for payload."+ + t.Errorf("mapRequest() failed to map the correct bytes for payload."+ "\nexpected: %+v\nreceived: %+v", payload, m.payload) } } @@ -91,13 +92,13 @@ func TestTransmitMessage_Marshal_Unmarshal(t *testing.T) { pubKeySize := prng.Intn(externalPayloadSize) data := make([]byte, externalPayloadSize) prng.Read(data) - m := mapTransmitMessage(data, pubKeySize) + m := mapRequest(data, pubKeySize) msgBytes := m.Marshal() - newMsg, err := unmarshalTransmitMessage(msgBytes, pubKeySize) + newMsg, err := unmarshalRequest(msgBytes, pubKeySize) if err != nil { - t.Errorf("unmarshalTransmitMessage produced an error: %+v", err) + t.Errorf("unmarshalRequest produced an error: %+v", err) } if !reflect.DeepEqual(m, newMsg) { @@ -108,9 +109,9 @@ func TestTransmitMessage_Marshal_Unmarshal(t *testing.T) { // Error path: public key size is larger than byte slice. func Test_unmarshalTransmitMessage_PubKeySizeError(t *testing.T) { - _, err := unmarshalTransmitMessage([]byte{1, 2, 3}, 5) + _, err := unmarshalRequest([]byte{1, 2, 3}, 5) if err == nil { - t.Error("unmarshalTransmitMessage() did not produce an error when the " + + t.Error("unmarshalRequest() did not produce an error when the " + "byte slice is smaller than the public key size.") } } @@ -120,7 +121,7 @@ func TestTransmitMessage_SetPubKey_GetPubKey_GetPubKeySize(t *testing.T) { grp := getGroup() pubKey := grp.NewInt(5) pubKeySize := 10 - m := newTransmitMessage(255, pubKeySize) + m := NewRequest(255, pubKeySize) m.SetPubKey(pubKey) testPubKey := m.GetPubKey(grp) @@ -143,7 +144,7 @@ func TestTransmitMessage_SetPayload_GetPayload_GetPayloadSize(t *testing.T) { payloadSize := externalPayloadSize - pubKeySize - transmitMessageVersionSize payload := make([]byte, payloadSize) prng.Read(payload) - m := newTransmitMessage(externalPayloadSize, pubKeySize) + m := NewRequest(externalPayloadSize, pubKeySize) m.SetPayload(payload) testPayload := m.GetPayload() @@ -168,7 +169,7 @@ func TestTransmitMessage_SetPayload_PayloadSizeError(t *testing.T) { } }() - m := newTransmitMessage(255, 10) + m := NewRequest(255, 10) m.SetPayload([]byte{5}) } @@ -176,34 +177,34 @@ func TestTransmitMessage_SetPayload_PayloadSizeError(t *testing.T) { func Test_newTransmitMessagePayload(t *testing.T) { prng := rand.New(rand.NewSource(42)) payloadSize := prng.Intn(2000) - expected := transmitMessagePayload{ - data: make([]byte, payloadSize), - tagFP: make([]byte, tagFPSize), - nonce: make([]byte, nonceSize), - maxParts: make([]byte, maxPartsSize), - size: make([]byte, sizeSize), - contents: make([]byte, payloadSize-transmitPlMinSize), + expected := RequestPayload{ + data: make([]byte, payloadSize), + tagFP: make([]byte, tagFPSize), + nonce: make([]byte, nonceSize), + maxResponseParts: make([]byte, maxPartsSize), + size: make([]byte, sizeSize), + contents: make([]byte, payloadSize-transmitPlMinSize), } - mp := newTransmitMessagePayload(payloadSize) + mp := NewRequestPayload(payloadSize) if !reflect.DeepEqual(expected, mp) { - t.Errorf("newTransmitMessagePayload() did not produce the expected "+ - "transmitMessagePayload.\nexpected: %+v\nreceived: %+v", expected, mp) + t.Errorf("NewRequestPayload() did not produce the expected "+ + "RequestPayload.\nexpected: %+v\nreceived: %+v", expected, mp) } } -// Error path: payload size is smaller than than rid size + maxParts size. +// Error path: payload size is smaller than than rid size + maxResponseParts size. func Test_newTransmitMessagePayload_PayloadSizeError(t *testing.T) { defer func() { if r := recover(); r == nil || !strings.Contains(r.(string), "Size of single-use transmission message payload") { - t.Error("newTransmitMessagePayload() did not panic when the size " + + t.Error("NewRequestPayload() did not panic when the size " + "of the payload is smaller than the size of the reception ID " + "+ the message count.") } }() - _ = newTransmitMessagePayload(10) + _ = NewRequestPayload(10) } // Happy path. @@ -221,35 +222,35 @@ func Test_mapTransmitMessagePayload(t *testing.T) { data = append(data, num) data = append(data, size...) data = append(data, contents...) - mp := mapTransmitMessagePayload(data) + mp := mapRequestPayload(data) if !bytes.Equal(data, mp.data) { - t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+ + t.Errorf("mapRequestPayload() failed to map the correct bytes "+ "for data.\nexpected: %+v\nreceived: %+v", data, mp.data) } if !bytes.Equal(tagFP.Bytes(), mp.tagFP) { - t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+ + t.Errorf("mapRequestPayload() failed to map the correct bytes "+ "for tagFP.\nexpected: %+v\nreceived: %+v", tagFP.Bytes(), mp.tagFP) } if !bytes.Equal(nonceBytes, mp.nonce) { - t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+ + t.Errorf("mapRequestPayload() failed to map the correct bytes "+ "for the nonce.\nexpected: %s\nreceived: %s", nonceBytes, mp.nonce) } - if num != mp.maxParts[0] { - t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+ - "for maxParts.\nexpected: %d\nreceived: %d", num, mp.maxParts[0]) + if num != mp.maxResponseParts[0] { + t.Errorf("mapRequestPayload() failed to map the correct bytes "+ + "for maxResponseParts.\nexpected: %d\nreceived: %d", num, mp.maxResponseParts[0]) } if !bytes.Equal(size, mp.size) { - t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+ + t.Errorf("mapRequestPayload() failed to map the correct bytes "+ "for size.\nexpected: %+v\nreceived: %+v", size, mp.size) } if !bytes.Equal(contents, mp.contents) { - t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+ + t.Errorf("mapRequestPayload() failed to map the correct bytes "+ "for contents.\nexpected: %+v\nreceived: %+v", contents, mp.contents) } } @@ -259,13 +260,13 @@ func TestTransmitMessagePayload_Marshal_Unmarshal(t *testing.T) { prng := rand.New(rand.NewSource(42)) data := make([]byte, prng.Intn(1000)) prng.Read(data) - mp := mapTransmitMessagePayload(data) + mp := mapRequestPayload(data) payloadBytes := mp.Marshal() - testPayload, err := unmarshalTransmitMessagePayload(payloadBytes) + testPayload, err := UnmarshalRequestPayload(payloadBytes) if err != nil { - t.Errorf("unmarshalTransmitMessagePayload() produced an error: %+v", err) + t.Errorf("UnmarshalRequestPayload() produced an error: %+v", err) } if !reflect.DeepEqual(mp, testPayload) { @@ -276,9 +277,9 @@ func TestTransmitMessagePayload_Marshal_Unmarshal(t *testing.T) { // Error path: supplied byte slice is too small for the ID and message count. func Test_unmarshalTransmitMessagePayload(t *testing.T) { - _, err := unmarshalTransmitMessagePayload([]byte{6}) + _, err := UnmarshalRequestPayload([]byte{6}) if err == nil { - t.Error("unmarshalTransmitMessagePayload() did not return an error " + + t.Error("UnmarshalRequestPayload() did not return an error " + "when the supplied byte slice was too small.") } } @@ -286,7 +287,7 @@ func Test_unmarshalTransmitMessagePayload(t *testing.T) { // Happy path. func TestTransmitMessagePayload_GetRID(t *testing.T) { prng := rand.New(rand.NewSource(42)) - mp := newTransmitMessagePayload(prng.Intn(2000)) + mp := NewRequestPayload(prng.Intn(2000)) expectedRID := singleUse.NewRecipientID(getGroup().NewInt(42), mp.Marshal()) testRID := mp.GetRID(getGroup().NewInt(42)) @@ -300,7 +301,7 @@ func TestTransmitMessagePayload_GetRID(t *testing.T) { // Happy path. func Test_transmitMessagePayload_SetNonce_GetNonce(t *testing.T) { prng := rand.New(rand.NewSource(42)) - mp := newTransmitMessagePayload(prng.Intn(2000)) + mp := NewRequestPayload(prng.Intn(2000)) expectedNonce := prng.Uint64() expectedNonceBytes := make([]byte, 8) @@ -319,9 +320,9 @@ func Test_transmitMessagePayload_SetNonce_GetNonce(t *testing.T) { // Error path: RNG return an error. func Test_transmitMessagePayload_SetNonce_RngError(t *testing.T) { prng := rand.New(rand.NewSource(42)) - mp := newTransmitMessagePayload(prng.Intn(2000)) + mp := NewRequestPayload(prng.Intn(2000)) err := mp.SetNonce(strings.NewReader("")) - if !check(err, "failed to generate nonce") { + if !single.check(err, "failed to generate nonce") { t.Errorf("SetNonce() did not return an error when nonce generation "+ "fails: %+v", err) } @@ -330,14 +331,14 @@ func Test_transmitMessagePayload_SetNonce_RngError(t *testing.T) { // Happy path. func TestTransmitMessagePayload_SetMaxParts_GetMaxParts(t *testing.T) { prng := rand.New(rand.NewSource(42)) - mp := newTransmitMessagePayload(prng.Intn(2000)) + mp := NewRequestPayload(prng.Intn(2000)) count := uint8(prng.Uint64()) - mp.SetMaxParts(count) - testCount := mp.GetMaxParts() + mp.SetMaxResponseParts(count) + testCount := mp.GetMaxResponseParts() if count != testCount { - t.Errorf("GetMaxParts() did not return the expected count."+ + t.Errorf("GetNumParts() did not return the expected count."+ "\nexpected: %d\nreceived: %d", count, testCount) } } @@ -345,7 +346,7 @@ func TestTransmitMessagePayload_SetMaxParts_GetMaxParts(t *testing.T) { // Happy path. func TestTransmitMessagePayload_SetContents_GetContents_GetContentsSize_GetMaxContentsSize(t *testing.T) { prng := rand.New(rand.NewSource(42)) - mp := newTransmitMessagePayload(format.MinimumPrimeSize) + mp := NewRequestPayload(format.MinimumPrimeSize) contentsSize := (format.MinimumPrimeSize - transmitPlMinSize) / 2 contents := make([]byte, contentsSize) prng.Read(contents) @@ -377,7 +378,7 @@ func TestTransmitMessagePayload_SetContents(t *testing.T) { } }() - mp := newTransmitMessagePayload(format.MinimumPrimeSize) + mp := NewRequestPayload(format.MinimumPrimeSize) mp.SetContents(make([]byte, format.MinimumPrimeSize+1)) } diff --git a/single/responseMessage.go b/single/message/response.go similarity index 71% rename from single/responseMessage.go rename to single/message/response.go index 191038dcb7665b17578268c85623af858b1601fb..2fde98e94019e538b4fbf638d143079a363ecaae 100644 --- a/single/responseMessage.go +++ b/single/message/response.go @@ -5,7 +5,7 @@ // LICENSE file // /////////////////////////////////////////////////////////////////////////////// -package single +package message import ( "encoding/binary" @@ -25,12 +25,12 @@ const ( +---------------------------------------------------+ | CMIX Message Contents | +---------+----------+---------+---------+----------+ -| version | maxParts | size | partNum | contents | +| version | maxResponseParts | size | partNum | contents | | 1 bytes | 1 byte | 2 bytes | 1 bytes | variable | +------------+----------+---------+------+----------+ */ -type responseMessagePart struct { +type ResponsePart struct { data []byte // Serial of all contents version []byte // Version of the message partNum []byte // Index of message in a series of messages @@ -39,9 +39,9 @@ type responseMessagePart struct { contents []byte // The encrypted contents } -// newResponseMessagePart generates a new response message part of the specified +// NewResponsePart generates a new response message part of the specified // size. -func newResponseMessagePart(externalPayloadSize int) responseMessagePart { +func NewResponsePart(externalPayloadSize int) ResponsePart { if externalPayloadSize < responseMinSize { jww.FATAL.Panicf("Failed to create new single-use response message "+ "part: size of external payload (%d) is too small to contain the "+ @@ -49,15 +49,15 @@ func newResponseMessagePart(externalPayloadSize int) responseMessagePart { externalPayloadSize, responseMinSize) } - rmp := mapResponseMessagePart(make([]byte, externalPayloadSize)) + rmp := mapResponsePart(make([]byte, externalPayloadSize)) rmp.version[0] = receptionMessageVersion return rmp } -// mapResponseMessagePart builds a message part mapped to the passed in data. +// mapResponsePart builds a message part mapped to the passed in data. // It is mapped by reference; a copy is not made. -func mapResponseMessagePart(data []byte) responseMessagePart { - return responseMessagePart{ +func mapResponsePart(data []byte) ResponsePart { + return ResponsePart{ data: data, version: data[:receptionMessageVersionLen], partNum: data[receptionMessageVersionLen : receptionMessageVersionLen+partNumLen], @@ -67,59 +67,59 @@ func mapResponseMessagePart(data []byte) responseMessagePart { } } -// unmarshalResponseMessage converts a byte buffer into a response message part. -func unmarshalResponseMessage(b []byte) (responseMessagePart, error) { +// UnmarshalResponse converts a byte buffer into a response message part. +func UnmarshalResponse(b []byte) (ResponsePart, error) { if len(b) < responseMinSize { - return responseMessagePart{}, errors.Errorf("Size of passed in bytes "+ + return ResponsePart{}, errors.Errorf("Size of passed in bytes "+ "(%d) is too small to contain the message part number and max "+ "parts (%d).", len(b), responseMinSize) } - return mapResponseMessagePart(b), nil + return mapResponsePart(b), nil } // Marshal returns the bytes of the message part. -func (m responseMessagePart) Marshal() []byte { +func (m ResponsePart) Marshal() []byte { return m.data } // GetPartNum returns the index of this part in the message. -func (m responseMessagePart) GetPartNum() uint8 { +func (m ResponsePart) GetPartNum() uint8 { return m.partNum[0] } // SetPartNum sets the part number of the message. -func (m responseMessagePart) SetPartNum(num uint8) { +func (m ResponsePart) SetPartNum(num uint8) { copy(m.partNum, []byte{num}) } -// GetMaxParts returns the number of parts in the message. -func (m responseMessagePart) GetMaxParts() uint8 { +// GetNumParts returns the number of parts in the message. +func (m ResponsePart) GetNumParts() uint8 { return m.maxParts[0] } -// SetMaxParts sets the number of parts in the message. -func (m responseMessagePart) SetMaxParts(max uint8) { +// SetNumParts sets the number of parts in the message. +func (m ResponsePart) SetNumParts(max uint8) { copy(m.maxParts, []byte{max}) } // GetContents returns the contents of the message part. -func (m responseMessagePart) GetContents() []byte { +func (m ResponsePart) GetContents() []byte { return m.contents[:binary.BigEndian.Uint16(m.size)] } // GetContentsSize returns the length of the contents. -func (m responseMessagePart) GetContentsSize() int { +func (m ResponsePart) GetContentsSize() int { return int(binary.BigEndian.Uint16(m.size)) } // GetMaxContentsSize returns the max capacity of the contents. -func (m responseMessagePart) GetMaxContentsSize() int { +func (m ResponsePart) GetMaxContentsSize() int { return len(m.contents) } // SetContents sets the contents of the message part. Does not zero out previous // contents. -func (m responseMessagePart) SetContents(contents []byte) { +func (m ResponsePart) SetContents(contents []byte) { if len(contents) > len(m.contents) { jww.FATAL.Panicf("Failed to set contents of single-use response "+ "message part: max size of message contents (%d) is smaller than "+ diff --git a/single/responseMessage_test.go b/single/message/response_test.go similarity index 77% rename from single/responseMessage_test.go rename to single/message/response_test.go index f9a78f068fb90021f1a7a9f9571ca02d244b91d1..4d4e85fcb2c6bae21f48c85c6059615f6a65c969 100644 --- a/single/responseMessage_test.go +++ b/single/message/response_test.go @@ -5,7 +5,7 @@ // LICENSE file // /////////////////////////////////////////////////////////////////////////////// -package single +package message import ( "bytes" @@ -19,7 +19,7 @@ import ( func Test_newResponseMessagePart(t *testing.T) { prng := rand.New(rand.NewSource(42)) payloadSize := prng.Intn(2000) - expected := responseMessagePart{ + expected := ResponsePart{ data: make([]byte, payloadSize), version: make([]byte, receptionMessageVersionLen), partNum: make([]byte, partNumLen), @@ -28,11 +28,11 @@ func Test_newResponseMessagePart(t *testing.T) { contents: make([]byte, payloadSize-partNumLen-maxPartsLen-sizeSize-receptionMessageVersionLen), } - rmp := newResponseMessagePart(payloadSize) + rmp := NewResponsePart(payloadSize) if !reflect.DeepEqual(expected, rmp) { - t.Errorf("newResponseMessagePart() did not return the expected "+ - "responseMessagePart.\nexpected: %+v\nreceived: %+v", expected, rmp) + t.Errorf("NewResponsePart() did not return the expected "+ + "ResponsePart.\nexpected: %+v\nreceived: %+v", expected, rmp) } } @@ -40,12 +40,12 @@ func Test_newResponseMessagePart(t *testing.T) { func Test_newResponseMessagePart_PayloadSizeError(t *testing.T) { defer func() { if r := recover(); r == nil || !strings.Contains(r.(string), "size of external payload") { - t.Error("newResponseMessagePart() did not panic when the size of " + + t.Error("NewResponsePart() did not panic when the size of " + "the payload is smaller than the required size.") } }() - _ = newResponseMessagePart(1) + _ = NewResponsePart(1) } // Happy path. @@ -62,25 +62,25 @@ func Test_mapResponseMessagePart(t *testing.T) { data = append(data, size...) data = append(data, expectedContents...) - rmp := mapResponseMessagePart(data) + rmp := mapResponsePart(data) if expectedPartNum != rmp.partNum[0] { - t.Errorf("mapResponseMessagePart() did not correctly map partNum."+ + t.Errorf("mapResponsePart() did not correctly map partNum."+ "\nexpected: %d\nreceived: %d", expectedPartNum, rmp.partNum[0]) } if expectedMaxParts != rmp.maxParts[0] { - t.Errorf("mapResponseMessagePart() did not correctly map maxParts."+ + t.Errorf("mapResponsePart() did not correctly map maxResponseParts."+ "\nexpected: %d\nreceived: %d", expectedMaxParts, rmp.maxParts[0]) } if !bytes.Equal(expectedContents, rmp.contents) { - t.Errorf("mapResponseMessagePart() did not correctly map contents."+ + t.Errorf("mapResponsePart() did not correctly map contents."+ "\nexpected: %+v\nreceived: %+v", expectedContents, rmp.contents) } if !bytes.Equal(data, rmp.data) { - t.Errorf("mapResponseMessagePart() did not save the data correctly."+ + t.Errorf("mapResponsePart() did not save the data correctly."+ "\nexpected: %+v\nreceived: %+v", data, rmp.data) } } @@ -90,26 +90,26 @@ func TestResponseMessagePart_Marshal_Unmarshal(t *testing.T) { prng := rand.New(rand.NewSource(42)) payload := make([]byte, prng.Intn(2000)) prng.Read(payload) - rmp := newResponseMessagePart(prng.Intn(2000)) + rmp := NewResponsePart(prng.Intn(2000)) data := rmp.Marshal() - newRmp, err := unmarshalResponseMessage(data) + newRmp, err := UnmarshalResponse(data) if err != nil { - t.Errorf("unmarshalResponseMessage() produced an error: %+v", err) + t.Errorf("UnmarshalResponse() produced an error: %+v", err) } if !reflect.DeepEqual(rmp, newRmp) { - t.Errorf("Failed to Marshal() and unmarshal() the responseMessagePart."+ + t.Errorf("Failed to Marshal() and unmarshal() the ResponsePart."+ "\nexpected: %+v\nrecieved: %+v", rmp, newRmp) } } // Error path: provided bytes are too small. func Test_unmarshalResponseMessage(t *testing.T) { - _, err := unmarshalResponseMessage([]byte{1}) + _, err := UnmarshalResponse([]byte{1}) if err == nil { - t.Error("unmarshalResponseMessage() did not produce an error when the " + + t.Error("UnmarshalResponse() did not produce an error when the " + "byte slice is smaller required.") } } @@ -118,7 +118,7 @@ func Test_unmarshalResponseMessage(t *testing.T) { func TestResponseMessagePart_SetPartNum_GetPartNum(t *testing.T) { prng := rand.New(rand.NewSource(42)) expectedPartNum := uint8(prng.Uint32()) - rmp := newResponseMessagePart(prng.Intn(2000)) + rmp := NewResponsePart(prng.Intn(2000)) rmp.SetPartNum(expectedPartNum) @@ -132,13 +132,13 @@ func TestResponseMessagePart_SetPartNum_GetPartNum(t *testing.T) { func TestResponseMessagePart_SetMaxParts_GetMaxParts(t *testing.T) { prng := rand.New(rand.NewSource(42)) expectedMaxParts := uint8(prng.Uint32()) - rmp := newResponseMessagePart(prng.Intn(2000)) + rmp := NewResponsePart(prng.Intn(2000)) - rmp.SetMaxParts(expectedMaxParts) + rmp.SetNumParts(expectedMaxParts) - if expectedMaxParts != rmp.GetMaxParts() { - t.Errorf("GetMaxParts() failed to return the expected max parts."+ - "\nexpected: %d\nrecieved: %d", expectedMaxParts, rmp.GetMaxParts()) + if expectedMaxParts != rmp.GetNumParts() { + t.Errorf("GetNumParts() failed to return the expected max parts."+ + "\nexpected: %d\nrecieved: %d", expectedMaxParts, rmp.GetNumParts()) } } @@ -149,7 +149,7 @@ func TestResponseMessagePart_SetContents_GetContents_GetContentsSize_GetMaxConte contentSize := externalPayloadSize - responseMinSize - 10 expectedContents := make([]byte, contentSize) prng.Read(expectedContents) - rmp := newResponseMessagePart(externalPayloadSize) + rmp := NewResponsePart(externalPayloadSize) rmp.SetContents(expectedContents) if !bytes.Equal(expectedContents, rmp.GetContents()) { @@ -178,6 +178,6 @@ func TestResponseMessagePart_SetContents_ContentsSizeError(t *testing.T) { } }() - rmp := newResponseMessagePart(255) + rmp := NewResponsePart(255) rmp.SetContents(make([]byte, 500)) } diff --git a/single/receiveResponse.go b/single/receiveResponse.go deleted file mode 100644 index 19d17da6ccb2e03efa1005060fc7252fa7497c7d..0000000000000000000000000000000000000000 --- a/single/receiveResponse.go +++ /dev/null @@ -1,128 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "fmt" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "strings" -) - -// receiveResponseHandler handles the reception of single-use response messages. -func (m *Manager) receiveResponseHandler(rawMessages chan message.Receive, - stop *stoppable.Single) { - jww.DEBUG.Print("Waiting to receive single-use response messages.") - for { - select { - case <-stop.Quit(): - jww.DEBUG.Printf("Stopping waiting to receive single-use " + - "response message.") - stop.ToStopped() - return - case msg := <-rawMessages: - jww.TRACE.Printf("Received CMIX message; checking if it is a " + - "single-use response.") - - // Process CMIX message - err := m.processesResponse(msg.RecipientID, msg.EphemeralID, msg.Payload) - if err != nil { - em := fmt.Sprintf("Failed to read single-use "+ - "CMIX message response: %+v", err) - if strings.Contains(err.Error(), "no state exists for the reception ID") { - jww.TRACE.Print(em) - } else { - if m.client != nil { - m.client.ReportEvent(9, "SingleUse", - "Error", em) - } - } - } - } - } -} - -// processesResponse processes the CMIX message and collates its payload. If the -// message is invalid, an error is returned. -func (m *Manager) processesResponse(rid *id.ID, ephID ephemeral.Id, - msgBytes []byte) error { - - // get the state from the map - m.p.RLock() - state, exists := m.p.singleUse[*rid] - m.p.RUnlock() - - // Check that the state exists - if !exists { - return errors.Errorf("no state exists for the reception ID %s.", rid) - } - - // Unmarshal CMIX message - cmixMsg, err := format.Unmarshal(msgBytes) - if err != nil { - return err - } - - // Ensure the fingerprints match - fp := cmixMsg.GetKeyFP() - key, exists := state.fpMap.getKey(state.dhKey, fp) - if !exists { - return errors.New("message fingerprint does not correspond to the " + - "expected fingerprint.") - } - - // Verify the CMIX message MAC - if !singleUse.VerifyMAC(key, cmixMsg.GetContents(), cmixMsg.GetMac()) { - return errors.New("failed to verify the CMIX message MAC.") - } - - // Denote that the message is not garbled - jww.DEBUG.Print("Received single-use response message.") - m.store.GetGarbledMessages().Remove(cmixMsg) - - // Decrypt and collate the payload - decryptedPayload := auth.Crypt(key, fp[:24], cmixMsg.GetContents()) - collatedPayload, collated, err := state.c.collate(decryptedPayload) - if err != nil { - return errors.Errorf("failed to collate payload: %+v", err) - } - jww.DEBUG.Print("Successfully processed single-use response message part.") - - // Once all message parts have been received delete and close everything - if collated { - if m.client != nil { - m.client.ReportEvent(1, "SingleUse", "MessageReceived", - fmt.Sprintf("Single use response received "+ - "from %s", rid)) - } - jww.DEBUG.Print("Received all parts of single-use response message.") - // Exit the timeout handler - state.quitChan <- struct{}{} - - // Remove identity - m.reception.RemoveIdentity(ephID) - - // Remove state from map - m.p.Lock() - delete(m.p.singleUse, *rid) - m.p.Unlock() - - // Call in separate routine to prevent blocking - jww.DEBUG.Print("Calling single-use response message callback.") - go state.callback(collatedPayload, nil) - } - - return nil -} diff --git a/single/receiveResponse_test.go b/single/receiveResponse_test.go deleted file mode 100644 index bed0f9053b72fad10233e28d16f2b0d044bbcd59..0000000000000000000000000000000000000000 --- a/single/receiveResponse_test.go +++ /dev/null @@ -1,330 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "bytes" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "gitlab.com/xx_network/primitives/netTime" - "math/rand" - "strings" - "testing" - "time" -) - -// Happy path. -func TestManager_ReceiveResponseHandler(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - stop := stoppable.NewSingle("singleStoppable") - partner := NewContact(id.NewIdFromString("recipientID", id.User, t), - m.store.E2e().GetGroup().NewInt(43), m.store.E2e().GetGroup().NewInt(42), - singleUse.TagFP{}, 8) - ephID, _, _, err := ephemeral.GetId(partner.partner, id.ArrIDLen, netTime.Now().UnixNano()) - payload := make([]byte, 2000) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReplyComm() - rid := id.NewIdFromString("rid", id.User, t) - - m.p.singleUse[*rid] = newState(partner.dhKey, partner.maxParts, callback) - - msgs, err := m.makeReplyCmixMessages(partner, payload) - if err != nil { - t.Fatalf("Failed to generate CMIX messages: %+v", err) - } - - go func() { - timer := time.NewTimer(50 * time.Millisecond) - select { - case <-timer.C: - t.Errorf("quitChan never set.") - case <-m.p.singleUse[*rid].quitChan: - } - }() - - go m.receiveResponseHandler(rawMessages, stop) - - for _, msg := range msgs { - rawMessages <- message.Receive{ - Payload: msg.Marshal(), - Sender: partner.partner, - RecipientID: rid, - EphemeralID: ephID, - } - } - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - if !bytes.Equal(results.payload, payload) { - t.Errorf("Callback received wrong payload."+ - "\nexpected: %+v\nreceived: %+v", payload, results.payload) - } - if results.err != nil { - t.Errorf("Callback received an error: %+v", results.err) - } - case <-timer.C: - t.Errorf("Callback failed to be called.") - } - - if err := stop.Close(); err != nil { - t.Errorf("Failed to signal close to process: %+v", err) - } -} - -// Error path: invalid CMIX message. -func TestManager_ReceiveResponseHandler_CmixMessageError(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - stop := stoppable.NewSingle("singleStoppable") - partner := NewContact(id.NewIdFromString("recipientID", id.User, t), - m.store.E2e().GetGroup().NewInt(43), m.store.E2e().GetGroup().NewInt(42), - singleUse.TagFP{}, 8) - ephID, _, _, _ := ephemeral.GetId(partner.partner, id.ArrIDLen, netTime.Now().UnixNano()) - payload := make([]byte, 2000) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReplyComm() - rid := id.NewIdFromString("rid", id.User, t) - - m.p.singleUse[*rid] = newState(partner.dhKey, partner.maxParts, callback) - - go func() { - timer := time.NewTimer(50 * time.Millisecond) - select { - case <-timer.C: - case <-m.p.singleUse[*rid].quitChan: - t.Error("quitChan called on error.") - } - }() - - go m.receiveResponseHandler(rawMessages, stop) - - rawMessages <- message.Receive{ - Payload: make([]byte, format.MinimumPrimeSize*2), - Sender: partner.partner, - RecipientID: rid, - EphemeralID: ephID, - } - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when it should not have been."+ - "payload: %+v\nerror: %+v", results.payload, results.err) - case <-timer.C: - } - - if err := stop.Close(); err != nil { - t.Errorf("Failed to signal close to process: %+v", err) - } -} - -// Happy path. -func TestManager_processesResponse(t *testing.T) { - m := newTestManager(0, false, t) - rid := id.NewIdFromString("test RID", id.User, t) - ephID, _, _, err := ephemeral.GetId(rid, id.ArrIDLen, netTime.Now().UnixNano()) - if err != nil { - t.Fatalf("Failed to create address ID: %+v", err) - } - dhKey := getGroup().NewInt(5) - maxMsgs := uint8(6) - timeout := 5 * time.Millisecond - callback, callbackChan := createReplyComm() - - m.p.singleUse[*rid] = newState(dhKey, maxMsgs, callback) - - expectedData := []string{"This i", "s the ", "expect", "ed fin", "al dat", "a."} - var msgs []format.Message - for fp, i := range m.p.singleUse[*rid].fpMap.fps { - newMsg := format.NewMessage(format.MinimumPrimeSize) - part := newResponseMessagePart(newMsg.ContentsSize()) - part.SetContents([]byte(expectedData[i])) - part.SetPartNum(uint8(i)) - part.SetMaxParts(maxMsgs) - - key := singleUse.NewResponseKey(dhKey, i) - encryptedPayload := auth.Crypt(key, fp[:24], part.Marshal()) - - newMsg.SetKeyFP(fp) - newMsg.SetMac(singleUse.MakeMAC(key, encryptedPayload)) - newMsg.SetContents(encryptedPayload) - msgs = append(msgs, newMsg) - } - - go func() { - timer := time.NewTimer(timeout) - select { - case <-timer.C: - t.Errorf("quitChan never set.") - case <-m.p.singleUse[*rid].quitChan: - } - }() - - for i, msg := range msgs { - err := m.processesResponse(rid, ephID, msg.Marshal()) - if err != nil { - t.Errorf("processesResponse() returned an error (%d): %+v", i, err) - } - } - - timer := time.NewTimer(timeout) - select { - case <-timer.C: - t.Errorf("Callback never called.") - case results := <-callbackChan: - if results.err != nil { - t.Errorf("Callback returned an error: %+v", err) - } - if !bytes.Equal([]byte(strings.Join(expectedData, "")), results.payload) { - t.Errorf("Callback returned incorrect data."+ - "\nexpected: %s\nreceived: %s", expectedData, results.payload) - } - } - - if _, exists := m.p.singleUse[*rid]; exists { - t.Error("Failed to delete the state after collation is complete.") - } -} - -// Error path: no state in map. -func TestManager_processesResponse_NoStateError(t *testing.T) { - m := newTestManager(0, false, t) - rid := id.NewIdFromString("test RID", id.User, t) - - err := m.processesResponse(rid, ephemeral.Id{}, []byte{}) - if !check(err, "no state exists for the reception ID") { - t.Errorf("processesResponse() did not return an error when the state "+ - "is not in the map: %+v", err) - } -} - -// Error path: failed to verify MAC. -func TestManager_processesResponse_MacVerificationError(t *testing.T) { - m := newTestManager(0, false, t) - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - timeout := 5 * time.Millisecond - callback := func(payload []byte, err error) {} - - quitChan, _, err := m.p.addState(rid, dhKey, 1, callback, timeout) - if err != nil { - t.Fatalf("Failed to add state: %+v", err) - } - quitChan <- struct{}{} - - newMsg := format.NewMessage(format.MinimumPrimeSize) - part := newResponseMessagePart(newMsg.ContentsSize()) - part.SetContents([]byte("payload data")) - part.SetPartNum(0) - part.SetMaxParts(1) - newMsg.SetMac(singleUse.MakeMAC(dhKey.Bytes(), []byte("some data"))) - newMsg.SetContents([]byte("payload data")) - - var fp format.Fingerprint - for fpt, i := range m.p.singleUse[*rid].fpMap.fps { - if i == 0 { - fp = fpt - } - } - newMsg.SetKeyFP(fp) - - err = m.processesResponse(rid, ephemeral.Id{}, newMsg.Marshal()) - if !check(err, "MAC") { - t.Errorf("processesResponse() did not return an error when MAC "+ - "verification should have failed: %+v", err) - } -} - -// Error path: CMIX message fingerprint does not match fingerprints in map. -func TestManager_processesResponse_FingerprintError(t *testing.T) { - m := newTestManager(0, false, t) - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - timeout := 5 * time.Millisecond - callback := func(payload []byte, err error) {} - - quitChan, _, err := m.p.addState(rid, dhKey, 1, callback, timeout) - if err != nil { - t.Fatalf("Failed to add state: %+v", err) - } - quitChan <- struct{}{} - - var fp format.Fingerprint - for fpt, i := range m.p.singleUse[*rid].fpMap.fps { - if i == 0 { - fp = fpt - } - } - newMsg := format.NewMessage(format.MinimumPrimeSize) - part := newResponseMessagePart(newMsg.ContentsSize()) - part.SetContents([]byte("payload data")) - part.SetPartNum(0) - part.SetMaxParts(1) - - key := singleUse.NewResponseKey(dhKey, 0) - encryptedPayload := auth.Crypt(key, fp[:24], part.Marshal()) - - newMsg.SetKeyFP(format.NewFingerprint([]byte("Invalid Fingerprint"))) - newMsg.SetMac(singleUse.MakeMAC(key, encryptedPayload)) - newMsg.SetContents(encryptedPayload) - - err = m.processesResponse(rid, ephemeral.Id{}, newMsg.Marshal()) - if !check(err, "fingerprint") { - t.Errorf("processesResponse() did not return an error when "+ - "fingerprint was wrong: %+v", err) - } -} - -// Error path: collator fails because part number is wrong. -func TestManager_processesResponse_CollatorError(t *testing.T) { - m := newTestManager(0, false, t) - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - timeout := 5 * time.Millisecond - callback := func(payload []byte, err error) {} - - quitChan, _, err := m.p.addState(rid, dhKey, 1, callback, timeout) - if err != nil { - t.Fatalf("Failed to add state: %+v", err) - } - quitChan <- struct{}{} - - var fp format.Fingerprint - for fpt, i := range m.p.singleUse[*rid].fpMap.fps { - if i == 0 { - fp = fpt - } - } - newMsg := format.NewMessage(format.MinimumPrimeSize) - part := newResponseMessagePart(newMsg.ContentsSize()) - part.SetContents([]byte("payload data")) - part.SetPartNum(5) - part.SetMaxParts(1) - - key := singleUse.NewResponseKey(dhKey, 0) - encryptedPayload := auth.Crypt(key, fp[:24], part.Marshal()) - - newMsg.SetKeyFP(fp) - newMsg.SetMac(singleUse.MakeMAC(key, encryptedPayload)) - newMsg.SetContents(encryptedPayload) - - err = m.processesResponse(rid, ephemeral.Id{}, newMsg.Marshal()) - if !check(err, "collate") { - t.Errorf("processesResponse() did not return an error when "+ - "collation should have failed: %+v", err) - } -} diff --git a/single/receivedRequest.go b/single/receivedRequest.go new file mode 100644 index 0000000000000000000000000000000000000000..b98d117504d895d9489fe41cc865161c1c5a3870 --- /dev/null +++ b/single/receivedRequest.go @@ -0,0 +1,186 @@ +package single + +import ( + "bytes" + "fmt" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix" + cmixMsg "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/single/message" + ds "gitlab.com/elixxir/comms/network/dataStructures" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/primitives/states" + "gitlab.com/xx_network/primitives/id" + "sync" + "sync/atomic" + "time" +) + +// Request contains the information to respond to a single-use contact. +type Request struct { + sender *id.ID // ID of the person to respond to + senderPubKey *cyclic.Int // Public key of the sender + dhKey *cyclic.Int // DH key + tag string // Identifies which callback to use + maxParts uint8 // Max number of messages allowed in reply + used *uint32 // Atomic variable + requestPayload []byte + net cmix.Client +} + +// GetMaxParts returns the maximum number of message parts that can be sent in a +// reply. +func (r Request) GetMaxParts() uint8 { + return r.maxParts +} + +func (r Request) GetMaxResponseLength() int { + responseMsg := message.NewResponsePart(r.net.GetMaxMessageLength()) + + // Maximum payload size is the maximum amount of room in each message + // multiplied by the number of messages + return responseMsg.GetMaxContentsSize() * int(r.GetMaxParts()) +} + +// GetPartner returns a copy of the sender ID. +func (r Request) GetPartner() *id.ID { + return r.sender.DeepCopy() +} + +// GetTag returns the tag for the request. +func (r Request) GetTag() string { + return r.tag +} + +// GetPayload returns the payload that came in the request +func (r Request) GetPayload() []byte { + return r.requestPayload +} + +// String returns a string of the Contact structure. +func (r Request) String() string { + format := "Request{sender:%s senderPubKey:%s dhKey:%s tagFP:%s " + + "maxParts:%d}" + return fmt.Sprintf(format, r.sender, r.senderPubKey.Text(10), + r.dhKey.Text(10), r.tag, r.maxParts) +} + +func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, + timeout time.Duration) ([]id.Round, error) { + // make sure this has only been run once + newRun := atomic.CompareAndSwapUint32(r.used, 0, 1) + if !newRun { + return nil, errors.Errorf("cannot respond to " + + "single-use response that has already been responded to.") + } + + //check that the payload isn't too long + if len(payload) > r.GetMaxResponseLength() { + return nil, errors.Errorf("length of provided "+ + "payload too long for message payload capacity, max: %d, "+ + "received: %d", r.GetMaxResponseLength(), + len(payload)) + } + + //partition the payload + parts := partitionResponse(payload, r.net.GetMaxMessageLength(), r.maxParts) + + //encrypt and send the partitions + cyphers := makeCyphers(r.dhKey, uint8(len(parts))) + rounds := make([]id.Round, len(parts)) + sendResults := make(chan ds.EventReturn, len(parts)) + + wg := sync.WaitGroup{} + wg.Add(len(parts)) + + if cmixParams.DebugTag != cmix.DefaultDebugTag { + cmixParams.DebugTag = "single.Response" + } + + svc := cmixMsg.Service{ + Identifier: r.dhKey.Bytes(), + Tag: "single.response-dummyservice", + Metadata: nil, + } + + for i := 0; i < len(parts); i++ { + // fixme: handle the case where a send fails, also on failures, + // unset use + go func(j int) { + defer wg.Done() + partFP, ecrPart, mac := cyphers[j].Encrypt(parts[j]) + // Send Message + round, ephID, err := r.net.Send(r.sender, partFP, svc, ecrPart, mac, + cmixParams) + if err != nil { + jww.ERROR.Printf("Failed to send single-use response CMIX "+ + "message part %d: %+v", j, err) + } + jww.DEBUG.Printf("Sending single-use response CMIX message part "+ + "%d on round %d to address ID %d.", j, round, ephID.Int64()) + rounds[j] = round + + r.net.GetInstance().GetRoundEvents().AddRoundEventChan(round, sendResults, + timeout, states.COMPLETED, states.FAILED) + }(i) + } + + // Wait for all go routines to finish + wg.Wait() + jww.DEBUG.Printf("Sent %d single-use response CMIX messages to %s.", + len(parts), r.sender) + + // Count the number of rounds + roundMap := map[id.Round]struct{}{} + for _, roundID := range rounds { + roundMap[roundID] = struct{}{} + } + + // Wait until the result tracking responds + success, numRoundFail, numTimeOut := cmix.TrackResults(sendResults, len(roundMap)) + if !success { + + return nil, errors.Errorf("tracking results of %d rounds: %d round "+ + "failures, %d round event time outs; the send cannot be retried.", + len(rounds), numRoundFail, numTimeOut) + } + + jww.DEBUG.Printf("Tracked %d single-use response message round(s).", len(roundMap)) + + return rounds, nil +} + +// partitionResponse breaks a payload into its sub payloads for sending +func partitionResponse(payload []byte, cmixMessageLength int, maxParts uint8) []message.ResponsePart { + responseMsg := message.NewResponsePart(cmixMessageLength) + + // Split payloads + payloadParts := splitPayload(payload, responseMsg.GetMaxContentsSize(), + int(maxParts)) + + // Create messages + parts := make([]message.ResponsePart, len(payloadParts)) + for i := range payloadParts { + nrp := message.NewResponsePart(cmixMessageLength) + nrp.SetPartNum(uint8(i)) + nrp.SetContents(payloadParts[i]) + nrp.SetNumParts(uint8(len(payloadParts))) + parts[i] = nrp + } + + return parts +} + +// splitPayload splits the given payload into separate payload parts and returns +// them in a slice. Each part's size is less than or equal to maxSize. Any extra +// data in the payload is not used if it is longer than the maximum capacity. +func splitPayload(payload []byte, maxSize, maxParts int) [][]byte { + var parts [][]byte + buff := bytes.NewBuffer(payload) + + for i := 0; i < maxParts && buff.Len() > 0; i++ { + parts = append(parts, buff.Next(maxSize)) + } + return parts +} diff --git a/single/reception.go b/single/reception.go deleted file mode 100644 index 01faada1fa07ada31f779ec25f2eecc973f95f9b..0000000000000000000000000000000000000000 --- a/single/reception.go +++ /dev/null @@ -1,121 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/stoppable" - cAuth "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" -) - -// receiveTransmissionHandler waits to receive single-use transmissions. When -// a message is received, its is returned via its registered callback. -func (m *Manager) receiveTransmissionHandler(rawMessages chan message.Receive, - stop *stoppable.Single) { - fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey()) - jww.DEBUG.Print("Waiting to receive single-use transmission messages.") - for { - select { - case <-stop.Quit(): - jww.DEBUG.Printf("Stopping waiting to receive single-use " + - "transmission message.") - stop.ToStopped() - return - case msg := <-rawMessages: - jww.TRACE.Printf("Received CMIX message; checking if it is a " + - "single-use transmission.") - go func(m *Manager, msg message.Receive) { - // Check if message is a single-use transmit message - cmixMsg, err := format.Unmarshal(msg.Payload) - if err != nil { - jww.ERROR.Printf("Could not unmarshal msg: %s", - err.Error()) - return - } - if fp != cmixMsg.GetKeyFP() { - // If the verification fails, then ignore the message as it is - // likely garbled or for a different protocol - jww.TRACE.Print("Failed to read single-use CMIX message: " + - "fingerprint verification failed.") - return - } - - // Denote that the message is not garbled - jww.DEBUG.Printf("Received single-use transmission message.") - m.store.GetGarbledMessages().Remove(cmixMsg) - - // Handle message - payload, c, err := m.processTransmission(cmixMsg, fp) - if err != nil { - jww.WARN.Printf("Failed to read single-use CMIX message: %+v", - err) - return - } - jww.DEBUG.Printf("Successfully processed single-use transmission message.") - - // Lookup the registered callback for the message's tag fingerprint - callback, err := m.callbackMap.getCallback(c.tagFP) - if err != nil { - jww.WARN.Printf("Failed to find module to pass single-use "+ - "payload: %+v", err) - return - } - - jww.DEBUG.Printf("Calling single-use callback with tag "+ - "fingerprint %s.", c.tagFP) - - callback(payload, c) - }(m, msg) - } - } -} - -// processTransmission unmarshalls and decrypts the message payload and -// returns the decrypted payload and the contact information for the sender. -func (m *Manager) processTransmission(msg format.Message, - fp format.Fingerprint) ([]byte, Contact, error) { - grp := m.store.E2e().GetGroup() - dhPrivKey := m.store.E2e().GetDHPrivateKey() - - // Unmarshal the CMIX message contents to a transmission message - transmitMsg, err := unmarshalTransmitMessage(msg.GetContents(), - grp.GetP().ByteLen()) - if err != nil { - return nil, Contact{}, errors.Errorf("failed to unmarshal contents: %+v", err) - } - - // Generate DH key and symmetric key - dhKey := grp.Exp(transmitMsg.GetPubKey(grp), dhPrivKey, grp.NewInt(1)) - key := singleUse.NewTransmitKey(dhKey) - - // Verify the MAC - if !singleUse.VerifyMAC(key, transmitMsg.GetPayload(), msg.GetMac()) { - return nil, Contact{}, errors.New("failed to verify MAC.") - } - - // Decrypt the transmission message payload - decryptedPayload := cAuth.Crypt(key, fp[:24], transmitMsg.GetPayload()) - - // Unmarshal the decrypted payload to a transmission message payload - payload, err := unmarshalTransmitMessagePayload(decryptedPayload) - if err != nil { - return nil, Contact{}, errors.Errorf("failed to unmarshal payload: %+v", err) - } - - c := NewContact(payload.GetRID(transmitMsg.GetPubKey(grp)), - transmitMsg.GetPubKey(grp), dhKey, payload.GetTagFP(), payload.GetMaxParts()) - - jww.INFO.Printf("Generated by singe use receiver reception id for single use. EphId %s, PubKey: %x", - c.partner, transmitMsg.GetPubKey(grp).Bytes()) - - return payload.GetContents(), c, nil -} diff --git a/single/reception_test.go b/single/reception_test.go deleted file mode 100644 index efa83406dbb27791d2a209430043520b4a30c4c3..0000000000000000000000000000000000000000 --- a/single/reception_test.go +++ /dev/null @@ -1,260 +0,0 @@ -package single - -import ( - "bytes" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/stoppable" - contact2 "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/netTime" - "math/rand" - "testing" - "time" -) - -// Happy path. -func TestManager_receiveTransmissionHandler(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReceiveComm() - - msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32, - 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create tranmission CMIX message: %+v", err) - } - - m.callbackMap.registerCallback(tag, callback) - - go m.receiveTransmissionHandler(rawMessages, stoppable.NewSingle("singleStoppable")) - rawMessages <- message.Receive{ - Payload: msg.Marshal(), - } - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - if !bytes.Equal(results.payload, payload) { - t.Errorf("Callback received wrong payload."+ - "\nexpected: %+v\nreceived: %+v", payload, results.payload) - } - case <-timer.C: - t.Errorf("Callback failed to be called.") - } -} - -// Happy path: quit channel. -func TestManager_receiveTransmissionHandler_QuitChan(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - stop := stoppable.NewSingle("singleStoppable") - tag := "Test tag" - payload := make([]byte, 132) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReceiveComm() - - m.callbackMap.registerCallback(tag, callback) - - go m.receiveTransmissionHandler(rawMessages, stop) - - if err := stop.Close(); err != nil { - t.Errorf("Failed to signal close to process: %+v", err) - } - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when the message should not have been processed."+ - "\npayload: %+v\ncontact: %+v", results.payload, results.c) - case <-timer.C: - } -} - -// Error path: CMIX message fingerprint does not match. -func TestManager_receiveTransmissionHandler_FingerPrintError(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - stop := stoppable.NewSingle("singleStoppable") - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(42), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReceiveComm() - - msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32, - 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create tranmission CMIX message: %+v", err) - } - - m.callbackMap.registerCallback(tag, callback) - - go m.receiveTransmissionHandler(rawMessages, stop) - rawMessages <- message.Receive{ - Payload: msg.Marshal(), - } - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when the fingerprints do not match."+ - "\npayload: %+v\ncontact: %+v", results.payload, results.c) - case <-timer.C: - } -} - -// Error path: cannot process transmission message. -func TestManager_receiveTransmissionHandler_ProcessMessageError(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - stop := stoppable.NewSingle("singleStoppable") - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReceiveComm() - - msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32, - 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create tranmission CMIX message: %+v", err) - } - - msg.SetMac(make([]byte, format.MacLen)) - - m.callbackMap.registerCallback(tag, callback) - - go m.receiveTransmissionHandler(rawMessages, stop) - rawMessages <- message.Receive{ - Payload: msg.Marshal(), - } - - timer := time.NewTimer(50 * time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when the message should not have been processed."+ - "\npayload: %+v\ncontact: %+v", results.payload, results.c) - case <-timer.C: - } -} - -// Error path: tag fingerprint does not match. -func TestManager_receiveTransmissionHandler_TagFpError(t *testing.T) { - m := newTestManager(0, false, t) - rawMessages := make(chan message.Receive, rawMessageBuffSize) - stop := stoppable.NewSingle("singleStoppable") - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - - msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32, - 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create tranmission CMIX message: %+v", err) - } - - go m.receiveTransmissionHandler(rawMessages, stop) - rawMessages <- message.Receive{ - Payload: msg.Marshal(), - } -} - -// Happy path. -func TestManager_processTransmission(t *testing.T) { - m := newTestManager(0, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("partnerID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - tag := "test tag" - payload := []byte("This is the payload.") - maxMsgs := uint8(6) - cmixMsg, dhKey, rid, _, err := m.makeTransmitCmixMessage(partner, payload, - tag, maxMsgs, 32, 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to generate expected CMIX message: %+v", err) - } - - tMsg, err := unmarshalTransmitMessage(cmixMsg.GetContents(), m.store.E2e().GetGroup().GetP().ByteLen()) - if err != nil { - t.Fatalf("Failed to make transmitMessage: %+v", err) - } - - expectedC := NewContact(rid, tMsg.GetPubKey(m.store.E2e().GetGroup()), - dhKey, singleUse.NewTagFP(tag), maxMsgs) - - fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey()) - content, testC, err := m.processTransmission(cmixMsg, fp) - if err != nil { - t.Errorf("processTransmission() produced an error: %+v", err) - } - - if !expectedC.Equal(testC) { - t.Errorf("processTransmission() did not return the expected values."+ - "\nexpected: %+v\nrecieved: %+v", expectedC, testC) - } - - if !bytes.Equal(payload, content) { - t.Errorf("processTransmission() returned the wrong payload."+ - "\nexpected: %+v\nreceived: %+v", payload, content) - } -} - -// Error path: fails to unmarshal transmitMessage. -func TestManager_processTransmission_TransmitMessageUnmarshalError(t *testing.T) { - m := newTestManager(0, false, t) - cmixMsg := format.NewMessage(format.MinimumPrimeSize) - - fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey()) - _, _, err := m.processTransmission(cmixMsg, fp) - if !check(err, "failed to unmarshal contents") { - t.Errorf("processTransmission() did not produce an error when "+ - "the transmitMessage failed to unmarshal: %+v", err) - } -} - -// Error path: MAC fails to verify. -func TestManager_processTransmission_MacVerifyError(t *testing.T) { - m := newTestManager(0, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("partnerID", id.User, t), - DhPubKey: m.store.E2e().GetDHPublicKey(), - } - cmixMsg, _, _, _, err := m.makeTransmitCmixMessage(partner, []byte{}, "", 6, - 32, 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to generate expected CMIX message: %+v", err) - } - - cmixMsg.SetMac(make([]byte, 32)) - - fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey()) - _, _, err = m.processTransmission(cmixMsg, fp) - if !check(err, "failed to verify MAC") { - t.Errorf("processTransmission() did not produce an error when "+ - "the MAC failed to verify: %+v", err) - } -} diff --git a/single/request.go b/single/request.go new file mode 100644 index 0000000000000000000000000000000000000000..c464eec421366c72a44e0c462900458d2e21bdf3 --- /dev/null +++ b/single/request.go @@ -0,0 +1,209 @@ +package single + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + cmixMsg "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/e2e/auth" + "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/netTime" + "io" + "sync" + "time" +) + +type Response interface { + Callback(payload []byte, receptionID receptionID.EphemeralIdentity, + round rounds.Round, err error) +} + +type RequestParams struct { + Timeout time.Duration + MaxMessages uint8 + CmixParam cmix.CMIXParams +} + +func TransmitRequest(recipient contact.Contact, tag string, payload []byte, + callback Response, param RequestParams, net cmix.Client, rng csprng.Source, + e2eGrp *cyclic.Group) (id.Round, receptionID.EphemeralIdentity, error) { + // get address ID address space size; this blocks until the address space + // size is set for the first time + addressSize := net.GetAddressSpace() + timeStart := netTime.Now() + + // Generate DH key and public key + dhKey, publicKey, err := generateDhKeys(e2eGrp, recipient.DhPubKey, rng) + if err != nil { + return 0, receptionID.EphemeralIdentity{}, err + } + + //build the message payload + request := message.NewRequest(net.GetMaxMessageLength(), + e2eGrp.GetP().ByteLen()) + requestPayload := message.NewRequestPayload(request.GetPayloadSize(), + payload, param.MaxMessages) + + // Generate new user ID and address ID + var sendingID receptionID.EphemeralIdentity + requestPayload, sendingID, err = makeIDs(requestPayload, publicKey, addressSize, param.Timeout, + timeStart, rng) + if err != nil { + return 0, receptionID.EphemeralIdentity{}, errors.Errorf("failed to generate IDs: %+v", err) + } + + // Encrypt payload + fp := singleUse.NewTransmitFingerprint(recipient.DhPubKey) + key := singleUse.NewTransmitKey(dhKey) + encryptedPayload := auth.Crypt(key, fp[:24], requestPayload.Marshal()) + + // Generate CMIX message MAC + mac := singleUse.MakeMAC(key, encryptedPayload) + + //assemble the payload + request.SetPubKey(publicKey) + request.SetPayload(encryptedPayload) + + //register the response pickup + collator := message.NewCollator(param.MaxMessages) + timeoutKillChan := make(chan bool) + callbackOnce := sync.Once{} + wrapper := func(payload []byte, receptionID receptionID.EphemeralIdentity, + round rounds.Round, err error) { + select { + case timeoutKillChan <- true: + default: + } + callbackOnce.Do(func() { + net.DeleteClientFingerprints(sendingID.Source) + go callback.Callback(payload, receptionID, round, err) + }) + } + + cyphers := makeCyphers(dhKey, param.MaxMessages) + + for i := uint8(0); i < param.MaxMessages; i++ { + processor := responceProcessor{ + sendingID: sendingID, + c: collator, + callback: wrapper, + cy: cyphers[i], + tag: tag, + recipient: &recipient, + } + + if err = net.AddFingerprint(sendingID.Source, processor.cy.GetFingerprint(), + &processor); err != nil { + return 0, receptionID.EphemeralIdentity{}, + errors.Errorf("failed to add fingerprint %d IDs: %+v", i, err) + } + } + + net.AddIdentity(sendingID.Source, timeStart.Add(param.Timeout), false) + + //send the payload + svc := cmixMsg.Service{ + Identifier: recipient.ID[:], + Tag: tag, + Metadata: nil, + } + param.CmixParam.Timeout = param.Timeout + + rid, _, err := net.Send(recipient.ID, cmixMsg.RandomFingerprint(rng), svc, request.Marshal(), mac, + param.CmixParam) + + if err != nil { + return 0, receptionID.EphemeralIdentity{}, err + } + + remainingTimeout := param.Timeout - netTime.Since(timeStart) + go waitForTimeout(timeoutKillChan, wrapper, remainingTimeout) + + return rid, sendingID, nil +} + +// generateDhKeys generates a new public key and DH key. +func generateDhKeys(grp *cyclic.Group, dhPubKey *cyclic.Int, + rng io.Reader) (*cyclic.Int, *cyclic.Int, error) { + // Generate private key + privKeyBytes, err := csprng.GenerateInGroup(grp.GetP().Bytes(), + grp.GetP().ByteLen(), rng) + if err != nil { + return nil, nil, errors.Errorf("failed to generate key in group: %+v", + err) + } + privKey := grp.NewIntFromBytes(privKeyBytes) + + // Generate public key and DH key + publicKey := grp.ExpG(privKey, grp.NewInt(1)) + dhKey := grp.Exp(dhPubKey, privKey, grp.NewInt(1)) + return dhKey, publicKey, nil +} + +// makeIDs generates a new user ID and address ID with a start and end within +// the given timout. The ID is generated from the unencrypted msg payload, which +// contains a nonce. If the generated address ID has a window that is not +// within +/- the given 2*Timeout from now, then the IDs are generated again +// using a new nonce. +func makeIDs(msg message.RequestPayload, publicKey *cyclic.Int, + addressSize uint8, timeout time.Duration, timeNow time.Time, + rng io.Reader) (message.RequestPayload, receptionID.EphemeralIdentity, error) { + var rid *id.ID + var ephID ephemeral.Id + + // Generate acceptable window for the address ID to exist in + windowStart, windowEnd := timeNow.Add(-2*timeout), timeNow.Add(2*timeout) + start, end := timeNow, timeNow + + // Loop until the address ID's start and end are within bounds + for windowStart.Before(start) || windowEnd.After(end) { + // Generate new nonce + err := msg.SetNonce(rng) + if err != nil { + return message.RequestPayload{}, receptionID.EphemeralIdentity{}, + errors.Errorf("failed to generate nonce: %+v", err) + } + + // Generate ID from unencrypted payload + rid = msg.GetRID(publicKey) + + // Generate the address ID + ephID, start, end, err = ephemeral.GetId(rid, uint(addressSize), + timeNow.UnixNano()) + if err != nil { + return message.RequestPayload{}, receptionID.EphemeralIdentity{}, + errors.Errorf("failed to generate "+ + "address ID from newly generated ID: %+v", err) + } + jww.DEBUG.Printf("address.GetId(%s, %d, %d) = %d", rid, + addressSize, timeNow.UnixNano(), ephID.Int64()) + } + + jww.INFO.Printf("generated by singe use sender reception id for single use: %s, "+ + "ephId: %d, pubkey: %x, msg: %s", rid, ephID.Int64(), publicKey.Bytes(), msg) + + return msg, receptionID.EphemeralIdentity{ + EphId: ephID, + Source: rid, + }, nil +} + +func waitForTimeout(kill chan bool, callback callbackWrapper, timeout time.Duration) { + timer := time.NewTimer(timeout) + select { + case <-kill: + return + case <-timer.C: + err := errors.Errorf("waiting for response to single-use transmission "+ + "timed out after %s.", timeout) + callback(nil, receptionID.EphemeralIdentity{}, rounds.Round{}, err) + } +} diff --git a/single/response.go b/single/response.go deleted file mode 100644 index 9856a919032592dc2a6074794059140fe779387c..0000000000000000000000000000000000000000 --- a/single/response.go +++ /dev/null @@ -1,191 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "bytes" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/cmix" - "gitlab.com/elixxir/client/interfaces/params" - ds "gitlab.com/elixxir/comms/network/dataStructures" - cAuth "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" - "gitlab.com/xx_network/primitives/id" - "sync" - "sync/atomic" - "time" -) - -// GetMaxResponsePayloadSize returns the maximum payload size for a response -// message. -func (m *Manager) GetMaxResponsePayloadSize() int { - // Generate empty messages to determine the available space for the payload - cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - responseMsg := newResponseMessagePart(cmixMsg.ContentsSize()) - - return responseMsg.GetMaxContentsSize() -} - -// RespondSingleUse creates the single-use response messages with the given -// payload and sends them to the given partner. -func (m *Manager) RespondSingleUse(partner Contact, payload []byte, - timeout time.Duration) error { - return m.respondSingleUse(partner, payload, timeout, - m.net.GetInstance().GetRoundEvents()) -} - -// respondSingleUse allows for easier testing. -func (m *Manager) respondSingleUse(partner Contact, payload []byte, - timeout time.Duration, roundEvents roundEvents) error { - // Ensure that only a single reply can be sent in response - firstUse := atomic.CompareAndSwapInt32(partner.used, 0, 1) - if !firstUse { - return errors.Errorf("cannot send to single-use contact that has " + - "already been sent to.") - } - - // Generate messages from payload - msgs, err := m.makeReplyCmixMessages(partner, payload) - if err != nil { - return errors.Errorf("failed to create new CMIX messages: %+v", err) - } - - jww.DEBUG.Printf("Created %d single-use response CMIX message parts.", len(msgs)) - - // Tracks the numbers of rounds that messages are sent on - rounds := make([]id.Round, len(msgs)) - - sendResults := make(chan ds.EventReturn, len(msgs)) - - // Send CMIX messages - wg := sync.WaitGroup{} - for i, cmixMsg := range msgs { - wg.Add(1) - cmixMsgFunc := cmixMsg - j := i - go func() { - defer wg.Done() - // Send Message - p := params.GetDefaultCMIX() - p.DebugTag = "single.Response" - round, ephID, err := m.net.SendCMIX(cmixMsgFunc, partner.partner, p) - if err != nil { - jww.ERROR.Printf("Failed to send single-use response CMIX "+ - "message part %d: %+v", j, err) - } - jww.DEBUG.Printf("Sending single-use response CMIX message part "+ - "%d on round %d to address ID %d.", j, round, ephID.Int64()) - rounds[j] = round - - roundEvents.AddRoundEventChan(round, sendResults, timeout, - states.COMPLETED, states.FAILED) - }() - } - - // Wait for all go routines to finish - wg.Wait() - jww.DEBUG.Printf("Sent %d single-use response CMIX messages to %s.", len(msgs), partner.partner) - - // Count the number of rounds - roundMap := map[id.Round]struct{}{} - for _, roundID := range rounds { - roundMap[roundID] = struct{}{} - } - - // Wait until the result tracking responds - success, numRoundFail, numTimeOut := cmix.TrackResults(sendResults, len(roundMap)) - if !success { - return errors.Errorf("tracking results of %d rounds: %d round "+ - "failures, %d round event time outs; the send cannot be retried.", - len(rounds), numRoundFail, numTimeOut) - } - jww.DEBUG.Printf("Tracked %d single-use response message round(s).", len(roundMap)) - - return nil -} - -// makeReplyCmixMessages -func (m *Manager) makeReplyCmixMessages(partner Contact, payload []byte) ([]format.Message, error) { - // Generate internal payloads based off key size to determine if the passed - // in payload is too large to fit in the available contents - cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - responseMsg := newResponseMessagePart(cmixMsg.ContentsSize()) - - // Maximum payload size is the maximum amount of room in each message - // multiplied by the number of messages - maxPayloadSize := responseMsg.GetMaxContentsSize() * int(partner.GetMaxParts()) - - if maxPayloadSize < len(payload) { - return nil, errors.Errorf("length of provided payload (%d) too long "+ - "for message payload capacity (%d = %d byte payload * %d messages).", - len(payload), maxPayloadSize, responseMsg.GetMaxContentsSize(), - partner.GetMaxParts()) - } - - // Split payloads - payloadParts := m.splitPayload(payload, responseMsg.GetMaxContentsSize(), - int(partner.GetMaxParts())) - - // Create CMIX messages - cmixMsgs := make([]format.Message, len(payloadParts)) - wg := sync.WaitGroup{} - for i, contents := range payloadParts { - wg.Add(1) - go func(partner Contact, contents []byte, i uint8) { - defer wg.Done() - cmixMsgs[i] = m.makeMessagePart(partner, contents, uint8(len(payloadParts)), i) - }(partner, contents, uint8(i)) - } - - // Wait for all go routines to finish - wg.Wait() - - return cmixMsgs, nil -} - -// makeMessagePart generates a CMIX message containing a responseMessagePart. -func (m *Manager) makeMessagePart(partner Contact, contents []byte, maxPart, i uint8) format.Message { - cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - responseMsg := newResponseMessagePart(cmixMsg.ContentsSize()) - - // Compose response message - responseMsg.SetMaxParts(maxPart) - responseMsg.SetPartNum(i) - responseMsg.SetContents(contents) - - // Encrypt payload - fp := singleUse.NewResponseFingerprint(partner.dhKey, uint64(i)) - key := singleUse.NewResponseKey(partner.dhKey, uint64(i)) - encryptedPayload := cAuth.Crypt(key, fp[:24], responseMsg.Marshal()) - - // Generate CMIX message MAC - mac := singleUse.MakeMAC(key, encryptedPayload) - - // Compose CMIX message contents - cmixMsg.SetContents(encryptedPayload) - cmixMsg.SetKeyFP(fp) - cmixMsg.SetMac(mac) - - return cmixMsg -} - -// splitPayload splits the given payload into separate payload parts and returns -// them in a slice. Each part's size is less than or equal to maxSize. Any extra -// data in the payload is not used if it is longer than the maximum capacity. -func (m *Manager) splitPayload(payload []byte, maxSize, maxParts int) [][]byte { - var parts [][]byte - buff := bytes.NewBuffer(payload) - - for i := 0; i < maxParts && buff.Len() > 0; i++ { - parts = append(parts, buff.Next(maxSize)) - } - return parts -} diff --git a/single/responseProcessor.go b/single/responseProcessor.go new file mode 100644 index 0000000000000000000000000000000000000000..9eb56ef2bf4ddb7ce14409962129a3f4fdf64188 --- /dev/null +++ b/single/responseProcessor.go @@ -0,0 +1,46 @@ +package single + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/primitives/format" +) + +type callbackWrapper func(payload []byte, + receptionID receptionID.EphemeralIdentity, round rounds.Round, err error) + +//by fingerprint +type responceProcessor struct { + sendingID receptionID.EphemeralIdentity + c *message.Collator + callback callbackWrapper + cy cypher + tag string + recipient *contact.Contact +} + +func (rsp *responceProcessor) Process(ecrMsg format.Message, + receptionID receptionID.EphemeralIdentity, + round rounds.Round) { + + decrypted, err := rsp.cy.Decrypt(ecrMsg.GetContents(), ecrMsg.GetMac()) + if err != nil { + jww.ERROR.Printf("Failed to decrypt payload for %s to %s, "+ + "single use may fail: %+v", rsp.tag, rsp.recipient.ID, err) + return + } + + payload, done, err := rsp.c.Collate(decrypted) + if err != nil { + jww.ERROR.Printf("Failed to collage payload for %s to %s, "+ + "single use may fail: %+v", rsp.tag, rsp.recipient.ID, err) + return + } + + if done { + rsp.callback(payload, receptionID, round, nil) + } +} diff --git a/single/response_test.go b/single/response_test.go deleted file mode 100644 index fcacf19a51bb2c15bcdb961b53665e67248837da..0000000000000000000000000000000000000000 --- a/single/response_test.go +++ /dev/null @@ -1,260 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "bytes" - cAuth "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" - "math/rand" - "reflect" - "testing" - "time" -) - -// Happy path. -func TestManager_GetMaxResponsePayloadSize(t *testing.T) { - m := newTestManager(0, false, t) - cmixPrimeSize := m.store.Cmix().GetGroup().GetP().ByteLen() - expectedSize := 2*cmixPrimeSize - format.KeyFPLen - format.MacLen - format.RecipientIDLen - responseMinSize - 1 - testSize := m.GetMaxResponsePayloadSize() - - if expectedSize != testSize { - t.Errorf("GetMaxResponsePayloadSize() failed to return the expected size."+ - "\nexpected: %d\nreceived: %d", expectedSize, testSize) - } -} - -// Happy path. -func TestManager_respondSingleUse(t *testing.T) { - m := newTestManager(0, false, t) - used := int32(0) - partner := Contact{ - partner: id.NewIdFromString("partner ID:", id.User, t), - partnerPubKey: m.store.E2e().GetGroup().NewInt(42), - dhKey: m.store.E2e().GetGroup().NewInt(99), - tagFP: singleUse.NewTagFP("tag"), - maxParts: 10, - used: &used, - } - payload := make([]byte, 400*int(partner.maxParts)) - rand.New(rand.NewSource(42)).Read(payload) - re := newTestRoundEvents(false) - - expectedMsgs, err := m.makeReplyCmixMessages(partner, payload) - if err != nil { - t.Fatalf("Failed to created expected messages: %+v", err) - } - - err = m.respondSingleUse(partner, payload, 10*time.Millisecond, re) - if err != nil { - t.Errorf("respondSingleUse() produced an error: %+v", err) - } - - // Check that all messages are expected and received - if len(m.net.(*testNetworkManager).msgs) != int(partner.GetMaxParts()) { - t.Errorf("Recieved incorrect number of messages."+ - "\nexpected: %d\nreceived: %d", int(partner.GetMaxParts()), - len(m.net.(*testNetworkManager).msgs)) - } - - // Check that all received messages were expected - var exists bool - for _, received := range m.net.(*testNetworkManager).msgs { - exists = false - for _, msg := range expectedMsgs { - if reflect.DeepEqual(msg, received) { - exists = true - } - } - if !exists { - t.Errorf("Unexpected message: %+v", received) - } - } -} - -// Error path: response has already been sent. -func TestManager_respondSingleUse_ResponseUsedError(t *testing.T) { - m := newTestManager(0, false, t) - used := int32(1) - partner := Contact{ - partner: id.NewIdFromString("partner ID:", id.User, t), - partnerPubKey: m.store.E2e().GetGroup().NewInt(42), - dhKey: m.store.E2e().GetGroup().NewInt(99), - tagFP: singleUse.NewTagFP("tag"), - maxParts: 10, - used: &used, - } - - err := m.respondSingleUse(partner, []byte{}, 10*time.Millisecond, newTestRoundEvents(false)) - if !check(err, "cannot send to single-use contact that has already been sent to") { - t.Errorf("respondSingleUse() did not produce the expected error when "+ - "the contact has been used: %+v", err) - } -} - -// Error path: cannot create CMIX message when payload is too large. -func TestManager_respondSingleUse_MakeCmixMessageError(t *testing.T) { - m := newTestManager(0, false, t) - used := int32(0) - partner := Contact{ - partner: id.NewIdFromString("partner ID:", id.User, t), - partnerPubKey: m.store.E2e().GetGroup().NewInt(42), - dhKey: m.store.E2e().GetGroup().NewInt(99), - tagFP: singleUse.NewTagFP("tag"), - maxParts: 10, - used: &used, - } - payload := make([]byte, 500*int(partner.maxParts)) - rand.New(rand.NewSource(42)).Read(payload) - - err := m.respondSingleUse(partner, payload, 10*time.Millisecond, newTestRoundEvents(false)) - if !check(err, "failed to create new CMIX messages") { - t.Errorf("respondSingleUse() did not produce the expected error when "+ - "the CMIX message creation failed: %+v", err) - } -} - -// Error path: TrackResults returns an error. -func TestManager_respondSingleUse_TrackResultsError(t *testing.T) { - m := newTestManager(0, false, t) - used := int32(0) - partner := Contact{ - partner: id.NewIdFromString("partner ID:", id.User, t), - partnerPubKey: m.store.E2e().GetGroup().NewInt(42), - dhKey: m.store.E2e().GetGroup().NewInt(99), - tagFP: singleUse.NewTagFP("tag"), - maxParts: 10, - used: &used, - } - payload := make([]byte, 400*int(partner.maxParts)) - rand.New(rand.NewSource(42)).Read(payload) - - err := m.respondSingleUse(partner, payload, 10*time.Millisecond, newTestRoundEvents(true)) - if !check(err, "tracking results of") { - t.Errorf("respondSingleUse() did not produce the expected error when "+ - "the CMIX message creation failed: %+v", err) - } -} - -// Happy path. -func TestManager_makeReplyCmixMessages(t *testing.T) { - m := newTestManager(0, false, t) - partner := Contact{ - partner: id.NewIdFromString("partner ID:", id.User, t), - partnerPubKey: m.store.E2e().GetGroup().NewInt(42), - dhKey: m.store.E2e().GetGroup().NewInt(99), - tagFP: singleUse.NewTagFP("tag"), - maxParts: 255, - } - payload := make([]byte, 400*int(partner.maxParts)) - rand.New(rand.NewSource(42)).Read(payload) - - msgs, err := m.makeReplyCmixMessages(partner, payload) - if err != nil { - t.Errorf("makeReplyCmixMessages() returned an error: %+v", err) - } - - buff := bytes.NewBuffer(payload) - for i, msg := range msgs { - checkReplyCmixMessage(partner, - buff.Next(len(msgs[0].GetContents())-responseMinSize), msg, len(msgs), i, t) - } -} - -// Error path: size of payload too large. -func TestManager_makeReplyCmixMessages_PayloadSizeError(t *testing.T) { - m := newTestManager(0, false, t) - partner := Contact{ - partner: id.NewIdFromString("partner ID:", id.User, t), - partnerPubKey: m.store.E2e().GetGroup().NewInt(42), - dhKey: m.store.E2e().GetGroup().NewInt(99), - tagFP: singleUse.NewTagFP("tag"), - maxParts: 255, - } - payload := make([]byte, 500*int(partner.maxParts)) - rand.New(rand.NewSource(42)).Read(payload) - - _, err := m.makeReplyCmixMessages(partner, payload) - if err == nil { - t.Error("makeReplyCmixMessages() did not return an error when the " + - "payload is too large.") - } -} - -func checkReplyCmixMessage(c Contact, payload []byte, msg format.Message, maxParts, i int, t *testing.T) { - expectedFP := singleUse.NewResponseFingerprint(c.dhKey, uint64(i)) - key := singleUse.NewResponseKey(c.dhKey, uint64(i)) - expectedMac := singleUse.MakeMAC(key, msg.GetContents()) - - // Check CMIX message - if expectedFP != msg.GetKeyFP() { - t.Errorf("CMIX message #%d had incorrect fingerprint."+ - "\nexpected: %s\nrecieved: %s", i, expectedFP, msg.GetKeyFP()) - } - - if !singleUse.VerifyMAC(key, msg.GetContents(), msg.GetMac()) { - t.Errorf("CMIX message #%d had incorrect MAC."+ - "\nexpected: %+v\nrecieved: %+v", i, expectedMac, msg.GetMac()) - } - - // Decrypt payload - decryptedPayload := cAuth.Crypt(key, expectedFP[:24], msg.GetContents()) - responseMsg, err := unmarshalResponseMessage(decryptedPayload) - if err != nil { - t.Errorf("Failed to unmarshal pay load of CMIX message #%d: %+v", i, err) - } - - if !bytes.Equal(payload, responseMsg.GetContents()) { - t.Errorf("Response message #%d had incorrect contents."+ - "\nexpected: %+v\nrecieved: %+v", - i, payload, responseMsg.GetContents()) - } - - if uint8(maxParts) != responseMsg.GetMaxParts() { - t.Errorf("Response message #%d had incorrect max parts."+ - "\nexpected: %+v\nrecieved: %+v", - i, maxParts, responseMsg.GetMaxParts()) - } - - if i != int(responseMsg.GetPartNum()) { - t.Errorf("Response message #%d had incorrect part number."+ - "\nexpected: %+v\nrecieved: %+v", - i, i, responseMsg.GetPartNum()) - } -} - -// Happy path. -func TestManager_splitPayload(t *testing.T) { - m := newTestManager(0, false, t) - maxSize := 5 - maxParts := 10 - payload := []byte("0123456789012345678901234567890123456789012345678901234" + - "5678901234567890123456789012345678901234567890123456789") - expectedParts := [][]byte{ - payload[:maxSize], - payload[maxSize : 2*maxSize], - payload[2*maxSize : 3*maxSize], - payload[3*maxSize : 4*maxSize], - payload[4*maxSize : 5*maxSize], - payload[5*maxSize : 6*maxSize], - payload[6*maxSize : 7*maxSize], - payload[7*maxSize : 8*maxSize], - payload[8*maxSize : 9*maxSize], - payload[9*maxSize : 10*maxSize], - } - - testParts := m.splitPayload(payload, maxSize, maxParts) - - if !reflect.DeepEqual(expectedParts, testParts) { - t.Errorf("splitPayload() failed to correctly split the payload."+ - "\nexpected: %s\nreceived: %s", expectedParts, testParts) - } -} diff --git a/single/singleUseMap.go b/single/singleUseMap.go deleted file mode 100644 index 01e1cca5b6b5472dc75063cf79f20b25e3cb65da..0000000000000000000000000000000000000000 --- a/single/singleUseMap.go +++ /dev/null @@ -1,123 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/xx_network/primitives/id" - "sync" - "sync/atomic" - "time" -) - -// pending contains a map of all pending single-use states. -type pending struct { - singleUse map[id.ID]*state - sync.RWMutex -} - -type ReplyComm func(payload []byte, err error) - -// state contains the information and state of each single-use message that is -// being transmitted. -type state struct { - dhKey *cyclic.Int - fpMap *fingerprintMap // List of fingerprints for each response part - c *collator // Collects all response message parts - callback ReplyComm // Returns the error status of the communication - quitChan chan struct{} // Sending on channel kills the timeout handler -} - -// newPending creates a pending object with an empty map. -func newPending() *pending { - return &pending{ - singleUse: map[id.ID]*state{}, - } -} - -// newState generates a new state object with the fingerprint map and collator -// initialised. -func newState(dhKey *cyclic.Int, messageCount uint8, callback ReplyComm) *state { - return &state{ - dhKey: dhKey, - fpMap: newFingerprintMap(dhKey, uint64(messageCount)), - c: newCollator(uint64(messageCount)), - callback: callback, - quitChan: make(chan struct{}), - } -} - -// addState adds a new state to the map and starts a thread waiting for all the -// message parts or for the timeout to occur. -func (p *pending) addState(rid *id.ID, dhKey *cyclic.Int, maxMsgs uint8, - callback ReplyComm, timeout time.Duration) (chan struct{}, *int32, error) { - p.Lock() - - // Check if the state already exists - if _, exists := p.singleUse[*rid]; exists { - return nil, nil, errors.Errorf("a state already exists in the map with "+ - "the ID %s.", rid) - } - - jww.DEBUG.Printf("Successfully added single-use state with the ID %s to "+ - "the map.", rid) - - // Add the state - p.singleUse[*rid] = newState(dhKey, maxMsgs, callback) - quitChan := p.singleUse[*rid].quitChan - p.Unlock() - - // Create atomic which is set when the timeoutHandler thread is killed - quit := int32(0) - - go p.timeoutHandler(rid, callback, timeout, quitChan, &quit) - - return quitChan, &quit, nil -} - -// timeoutHandler waits for the signal to complete or times out and deletes the -// state. -func (p *pending) timeoutHandler(rid *id.ID, callback ReplyComm, - timeout time.Duration, quitChan chan struct{}, quit *int32) { - jww.DEBUG.Printf("Starting handler for sending single-use transmission "+ - "that will timeout after %s.", timeout) - - timer := time.NewTimer(timeout) - - // Signal on the atomic when this thread quits - defer func() { - atomic.StoreInt32(quit, 1) - }() - - select { - case <-quitChan: - jww.DEBUG.Print("Single-use transmission timeout handler quitting.") - return - case <-timer.C: - jww.WARN.Printf("Single-use transmission timeout handler timed out "+ - "after %s.", timeout) - - p.Lock() - if _, exists := p.singleUse[*rid]; !exists { - p.Unlock() - return - } - delete(p.singleUse, *rid) - - p.Unlock() - - err := errors.Errorf("waiting for response to single-use transmission "+ - "timed out after %s.", timeout.String()) - jww.DEBUG.Printf("Deleted single-use from map. Calling callback with "+ - "error: %+v", err) - - callback(nil, err) - } -} diff --git a/single/singleUseMap_test.go b/single/singleUseMap_test.go deleted file mode 100644 index 1f91c755f048fa2a2444109a521931b21cdc6867..0000000000000000000000000000000000000000 --- a/single/singleUseMap_test.go +++ /dev/null @@ -1,206 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "gitlab.com/xx_network/primitives/id" - "reflect" - "strings" - "sync/atomic" - "testing" - "time" -) - -// Happy path: trigger the exit channel. -func Test_pending_addState_ExitChan(t *testing.T) { - p := newPending() - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - maxMsgs := uint8(6) - timeout := 500 * time.Millisecond - callback, callbackChan := createReplyComm() - - quitChan, quit, err := p.addState(rid, dhKey, maxMsgs, callback, timeout) - if err != nil { - t.Errorf("addState() returned an error: %+v", err) - } - - hasQuit := atomic.CompareAndSwapInt32(quit, 0, 1) - if !hasQuit { - t.Error("Quit atomic called.") - } - - expectedState := newState(dhKey, maxMsgs, callback) - - quitChan <- struct{}{} - - timer := time.NewTimer(timeout + 1*time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when the quit channel was used."+ - "\npayload: %+v\nerror: %+v", results.payload, results.err) - case <-timer.C: - } - - state, exists := p.singleUse[*rid] - if !exists { - t.Error("State not found in map.") - } - if !equalState(*expectedState, *state, t) { - t.Errorf("State in map is incorrect.\nexpected: %+v\nreceived: %+v", - *expectedState, *state) - } - - hasQuit = atomic.CompareAndSwapInt32(quit, 0, 1) - if hasQuit { - t.Error("Quit atomic not called.") - } -} - -// Happy path: state is removed before deletion can occur. -func Test_pending_addState_StateRemoved(t *testing.T) { - p := newPending() - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - maxMsgs := uint8(6) - timeout := 5 * time.Millisecond - callback, callbackChan := createReplyComm() - - _, _, err := p.addState(rid, dhKey, maxMsgs, callback, timeout) - if err != nil { - t.Errorf("addState() returned an error: %+v", err) - } - - p.Lock() - delete(p.singleUse, *rid) - p.Unlock() - - timer := time.NewTimer(timeout + 1*time.Millisecond) - - select { - case results := <-callbackChan: - t.Errorf("Callback should not have been called.\npayload: %+v\nerror: %+v", - results.payload, results.err) - case <-timer.C: - } -} - -// Error path: timeout occurs and deletes the entry from the map. -func Test_pending_addState_TimeoutError(t *testing.T) { - p := newPending() - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - maxMsgs := uint8(6) - timeout := 5 * time.Millisecond - callback, callbackChan := createReplyComm() - - _, _, err := p.addState(rid, dhKey, maxMsgs, callback, timeout) - if err != nil { - t.Errorf("addState() returned an error: %+v", err) - } - - expectedState := newState(dhKey, maxMsgs, callback) - p.Lock() - state, exists := p.singleUse[*rid] - p.Unlock() - if !exists { - t.Error("State not found in map.") - } - if !equalState(*expectedState, *state, t) { - t.Errorf("State in map is incorrect.\nexpected: %+v\nreceived: %+v", - *expectedState, *state) - } - - timerTimeout := timeout * 4 - - select { - case results := <-callbackChan: - state, exists = p.singleUse[*rid] - if exists { - t.Errorf("State found in map when it should have been deleted."+ - "\nstate: %+v", state) - } - if results.payload != nil { - t.Errorf("Payload not nil on timeout.\npayload: %+v", results.payload) - } - if results.err == nil || !strings.Contains(results.err.Error(), "timed out") { - t.Errorf("Callback did not return a time out error on return: %+v", results.err) - } - case <-time.NewTimer(timerTimeout).C: - t.Errorf("Failed to time out after %s.", timerTimeout) - } -} - -// Error path: state already exists. -func Test_pending_addState_StateExistsError(t *testing.T) { - p := newPending() - rid := id.NewIdFromString("test RID", id.User, t) - dhKey := getGroup().NewInt(5) - maxMsgs := uint8(6) - timeout := 5 * time.Millisecond - callback, _ := createReplyComm() - - quitChan, _, err := p.addState(rid, dhKey, maxMsgs, callback, timeout) - if err != nil { - t.Errorf("addState() returned an error: %+v", err) - } - quitChan <- struct{}{} - - quitChan, _, err = p.addState(rid, dhKey, maxMsgs, callback, timeout) - if !check(err, "a state already exists in the map") { - t.Errorf("addState() did not return an error when the state already "+ - "exists: %+v", err) - } -} - -type replyCommData struct { - payload []byte - err error -} - -func createReplyComm() (func(payload []byte, err error), chan replyCommData) { - callbackChan := make(chan replyCommData) - callback := func(payload []byte, err error) { - callbackChan <- replyCommData{ - payload: payload, - err: err, - } - } - return callback, callbackChan -} - -// equalState determines if the two states have equal values. -func equalState(a, b state, t *testing.T) bool { - if a.dhKey.Cmp(b.dhKey) != 0 { - t.Errorf("DH Keys differ.\nexpected: %s\nreceived: %s", - a.dhKey.Text(10), b.dhKey.Text(10)) - return false - } - if !reflect.DeepEqual(a.fpMap.fps, b.fpMap.fps) { - t.Errorf("Fingerprint maps differ.\nexpected: %+v\nreceived: %+v", - a.fpMap.fps, b.fpMap.fps) - return false - } - if !reflect.DeepEqual(b.c, b.c) { - t.Errorf("collators differ.\nexpected: %+v\nreceived: %+v", - a.c, b.c) - return false - } - if reflect.ValueOf(a.callback).Pointer() != reflect.ValueOf(b.callback).Pointer() { - t.Errorf("callbackFuncs differ.\nexpected: %p\nreceived: %p", - a.callback, b.callback) - return false - } - return true -} - -// check returns true if the error is not nil and contains the substring. -func check(err error, subStr string) bool { - return err != nil && strings.Contains(err.Error(), subStr) -} diff --git a/single/transmission.go b/single/transmission.go deleted file mode 100644 index 715078cf4050ce0b5fd4dbd8874e1acac6be8b89..0000000000000000000000000000000000000000 --- a/single/transmission.go +++ /dev/null @@ -1,267 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package single - -import ( - "fmt" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - ephemeral2 "gitlab.com/elixxir/client/cmix/identity/receptionID" - "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/interfaces/params" - ds "gitlab.com/elixxir/comms/network/dataStructures" - contact2 "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" - "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "gitlab.com/xx_network/primitives/netTime" - "io" - "sync/atomic" - "time" -) - -// GetMaxTransmissionPayloadSize returns the maximum payload size for a -// transmission message. -func (m *Manager) GetMaxTransmissionPayloadSize() int { - // Generate empty messages to determine the available space for the payload - cmixPrimeSize := m.store.Cmix().GetGroup().GetP().ByteLen() - e2ePrimeSize := m.store.E2e().GetGroup().GetP().ByteLen() - cmixMsg := format.NewMessage(cmixPrimeSize) - transmitMsg := newTransmitMessage(cmixMsg.ContentsSize(), e2ePrimeSize) - msgPayload := newTransmitMessagePayload(transmitMsg.GetPayloadSize()) - - return msgPayload.GetMaxContentsSize() -} - -// TransmitSingleUse creates a CMIX message, sends it, and waits for delivery. -func (m *Manager) TransmitSingleUse(partner contact2.Contact, payload []byte, - tag string, maxMsgs uint8, callback ReplyComm, timeout time.Duration) error { - - rngReader := m.rng.GetStream() - defer rngReader.Close() - - return m.transmitSingleUse(partner, payload, tag, maxMsgs, rngReader, callback, timeout) -} - -// roundEvents interface allows custom round events to be passed in for testing. -type roundEvents interface { - AddRoundEventChan(id.Round, chan ds.EventReturn, time.Duration, - ...states.Round) *ds.EventCallback -} - -// transmitSingleUse has the fields passed in for easier testing. -func (m *Manager) transmitSingleUse(partner contact2.Contact, payload []byte, - tag string, MaxMsgs uint8, rng io.Reader, callback ReplyComm, timeout time.Duration) error { - - // get address ID address space size; this blocks until the address space - // size is set for the first time - addressSize := m.net.GetAddressSize() - timeStart := netTime.Now() - - // Create new CMIX message containing the transmission payload - cmixMsg, dhKey, rid, ephID, err := m.makeTransmitCmixMessage(partner, - payload, tag, MaxMsgs, addressSize, timeout, timeStart, rng) - if err != nil { - return errors.Errorf("failed to create new CMIX message: %+v", err) - } - - startValid := timeStart.Add(-2 * timeout) - endValid := timeStart.Add(2 * timeout) - jww.DEBUG.Printf("Created single-use transmission CMIX message with new ID "+ - "%s and EphID %d (Valid %s - %s)", rid, ephID.Int64(), startValid.String(), endValid.String()) - - // Add message state to map - quitChan, quit, err := m.p.addState(rid, dhKey, MaxMsgs, callback, timeout) - if err != nil { - return errors.Errorf("failed to add pending state: %+v", err) - } - - // Add identity for newly generated ID - err = m.reception.AddIdentity(ephemeral2.Identity{ - EphId: ephID, - Source: rid, - AddressSize: addressSize, - End: endValid, - ExtraChecks: interfaces.DefaultExtraChecks, - StartValid: startValid, - EndValid: endValid, - Ephemeral: true, - }) - if err != nil { - errorString := fmt.Sprintf("failed to add new identity to "+ - "reception storage for single-use: %+v", err) - jww.ERROR.Print(errorString) - - // Exit the state timeout handler, delete the state from map, and - // return an error on the callback - quitChan <- struct{}{} - m.p.Lock() - delete(m.p.singleUse, *rid) - m.p.Unlock() - go callback(nil, errors.New(errorString)) - } - - go func() { - // Send Message - jww.DEBUG.Printf("Sending single-use transmission CMIX "+ - "message to %s.", partner.ID) - p := params.GetDefaultCMIX() - p.DebugTag = "single.Transmit" - round, _, err := m.net.SendCMIX(cmixMsg, partner.ID, p) - if err != nil { - errorString := fmt.Sprintf("failed to send single-use transmission "+ - "CMIX message: %+v", err) - jww.ERROR.Printf(errorString) - - // Exit the state timeout handler, delete the state from map, and - // return an error on the callback - quitChan <- struct{}{} - m.p.Lock() - delete(m.p.singleUse, *rid) - m.p.Unlock() - go callback(nil, errors.New(errorString)) - } - - // Check if the state timeout handler has quit - if atomic.LoadInt32(quit) == 1 { - jww.ERROR.Print("Stopping to send single-use transmission CMIX " + - "message because the timeout handler quit.") - return - } - jww.DEBUG.Printf("Sent single-use transmission CMIX "+ - "message to %s and address ID %d on round %d.", - partner.ID, ephID.Int64(), round) - }() - - return nil -} - -// makeTransmitCmixMessage generates a CMIX message containing the transmission message, -// which contains the encrypted payload. -func (m *Manager) makeTransmitCmixMessage(partner contact2.Contact, - payload []byte, tag string, maxMsgs uint8, addressSize uint8, - timeout time.Duration, timeNow time.Time, rng io.Reader) (format.Message, - *cyclic.Int, *id.ID, ephemeral.Id, error) { - e2eGrp := m.store.E2e().GetGroup() - - // Generate internal payloads based off key size to determine if the passed - // in payload is too large to fit in the available contents - cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - transmitMsg := newTransmitMessage(cmixMsg.ContentsSize(), e2eGrp.GetP().ByteLen()) - msgPayload := newTransmitMessagePayload(transmitMsg.GetPayloadSize()) - - if msgPayload.GetMaxContentsSize() < len(payload) { - return format.Message{}, nil, nil, ephemeral.Id{}, - errors.Errorf("length of provided payload (%d) too long for message "+ - "payload capacity (%d).", len(payload), len(msgPayload.contents)) - } - - // Generate DH key and public key - dhKey, publicKey, err := generateDhKeys(e2eGrp, partner.DhPubKey, rng) - if err != nil { - return format.Message{}, nil, nil, ephemeral.Id{}, err - } - - // Compose payload - msgPayload.SetTagFP(singleUse.NewTagFP(tag)) - msgPayload.SetMaxParts(maxMsgs) - msgPayload.SetContents(payload) - - // Generate new user ID and address ID - rid, ephID, err := makeIDs(&msgPayload, publicKey, addressSize, timeout, - timeNow, rng) - if err != nil { - return format.Message{}, nil, nil, ephemeral.Id{}, - errors.Errorf("failed to generate IDs: %+v", err) - } - - // Encrypt payload - fp := singleUse.NewTransmitFingerprint(partner.DhPubKey) - key := singleUse.NewTransmitKey(dhKey) - encryptedPayload := auth.Crypt(key, fp[:24], msgPayload.Marshal()) - - // Generate CMIX message MAC - mac := singleUse.MakeMAC(key, encryptedPayload) - - // Compose transmission message - transmitMsg.SetPubKey(publicKey) - transmitMsg.SetPayload(encryptedPayload) - - // Compose CMIX message contents - cmixMsg.SetContents(transmitMsg.Marshal()) - cmixMsg.SetKeyFP(fp) - cmixMsg.SetMac(mac) - - return cmixMsg, dhKey, rid, ephID, nil -} - -// generateDhKeys generates a new public key and DH key. -func generateDhKeys(grp *cyclic.Group, dhPubKey *cyclic.Int, - rng io.Reader) (*cyclic.Int, *cyclic.Int, error) { - // Generate private key - privKeyBytes, err := csprng.GenerateInGroup(grp.GetP().Bytes(), - grp.GetP().ByteLen(), rng) - if err != nil { - return nil, nil, errors.Errorf("failed to generate key in group: %+v", - err) - } - privKey := grp.NewIntFromBytes(privKeyBytes) - - // Generate public key and DH key - publicKey := grp.ExpG(privKey, grp.NewInt(1)) - dhKey := grp.Exp(dhPubKey, privKey, grp.NewInt(1)) - - return dhKey, publicKey, nil -} - -// makeIDs generates a new user ID and address ID with a start and end within -// the given timout. The ID is generated from the unencrypted msg payload, which -// contains a nonce. If the generated address ID has a window that is not -// within +/- the given 2*timeout from now, then the IDs are generated again -// using a new nonce. -func makeIDs(msg *transmitMessagePayload, publicKey *cyclic.Int, - addressSize uint8, timeout time.Duration, timeNow time.Time, - rng io.Reader) (*id.ID, ephemeral.Id, error) { - var rid *id.ID - var ephID ephemeral.Id - - // Generate acceptable window for the address ID to exist in - windowStart, windowEnd := timeNow.Add(-2*timeout), timeNow.Add(2*timeout) - start, end := timeNow, timeNow - - // Loop until the address ID's start and end are within bounds - for windowStart.Before(start) || windowEnd.After(end) { - // Generate new nonce - err := msg.SetNonce(rng) - if err != nil { - return nil, ephemeral.Id{}, - errors.Errorf("failed to generate nonce: %+v", err) - } - - // Generate ID from unencrypted payload - rid = msg.GetRID(publicKey) - - // Generate the address ID - ephID, start, end, err = ephemeral.GetId(rid, uint(addressSize), timeNow.UnixNano()) - if err != nil { - return nil, ephemeral.Id{}, errors.Errorf("failed to generate "+ - "address ID from newly generated ID: %+v", err) - } - jww.DEBUG.Printf("address.GetId(%s, %d, %d) = %d", rid, addressSize, timeNow.UnixNano(), ephID.Int64()) - } - - jww.INFO.Printf("generated by singe use sender reception id for single use: %s, "+ - "ephId: %d, pubkey: %x, msg: %s", rid, ephID.Int64(), publicKey.Bytes(), msg) - - return rid, ephID, nil -} diff --git a/single/transmission_test.go b/single/transmission_test.go deleted file mode 100644 index 5fd81ec1bea27ace947d46468a2e913dd9c713ff..0000000000000000000000000000000000000000 --- a/single/transmission_test.go +++ /dev/null @@ -1,445 +0,0 @@ -package single - -import ( - "bytes" - pb "gitlab.com/elixxir/comms/mixmessages" - ds "gitlab.com/elixxir/comms/network/dataStructures" - contact2 "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e/auth" - "gitlab.com/elixxir/crypto/e2e/singleUse" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "gitlab.com/xx_network/primitives/netTime" - "math/rand" - "reflect" - "strings" - "sync" - "testing" - "time" -) - -// Happy path. -func TestManager_GetMaxTransmissionPayloadSize(t *testing.T) { - m := newTestManager(0, false, t) - cmixPrimeSize := m.store.Cmix().GetGroup().GetP().ByteLen() - e2ePrimeSize := m.store.E2e().GetGroup().GetP().ByteLen() - expectedSize := 2*cmixPrimeSize - e2ePrimeSize - format.KeyFPLen - format.MacLen - format.RecipientIDLen - transmitPlMinSize - transmitMessageVersionSize - 1 - testSize := m.GetMaxTransmissionPayloadSize() - - if expectedSize != testSize { - t.Errorf("GetMaxTransmissionPayloadSize() failed to return the expected size."+ - "\nexpected: %d\nreceived: %d", expectedSize, testSize) - } -} - -// Happy path. -func TestManager_transmitSingleUse(t *testing.T) { - m := newTestManager(0, false, t) - prng := rand.New(rand.NewSource(42)) - partner := contact2.Contact{ - ID: id.NewIdFromString("Contact ID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(5), - } - payload := make([]byte, 95) - rand.New(rand.NewSource(42)).Read(payload) - tag := "testTag" - maxMsgs := uint8(8) - callback, callbackChan := createReplyComm() - timeout := 15 * time.Millisecond - - err := m.transmitSingleUse(partner, payload, tag, maxMsgs, prng, callback, timeout) - if err != nil { - t.Errorf("transmitSingleUse() returned an error: %+v", err) - } - - for _, state := range m.p.singleUse { - state.quitChan <- struct{}{} - } - - expectedMsg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, - tag, maxMsgs, 32, 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to make expected message: %+v", err) - } - - if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).GetMsg(0)) { - t.Errorf("transmitSingleUse() failed to send the correct CMIX message."+ - "\nexpected: %+v\nreceived: %+v", - expectedMsg, m.net.(*testNetworkManager).GetMsg(0)) - } - - timer := time.NewTimer(timeout * 2) - - select { - case results := <-callbackChan: - t.Errorf("Callback called when the thread should have quit."+ - "\npayload: %+v\nerror: %+v", results.payload, results.err) - case <-timer.C: - } -} - -// Error path: function quits early if the timoutHandler quit. -func TestManager_transmitSingleUse_QuitChanError(t *testing.T) { - m := newTestManager(10*time.Millisecond, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("Contact ID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(5), - } - callback, callbackChan := createReplyComm() - timeout := 15 * time.Millisecond - - err := m.transmitSingleUse(partner, []byte{}, "testTag", 9, - rand.New(rand.NewSource(42)), callback, timeout) - if err != nil { - t.Errorf("transmitSingleUse() returned an error: %+v", err) - } - - for _, state := range m.p.singleUse { - state.quitChan <- struct{}{} - } - - timer := time.NewTimer(2 * timeout) - - select { - case results := <-callbackChan: - if results.payload != nil || results.err != nil { - t.Errorf("Callback called when the timeout thread should have quit."+ - "\npayload: %+v\nerror: %+v", results.payload, results.err) - } - case <-timer.C: - } -} - -// Error path: fails to add a new identity. -func TestManager_transmitSingleUse_AddIdentityError(t *testing.T) { - timeout := 15 * time.Millisecond - m := newTestManager(timeout, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("Contact ID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(5), - } - callback, callbackChan := createReplyComm() - - err := m.transmitSingleUse(partner, []byte{}, "testTag", 9, - rand.New(rand.NewSource(42)), callback, timeout) - if err != nil { - t.Errorf("transmitSingleUse() returned an error: %+v", err) - } - - for _, state := range m.p.singleUse { - state.quitChan <- struct{}{} - } - - timer := time.NewTimer(2 * timeout) - - select { - case results := <-callbackChan: - if results.payload != nil || !check(results.err, "Failed to add new identity") { - t.Errorf("Callback did not return the correct error when the "+ - "routine quit early.\npayload: %+v\nerror: %+v", - results.payload, results.err) - } - case <-timer.C: - } -} - -// Error path: Send fails to send message. -func TestManager_transmitSingleUse_SendCMIXError(t *testing.T) { - m := newTestManager(0, true, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("Contact ID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(5), - } - callback, callbackChan := createReplyComm() - timeout := 15 * time.Millisecond - - err := m.transmitSingleUse(partner, []byte{}, "testTag", 9, - rand.New(rand.NewSource(42)), callback, timeout) - if err != nil { - t.Errorf("transmitSingleUse() returned an error: %+v", err) - } - - timer := time.NewTimer(timeout * 2) - - select { - case results := <-callbackChan: - if results.payload != nil || !check(results.err, "failed to send single-use transmission CMIX message") { - t.Errorf("Callback did not return the correct error when the "+ - "routine quit early.\npayload: %+v\nerror: %+v", - results.payload, results.err) - } - case <-timer.C: - } -} - -// Error path: failed to create CMIX message because the payload is too large. -func TestManager_transmitSingleUse_MakeTransmitCmixMessageError(t *testing.T) { - m := newTestManager(0, false, t) - prng := rand.New(rand.NewSource(42)) - payload := make([]byte, m.store.Cmix().GetGroup().GetP().ByteLen()) - - err := m.transmitSingleUse(contact2.Contact{}, payload, "", 0, prng, nil, 0) - if err == nil { - t.Error("transmitSingleUse() did not return an error when the payload " + - "is too large.") - } -} - -// Error path: failed to add pending state because is already exists. -func TestManager_transmitSingleUse_AddStateError(t *testing.T) { - m := newTestManager(0, false, t) - partner := contact2.Contact{ - ID: id.NewIdFromString("Contact ID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(5), - } - payload := make([]byte, 95) - rand.New(rand.NewSource(42)).Read(payload) - tag := "testTag" - maxMsgs := uint8(8) - callback, _ := createReplyComm() - timeout := 15 * time.Millisecond - - // Create new CMIX and add a state - _, dhKey, rid, _, err := m.makeTransmitCmixMessage(partner, payload, tag, - maxMsgs, 32, 30*time.Second, netTime.Now(), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to create new CMIX message: %+v", err) - } - m.p.singleUse[*rid] = newState(dhKey, maxMsgs, nil) - - err = m.transmitSingleUse(partner, payload, tag, maxMsgs, - rand.New(rand.NewSource(42)), callback, timeout) - if !check(err, "failed to add pending state") { - t.Errorf("transmitSingleUse() failed to error when on adding state "+ - "when the state already exists: %+v", err) - } -} - -// Error path: timeout occurs on tracking results of round. -func TestManager_transmitSingleUse_RoundTimeoutError(t *testing.T) { - m := newTestManager(0, false, t) - prng := rand.New(rand.NewSource(42)) - partner := contact2.Contact{ - ID: id.NewIdFromString("Contact ID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(5), - } - payload := make([]byte, 95) - rand.New(rand.NewSource(42)).Read(payload) - callback, callbackChan := createReplyComm() - timeout := 15 * time.Millisecond - - err := m.transmitSingleUse(partner, payload, "testTag", 8, prng, callback, timeout) - if err != nil { - t.Errorf("transmitSingleUse() returned an error: %+v", err) - } - - timer := time.NewTimer(timeout * 2) - - select { - case results := <-callbackChan: - if results.payload != nil || !check(results.err, "timed out") { - t.Errorf("Callback did not return the correct error when it "+ - "should have timed out.\npayload: %+v\nerror: %+v", - results.payload, results.err) - } - case <-timer.C: - } -} - -// Happy path -func TestManager_makeTransmitCmixMessage(t *testing.T) { - m := newTestManager(0, false, t) - prng := rand.New(rand.NewSource(42)) - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(42), - } - tag := "Test tag" - payload := make([]byte, 130) - rand.New(rand.NewSource(42)).Read(payload) - maxMsgs := uint8(8) - timeNow := netTime.Now() - - msg, dhKey, rid, _, err := m.makeTransmitCmixMessage(partner, payload, - tag, maxMsgs, 32, 30*time.Second, timeNow, prng) - - if err != nil { - t.Errorf("makeTransmitCmixMessage() produced an error: %+v", err) - } - - fp := singleUse.NewTransmitFingerprint(partner.DhPubKey) - key := singleUse.NewTransmitKey(dhKey) - - encPayload, err := unmarshalTransmitMessage(msg.GetContents(), - m.store.E2e().GetGroup().GetP().ByteLen()) - if err != nil { - t.Errorf("Failed to unmarshal contents: %+v", err) - } - - decryptedPayload, err := unmarshalTransmitMessagePayload(auth.Crypt(key, - fp[:24], encPayload.GetPayload())) - if err != nil { - t.Errorf("Failed to unmarshal payload: %+v", err) - } - - if !bytes.Equal(payload, decryptedPayload.GetContents()) { - t.Errorf("Failed to decrypt payload.\nexpected: %+v\nreceived: %+v", - payload, decryptedPayload.GetContents()) - } - - if !singleUse.VerifyMAC(key, encPayload.GetPayload(), msg.GetMac()) { - t.Error("Failed to verify the message MAC.") - } - - if fp != msg.GetKeyFP() { - t.Errorf("Failed to verify the CMIX message fingperprint."+ - "\nexpected: %s\nreceived: %s", fp, msg.GetKeyFP()) - } - - if maxMsgs != decryptedPayload.GetMaxParts() { - t.Errorf("Incorrect maxMsgs.\nexpected: %d\nreceived: %d", - maxMsgs, decryptedPayload.GetMaxParts()) - } - - expectedTagFP := singleUse.NewTagFP(tag) - if decryptedPayload.GetTagFP() != expectedTagFP { - t.Errorf("Incorrect TagFP.\nexpected: %s\nreceived: %s", - expectedTagFP, decryptedPayload.GetTagFP()) - } - - if !rid.Cmp(decryptedPayload.GetRID(encPayload.GetPubKey(m.store.E2e().GetGroup()))) { - t.Errorf("Returned incorrect recipient ID.\nexpected: %s\nreceived: %s", - decryptedPayload.GetRID(encPayload.GetPubKey(m.store.E2e().GetGroup())), rid) - } -} - -// Error path: supplied payload to large for message. -func TestManager_makeTransmitCmixMessage_PayloadTooLargeError(t *testing.T) { - m := newTestManager(0, false, t) - prng := rand.New(rand.NewSource(42)) - payload := make([]byte, 1000) - rand.New(rand.NewSource(42)).Read(payload) - - _, _, _, _, err := m.makeTransmitCmixMessage(contact2.Contact{}, payload, "", 8, 32, - 30*time.Second, netTime.Now(), prng) - - if !check(err, "too long for message payload capacity") { - t.Errorf("makeTransmitCmixMessage() failed to error when the payload is too "+ - "large: %+v", err) - } -} - -// Error path: key generation fails. -func TestManager_makeTransmitCmixMessage_KeyGenerationError(t *testing.T) { - m := newTestManager(0, false, t) - prng := strings.NewReader("a") - partner := contact2.Contact{ - ID: id.NewIdFromString("recipientID", id.User, t), - DhPubKey: m.store.E2e().GetGroup().NewInt(42), - } - - _, _, _, _, err := m.makeTransmitCmixMessage(partner, nil, "", 8, 32, - 30*time.Second, netTime.Now(), prng) - - if !check(err, "failed to generate key in group") { - t.Errorf("makeTransmitCmixMessage() failed to error when key "+ - "generation failed: %+v", err) - } -} - -// Happy path: test for consistency. -func Test_makeIDs_Consistency(t *testing.T) { - m := newTestManager(0, false, t) - cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - transmitMsg := newTransmitMessage(cmixMsg.ContentsSize(), m.store.E2e().GetGroup().GetP().ByteLen()) - msgPayload := newTransmitMessagePayload(transmitMsg.GetPayloadSize()) - msgPayload.SetTagFP(singleUse.NewTagFP("tag")) - msgPayload.SetMaxParts(8) - msgPayload.SetContents([]byte("payload")) - _, publicKey, err := generateDhKeys(m.store.E2e().GetGroup(), - m.store.E2e().GetGroup().NewInt(42), rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to generate public key: %+v", err) - } - addressSize := uint8(32) - - expectedPayload, err := unmarshalTransmitMessagePayload(msgPayload.Marshal()) - if err != nil { - t.Fatalf("Failed to copy payload: %+v", err) - } - - err = expectedPayload.SetNonce(rand.New(rand.NewSource(42))) - if err != nil { - t.Fatalf("Failed to set nonce: %+v", err) - } - - timeNow := netTime.Now() - - rid, ephID, err := makeIDs(&msgPayload, publicKey, addressSize, - 30*time.Second, timeNow, rand.New(rand.NewSource(42))) - if err != nil { - t.Errorf("makeIDs() returned an error: %+v", err) - } - - if expectedPayload.GetNonce() != msgPayload.GetNonce() { - t.Errorf("makeIDs() failed to set the expected nonce."+ - "\nexpected: %d\nreceived: %d", expectedPayload.GetNonce(), msgPayload.GetNonce()) - } - - if !expectedPayload.GetRID(publicKey).Cmp(rid) { - t.Errorf("makeIDs() did not return the expected ID."+ - "\nexpected: %s\nreceived: %s", expectedPayload.GetRID(publicKey), rid) - } - - expectedEphID, _, _, err := ephemeral.GetId(expectedPayload.GetRID(publicKey), - uint(addressSize), timeNow.UnixNano()) - if err != nil { - t.Fatalf("Failed to generate expected address ID: %+v", err) - } - - if expectedEphID != ephID { - t.Errorf("makeIDs() did not return the expected address ID."+ - "\nexpected: %d\nreceived: %d", expectedEphID.Int64(), ephID.Int64()) - } -} - -// Error path: failed to generate nonce. -func Test_makeIDs_NonceError(t *testing.T) { - msgPayload := newTransmitMessagePayload(transmitPlMinSize) - - _, _, err := makeIDs(&msgPayload, &cyclic.Int{}, 32, 30*time.Second, - netTime.Now(), strings.NewReader("")) - if !check(err, "failed to generate nonce") { - t.Errorf("makeIDs() did not return an error when failing to make nonce: %+v", err) - } -} - -type testRoundEvents struct { - callbacks map[id.Round][states.NUM_STATES]map[*ds.EventCallback]*ds.EventCallback - timeoutError bool - mux sync.RWMutex -} - -func newTestRoundEvents(timeoutError bool) *testRoundEvents { - return &testRoundEvents{ - callbacks: make(map[id.Round][states.NUM_STATES]map[*ds.EventCallback]*ds.EventCallback), - timeoutError: timeoutError, - } -} - -func (r *testRoundEvents) AddRoundEventChan(_ id.Round, - eventChan chan ds.EventReturn, _ time.Duration, _ ...states.Round) *ds.EventCallback { - - eventChan <- struct { - RoundInfo *pb.RoundInfo - TimedOut bool - }{ - RoundInfo: &pb.RoundInfo{State: uint32(states.COMPLETED)}, - TimedOut: r.timeoutError, - } - - return nil -} diff --git a/ud/lookup_test.go b/ud/lookup_test.go index 63cb7ab60c07c3b5740ab676a3579667192025c3..a6debf89424b24b44ad70033e32d947541277cf8 100644 --- a/ud/lookup_test.go +++ b/ud/lookup_test.go @@ -191,7 +191,7 @@ type mockSingleLookup struct { } func (s *mockSingleLookup) TransmitSingleUse(_ contact.Contact, payload []byte, - _ string, _ uint8, callback single.ReplyComm, _ time.Duration) error { + _ string, _ uint8, callback single.ReplyCallback, _ time.Duration) error { lookupMsg := &LookupSend{} if err := proto.Unmarshal(payload, lookupMsg); err != nil { diff --git a/ud/manager.go b/ud/manager.go index e0c0d8ea3dd6349e7e7b961a694d5bd5d592fdd4..6bd6b7b1a7f5f78532509d00c8a5f89ed4437651 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -6,6 +6,7 @@ import ( "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/client/single/old" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/comms/client" @@ -21,7 +22,7 @@ import ( ) type SingleInterface interface { - TransmitSingleUse(contact.Contact, []byte, string, uint8, single.ReplyComm, + TransmitSingleUse(contact.Contact, []byte, string, uint8, single.ReplyCallback, time.Duration) error StartProcesses() (stoppable.Stoppable, error) } @@ -59,7 +60,7 @@ type alternateUd struct { // NewManager builds a new user discovery manager. It requires that an updated // NDF is available and will error if one is not. -func NewManager(client *api.Client, single *single.Manager) (*Manager, error) { +func NewManager(client *api.Client, single *old.Manager) (*Manager, error) { jww.INFO.Println("ud.NewManager()") if client.NetworkFollowerStatus() != api.Running { return nil, errors.New( diff --git a/ud/search_test.go b/ud/search_test.go index 9a34441b18b97b7539c55071894af6cc07c037db..9ba8651529d1b0666bf7bdb9c268941fc7638551 100644 --- a/ud/search_test.go +++ b/ud/search_test.go @@ -521,7 +521,7 @@ type mockSingleSearch struct { } func (s *mockSingleSearch) TransmitSingleUse(partner contact.Contact, payload []byte, - _ string, _ uint8, callback single.ReplyComm, _ time.Duration) error { + _ string, _ uint8, callback single.ReplyCallback, _ time.Duration) error { searchMsg := &SearchSend{} if err := proto.Unmarshal(payload, searchMsg); err != nil {