diff --git a/restlike/connect/receiver.go b/restlike/connect/receiver.go new file mode 100644 index 0000000000000000000000000000000000000000..bfe1b55de138bc06e74bf9631bdad49d627207cf --- /dev/null +++ b/restlike/connect/receiver.go @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/restlike" + "google.golang.org/protobuf/proto" +) + +// processor is the reception handler for a RestServer +type connectReceiver struct { + conn connect.Connection + endpoints *restlike.Endpoints +} + +// Hear handles connect.Connection message reception for a RestServer +// Automatically responds to invalid endpoint requests +func (c connectReceiver) Hear(item receive.Message) { + // Unmarshal the request payload + newMessage := &restlike.Message{} + err := proto.Unmarshal(item.Payload, newMessage) + if err != nil { + jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err) + return + } + + var respondErr error + if cb, err := c.endpoints.Get(restlike.URI(newMessage.GetUri()), restlike.Method(newMessage.GetMethod())); err == nil { + // Send the payload to the proper Callback if it exists and singleRespond with the result + respondErr = connRespond(cb(newMessage), c.conn) + } else { + // If no callback, automatically send an error response + respondErr = connRespond(&restlike.Message{Error: err.Error()}, c.conn) + } + if respondErr != nil { + jww.ERROR.Printf("Unable to singleRespond to request: %+v", err) + } +} + +// connRespond to connect.Connection with the given Message +func connRespond(response *restlike.Message, conn connect.Connection) 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 = conn.SendE2E(catalog.XxMessage, payload, e2e.GetDefaultParams()) + if err != nil { + return errors.Errorf("unable to send restlike response message: %+v", err) + } + return nil +} + +// Name is used for debugging +func (c connectReceiver) Name() string { + return "Restlike" +} diff --git a/restlike/connect/request.go b/restlike/connect/request.go new file mode 100644 index 0000000000000000000000000000000000000000..3d836f2ad7377365bd83b4fc6abb214b7bb88b37 --- /dev/null +++ b/restlike/connect/request.go @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/crypto/csprng" + "google.golang.org/protobuf/proto" +) + +// Request allows for making REST-like requests to a RestServer using connect.Connection +// Can be used as stateful or declared inline without state +type Request struct { + Net connect.Connection + 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 *Request) Request(method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, e2eParams e2e.Params) (*restlike.Message, error) { + // Build the Message + newMessage := &restlike.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 response + signalChannel := make(chan *restlike.Message, 1) + cb := func(msg *restlike.Message) { + signalChannel <- msg + } + s.Net.RegisterListener(catalog.XxMessage, response{responseCallback: cb}) + + // Transmit the Message + _, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams) + if err != nil { + return nil, err + } + + // Block waiting for single-use response + jww.DEBUG.Printf("Restlike waiting for connect response from %s...", + s.Net.GetPartner().PartnerId().String()) + newResponse := <-signalChannel + jww.DEBUG.Printf("Restlike connect response received from %s", + s.Net.GetPartner().PartnerId().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 *Request) AsyncRequest(method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, e2eParams e2e.Params) error { + // Build the Message + newMessage := &restlike.Message{ + Content: content, + Headers: headers, + Method: uint32(method), + Uri: string(path), + } + msg, err := proto.Marshal(newMessage) + if err != nil { + return err + } + + // Build callback for the response + s.Net.RegisterListener(catalog.XxMessage, response{responseCallback: cb}) + + // Transmit the Message + _, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams) + return err +} diff --git a/restlike/connect/response.go b/restlike/connect/response.go new file mode 100644 index 0000000000000000000000000000000000000000..99042d7c4bd4947a2f2eed560f4fef8114998d9d --- /dev/null +++ b/restlike/connect/response.go @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/restlike" + "google.golang.org/protobuf/proto" +) + +// response is the response handler for a Request +type response struct { + responseCallback restlike.RequestCallback +} + +// Hear handles for connect.Connection message responses for a Request +func (r response) Hear(item receive.Message) { + newMessage := &restlike.Message{} + + // Unmarshal the payload + err := proto.Unmarshal(item.Payload, newMessage) + if err != nil { + newMessage.Error = err.Error() + } + + // Send the response payload to the responseCallback + r.responseCallback(newMessage) +} + +// Name is used for debugging +func (r response) Name() string { + return "Restlike" +} diff --git a/restlike/connect/server.go b/restlike/connect/server.go new file mode 100644 index 0000000000000000000000000000000000000000..1f09c30b38023ee9b8863f4c4e9b13cf8dae589b --- /dev/null +++ b/restlike/connect/server.go @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/primitives/id" +) + +// Server implements the RestServer interface using connect.Connection +type Server struct { + receptionId *id.ID + endpoints *restlike.Endpoints +} + +// NewServer builds a RestServer with connect.Connection and +// the provided arguments, then registers necessary external services +func NewServer(receptionId *id.ID, privKey *cyclic.Int, + rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, p connect.Params) (*Server, error) { + newServer := &Server{ + receptionId: receptionId, + endpoints: restlike.NewEndpoints(), + } + + // Callback for connection requests + cb := func(conn connect.Connection) { + handler := connectReceiver{endpoints: newServer.endpoints, conn: conn} + conn.RegisterListener(catalog.XxMessage, handler) + } + + // Build the connection listener + err := connect.StartServer(cb, receptionId, privKey, rng, grp, net, p) + if err != nil { + return nil, err + } + return newServer, nil +} + +// GetEndpoints returns the association of a Callback with +// a specific URI and a variety of different REST Method +func (c *Server) GetEndpoints() *restlike.Endpoints { + return c.endpoints +} + +// Close the internal RestServer endpoints and external services +func (c *Server) Close() { + // Clear all internal endpoints + c.endpoints = nil + // TODO: Destroy external services +} diff --git a/restlike/restServer.go b/restlike/restServer.go deleted file mode 100644 index dccb13ad3da83850bdd98be9b568dfab5c933102..0000000000000000000000000000000000000000 --- a/restlike/restServer.go +++ /dev/null @@ -1,67 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// 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/single/receiver.go similarity index 72% rename from restlike/singleReceiver.go rename to restlike/single/receiver.go index 7a137631bea709c8b9d7dabbb0888fec60a60cdc..5fe1691dc4302371e48c601d705575ab911ae4ea 100644 --- a/restlike/singleReceiver.go +++ b/restlike/single/receiver.go @@ -4,7 +4,7 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "github.com/pkg/errors" @@ -12,6 +12,7 @@ import ( "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/restlike" "gitlab.com/elixxir/client/single" "google.golang.org/protobuf/proto" "time" @@ -19,14 +20,14 @@ import ( // processor is the reception handler for a RestServer type singleReceiver struct { - endpoints *Endpoints + endpoints *restlike.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{} + newMessage := &restlike.Message{} err := proto.Unmarshal(req.GetPayload(), newMessage) if err != nil { jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err) @@ -34,20 +35,20 @@ func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.E } 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) + if cb, err := s.endpoints.Get(restlike.URI(newMessage.GetUri()), restlike.Method(newMessage.GetMethod())); err == nil { + // Send the payload to the proper Callback if it exists and singleRespond with the result + respondErr = singleRespond(cb(newMessage), req) } else { // If no callback, automatically send an error response - respondErr = respond(&Message{Error: err.Error()}, req) + respondErr = singleRespond(&restlike.Message{Error: err.Error()}, req) } if respondErr != nil { - jww.ERROR.Printf("Unable to respond to request: %+v", err) + jww.ERROR.Printf("Unable to singleRespond to request: %+v", err) } } -// respond to a single.Request with the given Message -func respond(response *Message, req *single.Request) error { +// singleRespond to a single.Request with the given Message +func singleRespond(response *restlike.Message, req *single.Request) error { payload, err := proto.Marshal(response) if err != nil { return errors.Errorf("unable to marshal restlike response message: %+v", err) diff --git a/restlike/singleReceiver_test.go b/restlike/single/receiver_test.go similarity index 95% rename from restlike/singleReceiver_test.go rename to restlike/single/receiver_test.go index 56a46586daaf6411d589b79c0e179ade2ce4e017..53ed5db5438465afeab39378202bb3a95174396b 100644 --- a/restlike/singleReceiver_test.go +++ b/restlike/single/receiver_test.go @@ -4,17 +4,18 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/restlike" "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)} + ep := restlike.NewEndpoints() receiver := singleReceiver{endpoints: ep} testReq := single.BuildTestRequest(make([]byte, 0), t) diff --git a/restlike/singleRequest.go b/restlike/single/request.go similarity index 69% rename from restlike/singleRequest.go rename to restlike/single/request.go index 774f112c43745c0a36b9692ceb209bd4e8b783f1..8a0b550867b5d2ebdaa43333fcf529940c701982 100644 --- a/restlike/singleRequest.go +++ b/restlike/single/request.go @@ -4,11 +4,12 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/restlike" "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" @@ -16,9 +17,9 @@ import ( "google.golang.org/protobuf/proto" ) -// SingleRequest allows for making REST-like requests to a RestServer using single-use messages +// Request 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 { +type Request struct { Net single.Cmix Rng csprng.Source E2eGrp *cyclic.Group @@ -26,10 +27,10 @@ type SingleRequest struct { // 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) { +func (s *Request) Request(recipient contact.Contact, method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, singleParams single.RequestParams) (*restlike.Message, error) { // Build the Message - newMessage := &Message{ + newMessage := &restlike.Message{ Content: content, Headers: headers, Method: uint32(method), @@ -41,14 +42,14 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U } // Build callback for the single-use response - signalChannel := make(chan *Message, 1) - cb := func(msg *Message) { + signalChannel := make(chan *restlike.Message, 1) + cb := func(msg *restlike.Message) { signalChannel <- msg } // Transmit the Message _, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, - &singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) + &response{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) if err != nil { return nil, err } @@ -63,10 +64,10 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, 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(recipient contact.Contact, method Method, path URI, - content Data, headers *Headers, cb RequestCallback, singleParams single.RequestParams) error { +func (s *Request) AsyncRequest(recipient contact.Contact, method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, singleParams single.RequestParams) error { // Build the Message - newMessage := &Message{ + newMessage := &restlike.Message{ Content: content, Headers: headers, Method: uint32(method), @@ -79,6 +80,6 @@ func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, p // Transmit the Message _, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, - &singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) + &response{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) return err } diff --git a/restlike/singleResponse.go b/restlike/single/response.go similarity index 74% rename from restlike/singleResponse.go rename to restlike/single/response.go index 41e21b6061c110eaaa06fc25a472bf0326ca1ff3..edc4833226d0372ae6816f4a190772127aaf6e7e 100644 --- a/restlike/singleResponse.go +++ b/restlike/single/response.go @@ -4,22 +4,23 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/restlike" "google.golang.org/protobuf/proto" ) -// processor is the response handler for a Request -type singleResponse struct { - responseCallback RequestCallback +// response is the response handler for a Request +type response struct { + responseCallback restlike.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{} +func (s *response) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) { + newMessage := &restlike.Message{} // Handle response errors if err != nil { diff --git a/restlike/singleResponse_test.go b/restlike/single/response_test.go similarity index 82% rename from restlike/singleResponse_test.go rename to restlike/single/response_test.go index 0381b452cca26c9d892abfe2b8329569a1eef689..5d500cd99c1fb072a2c5e13a2d9f1d6bb4d208d4 100644 --- a/restlike/singleResponse_test.go +++ b/restlike/single/response_test.go @@ -4,12 +4,13 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "bytes" "github.com/pkg/errors" "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/restlike" "google.golang.org/protobuf/proto" "testing" "time" @@ -17,13 +18,13 @@ import ( // Test happy path func TestSingleResponse_Callback(t *testing.T) { - resultChan := make(chan *Message, 1) - cb := func(input *Message) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { resultChan <- input } testPath := "test/path" - testMethod := Get - testMessage := &Message{ + testMethod := restlike.Get + testMessage := &restlike.Message{ Content: []byte("test"), Headers: nil, Method: uint32(testMethod), @@ -31,7 +32,7 @@ func TestSingleResponse_Callback(t *testing.T) { Error: "", } - response := singleResponse{cb} + response := response{cb} testPayload, err := proto.Marshal(testMessage) if err != nil { @@ -57,11 +58,11 @@ func TestSingleResponse_Callback(t *testing.T) { // Test error input path func TestSingleResponse_Callback_Err(t *testing.T) { - resultChan := make(chan *Message, 1) - cb := func(input *Message) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { resultChan <- input } - response := singleResponse{cb} + response := response{cb} response.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test")) @@ -77,11 +78,11 @@ func TestSingleResponse_Callback_Err(t *testing.T) { // Test proto error path func TestSingleResponse_Callback_ProtoErr(t *testing.T) { - resultChan := make(chan *Message, 1) - cb := func(input *Message) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { resultChan <- input } - response := singleResponse{cb} + response := response{cb} response.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil) diff --git a/restlike/single/server.go b/restlike/single/server.go new file mode 100644 index 0000000000000000000000000000000000000000..584acdddbd623d5f5f95b66eb42261c947a77d56 --- /dev/null +++ b/restlike/single/server.go @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/primitives/id" +) + +// Server implements the RestServer interface using single-use +type Server struct { + receptionId *id.ID + listener single.Listener + endpoints *restlike.Endpoints +} + +// NewServer builds a RestServer with single-use and +// the provided arguments, then registers necessary external services +func NewServer(receptionId *id.ID, privKey *cyclic.Int, net single.ListenCmix, e2eGrp *cyclic.Group) *Server { + newServer := &Server{ + receptionId: receptionId, + endpoints: restlike.NewEndpoints(), + } + newServer.listener = single.Listen(catalog.RestLike, receptionId, privKey, + net, e2eGrp, &singleReceiver{newServer.endpoints}) + return newServer +} + +// GetEndpoints returns the association of a Callback with +// a specific URI and a variety of different REST Method +func (r *Server) GetEndpoints() *restlike.Endpoints { + return r.endpoints +} + +// Close the internal RestServer endpoints and external services +func (r *Server) Close() { + // Clear all internal endpoints + r.endpoints = nil + // Destroy external services + r.listener.Stop() +} diff --git a/restlike/types.go b/restlike/types.go index 247943844613a43c99b5d666f412a5c53f078471..8937575ed720bb14634bf34e6d02908988a28b13 100644 --- a/restlike/types.go +++ b/restlike/types.go @@ -68,6 +68,11 @@ type Endpoints struct { sync.RWMutex } +// NewEndpoints returns a new Endpoints object +func NewEndpoints() *Endpoints { + return &Endpoints{endpoints: make(map[URI]map[Method]Callback)} +} + // Add a new Endpoint // Returns an error if Endpoint already exists func (e *Endpoints) Add(path URI, method Method, cb Callback) error {