From e41d519be1b9956ee36a677db45e034607cee8e7 Mon Sep 17 00:00:00 2001 From: web3-bot Date: Sun, 16 Feb 2025 12:35:24 +0000 Subject: [PATCH 1/5] chore!: bump go.mod to Go 1.23 and run go fix BREAKING CHANGE: Updating the Go version to 1.23 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3a48057..f1457a0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/multiformats/go-multiaddr -go 1.22 +go 1.23 require ( github.com/ipfs/go-cid v0.0.7 From 2ac523b0a26d6ce052095d0b4dbda8e3df75ed3d Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 21 Feb 2025 14:35:50 -0800 Subject: [PATCH 2/5] refactor: keep same api as v0.14.0 for SplitFirst/SplitLast (#271) * refactor: keep same api for SplitFirst/SplitLast Not worth breaking this function, and it saves us some pain with updating downstream deps * add defensive copy --- component.go | 89 +++++++++++++++++++++++++++++++++++++---------- multiaddr.go | 19 +++++++--- multiaddr_test.go | 50 +++++++++++++++++++++++--- util.go | 23 +++++++----- util_test.go | 8 ++--- 5 files changed, 147 insertions(+), 42 deletions(-) diff --git a/component.go b/component.go index 39f5d5a..d25f06a 100644 --- a/component.go +++ b/component.go @@ -19,30 +19,39 @@ type Component struct { valueStartIdx int // Index of the first byte of the Component's value in the bytes array } -func (c Component) AsMultiaddr() Multiaddr { +func (c *Component) AsMultiaddr() Multiaddr { if c.Empty() { return nil } - return []Component{c} + return []Component{*c} } -func (c Component) Encapsulate(o Multiaddr) Multiaddr { +func (c *Component) Encapsulate(o Multiaddr) Multiaddr { return c.AsMultiaddr().Encapsulate(o) } -func (c Component) Decapsulate(o Multiaddr) Multiaddr { +func (c *Component) Decapsulate(o Multiaddr) Multiaddr { return c.AsMultiaddr().Decapsulate(o) } -func (c Component) Empty() bool { +func (c *Component) Empty() bool { + if c == nil { + return true + } return len(c.bytes) == 0 } -func (c Component) Bytes() []byte { +func (c *Component) Bytes() []byte { + if c == nil { + return nil + } return []byte(c.bytes) } -func (c Component) MarshalBinary() ([]byte, error) { +func (c *Component) MarshalBinary() ([]byte, error) { + if c == nil { + return nil, errNilPtr + } return c.Bytes(), nil } @@ -58,7 +67,10 @@ func (c *Component) UnmarshalBinary(data []byte) error { return nil } -func (c Component) MarshalText() ([]byte, error) { +func (c *Component) MarshalText() ([]byte, error) { + if c == nil { + return nil, errNilPtr + } return []byte(c.String()), nil } @@ -79,7 +91,10 @@ func (c *Component) UnmarshalText(data []byte) error { return nil } -func (c Component) MarshalJSON() ([]byte, error) { +func (c *Component) MarshalJSON() ([]byte, error) { + if c == nil { + return nil, errNilPtr + } txt, err := c.MarshalText() if err != nil { return nil, err @@ -101,22 +116,40 @@ func (c *Component) UnmarshalJSON(data []byte) error { return c.UnmarshalText([]byte(v)) } -func (c Component) Equal(o Component) bool { +func (c *Component) Equal(o *Component) bool { + if c == nil || o == nil { + return c == o + } return c.bytes == o.bytes } -func (c Component) Compare(o Component) int { +func (c *Component) Compare(o *Component) int { + if c == nil && o == nil { + return 0 + } + if c == nil { + return -1 + } + if o == nil { + return 1 + } return strings.Compare(c.bytes, o.bytes) } -func (c Component) Protocols() []Protocol { +func (c *Component) Protocols() []Protocol { + if c == nil { + return nil + } if c.protocol == nil { return nil } return []Protocol{*c.protocol} } -func (c Component) ValueForProtocol(code int) (string, error) { +func (c *Component) ValueForProtocol(code int) (string, error) { + if c == nil { + return "", fmt.Errorf("component is nil") + } if c.protocol == nil { return "", fmt.Errorf("component has nil protocol") } @@ -126,18 +159,27 @@ func (c Component) ValueForProtocol(code int) (string, error) { return c.Value(), nil } -func (c Component) Protocol() Protocol { +func (c *Component) Protocol() Protocol { + if c == nil { + return Protocol{} + } if c.protocol == nil { return Protocol{} } return *c.protocol } -func (c Component) RawValue() []byte { +func (c *Component) RawValue() []byte { + if c == nil { + return nil + } return []byte(c.bytes[c.valueStartIdx:]) } -func (c Component) Value() string { +func (c *Component) Value() string { + if c == nil { + return "" + } if c.Empty() { return "" } @@ -146,7 +188,10 @@ func (c Component) Value() string { return value } -func (c Component) valueAndErr() (string, error) { +func (c *Component) valueAndErr() (string, error) { + if c == nil { + return "", errNilPtr + } if c.protocol == nil { return "", fmt.Errorf("component has nil protocol") } @@ -160,7 +205,10 @@ func (c Component) valueAndErr() (string, error) { return value, nil } -func (c Component) String() string { +func (c *Component) String() string { + if c == nil { + return "" + } var b strings.Builder c.writeTo(&b) return b.String() @@ -168,7 +216,10 @@ func (c Component) String() string { // writeTo is an efficient, private function for string-formatting a multiaddr. // Trust me, we tend to allocate a lot when doing this. -func (c Component) writeTo(b *strings.Builder) { +func (c *Component) writeTo(b *strings.Builder) { + if c == nil { + return + } if c.protocol == nil { return } diff --git a/multiaddr.go b/multiaddr.go index 86092ae..3a8a03b 100644 --- a/multiaddr.go +++ b/multiaddr.go @@ -16,6 +16,15 @@ var errNilPtr = errors.New("nil ptr") // Multiaddr is the data structure representing a Multiaddr type Multiaddr []Component +func (m Multiaddr) copy() Multiaddr { + if m == nil { + return nil + } + out := make(Multiaddr, len(m)) + copy(out, m) + return out +} + func (m Multiaddr) Empty() bool { if len(m) == 0 { return true @@ -71,7 +80,7 @@ func (m Multiaddr) Equal(m2 Multiaddr) bool { return false } for i, c := range m { - if !c.Equal(m2[i]) { + if !c.Equal(&m2[i]) { return false } } @@ -80,7 +89,7 @@ func (m Multiaddr) Equal(m2 Multiaddr) bool { func (m Multiaddr) Compare(o Multiaddr) int { for i := 0; i < len(m) && i < len(o); i++ { - if cmp := m[i].Compare(o[i]); cmp != 0 { + if cmp := m[i].Compare(&o[i]); cmp != 0 { return cmp } } @@ -177,13 +186,13 @@ func (m Multiaddr) Encapsulate(o Multiaddr) Multiaddr { return Join(m, o) } -func (m Multiaddr) EncapsulateC(c Component) Multiaddr { +func (m Multiaddr) EncapsulateC(c *Component) Multiaddr { if c.Empty() { return m } out := make([]Component, 0, len(m)+1) out = append(out, m...) - out = append(out, c) + out = append(out, *c) return out } @@ -200,7 +209,7 @@ func (m Multiaddr) Decapsulate(rightParts Multiaddr) Multiaddr { break } - foundMatch = rightC.Equal(leftParts[i+j]) + foundMatch = rightC.Equal(&leftParts[i+j]) if !foundMatch { break } diff --git a/multiaddr_test.go b/multiaddr_test.go index 95b57f4..28b068f 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -28,6 +28,12 @@ func TestReturnsNilOnEmpty(t *testing.T) { a, _ = SplitLast(a) require.Nil(t, a) + a, c := SplitLast(nil) + require.Zero(t, len(a.Protocols())) + require.Nil(t, a) + require.Nil(t, c) + require.True(t, c.Empty()) + // Test that empty multiaddr from various operations returns nil a = StringCast("/ip4/1.2.3.4/tcp/1234") _, a = SplitFirst(a) @@ -36,6 +42,11 @@ func TestReturnsNilOnEmpty(t *testing.T) { _, a = SplitFirst(a) require.Nil(t, a) + c, a = SplitFirst(nil) + require.Nil(t, a) + require.Nil(t, c) + require.True(t, c.Empty()) + a = StringCast("/ip4/1.2.3.4/tcp/1234") a = a.Decapsulate(a) require.Nil(t, a) @@ -400,7 +411,7 @@ func TestBytesSplitAndJoin(t *testing.T) { for i, a := range split { if a.String() != res[i] { - t.Errorf("split component failed: %s != %s", a, res[i]) + t.Errorf("split component failed: %s != %s", &a, res[i]) } } @@ -411,7 +422,7 @@ func TestBytesSplitAndJoin(t *testing.T) { for i, a := range split { if a.String() != res[i] { - t.Errorf("split component failed: %s != %s", a, res[i]) + t.Errorf("split component failed: %s != %s", &a, res[i]) } } } @@ -863,7 +874,7 @@ func TestComponentBinaryMarshaler(t *testing.T) { if err = comp2.UnmarshalBinary(b); err != nil { t.Fatal(err) } - if !comp.Equal(comp2) { + if !comp.Equal(&comp2) { t.Error("expected equal components in circular marshaling test") } } @@ -882,7 +893,7 @@ func TestComponentTextMarshaler(t *testing.T) { if err = comp2.UnmarshalText(b); err != nil { t.Fatal(err) } - if !comp.Equal(comp2) { + if !comp.Equal(&comp2) { t.Error("expected equal components in circular marshaling test") } } @@ -901,7 +912,7 @@ func TestComponentJSONMarshaler(t *testing.T) { if err = comp2.UnmarshalJSON(b); err != nil { t.Fatal(err) } - if !comp.Equal(comp2) { + if !comp.Equal(&comp2) { t.Error("expected equal components in circular marshaling test") } } @@ -914,6 +925,9 @@ func TestUseNil(t *testing.T) { _ = f() var foo Multiaddr = nil + _, right := SplitFirst(foo) + right.Protocols() + foo.Protocols() foo.Bytes() foo.Compare(nil) foo.Decapsulate(nil) @@ -930,6 +944,32 @@ func TestUseNil(t *testing.T) { _, _ = foo.ValueForProtocol(0) } +func TestUseNilComponent(t *testing.T) { + var foo *Component + foo.AsMultiaddr() + foo.Encapsulate(nil) + foo.Decapsulate(nil) + foo.Empty() + foo.Bytes() + foo.MarshalBinary() + foo.MarshalJSON() + foo.MarshalText() + foo.UnmarshalBinary(nil) + foo.UnmarshalJSON(nil) + foo.UnmarshalText(nil) + foo.Equal(nil) + foo.Compare(nil) + foo.Protocols() + foo.ValueForProtocol(0) + foo.Protocol() + foo.RawValue() + foo.Value() + _ = foo.String() + + var m Multiaddr = nil + m.EncapsulateC(foo) +} + func TestFilterAddrs(t *testing.T) { bad := []Multiaddr{ newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), diff --git a/util.go b/util.go index 71fb9bf..038c143 100644 --- a/util.go +++ b/util.go @@ -64,26 +64,30 @@ func StringCast(s string) Multiaddr { } // SplitFirst returns the first component and the rest of the multiaddr. -func SplitFirst(m Multiaddr) (Component, Multiaddr) { +func SplitFirst(m Multiaddr) (*Component, Multiaddr) { if m.Empty() { - return Component{}, nil + return nil, nil } if len(m) == 1 { - return m[0], nil + return &m[0], nil } - return m[0], m[1:] + // defensive copy. Users can avoid by doing the split themselves. + copyC := m[0] + return ©C, m[1:].copy() } // SplitLast returns the rest of the multiaddr and the last component. -func SplitLast(m Multiaddr) (Multiaddr, Component) { +func SplitLast(m Multiaddr) (Multiaddr, *Component) { if m.Empty() { - return nil, Component{} + return nil, nil } if len(m) == 1 { // We want to explicitly return a nil slice if the prefix is now empty. - return nil, m[0] + return nil, &m[0] } - return m[:len(m)-1], m[len(m)-1] + // defensive copy. Users can avoid by doing the split themselves. + copyC := m[len(m)-1] + return m[:len(m)-1].copy(), ©C } // SplitFunc splits the multiaddr when the callback first returns true. The @@ -108,7 +112,8 @@ func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { if post.Empty() { post = nil } - return pre, post + // defensive copy. Users can avoid by doing the split themselves. + return pre.copy(), post.copy() } // ForEach walks over the multiaddr, component by component. diff --git a/util_test.go b/util_test.go index 3494486..1409122 100644 --- a/util_test.go +++ b/util_test.go @@ -66,15 +66,15 @@ func TestSplitFirstLast(t *testing.T) { } ci, m := SplitFirst(c.AsMultiaddr()) - if !ci.Equal(c) || m != nil { + if !ci.Equal(&c) || m != nil { t.Error("split first on component failed") } m, ci = SplitLast(c.AsMultiaddr()) - if !ci.Equal(c) || m != nil { + if !ci.Equal(&c) || m != nil { t.Error("split last on component failed") } cis := Split(c.AsMultiaddr()) - if len(cis) != 1 || !cis[0].Equal(c) { + if len(cis) != 1 || !cis[0].Equal(&c) { t.Error("split on component failed") } m1, m2 := SplitFunc(c.AsMultiaddr(), func(c Component) bool { @@ -96,7 +96,7 @@ func TestSplitFirstLast(t *testing.T) { t.Error("expected exactly one component") } i++ - if !ci.Equal(c) { + if !ci.Equal(&c) { t.Error("foreach on component failed") } return true From 4d1f3557e51fc0723cb338e2f621594d63b891a2 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 24 Feb 2025 12:04:23 -0800 Subject: [PATCH 3/5] refactor: Backwards compatible Encapsulate/Decapsulate/Join/NewComponent (#272) * Backwards compatible Encapsulate/Decapsulate/Join * remove EncapsulateC * feat: Add Multiaddr.AppendComponent * remove .Empty (#274) prefer the simpler `len` check for Multiaddr and `== nil` for `*Component`. * export AsMultiaddrer interface * rename AsMultiaddr to Multiaddr Export Multiaddrer interface --- codec.go | 33 +++++++++++------- component.go | 86 +++++++++++++++++++++++------------------------ multiaddr.go | 49 ++++++++++++++------------- multiaddr_test.go | 75 ++++++++++++++++++++++++++++++++--------- net/convert.go | 6 ++-- net/ip.go | 10 +++--- net/resolve.go | 2 +- util.go | 40 +++++++++------------- util_test.go | 42 +++++++++++------------ v015-MIGRATION.md | 6 ++-- 10 files changed, 197 insertions(+), 152 deletions(-) diff --git a/codec.go b/codec.go index bdbeba6..1374aff 100644 --- a/codec.go +++ b/codec.go @@ -67,31 +67,36 @@ func stringToBytes(s string) ([]byte, error) { return b.Bytes(), nil } -func readComponent(b []byte) (int, Component, error) { +func readComponent(b []byte) (int, *Component, error) { var offset int code, n, err := ReadVarintCode(b) if err != nil { - return 0, Component{}, err + return 0, nil, err } offset += n p := ProtocolWithCode(code) if p.Code == 0 { - return 0, Component{}, fmt.Errorf("no protocol with code %d", code) + return 0, nil, fmt.Errorf("no protocol with code %d", code) } pPtr := protocolPtrByCode[code] if pPtr == nil { - return 0, Component{}, fmt.Errorf("no protocol with code %d", code) + return 0, nil, fmt.Errorf("no protocol with code %d", code) } if p.Size == 0 { - c, err := validateComponent(Component{ + c := &Component{ bytes: string(b[:offset]), valueStartIdx: offset, protocol: pPtr, - }) + } + + err := validateComponent(c) + if err != nil { + return 0, nil, err + } - return offset, c, err + return offset, c, nil } var size int @@ -100,7 +105,7 @@ func readComponent(b []byte) (int, Component, error) { var n int size, n, err = ReadVarintCode(b[offset:]) if err != nil { - return 0, Component{}, err + return 0, nil, err } offset += n } else { @@ -109,14 +114,18 @@ func readComponent(b []byte) (int, Component, error) { } if len(b[offset:]) < size || size <= 0 { - return 0, Component{}, fmt.Errorf("invalid value for size %d", len(b[offset:])) + return 0, nil, fmt.Errorf("invalid value for size %d", len(b[offset:])) } - c, err := validateComponent(Component{ + c := &Component{ bytes: string(b[:offset+size]), protocol: pPtr, valueStartIdx: offset, - }) + } + err = validateComponent(c) + if err != nil { + return 0, nil, err + } return offset + size, c, err } @@ -142,7 +151,7 @@ func readMultiaddr(b []byte) (int, Multiaddr, error) { return bytesRead, nil, fmt.Errorf("unexpected component after path component") } sawPathComponent = c.protocol.Path - res = append(res, c) + res = append(res, *c) } return bytesRead, res, nil } diff --git a/component.go b/component.go index d25f06a..6e8a640 100644 --- a/component.go +++ b/component.go @@ -19,26 +19,19 @@ type Component struct { valueStartIdx int // Index of the first byte of the Component's value in the bytes array } -func (c *Component) AsMultiaddr() Multiaddr { - if c.Empty() { +func (c *Component) Multiaddr() Multiaddr { + if c == nil { return nil } return []Component{*c} } -func (c *Component) Encapsulate(o Multiaddr) Multiaddr { - return c.AsMultiaddr().Encapsulate(o) -} - -func (c *Component) Decapsulate(o Multiaddr) Multiaddr { - return c.AsMultiaddr().Decapsulate(o) +func (c *Component) Encapsulate(o Multiaddrer) Multiaddr { + return c.Multiaddr().Encapsulate(o) } -func (c *Component) Empty() bool { - if c == nil { - return true - } - return len(c.bytes) == 0 +func (c *Component) Decapsulate(o Multiaddrer) Multiaddr { + return c.Multiaddr().Decapsulate(o) } func (c *Component) Bytes() []byte { @@ -63,7 +56,7 @@ func (c *Component) UnmarshalBinary(data []byte) error { if err != nil { return err } - *c = comp + *c = *comp return nil } @@ -87,7 +80,7 @@ func (c *Component) UnmarshalText(data []byte) error { if err != nil { return err } - *c = comp + *c = *comp return nil } @@ -180,9 +173,6 @@ func (c *Component) Value() string { if c == nil { return "" } - if c.Empty() { - return "" - } // This Component MUST have been checked by validateComponent when created value, _ := c.valueAndErr() return value @@ -236,24 +226,24 @@ func (c *Component) writeTo(b *strings.Builder) { } // NewComponent constructs a new multiaddr component -func NewComponent(protocol, value string) (Component, error) { +func NewComponent(protocol, value string) (*Component, error) { p := ProtocolWithName(protocol) if p.Code == 0 { - return Component{}, fmt.Errorf("unsupported protocol: %s", protocol) + return nil, fmt.Errorf("unsupported protocol: %s", protocol) } if p.Transcoder != nil { bts, err := p.Transcoder.StringToBytes(value) if err != nil { - return Component{}, err + return nil, err } return newComponent(p, bts) } else if value != "" { - return Component{}, fmt.Errorf("protocol %s doesn't take a value", p.Name) + return nil, fmt.Errorf("protocol %s doesn't take a value", p.Name) } return newComponent(p, nil) } -func newComponent(protocol Protocol, bvalue []byte) (Component, error) { +func newComponent(protocol Protocol, bvalue []byte) (*Component, error) { protocolPtr := protocolPtrByCode[protocol.Code] if protocolPtr == nil { protocolPtr = &protocol @@ -274,71 +264,79 @@ func newComponent(protocol Protocol, bvalue []byte) (Component, error) { // Shouldn't happen if len(maddr) != offset+len(bvalue) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(maddr), offset+len(bvalue)) + return nil, fmt.Errorf("component size mismatch: %d != %d", len(maddr), offset+len(bvalue)) } - return validateComponent( - Component{ - bytes: string(maddr), - protocol: protocolPtr, - valueStartIdx: offset, - }) + c := &Component{ + bytes: string(maddr), + protocol: protocolPtr, + valueStartIdx: offset, + } + + err := validateComponent(c) + if err != nil { + return nil, err + } + return c, nil } // validateComponent MUST be called after creating a non-zero Component. // It ensures that we will be able to call all methods on Component without // error. -func validateComponent(c Component) (Component, error) { +func validateComponent(c *Component) error { + if c == nil { + return errNilPtr + } if c.protocol == nil { - return Component{}, fmt.Errorf("component is missing its protocol") + return fmt.Errorf("component is missing its protocol") } if c.valueStartIdx > len(c.bytes) { - return Component{}, fmt.Errorf("component valueStartIdx is greater than the length of the component's bytes") + return fmt.Errorf("component valueStartIdx is greater than the length of the component's bytes") } if len(c.protocol.VCode) == 0 { - return Component{}, fmt.Errorf("Component is missing its protocol's VCode field") + return fmt.Errorf("Component is missing its protocol's VCode field") } if len(c.bytes) < len(c.protocol.VCode) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.bytes), len(c.protocol.VCode)) + return fmt.Errorf("component size mismatch: %d != %d", len(c.bytes), len(c.protocol.VCode)) } if !bytes.Equal([]byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) { - return Component{}, fmt.Errorf("component's VCode field is invalid: %v != %v", []byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) + return fmt.Errorf("component's VCode field is invalid: %v != %v", []byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) } if c.protocol.Size < 0 { size, n, err := ReadVarintCode([]byte(c.bytes[len(c.protocol.VCode):])) if err != nil { - return Component{}, err + return err } if size != len(c.bytes[c.valueStartIdx:]) { - return Component{}, fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) + return fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) } if len(c.protocol.VCode)+n+size != len(c.bytes) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+n+size, len(c.bytes)) + return fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+n+size, len(c.bytes)) } } else { // Fixed size value size := c.protocol.Size / 8 if size != len(c.bytes[c.valueStartIdx:]) { - return Component{}, fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) + return fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:])) } if len(c.protocol.VCode)+size != len(c.bytes) { - return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+size, len(c.bytes)) + return fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+size, len(c.bytes)) } } _, err := c.valueAndErr() if err != nil { - return Component{}, err + return err } if c.protocol.Transcoder != nil { err = c.protocol.Transcoder.ValidateBytes([]byte(c.bytes[c.valueStartIdx:])) if err != nil { - return Component{}, err + return err } } - return c, nil + return nil } diff --git a/multiaddr.go b/multiaddr.go index 3a8a03b..2565f51 100644 --- a/multiaddr.go +++ b/multiaddr.go @@ -25,18 +25,6 @@ func (m Multiaddr) copy() Multiaddr { return out } -func (m Multiaddr) Empty() bool { - if len(m) == 0 { - return true - } - for _, c := range m { - if !c.Empty() { - return false - } - } - return true -} - // NewMultiaddr parses and validates an input string, returning a *Multiaddr func NewMultiaddr(s string) (a Multiaddr, err error) { defer func() { @@ -181,23 +169,38 @@ func (m Multiaddr) Protocols() []Protocol { return out } -// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr -func (m Multiaddr) Encapsulate(o Multiaddr) Multiaddr { - return Join(m, o) +type Multiaddrer interface { + // Multiaddr returns the Multiaddr representation + Multiaddr() Multiaddr } -func (m Multiaddr) EncapsulateC(c *Component) Multiaddr { - if c.Empty() { - return m +func (m Multiaddr) Multiaddr() Multiaddr { + return m +} + +// AppendComponent is the same as using `append(m, *c)`, but with a safety check +// for a nil Component. +func (m Multiaddr) AppendComponent(cs ...*Component) Multiaddr { + for _, c := range cs { + if c == nil { + continue + } + m = append(m, *c) } - out := make([]Component, 0, len(m)+1) - out = append(out, m...) - out = append(out, *c) - return out + return m +} + +// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr +func (m Multiaddr) Encapsulate(other Multiaddrer) Multiaddr { + return Join(m, other) } // Decapsulate unwraps Multiaddr up until the given Multiaddr is found. -func (m Multiaddr) Decapsulate(rightParts Multiaddr) Multiaddr { +func (m Multiaddr) Decapsulate(rightPartsAny Multiaddrer) Multiaddr { + if rightPartsAny == nil { + return m + } + rightParts := rightPartsAny.Multiaddr() leftParts := m lastIndex := -1 diff --git a/multiaddr_test.go b/multiaddr_test.go index 28b068f..54fdc4c 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -32,7 +32,6 @@ func TestReturnsNilOnEmpty(t *testing.T) { require.Zero(t, len(a.Protocols())) require.Nil(t, a) require.Nil(t, c) - require.True(t, c.Empty()) // Test that empty multiaddr from various operations returns nil a = StringCast("/ip4/1.2.3.4/tcp/1234") @@ -45,7 +44,6 @@ func TestReturnsNilOnEmpty(t *testing.T) { c, a = SplitFirst(nil) require.Nil(t, a) require.Nil(t, c) - require.True(t, c.Empty()) a = StringCast("/ip4/1.2.3.4/tcp/1234") a = a.Decapsulate(a) @@ -72,13 +70,24 @@ func TestReturnsNilOnEmpty(t *testing.T) { _, err := NewMultiaddr("") require.Error(t, err) - a = JoinComponents() + var nilMultiaddr Multiaddr + a = nilMultiaddr.AppendComponent() require.Nil(t, a) a = Join() require.Nil(t, a) } +func TestJoinWithComponents(t *testing.T) { + var m Multiaddr + c, err := NewComponent("ip4", "127.0.0.1") + require.NoError(t, err) + + expected := "/ip4/127.0.0.1" + require.Equal(t, expected, Join(m, c).String()) + +} + func TestConstructFails(t *testing.T) { cases := []string{ "/ip4", @@ -313,7 +322,7 @@ func TestNilInterface(t *testing.T) { // Test components c, _ := SplitFirst(m1) - c.AsMultiaddr().Equal(m2) + c.Multiaddr().Equal(m2) c.Encapsulate(m2) c.Decapsulate(m2) @@ -415,7 +424,7 @@ func TestBytesSplitAndJoin(t *testing.T) { } } - joined := JoinComponents(split...) + joined := append(Multiaddr{}, split...) if !m.Equal(joined) { t.Errorf("joined components failed: %s != %s", m, joined) } @@ -520,6 +529,19 @@ func TestEncapsulate(t *testing.T) { if d != nil { t.Error("decapsulate /ip4 failed: ", d) } + + t.Run("Encapsulating with components", func(t *testing.T) { + left, last := SplitLast(m) + joined := left.Encapsulate(last) + require.True(t, joined.Equal(m)) + + first, rest := SplitFirst(m) + joined = first.Encapsulate(rest) + require.True(t, joined.Equal(m)) + // Component type + joined = (*first).Encapsulate(rest) + require.True(t, joined.Equal(m)) + }) } func TestDecapsulateComment(t *testing.T) { @@ -580,6 +602,16 @@ func TestDecapsulate(t *testing.T) { require.Equal(t, expected, actual) }) } + + for _, tc := range testcases { + t.Run("Decapsulating with components"+tc.left, func(t *testing.T) { + left, last := SplitLast(StringCast(tc.left)) + butLast := left.Decapsulate(last) + require.Equal(t, butLast.String(), left.String()) + // Round trip + require.Equal(t, tc.left, butLast.Encapsulate(last).String()) + }) + } } func assertValueForProto(t *testing.T, a Multiaddr, p int, exp string) { @@ -594,6 +626,17 @@ func assertValueForProto(t *testing.T, a Multiaddr, p int, exp string) { } } +func TestAppendComponent(t *testing.T) { + var m Multiaddr + res := m.AppendComponent(nil) + require.Equal(t, m, res) + + c, err := NewComponent("ip4", "127.0.0.1") + require.NoError(t, err) + res = m.AppendComponent(c) + require.Equal(t, "/ip4/127.0.0.1", res.String()) +} + func TestGetValue(t *testing.T) { a := newMultiaddr(t, "/ip4/127.0.0.1/utp/tcp/5555/udp/1234/tls/utp/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") assertValueForProto(t, a, P_IP4, "127.0.0.1") @@ -870,7 +913,7 @@ func TestComponentBinaryMarshaler(t *testing.T) { t.Fatal(err) } - comp2 := Component{} + var comp2 Component if err = comp2.UnmarshalBinary(b); err != nil { t.Fatal(err) } @@ -889,7 +932,7 @@ func TestComponentTextMarshaler(t *testing.T) { t.Fatal(err) } - comp2 := Component{} + var comp2 Component if err = comp2.UnmarshalText(b); err != nil { t.Fatal(err) } @@ -908,7 +951,7 @@ func TestComponentJSONMarshaler(t *testing.T) { t.Fatal(err) } - comp2 := Component{} + var comp2 Component if err = comp2.UnmarshalJSON(b); err != nil { t.Fatal(err) } @@ -946,10 +989,10 @@ func TestUseNil(t *testing.T) { func TestUseNilComponent(t *testing.T) { var foo *Component - foo.AsMultiaddr() + foo.Multiaddr() foo.Encapsulate(nil) foo.Decapsulate(nil) - foo.Empty() + require.True(t, foo == nil) foo.Bytes() foo.MarshalBinary() foo.MarshalJSON() @@ -967,7 +1010,7 @@ func TestUseNilComponent(t *testing.T) { _ = foo.String() var m Multiaddr = nil - m.EncapsulateC(foo) + m.Encapsulate(foo) } func TestFilterAddrs(t *testing.T) { @@ -1124,12 +1167,12 @@ func FuzzSplitRoundtrip(f *testing.F) { // Test SplitFirst first, rest := SplitFirst(addr) - joined := Join(first.AsMultiaddr(), rest) + joined := Join(first, rest) require.True(t, addr.Equal(joined), "SplitFirst and Join should round-trip") // Test SplitLast rest, last := SplitLast(addr) - joined = Join(rest, last.AsMultiaddr()) + joined = Join(rest, last) require.True(t, addr.Equal(joined), "SplitLast and Join should round-trip") p := addr.Protocols() @@ -1155,12 +1198,12 @@ func FuzzSplitRoundtrip(f *testing.F) { return c.Protocol().Code == proto.Code } beforeC, after := SplitFirst(addr) - joined = Join(beforeC.AsMultiaddr(), after) + joined = Join(beforeC, after) require.True(t, addr.Equal(joined)) tryPubMethods(after) before, afterC := SplitLast(addr) - joined = Join(before, afterC.AsMultiaddr()) + joined = Join(before, afterC) require.True(t, addr.Equal(joined)) tryPubMethods(before) @@ -1180,7 +1223,7 @@ func BenchmarkComponentValidation(b *testing.B) { } b.ReportAllocs() for i := 0; i < b.N; i++ { - _, err := validateComponent(comp) + err := validateComponent(comp) if err != nil { b.Fatal(err) } diff --git a/net/convert.go b/net/convert.go index be320d3..8056451 100644 --- a/net/convert.go +++ b/net/convert.go @@ -108,13 +108,13 @@ func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) { if err != nil { return nil, err } - return c.AsMultiaddr(), nil + return c.Multiaddr(), nil case ip.To16() != nil: ip6C, err := ma.NewComponent("ip6", ip.String()) if err != nil { return nil, err } - ip6 := ip6C.AsMultiaddr() + ip6 := ip6C.Multiaddr() if zone == "" { return ip6, nil } else { @@ -362,5 +362,5 @@ func parseUnixNetAddr(a net.Addr) (ma.Multiaddr, error) { if err != nil { return nil, err } - return c.AsMultiaddr(), nil + return c.Multiaddr(), nil } diff --git a/net/ip.go b/net/ip.go index e8acecb..def9321 100644 --- a/net/ip.go +++ b/net/ip.go @@ -64,7 +64,7 @@ func IsIPLoopback(m ma.Multiaddr) bool { return false } c, _ := ma.SplitFirst(m) - if c.Empty() { + if c == nil { return false } switch c.Protocol().Code { @@ -83,7 +83,7 @@ func IsIP6LinkLocal(m ma.Multiaddr) bool { return false } c, _ := ma.SplitFirst(m) - if c.Empty() || c.Protocol().Code != ma.P_IP6 { + if c == nil || c.Protocol().Code != ma.P_IP6 { return false } ip := net.IP(c.RawValue()) @@ -106,11 +106,11 @@ func IsIPUnspecified(m ma.Multiaddr) bool { // else return m func zoneless(m ma.Multiaddr) ma.Multiaddr { head, tail := ma.SplitFirst(m) - if head.Empty() { + if head == nil { return nil } if head.Protocol().Code == ma.P_IP6ZONE { - if tail.Empty() { + if tail == nil { return nil } tailhead, _ := ma.SplitFirst(tail) @@ -127,6 +127,6 @@ func zoneless(m ma.Multiaddr) ma.Multiaddr { // used for NAT64 Translation. See RFC 6052 func IsNAT64IPv4ConvertedIPv6Addr(addr ma.Multiaddr) bool { c, _ := ma.SplitFirst(addr) - return !c.Empty() && c.Protocol().Code == ma.P_IP6 && + return c != nil && c.Protocol().Code == ma.P_IP6 && inAddrRange(c.RawValue(), nat64) } diff --git a/net/resolve.go b/net/resolve.go index 0b43d08..cd98fd8 100644 --- a/net/resolve.go +++ b/net/resolve.go @@ -14,7 +14,7 @@ func ResolveUnspecifiedAddress(resolve ma.Multiaddr, ifaceAddrs []ma.Multiaddr) first, rest := ma.SplitFirst(resolve) // if first component (ip) is not unspecified, use it as is. - if !IsIPUnspecified(first.AsMultiaddr()) { + if !IsIPUnspecified(first.Multiaddr()) { return []ma.Multiaddr{resolve}, nil } diff --git a/util.go b/util.go index 038c143..d063e39 100644 --- a/util.go +++ b/util.go @@ -9,23 +9,17 @@ func Split(m Multiaddr) []Component { return m } -func JoinComponents(cs ...Component) Multiaddr { - if len(cs) == 0 { - return nil - } - out := make([]Component, 0, len(cs)) - for _, c := range cs { - if !c.Empty() { - out = append(out, c) - } - } - return out -} - // Join returns a combination of addresses. // Note: This copies all the components from the input Multiaddrs. Depending on // your use case, you may prefer to use `append(leftMA, rightMA...)` instead. -func Join(ms ...Multiaddr) Multiaddr { +func Join(msInterfaces ...Multiaddrer) Multiaddr { + ms := make([]Multiaddr, len(msInterfaces)) + for i, m := range msInterfaces { + if m == nil { + continue + } + ms[i] = m.Multiaddr() + } size := 0 for _, m := range ms { size += len(m) @@ -36,11 +30,7 @@ func Join(ms ...Multiaddr) Multiaddr { out := make([]Component, 0, size) for _, m := range ms { - for _, c := range m { - if !c.Empty() { - out = append(out, c) - } - } + out = append(out, m...) } return out } @@ -65,7 +55,7 @@ func StringCast(s string) Multiaddr { // SplitFirst returns the first component and the rest of the multiaddr. func SplitFirst(m Multiaddr) (*Component, Multiaddr) { - if m.Empty() { + if len(m) == 0 { return nil, nil } if len(m) == 1 { @@ -78,7 +68,7 @@ func SplitFirst(m Multiaddr) (*Component, Multiaddr) { // SplitLast returns the rest of the multiaddr and the last component. func SplitLast(m Multiaddr) (Multiaddr, *Component) { - if m.Empty() { + if len(m) == 0 { return nil, nil } if len(m) == 1 { @@ -94,7 +84,7 @@ func SplitLast(m Multiaddr) (Multiaddr, *Component) { // component on which the callback first returns will be included in the // *second* multiaddr. func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { - if m.Empty() { + if len(m) == 0 { return nil, nil } @@ -106,10 +96,10 @@ func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { } } pre, post := m[:idx], m[idx:] - if pre.Empty() { + if len(pre) == 0 { pre = nil } - if post.Empty() { + if len(post) == 0 { post = nil } // defensive copy. Users can avoid by doing the split themselves. @@ -121,7 +111,7 @@ func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { // This function iterates over components. // Return true to continue iteration, false to stop. // -// Prefer to use a standard for range loop instead +// Prefer a standard for range loop instead // e.g. `for _, c := range m { ... }` func ForEach(m Multiaddr, cb func(c Component) bool) { for _, c := range m { diff --git a/util_test.go b/util_test.go index 1409122..365ba41 100644 --- a/util_test.go +++ b/util_test.go @@ -21,8 +21,8 @@ func TestSplitFirstLast(t *testing.T) { head, tail := SplitFirst(addr) rest, last := SplitLast(addr) if len(x) == 0 { - if !head.Empty() { - t.Error("expected head to be empty") + if head != nil { + t.Error("expected head to be nil") } if tail != nil { t.Error("expected tail to be nil") @@ -30,15 +30,15 @@ func TestSplitFirstLast(t *testing.T) { if rest != nil { t.Error("expected rest to be nil") } - if !last.Empty() { - t.Error("expected last to be empty") + if last != nil { + t.Error("expected last to be nil") } continue } - if !head.AsMultiaddr().Equal(StringCast(x[0])) { + if !head.Multiaddr().Equal(StringCast(x[0])) { t.Errorf("expected %s to be %s", head, x[0]) } - if !last.AsMultiaddr().Equal(StringCast(x[len(x)-1])) { + if !last.Multiaddr().Equal(StringCast(x[len(x)-1])) { t.Errorf("expected %s to be %s", head, x[len(x)-1]) } if len(x) == 1 { @@ -65,38 +65,38 @@ func TestSplitFirstLast(t *testing.T) { t.Fatal(err) } - ci, m := SplitFirst(c.AsMultiaddr()) - if !ci.Equal(&c) || m != nil { + ci, m := SplitFirst(c.Multiaddr()) + if !ci.Equal(c) || m != nil { t.Error("split first on component failed") } - m, ci = SplitLast(c.AsMultiaddr()) - if !ci.Equal(&c) || m != nil { + m, ci = SplitLast(c.Multiaddr()) + if !ci.Equal(c) || m != nil { t.Error("split last on component failed") } - cis := Split(c.AsMultiaddr()) - if len(cis) != 1 || !cis[0].Equal(&c) { + cis := Split(c.Multiaddr()) + if len(cis) != 1 || !cis[0].Equal(c) { t.Error("split on component failed") } - m1, m2 := SplitFunc(c.AsMultiaddr(), func(c Component) bool { + m1, m2 := SplitFunc(c.Multiaddr(), func(c Component) bool { return true }) - if m1 != nil || !m2.Equal(c.AsMultiaddr()) { + if m1 != nil || !m2.Equal(c.Multiaddr()) { t.Error("split func(true) on component failed") } - m1, m2 = SplitFunc(c.AsMultiaddr(), func(c Component) bool { + m1, m2 = SplitFunc(c.Multiaddr(), func(c Component) bool { return false }) - if !m1.Equal(c.AsMultiaddr()) || m2 != nil { + if !m1.Equal(c.Multiaddr()) || m2 != nil { t.Error("split func(false) on component failed") } i := 0 - ForEach(c.AsMultiaddr(), func(ci Component) bool { + ForEach(c.Multiaddr(), func(ci Component) bool { if i != 0 { t.Error("expected exactly one component") } i++ - if !ci.Equal(&c) { + if !ci.Equal(c) { t.Error("foreach on component failed") } return true @@ -119,10 +119,10 @@ func TestSplitFunc(t *testing.T) { for i, cs := range x { target := StringCast(cs) a, b := SplitFunc(addr, func(c Component) bool { - return c.AsMultiaddr().Equal(target) + return c.Multiaddr().Equal(target) }) if i == 0 { - if !a.Empty() { + if a != nil { t.Error("expected nil addr") } } else { @@ -135,7 +135,7 @@ func TestSplitFunc(t *testing.T) { } } a, b := SplitFunc(addr, func(_ Component) bool { return false }) - if !a.Equal(addr) || !b.Empty() { + if !a.Equal(addr) || b != nil { t.Error("should not have split") } } diff --git a/v015-MIGRATION.md b/v015-MIGRATION.md index ab75a39..3af7180 100644 --- a/v015-MIGRATION.md +++ b/v015-MIGRATION.md @@ -2,7 +2,7 @@ - There is no `Multiaddr` interface type. - Multiaddr is now a concrete type. Not an interface. -- Empty Multiaddrs/ should be checked with `.Empty()`, not `== nil`. This is similar to how slices should be checked with `len(s) == 0` rather than `s == nil`. +- Empty Multiaddrs/Component should generally be checked with `.Empty()`, not `== nil`. This is similar to how slices should be checked with `len(s) == 0` rather than `s == nil`. - Components do not implement `Multiaddr` as there is no `Multiaddr` to implement. - `Multiaddr` can no longer be a key in a Map. If you want unique Multiaddrs, use `Multiaddr.String()` as the key, otherwise you can use the pointer value `*Multiaddr`. @@ -12,4 +12,6 @@ ## Migration tips for v0.15 -- If trying to encapsulate a Component to a Multiaddr, use `m.encapsulateC(c)`, instead of the old form of `m.Encapsulate(c)`. `Encapsulate` now only accepts a `Multiaddr`. `EncapsulateC` accepts a `Component`. +- If your use case supports it, prefer `append` to append a Component to a + Multiaddr rather than using `Encapsulate`. It's much faster as it does not do + a defensive copy. From 7ca597e9dbcd8409646e959996e759c14cc26cad Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Feb 2025 14:15:27 -0800 Subject: [PATCH 4/5] Add backwards compatibility test --- compattest/README.md | 10 ++ compattest/backwards_compat_test.go | 140 ++++++++++++++++++++++++++++ compattest/go.mod | 29 ++++++ compattest/go.sum | 52 +++++++++++ 4 files changed, 231 insertions(+) create mode 100644 compattest/README.md create mode 100644 compattest/backwards_compat_test.go create mode 100644 compattest/go.mod create mode 100644 compattest/go.sum diff --git a/compattest/README.md b/compattest/README.md new file mode 100644 index 0000000..09825c8 --- /dev/null +++ b/compattest/README.md @@ -0,0 +1,10 @@ +# Compatibility Test + +This package is used to test the backwards compatibility of the `go-multiaddr` +package against a previous version. + +To update the previous version, from the root of the repo: +```sh +git subtree pull --prefix=compattest/internal/prev https://github.com/multiformats/go-multiaddr.git --squash +``` + diff --git a/compattest/backwards_compat_test.go b/compattest/backwards_compat_test.go new file mode 100644 index 0000000..7e71b76 --- /dev/null +++ b/compattest/backwards_compat_test.go @@ -0,0 +1,140 @@ +package multiaddr + +import ( + "bytes" + "testing" + + multiaddr "github.com/multiformats/go-multiaddr" + prevmultiaddr "github.com/multiformats/go-multiaddr/compattest/internal/prev" +) + +func FuzzBackwardsCompat(f *testing.F) { + for _, v := range good { + f.Add(v) + } + + f.Fuzz(func(t *testing.T, addrStr string) { + oldAddr, err := prevmultiaddr.NewMultiaddr(addrStr) + if err != nil { + _, err := multiaddr.NewMultiaddr(addrStr) + if err == nil { + t.Fatalf("NewMultiaddr(%s) should fail", addrStr) + } + t.Skip() // Skip inputs that are not valid multiaddrs + } + addr, err := multiaddr.NewMultiaddr(addrStr) + if err != nil { + t.Fatalf("NewMultiaddr(%s) failed: %s", addrStr, err) + } + oldBytes := oldAddr.Bytes() + newBytes := addr.Bytes() + if !bytes.Equal(oldBytes, newBytes) { + t.Fatalf("NewMultiaddr(%s) returned different bytes than ma14.NewMultiaddr(%s)", addrStr, addrStr) + } + + addr, err = multiaddr.NewMultiaddrBytes(oldBytes) + if err != nil { + t.Fatalf("NewMultiaddrBytes(%s) failed: %s", oldBytes, err) + } + newBytes = addr.Bytes() + if !bytes.Equal(oldBytes, newBytes) { + t.Fatalf("NewMultiaddrBytes(%s) returned different bytes than NewMultiaddr(%s)", oldBytes, addrStr) + } + }) +} + +var good = []string{ + "/ip4/1.2.3.4", + "/ip4/0.0.0.0", + "/ip4/192.0.2.0/ipcidr/24", + "/ip6/::1", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic-v1", + "/ip6/2001:db8::/ipcidr/32", + "/ip6zone/x/ip6/fe80::1", + "/ip6zone/x%y/ip6/fe80::1", + "/ip6zone/x%y/ip6/::", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic-v1", + "/onion/timaq4ygg2iegci7:1234", + "/onion/timaq4ygg2iegci7:80/http", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/udp/8080", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwas", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwassw", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/http", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/udp/8080", + "/udp/0", + "/tcp/0", + "/sctp/0", + "/udp/1234", + "/tcp/1234", + "/sctp/1234", + "/udp/65535", + "/tcp/65535", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/bafzbeigvf25ytwc3akrijfecaotc74udrhcxzh2cx3we5qqnw5vgrei4bm", + "/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV", + "/p2p/k51qzi5uqu5dhb6l8spkdx7yxafegfkee5by8h7lmjh2ehc2sgg34z7c15vzqs", + "/p2p/bafzaajaiaejcalj543iwv2d7pkjt7ykvefrkfu7qjfi6sduakhso4lay6abn2d5u", + "/udp/1234/sctp/1234", + "/udp/1234/udt", + "/udp/1234/utp", + "/tcp/1234/http", + "/tcp/1234/tls/http", + "/tcp/1234/https", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/udp/1234", + "/ip4/127.0.0.1/udp/0", + "/ip4/127.0.0.1/tcp/1234", + "/ip4/127.0.0.1/tcp/1234/", + "/ip4/127.0.0.1/udp/1234/quic", + "/ip4/127.0.0.1/udp/1234/quic-v1", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/unix/a/b/c/d/e", + "/unix/stdio", + "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/tls", + "/ip4/127.0.0.1/tcp/127/tls/ws", + "/ip4/127.0.0.1/tcp/127/noise", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/webrtc-direct", + "/ip4/127.0.0.1/tcp/127/webrtc", + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + "/ip4/127.0.0.1/tcp/0/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV/http-path/foo", + "/ip4/127.0.0.1/tcp/443/tls/sni/example.com/http/http-path/foo", + "/memory/4", +} diff --git a/compattest/go.mod b/compattest/go.mod new file mode 100644 index 0000000..000e61a --- /dev/null +++ b/compattest/go.mod @@ -0,0 +1,29 @@ +module github.com/multiformats/go-multiaddr/compattest + +go 1.23.4 + +replace github.com/multiformats/go-multiaddr => ../ + +replace github.com/multiformats/go-multiaddr/compattest/internal/prev => ./internal/prev + +require ( + github.com/multiformats/go-multiaddr v0.0.0-00010101000000-000000000000 + github.com/multiformats/go-multiaddr/compattest/internal/prev v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/ipfs/go-cid v0.0.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20230725012225-302865e7556b // indirect + golang.org/x/sys v0.28.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect +) diff --git a/compattest/go.sum b/compattest/go.sum new file mode 100644 index 0000000..659d3e4 --- /dev/null +++ b/compattest/go.sum @@ -0,0 +1,52 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b h1:tK7yjGqVRzYdXsBcfD2MLhFAhHfDgGLm2rY1ub7FA9k= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= From 014cd90a97235eee3cf53f336f08d23d5bcc8b4e Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 24 Feb 2025 12:07:51 -0800 Subject: [PATCH 5/5] Squashed 'compattest/internal/prev/' content from commit 37363a0 git-subtree-dir: compattest/internal/prev git-subtree-split: 37363a08950edeb2dbd2ff8448e26048c84e59a0 --- .github/workflows/go-check.yml | 18 + .github/workflows/go-test.yml | 20 + .github/workflows/release-check.yml | 19 + .github/workflows/releaser.yml | 17 + .github/workflows/tagpush.yml | 18 + .gitignore | 3 + LICENSE | 21 + README.md | 117 ++ codec.go | 181 +++ codecov.yml | 2 + component.go | 186 +++ doc.go | 35 + filter.go | 145 +++ filter_test.go | 202 ++++ go.mod | 27 + go.sum | 56 + interface.go | 63 + multiaddr.go | 261 ++++ multiaddr/main.go | 93 ++ multiaddr_test.go | 1063 +++++++++++++++++ net/convert.go | 358 ++++++ net/convert_test.go | 266 +++++ net/doc.go | 5 + net/ip.go | 132 ++ net/ip_test.go | 54 + net/net.go | 430 +++++++ net/net_test.go | 693 +++++++++++ net/private.go | 208 ++++ net/private_test.go | 84 ++ net/registry.go | 97 ++ net/registry_test.go | 42 + net/resolve.go | 87 ++ net/resolve_test.go | 60 + package.json | 23 + protocol.go | 102 ++ protocols.go | 344 ++++++ .../FuzzNewMultiaddrBytes/0487b63847656fd4 | 2 + .../FuzzNewMultiaddrBytes/04a87ae2740f7195 | 2 + .../FuzzNewMultiaddrBytes/239d3594e0ee93bb | 2 + .../FuzzNewMultiaddrBytes/2ef0d600700564d4 | 2 + .../FuzzNewMultiaddrBytes/385d14fbb016b8c3 | 2 + .../FuzzNewMultiaddrBytes/511b72740453a863 | 2 + .../FuzzNewMultiaddrBytes/69ba454c4217999e | 2 + .../FuzzNewMultiaddrBytes/9f0d778549d2b28e | 2 + .../FuzzNewMultiaddrBytes/af9576bc28339a8d | 2 + .../FuzzNewMultiaddrBytes/e9317f1a3c43de50 | 2 + .../FuzzNewMultiaddrBytes/f1ebd17c93085805 | 2 + .../FuzzNewMultiaddrString/382a5bb1eff47833 | 2 + .../FuzzNewMultiaddrString/53eb3b6be337b1d7 | 2 + .../FuzzNewMultiaddrString/63891c9534054d61 | 2 + .../FuzzNewMultiaddrString/95a479f85dc92117 | 2 + .../FuzzNewMultiaddrString/9dba3b166a74fc47 | 2 + .../FuzzNewMultiaddrString/a2b937de623ded67 | 2 + .../FuzzNewMultiaddrString/bc05ef53a41e422a | 2 + .../FuzzNewMultiaddrString/d857c283ff1b2f2a | 2 + .../FuzzNewMultiaddrString/dffb2baac63c66ae | 2 + transcoders.go | 520 ++++++++ util.go | 204 ++++ util_test.go | 142 +++ varint.go | 27 + version.json | 3 + 61 files changed, 6468 insertions(+) create mode 100644 .github/workflows/go-check.yml create mode 100644 .github/workflows/go-test.yml create mode 100644 .github/workflows/release-check.yml create mode 100644 .github/workflows/releaser.yml create mode 100644 .github/workflows/tagpush.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 codec.go create mode 100644 codecov.yml create mode 100644 component.go create mode 100644 doc.go create mode 100644 filter.go create mode 100644 filter_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 interface.go create mode 100644 multiaddr.go create mode 100644 multiaddr/main.go create mode 100644 multiaddr_test.go create mode 100644 net/convert.go create mode 100644 net/convert_test.go create mode 100644 net/doc.go create mode 100644 net/ip.go create mode 100644 net/ip_test.go create mode 100644 net/net.go create mode 100644 net/net_test.go create mode 100644 net/private.go create mode 100644 net/private_test.go create mode 100644 net/registry.go create mode 100644 net/registry_test.go create mode 100644 net/resolve.go create mode 100644 net/resolve_test.go create mode 100644 package.json create mode 100644 protocol.go create mode 100644 protocols.go create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 create mode 100644 testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a create mode 100644 testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae create mode 100644 transcoders.go create mode 100644 util.go create mode 100644 util_test.go create mode 100644 varint.go create mode 100644 version.json diff --git a/.github/workflows/go-check.yml b/.github/workflows/go-check.yml new file mode 100644 index 0000000..26f63bc --- /dev/null +++ b/.github/workflows/go-check.yml @@ -0,0 +1,18 @@ +name: Go Checks + +on: + pull_request: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} + cancel-in-progress: true + +jobs: + go-check: + uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0 diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000..778de6e --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,20 @@ +name: Go Test + +on: + pull_request: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} + cancel-in-progress: true + +jobs: + go-test: + uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0 + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml new file mode 100644 index 0000000..0b5ff60 --- /dev/null +++ b/.github/workflows/release-check.yml @@ -0,0 +1,19 @@ +name: Release Checker + +on: + pull_request_target: + paths: [ 'version.json' ] + types: [ opened, synchronize, reopened, labeled, unlabeled ] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-check: + uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0 diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml new file mode 100644 index 0000000..2ebdbed --- /dev/null +++ b/.github/workflows/releaser.yml @@ -0,0 +1,17 @@ +name: Releaser + +on: + push: + paths: [ 'version.json' ] + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.sha }} + cancel-in-progress: true + +jobs: + releaser: + uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0 diff --git a/.github/workflows/tagpush.yml b/.github/workflows/tagpush.yml new file mode 100644 index 0000000..5ef3fb9 --- /dev/null +++ b/.github/workflows/tagpush.yml @@ -0,0 +1,18 @@ +name: Tag Push Checker + +on: + push: + tags: + - v* + +permissions: + contents: read + issues: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + releaser: + uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..699d271 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +multiaddr/multiaddr +tmp/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c7386b3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Juan Batiz-Benet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..df2766a --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# go-multiaddr + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) +[![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) +[![GoDoc](https://godoc.org/github.com/multiformats/go-multiaddr?status.svg)](https://godoc.org/github.com/multiformats/go-multiaddr) +[![Travis CI](https://img.shields.io/travis/multiformats/go-multiaddr.svg?style=flat-square&branch=master)](https://travis-ci.org/multiformats/go-multiaddr) +[![codecov.io](https://img.shields.io/codecov/c/github/multiformats/go-multiaddr.svg?style=flat-square&branch=master)](https://codecov.io/github/multiformats/go-multiaddr?branch=master) + +> [multiaddr](https://github.com/multiformats/multiaddr) implementation in go + +Multiaddr is a standard way to represent addresses that: + +- Support any standard network protocols. +- Self-describe (include protocols). +- Have a binary packed format. +- Have a nice string representation. +- Encapsulate well. + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) + - [Example](#example) + - [Simple](#simple) + - [Protocols](#protocols) + - [En/decapsulate](#endecapsulate) + - [Tunneling](#tunneling) +- [Maintainers](#maintainers) +- [Contribute](#contribute) +- [License](#license) + +## Install + +```sh +go get github.com/multiformats/go-multiaddr +``` + +## Usage + +### Example + +#### Simple + +```go +import ma "github.com/multiformats/go-multiaddr" + +// construct from a string (err signals parse failure) +m1, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") + +// construct from bytes (err signals parse failure) +m2, err := ma.NewMultiaddrBytes(m1.Bytes()) + +// true +strings.Equal(m1.String(), "/ip4/127.0.0.1/udp/1234") +strings.Equal(m1.String(), m2.String()) +bytes.Equal(m1.Bytes(), m2.Bytes()) +m1.Equal(m2) +m2.Equal(m1) +``` + +#### Protocols + +```go +// get the multiaddr protocol description objects +m1.Protocols() +// []Protocol{ +// Protocol{ Code: 4, Name: 'ip4', Size: 32}, +// Protocol{ Code: 17, Name: 'udp', Size: 16}, +// } +``` + +#### En/decapsulate + +```go +import ma "github.com/multiformats/go-multiaddr" + +m, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") +// + +sctpMA, err := ma.NewMultiaddr("/sctp/5678") + +m.Encapsulate(sctpMA) +// + +udpMA, err := ma.NewMultiaddr("/udp/1234") + +m.Decapsulate(udpMA) // up to + inc last occurrence of subaddr +// +``` + +#### Tunneling + +Multiaddr allows expressing tunnels very nicely. + +```js +printer, _ := ma.NewMultiaddr("/ip4/192.168.0.13/tcp/80") +proxy, _ := ma.NewMultiaddr("/ip4/10.20.30.40/tcp/443") +printerOverProxy := proxy.Encapsulate(printer) +// /ip4/10.20.30.40/tcp/443/ip4/192.168.0.13/tcp/80 + +proxyAgain := printerOverProxy.Decapsulate(printer) +// /ip4/10.20.30.40/tcp/443 +``` + +## Contribute + +Contributions welcome. Please check out [the issues](https://github.com/multiformats/go-multiaddr/issues). + +Check out our [contributing document](https://github.com/multiformats/multiformats/blob/master/contributing.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. + +## License + +[MIT](LICENSE) © 2014 Juan Batiz-Benet diff --git a/codec.go b/codec.go new file mode 100644 index 0000000..f2951c4 --- /dev/null +++ b/codec.go @@ -0,0 +1,181 @@ +package multiaddr + +import ( + "bytes" + "fmt" + "strings" + + "github.com/multiformats/go-varint" +) + +func stringToBytes(s string) ([]byte, error) { + // consume trailing slashes + s = strings.TrimRight(s, "/") + + var b bytes.Buffer + sp := strings.Split(s, "/") + + if sp[0] != "" { + return nil, fmt.Errorf("failed to parse multiaddr %q: must begin with /", s) + } + + // consume first empty elem + sp = sp[1:] + + if len(sp) == 0 { + return nil, fmt.Errorf("failed to parse multiaddr %q: empty multiaddr", s) + } + + for len(sp) > 0 { + name := sp[0] + p := ProtocolWithName(name) + if p.Code == 0 { + return nil, fmt.Errorf("failed to parse multiaddr %q: unknown protocol %s", s, sp[0]) + } + _, _ = b.Write(p.VCode) + sp = sp[1:] + + if p.Size == 0 { // no length. + continue + } + + if len(sp) < 1 { + return nil, fmt.Errorf("failed to parse multiaddr %q: unexpected end of multiaddr", s) + } + + if p.Path { + // it's a path protocol (terminal). + // consume the rest of the address as the next component. + sp = []string{"/" + strings.Join(sp, "/")} + } + + a, err := p.Transcoder.StringToBytes(sp[0]) + if err != nil { + return nil, fmt.Errorf("failed to parse multiaddr %q: invalid value %q for protocol %s: %s", s, sp[0], p.Name, err) + } + if p.Size < 0 { // varint size. + _, _ = b.Write(varint.ToUvarint(uint64(len(a)))) + } + b.Write(a) + sp = sp[1:] + } + + return b.Bytes(), nil +} + +func validateBytes(b []byte) (err error) { + if len(b) == 0 { + return fmt.Errorf("empty multiaddr") + } + for len(b) > 0 { + code, n, err := ReadVarintCode(b) + if err != nil { + return err + } + + b = b[n:] + p := ProtocolWithCode(code) + if p.Code == 0 { + return fmt.Errorf("no protocol with code %d", code) + } + + if p.Size == 0 { + continue + } + + n, size, err := sizeForAddr(p, b) + if err != nil { + return err + } + + b = b[n:] + + if len(b) < size || size < 0 { + return fmt.Errorf("invalid value for size %d", len(b)) + } + if p.Path && len(b) != size { + return fmt.Errorf("invalid size of component for path protocol %d: expected %d", size, len(b)) + } + + err = p.Transcoder.ValidateBytes(b[:size]) + if err != nil { + return err + } + + b = b[size:] + } + + return nil +} + +func readComponent(b []byte) (int, Component, error) { + var offset int + code, n, err := ReadVarintCode(b) + if err != nil { + return 0, Component{}, err + } + offset += n + + p := ProtocolWithCode(code) + if p.Code == 0 { + return 0, Component{}, fmt.Errorf("no protocol with code %d", code) + } + + if p.Size == 0 { + return offset, Component{ + bytes: b[:offset], + offset: offset, + protocol: p, + }, nil + } + + n, size, err := sizeForAddr(p, b[offset:]) + if err != nil { + return 0, Component{}, err + } + + offset += n + + if len(b[offset:]) < size || size < 0 { + return 0, Component{}, fmt.Errorf("invalid value for size %d", len(b[offset:])) + } + + return offset + size, Component{ + bytes: b[:offset+size], + protocol: p, + offset: offset, + }, nil +} + +func bytesToString(b []byte) (ret string, err error) { + if len(b) == 0 { + return "", fmt.Errorf("empty multiaddr") + } + var buf strings.Builder + + for len(b) > 0 { + n, c, err := readComponent(b) + if err != nil { + return "", err + } + b = b[n:] + c.writeTo(&buf) + } + + return buf.String(), nil +} + +func sizeForAddr(p Protocol, b []byte) (skip, size int, err error) { + switch { + case p.Size > 0: + return 0, (p.Size / 8), nil + case p.Size == 0: + return 0, 0, nil + default: + size, n, err := ReadVarintCode(b) + if err != nil { + return 0, 0, err + } + return n, size, nil + } +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..ca8100a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "multiaddr" diff --git a/component.go b/component.go new file mode 100644 index 0000000..4ee6809 --- /dev/null +++ b/component.go @@ -0,0 +1,186 @@ +package multiaddr + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "strings" + + "github.com/multiformats/go-varint" +) + +// Component is a single multiaddr Component. +type Component struct { + bytes []byte + protocol Protocol + offset int +} + +func (c *Component) Bytes() []byte { + return c.bytes +} + +func (c *Component) MarshalBinary() ([]byte, error) { + return c.Bytes(), nil +} + +func (c *Component) UnmarshalBinary(data []byte) error { + _, comp, err := readComponent(data) + if err != nil { + return err + } + *c = comp + return nil +} + +func (c *Component) MarshalText() ([]byte, error) { + return []byte(c.String()), nil +} + +func (c *Component) UnmarshalText(data []byte) error { + bytes, err := stringToBytes(string(data)) + if err != nil { + return err + } + _, comp, err := readComponent(bytes) + if err != nil { + return err + } + *c = comp + return nil +} + +func (c *Component) MarshalJSON() ([]byte, error) { + txt, err := c.MarshalText() + if err != nil { + return nil, err + } + + return json.Marshal(string(txt)) +} + +func (m *Component) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + return m.UnmarshalText([]byte(v)) +} + +func (c *Component) Equal(o Multiaddr) bool { + if o == nil { + return false + } + return bytes.Equal(c.bytes, o.Bytes()) +} + +func (c *Component) Protocols() []Protocol { + return []Protocol{c.protocol} +} + +func (c *Component) Decapsulate(o Multiaddr) Multiaddr { + if c.Equal(o) { + return nil + } + return c +} + +func (c *Component) Encapsulate(o Multiaddr) Multiaddr { + m := &multiaddr{bytes: c.bytes} + return m.Encapsulate(o) +} + +func (c *Component) ValueForProtocol(code int) (string, error) { + if c.protocol.Code != code { + return "", ErrProtocolNotFound + } + return c.Value(), nil +} + +func (c *Component) Protocol() Protocol { + return c.protocol +} + +func (c *Component) RawValue() []byte { + return c.bytes[c.offset:] +} + +func (c *Component) Value() string { + if c.protocol.Transcoder == nil { + return "" + } + value, err := c.protocol.Transcoder.BytesToString(c.bytes[c.offset:]) + if err != nil { + // This Component must have been checked. + panic(err) + } + return value +} + +func (c *Component) String() string { + var b strings.Builder + c.writeTo(&b) + return b.String() +} + +// writeTo is an efficient, private function for string-formatting a multiaddr. +// Trust me, we tend to allocate a lot when doing this. +func (c *Component) writeTo(b *strings.Builder) { + b.WriteByte('/') + b.WriteString(c.protocol.Name) + value := c.Value() + if len(value) == 0 { + return + } + if !(c.protocol.Path && value[0] == '/') { + b.WriteByte('/') + } + b.WriteString(value) +} + +// NewComponent constructs a new multiaddr component +func NewComponent(protocol, value string) (*Component, error) { + p := ProtocolWithName(protocol) + if p.Code == 0 { + return nil, fmt.Errorf("unsupported protocol: %s", protocol) + } + if p.Transcoder != nil { + bts, err := p.Transcoder.StringToBytes(value) + if err != nil { + return nil, err + } + return newComponent(p, bts), nil + } else if value != "" { + return nil, fmt.Errorf("protocol %s doesn't take a value", p.Name) + } + return newComponent(p, nil), nil + // TODO: handle path /? +} + +func newComponent(protocol Protocol, bvalue []byte) *Component { + size := len(bvalue) + size += len(protocol.VCode) + if protocol.Size < 0 { + size += varint.UvarintSize(uint64(len(bvalue))) + } + maddr := make([]byte, size) + var offset int + offset += copy(maddr[offset:], protocol.VCode) + if protocol.Size < 0 { + offset += binary.PutUvarint(maddr[offset:], uint64(len(bvalue))) + } + copy(maddr[offset:], bvalue) + + // For debugging + if len(maddr) != offset+len(bvalue) { + panic("incorrect length") + } + + return &Component{ + bytes: maddr, + protocol: protocol, + offset: offset, + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..b80f3ab --- /dev/null +++ b/doc.go @@ -0,0 +1,35 @@ +/* +Package multiaddr provides an implementation of the Multiaddr network +address format. Multiaddr emphasizes explicitness, self-description, and +portability. It allows applications to treat addresses as opaque tokens, +and to avoid making assumptions about the address representation (e.g. length). +Learn more at https://github.com/multiformats/multiaddr + +Basic Use: + + import ( + "bytes" + "strings" + ma "github.com/multiformats/go-multiaddr" + ) + + // construct from a string (err signals parse failure) + m1, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") + + // construct from bytes (err signals parse failure) + m2, err := ma.NewMultiaddrBytes(m1.Bytes()) + + // true + strings.Equal(m1.String(), "/ip4/127.0.0.1/udp/1234") + strings.Equal(m1.String(), m2.String()) + bytes.Equal(m1.Bytes(), m2.Bytes()) + m1.Equal(m2) + m2.Equal(m1) + + // tunneling (en/decap) + printer, _ := ma.NewMultiaddr("/ip4/192.168.0.13/tcp/80") + proxy, _ := ma.NewMultiaddr("/ip4/10.20.30.40/tcp/443") + printerOverProxy := proxy.Encapsulate(printer) + proxyAgain := printerOverProxy.Decapsulate(printer) +*/ +package multiaddr diff --git a/filter.go b/filter.go new file mode 100644 index 0000000..ba915da --- /dev/null +++ b/filter.go @@ -0,0 +1,145 @@ +package multiaddr + +import ( + "net" + "sync" +) + +// Action is an enum modelling all possible filter actions. +type Action int32 + +const ( + ActionNone Action = iota // zero value. + ActionAccept + ActionDeny +) + +type filterEntry struct { + f net.IPNet + action Action +} + +// Filters is a structure representing a collection of accept/deny +// net.IPNet filters, together with the DefaultAction flag, which +// represents the default filter policy. +// +// Note that the last policy added to the Filters is authoritative. +type Filters struct { + DefaultAction Action + + mu sync.RWMutex + filters []*filterEntry +} + +// NewFilters constructs and returns a new set of net.IPNet filters. +// By default, the new filter accepts all addresses. +func NewFilters() *Filters { + return &Filters{ + DefaultAction: ActionAccept, + filters: make([]*filterEntry, 0), + } +} + +func (fs *Filters) find(ipnet net.IPNet) (int, *filterEntry) { + s := ipnet.String() + for idx, ft := range fs.filters { + if ft.f.String() == s { + return idx, ft + } + } + return -1, nil +} + +// AddFilter adds a rule to the Filters set, enforcing the desired action for +// the provided IPNet mask. +func (fs *Filters) AddFilter(ipnet net.IPNet, action Action) { + fs.mu.Lock() + defer fs.mu.Unlock() + + if _, f := fs.find(ipnet); f != nil { + f.action = action + } else { + fs.filters = append(fs.filters, &filterEntry{ipnet, action}) + } +} + +// RemoveLiteral removes the first filter associated with the supplied IPNet, +// returning whether something was removed or not. It makes no distinction +// between whether the rule is an accept or a deny. +func (fs *Filters) RemoveLiteral(ipnet net.IPNet) (removed bool) { + fs.mu.Lock() + defer fs.mu.Unlock() + + if idx, _ := fs.find(ipnet); idx != -1 { + fs.filters = append(fs.filters[:idx], fs.filters[idx+1:]...) + return true + } + return false +} + +// AddrBlocked parses a ma.Multiaddr and, if a valid netip is found, it applies the +// Filter set rules, returning true if the given address should be denied, and false if +// the given address is accepted. +// +// If a parsing error occurs, or no filter matches, the Filters' +// default is returned. +// +// TODO: currently, the last filter to match wins always, but it shouldn't be that way. +// +// Instead, the highest-specific last filter should win; that way more specific filters +// override more general ones. +func (fs *Filters) AddrBlocked(a Multiaddr) (deny bool) { + var ( + netip net.IP + found bool + ) + + ForEach(a, func(c Component) bool { + switch c.Protocol().Code { + case P_IP6ZONE: + return true + case P_IP6, P_IP4: + found = true + netip = net.IP(c.RawValue()) + return false + default: + return false + } + }) + + if !found { + return fs.DefaultAction == ActionDeny + } + + fs.mu.RLock() + defer fs.mu.RUnlock() + + action := fs.DefaultAction + for _, ft := range fs.filters { + if ft.f.Contains(netip) { + action = ft.action + } + } + + return action == ActionDeny +} + +func (fs *Filters) ActionForFilter(ipnet net.IPNet) (action Action, ok bool) { + if _, f := fs.find(ipnet); f != nil { + return f.action, true + } + return ActionNone, false +} + +// FiltersForAction returns the filters associated with the indicated action. +func (fs *Filters) FiltersForAction(action Action) (result []net.IPNet) { + fs.mu.RLock() + defer fs.mu.RUnlock() + + for _, ff := range fs.filters { + if ff.action == action { + result = append(result, ff.f) + } + } + return result +} diff --git a/filter_test.go b/filter_test.go new file mode 100644 index 0000000..82b974d --- /dev/null +++ b/filter_test.go @@ -0,0 +1,202 @@ +package multiaddr + +import ( + "net" + "testing" +) + +func TestFilterListing(t *testing.T) { + f := NewFilters() + expected := map[string]bool{ + "1.2.3.0/24": true, + "4.3.2.1/32": true, + "fd00::/8": true, + "fc00::1/128": true, + } + for cidr := range expected { + _, ipnet, _ := net.ParseCIDR(cidr) + f.AddFilter(*ipnet, ActionDeny) + } + + for _, filter := range f.FiltersForAction(ActionDeny) { + cidr := filter.String() + if expected[cidr] { + delete(expected, cidr) + } else { + t.Errorf("unexected filter %s", cidr) + } + } + for cidr := range expected { + t.Errorf("expected filter %s", cidr) + } +} + +func TestFilterBlocking(t *testing.T) { + f := NewFilters() + + _, ipnet, _ := net.ParseCIDR("0.1.2.3/24") + f.AddFilter(*ipnet, ActionDeny) + filters := f.FiltersForAction(ActionDeny) + if len(filters) != 1 { + t.Fatal("Expected only 1 filter") + } + + if a, ok := f.ActionForFilter(*ipnet); !ok || a != ActionDeny { + t.Fatal("Expected filter with DENY action") + } + + if !f.RemoveLiteral(filters[0]) { + t.Error("expected true value from RemoveLiteral") + } + + for _, cidr := range []string{ + "1.2.3.0/24", + "4.3.2.1/32", + "fd00::/8", + "fc00::1/128", + } { + _, ipnet, _ := net.ParseCIDR(cidr) + f.AddFilter(*ipnet, ActionDeny) + } + + // These addresses should all be blocked + for _, blocked := range []string{ + "/ip4/1.2.3.4/tcp/123", + "/ip4/4.3.2.1/udp/123", + "/ip6/fd00::2/tcp/321", + "/ip6/fc00::1/udp/321", + } { + maddr, err := NewMultiaddr(blocked) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blocked", blocked) + } + } + + // test that other net intervals are not blocked + for _, addr := range []string{ + "/ip4/1.2.4.1/tcp/123", + "/ip4/4.3.2.2/udp/123", + "/ip6/fe00::1/tcp/321", + "/ip6/fc00::2/udp/321", + } { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if f.AddrBlocked(maddr) { + t.Fatalf("expected %s to not be blocked", addr) + } + } +} + +func TestFilterWhitelisting(t *testing.T) { + f := NewFilters() + + // Add default reject filter + f.DefaultAction = ActionDeny + + // Add a whitelist + _, ipnet, _ := net.ParseCIDR("1.2.3.0/24") + f.AddFilter(*ipnet, ActionAccept) + + if a, ok := f.ActionForFilter(*ipnet); !ok || a != ActionAccept { + t.Fatal("Expected filter with ACCEPT action") + } + + // That /24 should now be allowed + for _, addr := range []string{ + "/ip4/1.2.3.1/tcp/123", + "/ip4/1.2.3.254/tcp/123", + "/ip4/1.2.3.254/udp/321", + } { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be whitelisted", addr) + } + } + + // No policy matches these maddrs, they should be blocked by default + for _, blocked := range []string{ + "/ip4/1.2.4.4/tcp/123", + "/ip4/4.3.2.1/udp/123", + "/ip6/fd00::2/tcp/321", + "/ip6/fc00::1/udp/321", + } { + maddr, err := NewMultiaddr(blocked) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blocked", blocked) + } + } +} + +func TestFiltersRemoveRules(t *testing.T) { + f := NewFilters() + + ips := []string{ + "/ip4/1.2.3.1/tcp/123", + "/ip4/1.2.3.254/tcp/123", + } + + // Add default reject filter + f.DefaultAction = ActionDeny + + // Add whitelisting + _, ipnet, _ := net.ParseCIDR("1.2.3.0/24") + f.AddFilter(*ipnet, ActionAccept) + + if a, ok := f.ActionForFilter(*ipnet); !ok || a != ActionAccept { + t.Fatal("Expected filter with ACCEPT action") + } + + // these are all whitelisted, should be OK + for _, addr := range ips { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be whitelisted", addr) + } + } + + // Test removing the filter. It'll remove multiple, so make a dupe & + // a complement + f.AddFilter(*ipnet, ActionDeny) + + // Show that they all apply, these are now blacklisted & should fail + for _, addr := range ips { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blacklisted", addr) + } + } + + // remove those rules + if !f.RemoveLiteral(*ipnet) { + t.Error("expected true value from RemoveLiteral") + } + + // our default is reject, so the 1.2.3.0/24 should be rejected now, + // along with everything else + for _, addr := range ips { + maddr, err := NewMultiaddr(addr) + if err != nil { + t.Error(err) + } + if !f.AddrBlocked(maddr) { + t.Fatalf("expected %s to be blocked", addr) + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e3249bf --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module github.com/multiformats/go-multiaddr + +go 1.22 + +require ( + github.com/ipfs/go-cid v0.0.7 + github.com/multiformats/go-multibase v0.2.0 + github.com/multiformats/go-multihash v0.2.3 + github.com/multiformats/go-varint v0.0.7 + github.com/stretchr/testify v1.7.0 + golang.org/x/exp v0.0.0-20230725012225-302865e7556b +) + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..076449b --- /dev/null +++ b/go.sum @@ -0,0 +1,56 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b h1:tK7yjGqVRzYdXsBcfD2MLhFAhHfDgGLm2rY1ub7FA9k= +golang.org/x/exp v0.0.0-20230725012225-302865e7556b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/interface.go b/interface.go new file mode 100644 index 0000000..699c54d --- /dev/null +++ b/interface.go @@ -0,0 +1,63 @@ +package multiaddr + +import ( + "encoding" + "encoding/json" +) + +/* +Multiaddr is a cross-protocol, cross-platform format for representing +internet addresses. It emphasizes explicitness and self-description. +Learn more here: https://github.com/multiformats/multiaddr + +Multiaddrs have both a binary and string representation. + + import ma "github.com/multiformats/go-multiaddr" + + addr, err := ma.NewMultiaddr("/ip4/1.2.3.4/tcp/80") + // err non-nil when parsing failed. +*/ +type Multiaddr interface { + json.Marshaler + json.Unmarshaler + encoding.TextMarshaler + encoding.TextUnmarshaler + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + + // Equal returns whether two Multiaddrs are exactly equal + Equal(Multiaddr) bool + + // Bytes returns the []byte representation of this Multiaddr + // + // This function may expose immutable, internal state. Do not modify. + Bytes() []byte + + // String returns the string representation of this Multiaddr + // (may panic if internal state is corrupted) + String() string + + // Protocols returns the list of Protocols this Multiaddr includes + // will panic if protocol code incorrect (and bytes accessed incorrectly) + Protocols() []Protocol + + // Encapsulate wraps this Multiaddr around another. For example: + // + // /ip4/1.2.3.4 encapsulate /tcp/80 = /ip4/1.2.3.4/tcp/80 + // + Encapsulate(Multiaddr) Multiaddr + + // Decapsulate removes a Multiaddr wrapping. For example: + // + // /ip4/1.2.3.4/tcp/80 decapsulate /tcp/80 = /ip4/1.2.3.4 + // /ip4/1.2.3.4/tcp/80 decapsulate /udp/80 = /ip4/1.2.3.4/tcp/80 + // /ip4/1.2.3.4/tcp/80 decapsulate /ip4/1.2.3.4 = nil + // + Decapsulate(Multiaddr) Multiaddr + + // ValueForProtocol returns the value (if any) following the specified protocol + // + // Note: protocols can appear multiple times in a single multiaddr. + // Consider using `ForEach` to walk over the addr manually. + ValueForProtocol(code int) (string, error) +} diff --git a/multiaddr.go b/multiaddr.go new file mode 100644 index 0000000..5e60780 --- /dev/null +++ b/multiaddr.go @@ -0,0 +1,261 @@ +package multiaddr + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + + "golang.org/x/exp/slices" +) + +// multiaddr is the data structure representing a Multiaddr +type multiaddr struct { + bytes []byte +} + +// NewMultiaddr parses and validates an input string, returning a *Multiaddr +func NewMultiaddr(s string) (a Multiaddr, err error) { + defer func() { + if e := recover(); e != nil { + log.Printf("Panic in NewMultiaddr on input %q: %s", s, e) + err = fmt.Errorf("%v", e) + } + }() + b, err := stringToBytes(s) + if err != nil { + return nil, err + } + return &multiaddr{bytes: b}, nil +} + +// NewMultiaddrBytes initializes a Multiaddr from a byte representation. +// It validates it as an input string. +func NewMultiaddrBytes(b []byte) (a Multiaddr, err error) { + defer func() { + if e := recover(); e != nil { + log.Printf("Panic in NewMultiaddrBytes on input %q: %s", b, e) + err = fmt.Errorf("%v", e) + } + }() + + if err := validateBytes(b); err != nil { + return nil, err + } + + return &multiaddr{bytes: b}, nil +} + +// Equal tests whether two multiaddrs are equal +func (m *multiaddr) Equal(m2 Multiaddr) bool { + if m2 == nil { + return false + } + return bytes.Equal(m.bytes, m2.Bytes()) +} + +// Bytes returns the []byte representation of this Multiaddr +// +// Do not modify the returned buffer, it may be shared. +func (m *multiaddr) Bytes() []byte { + return m.bytes +} + +// String returns the string representation of a Multiaddr +func (m *multiaddr) String() string { + s, err := bytesToString(m.bytes) + if err != nil { + panic(fmt.Errorf("multiaddr failed to convert back to string. corrupted? %s", err)) + } + return s +} + +func (m *multiaddr) MarshalBinary() ([]byte, error) { + return m.Bytes(), nil +} + +func (m *multiaddr) UnmarshalBinary(data []byte) error { + new, err := NewMultiaddrBytes(data) + if err != nil { + return err + } + *m = *(new.(*multiaddr)) + return nil +} + +func (m *multiaddr) MarshalText() ([]byte, error) { + return []byte(m.String()), nil +} + +func (m *multiaddr) UnmarshalText(data []byte) error { + new, err := NewMultiaddr(string(data)) + if err != nil { + return err + } + *m = *(new.(*multiaddr)) + return nil +} + +func (m *multiaddr) MarshalJSON() ([]byte, error) { + return json.Marshal(m.String()) +} + +func (m *multiaddr) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + new, err := NewMultiaddr(v) + *m = *(new.(*multiaddr)) + return err +} + +// Protocols returns the list of protocols this Multiaddr has. +// will panic in case we access bytes incorrectly. +func (m *multiaddr) Protocols() []Protocol { + ps := make([]Protocol, 0, 8) + b := m.bytes + for len(b) > 0 { + code, n, err := ReadVarintCode(b) + if err != nil { + panic(err) + } + + p := ProtocolWithCode(code) + if p.Code == 0 { + // this is a panic (and not returning err) because this should've been + // caught on constructing the Multiaddr + panic(fmt.Errorf("no protocol with code %d", b[0])) + } + ps = append(ps, p) + b = b[n:] + + n, size, err := sizeForAddr(p, b) + if err != nil { + panic(err) + } + + b = b[n+size:] + } + return ps +} + +// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr +func (m *multiaddr) Encapsulate(o Multiaddr) Multiaddr { + if o == nil { + return m + } + + mb := m.bytes + ob := o.Bytes() + + b := make([]byte, len(mb)+len(ob)) + copy(b, mb) + copy(b[len(mb):], ob) + return &multiaddr{bytes: b} +} + +// Decapsulate unwraps Multiaddr up until the given Multiaddr is found. +func (m *multiaddr) Decapsulate(right Multiaddr) Multiaddr { + if right == nil { + return m + } + + leftParts := Split(m) + rightParts := Split(right) + + lastIndex := -1 + for i := range leftParts { + foundMatch := false + for j, rightC := range rightParts { + if len(leftParts) <= i+j { + foundMatch = false + break + } + + foundMatch = rightC.Equal(leftParts[i+j]) + if !foundMatch { + break + } + } + + if foundMatch { + lastIndex = i + } + } + + if lastIndex == 0 { + return nil + } + + if lastIndex < 0 { + // if multiaddr not contained, returns a copy. + cpy := make([]byte, len(m.bytes)) + copy(cpy, m.bytes) + return &multiaddr{bytes: cpy} + } + + return Join(leftParts[:lastIndex]...) +} + +var ErrProtocolNotFound = fmt.Errorf("protocol not found in multiaddr") + +func (m *multiaddr) ValueForProtocol(code int) (value string, err error) { + err = ErrProtocolNotFound + ForEach(m, func(c Component) bool { + if c.Protocol().Code == code { + value = c.Value() + err = nil + return false + } + return true + }) + return +} + +// FilterAddrs is a filter that removes certain addresses, according to the given filters. +// If all filters return true, the address is kept. +func FilterAddrs(a []Multiaddr, filters ...func(Multiaddr) bool) []Multiaddr { + b := make([]Multiaddr, 0, len(a)) +addrloop: + for _, addr := range a { + for _, filter := range filters { + if !filter(addr) { + continue addrloop + } + } + b = append(b, addr) + } + return b +} + +// Contains reports whether addr is contained in addrs. +func Contains(addrs []Multiaddr, addr Multiaddr) bool { + for _, a := range addrs { + if addr.Equal(a) { + return true + } + } + return false +} + +// Unique deduplicates addresses in place, leave only unique addresses. +// It doesn't allocate. +func Unique(addrs []Multiaddr) []Multiaddr { + if len(addrs) == 0 { + return addrs + } + // Use the new slices package here, as the sort function doesn't allocate (sort.Slice does). + slices.SortFunc(addrs, func(a, b Multiaddr) int { return bytes.Compare(a.Bytes(), b.Bytes()) }) + idx := 1 + for i := 1; i < len(addrs); i++ { + if !addrs[i-1].Equal(addrs[i]) { + addrs[idx] = addrs[i] + idx++ + } + } + for i := idx; i < len(addrs); i++ { + addrs[i] = nil + } + return addrs[:idx] +} diff --git a/multiaddr/main.go b/multiaddr/main.go new file mode 100644 index 0000000..6d0aa7b --- /dev/null +++ b/multiaddr/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "os" + "strings" + + maddr "github.com/multiformats/go-multiaddr" +) + +var ( + flagHelp bool +) + +func main() { + flag.Usage = func() { + usage := `usage: %s [options] ADDR + +Print details about the given multiaddr. + +Options: +` + fmt.Fprintf(os.Stderr, usage, os.Args[0]) + flag.PrintDefaults() + } + + flag.BoolVar(&flagHelp, "h", false, "display help message") + flag.Parse() + + if flagHelp || len(flag.Args()) == 0 { + flag.Usage() + os.Exit(0) + } + + addrStr := flag.Args()[0] + var addr maddr.Multiaddr + var merr error + if strings.HasPrefix(addrStr, "0x") { + addrBytes, err := hex.DecodeString(addrStr[2:]) + if err != nil { + fmt.Fprintf(os.Stderr, "parse error: %s\n", err) + os.Exit(1) + } + addr, merr = maddr.NewMultiaddrBytes(addrBytes) + } else { + addr, merr = maddr.NewMultiaddr(addrStr) + } + if merr != nil { + fmt.Fprintf(os.Stderr, "parse error: %s\n", merr) + os.Exit(1) + } + + infoCommand(addr) +} + +func infoCommand(addr maddr.Multiaddr) { + var compsJson []string + maddr.ForEach(addr, func(comp maddr.Component) bool { + lengthPrefix := "" + if comp.Protocol().Size == maddr.LengthPrefixedVarSize { + lengthPrefix = "0x" + hex.EncodeToString(maddr.CodeToVarint(len(comp.RawValue()))) + } + + compsJson = append(compsJson, `{`+ + fmt.Sprintf(`"string": "%s", `, comp.String())+ + fmt.Sprintf(`"stringSize": "%d", `, len(comp.String()))+ + fmt.Sprintf(`"packed": "0x%x", `, comp.Bytes())+ + fmt.Sprintf(`"packedSize": "%d", `, len(comp.Bytes()))+ + fmt.Sprintf(`"value": %#v, `, comp.Value())+ + fmt.Sprintf(`"rawValue": "0x%x", `, comp.RawValue())+ + fmt.Sprintf(`"valueSize": "%d", `, len(comp.RawValue()))+ + fmt.Sprintf(`"protocol": "%s", `, comp.Protocol().Name)+ + fmt.Sprintf(`"codec": "%d", `, comp.Protocol().Code)+ + fmt.Sprintf(`"uvarint": "0x%x", `, comp.Protocol().VCode)+ + fmt.Sprintf(`"lengthPrefix": "%s"`, lengthPrefix)+ + `}`) + return true + }) + + addrJson := `{ + "string": "%[1]s", + "stringSize": "%[2]d", + "packed": "0x%[3]x", + "packedSize": "%[4]d", + "components": [ + %[5]s + ] +}` + fmt.Fprintf(os.Stdout, addrJson+"\n", + addr.String(), len(addr.String()), addr.Bytes(), len(addr.Bytes()), strings.Join(compsJson, ",\n ")) +} diff --git a/multiaddr_test.go b/multiaddr_test.go new file mode 100644 index 0000000..2d3b73c --- /dev/null +++ b/multiaddr_test.go @@ -0,0 +1,1063 @@ +package multiaddr + +import ( + "bytes" + "encoding/hex" + "fmt" + "math" + "math/rand" + "testing" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" +) + +func newMultiaddr(t *testing.T, a string) Multiaddr { + m, err := NewMultiaddr(a) + if err != nil { + t.Error(err) + } + return m +} + +func TestConstructFails(t *testing.T) { + cases := []string{ + "/ip4", + "/ip4/::1", + "/ip4/fdpsofodsajfdoisa", + "/ip4/::/ipcidr/256", + "/ip6/::/ipcidr/1026", + "/ip6", + "/ip6zone", + "/ip6zone/", + "/ip6zone//ip6/fe80::1", + "/udp", + "/tcp", + "/sctp", + "/udp/65536", + "/tcp/65536", + "/quic/65536", + "/quic-v1/65536", + "/onion/9imaq4ygg2iegci7:80", + "/onion/aaimaq4ygg2iegci7:80", + "/onion/timaq4ygg2iegci7:0", + "/onion/timaq4ygg2iegci7:-1", + "/onion/timaq4ygg2iegci7", + "/onion/timaq4ygg2iegci@:666", + "/onion3/9ww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd7:80", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:0", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:-1", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyy@:666", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA7:80", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:-1", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA@:666", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA7:80", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:-1", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA@:666", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu77", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu:80", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq:-1", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu@", + "/udp/1234/sctp", + "/udp/1234/udt/1234", + "/udp/1234/utp/1234", + "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/udp", + "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/tcp", + "/ip4/127.0.0.1/quic/1234", + "/ip4/127.0.0.1/quic-v1/1234", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmp", // 1 character missing from certhash + "/ip4/127.0.0.1/ipfs", + "/ip4/127.0.0.1/ipfs/tcp", + "/ip4/127.0.0.1/p2p", + "/ip4/127.0.0.1/p2p/tcp", + "/unix", + "/ip4/1.2.3.4/tcp/80/unix", + "/ip4/1.2.3.4/tcp/-1", + "/ip4/127.0.0.1/tcp/9090/http/p2p-webcrt-direct", + fmt.Sprintf("/memory/%d1", uint64(1<<63)), + "/", + "", + "/p2p/QmxoHT6iViN5xAjoz1VZ553cL31U9F94ht3QvWR1FrEbZY", // sha256 multihash with digest len > 32 + } + + for _, a := range cases { + if _, err := NewMultiaddr(a); err == nil { + t.Errorf("should have failed: %s - %s", a, err) + } + } +} + +func TestEmptyMultiaddr(t *testing.T) { + _, err := NewMultiaddrBytes([]byte{}) + if err == nil { + t.Fatal("should have failed to parse empty multiaddr") + } +} + +var good = []string{ + "/ip4/1.2.3.4", + "/ip4/0.0.0.0", + "/ip4/192.0.2.0/ipcidr/24", + "/ip6/::1", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic-v1", + "/ip6/2001:db8::/ipcidr/32", + "/ip6zone/x/ip6/fe80::1", + "/ip6zone/x%y/ip6/fe80::1", + "/ip6zone/x%y/ip6/::", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic-v1", + "/onion/timaq4ygg2iegci7:1234", + "/onion/timaq4ygg2iegci7:80/http", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/http", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/udp/8080", + "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwas", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwassw", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/http", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/tcp/8080", + "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/udp/8080", + "/udp/0", + "/tcp/0", + "/sctp/0", + "/udp/1234", + "/tcp/1234", + "/sctp/1234", + "/udp/65535", + "/tcp/65535", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/p2p/bafzbeigvf25ytwc3akrijfecaotc74udrhcxzh2cx3we5qqnw5vgrei4bm", + "/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV", + "/p2p/k51qzi5uqu5dhb6l8spkdx7yxafegfkee5by8h7lmjh2ehc2sgg34z7c15vzqs", + "/p2p/bafzaajaiaejcalj543iwv2d7pkjt7ykvefrkfu7qjfi6sduakhso4lay6abn2d5u", + "/udp/1234/sctp/1234", + "/udp/1234/udt", + "/udp/1234/utp", + "/tcp/1234/http", + "/tcp/1234/tls/http", + "/tcp/1234/https", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/udp/1234", + "/ip4/127.0.0.1/udp/0", + "/ip4/127.0.0.1/tcp/1234", + "/ip4/127.0.0.1/tcp/1234/", + "/ip4/127.0.0.1/udp/1234/quic", + "/ip4/127.0.0.1/udp/1234/quic-v1", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", + "/unix/a/b/c/d/e", + "/unix/stdio", + "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/ws", + "/ip4/127.0.0.1/tcp/127/tls", + "/ip4/127.0.0.1/tcp/127/tls/ws", + "/ip4/127.0.0.1/tcp/127/noise", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/wss", + "/ip4/127.0.0.1/tcp/127/webrtc-direct", + "/ip4/127.0.0.1/tcp/127/webrtc", + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + "/ip4/127.0.0.1/tcp/0/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV/http-path/foo", + "/ip4/127.0.0.1/tcp/443/tls/sni/example.com/http/http-path/foo", + "/memory/4", +} + +func TestConstructSucceeds(t *testing.T) { + for _, a := range good { + if _, err := NewMultiaddr(a); err != nil { + t.Errorf("should have succeeded: %s -- %s", a, err) + } + } +} + +func TestEqual(t *testing.T) { + m1 := newMultiaddr(t, "/ip4/127.0.0.1/udp/1234") + m2 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234") + m3 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234") + m4 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234/") + + if m1.Equal(m2) { + t.Error("should not be equal") + } + + if m2.Equal(m1) { + t.Error("should not be equal") + } + + if !m2.Equal(m3) { + t.Error("should be equal") + } + + if !m3.Equal(m2) { + t.Error("should be equal") + } + + if !m1.Equal(m1) { + t.Error("should be equal") + } + + if !m2.Equal(m4) { + t.Error("should be equal") + } + + if !m4.Equal(m3) { + t.Error("should be equal") + } +} + +// TestNilInterface makes sure funcs that accept a multiaddr interface don't +// panic if it's passed a nil interface. +func TestNilInterface(t *testing.T) { + m1 := newMultiaddr(t, "/ip4/127.0.0.1/udp/1234") + var m2 Multiaddr + m1.Equal(m2) + m1.Encapsulate(m2) + m1.Decapsulate(m2) + + // Test components + c, _ := SplitFirst(m1) + c.Equal(m2) + c.Encapsulate(m2) + c.Decapsulate(m2) + + // Util funcs + _ = Split(m2) + _, _ = SplitFirst(m2) + _, _ = SplitLast(m2) + ForEach(m2, func(c Component) bool { return true }) +} + +func TestStringToBytes(t *testing.T) { + + testString := func(s string, h string) { + b1, err := hex.DecodeString(h) + if err != nil { + t.Error("failed to decode hex", h) + } + + // t.Log("196", h, []byte(b1)) + + b2, err := stringToBytes(s) + if err != nil { + t.Error("failed to convert", s, err) + } + + if !bytes.Equal(b1, b2) { + t.Error("failed to convert \n", s, "to\n", hex.EncodeToString(b1), "got\n", hex.EncodeToString(b2)) + } + + if err := validateBytes(b2); err != nil { + t.Error(err, "len:", len(b2)) + } + } + + testString("/ip4/127.0.0.1/udp/1234", "047f000001910204d2") + testString("/ip4/127.0.0.1/tcp/4321", "047f0000010610e1") + testString("/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321", "047f000001910204d2047f0000010610e1") + testString("/onion/aaimaq4ygg2iegci:80", "bc030010c0439831b48218480050") + testString("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", "bd03adadec040be047f9658668b11a504f3155001f231a37f54c4476c07fb4cc139ed7e30304d2") + testString("/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "be0383038d3fc8c976a86ae4e78ba378e75ec41bc9ab1542a9cb422581987e118f5cb0c024f3639d6ad9b3aff613672f07bfbbbfc2f920ef910534ecaa6ff9c03e0fa4872a764d2fce6d4cfc5a5a9800cd95944cc9ef0241f753fe71494a175f334b35682459acadc4076428ab49b5a83a49d2ea2366b06461e4a559b0111fa750e0de0c138a94d1231ed5979572ff53922905636221994bdabc44bd0c17fef11622b16432db3f193400af53cc61aa9bfc0c4c8d874b41a6e18732f0b60f5662ef1a89c80589dd8366c90bb58bb85ead56356aba2a244950ca170abbd01094539014f84bdd383e4a10e00cee63dfc3e809506e2d9b54edbdca1bace6eaa119e68573d30533791fba830f5d80be5c051a77c09415e3b8fe3139400848be5244b8ae96bb0c4a24f819cba0488f34985eac741d3359180bd72cafa1559e4c19f54ea8cedbb6a5afde4319396eb92aab340c60a50cc2284580cb3ad09017e8d9abc60269b3d8d687680bd86ce834412273d4f2e3bf68dd3d6fe87e2426ac658cd5c77fd5c0aa000000") + testString("/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "bf0320efbcd45d0c5dc79781ac6f20ea5055a036afb48d45a52e7d68ec7d4338919e69") + +} + +func TestBytesToString(t *testing.T) { + + testString := func(s1 string, h string) { + t.Helper() + b, err := hex.DecodeString(h) + if err != nil { + t.Error("failed to decode hex", h) + } + + if err := validateBytes(b); err != nil { + t.Error(err) + } + + s2, err := bytesToString(b) + if err != nil { + t.Log("236", s1, ":", string(h), ":", s2) + t.Error("failed to convert", b, err) + } + + if s1 != s2 { + t.Error("failed to convert", b, "to", s1, "got", s2) + } + } + + testString("/ip4/127.0.0.1/udp/1234", "047f000001910204d2") + testString("/ip4/127.0.0.1/tcp/4321", "047f0000010610e1") + testString("/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321", "047f000001910204d2047f0000010610e1") + testString("/onion/aaimaq4ygg2iegci:80", "bc030010c0439831b48218480050") + testString("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", "bd03adadec040be047f9658668b11a504f3155001f231a37f54c4476c07fb4cc139ed7e30304d2") + testString("/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", + "be0383038d3fc8c976a86ae4e78ba378e75ec41bc9ab1542a9cb422581987e118f5cb0c024f3639d6ad9b3aff613672f07bfbbbfc2f920ef910534ecaa6ff9c03e0fa4872a764d2fce6d4cfc5a5a9800cd95944cc9ef0241f753fe71494a175f334b35682459acadc4076428ab49b5a83a49d2ea2366b06461e4a559b0111fa750e0de0c138a94d1231ed5979572ff53922905636221994bdabc44bd0c17fef11622b16432db3f193400af53cc61aa9bfc0c4c8d874b41a6e18732f0b60f5662ef1a89c80589dd8366c90bb58bb85ead56356aba2a244950ca170abbd01094539014f84bdd383e4a10e00cee63dfc3e809506e2d9b54edbdca1bace6eaa119e68573d30533791fba830f5d80be5c051a77c09415e3b8fe3139400848be5244b8ae96bb0c4a24f819cba0488f34985eac741d3359180bd72cafa1559e4c19f54ea8cedbb6a5afde4319396eb92aab340c60a50cc2284580cb3ad09017e8d9abc60269b3d8d687680bd86ce834412273d4f2e3bf68dd3d6fe87e2426ac658cd5c77fd5c0aa000000") + testString("/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", + "bf0320efbcd45d0c5dc79781ac6f20ea5055a036afb48d45a52e7d68ec7d4338919e69") +} + +func TestBytesSplitAndJoin(t *testing.T) { + + testString := func(s string, res []string) { + m, err := NewMultiaddr(s) + if err != nil { + t.Fatal("failed to convert", s, err) + } + + split := Split(m) + if len(split) != len(res) { + t.Error("not enough split components", split) + return + } + + for i, a := range split { + if a.String() != res[i] { + t.Errorf("split component failed: %s != %s", a, res[i]) + } + } + + joined := Join(split...) + if !m.Equal(joined) { + t.Errorf("joined components failed: %s != %s", m, joined) + } + + for i, a := range split { + if a.String() != res[i] { + t.Errorf("split component failed: %s != %s", a, res[i]) + } + } + } + + testString("/ip4/1.2.3.4/udp/1234", []string{"/ip4/1.2.3.4", "/udp/1234"}) + testString("/ip4/1.2.3.4/tcp/1/ip4/2.3.4.5/udp/2", + []string{"/ip4/1.2.3.4", "/tcp/1", "/ip4/2.3.4.5", "/udp/2"}) + testString("/ip4/1.2.3.4/utp/ip4/2.3.4.5/udp/2/udt", + []string{"/ip4/1.2.3.4", "/utp", "/ip4/2.3.4.5", "/udp/2", "/udt"}) +} + +func TestProtocols(t *testing.T) { + m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234") + } + + ps := m.Protocols() + if ps[0].Code != ProtocolWithName("ip4").Code { + t.Error(ps[0], ProtocolWithName("ip4")) + t.Error("failed to get ip4 protocol") + } + + if ps[1].Code != ProtocolWithName("udp").Code { + t.Error(ps[1], ProtocolWithName("udp")) + t.Error("failed to get udp protocol") + } + +} + +func TestProtocolsWithString(t *testing.T) { + pwn := ProtocolWithName + good := map[string][]Protocol{ + "/ip4": {pwn("ip4")}, + "/ip4/tcp": {pwn("ip4"), pwn("tcp")}, + "ip4/tcp/udp/ip6": {pwn("ip4"), pwn("tcp"), pwn("udp"), pwn("ip6")}, + "////////ip4/tcp": {pwn("ip4"), pwn("tcp")}, + "ip4/udp/////////": {pwn("ip4"), pwn("udp")}, + "////////ip4/tcp////////": {pwn("ip4"), pwn("tcp")}, + } + + for s, ps1 := range good { + ps2, err := ProtocolsWithString(s) + if err != nil { + t.Errorf("ProtocolsWithString(%s) should have succeeded", s) + } + + for i, ps1p := range ps1 { + ps2p := ps2[i] + if ps1p.Code != ps2p.Code { + t.Errorf("mismatch: %s != %s, %s", ps1p.Name, ps2p.Name, s) + } + } + } + + bad := []string{ + "dsijafd", // bogus proto + "/ip4/tcp/fidosafoidsa", // bogus proto + "////////ip4/tcp/21432141/////////", // bogus proto + "////////ip4///////tcp/////////", // empty protos in between + } + + for _, s := range bad { + if _, err := ProtocolsWithString(s); err == nil { + t.Errorf("ProtocolsWithString(%s) should have failed", s) + } + } + +} + +func TestEncapsulate(t *testing.T) { + m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + t.Error(err) + } + + m2, err := NewMultiaddr("/udp/5678") + if err != nil { + t.Error(err) + } + + b := m.Encapsulate(m2) + if s := b.String(); s != "/ip4/127.0.0.1/udp/1234/udp/5678" { + t.Error("encapsulate /ip4/127.0.0.1/udp/1234/udp/5678 failed.", s) + } + + m3, _ := NewMultiaddr("/udp/5678") + c := b.Decapsulate(m3) + if s := c.String(); s != "/ip4/127.0.0.1/udp/1234" { + t.Error("decapsulate /udp failed.", "/ip4/127.0.0.1/udp/1234", s) + } + + m4, _ := NewMultiaddr("/ip4/127.0.0.1") + d := c.Decapsulate(m4) + if d != nil { + t.Error("decapsulate /ip4 failed: ", d) + } +} + +func TestDecapsulateComment(t *testing.T) { + // shows the behavior from the interface comment + m := StringCast("/ip4/1.2.3.4/tcp/80") + rest := m.Decapsulate(StringCast("/tcp/80")) + if rest.String() != "/ip4/1.2.3.4" { + t.Fatalf("Documented behavior is not correct. Expected %v saw %v", "/ip4/1.2.3.4/", rest.String()) + } + + m = StringCast("/ip4/1.2.3.4/tcp/80") + rest = m.Decapsulate(StringCast("/udp/80")) + if !rest.Equal(m) { + t.Fatalf("Documented behavior is not correct. Expected %v saw %v", "/ip4/1.2.3.4/tcp/80", rest.String()) + } + + m = StringCast("/ip4/1.2.3.4/tcp/80") + rest = m.Decapsulate(StringCast("/ip4/1.2.3.4")) + require.Nil(t, rest, "expected a nil multiaddr if we decapsulate everything") +} + +func TestDecapsulate(t *testing.T) { + t.Run("right is nil", func(t *testing.T) { + left := StringCast("/ip4/1.2.3.4/tcp/1") + var right Multiaddr + left.Decapsulate(right) + }) + + testcases := []struct { + left, right, expected string + }{ + {"/ip4/1.2.3.4/tcp/1234", "/ip4/1.2.3.4", ""}, + {"/ip4/1.2.3.4", "/ip4/1.2.3.4/tcp/1234", "/ip4/1.2.3.4"}, + {"/ip4/1.2.3.5/tcp/1234", "/ip4/5.3.2.1", "/ip4/1.2.3.5/tcp/1234"}, + {"/ip4/1.2.3.5/udp/1234/quic-v1", "/udp/1234", "/ip4/1.2.3.5"}, + {"/ip4/1.2.3.6/udp/1234/quic-v1", "/udp/1234/quic-v1", "/ip4/1.2.3.6"}, + {"/ip4/1.2.3.7/tcp/1234", "/ws", "/ip4/1.2.3.7/tcp/1234"}, + {"/dnsaddr/wss.com/tcp/4001", "/ws", "/dnsaddr/wss.com/tcp/4001"}, + {"/dnsaddr/wss.com/tcp/4001/ws", "/wss", "/dnsaddr/wss.com/tcp/4001/ws"}, + {"/dnsaddr/wss.com/ws", "/wss", "/dnsaddr/wss.com/ws"}, + {"/dnsaddr/wss.com/ws", "/dnsaddr/wss.com", ""}, + {"/dnsaddr/wss.com/tcp/4001/wss", "/wss", "/dnsaddr/wss.com/tcp/4001"}, + } + + for _, tc := range testcases { + t.Run(tc.left, func(t *testing.T) { + left := StringCast(tc.left) + right := StringCast(tc.right) + actualMa := left.Decapsulate(right) + + if tc.expected == "" { + require.Nil(t, actualMa, "expected nil") + return + } + + actual := actualMa.String() + expected := StringCast(tc.expected).String() + require.Equal(t, expected, actual) + }) + } +} + +func assertValueForProto(t *testing.T, a Multiaddr, p int, exp string) { + t.Logf("checking for %s in %s", ProtocolWithCode(p).Name, a) + fv, err := a.ValueForProtocol(p) + if err != nil { + t.Fatal(err) + } + + if fv != exp { + t.Fatalf("expected %q for %d in %s, but got %q instead", exp, p, a, fv) + } +} + +func TestGetValue(t *testing.T) { + a := newMultiaddr(t, "/ip4/127.0.0.1/utp/tcp/5555/udp/1234/tls/utp/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") + assertValueForProto(t, a, P_IP4, "127.0.0.1") + assertValueForProto(t, a, P_UTP, "") + assertValueForProto(t, a, P_TLS, "") + assertValueForProto(t, a, P_TCP, "5555") + assertValueForProto(t, a, P_UDP, "1234") + assertValueForProto(t, a, P_IPFS, "QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") + assertValueForProto(t, a, P_P2P, "QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP") + + _, err := a.ValueForProtocol(P_IP6) + switch err { + case ErrProtocolNotFound: + break + case nil: + t.Fatal("expected value lookup to fail") + default: + t.Fatalf("expected ErrProtocolNotFound but got: %s", err) + } + + a = newMultiaddr(t, "/ip4/0.0.0.0") // only one addr + assertValueForProto(t, a, P_IP4, "0.0.0.0") + + a = newMultiaddr(t, "/ip4/0.0.0.0/ip4/0.0.0.0/ip4/0.0.0.0") // same sub-addr + assertValueForProto(t, a, P_IP4, "0.0.0.0") + + a = newMultiaddr(t, "/ip4/0.0.0.0/udp/12345/utp") // ending in a no-value one. + assertValueForProto(t, a, P_IP4, "0.0.0.0") + assertValueForProto(t, a, P_UDP, "12345") + assertValueForProto(t, a, P_UTP, "") + + a = newMultiaddr(t, "/ip4/0.0.0.0/unix/a/b/c/d") // ending in a path one. + assertValueForProto(t, a, P_IP4, "0.0.0.0") + assertValueForProto(t, a, P_UNIX, "/a/b/c/d") +} + +func FuzzNewMultiaddrBytes(f *testing.F) { + for _, v := range good { + ma, err := NewMultiaddr(v) + if err != nil { + f.Fatal(err) + } + f.Add(ma.Bytes()) + } + + f.Fuzz(func(t *testing.T, b []byte) { + // just checking that it doesn't panic + ma, err := NewMultiaddrBytes(b) + if err == nil { + // for any valid multiaddrs, make sure these calls don't panic + ma.Protocols() + roundTripBytes(t, ma) + roundTripString(t, ma) + } + }) +} + +func FuzzNewMultiaddrString(f *testing.F) { + for _, v := range good { + if _, err := NewMultiaddr(v); err != nil { + // Validate maddrs + f.Fatal(err) + } + f.Add(v) + } + f.Fuzz(func(t *testing.T, s string) { + // just checking that it doesn't panic + ma, err := NewMultiaddr(s) + if err == nil { + // for any valid multiaddrs, make sure these calls don't panic + ma.Protocols() + roundTripBytes(t, ma) + roundTripString(t, ma) + } + }) +} + +func roundTripBytes(t *testing.T, orig Multiaddr) { + m2, err := NewMultiaddrBytes(orig.Bytes()) + if err != nil { + t.Fatalf("failed to parse maddr back from ma.Bytes, %v: %v", orig, err) + } + if !m2.Equal(orig) { + t.Fatalf("unequal maddr after roundTripBytes %v %v", orig, m2) + } +} + +func roundTripString(t *testing.T, orig Multiaddr) { + m2, err := NewMultiaddr(orig.String()) + if err != nil { + t.Fatalf("failed to parse maddr back from ma.String, %v: %v", orig, err) + } + if !m2.Equal(orig) { + t.Fatalf("unequal maddr after roundTripString %v %v\n% 02x\n% 02x\n", orig, m2, orig.Bytes(), m2.Bytes()) + } +} + +func TestBinaryRepresentation(t *testing.T) { + expected := []byte{0x4, 0x7f, 0x0, 0x0, 0x1, 0x91, 0x2, 0x4, 0xd2} + ma, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + t.Error(err) + } + + if !bytes.Equal(ma.Bytes(), expected) { + t.Errorf("expected %x, got %x", expected, ma.Bytes()) + } +} + +func TestRoundTrip(t *testing.T) { + for _, s := range []string{ + "/unix/a/b/c/d", + "/ip6/::ffff:127.0.0.1/tcp/111", + "/ip4/127.0.0.1/tcp/123", + "/ip4/127.0.0.1/tcp/123/tls", + "/ip4/127.0.0.1/udp/123", + "/ip4/127.0.0.1/udp/123/ip6/::", + "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g", + "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP", + "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP/unix/a/b/c", + "/http-path/tmp%2Fbar", + } { + ma, err := NewMultiaddr(s) + if err != nil { + t.Errorf("error when parsing %q: %s", s, err) + continue + } + if ma.String() != s { + t.Errorf("failed to round trip %q", s) + } + } +} + +func TestIPFSvP2P(t *testing.T) { + var ( + p2pAddr = "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP" + ipfsAddr = "/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP" + ) + + for _, s := range []string{p2pAddr, ipfsAddr} { + ma, err := NewMultiaddr(s) + if err != nil { + t.Errorf("error when parsing %q: %s", s, err) + } + if ma.String() != p2pAddr { + t.Errorf("expected %q, got %q", p2pAddr, ma.String()) + } + } +} + +func TestInvalidP2PAddrBytes(t *testing.T) { + badAddr := "a503221221c05877cbae039d70a5e600ea02c6f9f2942439285c9e344e26f8d280c850fad6" + bts, err := hex.DecodeString(badAddr) + if err != nil { + t.Fatal(err) + } + ma, err := NewMultiaddrBytes(bts) + if err == nil { + t.Error("should have failed") + // Check for panic + _ = ma.String() + } +} + +func TestInvalidP2PAddrString(t *testing.T) { + hashedData, err := mh.Sum([]byte("test"), mh.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + // using MD5 since it's not a valid data codec + unknownCodecCID := cid.NewCidV1(mh.MD5, hashedData).String() + + badStringAddrs := []string{ + "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl-", // invalid multibase encoding + "/p2p/?unknownmultibase", // invalid multibase encoding + "/p2p/k2jmtxwoe2phm1hbqp0e7nufqf6umvuu2e9qd7ana7h411a0haqj6i2z", // non-libp2p-key codec + "/p2p/" + unknownCodecCID, // impossible codec + } + for _, a := range badStringAddrs { + ma, err := NewMultiaddr(a) + if err == nil { + t.Error("should have failed") + // Check for panic + _ = ma.String() + } + } +} + +func TestZone(t *testing.T) { + ip6String := "/ip6zone/eth0/ip6/::1" + ip6Bytes := []byte{ + 0x2a, 4, + 'e', 't', 'h', '0', + 0x29, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1, + } + + ma, err := NewMultiaddr(ip6String) + if err != nil { + t.Error(err) + } + if !bytes.Equal(ma.Bytes(), ip6Bytes) { + t.Errorf("expected %x, got %x", ip6Bytes, ma.Bytes()) + } + + ma2, err2 := NewMultiaddrBytes(ip6Bytes) + if err2 != nil { + t.Error(err) + } + if ma2.String() != ip6String { + t.Errorf("expected %s, got %s", ip6String, ma2.String()) + } +} + +func TestBinaryMarshaler(t *testing.T) { + addr := newMultiaddr(t, "/ip4/0.0.0.0/tcp/4001/tls") + b, err := addr.MarshalBinary() + if err != nil { + t.Fatal(err) + } + + var addr2 multiaddr + if err = addr2.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + if !addr.Equal(&addr2) { + t.Error("expected equal addresses in circular marshaling test") + } +} + +func TestTextMarshaler(t *testing.T) { + addr := newMultiaddr(t, "/ip4/0.0.0.0/tcp/4001/tls") + b, err := addr.MarshalText() + if err != nil { + t.Fatal(err) + } + + var addr2 multiaddr + if err = addr2.UnmarshalText(b); err != nil { + t.Fatal(err) + } + if !addr.Equal(&addr2) { + t.Error("expected equal addresses in circular marshaling test") + } +} + +func TestJSONMarshaler(t *testing.T) { + addr := newMultiaddr(t, "/ip4/0.0.0.0/tcp/4001/tls") + b, err := addr.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + var addr2 multiaddr + if err = addr2.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + if !addr.Equal(&addr2) { + t.Error("expected equal addresses in circular marshaling test") + } +} + +func TestComponentBinaryMarshaler(t *testing.T) { + comp, err := NewComponent("ip4", "0.0.0.0") + if err != nil { + t.Fatal(err) + } + b, err := comp.MarshalBinary() + if err != nil { + t.Fatal(err) + } + + comp2 := &Component{} + if err = comp2.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + if !comp.Equal(comp2) { + t.Error("expected equal components in circular marshaling test") + } +} + +func TestComponentTextMarshaler(t *testing.T) { + comp, err := NewComponent("ip4", "0.0.0.0") + if err != nil { + t.Fatal(err) + } + b, err := comp.MarshalText() + if err != nil { + t.Fatal(err) + } + + comp2 := &Component{} + if err = comp2.UnmarshalText(b); err != nil { + t.Fatal(err) + } + if !comp.Equal(comp2) { + t.Error("expected equal components in circular marshaling test") + } +} + +func TestComponentJSONMarshaler(t *testing.T) { + comp, err := NewComponent("ip4", "0.0.0.0") + if err != nil { + t.Fatal(err) + } + b, err := comp.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + comp2 := &Component{} + if err = comp2.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + if !comp.Equal(comp2) { + t.Error("expected equal components in circular marshaling test") + } +} + +func TestFilterAddrs(t *testing.T) { + bad := []Multiaddr{ + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), + } + good := []Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/1.1.1.1/tcp/999"), + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), + } + goodAndBad := append(good, bad...) + + filter := func(addr Multiaddr) bool { + return addr.Protocols()[0].Code == P_IP4 + } + + require.Empty(t, FilterAddrs(bad, filter)) + require.ElementsMatch(t, FilterAddrs(good, filter), good) + require.ElementsMatch(t, FilterAddrs(goodAndBad, filter), good) +} + +func TestContains(t *testing.T) { + a1 := newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234") + a2 := newMultiaddr(t, "/ip4/1.1.1.1/tcp/999") + a3 := newMultiaddr(t, "/ip4/1.2.3.4/udp/443/quic") + a4 := newMultiaddr(t, "/ip4/1.2.3.4/udp/443/quic-v1") + addrs := []Multiaddr{a1, a2, a3, a4} + + require.True(t, Contains(addrs, a1)) + require.True(t, Contains(addrs, a2)) + require.True(t, Contains(addrs, a3)) + require.True(t, Contains(addrs, a4)) + require.False(t, Contains(addrs, newMultiaddr(t, "/ip4/4.3.2.1/udp/1234/utp"))) + require.False(t, Contains(nil, a1)) +} + +func TestUniqueAddrs(t *testing.T) { + tcpAddr := StringCast("/ip4/127.0.0.1/tcp/1234") + quicAddr := StringCast("/ip4/127.0.0.1/udp/1234/quic-v1") + wsAddr := StringCast("/ip4/127.0.0.1/tcp/1234/ws") + + type testcase struct { + in, out []Multiaddr + } + + for i, tc := range []testcase{ + {in: nil, out: nil}, + {in: []Multiaddr{tcpAddr}, out: []Multiaddr{tcpAddr}}, + {in: []Multiaddr{tcpAddr, tcpAddr, tcpAddr}, out: []Multiaddr{tcpAddr}}, + {in: []Multiaddr{tcpAddr, quicAddr, tcpAddr}, out: []Multiaddr{tcpAddr, quicAddr}}, + {in: []Multiaddr{tcpAddr, quicAddr, wsAddr}, out: []Multiaddr{tcpAddr, quicAddr, wsAddr}}, + } { + tc := tc + t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { + deduped := Unique(tc.in) + for _, a := range tc.out { + require.Contains(t, deduped, a) + } + }) + } +} + +func BenchmarkUniqueAddrs(b *testing.B) { + b.ReportAllocs() + var addrs []Multiaddr + r := rand.New(rand.NewSource(1234)) + for i := 0; i < 100; i++ { + tcpAddr := StringCast(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", r.Intn(math.MaxUint16))) + quicAddr := StringCast(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic-v1", r.Intn(math.MaxUint16))) + wsAddr := StringCast(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d/ws", r.Intn(math.MaxUint16))) + addrs = append(addrs, tcpAddr, tcpAddr, quicAddr, quicAddr, wsAddr) + } + for _, sz := range []int{10, 20, 30, 50, 100} { + b.Run(fmt.Sprintf("%d", sz), func(b *testing.B) { + items := make([]Multiaddr, sz) + for i := 0; i < b.N; i++ { + copy(items, addrs[:sz]) + Unique(items) + } + }) + } +} + +func TestDNS(t *testing.T) { + b := []byte("7*000000000000000000000000000000000000000000") + a, err := NewMultiaddrBytes(b) + if err != nil { + t.Fatal(err) + } + aa := StringCast(a.String()) + if !a.Equal(aa) { + t.Fatal("expected equality") + } +} + +func TestHTTPPath(t *testing.T) { + t.Run("bad addr", func(t *testing.T) { + badAddr := "/http-path/thisIsMissingAfullByte%f" + _, err := NewMultiaddr(badAddr) + require.Error(t, err) + }) + + t.Run("only reads the http-path part", func(t *testing.T) { + addr := "/http-path/tmp%2Fbar/p2p-circuit" // The http-path only reference the part immediately after it. It does not include the rest of the multiaddr (like the /path component sometimes does) + m, err := NewMultiaddr(addr) + require.NoError(t, err) + m.ValueForProtocol(P_HTTP_PATH) + v, err := m.ValueForProtocol(P_HTTP_PATH) + require.NoError(t, err) + require.Equal(t, "tmp%2Fbar", v) + }) + + t.Run("round trip", func(t *testing.T) { + cases := []string{ + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + "/ip4/127.0.0.1/tcp/0/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV/http-path/foo", + "/ip4/127.0.0.1/tcp/443/tls/sni/example.com/http/http-path/foo", + } + for _, c := range cases { + m, err := NewMultiaddr(c) + require.NoError(t, err) + require.Equal(t, c, m.String()) + } + }) + + t.Run("value for protocol", func(t *testing.T) { + m := StringCast("/http-path/tmp%2Fbar") + v, err := m.ValueForProtocol(P_HTTP_PATH) + require.NoError(t, err) + // This gives us the url escaped version + require.Equal(t, "tmp%2Fbar", v) + + // If we want the raw unescaped version, we can use the component and read it + _, component := SplitLast(m) + require.Equal(t, "tmp/bar", string(component.RawValue())) + }) +} + +func FuzzSplitRoundtrip(f *testing.F) { + for _, v := range good { + f.Add(v) + } + otherMultiaddr := StringCast("/udp/1337") + + f.Fuzz(func(t *testing.T, addrStr string) { + addr, err := NewMultiaddr(addrStr) + if err != nil { + t.Skip() // Skip inputs that are not valid multiaddrs + } + + // Test SplitFirst + first, rest := SplitFirst(addr) + joined := Join(first, rest) + require.Equal(t, addr, joined, "SplitFirst and Join should round-trip") + + // Test SplitLast + rest, last := SplitLast(addr) + joined = Join(rest, last) + require.Equal(t, addr, joined, "SplitLast and Join should round-trip") + + p := addr.Protocols() + if len(p) == 0 { + t.Skip() + } + + tryPubMethods := func(a Multiaddr) { + if a == nil { + return + } + _ = a.Equal(otherMultiaddr) + _ = a.Bytes() + _ = a.String() + _ = a.Protocols() + _ = a.Encapsulate(otherMultiaddr) + _ = a.Decapsulate(otherMultiaddr) + _, _ = a.ValueForProtocol(P_TCP) + } + + for _, proto := range p { + splitFunc := func(c Component) bool { + return c.Protocol().Code == proto.Code + } + beforeC, after := SplitFirst(addr) + joined = Join(beforeC, after) + require.Equal(t, addr, joined) + tryPubMethods(after) + + before, afterC := SplitLast(addr) + joined = Join(before, afterC) + require.Equal(t, addr, joined) + tryPubMethods(before) + + before, after = SplitFunc(addr, splitFunc) + joined = Join(before, after) + require.Equal(t, addr, joined) + tryPubMethods(before) + tryPubMethods(after) + } + }) +} diff --git a/net/convert.go b/net/convert.go new file mode 100644 index 0000000..4603fa2 --- /dev/null +++ b/net/convert.go @@ -0,0 +1,358 @@ +package manet + +import ( + "errors" + "fmt" + "net" + "path/filepath" + "runtime" + "strings" + + ma "github.com/multiformats/go-multiaddr" +) + +var errIncorrectNetAddr = fmt.Errorf("incorrect network addr conversion") +var errNotIP = fmt.Errorf("multiaddr does not start with an IP address") + +// FromNetAddr converts a net.Addr type to a Multiaddr. +func FromNetAddr(a net.Addr) (ma.Multiaddr, error) { + return defaultCodecs.FromNetAddr(a) +} + +// FromNetAddr converts a net.Addr to Multiaddress. +func (cm *CodecMap) FromNetAddr(a net.Addr) (ma.Multiaddr, error) { + if a == nil { + return nil, fmt.Errorf("nil multiaddr") + } + p, err := cm.getAddrParser(a.Network()) + if err != nil { + return nil, err + } + + return p(a) +} + +// ToNetAddr converts a Multiaddr to a net.Addr +// Must be ThinWaist. acceptable protocol stacks are: +// /ip{4,6}/{tcp, udp} +func ToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { + return defaultCodecs.ToNetAddr(maddr) +} + +// ToNetAddr converts a Multiaddress to a standard net.Addr. +func (cm *CodecMap) ToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { + protos := maddr.Protocols() + final := protos[len(protos)-1] + + p, err := cm.getMaddrParser(final.Name) + if err != nil { + return nil, err + } + + return p(maddr) +} + +// MultiaddrToIPNet converts a multiaddr to an IPNet. Useful for seeing if another IP address is contained within this multiaddr network+mask +func MultiaddrToIPNet(m ma.Multiaddr) (*net.IPNet, error) { + var ipString string + var mask string + + ma.ForEach(m, func(c ma.Component) bool { + if c.Protocol().Code == ma.P_IP4 || c.Protocol().Code == ma.P_IP6 { + ipString = c.Value() + } + if c.Protocol().Code == ma.P_IPCIDR { + mask = c.Value() + } + return ipString == "" || mask == "" + }) + + if ipString == "" { + return nil, errors.New("no ip protocol found") + } + + if mask == "" { + return nil, errors.New("no mask found") + } + + _, ipnet, err := net.ParseCIDR(ipString + "/" + string(mask)) + return ipnet, err +} + +func parseBasicNetMaddr(maddr ma.Multiaddr) (net.Addr, error) { + network, host, err := DialArgs(maddr) + if err != nil { + return nil, err + } + + switch network { + case "tcp", "tcp4", "tcp6": + return net.ResolveTCPAddr(network, host) + case "udp", "udp4", "udp6": + return net.ResolveUDPAddr(network, host) + case "ip", "ip4", "ip6": + return net.ResolveIPAddr(network, host) + case "unix": + return net.ResolveUnixAddr(network, host) + } + + return nil, fmt.Errorf("network not supported: %s", network) +} + +func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) { + switch { + case ip.To4() != nil: + return ma.NewComponent("ip4", ip.String()) + case ip.To16() != nil: + ip6, err := ma.NewComponent("ip6", ip.String()) + if err != nil { + return nil, err + } + if zone == "" { + return ip6, nil + } else { + zone, err := ma.NewComponent("ip6zone", zone) + if err != nil { + return nil, err + } + return zone.Encapsulate(ip6), nil + } + default: + return nil, errIncorrectNetAddr + } +} + +// FromIP converts a net.IP type to a Multiaddr. +func FromIP(ip net.IP) (ma.Multiaddr, error) { + return FromIPAndZone(ip, "") +} + +// ToIP converts a Multiaddr to a net.IP when possible +func ToIP(addr ma.Multiaddr) (net.IP, error) { + var ip net.IP + ma.ForEach(addr, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_IP6ZONE: + // we can't return these anyways. + return true + case ma.P_IP6, ma.P_IP4: + ip = net.IP(c.RawValue()) + return false + } + return false + }) + if ip == nil { + return nil, errNotIP + } + return ip, nil +} + +// DialArgs is a convenience function that returns network and address as +// expected by net.Dial. See https://godoc.org/net#Dial for an overview of +// possible return values (we do not support the unixpacket ones yet). Unix +// addresses do not, at present, compose. +func DialArgs(m ma.Multiaddr) (string, string, error) { + zone, network, ip, port, hostname, err := dialArgComponents(m) + if err != nil { + return "", "", err + } + + // If we have a hostname (dns*), we don't want any fancy ipv6 formatting + // logic (zone, brackets, etc.). + if hostname { + switch network { + case "ip", "ip4", "ip6": + return network, ip, nil + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + return network, ip + ":" + port, nil + } + // Hostname is only true when network is one of the above. + panic("unreachable") + } + + switch network { + case "ip6": + if zone != "" { + ip += "%" + zone + } + fallthrough + case "ip4": + return network, ip, nil + case "tcp4", "udp4": + return network, ip + ":" + port, nil + case "tcp6", "udp6": + if zone != "" { + ip += "%" + zone + } + return network, "[" + ip + "]" + ":" + port, nil + case "unix": + if runtime.GOOS == "windows" { + // convert /c:/... to c:\... + ip = filepath.FromSlash(strings.TrimLeft(ip, "/")) + } + return network, ip, nil + default: + return "", "", fmt.Errorf("%s is not a 'thin waist' address", m) + } +} + +// dialArgComponents extracts the raw pieces used in dialing a Multiaddr +func dialArgComponents(m ma.Multiaddr) (zone, network, ip, port string, hostname bool, err error) { + ma.ForEach(m, func(c ma.Component) bool { + switch network { + case "": + switch c.Protocol().Code { + case ma.P_IP6ZONE: + if zone != "" { + err = fmt.Errorf("%s has multiple zones", m) + return false + } + zone = c.Value() + return true + case ma.P_IP6: + network = "ip6" + ip = c.Value() + return true + case ma.P_IP4: + if zone != "" { + err = fmt.Errorf("%s has ip4 with zone", m) + return false + } + network = "ip4" + ip = c.Value() + return true + case ma.P_DNS: + network = "ip" + hostname = true + ip = c.Value() + return true + case ma.P_DNS4: + network = "ip4" + hostname = true + ip = c.Value() + return true + case ma.P_DNS6: + network = "ip6" + hostname = true + ip = c.Value() + return true + case ma.P_UNIX: + network = "unix" + ip = c.Value() + return false + } + case "ip": + switch c.Protocol().Code { + case ma.P_UDP: + network = "udp" + case ma.P_TCP: + network = "tcp" + default: + return false + } + port = c.Value() + case "ip4": + switch c.Protocol().Code { + case ma.P_UDP: + network = "udp4" + case ma.P_TCP: + network = "tcp4" + default: + return false + } + port = c.Value() + case "ip6": + switch c.Protocol().Code { + case ma.P_UDP: + network = "udp6" + case ma.P_TCP: + network = "tcp6" + default: + return false + } + port = c.Value() + } + // Done. + return false + }) + return +} + +func parseTCPNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.TCPAddr) + if !ok { + return nil, errIncorrectNetAddr + } + + // Get IP Addr + ipm, err := FromIPAndZone(ac.IP, ac.Zone) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Get TCP Addr + tcpm, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", ac.Port)) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Encapsulate + return ipm.Encapsulate(tcpm), nil +} + +func parseUDPNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.UDPAddr) + if !ok { + return nil, errIncorrectNetAddr + } + + // Get IP Addr + ipm, err := FromIPAndZone(ac.IP, ac.Zone) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Get UDP Addr + udpm, err := ma.NewMultiaddr(fmt.Sprintf("/udp/%d", ac.Port)) + if err != nil { + return nil, errIncorrectNetAddr + } + + // Encapsulate + return ipm.Encapsulate(udpm), nil +} + +func parseIPNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.IPAddr) + if !ok { + return nil, errIncorrectNetAddr + } + return FromIPAndZone(ac.IP, ac.Zone) +} + +func parseIPPlusNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.IPNet) + if !ok { + return nil, errIncorrectNetAddr + } + return FromIP(ac.IP) +} + +func parseUnixNetAddr(a net.Addr) (ma.Multiaddr, error) { + ac, ok := a.(*net.UnixAddr) + if !ok { + return nil, errIncorrectNetAddr + } + + path := ac.Name + if runtime.GOOS == "windows" { + // Convert c:\foobar\... to c:/foobar/... + path = filepath.ToSlash(path) + } + if len(path) == 0 || path[0] != '/' { + // convert "" and "c:/..." to "/..." + path = "/" + path + } + + return ma.NewComponent("unix", path) +} diff --git a/net/convert_test.go b/net/convert_test.go new file mode 100644 index 0000000..a36f5e9 --- /dev/null +++ b/net/convert_test.go @@ -0,0 +1,266 @@ +package manet + +import ( + "net" + "runtime" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +type GenFunc func() (ma.Multiaddr, error) + +func testConvert(t *testing.T, s string, gen GenFunc) { + m, err := gen() + if err != nil { + t.Fatal("failed to generate.") + } + + if s2 := m.String(); err != nil || s2 != s { + t.Fatal("failed to convert: " + s + " != " + s2) + } +} + +func testToNetAddr(t *testing.T, maddr, ntwk, addr string) { + m, err := ma.NewMultiaddr(maddr) + if err != nil { + t.Fatal("failed to generate.") + } + + naddr, err := ToNetAddr(m) + if addr == "" { // should fail + if err == nil { + t.Fatalf("failed to error: %s", m) + } + return + } + + // shouldn't fail + if err != nil { + t.Fatalf("failed to convert to net addr: %s", m) + } + + if naddr.String() != addr { + t.Fatalf("naddr.Address() == %s != %s", naddr, addr) + } + + if naddr.Network() != ntwk { + t.Fatalf("naddr.Network() == %s != %s", naddr.Network(), ntwk) + } + + // should convert properly + switch ntwk { + case "tcp": + taddr := naddr.(*net.TCPAddr) + if ip, err := ToIP(m); err != nil || !taddr.IP.Equal(ip) { + t.Fatalf("ToIP() and ToNetAddr diverged: %s != %s", taddr, ip) + } + case "udp": + uaddr := naddr.(*net.UDPAddr) + if ip, err := ToIP(m); err != nil || !uaddr.IP.Equal(ip) { + t.Fatalf("ToIP() and ToNetAddr diverged: %s != %s", uaddr, ip) + } + case "ip": + ipaddr := naddr.(*net.IPAddr) + if ip, err := ToIP(m); err != nil || !ipaddr.IP.Equal(ip) { + t.Fatalf("ToIP() and ToNetAddr diverged: %s != %s", ipaddr, ip) + } + } +} + +func TestFromIP4(t *testing.T) { + testConvert(t, "/ip4/10.20.30.40", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.IPAddr{IP: net.ParseIP("10.20.30.40")}) + }) +} + +func TestFromUnix(t *testing.T) { + path := "/C:/foo/bar" + if runtime.GOOS == "windows" { + path = `C:\foo\bar` + } + testConvert(t, "/unix/C:/foo/bar", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.UnixAddr{Name: path, Net: "unix"}) + }) +} + +func TestToUnix(t *testing.T) { + path := "/C:/foo/bar" + if runtime.GOOS == "windows" { + path = `C:\foo\bar` + } + testToNetAddr(t, "/unix/C:/foo/bar", "unix", path) +} + +func TestFromIP6(t *testing.T) { + testConvert(t, "/ip6/2001:4860:0:2001::68", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.IPAddr{IP: net.ParseIP("2001:4860:0:2001::68")}) + }) +} + +func TestFromTCP(t *testing.T) { + testConvert(t, "/ip4/10.20.30.40/tcp/1234", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.TCPAddr{ + IP: net.ParseIP("10.20.30.40"), + Port: 1234, + }) + }) +} + +func TestFromUDP(t *testing.T) { + testConvert(t, "/ip4/10.20.30.40/udp/1234", func() (ma.Multiaddr, error) { + return FromNetAddr(&net.UDPAddr{ + IP: net.ParseIP("10.20.30.40"), + Port: 1234, + }) + }) +} + +func TestThinWaist(t *testing.T) { + addrs := map[string]bool{ + "/ip4/127.0.0.1/udp/1234": true, + "/ip4/127.0.0.1/tcp/1234": true, + "/ip4/127.0.0.1/udp/1234/tcp/1234": true, + "/ip4/127.0.0.1/tcp/12345/ip4/1.2.3.4": true, + "/ip6/::1/tcp/80": true, + "/ip6/::1/udp/80": true, + "/ip6/::1": true, + "/ip6zone/hello/ip6/fe80::1/tcp/80": true, + "/ip6zone/hello/ip6/fe80::1": true, + "/tcp/1234/ip4/1.2.3.4": false, + "/tcp/1234": false, + "/tcp/1234/udp/1234": false, + "/ip4/1.2.3.4/ip4/2.3.4.5": true, + "/ip6/fe80::1/ip4/2.3.4.5": true, + "/ip6zone/hello/ip6/fe80::1/ip4/2.3.4.5": true, + + // Invalid ip6zone usage: + "/ip6zone/hello": false, + "/ip6zone/hello/ip4/1.1.1.1": false, + } + + for a, res := range addrs { + m, err := ma.NewMultiaddr(a) + if err != nil { + t.Fatalf("failed to construct Multiaddr: %s", a) + } + + if IsThinWaist(m) != res { + t.Fatalf("IsThinWaist(%s) != %v", a, res) + } + } +} + +func TestDialArgs(t *testing.T) { + test := func(e_maddr, e_nw, e_host string) { + m, err := ma.NewMultiaddr(e_maddr) + if err != nil { + t.Fatal("failed to construct", e_maddr) + } + + nw, host, err := DialArgs(m) + if err != nil { + t.Fatal("failed to get dial args", e_maddr, m, err) + } + + if nw != e_nw { + t.Error("failed to get udp network Dial Arg", e_nw, nw) + } + + if host != e_host { + t.Error("failed to get host:port Dial Arg", e_host, host) + } + } + + test_error := func(e_maddr string) { + m, err := ma.NewMultiaddr(e_maddr) + if err != nil { + t.Fatal("failed to construct", e_maddr) + } + + _, _, err = DialArgs(m) + if err == nil { + t.Fatal("expected DialArgs to fail on", e_maddr) + } + } + + test("/ip4/127.0.0.1/udp/1234", "udp4", "127.0.0.1:1234") + test("/ip4/127.0.0.1/tcp/4321", "tcp4", "127.0.0.1:4321") + test("/ip6/::1/udp/1234", "udp6", "[::1]:1234") + test("/ip6/::1/tcp/4321", "tcp6", "[::1]:4321") + test("/ip6/::1", "ip6", "::1") // Just an IP + test("/ip4/1.2.3.4", "ip4", "1.2.3.4") // Just an IP + test("/ip6zone/foo/ip6/::1/tcp/4321", "tcp6", "[::1%foo]:4321") // zone + test("/ip6zone/foo/ip6/::1/udp/4321", "udp6", "[::1%foo]:4321") // zone + test("/ip6zone/foo/ip6/::1", "ip6", "::1%foo") // no TCP + test_error("/ip6zone/foo/ip4/127.0.0.1") // IP4 doesn't take zone + test("/ip6zone/foo/ip6/::1/ip6zone/bar", "ip6", "::1%foo") // IP over IP + test_error("/ip6zone/foo/ip6zone/bar/ip6/::1") // Only one zone per IP6 + test("/dns/abc.com/tcp/1234", "tcp", "abc.com:1234") // DNS4:port + test("/dns4/abc.com/tcp/1234", "tcp4", "abc.com:1234") // DNS4:port + test("/dns4/abc.com", "ip4", "abc.com") // Just DNS4 + test("/dns6/abc.com/udp/1234", "udp6", "abc.com:1234") // DNS6:port + test("/dns6/abc.com", "ip6", "abc.com") // Just DNS6 +} + +func TestMultiaddrToIPNet(t *testing.T) { + type testCase struct { + name string + ma string + ips []string + contained []bool + } + + testCases := []testCase{ + { + name: "basic", + ma: "/ip4/1.2.3.0/ipcidr/24", + ips: []string{"1.2.3.4", "1.2.3.9", "2.1.1.1"}, + contained: []bool{true, true, false}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ma := ma.StringCast(tc.ma) + + ipnet, err := MultiaddrToIPNet(ma) + if err != nil { + t.Fatalf("failed to parse multiaddr %v into ipnet", ma) + } + for i, ipString := range tc.ips { + ip := net.ParseIP(ipString) + if ip == nil { + t.Fatalf("failed to parse IP %s", ipString) + } + if ipnet.Contains(ip) != tc.contained[i] { + t.Fatalf("Contains check failed. Expected %v got %v", tc.contained[i], ipnet.Contains(ip)) + } + } + }) + } +} + +func TestFailMultiaddrToIPNet(t *testing.T) { + type testCase struct { + name string + ma string + } + + testCases := []testCase{ + {name: "missing ip addr", ma: "/ipcidr/24"}, + {name: "wrong mask", ma: "/ip4/1.2.3.0/ipcidr/128"}, + {name: "wrong mask", ma: "/ip6/::/ipcidr/255"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ma := ma.StringCast(tc.ma) + + _, err := MultiaddrToIPNet(ma) + if err == nil { + t.Fatalf("Expected error when parsing: %s", tc.ma) + } + }) + } +} diff --git a/net/doc.go b/net/doc.go new file mode 100644 index 0000000..040ad3f --- /dev/null +++ b/net/doc.go @@ -0,0 +1,5 @@ +// Package manet provides Multiaddr specific versions of common +// functions in stdlib's net package. This means wrappers of +// standard net symbols like net.Dial and net.Listen, as well +// as conversion to/from net.Addr. +package manet diff --git a/net/ip.go b/net/ip.go new file mode 100644 index 0000000..def9321 --- /dev/null +++ b/net/ip.go @@ -0,0 +1,132 @@ +package manet + +import ( + "net" + + ma "github.com/multiformats/go-multiaddr" +) + +// Loopback Addresses +var ( + // IP4Loopback is the ip4 loopback multiaddr + IP4Loopback = ma.StringCast("/ip4/127.0.0.1") + + // IP6Loopback is the ip6 loopback multiaddr + IP6Loopback = ma.StringCast("/ip6/::1") + + // IP4MappedIP6Loopback is the IPv4 Mapped IPv6 loopback address. + IP4MappedIP6Loopback = ma.StringCast("/ip6/::ffff:127.0.0.1") +) + +// Unspecified Addresses (used for ) +var ( + IP4Unspecified = ma.StringCast("/ip4/0.0.0.0") + IP6Unspecified = ma.StringCast("/ip6/::") +) + +// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols. +// This means: /{IP4, IP6}[/{TCP, UDP}] +func IsThinWaist(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + p := m.Protocols() + + // nothing? not even a waist. + if len(p) == 0 { + return false + } + + if p[0].Code != ma.P_IP4 && p[0].Code != ma.P_IP6 { + return false + } + + // only IP? still counts. + if len(p) == 1 { + return true + } + + switch p[1].Code { + case ma.P_TCP, ma.P_UDP, ma.P_IP4, ma.P_IP6: + return true + default: + return false + } +} + +// IsIPLoopback returns whether a Multiaddr starts with a "Loopback" IP address +// This means either /ip4/127.*.*.*/*, /ip6/::1/*, or /ip6/::ffff:127.*.*.*.*/*, +// or /ip6zone//ip6//* +func IsIPLoopback(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + c, _ := ma.SplitFirst(m) + if c == nil { + return false + } + switch c.Protocol().Code { + case ma.P_IP4, ma.P_IP6: + return net.IP(c.RawValue()).IsLoopback() + } + return false +} + +// IsIP6LinkLocal returns whether a Multiaddr starts with an IPv6 link-local +// multiaddress (with zero or one leading zone). These addresses are non +// routable. +func IsIP6LinkLocal(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + c, _ := ma.SplitFirst(m) + if c == nil || c.Protocol().Code != ma.P_IP6 { + return false + } + ip := net.IP(c.RawValue()) + return ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() +} + +// IsIPUnspecified returns whether a Multiaddr starts with an Unspecified IP address +// This means either /ip4/0.0.0.0/* or /ip6/::/* +func IsIPUnspecified(m ma.Multiaddr) bool { + m = zoneless(m) + if m == nil { + return false + } + c, _ := ma.SplitFirst(m) + return net.IP(c.RawValue()).IsUnspecified() +} + +// If m matches [zone,ip6,...], return [ip6,...] +// else if m matches [], [zone], or [zone,...], return nil +// else return m +func zoneless(m ma.Multiaddr) ma.Multiaddr { + head, tail := ma.SplitFirst(m) + if head == nil { + return nil + } + if head.Protocol().Code == ma.P_IP6ZONE { + if tail == nil { + return nil + } + tailhead, _ := ma.SplitFirst(tail) + if tailhead.Protocol().Code != ma.P_IP6 { + return nil + } + return tail + } else { + return m + } +} + +// IsNAT64IPv4ConvertedIPv6Addr returns whether addr is a well-known prefix "64:ff9b::/96" addr +// used for NAT64 Translation. See RFC 6052 +func IsNAT64IPv4ConvertedIPv6Addr(addr ma.Multiaddr) bool { + c, _ := ma.SplitFirst(addr) + return c != nil && c.Protocol().Code == ma.P_IP6 && + inAddrRange(c.RawValue(), nat64) +} diff --git a/net/ip_test.go b/net/ip_test.go new file mode 100644 index 0000000..4d394b7 --- /dev/null +++ b/net/ip_test.go @@ -0,0 +1,54 @@ +package manet + +import ( + "fmt" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestIsWellKnownPrefixIPv4ConvertedIPv6Address(t *testing.T) { + cases := []struct { + addr ma.Multiaddr + want bool + failureReason string + }{ + { + addr: ma.StringCast("/ip4/1.2.3.4/tcp/1234"), + want: false, + failureReason: "ip4 addresses should return false", + }, + { + addr: ma.StringCast("/ip6/1::4/tcp/1234"), + want: false, + failureReason: "ip6 addresses doesn't have well-known prefix", + }, + { + addr: ma.StringCast("/ip6/::1/tcp/1234"), + want: false, + failureReason: "localhost addresses should return false", + }, + { + addr: ma.StringCast("/ip6/64:ff9b::192.0.1.2/tcp/1234"), + want: true, + failureReason: "ip6 address begins with well-known prefix", + }, + { + addr: ma.StringCast("/ip6/64:ff9b::1:192.0.1.2/tcp/1234"), + want: false, + failureReason: "64:ff9b::1 is not well-known prefix", + }, + { + addr: ma.StringCast("/ip6/64:ff9b:1::1:192.0.1.2/tcp/1234"), + want: true, + failureReason: "64:ff9b:1::1 is allowed for NAT64 translation", + }, + } + for i, tc := range cases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + if IsNAT64IPv4ConvertedIPv6Addr(tc.addr) != tc.want { + t.Fatalf("%s %s", tc.addr, tc.failureReason) + } + }) + } +} diff --git a/net/net.go b/net/net.go new file mode 100644 index 0000000..10fcff7 --- /dev/null +++ b/net/net.go @@ -0,0 +1,430 @@ +// Package manet provides Multiaddr +// (https://github.com/multiformats/go-multiaddr) specific versions of common +// functions in Go's standard `net` package. This means wrappers of standard +// net symbols like `net.Dial` and `net.Listen`, as well as conversion to +// and from `net.Addr`. +package manet + +import ( + "context" + "fmt" + "net" + + ma "github.com/multiformats/go-multiaddr" +) + +// Conn is the equivalent of a net.Conn object. It is the +// result of calling the Dial or Listen functions in this +// package, with associated local and remote Multiaddrs. +type Conn interface { + net.Conn + + // LocalMultiaddr returns the local Multiaddr associated + // with this connection + LocalMultiaddr() ma.Multiaddr + + // RemoteMultiaddr returns the remote Multiaddr associated + // with this connection + RemoteMultiaddr() ma.Multiaddr +} + +type halfOpen interface { + net.Conn + CloseRead() error + CloseWrite() error +} + +func wrap(nconn net.Conn, laddr, raddr ma.Multiaddr) Conn { + endpts := maEndpoints{ + laddr: laddr, + raddr: raddr, + } + // This sucks. However, it's the only way to reliably expose the + // underlying methods. This way, users that need access to, e.g., + // CloseRead and CloseWrite, can do so via type assertions. + switch nconn := nconn.(type) { + case *net.TCPConn: + return &struct { + *net.TCPConn + maEndpoints + }{nconn, endpts} + case *net.UDPConn: + return &struct { + *net.UDPConn + maEndpoints + }{nconn, endpts} + case *net.IPConn: + return &struct { + *net.IPConn + maEndpoints + }{nconn, endpts} + case *net.UnixConn: + return &struct { + *net.UnixConn + maEndpoints + }{nconn, endpts} + case halfOpen: + return &struct { + halfOpen + maEndpoints + }{nconn, endpts} + default: + return &struct { + net.Conn + maEndpoints + }{nconn, endpts} + } +} + +// WrapNetConn wraps a net.Conn object with a Multiaddr friendly Conn. +// +// This function does it's best to avoid "hiding" methods exposed by the wrapped +// type. Guarantees: +// +// - If the wrapped connection exposes the "half-open" closer methods +// (CloseWrite, CloseRead), these will be available on the wrapped connection +// via type assertions. +// - If the wrapped connection is a UnixConn, IPConn, TCPConn, or UDPConn, all +// methods on these wrapped connections will be available via type assertions. +func WrapNetConn(nconn net.Conn) (Conn, error) { + if nconn == nil { + return nil, fmt.Errorf("failed to convert nconn.LocalAddr: nil") + } + + laddr, err := FromNetAddr(nconn.LocalAddr()) + if err != nil { + return nil, fmt.Errorf("failed to convert nconn.LocalAddr: %s", err) + } + + raddr, err := FromNetAddr(nconn.RemoteAddr()) + if err != nil { + return nil, fmt.Errorf("failed to convert nconn.RemoteAddr: %s", err) + } + + return wrap(nconn, laddr, raddr), nil +} + +type maEndpoints struct { + laddr ma.Multiaddr + raddr ma.Multiaddr +} + +// LocalMultiaddr returns the local address associated with +// this connection +func (c *maEndpoints) LocalMultiaddr() ma.Multiaddr { + return c.laddr +} + +// RemoteMultiaddr returns the remote address associated with +// this connection +func (c *maEndpoints) RemoteMultiaddr() ma.Multiaddr { + return c.raddr +} + +// Dialer contains options for connecting to an address. It +// is effectively the same as net.Dialer, but its LocalAddr +// and RemoteAddr options are Multiaddrs, instead of net.Addrs. +type Dialer struct { + + // Dialer is just an embedded net.Dialer, with all its options. + net.Dialer + + // LocalAddr is the local address to use when dialing an + // address. The address must be of a compatible type for the + // network being dialed. + // If nil, a local address is automatically chosen. + LocalAddr ma.Multiaddr +} + +// Dial connects to a remote address, using the options of the +// Dialer. Dialer uses an underlying net.Dialer to Dial a +// net.Conn, then wraps that in a Conn object (with local and +// remote Multiaddrs). +func (d *Dialer) Dial(remote ma.Multiaddr) (Conn, error) { + return d.DialContext(context.Background(), remote) +} + +// DialContext allows to provide a custom context to Dial(). +func (d *Dialer) DialContext(ctx context.Context, remote ma.Multiaddr) (Conn, error) { + // if a LocalAddr is specified, use it on the embedded dialer. + if d.LocalAddr != nil { + // convert our multiaddr to net.Addr friendly + naddr, err := ToNetAddr(d.LocalAddr) + if err != nil { + return nil, err + } + + // set the dialer's LocalAddr as naddr + d.Dialer.LocalAddr = naddr + } + + // get the net.Dial friendly arguments from the remote addr + rnet, rnaddr, err := DialArgs(remote) + if err != nil { + return nil, err + } + + // ok, Dial! + var nconn net.Conn + switch rnet { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "unix": + nconn, err = d.Dialer.DialContext(ctx, rnet, rnaddr) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unrecognized network: %s", rnet) + } + + // get local address (pre-specified or assigned within net.Conn) + local := d.LocalAddr + // This block helps us avoid parsing addresses in transports (such as unix + // sockets) that don't have local addresses when dialing out. + if local == nil && nconn.LocalAddr().String() != "" { + local, err = FromNetAddr(nconn.LocalAddr()) + if err != nil { + return nil, err + } + } + return wrap(nconn, local, remote), nil +} + +// Dial connects to a remote address. It uses an underlying net.Conn, +// then wraps it in a Conn object (with local and remote Multiaddrs). +func Dial(remote ma.Multiaddr) (Conn, error) { + return (&Dialer{}).Dial(remote) +} + +// A Listener is a generic network listener for stream-oriented protocols. +// it uses an embedded net.Listener, overriding net.Listener.Accept to +// return a Conn and providing Multiaddr. +type Listener interface { + // Accept waits for and returns the next connection to the listener. + // Returns a Multiaddr friendly Conn + Accept() (Conn, error) + + // Close closes the listener. + // Any blocked Accept operations will be unblocked and return errors. + Close() error + + // Multiaddr returns the listener's (local) Multiaddr. + Multiaddr() ma.Multiaddr + + // Addr returns the net.Listener's network address. + Addr() net.Addr +} + +type netListenerAdapter struct { + Listener +} + +func (nla *netListenerAdapter) Accept() (net.Conn, error) { + return nla.Listener.Accept() +} + +// NetListener turns this Listener into a net.Listener. +// +// - Connections returned from Accept implement multiaddr/net Conn. +// - Calling WrapNetListener on the net.Listener returned by this function will +// return the original (underlying) multiaddr/net Listener. +func NetListener(l Listener) net.Listener { + return &netListenerAdapter{l} +} + +// maListener implements Listener +type maListener struct { + net.Listener + laddr ma.Multiaddr +} + +// Accept waits for and returns the next connection to the listener. +// Returns a Multiaddr friendly Conn +func (l *maListener) Accept() (Conn, error) { + nconn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + var raddr ma.Multiaddr + // This block protects us in transports (i.e. unix sockets) that don't have + // remote addresses for inbound connections. + if addr := nconn.RemoteAddr(); addr != nil && addr.String() != "" { + raddr, err = FromNetAddr(addr) + if err != nil { + return nil, fmt.Errorf("failed to convert conn.RemoteAddr: %s", err) + } + } + + var laddr ma.Multiaddr + if addr := nconn.LocalAddr(); addr != nil && addr.String() != "" { + laddr, err = FromNetAddr(addr) + if err != nil { + return nil, fmt.Errorf("failed to convert conn.LocalAddr: %s", err) + } + } + + return wrap(nconn, laddr, raddr), nil +} + +// Multiaddr returns the listener's (local) Multiaddr. +func (l *maListener) Multiaddr() ma.Multiaddr { + return l.laddr +} + +// Addr returns the listener's network address. +func (l *maListener) Addr() net.Addr { + return l.Listener.Addr() +} + +// Listen announces on the local network address laddr. +// The Multiaddr must be a "ThinWaist" stream-oriented network: +// ip4/tcp, ip6/tcp, (TODO: unix, unixpacket) +// See Dial for the syntax of laddr. +func Listen(laddr ma.Multiaddr) (Listener, error) { + + // get the net.Listen friendly arguments from the remote addr + lnet, lnaddr, err := DialArgs(laddr) + if err != nil { + return nil, err + } + + nl, err := net.Listen(lnet, lnaddr) + if err != nil { + return nil, err + } + + // we want to fetch the new multiaddr from the listener, as it may + // have resolved to some other value. WrapNetListener does it for us. + return WrapNetListener(nl) +} + +// WrapNetListener wraps a net.Listener with a manet.Listener. +func WrapNetListener(nl net.Listener) (Listener, error) { + if nla, ok := nl.(*netListenerAdapter); ok { + return nla.Listener, nil + } + + laddr, err := FromNetAddr(nl.Addr()) + if err != nil { + return nil, err + } + + return &maListener{ + Listener: nl, + laddr: laddr, + }, nil +} + +// A PacketConn is a generic packet oriented network connection which uses an +// underlying net.PacketConn, wrapped with the locally bound Multiaddr. +type PacketConn interface { + net.PacketConn + + LocalMultiaddr() ma.Multiaddr + + ReadFromMultiaddr(b []byte) (int, ma.Multiaddr, error) + WriteToMultiaddr(b []byte, maddr ma.Multiaddr) (int, error) +} + +// maPacketConn implements PacketConn +type maPacketConn struct { + net.PacketConn + laddr ma.Multiaddr +} + +var _ PacketConn = (*maPacketConn)(nil) + +// LocalMultiaddr returns the bound local Multiaddr. +func (l *maPacketConn) LocalMultiaddr() ma.Multiaddr { + return l.laddr +} + +func (l *maPacketConn) ReadFromMultiaddr(b []byte) (int, ma.Multiaddr, error) { + n, addr, err := l.ReadFrom(b) + maddr, _ := FromNetAddr(addr) + return n, maddr, err +} + +func (l *maPacketConn) WriteToMultiaddr(b []byte, maddr ma.Multiaddr) (int, error) { + addr, err := ToNetAddr(maddr) + if err != nil { + return 0, err + } + return l.WriteTo(b, addr) +} + +// ListenPacket announces on the local network address laddr. +// The Multiaddr must be a packet driven network, like udp4 or udp6. +// See Dial for the syntax of laddr. +func ListenPacket(laddr ma.Multiaddr) (PacketConn, error) { + lnet, lnaddr, err := DialArgs(laddr) + if err != nil { + return nil, err + } + + pc, err := net.ListenPacket(lnet, lnaddr) + if err != nil { + return nil, err + } + + // We want to fetch the new multiaddr from the listener, as it may + // have resolved to some other value. WrapPacketConn does this. + return WrapPacketConn(pc) +} + +// WrapPacketConn wraps a net.PacketConn with a manet.PacketConn. +func WrapPacketConn(pc net.PacketConn) (PacketConn, error) { + laddr, err := FromNetAddr(pc.LocalAddr()) + if err != nil { + return nil, err + } + + return &maPacketConn{ + PacketConn: pc, + laddr: laddr, + }, nil +} + +// InterfaceMultiaddrs will return the addresses matching net.InterfaceAddrs +func InterfaceMultiaddrs() ([]ma.Multiaddr, error) { + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, err + } + + maddrs := make([]ma.Multiaddr, len(addrs)) + for i, a := range addrs { + maddrs[i], err = FromNetAddr(a) + if err != nil { + return nil, err + } + } + return maddrs, nil +} + +// AddrMatch returns the Multiaddrs that match the protocol stack on addr +func AddrMatch(match ma.Multiaddr, addrs []ma.Multiaddr) []ma.Multiaddr { + + // we should match transports entirely. + p1s := match.Protocols() + + out := make([]ma.Multiaddr, 0, len(addrs)) + for _, a := range addrs { + p2s := a.Protocols() + if len(p1s) != len(p2s) { + continue + } + + match := true + for i, p2 := range p2s { + if p1s[i].Code != p2.Code { + match = false + break + } + } + if match { + out = append(out, a) + } + } + return out +} diff --git a/net/net_test.go b/net/net_test.go new file mode 100644 index 0000000..86cfc50 --- /dev/null +++ b/net/net_test.go @@ -0,0 +1,693 @@ +package manet + +import ( + "bytes" + "fmt" + "net" + "os" + "path/filepath" + "sync" + "testing" + "time" + + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +func newMultiaddr(t *testing.T, m string) ma.Multiaddr { + maddr, err := ma.NewMultiaddr(m) + if err != nil { + t.Fatal("failed to construct multiaddr:", m, err) + } + return maddr +} + +func TestDial(t *testing.T) { + + listener, err := net.Listen("tcp", "127.0.0.1:4321") + if err != nil { + t.Fatal("failed to listen") + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + cB, err := listener.Accept() + if err != nil { + t.Error("failed to accept") + } + + // echo out + buf := make([]byte, 1024) + for { + _, err := cB.Read(buf) + if err != nil { + break + } + cB.Write(buf) + } + + wg.Done() + }() + + maddr := newMultiaddr(t, "/ip4/127.0.0.1/tcp/4321") + cA, err := Dial(maddr) + if err != nil { + t.Fatal("failed to dial") + } + + buf := make([]byte, 1024) + if _, err := cA.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write:", err) + } + + if _, err := cA.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echo:", buf) + } + + maddr2 := cA.RemoteMultiaddr() + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cA.Close() + wg.Wait() +} + +func TestUnixSockets(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), "manettest") + if err != nil { + t.Fatal(err) + } + path := filepath.Join(dir, "listen.sock") + maddr := newMultiaddr(t, "/unix/"+path) + + listener, err := Listen(maddr) + if err != nil { + t.Fatal(err) + } + + payload := []byte("hello") + + // listen + done := make(chan struct{}, 1) + go func() { + conn, err := listener.Accept() + if err != nil { + t.Error(err) + } + defer conn.Close() + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + t.Error(err) + } + if n != len(payload) { + t.Error("failed to read appropriate number of bytes") + } + if !bytes.Equal(buf[0:n], payload) { + t.Error("payload did not match") + } + done <- struct{}{} + }() + + // dial + conn, err := Dial(maddr) + if err != nil { + t.Fatal(err) + } + n, err := conn.Write(payload) + if err != nil { + t.Fatal(err) + } + if n != len(payload) { + t.Fatal("failed to write appropriate number of bytes") + } + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for read") + } +} + +func TestListen(t *testing.T) { + + maddr := newMultiaddr(t, "/ip4/127.0.0.1/tcp/4322") + listener, err := Listen(maddr) + if err != nil { + t.Fatal("failed to listen") + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + cB, err := listener.Accept() + if err != nil { + t.Error("failed to accept") + } + + if !cB.LocalMultiaddr().Equal(maddr) { + t.Error("local multiaddr not equal:", maddr, cB.LocalMultiaddr()) + } + + // echo out + buf := make([]byte, 1024) + for { + _, err := cB.Read(buf) + if err != nil { + break + } + cB.Write(buf) + } + + wg.Done() + }() + + cA, err := net.Dial("tcp", "127.0.0.1:4322") + if err != nil { + t.Fatal("failed to dial") + } + + buf := make([]byte, 1024) + if _, err := cA.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write:", err) + } + + if _, err := cA.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echo:", buf) + } + + maddr2, err := FromNetAddr(cA.RemoteAddr()) + if err != nil { + t.Fatal("failed to convert", err) + } + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cA.Close() + wg.Wait() +} + +func TestListenAddrs(t *testing.T) { + + test := func(addr, resaddr string, succeed bool) { + if resaddr == "" { + resaddr = addr + } + + maddr := newMultiaddr(t, addr) + l, err := Listen(maddr) + if !succeed { + if err == nil { + t.Fatal("succeeded in listening", addr) + } + return + } + if succeed && err != nil { + t.Error("failed to listen", addr, err) + } + if l == nil { + t.Error("failed to listen", addr, succeed, err) + } + if l.Multiaddr().String() != resaddr { + t.Error("listen addr did not resolve properly", l.Multiaddr().String(), resaddr, succeed, err) + } + + if err = l.Close(); err != nil { + t.Fatal("failed to close listener", addr, err) + } + } + + test("/ip4/127.0.0.1/tcp/4324", "", true) + test("/ip4/127.0.0.1/udp/4325", "", false) + test("/ip4/127.0.0.1/udp/4326/udt", "", false) + + if len(os.Getenv("CI")) > 0 { + test("/ip4/0.0.0.0/tcp/4324", "", true) + test("/ip4/0.0.0.0/udp/4325", "", false) + test("/ip4/0.0.0.0/udp/4326/udt", "", false) + + test("/ip6/::1/tcp/4324", "", true) + test("/ip6/::1/udp/4325", "", false) + test("/ip6/::1/udp/4326/udt", "", false) + test("/ip6/::/tcp/4324", "", true) + test("/ip6/::/udp/4325", "", false) + test("/ip6/::/udp/4326/udt", "", false) + + /* "An implementation should also support the concept of a "default" + * zone for each scope. And, when supported, the index value zero + * at each scope SHOULD be reserved to mean "use the default zone"." + * -- rfc4007. So, this _should_ work everywhere(?). */ + test("/ip6zone/0/ip6/::1/tcp/4324", "/ip6/::1/tcp/4324", true) + test("/ip6zone/0/ip6/::1/udp/4324", "", false) + } else { + t.Skip("all tests only run on CI") + } +} + +func TestListenAndDial(t *testing.T) { + + maddr := newMultiaddr(t, "/ip4/127.0.0.1/tcp/4323") + listener, err := Listen(maddr) + if err != nil { + t.Fatal("failed to listen") + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + cB, err := listener.Accept() + if err != nil { + t.Error("failed to accept") + } + + if !cB.LocalMultiaddr().Equal(maddr) { + t.Error("local multiaddr not equal:", maddr, cB.LocalMultiaddr()) + } + + // echo out + buf := make([]byte, 1024) + for { + _, err := cB.Read(buf) + if err != nil { + break + } + cB.Write(buf) + } + + wg.Done() + }() + + cA, err := Dial(newMultiaddr(t, "/ip4/127.0.0.1/tcp/4323")) + if err != nil { + t.Fatal("failed to dial") + } + + buf := make([]byte, 1024) + if _, err := cA.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write:", err) + } + + if _, err := cA.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echo:", buf) + } + + maddr2 := cA.RemoteMultiaddr() + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cA.Close() + wg.Wait() +} + +func TestListenPacketAndDial(t *testing.T) { + maddr := newMultiaddr(t, "/ip4/127.0.0.1/udp/4324") + pc, err := ListenPacket(maddr) + if err != nil { + t.Fatal("failed to listen", err) + } + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + if !pc.LocalMultiaddr().Equal(maddr) { + t.Error("connection multiaddr not equal:", maddr, pc.LocalMultiaddr()) + } + + buffer := make([]byte, 1024) + _, addr, err := pc.ReadFrom(buffer) + if err != nil { + t.Error("failed to read into buffer", err) + } + pc.WriteTo(buffer, addr) + + wg.Done() + }() + + cn, err := Dial(maddr) + if err != nil { + t.Fatal("failed to dial", err) + } + + buf := make([]byte, 1024) + if _, err := cn.Write([]byte("beep boop")); err != nil { + t.Fatal("failed to write", err) + } + + if _, err := cn.Read(buf); err != nil { + t.Fatal("failed to read:", buf, err) + } + + if !bytes.Equal(buf[:9], []byte("beep boop")) { + t.Fatal("failed to echk:", buf) + } + + maddr2 := cn.RemoteMultiaddr() + if !maddr2.Equal(maddr) { + t.Fatal("remote multiaddr not equal:", maddr, maddr2) + } + + cn.Close() + pc.Close() + wg.Wait() +} + +func TestIPLoopback(t *testing.T) { + if IP4Loopback.String() != "/ip4/127.0.0.1" { + t.Error("IP4Loopback incorrect:", IP4Loopback) + } + + if IP6Loopback.String() != "/ip6/::1" { + t.Error("IP6Loopback incorrect:", IP6Loopback) + } + + if IP4MappedIP6Loopback.String() != "/ip6/::ffff:127.0.0.1" { + t.Error("IP4MappedIP6Loopback incorrect:", IP4MappedIP6Loopback) + } + + if !IsIPLoopback(IP4Loopback) { + t.Error("IsIPLoopback failed (IP4Loopback)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip4/127.1.80.9")) { + t.Error("IsIPLoopback failed (/ip4/127.1.80.9)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip4/112.123.11.1")) { + t.Error("IsIPLoopback false positive (/ip4/112.123.11.1)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip4/192.168.0.1/ip6/::1")) { + t.Error("IsIPLoopback false positive (/ip4/192.168.0.1/ip6/::1)") + } + + if !IsIPLoopback(IP6Loopback) { + t.Error("IsIPLoopback failed (IP6Loopback)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6/127.0.0.1")) { + t.Error("IsIPLoopback failed (/ip6/127.0.0.1)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6/127.99.3.2")) { + t.Error("IsIPLoopback failed (/ip6/127.99.3.2)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip6/::fffa:127.99.3.2")) { + t.Error("IsIPLoopback false positive (/ip6/::fffa:127.99.3.2)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/::1")) { + t.Error("IsIPLoopback failed (/ip6zone/0/ip6/::1)") + } + + if !IsIPLoopback(newMultiaddr(t, "/ip6zone/xxx/ip6/::1")) { + t.Error("IsIPLoopback failed (/ip6zone/xxx/ip6/::1)") + } + + if IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/1::1")) { + t.Errorf("IsIPLoopback false positive (/ip6zone/0/ip6/1::1)") + } +} + +func TestIPUnspecified(t *testing.T) { + if IP4Unspecified.String() != "/ip4/0.0.0.0" { + t.Error("IP4Unspecified incorrect:", IP4Unspecified) + } + + if IP6Unspecified.String() != "/ip6/::" { + t.Error("IP6Unspecified incorrect:", IP6Unspecified) + } + + if !IsIPUnspecified(IP4Unspecified) { + t.Error("IsIPUnspecified failed (IP4Unspecified)") + } + + if !IsIPUnspecified(IP6Unspecified) { + t.Error("IsIPUnspecified failed (IP6Unspecified)") + } + + if !IsIPUnspecified(newMultiaddr(t, "/ip6zone/xxx/ip6/::")) { + t.Error("IsIPUnspecified failed (/ip6zone/xxx/ip6/::)") + } +} + +func TestIP6LinkLocal(t *testing.T) { + for a := 0; a < 65536; a++ { + isLinkLocal := a&0xffc0 == 0xfe80 || a&0xff0f == 0xff02 + m := newMultiaddr(t, fmt.Sprintf("/ip6/%x::1", a)) + if IsIP6LinkLocal(m) != isLinkLocal { + t.Errorf("IsIP6LinkLocal failed (%s != %v)", m, isLinkLocal) + } + } + + if !IsIP6LinkLocal(newMultiaddr(t, "/ip6zone/hello/ip6/fe80::9999")) { + t.Error("IsIP6LinkLocal failed (/ip6/fe80::9999)") + } + + bad := []ma.Multiaddr{ + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), // link local + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), // link local + } + good := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), + } + for _, addr := range bad { + require.True(t, IsIP6LinkLocal(addr), "%s is a link local addr", addr) + } + for _, addr := range good { + require.False(t, IsIP6LinkLocal(addr), "%s is not a link local addr", addr) + } +} + +func TestConvertNetAddr(t *testing.T) { + m1 := newMultiaddr(t, "/ip4/1.2.3.4/tcp/4001") + + n1, err := ToNetAddr(m1) + if err != nil { + t.Fatal(err) + } + + m2, err := FromNetAddr(n1) + if err != nil { + t.Fatal(err) + } + + if m1.String() != m2.String() { + t.Fatal("ToNetAddr + FromNetAddr did not work") + } +} + +func TestWrapNetConn(t *testing.T) { + // test WrapNetConn nil + if _, err := WrapNetConn(nil); err == nil { + t.Error("WrapNetConn(nil) should return an error") + } + + checkErr := func(err error, s string) { + if err != nil { + t.Fatal(s, err) + } + } + + listener, err := net.Listen("tcp", "127.0.0.1:0") + checkErr(err, "failed to listen") + + var wg sync.WaitGroup + defer wg.Wait() + wg.Add(1) + go func() { + defer wg.Done() + cB, err := listener.Accept() + checkErr(err, "failed to accept") + _ = cB.(halfOpen) + cB.Close() + }() + + cA, err := net.Dial("tcp", listener.Addr().String()) + checkErr(err, "failed to dial") + defer cA.Close() + _ = cA.(halfOpen) + + lmaddr, err := FromNetAddr(cA.LocalAddr()) + checkErr(err, "failed to get local addr") + rmaddr, err := FromNetAddr(cA.RemoteAddr()) + checkErr(err, "failed to get remote addr") + + mcA, err := WrapNetConn(cA) + checkErr(err, "failed to wrap conn") + + _ = mcA.(halfOpen) + + if mcA.LocalAddr().String() != cA.LocalAddr().String() { + t.Error("wrapped conn local addr differs") + } + if mcA.RemoteAddr().String() != cA.RemoteAddr().String() { + t.Error("wrapped conn remote addr differs") + } + if mcA.LocalMultiaddr().String() != lmaddr.String() { + t.Error("wrapped conn local maddr differs") + } + if mcA.RemoteMultiaddr().String() != rmaddr.String() { + t.Error("wrapped conn remote maddr differs") + } +} + +func TestAddrMatch(t *testing.T) { + + test := func(m ma.Multiaddr, input, expect []ma.Multiaddr) { + actual := AddrMatch(m, input) + testSliceEqual(t, expect, actual) + } + + a := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + } + + test(a[0], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/2345"), + }) + test(a[2], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/tcp/2345"), + }) + test(a[4], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/udp/1234"), + }) + test(a[6], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234/ip6/::1"), + }) + test(a[8], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/2345"), + }) + test(a[10], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + newMultiaddr(t, "/ip6/::1/tcp/1234/tcp/2345"), + }) + test(a[12], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234/udp/1234"), + }) + test(a[14], a, []ma.Multiaddr{ + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + newMultiaddr(t, "/ip6/::1/tcp/1234/ip6/::1"), + }) + +} + +func testSliceEqual(t *testing.T, a, b []ma.Multiaddr) { + if len(a) != len(b) { + t.Error("differ", a, b) + } + for i, addrA := range a { + if !addrA.Equal(b[i]) { + t.Error("differ", a, b) + } + } +} + +func TestInterfaceAddressesWorks(t *testing.T) { + _, err := InterfaceMultiaddrs() + if err != nil { + t.Fatal(err) + } +} + +func TestNetListener(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:1234") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + malist, err := WrapNetListener(listener) + if err != nil { + t.Fatal(err) + } + if !malist.Multiaddr().Equal(newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234")) { + t.Fatal("unexpected multiaddr") + } + + go func() { + c, err := Dial(malist.Multiaddr()) + if err != nil { + t.Error("failed to dial") + } + if !c.RemoteMultiaddr().Equal(malist.Multiaddr()) { + t.Error("dialed wrong target") + } + c.Close() + + c, err = Dial(malist.Multiaddr()) + if err != nil { + t.Error("failed to dial") + } + c.Close() + }() + + c, err := malist.Accept() + if err != nil { + t.Fatal(err) + } + c.Close() + netList := NetListener(malist) + malist2, err := WrapNetListener(netList) + if err != nil { + t.Fatal(err) + } + if malist2 != malist { + t.Fatal("expected WrapNetListener(NetListener(malist)) == malist") + } + nc, err := netList.Accept() + if err != nil { + t.Fatal(err) + } + if !nc.(Conn).LocalMultiaddr().Equal(malist.Multiaddr()) { + t.Fatal("wrong multiaddr on conn") + } + nc.Close() +} + +func BenchmarkResolveUnspecifiedAddress(b *testing.B) { + b.ReportAllocs() + a := ma.StringCast("/ip4/0.0.0.0/udp/42/quic-v1") + iaddrs, _ := interfaceAddresses() + for i := 0; i < b.N; i++ { + ResolveUnspecifiedAddress(a, iaddrs) + } +} diff --git a/net/private.go b/net/private.go new file mode 100644 index 0000000..af55c2a --- /dev/null +++ b/net/private.go @@ -0,0 +1,208 @@ +package manet + +import ( + "net" + "strings" + + ma "github.com/multiformats/go-multiaddr" +) + +// Private4 and Private6 are well-known private networks +var Private4, Private6 []*net.IPNet +var privateCIDR4 = []string{ + // localhost + "127.0.0.0/8", + // private networks + "10.0.0.0/8", + "100.64.0.0/10", + "172.16.0.0/12", + "192.168.0.0/16", + // link local + "169.254.0.0/16", +} +var privateCIDR6 = []string{ + // localhost + "::1/128", + // ULA reserved + "fc00::/7", + // link local + "fe80::/10", +} + +// Unroutable4 and Unroutable6 are well known unroutable address ranges +var Unroutable4, Unroutable6 []*net.IPNet +var unroutableCIDR4 = []string{ + "0.0.0.0/8", + "192.0.0.0/26", + "192.0.2.0/24", + "192.88.99.0/24", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "224.0.0.0/4", + "240.0.0.0/4", + "255.255.255.255/32", +} +var unroutableCIDR6 = []string{ + "ff00::/8", // multicast + "2001:db8::/32", // documentation +} + +var globalUnicast []*net.IPNet +var globalUnicastCIDR6 = []string{ + "2000::/3", +} + +var nat64CIDRs = []string{ + "64:ff9b:1::/48", // RFC 8215 + "64:ff9b::/96", // RFC 6052 +} + +var nat64 []*net.IPNet + +// unResolvableDomains do not resolve to an IP address. +// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names +var unResolvableDomains = []string{ + // Reverse DNS Lookup + ".in-addr.arpa", + ".ip6.arpa", + + // RFC 6761: Users MAY assume that queries for "invalid" names will always return NXDOMAIN + // responses + ".invalid", +} + +// privateUseDomains are reserved for private use and have no central authority for consistent +// address resolution +// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names +var privateUseDomains = []string{ + // RFC 8375: Reserved for home networks + ".home.arpa", + + // MDNS + ".local", + + // RFC 6761: No central authority for .test names + ".test", +} + +// RFC 6761: Users may assume that IPv4 and IPv6 address queries for localhost names will +// always resolve to the respective IP loopback address +const localHostDomain = ".localhost" + +func init() { + Private4 = parseCIDR(privateCIDR4) + Private6 = parseCIDR(privateCIDR6) + Unroutable4 = parseCIDR(unroutableCIDR4) + Unroutable6 = parseCIDR(unroutableCIDR6) + globalUnicast = parseCIDR(globalUnicastCIDR6) + nat64 = parseCIDR(nat64CIDRs) +} + +func parseCIDR(cidrs []string) []*net.IPNet { + ipnets := make([]*net.IPNet, len(cidrs)) + for i, cidr := range cidrs { + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + panic(err) + } + ipnets[i] = ipnet + } + return ipnets +} + +// IsPublicAddr returns true if the IP part of the multiaddr is a publicly routable address +// or if it's a dns address without a special use domain e.g. .local. +func IsPublicAddr(a ma.Multiaddr) bool { + isPublic := false + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_IP6ZONE: + return true + case ma.P_IP4: + ip := net.IP(c.RawValue()) + isPublic = !inAddrRange(ip, Private4) && !inAddrRange(ip, Unroutable4) + case ma.P_IP6: + ip := net.IP(c.RawValue()) + // IP6 documentation prefix(part of Unroutable6) is a subset of the ip6 + // global unicast allocation so we ensure that it's not a documentation + // prefix by diffing with Unroutable6 + isPublicUnicastAddr := inAddrRange(ip, globalUnicast) && !inAddrRange(ip, Unroutable6) + if isPublicUnicastAddr { + isPublic = true + return false + } + // The WellKnown NAT64 prefix(RFC 6052) can only reference a public IPv4 + // address. + // The Local use NAT64 prefix(RFC 8215) can reference private IPv4 + // addresses. But since the translation from Local use NAT64 prefix to IPv4 + // address is left to the user we have no way of knowing which IPv4 address + // is referenced. We count these as Public addresses because a false + // negative for this method here is generally worse than a false positive. + isPublic = inAddrRange(ip, nat64) + return false + case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR: + dnsAddr := c.Value() + isPublic = true + if isSubdomain(dnsAddr, localHostDomain) { + isPublic = false + return false + } + for _, ud := range unResolvableDomains { + if isSubdomain(dnsAddr, ud) { + isPublic = false + return false + } + } + for _, pd := range privateUseDomains { + if isSubdomain(dnsAddr, pd) { + isPublic = false + break + } + } + } + return false + }) + return isPublic +} + +// isSubdomain checks if child is sub domain of parent. It also returns true if child and parent are +// the same domain. +// Parent must have a "." prefix. +func isSubdomain(child, parent string) bool { + return strings.HasSuffix(child, parent) || child == parent[1:] +} + +// IsPrivateAddr returns true if the IP part of the mutiaddr is in a private network +func IsPrivateAddr(a ma.Multiaddr) bool { + isPrivate := false + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_IP6ZONE: + return true + case ma.P_IP4: + isPrivate = inAddrRange(net.IP(c.RawValue()), Private4) + case ma.P_IP6: + isPrivate = inAddrRange(net.IP(c.RawValue()), Private6) + case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR: + dnsAddr := c.Value() + if isSubdomain(dnsAddr, localHostDomain) { + isPrivate = true + } + // We don't check for privateUseDomains because private use domains can + // resolve to public IP addresses + } + return false + }) + return isPrivate +} + +func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool { + for _, ipnet := range ipnets { + if ipnet.Contains(ip) { + return true + } + } + + return false +} diff --git a/net/private_test.go b/net/private_test.go new file mode 100644 index 0000000..c76b5db --- /dev/null +++ b/net/private_test.go @@ -0,0 +1,84 @@ +package manet + +import ( + "fmt" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestIsPublicAddr(t *testing.T) { + tests := []struct { + addr ma.Multiaddr + isPublic bool + isPrivate bool + }{ + { + addr: ma.StringCast("/ip4/192.168.1.1/tcp/80"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/ip4/1.1.1.1/tcp/80"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/tcp/80/ip4/1.1.1.1"), + isPublic: false, + isPrivate: false, + }, + { + addr: ma.StringCast("/dns/node.libp2p.io/udp/1/quic-v1"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/dnsaddr/node.libp2p.io/udp/1/quic-v1"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/dns/node.libp2p.local/udp/1/quic-v1"), + isPublic: false, + isPrivate: false, // You can configure .local domains in local networks to return public addrs + }, + { + addr: ma.StringCast("/dns/localhost/udp/1/quic-v1"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/dns/a.localhost/tcp/1"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/ip6/2400::1/tcp/10"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/ip6/2001:db8::42/tcp/10"), + isPublic: false, + isPrivate: false, + }, + { + addr: ma.StringCast("/ip6/64:ff9b::1.1.1.1/tcp/10"), + isPublic: true, + isPrivate: false, + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + isPublic := IsPublicAddr(tt.addr) + isPrivate := IsPrivateAddr(tt.addr) + if isPublic != tt.isPublic { + t.Errorf("IsPublicAddr check failed for %s: expected %t, got %t", tt.addr, tt.isPublic, isPublic) + } + if isPrivate != tt.isPrivate { + t.Errorf("IsPrivateAddr check failed for %s: expected %t, got %t", tt.addr, tt.isPrivate, isPrivate) + } + }) + } +} diff --git a/net/registry.go b/net/registry.go new file mode 100644 index 0000000..7883b95 --- /dev/null +++ b/net/registry.go @@ -0,0 +1,97 @@ +package manet + +import ( + "fmt" + "net" + "sync" + + ma "github.com/multiformats/go-multiaddr" +) + +// FromNetAddrFunc is a generic function which converts a net.Addr to Multiaddress +type FromNetAddrFunc func(a net.Addr) (ma.Multiaddr, error) + +// ToNetAddrFunc is a generic function which converts a Multiaddress to net.Addr +type ToNetAddrFunc func(ma ma.Multiaddr) (net.Addr, error) + +var defaultCodecs = NewCodecMap() + +func init() { + RegisterFromNetAddr(parseTCPNetAddr, "tcp", "tcp4", "tcp6") + RegisterFromNetAddr(parseUDPNetAddr, "udp", "udp4", "udp6") + RegisterFromNetAddr(parseIPNetAddr, "ip", "ip4", "ip6") + RegisterFromNetAddr(parseIPPlusNetAddr, "ip+net") + RegisterFromNetAddr(parseUnixNetAddr, "unix") + + RegisterToNetAddr(parseBasicNetMaddr, "tcp", "udp", "ip6", "ip4", "unix") +} + +// CodecMap holds a map of NetCodecs indexed by their Protocol ID +// along with parsers for the addresses they use. +// It is used to keep a list of supported network address codecs (protocols +// which addresses can be converted to and from multiaddresses). +type CodecMap struct { + addrParsers map[string]FromNetAddrFunc + maddrParsers map[string]ToNetAddrFunc + lk sync.Mutex +} + +// NewCodecMap initializes and returns a CodecMap object. +func NewCodecMap() *CodecMap { + return &CodecMap{ + addrParsers: make(map[string]FromNetAddrFunc), + maddrParsers: make(map[string]ToNetAddrFunc), + } +} + +// RegisterFromNetAddr registers a conversion from net.Addr instances to multiaddrs. +func RegisterFromNetAddr(from FromNetAddrFunc, networks ...string) { + defaultCodecs.RegisterFromNetAddr(from, networks...) +} + +// RegisterToNetAddr registers a conversion from multiaddrs to net.Addr instances. +func RegisterToNetAddr(to ToNetAddrFunc, protocols ...string) { + defaultCodecs.RegisterToNetAddr(to, protocols...) +} + +// RegisterFromNetAddr registers a conversion from net.Addr instances to multiaddrs +func (cm *CodecMap) RegisterFromNetAddr(from FromNetAddrFunc, networks ...string) { + cm.lk.Lock() + defer cm.lk.Unlock() + + for _, n := range networks { + cm.addrParsers[n] = from + } +} + +// RegisterToNetAddr registers a conversion from multiaddrs to net.Addr instances +func (cm *CodecMap) RegisterToNetAddr(to ToNetAddrFunc, protocols ...string) { + cm.lk.Lock() + defer cm.lk.Unlock() + + for _, p := range protocols { + cm.maddrParsers[p] = to + } +} + +func (cm *CodecMap) getAddrParser(net string) (FromNetAddrFunc, error) { + cm.lk.Lock() + defer cm.lk.Unlock() + + parser, ok := cm.addrParsers[net] + if !ok { + return nil, fmt.Errorf("unknown network %v", net) + } + return parser, nil +} + +func (cm *CodecMap) getMaddrParser(name string) (ToNetAddrFunc, error) { + cm.lk.Lock() + defer cm.lk.Unlock() + p, ok := cm.maddrParsers[name] + if !ok { + return nil, fmt.Errorf("network not supported: %s", name) + } + + return p, nil +} diff --git a/net/registry_test.go b/net/registry_test.go new file mode 100644 index 0000000..9aaf4de --- /dev/null +++ b/net/registry_test.go @@ -0,0 +1,42 @@ +package manet + +import ( + "net" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestRegisterFrom(t *testing.T) { + cm := NewCodecMap() + cm.RegisterFromNetAddr( + func(a net.Addr) (ma.Multiaddr, error) { return nil, nil }, + "test", "iptest", "blahtest", + ) + if _, ok := cm.addrParsers["test"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.addrParsers["iptest"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.addrParsers["blahtest"]; !ok { + t.Fatal("myproto not properly registered") + } +} + +func TestRegisterTo(t *testing.T) { + cm := NewCodecMap() + cm.RegisterToNetAddr( + func(a ma.Multiaddr) (net.Addr, error) { return nil, nil }, + "test", "iptest", "blahtest", + ) + if _, ok := cm.maddrParsers["test"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.maddrParsers["iptest"]; !ok { + t.Fatal("myproto not properly registered") + } + if _, ok := cm.maddrParsers["blahtest"]; !ok { + t.Fatal("myproto not properly registered") + } +} diff --git a/net/resolve.go b/net/resolve.go new file mode 100644 index 0000000..44c2ef1 --- /dev/null +++ b/net/resolve.go @@ -0,0 +1,87 @@ +package manet + +import ( + "fmt" + + ma "github.com/multiformats/go-multiaddr" +) + +// ResolveUnspecifiedAddress expands an unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. If ifaceAddr is nil, we request interface addresses +// from the network stack. (this is so you can provide a cached value if resolving many addrs) +func ResolveUnspecifiedAddress(resolve ma.Multiaddr, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // split address into its components + first, rest := ma.SplitFirst(resolve) + + // if first component (ip) is not unspecified, use it as is. + if !IsIPUnspecified(first) { + return []ma.Multiaddr{resolve}, nil + } + + resolveProto := resolve.Protocols()[0].Code + out := make([]ma.Multiaddr, 0, len(ifaceAddrs)) + for _, ia := range ifaceAddrs { + iafirst, _ := ma.SplitFirst(ia) + // must match the first protocol to be resolve. + if iafirst.Protocol().Code != resolveProto { + continue + } + + joined := ia + if rest != nil { + joined = ma.Join(ia, rest) + } + out = append(out, joined) + } + if len(out) < 1 { + return nil, fmt.Errorf("failed to resolve: %s", resolve) + } + return out, nil +} + +// ResolveUnspecifiedAddresses expands unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. +func ResolveUnspecifiedAddresses(unspecAddrs, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // todo optimize: only fetch these if we have a "any" addr. + if len(ifaceAddrs) < 1 { + var err error + ifaceAddrs, err = interfaceAddresses() + if err != nil { + return nil, err + } + } + + var outputAddrs []ma.Multiaddr + for _, a := range unspecAddrs { + // unspecified? + resolved, err := ResolveUnspecifiedAddress(a, ifaceAddrs) + if err != nil { + continue // optimistic. if we can't resolve anything, we'll know at the bottom. + } + outputAddrs = append(outputAddrs, resolved...) + } + + if len(outputAddrs) < 1 { + return nil, fmt.Errorf("failed to specify addrs: %s", unspecAddrs) + } + return outputAddrs, nil +} + +// interfaceAddresses returns a list of addresses associated with local machine +// Note: we do not return link local addresses. IP loopback is ok, because we +// may be connecting to other nodes in the same machine. +func interfaceAddresses() ([]ma.Multiaddr, error) { + maddrs, err := InterfaceMultiaddrs() + if err != nil { + return nil, err + } + + var out []ma.Multiaddr + for _, a := range maddrs { + if IsIP6LinkLocal(a) { + continue + } + out = append(out, a) + } + return out, nil +} diff --git a/net/resolve_test.go b/net/resolve_test.go new file mode 100644 index 0000000..e4af820 --- /dev/null +++ b/net/resolve_test.go @@ -0,0 +1,60 @@ +package manet + +import ( + "testing" + + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +func TestResolvingAddrs(t *testing.T) { + unspec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/0.0.0.0/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + newMultiaddr(t, "/ip4/0.0.0.0"), + } + + iface := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1"), + newMultiaddr(t, "/ip4/10.20.30.40"), + newMultiaddr(t, "/ip6/::1"), + newMultiaddr(t, "/ip6/::f"), + } + + spec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/10.20.30.40/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::f/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + newMultiaddr(t, "/ip4/127.0.0.1"), + newMultiaddr(t, "/ip4/10.20.30.40"), + } + + actual, err := ResolveUnspecifiedAddresses(unspec, iface) + require.NoError(t, err) + require.Equal(t, actual, spec) + + ip4u := []ma.Multiaddr{newMultiaddr(t, "/ip4/0.0.0.0")} + ip4i := []ma.Multiaddr{newMultiaddr(t, "/ip4/1.2.3.4")} + + ip6u := []ma.Multiaddr{newMultiaddr(t, "/ip6/::")} + ip6i := []ma.Multiaddr{newMultiaddr(t, "/ip6/::1")} + + if _, err := ResolveUnspecifiedAddress(ip4u[0], ip6i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddress(ip6u[0], ip4i); err == nil { + t.Fatal("should have failed") + } + + if _, err := ResolveUnspecifiedAddresses(ip6u, ip4i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddresses(ip4u, ip6i); err == nil { + t.Fatal("should have failed") + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c493b27 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "author": "multiformats", + "bugs": { + "url": "https://github.com/multiformats/go-multiaddr/issues" + }, + "gx": { + "dvcsimport": "github.com/multiformats/go-multiaddr" + }, + "gxDependencies": [ + { + "hash": "QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW", + "name": "go-multihash", + "version": "1.0.9" + } + ], + "gxVersion": "0.9.0", + "language": "go", + "license": "MIT", + "name": "go-multiaddr", + "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", + "version": "1.4.1" +} + diff --git a/protocol.go b/protocol.go new file mode 100644 index 0000000..61a2924 --- /dev/null +++ b/protocol.go @@ -0,0 +1,102 @@ +package multiaddr + +import ( + "fmt" + "strings" +) + +// These are special sizes +const ( + LengthPrefixedVarSize = -1 +) + +// Protocol is a Multiaddr protocol description structure. +type Protocol struct { + // Name is the string representation of the protocol code. E.g., ip4, + // ip6, tcp, udp, etc. + Name string + + // Code is the protocol's multicodec (a normal, non-varint number). + Code int + + // VCode is a precomputed varint encoded version of Code. + VCode []byte + + // Size is the size of the argument to this protocol. + // + // * Size == 0 means this protocol takes no argument. + // * Size > 0 means this protocol takes a constant sized argument. + // * Size < 0 means this protocol takes a variable length, varint + // prefixed argument. + Size int // a size of -1 indicates a length-prefixed variable size + + // Path indicates a path protocol (e.g., unix). When parsing multiaddr + // strings, path protocols consume the remainder of the address instead + // of stopping at the next forward slash. + // + // Size must be LengthPrefixedVarSize. + Path bool + + // Transcoder converts between the byte representation and the string + // representation of this protocol's argument (if any). + // + // This should only be non-nil if Size != 0 + Transcoder Transcoder +} + +var protocolsByName = map[string]Protocol{} +var protocolsByCode = map[int]Protocol{} + +// Protocols is the list of multiaddr protocols supported by this module. +var Protocols = []Protocol{} + +func AddProtocol(p Protocol) error { + if _, ok := protocolsByName[p.Name]; ok { + return fmt.Errorf("protocol by the name %q already exists", p.Name) + } + + if _, ok := protocolsByCode[p.Code]; ok { + return fmt.Errorf("protocol code %d already taken by %q", p.Code, p.Code) + } + + if p.Size != 0 && p.Transcoder == nil { + return fmt.Errorf("protocols with arguments must define transcoders") + } + if p.Path && p.Size >= 0 { + return fmt.Errorf("path protocols must have variable-length sizes") + } + + Protocols = append(Protocols, p) + protocolsByName[p.Name] = p + protocolsByCode[p.Code] = p + return nil +} + +// ProtocolWithName returns the Protocol description with given string name. +func ProtocolWithName(s string) Protocol { + return protocolsByName[s] +} + +// ProtocolWithCode returns the Protocol description with given protocol code. +func ProtocolWithCode(c int) Protocol { + return protocolsByCode[c] +} + +// ProtocolsWithString returns a slice of protocols matching given string. +func ProtocolsWithString(s string) ([]Protocol, error) { + s = strings.Trim(s, "/") + sp := strings.Split(s, "/") + if len(sp) == 0 { + return nil, nil + } + + t := make([]Protocol, len(sp)) + for i, name := range sp { + p := ProtocolWithName(name) + if p.Code == 0 { + return nil, fmt.Errorf("no protocol with name: %s", name) + } + t[i] = p + } + return t, nil +} diff --git a/protocols.go b/protocols.go new file mode 100644 index 0000000..0e74b5a --- /dev/null +++ b/protocols.go @@ -0,0 +1,344 @@ +package multiaddr + +// You **MUST** register your multicodecs with +// https://github.com/multiformats/multicodec before adding them here. +const ( + P_IP4 = 4 + P_TCP = 6 + P_DNS = 53 // 4 or 6 + P_DNS4 = 54 + P_DNS6 = 55 + P_DNSADDR = 56 + P_UDP = 273 + P_DCCP = 33 + P_IP6 = 41 + P_IP6ZONE = 42 + P_IPCIDR = 43 + P_QUIC = 460 + P_QUIC_V1 = 461 + P_WEBTRANSPORT = 465 + P_CERTHASH = 466 + P_SCTP = 132 + P_CIRCUIT = 290 + P_UDT = 301 + P_UTP = 302 + P_UNIX = 400 + P_P2P = 421 + P_IPFS = P_P2P // alias for backwards compatibility + P_HTTP = 480 + P_HTTP_PATH = 481 + P_HTTPS = 443 // deprecated alias for /tls/http + P_ONION = 444 // also for backwards compatibility + P_ONION3 = 445 + P_GARLIC64 = 446 + P_GARLIC32 = 447 + P_P2P_WEBRTC_DIRECT = 276 // Deprecated. use webrtc-direct instead + P_TLS = 448 + P_SNI = 449 + P_NOISE = 454 + P_WS = 477 + P_WSS = 478 // deprecated alias for /tls/ws + P_PLAINTEXTV2 = 7367777 + P_WEBRTC_DIRECT = 280 + P_WEBRTC = 281 + P_MEMORY = 777 +) + +var ( + protoIP4 = Protocol{ + Name: "ip4", + Code: P_IP4, + VCode: CodeToVarint(P_IP4), + Size: 32, + Path: false, + Transcoder: TranscoderIP4, + } + protoTCP = Protocol{ + Name: "tcp", + Code: P_TCP, + VCode: CodeToVarint(P_TCP), + Size: 16, + Path: false, + Transcoder: TranscoderPort, + } + protoDNS = Protocol{ + Code: P_DNS, + Size: LengthPrefixedVarSize, + Name: "dns", + VCode: CodeToVarint(P_DNS), + Transcoder: TranscoderDns, + } + protoDNS4 = Protocol{ + Code: P_DNS4, + Size: LengthPrefixedVarSize, + Name: "dns4", + VCode: CodeToVarint(P_DNS4), + Transcoder: TranscoderDns, + } + protoDNS6 = Protocol{ + Code: P_DNS6, + Size: LengthPrefixedVarSize, + Name: "dns6", + VCode: CodeToVarint(P_DNS6), + Transcoder: TranscoderDns, + } + protoDNSADDR = Protocol{ + Code: P_DNSADDR, + Size: LengthPrefixedVarSize, + Name: "dnsaddr", + VCode: CodeToVarint(P_DNSADDR), + Transcoder: TranscoderDns, + } + protoUDP = Protocol{ + Name: "udp", + Code: P_UDP, + VCode: CodeToVarint(P_UDP), + Size: 16, + Path: false, + Transcoder: TranscoderPort, + } + protoDCCP = Protocol{ + Name: "dccp", + Code: P_DCCP, + VCode: CodeToVarint(P_DCCP), + Size: 16, + Path: false, + Transcoder: TranscoderPort, + } + protoIP6 = Protocol{ + Name: "ip6", + Code: P_IP6, + VCode: CodeToVarint(P_IP6), + Size: 128, + Transcoder: TranscoderIP6, + } + protoIPCIDR = Protocol{ + Name: "ipcidr", + Code: P_IPCIDR, + VCode: CodeToVarint(P_IPCIDR), + Size: 8, + Transcoder: TranscoderIPCIDR, + } + // these require varint + protoIP6ZONE = Protocol{ + Name: "ip6zone", + Code: P_IP6ZONE, + VCode: CodeToVarint(P_IP6ZONE), + Size: LengthPrefixedVarSize, + Path: false, + Transcoder: TranscoderIP6Zone, + } + protoSCTP = Protocol{ + Name: "sctp", + Code: P_SCTP, + VCode: CodeToVarint(P_SCTP), + Size: 16, + Transcoder: TranscoderPort, + } + + protoCIRCUIT = Protocol{ + Code: P_CIRCUIT, + Size: 0, + Name: "p2p-circuit", + VCode: CodeToVarint(P_CIRCUIT), + } + + protoONION2 = Protocol{ + Name: "onion", + Code: P_ONION, + VCode: CodeToVarint(P_ONION), + Size: 96, + Transcoder: TranscoderOnion, + } + protoONION3 = Protocol{ + Name: "onion3", + Code: P_ONION3, + VCode: CodeToVarint(P_ONION3), + Size: 296, + Transcoder: TranscoderOnion3, + } + protoGARLIC64 = Protocol{ + Name: "garlic64", + Code: P_GARLIC64, + VCode: CodeToVarint(P_GARLIC64), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderGarlic64, + } + protoGARLIC32 = Protocol{ + Name: "garlic32", + Code: P_GARLIC32, + VCode: CodeToVarint(P_GARLIC32), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderGarlic32, + } + protoUTP = Protocol{ + Name: "utp", + Code: P_UTP, + VCode: CodeToVarint(P_UTP), + } + protoUDT = Protocol{ + Name: "udt", + Code: P_UDT, + VCode: CodeToVarint(P_UDT), + } + protoQUIC = Protocol{ + Name: "quic", + Code: P_QUIC, + VCode: CodeToVarint(P_QUIC), + } + protoQUICV1 = Protocol{ + Name: "quic-v1", + Code: P_QUIC_V1, + VCode: CodeToVarint(P_QUIC_V1), + } + protoWEBTRANSPORT = Protocol{ + Name: "webtransport", + Code: P_WEBTRANSPORT, + VCode: CodeToVarint(P_WEBTRANSPORT), + } + protoCERTHASH = Protocol{ + Name: "certhash", + Code: P_CERTHASH, + VCode: CodeToVarint(P_CERTHASH), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderCertHash, + } + protoHTTP = Protocol{ + Name: "http", + Code: P_HTTP, + VCode: CodeToVarint(P_HTTP), + } + protoHTTPPath = Protocol{ + Name: "http-path", + Code: P_HTTP_PATH, + VCode: CodeToVarint(P_HTTP_PATH), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderHTTPPath, + } + protoHTTPS = Protocol{ + Name: "https", + Code: P_HTTPS, + VCode: CodeToVarint(P_HTTPS), + } + protoP2P = Protocol{ + Name: "p2p", + Code: P_P2P, + VCode: CodeToVarint(P_P2P), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderP2P, + } + protoUNIX = Protocol{ + Name: "unix", + Code: P_UNIX, + VCode: CodeToVarint(P_UNIX), + Size: LengthPrefixedVarSize, + Path: true, + Transcoder: TranscoderUnix, + } + protoP2P_WEBRTC_DIRECT = Protocol{ + Name: "p2p-webrtc-direct", + Code: P_P2P_WEBRTC_DIRECT, + VCode: CodeToVarint(P_P2P_WEBRTC_DIRECT), + } + protoTLS = Protocol{ + Name: "tls", + Code: P_TLS, + VCode: CodeToVarint(P_TLS), + } + protoSNI = Protocol{ + Name: "sni", + Size: LengthPrefixedVarSize, + Code: P_SNI, + VCode: CodeToVarint(P_SNI), + Transcoder: TranscoderDns, + } + protoNOISE = Protocol{ + Name: "noise", + Code: P_NOISE, + VCode: CodeToVarint(P_NOISE), + } + protoPlaintextV2 = Protocol{ + Name: "plaintextv2", + Code: P_PLAINTEXTV2, + VCode: CodeToVarint(P_PLAINTEXTV2), + } + protoWS = Protocol{ + Name: "ws", + Code: P_WS, + VCode: CodeToVarint(P_WS), + } + protoWSS = Protocol{ + Name: "wss", + Code: P_WSS, + VCode: CodeToVarint(P_WSS), + } + protoWebRTCDirect = Protocol{ + Name: "webrtc-direct", + Code: P_WEBRTC_DIRECT, + VCode: CodeToVarint(P_WEBRTC_DIRECT), + } + protoWebRTC = Protocol{ + Name: "webrtc", + Code: P_WEBRTC, + VCode: CodeToVarint(P_WEBRTC), + } + + protoMemory = Protocol{ + Name: "memory", + Code: P_MEMORY, + VCode: CodeToVarint(P_MEMORY), + Size: 64, + Transcoder: TranscoderMemory, + } +) + +func init() { + for _, p := range []Protocol{ + protoIP4, + protoTCP, + protoDNS, + protoDNS4, + protoDNS6, + protoDNSADDR, + protoUDP, + protoDCCP, + protoIP6, + protoIP6ZONE, + protoIPCIDR, + protoSCTP, + protoCIRCUIT, + protoONION2, + protoONION3, + protoGARLIC64, + protoGARLIC32, + protoUTP, + protoUDT, + protoQUIC, + protoQUICV1, + protoWEBTRANSPORT, + protoCERTHASH, + protoHTTP, + protoHTTPPath, + protoHTTPS, + protoP2P, + protoUNIX, + protoP2P_WEBRTC_DIRECT, + protoTLS, + protoSNI, + protoNOISE, + protoWS, + protoWSS, + protoPlaintextV2, + protoWebRTCDirect, + protoWebRTC, + protoMemory, + } { + if err := AddProtocol(p); err != nil { + panic(err) + } + } + + // explicitly set both of these + protocolsByName["p2p"] = protoP2P + protocolsByName["ipfs"] = protoP2P +} diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 b/testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 new file mode 100644 index 0000000..013e7dc --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/0487b63847656fd4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xa5\x03&\x12$000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 b/testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 new file mode 100644 index 0000000..0292295 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/04a87ae2740f7195 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x90\x03\x06000000") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb b/testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb new file mode 100644 index 0000000..7fc603c --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/239d3594e0ee93bb @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xbd\x0300000000000000000000000000000000000\x00\x00") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 b/testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 new file mode 100644 index 0000000..77c10c8 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/2ef0d600700564d4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x90\x03\x06000000\x90\x03\x06000000") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 b/testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 new file mode 100644 index 0000000..17866a9 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/385d14fbb016b8c3 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("7*000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 b/testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 new file mode 100644 index 0000000..05d670c --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/511b72740453a863 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xd2\x03\x00") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e b/testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e new file mode 100644 index 0000000..62c184c --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/69ba454c4217999e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("7\x00") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e b/testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e new file mode 100644 index 0000000..7326a78 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/9f0d778549d2b28e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xbc\x030000000000\x00\x00") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d b/testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d new file mode 100644 index 0000000..f490395 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/af9576bc28339a8d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x84\xe0\xff\x00") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 b/testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 new file mode 100644 index 0000000..6633245 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/e9317f1a3c43de50 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x90\x03\x06/0000/") diff --git a/testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 b/testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 new file mode 100644 index 0000000..9a3e0dd --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrBytes/f1ebd17c93085805 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xa5\x03\x030\x010") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 b/testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 new file mode 100644 index 0000000..976f505 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/382a5bb1eff47833 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/garlic32/2222222222222222222222222222222222222222222222222222222") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 b/testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 new file mode 100644 index 0000000..f627d5d --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/53eb3b6be337b1d7 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/onion/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\xa0\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7fd\x00-\a\t\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x04\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x00\x01\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\n\xec\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\n\xff\v\v\v\v\v\v\v\v\v\v\v\v\v\x80\x00\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x87نŁ\xe2\xec\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\r\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xf2\n\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x1d\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v2222=:") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 b/testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 new file mode 100644 index 0000000..df7624a --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/63891c9534054d61 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/p2p/BAfZA2jA222222222222222222222222222222222222222222222222222222222") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 b/testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 new file mode 100644 index 0000000..3b6089e --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/95a479f85dc92117 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/p2p/BAfZBAjA222222222222222222222222222222222222222222222222222222222") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 b/testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 new file mode 100644 index 0000000..805c039 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/9dba3b166a74fc47 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/garlic64/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\r\r\r\r\r\r\r\r0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 b/testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 new file mode 100644 index 0000000..6c48422 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/a2b937de623ded67 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/garlic32/222222222222222222222222222222222222222222222\r\r\r\r\r\r\r") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a b/testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a new file mode 100644 index 0000000..a542e41 --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/bc05ef53a41e422a @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/p2p/Qm11ps1111111111111111111111111111111") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a b/testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a new file mode 100644 index 0000000..ec2b8bb --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/d857c283ff1b2f2a @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/sni//sni/0") diff --git a/testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae b/testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae new file mode 100644 index 0000000..829d05f --- /dev/null +++ b/testdata/fuzz/FuzzNewMultiaddrString/dffb2baac63c66ae @@ -0,0 +1,2 @@ +go test fuzz v1 +string("/onion/222222222222222=:1") diff --git a/transcoders.go b/transcoders.go new file mode 100644 index 0000000..03b39ae --- /dev/null +++ b/transcoders.go @@ -0,0 +1,520 @@ +package multiaddr + +import ( + "bytes" + "encoding/base32" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "net" + "net/url" + "strconv" + "strings" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multibase" + mh "github.com/multiformats/go-multihash" +) + +type Transcoder interface { + // Validates and encodes to bytes a multiaddr that's in the string representation. + StringToBytes(string) ([]byte, error) + // Validates and decodes to a string a multiaddr that's in the bytes representation. + BytesToString([]byte) (string, error) + // Validates bytes when parsing a multiaddr that's already in the bytes representation. + ValidateBytes([]byte) error +} + +func NewTranscoderFromFunctions( + s2b func(string) ([]byte, error), + b2s func([]byte) (string, error), + val func([]byte) error, +) Transcoder { + return twrp{s2b, b2s, val} +} + +type twrp struct { + strtobyte func(string) ([]byte, error) + bytetostr func([]byte) (string, error) + validbyte func([]byte) error +} + +func (t twrp) StringToBytes(s string) ([]byte, error) { + return t.strtobyte(s) +} +func (t twrp) BytesToString(b []byte) (string, error) { + return t.bytetostr(b) +} + +func (t twrp) ValidateBytes(b []byte) error { + if t.validbyte == nil { + return nil + } + return t.validbyte(b) +} + +var TranscoderIP4 = NewTranscoderFromFunctions(ip4StB, ip4BtS, nil) +var TranscoderIP6 = NewTranscoderFromFunctions(ip6StB, ip6BtS, nil) +var TranscoderIP6Zone = NewTranscoderFromFunctions(ip6zoneStB, ip6zoneBtS, ip6zoneVal) +var TranscoderIPCIDR = NewTranscoderFromFunctions(ipcidrStB, ipcidrBtS, ipcidrValidate) + +func ipcidrBtS(b []byte) (string, error) { + if err := ipcidrValidate(b); err != nil { + return "", err + } + return strconv.Itoa(int(b[0])), nil +} + +func ipcidrStB(s string) ([]byte, error) { + ipMask, err := strconv.ParseUint(s, 10, 8) + if err != nil { + return nil, err + } + return []byte{byte(uint8(ipMask))}, nil +} + +func ipcidrValidate(b []byte) error { + if len(b) != 1 { + return fmt.Errorf("invalid length (should be == 1)") + } + return nil +} + +func ip4StB(s string) ([]byte, error) { + i := net.ParseIP(s).To4() + if i == nil { + return nil, fmt.Errorf("failed to parse ip4 addr: %s", s) + } + return i, nil +} + +func ip6zoneStB(s string) ([]byte, error) { + if len(s) == 0 { + return nil, fmt.Errorf("empty ip6zone") + } + if strings.Contains(s, "/") { + return nil, fmt.Errorf("IPv6 zone ID contains '/': %s", s) + } + return []byte(s), nil +} + +func ip6zoneBtS(b []byte) (string, error) { + if len(b) == 0 { + return "", fmt.Errorf("invalid length (should be > 0)") + } + return string(b), nil +} + +func ip6zoneVal(b []byte) error { + if len(b) == 0 { + return fmt.Errorf("invalid length (should be > 0)") + } + // Not supported as this would break multiaddrs. + if bytes.IndexByte(b, '/') >= 0 { + return fmt.Errorf("IPv6 zone ID contains '/': %s", string(b)) + } + return nil +} + +func ip6StB(s string) ([]byte, error) { + i := net.ParseIP(s).To16() + if i == nil { + return nil, fmt.Errorf("failed to parse ip6 addr: %s", s) + } + return i, nil +} + +func ip6BtS(b []byte) (string, error) { + ip := net.IP(b) + if ip4 := ip.To4(); ip4 != nil { + // Go fails to prepend the `::ffff:` part. + return "::ffff:" + ip4.String(), nil + } + return ip.String(), nil +} + +func ip4BtS(b []byte) (string, error) { + return net.IP(b).String(), nil +} + +var TranscoderPort = NewTranscoderFromFunctions(portStB, portBtS, nil) + +func portStB(s string) ([]byte, error) { + i, err := strconv.ParseUint(s, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse port addr: %s", err) + } + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, uint16(i)) + return b, nil +} + +func portBtS(b []byte) (string, error) { + i := binary.BigEndian.Uint16(b) + return strconv.FormatUint(uint64(i), 10), nil +} + +var TranscoderOnion = NewTranscoderFromFunctions(onionStB, onionBtS, onionValidate) + +func onionStB(s string) ([]byte, error) { + addr := strings.Split(s, ":") + if len(addr) != 2 { + return nil, fmt.Errorf("failed to parse onion addr: %s does not contain a port number", s) + } + + onionHostBytes, err := base32.StdEncoding.DecodeString(strings.ToUpper(addr[0])) + if err != nil { + return nil, fmt.Errorf("failed to decode base32 onion addr: %s %s", s, err) + } + + // onion address without the ".onion" substring are 10 bytes long + if len(onionHostBytes) != 10 { + return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onion address", s) + } + + // onion port number + i, err := strconv.ParseUint(addr[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse onion addr: %s", err) + } + if i == 0 { + return nil, fmt.Errorf("failed to parse onion addr: %s", "non-zero port") + } + + onionPortBytes := make([]byte, 2) + binary.BigEndian.PutUint16(onionPortBytes, uint16(i)) + bytes := []byte{} + bytes = append(bytes, onionHostBytes...) + bytes = append(bytes, onionPortBytes...) + return bytes, nil +} + +func onionBtS(b []byte) (string, error) { + addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:10])) + port := binary.BigEndian.Uint16(b[10:12]) + if port == 0 { + return "", fmt.Errorf("failed to parse onion addr: %s", "non-zero port") + } + return addr + ":" + strconv.FormatUint(uint64(port), 10), nil +} + +func onionValidate(b []byte) error { + if len(b) != 12 { + return fmt.Errorf("invalid len for onion addr: got %d expected 12", len(b)) + } + port := binary.BigEndian.Uint16(b[10:12]) + if port == 0 { + return fmt.Errorf("invalid port 0 for onion addr") + } + return nil +} + +var TranscoderOnion3 = NewTranscoderFromFunctions(onion3StB, onion3BtS, onion3Validate) + +func onion3StB(s string) ([]byte, error) { + addr := strings.Split(s, ":") + if len(addr) != 2 { + return nil, fmt.Errorf("failed to parse onion addr: %s does not contain a port number", s) + } + + // onion address without the ".onion" substring + if len(addr[0]) != 56 { + return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onionv3 address. len == %d", s, len(addr[0])) + } + onionHostBytes, err := base32.StdEncoding.DecodeString(strings.ToUpper(addr[0])) + if err != nil { + return nil, fmt.Errorf("failed to decode base32 onion addr: %s %s", s, err) + } + + // onion port number + i, err := strconv.ParseUint(addr[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse onion addr: %s", err) + } + if i == 0 { + return nil, fmt.Errorf("failed to parse onion addr: %s", "non-zero port") + } + + onionPortBytes := make([]byte, 2) + binary.BigEndian.PutUint16(onionPortBytes, uint16(i)) + bytes := []byte{} + bytes = append(bytes, onionHostBytes[0:35]...) + bytes = append(bytes, onionPortBytes...) + return bytes, nil +} + +func onion3BtS(b []byte) (string, error) { + addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:35])) + port := binary.BigEndian.Uint16(b[35:37]) + if port < 1 { + return "", fmt.Errorf("failed to parse onion addr: %s", "port less than 1") + } + str := addr + ":" + strconv.FormatUint(uint64(port), 10) + return str, nil +} + +func onion3Validate(b []byte) error { + if len(b) != 37 { + return fmt.Errorf("invalid len for onion addr: got %d expected 37", len(b)) + } + port := binary.BigEndian.Uint16(b[35:37]) + if port == 0 { + return fmt.Errorf("invalid port 0 for onion addr") + } + return nil +} + +var TranscoderGarlic64 = NewTranscoderFromFunctions(garlic64StB, garlic64BtS, garlic64Validate) + +// i2p uses an alternate character set for base64 addresses. This returns an appropriate encoder. +var garlicBase64Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~") + +func garlic64StB(s string) ([]byte, error) { + garlicHostBytes, err := garlicBase64Encoding.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 i2p addr: %s %s", s, err) + } + + if err := garlic64Validate(garlicHostBytes); err != nil { + return nil, err + } + return garlicHostBytes, nil +} + +func garlic64BtS(b []byte) (string, error) { + if err := garlic64Validate(b); err != nil { + return "", err + } + addr := garlicBase64Encoding.EncodeToString(b) + return addr, nil +} + +func garlic64Validate(b []byte) error { + // A garlic64 address will always be greater than 386 bytes long when encoded. + if len(b) < 386 { + return fmt.Errorf("failed to validate garlic addr: %s not an i2p base64 address. len: %d", b, len(b)) + } + return nil +} + +var TranscoderGarlic32 = NewTranscoderFromFunctions(garlic32StB, garlic32BtS, garlic32Validate) + +var garlicBase32Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567") + +func garlic32StB(s string) ([]byte, error) { + for len(s)%8 != 0 { + s += "=" + } + garlicHostBytes, err := garlicBase32Encoding.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to decode base32 garlic addr: %s, err: %v len: %v", s, err, len(s)) + } + + if err := garlic32Validate(garlicHostBytes); err != nil { + return nil, err + } + return garlicHostBytes, nil +} + +func garlic32BtS(b []byte) (string, error) { + if err := garlic32Validate(b); err != nil { + return "", err + } + return strings.TrimRight(garlicBase32Encoding.EncodeToString(b), "="), nil +} + +func garlic32Validate(b []byte) error { + // an i2p address with encrypted leaseset has len >= 35 bytes + // all other addresses will always be exactly 32 bytes + // https://geti2p.net/spec/b32encrypted + if len(b) < 35 && len(b) != 32 { + return fmt.Errorf("failed to validate garlic addr: %s not an i2p base32 address. len: %d", b, len(b)) + } + return nil +} + +var TranscoderP2P = NewTranscoderFromFunctions(p2pStB, p2pBtS, p2pVal) + +// The encoded peer ID can either be a CID of a key or a raw multihash (identity +// or sha256-256). +func p2pStB(s string) ([]byte, error) { + // check if the address is a base58 encoded sha256 or identity multihash + if strings.HasPrefix(s, "Qm") || strings.HasPrefix(s, "1") { + m, err := mh.FromB58String(s) + if err != nil { + return nil, fmt.Errorf("failed to parse p2p addr: %s %s", s, err) + } + if err := p2pVal(m); err != nil { + return nil, err + } + return m, nil + } + + // check if the address is a CID + c, err := cid.Decode(s) + if err != nil { + return nil, fmt.Errorf("failed to parse p2p addr: %s %s", s, err) + } + + if ty := c.Type(); ty == cid.Libp2pKey { + if err := p2pVal(c.Hash()); err != nil { + return nil, err + } + return c.Hash(), nil + } else { + return nil, fmt.Errorf("failed to parse p2p addr: %s has the invalid codec %d", s, ty) + } +} + +func p2pVal(b []byte) error { + h, err := mh.Decode([]byte(b)) + if err != nil { + return fmt.Errorf("invalid multihash: %s", err) + } + // Peer IDs require either sha256 or identity multihash + // https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#peer-ids + if h.Code != mh.SHA2_256 && h.Code != mh.IDENTITY { + return fmt.Errorf("invalid multihash code %d expected sha-256 or identity", h.Code) + } + // This check should ideally be in multihash. sha256 digest lengths MUST be 32 + if h.Code == mh.SHA2_256 && h.Length != 32 { + return fmt.Errorf("invalid digest length %d for sha256 addr: expected 32", h.Length) + } + return nil +} + +func p2pBtS(b []byte) (string, error) { + m, err := mh.Cast(b) + if err != nil { + return "", err + } + return m.B58String(), nil +} + +var TranscoderUnix = NewTranscoderFromFunctions(unixStB, unixBtS, unixValidate) + +func unixStB(s string) ([]byte, error) { + return []byte(s), nil +} + +func unixBtS(b []byte) (string, error) { + return string(b), nil +} + +func unixValidate(b []byte) error { + // The string to bytes parser requires that all Path protocols begin with a '/' + // file://./codec.go#L49 + if len(b) < 2 { + return fmt.Errorf("byte slice too short: %d", len(b)) + } + if b[0] != '/' { + return errors.New("path protocol must begin with '/'") + } + if b[len(b)-1] == '/' { + return errors.New("unix socket path must not end in '/'") + } + return nil +} + +var TranscoderDns = NewTranscoderFromFunctions(dnsStB, dnsBtS, dnsVal) + +func dnsVal(b []byte) error { + if len(b) == 0 { + return fmt.Errorf("empty dns addr") + } + if bytes.IndexByte(b, '/') >= 0 { + return fmt.Errorf("domain name %q contains a slash", string(b)) + } + return nil +} + +func dnsStB(s string) ([]byte, error) { + b := []byte(s) + if err := dnsVal(b); err != nil { + return nil, err + } + return b, nil +} + +func dnsBtS(b []byte) (string, error) { + return string(b), nil +} + +var TranscoderCertHash = NewTranscoderFromFunctions(certHashStB, certHashBtS, validateCertHash) + +func certHashStB(s string) ([]byte, error) { + _, data, err := multibase.Decode(s) + if err != nil { + return nil, err + } + if _, err := mh.Decode(data); err != nil { + return nil, err + } + return data, nil +} + +func certHashBtS(b []byte) (string, error) { + return multibase.Encode(multibase.Base64url, b) +} + +func validateCertHash(b []byte) error { + _, err := mh.Decode(b) + return err +} + +var TranscoderHTTPPath = NewTranscoderFromFunctions(httpPathStB, httpPathBtS, validateHTTPPath) + +func httpPathStB(s string) ([]byte, error) { + unescaped, err := url.QueryUnescape(s) + if err != nil { + return nil, err + } + if len(unescaped) == 0 { + return nil, fmt.Errorf("empty http path is not allowed") + } + return []byte(unescaped), err +} + +func httpPathBtS(b []byte) (string, error) { + if len(b) == 0 { + return "", fmt.Errorf("empty http path is not allowed") + } + return url.QueryEscape(string(b)), nil +} + +func validateHTTPPath(b []byte) error { + if len(b) == 0 { + return fmt.Errorf("empty http path is not allowed") + } + return nil // We can represent any byte slice when we escape it. +} + +var TranscoderMemory = NewTranscoderFromFunctions(memoryStB, memoryBtS, memoryValidate) + +func memoryStB(s string) ([]byte, error) { + z, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, err + } + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, z) + return buf, nil +} + +func memoryBtS(b []byte) (string, error) { + if len(b) != 8 { + return "", fmt.Errorf("expected uint64, only found %d bits", len(b)*8) + } + z := binary.BigEndian.Uint64(b) + return strconv.FormatUint(z, 10), nil +} + +func memoryValidate(b []byte) error { + // Ensure the byte array is exactly 8 bytes long for a uint64 in big-endian format + if len(b) != 8 { + return errors.New("invalid length: must be exactly 8 bytes") + } + + return nil +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..16a347c --- /dev/null +++ b/util.go @@ -0,0 +1,204 @@ +package multiaddr + +import "fmt" + +// Split returns the sub-address portions of a multiaddr. +func Split(m Multiaddr) []Multiaddr { + if _, ok := m.(*Component); ok { + return []Multiaddr{m} + } + var addrs []Multiaddr + ForEach(m, func(c Component) bool { + addrs = append(addrs, &c) + return true + }) + return addrs +} + +// Join returns a combination of addresses. +func Join(ms ...Multiaddr) Multiaddr { + switch len(ms) { + case 0: + // empty multiaddr, unfortunately, we have callers that rely on + // this contract. + return &multiaddr{} + case 1: + return ms[0] + } + + length := 0 + for _, m := range ms { + if m == nil { + continue + } + length += len(m.Bytes()) + } + + bidx := 0 + b := make([]byte, length) + if length == 0 { + return nil + } + for _, mb := range ms { + if mb == nil { + continue + } + bidx += copy(b[bidx:], mb.Bytes()) + } + if length == 0 { + return nil + } + return &multiaddr{bytes: b} +} + +// Cast re-casts a byte slice as a multiaddr. will panic if it fails to parse. +func Cast(b []byte) Multiaddr { + m, err := NewMultiaddrBytes(b) + if err != nil { + panic(fmt.Errorf("multiaddr failed to parse: %s", err)) + } + return m +} + +// StringCast like Cast, but parses a string. Will also panic if it fails to parse. +func StringCast(s string) Multiaddr { + m, err := NewMultiaddr(s) + if err != nil { + panic(fmt.Errorf("multiaddr failed to parse: %s", err)) + } + return m +} + +// SplitFirst returns the first component and the rest of the multiaddr. +func SplitFirst(m Multiaddr) (*Component, Multiaddr) { + if m == nil { + return nil, nil + } + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + return c, nil + } + + b := m.Bytes() + if len(b) == 0 { + return nil, nil + } + n, c, err := readComponent(b) + if err != nil { + panic(err) + } + if len(b) == n { + return &c, nil + } + return &c, &multiaddr{b[n:]} +} + +// SplitLast returns the rest of the multiaddr and the last component. +func SplitLast(m Multiaddr) (Multiaddr, *Component) { + if m == nil { + return nil, nil + } + + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + return nil, c + } + + b := m.Bytes() + if len(b) == 0 { + return nil, nil + } + + var ( + c Component + err error + offset int + ) + for { + var n int + n, c, err = readComponent(b[offset:]) + if err != nil { + panic(err) + } + if len(b) == n+offset { + // Reached end + if offset == 0 { + // Only one component + return nil, &c + } + return &multiaddr{b[:offset]}, &c + } + offset += n + } +} + +// SplitFunc splits the multiaddr when the callback first returns true. The +// component on which the callback first returns will be included in the +// *second* multiaddr. +func SplitFunc(m Multiaddr, cb func(Component) bool) (Multiaddr, Multiaddr) { + if m == nil { + return nil, nil + } + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + if cb(*c) { + return nil, m + } + return m, nil + } + b := m.Bytes() + if len(b) == 0 { + return nil, nil + } + var ( + c Component + err error + offset int + ) + for offset < len(b) { + var n int + n, c, err = readComponent(b[offset:]) + if err != nil { + panic(err) + } + if cb(c) { + break + } + offset += n + } + switch offset { + case 0: + return nil, m + case len(b): + return m, nil + default: + return &multiaddr{b[:offset]}, &multiaddr{b[offset:]} + } +} + +// ForEach walks over the multiaddr, component by component. +// +// This function iterates over components *by value* to avoid allocating. +// Return true to continue iteration, false to stop. +func ForEach(m Multiaddr, cb func(c Component) bool) { + if m == nil { + return + } + // Shortcut if we already have a component + if c, ok := m.(*Component); ok { + cb(*c) + return + } + + b := m.Bytes() + for len(b) > 0 { + n, c, err := readComponent(b) + if err != nil { + panic(err) + } + if !cb(c) { + return + } + b = b[n:] + } +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..4976d7d --- /dev/null +++ b/util_test.go @@ -0,0 +1,142 @@ +package multiaddr + +import ( + "strings" + "testing" +) + +func TestSplitFirstLast(t *testing.T) { + ipStr := "/ip4/0.0.0.0" + tcpStr := "/tcp/123" + quicStr := "/quic" + ipfsStr := "/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7" + + for _, x := range [][]string{ + {ipStr, tcpStr, quicStr, ipfsStr}, + {ipStr, tcpStr, ipfsStr}, + {ipStr, tcpStr}, + {ipStr}, + } { + addr := StringCast(strings.Join(x, "")) + head, tail := SplitFirst(addr) + rest, last := SplitLast(addr) + if len(x) == 0 { + if head != nil { + t.Error("expected head to be nil") + } + if tail != nil { + t.Error("expected tail to be nil") + } + if rest != nil { + t.Error("expected rest to be nil") + } + if last != nil { + t.Error("expected last to be nil") + } + continue + } + if !head.Equal(StringCast(x[0])) { + t.Errorf("expected %s to be %s", head, x[0]) + } + if !last.Equal(StringCast(x[len(x)-1])) { + t.Errorf("expected %s to be %s", head, x[len(x)-1]) + } + if len(x) == 1 { + if tail != nil { + t.Error("expected tail to be nil") + } + if rest != nil { + t.Error("expected rest to be nil") + } + continue + } + tailExp := strings.Join(x[1:], "") + if !tail.Equal(StringCast(tailExp)) { + t.Errorf("expected %s to be %s", tail, tailExp) + } + restExp := strings.Join(x[:len(x)-1], "") + if !rest.Equal(StringCast(restExp)) { + t.Errorf("expected %s to be %s", rest, restExp) + } + } + + c, err := NewComponent("ip4", "127.0.0.1") + if err != nil { + t.Fatal(err) + } + + ci, m := SplitFirst(c) + if !ci.Equal(c) || m != nil { + t.Error("split first on component failed") + } + m, ci = SplitLast(c) + if !ci.Equal(c) || m != nil { + t.Error("split last on component failed") + } + cis := Split(c) + if len(cis) != 1 || !cis[0].Equal(c) { + t.Error("split on component failed") + } + m1, m2 := SplitFunc(c, func(c Component) bool { + return true + }) + if m1 != nil || !m2.Equal(c) { + t.Error("split func(true) on component failed") + } + m1, m2 = SplitFunc(c, func(c Component) bool { + return false + }) + if !m1.Equal(c) || m2 != nil { + t.Error("split func(false) on component failed") + } + + i := 0 + ForEach(c, func(ci Component) bool { + if i != 0 { + t.Error("expected exactly one component") + } + i++ + if !ci.Equal(c) { + t.Error("foreach on component failed") + } + return true + }) +} + +func TestSplitFunc(t *testing.T) { + ipStr := "/ip4/0.0.0.0" + tcpStr := "/tcp/123" + quicStr := "/quic" + ipfsStr := "/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7" + + for _, x := range [][]string{ + {ipStr, tcpStr, quicStr, ipfsStr}, + {ipStr, tcpStr, ipfsStr}, + {ipStr, tcpStr}, + {ipStr}, + } { + addr := StringCast(strings.Join(x, "")) + for i, cs := range x { + target := StringCast(cs) + a, b := SplitFunc(addr, func(c Component) bool { + return c.Equal(target) + }) + if i == 0 { + if a != nil { + t.Error("expected nil addr") + } + } else { + if !a.Equal(StringCast(strings.Join(x[:i], ""))) { + t.Error("split failed") + } + if !b.Equal(StringCast(strings.Join(x[i:], ""))) { + t.Error("split failed") + } + } + } + a, b := SplitFunc(addr, func(_ Component) bool { return false }) + if !a.Equal(addr) || b != nil { + t.Error("should not have split") + } + } +} diff --git a/varint.go b/varint.go new file mode 100644 index 0000000..d1ea7fc --- /dev/null +++ b/varint.go @@ -0,0 +1,27 @@ +package multiaddr + +import ( + "math" + + "github.com/multiformats/go-varint" +) + +// CodeToVarint converts an integer to a varint-encoded []byte +func CodeToVarint(num int) []byte { + if num < 0 || num > math.MaxInt32 { + panic("invalid code") + } + return varint.ToUvarint(uint64(num)) +} + +func ReadVarintCode(b []byte) (int, int, error) { + code, n, err := varint.FromUvarint(b) + if err != nil { + return 0, 0, err + } + if code > math.MaxInt32 { + // we only allow 32bit codes. + return 0, 0, varint.ErrOverflow + } + return int(code), n, err +} diff --git a/version.json b/version.json new file mode 100644 index 0000000..7dd7b57 --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "version": "v0.14.0" +}