Skip to content
Snippets Groups Projects
Commit aabbc7bb authored by Richard T. Carback III's avatar Richard T. Carback III
Browse files

Merge remote-tracking branch 'origin/XX-3826/Fingerprints' into XX-3829/Triggers

parents 436d2f43 0a142c7b
No related branches found
No related tags found
5 merge requests!510Release,!207WIP: Client Restructure,!203Symmetric broadcast,!187Xx 3829/triggers,!186Draft: Add fingerprinting logic
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package network
import (
"github.com/pkg/errors"
"gitlab.com/elixxir/client/interfaces"
"gitlab.com/elixxir/primitives/format"
"sync"
)
// Processor is an object which ties an interfaces.MessageProcessorFP
// to a lock. This prevents the processor from being used in multiple
// different threads.
type Processor struct {
interfaces.MessageProcessorFP
sync.Mutex
}
// NewFingerprints is a constructor function for the Processor object.
func newProcessor(mp interfaces.MessageProcessorFP) *Processor {
return &Processor{
MessageProcessorFP: mp,
Mutex: sync.Mutex{},
}
}
// Fingerprints is a thread-safe map, mapping format.Fingerprint's to
// a Processor object.
type Fingerprints struct {
fingerprints map[format.Fingerprint]*Processor
sync.RWMutex
}
// NewFingerprints is a constructor function for the Fingerprints tracker.
func NewFingerprints() *Fingerprints {
return &Fingerprints{
fingerprints: make(map[format.Fingerprint]*Processor),
RWMutex: sync.RWMutex{},
}
}
// Get is a thread-safe getter for the Fingerprints map. Get returns the mapped
// processor and true (representing that it exists in the map) if the provided
// fingerprint has an entry. Otherwise, Get returns nil and false.
func (f *Fingerprints) Get(fingerprint format.Fingerprint) (*Processor, bool) {
f.RLock()
defer f.RUnlock()
fp, exists := f.fingerprints[fingerprint]
if !exists {
return nil, false
}
return fp, true
}
// AddFingerprint is a thread-safe setter for the Fingerprints map. AddFingerprint
// maps the given fingerprint key to the processor value. If there is already
// an entry for this fingerprint, the method returns with no write operation.
func (f *Fingerprints) AddFingerprint(fingerprint format.Fingerprint,
processor interfaces.MessageProcessorFP) {
f.Lock()
defer f.Unlock()
f.addFingerprint(fingerprint, processor)
}
// AddFingerprints is a thread-safe setter for multiple entries into
// the Fingerprints map. If there is not a 1:1 relationship between
// fingerprints and processors slices (i.e. the lengths of these slices
// are equivalent), an error will be returned.
// Otherwise, each fingerprint is written to the associated processor.
// If there is already an entry for the given fingerprint/processor pair,
// no write operation will occur for this pair.
func (f *Fingerprints) AddFingerprints(fps []format.Fingerprint,
processors []interfaces.MessageProcessorFP) error {
f.Lock()
defer f.Unlock()
if len(fps) != len(processors) {
return errors.Errorf("Canot perform a batch add when there are "+
"not an equal amount of fingerprints and processors. "+
"Given %d fingerprints and %d processors.", len(fps), len(processors))
}
for i, fp := range fps {
f.addFingerprint(fp, processors[i])
}
return nil
}
// addFingerprint is a non-thread-safe helper function which writes a Processor
// to the given fingerprint key. If an entry already exists for this fingerprint key,
// no write operation occurs.
func (f *Fingerprints) addFingerprint(fingerprint format.Fingerprint,
processor interfaces.MessageProcessorFP) {
if _, exists := f.fingerprints[fingerprint]; exists {
return
}
newMsgProc := newProcessor(processor)
f.fingerprints[fingerprint] = newMsgProc
}
// RemoveFingerprint is a thread-safe deletion operation on the Fingerprints map.
// It will remove the entry for the given fingerprint from the map.
func (f *Fingerprints) RemoveFingerprint(fingerprint format.Fingerprint) {
f.Lock()
defer f.Unlock()
delete(f.fingerprints, fingerprint)
}
// RemoveFingerprints is a thread-safe batch deletion operation on the Fingerprints map.
// It will remove the entries for the given fingerprints from the map.
func (f *Fingerprints) RemoveFingerprints(fingerprint []format.Fingerprint) {
f.Lock()
defer f.Unlock()
for _, fp := range fingerprint {
delete(f.fingerprints, fp)
}
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package network
import (
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/interfaces"
"gitlab.com/elixxir/primitives/format"
"reflect"
"strconv"
"sync"
"testing"
)
// Unit test.
func TestNewFingerprints(t *testing.T) {
expected := &Fingerprints{
fingerprints: make(map[format.Fingerprint]*Processor),
RWMutex: sync.RWMutex{},
}
received := NewFingerprints()
if !reflect.DeepEqual(expected, received) {
t.Fatalf("NewFingerprint error: Did not construct expected object."+
"\nExpected: %v"+
"\nReceived: %v", expected, received)
}
}
// Unit test.
func TestFingerprints_Get(t *testing.T) {
// Construct fingerprint map
fpTracker := NewFingerprints()
// Construct fingerprint and processor values
fp := format.NewFingerprint([]byte("test"))
mp := NewMockMsgProcessor(t)
// Add the values to the tracker
fpTracker.AddFingerprint(fp, mp)
// Attempt to retrieve value from map
received, exists := fpTracker.Get(fp)
if !exists {
t.Fatalf("Get error: Did not retrieve fingerprint (%s) that "+
"should have been in map.", fp)
}
// Check that received value contains the expected data
expected := newProcessor(mp)
if !reflect.DeepEqual(received, expected) {
t.Fatalf("Get error: Map does not contain expected data."+
"\nExpected: %v"+
"\nReceived: %v", expected, received)
}
}
// Unit test.
func TestFingerprints_AddFingerprint(t *testing.T) {
// Construct fingerprint map
fpTracker := NewFingerprints()
// Construct fingerprint and processor values
fp := format.NewFingerprint([]byte("test"))
mp := NewMockMsgProcessor(t)
// Add the values to the tracker
fpTracker.AddFingerprint(fp, mp)
// Check that the fingerprint key has a map entry
received, exists := fpTracker.fingerprints[fp]
if !exists {
t.Fatalf("AddFingerprint did not write to map as expected. "+
"Fingerprint %s not found in map", fp)
}
// Check that received value contains the expected data
expected := newProcessor(mp)
if !reflect.DeepEqual(received, expected) {
t.Fatalf("AddFingerprint error: Map does not contain expected data."+
"\nExpected: %v"+
"\nReceived: %v", expected, received)
}
}
// Unit test.
func TestFingerprints_AddFingerprints(t *testing.T) {
// Construct fingerprints map
fpTracker := NewFingerprints()
// Construct slices of fingerprints and processors
numTests := 100
fingerprints := make([]format.Fingerprint, 0, numTests)
processors := make([]interfaces.MessageProcessorFP, 0, numTests)
for i := 0; i < numTests; i++ {
fp := format.NewFingerprint([]byte(strconv.Itoa(i)))
mp := NewMockMsgProcessor(t)
fingerprints = append(fingerprints, fp)
processors = append(processors, mp)
}
// Add slices to map
err := fpTracker.AddFingerprints(fingerprints, processors)
if err != nil {
t.Fatalf("AddFingerprints unexpected error: %v", err)
}
// Make sure every fingerprint is mapped to it's expected processor
for i, expected := range fingerprints {
received, exists := fpTracker.fingerprints[expected]
if !exists {
t.Errorf("AddFingerprints did not write to map as expected. "+
"Fingerprint number %d (value: %s) not found in map", i, expected)
}
if !reflect.DeepEqual(received, expected) {
t.Fatalf("AddFingerprints error: Map does not contain expected data for "+
"fingerprint number %d."+
"\nExpected: %v"+
"\nReceived: %v", i, expected, received)
}
}
}
// Error case: Call Fingerprints.AddFingerprints with fingerprint and processor
// slices of different lengths.
func TestFingerprints_AddFingerprints_Error(t *testing.T) {
// Construct fingerprint map
fpTracker := NewFingerprints()
// Construct 2 slices of different lengths
fingerprints := []format.Fingerprint{
format.NewFingerprint([]byte("1")),
format.NewFingerprint([]byte("2")),
format.NewFingerprint([]byte("3")),
}
processors := []interfaces.MessageProcessorFP{
NewMockMsgProcessor(t),
}
// Attempt to add fingerprints
err := fpTracker.AddFingerprints(fingerprints, processors)
if err == nil {
t.Fatalf("AddFingerprints should have received an error with mismatched " +
"slices length")
}
}
func TestFingerprints_RemoveFingerprint(t *testing.T) {
// Construct fingerprint map
fpTracker := NewFingerprints()
// Construct fingerprint and processor values
fp := format.NewFingerprint([]byte("test"))
mp := NewMockMsgProcessor(t)
// Add the values to the tracker
fpTracker.AddFingerprint(fp, mp)
// Remove value from tracker
fpTracker.RemoveFingerprint(fp)
// Check that value no longer exists within the map
if _, exists := fpTracker.fingerprints[fp]; exists {
t.Fatalf("RemoveFingerprint error: "+
"Fingerprint %s exists in map after a RemoveFingerprint call", fp)
}
}
// Unit test.
func TestFingerprints_RemoveFingerprints(t *testing.T) {
// Construct fingerprints map
fpTracker := NewFingerprints()
// Construct slices of fingerprints and processors
numTests := 100
fingerprints := make([]format.Fingerprint, 0, numTests)
processors := make([]interfaces.MessageProcessorFP, 0, numTests)
for i := 0; i < numTests; i++ {
fp := format.NewFingerprint([]byte(strconv.Itoa(i)))
mp := NewMockMsgProcessor(t)
fingerprints = append(fingerprints, fp)
processors = append(processors, mp)
}
// Add slices to map
err := fpTracker.AddFingerprints(fingerprints, processors)
if err != nil {
t.Fatalf("AddFingerprints unexpected error: %v", err)
}
fpTracker.RemoveFingerprints(fingerprints)
// Make sure every fingerprint is mapped to it's expected processor
for i, expected := range fingerprints {
if received, exists := fpTracker.fingerprints[expected]; !exists {
t.Fatalf("RemoveFingerprints error: Map does not contain "+
"expected data for fingerprint number %d."+
"\nExpected: %v"+
"\nReceived: %v", i, expected, received)
}
}
}
// todo: consider moving this to a test utils somewhere else.. maybe in the interfaces package?
type MockMsgProcessor struct{}
func NewMockMsgProcessor(face interface{}) *MockMsgProcessor {
switch face.(type) {
case *testing.T, *testing.M, *testing.B, *testing.PB:
break
default:
jww.FATAL.Panicf("NewMockMsgProcessor is restricted to testing only. Got %T", face)
}
return &MockMsgProcessor{}
}
func (mock *MockMsgProcessor) MarkFingerprintUsed(fingerprint format.Fingerprint) {
return
}
func (mock *MockMsgProcessor) Process(message format.Message, fingerprint format.Fingerprint) {
return
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment