From c90ef4472ffe34d61c9ff7f850869bf8904ae05a Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Mon, 6 Oct 2014 03:27:29 -0700 Subject: [PATCH] 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 --- index.go | 116 ---------------------------------------------- interface.go | 42 +++++++++++++++++ multiaddr.go | 110 +++++++++++++++++++++++++++++++++++++++++++ multiaddr_test.go | 40 ++-------------- net.go | 40 +++++++++++++++- net_test.go | 30 ++++++++++-- 6 files changed, 219 insertions(+), 159 deletions(-) delete mode 100644 index.go create mode 100644 interface.go create mode 100644 multiaddr.go diff --git a/index.go b/index.go deleted file mode 100644 index 65ff750..0000000 --- a/index.go +++ /dev/null @@ -1,116 +0,0 @@ -package multiaddr - -import ( - "bytes" - "fmt" - "strings" -) - -// 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) (*Multiaddr, error) { - b, err := stringToBytes(s) - if err != nil { - return nil, err - } - return &Multiaddr{Bytes: b}, nil -} - -// Equal tests whether two multiaddrs are equal -func (m *Multiaddr) Equal(m2 *Multiaddr) bool { - return bytes.Equal(m.Bytes, m2.Bytes) -} - -// String returns the string representation of a Multiaddr -func (m *Multiaddr) String() string { - s, err := bytesToString(m.Bytes) - if err != nil { - panic("multiaddr failed to convert back to string. corrupted?") - } - return s -} - -// Protocols returns the list of protocols this Multiaddr has. -func (m *Multiaddr) Protocols() (ret []*Protocol, err error) { - - // panic handler, in case we try accessing bytes incorrectly. - defer func() { - if e := recover(); e != nil { - ret = nil - err = e.(error) - } - }() - - ps := []*Protocol{} - b := m.Bytes[:] - for len(b) > 0 { - p := ProtocolWithCode(int(b[0])) - if p == nil { - return nil, fmt.Errorf("no protocol with code %d", b[0]) - } - ps = append(ps, p) - b = b[1+(p.Size/8):] - } - return ps, nil -} - -// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr -func (m *Multiaddr) Encapsulate(o *Multiaddr) *Multiaddr { - b := make([]byte, len(m.Bytes)+len(o.Bytes)) - b = append(m.Bytes, o.Bytes...) - return &Multiaddr{Bytes: b} -} - -// Decapsulate unwraps Multiaddr up until the given Multiaddr is found. -func (m *Multiaddr) Decapsulate(o *Multiaddr) (*Multiaddr, error) { - s1 := m.String() - s2 := o.String() - i := strings.LastIndex(s1, s2) - if i < 0 { - return nil, fmt.Errorf("%s not contained in %s", s2, s1) - } - return NewMultiaddr(s1[:i]) -} - -// 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() - 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 { - return false - } - - 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 -} diff --git a/interface.go b/interface.go new file mode 100644 index 0000000..6f57625 --- /dev/null +++ b/interface.go @@ -0,0 +1,42 @@ +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 +} diff --git a/multiaddr.go b/multiaddr.go new file mode 100644 index 0000000..4ee63ca --- /dev/null +++ b/multiaddr.go @@ -0,0 +1,110 @@ +package multiaddr + +import ( + "bytes" + "fmt" + "strings" +) + +// 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) (Multiaddr, error) { + 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) (Multiaddr, error) { + s, err := bytesToString(b) + if err != nil { + return nil, err + } + return NewMultiaddr(s) +} + +// Equal tests whether two multiaddrs are equal +func (m *multiaddr) Equal(m2 Multiaddr) bool { + 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 +func (m *multiaddr) String() string { + s, err := bytesToString(m.bytes) + if err != nil { + panic("multiaddr failed to convert back to string. corrupted?") + } + return s +} + +// Protocols returns the list of protocols this Multiaddr has. +// will panic in case we access bytes incorrectly. +func (m *multiaddr) Protocols() []*Protocol { + + // panic handler, in case we try accessing bytes incorrectly. + defer func() { + if e := recover(); e != nil { + err := e.(error) + panic("Multiaddr.Protocols error: " + err.Error()) + } + }() + + ps := []*Protocol{} + b := m.bytes[:] + for len(b) > 0 { + p := ProtocolWithCode(int(b[0])) + if p == nil { + // 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[1+(p.Size/8):] + } + return ps +} + +// Encapsulate wraps a given Multiaddr, returning the resulting joined Multiaddr +func (m *multiaddr) Encapsulate(o Multiaddr) Multiaddr { + mb := m.bytes + ob := o.Bytes() + + 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. +func (m *multiaddr) Decapsulate(o Multiaddr) Multiaddr { + s1 := m.String() + s2 := o.String() + i := strings.LastIndex(s1, s2) + if i < 0 { + // if multiaddr not contained, returns a copy. + cpy := make([]byte, len(m.bytes)) + copy(cpy, m.bytes) + return &multiaddr{bytes: cpy} + } + + ma, err := NewMultiaddr(s1[:i]) + if err != nil { + panic("Multiaddr.Decapsulate incorrect byte boundaries.") + } + return ma +} diff --git a/multiaddr_test.go b/multiaddr_test.go index 5de5aaa..12d6214 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -func newMultiaddr(t *testing.T, a string) *Multiaddr { +func newMultiaddr(t *testing.T, a string) Multiaddr { m, err := NewMultiaddr(a) if err != nil { t.Error(err) @@ -88,11 +88,7 @@ func TestProtocols(t *testing.T) { t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234") } - ps, err := m.Protocols() - if err != nil { - t.Error("failed to get protocols", "/ip4/127.0.0.1/udp/1234") - } - + ps := m.Protocols() if ps[0] != ProtocolWithName("ip4") { t.Error(ps[0], ProtocolWithName("ip4")) t.Error("failed to get ip4 protocol") @@ -122,42 +118,14 @@ func TestEncapsulate(t *testing.T) { } m3, _ := NewMultiaddr("/udp/5678") - c, err := b.Decapsulate(m3) - if err != nil { - t.Error("decapsulate /udp failed.", err) - } - + 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, err := c.Decapsulate(m4) - if err != nil { - t.Error("decapsulate /ip4 failed.", err) - } - + d := c.Decapsulate(m4) if s := d.String(); 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") - } -} diff --git a/net.go b/net.go index 516fe83..ed91dc2 100644 --- a/net.go +++ b/net.go @@ -3,12 +3,13 @@ package multiaddr import ( "fmt" "net" + "strings" ) var errIncorrectNetAddr = fmt.Errorf("incorrect network addr conversion") // 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() { case "tcp", "tcp4", "tcp6": ac, ok := a.(*net.TCPAddr) @@ -65,7 +66,7 @@ func FromNetAddr(a net.Addr) (*Multiaddr, error) { } // FromIP converts a net.IP type to a Multiaddr. -func FromIP(ip net.IP) (*Multiaddr, error) { +func FromIP(ip net.IP) (Multiaddr, error) { switch { case ip.To4() != nil: return NewMultiaddr("/ip4/" + ip.String()) @@ -75,3 +76,38 @@ func FromIP(ip net.IP) (*Multiaddr, error) { 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 +} diff --git a/net_test.go b/net_test.go index f734b28..c9cb4b4 100644 --- a/net_test.go +++ b/net_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -type GenFunc func() (*Multiaddr, error) +type GenFunc func() (Multiaddr, error) func testConvert(t *testing.T, s string, gen GenFunc) { m, err := gen() @@ -19,19 +19,19 @@ func testConvert(t *testing.T, s string, gen GenFunc) { } 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")) }) } 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")) }) } 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{ IP: net.ParseIP("10.20.30.40"), Port: 1234, @@ -40,10 +40,30 @@ func TestFromTCP(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{ IP: net.ParseIP("10.20.30.40"), 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") + } +}