diff --git a/catalog/services.go b/catalog/services.go index cd28b663e04c925eadd329a49f82aa84aec824de..24ab74a466f6499654267861760b72cbc2d6472e 100644 --- a/catalog/services.go +++ b/catalog/services.go @@ -23,4 +23,6 @@ const ( Group = "group" EndFT = "endFT" GroupRq = "groupRq" + + RestLike = "restLike" ) 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/fpGenerator.go b/e2e/fpGenerator.go index 3a9a6c00ee1fdd753e3a6aaa78ef9e17e56b91cc..5e89dd8016e3c231fd968c54bdb4824f9ceeac9c 100644 --- a/e2e/fpGenerator.go +++ b/e2e/fpGenerator.go @@ -5,25 +5,24 @@ import ( "gitlab.com/elixxir/client/e2e/ratchet/partner/session" ) -// Wrapper which allows the network>manager's fingerprint interface to be -// passed into ratchet without exposing ratchet to buisness logic -// adheres to the CypherHandler interface in session +// fpGenerator is a wrapper that allows the network manager's fingerprint +// interface to be passed into ratchet without exposing ratchet to the business +// logic. type fpGenerator struct { - *manager + m *manager } -func (fp *fpGenerator) AddKey(k *session.Cypher) { - err := fp.net.AddFingerprint(fp.myID, k.Fingerprint(), - &processor{ - cy: k, - m: fp.manager, - }) +// AddKey adds a fingerprint to be tracked for the given cypher. +func (fpg *fpGenerator) AddKey(cy session.Cypher) { + err := fpg.m.net.AddFingerprint( + fpg.m.myID, cy.Fingerprint(), &processor{cy, fpg.m}) if err != nil { - jww.ERROR.Printf("Could not add fingerprint %s: %+v", - k.Fingerprint(), err) + jww.ERROR.Printf( + "Could not add fingerprint %s: %+v", cy.Fingerprint(), err) } } -func (fp *fpGenerator) DeleteKey(k *session.Cypher) { - fp.net.DeleteFingerprint(fp.myID, k.Fingerprint()) +// DeleteKey deletes the fingerprint for the given cypher. +func (fpg *fpGenerator) DeleteKey(cy session.Cypher) { + fpg.m.net.DeleteFingerprint(fpg.m.myID, cy.Fingerprint()) } diff --git a/e2e/fpGenerator_test.go b/e2e/fpGenerator_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ada64460a28221276304055f21056b3be92ae3dd --- /dev/null +++ b/e2e/fpGenerator_test.go @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package e2e + +import ( + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/gateway" + "gitlab.com/elixxir/client/cmix/identity" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/e2e/ratchet/partner/session" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" + "math/rand" + "sync" + "testing" + "time" +) + +// Adds a list of cyphers with different fingerprints with fpGenerator.AddKey +// and then checks that they were added to the mock cMix fingerprint tracker. +func Test_fpGenerator_AddKey(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + net := newMockFpgCmix() + fpg := &fpGenerator{&manager{ + net: net, + myID: id.NewIdFromString("myID", id.User, t), + }} + + fps := make([]format.Fingerprint, 20) + for i := range fps { + prng.Read(fps[i][:]) + fpg.AddKey(mockSessionCypher{fps[i]}) + } + + for i, fp := range fps { + if _, exists := net.processors[*fpg.m.myID][fp]; !exists { + t.Errorf("Fingerprint #%d does not exist.", i) + } else { + delete(net.processors[*fpg.m.myID], fp) + } + } + + if len(net.processors[*fpg.m.myID]) != 0 { + t.Errorf("%d extra fingerprints found: %+v", + len(net.processors[*fpg.m.myID]), net.processors[*fpg.m.myID]) + } +} + +// Adds a list of cyphers with different fingerprints and then deletes all of +// them with fpGenerator.DeleteKey and checks that all keys were deleted. +func Test_fpGenerator_DeleteKey(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + net := newMockFpgCmix() + fpg := &fpGenerator{&manager{ + net: net, + myID: id.NewIdFromString("myID", id.User, t), + }} + + fps := make([]format.Fingerprint, 20) + for i := range fps { + prng.Read(fps[i][:]) + fpg.AddKey(mockSessionCypher{fps[i]}) + } + + for _, fp := range fps { + fpg.DeleteKey(mockSessionCypher{fp}) + } + + if len(net.processors[*fpg.m.myID]) != 0 { + t.Errorf("%d extra fingerprints found: %+v", + len(net.processors[*fpg.m.myID]), net.processors[*fpg.m.myID]) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Mock Session Cypher // +//////////////////////////////////////////////////////////////////////////////// + +type mockSessionCypher struct { + fp format.Fingerprint +} + +func (m mockSessionCypher) GetSession() *session.Session { return nil } +func (m mockSessionCypher) Fingerprint() format.Fingerprint { return m.fp } +func (m mockSessionCypher) Encrypt([]byte) (ecrContents, mac []byte) { return nil, nil } +func (m mockSessionCypher) Decrypt(format.Message) ([]byte, error) { return nil, nil } +func (m mockSessionCypher) Use() {} + +//////////////////////////////////////////////////////////////////////////////// +// Mock cMix Client // +//////////////////////////////////////////////////////////////////////////////// + +type mockFpgCmix struct { + processors map[id.ID]map[format.Fingerprint]message.Processor + sync.Mutex +} + +func newMockFpgCmix() *mockFpgCmix { + return &mockFpgCmix{ + processors: make(map[id.ID]map[format.Fingerprint]message.Processor), + } +} + +func (m *mockFpgCmix) Connect(*ndf.NetworkDefinition) error { return nil } +func (m *mockFpgCmix) Follow(cmix.ClientErrorReport) (stoppable.Stoppable, error) { return nil, nil } +func (m *mockFpgCmix) GetMaxMessageLength() int { return 0 } +func (m *mockFpgCmix) Send(*id.ID, format.Fingerprint, message.Service, []byte, []byte, cmix.CMIXParams) (id.Round, ephemeral.Id, error) { + return 0, ephemeral.Id{}, nil +} +func (m *mockFpgCmix) SendMany([]cmix.TargetedCmixMessage, cmix.CMIXParams) (id.Round, []ephemeral.Id, error) { + return 0, nil, nil +} +func (m *mockFpgCmix) AddIdentity(*id.ID, time.Time, bool) {} +func (m *mockFpgCmix) RemoveIdentity(*id.ID) {} +func (m *mockFpgCmix) GetIdentity(*id.ID) (identity.TrackedID, error) { + return identity.TrackedID{}, nil +} + +func (m *mockFpgCmix) AddFingerprint(uid *id.ID, fp format.Fingerprint, mp message.Processor) error { + m.Lock() + defer m.Unlock() + + if _, exists := m.processors[*uid]; !exists { + m.processors[*uid] = + map[format.Fingerprint]message.Processor{fp: mp} + } else if _, exists = m.processors[*uid][fp]; !exists { + m.processors[*uid][fp] = mp + } + + return nil +} + +func (m *mockFpgCmix) DeleteFingerprint(uid *id.ID, fp format.Fingerprint) { + m.Lock() + defer m.Unlock() + + if _, exists := m.processors[*uid]; exists { + delete(m.processors[*uid], fp) + } +} + +func (m *mockFpgCmix) DeleteClientFingerprints(*id.ID) {} +func (m *mockFpgCmix) AddService(*id.ID, message.Service, message.Processor) {} +func (m *mockFpgCmix) DeleteService(*id.ID, message.Service, message.Processor) {} +func (m *mockFpgCmix) DeleteClientService(*id.ID) {} +func (m *mockFpgCmix) TrackServices(message.ServicesTracker) {} +func (m *mockFpgCmix) CheckInProgressMessages() {} +func (m *mockFpgCmix) IsHealthy() bool { return false } +func (m *mockFpgCmix) WasHealthy() bool { return false } +func (m *mockFpgCmix) AddHealthCallback(func(bool)) uint64 { return 0 } +func (m *mockFpgCmix) RemoveHealthCallback(uint64) {} +func (m *mockFpgCmix) HasNode(*id.ID) bool { return false } +func (m *mockFpgCmix) NumRegisteredNodes() int { return 0 } +func (m *mockFpgCmix) TriggerNodeRegistration(*id.ID) {} +func (m *mockFpgCmix) GetRoundResults(time.Duration, cmix.RoundEventCallback, ...id.Round) error { + return nil +} +func (m *mockFpgCmix) LookupHistoricalRound(id.Round, rounds.RoundResultCallback) error { return nil } +func (m *mockFpgCmix) SendToAny(func(host *connect.Host) (interface{}, error), *stoppable.Single) (interface{}, error) { + return nil, nil +} +func (m *mockFpgCmix) SendToPreferred([]*id.ID, gateway.SendToPreferredFunc, *stoppable.Single, time.Duration) (interface{}, error) { + return nil, nil +} +func (m *mockFpgCmix) SetGatewayFilter(gateway.Filter) {} +func (m *mockFpgCmix) GetHostParams() connect.HostParams { return connect.HostParams{} } +func (m *mockFpgCmix) GetAddressSpace() uint8 { return 0 } +func (m *mockFpgCmix) RegisterAddressSpaceNotification(string) (chan uint8, error) { return nil, nil } +func (m *mockFpgCmix) UnregisterAddressSpaceNotification(string) {} +func (m *mockFpgCmix) GetInstance() *network.Instance { return nil } +func (m *mockFpgCmix) GetVerboseRounds() string { return "" } diff --git a/e2e/manager.go b/e2e/manager.go index 73eeb111abdcc7db9b360e7e06065e5f7bce4a49..2bdb8c21f1afcfd20cf41a2ba06fdc06d08bd13e 100644 --- a/e2e/manager.go +++ b/e2e/manager.go @@ -118,8 +118,7 @@ func loadE2E(kv *versioned.KV, net cmix.Client, myDefaultID *id.ID, m := &manager{ Switchboard: receive.New(), - partitioner: parse.NewPartitioner(kv, - net.GetMaxMessageLength()), + partitioner: parse.NewPartitioner(kv, net.GetMaxMessageLength()), net: net, myID: myDefaultID, events: events, diff --git a/e2e/manager_test.go b/e2e/manager_test.go index af4736d0ddb1fee413be96e026dbb4ea05c75109..5f89bc5ecd2c9434557606001a40ed21803171fc 100644 --- a/e2e/manager_test.go +++ b/e2e/manager_test.go @@ -26,7 +26,7 @@ type legacyPartnerData struct { mySidhPrivKey *sidh.PrivateKey partnerSidhPubKey *sidh.PublicKey sendFP []byte - recieveFp []byte + receiveFp []byte } // TestRatchet_unmarshalOld tests the loading of legacy data @@ -87,7 +87,7 @@ func TestLoadLegacy(t *testing.T) { // the legacy loading tests sendFP: e2e.MakeRelationshipFingerprint(myPubKey, partnerPubKey, myId, partnerID), - recieveFp: e2e.MakeRelationshipFingerprint(myPubKey, partnerPubKey, + receiveFp: e2e.MakeRelationshipFingerprint(myPubKey, partnerPubKey, partnerID, myId), } @@ -96,12 +96,12 @@ func TestLoadLegacy(t *testing.T) { } // Construct kv with legacy data - //fs, err := ekv.NewFilestore("/home/josh/src/client/e2e/legacyEkv", "hello") - //if err != nil { + // fs, err := ekv.NewFilestore("/home/josh/src/client/e2e/legacyEkv", "hello") + // if err != nil { // t.Fatalf( // "Failed to create storage session: %+v", err) - //} - kv := versioned.NewKV(&ekv.Memstore{}) + // } + kv := versioned.NewKV(ekv.MakeMemstore()) err := ratchet.New(kv, myId, myPrivKey, grp) if err != nil { @@ -111,7 +111,7 @@ func TestLoadLegacy(t *testing.T) { rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) // Load legacy data - h, err := LoadLegacy(kv, &mockCmixNet{testingInterface: t}, myId, + h, err := LoadLegacy(kv, newMockCmix(nil, nil, t), myId, grp, rng, mockEventsManager{}, rekey.GetDefaultParams()) if err != nil { t.Fatalf("LoadLegacy error: %v", err) @@ -121,20 +121,19 @@ func TestLoadLegacy(t *testing.T) { for _, legacyPartner := range legacyData { partnerManager, err := h.GetPartner(legacyPartner.partnerId) if err != nil { - t.Errorf("Partner %d does not exist in handler.", legacyPartner.partnerId) + t.Errorf("Partner %s does not exist in handler.", legacyPartner.partnerId) + } else { + if !bytes.Equal(partnerManager.SendRelationshipFingerprint(), legacyPartner.sendFP) { + t.Fatalf("Send relationship fingerprint pulled from legacy does not match expected data."+ + "\nExpected: %v"+ + "\nReceived: %v", legacyPartner.sendFP, partnerManager.SendRelationshipFingerprint()) + } + + if !bytes.Equal(partnerManager.ReceiveRelationshipFingerprint(), legacyPartner.receiveFp) { + t.Fatalf("Receive relationship fingerprint pulled from legacy does not match expected data."+ + "\nExpected: %v"+ + "\nReceived: %v", legacyPartner.sendFP, partnerManager.SendRelationshipFingerprint()) + } } - - if !bytes.Equal(partnerManager.SendRelationshipFingerprint(), legacyPartner.sendFP) { - t.Fatalf("Send relationship fingerprint pulled from legacy does not match expected data."+ - "\nExpected: %v"+ - "\nReceived: %v", legacyPartner.sendFP, partnerManager.SendRelationshipFingerprint()) - } - - if !bytes.Equal(partnerManager.ReceiveRelationshipFingerprint(), legacyPartner.recieveFp) { - t.Fatalf("Receive relationship fingerprint pulled from legacy does not match expected data."+ - "\nExpected: %v"+ - "\nReceived: %v", legacyPartner.sendFP, partnerManager.SendRelationshipFingerprint()) - } - } } diff --git a/e2e/parse/conversation/partner_test.go b/e2e/parse/conversation/partner_test.go index 9a52791f147f1ad9b69eace2bc6cc3378320f265..41dd638ca5a04fefc125c2a4977715c5c4493f5c 100644 --- a/e2e/parse/conversation/partner_test.go +++ b/e2e/parse/conversation/partner_test.go @@ -19,7 +19,7 @@ import ( // Tests happy path of LoadOrMakeConversation when making a new Conversation. func TestLoadOrMakeConversation_New(t *testing.T) { // Set up test values - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) expectedConv := &Conversation{ lastReceivedID: 0, @@ -42,7 +42,7 @@ func TestLoadOrMakeConversation_New(t *testing.T) { // Tests happy path of LoadOrMakeConversation when loading a Conversation. func TestLoadOrMakeConversation_Load(t *testing.T) { // Set up test values - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) expectedConv := LoadOrMakeConversation(kv, partner) @@ -60,7 +60,7 @@ func TestLoadOrMakeConversation_Load(t *testing.T) { func TestConversation_ProcessReceivedMessageID_Case_1(t *testing.T) { // Set up test values mid := uint32(5) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) expectedConv := LoadOrMakeConversation(kv, partner) expectedConv.lastReceivedID = mid @@ -84,7 +84,7 @@ func TestConversation_ProcessReceivedMessageID_Case_1(t *testing.T) { func TestConversation_ProcessReceivedMessageID_Case_0(t *testing.T) { // Set up test values mid := uint32(5) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) expectedConv := LoadOrMakeConversation(kv, partner) expectedConv.lastReceivedID = mid @@ -106,7 +106,7 @@ func TestConversation_ProcessReceivedMessageID_Case_0(t *testing.T) { func TestConversation_ProcessReceivedMessageID_Case_Neg1(t *testing.T) { // Set up test values mid := uint32(topRegion + 5) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) expectedConv := LoadOrMakeConversation(kv, partner) expectedConv.lastReceivedID = bottomRegion - 5 @@ -128,7 +128,7 @@ func TestConversation_ProcessReceivedMessageID_Case_Neg1(t *testing.T) { // Tests happy path of Conversation.GetNextSendID. func TestConversation_GetNextSendID(t *testing.T) { // Set up test values - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) conv := LoadOrMakeConversation(kv, partner) conv.nextSentID = maxTruncatedID - 100 @@ -148,7 +148,7 @@ func TestConversation_GetNextSendID(t *testing.T) { // Tests the happy path of save and loadConversation. func TestConversation_save_load(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) expectedConv := makeRandomConv(kv, partner) expectedErr := "loadConversation produced an error: Failed to Load " + @@ -169,7 +169,7 @@ func TestConversation_save_load(t *testing.T) { "\nexpected: %+v\nreceived: %+v", expectedConv, testConv) } - _, err = loadConversation(versioned.NewKV(make(ekv.Memstore)), partner) + _, err = loadConversation(versioned.NewKV(ekv.MakeMemstore()), partner) if err == nil { t.Errorf("loadConversation failed to produce an error."+ "\nexpected: %s\nreceived: %v", expectedErr, nil) @@ -178,7 +178,7 @@ func TestConversation_save_load(t *testing.T) { // Happy path. func TestConversation_Delete(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partner := id.NewIdFromString("partner ID", id.User, t) conv := makeRandomConv(kv, partner) @@ -201,7 +201,7 @@ func TestConversation_Delete(t *testing.T) { // Tests the happy path of marshal and unmarshal. func TestConversation_marshal_unmarshal(t *testing.T) { - expectedConv := makeRandomConv(versioned.NewKV(make(ekv.Memstore)), + expectedConv := makeRandomConv(versioned.NewKV(ekv.MakeMemstore()), id.NewIdFromString("partner ID", id.User, t)) testConv := LoadOrMakeConversation(expectedConv.kv, expectedConv.partner) diff --git a/e2e/parse/conversation/ring_test.go b/e2e/parse/conversation/ring_test.go index e9174d366681c427b33bfb626c51a292001a1f30..1d1db1983fbe265d6841b22c1f969b21119806f7 100644 --- a/e2e/parse/conversation/ring_test.go +++ b/e2e/parse/conversation/ring_test.go @@ -19,7 +19,7 @@ import ( // TestNewBuff tests the creation of a Buff object. func TestNewBuff(t *testing.T) { // Initialize buffer - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) buffLen := 20 testBuff, err := NewBuff(kv, buffLen) if err != nil { @@ -43,7 +43,7 @@ func TestNewBuff(t *testing.T) { // the Buff.buff, buff.lookup, and proper index updates. func TestBuff_Add(t *testing.T) { // Initialize buffer - testBuff, err := NewBuff(versioned.NewKV(make(ekv.Memstore)), 20) + testBuff, err := NewBuff(versioned.NewKV(ekv.MakeMemstore()), 20) if err != nil { t.Errorf("Failed to make new Buff: %+v", err) } @@ -98,7 +98,7 @@ func TestBuff_Add(t *testing.T) { // value is overwritten. func TestBuff_Add_Overflow(t *testing.T) { buffLen := 20 - testBuff, err := NewBuff(versioned.NewKV(make(ekv.Memstore)), buffLen) + testBuff, err := NewBuff(versioned.NewKV(ekv.MakeMemstore()), buffLen) if err != nil { t.Errorf("Failed to make new Buff: %+v", err) } @@ -142,7 +142,7 @@ func TestBuff_Add_Overflow(t *testing.T) { // Tests that Buff.Get returns the latest inserted Message. func TestBuff_Get(t *testing.T) { // Initialize buffer - testBuff, err := NewBuff(versioned.NewKV(make(ekv.Memstore)), 20) + testBuff, err := NewBuff(versioned.NewKV(ekv.MakeMemstore()), 20) if err != nil { t.Errorf("Failed to make new Buff: %+v", err) } @@ -196,7 +196,7 @@ func TestBuff_Get(t *testing.T) { // MessageID. func TestBuff_GetByMessageID(t *testing.T) { // Initialize buffer - testBuff, err := NewBuff(versioned.NewKV(make(ekv.Memstore)), 20) + testBuff, err := NewBuff(versioned.NewKV(ekv.MakeMemstore()), 20) if err != nil { t.Errorf("Failed to make new Buff: %+v", err) } @@ -234,7 +234,7 @@ func TestBuff_GetByMessageID(t *testing.T) { // that does not exist in Buff. func TestBuff_GetByMessageID_Error(t *testing.T) { // Initialize buffer - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) buffLen := 20 testBuff, err := NewBuff(kv, buffLen) if err != nil { @@ -254,7 +254,7 @@ func TestBuff_GetByMessageID_Error(t *testing.T) { func TestBuff_GetNextMessage(t *testing.T) { // Initialize buffer - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) buffLen := 20 testBuff, err := NewBuff(kv, buffLen) if err != nil { @@ -298,7 +298,7 @@ func TestBuff_GetNextMessage(t *testing.T) { func TestLoadBuff(t *testing.T) { // Initialize buffer - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) buffLen := 20 testBuff, err := NewBuff(kv, buffLen) if err != nil { diff --git a/e2e/parse/conversation/store_test.go b/e2e/parse/conversation/store_test.go index 16598c3cd268194c32f89eba021ccd11e4e7733b..176d251be7085dba1baa22e9038aeef10efb58fc 100644 --- a/e2e/parse/conversation/store_test.go +++ b/e2e/parse/conversation/store_test.go @@ -17,7 +17,7 @@ import ( // Happy path. func TestStore_Delete(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) store := NewStore(kv) pIDs := make([]*id.ID, 10) diff --git a/e2e/parse/partition/multiPartMessage_test.go b/e2e/parse/partition/multiPartMessage_test.go index 8bd4846625bde3c50dcdb30a2c6ab19e3e423bdb..616b7dda8d3bcae9799b78ac595f4a6fe3f123ad 100644 --- a/e2e/parse/partition/multiPartMessage_test.go +++ b/e2e/parse/partition/multiPartMessage_test.go @@ -34,7 +34,7 @@ func Test_loadOrCreateMultiPartMessage_Create(t *testing.T) { PresentParts: 0, SenderTimestamp: time.Time{}, MessageType: 0, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } expectedData, err := json.Marshal(expectedMpm) if err != nil { @@ -70,7 +70,7 @@ func Test_loadOrCreateMultiPartMessage_Load(t *testing.T) { PresentParts: 0, SenderTimestamp: time.Time{}, MessageType: 0, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } err := expectedMpm.save() if err != nil { @@ -136,7 +136,7 @@ func TestMultiPartMessage_Add(t *testing.T) { prng := rand.New(rand.NewSource(netTime.Now().UnixNano())) mpm := loadOrCreateMultiPartMessage( id.NewIdFromUInt(prng.Uint64(), id.User, t), prng.Uint64(), - versioned.NewKV(make(ekv.Memstore))) + versioned.NewKV(ekv.MakeMemstore())) partNums, parts := generateParts(prng, 0) for i := range partNums { @@ -184,7 +184,7 @@ func TestMultiPartMessage_AddFirst(t *testing.T) { SenderTimestamp: netTime.Now(), MessageType: catalog.NoType, parts: make([][]byte, 3), - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } expectedMpm.parts[2] = []byte{5, 8, 78, 9} npm := loadOrCreateMultiPartMessage(expectedMpm.Sender, @@ -213,7 +213,7 @@ func TestMultiPartMessage_IsComplete(t *testing.T) { mid := prng.Uint64() mpm := loadOrCreateMultiPartMessage( id.NewIdFromUInt(prng.Uint64(), id.User, t), mid, - versioned.NewKV(make(ekv.Memstore))) + versioned.NewKV(ekv.MakeMemstore())) partNums, parts := generateParts(prng, 75) // Check that IsComplete is false where there are no parts @@ -258,7 +258,7 @@ func TestMultiPartMessage_IsComplete(t *testing.T) { // Tests happy path of multiPartMessage.delete. func TestMultiPartMessage_delete(t *testing.T) { prng := rand.New(rand.NewSource(netTime.Now().UnixNano())) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) mpm := loadOrCreateMultiPartMessage( id.NewIdFromUInt(prng.Uint64(), id.User, t), prng.Uint64(), kv) diff --git a/e2e/parse/partition/part_test.go b/e2e/parse/partition/part_test.go index 936de7af865b4379ee4e72eb396bf926f1ff56bd..53366fdb6d211a74e531e21cf8b4f0a9665079c3 100644 --- a/e2e/parse/partition/part_test.go +++ b/e2e/parse/partition/part_test.go @@ -20,7 +20,7 @@ import ( func Test_savePart(t *testing.T) { // Set up test values prng := rand.New(rand.NewSource(netTime.Now().UnixNano())) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partNum := uint8(prng.Uint32()) part := make([]byte, prng.Int31n(500)) prng.Read(part) @@ -49,7 +49,7 @@ func Test_savePart(t *testing.T) { func Test_loadPart(t *testing.T) { // Set up test values prng := rand.New(rand.NewSource(netTime.Now().UnixNano())) - rootKv := versioned.NewKV(make(ekv.Memstore)) + rootKv := versioned.NewKV(ekv.MakeMemstore()) partNum := uint8(prng.Uint32()) part := make([]byte, prng.Int31n(500)) prng.Read(part) @@ -80,7 +80,7 @@ func Test_loadPart(t *testing.T) { func Test_loadPart_NotFoundError(t *testing.T) { // Set up test values prng := rand.New(rand.NewSource(netTime.Now().UnixNano())) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partNum := uint8(prng.Uint32()) part := make([]byte, prng.Int31n(500)) prng.Read(part) @@ -102,7 +102,7 @@ func Test_loadPart_NotFoundError(t *testing.T) { func TestDeletePart(t *testing.T) { // Set up test values prng := rand.New(rand.NewSource(netTime.Now().UnixNano())) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partNum := uint8(prng.Uint32()) part := make([]byte, prng.Int31n(500)) prng.Read(part) diff --git a/e2e/parse/partition/store_test.go b/e2e/parse/partition/store_test.go index 9a86fb17b65997dc08be57aeb63800a3ba775f4c..909de9b829d54e7b07fc4936ed82bdd2337ccc5f 100644 --- a/e2e/parse/partition/store_test.go +++ b/e2e/parse/partition/store_test.go @@ -20,7 +20,7 @@ import ( // Tests happy path of NewOrLoad. func TestNewOrLoad(t *testing.T) { - rootKv := versioned.NewKV(make(ekv.Memstore)) + rootKv := versioned.NewKV(ekv.MakeMemstore()) expectedStore := &Store{ multiParts: make(map[multiPartID]*multiPartMessage), activeParts: make(map[*multiPartMessage]bool), @@ -38,7 +38,7 @@ func TestNewOrLoad(t *testing.T) { // Tests happy path of Store.AddFirst. func TestStore_AddFirst(t *testing.T) { part := []byte("Test message.") - s := NewOrLoad(versioned.NewKV(ekv.Memstore{})) + s := NewOrLoad(versioned.NewKV(ekv.MakeMemstore())) msg, complete := s.AddFirst(id.NewIdFromString("User", id.User, t), catalog.XxMessage, 5, 0, 1, netTime.Now(), netTime.Now(), part, @@ -58,7 +58,7 @@ func TestStore_AddFirst(t *testing.T) { func TestStore_Add(t *testing.T) { part1 := []byte("Test message.") part2 := []byte("Second Sentence.") - s := NewOrLoad(versioned.NewKV(ekv.Memstore{})) + s := NewOrLoad(versioned.NewKV(ekv.MakeMemstore())) msg, complete := s.AddFirst(id.NewIdFromString("User", id.User, t), catalog.XxMessage, 5, 0, 2, netTime.Now(), netTime.Now(), part1, @@ -87,7 +87,7 @@ func TestStore_prune(t *testing.T) { // new message part1 := []byte("Test message.") part2 := []byte("Second Sentence.") - s := NewOrLoad(versioned.NewKV(ekv.Memstore{})) + s := NewOrLoad(versioned.NewKV(ekv.MakeMemstore())) partner1 := id.NewIdFromString("User", id.User, t) messageId1 := uint64(5) diff --git a/e2e/parse/partition_test.go b/e2e/parse/partition_test.go index 98970c881d67f9a69bc4dda4a0114f3644459055..e8440ef1a6f62b34ec4ef6a7378c56218157a88c 100644 --- a/e2e/parse/partition_test.go +++ b/e2e/parse/partition_test.go @@ -24,7 +24,7 @@ var ipsumTestStr = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cra // Test that NewPartitioner outputs a correctly made Partitioner func TestNewPartitioner(t *testing.T) { - p := NewPartitioner(versioned.NewKV(make(ekv.Memstore)), 4096) + p := NewPartitioner(versioned.NewKV(ekv.MakeMemstore()), 4096) if p.baseMessageSize != 4096 { t.Errorf("baseMessageSize content mismatch."+ @@ -57,7 +57,7 @@ func TestNewPartitioner(t *testing.T) { // Test that no error is returned running Partitioner.Partition. func TestPartitioner_Partition(t *testing.T) { - p := NewPartitioner(versioned.NewKV(make(ekv.Memstore)), len(ipsumTestStr)) + p := NewPartitioner(versioned.NewKV(ekv.MakeMemstore()), len(ipsumTestStr)) _, _, err := p.Partition( &id.DummyUser, catalog.XxMessage, netTime.Now(), []byte(ipsumTestStr)) @@ -68,7 +68,7 @@ func TestPartitioner_Partition(t *testing.T) { // Test that Partitioner.HandlePartition can handle a message part. func TestPartitioner_HandlePartition(t *testing.T) { - p := NewPartitioner(versioned.NewKV(make(ekv.Memstore)), len(ipsumTestStr)) + p := NewPartitioner(versioned.NewKV(ekv.MakeMemstore()), len(ipsumTestStr)) m := newMessagePart(1107, 1, []byte(ipsumTestStr)) _, _ = p.HandlePartition( @@ -80,7 +80,7 @@ func TestPartitioner_HandlePartition(t *testing.T) { // Test that HandlePartition can handle a first message part func TestPartitioner_HandleFirstPartition(t *testing.T) { - p := NewPartitioner(versioned.NewKV(make(ekv.Memstore)), len(ipsumTestStr)) + p := NewPartitioner(versioned.NewKV(ekv.MakeMemstore()), len(ipsumTestStr)) m := newFirstMessagePart( catalog.XxMessage, 1107, 1, netTime.Now(), []byte(ipsumTestStr)) diff --git a/e2e/processor.go b/e2e/processor.go index ea7d4aef499eb4b6c8ca012e5fc3c07695568020..1b6620149005c12ad456fc3b3b34ead31890a240 100644 --- a/e2e/processor.go +++ b/e2e/processor.go @@ -2,16 +2,16 @@ package e2e import ( "fmt" + "gitlab.com/elixxir/client/e2e/ratchet/partner/session" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/rounds" - "gitlab.com/elixxir/client/e2e/ratchet/partner/session" "gitlab.com/elixxir/primitives/format" ) type processor struct { - cy *session.Cypher + cy session.Cypher m *manager } diff --git a/e2e/ratchet/partner/interface.go b/e2e/ratchet/partner/interface.go index 02315b1ff7e4f63845f79a9feee3bd8668e79656..1074893e8ead14f8280133bd59a5276723b69339 100644 --- a/e2e/ratchet/partner/interface.go +++ b/e2e/ratchet/partner/interface.go @@ -29,9 +29,9 @@ type Manager interface { Contact() contact.Contact // PopSendCypher returns the key which is most likely to be successful for sending - PopSendCypher() (*session.Cypher, error) + PopSendCypher() (session.Cypher, error) // PopRekeyCypher returns a key which should be used for rekeying - PopRekeyCypher() (*session.Cypher, error) + PopRekeyCypher() (session.Cypher, error) // NewReceiveSession creates a new Receive session using the latest private key // this user has sent and the new public key received from the partner. If the diff --git a/e2e/ratchet/partner/manager.go b/e2e/ratchet/partner/manager.go index fbb33af91e1cb1b00508cb471422a41e925d46dd..ec7799363f2a107da4a49d42f8b2404d0dc0ac85 100644 --- a/e2e/ratchet/partner/manager.go +++ b/e2e/ratchet/partner/manager.go @@ -248,12 +248,12 @@ func (m *manager) NewSendSession(myPrivKey *cyclic.Int, } // PopSendCypher returns the key which is most likely to be successful for sending -func (m *manager) PopSendCypher() (*session.Cypher, error) { +func (m *manager) PopSendCypher() (session.Cypher, error) { return m.send.getKeyForSending() } // PopRekeyCypher returns a key which should be used for rekeying -func (m *manager) PopRekeyCypher() (*session.Cypher, error) { +func (m *manager) PopRekeyCypher() (session.Cypher, error) { return m.send.getKeyForRekey() } diff --git a/e2e/ratchet/partner/relationship.go b/e2e/ratchet/partner/relationship.go index 56b08a57137ac48f0fe27b953452dee0bbe2c01a..ff321d04becdce2e4ddeb9f18d520174b40f1b5c 100644 --- a/e2e/ratchet/partner/relationship.go +++ b/e2e/ratchet/partner/relationship.go @@ -249,7 +249,7 @@ func (r *relationship) GetNewest() *session.Session { } // returns the key which is most likely to be successful for sending -func (r *relationship) getKeyForSending() (*session.Cypher, error) { +func (r *relationship) getKeyForSending() (session.Cypher, error) { r.sendMux.Lock() defer r.sendMux.Unlock() s := r.getSessionForSending() @@ -325,7 +325,7 @@ func (r *relationship) TriggerNegotiation() []*session.Session { } // returns a key which should be used for rekeying -func (r *relationship) getKeyForRekey() (*session.Cypher, error) { +func (r *relationship) getKeyForRekey() (session.Cypher, error) { r.sendMux.Lock() defer r.sendMux.Unlock() s := r.getNewestRekeyableSession() diff --git a/e2e/ratchet/partner/relationship_test.go b/e2e/ratchet/partner/relationship_test.go index 9591ecea3193a0696b7910a64c130e537550a18c..5160844daf0c6c3dcc6e4ea2844065f42f2b612b 100644 --- a/e2e/ratchet/partner/relationship_test.go +++ b/e2e/ratchet/partner/relationship_test.go @@ -875,7 +875,7 @@ func makeTestRelationshipManager(t *testing.T) (*manager, *versioned.KV) { mySIDHPrivKey.Generate(rng) mySIDHPrivKey.GeneratePublicKey(mySIDHPubKey) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) frng := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) return &manager{ kv: kv, diff --git a/e2e/ratchet/partner/session/cypher.go b/e2e/ratchet/partner/session/cypher.go index fe713eb08c2cf07f563e9ebcfee80b8f0e94c5e2..ef4094a40f0967f199dd4f89786fd6a2dcda5459 100644 --- a/e2e/ratchet/partner/session/cypher.go +++ b/e2e/ratchet/partner/session/cypher.go @@ -19,7 +19,7 @@ import ( ) // GenerateE2ESessionBaseKey returns the baseKey symmetric encryption key root. -// The baseKey is created by hashing the results of the diffie-helman (DH) key +// The baseKey is created by hashing the results of the Diffie-Hellman (DH) key // exchange with the post-quantum secure Supersingular Isogeny DH exchange // results. func GenerateE2ESessionBaseKey(myDHPrivKey, theirDHPubKey *cyclic.Int, @@ -50,61 +50,85 @@ func GenerateE2ESessionBaseKey(myDHPrivKey, theirDHPubKey *cyclic.Int, return baseKey } -type Cypher struct { - // Links +// Cypher manages the cryptographic material for E2E messages and provides +// methods to encrypt and decrypt them. +type Cypher interface { + + // GetSession return pointers to higher level management structures. + GetSession() *Session + + // Fingerprint returns the Cypher key fingerprint, if it has it. Otherwise, + // it generates and returns a new one. + Fingerprint() format.Fingerprint + + // Encrypt uses the E2E key to encrypt the message to its intended + // recipient. It also properly populates the associated data, including the + // MAC, fingerprint, and encrypted timestamp. + Encrypt(contents []byte) (ecrContents, mac []byte) + + // Decrypt uses the E2E key to decrypt the message. It returns an error in + // case of HMAC verification failure or in case of a decryption error + // (related to padding). + Decrypt(msg format.Message) ([]byte, error) + + // Use sets the key as used. It cannot be used again. + Use() +} + +// cypher adheres to the Cypher interface. +type cypher struct { session *Session fp *format.Fingerprint - // keyNum is the index of the key by order of creation - // it is used to identify the key in the key.Session + // keyNum is the index of the key by order of creation. It is used to + // identify the key in the cypher.Session. keyNum uint32 } -func newKey(session *Session, keynum uint32) *Cypher { - return &Cypher{ +func newCypher(session *Session, keyNum uint32) *cypher { + return &cypher{ session: session, - keyNum: keynum, + keyNum: keyNum, } } -// return pointers to higher level management structures -func (k *Cypher) GetSession() *Session { return k.session } +// GetSession return pointers to higher level management structures. +func (k *cypher) GetSession() *Session { return k.session } -// returns the key fingerprint if it has it, otherwise generates it -// this function does not memoize the fingerprint if it doesnt have it because -// in most cases it will not be used for a long time and as a result should not -// be stored in ram. -func (k *Cypher) Fingerprint() format.Fingerprint { +// Fingerprint returns the Cypher key fingerprint, if it has it. Otherwise, it +// generates and returns a new one. This function does not memoize the +// fingerprint if it does not have it because in most cases, it will not be used +// for a long time and as a result, should not be stored in memory. +func (k *cypher) Fingerprint() format.Fingerprint { if k.fp != nil { return *k.fp } - return e2eCrypto.DeriveKeyFingerprint(k.session.baseKey, k.keyNum, - k.session.relationshipFingerprint) + return e2eCrypto.DeriveKeyFingerprint( + k.session.baseKey, k.keyNum, k.session.relationshipFingerprint) } -// the E2E key to encrypt msg to its intended recipient -// It also properly populates the associated data, including the MAC, fingerprint, -// and encrypted timestamp -func (k *Cypher) Encrypt(contents []byte) (ecrContents, mac []byte) { +// Encrypt uses the E2E key to encrypt the message to its intended recipient. It +// also properly populates the associated data, including the MAC, fingerprint, +// and encrypted timestamp. +func (k *cypher) Encrypt(contents []byte) (ecrContents, mac []byte) { fp := k.Fingerprint() key := k.generateKey() // encrypt the payload ecrContents = e2eCrypto.Crypt(key, fp, contents) - // create the MAC - // MAC is HMAC(key, ciphertext) + // Create the MAC, which is HMAC(key, ciphertext) // Currently, the MAC doesn't include any of the associated data mac = hash.CreateHMAC(ecrContents, key[:]) return ecrContents, mac } -// Decrypt uses the E2E key to decrypt the message -// It returns an error in case of HMAC verification failure -// or in case of a decryption error (related to padding) -func (k *Cypher) Decrypt(msg format.Message) ([]byte, error) { +// Decrypt uses the E2E key to decrypt the message. It returns an error in case +// of HMAC verification failure or in case of a decryption error (related to +// padding). +func (k *cypher) Decrypt(msg format.Message) ([]byte, error) { fp := k.Fingerprint() key := k.generateKey() @@ -120,13 +144,13 @@ func (k *Cypher) Decrypt(msg format.Message) ([]byte, error) { } // Use sets the key as used. It cannot be used again. -func (k *Cypher) Use() { +func (k *cypher) Use() { k.session.useKey(k.keyNum) } -// generateKey derives the current e2e key from the baseKey and the index -// keyNum and returns it -func (k *Cypher) generateKey() e2eCrypto.Key { - return e2eCrypto.DeriveKey(k.session.baseKey, k.keyNum, - k.session.relationshipFingerprint) +// generateKey derives the current e2e key from the baseKey and the index keyNum +// and returns it. +func (k *cypher) generateKey() e2eCrypto.Key { + return e2eCrypto.DeriveKey( + k.session.baseKey, k.keyNum, k.session.relationshipFingerprint) } diff --git a/e2e/ratchet/partner/session/cypherHandler.go b/e2e/ratchet/partner/session/cypherHandler.go index 535d5ac63dd39bcfff24db572633874a87d2b02e..9bba56fec7c40ab1d0103cceb21aa0ab542d042a 100644 --- a/e2e/ratchet/partner/session/cypherHandler.go +++ b/e2e/ratchet/partner/session/cypherHandler.go @@ -1,6 +1,6 @@ package session type CypherHandler interface { - AddKey(k *Cypher) - DeleteKey(k *Cypher) + AddKey(cy Cypher) + DeleteKey(cy Cypher) } diff --git a/e2e/ratchet/partner/session/cypher_test.go b/e2e/ratchet/partner/session/cypher_test.go index f78cfb879baca57c2c9661b4bb390ff48e798df9..b6c92de98bc3dcaf1b6e18237df418ad246dbe5b 100644 --- a/e2e/ratchet/partner/session/cypher_test.go +++ b/e2e/ratchet/partner/session/cypher_test.go @@ -39,11 +39,11 @@ func TestGenerateE2ESessionBaseKey(t *testing.T) { // SIDH keys pubA := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSidhA) privA := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSidhA) - privA.Generate(myRng) + _ = privA.Generate(myRng) privA.GeneratePublicKey(pubA) pubB := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSidhB) privB := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSidhB) - privB.Generate(myRng) + _ = privB.Generate(myRng) privB.GeneratePublicKey(pubB) myRng.Close() @@ -60,47 +60,45 @@ func TestGenerateE2ESessionBaseKey(t *testing.T) { } -// Happy path of newKey(). -func Test_newKey(t *testing.T) { +// Happy path of newCypher. +func Test_newCypher(t *testing.T) { s, _ := makeTestSession() - expectedKey := &Cypher{ + expectedKey := &cypher{ session: s, keyNum: rand.Uint32(), } - testKey := newKey(expectedKey.session, expectedKey.keyNum) + testKey := newCypher(expectedKey.session, expectedKey.keyNum) if !reflect.DeepEqual(expectedKey, testKey) { - t.Errorf("newKey() did not produce the expected Key."+ - "\n\texpected: %v\n\treceived: %v", + t.Errorf("Unexpected new key.\nexpected: %+v\nreceived: %v", expectedKey, testKey) } } -// Happy path of Key.GetSession(). -func TestKey_GetSession(t *testing.T) { +// Happy path of cypher.GetSession. +func Test_cypher_GetSession(t *testing.T) { s, _ := makeTestSession() - k := newKey(s, rand.Uint32()) + cy := newCypher(s, rand.Uint32()) - testSession := k.GetSession() + testSession := cy.GetSession() - if !reflect.DeepEqual(k.session, testSession) { - - if !reflect.DeepEqual(k.session, testSession) { + if !reflect.DeepEqual(cy.session, testSession) { + if !reflect.DeepEqual(cy.session, testSession) { t.Errorf("GetSession() did not produce the expected Session."+ "\n\texpected: %v\n\treceived: %v", - k.session, testSession) + cy.session, testSession) } } } -// Happy path of Key.Fingerprint(). -func TestKey_Fingerprint(t *testing.T) { +// Happy path of cypher.Fingerprint. +func Test_cypher_Fingerprint(t *testing.T) { s, _ := makeTestSession() - k := newKey(s, rand.Uint32()) + cy := newCypher(s, rand.Uint32()) // Generate test and expected fingerprints testFingerprint := getFingerprint() @@ -109,13 +107,13 @@ func TestKey_Fingerprint(t *testing.T) { expectedFP format.Fingerprint }{ {testFingerprint, *testFingerprint}, - {nil, e2e.DeriveKeyFingerprint(k.session.baseKey, k.keyNum)}, + {nil, e2e.DeriveKeyFingerprint(cy.session.baseKey, cy.keyNum)}, } // Test cases for _, data := range testData { - k.fp = data.testFP - testFP := k.Fingerprint() + cy.fp = data.testFP + testFP := cy.Fingerprint() if !reflect.DeepEqual(data.expectedFP, testFP) { t.Errorf("Fingerprint() did not produce the expected Fingerprint."+ @@ -125,7 +123,7 @@ func TestKey_Fingerprint(t *testing.T) { } } -func TestKey_EncryptDecrypt(t *testing.T) { +func Test_cypher_EncryptDecrypt(t *testing.T) { const numTests = 100 @@ -134,7 +132,7 @@ func TestKey_EncryptDecrypt(t *testing.T) { prng := rand.New(rand.NewSource(42)) for i := 0; i < numTests; i++ { - // finalizeKeyNegotation the baseKey and session + // Finalize Key negotiation the baseKey and session privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng) publicKey := dh.GeneratePublicKey(privateKey, grp) baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp) @@ -143,30 +141,30 @@ func TestKey_EncryptDecrypt(t *testing.T) { baseKey: baseKey, } - //create the keys - k := newKey(s, prng.Uint32()) + // Create the cypher + cy := newCypher(s, prng.Uint32()) - //make the message to be encrypted + // Make the message to be encrypted msg := format.NewMessage(grp.GetP().ByteLen()) - //set the contents + // Set the contents contents := make([]byte, msg.ContentsSize()) prng.Read(contents) msg.SetContents(contents) // Encrypt - contentsEnc, mac := k.Encrypt(msg.GetContents()) + contentsEnc, mac := cy.Encrypt(msg.GetContents()) - //make the encrypted message + // Make the encrypted message ecrMsg := format.NewMessage(grp.GetP().ByteLen()) - ecrMsg.SetKeyFP(k.Fingerprint()) + ecrMsg.SetKeyFP(cy.Fingerprint()) ecrMsg.SetContents(contentsEnc) ecrMsg.SetMac(mac) // Decrypt - contentsDecr, err := k.Decrypt(ecrMsg) + contentsDecr, err := cy.Decrypt(ecrMsg) if err != nil { - t.Fatalf("Decrypt error: %v", err) + t.Fatalf("Decrypt error: %+v", err) } if !bytes.Equal(contentsDecr, msg.GetContents()) { @@ -176,35 +174,34 @@ func TestKey_EncryptDecrypt(t *testing.T) { } } -// Happy path of Key.Use() -func TestKey_denoteUse(t *testing.T) { +// Happy path of cypher.Use. +func Test_cypher_Use(t *testing.T) { s, _ := makeTestSession() keyNum := uint32(rand.Int31n(31)) - k := newKey(s, keyNum) + k := newCypher(s, keyNum) k.Use() if !k.session.keyState.Used(keyNum) { - t.Errorf("Use() did not use the key") + t.Errorf("Use did not use the key") } } -// Happy path of generateKey(). -func TestKey_generateKey(t *testing.T) { +// Happy path of cypher.generateKey. +func Test_cypher_generateKey(t *testing.T) { s, _ := makeTestSession() - k := newKey(s, rand.Uint32()) + k := newCypher(s, rand.Uint32()) // Generate test CryptoType values and expected keys expectedKey := e2e.DeriveKey(k.session.baseKey, k.keyNum) testKey := k.generateKey() if !reflect.DeepEqual(expectedKey, testKey) { - t.Errorf("generateKey() did not produce the expected e2e key."+ - "\n\texpected: %v\n\treceived: %v", - expectedKey, testKey) + t.Errorf("generateKey did not produce the expected e2e key."+ + "\nexpected: %v\nreceived: %v", expectedKey, testKey) } } diff --git a/e2e/ratchet/partner/session/session.go b/e2e/ratchet/partner/session/session.go index 234c8b357a58f8bb5b328a49e631fc41effe44f6..58d1ca8ac4ca71d2adbc3f0eaf554e1507dfee0c 100644 --- a/e2e/ratchet/partner/session/session.go +++ b/e2e/ratchet/partner/session/session.go @@ -327,7 +327,7 @@ func (s *Session) GetPartner() *id.ID { // PopKey Pops the first unused key, skipping any which are denoted as used. // will return if the remaining keys are designated as rekeys -func (s *Session) PopKey() (*Cypher, error) { +func (s *Session) PopKey() (Cypher, error) { if s.keyState.GetNumAvailable() <= uint32(s.e2eParams.NumRekeys) { return nil, errors.New("no more keys left, remaining reserved " + "for rekey") @@ -337,18 +337,18 @@ func (s *Session) PopKey() (*Cypher, error) { return nil, err } - return newKey(s, keyNum), nil + return newCypher(s, keyNum), nil } // PopReKey Pops the first unused key, skipping any which are denoted as used, // including keys designated for rekeys -func (s *Session) PopReKey() (*Cypher, error) { +func (s *Session) PopReKey() (Cypher, error) { keyNum, err := s.keyState.Next() if err != nil { return nil, err } - return newKey(s, keyNum), nil + return newCypher(s, keyNum), nil } // todo - doscstring @@ -600,12 +600,12 @@ func (s *Session) buildChildKeys() { } //returns key objects for all unused keys -func (s *Session) getUnusedKeys() []*Cypher { +func (s *Session) getUnusedKeys() []Cypher { keyNums := s.keyState.GetUnusedKeyNums() - keys := make([]*Cypher, len(keyNums)) + keys := make([]Cypher, len(keyNums)) for i, keyNum := range keyNums { - keys[i] = newKey(s, keyNum) + keys[i] = newCypher(s, keyNum) } return keys diff --git a/e2e/ratchet/partner/session/session_test.go b/e2e/ratchet/partner/session/session_test.go index c22fdc582fcebf1a29b021bdafc6ed8b5cebd127..cd72eee94c90ab559435e8dded5ecc0dbf346094 100644 --- a/e2e/ratchet/partner/session/session_test.go +++ b/e2e/ratchet/partner/session/session_test.go @@ -172,10 +172,12 @@ func TestSession_Serialization(t *testing.T) { // PopKey should return a new key from this session func TestSession_PopKey(t *testing.T) { s, _ := makeTestSession() - key, err := s.PopKey() + keyInterface, err := s.PopKey() if err != nil { t.Fatal(err) } + key := keyInterface.(*cypher) + if key == nil { t.Error("PopKey should have returned non-nil key") } @@ -232,10 +234,12 @@ func TestSession_PopKey_Error(t *testing.T) { // There's no boundary, except for the number of keyNums in the state vector func TestSession_PopReKey(t *testing.T) { s, _ := makeTestSession() - key, err := s.PopReKey() + keyInterface, err := s.PopReKey() if err != nil { t.Fatal("PopKey should have returned an error") } + key := keyInterface.(*cypher) + if key == nil { t.Error("Key should be non-nil") } diff --git a/e2e/ratchet/partner/session/testUtils.go b/e2e/ratchet/partner/session/testUtils.go index 2a5abb32725b180bd9300e98c0cb78946dddc90d..61d82ea1f651820f0a9d76bcec396f094c820d4e 100644 --- a/e2e/ratchet/partner/session/testUtils.go +++ b/e2e/ratchet/partner/session/testUtils.go @@ -97,7 +97,7 @@ func makeTestSession() (*Session, *versioned.KV) { rekeyThreshold: 5, partner: &id.ID{}, grp: grp, - cyHandler: mockCyHandler{}, + cyHandler: &mockCyHandler{}, rng: fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG), } var err error @@ -147,13 +147,7 @@ func cmpSerializedFields(a *Session, b *Session) error { return nil } -type mockCyHandler struct { -} - -func (m mockCyHandler) AddKey(k *Cypher) { - return -} +type mockCyHandler struct{} -func (m mockCyHandler) DeleteKey(k *Cypher) { - return -} +func (m *mockCyHandler) AddKey(Cypher) {} +func (m *mockCyHandler) DeleteKey(Cypher) {} diff --git a/e2e/ratchet/partner/utils.go b/e2e/ratchet/partner/utils.go index 0bfc02fbc28338f628d967f2f092a32c7deb951b..aa4c7d90e267848c50d53ce32ae87c63cb6febc9 100644 --- a/e2e/ratchet/partner/utils.go +++ b/e2e/ratchet/partner/utils.go @@ -73,11 +73,11 @@ func (p *testManager) Contact() contact.Contact { panic("implement me") } -func (p *testManager) PopSendCypher() (*session.Cypher, error) { +func (p *testManager) PopSendCypher() (session.Cypher, error) { panic("implement me") } -func (p *testManager) PopRekeyCypher() (*session.Cypher, error) { +func (p *testManager) PopRekeyCypher() (session.Cypher, error) { panic("implement me") } diff --git a/e2e/ratchet/partner/utils_test.go b/e2e/ratchet/partner/utils_test.go index 910384d7ce98fa557f936f13e5b94bfbbe7650a6..17d28200b7c79868c7ca3f156b2d8f897b635e02 100644 --- a/e2e/ratchet/partner/utils_test.go +++ b/e2e/ratchet/partner/utils_test.go @@ -21,13 +21,9 @@ import ( type mockCyHandler struct { } -func (m mockCyHandler) AddKey(k *session.Cypher) { - return -} +func (m mockCyHandler) AddKey(session.Cypher) {} -func (m mockCyHandler) DeleteKey(k *session.Cypher) { - return -} +func (m mockCyHandler) DeleteKey(session.Cypher) {} func getGroup() *cyclic.Group { e2eGrp := cyclic.NewGroup( @@ -65,14 +61,20 @@ func newTestManager(t *testing.T) (manager, *versioned.KV) { partnerSIDHPrivKey := util.NewSIDHPrivateKey(sidh.KeyVariantSidhA) partnerSIDHPubKey := util.NewSIDHPublicKey(sidh.KeyVariantSidhA) - partnerSIDHPrivKey.Generate(rng.GetStream()) + err := partnerSIDHPrivKey.Generate(rng.GetStream()) + if err != nil { + t.Errorf("Failed to generate private key: %+v", err) + } partnerSIDHPrivKey.GeneratePublicKey(partnerSIDHPubKey) mySIDHPrivKey := util.NewSIDHPrivateKey(sidh.KeyVariantSidhB) mySIDHPubKey := util.NewSIDHPublicKey(sidh.KeyVariantSidhB) - mySIDHPrivKey.Generate(rng.GetStream()) + err = mySIDHPrivKey.Generate(rng.GetStream()) + if err != nil { + t.Errorf("Failed to generate private key: %+v", err) + } mySIDHPrivKey.GeneratePublicKey(mySIDHPubKey) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) partnerID := id.NewIdFromString("partner", id.User, t) myId := id.NewIdFromString("me", id.User, t) diff --git a/e2e/ratchet/ratchet.go b/e2e/ratchet/ratchet.go index 92ff4ba4250d23138c5559bd90cc4e5a584080ce..1a272b9a942c63d6fe88a492040eda68da19180c 100644 --- a/e2e/ratchet/ratchet.go +++ b/e2e/ratchet/ratchet.go @@ -44,18 +44,18 @@ type Ratchet struct { cyHandler session.CypherHandler rng *fastRNG.StreamGenerator - //services handler + // services handler services map[string]message.Processor - sInteface Services - servicesmux sync.RWMutex + sInterface Services + servicesMux sync.RWMutex kv *versioned.KV } -// New creates a new store for the passed user id and private key. +// New creates a new store for the passed user ID and private key. // The store can then be accessed by calling LoadStore. // Does not create at a unique prefix, if multiple Ratchets are needed, make -// sure to add a uint prefix to the KV before instantiation. +// sure to add an uint prefix to the KV before instantiation. func New(kv *versioned.KV, myID *id.ID, privKey *cyclic.Int, grp *cyclic.Group) error { @@ -125,7 +125,7 @@ func (r *Ratchet) AddPartner(partnerID *id.ID, partnerID, err) } - //add services for the manager + // Add services for the manager r.add(m) return m, nil @@ -158,7 +158,7 @@ func (r *Ratchet) DeletePartner(partnerID *id.ID) error { partnerID) } - //delete services + // Delete services r.delete(m) delete(r.managers, *partnerID) diff --git a/e2e/ratchet/ratchet_test.go b/e2e/ratchet/ratchet_test.go index 2fa7f095f06a294587b2a3c89a0bc25dd9919dc7..a1cdbcb881b893dd5ebb4ce470078236e62b9f40 100644 --- a/e2e/ratchet/ratchet_test.go +++ b/e2e/ratchet/ratchet_test.go @@ -30,7 +30,7 @@ import ( func TestNewStore(t *testing.T) { grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2)) privKey := grp.NewInt(57) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore := &Ratchet{ managers: make(map[id.ID]partner.Manager), advertisedDHPrivateKey: privKey, @@ -68,7 +68,7 @@ func TestLoadStore(t *testing.T) { } store, err := Load(kv, &id.ID{}, - expectedRatchet.grp, expectedRatchet.cyHandler, expectedRatchet.sInteface, + expectedRatchet.grp, expectedRatchet.cyHandler, expectedRatchet.sInterface, expectedRatchet.rng) if err != nil { t.Errorf("LoadStore() produced an error: %v", err) diff --git a/e2e/ratchet/serviceList.go b/e2e/ratchet/serviceList.go index 0bd23650b751e167b92a7eb608b92d2a4b0c6483..b382b5aced663ae8384b497052c29d57eb0ba71e 100644 --- a/e2e/ratchet/serviceList.go +++ b/e2e/ratchet/serviceList.go @@ -9,31 +9,31 @@ import ( // Services is a subsection of the cmix.Manager interface used for services type Services interface { - AddService(AddService *id.ID, newService message.Service, - response message.Processor) - DeleteService(clientID *id.ID, toDelete message.Service, - processor message.Processor) + AddService( + clientID *id.ID, newService message.Service, response message.Processor) + DeleteService( + clientID *id.ID, toDelete message.Service, processor message.Processor) } func (r *Ratchet) add(m partner.Manager) { - r.servicesmux.RLock() - defer r.servicesmux.RUnlock() + r.servicesMux.RLock() + defer r.servicesMux.RUnlock() for tag, process := range r.services { - r.sInteface.AddService(r.myID, m.MakeService(tag), process) + r.sInterface.AddService(r.myID, m.MakeService(tag), process) } } func (r *Ratchet) delete(m partner.Manager) { - r.servicesmux.RLock() - defer r.servicesmux.RUnlock() + r.servicesMux.RLock() + defer r.servicesMux.RUnlock() for tag, process := range r.services { - r.sInteface.DeleteService(r.myID, m.MakeService(tag), process) + r.sInterface.DeleteService(r.myID, m.MakeService(tag), process) } } func (r *Ratchet) AddService(tag string, processor message.Processor) error { - r.servicesmux.Lock() - defer r.servicesmux.Unlock() + r.servicesMux.Lock() + defer r.servicesMux.Unlock() //add the services to the list if _, exists := r.services[tag]; exists { return errors.Errorf("Cannot add more than one service '%s'", tag) @@ -42,15 +42,15 @@ func (r *Ratchet) AddService(tag string, processor message.Processor) error { //add a service for every manager for _, m := range r.managers { - r.sInteface.AddService(r.myID, m.MakeService(tag), processor) + r.sInterface.AddService(r.myID, m.MakeService(tag), processor) } return nil } func (r *Ratchet) RemoveService(tag string) error { - r.servicesmux.Lock() - defer r.servicesmux.Unlock() + r.servicesMux.Lock() + defer r.servicesMux.Unlock() oldServiceProcess, exists := r.services[tag] if !exists { @@ -61,7 +61,7 @@ func (r *Ratchet) RemoveService(tag string) error { delete(r.services, tag) for _, m := range r.managers { - r.sInteface.DeleteService(r.myID, m.MakeService(tag), oldServiceProcess) + r.sInterface.DeleteService(r.myID, m.MakeService(tag), oldServiceProcess) } return nil diff --git a/e2e/ratchet/storage.go b/e2e/ratchet/storage.go index 146a51752419746c070eda7fd640e734899f3d79..999938dc7ec1784743163acfd34a550a24134544 100644 --- a/e2e/ratchet/storage.go +++ b/e2e/ratchet/storage.go @@ -49,10 +49,10 @@ func Load(kv *versioned.KV, myID *id.ID, grp *cyclic.Group, kv: kv, - cyHandler: cyHandler, - grp: grp, - rng: rng, - sInteface: services, + cyHandler: cyHandler, + grp: grp, + rng: rng, + sInterface: services, } obj, err := kv.Get(storeKey, currentStoreVersion) diff --git a/e2e/ratchet/utils_test.go b/e2e/ratchet/utils_test.go index 02c9ed79297487e013c72174aea994d2840ccb3e..0a0cc267f842f98cd43c568a139b63feb3808f8b 100644 --- a/e2e/ratchet/utils_test.go +++ b/e2e/ratchet/utils_test.go @@ -3,7 +3,6 @@ package ratchet import ( "io" "reflect" - "strings" "testing" "github.com/cloudflare/circl/dh/sidh" @@ -24,7 +23,7 @@ import ( func makeTestRatchet() (*Ratchet, *versioned.KV, error) { grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2)) privKey := grp.NewInt(57) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) err := New(kv, &id.ID{}, privKey, grp) if err != nil { @@ -52,7 +51,7 @@ func managersEqual(expected, received partner.Manager, t *testing.T) bool { equal = false } - if !strings.EqualFold(expected.ConnectionFingerprint(), received.ConnectionFingerprint()) { + if !reflect.DeepEqual(expected.ConnectionFingerprint(), received.ConnectionFingerprint()) { t.Errorf("Did not Receive expected Manager.Receive."+ "\n\texpected: %+v\n\treceived: %+v", expected.ConnectionFingerprint(), received.ConnectionFingerprint()) @@ -96,11 +95,11 @@ func genSidhKeys(rng io.Reader, variant sidh.KeyVariant) (*sidh.PrivateKey, *sid type mockCyHandler struct{} -func (m mockCyHandler) AddKey(k *session.Cypher) { +func (m mockCyHandler) AddKey(k session.Cypher) { return } -func (m mockCyHandler) DeleteKey(k *session.Cypher) { +func (m mockCyHandler) DeleteKey(k session.Cypher) { return } diff --git a/e2e/rekey/confirm_test.go b/e2e/rekey/confirm_test.go index fe9115392b6376c87de4805c76a685dacbbe5881..24de5fda04353b367d1d222d1517f48c09fb7cde 100644 --- a/e2e/rekey/confirm_test.go +++ b/e2e/rekey/confirm_test.go @@ -33,7 +33,7 @@ func TestHandleConfirm(t *testing.T) { rng := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) myID := id.NewIdFromString("zezima", id.User, t) - kv := versioned.NewKV(ekv.Memstore{}) + kv := versioned.NewKV(ekv.MakeMemstore()) // Maintain an ID for bob bobID := id.NewIdFromBytes([]byte("test"), t) diff --git a/e2e/rekey/exchange_test.go b/e2e/rekey/exchange_test.go index 6e4f3036144fb3fcf33617dfc26fcb9c3021bb27..2967d81cbf2cea380ae1c0e64393fbe493fa1851 100644 --- a/e2e/rekey/exchange_test.go +++ b/e2e/rekey/exchange_test.go @@ -41,7 +41,7 @@ func TestFullExchange(t *testing.T) { rng := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) aliceID = id.NewIdFromString("zezima", id.User, t) - kv := versioned.NewKV(ekv.Memstore{}) + kv := versioned.NewKV(ekv.MakeMemstore()) // Maintain an ID for bob bobID = id.NewIdFromBytes([]byte("test"), t) diff --git a/e2e/rekey/rekey_test.go b/e2e/rekey/rekey_test.go index 0e35f8124299ec22fe11b02f9261c88348d7b524..ac5afe5fd8b9feb256e7b47c7dccc1efd85500be 100644 --- a/e2e/rekey/rekey_test.go +++ b/e2e/rekey/rekey_test.go @@ -39,7 +39,7 @@ func TestRekey(t *testing.T) { bobID = id.NewIdFromUInt(rand.Uint64(), id.User, t) - kv := versioned.NewKV(ekv.Memstore{}) + kv := versioned.NewKV(ekv.MakeMemstore()) err := ratchet.New(kv, aliceID, alicePrivKey, grp) if err != nil { diff --git a/e2e/rekey/trigger_test.go b/e2e/rekey/trigger_test.go index 7d6586ac6dc2d63c6ccab8f7699ecbdf7bfcaa1d..8e9ab6658eb627babb1dd9acacd0150222f1a71c 100644 --- a/e2e/rekey/trigger_test.go +++ b/e2e/rekey/trigger_test.go @@ -70,7 +70,7 @@ func TestHandleTrigger(t *testing.T) { // Maintain an ID for bob bobID = id.NewIdFromBytes([]byte("test"), t) aliceID = id.NewIdFromString("zezima", id.User, t) - kv := versioned.NewKV(ekv.Memstore{}) + kv := versioned.NewKV(ekv.MakeMemstore()) err := ratchet.New(kv, aliceID, alicePrivKey, grp) if err != nil { diff --git a/e2e/rekey/utils_test.go b/e2e/rekey/utils_test.go index c7296ecd26fe92a207e39cf43140805c4af6710c..e30b22c255aa47c36dd3cb917c2c34a7a84219ef 100644 --- a/e2e/rekey/utils_test.go +++ b/e2e/rekey/utils_test.go @@ -199,16 +199,10 @@ func (mci *mockCommsInstance) GetRoundEvents() *ds.RoundEvents { return mci.RoundEvents } -type mockCyHandler struct { -} +type mockCyHandler struct{} -func (m mockCyHandler) AddKey(k *session2.Cypher) { - return -} - -func (m mockCyHandler) DeleteKey(k *session2.Cypher) { - return -} +func (m mockCyHandler) AddKey(session2.Cypher) {} +func (m mockCyHandler) DeleteKey(session2.Cypher) {} type mockServiceHandler struct { } @@ -224,6 +218,11 @@ func (m mockServiceHandler) DeleteService(clientID *id.ID, toDelete message.Serv type mockNetManager struct{} +func (m *mockNetManager) Connect(ndf *ndf.NetworkDefinition) error { + // TODO implement me + panic("implement me") +} + func (m *mockNetManager) GetIdentity(get *id.ID) (identity.TrackedID, error) { //TODO implement me panic("implement me") diff --git a/e2e/sendE2E.go b/e2e/sendE2E.go index e2f6ddeeb40fe4e1df555d69129674c0022f3865..2a50201de5e20d75eb41602f194d58e99a0d6af1 100644 --- a/e2e/sendE2E.go +++ b/e2e/sendE2E.go @@ -19,70 +19,66 @@ import ( ) func (m *manager) SendE2E(mt catalog.MessageType, recipient *id.ID, - payload []byte, params Params) ([]id.Round, e2e.MessageID, - time.Time, error) { + payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error) { if !m.net.IsHealthy() { - return nil, e2e.MessageID{}, time.Time{}, errors.New("cannot " + - "sendE2E when network is not healthy") + return nil, e2e.MessageID{}, time.Time{}, + errors.New("cannot sendE2E when network is not healthy") } handleCritical := params.Critical if handleCritical { m.crit.AddProcessing(mt, recipient, payload, params) - // set critical to false so the network layer doesnt - // make the messages critical as well + // Set critical to false so that the network layer does not make the + // messages critical as well params.Critical = false } - rnds, msgID, t, err := m.sendE2E(mt, recipient, payload, params) + rounds, msgID, t, err := m.sendE2E(mt, recipient, payload, params) if handleCritical { - m.crit.handle(mt, recipient, payload, rnds, err) + m.crit.handle(mt, recipient, payload, rounds, err) } - return rnds, msgID, t, err + return rounds, msgID, t, err } func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, - payload []byte, params Params) ([]id.Round, e2e.MessageID, - time.Time, error) { + payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error) { ts := netTime.Now() partitions, internalMsgId, err := m.partitioner.Partition(recipient, mt, ts, payload) if err != nil { - err = errors.WithMessage(err, "failed to send unsafe message") - return nil, e2e.MessageID{}, time.Time{}, err + return nil, e2e.MessageID{}, time.Time{}, + errors.WithMessage(err, "failed to send unsafe message") } - jww.INFO.Printf("E2E sending %d messages to %s", - len(partitions), recipient) + jww.INFO.Printf("E2E sending %d messages to %s", len(partitions), recipient) - // When sending E2E messages, we first partition into cMix packets and - // then send each partition over cMix. + // When sending E2E messages, we first partition into cMix packets and then + // send each partition over cMix roundIds := make([]id.Round, len(partitions)) errCh := make(chan error, len(partitions)) - // The Key manager for the partner (recipient) ensures single - // use of each key negotiated for the ratchet. + // The Key manager for the partner (recipient) ensures single use of each + // key negotiated for the ratchet partner, err := m.Ratchet.GetPartner(recipient) if err != nil { - err = errors.WithMessagef(err, "cannot send E2E message "+ - "no relationship found with %s", recipient) - return nil, e2e.MessageID{}, time.Time{}, err + return nil, e2e.MessageID{}, time.Time{}, errors.WithMessagef(err, + "cannot send E2E message no relationship found with %s", recipient) } - msgID := e2e.NewMessageID(partner.SendRelationshipFingerprint(), - internalMsgId) + msgID := e2e.NewMessageID( + partner.SendRelationshipFingerprint(), internalMsgId) wg := sync.WaitGroup{} for i, p := range partitions { if mt != catalog.KeyExchangeTrigger { - // check if any rekeys need to happen and trigger them - rekeySendFunc := func(mt catalog.MessageType, recipient *id.ID, payload []byte, - cmixParams cmix.CMIXParams) ( + // Check if any rekeys need to happen and trigger them + rekeySendFunc := func(mt catalog.MessageType, recipient *id.ID, + payload []byte, cmixParams cmix.CMIXParams) ( []id.Round, e2e.MessageID, time.Time, error) { par := GetDefaultParams() par.CMIXParams = cmixParams @@ -92,32 +88,30 @@ func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, m.events, partner, m.rekeyParams, 1*time.Minute) } - var keyGetter func() (*session.Cypher, error) + var keyGetter func() (session.Cypher, error) if params.Rekey { keyGetter = partner.PopRekeyCypher } else { keyGetter = partner.PopSendCypher } - // FIXME: remove this wait, it is weird. Why is it - // here? we cant remember. - key, err := waitForKey(keyGetter, params.KeyGetRetryCount, - params.KeyGeRetryDelay, params.Stop, recipient, - format.DigestContents(p), i) + // FIXME: remove this wait, it is weird. Why is it here? we cant remember. + key, err := waitForKey( + keyGetter, params.KeyGetRetryCount, params.KeyGeRetryDelay, + params.Stop, recipient, format.DigestContents(p), i) if err != nil { - err = errors.WithMessagef(err, - "Failed to get key for end to end encryption") - return nil, e2e.MessageID{}, time.Time{}, err + return nil, e2e.MessageID{}, time.Time{}, errors.WithMessagef(err, + "Failed to get key for end-to-end encryption") } - // This does not encrypt for cMix but - // instead end to end encrypts the cmix message + // This does not encrypt for cMix but instead end-to-end encrypts the + // cMix message contentsEnc, mac := key.Encrypt(p) - jww.INFO.Printf("E2E sending %d/%d to %s with key fp: "+ - "%s, msgID: %s (msgDigest %s)", - i+i, len(partitions), recipient, - key.Fingerprint(), msgID, format.DigestContents(p)) + jww.INFO.Printf( + "E2E sending %d/%d to %s with key fp: %s, msgID: %s (msgDigest %s)", + i+i, len(partitions), recipient, key.Fingerprint(), msgID, + format.DigestContents(p)) var s message.Service if i == len(partitions)-1 { @@ -126,14 +120,13 @@ func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, s = partner.MakeService(params.ServiceTag) } - // We send each partition in it's own thread here, some - // may send in round X, others in X+1 or X+2, and so on. + // We send each partition in its own thread here; some may send in round + // X, others in X+1 or X+2, and so on wg.Add(1) go func(i int) { var err error roundIds[i], _, err = m.net.Send(recipient, - key.Fingerprint(), s, contentsEnc, mac, - params.CMIXParams) + key.Fingerprint(), s, contentsEnc, mac, params.CMIXParams) if err != nil { errCh <- err } @@ -147,24 +140,25 @@ func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, if numFail > 0 { jww.INFO.Printf("Failed to E2E send %d/%d to %s", numFail, len(partitions), recipient) - err = errors.Errorf("Failed to E2E send %v/%v sub payloads:"+ - " %s", numFail, len(partitions), errRtn) - return nil, e2e.MessageID{}, time.Time{}, err + return nil, e2e.MessageID{}, time.Time{}, errors.Errorf( + "Failed to E2E send %v/%v sub payloads: %s", + numFail, len(partitions), errRtn) } else { jww.INFO.Printf("Successfully E2E sent %d/%d to %s", len(partitions)-numFail, len(partitions), recipient) } - jww.INFO.Printf("Successful E2E Send of %d messages to %s with msgID "+ - "%s", len(partitions), recipient, msgID) + jww.INFO.Printf("Successful E2E Send of %d messages to %s with msgID %s", + len(partitions), recipient, msgID) + return roundIds, msgID, ts, nil } // waitForKey waits the designated amount of time for a key to become available // with the partner. -func waitForKey(keyGetter func() (*session.Cypher, error), numAttempts uint, - wait time.Duration, stop *stoppable.Single, recipient *id.ID, - digest string, partition int) (*session.Cypher, error) { +func waitForKey(keyGetter func() (session.Cypher, error), numAttempts uint, + wait time.Duration, stop *stoppable.Single, recipient *id.ID, digest string, + partition int) (session.Cypher, error) { key, err := keyGetter() if err == nil { return key, nil @@ -173,13 +167,11 @@ func waitForKey(keyGetter func() (*session.Cypher, error), numAttempts uint, ticker := time.NewTicker(wait) defer ticker.Stop() - for keyTries := uint(1); err != nil && - keyTries < numAttempts; keyTries++ { - jww.WARN.Printf("Out of sending keys for %s "+ - "(digest: %s, partition: %d), this can "+ - "happen when sending messages faster than "+ - "the client can negotiate keys. Please "+ - "adjust your e2e key parameters", + for keyTries := uint(1); err != nil && keyTries < numAttempts; keyTries++ { + jww.WARN.Printf( + "Out of sending keys for %s (digest: %s, partition: %d), this can "+ + "happen when sending messages faster than the client can "+ + "negotiate keys. Please adjust your e2e key parameters.", recipient, digest, partition) select { @@ -194,19 +186,16 @@ func waitForKey(keyGetter func() (*session.Cypher, error), numAttempts uint, return key, err } -// getSendErrors returns any errors on the error channel -func getSendErrors(c chan error) (int, string) { - var errRtn string - numFail := 0 - done := false - for !done { +// getSendErrors returns a string of all error received on the error channel and +// a count of the number of errors. +func getSendErrors(c chan error) (numFail int, errRtn string) { + for { select { case err := <-c: errRtn += err.Error() numFail++ default: - done = true + return numFail, errRtn } } - return numFail, errRtn } diff --git a/e2e/sendE2E_test.go b/e2e/sendE2E_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d316b90c79a388ac32c405542e49d882089138d1 --- /dev/null +++ b/e2e/sendE2E_test.go @@ -0,0 +1,256 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package e2e + +import ( + "bytes" + "github.com/cloudflare/circl/dh/sidh" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/e2e/parse" + "gitlab.com/elixxir/client/e2e/ratchet" + "gitlab.com/elixxir/client/e2e/ratchet/partner/session" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/e2e/rekey" + "gitlab.com/elixxir/client/stoppable" + util "gitlab.com/elixxir/client/storage/utility" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/crypto/cyclic" + dh "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/elixxir/ekv" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "io" + "testing" + "time" +) + +func Test_manager_SendE2E_Smoke(t *testing.T) { + streamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) + rng := streamGen.GetStream() + defer rng.Close() + netHandler := newMockCmixHandler() + + // Generate new E2E manager + myKv := versioned.NewKV(ekv.MakeMemstore()) + myID := id.NewIdFromString("myID", id.User, t) + myNet := newMockCmix(myID, netHandler, t) + m1 := &manager{ + Switchboard: receive.New(), + partitioner: parse.NewPartitioner(myKv, myNet.GetMaxMessageLength()), + net: myNet, + myID: myID, + events: mockEventsManager{}, + grp: myNet.GetInstance().GetE2EGroup(), + rekeyParams: rekey.GetDefaultParams(), + } + + myPrivKey := dh.GeneratePrivateKey( + dh.DefaultPrivateKeyLength, m1.grp, rng) + err := ratchet.New(myKv, myID, myPrivKey, m1.grp) + if err != nil { + t.Errorf("Failed to generate new ratchet: %+v", err) + } + + myFpGen := &fpGenerator{m1} + myServices := newMockServices() + + m1.Ratchet, err = ratchet.Load( + myKv, myID, m1.grp, myFpGen, myServices, streamGen) + + // Generate new E2E manager + partnerKv := versioned.NewKV(ekv.MakeMemstore()) + partnerID := id.NewIdFromString("partnerID", id.User, t) + partnerNet := newMockCmix(partnerID, netHandler, t) + m2 := &manager{ + Switchboard: receive.New(), + partitioner: parse.NewPartitioner(partnerKv, partnerNet.GetMaxMessageLength()), + net: partnerNet, + myID: partnerID, + events: mockEventsManager{}, + grp: partnerNet.GetInstance().GetE2EGroup(), + rekeyParams: rekey.GetDefaultParams(), + } + + receiveChan := make(chan receive.Message, 10) + m2.Switchboard.RegisterListener(partnerID, catalog.NoType, &mockListener{receiveChan}) + + partnerPrivKey := dh.GeneratePrivateKey( + dh.DefaultPrivateKeyLength, m2.grp, rng) + err = ratchet.New(partnerKv, partnerID, partnerPrivKey, m2.grp) + if err != nil { + t.Errorf("Failed to generate new ratchet: %+v", err) + } + + partnerFpGen := &fpGenerator{m2} + partnerServices := newMockServices() + + m1.Ratchet, err = ratchet.Load( + partnerKv, partnerID, m2.grp, partnerFpGen, partnerServices, streamGen) + + // Generate partner identity and add partner + partnerPubKey, partnerSidhPubKey, mySidhPrivKey, sessionParams := + genPartnerKeys(partnerPrivKey, m1.grp, rng, t) + _, err = m1.Ratchet.AddPartner(partnerID, partnerPubKey, myPrivKey, + partnerSidhPubKey, mySidhPrivKey, sessionParams, sessionParams) + if err != nil { + t.Errorf("Failed to add partner: %+v", err) + } + + payload := []byte("My Payload") + p := GetDefaultParams() + _, _, _, err = m1.SendE2E(catalog.NoType, partnerID, payload, p) + if err != nil { + t.Errorf("SendE2E failed: %+v", err) + } + + select { + case r := <-receiveChan: + if !bytes.Equal(payload, r.Payload) { + t.Errorf("Received payload does not match sent payload."+ + "\nexpected: %q\nreceived: %q", payload, r.Payload) + } + case <-time.After(305 * time.Millisecond): + t.Errorf("Timed out waiting for E2E message.") + } +} + +// genPartnerKeys generates the keys needed to add a partner. +func genPartnerKeys(partnerPrivKey *cyclic.Int, grp *cyclic.Group, + rng io.Reader, t testing.TB) ( + partnerPubKey *cyclic.Int, partnerSidhPubKey *sidh.PublicKey, + mySidhPrivKey *sidh.PrivateKey, params session.Params) { + + partnerPubKey = dh.GeneratePublicKey(partnerPrivKey, grp) + + partnerSidhPrivKey := util.NewSIDHPrivateKey(sidh.KeyVariantSidhA) + partnerSidhPubKey = util.NewSIDHPublicKey(sidh.KeyVariantSidhA) + err := partnerSidhPrivKey.Generate(rng) + if err != nil { + t.Fatalf("Failed to generate partner SIDH private key: %+v", err) + } + partnerSidhPrivKey.GeneratePublicKey(partnerSidhPubKey) + + mySidhPrivKey = util.NewSIDHPrivateKey(sidh.KeyVariantSidhB) + mySidhPubKey := util.NewSIDHPublicKey(sidh.KeyVariantSidhB) + err = mySidhPrivKey.Generate(rng) + if err != nil { + t.Fatalf("Failed to generate my SIDH private key: %+v", err) + } + mySidhPrivKey.GeneratePublicKey(mySidhPubKey) + + params = session.GetDefaultParams() + + return partnerPubKey, partnerSidhPubKey, mySidhPrivKey, params +} + +// Tests that waitForKey returns a key after it waits 5 times. +func Test_waitForKey(t *testing.T) { + wait := 15 * time.Millisecond + numAttempts := uint(10) + expectedCypher := &mockWaitForKeyCypher{5} + var attempt uint + keyGetter := func() (session.Cypher, error) { + if attempt >= (numAttempts / 2) { + return expectedCypher, nil + } + attempt++ + return nil, errors.New("Failed to get key.") + } + stop := stoppable.NewSingle("Test_waitForKey") + + c, err := waitForKey(keyGetter, numAttempts, wait, stop, &id.ID{}, "", 0) + if err != nil { + t.Errorf("waitForKey returned an error: %+v", err) + } + + if *c.(*mockWaitForKeyCypher) != *expectedCypher { + t.Errorf("Received unexpected cypher.\nexpected: %#v\nreceived: %#v", + *expectedCypher, *c.(*mockWaitForKeyCypher)) + } +} + +// Error path: tests that waitForKey returns an error after the key getter does +// not return any keys after all attempts +func Test_waitForKey_NoKeyError(t *testing.T) { + expectedErr := "Failed to get key." + keyGetter := func() (session.Cypher, error) { + return nil, errors.New(expectedErr) + } + stop := stoppable.NewSingle("Test_waitForKey") + + _, err := waitForKey(keyGetter, 10, 1, stop, &id.ID{}, "", 0) + if err == nil || err.Error() != expectedErr { + t.Errorf("waitForKey did not return the expected error when no key "+ + "is available.\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Error path: tests that waitForKey returns an error after the stoppable is +// triggered. +func Test_waitForKey_StopError(t *testing.T) { + expectedErr := "Stopped by stopper" + keyGetter := func() (session.Cypher, error) { + return nil, errors.New("Failed to get key.") + } + stop := stoppable.NewSingle("Test_waitForKey") + + go func() { + _, err := waitForKey(keyGetter, 10, 1, stop, &id.ID{}, "", 0) + if err == nil || err.Error() != expectedErr { + t.Errorf("waitForKey did not return the expected error when the "+ + "stoppable was triggered.\nexpected: %s\nreceived: %+v", + expectedErr, err) + } + }() + + err := stop.Close() + if err != nil { + t.Errorf("Failed to stop stoppable: %+v", err) + } +} + +type mockWaitForKeyCypher struct { + cypherNum int +} + +func (m *mockWaitForKeyCypher) GetSession() *session.Session { return nil } +func (m *mockWaitForKeyCypher) Fingerprint() format.Fingerprint { return format.Fingerprint{} } +func (m *mockWaitForKeyCypher) Encrypt([]byte) ([]byte, []byte) { return nil, nil } +func (m *mockWaitForKeyCypher) Decrypt(format.Message) ([]byte, error) { return nil, nil } +func (m *mockWaitForKeyCypher) Use() {} + +// Tests that getSendErrors returns all the errors on the channel. +func Test_getSendErrors(t *testing.T) { + const n = 10 + var expectedErrors string + errorList := make([]error, n) + for i := range errorList { + errorList[i] = errors.Errorf("Error %d of %d", i, n) + expectedErrors += errorList[i].Error() + } + + c := make(chan error, n*2) + for _, e := range errorList { + c <- e + } + + numFail, errRtn := getSendErrors(c) + + if numFail != n { + t.Errorf("Incorrect number of failed.\nexpected: %d\nreceived: %d", + n, numFail) + } + + if errRtn != expectedErrors { + t.Errorf("Received incorrect errors.\nexpected: %q\nreceived: %q", + expectedErrors, errRtn) + } +} diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 7a71a84091e80cbaaed1e30b7a0f531a63c828a2..32a3d986b7a6c462d2636e79d73524356d574cab 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -2,11 +2,14 @@ package e2e import ( "bytes" + "github.com/pkg/errors" "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/gateway" "gitlab.com/elixxir/client/cmix/identity" + "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/e2e/receive" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/primitives/format" @@ -16,52 +19,11 @@ import ( "gitlab.com/xx_network/primitives/ndf" "gitlab.com/xx_network/primitives/netTime" "math/rand" + "sync" "testing" "time" ) -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", - }, - } -} - func e2eMessagesEqual(received, expected e2eMessage, t *testing.T) bool { equals := true if !bytes.Equal(received.Recipient, expected.Recipient) { @@ -101,188 +63,230 @@ func makeTestE2EMessages(n int, t *testing.T) []e2eMessage { return msgs } -type mockEventsManager struct{} - -func (m mockEventsManager) Report(priority int, category, evtType, details string) { +//////////////////////////////////////////////////////////////////////////////// +// Mock Listener // +//////////////////////////////////////////////////////////////////////////////// +type mockListener struct { + receiveChan chan receive.Message } -// todo: implement this for specific tests -type mockCmixNet struct { - testingInterface interface{} - instance *network.Instance -} +func (m *mockListener) Hear(item receive.Message) { m.receiveChan <- item } +func (m *mockListener) Name() string { return "" } -func (m mockCmixNet) Follow(report cmix.ClientErrorReport) (stoppable.Stoppable, error) { - //TODO implement me - return nil, nil -} +//////////////////////////////////////////////////////////////////////////////// +// Mock Events Manager // +//////////////////////////////////////////////////////////////////////////////// -func (m mockCmixNet) GetMaxMessageLength() int { - //TODO implement me - return 0 -} +type mockEventsManager struct{} -func (m mockCmixNet) Send(recipient *id.ID, fingerprint format.Fingerprint, service message.Service, payload, mac []byte, cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) { - //TODO implement me - return 0, ephemeral.Id{}, nil -} +func (m mockEventsManager) Report(int, string, string, string) {} -func (m mockCmixNet) SendMany(messages []cmix.TargetedCmixMessage, p cmix.CMIXParams) (id.Round, []ephemeral.Id, error) { - //TODO implement me - return 0, nil, nil -} +//////////////////////////////////////////////////////////////////////////////// +// Mock Services // +//////////////////////////////////////////////////////////////////////////////// -func (m mockCmixNet) AddIdentity(id *id.ID, validUntil time.Time, persistent bool) { - //TODO implement me - return +type mockServices struct { + services map[id.ID]map[string]message.Processor + sync.Mutex } -func (m mockCmixNet) RemoveIdentity(id *id.ID) { - //TODO implement me - return +func newMockServices() *mockServices { + return &mockServices{ + services: make(map[id.ID]map[string]message.Processor), + } } -func (m mockCmixNet) GetIdentity(get *id.ID) (identity.TrackedID, error) { - //TODO implement me - return identity.TrackedID{}, nil -} +func (m *mockServices) AddService( + clientID *id.ID, ms message.Service, p message.Processor) { + m.Lock() + defer m.Unlock() -func (m mockCmixNet) AddFingerprint(identity *id.ID, fingerprint format.Fingerprint, mp message.Processor) error { - //TODO implement me - return nil + if m.services[*clientID] == nil { + m.services[*clientID] = map[string]message.Processor{ms.Tag: p} + } else { + m.services[*clientID][ms.Tag] = p + } + m.services[*clientID][ms.Tag] = p } -func (m mockCmixNet) DeleteFingerprint(identity *id.ID, fingerprint format.Fingerprint) { - //TODO implement me - return -} +func (m *mockServices) DeleteService( + clientID *id.ID, ms message.Service, _ message.Processor) { + m.Lock() + defer m.Unlock() -func (m mockCmixNet) DeleteClientFingerprints(identity *id.ID) { - //TODO implement me - return + if m.services[*clientID] != nil { + delete(m.services[*clientID], ms.Tag) + } } -func (m mockCmixNet) AddService(clientID *id.ID, newService message.Service, response message.Processor) { - //TODO implement me - return -} +//////////////////////////////////////////////////////////////////////////////// +// Mock cMix Client // +//////////////////////////////////////////////////////////////////////////////// -func (m mockCmixNet) DeleteService(clientID *id.ID, toDelete message.Service, processor message.Processor) { - //TODO implement me - return +type mockCmixHandler struct { + processorMap map[format.Fingerprint]message.Processor + sync.Mutex } -func (m mockCmixNet) DeleteClientService(clientID *id.ID) { - //TODO implement me - return +func newMockCmixHandler() *mockCmixHandler { + return &mockCmixHandler{ + processorMap: make(map[format.Fingerprint]message.Processor), + } } -func (m mockCmixNet) TrackServices(tracker message.ServicesTracker) { - //TODO implement me - return -} +// todo: implement this for specific tests +type mockCmix struct { + t testing.TB + myID *id.ID + numPrimeBytes int + health bool + handler *mockCmixHandler + instance *network.Instance +} + +func newMockCmix(myID *id.ID, handler *mockCmixHandler, t testing.TB) *mockCmix { + comms := &connect.ProtoComms{Manager: connect.NewManagerTesting(t)} + def := getNDF() + + instance, err := network.NewInstanceTesting(comms, def, def, nil, nil, t) + if err != nil { + panic(err) + } -func (m mockCmixNet) CheckInProgressMessages() { - //TODO implement me - return + return &mockCmix{ + t: t, + myID: myID, + numPrimeBytes: 4096, + health: true, + handler: handler, + instance: instance, + } } -func (m mockCmixNet) IsHealthy() bool { - //TODO implement me - return true -} +func (m *mockCmix) Connect(*ndf.NetworkDefinition) error { return nil } +func (m *mockCmix) Follow(cmix.ClientErrorReport) (stoppable.Stoppable, error) { return nil, nil } -func (m mockCmixNet) WasHealthy() bool { - //TODO implement me - return true +func (m *mockCmix) GetMaxMessageLength() int { + msg := format.NewMessage(m.numPrimeBytes) + return msg.ContentsSize() } -func (m mockCmixNet) AddHealthCallback(f func(bool)) uint64 { - //TODO implement me - return 0 -} +func (m *mockCmix) Send(_ *id.ID, fp format.Fingerprint, _ message.Service, + payload, mac []byte, _ cmix.CMIXParams) (id.Round, ephemeral.Id, error) { + m.handler.Lock() + defer m.handler.Unlock() -func (m mockCmixNet) RemoveHealthCallback(u uint64) { - //TODO implement me - return -} + msg := format.NewMessage(m.numPrimeBytes) + msg.SetContents(payload) + msg.SetMac(mac) + msg.SetKeyFP(fp) -func (m mockCmixNet) HasNode(nid *id.ID) bool { - //TODO implement me - return true -} + if m.handler.processorMap[fp] == nil { + m.t.Errorf("No processor found for fingerprint %s", fp) + return 0, ephemeral.Id{}, + errors.Errorf("No processor found for fingerprint %s", fp) + } -func (m mockCmixNet) NumRegisteredNodes() int { - //TODO implement me - return 0 + m.handler.processorMap[fp].Process( + msg, receptionID.EphemeralIdentity{}, rounds.Round{}) + + return 0, ephemeral.Id{}, nil } -func (m mockCmixNet) TriggerNodeRegistration(nid *id.ID) { - //TODO implement me - return +func (m *mockCmix) SendMany([]cmix.TargetedCmixMessage, cmix.CMIXParams) (id.Round, []ephemeral.Id, error) { + return 0, nil, nil } +func (m *mockCmix) AddIdentity(*id.ID, time.Time, bool) {} +func (m *mockCmix) RemoveIdentity(*id.ID) {} +func (m *mockCmix) GetIdentity(*id.ID) (identity.TrackedID, error) { return identity.TrackedID{}, nil } -func (m mockCmixNet) GetRoundResults(timeout time.Duration, roundCallback cmix.RoundEventCallback, roundList ...id.Round) error { - //TODO implement me +func (m *mockCmix) AddFingerprint(_ *id.ID, fp format.Fingerprint, mp message.Processor) error { + m.handler.Lock() + defer m.handler.Unlock() + m.handler.processorMap[fp] = mp return nil } -func (m mockCmixNet) LookupHistoricalRound(rid id.Round, callback rounds.RoundResultCallback) error { - //TODO implement me +func (m *mockCmix) DeleteFingerprint(_ *id.ID, fp format.Fingerprint) { + m.handler.Lock() + delete(m.handler.processorMap, fp) + m.handler.Unlock() +} + +func (m *mockCmix) DeleteClientFingerprints(*id.ID) {} +func (m *mockCmix) AddService(*id.ID, message.Service, message.Processor) {} +func (m *mockCmix) DeleteService(*id.ID, message.Service, message.Processor) {} +func (m *mockCmix) DeleteClientService(*id.ID) {} +func (m *mockCmix) TrackServices(message.ServicesTracker) {} +func (m *mockCmix) CheckInProgressMessages() {} +func (m *mockCmix) IsHealthy() bool { return m.health } +func (m *mockCmix) WasHealthy() bool { return true } +func (m *mockCmix) AddHealthCallback(func(bool)) uint64 { return 0 } +func (m *mockCmix) RemoveHealthCallback(uint64) {} +func (m *mockCmix) HasNode(*id.ID) bool { return true } +func (m *mockCmix) NumRegisteredNodes() int { return 0 } +func (m *mockCmix) TriggerNodeRegistration(*id.ID) {} +func (m *mockCmix) GetRoundResults(time.Duration, cmix.RoundEventCallback, ...id.Round) error { return nil } - -func (m mockCmixNet) SendToAny(sendFunc func(host *connect.Host) (interface{}, error), stop *stoppable.Single) (interface{}, error) { - //TODO implement me +func (m *mockCmix) LookupHistoricalRound(id.Round, rounds.RoundResultCallback) error { return nil } +func (m *mockCmix) SendToAny(func(host *connect.Host) (interface{}, error), *stoppable.Single) (interface{}, error) { return nil, nil } - -func (m mockCmixNet) SendToPreferred(targets []*id.ID, sendFunc gateway.SendToPreferredFunc, stop *stoppable.Single, timeout time.Duration) (interface{}, error) { +func (m *mockCmix) SendToPreferred([]*id.ID, gateway.SendToPreferredFunc, *stoppable.Single, time.Duration) (interface{}, error) { return nil, nil } +func (m *mockCmix) SetGatewayFilter(gateway.Filter) {} +func (m *mockCmix) GetHostParams() connect.HostParams { return connect.HostParams{} } +func (m *mockCmix) GetAddressSpace() uint8 { return 0 } +func (m *mockCmix) RegisterAddressSpaceNotification(string) (chan uint8, error) { return nil, nil } +func (m *mockCmix) UnregisterAddressSpaceNotification(string) { return } +func (m *mockCmix) GetInstance() *network.Instance { return m.instance } +func (m *mockCmix) GetVerboseRounds() string { return "" } -func (m mockCmixNet) SetGatewayFilter(f gateway.Filter) { - return -} - -func (m mockCmixNet) GetHostParams() connect.HostParams { - return connect.HostParams{} -} - -func (m mockCmixNet) GetAddressSpace() uint8 { - return 0 -} +//////////////////////////////////////////////////////////////////////////////// +// NDF // +//////////////////////////////////////////////////////////////////////////////// -func (m mockCmixNet) RegisterAddressSpaceNotification(tag string) (chan uint8, error) { - return nil, nil -} - -func (m mockCmixNet) UnregisterAddressSpaceNotification(tag string) { - return -} - -func (m *mockCmixNet) GetInstance() *network.Instance { - if m.instance == nil { - commsManager := connect.NewManagerTesting(m.testingInterface) - - instanceComms := &connect.ProtoComms{ - Manager: commsManager, - } - - def := getNDF() - - thisInstance, err := network.NewInstanceTesting(instanceComms, def, def, nil, nil, m.testingInterface) - if err != nil { - panic(err) - } - - m.instance = thisInstance +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", + }, } - - return m.instance -} - -func (m mockCmixNet) GetVerboseRounds() string { - return "" } diff --git a/restlike/generateProto.sh b/restlike/generateProto.sh new file mode 100755 index 0000000000000000000000000000000000000000..67b6d293f6f4e6a68eff4162a42acb242129cd18 --- /dev/null +++ b/restlike/generateProto.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +protoc --go_out=paths=source_relative:. restlike/restLikeMessages.proto diff --git a/restlike/restLikeMessages.pb.go b/restlike/restLikeMessages.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..c6666a5d08dd3c0a086a100a19ca0a74f630af79 --- /dev/null +++ b/restlike/restLikeMessages.pb.go @@ -0,0 +1,270 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: restlike/restLikeMessages.proto + +package restlike + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Message are used for sending to and receiving from a RestServer +type Message struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Content []byte `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` + Headers *Headers `protobuf:"bytes,2,opt,name=headers,proto3" json:"headers,omitempty"` + Method uint32 `protobuf:"varint,3,opt,name=method,proto3" json:"method,omitempty"` + Uri string `protobuf:"bytes,4,opt,name=uri,proto3" json:"uri,omitempty"` + Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *Message) Reset() { + *x = Message{} + if protoimpl.UnsafeEnabled { + mi := &file_restlike_restLikeMessages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Message) ProtoMessage() {} + +func (x *Message) ProtoReflect() protoreflect.Message { + mi := &file_restlike_restLikeMessages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Message.ProtoReflect.Descriptor instead. +func (*Message) Descriptor() ([]byte, []int) { + return file_restlike_restLikeMessages_proto_rawDescGZIP(), []int{0} +} + +func (x *Message) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +func (x *Message) GetHeaders() *Headers { + if x != nil { + return x.Headers + } + return nil +} + +func (x *Message) GetMethod() uint32 { + if x != nil { + return x.Method + } + return 0 +} + +func (x *Message) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *Message) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +// Headers allows different configurations for each Request +// that will be specified in the Request header +type Headers struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Headers allows for custom headers to be included with a Request + Headers []byte `protobuf:"bytes,1,opt,name=headers,proto3" json:"headers,omitempty"` + // Version allows for endpoints to be backwards-compatible + // and handle different formats of the same Request + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *Headers) Reset() { + *x = Headers{} + if protoimpl.UnsafeEnabled { + mi := &file_restlike_restLikeMessages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Headers) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Headers) ProtoMessage() {} + +func (x *Headers) ProtoReflect() protoreflect.Message { + mi := &file_restlike_restLikeMessages_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Headers.ProtoReflect.Descriptor instead. +func (*Headers) Descriptor() ([]byte, []int) { + return file_restlike_restLikeMessages_proto_rawDescGZIP(), []int{1} +} + +func (x *Headers) GetHeaders() []byte { + if x != nil { + return x.Headers + } + return nil +} + +func (x *Headers) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +var File_restlike_restLikeMessages_proto protoreflect.FileDescriptor + +var file_restlike_restLikeMessages_proto_rawDesc = []byte{ + 0x0a, 0x1f, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6b, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x4c, + 0x69, 0x6b, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x10, 0x72, 0x65, 0x73, 0x74, 0x4c, 0x69, 0x6b, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, + 0x74, 0x4c, 0x69, 0x6b, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3d, + 0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x24, 0x5a, + 0x22, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6c, 0x69, 0x78, + 0x78, 0x69, 0x72, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x6c, + 0x69, 0x6b, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_restlike_restLikeMessages_proto_rawDescOnce sync.Once + file_restlike_restLikeMessages_proto_rawDescData = file_restlike_restLikeMessages_proto_rawDesc +) + +func file_restlike_restLikeMessages_proto_rawDescGZIP() []byte { + file_restlike_restLikeMessages_proto_rawDescOnce.Do(func() { + file_restlike_restLikeMessages_proto_rawDescData = protoimpl.X.CompressGZIP(file_restlike_restLikeMessages_proto_rawDescData) + }) + return file_restlike_restLikeMessages_proto_rawDescData +} + +var file_restlike_restLikeMessages_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_restlike_restLikeMessages_proto_goTypes = []interface{}{ + (*Message)(nil), // 0: restLikeMessages.Message + (*Headers)(nil), // 1: restLikeMessages.Headers +} +var file_restlike_restLikeMessages_proto_depIdxs = []int32{ + 1, // 0: restLikeMessages.Message.headers:type_name -> restLikeMessages.Headers + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_restlike_restLikeMessages_proto_init() } +func file_restlike_restLikeMessages_proto_init() { + if File_restlike_restLikeMessages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_restlike_restLikeMessages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Message); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_restlike_restLikeMessages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Headers); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_restlike_restLikeMessages_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_restlike_restLikeMessages_proto_goTypes, + DependencyIndexes: file_restlike_restLikeMessages_proto_depIdxs, + MessageInfos: file_restlike_restLikeMessages_proto_msgTypes, + }.Build() + File_restlike_restLikeMessages_proto = out.File + file_restlike_restLikeMessages_proto_rawDesc = nil + file_restlike_restLikeMessages_proto_goTypes = nil + file_restlike_restLikeMessages_proto_depIdxs = nil +} diff --git a/restlike/restLikeMessages.proto b/restlike/restLikeMessages.proto new file mode 100644 index 0000000000000000000000000000000000000000..00156d5443904383deb89f64c391e0009741dc78 --- /dev/null +++ b/restlike/restLikeMessages.proto @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +syntax = "proto3"; +package restLikeMessages; +option go_package = "gitlab.com/elixxir/client/restlike"; + +// Message are used for sending to and receiving from a RestServer +message Message { + bytes content = 1; + Headers headers = 2; + uint32 method = 3; + string uri = 4; + string error = 5; +} + +// Headers allows different configurations for each Request +// that will be specified in the Request header +message Headers { + // Headers allows for custom headers to be included with a Request + bytes headers = 1; + + // Version allows for endpoints to be backwards-compatible + // and handle different formats of the same Request + uint32 version = 2; +} \ No newline at end of file diff --git a/restlike/restServer.go b/restlike/restServer.go new file mode 100644 index 0000000000000000000000000000000000000000..dccb13ad3da83850bdd98be9b568dfab5c933102 --- /dev/null +++ b/restlike/restServer.go @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/primitives/id" +) + +// RestServer allows for clients to make REST-like requests this client +type RestServer interface { + // RegisterEndpoint allows the association of a Callback with + // a specific URI and a variety of different REST Method + RegisterEndpoint(path URI, method Method, cb Callback) error + + // UnregisterEndpoint removes the Callback associated with + // a specific URI and REST Method + UnregisterEndpoint(path URI, method Method) error + + // Close the internal RestServer endpoints and external services + Close() +} + +// singleServer implements the RestServer interface using single-use +type singleServer struct { + receptionId *id.ID + listener single.Listener + endpoints *Endpoints +} + +// NewSingleServer builds a RestServer with single-use and +// the provided arguments, then registers necessary external services +func NewSingleServer(receptionId *id.ID, privKey *cyclic.Int, net single.ListenCmix, e2eGrp *cyclic.Group) RestServer { + newServer := &singleServer{ + receptionId: receptionId, + endpoints: &Endpoints{endpoints: make(map[URI]map[Method]Callback)}, + } + newServer.listener = single.Listen(catalog.RestLike, receptionId, privKey, + net, e2eGrp, &singleReceiver{newServer.endpoints}) + return newServer +} + +// RegisterEndpoint allows the association of a Callback with +// a specific URI and a variety of different REST Method +func (r *singleServer) RegisterEndpoint(path URI, method Method, cb Callback) error { + return r.endpoints.Add(path, method, cb) +} + +// UnregisterEndpoint removes the Callback associated with +// a specific URI and REST Method +func (r *singleServer) UnregisterEndpoint(path URI, method Method) error { + return r.endpoints.Remove(path, method) +} + +// Close the internal RestServer endpoints and external services +func (r *singleServer) Close() { + // Clear all internal endpoints + r.endpoints = nil + // Destroy external services + r.listener.Stop() +} diff --git a/restlike/singleReceiver.go b/restlike/singleReceiver.go new file mode 100644 index 0000000000000000000000000000000000000000..7a137631bea709c8b9d7dabbb0888fec60a60cdc --- /dev/null +++ b/restlike/singleReceiver.go @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single" + "google.golang.org/protobuf/proto" + "time" +) + +// processor is the reception handler for a RestServer +type singleReceiver struct { + endpoints *Endpoints +} + +// Callback is the handler for single-use message reception for a RestServer +// Automatically responds to invalid endpoint requests +func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) { + // Unmarshal the request payload + newMessage := &Message{} + err := proto.Unmarshal(req.GetPayload(), newMessage) + if err != nil { + jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err) + return + } + + var respondErr error + if cb, err := s.endpoints.Get(URI(newMessage.GetUri()), Method(newMessage.GetMethod())); err == nil { + // Send the payload to the proper Callback if it exists and respond with the result + respondErr = respond(cb(newMessage), req) + } else { + // If no callback, automatically send an error response + respondErr = respond(&Message{Error: err.Error()}, req) + } + if respondErr != nil { + jww.ERROR.Printf("Unable to respond to request: %+v", err) + } +} + +// respond to a single.Request with the given Message +func respond(response *Message, req *single.Request) error { + payload, err := proto.Marshal(response) + if err != nil { + return errors.Errorf("unable to marshal restlike response message: %+v", err) + } + + // TODO: Parameterize params and timeout + _, err = req.Respond(payload, cmix.GetDefaultCMIXParams(), 30*time.Second) + if err != nil { + return errors.Errorf("unable to send restlike response message: %+v", err) + } + return nil +} diff --git a/restlike/singleReceiver_test.go b/restlike/singleReceiver_test.go new file mode 100644 index 0000000000000000000000000000000000000000..56a46586daaf6411d589b79c0e179ade2ce4e017 --- /dev/null +++ b/restlike/singleReceiver_test.go @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/single" + "testing" +) + +// Test failure of proto unmarshal +func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) { + ep := &Endpoints{endpoints: make(map[URI]map[Method]Callback)} + receiver := singleReceiver{endpoints: ep} + + testReq := single.BuildTestRequest(make([]byte, 0), t) + receiver.Callback(testReq, receptionID.EphemeralIdentity{}, nil) +} + +// Test happy path +//func TestSingleReceiver_Callback(t *testing.T) { +// ep := &Endpoints{endpoints: make(map[URI]map[Method]Callback)} +// resultChan := make(chan interface{}, 1) +// cb := func(*Message) *Message { +// resultChan <- "" +// return nil +// } +// testPath := URI("test/path") +// testMethod := Get +// testMessage := &Message{ +// Content: []byte("test"), +// Headers: nil, +// Method: uint32(testMethod), +// Uri: string(testPath), +// Error: "", +// } +// +// err := ep.Add(testPath, testMethod, cb) +// if err != nil { +// t.Errorf(err.Error()) +// } +// receiver := singleReceiver{endpoints: ep} +// +// testPayload, err := proto.Marshal(testMessage) +// if err != nil { +// t.Errorf(err.Error()) +// } +// testReq := single.BuildTestRequest(testPayload, t) +// receiver.Callback(testReq, receptionID.EphemeralIdentity{}, nil) +// +// select { +// case _ = <-resultChan: +// case <-time.After(3 * time.Second): +// t.Errorf("Test SingleReceiver timed out!") +// } +//} diff --git a/restlike/singleRequest.go b/restlike/singleRequest.go new file mode 100644 index 0000000000000000000000000000000000000000..774f112c43745c0a36b9692ceb209bd4e8b783f1 --- /dev/null +++ b/restlike/singleRequest.go @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/crypto/csprng" + "google.golang.org/protobuf/proto" +) + +// SingleRequest allows for making REST-like requests to a RestServer using single-use messages +// Can be used as stateful or declared inline without state +type SingleRequest struct { + Net single.Cmix + Rng csprng.Source + E2eGrp *cyclic.Group +} + +// Request provides several Method of sending Data to the given URI +// and blocks until the Message is returned +func (s *SingleRequest) Request(recipient contact.Contact, method Method, path URI, + content Data, headers *Headers, singleParams single.RequestParams) (*Message, error) { + // Build the Message + newMessage := &Message{ + Content: content, + Headers: headers, + Method: uint32(method), + Uri: string(path), + } + msg, err := proto.Marshal(newMessage) + if err != nil { + return nil, err + } + + // Build callback for the single-use response + signalChannel := make(chan *Message, 1) + cb := func(msg *Message) { + signalChannel <- msg + } + + // Transmit the Message + _, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, + &singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) + if err != nil { + return nil, err + } + + // Block waiting for single-use response + jww.DEBUG.Printf("Restlike waiting for single-use response from %s...", recipient.ID.String()) + newResponse := <-signalChannel + jww.DEBUG.Printf("Restlike single-use response received from %s", recipient.ID.String()) + + return newResponse, nil +} + +// AsyncRequest provides several Method of sending Data to the given URI +// and will return the Message to the given Callback when received +func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, path URI, + content Data, headers *Headers, cb RequestCallback, singleParams single.RequestParams) error { + // Build the Message + newMessage := &Message{ + Content: content, + Headers: headers, + Method: uint32(method), + Uri: string(path), + } + msg, err := proto.Marshal(newMessage) + if err != nil { + return err + } + + // Transmit the Message + _, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, + &singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) + return err +} diff --git a/restlike/singleResponse.go b/restlike/singleResponse.go new file mode 100644 index 0000000000000000000000000000000000000000..41e21b6061c110eaaa06fc25a472bf0326ca1ff3 --- /dev/null +++ b/restlike/singleResponse.go @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "google.golang.org/protobuf/proto" +) + +// processor is the response handler for a Request +type singleResponse struct { + responseCallback RequestCallback +} + +// Callback is the handler for single-use message responses for a Request +func (s *singleResponse) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) { + newMessage := &Message{} + + // Handle response errors + if err != nil { + newMessage.Error = err.Error() + s.responseCallback(newMessage) + return + } + + // Unmarshal the payload + err = proto.Unmarshal(payload, newMessage) + if err != nil { + newMessage.Error = err.Error() + } + + // Send the response payload to the responseCallback + s.responseCallback(newMessage) +} diff --git a/restlike/singleResponse_test.go b/restlike/singleResponse_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0381b452cca26c9d892abfe2b8329569a1eef689 --- /dev/null +++ b/restlike/singleResponse_test.go @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + "bytes" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "google.golang.org/protobuf/proto" + "testing" + "time" +) + +// Test happy path +func TestSingleResponse_Callback(t *testing.T) { + resultChan := make(chan *Message, 1) + cb := func(input *Message) { + resultChan <- input + } + testPath := "test/path" + testMethod := Get + testMessage := &Message{ + Content: []byte("test"), + Headers: nil, + Method: uint32(testMethod), + Uri: testPath, + Error: "", + } + + response := singleResponse{cb} + + testPayload, err := proto.Marshal(testMessage) + if err != nil { + t.Errorf(err.Error()) + } + response.Callback(testPayload, receptionID.EphemeralIdentity{}, nil, nil) + + select { + case result := <-resultChan: + if result.Uri != testPath { + t.Errorf("Mismatched uri") + } + if result.Method != uint32(testMethod) { + t.Errorf("Mismatched method") + } + if !bytes.Equal(testMessage.Content, result.Content) { + t.Errorf("Mismatched content") + } + case <-time.After(3 * time.Second): + t.Errorf("Test SingleResponse timed out!") + } +} + +// Test error input path +func TestSingleResponse_Callback_Err(t *testing.T) { + resultChan := make(chan *Message, 1) + cb := func(input *Message) { + resultChan <- input + } + response := singleResponse{cb} + + response.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test")) + + select { + case result := <-resultChan: + if len(result.Error) == 0 { + t.Errorf("Expected cb error!") + } + case <-time.After(3 * time.Second): + t.Errorf("Test SingleResponse input error timed out!") + } +} + +// Test proto error path +func TestSingleResponse_Callback_ProtoErr(t *testing.T) { + resultChan := make(chan *Message, 1) + cb := func(input *Message) { + resultChan <- input + } + response := singleResponse{cb} + + response.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil) + + select { + case result := <-resultChan: + if len(result.Error) == 0 { + t.Errorf("Expected cb proto error!") + } + case <-time.After(3 * time.Second): + t.Errorf("Test SingleResponse proto error timed out!") + } +} diff --git a/restlike/types.go b/restlike/types.go new file mode 100644 index 0000000000000000000000000000000000000000..247943844613a43c99b5d666f412a5c53f078471 --- /dev/null +++ b/restlike/types.go @@ -0,0 +1,116 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import ( + "github.com/pkg/errors" + "sync" +) + +// URI defines the destination endpoint of a Request +type URI string + +// Data provides a generic structure for data sent with a Request or received in a Message +// NOTE: The way this is encoded is up to the implementation. For example, protobuf or JSON +type Data []byte + +// Method defines the possible Request types +type Method uint32 + +// RequestCallback provides the ability to make asynchronous Request +// in order to get the Message later without blocking +type RequestCallback func(*Message) + +// Callback serves as an Endpoint function to be called when a Request is received +// Should return the desired response to be sent back to the sender +type Callback func(*Message) *Message + +const ( + // Undefined default value + Undefined Method = iota + // Get retrieve an existing resource. + Get + // Post creates a new resource. + Post + // Put updates an existing resource. + Put + // Patch partially updates an existing resource. + Patch + // Delete a resource. + Delete +) + +// methodStrings is a map of Method values back to their constant names for printing +var methodStrings = map[Method]string{ + Undefined: "undefined", + Get: "get", + Post: "post", + Put: "put", + Patch: "patch", + Delete: "delete", +} + +// String returns the Method as a human-readable name. +func (m Method) String() string { + if methodStr, ok := methodStrings[m]; ok { + return methodStr + } + return methodStrings[Undefined] +} + +// Endpoints represents a map of internal endpoints for a RestServer +type Endpoints struct { + endpoints map[URI]map[Method]Callback + sync.RWMutex +} + +// Add a new Endpoint +// Returns an error if Endpoint already exists +func (e *Endpoints) Add(path URI, method Method, cb Callback) error { + e.Lock() + defer e.Unlock() + + if _, ok := e.endpoints[path]; !ok { + e.endpoints[path] = make(map[Method]Callback) + } + if _, ok := e.endpoints[path][method]; ok { + return errors.Errorf("unable to RegisterEndpoint: %s/%s already exists", path, method) + } + e.endpoints[path][method] = cb + return nil +} + +// Get an Endpoint +// Returns an error if Endpoint does not exist +func (e *Endpoints) Get(path URI, method Method) (Callback, error) { + e.RLock() + defer e.RUnlock() + + if _, ok := e.endpoints[path]; !ok { + return nil, errors.Errorf("unable to locate endpoint: %s", path) + } + if _, innerOk := e.endpoints[path][method]; !innerOk { + return nil, errors.Errorf("unable to locate endpoint: %s/%s", path, method) + } + return e.endpoints[path][method], nil +} + +// Remove an Endpoint +// Returns an error if Endpoint does not exist +func (e *Endpoints) Remove(path URI, method Method) error { + if _, err := e.Get(path, method); err != nil { + return errors.Errorf("unable to UnregisterEndpoint: %s", err.Error()) + } + + e.Lock() + defer e.Unlock() + delete(e.endpoints[path], method) + if len(e.endpoints[path]) == 0 { + delete(e.endpoints, path) + } + return nil +} diff --git a/restlike/types_test.go b/restlike/types_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7b5b1a9d5b1bdaebc556ee4521379e48478019cb --- /dev/null +++ b/restlike/types_test.go @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package restlike + +import "testing" + +// Full test for all add/get/remove cases +func TestEndpoints(t *testing.T) { + ep := &Endpoints{endpoints: make(map[URI]map[Method]Callback)} + cb := func(*Message) *Message { + return nil + } + + testPath := URI("test/path") + testMethod := Get + err := ep.Add(testPath, testMethod, cb) + if _, ok := ep.endpoints[testPath][testMethod]; err != nil || !ok { + t.Errorf("Failed to add endpoint: %+v", err) + } + err = ep.Add(testPath, testMethod, cb) + if _, ok := ep.endpoints[testPath][testMethod]; err == nil || !ok { + t.Errorf("Expected failure to add endpoint") + } + + resultCb, err := ep.Get(testPath, testMethod) + if resultCb == nil || err != nil { + t.Errorf("Expected to get endpoint: %+v", err) + } + + err = ep.Remove(testPath, testMethod) + if _, ok := ep.endpoints[testPath][testMethod]; err != nil || ok { + t.Errorf("Failed to remove endpoint: %+v", err) + } + err = ep.Remove(testPath, testMethod) + if _, ok := ep.endpoints[testPath][testMethod]; err == nil || ok { + t.Errorf("Expected failure to remove endpoint") + } + + resultCb, err = ep.Get(testPath, testMethod) + if resultCb != nil || err == nil { + t.Errorf("Expected failure to get endpoint: %+v", err) + } +} 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..cb445e8b0a02b15d6458dd824a3cdd478d1167f3 100644 --- a/single/receivedRequest.go +++ b/single/receivedRequest.go @@ -18,21 +18,25 @@ import ( "gitlab.com/xx_network/primitives/id/ephemeral" "sync" "sync/atomic" + "testing" "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 +45,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 +147,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)) @@ -218,3 +224,17 @@ func splitPayload(payload []byte, maxSize, maxParts int) [][]byte { } return parts } + +// BuildTestRequest can be used for mocking a Request +func BuildTestRequest(payload []byte, t *testing.T) *Request { + return &Request{ + sender: nil, + senderPubKey: nil, + dhKey: nil, + tag: "", + maxParts: 0, + used: nil, + requestPayload: payload, + net: nil, + } +} 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", + }, + } +}