Skip to content

Megular Expressions #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions component.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ func (c *Component) Protocol() Protocol {
return *c.protocol
}

func (c *Component) Code() int {
if c == nil {
return 0
}
return c.Protocol().Code
}

func (c *Component) RawValue() []byte {
if c == nil {
return nil
Expand Down
54 changes: 54 additions & 0 deletions meg_capturers.go
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this?
I'd prefer a method in manet that returns (network string, ipPort netip.AddrPort)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This composes better. You can use this and capture other parts of the multiaddr in a single pass.

We don't need this, but I think it's helpful to demonstrate this 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
}
72 changes: 72 additions & 0 deletions meg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package multiaddr

import (
"net/netip"
"testing"

"github.com/multiformats/go-multiaddr/x/meg"
)

func TestMatchAndCaptureMultiaddr(t *testing.T) {
m := StringCast("/ip4/1.2.3.4/udp/8231/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41")

var udpPort string
var certhashes []string
found, _ := m.Match(
meg.Or(
meg.Val(P_IP4),
meg.Val(P_IP6),
),
meg.CaptureStringVal(P_UDP, &udpPort),
meg.Val(P_QUIC_V1),
meg.Val(P_WEBTRANSPORT),
meg.CaptureZeroOrMoreStrings(P_CERTHASH, &certhashes),
)
if !found {
t.Fatal("failed to match")
}
if udpPort != "8231" {
t.Fatal("unexpected value")
}

if len(certhashes) != 2 {
t.Fatal("Didn't capture all certhashes")
}

{
m, c := SplitLast(m)
if c.Value() != certhashes[1] {
t.Fatal("unexpected value. Expected", c.RawValue(), "but got", []byte(certhashes[1]))
}
_, c = SplitLast(m)
if c.Value() != certhashes[0] {
t.Fatal("unexpected value. Expected", c.RawValue(), "but got", []byte(certhashes[0]))
}
}
}

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)
}
}
16 changes: 16 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package multiaddr

import (
"fmt"

"github.com/multiformats/go-multiaddr/x/meg"
)

// Split returns the sub-address portions of a multiaddr.
Expand Down Expand Up @@ -120,3 +122,17 @@ func ForEach(m Multiaddr, cb func(c Component) bool) {
}
}
}

type componentList []Component

func (m componentList) Get(i int) meg.Matchable {
return &m[i]
}

func (m componentList) Len() int {
return len(m)
}
func (m Multiaddr) Match(p ...meg.Pattern) (bool, error) {
matcher := meg.PatternToMatcher(p...)
return meg.Match(matcher, componentList(m))
}
Loading