diff --git a/client/registration.go b/client/registration.go index 2f999f0ad56862a62554c5da9931c95b901d4a45..fdc5dd0ba94857a6cdec0ce69c0dce980403a41f 100644 --- a/client/registration.go +++ b/client/registration.go @@ -10,6 +10,7 @@ package client import ( + "crypto/sha256" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/any" "github.com/pkg/errors" @@ -17,7 +18,11 @@ import ( pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/comms/messages" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" "google.golang.org/grpc" + "strings" + "time" ) // Client -> Registration Send Function @@ -81,3 +86,95 @@ func (c *Comms) SendGetCurrentClientVersionMessage( result := &pb.ClientVersion{} return result, ptypes.UnmarshalAny(resultMsg, result) } + +// RequestNdf is used to Request an ndf from permissioning +// Used by gateway, client, nodes and gateways +func (c *Comms) RequestNdf(host *connect.Host, + message *pb.NDFHash) (*pb.NDF, error) { + + // Create the Send Function + f := func(conn *grpc.ClientConn) (*any.Any, error) { + // Set up the context + ctx, cancel := connect.MessagingContext() + defer cancel() + + // Send the message + resultMsg, err := pb.NewRegistrationClient( + conn).PollNdf(ctx, message) + if err != nil { + return nil, errors.New(err.Error()) + } + return ptypes.MarshalAny(resultMsg) + } + + // Execute the Send function + jww.TRACE.Printf("Sending Request Ndf message: %+v", message) + resultMsg, err := c.Send(host, f) + if err != nil { + return nil, err + } + + result := &pb.NDF{} + return result, ptypes.UnmarshalAny(resultMsg, result) + +} + +// RetrieveNdf, attempts to connect to the permissioning server to retrieve the latest ndf for the notifications bot +func (c *Comms) RetrieveNdf(currentDef *ndf.NetworkDefinition) (*ndf.NetworkDefinition, error) { + //Hash the notifications bot ndf for comparison with registration's ndf + var ndfHash []byte + // If the ndf passed not nil, serialize and hash it + if currentDef != nil { + //Hash the notifications bot ndf for comparison with registration's ndf + hash := sha256.New() + ndfBytes, err := currentDef.Marshal() + if err != nil { + return nil, err + } + hash.Write(ndfBytes) + ndfHash = hash.Sum(nil) + } + //Put the hash in a message + msg := &pb.NDFHash{Hash: ndfHash} + + regHost, ok := c.Manager.GetHost(&id.Permissioning) + if !ok { + return nil, errors.New("Failed to find permissioning host") + } + + //Send the hash to registration + response, err := c.RequestNdf(regHost, msg) + + // Keep going until we get a grpc error or we get an ndf + for err != nil { + // If there is an unexpected error + if !strings.Contains(err.Error(), ndf.NO_NDF) { + // If it is not an issue with no ndf, return the error up the stack + errMsg := errors.Errorf("Failed to get ndf from permissioning: %v", err) + return nil, errMsg + } + + // If the error is that the permissioning server is not ready, ask again + jww.WARN.Println("Failed to get an ndf, possibly not ready yet. Retying now...") + time.Sleep(250 * time.Millisecond) + response, err = c.RequestNdf(regHost, msg) + + } + + //If there was no error and the response is nil, client's ndf is up-to-date + if response == nil || response.Ndf == nil { + jww.DEBUG.Printf("Our NDF is up-to-date") + return nil, nil + } + + jww.INFO.Printf("Remote NDF: %s", string(response.Ndf)) + + //Otherwise pull the ndf out of the response + updatedNdf, _, err := ndf.DecodeNDF(string(response.Ndf)) + if err != nil { + //If there was an error decoding ndf + errMsg := errors.Errorf("Failed to decode response to ndf: %v", err) + return nil, errMsg + } + return updatedNdf, nil +} diff --git a/client/registration_test.go b/client/registration_test.go index d2480e44d69b5bfdc556d819799b92be88593505..c3ca28dbef6a4e7951f5b6841bf091fb50d6f5d9 100644 --- a/client/registration_test.go +++ b/client/registration_test.go @@ -10,8 +10,10 @@ package client import ( pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/comms/registration" + "gitlab.com/elixxir/comms/testutils" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" "testing" ) @@ -70,3 +72,120 @@ func TestSendCheckClientVersionMessage(t *testing.T) { t.Errorf("CheckClientVersion: Error received: %s", err) } } + +//Smoke test RequestNdf +func TestSendGetUpdatedNDF(t *testing.T) { + GatewayAddress := getNextAddress() + testId := id.NewIdFromString("test", id.Generic, t) + clientId := id.NewIdFromString("client", id.Generic, t) + + rg := registration.StartRegistrationServer(testId, GatewayAddress, + registration.NewImplementation(), nil, nil) + defer rg.Shutdown() + c, err := NewClientComms(clientId, nil, nil, nil) + if err != nil { + t.Errorf("Can't create client comms: %+v", err) + } + manager := connect.NewManagerTesting(t) + + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + host, err := manager.AddHost(testId, GatewayAddress, nil, params) + if err != nil { + t.Errorf("Unable to call NewHost: %+v", err) + } + + _, err = c.RequestNdf(host, &pb.NDFHash{}) + + if err != nil { + t.Errorf("RequestNdf: Error received: %s", err) + } +} + +// Test that Poll NDF handles all comms errors returned properly, and that it decodes and successfully returns an ndf +func TestProtoComms_PollNdf(t *testing.T) { + + // Define a client object + clientId := id.NewIdFromString("client", id.Generic, t) + c, err := NewClientComms(clientId, nil, nil, nil) + if err != nil { + t.Errorf("Can't create client comms: %+v", err) + } + + mockPermServer := registration.StartRegistrationServer(&id.Permissioning, RegistrationAddr, RegistrationHandler, nil, nil) + defer mockPermServer.Shutdown() + + newNdf := &ndf.NetworkDefinition{} + + // Test that poll ndf fails if getHost returns an error + GetHostErrBool = false + RequestNdfErr = nil + + _, err = c.RetrieveNdf(newNdf) + + if err == nil { + t.Errorf("GetHost should have failed but it didnt't: %+v", err) + t.Fail() + } + + // Test that pollNdf returns an error in this case + // This enters an infinite loop is there a way to fix this test? + + // Test that pollNdf Fails if it cant decode the request msg + RequestNdfErr = nil + GetHostErrBool = true + NdfToreturn.Ndf = []byte(ExampleBadNdfJSON) + _, err = c.RetrieveNdf(newNdf) + + if err == nil { + t.Logf("RequestNdf should have failed to parse bad ndf: %+v", err) + t.Fail() + } + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + _, err = c.ProtoComms.AddHost(&id.Permissioning, RegistrationAddr, nil, params) + if err != nil { + t.Errorf("Failed to add permissioning as a host: %+v", err) + } + + // Test that pollNDf Is successful with expected result + RequestNdfErr = nil + GetHostErrBool = true + NdfToreturn.Ndf = []byte(testutils.ExampleJSON) + _, err = c.RetrieveNdf(newNdf) + //comms.mockManager.AddHost() + if err != nil { + t.Logf("Ndf failed to parse: %+v", err) + t.Fail() + } + +} + +// Happy path +func TestProtoComms_PollNdfRepeatedly(t *testing.T) { + // Define a client object + clientId := id.NewIdFromString("client", id.Generic, t) + c, err := NewClientComms(clientId, nil, nil, nil) + if err != nil { + t.Errorf("Can't create client comms: %+v", err) + } + // Start up the mock reg server + mockPermServer := registration.StartRegistrationServer(&id.Permissioning, RegistrationAddrErr, RegistrationError, nil, nil) + defer mockPermServer.Shutdown() + + // Add the host to the comms object + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + _, err = c.ProtoComms.AddHost(&id.Permissioning, RegistrationAddrErr, nil, params) + if err != nil { + t.Errorf("Failed to add permissioning as a host: %+v", err) + } + + newNdf := &ndf.NetworkDefinition{} + + // This should hit the loop until the number of retries is satisfied in the error handler + _, err = c.RetrieveNdf(newNdf) + if err != nil { + t.Errorf("Expected error case, should not return non-error until attempt #5") + } +}