Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
feat: Transport Return Route - Framework Configuration
Browse files Browse the repository at this point in the history
- Aries framework level option to set the transport return route value [none, all, thread]; Added the option at framework level(edge agent doesn't have inbound capability) rather than each DIDComm message type.
- Pass the framework option to Outbound Dispatcher
- Outbound dispatcher injects the transport decorator to the outbound message before packing

Signed-off-by: Rolson Quadras <[email protected]>
  • Loading branch information
rolsonquadras committed Nov 29, 2019
1 parent cdc260d commit 106115c
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 36 deletions.
39 changes: 33 additions & 6 deletions pkg/didcomm/dispatcher/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,35 @@ package dispatcher
import (
"encoding/json"
"fmt"
"strings"

"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
commontransport "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/transport"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
)

// provider interface for outbound ctx
type provider interface {
Packager() commontransport.Packager
OutboundTransports() []transport.OutboundTransport
TransportReturnRoute() string
}

// OutboundDispatcher dispatch msgs to destination
type OutboundDispatcher struct {
outboundTransports []transport.OutboundTransport
packager commontransport.Packager
outboundTransports []transport.OutboundTransport
packager commontransport.Packager
transportReturnRoute string
}

// NewOutbound return new dispatcher outbound instance
func NewOutbound(prov provider) *OutboundDispatcher {
return &OutboundDispatcher{outboundTransports: prov.OutboundTransports(), packager: prov.Packager()}
return &OutboundDispatcher{
outboundTransports: prov.OutboundTransports(),
packager: prov.Packager(),
transportReturnRoute: prov.TransportReturnRoute(),
}
}

// Send msg
Expand All @@ -39,18 +47,37 @@ func (o *OutboundDispatcher) Send(msg interface{}, senderVerKey string, des *ser
continue
}

bytes, err := json.Marshal(msg)
req, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed marshal to bytes: %w", err)
}

// update the outbound message with transport return route option [all or thread]
if o.transportReturnRoute == decorator.TransportReturnRouteAll ||
o.transportReturnRoute == decorator.TransportReturnRouteThread {
// create the decorator with the option set in the framework
transportDec := &decorator.Transport{ReturnRoute: &decorator.ReturnRoute{Value: o.transportReturnRoute}}

transportDecJSON, jsonErr := json.Marshal(transportDec)
if jsonErr != nil {
return fmt.Errorf("json marshal : %w", jsonErr)
}

request := string(req)
index := strings.Index(request, "{")

// add transport route option decorator to the original request
req = []byte(request[:index+1] + string(transportDecJSON)[1:len(string(transportDecJSON))-1] + "," +
request[index+1:])
}

packedMsg, err := o.packager.PackMessage(
&commontransport.Envelope{Message: bytes, FromVerKey: senderVerKey, ToVerKeys: des.RecipientKeys})
&commontransport.Envelope{Message: req, FromVerKey: senderVerKey, ToVerKeys: des.RecipientKeys})
if err != nil {
return fmt.Errorf("failed to pack msg: %w", err)
}

_, err = v.Send(packedMsg, des.ServiceEndpoint)
_, err = v.Send(packedMsg, des)
if err != nil {
return fmt.Errorf("failed to send msg using http outbound transport: %w", err)
}
Expand Down
123 changes: 121 additions & 2 deletions pkg/didcomm/dispatcher/outbound_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,28 @@ SPDX-License-Identifier: Apache-2.0
package dispatcher

import (
"encoding/json"
"errors"
"fmt"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
commontransport "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/transport"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
mockdidcomm "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm"
mockpackager "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/packager"
)

func TestOutboundDispatcher_Send(t *testing.T) {
t.Run("test success", func(t *testing.T) {
o := NewOutbound(&mockProvider{packagerValue: &mockpackager.Packager{},
outboundTransportsValue: []transport.OutboundTransport{&mockdidcomm.MockOutboundTransport{AcceptValue: true}}})
o := NewOutbound(&mockProvider{
packagerValue: &mockpackager.Packager{},
outboundTransportsValue: []transport.OutboundTransport{&mockdidcomm.MockOutboundTransport{AcceptValue: true}},
})
require.NoError(t, o.Send("data", "", &service.Destination{ServiceEndpoint: "url"}))
})

Expand Down Expand Up @@ -52,9 +58,89 @@ func TestOutboundDispatcher_Send(t *testing.T) {
})
}

func TestOutboundDispatcherTransportReturnRoute(t *testing.T) {
t.Run("transport route option - value set all", func(t *testing.T) {
transportReturnRoute := "all"
req := &decorator.Thread{
ID: uuid.New().String(),
}

outboundReq := struct {
*decorator.Transport
*decorator.Thread
}{
&decorator.Transport{ReturnRoute: &decorator.ReturnRoute{Value: transportReturnRoute}},
req,
}
expectedRequest, err := json.Marshal(outboundReq)
require.NoError(t, err)
require.NotNil(t, expectedRequest)

o := NewOutbound(&mockProvider{
packagerValue: &mockPackager{},
outboundTransportsValue: []transport.OutboundTransport{&mockOutboundTransport{
expectedRequest: string(expectedRequest)},
},
transportReturnRoute: transportReturnRoute,
})

require.NoError(t, o.Send(req, "", &service.Destination{ServiceEndpoint: "url"}))
})

t.Run("transport route option - value set thread", func(t *testing.T) {
transportReturnRoute := "thread"
req := &decorator.Thread{
ID: uuid.New().String(),
}

outboundReq := struct {
*decorator.Transport
*decorator.Thread
}{
&decorator.Transport{ReturnRoute: &decorator.ReturnRoute{Value: transportReturnRoute}},
req,
}
expectedRequest, err := json.Marshal(outboundReq)
require.NoError(t, err)
require.NotNil(t, expectedRequest)

o := NewOutbound(&mockProvider{
packagerValue: &mockPackager{},
outboundTransportsValue: []transport.OutboundTransport{&mockOutboundTransport{
expectedRequest: string(expectedRequest)},
},
transportReturnRoute: transportReturnRoute,
})

require.NoError(t, o.Send(req, "", &service.Destination{ServiceEndpoint: "url"}))
})

t.Run("transport route option - no value set", func(t *testing.T) {
req := &decorator.Thread{
ID: uuid.New().String(),
}

expectedRequest, err := json.Marshal(req)
require.NoError(t, err)
require.NotNil(t, expectedRequest)

o := NewOutbound(&mockProvider{
packagerValue: &mockPackager{},
outboundTransportsValue: []transport.OutboundTransport{&mockOutboundTransport{
expectedRequest: string(expectedRequest)},
},
transportReturnRoute: "",
})

require.NoError(t, o.Send(req, "", &service.Destination{ServiceEndpoint: "url"}))
})
}

// mockProvider mock provider
type mockProvider struct {
packagerValue commontransport.Packager
outboundTransportsValue []transport.OutboundTransport
transportReturnRoute string
}

func (p *mockProvider) Packager() commontransport.Packager {
Expand All @@ -64,3 +150,36 @@ func (p *mockProvider) Packager() commontransport.Packager {
func (p *mockProvider) OutboundTransports() []transport.OutboundTransport {
return p.outboundTransportsValue
}

func (p *mockProvider) TransportReturnRoute() string {
return p.transportReturnRoute
}

// mockOutboundTransport mock outbound transport
type mockOutboundTransport struct {
expectedRequest string
}

func (o *mockOutboundTransport) Send(data []byte, destination *service.Destination) (string, error) {
if string(data) != o.expectedRequest {
return "", errors.New("invalid request")
}

return "", nil
}

func (o *mockOutboundTransport) Accept(url string) bool {
return true
}

// mockPackager mock packager
type mockPackager struct {
}

func (m *mockPackager) PackMessage(e *commontransport.Envelope) ([]byte, error) {
return e.Message, nil
}

func (m *mockPackager) UnpackMessage(encMessage []byte) (*commontransport.Envelope, error) {
return nil, nil
}
22 changes: 22 additions & 0 deletions pkg/didcomm/protocol/decorator/decorator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ package decorator

import "time"

const (
// TransportReturnRouteNone return route option none
TransportReturnRouteNone = "none"

// TransportReturnRouteAll return route option all
TransportReturnRouteAll = "all"

// TransportReturnRouteThread return route option thread
TransportReturnRouteThread = "thread"
)

// Thread thread data
type Thread struct {
ID string `json:"thid,omitempty"`
Expand All @@ -18,3 +29,14 @@ type Thread struct {
type Timing struct {
ExpiresTime time.Time `json:"expires_time,omitempty"`
}

// Transport transport decorator
// https://github.com/hyperledger/aries-rfcs/tree/master/features/0092-transport-return-route
type Transport struct {
ReturnRoute *ReturnRoute `json:"~transport,omitempty"`
}

// ReturnRoute works with Transport decorator. Acceptable values - "none", "all" or "thread".
type ReturnRoute struct {
Value string `json:"~return_route,omitempty"`
}
11 changes: 7 additions & 4 deletions pkg/didcomm/transport/http/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"net/http"
"strings"
"time"

"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
)

//go:generate testdata/scripts/openssl_env.sh testdata/scripts/generate_test_keys.sh
Expand Down Expand Up @@ -83,10 +85,10 @@ func NewOutbound(opts ...OutboundHTTPOpt) (*OutboundHTTPClient, error) {
}

// Send sends a2a exchange data via HTTP (client side)
func (cs *OutboundHTTPClient) Send(data []byte, url string) (string, error) {
resp, err := cs.client.Post(url, commContentType, bytes.NewBuffer(data))
func (cs *OutboundHTTPClient) Send(data []byte, destination *service.Destination) (string, error) {
resp, err := cs.client.Post(destination.ServiceEndpoint, commContentType, bytes.NewBuffer(data))
if err != nil {
logger.Errorf("posting DID envelope to agent failed [%s, %v]", url, err)
logger.Errorf("posting DID envelope to agent failed [%s, %v]", destination.ServiceEndpoint, err)
return "", err
}

Expand All @@ -95,7 +97,8 @@ func (cs *OutboundHTTPClient) Send(data []byte, url string) (string, error) {
if resp != nil {
isStatusSuccess := resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusOK
if !isStatusSuccess {
return "", fmt.Errorf("received unsuccessful POST HTTP status from agent [%s, %v]", url, resp.Status)
return "", fmt.Errorf("received unsuccessful POST HTTP status from agent "+
"[%s, %v]", destination.ServiceEndpoint, resp.Status)
}
// handle response
defer func() {
Expand Down
16 changes: 12 additions & 4 deletions pkg/didcomm/transport/http/outbound_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"testing"

"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
)

func TestWithOutboundOpts(t *testing.T) {
Expand Down Expand Up @@ -72,25 +74,31 @@ func TestOutboundHTTPTransport(t *testing.T) {

// test Outbound transport's api
// first with an empty url
r, e := ot.Send([]byte("Hello World"), "")
r, e := ot.Send([]byte("Hello World"), prepareDestination("serverURL"))
require.Error(t, e)
require.Empty(t, r)

// now try a bad url
r, e = ot.Send([]byte("Hello World"), "https://badurl")
r, e = ot.Send([]byte("Hello World"), prepareDestination("https://badurl"))
require.Error(t, e)
require.Empty(t, r)

// and try with a 'bad' payload with a valid url..
r, e = ot.Send([]byte("bad"), serverURL)
r, e = ot.Send([]byte("bad"), prepareDestination(serverURL))
require.Error(t, e)
require.Empty(t, r)

// finally using a valid url
r, e = ot.Send([]byte("Hello World"), serverURL)
r, e = ot.Send([]byte("Hello World"), prepareDestination(serverURL))
require.NoError(t, e)
require.NotEmpty(t, r)

require.True(t, ot.Accept("http://example.com"))
require.False(t, ot.Accept("123:22"))
}

func prepareDestination(endPoint string) *service.Destination {
return &service.Destination{
ServiceEndpoint: endPoint,
}
}
3 changes: 2 additions & 1 deletion pkg/didcomm/transport/transport_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ SPDX-License-Identifier: Apache-2.0
package transport

import (
"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/transport"
)

// OutboundTransport interface definition for transport layer
// This is the client side of the agent
type OutboundTransport interface {
// Send send a2a exchange data
Send(data []byte, destination string) (string, error)
Send(data []byte, destination *service.Destination) (string, error)
// Accept url
Accept(string) bool
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/didcomm/transport/ws/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"strings"

"nhooyr.io/websocket"

"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
)

const webSocketScheme = "ws"
Expand All @@ -27,12 +29,12 @@ func NewOutbound() *OutboundClient {
}

// Send sends a2a data via WS.
func (cs *OutboundClient) Send(data []byte, url string) (string, error) {
if url == "" {
func (cs *OutboundClient) Send(data []byte, destination *service.Destination) (string, error) {
if destination.ServiceEndpoint == "" {
return "", errors.New("url is mandatory")
}

client, _, err := websocket.Dial(context.Background(), url, nil)
client, _, err := websocket.Dial(context.Background(), destination.ServiceEndpoint, nil)
if err != nil {
return "", fmt.Errorf("websocket client : %w", err)
}
Expand Down
Loading

0 comments on commit 106115c

Please sign in to comment.