From c95741c15ecf0354a913a63d63b3ec150e275cb0 Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Mon, 19 Jan 2026 15:17:29 +0100 Subject: [PATCH 1/5] implement default discovery handler --- handlers/discovery/discovery.go | 144 +++++++++++++++ handlers/discovery/discovery_test.go | 257 +++++++++++++++++++++++++++ handlers/discovery/utils.go | 40 +++++ handlers/discovery/utils_test.go | 44 +++++ 4 files changed, 485 insertions(+) create mode 100644 handlers/discovery/discovery.go create mode 100644 handlers/discovery/discovery_test.go create mode 100644 handlers/discovery/utils.go create mode 100644 handlers/discovery/utils_test.go diff --git a/handlers/discovery/discovery.go b/handlers/discovery/discovery.go new file mode 100644 index 0000000..7f712cc --- /dev/null +++ b/handlers/discovery/discovery.go @@ -0,0 +1,144 @@ +package discovery + +import ( + "context" + "regexp" + "strings" + + "github.com/google/uuid" + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/packers" + "github.com/iden3/iden3comm/v2/protocol" +) + +type Discovery struct { + packerManager *iden3comm.PackageManager + supportedProtocols []iden3comm.ProtocolMessage +} + +func New(packerManager *iden3comm.PackageManager, supportedProtocols []iden3comm.ProtocolMessage) *Discovery { + return &Discovery{ + packerManager: packerManager, + supportedProtocols: supportedProtocols, + } +} + +func (d *Discovery) Handle(ctx context.Context, + discoverInputMessage protocol.DiscoverFeatureQueriesMessage) (protocol.DiscoverFeatureDiscloseMessage, error) { + queries := discoverInputMessage.Body.Queries + + var ( + disclosures []protocol.DiscoverFeatureDisclosure + err error + ) + + for _, query := range queries { + var disclosuresToAppend []protocol.DiscoverFeatureDisclosure + switch query.FeatureType { + case protocol.DiscoveryProtocolFeatureTypeAccept: + disclosuresToAppend = d.handleAccept(ctx) + case protocol.DiscoveryProtocolFeatureTypeGoalCode: + disclosuresToAppend = d.handleGoalCode(ctx) + case protocol.DiscoveryProtocolFeatureTypeProtocol: + disclosuresToAppend = d.handleProtocol(ctx) + case protocol.DiscoveryProtocolFeatureTypeHeader: + disclosuresToAppend = d.handleHeader(ctx) + } + disclosuresToAppend, err = d.handleMatch(disclosuresToAppend, query.Match) + if err != nil { + return protocol.DiscoverFeatureDiscloseMessage{}, err + } + disclosures = append(disclosures, disclosuresToAppend...) + } + + return protocol.DiscoverFeatureDiscloseMessage{ + ID: uuid.NewString(), + Typ: packers.MediaTypePlainMessage, + Type: protocol.DiscoverFeatureDiscloseMessageType, + ThreadID: discoverInputMessage.ThreadID, + Body: protocol.DiscoverFeatureDiscloseMessageBody{ + Disclosures: disclosures, + }, + From: discoverInputMessage.To, + To: discoverInputMessage.From, + }, nil +} + +func (d *Discovery) handleAccept(_ context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + + profiles := d.packerManager.GetSupportedProfiles() + for _, profile := range profiles { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ID: profile, + }) + } + return disclosures +} + +func (d *Discovery) handleProtocol(_ context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + for _, protocolMessage := range d.supportedProtocols { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocolMessage), + }) + } + return disclosures +} + +func (d *Discovery) handleGoalCode(_ context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + return disclosures +} + +func (d *Discovery) handleHeader(_ context.Context) []protocol.DiscoverFeatureDisclosure { + headers := []string{ + "id", + "typ", + "type", + "thid", + "body", + "from", + "to", + "created_time", + "expires_time", + } + + disclosures := []protocol.DiscoverFeatureDisclosure{} + + for _, header := range headers { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, + ID: header, + }) + } + + return disclosures +} + +func (d *Discovery) handleMatch(disclosures []protocol.DiscoverFeatureDisclosure, match string) ([]protocol.DiscoverFeatureDisclosure, error) { + if match == "" || match == "*" { + return disclosures, nil + } + + regExp, err := wildcardToRegExp(match) + if err != nil { + return nil, err + } + var filtered []protocol.DiscoverFeatureDisclosure + for _, disclosure := range disclosures { + if regExp.MatchString(disclosure.ID) { + filtered = append(filtered, disclosure) + } + } + return filtered, nil +} + +func wildcardToRegExp(match string) (*regexp.Regexp, error) { + // Escape special regex characters and replace `*` with `.*` + regexPattern := regexp.QuoteMeta(match) + regexPattern = strings.ReplaceAll(regexPattern, "\\*", ".*") + return regexp.Compile("^" + regexPattern + "$") +} diff --git a/handlers/discovery/discovery_test.go b/handlers/discovery/discovery_test.go new file mode 100644 index 0000000..43df1db --- /dev/null +++ b/handlers/discovery/discovery_test.go @@ -0,0 +1,257 @@ +package discovery_test + +import ( + "context" + "testing" + + "github.com/iden3/go-jwz/v2" + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/handlers/discovery" + "github.com/iden3/iden3comm/v2/packers" + "github.com/iden3/iden3comm/v2/protocol" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type want struct { + FeatureType protocol.DiscoveryProtocolFeatureType + ParsedFeatures discovery.Feature + ID string +} + +func TestDiscovery_Handle(t *testing.T) { + tests := []struct { + name string + discoveryFactory func(t *testing.T) *discovery.Discovery + discoverInputMessage protocol.DiscoverFeatureQueriesMessage + want []want + }{ + { + name: "Support only zkp packer with authV3 groth16 and authV3-8-32 groth16", + discoveryFactory: func(t *testing.T) *discovery.Discovery { + zkpPacker := packers.NewZKPPacker( + map[jwz.ProvingMethodAlg]packers.ProvingParams{}, + map[jwz.ProvingMethodAlg]packers.VerificationParams{ + jwz.AuthV3Groth16Alg: {}, + jwz.AuthV3_8_32Groth16Alg: {}, + }, + ) + + pm := iden3comm.NewPackageManager() + err := pm.RegisterPackers(zkpPacker) + require.NoError(t, err) + + return discovery.New(pm, []iden3comm.ProtocolMessage{}) + }, + discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + }, + }), + want: []want{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ParsedFeatures: discovery.Feature{ + Version: "iden3comm/v1", + Env: "application/iden3-zkp-json", + Algs: []string{"groth16"}, + CircuitIds: []string{ + "authV3", + "authV3-8-32", + }, + }, + }, + }, + }, + { + name: "Support zkp, plain text packers with authV3 groth16 and authV3-8-32 groth16", + discoveryFactory: func(t *testing.T) *discovery.Discovery { + zkpPacker := packers.NewZKPPacker( + map[jwz.ProvingMethodAlg]packers.ProvingParams{}, + map[jwz.ProvingMethodAlg]packers.VerificationParams{ + jwz.AuthV3Groth16Alg: {}, + jwz.AuthV3_8_32Groth16Alg: {}, + }, + ) + + pm := iden3comm.NewPackageManager() + err := pm.RegisterPackers(zkpPacker, &packers.PlainMessagePacker{}) + require.NoError(t, err) + + return discovery.New(pm, []iden3comm.ProtocolMessage{}) + }, + discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + }, + }), + want: []want{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ParsedFeatures: discovery.Feature{ + Version: "iden3comm/v1", + Env: "application/iden3-zkp-json", + Algs: []string{"groth16"}, + CircuitIds: []string{ + "authV3", + "authV3-8-32", + }, + }, + }, + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ParsedFeatures: discovery.Feature{ + Version: "iden3comm/v1", + Env: "application/iden3comm-plain-json", + }, + }, + }, + }, + { + name: "Support message type: credential proposal request, credential fetch", + discoveryFactory: func(t *testing.T) *discovery.Discovery { + pm := iden3comm.NewPackageManager() + err := pm.RegisterPackers(&packers.PlainMessagePacker{}) + require.NoError(t, err) + + return discovery.New(pm, []iden3comm.ProtocolMessage{ + protocol.CredentialProposalRequestMessageType, + protocol.CredentialFetchRequestMessageType, + }) + }, + discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + }, + }), + want: []want{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocol.CredentialProposalRequestMessageType), + }, + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocol.CredentialFetchRequestMessageType), + }, + }, + }, + { + name: "Support feature and protocol requests", + discoveryFactory: func(t *testing.T) *discovery.Discovery { + pm := iden3comm.NewPackageManager() + err := pm.RegisterPackers(&packers.PlainMessagePacker{}) + require.NoError(t, err) + + return discovery.New(pm, []iden3comm.ProtocolMessage{ + protocol.CredentialProposalRequestMessageType, + protocol.CredentialFetchRequestMessageType, + }) + }, + discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + }, + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + }, + }), + want: []want{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocol.CredentialProposalRequestMessageType), + }, + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocol.CredentialFetchRequestMessageType), + }, + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ParsedFeatures: discovery.Feature{ + Version: "iden3comm/v1", + Env: "application/iden3comm-plain-json", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := tt.discoveryFactory(t) + actual, err := d.Handle( + context.Background(), + tt.discoverInputMessage, + ) + testActualID(t, actual.Body.Disclosures, tt.want) + require.NoError(t, err) + + }) + } +} + +func testActualID(t *testing.T, actual []protocol.DiscoverFeatureDisclosure, want []want) { + require.Equal(t, len(actual), len(want)) + // try to find actual in want + for _, a := range actual { + found := false + for i, w := range want { + if a.FeatureType != w.FeatureType { + continue + } + + if a.FeatureType == protocol.DiscoveryProtocolFeatureTypeAccept { + isEqual := isEqualAccpetFeature(a, w.ParsedFeatures) + if isEqual { + // nullify to avoid duplicate matching + want = nullify(want, i) + found = true + break + } + } + // for another types, just match by FeatureType and ID + if a.ID == w.ID { + // nullify to avoid duplicate matching + want = nullify(want, i) + found = true + break + } + } + require.True(t, found, "actual ID not found in want: %v", a) + } +} + +type mockT struct{} + +func (m *mockT) Errorf(format string, args ...interface{}) {} + +func isEqualAccpetFeature(actual protocol.DiscoverFeatureDisclosure, want discovery.Feature) bool { + parsed := discovery.ParseFeature(actual.ID) + + if want.Version != parsed.Version { + return false + } + if want.Env != parsed.Env { + return false + } + + t := &mockT{} + return assert.ElementsMatch(t, want.Algs, parsed.Algs) && + assert.ElementsMatch(t, want.CircuitIds, parsed.CircuitIds) +} + +func nullify(w []want, index int) []want { + w[index] = want{} + return w +} + +func newDiscoverFeatureQueriesMessage(queries []protocol.DiscoverFeatureQuery) protocol.DiscoverFeatureQueriesMessage { + return protocol.DiscoverFeatureQueriesMessage{ + ID: "c0fc0f29-4f34-4bea-851b-58b7639fe29c", + ThreadID: "becfc675-b15d-4817-98a5-7fce0240a48a", + Typ: packers.MediaTypePlainMessage, + Type: protocol.DiscoverFeatureQueriesMessageType, + Body: protocol.DiscoverFeatureQueriesMessageBody{ + Queries: queries, + }, + } +} diff --git a/handlers/discovery/utils.go b/handlers/discovery/utils.go new file mode 100644 index 0000000..0b85b1f --- /dev/null +++ b/handlers/discovery/utils.go @@ -0,0 +1,40 @@ +package discovery + +import "strings" + +type Feature struct { + Version string + Env string + Algs []string + CircuitIds []string +} + +func ParseFeature(id string) Feature { + parts := strings.Split(id, ";") + if len(parts) == 0 { + return Feature{} + } + + f := Feature{ + Version: parts[0], + } + + for _, part := range parts[1:] { + kv := strings.SplitN(part, "=", 2) + if len(kv) != 2 { + continue + } + key := strings.TrimSpace(kv[0]) + value := strings.TrimSpace(kv[1]) + + switch key { + case "env": + f.Env = value + case "alg": + f.Algs = strings.Split(value, ",") + case "circuitIds": + f.CircuitIds = strings.Split(value, ",") + } + } + return f +} diff --git a/handlers/discovery/utils_test.go b/handlers/discovery/utils_test.go new file mode 100644 index 0000000..0eeb621 --- /dev/null +++ b/handlers/discovery/utils_test.go @@ -0,0 +1,44 @@ +package discovery_test + +import ( + "reflect" + "testing" + + "github.com/iden3/iden3comm/v2/handlers/discovery" +) + +func TestParseFeature(t *testing.T) { + tests := []struct { + name string + id string + want discovery.Feature + }{ + { + name: "full feature", + id: "iden3comm/v1;env=application/iden3-zkp-json;alg=groth16;circuitIds=authV3,authV3-8-32", + want: discovery.Feature{ + Version: "iden3comm/v1", + Env: "application/iden3-zkp-json", + Algs: []string{"groth16"}, + CircuitIds: []string{"authV3", "authV3-8-32"}, + }, + }, + { + name: "multiple algs", + id: "iden3comm/v1;alg=alg1,alg2", + want: discovery.Feature{ + Version: "iden3comm/v1", + Algs: []string{"alg1", "alg2"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := discovery.ParseFeature(tt.id) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseFeature(%q) = %+v, want %+v", tt.id, got, tt.want) + } + }) + } +} From bf3a6d24218fb5de8cfa4be7605a74a47df41bd3 Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Mon, 19 Jan 2026 16:49:05 +0100 Subject: [PATCH 2/5] move features under interface --- handlers/discovery/discovery.go | 75 ++---------------- handlers/discovery/discovery_test.go | 36 +++++++-- handlers/discovery/features.go | 111 +++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 77 deletions(-) create mode 100644 handlers/discovery/features.go diff --git a/handlers/discovery/discovery.go b/handlers/discovery/discovery.go index 7f712cc..2cefaf9 100644 --- a/handlers/discovery/discovery.go +++ b/handlers/discovery/discovery.go @@ -6,20 +6,17 @@ import ( "strings" "github.com/google/uuid" - "github.com/iden3/iden3comm/v2" "github.com/iden3/iden3comm/v2/packers" "github.com/iden3/iden3comm/v2/protocol" ) type Discovery struct { - packerManager *iden3comm.PackageManager - supportedProtocols []iden3comm.ProtocolMessage + features map[protocol.DiscoveryProtocolFeatureType]Featurer } -func New(packerManager *iden3comm.PackageManager, supportedProtocols []iden3comm.ProtocolMessage) *Discovery { +func New(features map[protocol.DiscoveryProtocolFeatureType]Featurer) *Discovery { return &Discovery{ - packerManager: packerManager, - supportedProtocols: supportedProtocols, + features: features, } } @@ -34,16 +31,10 @@ func (d *Discovery) Handle(ctx context.Context, for _, query := range queries { var disclosuresToAppend []protocol.DiscoverFeatureDisclosure - switch query.FeatureType { - case protocol.DiscoveryProtocolFeatureTypeAccept: - disclosuresToAppend = d.handleAccept(ctx) - case protocol.DiscoveryProtocolFeatureTypeGoalCode: - disclosuresToAppend = d.handleGoalCode(ctx) - case protocol.DiscoveryProtocolFeatureTypeProtocol: - disclosuresToAppend = d.handleProtocol(ctx) - case protocol.DiscoveryProtocolFeatureTypeHeader: - disclosuresToAppend = d.handleHeader(ctx) + if featurer, ok := d.features[query.FeatureType]; ok { + disclosuresToAppend = featurer.Handle(ctx) } + disclosuresToAppend, err = d.handleMatch(disclosuresToAppend, query.Match) if err != nil { return protocol.DiscoverFeatureDiscloseMessage{}, err @@ -64,60 +55,6 @@ func (d *Discovery) Handle(ctx context.Context, }, nil } -func (d *Discovery) handleAccept(_ context.Context) []protocol.DiscoverFeatureDisclosure { - disclosures := []protocol.DiscoverFeatureDisclosure{} - - profiles := d.packerManager.GetSupportedProfiles() - for _, profile := range profiles { - disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ - FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, - ID: profile, - }) - } - return disclosures -} - -func (d *Discovery) handleProtocol(_ context.Context) []protocol.DiscoverFeatureDisclosure { - disclosures := []protocol.DiscoverFeatureDisclosure{} - for _, protocolMessage := range d.supportedProtocols { - disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ - FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, - ID: string(protocolMessage), - }) - } - return disclosures -} - -func (d *Discovery) handleGoalCode(_ context.Context) []protocol.DiscoverFeatureDisclosure { - disclosures := []protocol.DiscoverFeatureDisclosure{} - return disclosures -} - -func (d *Discovery) handleHeader(_ context.Context) []protocol.DiscoverFeatureDisclosure { - headers := []string{ - "id", - "typ", - "type", - "thid", - "body", - "from", - "to", - "created_time", - "expires_time", - } - - disclosures := []protocol.DiscoverFeatureDisclosure{} - - for _, header := range headers { - disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ - FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, - ID: header, - }) - } - - return disclosures -} - func (d *Discovery) handleMatch(disclosures []protocol.DiscoverFeatureDisclosure, match string) ([]protocol.DiscoverFeatureDisclosure, error) { if match == "" || match == "*" { return disclosures, nil diff --git a/handlers/discovery/discovery_test.go b/handlers/discovery/discovery_test.go index 43df1db..ff9623b 100644 --- a/handlers/discovery/discovery_test.go +++ b/handlers/discovery/discovery_test.go @@ -41,7 +41,12 @@ func TestDiscovery_Handle(t *testing.T) { err := pm.RegisterPackers(zkpPacker) require.NoError(t, err) - return discovery.New(pm, []iden3comm.ProtocolMessage{}) + return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ + protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), + protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ { @@ -78,7 +83,12 @@ func TestDiscovery_Handle(t *testing.T) { err := pm.RegisterPackers(zkpPacker, &packers.PlainMessagePacker{}) require.NoError(t, err) - return discovery.New(pm, []iden3comm.ProtocolMessage{}) + return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ + protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), + protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ { @@ -114,9 +124,14 @@ func TestDiscovery_Handle(t *testing.T) { err := pm.RegisterPackers(&packers.PlainMessagePacker{}) require.NoError(t, err) - return discovery.New(pm, []iden3comm.ProtocolMessage{ - protocol.CredentialProposalRequestMessageType, - protocol.CredentialFetchRequestMessageType, + return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ + protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{ + protocol.CredentialProposalRequestMessageType, + protocol.CredentialFetchRequestMessageType, + }), + protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ @@ -142,9 +157,14 @@ func TestDiscovery_Handle(t *testing.T) { err := pm.RegisterPackers(&packers.PlainMessagePacker{}) require.NoError(t, err) - return discovery.New(pm, []iden3comm.ProtocolMessage{ - protocol.CredentialProposalRequestMessageType, - protocol.CredentialFetchRequestMessageType, + return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ + protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{ + protocol.CredentialProposalRequestMessageType, + protocol.CredentialFetchRequestMessageType, + }), + protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ diff --git a/handlers/discovery/features.go b/handlers/discovery/features.go new file mode 100644 index 0000000..205e6f2 --- /dev/null +++ b/handlers/discovery/features.go @@ -0,0 +1,111 @@ +package discovery + +import ( + "context" + + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/protocol" +) + +// Featurer interface for feature handlers +type Featurer interface { + Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure +} + +// AcceptFeaturer implementation +type AcceptFeaturer struct { + packerManager *iden3comm.PackageManager +} + +// NewAcceptFeaturer constructor +func NewAcceptFeaturer(packerManager *iden3comm.PackageManager) *AcceptFeaturer { + return &AcceptFeaturer{ + packerManager: packerManager, + } +} + +// Handle implementation for AcceptFeaturer +func (a *AcceptFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + + profiles := a.packerManager.GetSupportedProfiles() + for _, profile := range profiles { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ID: profile, + }) + } + return disclosures +} + +// ProtocolFeaturer implementation +type ProtocolFeaturer struct { + supportedProtocols []iden3comm.ProtocolMessage +} + +// NewProtocolFeaturer constructor +func NewProtocolFeaturer(supportedProtocols []iden3comm.ProtocolMessage) *ProtocolFeaturer { + return &ProtocolFeaturer{ + supportedProtocols: supportedProtocols, + } +} + +// Handle implementation for ProtocolFeaturer +func (p *ProtocolFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + for _, protocolMessage := range p.supportedProtocols { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocolMessage), + }) + } + return disclosures +} + +// GoalCodeFeaturer implementation +type GoalCodeFeaturer struct{} + +// NewGoalCodeFeaturer constructor +func NewGoalCodeFeaturer() *GoalCodeFeaturer { + return &GoalCodeFeaturer{} +} + +// Handle implementation for GoalCodeFeaturer +func (g *GoalCodeFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + return disclosures +} + +// HeaderFeaturer implementation +type HeaderFeaturer struct{} + +// NewHeaderFeaturer constructor +func NewHeaderFeaturer() *HeaderFeaturer { + return &HeaderFeaturer{} +} + +// Handle implementation for HeaderFeaturer +func (h *HeaderFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + headers := []string{ + "id", + "typ", + "type", + "thid", + "body", + "from", + "to", + "created_time", + "expires_time", + } + + disclosures := []protocol.DiscoverFeatureDisclosure{} + + for _, header := range headers { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, + ID: header, + }) + } + + return disclosures +} From f8ec0c4b2ace1a0d829dc3ea2989c25098dd3082 Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Mon, 19 Jan 2026 16:51:15 +0100 Subject: [PATCH 3/5] add attachments headers --- handlers/discovery/features.go | 1 + 1 file changed, 1 insertion(+) diff --git a/handlers/discovery/features.go b/handlers/discovery/features.go index 205e6f2..82002ef 100644 --- a/handlers/discovery/features.go +++ b/handlers/discovery/features.go @@ -96,6 +96,7 @@ func (h *HeaderFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureD "to", "created_time", "expires_time", + "attachments", } disclosures := []protocol.DiscoverFeatureDisclosure{} From a6d41d3e6feb213d6d1f446115c6d9341081b084 Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Tue, 20 Jan 2026 12:37:16 +0100 Subject: [PATCH 4/5] fix github copilot comments --- handlers/discovery/discovery.go | 3 ++ handlers/discovery/discovery_test.go | 42 ++++++++++++++++++++++++++-- handlers/discovery/features.go | 8 +++--- handlers/discovery/utils.go | 10 +++++-- handlers/discovery/utils_test.go | 13 ++++++--- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/handlers/discovery/discovery.go b/handlers/discovery/discovery.go index 2cefaf9..3f22f84 100644 --- a/handlers/discovery/discovery.go +++ b/handlers/discovery/discovery.go @@ -10,16 +10,19 @@ import ( "github.com/iden3/iden3comm/v2/protocol" ) +// Discovery handler type Discovery struct { features map[protocol.DiscoveryProtocolFeatureType]Featurer } +// New creates a new Discovery handler func New(features map[protocol.DiscoveryProtocolFeatureType]Featurer) *Discovery { return &Discovery{ features: features, } } +// Handle processes a DiscoverFeatureQueriesMessage and returns a DiscoverFeatureDiscloseMessage func (d *Discovery) Handle(ctx context.Context, discoverInputMessage protocol.DiscoverFeatureQueriesMessage) (protocol.DiscoverFeatureDiscloseMessage, error) { queries := discoverInputMessage.Body.Queries diff --git a/handlers/discovery/discovery_test.go b/handlers/discovery/discovery_test.go index ff9623b..4aed6e5 100644 --- a/handlers/discovery/discovery_test.go +++ b/handlers/discovery/discovery_test.go @@ -193,6 +193,44 @@ func TestDiscovery_Handle(t *testing.T) { }, }, }, + { + name: "With match wildcard. Filter only application/iden3comm-plain-json packers", + discoveryFactory: func(t *testing.T) *discovery.Discovery { + zkpPacker := packers.NewZKPPacker( + map[jwz.ProvingMethodAlg]packers.ProvingParams{}, + map[jwz.ProvingMethodAlg]packers.VerificationParams{ + jwz.AuthV3Groth16Alg: {}, + jwz.AuthV3_8_32Groth16Alg: {}, + }, + ) + + pm := iden3comm.NewPackageManager() + err := pm.RegisterPackers(zkpPacker, &packers.PlainMessagePacker{}) + require.NoError(t, err) + + return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ + protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), + protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + }) + }, + discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + Match: "*iden3comm-plain-json", + }, + }), + want: []want{ + { + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ParsedFeatures: discovery.Feature{ + Version: "iden3comm/v1", + Env: "application/iden3comm-plain-json", + }, + }, + }, + }, } for _, tt := range tests { @@ -220,7 +258,7 @@ func testActualID(t *testing.T, actual []protocol.DiscoverFeatureDisclosure, wan } if a.FeatureType == protocol.DiscoveryProtocolFeatureTypeAccept { - isEqual := isEqualAccpetFeature(a, w.ParsedFeatures) + isEqual := isEqualAcceptFeature(a, w.ParsedFeatures) if isEqual { // nullify to avoid duplicate matching want = nullify(want, i) @@ -244,7 +282,7 @@ type mockT struct{} func (m *mockT) Errorf(format string, args ...interface{}) {} -func isEqualAccpetFeature(actual protocol.DiscoverFeatureDisclosure, want discovery.Feature) bool { +func isEqualAcceptFeature(actual protocol.DiscoverFeatureDisclosure, want discovery.Feature) bool { parsed := discovery.ParseFeature(actual.ID) if want.Version != parsed.Version { diff --git a/handlers/discovery/features.go b/handlers/discovery/features.go index 82002ef..3a7b687 100644 --- a/handlers/discovery/features.go +++ b/handlers/discovery/features.go @@ -14,13 +14,13 @@ type Featurer interface { // AcceptFeaturer implementation type AcceptFeaturer struct { - packerManager *iden3comm.PackageManager + packageManager *iden3comm.PackageManager } // NewAcceptFeaturer constructor -func NewAcceptFeaturer(packerManager *iden3comm.PackageManager) *AcceptFeaturer { +func NewAcceptFeaturer(packageManager *iden3comm.PackageManager) *AcceptFeaturer { return &AcceptFeaturer{ - packerManager: packerManager, + packageManager: packageManager, } } @@ -28,7 +28,7 @@ func NewAcceptFeaturer(packerManager *iden3comm.PackageManager) *AcceptFeaturer func (a *AcceptFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { disclosures := []protocol.DiscoverFeatureDisclosure{} - profiles := a.packerManager.GetSupportedProfiles() + profiles := a.packageManager.GetSupportedProfiles() for _, profile := range profiles { disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, diff --git a/handlers/discovery/utils.go b/handlers/discovery/utils.go index 0b85b1f..95a5dbe 100644 --- a/handlers/discovery/utils.go +++ b/handlers/discovery/utils.go @@ -2,6 +2,7 @@ package discovery import "strings" +// Feature represents a parsed feature with its components type Feature struct { Version string Env string @@ -9,11 +10,13 @@ type Feature struct { CircuitIds []string } +// ParseFeature parses a feature ID string into a Feature struct func ParseFeature(id string) Feature { - parts := strings.Split(id, ";") - if len(parts) == 0 { + id = strings.TrimSpace(id) + if id == "" { return Feature{} } + parts := strings.Split(id, ";") f := Feature{ Version: parts[0], @@ -26,6 +29,9 @@ func ParseFeature(id string) Feature { } key := strings.TrimSpace(kv[0]) value := strings.TrimSpace(kv[1]) + if value == "" { + continue + } switch key { case "env": diff --git a/handlers/discovery/utils_test.go b/handlers/discovery/utils_test.go index 0eeb621..897ad83 100644 --- a/handlers/discovery/utils_test.go +++ b/handlers/discovery/utils_test.go @@ -1,10 +1,10 @@ package discovery_test import ( - "reflect" "testing" "github.com/iden3/iden3comm/v2/handlers/discovery" + "github.com/stretchr/testify/require" ) func TestParseFeature(t *testing.T) { @@ -31,14 +31,19 @@ func TestParseFeature(t *testing.T) { Algs: []string{"alg1", "alg2"}, }, }, + { + name: "empty alg list", + id: "iden3comm/v1;alg=", + want: discovery.Feature{ + Version: "iden3comm/v1", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := discovery.ParseFeature(tt.id) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseFeature(%q) = %+v, want %+v", tt.id, got, tt.want) - } + require.Equal(t, tt.want, got) }) } } From 101afe93fe0ac90a55667d74de44be791453caf2 Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Wed, 21 Jan 2026 13:13:08 +0100 Subject: [PATCH 5/5] refactor; add Experimental tag --- handlers/discovery/discovery.go | 9 ++ handlers/discovery/discovery_test.go | 41 +++---- handlers/discovery/features.go | 112 ------------------- handlers/discovery/features/accept.go | 34 ++++++ handlers/discovery/features/goalcode.go | 50 +++++++++ handlers/discovery/features/goalcode_test.go | 54 +++++++++ handlers/discovery/features/header.go | 64 +++++++++++ handlers/discovery/features/header_test.go | 65 +++++++++++ handlers/discovery/features/protocol.go | 35 ++++++ handlers/discovery/utils.go | 2 + 10 files changed, 334 insertions(+), 132 deletions(-) delete mode 100644 handlers/discovery/features.go create mode 100644 handlers/discovery/features/accept.go create mode 100644 handlers/discovery/features/goalcode.go create mode 100644 handlers/discovery/features/goalcode_test.go create mode 100644 handlers/discovery/features/header.go create mode 100644 handlers/discovery/features/header_test.go create mode 100644 handlers/discovery/features/protocol.go diff --git a/handlers/discovery/discovery.go b/handlers/discovery/discovery.go index 3f22f84..9f90e3d 100644 --- a/handlers/discovery/discovery.go +++ b/handlers/discovery/discovery.go @@ -10,12 +10,20 @@ import ( "github.com/iden3/iden3comm/v2/protocol" ) +// Featurer interface for feature handlers +// # Experimental +type Featurer interface { + Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure +} + // Discovery handler +// # Experimental type Discovery struct { features map[protocol.DiscoveryProtocolFeatureType]Featurer } // New creates a new Discovery handler +// # Experimental func New(features map[protocol.DiscoveryProtocolFeatureType]Featurer) *Discovery { return &Discovery{ features: features, @@ -23,6 +31,7 @@ func New(features map[protocol.DiscoveryProtocolFeatureType]Featurer) *Discovery } // Handle processes a DiscoverFeatureQueriesMessage and returns a DiscoverFeatureDiscloseMessage +// # Experimental func (d *Discovery) Handle(ctx context.Context, discoverInputMessage protocol.DiscoverFeatureQueriesMessage) (protocol.DiscoverFeatureDiscloseMessage, error) { queries := discoverInputMessage.Body.Queries diff --git a/handlers/discovery/discovery_test.go b/handlers/discovery/discovery_test.go index 4aed6e5..54fafa9 100644 --- a/handlers/discovery/discovery_test.go +++ b/handlers/discovery/discovery_test.go @@ -7,6 +7,7 @@ import ( "github.com/iden3/go-jwz/v2" "github.com/iden3/iden3comm/v2" "github.com/iden3/iden3comm/v2/handlers/discovery" + "github.com/iden3/iden3comm/v2/handlers/discovery/features" "github.com/iden3/iden3comm/v2/packers" "github.com/iden3/iden3comm/v2/protocol" "github.com/stretchr/testify/assert" @@ -42,10 +43,10 @@ func TestDiscovery_Handle(t *testing.T) { require.NoError(t, err) return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ - protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), - protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), - protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), - protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + protocol.DiscoveryProtocolFeatureTypeAccept: features.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: features.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), + protocol.DiscoveryProtocolFeatureTypeGoalCode: features.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: features.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ @@ -84,10 +85,10 @@ func TestDiscovery_Handle(t *testing.T) { require.NoError(t, err) return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ - protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), - protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), - protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), - protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + protocol.DiscoveryProtocolFeatureTypeAccept: features.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: features.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), + protocol.DiscoveryProtocolFeatureTypeGoalCode: features.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: features.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ @@ -125,13 +126,13 @@ func TestDiscovery_Handle(t *testing.T) { require.NoError(t, err) return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ - protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), - protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{ + protocol.DiscoveryProtocolFeatureTypeAccept: features.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: features.NewProtocolFeaturer([]iden3comm.ProtocolMessage{ protocol.CredentialProposalRequestMessageType, protocol.CredentialFetchRequestMessageType, }), - protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), - protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + protocol.DiscoveryProtocolFeatureTypeGoalCode: features.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: features.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ @@ -158,13 +159,13 @@ func TestDiscovery_Handle(t *testing.T) { require.NoError(t, err) return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ - protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), - protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{ + protocol.DiscoveryProtocolFeatureTypeAccept: features.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: features.NewProtocolFeaturer([]iden3comm.ProtocolMessage{ protocol.CredentialProposalRequestMessageType, protocol.CredentialFetchRequestMessageType, }), - protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), - protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + protocol.DiscoveryProtocolFeatureTypeGoalCode: features.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: features.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ @@ -209,10 +210,10 @@ func TestDiscovery_Handle(t *testing.T) { require.NoError(t, err) return discovery.New(map[protocol.DiscoveryProtocolFeatureType]discovery.Featurer{ - protocol.DiscoveryProtocolFeatureTypeAccept: discovery.NewAcceptFeaturer(pm), - protocol.DiscoveryProtocolFeatureTypeProtocol: discovery.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), - protocol.DiscoveryProtocolFeatureTypeGoalCode: discovery.NewGoalCodeFeaturer(), - protocol.DiscoveryProtocolFeatureTypeHeader: discovery.NewHeaderFeaturer(), + protocol.DiscoveryProtocolFeatureTypeAccept: features.NewAcceptFeaturer(pm), + protocol.DiscoveryProtocolFeatureTypeProtocol: features.NewProtocolFeaturer([]iden3comm.ProtocolMessage{}), + protocol.DiscoveryProtocolFeatureTypeGoalCode: features.NewGoalCodeFeaturer(), + protocol.DiscoveryProtocolFeatureTypeHeader: features.NewHeaderFeaturer(), }) }, discoverInputMessage: newDiscoverFeatureQueriesMessage([]protocol.DiscoverFeatureQuery{ diff --git a/handlers/discovery/features.go b/handlers/discovery/features.go deleted file mode 100644 index 3a7b687..0000000 --- a/handlers/discovery/features.go +++ /dev/null @@ -1,112 +0,0 @@ -package discovery - -import ( - "context" - - "github.com/iden3/iden3comm/v2" - "github.com/iden3/iden3comm/v2/protocol" -) - -// Featurer interface for feature handlers -type Featurer interface { - Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure -} - -// AcceptFeaturer implementation -type AcceptFeaturer struct { - packageManager *iden3comm.PackageManager -} - -// NewAcceptFeaturer constructor -func NewAcceptFeaturer(packageManager *iden3comm.PackageManager) *AcceptFeaturer { - return &AcceptFeaturer{ - packageManager: packageManager, - } -} - -// Handle implementation for AcceptFeaturer -func (a *AcceptFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { - disclosures := []protocol.DiscoverFeatureDisclosure{} - - profiles := a.packageManager.GetSupportedProfiles() - for _, profile := range profiles { - disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ - FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, - ID: profile, - }) - } - return disclosures -} - -// ProtocolFeaturer implementation -type ProtocolFeaturer struct { - supportedProtocols []iden3comm.ProtocolMessage -} - -// NewProtocolFeaturer constructor -func NewProtocolFeaturer(supportedProtocols []iden3comm.ProtocolMessage) *ProtocolFeaturer { - return &ProtocolFeaturer{ - supportedProtocols: supportedProtocols, - } -} - -// Handle implementation for ProtocolFeaturer -func (p *ProtocolFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { - disclosures := []protocol.DiscoverFeatureDisclosure{} - for _, protocolMessage := range p.supportedProtocols { - disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ - FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, - ID: string(protocolMessage), - }) - } - return disclosures -} - -// GoalCodeFeaturer implementation -type GoalCodeFeaturer struct{} - -// NewGoalCodeFeaturer constructor -func NewGoalCodeFeaturer() *GoalCodeFeaturer { - return &GoalCodeFeaturer{} -} - -// Handle implementation for GoalCodeFeaturer -func (g *GoalCodeFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { - disclosures := []protocol.DiscoverFeatureDisclosure{} - return disclosures -} - -// HeaderFeaturer implementation -type HeaderFeaturer struct{} - -// NewHeaderFeaturer constructor -func NewHeaderFeaturer() *HeaderFeaturer { - return &HeaderFeaturer{} -} - -// Handle implementation for HeaderFeaturer -func (h *HeaderFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { - headers := []string{ - "id", - "typ", - "type", - "thid", - "body", - "from", - "to", - "created_time", - "expires_time", - "attachments", - } - - disclosures := []protocol.DiscoverFeatureDisclosure{} - - for _, header := range headers { - disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ - FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, - ID: header, - }) - } - - return disclosures -} diff --git a/handlers/discovery/features/accept.go b/handlers/discovery/features/accept.go new file mode 100644 index 0000000..64e41d0 --- /dev/null +++ b/handlers/discovery/features/accept.go @@ -0,0 +1,34 @@ +package features + +import ( + "context" + + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/protocol" +) + +// AcceptFeaturer implementation +type AcceptFeaturer struct { + packageManager *iden3comm.PackageManager +} + +// NewAcceptFeaturer constructor +func NewAcceptFeaturer(packageManager *iden3comm.PackageManager) *AcceptFeaturer { + return &AcceptFeaturer{ + packageManager: packageManager, + } +} + +// Handle implementation for AcceptFeaturer +func (a *AcceptFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + + profiles := a.packageManager.GetSupportedProfiles() + for _, profile := range profiles { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeAccept, + ID: profile, + }) + } + return disclosures +} diff --git a/handlers/discovery/features/goalcode.go b/handlers/discovery/features/goalcode.go new file mode 100644 index 0000000..5a2775e --- /dev/null +++ b/handlers/discovery/features/goalcode.go @@ -0,0 +1,50 @@ +package features + +import ( + "context" + + "github.com/iden3/iden3comm/v2/protocol" +) + +// GoalCodeFeaturer implementation +// # Experimental +type GoalCodeFeaturer struct { + goalCodes []string +} + +// GoalCodeOption configures a GoalCodeFeaturer +// # Experimental +type GoalCodeOption func(*GoalCodeFeaturer) + +// WithGoalCodes sets custom goal codes for GoalCodeFeaturer +// # Experimental +func WithGoalCodes(goalCodes ...string) GoalCodeOption { + return func(g *GoalCodeFeaturer) { + g.goalCodes = goalCodes + } +} + +// NewGoalCodeFeaturer constructor +// # Experimental +func NewGoalCodeFeaturer(opts ...GoalCodeOption) *GoalCodeFeaturer { + g := &GoalCodeFeaturer{} + for _, opt := range opts { + opt(g) + } + return g +} + +// Handle implementation for GoalCodeFeaturer +// # Experimental +func (g *GoalCodeFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := make([]protocol.DiscoverFeatureDisclosure, 0, len(g.goalCodes)) + + for _, goalCode := range g.goalCodes { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeGoalCode, + ID: goalCode, + }) + } + + return disclosures +} diff --git a/handlers/discovery/features/goalcode_test.go b/handlers/discovery/features/goalcode_test.go new file mode 100644 index 0000000..867325a --- /dev/null +++ b/handlers/discovery/features/goalcode_test.go @@ -0,0 +1,54 @@ +package features_test + +import ( + "context" + "testing" + + "github.com/iden3/iden3comm/v2/handlers/discovery/features" + "github.com/iden3/iden3comm/v2/protocol" + "github.com/stretchr/testify/require" +) + +func TestGoalCodeFeaturer_Handle(t *testing.T) { + tests := []struct { + name string + supportedGoalCodes []string + expectedDisclosures []protocol.DiscoverFeatureDisclosure + }{ + { + name: "should return default disclosures", + supportedGoalCodes: nil, + expectedDisclosures: []protocol.DiscoverFeatureDisclosure{}, + }, + { + name: "should return two goal codes", + supportedGoalCodes: []string{ + "goal-1", + "goal-2", + }, + expectedDisclosures: []protocol.DiscoverFeatureDisclosure{ + {FeatureType: protocol.DiscoveryProtocolFeatureTypeGoalCode, ID: "goal-1"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeGoalCode, ID: "goal-2"}, + }, + }, + { + name: "should return empty disclosures", + supportedGoalCodes: []string{}, + expectedDisclosures: []protocol.DiscoverFeatureDisclosure{}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var g *features.GoalCodeFeaturer + if tc.supportedGoalCodes == nil { + g = features.NewGoalCodeFeaturer() + } else { + g = features.NewGoalCodeFeaturer(features.WithGoalCodes(tc.supportedGoalCodes...)) + } + + disclosures := g.Handle(context.Background()) + require.ElementsMatch(t, tc.expectedDisclosures, disclosures) + }) + } +} diff --git a/handlers/discovery/features/header.go b/handlers/discovery/features/header.go new file mode 100644 index 0000000..f5711ab --- /dev/null +++ b/handlers/discovery/features/header.go @@ -0,0 +1,64 @@ +package features + +import ( + "context" + + "github.com/iden3/iden3comm/v2/protocol" +) + +// HeaderFeaturer implementation +// # Experimental +type HeaderFeaturer struct { + headers []string +} + +// HeaderOption configures a HeaderFeaturer +// # Experimental +type HeaderOption func(*HeaderFeaturer) + +// WithHeaders sets custom headers for HeaderFeaturer +// # Experimental +func WithHeaders(headers ...string) HeaderOption { + return func(h *HeaderFeaturer) { + h.headers = headers + } +} + +// NewHeaderFeaturer constructor accepts functional options. +// If no headers option is provided, it falls back to the defaultHeaders. +// # Experimental +func NewHeaderFeaturer(opts ...HeaderOption) *HeaderFeaturer { + h := &HeaderFeaturer{ + headers: []string{ + "id", + "typ", + "type", + "thid", + "body", + "from", + "to", + "created_time", + "expires_time", + "attachments", + }, + } + for _, opt := range opts { + opt(h) + } + return h +} + +// Handle implementation for HeaderFeaturer +// # Experimental +func (h *HeaderFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := make([]protocol.DiscoverFeatureDisclosure, 0, len(h.headers)) + + for _, header := range h.headers { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, + ID: header, + }) + } + + return disclosures +} diff --git a/handlers/discovery/features/header_test.go b/handlers/discovery/features/header_test.go new file mode 100644 index 0000000..0f8de18 --- /dev/null +++ b/handlers/discovery/features/header_test.go @@ -0,0 +1,65 @@ +package features_test + +import ( + "context" + "testing" + + "github.com/iden3/iden3comm/v2/handlers/discovery/features" + "github.com/iden3/iden3comm/v2/protocol" + "github.com/stretchr/testify/require" +) + +func TestHeaderFeaturer_Handle(t *testing.T) { + tests := []struct { + name string + supportedHeaders []string + expectedDisclosures []protocol.DiscoverFeatureDisclosure + }{ + { + name: "should return default disclosures", + supportedHeaders: nil, + expectedDisclosures: []protocol.DiscoverFeatureDisclosure{ + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "id"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "typ"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "type"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "thid"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "body"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "from"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "to"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "created_time"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "expires_time"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "attachments"}, + }, + }, + { + name: "should return id typ only", + supportedHeaders: []string{ + "id", + "typ", + }, + expectedDisclosures: []protocol.DiscoverFeatureDisclosure{ + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "id"}, + {FeatureType: protocol.DiscoveryProtocolFeatureTypeHeader, ID: "typ"}, + }, + }, + { + name: "should return empty disclosures", + supportedHeaders: []string{}, + expectedDisclosures: []protocol.DiscoverFeatureDisclosure{}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var headerFeaturer *features.HeaderFeaturer + if tc.supportedHeaders == nil { + headerFeaturer = features.NewHeaderFeaturer() + } else { + headerFeaturer = features.NewHeaderFeaturer(features.WithHeaders(tc.supportedHeaders...)) + } + + disclosures := headerFeaturer.Handle(context.Background()) + require.ElementsMatch(t, tc.expectedDisclosures, disclosures) + }) + } +} diff --git a/handlers/discovery/features/protocol.go b/handlers/discovery/features/protocol.go new file mode 100644 index 0000000..6d024ca --- /dev/null +++ b/handlers/discovery/features/protocol.go @@ -0,0 +1,35 @@ +package features + +import ( + "context" + + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/protocol" +) + +// ProtocolFeaturer implementation +// # Experimental +type ProtocolFeaturer struct { + supportedProtocols []iden3comm.ProtocolMessage +} + +// NewProtocolFeaturer constructor +// # Experimental +func NewProtocolFeaturer(supportedProtocols []iden3comm.ProtocolMessage) *ProtocolFeaturer { + return &ProtocolFeaturer{ + supportedProtocols: supportedProtocols, + } +} + +// Handle implementation for ProtocolFeaturer +// # Experimental +func (p *ProtocolFeaturer) Handle(ctx context.Context) []protocol.DiscoverFeatureDisclosure { + disclosures := []protocol.DiscoverFeatureDisclosure{} + for _, protocolMessage := range p.supportedProtocols { + disclosures = append(disclosures, protocol.DiscoverFeatureDisclosure{ + FeatureType: protocol.DiscoveryProtocolFeatureTypeProtocol, + ID: string(protocolMessage), + }) + } + return disclosures +} diff --git a/handlers/discovery/utils.go b/handlers/discovery/utils.go index 95a5dbe..db82920 100644 --- a/handlers/discovery/utils.go +++ b/handlers/discovery/utils.go @@ -3,6 +3,7 @@ package discovery import "strings" // Feature represents a parsed feature with its components +// # Experimental type Feature struct { Version string Env string @@ -11,6 +12,7 @@ type Feature struct { } // ParseFeature parses a feature ID string into a Feature struct +// # Experimental func ParseFeature(id string) Feature { id = strings.TrimSpace(id) if id == "" {