diff --git a/network/rounds/check.go b/network/rounds/check.go index acd81a429a3d8dd8d9f394b50f772a2412eaa3b5..1f262197825923d255f1ec2492c006bb07a40613 100644 --- a/network/rounds/check.go +++ b/network/rounds/check.go @@ -65,7 +65,7 @@ func (m *Manager) GetMessagesFromRound(roundID id.Round, identity reception.Iden identity.Source) //store the round as an unretreived round err = m.Session.UncheckedRounds().AddRound(roundID,nil, - identity.EphId, identity.Source) + identity.Source, identity.EphId) if err != nil { jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", roundID) } @@ -81,7 +81,7 @@ func (m *Manager) GetMessagesFromRound(roundID id.Round, identity reception.Iden identity.Source) //store the round as an unretreived round err = m.Session.UncheckedRounds().AddRound(roundID,ri, - identity.EphId, identity.Source) + identity.Source, identity.EphId) if err != nil { jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", roundID) } diff --git a/network/rounds/retrieve.go b/network/rounds/retrieve.go index f531292c56829a0f943dc639fe356283defb47b1..3c7a8ca67abd2d3f6a7ebe2cc5d463dfed2289f7 100644 --- a/network/rounds/retrieve.go +++ b/network/rounds/retrieve.go @@ -49,10 +49,10 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms, case rl := <-m.lookupRoundMessages: ri := rl.roundInfo jww.DEBUG.Printf("Checking for messages in round %d", ri.ID) - err := m.Session.UncheckedRounds().AddRound(id.Round(ri.ID),nil, - rl.identity.EphId, rl.identity.Source) + err := m.Session.UncheckedRounds().AddRound(id.Round(ri.ID), nil, + rl.identity.Source, rl.identity.EphId) if err != nil { - jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d",id.Round(ri.ID)) + jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", id.Round(ri.ID)) } // Convert gateways in round to proper ID format @@ -120,7 +120,7 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms, if len(bundle.Messages) != 0 { jww.DEBUG.Printf("Removing round %d from unchecked store", ri.ID) - err = m.Session.UncheckedRounds().Remove(id.Round(ri.ID)) + err = m.Session.UncheckedRounds().Remove(id.Round(ri.ID), rl.identity.Source, rl.identity.EphId) if err != nil { jww.ERROR.Printf("Could not remove round %d "+ "from unchecked rounds store: %v", ri.ID, err) @@ -161,6 +161,11 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, return message.Bundle{}, errors.WithMessage(errRtn, gateway.RetryableError) } + if !msgResp.HasRound { + return nil, errors.Errorf("cannot pickup messages for round %d "+ + "from %s, it does not have the round yet", roundID, host.GetId()) + } + return msgResp, err }, stop) jww.INFO.Printf("Received message for round %d, processing...", roundID) @@ -172,13 +177,19 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, msgResp := result.(*pb.GetMessagesResponse) // If there are no messages print a warning. Due to the probabilistic nature - // of the bloom filters, false positives will happen some times + // of the bloom filters, false positives will happen sometimes msgs := msgResp.GetMessages() if msgs == nil || len(msgs) == 0 { jww.WARN.Printf("no messages for client %s "+ " in round %d. This happening every once in a while is normal,"+ " but can be indicative of a problem if it is consistent", m.TransmissionID, roundID) + + err = m.Session.UncheckedRounds().Remove(roundID, identity.Source, identity.EphId) + if err != nil { + jww.FATAL.Panicf("Failed to remove round %d: %+v", roundID, err) + } + return message.Bundle{}, nil } @@ -210,7 +221,7 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, func (m *Manager) forceMessagePickupRetry(ri *pb.RoundInfo, rl roundLookup, comms messageRetrievalComms, gwIds []*id.ID, stop *stoppable.Single) (bundle message.Bundle, err error) { - rnd, _ := m.Session.UncheckedRounds().GetRound(id.Round(ri.ID)) + rnd, _ := m.Session.UncheckedRounds().GetRound(id.Round(ri.ID), rl.identity.Source, rl.identity.EphId) if rnd.NumChecks == 0 { // Flip a coin to determine whether to pick up message stream := m.Rng.GetStream() diff --git a/network/rounds/unchecked.go b/network/rounds/unchecked.go index fbdf62b6e687b8a2b1eecc384b77f16e49630eef..81c333392532f75376ff5665f29a3a08428ebd99 100644 --- a/network/rounds/unchecked.go +++ b/network/rounds/unchecked.go @@ -95,7 +95,7 @@ func (m *Manager) processUncheckedRounds(checkInterval time.Duration, backoffTab } // Update the state of the round for next look-up (if needed) - err := uncheckedRoundStore.IncrementCheck(rid) + err := uncheckedRoundStore.IncrementCheck(rid, rnd.Source, rnd.EpdId) if err != nil { jww.ERROR.Printf("processUncheckedRounds error: Could not "+ "increment check attempts for round %d: %v", rid, err) diff --git a/network/rounds/unchecked_test.go b/network/rounds/unchecked_test.go index d50c936425f1b741eee94f51162b875ba987a9a2..646af6e71598d835d03dd29ae168e12cb5bd8ac8 100644 --- a/network/rounds/unchecked_test.go +++ b/network/rounds/unchecked_test.go @@ -63,7 +63,7 @@ func TestUncheckedRoundScheduler(t *testing.T) { } // Add round ot check - err := testManager.Session.UncheckedRounds().AddRound(roundId, roundInfo, expectedEphID, requestGateway) + err := testManager.Session.UncheckedRounds().AddRound(roundId, roundInfo, requestGateway, expectedEphID) if err != nil { t.Fatalf("Could not add round to session: %v", err) } @@ -96,7 +96,8 @@ func TestUncheckedRoundScheduler(t *testing.T) { "\n\tReceived: %v", expectedEphID, testBundle.Identity.EphId) } - _, exists := testManager.Session.UncheckedRounds().GetRound(roundId) + _, exists := testManager.Session.UncheckedRounds().GetRound( + roundId, testBundle.Identity.Source, testBundle.Identity.EphId) if exists { t.Fatalf("Expected round %d to be removed after being processed", roundId) } diff --git a/storage/rounds/roundIdentity.go b/storage/rounds/roundIdentity.go new file mode 100644 index 0000000000000000000000000000000000000000..4148ec014eaa238a2d40af1ee51602f7453aa910 --- /dev/null +++ b/storage/rounds/roundIdentity.go @@ -0,0 +1,50 @@ +package rounds + +import ( + "encoding/base64" + "encoding/binary" + "gitlab.com/elixxir/crypto/hash" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" +) + +// roundIdentitySize is the size of a roundIdentity. +const roundIdentitySize = 32 + +// roundIdentity uniquely identifies a round ID for a specific identity. +type roundIdentity [roundIdentitySize]byte + +// newRoundIdentity generates a new unique round identifier for the round ID, +// recipient ID, and ephemeral ID. +func newRoundIdentity(rid id.Round, recipient *id.ID, ephID ephemeral.Id) roundIdentity { + h, _ := hash.NewCMixHash() + ridBytes := make([]byte, 8) + binary.BigEndian.PutUint64(ridBytes, uint64(rid)) + h.Write(ridBytes) + h.Write(recipient[:]) + h.Write(ephID[:]) + riBytes := h.Sum(nil) + + ri := unmarshalRoundIdentity(riBytes) + + return ri +} + +// String prints a base 64 string representation of roundIdentity. This function +// satisfies the fmt.Stringer interface. +func (ri roundIdentity) String() string { + return base64.StdEncoding.EncodeToString(ri[:]) +} + +// Marshal returns the roundIdentity as a byte slice. +func (ri roundIdentity) Marshal() []byte { + return ri[:] +} + +// unmarshalRoundIdentity unmarshalls the byte slice into a roundIdentity. +func unmarshalRoundIdentity(b []byte) roundIdentity { + var ri roundIdentity + copy(ri[:], b) + + return ri +} diff --git a/storage/rounds/uncheckedRounds.go b/storage/rounds/uncheckedRounds.go index 8eec334e07891b7b296f987ac986d2b3d65362ca..d8082628eb0536b9cb1663f14ef0b82dbee2b193 100644 --- a/storage/rounds/uncheckedRounds.go +++ b/storage/rounds/uncheckedRounds.go @@ -10,7 +10,6 @@ package rounds import ( "bytes" "encoding/binary" - "fmt" "github.com/golang/protobuf/proto" "github.com/pkg/errors" "gitlab.com/elixxir/client/storage/versioned" @@ -27,26 +26,28 @@ const ( uncheckedRoundVersion = 0 roundInfoVersion = 0 uncheckedRoundPrefix = "uncheckedRoundPrefix" + roundKeyPrefix = "roundInfo:" + // Key to store rounds uncheckedRoundKey = "uncheckRounds" - // Key to store individual round + // Housekeeping constant (used for serializing uint64 ie id.Round) uint64Size = 8 - // Maximum checks that can be performed on a round. Intended so that - // a round is checked no more than 1 week approximately (network/rounds.cappedTries + 7) + + // Maximum checks that can be performed on a round. Intended so that a round + // is checked no more than 1 week approximately (network/rounds.cappedTries + 7) maxChecks = 14 ) -// Round identity information used in message retrieval -// Derived from reception.Identity saving data needed -// for message retrieval +// Identity contains round identity information used in message retrieval. +// Derived from reception.Identity saving data needed for message retrieval. type Identity struct { EpdId ephemeral.Id Source *id.ID } -// Unchecked round structure is rounds which failed on message retrieval -// These rounds are stored for retry of message retrieval +// UncheckedRound contains rounds that failed on message retrieval. These rounds +// are stored for retry of message retrieval. type UncheckedRound struct { Info *pb.RoundInfo Id id.Round @@ -58,17 +59,18 @@ type UncheckedRound struct { NumChecks uint64 } -// marshal serializes UncheckedRound r into a byte slice +// marshal serializes UncheckedRound r into a byte slice. func (r UncheckedRound) marshal(kv *versioned.KV) ([]byte, error) { buf := bytes.NewBuffer(nil) // Store teh round info - if r.Info !=nil{ - if err := storeRoundInfo(kv, r.Info); err!=nil{ - return nil,errors.WithMessagef(err,"failed to marshal unchecked rounds") + if r.Info != nil { + if err := storeRoundInfo(kv, r.Info, r.Source, r.EpdId); err != nil { + return nil, errors.WithMessagef(err, + "failed to marshal unchecked rounds") } } - //marshel the round ID + // Marshal the round ID b := make([]byte, uint64Size) binary.LittleEndian.PutUint64(b, uint64(r.Id)) buf.Write(b) @@ -99,7 +101,7 @@ func (r UncheckedRound) marshal(kv *versioned.KV) ([]byte, error) { return buf.Bytes(), nil } -// unmarshal deserializes round data from buff into UncheckedRound r +// unmarshal deserializes round data from buff into UncheckedRound r. func (r *UncheckedRound) unmarshal(kv *versioned.KV, buff *bytes.Buffer) error { // Deserialize the roundInfo r.Id = id.Round(binary.LittleEndian.Uint64(buff.Next(uint64Size))) @@ -109,46 +111,47 @@ func (r *UncheckedRound) unmarshal(kv *versioned.KV, buff *bytes.Buffer) error { sourceId, err := id.Unmarshal(buff.Next(id.ArrIDLen)) if err != nil { - return errors.WithMessagef(err, "Failed to unmarshal round identity.source of %d", r.Id) + return errors.WithMessagef(err, + "Failed to unmarshal round identity.source of %d", r.Id) } r.Source = sourceId // Deserialize the timestamp bytes timestampLen := binary.LittleEndian.Uint64(buff.Next(uint64Size)) - tsByes := buff.Next(int(uint64(timestampLen))) + tsByes := buff.Next(int(timestampLen)) if err = r.LastCheck.UnmarshalBinary(tsByes); err != nil { - return errors.WithMessagef(err, "Failed to unmarshal round timestamp of %d", r.Id) + return errors.WithMessagef(err, + "Failed to unmarshal round timestamp of %d", r.Id) } r.NumChecks = binary.LittleEndian.Uint64(buff.Next(uint64Size)) - r.Info, _ = loadRoundInfo(kv, id.Round(r.Id)) + r.Info, _ = loadRoundInfo(kv, r.Id, r.Source, r.EpdId) return nil } -// Storage object saving rounds to retry for message retrieval +// UncheckedRoundStore stores rounds to retry for message retrieval. type UncheckedRoundStore struct { - list map[id.Round]UncheckedRound + list map[roundIdentity]UncheckedRound mux sync.RWMutex kv *versioned.KV } -// Constructor for a UncheckedRoundStore +// NewUncheckedStore is a constructor for a UncheckedRoundStore. func NewUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { kv = kv.Prefix(uncheckedRoundPrefix) urs := &UncheckedRoundStore{ - list: make(map[id.Round]UncheckedRound, 0), + list: make(map[roundIdentity]UncheckedRound, 0), kv: kv, } return urs, urs.save() - } -// Loads an deserializes a UncheckedRoundStore from memory +// LoadUncheckedStore loads a deserializes a UncheckedRoundStore from memory. func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { kv = kv.Prefix(uncheckedRoundPrefix) @@ -158,7 +161,7 @@ func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { } urs := &UncheckedRoundStore{ - list: make(map[id.Round]UncheckedRound), + list: make(map[roundIdentity]UncheckedRound), kv: kv, } @@ -170,14 +173,16 @@ func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { return urs, err } -// Adds a round to check on the list and saves to memory -func (s *UncheckedRoundStore) AddRound(rid id.Round, ri *pb.RoundInfo, ephID ephemeral.Id, source *id.ID) error { +// AddRound adds a round to check on the list and saves to memory. +func (s *UncheckedRoundStore) AddRound(rid id.Round, ri *pb.RoundInfo, + source *id.ID, ephID ephemeral.Id) error { s.mux.Lock() defer s.mux.Unlock() + roundId := newRoundIdentity(rid, source, ephID) - stored, exists := s.list[rid] + stored, exists := s.list[roundId] - if !exists ||stored.Info == nil { + if !exists || stored.Info == nil { newUncheckedRound := UncheckedRound{ Info: ri, Identity: Identity{ @@ -188,50 +193,55 @@ func (s *UncheckedRoundStore) AddRound(rid id.Round, ri *pb.RoundInfo, ephID eph NumChecks: 0, } - s.list[rid] = newUncheckedRound + s.list[roundId] = newUncheckedRound return s.save() } return nil } -// Retrieves an UncheckedRound from the map, if it exists -func (s *UncheckedRoundStore) GetRound(rid id.Round) (UncheckedRound, bool) { +// GetRound retrieves an UncheckedRound from the map, if it exists. +func (s *UncheckedRoundStore) GetRound(rid id.Round, recipient *id.ID, + ephId ephemeral.Id) (UncheckedRound, bool) { s.mux.RLock() defer s.mux.RUnlock() - rnd, exists := s.list[rid] + rnd, exists := s.list[newRoundIdentity(rid, recipient, ephId)] return rnd, exists } -func (s *UncheckedRoundStore) GetList(t *testing.T) map[id.Round]UncheckedRound{ +func (s *UncheckedRoundStore) GetList(*testing.T) map[roundIdentity]UncheckedRound { s.mux.RLock() defer s.mux.RUnlock() return s.list } - -// Retrieves the list of rounds -func (s *UncheckedRoundStore) IterateOverList(iterator func(rid id.Round, rnd UncheckedRound)) { +// IterateOverList retrieves the list of rounds. +func (s *UncheckedRoundStore) IterateOverList(iterator func(rid id.Round, + rnd UncheckedRound)) { s.mux.RLock() defer s.mux.RUnlock() - for rid, rnd := range s.list { - go iterator(rid, rnd) + + for _, rnd := range s.list { + go iterator(rnd.Id, rnd) } } -// Increments the amount of checks performed on this stored round -func (s *UncheckedRoundStore) IncrementCheck(rid id.Round) error { +// IncrementCheck increments the amount of checks performed on this stored +// round. +func (s *UncheckedRoundStore) IncrementCheck(rid id.Round, recipient *id.ID, + ephId ephemeral.Id) error { s.mux.Lock() defer s.mux.Unlock() - rnd, exists := s.list[rid] + nri := newRoundIdentity(rid, recipient, ephId) + rnd, exists := s.list[nri] if !exists { return errors.Errorf("round %d could not be found in RAM", rid) } - // If a round has been checked the maximum amount of times, - // we bail the round by removing it from store and no longer checking + // If a round has been checked the maximum amount of times, then bail the + // round by removing it from store and no longer checking if rnd.NumChecks >= maxChecks { - if err := s.remove(rid); err != nil { + if err := s.remove(rid, rnd.Identity.Source, ephId); err != nil { return errors.WithMessagef(err, "Round %d reached maximum checks "+ "but could not be removed", rid) } @@ -241,47 +251,56 @@ func (s *UncheckedRoundStore) IncrementCheck(rid id.Round) error { // Update the rounds state rnd.LastCheck = netTime.Now() rnd.NumChecks++ - s.list[rid] = rnd + s.list[nri] = rnd return s.save() } -// Remove deletes a round from UncheckedRoundStore's list and from storage -func (s *UncheckedRoundStore) Remove(rid id.Round) error { +// Remove deletes a round from UncheckedRoundStore's list and from storage. +func (s *UncheckedRoundStore) Remove(rid id.Round, source *id.ID, + ephId ephemeral.Id) error { s.mux.Lock() defer s.mux.Unlock() - return s.remove(rid) + return s.remove(rid, source, ephId) } -// Remove is a helper function which removes the round from UncheckedRoundStore's list -// Note this method is unsafe and should only be used by methods with a lock -func (s *UncheckedRoundStore) remove(rid id.Round) error { - ur, exists := s.list[rid] +// Remove is a helper function which removes the round from +// UncheckedRoundStore's list. Note that this method is unsafe and should only +// be used by methods with a lock. +func (s *UncheckedRoundStore) remove(rid id.Round, recipient *id.ID, + ephId ephemeral.Id) error { + roundId := newRoundIdentity(rid, recipient, ephId) + ur, exists := s.list[roundId] if !exists { return errors.Errorf("round %d does not exist in store", rid) } - delete(s.list, rid) - if err := s.save(); err!=nil{ - return errors.WithMessagef(err,"Failed to delete round %d from unchecked round store", rid) + + delete(s.list, roundId) + if err := s.save(); err != nil { + return errors.WithMessagef(err, + "Failed to delete round %d from unchecked round store", rid) } - //dont delete round infos if none exist - if ur.Info==nil{ + // Do not delete round infos if none exist + if ur.Info == nil { return nil } - if err := deleteRoundInfo(s.kv, rid); err!=nil{ - return errors.WithMessagef(err,"Failed to delete round %d's roundinfo from unchecked round store, " + - "round itself deleted. This is a storage leak", rid) + if err := deleteRoundInfo(s.kv, rid, recipient, ephId); err != nil { + return errors.WithMessagef(err, + "Failed to delete round %d's roundinfo from unchecked round store, "+ + "round itself deleted. This is a storage leak", rid) } + return nil } -// save stores the information from the round list into storage +// save stores the information from the round list into storage. func (s *UncheckedRoundStore) save() error { // Store list of rounds data, err := s.marshal() if err != nil { - return errors.WithMessagef(err, "Could not marshal data for unchecked rounds") + return errors.WithMessagef(err, + "Could not marshal data for unchecked rounds") } // Create the versioned object @@ -294,15 +313,17 @@ func (s *UncheckedRoundStore) save() error { // Save to storage err = s.kv.Set(uncheckedRoundKey, uncheckedRoundVersion, obj) if err != nil { - return errors.WithMessagef(err, "Could not store data for unchecked rounds") + return errors.WithMessagef(err, + "Could not store data for unchecked rounds") } return nil } -// marshal is a helper function which serializes all rounds in list to bytes +// marshal is a helper function which serializes all rounds in list to bytes. func (s *UncheckedRoundStore) marshal() ([]byte, error) { buf := bytes.NewBuffer(nil) + // Write number of rounds the buffer b := make([]byte, 8) binary.PutVarint(b, int64(len(s.list))) @@ -311,7 +332,8 @@ func (s *UncheckedRoundStore) marshal() ([]byte, error) { for rid, rnd := range s.list { rndData, err := rnd.marshal(s.kv) if err != nil { - return nil, errors.WithMessagef(err, "Failed to marshal round %d", rid) + return nil, errors.WithMessagef(err, + "Failed to marshal round %d", rid) } buf.Write(rndData) @@ -321,9 +343,10 @@ func (s *UncheckedRoundStore) marshal() ([]byte, error) { return buf.Bytes(), nil } -// unmarshal deserializes an UncheckedRound from its stored byte data +// unmarshal deserializes an UncheckedRound from its stored byte data. func (s *UncheckedRoundStore) unmarshal(data []byte) error { buff := bytes.NewBuffer(data) + // Get number of rounds in list length, _ := binary.Varint(buff.Next(8)) @@ -331,21 +354,24 @@ func (s *UncheckedRoundStore) unmarshal(data []byte) error { rnd := UncheckedRound{} err := rnd.unmarshal(s.kv, buff) if err != nil { - return errors.WithMessage(err, "Failed to unmarshal rounds in storage") + return errors.WithMessage(err, + "Failed to unmarshal rounds in storage") } - s.list[rnd.Id] = rnd + s.list[newRoundIdentity(rnd.Id, rnd.Source, rnd.EpdId)] = rnd } return nil } -func storeRoundInfo(kv *versioned.KV, info *pb.RoundInfo)error{ +func storeRoundInfo(kv *versioned.KV, info *pb.RoundInfo, recipient *id.ID, + ephID ephemeral.Id) error { now := netTime.Now() data, err := proto.Marshal(info) - if err!=nil{ - return errors.WithMessagef(err, "Failed to store individual unchecked round") + if err != nil { + return errors.WithMessagef(err, + "Failed to store individual unchecked round") } obj := versioned.Object{ @@ -354,11 +380,14 @@ func storeRoundInfo(kv *versioned.KV, info *pb.RoundInfo)error{ Data: data, } - return kv.Set(roundKey(id.Round(info.ID)), roundInfoVersion, &obj) + return kv.Set( + roundKey(id.Round(info.ID), recipient, ephID), roundInfoVersion, &obj) } -func loadRoundInfo(kv *versioned.KV, id id.Round)( *pb.RoundInfo, error){ - vo, err := kv.Get(roundKey(id), roundInfoVersion) +func loadRoundInfo(kv *versioned.KV, id id.Round, recipient *id.ID, + ephID ephemeral.Id) (*pb.RoundInfo, error) { + + vo, err := kv.Get(roundKey(id, recipient, ephID), roundInfoVersion) if err != nil { return nil, err } @@ -371,10 +400,11 @@ func loadRoundInfo(kv *versioned.KV, id id.Round)( *pb.RoundInfo, error){ return ri, nil } -func deleteRoundInfo(kv *versioned.KV, id id.Round)error{ - return kv.Delete(roundKey(id), roundInfoVersion) +func deleteRoundInfo(kv *versioned.KV, id id.Round, recipient *id.ID, + ephID ephemeral.Id) error { + return kv.Delete(roundKey(id, recipient, ephID), roundInfoVersion) } -func roundKey(roundID id.Round)string{ - return fmt.Sprintf("roundInfo:%d", roundID) +func roundKey(roundID id.Round, recipient *id.ID, ephID ephemeral.Id) string { + return roundKeyPrefix + newRoundIdentity(roundID, recipient, ephID).String() } diff --git a/storage/rounds/uncheckedRounds_test.go b/storage/rounds/uncheckedRounds_test.go index 6d962a0dc5fc8e72fb2d42b978f81d41c89eb431..4afecef152219391e6e3990824cdaa047be0783e 100644 --- a/storage/rounds/uncheckedRounds_test.go +++ b/storage/rounds/uncheckedRounds_test.go @@ -24,57 +24,52 @@ func TestNewUncheckedStore(t *testing.T) { kv := versioned.NewKV(make(ekv.Memstore)) testStore := &UncheckedRoundStore{ - list: make(map[id.Round]UncheckedRound), + list: make(map[roundIdentity]UncheckedRound), kv: kv.Prefix(uncheckedRoundPrefix), } store, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not create unchecked stor: %v", err) + t.Fatalf("NewUncheckedStore returned an error: %+v", err) } // Compare manually created object with NewUnknownRoundsStore if !reflect.DeepEqual(testStore, store) { - t.Fatalf("NewUncheckedStore error: "+ - "Returned incorrect Store."+ - "\n\texpected: %+v\n\treceived: %+v", testStore, store) + t.Fatalf("NewUncheckedStore returned incorrect Store."+ + "\nexpected: %+v\nreceived: %+v", testStore, store) } rid := id.Round(1) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} + recipient := id.NewIdFromString("recipientID", id.User, t) + ephID, _, _, _ := ephemeral.GetId(recipient, id.ArrIDLen, netTime.Now().UnixNano()) uncheckedRound := UncheckedRound{ Info: roundInfo, LastCheck: netTime.Now(), NumChecks: 0, + Identity: Identity{Source: recipient, EpdId: ephID}, } - store.list[rid] = uncheckedRound + ri := newRoundIdentity(rid, recipient, ephID) + store.list[ri] = uncheckedRound if err = store.save(); err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not save store: %v", err) + t.Fatalf("Could not save store: %+v", err) } // Test if round list data matches expectedRoundData, err := store.marshal() if err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not marshal data: %v", err) + t.Fatalf("Failed to marshal UncheckedRoundStore: %+v", err) } roundData, err := store.kv.Get(uncheckedRoundKey, uncheckedRoundVersion) if err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not retrieve round list form storage: %v", err) + t.Fatalf("Failed to get round list from storage: %+v", err) } if !bytes.Equal(expectedRoundData, roundData.Data) { - t.Fatalf("NewUncheckedStore error: "+ - "Data from store was not expected"+ - "\n\tExpected %v\n\tReceived: %v", expectedRoundData, roundData.Data) + t.Fatalf("Data from store unexpected.\nexpected %+v\nreceived: %v", + expectedRoundData, roundData.Data) } - } // Unit test @@ -83,51 +78,39 @@ func TestLoadUncheckedStore(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("LoadUncheckedStore error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add round to store rid := id.Round(0) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } - + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromBytes([]byte("Sauron"), t) - err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Fatalf("LoadUncheckedStore error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } // Load store loadedStore, err := LoadUncheckedStore(kv) if err != nil { - t.Fatalf("LoadUncheckedStore error: "+ - "Could not call LoadUncheckedStore: %v", err) + t.Fatalf("LoadUncheckedStore returned an error: %+v", err) } // Check if round is in loaded store - rnd, exists := loadedStore.list[rid] + ri := newRoundIdentity(rid, source, ephId) + rnd, exists := loadedStore.list[ri] if !exists { - t.Fatalf("LoadUncheckedStore error: "+ - "Added round %d not found in loaded store", rid) + t.Fatalf("Added round %d not found in loaded store.", rid) } // Check if set values are expected - if !bytes.Equal(rnd.EpdId[:], ephId[:]) || - !source.Cmp(rnd.Source) { - t.Fatalf("LoadUncheckedStore error: "+ - "Values in loaded round %d are not expected."+ - "\n\tExpected ephemeral: %v"+ - "\n\tReceived ephemeral: %v"+ - "\n\tExpected source: %v"+ - "\n\tReceived source: %v", rid, - ephId, rnd.EpdId, - source, rnd.Source) + if !bytes.Equal(rnd.EpdId[:], ephId[:]) || !source.Cmp(rnd.Source) { + t.Fatalf("Values in loaded round %d are not expected."+ + "\nexpected ephemeral: %d\nreceived ephemeral: %d"+ + "\nexpected source: %s\nreceived source: %s", + rid, ephId.Int64(), rnd.EpdId.Int64(), source, rnd.Source) } - } // Unit test @@ -136,28 +119,23 @@ func TestUncheckedRoundStore_AddRound(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("AddRound error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add round to store rid := id.Round(0) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromBytes([]byte("Sauron"), t) - err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Fatalf("AddRound error: "+ - "Could not add round to store: %v", err) + t.Fatalf("AddRound returned an error: %+v", err) } - if _, exists := testStore.list[rid]; !exists { - t.Errorf("AddRound error: " + - "Could not find added round in list") + ri := newRoundIdentity(rid, source, ephId) + if _, exists := testStore.list[ri]; !exists { + t.Error("Could not find added round in list") } - } // Unit test @@ -166,50 +144,118 @@ func TestUncheckedRoundStore_GetRound(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("GetRound error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add round to store rid := id.Round(0) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} - source := id.NewIdFromBytes([]byte("Sauron"), t) - err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, ephId, source) + source := id.NewIdFromString("Sauron", id.User, t) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Fatalf("GetRound error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } // Retrieve round that was inserted - retrievedRound, exists := testStore.GetRound(rid) + retrievedRound, exists := testStore.GetRound(rid, source, ephId) if !exists { t.Fatalf("GetRound error: " + "Could not get round from store") } - if !bytes.Equal(retrievedRound.EpdId[:], ephId[:]) || - !source.Cmp(retrievedRound.Source) { - t.Fatalf("GetRound error: "+ - "Values in loaded round %d are not expected."+ - "\n\tExpected ephemeral: %v"+ - "\n\tReceived ephemeral: %v"+ - "\n\tExpected source: %v"+ - "\n\tReceived source: %v", rid, - ephId, retrievedRound.EpdId, - source, retrievedRound.Source) + if !bytes.Equal(retrievedRound.EpdId[:], ephId[:]) { + t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+ + "\nexpected: %d\nreceived: %d", rid, ephId.Int64(), + retrievedRound.EpdId.Int64()) + } + + if !source.Cmp(retrievedRound.Source) { + t.Fatalf("Retrieved source ID for round %d does not match expected."+ + "\nexpected: %s\nreceived: %s", rid, source, retrievedRound.Source) } // Try to pull unknown round from store unknownRound := id.Round(1) - _, exists = testStore.GetRound(unknownRound) + unknownRecipient := id.NewIdFromString("invalidID", id.User, t) + unknownEphId := ephemeral.Id{11, 12, 13, 14, 15, 16, 17, 18} + _, exists = testStore.GetRound(unknownRound, unknownRecipient, unknownEphId) if exists { - t.Fatalf("GetRound error: " + - "Should not find unknown round in store.") + t.Fatalf("Should not find unknown round %d in store.", unknownRound) + } +} + +// Tests that two identifies for the same round can be retrieved separately. +func TestUncheckedRoundStore_GetRound_TwoIDs(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + + s, err := NewUncheckedStore(kv) + if err != nil { + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) + } + + // Add round to store for the same round but two sources + rid := id.Round(0) + roundInfo := &pb.RoundInfo{ID: uint64(rid)} + ephId1 := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source1 := id.NewIdFromString("Sauron", id.User, t) + err = s.AddRound(rid, roundInfo, source1, ephId1) + if err != nil { + t.Fatalf("Failed to add round for source 1 to store: %+v", err) + } + + ephId2 := ephemeral.Id{11, 12, 13, 14, 15, 16, 17, 18} + source2 := id.NewIdFromString("Sauron2", id.User, t) + err = s.AddRound(rid, roundInfo, source2, ephId2) + if err != nil { + t.Fatalf("Failed to add round for source 2 to store: %+v", err) + } + + // Increment each a set number of times + incNum1, incNum2 := 3, 13 + for i := 0; i < incNum1; i++ { + if err = s.IncrementCheck(rid, source1, ephId1); err != nil { + t.Errorf("Failed to incremement for source 1 (%d): %+v", i, err) + } + } + for i := 0; i < incNum2; i++ { + if err = s.IncrementCheck(rid, source2, ephId2); err != nil { + t.Errorf("Failed to incremement for source 2 (%d): %+v", i, err) + } + } + + // Retrieve round that was inserted + retrievedRound, exists := s.GetRound(rid, source1, ephId1) + if !exists { + t.Fatalf("Could not get round for source 1 from store") + } + + if !bytes.Equal(retrievedRound.EpdId[:], ephId1[:]) { + t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+ + "\nexpected: %d\nreceived: %d", rid, ephId1.Int64(), + retrievedRound.EpdId.Int64()) + } + + if !source1.Cmp(retrievedRound.Source) { + t.Fatalf("Retrieved source ID for round %d does not match expected."+ + "\nexpected: %s\nreceived: %s", rid, source1, retrievedRound.Source) + } + + retrievedRound, exists = s.GetRound(rid, source2, ephId2) + if !exists { + t.Fatalf("Could not get round for source 2 from store") + } + + if !bytes.Equal(retrievedRound.EpdId[:], ephId2[:]) { + t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+ + "\nexpected: %d\nreceived: %d", rid, ephId2.Int64(), + retrievedRound.EpdId.Int64()) } + if !source2.Cmp(retrievedRound.Source) { + t.Fatalf("Retrieved source ID for round %d does not match expected."+ + "\nexpected: %s\nreceived: %s", rid, source2, retrievedRound.Source) + } } // Unit test @@ -218,39 +264,36 @@ func TestUncheckedRoundStore_GetList(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("GetList error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add rounds to store numRounds := 10 for i := 0; i < numRounds; i++ { rid := id.Round(i) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromUInt(uint64(i), id.User, t) - err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Errorf("GetList error: "+ - "Could not add round to store: %v", err) + t.Errorf("Failed to add round to store: %+v", err) } } // Retrieve list retrievedList := testStore.GetList(t) if len(retrievedList) != numRounds { - t.Errorf("GetList error: "+ - "List returned is not of expected size."+ - "\n\tExpected: %v\n\tReceived: %v", numRounds, len(retrievedList)) + t.Errorf("List returned is not of expected size."+ + "\nexpected: %d\nreceived: %d", numRounds, len(retrievedList)) } for i := 0; i < numRounds; i++ { rid := id.Round(i) - if _, exists := retrievedList[rid]; !exists { - t.Errorf("GetList error: "+ - "Retrieved list does not contain expected round %d.", rid) + ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source := id.NewIdFromUInt(uint64(i), id.User, t) + ri := newRoundIdentity(rid, source, ephId) + if _, exists := retrievedList[ri]; !exists { + t.Errorf("Retrieved list does not contain expected round %d.", rid) } } @@ -262,62 +305,55 @@ func TestUncheckedRoundStore_IncrementCheck(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("IncrementCheck error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add rounds to store numRounds := 10 for i := 0; i < numRounds; i++ { - rid := id.Round(i) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(i)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromUInt(uint64(i), id.User, t) - err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Errorf("IncrementCheck error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } } testRound := id.Round(3) + ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source := id.NewIdFromUInt(uint64(testRound), id.User, t) numChecks := 4 for i := 0; i < numChecks; i++ { - err = testStore.IncrementCheck(testRound) + err = testStore.IncrementCheck(testRound, source, ephId) if err != nil { - t.Errorf("IncrementCheck error: "+ - "Could not increment check for round %d: %v", testRound, err) + t.Errorf("Could not increment check for round %d: %v", testRound, err) } } - rnd, _ := testStore.GetRound(testRound) + rnd, _ := testStore.GetRound(testRound, source, ephId) if rnd.NumChecks != uint64(numChecks) { - t.Errorf("IncrementCheck error: "+ - "Round %d did not have expected number of checks."+ - "\n\tExpected: %v\n\tReceived: %v", testRound, numChecks, rnd.NumChecks) + t.Errorf("Round %d did not have expected number of checks."+ + "\nexpected: %v\nreceived: %v", testRound, numChecks, rnd.NumChecks) } // Error path: check unknown round can not be incremented unknownRound := id.Round(numRounds + 5) - err = testStore.IncrementCheck(unknownRound) + err = testStore.IncrementCheck(unknownRound, source, ephId) if err == nil { - t.Errorf("IncrementCheck error: "+ - "Should not find round %d which was not added to store", unknownRound) + t.Errorf("Should not find round %d which was not added to store", + unknownRound) } // Reach max checks, ensure that round is removed maxRound := id.Round(7) + source = id.NewIdFromUInt(uint64(maxRound), id.User, t) for i := 0; i < maxChecks+1; i++ { - err = testStore.IncrementCheck(maxRound) + err = testStore.IncrementCheck(maxRound, source, ephId) if err != nil { - t.Errorf("IncrementCheck error: "+ - "Could not increment check for round %d: %v", maxRound, err) + t.Errorf("Could not increment check for round %d: %v", maxRound, err) } - } - } // Unit test @@ -325,47 +361,40 @@ func TestUncheckedRoundStore_Remove(t *testing.T) { kv := versioned.NewKV(make(ekv.Memstore)) testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("Remove error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add rounds to store numRounds := 10 for i := 0; i < numRounds; i++ { - rid := id.Round(i) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(i)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromUInt(uint64(i), id.User, t) - err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Errorf("Remove error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } } // Remove round from storage removedRound := id.Round(1) - err = testStore.Remove(removedRound) + ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source := id.NewIdFromUInt(uint64(removedRound), id.User, t) + err = testStore.Remove(removedRound, source, ephId) if err != nil { - t.Errorf("Remove error: "+ - "Could not removed round %d from storage: %v", removedRound, err) + t.Errorf("Could not removed round %d from storage: %v", removedRound, err) } // Check that round was removed - _, exists := testStore.GetRound(removedRound) + _, exists := testStore.GetRound(removedRound, source, ephId) if exists { - t.Errorf("Remove error: "+ - "Round %d expected to be removed from storage", removedRound) + t.Errorf("Round %d expected to be removed from storage", removedRound) } // Error path: attempt to remove unknown round unknownRound := id.Round(numRounds + 5) - err = testStore.Remove(unknownRound) + err = testStore.Remove(unknownRound, source, ephId) if err == nil { - t.Errorf("Remove error: "+ - "Should not removed round %d which is not in storage", unknownRound) + t.Errorf("Should not removed round %d which is not in storage", unknownRound) } - }