Skip to content

Commit 5426748

Browse files
authored
Megular Expressions (#263)
* Add multiaddr expression group matching Support captures export some things wip thinking about public API Think about exposing meg as a public API doc comments Finish rename Add helper for meg and add test add comment for devs * Move meg package to /x/ for experimental * Add fuzz cases * faster Code() method twice as fast without the copy * avoid copying an empty map if no captures * Faster megular expressions (#265) * much cheaper copies of captures * Add a benchmark * allocate to a slice. Use indexes as handles * cleanup * Add nocapture loop benchmark It's really fast. No surprise * cleanup * nits * feat(x/meg): Support capturing components (#269) * Use Matchable interface * Add Bytes to Matchable interface * feat(x/meg): Support capturing bytes * Export CaptureWithF Can be used by more specific capturers (e.g capture net.AddrIP) * Support Any match, RawValue, and multiple Concatenations * Add CaptureAddrPort * Fix typo in rebase * workaround for go generics * less hacky workaround for go generics * Avoid unnecessary copy * Make `matchAny` less greedy when there are alternatives The state machine handling was modified to deprioritize the `matchAny` path when alternatives exist, resulting in less greedy matching behavior when using Any patterns. * PR comments * Update Pattern type to return next state
1 parent 61c0d1f commit 5426748

File tree

14 files changed

+1549
-0
lines changed

14 files changed

+1549
-0
lines changed

component.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ func (c *Component) Protocol() Protocol {
162162
return *c.protocol
163163
}
164164

165+
func (c *Component) Code() int {
166+
if c == nil {
167+
return 0
168+
}
169+
return c.Protocol().Code
170+
}
171+
165172
func (c *Component) RawValue() []byte {
166173
if c == nil {
167174
return nil

meg_capturers.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package multiaddr
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"net/netip"
7+
8+
"github.com/multiformats/go-multiaddr/x/meg"
9+
)
10+
11+
func CaptureAddrPort(network *string, ipPort *netip.AddrPort) (capturePattern meg.Pattern) {
12+
var ipOnly netip.Addr
13+
capturePort := func(s meg.Matchable) error {
14+
switch s.Code() {
15+
case P_UDP:
16+
*network = "udp"
17+
case P_TCP:
18+
*network = "tcp"
19+
default:
20+
return fmt.Errorf("invalid network: %s", s.Value())
21+
}
22+
23+
port := binary.BigEndian.Uint16(s.RawValue())
24+
*ipPort = netip.AddrPortFrom(ipOnly, port)
25+
return nil
26+
}
27+
28+
pattern := meg.Cat(
29+
meg.Or(
30+
meg.CaptureWithF(P_IP4, func(s meg.Matchable) error {
31+
var ok bool
32+
ipOnly, ok = netip.AddrFromSlice(s.RawValue())
33+
if !ok {
34+
return fmt.Errorf("invalid ip4 address: %s", s.Value())
35+
}
36+
return nil
37+
}),
38+
meg.CaptureWithF(P_IP6, func(s meg.Matchable) error {
39+
var ok bool
40+
ipOnly, ok = netip.AddrFromSlice(s.RawValue())
41+
if !ok {
42+
return fmt.Errorf("invalid ip6 address: %s", s.Value())
43+
}
44+
return nil
45+
}),
46+
),
47+
meg.Or(
48+
meg.CaptureWithF(P_UDP, capturePort),
49+
meg.CaptureWithF(P_TCP, capturePort),
50+
),
51+
)
52+
53+
return pattern
54+
}

meg_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package multiaddr
2+
3+
import (
4+
"net/netip"
5+
"testing"
6+
7+
"github.com/multiformats/go-multiaddr/x/meg"
8+
)
9+
10+
func TestMatchAndCaptureMultiaddr(t *testing.T) {
11+
m := StringCast("/ip4/1.2.3.4/udp/8231/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41")
12+
13+
var udpPort string
14+
var certhashes []string
15+
found, _ := m.Match(
16+
meg.Or(
17+
meg.Val(P_IP4),
18+
meg.Val(P_IP6),
19+
),
20+
meg.CaptureStringVal(P_UDP, &udpPort),
21+
meg.Val(P_QUIC_V1),
22+
meg.Val(P_WEBTRANSPORT),
23+
meg.CaptureZeroOrMoreStrings(P_CERTHASH, &certhashes),
24+
)
25+
if !found {
26+
t.Fatal("failed to match")
27+
}
28+
if udpPort != "8231" {
29+
t.Fatal("unexpected value")
30+
}
31+
32+
if len(certhashes) != 2 {
33+
t.Fatal("Didn't capture all certhashes")
34+
}
35+
36+
{
37+
m, c := SplitLast(m)
38+
if c.Value() != certhashes[1] {
39+
t.Fatal("unexpected value. Expected", c.RawValue(), "but got", []byte(certhashes[1]))
40+
}
41+
_, c = SplitLast(m)
42+
if c.Value() != certhashes[0] {
43+
t.Fatal("unexpected value. Expected", c.RawValue(), "but got", []byte(certhashes[0]))
44+
}
45+
}
46+
}
47+
48+
func TestCaptureAddrPort(t *testing.T) {
49+
m := StringCast("/ip4/1.2.3.4/udp/8231/quic-v1/webtransport")
50+
var addrPort netip.AddrPort
51+
var network string
52+
53+
found, err := m.Match(
54+
CaptureAddrPort(&network, &addrPort),
55+
meg.ZeroOrMore(meg.Any),
56+
)
57+
if err != nil {
58+
t.Fatal("error", err)
59+
}
60+
if !found {
61+
t.Fatal("failed to match")
62+
}
63+
if !addrPort.IsValid() {
64+
t.Fatal("failed to capture addrPort")
65+
}
66+
if network != "udp" {
67+
t.Fatal("unexpected network", network)
68+
}
69+
if addrPort.String() != "1.2.3.4:8231" {
70+
t.Fatal("unexpected ipPort", addrPort)
71+
}
72+
}

util.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package multiaddr
22

33
import (
44
"fmt"
5+
6+
"github.com/multiformats/go-multiaddr/x/meg"
57
)
68

79
// Split returns the sub-address portions of a multiaddr.
@@ -120,3 +122,17 @@ func ForEach(m Multiaddr, cb func(c Component) bool) {
120122
}
121123
}
122124
}
125+
126+
type componentList []Component
127+
128+
func (m componentList) Get(i int) meg.Matchable {
129+
return &m[i]
130+
}
131+
132+
func (m componentList) Len() int {
133+
return len(m)
134+
}
135+
func (m Multiaddr) Match(p ...meg.Pattern) (bool, error) {
136+
matcher := meg.PatternToMatcher(p...)
137+
return meg.Match(matcher, componentList(m))
138+
}

0 commit comments

Comments
 (0)