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 (
"strings"
)
// Multiaddr is the data structure representing a multiaddr
type Multiaddr struct {
Bytes []byte
// 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) {
func NewMultiaddr(s string) (Multiaddr, error) {
b, err := stringToBytes(s)
if err != nil {
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
func (m *Multiaddr) Equal(m2 *Multiaddr) bool {
return bytes.Equal(m.Bytes, m2.Bytes)
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)
func (m *multiaddr) String() string {
s, err := bytesToString(m.bytes)
if err != nil {
panic("multiaddr failed to convert back to string. corrupted?")
}
......@@ -35,82 +53,58 @@ func (m *Multiaddr) String() string {
}
// 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.
defer func() {
if e := recover(); e != nil {
ret = nil
err = e.(error)
err := e.(error)
panic("Multiaddr.Protocols error: " + err.Error())
}
}()
ps := []*Protocol{}
b := m.Bytes[:]
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])
// 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, nil
return ps
}
// 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}
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, error) {
func (m *multiaddr) Decapsulate(o Multiaddr) Multiaddr {
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)
// if multiaddr not contained, returns a copy.
cpy := make([]byte, len(m.bytes))
copy(cpy, m.bytes)
return &multiaddr{bytes: cpy}
}
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()
ma, err := NewMultiaddr(s1[:i])
if err != nil {
return false
panic("Multiaddr.Decapsulate incorrect byte boundaries.")
}
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
return ma
}
......@@ -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")
}
}
......@@ -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
}
......@@ -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")
}
}
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