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

Merge remote-tracking branch 'origin/restructure' into api2.0

parents e6f5b747 20f588be
Branches
Tags
3 merge requests!510Release,!226WIP: Api2.0,!207WIP: Client Restructure
Showing
with 1626 additions and 96 deletions
...@@ -42,4 +42,9 @@ const ( ...@@ -42,4 +42,9 @@ const (
// EndFileTransfer is sent once all file parts have been transmitted to // EndFileTransfer is sent once all file parts have been transmitted to
// inform the receiver that the file transfer has ended. // inform the receiver that the file transfer has ended.
EndFileTransfer = 51 EndFileTransfer = 51
// ConnectionAuthenticationRequest is sent by the recipient
// of an authenticated connection request
// (see the connect/ package)
ConnectionAuthenticationRequest = 60
) )
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package connect
import (
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/cmix"
clientE2e "gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/primitives/id"
"gitlab.com/xx_network/primitives/netTime"
"sync"
"time"
)
// Constant error messages
const (
roundTrackingTimeoutErr = "timed out waiting for round results"
notAllRoundsSucceededErr = "not all rounds succeeded"
failedToCloseConnectionErr = "failed to close connection with %s " +
"after error %v: %+v"
)
// AuthenticatedConnection is a connect.Connection interface that
// has the receiver authenticating their identity back to the
// initiator.
type AuthenticatedConnection interface {
// Connection is the base Connect API. This allows
// sending and listening to the partner
Connection
// IsAuthenticated is a function which returns whether the
// authenticated connection has been completely established.
IsAuthenticated() bool
}
// AuthenticatedCallback is the callback format required to retrieve
// new AuthenticatedConnection objects as they are established.
type AuthenticatedCallback func(connection AuthenticatedConnection)
// ConnectWithAuthentication is called by the client, ie the one establishing
// connection with the server. Once a connect.Connection has been established
// with the server and then authenticate their identity to the server.
func ConnectWithAuthentication(recipient contact.Contact, myId *id.ID,
salt []byte, myRsaPrivKey *rsa.PrivateKey, myDhPrivKey *cyclic.Int,
rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client,
p Params) (AuthenticatedConnection, error) {
// Track the time since we started to attempt to establish a connection
timeStart := netTime.Now()
// Establish a connection with the server
conn, err := Connect(recipient, myId, myDhPrivKey, rng, grp, net, p)
if err != nil {
return nil, errors.Errorf("failed to establish connection "+
"with recipient %s: %+v", recipient.ID, err)
}
// Build the authenticated connection and return
return connectWithAuthentication(conn, timeStart, recipient, salt, myRsaPrivKey,
rng, net, p)
}
// connectWithAuthentication builds and sends an IdentityAuthentication to
// the server. This will wait until the round it sends on completes or a
// timeout occurs.
func connectWithAuthentication(conn Connection, timeStart time.Time,
recipient contact.Contact, salt []byte, myRsaPrivKey *rsa.PrivateKey,
rng *fastRNG.StreamGenerator,
net cmix.Client, p Params) (AuthenticatedConnection, error) {
// Construct message to prove your identity to the server
payload, err := buildClientAuthRequest(conn.GetPartner(), rng,
myRsaPrivKey, salt)
if err != nil {
// Close connection on an error
errClose := conn.Close()
if errClose != nil {
return nil, errors.Errorf(
failedToCloseConnectionErr,
recipient.ID, err, errClose)
}
return nil, errors.WithMessagef(err, "failed to construct client "+
"authentication message")
}
// Send message to server
rids, _, _, err := conn.SendE2E(catalog.ConnectionAuthenticationRequest,
payload, clientE2e.GetDefaultParams())
if err != nil {
// Close connection on an error
errClose := conn.Close()
if errClose != nil {
return nil, errors.Errorf(
failedToCloseConnectionErr,
recipient.ID, err, errClose)
}
return nil, errors.WithMessagef(err, "failed to send client "+
"authentication message")
}
// Determine that the message is properly sent by tracking the success
// of the round(s)
roundErr := make(chan error, 1)
roundCb := cmix.RoundEventCallback(func(allRoundsSucceeded,
timedOut bool, rounds map[id.Round]cmix.RoundResult) {
// Check for failures while tracking rounds
if timedOut || !allRoundsSucceeded {
if timedOut {
roundErr <- errors.New(roundTrackingTimeoutErr)
} else {
// If we did not time out, then not all rounds succeeded
roundErr <- errors.New(notAllRoundsSucceededErr)
}
return
}
// If no errors occurred, signal so; an authenticated channel may
// be constructed now
roundErr <- nil
})
// Find the remaining time in the timeout since we first sent the message
remainingTime := p.Timeout - netTime.Since(timeStart)
// Track the result of the round(s) we sent the
// identity authentication message on
err = net.GetRoundResults(remainingTime,
roundCb, rids...)
if err != nil {
return nil, errors.Errorf("could not track rounds for successful " +
"identity confirmation message delivery")
}
// Block waiting for confirmation of the round(s) success (or timeout
jww.DEBUG.Printf("AuthenticatedConnection waiting for authenticated "+
"connection with %s to be established...", recipient.ID.String())
// Wait for the round callback to send a round error
err = <-roundErr
if err != nil {
// Close connection on an error
errClose := conn.Close()
if errClose != nil {
return nil, errors.Errorf(
failedToCloseConnectionErr,
recipient.ID, err, errClose)
}
return nil, errors.Errorf("failed to confirm if identity "+
"authentication message was sent to %s: %v", recipient.ID, err)
}
// If channel received no error, construct and return the
// authenticated connection
authConn := buildAuthenticatedConnection(conn)
authConn.setAuthenticated()
return authConn, nil
}
// StartAuthenticatedServer is called by the receiver of an
// authenticated connection request. Calling this will indicate that they
// will handle authenticated requests and verify the client's attempt to
// authenticate themselves. An established AuthenticatedConnection will
// be passed via the callback.
func StartAuthenticatedServer(cb AuthenticatedCallback,
myId *id.ID, privKey *cyclic.Int,
rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client,
p Params) error {
// Register the waiter for a connection establishment
connCb := Callback(func(connection Connection) {
// Upon establishing a connection, register a listener for the
// client's identity proof. If an identity authentication
// message is received and validated, an authenticated connection will
// be passed along via the AuthenticatedCallback
connection.RegisterListener(catalog.ConnectionAuthenticationRequest,
buildAuthConfirmationHandler(cb, connection))
})
return StartServer(connCb, myId, privKey, rng, grp,
net, p)
}
// authenticatedHandler provides an implementation for the
// AuthenticatedConnection interface.
type authenticatedHandler struct {
Connection
isAuthenticated bool
authMux sync.Mutex
}
// buildAuthenticatedConnection assembles an AuthenticatedConnection object.
func buildAuthenticatedConnection(conn Connection) *authenticatedHandler {
return &authenticatedHandler{
Connection: conn,
isAuthenticated: false,
}
}
// IsAuthenticated returns whether the AuthenticatedConnection has completed
// the authentication process.
func (h *authenticatedHandler) IsAuthenticated() bool {
return h.isAuthenticated
}
// setAuthenticated is a helper function which sets the
// AuthenticatedConnection as authenticated.
func (h *authenticatedHandler) setAuthenticated() {
h.authMux.Lock()
defer h.authMux.Unlock()
h.isAuthenticated = true
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: connect/authenticated/authenticated.proto
package connect // import "gitlab.com/elixxir/client/connect/authenticated"
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// Sent by the receiver of the authenticated connection request.
type IdentityAuthentication struct {
Signature []byte `protobuf:"bytes,1,opt,name=Signature,proto3" json:"Signature,omitempty"`
// established between the two partners
RsaPubKey []byte `protobuf:"bytes,2,opt,name=RsaPubKey,proto3" json:"RsaPubKey,omitempty"`
// PEM-encoded
Salt []byte `protobuf:"bytes,3,opt,name=Salt,proto3" json:"Salt,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *IdentityAuthentication) Reset() { *m = IdentityAuthentication{} }
func (m *IdentityAuthentication) String() string { return proto.CompactTextString(m) }
func (*IdentityAuthentication) ProtoMessage() {}
func (*IdentityAuthentication) Descriptor() ([]byte, []int) {
return fileDescriptor_authenticated_9ed9358e4abe7a3a, []int{0}
}
func (m *IdentityAuthentication) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IdentityAuthentication.Unmarshal(m, b)
}
func (m *IdentityAuthentication) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_IdentityAuthentication.Marshal(b, m, deterministic)
}
func (dst *IdentityAuthentication) XXX_Merge(src proto.Message) {
xxx_messageInfo_IdentityAuthentication.Merge(dst, src)
}
func (m *IdentityAuthentication) XXX_Size() int {
return xxx_messageInfo_IdentityAuthentication.Size(m)
}
func (m *IdentityAuthentication) XXX_DiscardUnknown() {
xxx_messageInfo_IdentityAuthentication.DiscardUnknown(m)
}
var xxx_messageInfo_IdentityAuthentication proto.InternalMessageInfo
func (m *IdentityAuthentication) GetSignature() []byte {
if m != nil {
return m.Signature
}
return nil
}
func (m *IdentityAuthentication) GetRsaPubKey() []byte {
if m != nil {
return m.RsaPubKey
}
return nil
}
func (m *IdentityAuthentication) GetSalt() []byte {
if m != nil {
return m.Salt
}
return nil
}
func init() {
proto.RegisterType((*IdentityAuthentication)(nil), "authenticatedConnectionMessages.IdentityAuthentication")
}
func init() {
proto.RegisterFile("connect/authenticated/authenticated.proto", fileDescriptor_authenticated_9ed9358e4abe7a3a)
}
var fileDescriptor_authenticated_9ed9358e4abe7a3a = []byte{
// 180 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xce, 0xcf, 0xcb,
0x4b, 0x4d, 0x2e, 0xc9, 0xcc, 0xcf, 0x2b, 0xd6, 0x4f, 0x2c, 0x2d, 0xc9, 0x48, 0xcd, 0x2b, 0xc9,
0x4c, 0x4e, 0x2c, 0x49, 0x4d, 0x41, 0xe5, 0xe9, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0xc9, 0xa3,
0x08, 0x3a, 0xc3, 0xf5, 0xfa, 0xa6, 0x16, 0x17, 0x27, 0xa6, 0xa7, 0x16, 0x2b, 0x65, 0x70, 0x89,
0x79, 0xa6, 0x80, 0x14, 0x94, 0x54, 0x3a, 0x22, 0x94, 0x66, 0xe6, 0xe7, 0x09, 0xc9, 0x70, 0x71,
0x06, 0x67, 0xa6, 0xe7, 0x25, 0x96, 0x94, 0x16, 0xa5, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04,
0x21, 0x04, 0x40, 0xb2, 0x41, 0xc5, 0x89, 0x01, 0xa5, 0x49, 0xde, 0xa9, 0x95, 0x12, 0x4c, 0x10,
0x59, 0xb8, 0x80, 0x90, 0x10, 0x17, 0x4b, 0x70, 0x62, 0x4e, 0x89, 0x04, 0x33, 0x58, 0x02, 0xcc,
0x76, 0x32, 0x8d, 0x32, 0x4e, 0xcf, 0x2c, 0xc9, 0x49, 0x4c, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f,
0xcd, 0xc9, 0xac, 0xa8, 0xc8, 0x2c, 0xd2, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0xc7, 0xe9,
0xab, 0x24, 0x36, 0xb0, 0x47, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x71, 0x89, 0x27, 0xcf,
0xf9, 0x00, 0x00, 0x00,
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
syntax = "proto3";
package authenticatedConnectionMessages;
option go_package = "gitlab.com/elixxir/client/connect/authenticated";
// Sent by the receiver of the authenticated connection request.
message IdentityAuthentication {
bytes Signature = 1; // Signature of the connection fingerprint
// established between the two partners
bytes RsaPubKey = 2; // The RSA public key of the sender of this message,
// PEM-encoded
bytes Salt = 3; // Salt used to generate the network ID of the client
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package connect
import (
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/diffieHellman"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/crypto/xx"
"gitlab.com/xx_network/primitives/id"
"math/rand"
"testing"
"time"
)
// TestConnectWithAuthentication will test the client/server relationship for
// an AuthenticatedConnection. This will construct a client which will send an
// IdentityAuthentication message to the server, who will hear it and verify
// the contents. This will use a mock connection interface and private
// production code helper functions for easier testing.
func TestConnectWithAuthentication(t *testing.T) {
grp := getGroup()
numPrimeByte := len(grp.GetPBytes())
// Set up cmix handler
mockNet := newMockCmix()
// Set up connect arguments
prng := rand.New(rand.NewSource(42))
dhPrivKey := diffieHellman.GeneratePrivateKey(
numPrimeByte, grp, prng)
dhPubKey := diffieHellman.GeneratePublicKey(dhPrivKey, grp)
salt := make([]byte, 32)
copy(salt, "salt")
myRsaPrivKey, err := rsa.LoadPrivateKeyFromPem(getPrivKey())
if err != nil {
t.Fatalf("Faled to load private key: %v", err)
}
// Construct client ID the proper way as server will need to verify it
// using the xx.NewID function call
myId, err := xx.NewID(myRsaPrivKey.GetPublic(), salt, id.User)
if err != nil {
t.Fatalf("Failed to generate client's id: %+v", err)
}
// Generate server ID using testing interface
serverID := id.NewIdFromString("server", id.User, t)
// Construct recipient
recipient := contact.Contact{
ID: serverID,
DhPubKey: dhPubKey,
}
rng := fastRNG.NewStreamGenerator(1, 1,
csprng.NewSystemRNG)
// Create the mock connection, which will be shared by the client and
// server. This will send the client's request to the server internally
mockConn := newMockConnection(myId, serverID, dhPrivKey, dhPubKey)
// Set up the server's callback, which will pass the authenticated
// connection through via a channel
authConnChan := make(chan AuthenticatedConnection, 1)
serverCb := AuthenticatedCallback(
func(connection AuthenticatedConnection) {
authConnChan <- connection
})
// Initialize params with a shorter timeout to hasten test results
customParams := GetDefaultParams()
customParams.Timeout = 3 * time.Second
// Initialize the server
serverHandler := buildAuthConfirmationHandler(serverCb, mockConn)
// Pass the server's listener to the mock connection so the connection
// can pass the client's message directly to the server
mockConn.listener = serverHandler
// Initialize the client
_, err = connectWithAuthentication(mockConn, time.Now(), recipient,
salt, myRsaPrivKey, rng, mockNet,
customParams)
if err != nil {
t.Fatalf("ConnectWithAuthentication error: %+v", err)
}
// Wait for the server to establish it's connection via the callback
timeout := time.NewTimer(customParams.Timeout)
select {
case <-authConnChan:
return
case <-timeout.C:
t.Fatalf("Timed out waiting for server's authenticated connection " +
"to be established")
}
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package connect
import (
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"gitlab.com/elixxir/client/e2e/ratchet/partner"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/crypto/signature/rsa"
)
// buildClientAuthRequest is a helper function which constructs a marshalled
// IdentityAuthentication message.
func buildClientAuthRequest(newPartner partner.Manager,
rng *fastRNG.StreamGenerator, rsaPrivKey *rsa.PrivateKey,
salt []byte) ([]byte, error) {
// The connection fingerprint (hashed) will be used as a nonce
connectionFp := newPartner.ConnectionFingerprint().Bytes()
opts := rsa.NewDefaultOptions()
h := opts.Hash.New()
h.Write(connectionFp)
nonce := h.Sum(nil)
// Sign the connection fingerprint
stream := rng.GetStream()
defer stream.Close()
signature, err := rsa.Sign(stream, rsaPrivKey,
opts.Hash, nonce, opts)
if err != nil {
return nil, errors.Errorf("failed to sign nonce: %+v", err)
}
// Construct message
pemEncodedRsaPubKey := rsa.CreatePublicKeyPem(rsaPrivKey.GetPublic())
iar := &IdentityAuthentication{
Signature: signature,
RsaPubKey: pemEncodedRsaPubKey,
Salt: salt,
}
payload, err := proto.Marshal(iar)
if err != nil {
return nil, errors.Errorf("failed to marshal identity request "+
"message: %+v", err)
}
return payload, nil
}
...@@ -30,10 +30,17 @@ import ( ...@@ -30,10 +30,17 @@ import (
"time" "time"
) )
const (
// connectionTimeout is the time.Duration for a connection
// to be established before the requester times out.
connectionTimeout = 15 * time.Second
)
// Connection is a wrapper for the E2E and auth packages. // Connection is a wrapper for the E2E and auth packages.
// It can be used to automatically establish an E2E partnership // It can be used to automatically establish an E2E partnership
// with a partner.Manager, or be built from an existing E2E partnership. // with a partner.Manager, or be built from an existing E2E partnership.
// You can then use this interface to send to and receive from the newly-established partner.Manager. // You can then use this interface to send to and receive from the
// newly-established partner.Manager.
type Connection interface { type Connection interface {
// Closer deletes this Connection's partner.Manager and releases resources // Closer deletes this Connection's partner.Manager and releases resources
io.Closer io.Closer
...@@ -41,7 +48,8 @@ type Connection interface { ...@@ -41,7 +48,8 @@ type Connection interface {
// GetPartner returns the partner.Manager for this Connection // GetPartner returns the partner.Manager for this Connection
GetPartner() partner.Manager GetPartner() partner.Manager
// SendE2E is a wrapper for sending specifically to the Connection's partner.Manager // SendE2E is a wrapper for sending specifically to the Connection's
// partner.Manager
SendE2E(mt catalog.MessageType, payload []byte, params clientE2e.Params) ( SendE2E(mt catalog.MessageType, payload []byte, params clientE2e.Params) (
[]id.Round, e2e.MessageID, time.Time, error) []id.Round, e2e.MessageID, time.Time, error)
...@@ -53,37 +61,35 @@ type Connection interface { ...@@ -53,37 +61,35 @@ type Connection interface {
Unregister(listenerID receive.ListenerID) Unregister(listenerID receive.ListenerID)
} }
// Callback is the callback format required to retrieve new Connection objects as they are established // Callback is the callback format required to retrieve
// new Connection objects as they are established.
type Callback func(connection Connection) type Callback func(connection Connection)
// handler provides an implementation for the Connection interface // Params for managing Connection objects.
type handler struct {
partner partner.Manager
e2e clientE2e.Handler
params Params
}
// Params for managing Connection objects
type Params struct { type Params struct {
Auth auth.Param Auth auth.Param
Rekey rekey.Params Rekey rekey.Params
Event event.Reporter Event event.Reporter
Timeout time.Duration
} }
// GetDefaultParams returns a usable set of default Connection parameters // GetDefaultParams returns a usable set of default Connection parameters.
func GetDefaultParams() Params { func GetDefaultParams() Params {
return Params{ return Params{
Auth: auth.GetDefaultParams(), Auth: auth.GetDefaultParams(),
Rekey: rekey.GetDefaultParams(), Rekey: rekey.GetDefaultParams(),
Event: event.NewEventManager(), Event: event.NewEventManager(),
Timeout: connectionTimeout,
} }
} }
// Connect performs auth key negotiation with the given recipient, // Connect performs auth key negotiation with the given recipient,
// and returns a Connection object for the newly-created partner.Manager // and returns a Connection object for the newly-created partner.Manager
// This function is to be used sender-side and will block until the partner.Manager is confirmed // This function is to be used sender-side and will block until the
func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int, rng *fastRNG.StreamGenerator, // partner.Manager is confirmed.
grp *cyclic.Group, net cmix.Client, p Params) (Connection, error) { func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int,
rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client,
p Params) (Connection, error) {
// Build an ephemeral KV // Build an ephemeral KV
kv := versioned.NewKV(ekv.MakeMemstore()) kv := versioned.NewKV(ekv.MakeMemstore())
...@@ -119,23 +125,32 @@ func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int, rng *f ...@@ -119,23 +125,32 @@ func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int, rng *f
} }
// Block waiting for auth to confirm // Block waiting for auth to confirm
jww.DEBUG.Printf("Connection waiting for auth request for %s to be confirmed...", recipient.ID.String()) jww.DEBUG.Printf("Connection waiting for auth request "+
newConnection := <-signalChannel "for %s to be confirmed...", recipient.ID.String())
timeout := time.NewTimer(p.Timeout)
defer timeout.Stop()
select {
case newConnection := <-signalChannel:
// Verify the Connection is complete // Verify the Connection is complete
if newConnection == nil { if newConnection == nil {
return nil, errors.Errorf("Unable to complete connection with partner %s", recipient.ID.String()) return nil, errors.Errorf("Unable to complete connection "+
"with partner %s", recipient.ID.String())
} }
jww.DEBUG.Printf("Connection auth request for %s confirmed", recipient.ID.String()) jww.DEBUG.Printf("Connection auth request for %s confirmed",
recipient.ID.String())
return newConnection, nil return newConnection, nil
case <-timeout.C:
return nil, errors.Errorf("Connection request with "+
"partner %s timed out", recipient.ID.String())
}
} }
// RegisterConnectionCallback assembles a Connection object on the reception-side // StartServer assembles a Connection object on the reception-side
// and feeds it into the given Callback whenever an incoming request // and feeds it into the given Callback whenever an incoming request
// for an E2E partnership with a partner.Manager is confirmed. // for an E2E partnership with a partner.Manager is confirmed.
func RegisterConnectionCallback(cb Callback, myId *id.ID, privKey *cyclic.Int, rng *fastRNG.StreamGenerator, func StartServer(cb Callback, myId *id.ID, privKey *cyclic.Int,
grp *cyclic.Group, net cmix.Client, p Params) error { rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client,
p Params) error {
// Build an ephemeral KV // Build an ephemeral KV
kv := versioned.NewKV(ekv.MakeMemstore()) kv := versioned.NewKV(ekv.MakeMemstore())
...@@ -154,14 +169,24 @@ func RegisterConnectionCallback(cb Callback, myId *id.ID, privKey *cyclic.Int, r ...@@ -154,14 +169,24 @@ func RegisterConnectionCallback(cb Callback, myId *id.ID, privKey *cyclic.Int, r
callback := getAuthCallback(cb, e2eHandler, p) callback := getAuthCallback(cb, e2eHandler, p)
// Build auth object for E2E negotiation // Build auth object for E2E negotiation
_, err = auth.NewState(kv, net, e2eHandler, authState, err := auth.NewState(kv, net, e2eHandler,
rng, p.Event, p.Auth, callback, nil) rng, p.Event, p.Auth, callback, nil)
callback.authState = authState
return err return err
} }
// handler provides an implementation for the Connection interface.
type handler struct {
partner partner.Manager
e2e clientE2e.Handler
params Params
}
// BuildConnection assembles a Connection object // BuildConnection assembles a Connection object
// after an E2E partnership has already been confirmed with the given partner.Manager // after an E2E partnership has already been confirmed with the given
func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler, p Params) Connection { // partner.Manager.
func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler,
p Params) Connection {
return &handler{ return &handler{
partner: partner, partner: partner,
params: p, params: p,
...@@ -169,36 +194,41 @@ func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler, p Pa ...@@ -169,36 +194,41 @@ func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler, p Pa
} }
} }
// Close deletes this Connection's partner.Manager and releases resources // Close deletes this Connection's partner.Manager and releases resources.
func (h *handler) Close() error { func (h *handler) Close() error {
return h.e2e.DeletePartner(h.partner.PartnerId()) return h.e2e.DeletePartner(h.partner.PartnerId())
} }
// GetPartner returns the partner.Manager for this Connection // GetPartner returns the partner.Manager for this Connection.
func (h *handler) GetPartner() partner.Manager { func (h *handler) GetPartner() partner.Manager {
return h.partner return h.partner
} }
// SendE2E is a wrapper for sending specifically to the Connection's partner.Manager // SendE2E is a wrapper for sending specifically to the Connection's
func (h *handler) SendE2E(mt catalog.MessageType, payload []byte, params clientE2e.Params) ( // partner.Manager.
func (h *handler) SendE2E(mt catalog.MessageType, payload []byte,
params clientE2e.Params) (
[]id.Round, e2e.MessageID, time.Time, error) { []id.Round, e2e.MessageID, time.Time, error) {
return h.e2e.SendE2E(mt, h.partner.PartnerId(), payload, params) return h.e2e.SendE2E(mt, h.partner.PartnerId(), payload, params)
} }
// RegisterListener is used for E2E reception // RegisterListener is used for E2E reception
// and allows for reading data sent from the partner.Manager // and allows for reading data sent from the partner.Manager.
func (h *handler) RegisterListener(messageType catalog.MessageType, newListener receive.Listener) receive.ListenerID { func (h *handler) RegisterListener(messageType catalog.MessageType,
return h.e2e.RegisterListener(h.partner.PartnerId(), messageType, newListener) newListener receive.Listener) receive.ListenerID {
return h.e2e.RegisterListener(h.partner.PartnerId(),
messageType, newListener)
} }
// Unregister listener for E2E reception // Unregister listener for E2E reception.
func (h *handler) Unregister(listenerID receive.ListenerID) { func (h *handler) Unregister(listenerID receive.ListenerID) {
h.e2e.Unregister(listenerID) h.e2e.Unregister(listenerID)
} }
// authCallback provides callback functionality for interfacing between auth.State and Connection // authCallback provides callback functionality for interfacing between
// This is used both for blocking creation of a Connection object until the auth Request is confirmed // auth.State and Connection. This is used both for blocking creation of a
// and for dynamically building new Connection objects when an auth Request is received. // Connection object until the auth Request is confirmed and for dynamically
// building new Connection objects when an auth Request is received.
type authCallback struct { type authCallback struct {
// Used for signaling confirmation of E2E partnership // Used for signaling confirmation of E2E partnership
connectionCallback Callback connectionCallback Callback
...@@ -206,38 +236,54 @@ type authCallback struct { ...@@ -206,38 +236,54 @@ type authCallback struct {
// Used for building new Connection objects // Used for building new Connection objects
connectionE2e clientE2e.Handler connectionE2e clientE2e.Handler
connectionParams Params connectionParams Params
authState auth.State
} }
// getAuthCallback returns a callback interface to be passed into the creation of an auth.State object. // getAuthCallback returns a callback interface to be passed into the creation
func getAuthCallback(cb Callback, e2e clientE2e.Handler, params Params) authCallback { // of an auth.State object.
return authCallback{ func getAuthCallback(cb Callback, e2e clientE2e.Handler,
params Params) *authCallback {
return &authCallback{
connectionCallback: cb, connectionCallback: cb,
connectionE2e: e2e, connectionE2e: e2e,
connectionParams: params, connectionParams: params,
} }
} }
// Confirm will be called when an auth Confirm message is processed // Confirm will be called when an auth Confirm message is processed.
func (a authCallback) Confirm(requestor contact.Contact, receptionID receptionID.EphemeralIdentity, round rounds.Round) { func (a authCallback) Confirm(requestor contact.Contact,
jww.DEBUG.Printf("Connection auth request for %s confirmed", requestor.ID.String()) receptionID receptionID.EphemeralIdentity, round rounds.Round) {
jww.DEBUG.Printf("Connection auth request for %s confirmed",
requestor.ID.String())
// After confirmation, get the new partner // After confirmation, get the new partner
newPartner, err := a.connectionE2e.GetPartner(requestor.ID) newPartner, err := a.connectionE2e.GetPartner(requestor.ID)
if err != nil { if err != nil {
jww.ERROR.Printf("Unable to build connection with partner %s: %+v", requestor.ID, err) jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", requestor.ID, err)
// Send a nil connection to avoid hold-ups down the line // Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil) a.connectionCallback(nil)
return return
} }
// Return the new Connection object // Return the new Connection object
a.connectionCallback(BuildConnection(newPartner, a.connectionE2e, a.connectionParams)) a.connectionCallback(BuildConnection(newPartner, a.connectionE2e,
a.connectionParams))
} }
// Request will be called when an auth Request message is processed // Request will be called when an auth Request message is processed.
func (a authCallback) Request(requestor contact.Contact, receptionID receptionID.EphemeralIdentity, round rounds.Round) { func (a authCallback) Request(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
_, err := a.authState.Confirm(requestor)
if err != nil {
jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", requestor.ID, err)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
}
} }
// Reset will be called when an auth Reset operation occurs // Reset will be called when an auth Reset operation occurs.
func (a authCallback) Reset(requestor contact.Contact, receptionID receptionID.EphemeralIdentity, round rounds.Round) { func (a authCallback) Reset(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
} }
#!/bin/bash
# This script will generate the protobuf Golang file (pb.go) out of the protobuf file (.proto).
# This is meant to be called from the top level of the repo.
protoc --go_out=paths=source_relative:. connections/authenticated/authenticated.proto
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package connect
import (
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/crypto/xx"
"gitlab.com/xx_network/primitives/id"
)
// authenticatedServerListenerName is the name of the client's
//listener interface.
const authenticatedServerListenerName = "AuthenticatedServerListener"
// server is an interface that wraps receive.Listener. This handles
// the server listening for the client's proof of identity message.
type server interface {
receive.Listener
}
// serverListener provides an implementation of the server interface.
// This will handle the identity message sent by the client.
type serverListener struct {
// connectionCallback allows an AuthenticatedConnection
// to be passed back upon establishment.
connectionCallback AuthenticatedCallback
// conn used to retrieve the connection context with the partner.
conn Connection
}
// buildAuthConfirmationHandler returns a serverListener object.
// This will handle incoming identity authentication confirmations
// via the serverListener.Hear method. A successful AuthenticatedConnection
// will be passed along via the serverListener.connectionCallback
func buildAuthConfirmationHandler(cb AuthenticatedCallback,
connection Connection) server {
return &serverListener{
connectionCallback: cb,
conn: connection,
}
}
// Hear handles the reception of an IdentityAuthentication by the
// server. It will attempt to verify the identity confirmation of
// the given client.
func (a serverListener) Hear(item receive.Message) {
// Process the message data into a protobuf
iar := &IdentityAuthentication{}
err := proto.Unmarshal(item.Payload, iar)
if err != nil {
a.handleAuthConfirmationErr(err, item.Sender)
return
}
// Process the PEM encoded public key to an rsa.PublicKey object
partnerPubKey, err := rsa.LoadPublicKeyFromPem(iar.RsaPubKey)
if err != nil {
a.handleAuthConfirmationErr(err, item.Sender)
return
}
// Get the new partner
newPartner := a.conn.GetPartner()
// Verify the partner's known ID against the information passed
// along the wire
partnerWireId, err := xx.NewID(partnerPubKey, iar.Salt, id.User)
if err != nil {
a.handleAuthConfirmationErr(err, item.Sender)
return
}
if !newPartner.PartnerId().Cmp(partnerWireId) {
err = errors.New("Failed confirm partner's ID over the wire")
a.handleAuthConfirmationErr(err, item.Sender)
return
}
// The connection fingerprint (hashed) will be used as a nonce
connectionFp := newPartner.ConnectionFingerprint().Bytes()
// Hash the connection fingerprint
opts := rsa.NewDefaultOptions()
h := opts.Hash.New()
h.Write(connectionFp)
nonce := h.Sum(nil)
// Verify the signature
err = rsa.Verify(partnerPubKey, opts.Hash, nonce, iar.Signature, opts)
if err != nil {
a.handleAuthConfirmationErr(err, item.Sender)
return
}
// If successful, pass along the established authenticated connection
// via the callback
jww.DEBUG.Printf("AuthenticatedConnection auth request for %s confirmed",
item.Sender.String())
authConn := buildAuthenticatedConnection(a.conn)
authConn.setAuthenticated()
go a.connectionCallback(authConn)
}
// handleAuthConfirmationErr is a helper function which will close the connection
// between the server and the client. It will also print out the passed in error.
func (a serverListener) handleAuthConfirmationErr(err error, sender *id.ID) {
jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", sender, err)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
err = a.conn.Close()
if err != nil {
jww.ERROR.Printf("Failed to close connection with partner %s: %v",
sender, err)
}
}
// Name returns the name of this listener. This is typically for
// printing/debugging purposes.
func (a serverListener) Name() string {
return authenticatedServerListenerName
}
This diff is collapsed.
////////////////////////////////////////////////////////////////////////////////
// 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"
)
// receiver is the reception handler for a RestServer
type receiver struct {
conn connect.Connection
endpoints *restlike.Endpoints
}
// Hear handles connect.Connection message reception for a RestServer
// Automatically responds to invalid endpoint requests
func (c receiver) 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 = respond(cb(newMessage), c.conn)
} else {
// If no callback, automatically send an error response
respondErr = respond(&restlike.Message{Error: err.Error()}, c.conn)
}
if respondErr != nil {
jww.ERROR.Printf("Unable to singleRespond to request: %+v", err)
}
}
// respond to connect.Connection with the given Message
func respond(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
_, _, _, 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 receiver) Name() string {
return "Restlike"
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/client/restlike"
"testing"
)
// Test failure of proto unmarshal
func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) {
ep := restlike.NewEndpoints()
r := receiver{endpoints: ep}
r.Hear(receive.Message{Payload: []byte("test")})
}
////////////////////////////////////////////////////////////////////////////////
// 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
}
////////////////////////////////////////////////////////////////////////////////
// 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"
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"bytes"
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto"
"testing"
"time"
)
// Test happy path
func TestSingleResponse_Callback(t *testing.T) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
testPath := "test/path"
testMethod := restlike.Get
testMessage := &restlike.Message{
Content: []byte("test"),
Headers: nil,
Method: uint32(testMethod),
Uri: testPath,
Error: "",
}
r := response{cb}
testPayload, err := proto.Marshal(testMessage)
if err != nil {
t.Errorf(err.Error())
}
r.Hear(receive.Message{Payload: testPayload})
select {
case result := <-resultChan:
if result.Uri != testPath {
t.Errorf("Mismatched uri")
}
if result.Method != uint32(testMethod) {
t.Errorf("Mismatched method")
}
if !bytes.Equal(testMessage.Content, result.Content) {
t.Errorf("Mismatched content")
}
case <-time.After(3 * time.Second):
t.Errorf("Test SingleResponse timed out!")
}
}
// Test proto error path
func TestSingleResponse_Callback_ProtoErr(t *testing.T) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
r := response{cb}
r.Hear(receive.Message{Payload: []byte("test")})
select {
case result := <-resultChan:
if len(result.Error) == 0 {
t.Errorf("Expected cb proto error!")
}
case <-time.After(3 * time.Second):
t.Errorf("Test SingleResponse proto error timed out!")
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 := receiver{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
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// All rights reserved. / // All rights reserved. /
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
package restlike package single
import ( import (
"github.com/pkg/errors" "github.com/pkg/errors"
...@@ -12,21 +12,22 @@ import ( ...@@ -12,21 +12,22 @@ import (
"gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix"
"gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds" "gitlab.com/elixxir/client/cmix/rounds"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/single"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"time" "time"
) )
// processor is the reception handler for a RestServer // receiver is the reception handler for a RestServer
type singleReceiver struct { type receiver struct {
endpoints *Endpoints endpoints *restlike.Endpoints
} }
// Callback is the handler for single-use message reception for a RestServer // Callback is the handler for single-use message reception for a RestServer
// Automatically responds to invalid endpoint requests // Automatically responds to invalid endpoint requests
func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) { func (s *receiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) {
// Unmarshal the request payload // Unmarshal the request payload
newMessage := &Message{} newMessage := &restlike.Message{}
err := proto.Unmarshal(req.GetPayload(), newMessage) err := proto.Unmarshal(req.GetPayload(), newMessage)
if err != nil { if err != nil {
jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err) jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err)
...@@ -34,20 +35,20 @@ func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.E ...@@ -34,20 +35,20 @@ func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.E
} }
var respondErr error var respondErr error
if cb, err := s.endpoints.Get(URI(newMessage.GetUri()), Method(newMessage.GetMethod())); err == nil { 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 respond with the result // Send the payload to the proper Callback if it exists and singleRespond with the result
respondErr = respond(cb(newMessage), req) respondErr = singleRespond(cb(newMessage), req)
} else { } else {
// If no callback, automatically send an error response // 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 { 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 // singleRespond to a single.Request with the given Message
func respond(response *Message, req *single.Request) error { func singleRespond(response *restlike.Message, req *single.Request) error {
payload, err := proto.Marshal(response) payload, err := proto.Marshal(response)
if err != nil { if err != nil {
return errors.Errorf("unable to marshal restlike response message: %+v", err) return errors.Errorf("unable to marshal restlike response message: %+v", err)
......
...@@ -4,21 +4,22 @@ ...@@ -4,21 +4,22 @@
// All rights reserved. / // All rights reserved. /
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
package restlike package single
import ( import (
"gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/single"
"testing" "testing"
) )
// Test failure of proto unmarshal // Test failure of proto unmarshal
func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) { func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) {
ep := &Endpoints{endpoints: make(map[URI]map[Method]Callback)} ep := restlike.NewEndpoints()
receiver := singleReceiver{endpoints: ep} r := receiver{endpoints: ep}
testReq := single.BuildTestRequest(make([]byte, 0), t) testReq := single.BuildTestRequest(make([]byte, 0), t)
receiver.Callback(testReq, receptionID.EphemeralIdentity{}, nil) r.Callback(testReq, receptionID.EphemeralIdentity{}, nil)
} }
// Test happy path // Test happy path
...@@ -43,7 +44,7 @@ func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) { ...@@ -43,7 +44,7 @@ func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) {
// if err != nil { // if err != nil {
// t.Errorf(err.Error()) // t.Errorf(err.Error())
// } // }
// receiver := singleReceiver{endpoints: ep} // receiver := receiver{endpoints: ep}
// //
// testPayload, err := proto.Marshal(testMessage) // testPayload, err := proto.Marshal(testMessage)
// if err != nil { // if err != nil {
......
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
// All rights reserved. / // All rights reserved. /
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
package restlike package single
import ( import (
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/catalog" "gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/single"
"gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
...@@ -16,9 +17,9 @@ import ( ...@@ -16,9 +17,9 @@ import (
"google.golang.org/protobuf/proto" "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 // Can be used as stateful or declared inline without state
type SingleRequest struct { type Request struct {
Net single.Cmix Net single.Cmix
Rng csprng.Source Rng csprng.Source
E2eGrp *cyclic.Group E2eGrp *cyclic.Group
...@@ -26,10 +27,10 @@ type SingleRequest struct { ...@@ -26,10 +27,10 @@ type SingleRequest struct {
// Request provides several Method of sending Data to the given URI // Request provides several Method of sending Data to the given URI
// and blocks until the Message is returned // and blocks until the Message is returned
func (s *SingleRequest) Request(recipient contact.Contact, method Method, path URI, func (s *Request) Request(recipient contact.Contact, method restlike.Method, path restlike.URI,
content Data, headers *Headers, singleParams single.RequestParams) (*Message, error) { content restlike.Data, headers *restlike.Headers, singleParams single.RequestParams) (*restlike.Message, error) {
// Build the Message // Build the Message
newMessage := &Message{ newMessage := &restlike.Message{
Content: content, Content: content,
Headers: headers, Headers: headers,
Method: uint32(method), Method: uint32(method),
...@@ -41,14 +42,14 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U ...@@ -41,14 +42,14 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U
} }
// Build callback for the single-use response // Build callback for the single-use response
signalChannel := make(chan *Message, 1) signalChannel := make(chan *restlike.Message, 1)
cb := func(msg *Message) { cb := func(msg *restlike.Message) {
signalChannel <- msg signalChannel <- msg
} }
// Transmit the Message // Transmit the Message
_, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, _, _, 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -63,10 +64,10 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U ...@@ -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 // AsyncRequest provides several Method of sending Data to the given URI
// and will return the Message to the given Callback when received // and will return the Message to the given Callback when received
func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, path URI, func (s *Request) AsyncRequest(recipient contact.Contact, method restlike.Method, path restlike.URI,
content Data, headers *Headers, cb RequestCallback, singleParams single.RequestParams) error { content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, singleParams single.RequestParams) error {
// Build the Message // Build the Message
newMessage := &Message{ newMessage := &restlike.Message{
Content: content, Content: content,
Headers: headers, Headers: headers,
Method: uint32(method), Method: uint32(method),
...@@ -79,6 +80,6 @@ func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, p ...@@ -79,6 +80,6 @@ func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, p
// Transmit the Message // Transmit the Message
_, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, _, _, 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 return err
} }
...@@ -4,22 +4,23 @@ ...@@ -4,22 +4,23 @@
// All rights reserved. / // All rights reserved. /
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
package restlike package single
import ( import (
"gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds" "gitlab.com/elixxir/client/cmix/rounds"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
// processor is the response handler for a Request // response is the response handler for a Request
type singleResponse struct { type response struct {
responseCallback RequestCallback responseCallback restlike.RequestCallback
} }
// Callback is the handler for single-use message responses for a Request // 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) { func (s *response) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) {
newMessage := &Message{} newMessage := &restlike.Message{}
// Handle response errors // Handle response errors
if err != nil { if err != nil {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment