From 19193054189bec4bf475b6b7538dbe953b63a8d3 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 14 Oct 2016 11:48:19 -0400 Subject: [PATCH] add Transocoder functionality Allowing multiaddr extensions to have data with --- codec.go | 158 +++----------------------------------------- protocols.go | 37 ++++++----- transcoders.go | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 168 deletions(-) create mode 100644 transcoders.go diff --git a/codec.go b/codec.go index 3fc7dd2..29573f2 100644 --- a/codec.go +++ b/codec.go @@ -2,15 +2,8 @@ package multiaddr import ( "bytes" - "encoding/base32" - "encoding/binary" - "errors" "fmt" - "net" - "strconv" "strings" - - mh "github.com/multiformats/go-multihash" ) func stringToBytes(s string) ([]byte, error) { @@ -50,7 +43,10 @@ func stringToBytes(s string) ([]byte, error) { sp = []string{"/" + strings.Join(sp, "/")} } - a, err := addressStringToBytes(p, sp[0]) + if p.Transcoder == nil { + return nil, fmt.Errorf("no transcoder for %s protocol", p.Name) + } + a, err := p.Transcoder.StringToBytes(sp[0]) if err != nil { return nil, fmt.Errorf("failed to parse %s: %s %s", p.Name, sp[0], err) } @@ -122,7 +118,10 @@ func bytesToString(b []byte) (ret string, err error) { return "", fmt.Errorf("invalid value for size") } - a, err := addressBytesToString(p, b[:size]) + if p.Transcoder == nil { + return "", fmt.Errorf("no transcoder for %s protocol", p.Name) + } + a, err := p.Transcoder.BytesToString(b[:size]) if err != nil { return "", err } @@ -181,144 +180,3 @@ func bytesSplit(b []byte) ([][]byte, error) { return ret, nil } - -func addressStringToBytes(p Protocol, s string) ([]byte, error) { - switch p.Code { - - case P_IP4: // ipv4 - i := net.ParseIP(s).To4() - if i == nil { - return nil, fmt.Errorf("failed to parse ip4 addr: %s", s) - } - return i, nil - - case P_IP6: // ipv6 - i := net.ParseIP(s).To16() - if i == nil { - return nil, fmt.Errorf("failed to parse ip6 addr: %s", s) - } - return i, nil - - // tcp udp dccp sctp - case P_TCP, P_UDP, P_DCCP, P_SCTP: - i, err := strconv.Atoi(s) - if err != nil { - return nil, fmt.Errorf("failed to parse %s addr: %s", p.Name, err) - } - if i >= 65536 { - return nil, fmt.Errorf("failed to parse %s addr: %s", p.Name, "greater than 65536") - } - b := make([]byte, 2) - binary.BigEndian.PutUint16(b, uint16(i)) - return b, nil - - case P_ONION: - addr := strings.Split(s, ":") - if len(addr) != 2 { - return nil, fmt.Errorf("failed to parse %s addr: %s does not contain a port number.", p.Name, s) - } - - // onion address without the ".onion" substring - if len(addr[0]) != 16 { - return nil, fmt.Errorf("failed to parse %s addr: %s not a Tor onion address.", p.Name, s) - } - onionHostBytes, err := base32.StdEncoding.DecodeString(strings.ToUpper(addr[0])) - if err != nil { - return nil, fmt.Errorf("failed to decode base32 %s addr: %s %s", p.Name, s, err) - } - - // onion port number - i, err := strconv.Atoi(addr[1]) - if err != nil { - return nil, fmt.Errorf("failed to parse %s addr: %s", p.Name, err) - } - if i >= 65536 { - return nil, fmt.Errorf("failed to parse %s addr: %s", p.Name, "port greater than 65536") - } - if i < 1 { - return nil, fmt.Errorf("failed to parse %s addr: %s", p.Name, "port less than 1") - } - - onionPortBytes := make([]byte, 2) - binary.BigEndian.PutUint16(onionPortBytes, uint16(i)) - bytes := []byte{} - bytes = append(bytes, onionHostBytes...) - bytes = append(bytes, onionPortBytes...) - return bytes, nil - - case P_IPFS: // ipfs - // the address is a varint prefixed multihash string representation - m, err := mh.FromB58String(s) - if err != nil { - return nil, fmt.Errorf("failed to parse ipfs addr: %s %s", s, err) - } - size := CodeToVarint(len(m)) - b := append(size, m...) - return b, nil - - case P_UNIX: - // the address is the whole remaining string, prefixed by a varint len - size := CodeToVarint(len(s)) - b := append(size, []byte(s)...) - return b, nil - } - - return []byte{}, fmt.Errorf("failed to parse %s addr: unknown", p.Name) -} - -func addressBytesToString(p Protocol, b []byte) (string, error) { - switch p.Code { - - // ipv4,6 - case P_IP4, P_IP6: - return net.IP(b).String(), nil - - // tcp udp dccp sctp - case P_TCP, P_UDP, P_DCCP, P_SCTP: - i := binary.BigEndian.Uint16(b) - return strconv.Itoa(int(i)), nil - - case P_IPFS: // ipfs - // the address is a varint-prefixed multihash string representation - size, n, err := ReadVarintCode(b) - if err != nil { - return "", err - } - - b = b[n:] - if len(b) != size { - return "", errors.New("inconsistent lengths") - } - m, err := mh.Cast(b) - if err != nil { - return "", err - } - return m.B58String(), nil - - case P_ONION: - addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:10])) - port := binary.BigEndian.Uint16(b[10:12]) - return addr + ":" + strconv.Itoa(int(port)), nil - - case P_UNIX: - // the address is a varint len prefixed string - size, n, err := ReadVarintCode(b) - if err != nil { - return "", err - } - - b = b[n:] - if len(b) != size { - return "", errors.New("inconsistent lengths") - } - if size == 0 { - return "", errors.New("invalid length") - } - s := string(b) - s = s[1:] // remove starting slash - return s, nil - - default: - return "", errors.New("unknown protocol") - } -} diff --git a/protocols.go b/protocols.go index aff30dc..1114d4e 100644 --- a/protocols.go +++ b/protocols.go @@ -8,11 +8,12 @@ import ( // Protocol is a Multiaddr protocol description structure. type Protocol struct { - Code int - Size int // a size of -1 indicates a length-prefixed variable size - Name string - VCode []byte - Path bool // indicates a path protocol (eg unix, http) + Code int + Size int // a size of -1 indicates a length-prefixed variable size + Name string + VCode []byte + Path bool // indicates a path protocol (eg unix, http) + Transcoder Transcoder } // replicating table here to: @@ -42,20 +43,20 @@ const ( // Protocols is the list of multiaddr protocols supported by this module. var Protocols = []Protocol{ - Protocol{P_IP4, 32, "ip4", CodeToVarint(P_IP4), false}, - Protocol{P_TCP, 16, "tcp", CodeToVarint(P_TCP), false}, - Protocol{P_UDP, 16, "udp", CodeToVarint(P_UDP), false}, - Protocol{P_DCCP, 16, "dccp", CodeToVarint(P_DCCP), false}, - Protocol{P_IP6, 128, "ip6", CodeToVarint(P_IP6), false}, + Protocol{P_IP4, 32, "ip4", CodeToVarint(P_IP4), false, TranscoderIP4}, + Protocol{P_TCP, 16, "tcp", CodeToVarint(P_TCP), false, TranscoderPort}, + Protocol{P_UDP, 16, "udp", CodeToVarint(P_UDP), false, TranscoderPort}, + Protocol{P_DCCP, 16, "dccp", CodeToVarint(P_DCCP), false, TranscoderPort}, + Protocol{P_IP6, 128, "ip6", CodeToVarint(P_IP6), false, TranscoderIP6}, // these require varint: - Protocol{P_SCTP, 16, "sctp", CodeToVarint(P_SCTP), false}, - Protocol{P_ONION, 96, "onion", CodeToVarint(P_ONION), false}, - Protocol{P_UTP, 0, "utp", CodeToVarint(P_UTP), false}, - Protocol{P_UDT, 0, "udt", CodeToVarint(P_UDT), false}, - Protocol{P_HTTP, 0, "http", CodeToVarint(P_HTTP), false}, - Protocol{P_HTTPS, 0, "https", CodeToVarint(P_HTTPS), false}, - Protocol{P_IPFS, LengthPrefixedVarSize, "ipfs", CodeToVarint(P_IPFS), false}, - Protocol{P_UNIX, LengthPrefixedVarSize, "unix", CodeToVarint(P_UNIX), true}, + Protocol{P_SCTP, 16, "sctp", CodeToVarint(P_SCTP), false, TranscoderPort}, + Protocol{P_ONION, 96, "onion", CodeToVarint(P_ONION), false, TranscoderOnion}, + Protocol{P_UTP, 0, "utp", CodeToVarint(P_UTP), false, nil}, + Protocol{P_UDT, 0, "udt", CodeToVarint(P_UDT), false, nil}, + Protocol{P_HTTP, 0, "http", CodeToVarint(P_HTTP), false, nil}, + Protocol{P_HTTPS, 0, "https", CodeToVarint(P_HTTPS), false, nil}, + Protocol{P_IPFS, LengthPrefixedVarSize, "ipfs", CodeToVarint(P_IPFS), false, TranscoderIPFS}, + Protocol{P_UNIX, LengthPrefixedVarSize, "unix", CodeToVarint(P_UNIX), true, TranscoderUnix}, } func AddProtocol(p Protocol) error { diff --git a/transcoders.go b/transcoders.go new file mode 100644 index 0000000..d88f3d6 --- /dev/null +++ b/transcoders.go @@ -0,0 +1,175 @@ +package multiaddr + +import ( + "encoding/base32" + "encoding/binary" + "errors" + "fmt" + "net" + "strconv" + "strings" + + mh "github.com/multiformats/go-multihash" +) + +type Transcoder interface { + StringToBytes(string) ([]byte, error) + BytesToString([]byte) (string, error) +} + +type twrp struct { + strtobyte func(string) ([]byte, error) + bytetostr func([]byte) (string, 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) +} + +var TranscoderIP4 = (Transcoder)(twrp{ip4StB, ipBtS}) +var TranscoderIP6 = (Transcoder)(twrp{ip6StB, ipBtS}) + +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 ip6StB(s string) ([]byte, error) { + i := net.ParseIP(s).To16() + if i == nil { + return nil, fmt.Errorf("failed to parse ip4 addr: %s", s) + } + return i, nil +} + +func ipBtS(b []byte) (string, error) { + return net.IP(b).String(), nil +} + +var TranscoderPort = (Transcoder)(twrp{portStB, portBtS}) + +func portStB(s string) ([]byte, error) { + i, err := strconv.Atoi(s) + if err != nil { + return nil, fmt.Errorf("failed to parse port addr: %s", err) + } + if i >= 65536 { + return nil, fmt.Errorf("failed to parse port addr: %s", "greater than 65536") + } + 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.Itoa(int(i)), nil +} + +var TranscoderOnion = (Transcoder)(twrp{onionStB, onionBtS}) + +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) + } + + // onion address without the ".onion" substring + if len(addr[0]) != 16 { + return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onion address.", 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 port number + i, err := strconv.Atoi(addr[1]) + if err != nil { + return nil, fmt.Errorf("failed to parse onion addr: %s", err) + } + if i >= 65536 { + return nil, fmt.Errorf("failed to parse onion addr: %s", "port greater than 65536") + } + if i < 1 { + return nil, fmt.Errorf("failed to parse onion addr: %s", "port less than 1") + } + + 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]) + return addr + ":" + strconv.Itoa(int(port)), nil +} + +var TranscoderIPFS = (Transcoder)(twrp{ipfsStB, ipfsBtS}) + +func ipfsStB(s string) ([]byte, error) { + // the address is a varint prefixed multihash string representation + m, err := mh.FromB58String(s) + if err != nil { + return nil, fmt.Errorf("failed to parse ipfs addr: %s %s", s, err) + } + size := CodeToVarint(len(m)) + b := append(size, m...) + return b, nil +} + +func ipfsBtS(b []byte) (string, error) { + // the address is a varint-prefixed multihash string representation + size, n, err := ReadVarintCode(b) + if err != nil { + return "", err + } + + b = b[n:] + if len(b) != size { + return "", errors.New("inconsistent lengths") + } + m, err := mh.Cast(b) + if err != nil { + return "", err + } + return m.B58String(), nil +} + +var TranscoderUnix = (Transcoder)(twrp{unixStB, unixBtS}) + +func unixStB(s string) ([]byte, error) { + // the address is the whole remaining string, prefixed by a varint len + size := CodeToVarint(len(s)) + b := append(size, []byte(s)...) + return b, nil +} + +func unixBtS(b []byte) (string, error) { + // the address is a varint len prefixed string + size, n, err := ReadVarintCode(b) + if err != nil { + return "", err + } + + b = b[n:] + if len(b) != size { + return "", errors.New("inconsistent lengths") + } + if size == 0 { + return "", errors.New("invalid length") + } + s := string(b) + s = s[1:] // remove starting slash + return s, nil +}