ceremonyclient/go-multiaddr/transcoders.go
2024-10-12 11:48:25 -07:00

492 lines
13 KiB
Go

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.
}