diff --git a/restlike/receiver.go b/restlike/receiver.go index a32de80e5a772846f23a1420ec0e3e88eb4d3139..d6a406e1d6cc023247700cb3ef32b7b87bb1e7c4 100644 --- a/restlike/receiver.go +++ b/restlike/receiver.go @@ -7,6 +7,7 @@ package restlike import ( + "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" @@ -22,6 +23,7 @@ type singleReceiver struct { } // 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 payload newMessage := &Message{} @@ -31,22 +33,30 @@ func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.E return } - // Send the payload to the proper Callback + var respondErr error if cb, err := s.endpoints.Get(URI(newMessage.GetUri()), Method(newMessage.GetMethod())); err == nil { - cb(newMessage) + // Send the payload to the proper Callback if it exists + respondErr = respond(cb(newMessage), req) } else { - // If no callback, send an error response - responseMessage := &Message{Error: err.Error()} - payload, err := proto.Marshal(responseMessage) - if err != nil { - jww.ERROR.Printf("Unable to marshal restlike response message: %+v", err) - return - } - // Send the response - // TODO: Parameterize params and timeout - _, err = req.Respond(payload, cmix.GetDefaultCMIXParams(), 30*time.Second) - if err != nil { - jww.ERROR.Printf("Unable to send restlike response message: %+v", err) - } + // 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/receiver_test.go b/restlike/receiver_test.go new file mode 100644 index 0000000000000000000000000000000000000000..56a46586daaf6411d589b79c0e179ade2ce4e017 --- /dev/null +++ b/restlike/receiver_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/request.go b/restlike/request.go index 8991b003b8f2154308aeeebb0d4ea30a08aa4e9c..5b9e0427089dbeb53a4474dbb0f2afa01112e137 100644 --- a/restlike/request.go +++ b/restlike/request.go @@ -64,7 +64,7 @@ func (s *SingleRequest) Request(method Method, recipient contact.Contact, path U // 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(method Method, recipient contact.Contact, path URI, - content Data, headers *Headers, cb Callback, singleParams single.RequestParams) error { + content Data, headers *Headers, cb RequestCallback, singleParams single.RequestParams) error { // Build the Message newMessage := &Message{ Content: content, diff --git a/restlike/response.go b/restlike/response.go index a81030ddde6d486af472101ad86c23cfc3950b07..41e21b6061c110eaaa06fc25a472bf0326ca1ff3 100644 --- a/restlike/response.go +++ b/restlike/response.go @@ -14,7 +14,7 @@ import ( // processor is the response handler for a Request type singleResponse struct { - responseCallback Callback + responseCallback RequestCallback } // Callback is the handler for single-use message responses for a Request diff --git a/restlike/types.go b/restlike/types.go index 199142892e81e98262fbea4500ed37c2a17eea14..247943844613a43c99b5d666f412a5c53f078471 100644 --- a/restlike/types.go +++ b/restlike/types.go @@ -21,9 +21,13 @@ type Data []byte // Method defines the possible Request types type Method uint32 -// Callback provides the ability to make asynchronous Request +// RequestCallback provides the ability to make asynchronous Request // in order to get the Message later without blocking -type Callback func(*Message) +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 @@ -98,12 +102,12 @@ func (e *Endpoints) Get(path URI, method Method) (Callback, error) { // Remove an Endpoint // Returns an error if Endpoint does not exist func (e *Endpoints) Remove(path URI, method Method) error { - e.Lock() - defer e.Unlock() - 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) 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/receivedRequest.go b/single/receivedRequest.go index 372557fbb0962bfcf3b9ddd4dffd8e747930b3e7..cb445e8b0a02b15d6458dd824a3cdd478d1167f3 100644 --- a/single/receivedRequest.go +++ b/single/receivedRequest.go @@ -18,6 +18,7 @@ import ( "gitlab.com/xx_network/primitives/id/ephemeral" "sync" "sync/atomic" + "testing" "time" ) @@ -223,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, + } +}