diff --git a/cmd/single.go b/cmd/single.go index 55f33b26b8328c9381a1c2a88fc37cd454c585e1..00670077a2cf169ca36ca9352238ae0d36f0b714 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -221,7 +221,7 @@ func replySingleUse(timeout time.Duration, receiver *Receiver) { // Create new payload from repeated received payloads so that each // message part contains the same payload resPayload := makeResponsePayload(payload, results.request.GetMaxParts(), - results.request.GetMaxContentsSize()) + results.request.GetMaxResponsePartSize()) fmt.Printf("Sending single-use response message: %s\n", payload) jww.DEBUG.Printf("Sending single-use response to %s: %s", diff --git a/e2e/e2eMessageBuffer_test.go b/e2e/e2eMessageBuffer_test.go index b10433dac17a2f72b7b71bfb6903e5c6731f0a26..37826cfcff8dee48b705ad4f0efa7b2bd83bd6ac 100644 --- a/e2e/e2eMessageBuffer_test.go +++ b/e2e/e2eMessageBuffer_test.go @@ -23,7 +23,7 @@ import ( func TestE2EMessageHandler_SaveMessage(t *testing.T) { // Set up test values emg := &e2eMessageHandler{} - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) testMsgs := makeTestE2EMessages(10, t) for _, msg := range testMsgs { @@ -60,7 +60,7 @@ func TestE2EMessageHandler_SaveMessage(t *testing.T) { func TestE2EMessageHandler_LoadMessage(t *testing.T) { // Set up test values cmh := &e2eMessageHandler{} - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) testMsgs := makeTestE2EMessages(10, t) for _, msg := range testMsgs { @@ -96,7 +96,7 @@ func TestE2EMessageHandler_LoadMessage(t *testing.T) { func TestE2EMessageHandler_Smoke(t *testing.T) { // Set up test messages testMsgs := makeTestE2EMessages(2, t) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) // Create new buffer cmb, err := NewOrLoadE2eMessageBuffer(kv, "testKey") if err != nil { @@ -178,7 +178,7 @@ func TestE2EParamMarshalUnmarshal(t *testing.T) { Payload: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, MessageType: 42, Params: Params{ - CMIX: cmix.CMIXParams{ + CMIXParams: cmix.CMIXParams{ RoundTries: 6, Timeout: 99, RetryDelay: -4, diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 7a71a84091e80cbaaed1e30b7a0f531a63c828a2..2dbd52c26f4cdbf222df26721defbff1604cc3bb 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -113,6 +113,11 @@ type mockCmixNet struct { instance *network.Instance } +func (m mockCmixNet) Connect(ndf *ndf.NetworkDefinition) error { + // TODO implement me + panic("implement me") +} + func (m mockCmixNet) Follow(report cmix.ClientErrorReport) (stoppable.Stoppable, error) { //TODO implement me return nil, nil diff --git a/single/listener_test.go b/single/listener_test.go index 3e2755a8f1d85404d2a0af6bdc7a0a8bf0c8be07..366907e527492c921d382bebead2499cf7d7a18e 100644 --- a/single/listener_test.go +++ b/single/listener_test.go @@ -220,6 +220,10 @@ func Test_listener_Stop(t *testing.T) { } } +//////////////////////////////////////////////////////////////////////////////// +// Mock cMix Client // +//////////////////////////////////////////////////////////////////////////////// + type mockListenCmixHandler struct { fingerprintMap map[id.ID]map[format.Fingerprint][]cMixMsg.Processor serviceMap map[id.ID]map[string][]cMixMsg.Processor diff --git a/single/message/requestPart_test.go b/single/message/requestPart_test.go index 75090ffd6823c5c6844fbaa60bea64057f380fca..8f3bbdf8e7cafee33432674fdaad8786dbfad5da 100644 --- a/single/message/requestPart_test.go +++ b/single/message/requestPart_test.go @@ -159,7 +159,7 @@ func TestRequestPart_SetContents_GetContents_GetContentsSize_GetMaxContentsSize( } if externalPayloadSize-reqPartMinSize != rmp.GetMaxContentsSize() { - t.Errorf("GetMaxContentsSize failed to return the expected max "+ + t.Errorf("GetMaxResponsePartSize failed to return the expected max "+ "contents size.\nexpected: %d\nrecieved: %d", externalPayloadSize-reqPartMinSize, rmp.GetMaxContentsSize()) } diff --git a/single/message/request_test.go b/single/message/request_test.go index b2ee1dca55ecef72b534972b2303d43d63cf252a..8bdc81cf91acd357db9d471bffc2235fe34ef2d2 100644 --- a/single/message/request_test.go +++ b/single/message/request_test.go @@ -423,7 +423,7 @@ func TestRequestPayload_GetContents_GetContentsSize_GetMaxContentsSize(t *testin } if format.MinimumPrimeSize-requestMinSize != mp.GetMaxContentsSize() { - t.Errorf("GetMaxContentsSize did not return the expected size."+ + t.Errorf("GetMaxResponsePartSize did not return the expected size."+ "\nexpected: %d\nreceived: %d", format.MinimumPrimeSize-requestMinSize, mp.GetMaxContentsSize()) } diff --git a/single/message/responsePart_test.go b/single/message/responsePart_test.go index f8a315c13f66d0f7a261844f4593d73d98ee8cf3..45c06773f113fb64f3e67db2cbeaba4d3bef6bb7 100644 --- a/single/message/responsePart_test.go +++ b/single/message/responsePart_test.go @@ -170,7 +170,7 @@ func TestResponsePart_SetContents_GetContents_GetContentsSize_GetMaxContentsSize } if externalPayloadSize-resPartMinSize != rmp.GetMaxContentsSize() { - t.Errorf("GetMaxContentsSize failed to return the expected max "+ + t.Errorf("GetMaxResponsePartSize failed to return the expected max "+ "contents size.\nexpected: %d\nrecieved: %d", externalPayloadSize-resPartMinSize, rmp.GetMaxContentsSize()) } diff --git a/single/receivedRequest.go b/single/receivedRequest.go index 5026f353065f0405e497e18be194b3cde82477cc..372557fbb0962bfcf3b9ddd4dffd8e747930b3e7 100644 --- a/single/receivedRequest.go +++ b/single/receivedRequest.go @@ -21,18 +21,21 @@ import ( "time" ) -// Request contains the information to respond to a single-use contact. +// Request contains the information contained in a single-use request message. type Request struct { - sender *id.ID // ID of the person to respond to + sender *id.ID // ID of the sender/ID to send response 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 RequestCmix + used *uint32 // Set when response is sent + requestPayload []byte // Request message payload + + net RequestCmix } +// RequestCmix interface matches a subset of the cmix.Client methods used by the +// Request for easier testing. type RequestCmix interface { GetMaxMessageLength() int Send(recipient *id.ID, fingerprint format.Fingerprint, @@ -41,55 +44,13 @@ type RequestCmix interface { GetInstance() *network.Instance } -// GetMaxParts returns the maximum number of message parts that can be sent in a -// reply. -func (r Request) GetMaxParts() uint8 { - return r.maxParts -} - -// GetMaxResponseLength returns the maximum total payload size, which is the -// maximum size of each individual part multiplied by the maximum number of parts -func (r Request) GetMaxResponseLength() int { - return r.GetMaxContentsSize() * int(r.GetMaxParts()) -} - -// GetMaxContentsSize returns maximum payload size for an individual part -func (r Request) GetMaxContentsSize() int { - responseMsg := message.NewResponsePart(r.net.GetMaxMessageLength()) - return responseMsg.GetMaxContentsSize() -} - -// 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 { - return fmt.Sprintf( - "{sender:%s senderPubKey:%s dhKey:%s tag:%q maxParts:%d used:%p(%d) "+ - "requestPayload:%q net:%p}", - r.sender, r.senderPubKey.Text(10), r.dhKey.Text(10), r.tag, r.maxParts, - r.used, atomic.LoadUint32(r.used), r.requestPayload, r.net) -} - // Respond is used to respond to the request. It sends a payload up to // Request.GetMaxResponseLength. It will chunk the message into multiple cMix // messages if it is too long for a single message. It will fail if a single // cMix message cannot be sent. -func (r Request) Respond(payload []byte, cMixParams cmix.CMIXParams, +func (r *Request) Respond(payload []byte, cMixParams cmix.CMIXParams, timeout time.Duration) ([]id.Round, error) { - // make sure this has only been run once + // 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 " + @@ -185,13 +146,57 @@ func (r Request) Respond(payload []byte, cMixParams cmix.CMIXParams, return rounds, nil } +// GetMaxParts returns the maximum number of messages allowed to send in the +// reply. +func (r *Request) GetMaxParts() uint8 { + return r.maxParts +} + +// GetMaxResponseLength returns the maximum size of the entire response message. +func (r *Request) GetMaxResponseLength() int { + return r.GetMaxResponsePartSize() * int(r.GetMaxParts()) +} + +// GetMaxResponsePartSize returns maximum payload size for an individual part of +// the response message. +func (r *Request) GetMaxResponsePartSize() int { + responseMsg := message.NewResponsePart(r.net.GetMaxMessageLength()) + return responseMsg.GetMaxContentsSize() +} + +// 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 +} + +// GoString returns string showing the values of all the fields of Request. +// Adheres to the fmt.GoStringer interface. +func (r *Request) GoString() string { + return fmt.Sprintf( + "{sender:%s senderPubKey:%s dhKey:%s tag:%q maxParts:%d used:%p(%d) "+ + "requestPayload:%q net:%p}", + r.sender, r.senderPubKey.Text(10), r.dhKey.Text(10), r.tag, r.maxParts, + r.used, atomic.LoadUint32(r.used), r.requestPayload, r.net) +} + // partitionResponse breaks a payload into its sub payloads for sending. -func partitionResponse(payload []byte, cmixMessageLength int, maxParts uint8) []message.ResponsePart { +func partitionResponse(payload []byte, cmixMessageLength int, + maxParts uint8) []message.ResponsePart { responseMsg := message.NewResponsePart(cmixMessageLength) // Split payloads - payloadParts := splitPayload(payload, responseMsg.GetMaxContentsSize(), - int(maxParts)) + payloadParts := splitPayload( + payload, responseMsg.GetMaxContentsSize(), int(maxParts)) // Create messages parts := make([]message.ResponsePart, len(payloadParts)) diff --git a/single/receivedRequest_test.go b/single/receivedRequest_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d5d8b15fb500270160f657ad82db712094a61e88 --- /dev/null +++ b/single/receivedRequest_test.go @@ -0,0 +1,243 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/cmix" + cmixMsg "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/large" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" + "reflect" + "testing" + "time" +) + +// Tests that RequestCmix adheres to the cmix.Client interface. +var _ RequestCmix = (cmix.Client)(nil) + +func TestRequest_Respond(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + privKey := grp.NewInt(42) + used := uint32(0) + payloadChan := make(chan format.Message, 10) + net := newMockRequestCmix(payloadChan, t) + r := &Request{ + sender: id.NewIdFromString("singleUseRequest", id.User, t), + senderPubKey: grp.NewInt(42), + dhKey: grp.ExpG(privKey, grp.NewInt(1)), + tag: "requestTag", + maxParts: 10, + used: &used, + requestPayload: []byte("test"), + net: net, + } + + payload := []byte("My Response.") + cMixParams := cmix.GetDefaultCMIXParams() + + _, err := r.Respond(payload, cMixParams, 0) + if err != nil { + t.Errorf("Respond returned an error: %+v", err) + } + + select { + case ecrMsg := <-payloadChan: + c := &cypher{ + dhKey: r.dhKey, + num: 0, + newKey: singleUse.NewResponseKey, + newFp: singleUse.NewResponseFingerprint, + } + + decrypted, err := c.decrypt(ecrMsg.GetContents(), ecrMsg.GetMac()) + if err != nil { + t.Errorf("Failed to decrypt single-use response payload: %+v", err) + return + } + + // Unmarshal the cMix message contents to a request message + responsePart, err := message.UnmarshalResponsePart(decrypted) + if err != nil { + t.Errorf("could not unmarshal ResponsePart: %+v", err) + } + + if !bytes.Equal(payload, responsePart.GetContents()) { + t.Errorf("Did not receive expected payload."+ + "\nexpected: %q\nreceived: %q", + payload, responsePart.GetContents()) + } + + case <-time.After(15 * time.Millisecond): + t.Errorf("Timed out waiting for response.") + } +} + +// Tests that partitionResponse creates a list of message.ResponsePart each with +// the expected contents and part number. +func Test_partitionResponse(t *testing.T) { + cmixMessageLength := 10 + + maxSize := message.NewResponsePart(cmixMessageLength).GetMaxContentsSize() + maxParts := uint8(10) + payload := []byte("012345678901234567890123456789012345678901234567890123" + + "45678901234567890123456789012345678901234567890123456789") + 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], + } + + parts := partitionResponse(payload, cmixMessageLength, maxParts) + + for i, part := range parts { + if part.GetNumParts() != maxParts { + t.Errorf("Part #%d has wrong numParts.\nexpected: %d\nreceived: %d", + i, maxParts, part.GetNumParts()) + } + if int(part.GetPartNum()) != i { + t.Errorf("Part #%d has wrong part num.\nexpected: %d\nreceived: %d", + i, i, part.GetPartNum()) + } + if !bytes.Equal(part.GetContents(), expectedParts[i]) { + t.Errorf("Part #%d has wrong contents.\nexpected: %q\nreceived: %q", + i, expectedParts[i], part.GetContents()) + } + } +} + +// Tests that splitPayload splits the payload to match the expected. +func Test_splitPayload(t *testing.T) { + maxSize := 5 + maxParts := 10 + payload := []byte("012345678901234567890123456789012345678901234567890123" + + "45678901234567890123456789012345678901234567890123456789") + 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 := splitPayload(payload, maxSize, maxParts) + + if !reflect.DeepEqual(expectedParts, testParts) { + t.Errorf("splitPayload() failed to correctly split the payload."+ + "\nexpected: %s\nreceived: %s", expectedParts, testParts) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Mock cMix Client // +//////////////////////////////////////////////////////////////////////////////// + +type mockRequestCmix struct { + sendPayload chan format.Message + numPrimeBytes int + instance *network.Instance +} + +func newMockRequestCmix(sendPayload chan format.Message, t *testing.T) *mockRequestCmix { + instanceComms := &connect.ProtoComms{Manager: connect.NewManagerTesting(t)} + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + thisInstance, err := network.NewInstanceTesting( + instanceComms, getNDF(), getNDF(), grp, grp, t) + if err != nil { + t.Errorf("Failed to create new test instance: %v", err) + } + + return &mockRequestCmix{ + sendPayload: sendPayload, + numPrimeBytes: 97, + instance: thisInstance, + } +} + +func (m *mockRequestCmix) GetMaxMessageLength() int { + msg := format.NewMessage(m.numPrimeBytes) + return msg.ContentsSize() +} + +func (m *mockRequestCmix) Send(_ *id.ID, fp format.Fingerprint, + _ cmixMsg.Service, payload, mac []byte, _ cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + msg := format.NewMessage(m.numPrimeBytes) + msg.SetMac(mac) + msg.SetKeyFP(fp) + msg.SetContents(payload) + m.sendPayload <- msg + + return 0, ephemeral.Id{}, nil +} + +func (m *mockRequestCmix) GetInstance() *network.Instance { + return m.instance +} + +func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + E2E: ndf.Group{ + Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" + + "8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" + + "D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" + + "75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" + + "6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" + + "4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" + + "6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" + + "448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" + + "198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" + + "DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" + + "631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" + + "3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" + + "19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" + + "5873847AEF49F66E43873", + Generator: "2", + }, + CMIX: ndf.Group{ + Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" + + "F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" + + "264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" + + "9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" + + "B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" + + "0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" + + "92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" + + "2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" + + "995FAD5AABBCFBE3EDA2741E375404AE25B", + Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" + + "9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" + + "1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" + + "8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" + + "C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" + + "5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" + + "59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" + + "2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" + + "B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + }, + } +}