From 40e61c959cd6f1e4d8f41e646c0625901e2cd36e Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Wed, 19 Feb 2025 17:34:06 -0800 Subject: [PATCH 1/6] Use Matchable interface --- x/meg/meg.go | 8 ++++---- x/meg/sugar.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x/meg/meg.go b/x/meg/meg.go index ccc2275..6c3927d 100644 --- a/x/meg/meg.go +++ b/x/meg/meg.go @@ -27,12 +27,12 @@ type MatchState struct { codeOrKind int } -type captureFunc func(string) error +type captureFunc func(Matchable) error // capture is a linked list of capture funcs with values. type capture struct { f captureFunc - v string + v Matchable prev *capture } @@ -94,7 +94,7 @@ func Match[S ~[]T, T Matchable](matcher Matcher, components S) (bool, error) { if s.capture != nil { next := &capture{ f: s.capture, - v: c.Value(), + v: c, } if cm == nil { cm = next @@ -123,7 +123,7 @@ func Match[S ~[]T, T Matchable](matcher Matcher, components S) (bool, error) { // to left, but users expect them left to right. type captureWithVal struct { f captureFunc - v string + v Matchable } reversedCaptures := make([]captureWithVal, 0, 16) for c != nil { diff --git a/x/meg/sugar.go b/x/meg/sugar.go index ee961cc..44cf829 100644 --- a/x/meg/sugar.go +++ b/x/meg/sugar.go @@ -75,12 +75,12 @@ func captureOneValueOrErr(val *string) captureFunc { return nil } var set bool - f := func(s string) error { + f := func(s Matchable) error { if set { *val = "" return errAlreadyCapture } - *val = s + *val = s.Value() return nil } return f @@ -90,8 +90,8 @@ func captureMany(vals *[]string) captureFunc { if vals == nil { return nil } - f := func(s string) error { - *vals = append(*vals, s) + f := func(s Matchable) error { + *vals = append(*vals, s.Value()) return nil } return f From 9fb6d9910b4b09225ef28ed42286e1366ef717e0 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Wed, 19 Feb 2025 17:38:19 -0800 Subject: [PATCH 2/6] Add Bytes to Matchable interface --- x/meg/meg.go | 1 + x/meg/meg_test.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/x/meg/meg.go b/x/meg/meg.go index 6c3927d..c3b6b4f 100644 --- a/x/meg/meg.go +++ b/x/meg/meg.go @@ -54,6 +54,7 @@ func (s MatchState) String() string { type Matchable interface { Code() int Value() string // Used when capturing the value + Bytes() []byte } // Match returns whether the given Components match the Pattern defined in MatchState. diff --git a/x/meg/meg_test.go b/x/meg/meg_test.go index e0265c4..f077a34 100644 --- a/x/meg/meg_test.go +++ b/x/meg/meg_test.go @@ -22,6 +22,11 @@ func (c codeAndValue) Value() string { return c.val } +// Bytes implements Matchable. +func (c codeAndValue) Bytes() []byte { + return []byte(c.val) +} + var _ Matchable = codeAndValue{} func TestSimple(t *testing.T) { From 6b38db0dcba96316306ac16b39e914875039074a Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Wed, 19 Feb 2025 17:44:58 -0800 Subject: [PATCH 3/6] feat(x/meg): Support capturing bytes --- meg_test.go | 4 +-- x/meg/bench_test.go | 75 +++++++++++++++++++++++++++++++++++---------- x/meg/meg_test.go | 4 +-- x/meg/sugar.go | 67 +++++++++++++++++++++++++++++++++------- 4 files changed, 118 insertions(+), 32 deletions(-) diff --git a/meg_test.go b/meg_test.go index 32fcadd..908dd81 100644 --- a/meg_test.go +++ b/meg_test.go @@ -16,10 +16,10 @@ func TestMatchAndCaptureMultiaddr(t *testing.T) { meg.Val(P_IP4), meg.Val(P_IP6), ), - meg.CaptureVal(P_UDP, &udpPort), + meg.CaptureStringVal(P_UDP, &udpPort), meg.Val(P_QUIC_V1), meg.Val(P_WEBTRANSPORT), - meg.CaptureZeroOrMore(P_CERTHASH, &certhashes), + meg.CaptureZeroOrMoreStringVals(P_CERTHASH, &certhashes), ) if !found { t.Fatal("failed to match") diff --git a/x/meg/bench_test.go b/x/meg/bench_test.go index 15bf386..5eff21f 100644 --- a/x/meg/bench_test.go +++ b/x/meg/bench_test.go @@ -22,7 +22,7 @@ func preallocateCapture() *preallocatedCapture { ), meg.Val(multiaddr.P_UDP), meg.Val(multiaddr.P_WEBRTC_DIRECT), - meg.CaptureZeroOrMore(multiaddr.P_CERTHASH, &p.certHashes), + meg.CaptureZeroOrMoreStringVals(multiaddr.P_CERTHASH, &p.certHashes), ) return p } @@ -87,19 +87,19 @@ func isWebTransportMultiaddrPrealloc() *preallocatedCapture { var sni string p.matcher = meg.PatternToMatcher( meg.Or( - meg.CaptureVal(multiaddr.P_IP4, &ip4Addr), - meg.CaptureVal(multiaddr.P_IP6, &ip6Addr), - meg.CaptureVal(multiaddr.P_DNS4, &dnsName), - meg.CaptureVal(multiaddr.P_DNS6, &dnsName), - meg.CaptureVal(multiaddr.P_DNS, &dnsName), + meg.CaptureStringVal(multiaddr.P_IP4, &ip4Addr), + meg.CaptureStringVal(multiaddr.P_IP6, &ip6Addr), + meg.CaptureStringVal(multiaddr.P_DNS4, &dnsName), + meg.CaptureStringVal(multiaddr.P_DNS6, &dnsName), + meg.CaptureStringVal(multiaddr.P_DNS, &dnsName), ), - meg.CaptureVal(multiaddr.P_UDP, &udpPort), + meg.CaptureStringVal(multiaddr.P_UDP, &udpPort), meg.Val(multiaddr.P_QUIC_V1), meg.Optional( - meg.CaptureVal(multiaddr.P_SNI, &sni), + meg.CaptureStringVal(multiaddr.P_SNI, &sni), ), meg.Val(multiaddr.P_WEBTRANSPORT), - meg.CaptureZeroOrMore(multiaddr.P_CERTHASH, &p.certHashes), + meg.CaptureZeroOrMoreStringVals(multiaddr.P_CERTHASH, &p.certHashes), ) wtPrealloc = p return p @@ -120,19 +120,19 @@ func IsWebTransportMultiaddr(m multiaddr.Multiaddr) (bool, int) { var certHashesStr []string matched, _ := m.Match( meg.Or( - meg.CaptureVal(multiaddr.P_IP4, &ip4Addr), - meg.CaptureVal(multiaddr.P_IP6, &ip6Addr), - meg.CaptureVal(multiaddr.P_DNS4, &dnsName), - meg.CaptureVal(multiaddr.P_DNS6, &dnsName), - meg.CaptureVal(multiaddr.P_DNS, &dnsName), + meg.CaptureStringVal(multiaddr.P_IP4, &ip4Addr), + meg.CaptureStringVal(multiaddr.P_IP6, &ip6Addr), + meg.CaptureStringVal(multiaddr.P_DNS4, &dnsName), + meg.CaptureStringVal(multiaddr.P_DNS6, &dnsName), + meg.CaptureStringVal(multiaddr.P_DNS, &dnsName), ), - meg.CaptureVal(multiaddr.P_UDP, &udpPort), + meg.CaptureStringVal(multiaddr.P_UDP, &udpPort), meg.Val(multiaddr.P_QUIC_V1), meg.Optional( - meg.CaptureVal(multiaddr.P_SNI, &sni), + meg.CaptureStringVal(multiaddr.P_SNI, &sni), ), meg.Val(multiaddr.P_WEBTRANSPORT), - meg.CaptureZeroOrMore(multiaddr.P_CERTHASH, &certHashesStr), + meg.CaptureZeroOrMoreStringVals(multiaddr.P_CERTHASH, &certHashesStr), ) if !matched { return false, 0 @@ -140,6 +140,35 @@ func IsWebTransportMultiaddr(m multiaddr.Multiaddr) (bool, int) { return true, len(certHashesStr) } +func IsWebTransportMultiaddrCaptureBytes(m multiaddr.Multiaddr) (bool, int) { + var dnsName []byte + var ip4Addr []byte + var ip6Addr []byte + var udpPort []byte + var sni []byte + var certHashes [][]byte + matched, _ := m.Match( + meg.Or( + meg.CaptureBytes(multiaddr.P_IP4, &ip4Addr), + meg.CaptureBytes(multiaddr.P_IP6, &ip6Addr), + meg.CaptureBytes(multiaddr.P_DNS4, &dnsName), + meg.CaptureBytes(multiaddr.P_DNS6, &dnsName), + meg.CaptureBytes(multiaddr.P_DNS, &dnsName), + ), + meg.CaptureBytes(multiaddr.P_UDP, &udpPort), + meg.Val(multiaddr.P_QUIC_V1), + meg.Optional( + meg.CaptureBytes(multiaddr.P_SNI, &sni), + ), + meg.Val(multiaddr.P_WEBTRANSPORT), + meg.CaptureZeroOrMoreBytes(multiaddr.P_CERTHASH, &certHashes), + ) + if !matched { + return false, 0 + } + return true, len(certHashes) +} + func IsWebTransportMultiaddrNoCapture(m multiaddr.Multiaddr) (bool, int) { matched, _ := m.Match( meg.Or( @@ -355,6 +384,18 @@ func BenchmarkIsWebTransportMultiaddrNoCapture(b *testing.B) { } } +func BenchmarkIsWebTransportMultiaddrCaptureBytes(b *testing.B) { + addr := multiaddr.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1/sni/example.com/webtransport") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + isWT, count := IsWebTransportMultiaddrCaptureBytes(addr) + if !isWT || count != 0 { + b.Fatal("unexpected result") + } + } +} + func BenchmarkIsWebTransportMultiaddr(b *testing.B) { addr := multiaddr.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1/sni/example.com/webtransport") diff --git a/x/meg/meg_test.go b/x/meg/meg_test.go index f077a34..e8847cb 100644 --- a/x/meg/meg_test.go +++ b/x/meg/meg_test.go @@ -124,7 +124,7 @@ func TestCapture(t *testing.T) { { setup: func() (Matcher, func()) { var code0str string - return PatternToMatcher(CaptureVal(0, &code0str), Val(1)), func() { + return PatternToMatcher(CaptureStringVal(0, &code0str), Val(1)), func() { if code0str != "hello" { panic("unexpected value") } @@ -135,7 +135,7 @@ func TestCapture(t *testing.T) { { setup: func() (Matcher, func()) { var code0strs []string - return PatternToMatcher(CaptureOneOrMore(0, &code0strs), Val(1)), func() { + return PatternToMatcher(CaptureOneOrMoreStringVals(0, &code0strs), Val(1)), func() { if code0strs[0] != "hello" { panic("unexpected value") } diff --git a/x/meg/sugar.go b/x/meg/sugar.go index 44cf829..028009b 100644 --- a/x/meg/sugar.go +++ b/x/meg/sugar.go @@ -70,7 +70,23 @@ func Or(p ...Pattern) Pattern { var errAlreadyCapture = errors.New("already captured") -func captureOneValueOrErr(val *string) captureFunc { +func captureOneBytesOrErr(val *[]byte) captureFunc { + if val == nil { + return nil + } + var set bool + f := func(s Matchable) error { + if set { + *val = nil + return errAlreadyCapture + } + *val = s.Bytes() + return nil + } + return f +} + +func captureOneStringValueOrErr(val *string) captureFunc { if val == nil { return nil } @@ -86,7 +102,18 @@ func captureOneValueOrErr(val *string) captureFunc { return f } -func captureMany(vals *[]string) captureFunc { +func captureManyBytes(vals *[][]byte) captureFunc { + if vals == nil { + return nil + } + f := func(s Matchable) error { + *vals = append(*vals, s.Bytes()) + return nil + } + return f +} + +func captureManyStrings(vals *[]string) captureFunc { if vals == nil { return nil } @@ -110,15 +137,19 @@ func captureValWithF(code int, f captureFunc) Pattern { } func Val(code int) Pattern { - return CaptureVal(code, nil) + return CaptureStringVal(code, nil) } -func CaptureVal(code int, val *string) Pattern { - return captureValWithF(code, captureOneValueOrErr(val)) +func CaptureStringVal(code int, val *string) Pattern { + return captureValWithF(code, captureOneStringValueOrErr(val)) +} + +func CaptureBytes(code int, val *[]byte) Pattern { + return captureValWithF(code, captureOneBytesOrErr(val)) } func ZeroOrMore(code int) Pattern { - return CaptureZeroOrMore(code, nil) + return CaptureZeroOrMoreStringVals(code, nil) } func captureZeroOrMoreWithF(code int, f captureFunc) Pattern { @@ -146,16 +177,30 @@ func captureZeroOrMoreWithF(code int, f captureFunc) Pattern { } } -func CaptureZeroOrMore(code int, vals *[]string) Pattern { - return captureZeroOrMoreWithF(code, captureMany(vals)) +func CaptureZeroOrMoreBytes(code int, vals *[][]byte) Pattern { + return captureZeroOrMoreWithF(code, captureManyBytes(vals)) +} + +func CaptureZeroOrMoreStringVals(code int, vals *[]string) Pattern { + return captureZeroOrMoreWithF(code, captureManyStrings(vals)) } func OneOrMore(code int) Pattern { - return CaptureOneOrMore(code, nil) + return CaptureOneOrMoreStringVals(code, nil) +} + +func CaptureOneOrMoreStringVals(code int, vals *[]string) Pattern { + f := captureManyStrings(vals) + return func(states *[]MatchState, nextIdx int) int { + // First attach the zero-or-more loop. + zeroOrMoreIdx := captureZeroOrMoreWithF(code, f)(states, nextIdx) + // Then put the capture state before the loop. + return captureValWithF(code, f)(states, zeroOrMoreIdx) + } } -func CaptureOneOrMore(code int, vals *[]string) Pattern { - f := captureMany(vals) +func CaptureOneOrMoreBytes(code int, vals *[][]byte) Pattern { + f := captureManyBytes(vals) return func(states *[]MatchState, nextIdx int) int { // First attach the zero-or-more loop. zeroOrMoreIdx := captureZeroOrMoreWithF(code, f)(states, nextIdx) From ec579f9c863355d607f7ff17c7a1d50f456d62a7 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Feb 2025 08:43:26 -0800 Subject: [PATCH 4/6] Export CaptureWithF Can be used by more specific capturers (e.g capture net.AddrIP) --- x/meg/meg.go | 8 ++++---- x/meg/sugar.go | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/x/meg/meg.go b/x/meg/meg.go index c3b6b4f..5e4e66c 100644 --- a/x/meg/meg.go +++ b/x/meg/meg.go @@ -18,7 +18,7 @@ const ( // MatchState is the Thompson NFA for a regular expression. type MatchState struct { - capture captureFunc + capture CaptureFunc // next is is the index of the next state. in the MatchState array. next int // If codeOrKind is negative, it is a kind. @@ -27,11 +27,11 @@ type MatchState struct { codeOrKind int } -type captureFunc func(Matchable) error +type CaptureFunc func(Matchable) error // capture is a linked list of capture funcs with values. type capture struct { - f captureFunc + f CaptureFunc v Matchable prev *capture } @@ -123,7 +123,7 @@ func Match[S ~[]T, T Matchable](matcher Matcher, components S) (bool, error) { // Flip the order of the captures because we see captures from right // to left, but users expect them left to right. type captureWithVal struct { - f captureFunc + f CaptureFunc v Matchable } reversedCaptures := make([]captureWithVal, 0, 16) diff --git a/x/meg/sugar.go b/x/meg/sugar.go index 028009b..9858164 100644 --- a/x/meg/sugar.go +++ b/x/meg/sugar.go @@ -70,7 +70,7 @@ func Or(p ...Pattern) Pattern { var errAlreadyCapture = errors.New("already captured") -func captureOneBytesOrErr(val *[]byte) captureFunc { +func captureOneBytesOrErr(val *[]byte) CaptureFunc { if val == nil { return nil } @@ -86,7 +86,7 @@ func captureOneBytesOrErr(val *[]byte) captureFunc { return f } -func captureOneStringValueOrErr(val *string) captureFunc { +func captureOneStringValueOrErr(val *string) CaptureFunc { if val == nil { return nil } @@ -102,7 +102,7 @@ func captureOneStringValueOrErr(val *string) captureFunc { return f } -func captureManyBytes(vals *[][]byte) captureFunc { +func captureManyBytes(vals *[][]byte) CaptureFunc { if vals == nil { return nil } @@ -113,7 +113,7 @@ func captureManyBytes(vals *[][]byte) captureFunc { return f } -func captureManyStrings(vals *[]string) captureFunc { +func captureManyStrings(vals *[]string) CaptureFunc { if vals == nil { return nil } @@ -124,7 +124,7 @@ func captureManyStrings(vals *[]string) captureFunc { return f } -func captureValWithF(code int, f captureFunc) Pattern { +func CaptureWithF(code int, f CaptureFunc) Pattern { return func(states *[]MatchState, nextIdx int) int { newState := MatchState{ capture: f, @@ -141,18 +141,18 @@ func Val(code int) Pattern { } func CaptureStringVal(code int, val *string) Pattern { - return captureValWithF(code, captureOneStringValueOrErr(val)) + return CaptureWithF(code, captureOneStringValueOrErr(val)) } func CaptureBytes(code int, val *[]byte) Pattern { - return captureValWithF(code, captureOneBytesOrErr(val)) + return CaptureWithF(code, captureOneBytesOrErr(val)) } func ZeroOrMore(code int) Pattern { return CaptureZeroOrMoreStringVals(code, nil) } -func captureZeroOrMoreWithF(code int, f captureFunc) Pattern { +func CaptureZeroOrMoreWithF(code int, f CaptureFunc) Pattern { return func(states *[]MatchState, nextIdx int) int { // Create the match state. matchState := MatchState{ @@ -178,11 +178,11 @@ func captureZeroOrMoreWithF(code int, f captureFunc) Pattern { } func CaptureZeroOrMoreBytes(code int, vals *[][]byte) Pattern { - return captureZeroOrMoreWithF(code, captureManyBytes(vals)) + return CaptureZeroOrMoreWithF(code, captureManyBytes(vals)) } func CaptureZeroOrMoreStringVals(code int, vals *[]string) Pattern { - return captureZeroOrMoreWithF(code, captureManyStrings(vals)) + return CaptureZeroOrMoreWithF(code, captureManyStrings(vals)) } func OneOrMore(code int) Pattern { @@ -193,9 +193,9 @@ func CaptureOneOrMoreStringVals(code int, vals *[]string) Pattern { f := captureManyStrings(vals) return func(states *[]MatchState, nextIdx int) int { // First attach the zero-or-more loop. - zeroOrMoreIdx := captureZeroOrMoreWithF(code, f)(states, nextIdx) + zeroOrMoreIdx := CaptureZeroOrMoreWithF(code, f)(states, nextIdx) // Then put the capture state before the loop. - return captureValWithF(code, f)(states, zeroOrMoreIdx) + return CaptureWithF(code, f)(states, zeroOrMoreIdx) } } @@ -203,9 +203,9 @@ func CaptureOneOrMoreBytes(code int, vals *[][]byte) Pattern { f := captureManyBytes(vals) return func(states *[]MatchState, nextIdx int) int { // First attach the zero-or-more loop. - zeroOrMoreIdx := captureZeroOrMoreWithF(code, f)(states, nextIdx) + zeroOrMoreIdx := CaptureZeroOrMoreWithF(code, f)(states, nextIdx) // Then put the capture state before the loop. - return captureValWithF(code, f)(states, zeroOrMoreIdx) + return CaptureWithF(code, f)(states, zeroOrMoreIdx) } } From 0feb6e40e260da6ef0e0bc4564301d95f11a7ff8 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Feb 2025 09:53:05 -0800 Subject: [PATCH 5/6] Support Any match, RawValue, and multiple Concatenations --- x/meg/meg.go | 25 ++++++++++++++++++------- x/meg/meg_test.go | 21 +++++++++++++++++++++ x/meg/sugar.go | 31 +++++++++++++++++++++++++------ 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/x/meg/meg.go b/x/meg/meg.go index 5e4e66c..c70a139 100644 --- a/x/meg/meg.go +++ b/x/meg/meg.go @@ -12,8 +12,11 @@ import ( type stateKind = int const ( - done stateKind = (iota * -1) - 1 - // split anything else that is negative + matchAny stateKind = (iota * -1) - 1 + // done MUST be the last stateKind in this list. We use it to determine if a + // state is a split index. + done + // Anything that is less than done is a split index ) // MatchState is the Thompson NFA for a regular expression. @@ -22,7 +25,7 @@ type MatchState struct { // next is is the index of the next state. in the MatchState array. next int // If codeOrKind is negative, it is a kind. - // If it is negative, but not a `done`, then it is the index to the next split. + // If it is negative, and less than `done`, then it is the index to the next split. // This is done to keep the `MatchState` struct small and cache friendly. codeOrKind int } @@ -53,7 +56,13 @@ func (s MatchState) String() string { type Matchable interface { Code() int - Value() string // Used when capturing the value + // Value() returns the string representation of the matchable. + Value() string + // RawValue() returns the byte representation of the Value + RawValue() []byte + // Bytes() returns the underlying bytes of the matchable. For multiaddr + // Components, this includes the protocol code and possibly the varint + // encoded size. Bytes() []byte } @@ -90,7 +99,7 @@ func Match[S ~[]T, T Matchable](matcher Matcher, components S) (bool, error) { } for i, stateIndex := range currentStates.states { s := states[stateIndex] - if s.codeOrKind >= 0 && s.codeOrKind == c.Code() { + if s.codeOrKind == matchAny || (s.codeOrKind >= 0 && s.codeOrKind == c.Code()) { cm := currentStates.captures[i] if s.capture != nil { next := &capture{ @@ -191,10 +200,12 @@ func appendState(arr statesAndCaptures, states []MatchState, stateIndex int, c * return arr } +const splitIdxOffset = (-1 * (done - 1)) + func storeSplitIdx(codeOrKind int) int { - return (codeOrKind + 2) * -1 + return (codeOrKind + splitIdxOffset) * -1 } func restoreSplitIdx(splitIdx int) int { - return (splitIdx * -1) - 2 + return (splitIdx * -1) - splitIdxOffset } diff --git a/x/meg/meg_test.go b/x/meg/meg_test.go index e8847cb..8f5cff3 100644 --- a/x/meg/meg_test.go +++ b/x/meg/meg_test.go @@ -27,6 +27,11 @@ func (c codeAndValue) Bytes() []byte { return []byte(c.val) } +// RawValue implements Matchable. +func (c codeAndValue) RawValue() []byte { + return []byte(c.val) +} + var _ Matchable = codeAndValue{} func TestSimple(t *testing.T) { @@ -38,6 +43,22 @@ func TestSimple(t *testing.T) { } testCases := []testCase{ + { + pattern: PatternToMatcher(Val(Any), Val(1)), + shouldMatch: [][]int{ + {0, 1}, + {1, 1}, + {2, 1}, + {3, 1}, + {4, 1}, + }, + shouldNotMatch: [][]int{ + {0}, + {0, 0}, + {0, 1, 0}, + }, + skipQuickCheck: true, + }, { pattern: PatternToMatcher(Val(0), Val(1)), shouldMatch: [][]int{{0, 1}}, diff --git a/x/meg/sugar.go b/x/meg/sugar.go index 9858164..41e18e3 100644 --- a/x/meg/sugar.go +++ b/x/meg/sugar.go @@ -40,10 +40,26 @@ func PatternToMatcher(patterns ...Pattern) Matcher { return Matcher{states: states, startIdx: nextIdx} } -func Cat(left, right Pattern) Pattern { - return func(states *[]MatchState, nextIdx int) int { - // First run the right pattern, then feed the result into left. - return left(states, right(states, nextIdx)) +func Cat(patterns ...Pattern) Pattern { + switch len(patterns) { + case 0: + return func(states *[]MatchState, nextIdx int) int { + return nextIdx + } + case 1: + return patterns[0] + case 2: + return func(states *[]MatchState, nextIdx int) int { + left := patterns[0] + right := patterns[1] + // First run the right pattern, then feed the result into left. + return left(states, right(states, nextIdx)) + } + default: + return Cat( + Cat(patterns[:len(patterns)-1]...), + patterns[len(patterns)-1], + ) } } @@ -80,7 +96,7 @@ func captureOneBytesOrErr(val *[]byte) CaptureFunc { *val = nil return errAlreadyCapture } - *val = s.Bytes() + *val = s.RawValue() return nil } return f @@ -107,7 +123,7 @@ func captureManyBytes(vals *[][]byte) CaptureFunc { return nil } f := func(s Matchable) error { - *vals = append(*vals, s.Bytes()) + *vals = append(*vals, s.RawValue()) return nil } return f @@ -140,6 +156,9 @@ func Val(code int) Pattern { return CaptureStringVal(code, nil) } +// Any is a special code that matches any value. +var Any int = matchAny + func CaptureStringVal(code int, val *string) Pattern { return CaptureWithF(code, captureOneStringValueOrErr(val)) } From ddb6e55b422f6f54ab56864f3c7015a02ce8cf3b Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Feb 2025 09:53:18 -0800 Subject: [PATCH 6/6] Add CaptureAddrPort --- meg_capturers.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ meg_test.go | 27 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 meg_capturers.go diff --git a/meg_capturers.go b/meg_capturers.go new file mode 100644 index 0000000..21581e7 --- /dev/null +++ b/meg_capturers.go @@ -0,0 +1,54 @@ +package multiaddr + +import ( + "encoding/binary" + "fmt" + "net/netip" + + "github.com/multiformats/go-multiaddr/x/meg" +) + +func CaptureAddrPort(network *string, ipPort *netip.AddrPort) (capturePattern meg.Pattern) { + var ipOnly netip.Addr + capturePort := func(s meg.Matchable) error { + switch s.Code() { + case P_UDP: + *network = "udp" + case P_TCP: + *network = "tcp" + default: + return fmt.Errorf("invalid network: %s", s.Value()) + } + + port := binary.BigEndian.Uint16(s.RawValue()) + *ipPort = netip.AddrPortFrom(ipOnly, port) + return nil + } + + pattern := meg.Cat( + meg.Or( + meg.CaptureWithF(P_IP4, func(s meg.Matchable) error { + var ok bool + ipOnly, ok = netip.AddrFromSlice(s.RawValue()) + if !ok { + return fmt.Errorf("invalid ip4 address: %s", s.Value()) + } + return nil + }), + meg.CaptureWithF(P_IP6, func(s meg.Matchable) error { + var ok bool + ipOnly, ok = netip.AddrFromSlice(s.RawValue()) + if !ok { + return fmt.Errorf("invalid ip6 address: %s", s.Value()) + } + return nil + }), + ), + meg.Or( + meg.CaptureWithF(P_UDP, capturePort), + meg.CaptureWithF(P_TCP, capturePort), + ), + ) + + return pattern +} diff --git a/meg_test.go b/meg_test.go index 908dd81..d5e0ee0 100644 --- a/meg_test.go +++ b/meg_test.go @@ -1,6 +1,7 @@ package multiaddr import ( + "net/netip" "testing" "github.com/multiformats/go-multiaddr/x/meg" @@ -43,3 +44,29 @@ func TestMatchAndCaptureMultiaddr(t *testing.T) { } } } + +func TestCaptureAddrPort(t *testing.T) { + m := StringCast("/ip4/1.2.3.4/udp/8231/quic-v1/webtransport") + var addrPort netip.AddrPort + var network string + + found, err := m.Match( + CaptureAddrPort(&network, &addrPort), + meg.ZeroOrMore(meg.Any), + ) + if err != nil { + t.Fatal("error", err) + } + if !found { + t.Fatal("failed to match") + } + if !addrPort.IsValid() { + t.Fatal("failed to capture addrPort") + } + if network != "udp" { + t.Fatal("unexpected network", network) + } + if addrPort.String() != "1.2.3.4:8231" { + t.Fatal("unexpected ipPort", addrPort) + } +}