Commit c90ef447 authored by Juan Batiz-Benet's avatar Juan Batiz-Benet

New Multiaddr interface

This commit changes the struct to a new Multiaddr interface:

```Go
type Multiaddr interface {
  Equal(Multiaddr) bool
  Bytes() []byte
  String() string
  Protocols() []*Protocol
  Encapsulate(Multiaddr) Multiaddr
  Decapsulate(Multiaddr) Multiaddr
}
```

This means a few things have changed:

- use Multiaddr interface, struct not exported
- Bytes returns a copy of the internal bytes
- Some methods no longer return errors (catch errors in NewMultiaddr)
  - String (panics if malformed)
  - Protocols (panics if malformed)
  - Decapsulate (no-op if not prefix)
- Moved net-specific functions to package
  - Multiaddr.DialArgs() -> DialArgs(Multiaddr)
  - Multiaddr.IsThinWaist() -> IsThinWaist(Multiaddr)

cc @whyrusleeping @perfmode
parent bd30912d
package multiaddr
/*
Multiaddr is a cross-protocol, cross-platform format for representing
internet addresses. It emphasizes explicitness and self-description.
Learn more here: https://github.com/jbenet/multiaddr
Multiaddrs have both a binary and string representation.
import ma "github.com/jbenet/go-multiaddr"
addr, err := ma.NewMultiaddr("/ip4/1.2.3.4/tcp/80")
// err non-nil when parsing failed.
*/
type Multiaddr interface {
// Equal returns whether two Multiaddrs are exactly equal
Equal(Multiaddr) bool
// Bytes returns the []byte representation of this Multiaddr
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
// Decapsultate removes a Multiaddr wrapping. For example:
//
// /ip4/1.2.3.4/tcp/80 decapsulate /ip4/1.2.3.4 = /tcp/80
//
Decapsulate(Multiaddr) Multiaddr
}
...@@ -6,28 +6,46 @@ import ( ...@@ -6,28 +6,46 @@ import (
"strings" "strings"
) )
// Multiaddr is the data structure representing a multiaddr // multiaddr is the data structure representing a Multiaddr
type Multiaddr struct { type multiaddr struct {
Bytes []byte bytes []byte
} }
// NewMultiaddr parses and validates an input string, returning a *Multiaddr // NewMultiaddr parses and validates an input string, returning a *Multiaddr
func NewMultiaddr(s string) (*Multiaddr, error) { func NewMultiaddr(s string) (Multiaddr, error) {
b, err := stringToBytes(s) b, err := stringToBytes(s)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Multiaddr{Bytes: b}, nil return &multiaddr{bytes: b}, nil
}
// NewMultiaddrBytes initializes a Multiaddr from a byte representation.
// It validates it as an input string.
func NewMultiaddrBytes(b []byte) (Multiaddr, error) {
s, err := bytesToString(b)
if err != nil {
return nil, err
}
return NewMultiaddr(s)
} }
// Equal tests whether two multiaddrs are equal // Equal tests whether two multiaddrs are equal
func (m *Multiaddr) Equal(m2 *Multiaddr) bool { func (m *multiaddr) Equal(m2 Multiaddr) bool {
return bytes.Equal(m.Bytes, m2.Bytes) return bytes.Equal(m.bytes, m2.Bytes())
}
// Bytes returns the []byte representation of this Multiaddr
func (m *multiaddr) Bytes() []byte {
// consider returning copy to prevent changing underneath us?
cpy := make([]byte, len(m.bytes))
copy(cpy, m.bytes)
return cpy
} }
// String returns the string representation of a Multiaddr // String returns the string representation of a Multiaddr
func (m *Multiaddr) String() string { func (m *multiaddr) String() string {
s, err := bytesToString(m.Bytes) s, err := bytesToString(m.bytes)
if err != nil { if err != nil {
panic("multiaddr failed to convert back to string. corrupted?") panic("multiaddr failed to convert back to string. corrupted?")
} }
...@@ -35,82 +53,58 @@ func (m *Multiaddr) String() string { ...@@ -35,82 +53,58 @@ func (m *Multiaddr) String() string {
} }
// Protocols returns the list of protocols this Multiaddr has. // Protocols returns the list of protocols this Multiaddr has.
func (m *Multiaddr) Protocols() (ret []*Protocol, err error) { // will panic in case we access bytes incorrectly.
func (m *multiaddr) Protocols() []*Protocol {
// panic handler, in case we try accessing bytes incorrectly. // panic handler, in case we try accessing bytes incorrectly.
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
ret = nil err := e.(error)
err = e.(error) panic("Multiaddr.Protocols error: " + err.Error())
} }
}() }()
ps := []*Protocol{} ps := []*Protocol{}
b := m.Bytes[:] b := m.bytes[:]
for len(b) > 0 { for len(b) > 0 {
p := ProtocolWithCode(int(b[0])) p := ProtocolWithCode(int(b[0]))
if p == nil { if p == nil {
return nil, fmt.Errorf("no protocol with code %d", b[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) ps = append(ps, p)
b = b[1+(p.Size/8):] b = b[1+(p.Size/8):]
} }
return ps, nil return ps
} }
// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr // Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr
func (m *Multiaddr) Encapsulate(o *Multiaddr) *Multiaddr { func (m *multiaddr) Encapsulate(o Multiaddr) Multiaddr {
b := make([]byte, len(m.Bytes)+len(o.Bytes)) mb := m.bytes
b = append(m.Bytes, o.Bytes...) ob := o.Bytes()
return &Multiaddr{Bytes: b}
var b bytes.Buffer
b.Write(mb)
b.Write(ob)
return &multiaddr{bytes: b.Bytes()}
} }
// Decapsulate unwraps Multiaddr up until the given Multiaddr is found. // Decapsulate unwraps Multiaddr up until the given Multiaddr is found.
func (m *Multiaddr) Decapsulate(o *Multiaddr) (*Multiaddr, error) { func (m *multiaddr) Decapsulate(o Multiaddr) Multiaddr {
s1 := m.String() s1 := m.String()
s2 := o.String() s2 := o.String()
i := strings.LastIndex(s1, s2) i := strings.LastIndex(s1, s2)
if i < 0 { if i < 0 {
return nil, fmt.Errorf("%s not contained in %s", s2, s1) // if multiaddr not contained, returns a copy.
} cpy := make([]byte, len(m.bytes))
return NewMultiaddr(s1[:i]) copy(cpy, m.bytes)
} return &multiaddr{bytes: cpy}
// DialArgs is a convenience function returning arguments for use in net.Dial
func (m *Multiaddr) DialArgs() (string, string, error) {
if !m.IsThinWaist() {
return "", "", fmt.Errorf("%s is not a 'thin waist' address", m)
} }
str := m.String() ma, err := NewMultiaddr(s1[:i])
parts := strings.Split(str, "/")[1:]
network := parts[2]
var host string
switch parts[0] {
case "ip4":
host = strings.Join([]string{parts[1], parts[3]}, ":")
case "ip6":
host = fmt.Sprintf("[%s]:%s", parts[1], parts[3])
}
return network, host, nil
}
// IsThinWaist returns whether this multiaddr includes "Thin Waist" Protocols.
// This means: /{IP4, IP6}/{TCP, UDP}
func (m *Multiaddr) IsThinWaist() bool {
p, err := m.Protocols()
if err != nil { if err != nil {
return false panic("Multiaddr.Decapsulate incorrect byte boundaries.")
} }
return ma
if p[0].Code != P_IP4 && p[0].Code != P_IP6 {
return false
}
if p[1].Code != P_TCP && p[1].Code != P_UDP {
return false
}
return true
} }
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"testing" "testing"
) )
func newMultiaddr(t *testing.T, a string) *Multiaddr { func newMultiaddr(t *testing.T, a string) Multiaddr {
m, err := NewMultiaddr(a) m, err := NewMultiaddr(a)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
...@@ -88,11 +88,7 @@ func TestProtocols(t *testing.T) { ...@@ -88,11 +88,7 @@ func TestProtocols(t *testing.T) {
t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234") t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234")
} }
ps, err := m.Protocols() ps := m.Protocols()
if err != nil {
t.Error("failed to get protocols", "/ip4/127.0.0.1/udp/1234")
}
if ps[0] != ProtocolWithName("ip4") { if ps[0] != ProtocolWithName("ip4") {
t.Error(ps[0], ProtocolWithName("ip4")) t.Error(ps[0], ProtocolWithName("ip4"))
t.Error("failed to get ip4 protocol") t.Error("failed to get ip4 protocol")
...@@ -122,42 +118,14 @@ func TestEncapsulate(t *testing.T) { ...@@ -122,42 +118,14 @@ func TestEncapsulate(t *testing.T) {
} }
m3, _ := NewMultiaddr("/udp/5678") m3, _ := NewMultiaddr("/udp/5678")
c, err := b.Decapsulate(m3) c := b.Decapsulate(m3)
if err != nil {
t.Error("decapsulate /udp failed.", err)
}
if s := c.String(); s != "/ip4/127.0.0.1/udp/1234" { 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) t.Error("decapsulate /udp failed.", "/ip4/127.0.0.1/udp/1234", s)
} }
m4, _ := NewMultiaddr("/ip4/127.0.0.1") m4, _ := NewMultiaddr("/ip4/127.0.0.1")
d, err := c.Decapsulate(m4) d := c.Decapsulate(m4)
if err != nil {
t.Error("decapsulate /ip4 failed.", err)
}
if s := d.String(); s != "" { if s := d.String(); s != "" {
t.Error("decapsulate /ip4 failed.", "/", s) t.Error("decapsulate /ip4 failed.", "/", s)
} }
} }
func TestDialArgs(t *testing.T) {
m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234")
if err != nil {
t.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234")
}
nw, host, err := m.DialArgs()
if err != nil {
t.Fatal("failed to get dial args", "/ip4/127.0.0.1/udp/1234", err)
}
if nw != "udp" {
t.Error("failed to get udp network Dial Arg")
}
if host != "127.0.0.1:1234" {
t.Error("failed to get host:port Dial Arg")
}
}
...@@ -3,12 +3,13 @@ package multiaddr ...@@ -3,12 +3,13 @@ package multiaddr
import ( import (
"fmt" "fmt"
"net" "net"
"strings"
) )
var errIncorrectNetAddr = fmt.Errorf("incorrect network addr conversion") var errIncorrectNetAddr = fmt.Errorf("incorrect network addr conversion")
// FromNetAddr converts a net.Addr type to a Multiaddr. // FromNetAddr converts a net.Addr type to a Multiaddr.
func FromNetAddr(a net.Addr) (*Multiaddr, error) { func FromNetAddr(a net.Addr) (Multiaddr, error) {
switch a.Network() { switch a.Network() {
case "tcp", "tcp4", "tcp6": case "tcp", "tcp4", "tcp6":
ac, ok := a.(*net.TCPAddr) ac, ok := a.(*net.TCPAddr)
...@@ -65,7 +66,7 @@ func FromNetAddr(a net.Addr) (*Multiaddr, error) { ...@@ -65,7 +66,7 @@ func FromNetAddr(a net.Addr) (*Multiaddr, error) {
} }
// FromIP converts a net.IP type to a Multiaddr. // FromIP converts a net.IP type to a Multiaddr.
func FromIP(ip net.IP) (*Multiaddr, error) { func FromIP(ip net.IP) (Multiaddr, error) {
switch { switch {
case ip.To4() != nil: case ip.To4() != nil:
return NewMultiaddr("/ip4/" + ip.String()) return NewMultiaddr("/ip4/" + ip.String())
...@@ -75,3 +76,38 @@ func FromIP(ip net.IP) (*Multiaddr, error) { ...@@ -75,3 +76,38 @@ func FromIP(ip net.IP) (*Multiaddr, error) {
return nil, errIncorrectNetAddr return nil, errIncorrectNetAddr
} }
} }
// DialArgs is a convenience function returning arguments for use in net.Dial
func DialArgs(m Multiaddr) (string, string, error) {
if !IsThinWaist(m) {
return "", "", fmt.Errorf("%s is not a 'thin waist' address", m)
}
str := m.String()
parts := strings.Split(str, "/")[1:]
network := parts[2]
var host string
switch parts[0] {
case "ip4":
host = strings.Join([]string{parts[1], parts[3]}, ":")
case "ip6":
host = fmt.Sprintf("[%s]:%s", parts[1], parts[3])
}
return network, host, nil
}
// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols.
// This means: /{IP4, IP6}/{TCP, UDP}
func IsThinWaist(m Multiaddr) bool {
p := m.Protocols()
if p[0].Code != P_IP4 && p[0].Code != P_IP6 {
return false
}
if p[1].Code != P_TCP && p[1].Code != P_UDP {
return false
}
return true
}
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"testing" "testing"
) )
type GenFunc func() (*Multiaddr, error) type GenFunc func() (Multiaddr, error)
func testConvert(t *testing.T, s string, gen GenFunc) { func testConvert(t *testing.T, s string, gen GenFunc) {
m, err := gen() m, err := gen()
...@@ -19,19 +19,19 @@ func testConvert(t *testing.T, s string, gen GenFunc) { ...@@ -19,19 +19,19 @@ func testConvert(t *testing.T, s string, gen GenFunc) {
} }
func TestFromIP4(t *testing.T) { func TestFromIP4(t *testing.T) {
testConvert(t, "/ip4/10.20.30.40", func() (*Multiaddr, error) { testConvert(t, "/ip4/10.20.30.40", func() (Multiaddr, error) {
return FromIP(net.ParseIP("10.20.30.40")) return FromIP(net.ParseIP("10.20.30.40"))
}) })
} }
func TestFromIP6(t *testing.T) { func TestFromIP6(t *testing.T) {
testConvert(t, "/ip6/2001:4860:0:2001::68", func() (*Multiaddr, error) { testConvert(t, "/ip6/2001:4860:0:2001::68", func() (Multiaddr, error) {
return FromIP(net.ParseIP("2001:4860:0:2001::68")) return FromIP(net.ParseIP("2001:4860:0:2001::68"))
}) })
} }
func TestFromTCP(t *testing.T) { func TestFromTCP(t *testing.T) {
testConvert(t, "/ip4/10.20.30.40/tcp/1234", func() (*Multiaddr, error) { testConvert(t, "/ip4/10.20.30.40/tcp/1234", func() (Multiaddr, error) {
return FromNetAddr(&net.TCPAddr{ return FromNetAddr(&net.TCPAddr{
IP: net.ParseIP("10.20.30.40"), IP: net.ParseIP("10.20.30.40"),
Port: 1234, Port: 1234,
...@@ -40,10 +40,30 @@ func TestFromTCP(t *testing.T) { ...@@ -40,10 +40,30 @@ func TestFromTCP(t *testing.T) {
} }
func TestFromUDP(t *testing.T) { func TestFromUDP(t *testing.T) {
testConvert(t, "/ip4/10.20.30.40/udp/1234", func() (*Multiaddr, error) { testConvert(t, "/ip4/10.20.30.40/udp/1234", func() (Multiaddr, error) {
return FromNetAddr(&net.UDPAddr{ return FromNetAddr(&net.UDPAddr{
IP: net.ParseIP("10.20.30.40"), IP: net.ParseIP("10.20.30.40"),
Port: 1234, Port: 1234,
}) })
}) })
} }
func TestDialArgs(t *testing.T) {
m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234")
if err != nil {
t.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234")
}
nw, host, err := DialArgs(m)
if err != nil {
t.Fatal("failed to get dial args", "/ip4/127.0.0.1/udp/1234", err)
}
if nw != "udp" {
t.Error("failed to get udp network Dial Arg")
}
if host != "127.0.0.1:1234" {
t.Error("failed to get host:port Dial Arg")
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment