Browse Source

Simplification of addRoot/removeRoot, and some code reformatting and other cleanup across multiple files.

Adam Ierymenko 5 years ago
parent
commit
b9bf6d1242

+ 0 - 145
go/attic/endpoint.go

@@ -1,145 +0,0 @@
-package attic
-
-import (
-	"encoding/binary"
-	"errors"
-)
-
-// Endpoint types are the same as the enum values in Endpoint.hpp in the core.
-const (
-	EndpointTypeNil          = 0
-	EndpointTypeInetAddr     = 1
-	EndpointTypeDnsName      = 2
-	EndpointTypeZeroTier     = 3
-	EndpointTypeUrl          = 4
-	EndpointTypeEthernet     = 5
-	EndpointTypeUnrecognized = 255
-)
-
-// Endpoint wraps a variety of different ways of describing a node's physical network location.
-type Endpoint struct {
-	// Type is this endpoint's type
-	Type int
-
-	// Location is the X, Y, Z coordinate of this endpoint or 0,0,0 if unspecified.
-	Location [3]int
-
-	value, value2 interface{}
-}
-
-var (
-	ErrInvalidEndpoint = errors.New("invalid marshaled endpoint object")
-)
-
-func (ep *Endpoint) unmarshalZT(b []byte) (int, error) {
-	if len(b) < 7 {
-		return 0, ErrInvalidEndpoint
-	}
-	ep.Type = int(b[0])
-	ep.Location[0] = int(binary.BigEndian.Uint16(b[1:3]))
-	ep.Location[1] = int(binary.BigEndian.Uint16(b[3:5]))
-	ep.Location[2] = int(binary.BigEndian.Uint16(b[5:7]))
-	ep.value = nil
-	ep.value2 = nil
-	switch ep.Type {
-	case EndpointTypeNil:
-		return 7, nil
-	case EndpointTypeInetAddr:
-		ina := new(InetAddress)
-		inlen, err := ina.unmarshalZT(b[7:])
-		if err != nil {
-			return 0, err
-		}
-		ep.value = ina
-		return 7 + inlen, nil
-	case EndpointTypeDnsName:
-		stringEnd := 0
-		for i := 7; i < len(b); i++ {
-			if b[i] == 0 {
-				stringEnd = i + 1
-				break
-			}
-		}
-		if stringEnd == 0 || (stringEnd+2) > len(b) {
-			return 0, ErrInvalidEndpoint
-		}
-		ep.value = string(b[7:stringEnd])
-		port := binary.BigEndian.Uint16(b[stringEnd : stringEnd+2])
-		ep.value2 = &port
-		return stringEnd + 2, nil
-	case EndpointTypeZeroTier:
-		if len(b) < 60 {
-			return 0, ErrInvalidEndpoint
-		}
-		a, err := NewAddressFromBytes(b[7:12])
-		if err != nil {
-			return 0, err
-		}
-		ep.value = a
-		ep.value2 = append(make([]byte, 0, 48), b[12:60]...)
-		return 60, nil
-	case EndpointTypeUrl:
-		stringEnd := 0
-		for i := 7; i < len(b); i++ {
-			if b[i] == 0 {
-				stringEnd = i + 1
-				break
-			}
-		}
-		if stringEnd == 0 {
-			return 0, ErrInvalidEndpoint
-		}
-		ep.value = string(b[7:stringEnd])
-		return stringEnd, nil
-	case EndpointTypeEthernet:
-		if len(b) < 13 {
-			return 0, ErrInvalidEndpoint
-		}
-		m, err := NewMACFromBytes(b[7:13])
-		if err != nil {
-			return 0, err
-		}
-		ep.value = m
-		return 13, nil
-	default:
-		if len(b) < 8 {
-			return 0, ErrInvalidEndpoint
-		}
-		ep.Type = EndpointTypeUnrecognized
-		return 8 + int(b[1]), nil
-	}
-}
-
-// InetAddress gets the address associated with this endpoint or nil if it is not of this type.
-func (ep *Endpoint) InetAddress() *InetAddress {
-	v, _ := ep.value.(*InetAddress)
-	return v
-}
-
-// Address gets the address associated with this endpoint or nil if it is not of this type.
-func (ep *Endpoint) Address() *Address {
-	v, _ := ep.value.(*Address)
-	return v
-}
-
-// DNSName gets the DNS name and port associated with this endpoint or an empty string and -1 if it is not of this type.
-func (ep *Endpoint) DNSName() (string, int) {
-	if ep.Type == EndpointTypeDnsName {
-		return ep.value.(string), int(*(ep.value2.(*uint16)))
-	}
-	return "", -1
-}
-
-// InetAddress gets the URL assocaited with this endpoint or an empty string if it is not of this type.
-func (ep *Endpoint) URL() string {
-	if ep.Type == EndpointTypeUrl {
-		return ep.value.(string)
-	}
-	return ""
-}
-
-// Ethernet gets the address associated with this endpoint or nil if it is not of this type.
-func (ep *Endpoint) Ethernet() *MAC {
-	v, _ := ep.value.(*MAC)
-	return v
-}

+ 0 - 56
go/attic/locator.go

@@ -1,56 +0,0 @@
-package attic
-
-import (
-	"encoding/binary"
-	"errors"
-)
-
-// Locator objects are signed collections of physical or virtual endpoints for a node.
-type Locator []byte
-
-var (
-	ErrInvalidLocator = errors.New("invalid marshaled locator object")
-)
-
-// Timestamp returns this locator's timestamp in milliseconds since epoch.
-func (l Locator) Timestamp() int64 {
-	if len(l) >= 8 {
-		return int64(binary.BigEndian.Uint64(l[0:8]))
-	}
-	return 0
-}
-
-// Nil returns true if this is a nil/empty locator.
-func (l Locator) Nil() bool {
-	return len(l) < 8 || int64(binary.BigEndian.Uint64(l[0:8])) <= 0
-}
-
-// Endpoints obtains the endpoints described by this locator.
-func (l Locator) Endpoints() (eps []Endpoint, err error) {
-	if len(l) < 8 {
-		err = ErrInvalidLocator
-		return
-	}
-	if int64(binary.BigEndian.Uint64(l[0:8])) > 0 {
-		if len(l) < 10 {
-			err = ErrInvalidLocator
-			return
-		}
-		endpointCount := int(binary.BigEndian.Uint16(l[8:10]))
-		eps = make([]Endpoint, endpointCount)
-		p := 10
-		for e := 0; e < endpointCount; e++ {
-			if p >= len(l) {
-				err = ErrInvalidLocator
-				return
-			}
-			var elen int
-			elen, err = eps[e].unmarshalZT(l[p:])
-			if err != nil {
-				return
-			}
-			p += elen
-		}
-	}
-	return
-}

+ 0 - 4
go/cmd/zerotier-fuzz/zerotier-fuzz.go

@@ -1,4 +0,0 @@
-package main
-
-func main() {
-}

+ 0 - 1
go/cmd/zerotier/cli/identity.go

@@ -104,7 +104,6 @@ func Identity(args []string) {
 
 		case "makeroot":
 			if len(args) >= 2 {
-				//id := readIdentity(args[1])
 			}
 
 		}

+ 30 - 37
go/cmd/zerotier/zerotier.go

@@ -27,53 +27,32 @@ import (
 	"zerotier/pkg/zerotier"
 )
 
-func readAuthToken(basePath string) string {
-	data, _ := ioutil.ReadFile(path.Join(basePath, "authtoken.secret"))
-	if len(data) > 0 {
-		return string(data)
-	}
+func getAuthTokenPaths(basePath string) (p []string) {
+	p = append(p,path.Join(basePath,"authtoken.secret"))
 	userHome, _ := os.UserHomeDir()
 	if len(userHome) > 0 {
 		if runtime.GOOS == "darwin" {
-			data, _ = ioutil.ReadFile(userHome + "/Library/Application Support/ZeroTier/authtoken.secret")
-			if len(data) > 0 {
-				return string(data)
-			}
-			data, _ = ioutil.ReadFile(userHome + "/Library/Application Support/ZeroTier/One/authtoken.secret")
-			if len(data) > 0 {
-				return string(data)
-			}
-		}
-		data, _ = ioutil.ReadFile(path.Join(userHome, ".zerotierauth"))
-		if len(data) > 0 {
-			return string(data)
-		}
-		data, _ = ioutil.ReadFile(path.Join(userHome, ".zeroTierOneAuthToken"))
-		if len(data) > 0 {
-			return string(data)
+			p = append(p,path.Join(userHome,"Library","Application Support","ZeroTier","authtoken.secret"))
+			p = append(p,path.Join(userHome,"Library","Application Support","ZeroTier","One","authtoken.secret"))
 		}
+		p = append(p,path.Join(userHome,".zerotierauth"))
+		p = append(p,path.Join(userHome,".zeroTierOneAuthToken"))
 	}
-	return ""
+	return p
 }
 
 func authTokenRequired(authToken string) {
 	if len(authToken) == 0 {
-		fmt.Println("FATAL: unable to read API authorization token from service path or user home ('sudo' may be needed)")
+		fmt.Println("FATAL: unable to read API authorization token from command line or any filesystem location.")
 		os.Exit(1)
 	}
 }
 
 func main() {
-	// Reduce Go's threads to 1-2 depending on whether this is single core or
-	// multi-core. Note that I/O threads are in C++ and are separate and Go
-	// code only does service control and CLI stuff, so this reduces memory
-	// use and competition with I/O but shouldn't impact throughput. We also
-	// crank up the GC to reduce memory usage a little bit.
-	if runtime.NumCPU() >= 2 {
-		runtime.GOMAXPROCS(2)
-	} else {
-		runtime.GOMAXPROCS(1)
-	}
+	// Reduce Go's thread and memory footprint. This would slow things down if the Go code
+	// were doing a lot, but it's not. It just manages the core and is not directly involved
+	// in pushing a lot of packets around. If that ever changes this should be adjusted.
+	runtime.GOMAXPROCS(1)
 	debug.SetGCPercent(20)
 
 	globalOpts := flag.NewFlagSet("global", flag.ContinueOnError)
@@ -103,19 +82,33 @@ func main() {
 	if len(*pflag) > 0 {
 		basePath = *pflag
 	}
+	authTokenPaths := getAuthTokenPaths(basePath)
 
 	var authToken string
 	if len(*tflag) > 0 {
 		at, err := ioutil.ReadFile(*tflag)
 		if err != nil || len(at) == 0 {
-			fmt.Println("FATAL: unable to read API authorization token from file '" + *tflag + "'")
+			fmt.Println("FATAL: unable to read local service API authorization token from " + *tflag)
 			os.Exit(1)
 		}
-		authToken = strings.TrimSpace(string(at))
+		authToken = string(at)
 	} else if len(*tTflag) > 0 {
-		authToken = strings.TrimSpace(*tTflag)
+		authToken = *tTflag
 	} else {
-		authToken = readAuthToken(basePath)
+		for _, p := range authTokenPaths {
+			tmp, _ := ioutil.ReadFile(p)
+			if len(tmp) > 0 {
+				authToken = string(tmp)
+				break;
+			}
+		}
+		if len(authToken) == 0 {
+			fmt.Println("FATAL: unable to read local service API authorization token from any of:")
+			for _, p := range authTokenPaths {
+				fmt.Println("  " + p)
+			}
+			os.Exit(1)
+		}
 	}
 	authToken = strings.TrimSpace(authToken)
 

+ 93 - 0
go/pkg/zerotier/endpoint.go

@@ -0,0 +1,93 @@
+package zerotier
+
+// #include "../../native/GoGlue.h"
+import "C"
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"strings"
+	"unsafe"
+)
+
+const (
+	EndpointTypeNil        = C.ZT_ENDPOINT_TYPE_NIL
+	EndpointTypeZeroTier   = C.ZT_ENDPOINT_TYPE_ZEROTIER
+	EndpointTypeEthernet   = C.ZT_ENDPOINT_TYPE_ETHERNET
+	EndpointTypeWifiDirect = C.ZT_ENDPOINT_TYPE_WIFI_DIRECT
+	EndpointTypeBluetooth  = C.ZT_ENDPOINT_TYPE_BLUETOOTH
+	EndpointTypeIp         = C.ZT_ENDPOINT_TYPE_IP
+	EndpointTypeIpUdp      = C.ZT_ENDPOINT_TYPE_IP_UDP
+	EndpointTypeIpTcp      = C.ZT_ENDPOINT_TYPE_IP_TCP
+	EndpointTypeIpHttp2    = C.ZT_ENDPOINT_TYPE_IP_HTTP2
+)
+
+type Endpoint struct {
+	cep C.ZT_Endpoint
+}
+
+// Type returns this endpoint's type.
+func (ep *Endpoint) Type() int {
+	return ep.cep._type
+}
+
+// InetAddress gets this Endpoint as an InetAddress or nil if its type is not addressed by one.
+func (ep *Endpoint) InetAddress() *InetAddress {
+	switch ep.cep._type {
+	case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2:
+		ua := sockaddrStorageToUDPAddr(&(ep.cep.a.ss))
+		return &InetAddress{IP: ua.IP, Port: ua.Port}
+	}
+	return nil
+}
+
+func (ep *Endpoint) String() string {
+	switch ep.cep._type {
+	case EndpointTypeZeroTier:
+		fp := Fingerprint{Address: Address(ep.cep.a.fp.address), Hash: *((*[48]byte)(unsafe.Pointer(&ep.cep.a.fp.hash[0])))}
+		return fmt.Sprintf("%d/%s", ep.Type(), fp.String())
+	case EndpointTypeEthernet, EndpointTypeWifiDirect, EndpointTypeBluetooth:
+		return fmt.Sprintf("%d/%s", ep.Type(), MAC(ep.cep.a.mac).String())
+	case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2:
+		return fmt.Sprintf("%d/%s", ep.Type(), ep.InetAddress().String())
+	}
+	return fmt.Sprintf("%d", ep.Type())
+}
+
+func (ep *Endpoint) MarshalJSON() ([]byte, error) {
+	s := ep.String()
+	return json.Marshal(&s)
+}
+
+func (ep *Endpoint) UnmarshalJSON(j []byte) error {
+	var s string
+	err := json.Unmarshal(j, &s)
+	if err != nil {
+		return err
+	}
+
+	slashIdx := strings.IndexRune(s, '/')
+	if slashIdx < 0 {
+		ep.cep._type = C.uint(strconv.ParseUint(s, 10, 32))
+	} else if slashIdx == 0 || slashIdx >= (len(s)-1) {
+		return ErrInvalidParameter
+	} else {
+		ep.cep._type = C.uint(strconv.ParseUint(s[0:slashIdx], 10, 32))
+		s = s[slashIdx+1:]
+	}
+
+	switch ep.cep._type {
+	case EndpointTypeNil:
+		return nil
+	case EndpointTypeZeroTier:
+		fp, err := NewFingerprintFromString(s)
+		if err != nil {
+			return err
+		}
+		ep.cep.a.fp.address = C.uint64_t(fp.Address)
+		copy(((*[48]byte)(unsafe.Pointer(&ep.cep.a.fp.hash[0])))[:], fp.Hash[:])
+		return nil
+	}
+	return ErrInvalidParameter
+}

+ 26 - 16
go/pkg/zerotier/fingerprint.go

@@ -13,41 +13,51 @@
 
 package zerotier
 
-//#cgo CFLAGS: -O3
-//#include "../../native/GoGlue.h"
+// #include "../../native/GoGlue.h"
 import "C"
 
 import (
 	"bytes"
-	"errors"
+	"fmt"
 	"strings"
 	"unsafe"
 )
 
 type Fingerprint struct {
-	Address Address  `json:"address"`
-	Hash    [48]byte `json:"hash"`
+	Address Address `json:"address"`
+	Hash    []byte  `json:"hash"`
 }
 
 func NewFingerprintFromString(fps string) (*Fingerprint, error) {
-	fpb, err := Base32StdLowerCase.DecodeString(strings.TrimSpace(strings.ToLower(fps)))
+	if len(fps) < AddressStringLength {
+		return nil, ErrInvalidZeroTierAddress
+	}
+	ss := strings.Split(fps, "/")
+	if len(ss) < 1 || len(ss) > 2 {
+		return nil, ErrInvalidParameter
+	}
+	a, err := NewAddressFromString(ss[0])
 	if err != nil {
 		return nil, err
 	}
-	if len(fpb) != 53 {
-		return nil, errors.New("invalid fingerprint length")
+	if len(ss) == 2 {
+		h, err := Base32StdLowerCase.DecodeString(ss[1])
+		if err != nil {
+			return nil, err
+		}
+		if len(h) != 48 {
+			return nil, ErrInvalidParameter
+		}
+		return &Fingerprint{Address: a, Hash: h}, nil
 	}
-	var fp Fingerprint
-	fp.Address, _ = NewAddressFromBytes(fpb[0:5])
-	copy(fp.Hash[:],fpb[5:])
-	return &fp, nil
+	return &Fingerprint{Address: a, Hash: nil}, nil
 }
 
 func (fp *Fingerprint) String() string {
-	var tmp [53]byte
-	fp.Address.CopyTo(tmp[0:5])
-	copy(tmp[5:],fp.Hash[:])
-	return Base32StdLowerCase.EncodeToString(tmp[:])
+	if len(fp.Hash) == 48 {
+		return fmt.Sprintf("%.10x/%s", uint64(fp.Address), Base32StdLowerCase.EncodeToString(fp.Hash))
+	}
+	return fp.Address.String()
 }
 
 func (fp *Fingerprint) Equals(fp2 *Fingerprint) bool {

+ 0 - 1
go/pkg/zerotier/node.go

@@ -510,7 +510,6 @@ func (n *Node) Peers() []*Peer {
 			p2.Version = [3]int{int(p.versionMajor), int(p.versionMinor), int(p.versionRev)}
 			p2.Latency = int(p.latency)
 			p2.Root = p.root != 0
-			p2.Bootstrap = NewInetAddressFromSockaddr(unsafe.Pointer(&p.bootstrap))
 
 			p2.Paths = make([]Path, 0, int(p.pathCount))
 			for j := 0; j < len(p2.Paths); j++ {

+ 9 - 8
go/pkg/zerotier/peer.go

@@ -15,12 +15,13 @@ package zerotier
 
 // Peer is another ZeroTier node
 type Peer struct {
-	Address      Address      `json:"address"`
-	Identity     *Identity    `json:"identity"`
-	Fingerprint  Fingerprint  `json:"fingerprint"`
-	Version      [3]int       `json:"version"`
-	Latency      int          `json:"latency"`
-	Root         bool         `json:"root"`
-	Bootstrap    *InetAddress `json:"bootstrap,omitempty"`
-	Paths        []Path       `json:"paths,omitempty"`
+	Address          Address     `json:"address"`
+	Identity         *Identity   `json:"identity"`
+	Fingerprint      Fingerprint `json:"fingerprint"`
+	Version          [3]int      `json:"version"`
+	Latency          int         `json:"latency"`
+	Root             bool        `json:"root"`
+	Paths            []Path      `json:"paths,omitempty"`
+	LocatorTimestamp int64       `json:"locatorTimestamp"`
+	LocatorEndpoints []Endpoint  `json:"locatorEndpoints,omitempty"`
 }

+ 250 - 100
include/ZeroTierCore.h

@@ -153,11 +153,6 @@ extern "C" {
  */
 #define ZT_MAX_PEER_NETWORK_PATHS 16
 
-/**
- * Maximum number of path configurations that can be set
- */
-#define ZT_MAX_CONFIGURABLE_PATHS 32
-
 /**
  * Maximum number of rules per capability object
  *
@@ -175,15 +170,6 @@ extern "C" {
  */
 #define ZT_MAX_CERTIFICATES_OF_OWNERSHIP 4
 
-/**
- * Maximum size in bytes for a root specification
- *
- * A root specification is just a serialized identity followed by a serialized
- * locator. This provides the maximum size of those plus a lot of extra margin
- * for any future expansions, but could change in future versions.
- */
-#define ZT_ROOT_SPEC_MAX_SIZE 8192
-
 /**
  * Packet characteristics flag: packet direction, 1 if inbound 0 if outbound
  */
@@ -274,32 +260,21 @@ extern "C" {
 /**
  * Identity type codes (must be the same as Identity.hpp).
  */
-enum ZT_Identity_Type
+enum ZT_IdentityType
 {
 	ZT_IDENTITY_TYPE_C25519 = 0, /* C25519/Ed25519 */
 	ZT_IDENTITY_TYPE_P384 =   1  /* Combined C25519/NIST-P-384 key */
 };
 
 /**
- * A ZeroTier identity (opaque)
+ * ZeroTier identity (address plus keys)
  */
 typedef void ZT_Identity;
 
 /**
- * Full identity fingerprint with address and 384-bit hash of public key(s)
+ * Locator is a signed list of endpoints
  */
-typedef struct
-{
-	/**
-	 * Short address (only least significant 40 bits are used)
-	 */
-	uint64_t address;
-
-	/**
-	 * 384-bit hash of identity public key(s)
-	 */
-	uint8_t hash[48];
-} ZT_Fingerprint;
+typedef void ZT_Locator;
 
 /**
  * Credential type IDs
@@ -333,6 +308,27 @@ enum ZT_EndpointType
 	ZT_ENDPOINT_TYPE_IP_HTTP2 =      8   // IP/HTTP2 encapsulation
 };
 
+/**
+ * A string that contains endpoint type IDs indexed by endpoint type (can be used as a lookup array)
+ */
+#define ZT_ENDPOINT_TYPE_CHAR_INDEX "012345678"
+
+/**
+ * Full identity fingerprint with address and 384-bit hash of public key(s)
+ */
+typedef struct
+{
+	/**
+	 * Short address (only least significant 40 bits are used)
+	 */
+	uint64_t address;
+
+	/**
+	 * 384-bit hash of identity public key(s)
+	 */
+	uint8_t hash[48];
+} ZT_Fingerprint;
+
 /**
  * Flag indicating that VL1 tracing should be generated
  */
@@ -415,7 +411,7 @@ enum ZT_TraceCredentialRejectionReason
 };
 
 #define ZT_TRACE_FIELD_TYPE                               "t"
-#define ZT_TRACE_FIELD_CODE_LOCATION                      "@"
+#define ZT_TRACE_FIELD_CODE_LOCATION                      "c"
 #define ZT_TRACE_FIELD_ENDPOINT                           "e"
 #define ZT_TRACE_FIELD_OLD_ENDPOINT                       "oe"
 #define ZT_TRACE_FIELD_NEW_ENDPOINT                       "ne"
@@ -498,7 +494,12 @@ enum ZT_ResultCode
 	/**
 	 * The requested operation was given a bad parameter or was called in an invalid state
 	 */
-	ZT_RESULT_ERROR_BAD_PARAMETER = 1002
+	ZT_RESULT_ERROR_BAD_PARAMETER = 1002,
+
+	/**
+	 * A credential or other object was supplied that failed cryptographic signature or integrity check
+	 */
+	ZT_RESULT_ERROR_INVALID_CREDENTIAL = 1003
 };
 
 /**
@@ -1119,14 +1120,57 @@ typedef struct
 } ZT_InterfaceAddress;
 
 /**
- * Physical network path to a peer
+ * Variant type for storing possible path endpoints or peer contact points.
  */
 typedef struct
 {
 	/**
-	 * Address of endpoint
+	 * Endpoint type, which determines what field in the union 'a' applies.
 	 */
-	struct sockaddr_storage address;
+	enum ZT_EndpointType type;
+
+	union {
+		/**
+		 * Socket address generic buffer
+		 */
+		struct sockaddr_storage ss;
+
+		/**
+		 * Socket address header, for all ZT_ENDPOINT_TYPE_IP types
+		 */
+		struct sockaddr sa;
+
+		/**
+		 * IPv4 address, for all ZT_ENDPOINT_TYPE_IP types if family is AF_INET
+		 */
+		struct sockaddr_in sa_in;
+
+		/**
+		 * IPv6 address, for all ZT_ENDPOINT_TYPE_IP types if family is AF_INET6
+		 */
+		struct sockaddr_in6 sa_in6;
+
+		/**
+		 * MAC address (least significant 48 bites) for ZT_ENDPOINT_TYPE_ETHERNET and other MAC addressed types
+		 */
+		uint64_t mac;
+
+		/**
+		 * ZeroTier node address and identity fingerprint for ZT_ENDPOINT_TYPE_ZEROTIER
+		 */
+		ZT_Fingerprint fp;
+	} value;
+} ZT_Endpoint;
+
+/**
+ * Network path to a peer
+ */
+typedef struct
+{
+	/**
+	 * Path endpoint
+	 */
+	ZT_Endpoint endpoint;
 
 	/**
 	 * Time of last send in milliseconds or 0 for never
@@ -1147,10 +1191,10 @@ typedef struct
 	 * Is path preferred?
 	 */
 	int preferred;
-} ZT_PeerPhysicalPath;
+} ZT_Path;
 
 /**
- * Peer status result buffer
+ * Peer information
  */
 typedef struct
 {
@@ -1194,20 +1238,6 @@ typedef struct
 	 */
 	int root;
 
-	/**
-	 * Number of bootstrap addresses
-	 */
-	unsigned int bootstrapAddressCount;
-
-	/**
-	 * Bootstrap addresses
-	 *
-	 * This is a memo-ized recently valid address that can be saved and used
-	 * to attempt rapid reconnection with this peer. If the ss_family field
-	 * is 0 this field is considered null/empty.
-	 */
-	struct sockaddr_storage bootstrap[ZT_MAX_PEER_NETWORK_PATHS];
-
 	/**
 	 * Number of networks in which this peer is authenticated
 	 */
@@ -1224,9 +1254,28 @@ typedef struct
 	unsigned int pathCount;
 
 	/**
-	 * Known network paths to peer (array size: pathCount)
+	 * Known network paths to peer (array size: pathCount).
+	 *
+	 * These are direct paths only. Endpoints can also describe indirect paths,
+	 * but those would not appear here. Right now those can only be relaying via
+	 * a root.
 	 */
-	ZT_PeerPhysicalPath *paths;
+	ZT_Path *paths;
+
+	/**
+	 * Timestamp of peer's locator or 0 if none on file
+	 */
+	int64_t locatorTimestamp;
+
+	/**
+	 * Number of endpoints in locator
+	 */
+	unsigned int locatorEndpointCount;
+
+	/**
+	 * Endpoints in peer's locator
+	 */
+	ZT_Endpoint *locatorEndpoints;
 } ZT_Peer;
 
 /**
@@ -1567,6 +1616,16 @@ ZT_SDK_API void *ZT_getBuffer();
  */
 ZT_SDK_API void ZT_freeBuffer(void *b);
 
+/**
+ * Free a query result buffer
+ *
+ * Use this to free the return values of listNetworks(), listPeers(), and
+ * other query functions that return allocated structures or buffers.
+ *
+ * @param qr Query result buffer
+ */
+ZT_SDK_API void ZT_freeQueryResult(void *qr);
+
 /* ---------------------------------------------------------------------------------------------------------------- */
 
 /**
@@ -1584,7 +1643,12 @@ ZT_SDK_API void ZT_freeBuffer(void *b);
  * @param now Current clock in milliseconds
  * @return OK (0) or error code if a fatal error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_new(ZT_Node **node,void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64_t now);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_new(
+	ZT_Node **node,
+	void *uptr,
+	void *tptr,
+	const struct ZT_Node_Callbacks *callbacks,
+	int64_t now);
 
 /**
  * Delete a node and free all resources it consumes
@@ -1595,7 +1659,9 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_new(ZT_Node **node,void *uptr,void *tptr,c
  * @param node Node to delete
  * @param tptr Thread pointer to pass to functions/callbacks resulting from this call
  */
-ZT_SDK_API void ZT_Node_delete(ZT_Node *node,void *tptr);
+ZT_SDK_API void ZT_Node_delete(
+	ZT_Node *node,
+	void *tptr);
 
 /**
  * Process a packet received from the physical wire
@@ -1684,7 +1750,12 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_processBackgroundTasks(
  * @param tptr Thread pointer to pass to functions/callbacks resulting from this call
  * @return OK (0) or error code if a fatal error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,const ZT_Fingerprint *controllerFingerprint,void *uptr,void *tptr);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_join(
+	ZT_Node *node,
+	uint64_t nwid,
+	const ZT_Fingerprint *controllerFingerprint,
+	void *uptr,
+	void *tptr);
 
 /**
  * Leave a network
@@ -1702,7 +1773,11 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,const ZT_
  * @param tptr Thread pointer to pass to functions/callbacks resulting from this call
  * @return OK (0) or error code if a fatal error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr,void *tptr);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_leave(
+	ZT_Node *node,
+	uint64_t nwid,
+	void **uptr,
+	void *tptr);
 
 /**
  * Subscribe to an Ethernet multicast group
@@ -1730,7 +1805,12 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **u
  * @param multicastAdi Multicast ADI (least significant 32 bits only, use 0 if not needed)
  * @return OK (0) or error code if a fatal error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node,void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_multicastSubscribe(
+	ZT_Node *node,
+	void *tptr,
+	uint64_t nwid,
+	uint64_t multicastGroup,
+	unsigned long multicastAdi);
 
 /**
  * Unsubscribe from an Ethernet multicast group (or all groups)
@@ -1746,19 +1826,30 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node,void *tpt
  * @param multicastAdi Multicast ADI (least significant 32 bits only, use 0 if not needed)
  * @return OK (0) or error code if a fatal error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_multicastUnsubscribe(ZT_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_multicastUnsubscribe(
+	ZT_Node *node,
+	uint64_t nwid,
+	uint64_t multicastGroup,
+	unsigned long multicastAdi);
 
 /**
  * Add a root node or update its locator
  *
+ * ZeroTier does not take possession of the id or loc objects. The caller
+ * must still eventually delete them with ZT_Identity_delete() and
+ * ZT_Locator_delete().
+ *
  * @param node Node instance
  * @param tptr Thread pointer to pass to functions/callbacks resulting from this call
- * @param rdef Root definition (serialized identity and locator)
- * @param rdeflen Length of root definition in bytes
- * @param address If non-NULL will be filled with the ZeroTier address of the root (only defined if return is OK)
- * @return OK (0) or error code if a fatal error condition has occurred
+ * @param id Identity of root to add
+ * @param loc Root locator
+ * @return OK (0) or error code if an error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_addRoot(ZT_Node *node,void *tptr,const void *rdef,unsigned int rdeflen,uint64_t *address);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_addRoot(
+	ZT_Node *node,
+	void *tptr,
+	const ZT_Identity *id,
+	const ZT_Locator *loc);
 
 /**
  * Remove a root
@@ -1769,10 +1860,13 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_addRoot(ZT_Node *node,void *tptr,const voi
  *
  * @param node Node instance
  * @param tptr Thread pointer to pass to functions/callbacks resulting from this call
- * @param fp Fingerprint of root (will be looked up by address only if hash is all zeroes)
- * @return OK (0) or error code if a fatal error condition has occurred
+ * @param address ZeroTier address to remove
+ * @return OK (0) or error code if an error condition has occurred
  */
-ZT_SDK_API enum ZT_ResultCode ZT_Node_removeRoot(ZT_Node *node,void *tptr,const ZT_Fingerprint *fp);
+ZT_SDK_API enum ZT_ResultCode ZT_Node_removeRoot(
+	ZT_Node *node,
+	void *tptr,
+	const uint64_t address);
 
 /**
  * Get this node's 40-bit ZeroTier address
@@ -1799,7 +1893,9 @@ ZT_SDK_API const ZT_Identity *ZT_Node_identity(ZT_Node *node);
  * @param node Node instance
  * @param status Buffer to fill with current node status
  */
-ZT_SDK_API void ZT_Node_status(ZT_Node *node,ZT_NodeStatus *status);
+ZT_SDK_API void ZT_Node_status(
+	ZT_Node *node,
+	ZT_NodeStatus *status);
 
 /**
  * Get a list of known peer nodes
@@ -1822,7 +1918,9 @@ ZT_SDK_API ZT_PeerList *ZT_Node_peers(ZT_Node *node);
  * @param nwid 64-bit network ID
  * @return Network configuration or NULL if we are not a member of this network
  */
-ZT_SDK_API ZT_VirtualNetworkConfig *ZT_Node_networkConfig(ZT_Node *node,uint64_t nwid);
+ZT_SDK_API ZT_VirtualNetworkConfig *ZT_Node_networkConfig(
+	ZT_Node *node,
+	uint64_t nwid);
 
 /**
  * Enumerate and get status of all networks
@@ -1841,17 +1939,10 @@ ZT_SDK_API ZT_VirtualNetworkList *ZT_Node_networks(ZT_Node *node);
  * @param nwid Network ID
  * @param ptr New network-associated pointer
  */
-ZT_SDK_API void ZT_Node_setNetworkUserPtr(ZT_Node *node,uint64_t nwid,void *ptr);
-
-/**
- * Free a query result buffer
- *
- * Use this to free the return values of listNetworks(), listPeers(), etc.
- *
- * @param node Node instance
- * @param qr Query result buffer
- */
-ZT_SDK_API void ZT_Node_freeQueryResult(ZT_Node *node,void *qr);
+ZT_SDK_API void ZT_Node_setNetworkUserPtr(
+	ZT_Node *node,
+	uint64_t nwid,
+	void *ptr);
 
 /**
  * Set external interface addresses where this node could be reached
@@ -1860,7 +1951,10 @@ ZT_SDK_API void ZT_Node_freeQueryResult(ZT_Node *node,void *qr);
  * @param addrs Addresses
  * @param addrCount Number of items in addrs[]
  */
-ZT_SDK_API void ZT_Node_setInterfaceAddresses(ZT_Node *node,const ZT_InterfaceAddress *addrs,unsigned int addrCount);
+ZT_SDK_API void ZT_Node_setInterfaceAddresses(
+	ZT_Node *node,
+	const ZT_InterfaceAddress *addrs,
+	unsigned int addrCount);
 
 /**
  * Send a VERB_USER_MESSAGE to another ZeroTier node
@@ -1876,7 +1970,13 @@ ZT_SDK_API void ZT_Node_setInterfaceAddresses(ZT_Node *node,const ZT_InterfaceAd
  * @param len Length of data in bytes
  * @return Boolean: non-zero on success, zero on failure
  */
-ZT_SDK_API int ZT_Node_sendUserMessage(ZT_Node *node,void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len);
+ZT_SDK_API int ZT_Node_sendUserMessage(
+	ZT_Node *node,
+	void *tptr,
+	uint64_t dest,
+	uint64_t typeId,
+	const void *data,
+	unsigned int len);
 
 /**
  * Set a network controller instance for this node
@@ -1893,7 +1993,9 @@ ZT_SDK_API int ZT_Node_sendUserMessage(ZT_Node *node,void *tptr,uint64_t dest,ui
  * @param networkConfigMasterInstance Instance of NetworkConfigMaster C++ class or NULL to disable
  * @return OK (0) or error code if a fatal error condition has occurred
  */
-ZT_SDK_API void ZT_Node_setController(ZT_Node *node,void *networkConfigMasterInstance);
+ZT_SDK_API void ZT_Node_setController(
+	ZT_Node *node,
+	void *networkConfigMasterInstance);
 
 /* ---------------------------------------------------------------------------------------------------------------- */
 
@@ -1907,7 +2009,7 @@ ZT_SDK_API void ZT_Node_setController(ZT_Node *node,void *networkConfigMasterIns
  * @param type Type of identity to generate
  * @return New identity or NULL on error
  */
-ZT_SDK_API ZT_Identity *ZT_Identity_new(enum ZT_Identity_Type type);
+ZT_SDK_API ZT_Identity *ZT_Identity_new(enum ZT_IdentityType type);
 
 /**
  * Create a new identity object from a string-serialized identity
@@ -1938,7 +2040,12 @@ ZT_SDK_API int ZT_Identity_validate(const ZT_Identity *id);
  * @param signatureBufferLength Length of buffer (must be at least 96 bytes)
  * @return Length of signature in bytes or 0 on failure.
  */
-ZT_SDK_API unsigned int ZT_Identity_sign(const ZT_Identity *id,const void *data,unsigned int len,void *signature,unsigned int signatureBufferLength);
+ZT_SDK_API unsigned int ZT_Identity_sign(
+	const ZT_Identity *id,
+	const void *data,
+	unsigned int len,
+	void *signature,
+	unsigned int signatureBufferLength);
 
 /**
  * Verify a signature
@@ -1950,7 +2057,12 @@ ZT_SDK_API unsigned int ZT_Identity_sign(const ZT_Identity *id,const void *data,
  * @param sigLen Length of signature in bytes
  * @return Non-zero if signature is valid
  */
-ZT_SDK_API int ZT_Identity_verify(const ZT_Identity *id,const void *data,unsigned int len,const void *signature,unsigned int sigLen);
+ZT_SDK_API int ZT_Identity_verify(
+	const ZT_Identity *id,
+	const void *data,
+	unsigned int len,
+	const void *signature,
+	unsigned int sigLen);
 
 /**
  * Get identity type
@@ -1958,7 +2070,7 @@ ZT_SDK_API int ZT_Identity_verify(const ZT_Identity *id,const void *data,unsigne
  * @param id Identity to query
  * @return Identity type code
  */
-ZT_SDK_API enum ZT_Identity_Type ZT_Identity_type(const ZT_Identity *id);
+ZT_SDK_API enum ZT_IdentityType ZT_Identity_type(const ZT_Identity *id);
 
 /**
  * Convert an identity to its string representation
@@ -1969,7 +2081,11 @@ ZT_SDK_API enum ZT_Identity_Type ZT_Identity_type(const ZT_Identity *id);
  * @param includePrivate If true include the private key if present
  * @return Pointer to buf or NULL on overflow or other error
  */
-ZT_SDK_API char *ZT_Identity_toString(const ZT_Identity *id,char *buf,int capacity,int includePrivate);
+ZT_SDK_API char *ZT_Identity_toString(
+	const ZT_Identity *id,
+	char *buf,
+	int capacity,
+	int includePrivate);
 
 /**
  * Check whether this identity object also holds a private key
@@ -1995,19 +2111,6 @@ ZT_SDK_API uint64_t ZT_Identity_address(const ZT_Identity *id);
  */
 ZT_SDK_API const ZT_Fingerprint *ZT_Identity_fingerprint(const ZT_Identity *id);
 
-/**
- * Make a root specification
- *
- * @param id Identity to sign root with (must have private key)
- * @param ts Timestamp for root specification in milliseconds since epoch
- * @param addrs Physical addresses for root
- * @param addrcnt Number of physical addresses for root
- * @param rootSpecBuf Buffer to receive result, should be at least ZT_ROOT_SPEC_MAX_SIZE bytes
- * @param rootSpecBufSize Size of rootSpecBuf in bytes
- * @return Bytes written to rootSpecBuf or -1 on error
- */
-ZT_SDK_API int ZT_Identity_makeRootSpecification(ZT_Identity *id,int64_t ts,struct sockaddr_storage *addrs,unsigned int addrcnt,void *rootSpecBuf,unsigned int rootSpecBufSize);
-
 /**
  * Delete an identity and free associated memory
  *
@@ -2020,6 +2123,53 @@ ZT_SDK_API void ZT_Identity_delete(ZT_Identity *id);
 
 /* ---------------------------------------------------------------------------------------------------------------- */
 
+/**
+ * Create and sign a new locator
+ *
+ * @param ts Locator timestamp
+ * @param endpoints List of endpoints to store in locator
+ * @param endpointCount Number of endpoints (maximum: 8)
+ * @param signer Identity to sign locator (must include private key)
+ * @return Locator or NULL on error (too many endpoints or identity does not have private key)
+ */
+ZT_SDK_API ZT_Locator *ZT_Locator_create(int64_t ts,const ZT_Endpoint *endpoints,unsigned int endpointCount,const ZT_Identity *signer);
+
+/**
+ * Decode a serialized locator
+ *
+ * @param data Data to deserialize
+ * @param len Length of data
+ * @return Locator or NULL if data is not valid
+ */
+ZT_SDK_API ZT_Locator *ZT_Locator_unmarshal(const void *data,unsigned int len);
+
+/**
+ * Get the number of endpoints in this locator
+ *
+ * @param loc Locator to query
+ * @return Number of endpoints
+ */
+ZT_SDK_API unsigned int ZT_Locator_endpointCount(const ZT_Locator *loc);
+
+/**
+ * Get a pointer to an endpoint in a locator
+ *
+ * The returned pointer remains valid as long as the Locator is not deleted.
+ *
+ * @param ep Endpoint number from 0 to 1 - endpointCount()
+ * @return Endpoint or NULL if out of bounds
+ */
+ZT_SDK_API const ZT_Endpoint *ZT_Locator_endpoint(const unsigned int ep);
+
+/**
+ * Delete a locator
+ *
+ * @param loc Locator to delete
+ */
+ZT_SDK_API void ZT_Locator_delete(ZT_Locator *loc);
+
+/* ---------------------------------------------------------------------------------------------------------------- */
+
 /**
  * Get ZeroTier One version
  *

+ 8 - 1
node/Address.hpp

@@ -35,7 +35,6 @@ public:
 	explicit ZT_INLINE Address(const uint64_t a) noexcept : _a(a) {}
 	explicit ZT_INLINE Address(const uint8_t b[5]) noexcept : _a(((uint64_t)b[0] << 32U) | ((uint64_t)b[1] << 24U) | ((uint64_t)b[2] << 16U) | ((uint64_t)b[3] << 8U) | (uint64_t)b[4]) {}
 
-	ZT_INLINE Address &operator=(const Address &a) noexcept { _a = a._a; return *this; }
 	ZT_INLINE Address &operator=(const uint64_t a) noexcept { _a = a; return *this; }
 
 	/**
@@ -108,6 +107,7 @@ public:
 	ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)_a; }
 
 	ZT_INLINE operator bool() const noexcept { return (_a != 0); }
+	ZT_INLINE operator uint64_t() const noexcept { return _a; }
 
 	ZT_INLINE bool operator==(const Address &a) const noexcept { return _a == a._a; }
 	ZT_INLINE bool operator!=(const Address &a) const noexcept { return _a != a._a; }
@@ -116,6 +116,13 @@ public:
 	ZT_INLINE bool operator>=(const Address &a) const noexcept { return _a >= a._a; }
 	ZT_INLINE bool operator<=(const Address &a) const noexcept { return _a <= a._a; }
 
+	ZT_INLINE bool operator==(const uint64_t a) const noexcept { return _a == a; }
+	ZT_INLINE bool operator!=(const uint64_t a) const noexcept { return _a != a; }
+	ZT_INLINE bool operator>(const uint64_t a) const noexcept { return _a > a; }
+	ZT_INLINE bool operator<(const uint64_t a) const noexcept { return _a < a; }
+	ZT_INLINE bool operator>=(const uint64_t a) const noexcept { return _a >= a; }
+	ZT_INLINE bool operator<=(const uint64_t a) const noexcept { return _a <= a; }
+
 private:
 	uint64_t _a;
 };

+ 1 - 0
node/CMakeLists.txt

@@ -15,6 +15,7 @@ set(core_headers
 	Defragmenter.hpp
 	Dictionary.hpp
 	ECC384.hpp
+	EphemeralKey.hpp
 	Expect.hpp
 	FCV.hpp
 	Fingerprint.hpp

+ 2 - 0
node/Containers.hpp

@@ -139,6 +139,8 @@ class Vector : public std::vector< V,Utils::Mallocator<V> >
 {
 public:
 	ZT_INLINE Vector() {}
+	template<typename I>
+	ZT_INLINE Vector(I begin,I end) : std::vector< V,Utils::Mallocator<V> >(begin,end) {}
 };
 
 template<typename V>

+ 98 - 42
node/Dictionary.cpp

@@ -12,65 +12,71 @@
 /****/
 
 #include "Dictionary.hpp"
+#include "Identity.hpp"
 
 namespace ZeroTier {
 
+static const FCV<char, 8> s_signatureFingerprint("@Si", 4);
+static const FCV<char, 8> s_signatureData("@Ss", 4);
+
 Dictionary::Dictionary()
 {
 }
 
 Vector<uint8_t> &Dictionary::operator[](const char *k)
 {
-	return m_entries[s_toKey(k)];
+	FCV<char, 8> key;
+	return m_entries[s_key(key, k)];
 }
 
 const Vector<uint8_t> &Dictionary::operator[](const char *k) const
 {
 	static const Vector<uint8_t> s_emptyEntry;
-	Map< uint64_t,Vector<uint8_t> >::const_iterator e(m_entries.find(s_toKey(k)));
+	FCV<char, 8> key;
+	SortedMap<FCV<char, 8>, Vector<uint8_t> >::const_iterator e(m_entries.find(s_key(key, k)));
 	return (e == m_entries.end()) ? s_emptyEntry : e->second;
 }
 
-void Dictionary::add(const char *k,bool v)
+void Dictionary::add(const char *k, bool v)
 {
 	Vector<uint8_t> &e = (*this)[k];
 	e.resize(2);
-	e[0] = (uint8_t)(v ? '1' : '0');
+	e[0] = (uint8_t) (v ? '1' : '0');
 	e[1] = 0;
 }
 
-void Dictionary::add(const char *k,const Address &v)
+void Dictionary::add(const char *k, const Address &v)
 {
 	Vector<uint8_t> &e = (*this)[k];
 	e.resize(ZT_ADDRESS_STRING_SIZE_MAX);
-	v.toString((char *)e.data());
+	v.toString((char *) e.data());
 }
 
-void Dictionary::add(const char *k,const char *v)
+void Dictionary::add(const char *k, const char *v)
 {
-	if ((v)&&(*v)) {
+	if ((v) && (*v)) {
 		Vector<uint8_t> &e = (*this)[k];
 		e.clear();
 		while (*v)
-			e.push_back((uint8_t)*(v++));
+			e.push_back((uint8_t) *(v++));
 	}
 }
 
-void Dictionary::add(const char *k,const void *data,unsigned int len)
+void Dictionary::add(const char *k, const void *data, unsigned int len)
 {
 	Vector<uint8_t> &e = (*this)[k];
 	if (len != 0) {
-		e.assign((const uint8_t *)data,(const uint8_t *)data + len);
+		e.assign((const uint8_t *) data, (const uint8_t *) data + len);
 	} else {
 		e.clear();
 	}
 }
 
-bool Dictionary::getB(const char *k,bool dfl) const
+bool Dictionary::getB(const char *k, bool dfl) const
 {
 	const Vector<uint8_t> &e = (*this)[k];
 	if (!e.empty()) {
-		switch ((char)e[0]) {
+		switch ((char) e[0]) {
 			case '1':
 			case 't':
 			case 'T':
@@ -84,7 +90,7 @@ bool Dictionary::getB(const char *k,bool dfl) const
 	return dfl;
 }
 
-uint64_t Dictionary::getUI(const char *k,uint64_t dfl) const
+uint64_t Dictionary::getUI(const char *k, uint64_t dfl) const
 {
 	uint8_t tmp[18];
 	uint64_t v = dfl;
@@ -92,29 +98,79 @@ uint64_t Dictionary::getUI(const char *k,uint64_t dfl) const
 	if (!e.empty()) {
 		if (e.back() != 0) {
 			const unsigned long sl = e.size();
-			Utils::copy(tmp,e.data(),(sl > 17) ? 17 : sl);
+			Utils::copy(tmp, e.data(), (sl > 17) ? 17 : sl);
 			tmp[17] = 0;
-			return Utils::unhex((const char *)tmp);
+			return Utils::unhex((const char *) tmp);
 		}
-		return Utils::unhex((const char *)e.data());
+		return Utils::unhex((const char *) e.data());
 	}
 	return v;
 }
 
-void Dictionary::getS(const char *k,char *v,const unsigned int cap) const
+char *Dictionary::getS(const char *k, char *v, const unsigned int cap) const
 {
 	if (cap == 0) // sanity check
 		return;
 	const Vector<uint8_t> &e = (*this)[k];
 	unsigned int i = 0;
 	const unsigned int last = cap - 1;
-	for(;;) {
-		if ((i == last)||(i >= (unsigned int)e.size()))
+	for (;;) {
+		if ((i == last) || (i >= (unsigned int)e.size()))
 			break;
-		v[i] = (char)e[i];
+		v[i] = (char) e[i];
 		++i;
 	}
 	v[i] = 0;
+	return v;
+}
+
+bool Dictionary::sign(const Identity &signer)
+{
+	Vector<uint8_t> data;
+	encode(data, true);
+	uint8_t sig[ZT_SIGNATURE_BUFFER_SIZE];
+	const unsigned int siglen = signer.sign(data.data(), (unsigned int) data.size(), sig, ZT_SIGNATURE_BUFFER_SIZE);
+	if (siglen == 0)
+		return false;
+
+	uint8_t fp[ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE];
+	signer.fingerprint().address().copyTo(fp);
+	Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(fp + ZT_ADDRESS_LENGTH, signer.fingerprint().hash());
+
+	m_entries[s_signatureFingerprint].assign(fp, fp + ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE);
+	m_entries[s_signatureData].assign(sig, sig + siglen);
+
+	return true;
+}
+
+Fingerprint Dictionary::signer() const
+{
+	SortedMap<FCV<char, 8>, Vector<uint8_t> >::const_iterator sigfp(m_entries.find(s_signatureFingerprint));
+	Fingerprint fp;
+	if ((sigfp != m_entries.end()) && (sigfp->second.size() == (ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE))) {
+		fp.apiFingerprint()->address = Address(sigfp->second.data()).toInt();
+		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(fp.apiFingerprint()->hash, sigfp->second.data() + ZT_ADDRESS_LENGTH);
+	}
+	return fp;
+}
+
+bool Dictionary::verify(const Identity &signer) const
+{
+	SortedMap< FCV<char, 8>, Vector<uint8_t> >::const_iterator sigfp(m_entries.find(s_signatureFingerprint));
+	if (
+		(sigfp == m_entries.end()) ||
+		(sigfp->second.size() != (ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE)) ||
+		(Address(sigfp->second.data()) != signer.address()) ||
+		(memcmp(sigfp->second.data() + ZT_ADDRESS_LENGTH,signer.fingerprint().hash(),ZT_FINGERPRINT_HASH_SIZE) != 0))
+		return false;
+
+	SortedMap< FCV<char, 8>, Vector<uint8_t> >::const_iterator sig(m_entries.find(s_signatureData));
+	if ((sig == m_entries.end()) || (sig->second.empty()))
+		return false;
+
+	Vector<uint8_t> data;
+	encode(data, true);
+	return signer.verify(data.data(),(unsigned int)data.size(),sig->second.data(),(unsigned int)sig->second.size());
 }
 
 void Dictionary::clear()
@@ -122,34 +178,33 @@ void Dictionary::clear()
 	m_entries.clear();
 }
 
-void Dictionary::encode(Vector<uint8_t> &out) const
+void Dictionary::encode(Vector<uint8_t> &out, const bool omitSignatureFields) const
 {
-	uint64_t str[2] = { 0,0 }; // second uint64_t being 0 means all strings always 0-terminated
 	out.clear();
-	for(Map< uint64_t,Vector<uint8_t> >::const_iterator ti(m_entries.begin());ti != m_entries.end();++ti) {
-		str[0] = ti->first;
-		s_appendKey(out,reinterpret_cast<const char *>(str));
-		for(Vector<uint8_t>::const_iterator i(ti->second.begin());i!=ti->second.end();++i)
-			s_appendValueByte(out,*i);
-		out.push_back((uint8_t)'\n');
+	for (SortedMap<FCV<char, 8>, Vector<uint8_t> >::const_iterator ti(m_entries.begin());ti != m_entries.end();++ti) {
+		if ((!omitSignatureFields) || ((ti->first != s_signatureFingerprint) && (ti->first != s_signatureData))) {
+			s_appendKey(out, ti->first.data());
+			for (Vector<uint8_t>::const_iterator i(ti->second.begin());i != ti->second.end();++i)
+				s_appendValueByte(out, *i);
+			out.push_back((uint8_t) '\n');
+		}
 	}
+	out.push_back(0);
 }
 
-bool Dictionary::decode(const void *data,unsigned int len)
+bool Dictionary::decode(const void *data, unsigned int len)
 {
 	clear();
-
-	uint64_t k = 0;
-	unsigned int ki = 0;
+	FCV<char, 8> k;
 	Vector<uint8_t> *v = nullptr;
 	bool escape = false;
-	for(unsigned int di=0;di<len;++di) {
+	for (unsigned int di = 0;di < len;++di) {
 		uint8_t c = reinterpret_cast<const uint8_t *>(data)[di];
 		if (!c) break;
 		if (v) {
 			if (escape) {
 				escape = false;
-				switch(c) {
+				switch (c) {
 					case 48:
 						v->push_back(0);
 						break;
@@ -167,9 +222,8 @@ bool Dictionary::decode(const void *data,unsigned int len)
 						break;
 				}
 			} else {
-				if (c == (uint8_t)'\n') {
-					k = 0;
-					ki = 0;
+				if (c == (uint8_t) '\n') {
+					k.clear();
 					v = nullptr;
 				} else if (c == 92) { // backslash
 					escape = true;
@@ -178,16 +232,18 @@ bool Dictionary::decode(const void *data,unsigned int len)
 				}
 			}
 		} else {
-			if ((c < 33)||(c > 126)||(c == 92)) {
+			if ((c < 33) || (c > 126) || (c == 92)) {
 				return false;
-			} else if (c == (uint8_t)'=') {
+			} else if (c == (uint8_t) '=') {
+				k.push_back(0);
 				v = &m_entries[k];
+			} else if (k.size() < 7) {
+				k.push_back(c);
 			} else {
-				reinterpret_cast<uint8_t *>(&k)[ki & 7U] ^= c;
+				return false;
 			}
 		}
 	}
-
 	return true;
 }
 

+ 51 - 19
node/Dictionary.hpp

@@ -18,16 +18,21 @@
 #include "Utils.hpp"
 #include "Address.hpp"
 #include "Buf.hpp"
+#include "FCV.hpp"
+#include "SHA512.hpp"
+#include "Fingerprint.hpp"
 #include "Containers.hpp"
 
 namespace ZeroTier {
 
+class Identity;
+
 /**
  * A simple key-value store for short keys
  *
  * This data structure is used for network configurations, node meta-data,
  * and other open-definition protocol objects. It consists of a key-value
- * store with short (max: 8 characters) keys that map to strings, blobs,
+ * store with short (max: 7 characters) keys that map to strings, blobs,
  * or integers with the latter being by convention in hex format.
  *
  * If this seems a little odd, it is. It dates back to the very first alpha
@@ -118,7 +123,7 @@ public:
 	 * @param v Buffer to hold string
 	 * @param cap Maximum size of string (including terminating null)
 	 */
-	void getS(const char *k,char *v,unsigned int cap) const;
+	char *getS(const char *k,char *v,unsigned int cap) const;
 
 	/**
 	 * Get an object supporting the marshal/unmarshal interface pattern
@@ -133,11 +138,36 @@ public:
 		const Vector<uint8_t> &d = (*this)[k];
 		if (d.empty())
 			return false;
-		if (obj.unmarshal(d.data(),(unsigned int)d.size()) <= 0)
-			return false;
-		return true;
+		return (obj.unmarshal(d.data(),(unsigned int)d.size()) > 0);
 	}
 
+	/**
+	 * Sign this identity
+	 *
+	 * This adds two fields:
+	 *   "@Si" contains the fingerprint (address followed by hash) of the signer
+	 *   "@Ss" contains the signature
+	 *
+	 * @param signer Signing identity (must contain secret)
+	 * @return True if signature was successful
+	 */
+	bool sign(const Identity &signer);
+
+	/**
+	 * Get the signer's fingerprint for this dictionary or a NIL fingerprint if not signed.
+	 *
+	 * @return Signer
+	 */
+	Fingerprint signer() const;
+
+	/**
+	 * Verify this identity's signature
+	 *
+	 * @param signer
+	 * @return
+	 */
+	bool verify(const Identity &signer) const;
+
 	/**
 	 * Erase all entries in dictionary
 	 */
@@ -156,12 +186,10 @@ public:
 	/**
 	 * Encode to a string in the supplied vector
 	 *
-	 * This does not add a terminating zero. This must be pushed afterwords
-	 * if the result is to be handled as a C string.
-	 *
 	 * @param out String encoded dictionary
+	 * @param omitSignatureFields If true omit the signature fields from encoding (default: false)
 	 */
-	void encode(Vector<uint8_t> &out) const;
+	void encode(Vector<uint8_t> &out,bool omitSignatureFields = false) const;
 
 	/**
 	 * Decode a string encoded dictionary
@@ -347,31 +375,35 @@ private:
 				break;
 		}
 	}
+
 	template<typename V>
 	ZT_INLINE static void s_appendKey(V &out,const char *const k)
 	{
-		for(unsigned int i=0;i<8;++i) {
+		for(unsigned int i=0;i<7;++i) {
 			const char kc = k[i];
-			if (!kc) break;
+			if (kc == 0)
+				break;
 			if ((kc >= 33)&&(kc <= 126)&&(kc != 61)&&(kc != 92)) // printable ASCII with no spaces, equals, or backslash
 				out.push_back((uint8_t)kc);
 		}
 		out.push_back((uint8_t)'=');
 	}
 
-	// This just packs up to 8 character bytes into a 64-bit word. There is no need
-	// for this to be portable in terms of endian-ness. It's just for fast key lookup.
-	static ZT_INLINE uint64_t s_toKey(const char *k)
+	ZT_INLINE static FCV<char,8> &s_key(FCV<char,8> &buf,const char *k) noexcept
 	{
-		uint64_t key = 0;
-		for(int i=0;i<8;++i) {
-			if ((reinterpret_cast<uint8_t *>(&key)[i] = *(k++)) == 0)
+		buf.clear();
+		for(unsigned int i=0;i<7;++i) {
+			const char kc = k[i];
+			if (kc == 0)
 				break;
+			if ((kc >= 33)&&(kc <= 126)&&(kc != 61)&&(kc != 92)) // printable ASCII with no spaces, equals, or backslash
+				buf.push_back(kc);
 		}
-		return key;
+		buf.push_back(0);
+		return buf;
 	}
 
-	Map< uint64_t,Vector<uint8_t> > m_entries;
+	SortedMap< FCV<char,8>,Vector<uint8_t> > m_entries;
 };
 
 } // namespace ZeroTier

+ 156 - 21
node/Endpoint.cpp

@@ -16,39 +16,128 @@
 
 namespace ZeroTier {
 
-int Endpoint::marshal(uint8_t data[ZT_ENDPOINT_MARSHAL_SIZE_MAX]) const noexcept
+void Endpoint::toString(char s[ZT_ENDPOINT_STRING_SIZE_MAX]) const noexcept
 {
-	switch(m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1]) {
+	static const char *const s_endpointTypeChars = ZT_ENDPOINT_TYPE_CHAR_INDEX;
+
+	static_assert(ZT_ENDPOINT_STRING_SIZE_MAX > (ZT_INETADDRESS_STRING_SIZE_MAX + 4), "overflow");
+	static_assert(ZT_ENDPOINT_STRING_SIZE_MAX > (ZT_FINGERPRINT_STRING_SIZE_MAX + 4), "overflow");
+
+	switch (this->type) {
 		default:
+			s[0] = s_endpointTypeChars[ZT_ENDPOINT_TYPE_NIL];
+			s[1] = 0;
+			break;
+		case ZT_ENDPOINT_TYPE_ZEROTIER:
+			s[0] = s_endpointTypeChars[ZT_ENDPOINT_TYPE_ZEROTIER];
+			s[1] = '/';
+			zt().toString(s + 2);
+			break;
+		case ZT_ENDPOINT_TYPE_ETHERNET:
+		case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
+		case ZT_ENDPOINT_TYPE_BLUETOOTH:
+			s[0] = s_endpointTypeChars[this->type];
+			s[1] = '/';
+			eth().toString(s + 2);
+			break;
+		case ZT_ENDPOINT_TYPE_IP:
+		case ZT_ENDPOINT_TYPE_IP_UDP:
+		case ZT_ENDPOINT_TYPE_IP_TCP:
+		case ZT_ENDPOINT_TYPE_IP_HTTP2:
+			s[0] = s_endpointTypeChars[this->type];
+			s[1] = '/';
+			ip().toString(s + 2);
+			break;
+	}
+}
+
+bool Endpoint::fromString(const char *s) noexcept
+{
+	memoryZero(this);
+	if ((!s) || (!*s))
+		return true;
+
+	const char *start = strchr(s, '/');
+	if (start) {
+		char tmp[16];
+		for (unsigned int i = 0;i < 15;++i) {
+			if ((tmp[i] = s[i]) == 0)
+				break;
+		}
+		tmp[15] = 0;
+		this->type = (ZT_EndpointType)Utils::strToUInt(tmp);
+
+		++start;
+		Fingerprint tmpfp;
+		MAC tmpmac;
+		switch (this->type) {
+			case ZT_ENDPOINT_TYPE_NIL:
+				break;
+			case ZT_ENDPOINT_TYPE_ZEROTIER:
+				if (!tmpfp.fromString(start))
+					return false;
+				this->value.fp = tmpfp;
+				break;
+			case ZT_ENDPOINT_TYPE_ETHERNET:
+			case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
+			case ZT_ENDPOINT_TYPE_BLUETOOTH:
+				tmpmac.fromString(start);
+				this->value.mac = tmpmac.toInt();
+				break;
+			case ZT_ENDPOINT_TYPE_IP:
+			case ZT_ENDPOINT_TYPE_IP_UDP:
+			case ZT_ENDPOINT_TYPE_IP_TCP:
+			case ZT_ENDPOINT_TYPE_IP_HTTP2:
+				if (!asInetAddress(this->value.ss).fromString(start))
+					return false;
+			default:
+				this->type = ZT_ENDPOINT_TYPE_NIL;
+				return false;
+		}
+	} else {
+		if (Utils::strToUInt(s) != (unsigned int)ZT_ENDPOINT_TYPE_NIL)
+			return false;
+	}
+
+	return true;
+}
+
+int Endpoint::marshal(uint8_t data[ZT_ENDPOINT_MARSHAL_SIZE_MAX]) const noexcept
+{
+	switch (this->type) {
 		//case ZT_ENDPOINT_TYPE_NIL:
-			data[0] = 0;
+		default:
+			// NIL endpoints get serialized like NIL InetAddress instances.
+			data[0] = ZT_ENDPOINT_TYPE_NIL;
 			return 1;
 
 		case ZT_ENDPOINT_TYPE_ZEROTIER:
 			data[0] = 16 + ZT_ENDPOINT_TYPE_ZEROTIER;
-			reinterpret_cast<const Fingerprint *>(m_value)->address().copyTo(data + 1);
-			Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(data + 1 + ZT_ADDRESS_LENGTH,reinterpret_cast<const Fingerprint *>(m_value)->hash());
+			Address(this->value.fp.address).copyTo(data + 1);
+			Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(data + 1 + ZT_ADDRESS_LENGTH, this->value.fp.hash);
 			return 1 + ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE;
 
 		case ZT_ENDPOINT_TYPE_ETHERNET:
 		case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
 		case ZT_ENDPOINT_TYPE_BLUETOOTH:
-			data[0] = 16 + m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1];
-			reinterpret_cast<const MAC *>(m_value)->copyTo(data + 1);
+			data[0] = 16 + (uint8_t)this->type;
+			MAC(this->value.mac).copyTo(data + 1);
 			return 7;
 
 		case ZT_ENDPOINT_TYPE_IP_UDP:
-			return reinterpret_cast<const InetAddress *>(m_value)->marshal(data);
+			// Default UDP mode gets serialized to look exactly like an InetAddress.
+			return asInetAddress(this->value.ss).marshal(data);
 
 		case ZT_ENDPOINT_TYPE_IP:
 		case ZT_ENDPOINT_TYPE_IP_TCP:
 		case ZT_ENDPOINT_TYPE_IP_HTTP2:
-			data[0] = 16 + m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1];
-			return 1 + reinterpret_cast<const InetAddress *>(m_value)->marshal(data + 1);
+			// Other IP types get serialized as new version Endpoint instances with type.
+			data[0] = 16 + (uint8_t)this->type;
+			return 1 + asInetAddress(this->value.ss).marshal(data + 1);
 	}
 }
 
-int Endpoint::unmarshal(const uint8_t *restrict data,int len) noexcept
+int Endpoint::unmarshal(const uint8_t *restrict data, int len) noexcept
 {
 	memoryZero(this);
 	if (unlikely(len <= 0))
@@ -59,25 +148,25 @@ int Endpoint::unmarshal(const uint8_t *restrict data,int len) noexcept
 	// This allows backward compatibility with old endpoint fields in the
 	// protocol that were serialized InetAddress instances.
 	if (data[0] < 16) {
-		switch(data[0]) {
+		switch (data[0]) {
 			case 0:
 				return 1;
 			case 4:
 			case 6:
-				m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] = (uint8_t)ZT_ENDPOINT_TYPE_IP_UDP;
-				return reinterpret_cast<InetAddress *>(m_value)->unmarshal(data,len);
+				this->type = ZT_ENDPOINT_TYPE_IP_UDP;
+				return asInetAddress(this->value.ss).unmarshal(data, len);
 		}
 		return -1;
 	}
 
-	switch((m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] = (data[0] - 16))) {
+	switch ((this->type = (ZT_EndpointType)(data[0] - 16))) {
 		case ZT_ENDPOINT_TYPE_NIL:
 			return 1;
 
 		case ZT_ENDPOINT_TYPE_ZEROTIER:
 			if (len >= (1 + ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE)) {
-				reinterpret_cast<Fingerprint *>(m_value)->apiFingerprint()->address = Address(data + 1).toInt();
-				Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(reinterpret_cast<Fingerprint *>(m_value)->apiFingerprint()->hash,data + 1 + ZT_ADDRESS_LENGTH);
+				this->value.fp.address = Address(data + 1).toInt();
+				Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(this->value.fp.hash, data + 1 + ZT_ADDRESS_LENGTH);
 				return 1 + ZT_ADDRESS_LENGTH + ZT_FINGERPRINT_HASH_SIZE;
 			}
 			return -1;
@@ -86,7 +175,9 @@ int Endpoint::unmarshal(const uint8_t *restrict data,int len) noexcept
 		case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
 		case ZT_ENDPOINT_TYPE_BLUETOOTH:
 			if (len >= 7) {
-				reinterpret_cast<MAC *>(m_value)->setTo(data + 1);
+				MAC tmp;
+				tmp.setTo(data + 1);
+				this->value.mac = tmp.toInt();
 				return 7;
 			}
 			return -1;
@@ -95,7 +186,7 @@ int Endpoint::unmarshal(const uint8_t *restrict data,int len) noexcept
 		case ZT_ENDPOINT_TYPE_IP_UDP:
 		case ZT_ENDPOINT_TYPE_IP_TCP:
 		case ZT_ENDPOINT_TYPE_IP_HTTP2:
-			return reinterpret_cast<InetAddress *>(m_value)->unmarshal(data + 1,len - 1);
+			return asInetAddress(this->value.ss).unmarshal(data + 1, len - 1);
 
 		default:
 			break;
@@ -104,11 +195,55 @@ int Endpoint::unmarshal(const uint8_t *restrict data,int len) noexcept
 	// Unrecognized types can still be passed over in a valid stream if they are
 	// prefixed by a 16-bit size. This allows forward compatibility with future
 	// endpoint types.
-	m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] = (uint8_t)ZT_ENDPOINT_TYPE_NIL;
+	this->type = ZT_ENDPOINT_TYPE_NIL;
 	if (len < 3)
 		return -1;
-	const int unrecLen = 1 + (int)Utils::loadBigEndian<uint16_t>(data + 1);
+	const int unrecLen = 1 + (int) Utils::loadBigEndian<uint16_t>(data + 1);
 	return (unrecLen > len) ? -1 : unrecLen;
 }
 
+bool Endpoint::operator==(const Endpoint &ep) const noexcept
+{
+	if (this->type == ep.type) {
+		switch(this->type) {
+			case ZT_ENDPOINT_TYPE_ZEROTIER:
+				return zt() == ep.zt();
+			case ZT_ENDPOINT_TYPE_ETHERNET:
+			case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
+			case ZT_ENDPOINT_TYPE_BLUETOOTH:
+				return this->value.mac == ep.value.mac;
+			case ZT_ENDPOINT_TYPE_IP:
+			case ZT_ENDPOINT_TYPE_IP_UDP:
+			case ZT_ENDPOINT_TYPE_IP_TCP:
+			case ZT_ENDPOINT_TYPE_IP_HTTP2:
+				return ip() == ep.ip();
+			default:
+				return true;
+		}
+	}
+	return false;
+}
+
+bool Endpoint::operator<(const Endpoint &ep) const noexcept
+{
+	if (this->type == ep.type) {
+		switch(this->type) {
+			case ZT_ENDPOINT_TYPE_ZEROTIER:
+				return zt() < ep.zt();
+			case ZT_ENDPOINT_TYPE_ETHERNET:
+			case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
+			case ZT_ENDPOINT_TYPE_BLUETOOTH:
+				return this->value.mac < ep.value.mac;
+			case ZT_ENDPOINT_TYPE_IP:
+			case ZT_ENDPOINT_TYPE_IP_UDP:
+			case ZT_ENDPOINT_TYPE_IP_TCP:
+			case ZT_ENDPOINT_TYPE_IP_HTTP2:
+				return ip() < ep.ip();
+			default:
+				return true;
+		}
+	}
+	return (int)this->type < (int)ep.type;
+}
+
 } // namespace ZeroTier

+ 63 - 34
node/Endpoint.hpp

@@ -22,13 +22,17 @@
 #include "Fingerprint.hpp"
 #include "MAC.hpp"
 
-#define ZT_ENDPOINT_MARSHAL_SIZE_MAX 128
-
-static_assert(ZT_ENDPOINT_MARSHAL_SIZE_MAX > (ZT_INETADDRESS_MARSHAL_SIZE_MAX + 1),"ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
-static_assert(ZT_ENDPOINT_MARSHAL_SIZE_MAX > (sizeof(ZT_Fingerprint) + 1),"ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
+#define ZT_ENDPOINT_STRING_SIZE_MAX 256
+#define ZT_ENDPOINT_MARSHAL_SIZE_MAX 192
 
 namespace ZeroTier {
 
+static_assert((ZT_ENDPOINT_MARSHAL_SIZE_MAX - 1) > ZT_INETADDRESS_MARSHAL_SIZE_MAX, "ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
+static_assert((ZT_ENDPOINT_MARSHAL_SIZE_MAX - 1) > sizeof(ZT_Fingerprint), "ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
+static_assert((ZT_ENDPOINT_MARSHAL_SIZE_MAX - 1) > sizeof(InetAddress), "ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
+static_assert((ZT_ENDPOINT_MARSHAL_SIZE_MAX - 1) > sizeof(MAC), "ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
+static_assert((ZT_ENDPOINT_MARSHAL_SIZE_MAX - 1) > sizeof(Fingerprint), "ZT_ENDPOINT_MARSHAL_SIZE_MAX not large enough");
+
 /**
  * Endpoint variant specifying some form of network endpoint.
  * 
@@ -38,18 +42,17 @@ namespace ZeroTier {
  * where InetAddress was used as long as only the UDP type is exchanged
  * with those nodes.
  */
-class Endpoint : public TriviallyCopyable
+class Endpoint : public ZT_Endpoint, public TriviallyCopyable
 {
 public:
-	/**
-	 * Endpoint type (defined in the API)
-	 */
-	typedef ZT_EndpointType Type;
-
 	/**
 	 * Create a NIL/empty endpoint
 	 */
-	ZT_INLINE Endpoint() noexcept { memoryZero(this); }
+	ZT_INLINE Endpoint() noexcept
+	{ memoryZero(this); }
+
+	ZT_INLINE Endpoint(const ZT_Endpoint &ep) noexcept
+	{ *this = ep; }
 
 	/**
 	 * Create an endpoint for a type that uses an IP
@@ -57,11 +60,11 @@ public:
 	 * @param a IP/port
 	 * @param et Endpoint type (default: IP_UDP)
 	 */
-	ZT_INLINE Endpoint(const InetAddress &a,const Type et = ZT_ENDPOINT_TYPE_IP_UDP) noexcept
+	ZT_INLINE Endpoint(const InetAddress &inaddr, const ZT_EndpointType et = ZT_ENDPOINT_TYPE_IP_UDP) noexcept
 	{
-		if (a) {
-			Utils::copy<sizeof(InetAddress)>(m_value,&a);
-			m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] = (uint8_t)et;
+		if (inaddr) {
+			this->type = et;
+			Utils::copy<sizeof(struct sockaddr_storage)>(&(this->value.ss), &(inaddr.as.ss));
 		} else {
 			memoryZero(this);
 		}
@@ -75,8 +78,8 @@ public:
 	ZT_INLINE Endpoint(const Fingerprint &zt_) noexcept
 	{
 		if (zt_) {
-			Utils::copy<sizeof(Fingerprint)>(m_value,&zt_);
-			m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] = (uint8_t)ZT_ENDPOINT_TYPE_ZEROTIER;
+			this->type = ZT_ENDPOINT_TYPE_ZEROTIER;
+			this->value.fp = zt_;
 		} else {
 			memoryZero(this);
 		}
@@ -88,32 +91,28 @@ public:
 	 * @param eth_ Ethernet address
 	 * @param et Endpoint type (default: ETHERNET)
 	 */
-	ZT_INLINE Endpoint(const MAC &eth_,const Type et = ZT_ENDPOINT_TYPE_ETHERNET) noexcept
+	ZT_INLINE Endpoint(const MAC &eth_, const ZT_EndpointType et = ZT_ENDPOINT_TYPE_ETHERNET) noexcept
 	{
 		if (eth_) {
-			Utils::copy<sizeof(MAC)>(m_value,&eth_);
-			m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] = (uint8_t)et;
+			this->type = et;
+			this->value.mac = eth_.toInt();
 		} else {
 			memoryZero(this);
 		}
 	}
 
-	/**
-	 * @return Endpoint type
-	 */
-	ZT_INLINE Type type() const noexcept { return (Type)m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1]; }
-
 	/**
 	 * @return True if endpoint type isn't NIL
 	 */
-	ZT_INLINE operator bool() const noexcept { return (m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX-1] != (uint8_t)ZT_ENDPOINT_TYPE_NIL); }
+	ZT_INLINE operator bool() const noexcept
+	{ return this->type != ZT_ENDPOINT_TYPE_NIL; }
 
 	/**
 	 * @return True if this endpoint type has an InetAddress address type and thus ip() is valid
 	 */
 	ZT_INLINE bool isInetAddr() const noexcept
 	{
-		switch(this->type()) {
+		switch (this->type) {
 			case ZT_ENDPOINT_TYPE_IP:
 			case ZT_ENDPOINT_TYPE_IP_UDP:
 			case ZT_ENDPOINT_TYPE_IP_TCP:
@@ -129,28 +128,58 @@ public:
 	 * 
 	 * @return InetAddress instance
 	 */
-	ZT_INLINE const InetAddress &ip() const noexcept { return *reinterpret_cast<const InetAddress *>(m_value); }
+	ZT_INLINE const InetAddress &ip() const noexcept
+	{ return asInetAddress(this->value.ss); }
 
 	/**
 	 * Get MAC if this is an Ethernet, WiFi direct, or Bluetooth type (undefined otherwise)
 	 *
 	 * @return Ethernet MAC
 	 */
-	ZT_INLINE const MAC &eth() const noexcept { return *reinterpret_cast<const MAC *>(m_value); }
+	ZT_INLINE MAC eth() const noexcept
+	{ return MAC(this->value.mac); }
 
 	/**
 	 * Get fingerprint if this is a ZeroTier endpoint type (undefined otherwise)
 	 * 
 	 * @return ZeroTier fingerprint
 	 */
-	ZT_INLINE const Fingerprint &zt() const noexcept { return *reinterpret_cast<const Fingerprint *>(m_value); }
+	ZT_INLINE Fingerprint zt() const noexcept
+	{ return Fingerprint(this->value.fp); }
+
+	void toString(char s[ZT_ENDPOINT_STRING_SIZE_MAX]) const noexcept;
+
+	ZT_INLINE String toString() const
+	{
+		char tmp[ZT_ENDPOINT_STRING_SIZE_MAX];
+		toString(tmp);
+		return String(tmp);
+	}
+
+	bool fromString(const char *s) noexcept;
+
+	static constexpr int marshalSizeMax() noexcept
+	{ return ZT_ENDPOINT_MARSHAL_SIZE_MAX; }
 
-	static constexpr int marshalSizeMax() noexcept { return ZT_ENDPOINT_MARSHAL_SIZE_MAX; }
 	int marshal(uint8_t data[ZT_ENDPOINT_MARSHAL_SIZE_MAX]) const noexcept;
-	int unmarshal(const uint8_t *restrict data,int len) noexcept;
 
-private:
-	uint8_t m_value[ZT_ENDPOINT_MARSHAL_SIZE_MAX]; // the last byte in this buffer is the type
+	int unmarshal(const uint8_t *restrict data, int len) noexcept;
+
+	bool operator==(const Endpoint &ep) const noexcept;
+
+	ZT_INLINE bool operator!=(const Endpoint &ep) const noexcept
+	{ return !((*this) == ep); }
+
+	bool operator<(const Endpoint &ep) const noexcept;
+
+	ZT_INLINE bool operator>(const Endpoint &ep) const noexcept
+	{ return (ep < *this); }
+
+	ZT_INLINE bool operator<=(const Endpoint &ep) const noexcept
+	{ return !(ep < *this); }
+
+	ZT_INLINE bool operator>=(const Endpoint &ep) const noexcept
+	{ return !(*this < ep); }
 };
 
 } // namespace ZeroTier

+ 75 - 30
node/FCV.hpp

@@ -31,18 +31,28 @@ namespace ZeroTier {
  * @tparam T Type to contain
  * @tparam C Maximum capacity of vector
  */
-template<typename T,unsigned int C>
+template<typename T, unsigned int C>
 class FCV
 {
 public:
-	typedef T * iterator;
-	typedef const T * const_iterator;
+	typedef T *iterator;
+	typedef const T *const_iterator;
 
-	ZT_INLINE FCV() noexcept : _s(0) {}
-	ZT_INLINE FCV(const FCV &v) : _s(0) { *this = v; }
+	ZT_INLINE FCV() noexcept: _s(0)
+	{}
+
+	ZT_INLINE FCV(const FCV &v) : _s(0)
+	{ *this = v; }
+
+	ZT_INLINE FCV(const T *const contents, const unsigned int len) :
+		_s(0)
+	{
+		for (unsigned int i = 0;i < len;++i)
+			push_back(contents[i]);
+	}
 
 	template<typename I>
-	ZT_INLINE FCV(I i,I end) :
+	ZT_INLINE FCV(I i, I end) :
 		_s(0)
 	{
 		while (i != end) {
@@ -51,7 +61,8 @@ public:
 		}
 	}
 
-	ZT_INLINE ~FCV() { this->clear(); }
+	ZT_INLINE ~FCV()
+	{ this->clear(); }
 
 	ZT_INLINE FCV &operator=(const FCV &v)
 	{
@@ -59,7 +70,7 @@ public:
 			this->clear();
 			const unsigned int s = v._s;
 			_s = s;
-			for (unsigned int i=0;i<s;++i)
+			for (unsigned int i = 0;i < s;++i)
 				new(reinterpret_cast<T *>(_m) + i) T(*(reinterpret_cast<const T *>(v._m) + i));
 		}
 		return *this;
@@ -72,7 +83,7 @@ public:
 	{
 		const unsigned int s = _s;
 		_s = 0;
-		for(unsigned int i=0;i<s;++i)
+		for (unsigned int i = 0;i < s;++i)
 			(reinterpret_cast<T *>(_m) + i)->~T();
 	}
 
@@ -83,14 +94,21 @@ public:
 	 */
 	ZT_INLINE void unsafeMoveTo(FCV &v) noexcept
 	{
-		Utils::copy(v._m,_m,(v._s = _s) * sizeof(T));
+		Utils::copy(v._m, _m, (v._s = _s) * sizeof(T));
 		_s = 0;
 	}
 
-	ZT_INLINE iterator begin() noexcept { return reinterpret_cast<T *>(_m); }
-	ZT_INLINE iterator end() noexcept { return reinterpret_cast<T *>(_m) + _s; }
-	ZT_INLINE const_iterator begin() const noexcept { return reinterpret_cast<const T *>(_m); }
-	ZT_INLINE const_iterator end() const noexcept { return reinterpret_cast<const T *>(_m) + _s; }
+	ZT_INLINE iterator begin() noexcept
+	{ return reinterpret_cast<T *>(_m); }
+
+	ZT_INLINE iterator end() noexcept
+	{ return reinterpret_cast<T *>(_m) + _s; }
+
+	ZT_INLINE const_iterator begin() const noexcept
+	{ return reinterpret_cast<const T *>(_m); }
+
+	ZT_INLINE const_iterator end() const noexcept
+	{ return reinterpret_cast<const T *>(_m) + _s; }
 
 	ZT_INLINE T &operator[](const unsigned int i)
 	{
@@ -98,6 +116,7 @@ public:
 			return reinterpret_cast<T *>(_m)[i];
 		throw std::out_of_range("i > capacity");
 	}
+
 	ZT_INLINE const T &operator[](const unsigned int i) const
 	{
 		if (likely(i < _s))
@@ -105,12 +124,20 @@ public:
 		throw std::out_of_range("i > capacity");
 	}
 
-	static constexpr unsigned int capacity() noexcept { return C; }
-	ZT_INLINE unsigned int size() const noexcept { return _s; }
-	ZT_INLINE bool empty() const noexcept { return (_s == 0); }
+	static constexpr unsigned int capacity() noexcept
+	{ return C; }
+
+	ZT_INLINE unsigned int size() const noexcept
+	{ return _s; }
+
+	ZT_INLINE bool empty() const noexcept
+	{ return (_s == 0); }
 
-	ZT_INLINE T *data() noexcept { return reinterpret_cast<T *>(_m); }
-	ZT_INLINE const T *data() const noexcept { return reinterpret_cast<const T *>(_m); }
+	ZT_INLINE T *data() noexcept
+	{ return reinterpret_cast<T *>(_m); }
+
+	ZT_INLINE const T *data() const noexcept
+	{ return reinterpret_cast<const T *>(_m); }
 
 	/**
 	 * Push a value onto the back of this vector
@@ -122,7 +149,7 @@ public:
 	ZT_INLINE void push_back(const T &v)
 	{
 		if (likely(_s < C))
-			new (reinterpret_cast<T *>(_m) + _s++) T(v);
+			new(reinterpret_cast<T *>(_m) + _s++) T(v);
 		else throw std::out_of_range("capacity exceeded");
 	}
 
@@ -182,6 +209,14 @@ public:
 		_s = s;
 	}
 
+	/**
+	 * Set the size of this vector without otherwise changing anything
+	 *
+	 * @param ns New size
+	 */
+	ZT_INLINE void unsafeSetSize(unsigned int ns)
+	{ _s = ns; }
+
 	/**
 	 * This is a bounds checked auto-resizing variant of the [] operator
 	 *
@@ -213,12 +248,12 @@ public:
 	 * @param end Ending iterator (must be greater than start)
 	 */
 	template<typename X>
-	ZT_INLINE void assign(X start,const X &end)
+	ZT_INLINE void assign(X start, const X &end)
 	{
-		const int l = std::min((int)std::distance(start,end),(int)C);
+		const int l = std::min((int) std::distance(start, end), (int) C);
 		if (l > 0) {
-			this->resize((unsigned int)l);
-			for(int i=0;i<l;++i)
+			this->resize((unsigned int) l);
+			for (int i = 0;i < l;++i)
 				reinterpret_cast<T *>(_m)[i] = *(start++);
 		} else {
 			this->clear();
@@ -228,7 +263,7 @@ public:
 	ZT_INLINE bool operator==(const FCV &v) const noexcept
 	{
 		if (_s == v._s) {
-			for(unsigned int i=0;i<_s;++i) {
+			for (unsigned int i = 0;i < _s;++i) {
 				if (!(*(reinterpret_cast<const T *>(_m) + i) == *(reinterpret_cast<const T *>(v._m) + i)))
 					return false;
 			}
@@ -236,11 +271,21 @@ public:
 		}
 		return false;
 	}
-	ZT_INLINE bool operator!=(const FCV &v) const noexcept { return (!(*this == v)); }
-	ZT_INLINE bool operator<(const FCV &v) const noexcept { return std::lexicographical_compare(begin(),end(),v.begin(),v.end()); }
-	ZT_INLINE bool operator>(const FCV &v) const noexcept { return (v < *this); }
-	ZT_INLINE bool operator<=(const FCV &v) const noexcept { return !(v < *this); }
-	ZT_INLINE bool operator>=(const FCV &v) const noexcept { return !(*this < v); }
+
+	ZT_INLINE bool operator!=(const FCV &v) const noexcept
+	{ return (!(*this == v)); }
+
+	ZT_INLINE bool operator<(const FCV &v) const noexcept
+	{ return std::lexicographical_compare(begin(), end(), v.begin(), v.end()); }
+
+	ZT_INLINE bool operator>(const FCV &v) const noexcept
+	{ return (v < *this); }
+
+	ZT_INLINE bool operator<=(const FCV &v) const noexcept
+	{ return !(v < *this); }
+
+	ZT_INLINE bool operator>=(const FCV &v) const noexcept
+	{ return !(*this < v); }
 
 private:
 #ifdef _MSC_VER

+ 54 - 46
node/Fingerprint.hpp

@@ -19,59 +19,43 @@
 #include "Address.hpp"
 #include "Utils.hpp"
 
-#include <algorithm>
-
-#define ZT_FINGERPRINT_STRING_BUFFER_LENGTH 96
+#define ZT_FINGERPRINT_STRING_SIZE_MAX 128
 
 namespace ZeroTier {
 
-class Identity;
-
 /**
  * Address and full hash of an identity's public keys.
  *
  * This is the same size as ZT_Fingerprint and should be cast-able back and forth.
  * This is checked in Tests.cpp.
  */
-class Fingerprint : public TriviallyCopyable
+class Fingerprint : public ZT_Fingerprint, public TriviallyCopyable
 {
-	friend class Identity;
-
 public:
-	/**
-	 * Create an empty/nil fingerprint
-	 */
-	ZT_INLINE Fingerprint() noexcept { memoryZero(this); }
-
-	/**
-	 * Create a Fingerprint that is a copy of the external API companion structure
-	 *
-	 * @param apifp API fingerprint
-	 */
-	ZT_INLINE Fingerprint(const ZT_Fingerprint &apifp) noexcept { Utils::copy<sizeof(ZT_Fingerprint)>(&m_cfp,&apifp); }
+	ZT_INLINE Fingerprint() noexcept
+	{ memoryZero(this); }
 
-	ZT_INLINE Address address() const noexcept { return Address(m_cfp.address); }
-	ZT_INLINE const uint8_t *hash() const noexcept { return m_cfp.hash; }
-	ZT_INLINE ZT_Fingerprint *apiFingerprint() noexcept { return &m_cfp; }
-	ZT_INLINE const ZT_Fingerprint *apiFingerprint() const noexcept { return &m_cfp; }
+	ZT_INLINE Fingerprint(const ZT_Fingerprint &fp) noexcept
+	{ Utils::copy<sizeof(ZT_Fingerprint)>(this, &fp); }
 
 	/**
 	 * @return True if hash is not all zero (missing/unspecified)
 	 */
-	ZT_INLINE bool haveHash() const noexcept { return (!Utils::allZero(m_cfp.hash, sizeof(m_cfp.hash))); }
+	ZT_INLINE bool haveHash() const noexcept
+	{ return (!Utils::allZero(this->hash, ZT_FINGERPRINT_HASH_SIZE)); }
 
 	/**
 	 * Get a base32-encoded representation of this fingerprint
 	 *
 	 * @param s Base32 string
 	 */
-	ZT_INLINE void toString(char s[ZT_FINGERPRINT_STRING_BUFFER_LENGTH]) const noexcept
+	ZT_INLINE void toString(char s[ZT_FINGERPRINT_STRING_SIZE_MAX]) const noexcept
 	{
-		uint8_t tmp[48 + 5];
-		address().copyTo(tmp);
-		Utils::copy<48>(tmp + 5, m_cfp.hash);
-		Utils::b32e(tmp,sizeof(tmp),s,ZT_FINGERPRINT_STRING_BUFFER_LENGTH);
-		s[ZT_FINGERPRINT_STRING_BUFFER_LENGTH-1] = 0; // sanity check, ensure always zero terminated
+		Address(this->address).toString(s);
+		if (haveHash()) {
+			s[ZT_ADDRESS_LENGTH_HEX] = '/';
+			Utils::b32e(this->hash, ZT_FINGERPRINT_HASH_SIZE, s + (ZT_ADDRESS_LENGTH_HEX + 1), ZT_FINGERPRINT_STRING_SIZE_MAX - (ZT_ADDRESS_LENGTH_HEX + 1));
+		}
 	}
 
 	/**
@@ -80,32 +64,56 @@ public:
 	 * @param s String to decode
 	 * @return True if string appears to be valid and of the proper length (no other checking is done)
 	 */
-	ZT_INLINE bool fromString(const char *s) noexcept
+	ZT_INLINE bool fromString(const char *const s) noexcept
 	{
-		uint8_t tmp[48 + 5];
-		if (Utils::b32d(s,tmp,sizeof(tmp)) != sizeof(tmp))
+		if (!s)
+			return false;
+		const int l = (int) strlen(s);
+		if (l < ZT_ADDRESS_LENGTH_HEX)
 			return false;
-		m_cfp.address = Address(tmp).toInt();
-		Utils::copy<48>(m_cfp.hash, tmp + 5);
+		char a[ZT_ADDRESS_LENGTH_HEX + 1];
+		Utils::copy<ZT_ADDRESS_LENGTH_HEX>(a, s);
+		a[ZT_ADDRESS_LENGTH_HEX] = 0;
+		this->address = Utils::hexStrToU64(a) & ZT_ADDRESS_MASK;
+		if (l > (ZT_ADDRESS_LENGTH_HEX + 1)) {
+			if (Utils::b32d(s + (ZT_ADDRESS_LENGTH_HEX + 1), this->hash, ZT_FINGERPRINT_HASH_SIZE) != ZT_FINGERPRINT_HASH_SIZE)
+				return false;
+		} else {
+			Utils::zero<ZT_FINGERPRINT_HASH_SIZE>(this->hash);
+		}
 		return true;
 	}
 
-	ZT_INLINE void zero() noexcept { memoryZero(this); }
-	ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)m_cfp.address; }
+	ZT_INLINE void zero() noexcept
+	{ memoryZero(this); }
+
+	ZT_INLINE unsigned long hashCode() const noexcept
+	{ return (unsigned long)this->address; }
+
+	ZT_INLINE operator bool() const noexcept
+	{ return this->address != 0; }
 
-	ZT_INLINE operator bool() const noexcept { return (m_cfp.address != 0); }
+	ZT_INLINE bool operator==(const Fingerprint &h) const noexcept
+	{ return ((this->address == h.address) && (memcmp(this->hash, h.hash, ZT_FINGERPRINT_HASH_SIZE) == 0)); }
 
-	ZT_INLINE bool operator==(const Fingerprint &h) const noexcept { return ((m_cfp.address == h.m_cfp.address) && (memcmp(m_cfp.hash, h.m_cfp.hash, ZT_FINGERPRINT_HASH_SIZE) == 0)); }
-	ZT_INLINE bool operator!=(const Fingerprint &h) const noexcept { return !(*this == h); }
-	ZT_INLINE bool operator<(const Fingerprint &h) const noexcept { return ((m_cfp.address < h.m_cfp.address) || ((m_cfp.address == h.m_cfp.address) && (memcmp(m_cfp.hash, h.m_cfp.hash, ZT_FINGERPRINT_HASH_SIZE) < 0))); }
-	ZT_INLINE bool operator>(const Fingerprint &h) const noexcept { return (h < *this); }
-	ZT_INLINE bool operator<=(const Fingerprint &h) const noexcept { return !(h < *this); }
-	ZT_INLINE bool operator>=(const Fingerprint &h) const noexcept { return !(*this < h); }
+	ZT_INLINE bool operator!=(const Fingerprint &h) const noexcept
+	{ return !(*this == h); }
 
-private:
-	ZT_Fingerprint m_cfp;
+	ZT_INLINE bool operator<(const Fingerprint &h) const noexcept
+	{ return ((this->address < h.address) || ((this->address == h.address) && (memcmp(this->hash, h.hash, ZT_FINGERPRINT_HASH_SIZE) < 0))); }
+
+	ZT_INLINE bool operator>(const Fingerprint &h) const noexcept
+	{ return (h < *this); }
+
+	ZT_INLINE bool operator<=(const Fingerprint &h) const noexcept
+	{ return !(h < *this); }
+
+	ZT_INLINE bool operator>=(const Fingerprint &h) const noexcept
+	{ return !(*this < h); }
 };
 
+static_assert(sizeof(Fingerprint) == sizeof(ZT_Fingerprint), "size mismatch");
+
 } // namespace ZeroTier
 
 #endif

+ 23 - 32
node/Identity.cpp

@@ -157,7 +157,7 @@ bool Identity::generate(const Type t)
 				address.setTo(digest + 59);
 			} while (address.isReserved());
 			delete[] genmem;
-			m_fp.m_cfp.address = address.toInt(); // address comes from PoW hash for type 0 identities
+			m_fp.address = address; // address comes from PoW hash for type 0 identities
 			m_computeHash();
 		} break;
 
@@ -179,9 +179,11 @@ bool Identity::generate(const Type t)
 				// If we passed PoW then check that the address is valid, otherwise loop
 				// back around and run the whole process again.
 				m_computeHash();
-				m_fp.m_cfp.address = Address(m_fp.m_cfp.hash).toInt();
-				if (!m_fp.address().isReserved())
+				const Address addr(m_fp.hash);
+				if (!addr.isReserved()) {
+					m_fp.address = addr;
 					break;
+				}
 			}
 		} break;
 
@@ -195,7 +197,7 @@ bool Identity::generate(const Type t)
 bool Identity::locallyValidate() const noexcept
 {
 	try {
-		if ((m_fp) && ((!m_fp.address().isReserved()))) {
+		if ((m_fp) && ((!Address(m_fp.address).isReserved()))) {
 			switch (m_type) {
 				case C25519: {
 					uint8_t digest[64];
@@ -204,10 +206,10 @@ bool Identity::locallyValidate() const noexcept
 						return false;
 					identityV0ProofOfWorkFrankenhash(m_pub, ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, digest, genmem);
 					free(genmem);
-					return ((m_fp.address() == Address(digest + 59)) && (digest[0] < 17));
+					return ((Address(digest + 59) == m_fp.address) && (digest[0] < 17));
 				}
 				case P384: {
-					if (m_fp.address() != Address(m_fp.hash()))
+					if (Address(m_fp.hash) != m_fp.address)
 						return false;
 					return identityV1ProofOfWorkCriteria(m_pub, sizeof(m_pub));
 				}
@@ -312,7 +314,7 @@ bool Identity::agree(const Identity &id, uint8_t key[ZT_SYMMETRIC_KEY_SIZE]) con
 char *Identity::toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const
 {
 	char *p = buf;
-	m_fp.address().toString(p);
+	Address(m_fp.address).toString(p);
 	p += 10;
 	*(p++) = ':';
 
@@ -363,8 +365,8 @@ bool Identity::fromString(const char *str)
 		switch (fno++) {
 
 			case 0:
-				m_fp.m_cfp.address = Utils::hexStrToU64(f) & ZT_ADDRESS_MASK;
-				if (m_fp.address().isReserved())
+				m_fp.address = Utils::hexStrToU64(f) & ZT_ADDRESS_MASK;
+				if (Address(m_fp.address).isReserved())
 					return false;
 				break;
 
@@ -425,12 +427,12 @@ bool Identity::fromString(const char *str)
 		return false;
 
 	m_computeHash();
-	return !((m_type == P384) && (m_fp.address() != Address(m_fp.hash())));
+	return !((m_type == P384) && (Address(m_fp.hash) != m_fp.address));
 }
 
 int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX], const bool includePrivate) const noexcept
 {
-	m_fp.address().copyTo(data);
+	Address(m_fp.address).copyTo(data);
 	switch (m_type) {
 
 		case C25519:
@@ -467,7 +469,7 @@ int Identity::unmarshal(const uint8_t *data, const int len) noexcept
 
 	if (len < (1 + ZT_ADDRESS_LENGTH))
 		return -1;
-	m_fp.m_cfp.address = Address(data).toInt();
+	m_fp.address = Address(data);
 
 	unsigned int privlen;
 	switch ((m_type = (Type) data[ZT_ADDRESS_LENGTH])) {
@@ -498,7 +500,7 @@ int Identity::unmarshal(const uint8_t *data, const int len) noexcept
 
 			Utils::copy<ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE>(m_pub, data + ZT_ADDRESS_LENGTH + 1);
 			m_computeHash(); // this sets the address for P384
-			if (m_fp.address() != Address(m_fp.hash())) // this sanity check is possible with V1 identities
+			if (Address(m_fp.hash) != m_fp.address) // this sanity check is possible with V1 identities
 				return -1;
 
 			privlen = data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE];
@@ -538,12 +540,12 @@ void Identity::m_computeHash()
 
 extern "C" {
 
-ZT_Identity *ZT_Identity_new(enum ZT_Identity_Type type)
+ZT_Identity *ZT_Identity_new(enum ZT_IdentityType type)
 {
 	if ((type != ZT_IDENTITY_TYPE_C25519) && (type != ZT_IDENTITY_TYPE_P384))
 		return nullptr;
 	try {
-		ZeroTier::Identity *const id = new ZeroTier::Identity(); // NOLINT(hicpp-use-auto,modernize-use-auto)
+		ZeroTier::Identity *const id = new ZeroTier::Identity();
 		id->generate((ZeroTier::Identity::Type) type);
 		return reinterpret_cast<ZT_Identity *>(id);
 	} catch (...) {
@@ -556,7 +558,7 @@ ZT_Identity *ZT_Identity_fromString(const char *idStr)
 	if (!idStr)
 		return nullptr;
 	try {
-		ZeroTier::Identity *const id = new ZeroTier::Identity(); // NOLINT(hicpp-use-auto,modernize-use-auto)
+		ZeroTier::Identity *const id = new ZeroTier::Identity();
 		if (!id->fromString(idStr)) {
 			delete id;
 			return nullptr;
@@ -590,11 +592,11 @@ int ZT_Identity_verify(const ZT_Identity *id, const void *data, unsigned int len
 	return reinterpret_cast<const ZeroTier::Identity *>(id)->verify(data, len, signature, sigLen) ? 1 : 0;
 }
 
-enum ZT_Identity_Type ZT_Identity_type(const ZT_Identity *id)
+enum ZT_IdentityType ZT_Identity_type(const ZT_Identity *id)
 {
 	if (!id)
-		return (ZT_Identity_Type) 0;
-	return (enum ZT_Identity_Type) reinterpret_cast<const ZeroTier::Identity *>(id)->type();
+		return (ZT_IdentityType) 0;
+	return (enum ZT_IdentityType) reinterpret_cast<const ZeroTier::Identity *>(id)->type();
 }
 
 char *ZT_Identity_toString(const ZT_Identity *id, char *buf, int capacity, int includePrivate)
@@ -616,25 +618,14 @@ uint64_t ZT_Identity_address(const ZT_Identity *id)
 {
 	if (!id)
 		return 0;
-	return reinterpret_cast<const ZeroTier::Identity *>(id)->address().toInt();
+	return reinterpret_cast<const ZeroTier::Identity *>(id)->address();
 }
 
 const ZT_Fingerprint *ZT_Identity_fingerprint(const ZT_Identity *id)
 {
 	if (!id)
 		return nullptr;
-	return reinterpret_cast<const ZeroTier::Identity *>(id)->fingerprint().apiFingerprint();
-}
-
-int ZT_Identity_makeRootSpecification(ZT_Identity *id,int64_t ts,struct sockaddr_storage *addrs,unsigned int addrcnt,void *rootSpecBuf,unsigned int rootSpecBufSize)
-{
-	if ((!id)||(!addrs)||(!addrcnt)||(!rootSpecBuf))
-		return -1;
-	ZeroTier::Vector<ZeroTier::Endpoint> endpoints;
-	endpoints.reserve(addrcnt);
-	for(unsigned int i=0;i<addrcnt;++i)
-		endpoints.push_back(ZeroTier::Endpoint(ZeroTier::asInetAddress(addrs[i])));
-	return ZeroTier::Locator::makeRootSpecification(*reinterpret_cast<const ZeroTier::Identity *>(id),ts,endpoints,rootSpecBuf,rootSpecBufSize);
+	return &(reinterpret_cast<const ZeroTier::Identity *>(id)->fingerprint());
 }
 
 ZT_SDK_API void ZT_Identity_delete(ZT_Identity *id)

+ 4 - 4
node/Identity.hpp

@@ -53,8 +53,8 @@ public:
 	 */
 	enum Type
 	{
-		C25519 = ZT_CRYPTO_ALG_C25519, // Type 0 -- Curve25519 and Ed25519 (1.x and 2.x, default)
-		P384 = ZT_CRYPTO_ALG_P384      // Type 1 -- NIST P-384 with linked Curve25519/Ed25519 secondaries (2.x+)
+		C25519 = ZT_IDENTITY_TYPE_C25519, // Type 0 -- Curve25519 and Ed25519 (1.x and 2.x, default)
+		P384 = ZT_IDENTITY_TYPE_P384      // Type 1 -- NIST P-384 with linked Curve25519/Ed25519 secondaries (2.x+)
 	};
 
 	/**
@@ -62,7 +62,7 @@ public:
 	 */
 	static const Identity NIL;
 
-	ZT_INLINE Identity() noexcept // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
+	ZT_INLINE Identity() noexcept
 	{
 		Utils::memoryLock(this,sizeof(Identity));
 		memoryZero(this);
@@ -76,7 +76,7 @@ public:
 	 *
 	 * @param str Identity in canonical string format
 	 */
-	explicit ZT_INLINE Identity(const char *str) // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
+	explicit ZT_INLINE Identity(const char *str)
 	{
 		Utils::memoryLock(this,sizeof(Identity));
 		fromString(str);

+ 65 - 111
node/Locator.cpp

@@ -13,145 +13,99 @@
 
 #include "Locator.hpp"
 
+#include <algorithm>
+
 namespace ZeroTier {
 
+bool Locator::add(const Endpoint &ep)
+{
+	if (m_endpoints.size() < ZT_LOCATOR_MAX_ENDPOINTS) {
+		if (std::find(m_endpoints.begin(), m_endpoints.end(), ep) == m_endpoints.end())
+			m_endpoints.push_back(ep);
+		return true;
+	}
+	return false;
+}
+
 bool Locator::sign(const int64_t ts, const Identity &id) noexcept
 {
-	uint8_t signData[ZT_LOCATOR_MARSHAL_SIZE_MAX];
-	if (!id.hasPrivate())
-		return false;
 	m_ts = ts;
-	if (m_endpointCount > 0)
-		std::sort(m_at, m_at + m_endpointCount);
-	const unsigned int signLen = marshal(signData, true);
-	m_signatureLength = id.sign(signData, signLen, m_signature, sizeof(m_signature));
-	return (m_signatureLength > 0);
+	std::sort(m_endpoints.begin(), m_endpoints.end());
+
+	uint8_t signdata[ZT_LOCATOR_MARSHAL_SIZE_MAX];
+	const unsigned int signlen = marshal(signdata, true);
+
+	const unsigned int siglen = id.sign(signdata, signlen, m_signature.data(), m_signature.capacity());
+	if (siglen == 0)
+		return false;
+	m_signature.unsafeSetSize(siglen);
+
+	return true;
 }
 
 bool Locator::verify(const Identity &id) const noexcept
 {
-	if ((m_ts == 0) || (m_endpointCount > ZT_LOCATOR_MAX_ENDPOINTS) || (m_signatureLength > ZT_SIGNATURE_BUFFER_SIZE))
+	if (m_ts <= 0)
 		return false;
-	uint8_t signData[ZT_LOCATOR_MARSHAL_SIZE_MAX];
-	const unsigned int signLen = marshal(signData, true);
-	return id.verify(signData, signLen, m_signature, m_signatureLength);
+	uint8_t signdata[ZT_LOCATOR_MARSHAL_SIZE_MAX];
+	const unsigned int signlen = marshal(signdata, true);
+	return id.verify(signdata, signlen, m_signature.data(), m_signature.size());
 }
 
 int Locator::marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX], const bool excludeSignature) const noexcept
 {
-	if ((m_endpointCount > ZT_LOCATOR_MAX_ENDPOINTS) || (m_signatureLength > ZT_SIGNATURE_BUFFER_SIZE))
-		return -1;
-
-	data[0] = 0xff; // version byte, currently 0xff to never be the same as byte 0 of an identity for legacy compatibility reasons
-	Utils::storeBigEndian<int64_t>(data + 1, m_ts);
-	int p = 9;
-
-	if (m_ts > 0) {
-		Utils::storeBigEndian(data + p, (uint16_t) m_endpointCount);
-		p += 2;
-		for (unsigned int i = 0;i < m_endpointCount;++i) {
-			int tmp = m_at[i].marshal(data + p);
-			if (tmp < 0)
-				return -1;
-			p += tmp;
-		}
-
-		if (!excludeSignature) {
-			Utils::storeBigEndian(data + p, (uint16_t) m_signatureLength);
-			p += 2;
-			Utils::copy(data + p, m_signature, m_signatureLength);
-			p += (int) m_signatureLength;
-		}
-
-		Utils::storeBigEndian(data + p, m_flags);
+	Utils::storeBigEndian<uint64_t>(data, (uint64_t) m_ts);
+	Utils::storeBigEndian<uint16_t>(data + 8, (uint16_t) m_endpoints.size());
+	int p = 10;
+	for (Vector<Endpoint>::const_iterator e(m_endpoints.begin());e != m_endpoints.end();++e) {
+		int l = e->marshal(data + p);
+		if (l <= 0)
+			return -1;
+		p += l;
+	}
+	Utils::storeAsIsEndian<uint16_t>(data + p, 0); // length of meta-data, currently always 0
+	p += 2;
+	if (!excludeSignature) {
+		Utils::storeBigEndian<uint16_t>(data + p, (uint16_t) m_signature.size());
 		p += 2;
+		Utils::copy(data + p, m_signature.data(), m_signature.size());
+		p += (int) m_signature.size();
 	}
-
 	return p;
 }
 
-int Locator::unmarshal(const uint8_t *restrict data, const int len) noexcept
+int Locator::unmarshal(const uint8_t *data, const int len) noexcept
 {
-	if (len <= (1 + 8 + 2 + 48))
+	if (unlikely(len < 10))
 		return -1;
-
-	if (data[0] != 0xff)
+	m_ts = (int64_t) Utils::loadBigEndian<uint64_t>(data);
+	unsigned int endpointCount = Utils::loadBigEndian<uint16_t>(data + 8);
+	if (unlikely(endpointCount > ZT_LOCATOR_MAX_ENDPOINTS))
 		return -1;
-	m_ts = Utils::loadBigEndian<int64_t>(data + 1);
-	int p = 9;
-
-	if (m_ts > 0) {
-		const unsigned int ec = Utils::loadBigEndian<uint16_t>(data + p);
-		p += 2;
-		if (ec > ZT_LOCATOR_MAX_ENDPOINTS)
+	int p = 10;
+	m_endpoints.resize(endpointCount);
+	m_endpoints.shrink_to_fit();
+	for (unsigned int i = 0;i < endpointCount;++i) {
+		int l = m_endpoints[i].unmarshal(data + p, len - p);
+		if (l <= 0)
 			return -1;
-		m_endpointCount = ec;
-		for (unsigned int i = 0;i < ec;++i) {
-			int tmp = m_at[i].unmarshal(data + p, len - p);
-			if (tmp < 0)
-				return -1;
-			p += tmp;
-		}
-
-		if ((p + 2) > len)
-			return -1;
-		const unsigned int sl = Utils::loadBigEndian<uint16_t>(data + p);
-		p += 2;
-		if (sl > ZT_SIGNATURE_BUFFER_SIZE)
-			return -1;
-		m_signatureLength = sl;
-		if ((p + (int)sl) > len)
-			return -1;
-		Utils::copy(m_signature, data + p, sl);
-		p += (int)sl;
-
-		if ((p + 2) > len)
-			return -1;
-		m_flags = Utils::loadBigEndian<uint16_t>(data + p);
-		p += 2;
-	} else {
-		m_ts = 0;
+		p += l;
 	}
-
-	return p;
-}
-
-int Locator::makeRootSpecification(const Identity &id,int64_t ts,const Vector<Endpoint> &endpoints,void *rootSpecBuf,unsigned int rootSpecBufSize)
-{
-	if (endpoints.size() > ZT_LOCATOR_MAX_ENDPOINTS)
+	if (unlikely((p + 2) > len))
 		return -1;
-	if (rootSpecBufSize < (ZT_IDENTITY_MARSHAL_SIZE_MAX + ZT_LOCATOR_MARSHAL_SIZE_MAX + 1))
+	p += 2 + (int) Utils::loadBigEndian<uint16_t>(data + p);
+	if (unlikely((p + 2) > len))
 		return -1;
-
-	Locator loc;
-	for (Vector<Endpoint>::const_iterator e(endpoints.begin());e!=endpoints.end();++e)
-		loc.add(*e);
-	if (!loc.sign(ts,id))
-		return -1;
-
-	uint8_t *buf = reinterpret_cast<uint8_t *>(rootSpecBuf);
-	int idl = id.marshal(buf,false);
-	if (idl <= 0)
+	unsigned int siglen = Utils::loadBigEndian<uint16_t>(data + p);
+	p += 2;
+	if (unlikely((siglen > ZT_SIGNATURE_BUFFER_SIZE) || ((p + (int) siglen) > len)))
 		return -1;
-	buf += idl;
-	int locl = loc.marshal(buf);
-	if (locl <= 0)
+	m_signature.unsafeSetSize(siglen);
+	Utils::copy(m_signature.data(), data + p, siglen);
+	p += siglen;
+	if (unlikely(p > len))
 		return -1;
-	return idl + locl;
-}
-
-std::pair<Identity,Locator> Locator::parseRootSpecification(const void *rootSpec,unsigned int rootSpecSize)
-{
-	std::pair<Identity,Locator> rs;
-	int l = rs.first.unmarshal(reinterpret_cast<const uint8_t *>(rootSpec),(int)rootSpecSize);
-	if (l <= 0) {
-		rs.first.zero();
-		return rs;
-	}
-	l = rs.second.unmarshal(reinterpret_cast<const uint8_t *>(rootSpec) + l,(int)rootSpecSize - l);
-	if (l <= 0)
-		rs.first.zero();
-	return rs;
+	return p;
 }
 
 } // namespace ZeroTier

+ 35 - 67
node/Locator.hpp

@@ -14,17 +14,16 @@
 #ifndef ZT_LOCATOR_HPP
 #define ZT_LOCATOR_HPP
 
-#include <algorithm>
-#include <vector>
-#include <cstdint>
-
 #include "Constants.hpp"
 #include "Endpoint.hpp"
 #include "Identity.hpp"
 #include "TriviallyCopyable.hpp"
+#include "SharedPtr.hpp"
+#include "FCV.hpp"
+#include "Containers.hpp"
 
 #define ZT_LOCATOR_MAX_ENDPOINTS 8
-#define ZT_LOCATOR_MARSHAL_SIZE_MAX (1 + 8 + 2 + (ZT_ENDPOINT_MARSHAL_SIZE_MAX * ZT_LOCATOR_MAX_ENDPOINTS) + 2 + 2 + ZT_SIGNATURE_BUFFER_SIZE)
+#define ZT_LOCATOR_MARSHAL_SIZE_MAX (8 + 2 + (ZT_LOCATOR_MAX_ENDPOINTS * ZT_ENDPOINT_MARSHAL_SIZE_MAX) + 2 + 2 + ZT_SIGNATURE_BUFFER_SIZE)
 
 namespace ZeroTier {
 
@@ -34,45 +33,39 @@ namespace ZeroTier {
  * A locator contains long-lived endpoints for a node such as IP/port pairs,
  * URLs, or other nodes, and is signed by the node it describes.
  */
-class Locator : public TriviallyCopyable
+class Locator
 {
+	friend class SharedPtr<Locator>;
+
 public:
-	ZT_INLINE Locator() noexcept { memoryZero(this); }
+	ZT_INLINE Locator() noexcept :
+		m_ts(0)
+	{}
 
-	/**
-	 * Zero the Locator data structure
-	 */
-	ZT_INLINE void clear() noexcept { memoryZero(this); }
+	ZT_INLINE Locator(const Locator &loc) noexcept :
+		m_ts(loc.m_ts),
+		m_endpoints(loc.m_endpoints),
+		m_signature(loc.m_signature),
+		__refCount(0)
+	{}
 
 	/**
 	 * @return Timestamp (a.k.a. revision number) set by Location signer
 	 */
-	ZT_INLINE int64_t timestamp() const noexcept { return m_ts; }
-
-	/**
-	 * @return True if locator is signed
-	 */
-	ZT_INLINE bool isSigned() const noexcept { return m_signatureLength > 0; }
-
-	/**
-	 * @return Length of signature in bytes or 0 if none
-	 */
-	ZT_INLINE unsigned int signatureLength() const noexcept { return m_signatureLength; }
+	ZT_INLINE int64_t timestamp() const noexcept
+	{ return m_ts; }
 
 	/**
-	 * @return Pointer to signature bytes
+	 * @return Endpoints specified in locator
 	 */
-	ZT_INLINE const uint8_t *signature() const noexcept { return m_signature; }
+	ZT_INLINE const Vector<Endpoint> &endpoints() const noexcept
+	{ return m_endpoints; }
 
 	/**
-	 * @return Number of endpoints in this locator
+	 * @return Signature data
 	 */
-	ZT_INLINE unsigned int endpointCount() const noexcept { return m_endpointCount; }
-
-	/**
-	 * @return Pointer to array of endpoints
-	 */
-	ZT_INLINE const Endpoint *endpoints() const noexcept { return m_at; }
+	ZT_INLINE const FCV<uint8_t, ZT_SIGNATURE_BUFFER_SIZE> &signature() const noexcept
+	{ return m_signature; }
 
 	/**
 	 * Add an endpoint to this locator
@@ -83,13 +76,7 @@ public:
 	 * @param ep Endpoint to add
 	 * @return True if endpoint was added (or already present), false if locator is full
 	 */
-	ZT_INLINE bool add(const Endpoint &ep) noexcept
-	{
-		if (m_endpointCount >= ZT_LOCATOR_MAX_ENDPOINTS)
-			return false;
-		m_at[m_endpointCount++] = ep;
-		return true;
-	}
+	bool add(const Endpoint &ep);
 
 	/**
 	 * Sign this locator
@@ -100,7 +87,7 @@ public:
 	 * @param id Identity that includes private key
 	 * @return True if signature successful
 	 */
-	bool sign(int64_t ts,const Identity &id) noexcept;
+	bool sign(int64_t ts, const Identity &id) noexcept;
 
 	/**
 	 * Verify this Locator's validity and signature
@@ -110,40 +97,21 @@ public:
 	 */
 	bool verify(const Identity &id) const noexcept;
 
-	explicit ZT_INLINE operator bool() const noexcept { return m_ts != 0; }
+	explicit ZT_INLINE operator bool() const noexcept
+	{ return m_ts > 0; }
 
-	static constexpr int marshalSizeMax() noexcept { return ZT_LOCATOR_MARSHAL_SIZE_MAX; }
-	int marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX],bool excludeSignature = false) const noexcept;
-	int unmarshal(const uint8_t *restrict data,int len) noexcept;
+	static constexpr int marshalSizeMax() noexcept
+	{ return ZT_LOCATOR_MARSHAL_SIZE_MAX; }
 
-	/**
-	 * Create a signed Locator and package it with the root's identity to make a root spec
-	 *
-	 * @param id Identity (must have secret)
-	 * @param ts Timestamp
-	 * @param endpoints Endpoints
-	 * @param rootSpecBuf Buffer to store identity and locator into
-	 * @param rootSpecBufSize Size of buffer
-	 * @return Bytes written to buffer or -1 on error
-	 */
-	static int makeRootSpecification(const Identity &id,int64_t ts,const Vector<Endpoint> &endpoints,void *rootSpecBuf,unsigned int rootSpecBufSize);
+	int marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX], bool excludeSignature = false) const noexcept;
 
-	/**
-	 * Parse a root specification and decode the identity and locator
-	 *
-	 * @param rootSpec Root spec bytes
-	 * @param rootSpecSize Size in bytes
-	 * @return Identity and locator, with identity NULL if an error occurs
-	 */
-	static std::pair<Identity,Locator> parseRootSpecification(const void *rootSpec,unsigned int rootSpecSize);
+	int unmarshal(const uint8_t *data, int len) noexcept;
 
 private:
 	int64_t m_ts;
-	unsigned int m_endpointCount;
-	unsigned int m_signatureLength;
-	Endpoint m_at[ZT_LOCATOR_MAX_ENDPOINTS];
-	uint16_t m_flags;
-	uint8_t m_signature[ZT_SIGNATURE_BUFFER_SIZE];
+	Vector<Endpoint> m_endpoints;
+	FCV<uint8_t, ZT_SIGNATURE_BUFFER_SIZE> m_signature;
+	std::atomic<int> __refCount;
 };
 
 } // namespace ZeroTier

+ 58 - 14
node/MAC.hpp

@@ -14,14 +14,11 @@
 #ifndef ZT_MAC_HPP
 #define ZT_MAC_HPP
 
-#include <cstdio>
-#include <cstdlib>
-#include <cstdint>
-
 #include "Constants.hpp"
 #include "Utils.hpp"
 #include "Address.hpp"
 #include "TriviallyCopyable.hpp"
+#include "Containers.hpp"
 
 namespace ZeroTier {
 
@@ -32,10 +29,20 @@ class MAC : public TriviallyCopyable
 {
 public:
 	ZT_INLINE MAC() noexcept : m_mac(0ULL) {}
-	ZT_INLINE MAC(const uint8_t a,const uint8_t b,const uint8_t c,const uint8_t d,const uint8_t e,const uint8_t f) noexcept : m_mac((((uint64_t)a) << 40U) | (((uint64_t)b) << 32U) | (((uint64_t)c) << 24U) | (((uint64_t)d) << 16U) | (((uint64_t)e) << 8U) | ((uint64_t)f) ) {}
-	explicit ZT_INLINE MAC(const uint64_t m) noexcept : m_mac(m) {}
-	explicit ZT_INLINE MAC(const uint8_t b[6]) noexcept { setTo(b); } // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
-	ZT_INLINE MAC(const Address &ztaddr,const uint64_t nwid) noexcept { fromAddress(ztaddr,nwid); } // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
+
+	ZT_INLINE MAC(const uint8_t a,const uint8_t b,const uint8_t c,const uint8_t d,const uint8_t e,const uint8_t f) noexcept :
+		m_mac((((uint64_t)a) << 40U) | (((uint64_t)b) << 32U) | (((uint64_t)c) << 24U) | (((uint64_t)d) << 16U) | (((uint64_t)e) << 8U) | ((uint64_t)f) )
+	{}
+
+	explicit ZT_INLINE MAC(const uint64_t m) noexcept :
+		m_mac(m)
+	{}
+
+	explicit ZT_INLINE MAC(const uint8_t b[6]) noexcept
+	{ setTo(b); }
+
+	ZT_INLINE MAC(const Address &ztaddr,const uint64_t nwid) noexcept
+	{ fromAddress(ztaddr,nwid); }
 
 	/**
 	 * @return MAC in 64-bit integer
@@ -47,11 +54,6 @@ public:
 	 */
 	ZT_INLINE void zero() noexcept { m_mac = 0ULL; }
 
-	/**
-	 * @return True if MAC is non-zero
-	 */
-	ZT_INLINE operator bool() const noexcept { return (m_mac != 0ULL); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
-
 	/**
 	 * @param bits Raw MAC in big-endian byte order
 	 * @param len Length, must be >= 6 or result is zero
@@ -135,7 +137,7 @@ public:
 	 * @param i Value from 0 to 5 (inclusive)
 	 * @return Byte at said position (address interpreted in big-endian order)
 	 */
-	ZT_INLINE uint8_t operator[](unsigned int i) const noexcept { return (uint8_t)(m_mac >> (40 - (i * 8))); }
+	ZT_INLINE uint8_t operator[](unsigned int i) const noexcept { return (uint8_t)(m_mac >> (unsigned int)(40 - (i * 8))); }
 
 	/**
 	 * @return 6, which is the number of bytes in a MAC, for container compliance
@@ -144,6 +146,15 @@ public:
 
 	ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)Utils::hash64(m_mac); }
 
+	ZT_INLINE operator bool() const noexcept { return (m_mac != 0ULL); }
+	ZT_INLINE operator uint64_t() const noexcept { return m_mac; }
+
+	/**
+	 * Convert this MAC to a standard format colon-separated hex string
+	 *
+	 * @param buf Buffer to store string
+	 * @return Pointer to buf
+	 */
 	ZT_INLINE char *toString(char buf[18]) const noexcept
 	{
 		buf[0] = Utils::HEXCHARS[(m_mac >> 44U) & 0xfU];
@@ -166,6 +177,32 @@ public:
 		buf[17] = (char)0;
 		return buf;
 	}
+	ZT_INLINE String toString() const { char tmp[18]; return String(toString(tmp)); }
+
+	/**
+	 * Parse a MAC address in hex format with or without : separators and ignoring non-hex characters.
+	 *
+	 * @param s String to parse
+	 */
+	ZT_INLINE void fromString(const char *s) noexcept
+	{
+		m_mac = 0;
+		if (s) {
+			while (*s) {
+				uint64_t c;
+				const char hc = *s++;
+				if ((hc >= 48)&&(hc <= 57))
+					c = (uint64_t)hc - 48;
+				else if ((hc >= 97)&&(hc <= 102))
+					c = (uint64_t)hc - 87;
+				else if ((hc >= 65)&&(hc <= 70))
+					c = (uint64_t)hc - 55;
+				else continue;
+				m_mac = (m_mac << 4U) | c;
+			}
+			m_mac &= 0xffffffffffffULL;
+		}
+	}
 
 	ZT_INLINE MAC &operator=(const uint64_t m) noexcept { m_mac = m; return *this; }
 
@@ -176,6 +213,13 @@ public:
 	ZT_INLINE bool operator>(const MAC &m) const noexcept { return (m_mac > m.m_mac); }
 	ZT_INLINE bool operator>=(const MAC &m) const noexcept { return (m_mac >= m.m_mac); }
 
+	ZT_INLINE bool operator==(const uint64_t m) const noexcept { return (m_mac == m); }
+	ZT_INLINE bool operator!=(const uint64_t m) const noexcept { return (m_mac != m); }
+	ZT_INLINE bool operator<(const uint64_t m) const noexcept { return (m_mac < m); }
+	ZT_INLINE bool operator<=(const uint64_t m) const noexcept { return (m_mac <= m); }
+	ZT_INLINE bool operator>(const uint64_t m) const noexcept { return (m_mac > m); }
+	ZT_INLINE bool operator>=(const uint64_t m) const noexcept { return (m_mac >= m); }
+
 private:
 	uint64_t m_mac;
 };

+ 1 - 1
node/Membership.hpp

@@ -132,7 +132,7 @@ public:
 					return false;
 			} else {
 				// LEGACY: support networks run by old controllers.
-				if (localCom.issuedTo().address() != m_com.issuedTo().address())
+				if (localCom.issuedTo().address != m_com.issuedTo().address)
 					return false;
 			}
 

+ 0 - 2
node/MulticastGroup.hpp

@@ -20,8 +20,6 @@
 #include "Utils.hpp"
 #include "TriviallyCopyable.hpp"
 
-#include <cstdint>
-
 namespace ZeroTier {
 
 /**

+ 1 - 1
node/Network.cpp

@@ -1022,7 +1022,7 @@ int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToD
 	try {
 		if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != m_id))
 			return 0; // invalid config that is not for us or not for this network
-		if ((!Utils::allZero(nconf.issuedToFingerprintHash,ZT_FINGERPRINT_HASH_SIZE)) && (memcmp(nconf.issuedToFingerprintHash,RR->identity.fingerprint().hash(),ZT_FINGERPRINT_HASH_SIZE) != 0))
+		if ((!Utils::allZero(nconf.issuedToFingerprintHash,ZT_FINGERPRINT_HASH_SIZE)) && (memcmp(nconf.issuedToFingerprintHash,RR->identity.fingerprint().hash,ZT_FINGERPRINT_HASH_SIZE) != 0))
 			return 0; // full identity hash is present and does not match
 
 		if (m_config == nconf)

+ 22 - 23
node/NetworkConfig.hpp

@@ -70,16 +70,6 @@ namespace ZeroTier {
  */
 #define ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE 0x0000020000000000ULL
 
-/**
- * Device that replicates multicasts
- */
-#define ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR 0x0000040000000000ULL
-
-/**
- * Device that is allowed to remotely debug this network and query other peers for e.g. remote trace data
- */
-#define ZT_NETWORKCONFIG_SPECIALIST_TYPE_DIAGNOSTICIAN 0x0000080000000000ULL
-
 // Fields for meta-data sent with network config requests
 
 // Protocol version (see Packet.hpp)
@@ -156,7 +146,8 @@ namespace ZeroTier {
  */
 struct NetworkConfig : TriviallyCopyable
 {
-	ZT_INLINE NetworkConfig() noexcept { memoryZero(this); } // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
+	ZT_INLINE NetworkConfig() noexcept
+	{ memoryZero(this); } // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
 
 	/**
 	 * Write this network config to a dictionary for transport
@@ -177,22 +168,26 @@ struct NetworkConfig : TriviallyCopyable
 	/**
 	 * @return True if broadcast (ff:ff:ff:ff:ff:ff) address should work on this network
 	 */
-	ZT_INLINE bool enableBroadcast() const noexcept { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0); }
+	ZT_INLINE bool enableBroadcast() const noexcept
+	{ return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0); }
 
 	/**
 	 * @return True if IPv6 NDP emulation should be allowed for certain "magic" IPv6 address patterns
 	 */
-	ZT_INLINE bool ndpEmulation() const noexcept { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0); }
+	ZT_INLINE bool ndpEmulation() const noexcept
+	{ return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0); }
 
 	/**
 	 * @return Network type is public (no access control)
 	 */
-	ZT_INLINE bool isPublic() const noexcept { return (this->type == ZT_NETWORK_TYPE_PUBLIC); }
+	ZT_INLINE bool isPublic() const noexcept
+	{ return (this->type == ZT_NETWORK_TYPE_PUBLIC); }
 
 	/**
 	 * @return Network type is private (certificate access control)
 	 */
-	ZT_INLINE bool isPrivate() const noexcept { return (this->type == ZT_NETWORK_TYPE_PRIVATE); }
+	ZT_INLINE bool isPrivate() const noexcept
+	{ return (this->type == ZT_NETWORK_TYPE_PRIVATE); }
 
 	/**
 	 * @param fromPeer Peer attempting to bridge other Ethernet peers onto network
@@ -200,16 +195,20 @@ struct NetworkConfig : TriviallyCopyable
 	 */
 	ZT_INLINE bool permitsBridging(const Address &fromPeer) const noexcept
 	{
-		for(unsigned int i=0;i<specialistCount;++i) {
-			if ((fromPeer.toInt() == (specialists[i] & ZT_ADDRESS_MASK))&&((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0))
+		for (unsigned int i = 0;i < specialistCount;++i) {
+			if ((fromPeer.toInt() == (specialists[i] & ZT_ADDRESS_MASK)) && ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0))
 				return true;
 		}
 		return false;
 	}
 
-	ZT_INLINE operator bool() const noexcept { return (networkId != 0); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
-	ZT_INLINE bool operator==(const NetworkConfig &nc) const noexcept { return (memcmp(this,&nc,sizeof(NetworkConfig)) == 0); }
-	ZT_INLINE bool operator!=(const NetworkConfig &nc) const noexcept { return (!(*this == nc)); }
+	ZT_INLINE operator bool() const noexcept
+	{ return (networkId != 0); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
+	ZT_INLINE bool operator==(const NetworkConfig &nc) const noexcept
+	{ return (memcmp(this, &nc, sizeof(NetworkConfig)) == 0); }
+
+	ZT_INLINE bool operator!=(const NetworkConfig &nc) const noexcept
+	{ return (!(*this == nc)); }
 
 	/**
 	 * Add a specialist or mask flags if already present
@@ -221,11 +220,11 @@ struct NetworkConfig : TriviallyCopyable
 	 * @param f Flags (OR of specialist role/type flags)
 	 * @return True if successfully masked or added
 	 */
-	bool addSpecialist(const Address &a,uint64_t f) noexcept;
+	bool addSpecialist(const Address &a, uint64_t f) noexcept;
 
 	ZT_INLINE const Capability *capability(const uint32_t id) const
 	{
-		for(unsigned int i=0;i<capabilityCount;++i) {
+		for (unsigned int i = 0;i < capabilityCount;++i) {
 			if (capabilities[i].id() == id)
 				return &(capabilities[i]);
 		}
@@ -234,7 +233,7 @@ struct NetworkConfig : TriviallyCopyable
 
 	ZT_INLINE const Tag *tag(const uint32_t id) const
 	{
-		for(unsigned int i=0;i<tagCount;++i) {
+		for (unsigned int i = 0;i < tagCount;++i) {
 			if (tags[i].id() == id)
 				return &(tags[i]);
 		}

+ 207 - 187
node/Node.cpp

@@ -34,13 +34,13 @@ namespace {
 // Structure containing all the core objects for a ZeroTier node to reduce memory allocations.
 struct _NodeObjects
 {
-	ZT_INLINE _NodeObjects(RuntimeEnvironment *const RR,void *const tPtr) :
+	ZT_INLINE _NodeObjects(RuntimeEnvironment *const RR, void *const tPtr) :
 		t(RR),
 		expect(),
 		vl2(RR),
 		vl1(RR),
 		sa(RR),
-		topology(RR,tPtr)
+		topology(RR, tPtr)
 	{
 		RR->t = &t;
 		RR->expect = &expect;
@@ -49,6 +49,7 @@ struct _NodeObjects
 		RR->sa = &sa;
 		RR->topology = &topology;
 	}
+
 	Trace t;
 	Expect expect;
 	VL2 vl2;
@@ -59,12 +60,13 @@ struct _NodeObjects
 
 struct _sortPeerPtrsByAddress
 {
-	ZT_INLINE bool operator()(const SharedPtr<Peer> &a,const SharedPtr<Peer> &b) const { return (a->address() < b->address()); }
+	ZT_INLINE bool operator()(const SharedPtr<Peer> &a, const SharedPtr<Peer> &b) const
+	{ return (a->address() < b->address()); }
 };
 
 } // anonymous namespace
 
-Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64_t now) :
+Node::Node(void *uPtr, void *tPtr, const struct ZT_Node_Callbacks *callbacks, int64_t now) :
 	m_RR(this),
 	RR(&m_RR),
 	m_objects(nullptr),
@@ -79,14 +81,16 @@ Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64
 	m_online(false)
 {
 	// Load this node's identity.
-	uint64_t idtmp[2]; idtmp[0] = 0; idtmp[1] = 0;
-	Vector<uint8_t> data(stateObjectGet(tPtr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp));
+	uint64_t idtmp[2];
+	idtmp[0] = 0;
+	idtmp[1] = 0;
+	Vector<uint8_t> data(stateObjectGet(tPtr, ZT_STATE_OBJECT_IDENTITY_SECRET, idtmp));
 	bool haveIdentity = false;
 	if (!data.empty()) {
 		data.push_back(0); // zero-terminate string
-		if (RR->identity.fromString((const char *)data.data())) {
-			RR->identity.toString(false,RR->publicIdentityStr);
-			RR->identity.toString(true,RR->secretIdentityStr);
+		if (RR->identity.fromString((const char *) data.data())) {
+			RR->identity.toString(false, RR->publicIdentityStr);
+			RR->identity.toString(true, RR->secretIdentityStr);
 			haveIdentity = true;
 		}
 	}
@@ -94,16 +98,18 @@ Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64
 	// Generate a new identity if we don't have one.
 	if (!haveIdentity) {
 		RR->identity.generate(Identity::C25519);
-		RR->identity.toString(false,RR->publicIdentityStr);
-		RR->identity.toString(true,RR->secretIdentityStr);
-		idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0;
-		stateObjectPut(tPtr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,RR->secretIdentityStr,(unsigned int)strlen(RR->secretIdentityStr));
-		stateObjectPut(tPtr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr));
+		RR->identity.toString(false, RR->publicIdentityStr);
+		RR->identity.toString(true, RR->secretIdentityStr);
+		idtmp[0] = RR->identity.address();
+		idtmp[1] = 0;
+		stateObjectPut(tPtr, ZT_STATE_OBJECT_IDENTITY_SECRET, idtmp, RR->secretIdentityStr, (unsigned int) strlen(RR->secretIdentityStr));
+		stateObjectPut(tPtr, ZT_STATE_OBJECT_IDENTITY_PUBLIC, idtmp, RR->publicIdentityStr, (unsigned int) strlen(RR->publicIdentityStr));
 	} else {
-		idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0;
-		data = stateObjectGet(tPtr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp);
-		if ((data.empty())||(memcmp(data.data(),RR->publicIdentityStr,strlen(RR->publicIdentityStr)) != 0))
-			stateObjectPut(tPtr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr));
+		idtmp[0] = RR->identity.address();
+		idtmp[1] = 0;
+		data = stateObjectGet(tPtr, ZT_STATE_OBJECT_IDENTITY_PUBLIC, idtmp);
+		if ((data.empty()) || (memcmp(data.data(), RR->publicIdentityStr, strlen(RR->publicIdentityStr)) != 0))
+			stateObjectPut(tPtr, ZT_STATE_OBJECT_IDENTITY_PUBLIC, idtmp, RR->publicIdentityStr, (unsigned int) strlen(RR->publicIdentityStr));
 	}
 
 	uint8_t tmph[ZT_SHA384_DIGEST_SIZE];
@@ -126,7 +132,7 @@ Node::~Node()
 	m_networks_l.unlock();
 
 	if (m_objects)
-		delete (_NodeObjects *)m_objects;
+		delete (_NodeObjects *) m_objects;
 
 	// Let go of cached Buf objects. If other nodes happen to be running in this
 	// same process space new Bufs will be allocated as needed, but this is almost
@@ -137,6 +143,7 @@ Node::~Node()
 
 void Node::shutdown(void *tPtr)
 {
+	postEvent(tPtr, ZT_EVENT_DOWN);
 	if (RR->topology)
 		RR->topology->saveAll(tPtr);
 }
@@ -151,7 +158,7 @@ ZT_ResultCode Node::processWirePacket(
 	volatile int64_t *nextBackgroundTaskDeadline)
 {
 	m_now = now;
-	RR->vl1->onRemotePacket(tPtr,localSocket,(remoteAddress) ? InetAddress::NIL : *asInetAddress(remoteAddress),packetData,packetLength);
+	RR->vl1->onRemotePacket(tPtr, localSocket, (remoteAddress) ? InetAddress::NIL : *asInetAddress(remoteAddress), packetData, packetLength);
 	return ZT_RESULT_OK;
 }
 
@@ -170,7 +177,7 @@ ZT_ResultCode Node::processVirtualNetworkFrame(
 	m_now = now;
 	SharedPtr<Network> nw(this->network(nwid));
 	if (nw) {
-		RR->vl2->onLocalEthernet(tPtr,nw,MAC(sourceMac),MAC(destMac),etherType,vlanId,frameData,frameLength);
+		RR->vl2->onLocalEthernet(tPtr, nw, MAC(sourceMac), MAC(destMac), etherType, vlanId, frameData, frameLength);
 		return ZT_RESULT_OK;
 	} else {
 		return ZT_RESULT_ERROR_NETWORK_NOT_FOUND;
@@ -179,20 +186,22 @@ ZT_ResultCode Node::processVirtualNetworkFrame(
 
 struct _processBackgroundTasks_eachPeer
 {
-	ZT_INLINE _processBackgroundTasks_eachPeer(const int64_t now_,Node *const parent_,void *const tPtr_) noexcept :
+	ZT_INLINE _processBackgroundTasks_eachPeer(const int64_t now_, Node *const parent_, void *const tPtr_) noexcept:
 		now(now_),
 		parent(parent_),
 		tPtr(tPtr_),
 		online(false),
-		rootsNotOnline() {}
+		rootsNotOnline()
+	{}
+
 	const int64_t now;
 	Node *const parent;
 	void *const tPtr;
 	bool online;
-	Vector< SharedPtr<Peer> > rootsNotOnline;
-	ZT_INLINE void operator()(const SharedPtr<Peer> &peer,const bool isRoot) noexcept
+	Vector<SharedPtr<Peer> > rootsNotOnline;
+	ZT_INLINE void operator()(const SharedPtr<Peer> &peer, const bool isRoot) noexcept
 	{
-		peer->pulse(tPtr,now,isRoot);
+		peer->pulse(tPtr, now, isRoot);
 		if (isRoot) {
 			if (peer->directlyConnected(now)) {
 				online = true;
@@ -202,7 +211,8 @@ struct _processBackgroundTasks_eachPeer
 		}
 	}
 };
-ZT_ResultCode Node::processBackgroundTasks(void *tPtr,int64_t now,volatile int64_t *nextBackgroundTaskDeadline)
+
+ZT_ResultCode Node::processBackgroundTasks(void *tPtr, int64_t now, volatile int64_t *nextBackgroundTaskDeadline)
 {
 	m_now = now;
 	Mutex::Lock bl(m_backgroundTasksLock);
@@ -212,7 +222,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr,int64_t now,volatile int64
 		if ((now - m_lastPeerPulse) >= ZT_PEER_PULSE_INTERVAL) {
 			m_lastPeerPulse = now;
 			try {
-				_processBackgroundTasks_eachPeer pf(now,this,tPtr);
+				_processBackgroundTasks_eachPeer pf(now, this, tPtr);
 				RR->topology->eachPeerWithRoot<_processBackgroundTasks_eachPeer &>(pf);
 
 				if (pf.online != m_online) {
@@ -228,7 +238,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr,int64_t now,volatile int64
 					//for (Vector<Address>::const_iterator r(pf.rootsNotOnline.begin()); r != pf.rootsNotOnline.end(); ++r)
 					//	RR->sw->requestWhois(tPtr,now,*r);
 				}
-			} catch ( ... ) {
+			} catch (...) {
 				return ZT_RESULT_FATAL_ERROR_INTERNAL;
 			}
 		}
@@ -238,8 +248,8 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr,int64_t now,volatile int64
 			m_lastHousekeepingRun = now;
 			{
 				RWMutex::RLock l(m_networks_l);
-				for(Map< uint64_t,SharedPtr<Network> >::const_iterator i(m_networks.begin());i != m_networks.end();++i)
-					i->second->doPeriodicTasks(tPtr,now);
+				for (Map<uint64_t, SharedPtr<Network> >::const_iterator i(m_networks.begin());i != m_networks.end();++i)
+					i->second->doPeriodicTasks(tPtr, now);
 			}
 		}
 
@@ -251,7 +261,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr,int64_t now,volatile int64
 				// optimization for network controllers to know whether to accept
 				// or trust nodes without doing an extra cert check.
 				m_localControllerAuthorizations_l.lock();
-				for(Map<p_LocalControllerAuth,int64_t>::iterator i(m_localControllerAuthorizations.begin());i != m_localControllerAuthorizations.end();) { // NOLINT(hicpp-use-auto,modernize-use-auto)
+				for (Map<p_LocalControllerAuth, int64_t>::iterator i(m_localControllerAuthorizations.begin());i != m_localControllerAuthorizations.end();) { // NOLINT(hicpp-use-auto,modernize-use-auto)
 					if ((i->second - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3))
 						m_localControllerAuthorizations.erase(i++);
 					else ++i;
@@ -260,40 +270,40 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr,int64_t now,volatile int64
 
 				RR->topology->doPeriodicTasks(tPtr, now);
 				RR->sa->clean(now);
-			} catch ( ... ) {
+			} catch (...) {
 				return ZT_RESULT_FATAL_ERROR_INTERNAL;
 			}
 		}
 
 		*nextBackgroundTaskDeadline = now + ZT_TIMER_TASK_INTERVAL;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 
 	return ZT_RESULT_OK;
 }
 
-ZT_ResultCode Node::join(uint64_t nwid,const ZT_Fingerprint *controllerFingerprint,void *uptr,void *tptr)
+ZT_ResultCode Node::join(uint64_t nwid, const ZT_Fingerprint *controllerFingerprint, void *uptr, void *tptr)
 {
 	Fingerprint fp;
 	if (controllerFingerprint)
-		Utils::copy<sizeof(ZT_Fingerprint)>(fp.apiFingerprint(),controllerFingerprint);
+		Utils::copy<sizeof(ZT_Fingerprint)>(fp.apiFingerprint(), controllerFingerprint);
 
 	RWMutex::Lock l(m_networks_l);
 	SharedPtr<Network> &nw = m_networks[nwid];
 	if (nw)
 		return ZT_RESULT_OK;
-	nw.set(new Network(RR,tptr,nwid,fp,uptr,nullptr));
+	nw.set(new Network(RR, tptr, nwid, fp, uptr, nullptr));
 
 	return ZT_RESULT_OK;
 }
 
-ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr)
+ZT_ResultCode Node::leave(uint64_t nwid, void **uptr, void *tptr)
 {
 	ZT_VirtualNetworkConfig ctmp;
 
 	m_networks_l.lock();
-	Map< uint64_t,SharedPtr<Network> >::iterator nwi(m_networks.find(nwid)); // NOLINT(hicpp-use-auto,modernize-use-auto)
+	Map<uint64_t, SharedPtr<Network> >::iterator nwi(m_networks.find(nwid)); // NOLINT(hicpp-use-auto,modernize-use-auto)
 	if (nwi == m_networks.end()) {
 		m_networks_l.unlock();
 		return ZT_RESULT_OK;
@@ -306,53 +316,50 @@ ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr)
 		*uptr = *nw->userPtr();
 	nw->externalConfig(&ctmp);
 
-	RR->node->configureVirtualNetworkPort(tptr,nwid,uptr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
+	RR->node->configureVirtualNetworkPort(tptr, nwid, uptr, ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY, &ctmp);
 
 	nw->destroy();
 	nw.zero();
 
 	uint64_t tmp[2];
-	tmp[0] = nwid; tmp[1] = 0;
-	RR->node->stateObjectDelete(tptr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp);
+	tmp[0] = nwid;
+	tmp[1] = 0;
+	RR->node->stateObjectDelete(tptr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp);
 
 	return ZT_RESULT_OK;
 }
 
-ZT_ResultCode Node::multicastSubscribe(void *tPtr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi)
+ZT_ResultCode Node::multicastSubscribe(void *tPtr, uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi)
 {
 	const SharedPtr<Network> nw(this->network(nwid));
 	if (nw) {
-		nw->multicastSubscribe(tPtr,MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff)));
+		nw->multicastSubscribe(tPtr, MulticastGroup(MAC(multicastGroup), (uint32_t) (multicastAdi & 0xffffffff)));
 		return ZT_RESULT_OK;
 	} else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND;
 }
 
-ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi)
+ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi)
 {
 	const SharedPtr<Network> nw(this->network(nwid));
 	if (nw) {
-		nw->multicastUnsubscribe(MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff)));
+		nw->multicastUnsubscribe(MulticastGroup(MAC(multicastGroup), (uint32_t) (multicastAdi & 0xffffffff)));
 		return ZT_RESULT_OK;
 	} else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND;
 }
 
-ZT_ResultCode Node::addRoot(void *tPtr,const void *rdef,unsigned int rdeflen,uint64_t *address)
+ZT_ResultCode Node::addRoot(void *tPtr, const ZT_Identity *id, const ZT_Locator *loc)
 {
-	if ((!rdef)||(rdeflen == 0))
+	if ((!id)||(!loc))
 		return ZT_RESULT_ERROR_BAD_PARAMETER;
-	std::pair<Identity,Locator> r(Locator::parseRootSpecification(rdef,rdeflen));
-	if (address)
-		*address = r.first.address().toInt();
-	return ((r.first)&&(RR->topology->addRoot(tPtr,r.first,r.second))) ? ZT_RESULT_OK : ZT_RESULT_ERROR_BAD_PARAMETER;
+	const SharedPtr<const Locator> locator(new Locator(*reinterpret_cast<const Locator *>(loc)));
+	// SECURITY: locator credential validation happens in Topology.cpp in addRoot().
+	return RR->topology->addRoot(tPtr, *reinterpret_cast<const Identity *>(id), locator) ? ZT_RESULT_OK : ZT_RESULT_ERROR_INVALID_CREDENTIAL;
 }
 
-ZT_ResultCode Node::removeRoot(void *tPtr,const ZT_Fingerprint *fp)
+ZT_ResultCode Node::removeRoot(void *tPtr, const uint64_t address)
 {
-	if (fp) {
-		RR->topology->removeRoot(tPtr,Fingerprint(*fp));
-		return ZT_RESULT_OK;
-	}
-	return ZT_RESULT_ERROR_BAD_PARAMETER;
+	RR->topology->removeRoot(tPtr, Address(address));
+	return ZT_RESULT_OK;
 }
 
 uint64_t Node::address() const
@@ -371,31 +378,45 @@ void Node::status(ZT_NodeStatus *status) const
 
 ZT_PeerList *Node::peers() const
 {
-	Vector< SharedPtr<Peer> > peers;
+	Vector<SharedPtr<Peer> > peers;
 	RR->topology->getAllPeers(peers);
-	std::sort(peers.begin(),peers.end(),_sortPeerPtrsByAddress());
-
-	char *buf = (char *)::malloc(sizeof(ZT_PeerList) + (sizeof(ZT_Peer) * peers.size()) + (sizeof(Identity) * peers.size()));
+	std::sort(peers.begin(), peers.end(), _sortPeerPtrsByAddress());
+
+	const unsigned int bufSize =
+		sizeof(ZT_PeerList) +
+		(sizeof(ZT_Peer) * peers.size()) +
+		((sizeof(ZT_Path) * ZT_MAX_PEER_NETWORK_PATHS) * peers.size()) +
+		(sizeof(Identity) * peers.size()) +
+		((sizeof(ZT_Endpoint) * ZT_LOCATOR_MAX_ENDPOINTS) * peers.size());
+	char *buf = (char *) malloc(bufSize);
 	if (!buf)
 		return nullptr;
-	ZT_PeerList *pl = (ZT_PeerList *)buf; // NOLINT(modernize-use-auto,hicpp-use-auto)
-	pl->peers = (ZT_Peer *)(buf + sizeof(ZT_PeerList));
-	Identity *identities = (Identity *)(buf + sizeof(ZT_PeerList) + (sizeof(ZT_Peer) * peers.size())); // NOLINT(modernize-use-auto,hicpp-use-auto)
+	Utils::zero(buf, bufSize);
+	ZT_PeerList *pl = reinterpret_cast<ZT_PeerList *>(buf);
+	buf += sizeof(ZT_PeerList);
+	pl->peers = reinterpret_cast<ZT_Peer *>(buf);
+	buf += sizeof(ZT_Peer) * peers.size();
+	ZT_Path *peerPath = reinterpret_cast<ZT_Path *>(buf);
+	buf += (sizeof(ZT_Path) * ZT_MAX_PEER_NETWORK_PATHS) * peers.size();
+	Identity *identities = reinterpret_cast<Identity *>(buf);
+	buf += sizeof(Identity) * peers.size();
+	ZT_Endpoint *locatorEndpoint = reinterpret_cast<ZT_Endpoint *>(buf);
 
 	const int64_t now = m_now;
+
 	pl->peerCount = 0;
-	for(Vector< SharedPtr<Peer> >::iterator pi(peers.begin());pi!=peers.end();++pi) { // NOLINT(modernize-use-auto,modernize-loop-convert,hicpp-use-auto)
-		ZT_Peer *const p = &(pl->peers[pl->peerCount]);
+	for (Vector<SharedPtr<Peer> >::iterator pi(peers.begin());pi != peers.end();++pi) {
+		ZT_Peer *const p = pl->peers + pl->peerCount;
 
 		p->address = (*pi)->address().toInt();
 		identities[pl->peerCount] = (*pi)->identity(); // need to make a copy in case peer gets deleted
-		p->identity = &identities[pl->peerCount];
+		p->identity = identities + pl->peerCount;
 		p->fingerprint.address = p->address;
-		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(p->fingerprint.hash,(*pi)->identity().fingerprint().hash());
+		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(p->fingerprint.hash, (*pi)->identity().fingerprint().hash);
 		if ((*pi)->remoteVersionKnown()) {
-			p->versionMajor = (int)(*pi)->remoteVersionMajor();
-			p->versionMinor = (int)(*pi)->remoteVersionMinor();
-			p->versionRev = (int)(*pi)->remoteVersionRevision();
+			p->versionMajor = (int) (*pi)->remoteVersionMajor();
+			p->versionMinor = (int) (*pi)->remoteVersionMinor();
+			p->versionRev = (int) (*pi)->remoteVersionRevision();
 		} else {
 			p->versionMajor = -1;
 			p->versionMinor = -1;
@@ -404,25 +425,30 @@ ZT_PeerList *Node::peers() const
 		p->latency = (*pi)->latency();
 		p->root = RR->topology->isRoot((*pi)->identity()) ? 1 : 0;
 
-		{
-			FCV<Endpoint,ZT_MAX_PEER_NETWORK_PATHS> bs((*pi)->bootstrap());
-			p->bootstrapAddressCount = 0;
-			for (FCV<Endpoint,ZT_MAX_PEER_NETWORK_PATHS>::const_iterator i(bs.begin());i!=bs.end();++i) // NOLINT(modernize-loop-convert)
-				Utils::copy<sizeof(sockaddr_storage)>(&(p->bootstrap[p->bootstrapAddressCount++]),&(*i));
-		}
+		p->networkCount = 0;
+		// TODO: enumerate network memberships
 
-		Vector< SharedPtr<Path> > paths;
+		Vector<SharedPtr<Path> > paths;
 		(*pi)->getAllPaths(paths);
-		p->pathCount = 0;
-		for(Vector< SharedPtr<Path> >::iterator path(paths.begin());path!=paths.end();++path) { // NOLINT(modernize-use-auto,modernize-loop-convert,hicpp-use-auto)
-			Utils::copy<sizeof(sockaddr_storage)>(&(p->paths[p->pathCount].address),&((*path)->address()));
-			p->paths[p->pathCount].lastSend = (*path)->lastOut();
-			p->paths[p->pathCount].lastReceive = (*path)->lastIn();
-			// TODO
-			//p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address());
-			p->paths[p->pathCount].alive = (*path)->alive(now) ? 1 : 0;
-			p->paths[p->pathCount].preferred = (p->pathCount == 0) ? 1 : 0;
-			++p->pathCount;
+		p->pathCount = (unsigned int) paths.size();
+		p->paths = peerPath;
+		for (Vector<SharedPtr<Path> >::iterator path(paths.begin());path != paths.end();++path) {
+			ZT_Path *const pp = peerPath++;
+			pp->endpoint.type = ZT_ENDPOINT_TYPE_IP_UDP; // only type supported right now
+			Utils::copy<sizeof(sockaddr_storage)>(&pp->endpoint.value.ss, &((*path)->address().as.ss));
+			pp->lastSend = (*path)->lastOut();
+			pp->lastReceive = (*path)->lastIn();
+			pp->alive = (*path)->alive(now) ? 1 : 0;
+			pp->preferred = (p->pathCount == 0) ? 1 : 0;
+		}
+
+		const SharedPtr<const Locator> loc((*pi)->locator());
+		if (loc) {
+			p->locatorTimestamp = loc->timestamp();
+			p->locatorEndpointCount = (unsigned int)loc->endpoints().size();
+			p->locatorEndpoints = locatorEndpoint;
+			for(Vector<Endpoint>::const_iterator ep(loc->endpoints().begin());ep!=loc->endpoints().end();++ep)
+				*(locatorEndpoint++) = *ep;
 		}
 
 		++pl->peerCount;
@@ -435,7 +461,7 @@ ZT_VirtualNetworkConfig *Node::networkConfig(uint64_t nwid) const
 {
 	SharedPtr<Network> nw(network(nwid));
 	if (nw) {
-		ZT_VirtualNetworkConfig *const nc = (ZT_VirtualNetworkConfig *)::malloc(sizeof(ZT_VirtualNetworkConfig)); // NOLINT(modernize-use-auto,hicpp-use-auto)
+		ZT_VirtualNetworkConfig *const nc = (ZT_VirtualNetworkConfig *) ::malloc(sizeof(ZT_VirtualNetworkConfig));
 		nw->externalConfig(nc);
 		return nc;
 	}
@@ -446,39 +472,33 @@ ZT_VirtualNetworkList *Node::networks() const
 {
 	RWMutex::RLock l(m_networks_l);
 
-	char *const buf = (char *)::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * m_networks.size()));
+	char *const buf = (char *) ::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * m_networks.size()));
 	if (!buf)
 		return nullptr;
-	ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *)buf; // NOLINT(modernize-use-auto,hicpp-use-auto)
-	nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList));
+	ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *) buf; // NOLINT(modernize-use-auto,hicpp-use-auto)
+	nl->networks = (ZT_VirtualNetworkConfig *) (buf + sizeof(ZT_VirtualNetworkList));
 
 	nl->networkCount = 0;
-	for(Map< uint64_t,SharedPtr<Network> >::const_iterator i(m_networks.begin());i != m_networks.end();++i) // NOLINT(modernize-use-auto,modernize-loop-convert,hicpp-use-auto)
+	for (Map<uint64_t, SharedPtr<Network> >::const_iterator i(m_networks.begin());i != m_networks.end();++i) // NOLINT(modernize-use-auto,modernize-loop-convert,hicpp-use-auto)
 		i->second->externalConfig(&(nl->networks[nl->networkCount++]));
 
 	return nl;
 }
 
-void Node::setNetworkUserPtr(uint64_t nwid,void *ptr)
+void Node::setNetworkUserPtr(uint64_t nwid, void *ptr)
 {
 	SharedPtr<Network> nw(network(nwid));
 	if (nw)
 		*(nw->userPtr()) = ptr;
 }
 
-void Node::freeQueryResult(void *qr)
-{
-	if (qr)
-		::free(qr);
-}
-
-void Node::setInterfaceAddresses(const ZT_InterfaceAddress *addrs,unsigned int addrCount)
+void Node::setInterfaceAddresses(const ZT_InterfaceAddress *addrs, unsigned int addrCount)
 {
 	Mutex::Lock _l(m_localInterfaceAddresses_m);
 	m_localInterfaceAddresses.clear();
-	for(unsigned int i=0;i<addrCount;++i) {
+	for (unsigned int i = 0;i < addrCount;++i) {
 		bool dupe = false;
-		for(unsigned int j=0;j<i;++j) {
+		for (unsigned int j = 0;j < i;++j) {
 			if (*(reinterpret_cast<const InetAddress *>(&addrs[j].address)) == *(reinterpret_cast<const InetAddress *>(&addrs[i].address))) {
 				dupe = true;
 				break;
@@ -489,7 +509,7 @@ void Node::setInterfaceAddresses(const ZT_InterfaceAddress *addrs,unsigned int a
 	}
 }
 
-int Node::sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len)
+int Node::sendUserMessage(void *tptr, uint64_t dest, uint64_t typeId, const void *data, unsigned int len)
 {
 	try {
 		if (RR->identity.address().toInt() != dest) {
@@ -503,7 +523,7 @@ int Node::sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *d
 			*/
 			return 1;
 		}
-	} catch ( ... ) {}
+	} catch (...) {}
 	return 0;
 }
 
@@ -511,12 +531,12 @@ void Node::setController(void *networkControllerInstance)
 {
 	RR->localNetworkController = reinterpret_cast<NetworkController *>(networkControllerInstance);
 	if (networkControllerInstance)
-		RR->localNetworkController->init(RR->identity,this);
+		RR->localNetworkController->init(RR->identity, this);
 }
 
 // Methods used only within the core ----------------------------------------------------------------------------------
 
-Vector<uint8_t> Node::stateObjectGet(void *const tPtr,ZT_StateObjectType type,const uint64_t id[2])
+Vector<uint8_t> Node::stateObjectGet(void *const tPtr, ZT_StateObjectType type, const uint64_t id[2])
 {
 	Vector<uint8_t> r;
 	if (m_cb.stateGetFunction) {
@@ -530,20 +550,20 @@ Vector<uint8_t> Node::stateObjectGet(void *const tPtr,ZT_StateObjectType type,co
 			id,
 			&data,
 			&freeFunc);
-		if ((l > 0)&&(data)&&(freeFunc)) {
-			r.assign(reinterpret_cast<const uint8_t *>(data),reinterpret_cast<const uint8_t *>(data) + l);
+		if ((l > 0) && (data) && (freeFunc)) {
+			r.assign(reinterpret_cast<const uint8_t *>(data), reinterpret_cast<const uint8_t *>(data) + l);
 			freeFunc(data);
 		}
 	}
 	return r;
 }
 
-bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Identity &id,const int64_t localSocket,const InetAddress &remoteAddress)
+bool Node::shouldUsePathForZeroTierTraffic(void *tPtr, const Identity &id, const int64_t localSocket, const InetAddress &remoteAddress)
 {
 	{
 		RWMutex::RLock l(m_networks_l);
-		for(Map< uint64_t,SharedPtr<Network> >::iterator i(m_networks.begin());i != m_networks.end();++i) { // NOLINT(hicpp-use-auto,modernize-use-auto,modernize-loop-convert)
-			for (unsigned int k = 0,j = i->second->config().staticIpCount; k < j; ++k) {
+		for (Map<uint64_t, SharedPtr<Network> >::iterator i(m_networks.begin());i != m_networks.end();++i) { // NOLINT(hicpp-use-auto,modernize-use-auto,modernize-loop-convert)
+			for (unsigned int k = 0, j = i->second->config().staticIpCount;k < j;++k) {
 				if (i->second->config().staticIps[k].containsAddress(remoteAddress))
 					return false;
 			}
@@ -556,7 +576,7 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Identity &id,const i
 			m_uPtr,
 			tPtr,
 			id.address().toInt(),
-			(const ZT_Identity *)&id,
+			(const ZT_Identity *) &id,
 			localSocket,
 			reinterpret_cast<const struct sockaddr_storage *>(&remoteAddress)) != 0);
 	}
@@ -564,7 +584,7 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Identity &id,const i
 	return true;
 }
 
-bool Node::externalPathLookup(void *tPtr,const Identity &id,int family,InetAddress &addr)
+bool Node::externalPathLookup(void *tPtr, const Identity &id, int family, InetAddress &addr)
 {
 	if (m_cb.pathLookupFunction) {
 		return (m_cb.pathLookupFunction(
@@ -579,7 +599,7 @@ bool Node::externalPathLookup(void *tPtr,const Identity &id,int family,InetAddre
 	return false;
 }
 
-bool Node::localControllerHasAuthorized(const int64_t now,const uint64_t nwid,const Address &addr) const
+bool Node::localControllerHasAuthorized(const int64_t now, const uint64_t nwid, const Address &addr) const
 {
 	m_localControllerAuthorizations_l.lock();
 	const int64_t *const at = m_localControllerAuthorizations.get(p_LocalControllerAuth(nwid, addr));
@@ -591,7 +611,7 @@ bool Node::localControllerHasAuthorized(const int64_t now,const uint64_t nwid,co
 
 // Implementation of NetworkController::Sender ------------------------------------------------------------------------
 
-void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig)
+void Node::ncSendConfig(uint64_t nwid, uint64_t requestPacketId, const Address &destination, const NetworkConfig &nc, bool sendLegacyFormatConfig)
 {
 	m_localControllerAuthorizations_l.lock();
 	m_localControllerAuthorizations[p_LocalControllerAuth(nwid, destination)] = now();
@@ -601,7 +621,7 @@ void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &de
 		SharedPtr<Network> n(network(nwid));
 		if (!n)
 			return;
-		n->setConfiguration((void *)0,nc,true);
+		n->setConfiguration((void *) 0, nc, true);
 	} else {
 		Dictionary dconf;
 		if (nc.toDictionary(dconf)) {
@@ -647,12 +667,12 @@ void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &de
 	}
 }
 
-void Node::ncSendRevocation(const Address &destination,const Revocation &rev)
+void Node::ncSendRevocation(const Address &destination, const Revocation &rev)
 {
 	if (destination == RR->identity.address()) {
 		SharedPtr<Network> n(network(rev.networkId()));
 		if (!n) return;
-		n->addCredential(nullptr,RR->identity,rev);
+		n->addCredential(nullptr, RR->identity, rev);
 	} else {
 		// TODO
 		/*
@@ -668,12 +688,12 @@ void Node::ncSendRevocation(const Address &destination,const Revocation &rev)
 	}
 }
 
-void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode)
+void Node::ncSendError(uint64_t nwid, uint64_t requestPacketId, const Address &destination, NetworkController::ErrorCode errorCode)
 {
 	if (destination == RR->identity.address()) {
 		SharedPtr<Network> n(network(nwid));
 		if (!n) return;
-		switch(errorCode) {
+		switch (errorCode) {
 			case NetworkController::NC_ERROR_OBJECT_NOT_FOUND:
 			case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR:
 				n->setNotFound();
@@ -682,7 +702,8 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des
 				n->setAccessDenied();
 				break;
 
-			default: break;
+			default:
+				break;
 		}
 	} else if (requestPacketId) {
 		// TODO
@@ -727,38 +748,44 @@ void *ZT_getBuffer()
 	// wrapped in a SharedPtr<> to be passed into the core.
 	try {
 		return _ZT_BUFTOPTR(new ZeroTier::Buf());
-	} catch ( ... ) {
+	} catch (...) {
 		return nullptr; // can only happen on out of memory condition
 	}
 }
 
-ZT_SDK_API void ZT_freeBuffer(void *b)
+void ZT_freeBuffer(void *b)
 {
 	if (b)
 		delete _ZT_PTRTOBUF(b);
 }
 
-enum ZT_ResultCode ZT_Node_new(ZT_Node **node,void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64_t now)
+void ZT_freeQueryResult(void *qr)
 {
-	*node = (ZT_Node *)0;
+	if (qr)
+		free(qr);
+}
+
+enum ZT_ResultCode ZT_Node_new(ZT_Node **node, void *uptr, void *tptr, const struct ZT_Node_Callbacks *callbacks, int64_t now)
+{
+	*node = (ZT_Node *) 0;
 	try {
-		*node = reinterpret_cast<ZT_Node *>(new ZeroTier::Node(uptr,tptr,callbacks,now));
+		*node = reinterpret_cast<ZT_Node *>(new ZeroTier::Node(uptr, tptr, callbacks, now));
 		return ZT_RESULT_OK;
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
 	} catch (std::runtime_error &exc) {
 		return ZT_RESULT_FATAL_ERROR_DATA_STORE_FAILED;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-void ZT_Node_delete(ZT_Node *node,void *tPtr)
+void ZT_Node_delete(ZT_Node *node, void *tPtr)
 {
 	try {
 		reinterpret_cast<ZeroTier::Node *>(node)->shutdown(tPtr);
 		delete (reinterpret_cast<ZeroTier::Node *>(node));
-	} catch ( ... ) {}
+	} catch (...) {}
 }
 
 enum ZT_ResultCode ZT_Node_processWirePacket(
@@ -773,11 +800,11 @@ enum ZT_ResultCode ZT_Node_processWirePacket(
 	volatile int64_t *nextBackgroundTaskDeadline)
 {
 	try {
-		ZeroTier::SharedPtr<ZeroTier::Buf> buf((isZtBuffer) ? _ZT_PTRTOBUF(packetData) : new ZeroTier::Buf(packetData,packetLength & ZT_BUF_MEM_MASK));
-		return reinterpret_cast<ZeroTier::Node *>(node)->processWirePacket(tptr,now,localSocket,remoteAddress,buf,packetLength,nextBackgroundTaskDeadline);
+		ZeroTier::SharedPtr<ZeroTier::Buf> buf((isZtBuffer) ? _ZT_PTRTOBUF(packetData) : new ZeroTier::Buf(packetData, packetLength & ZT_BUF_MEM_MASK));
+		return reinterpret_cast<ZeroTier::Node *>(node)->processWirePacket(tptr, now, localSocket, remoteAddress, buf, packetLength, nextBackgroundTaskDeadline);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_OK; // "OK" since invalid packets are simply dropped, but the system is still up
 	}
 }
@@ -797,88 +824,88 @@ enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame(
 	volatile int64_t *nextBackgroundTaskDeadline)
 {
 	try {
-		ZeroTier::SharedPtr<ZeroTier::Buf> buf((isZtBuffer) ? _ZT_PTRTOBUF(frameData) : new ZeroTier::Buf(frameData,frameLength & ZT_BUF_MEM_MASK));
-		return reinterpret_cast<ZeroTier::Node *>(node)->processVirtualNetworkFrame(tptr,now,nwid,sourceMac,destMac,etherType,vlanId,buf,frameLength,nextBackgroundTaskDeadline);
+		ZeroTier::SharedPtr<ZeroTier::Buf> buf((isZtBuffer) ? _ZT_PTRTOBUF(frameData) : new ZeroTier::Buf(frameData, frameLength & ZT_BUF_MEM_MASK));
+		return reinterpret_cast<ZeroTier::Node *>(node)->processVirtualNetworkFrame(tptr, now, nwid, sourceMac, destMac, etherType, vlanId, buf, frameLength, nextBackgroundTaskDeadline);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,void *tptr,int64_t now,volatile int64_t *nextBackgroundTaskDeadline)
+enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node, void *tptr, int64_t now, volatile int64_t *nextBackgroundTaskDeadline)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->processBackgroundTasks(tptr,now,nextBackgroundTaskDeadline);
+		return reinterpret_cast<ZeroTier::Node *>(node)->processBackgroundTasks(tptr, now, nextBackgroundTaskDeadline);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,const ZT_Fingerprint *controllerFingerprint,void *uptr,void *tptr)
+enum ZT_ResultCode ZT_Node_join(ZT_Node *node, uint64_t nwid, const ZT_Fingerprint *controllerFingerprint, void *uptr, void *tptr)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->join(nwid,controllerFingerprint,uptr,tptr);
+		return reinterpret_cast<ZeroTier::Node *>(node)->join(nwid, controllerFingerprint, uptr, tptr);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr,void *tptr)
+enum ZT_ResultCode ZT_Node_leave(ZT_Node *node, uint64_t nwid, void **uptr, void *tptr)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->leave(nwid,uptr,tptr);
+		return reinterpret_cast<ZeroTier::Node *>(node)->leave(nwid, uptr, tptr);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node,void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi)
+enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node, void *tptr, uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->multicastSubscribe(tptr,nwid,multicastGroup,multicastAdi);
+		return reinterpret_cast<ZeroTier::Node *>(node)->multicastSubscribe(tptr, nwid, multicastGroup, multicastAdi);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_multicastUnsubscribe(ZT_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi)
+enum ZT_ResultCode ZT_Node_multicastUnsubscribe(ZT_Node *node, uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->multicastUnsubscribe(nwid,multicastGroup,multicastAdi);
+		return reinterpret_cast<ZeroTier::Node *>(node)->multicastUnsubscribe(nwid, multicastGroup, multicastAdi);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_addRoot(ZT_Node *node,void *tptr,const void *rdef,unsigned int rdeflen,uint64_t *address)
+enum ZT_ResultCode ZT_Node_addRoot(ZT_Node *node, void *tptr, const ZT_Identity *id, const ZT_Locator *loc)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->addRoot(tptr,rdef,rdeflen,address);
+		return reinterpret_cast<ZeroTier::Node *>(node)->addRoot(tptr, id, loc);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
 
-enum ZT_ResultCode ZT_Node_removeRoot(ZT_Node *node,void *tptr,const ZT_Fingerprint *fp)
+enum ZT_ResultCode ZT_Node_removeRoot(ZT_Node *node, void *tptr, const uint64_t address)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->removeRoot(tptr,fp);
+		return reinterpret_cast<ZeroTier::Node *>(node)->removeRoot(tptr, address);
 	} catch (std::bad_alloc &exc) {
 		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_RESULT_FATAL_ERROR_INTERNAL;
 	}
 }
@@ -890,31 +917,31 @@ uint64_t ZT_Node_address(ZT_Node *node)
 
 const ZT_Identity *ZT_Node_identity(ZT_Node *node)
 {
-	return (const ZT_Identity *)(&(reinterpret_cast<ZeroTier::Node *>(node)->identity()));
+	return (const ZT_Identity *) (&(reinterpret_cast<ZeroTier::Node *>(node)->identity()));
 }
 
-void ZT_Node_status(ZT_Node *node,ZT_NodeStatus *status)
+void ZT_Node_status(ZT_Node *node, ZT_NodeStatus *status)
 {
 	try {
 		reinterpret_cast<ZeroTier::Node *>(node)->status(status);
-	} catch ( ... ) {}
+	} catch (...) {}
 }
 
 ZT_PeerList *ZT_Node_peers(ZT_Node *node)
 {
 	try {
 		return reinterpret_cast<ZeroTier::Node *>(node)->peers();
-	} catch ( ... ) {
-		return (ZT_PeerList *)0;
+	} catch (...) {
+		return (ZT_PeerList *) 0;
 	}
 }
 
-ZT_VirtualNetworkConfig *ZT_Node_networkConfig(ZT_Node *node,uint64_t nwid)
+ZT_VirtualNetworkConfig *ZT_Node_networkConfig(ZT_Node *node, uint64_t nwid)
 {
 	try {
 		return reinterpret_cast<ZeroTier::Node *>(node)->networkConfig(nwid);
-	} catch ( ... ) {
-		return (ZT_VirtualNetworkConfig *)0;
+	} catch (...) {
+		return (ZT_VirtualNetworkConfig *) 0;
 	}
 }
 
@@ -922,49 +949,42 @@ ZT_VirtualNetworkList *ZT_Node_networks(ZT_Node *node)
 {
 	try {
 		return reinterpret_cast<ZeroTier::Node *>(node)->networks();
-	} catch ( ... ) {
-		return (ZT_VirtualNetworkList *)0;
+	} catch (...) {
+		return (ZT_VirtualNetworkList *) 0;
 	}
 }
 
-void ZT_Node_setNetworkUserPtr(ZT_Node *node,uint64_t nwid,void *ptr)
-{
-	try {
-		reinterpret_cast<ZeroTier::Node *>(node)->setNetworkUserPtr(nwid,ptr);
-	} catch ( ... ) {}
-}
-
-void ZT_Node_freeQueryResult(ZT_Node *node,void *qr)
+void ZT_Node_setNetworkUserPtr(ZT_Node *node, uint64_t nwid, void *ptr)
 {
 	try {
-		reinterpret_cast<ZeroTier::Node *>(node)->freeQueryResult(qr);
-	} catch ( ... ) {}
+		reinterpret_cast<ZeroTier::Node *>(node)->setNetworkUserPtr(nwid, ptr);
+	} catch (...) {}
 }
 
-void ZT_Node_setInterfaceAddresses(ZT_Node *node,const ZT_InterfaceAddress *addrs,unsigned int addrCount)
+void ZT_Node_setInterfaceAddresses(ZT_Node *node, const ZT_InterfaceAddress *addrs, unsigned int addrCount)
 {
 	try {
-		reinterpret_cast<ZeroTier::Node *>(node)->setInterfaceAddresses(addrs,addrCount);
-	} catch ( ... ) {}
+		reinterpret_cast<ZeroTier::Node *>(node)->setInterfaceAddresses(addrs, addrCount);
+	} catch (...) {}
 }
 
-int ZT_Node_sendUserMessage(ZT_Node *node,void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len)
+int ZT_Node_sendUserMessage(ZT_Node *node, void *tptr, uint64_t dest, uint64_t typeId, const void *data, unsigned int len)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->sendUserMessage(tptr,dest,typeId,data,len);
-	} catch ( ... ) {
+		return reinterpret_cast<ZeroTier::Node *>(node)->sendUserMessage(tptr, dest, typeId, data, len);
+	} catch (...) {
 		return 0;
 	}
 }
 
-void ZT_Node_setController(ZT_Node *node,void *networkControllerInstance)
+void ZT_Node_setController(ZT_Node *node, void *networkControllerInstance)
 {
 	try {
 		reinterpret_cast<ZeroTier::Node *>(node)->setController(networkControllerInstance);
-	} catch ( ... ) {}
+	} catch (...) {}
 }
 
-void ZT_version(int *major,int *minor,int *revision,int *build)
+void ZT_version(int *major, int *minor, int *revision, int *build)
 {
 	if (major)
 		*major = ZEROTIER_VERSION_MAJOR;

+ 113 - 65
node/Node.hpp

@@ -26,20 +26,8 @@
 #include "Buf.hpp"
 #include "Containers.hpp"
 
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <vector>
-#include <map>
-
-// Bit mask for "expecting reply" hash
-#define ZT_EXPECTING_REPLIES_BUCKET_MASK1 255
-#define ZT_EXPECTING_REPLIES_BUCKET_MASK2 31
-
 namespace ZeroTier {
 
-class Locator;
-
 /**
  * Implementation of Node object as defined in CAPI
  *
@@ -48,24 +36,18 @@ class Locator;
 class Node : public NetworkController::Sender
 {
 public:
-	Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64_t now);
-	virtual ~Node();
-
-	/**
-	 * Perform any operations that should be done prior to deleting a Node
-	 *
-	 * This is technically optional but recommended.
-	 *
-	 * @param tPtr Thread pointer to pass through to callbacks
-	 */
-	void shutdown(void *tPtr);
-
 	// Get rid of alignment warnings on 32-bit Windows
 #ifdef __WINDOWS__
 	void * operator new(size_t i) { return _mm_malloc(i,16); }
 	void operator delete(void* p) { _mm_free(p); }
 #endif
 
+	Node(void *uPtr, void *tPtr, const struct ZT_Node_Callbacks *callbacks, int64_t now);
+
+	virtual ~Node();
+
+	void shutdown(void *tPtr);
+
 	// Public API Functions ---------------------------------------------------------------------------------------------
 
 	ZT_ResultCode processWirePacket(
@@ -76,6 +58,7 @@ public:
 		SharedPtr<Buf> &packetData,
 		unsigned int packetLength,
 		volatile int64_t *nextBackgroundTaskDeadline);
+
 	ZT_ResultCode processVirtualNetworkFrame(
 		void *tPtr,
 		int64_t now,
@@ -87,30 +70,80 @@ public:
 		SharedPtr<Buf> &frameData,
 		unsigned int frameLength,
 		volatile int64_t *nextBackgroundTaskDeadline);
-	ZT_ResultCode processBackgroundTasks(void *tPtr, int64_t now, volatile int64_t *nextBackgroundTaskDeadline);
-	ZT_ResultCode join(uint64_t nwid,const ZT_Fingerprint *controllerFingerprint,void *uptr,void *tptr);
-	ZT_ResultCode leave(uint64_t nwid,void **uptr,void *tptr);
-	ZT_ResultCode multicastSubscribe(void *tPtr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
-	ZT_ResultCode multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
-	ZT_ResultCode addRoot(void *tptr,const void *rdef,unsigned int rdeflen,uint64_t *address);
-	ZT_ResultCode removeRoot(void *tptr,const ZT_Fingerprint *fp);
+
+	ZT_ResultCode processBackgroundTasks(
+		void *tPtr,
+		int64_t now,
+		volatile int64_t *nextBackgroundTaskDeadline);
+
+	ZT_ResultCode join(
+		uint64_t nwid,
+		const ZT_Fingerprint *controllerFingerprint,
+		void *uptr,
+		void *tptr);
+
+	ZT_ResultCode leave(
+		uint64_t nwid,
+		void **uptr,
+		void *tptr);
+
+	ZT_ResultCode multicastSubscribe(
+		void *tPtr,
+		uint64_t nwid,
+		uint64_t multicastGroup,
+		unsigned long multicastAdi);
+
+	ZT_ResultCode multicastUnsubscribe(
+		uint64_t nwid,
+		uint64_t multicastGroup,
+		unsigned long multicastAdi);
+
+	ZT_ResultCode addRoot(
+		void *tptr,
+		const ZT_Identity *id,
+		const ZT_Locator *loc);
+
+	ZT_ResultCode removeRoot(
+		void *tptr,
+		const uint64_t address);
+
 	uint64_t address() const;
-	void status(ZT_NodeStatus *status) const;
+
+	void status(
+		ZT_NodeStatus *status) const;
+
 	ZT_PeerList *peers() const;
-	ZT_VirtualNetworkConfig *networkConfig(uint64_t nwid) const;
+
+	ZT_VirtualNetworkConfig *networkConfig(
+		uint64_t nwid) const;
+
 	ZT_VirtualNetworkList *networks() const;
-	void setNetworkUserPtr(uint64_t nwid,void *ptr);
-	void freeQueryResult(void *qr);
-	void setInterfaceAddresses(const ZT_InterfaceAddress *addrs,unsigned int addrCount);
-	int sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len);
-	void setController(void *networkControllerInstance);
+
+	void setNetworkUserPtr(
+		uint64_t nwid,
+		void *ptr);
+
+	void setInterfaceAddresses(
+		const ZT_InterfaceAddress *addrs,
+		unsigned int addrCount);
+
+	int sendUserMessage(
+		void *tptr,
+		uint64_t dest,
+		uint64_t typeId,
+		const void *data,
+		unsigned int len);
+
+	void setController(
+		void *networkControllerInstance);
 
 	// Internal functions -----------------------------------------------------------------------------------------------
 
 	/**
 	 * @return Most recent time value supplied to core via API
 	 */
-	ZT_INLINE int64_t now() const noexcept { return m_now; }
+	ZT_INLINE int64_t now() const noexcept
+	{ return m_now; }
 
 	/**
 	 * Send packet to to the physical wire via callback
@@ -123,7 +156,7 @@ public:
 	 * @param ttl TTL or 0 for default/max
 	 * @return True if send appears successful
 	 */
-	ZT_INLINE bool putPacket(void *tPtr,const int64_t localSocket,const InetAddress &addr,const void *data,unsigned int len,unsigned int ttl = 0) noexcept
+	ZT_INLINE bool putPacket(void *tPtr, const int64_t localSocket, const InetAddress &addr, const void *data, unsigned int len, unsigned int ttl = 0) noexcept
 	{
 		return (m_cb.wirePacketSendFunction(
 			reinterpret_cast<ZT_Node *>(this),
@@ -149,7 +182,7 @@ public:
 	 * @param data Ethernet frame data
 	 * @param len Ethernet frame length in bytes
 	 */
-	ZT_INLINE void putFrame(void *tPtr,uint64_t nwid,void **nuptr,const MAC &source,const MAC &dest,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) noexcept
+	ZT_INLINE void putFrame(void *tPtr, uint64_t nwid, void **nuptr, const MAC &source, const MAC &dest, unsigned int etherType, unsigned int vlanId, const void *data, unsigned int len) noexcept
 	{
 		m_cb.virtualNetworkFrameFunction(
 			reinterpret_cast<ZT_Node *>(this),
@@ -194,7 +227,7 @@ public:
 	 * @param ev Event object
 	 * @param md Event data or NULL if none
 	 */
-	ZT_INLINE void postEvent(void *tPtr,ZT_Event ev,const void *md = nullptr) noexcept
+	ZT_INLINE void postEvent(void *tPtr, ZT_Event ev, const void *md = nullptr) noexcept
 	{
 		m_cb.eventCallback(reinterpret_cast<ZT_Node *>(this), m_uPtr, tPtr, ev, md);
 	}
@@ -208,7 +241,7 @@ public:
 	 * @param op Config operation or event type
 	 * @param nc Network config info
 	 */
-	ZT_INLINE void configureVirtualNetworkPort(void *tPtr,uint64_t nwid,void **nuptr,ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nc) noexcept
+	ZT_INLINE void configureVirtualNetworkPort(void *tPtr, uint64_t nwid, void **nuptr, ZT_VirtualNetworkConfigOperation op, const ZT_VirtualNetworkConfig *nc) noexcept
 	{
 		m_cb.virtualNetworkConfigFunction(reinterpret_cast<ZT_Node *>(this), m_uPtr, tPtr, nwid, nuptr, op, nc);
 	}
@@ -216,7 +249,8 @@ public:
 	/**
 	 * @return True if node appears online
 	 */
-	ZT_INLINE bool online() const noexcept { return m_online; }
+	ZT_INLINE bool online() const noexcept
+	{ return m_online; }
 
 	/**
 	 * Get a state object
@@ -226,7 +260,7 @@ public:
 	 * @param id Object ID
 	 * @return Vector containing data or empty vector if not found or empty
 	 */
-	Vector<uint8_t> stateObjectGet(void *tPtr,ZT_StateObjectType type,const uint64_t id[2]);
+	Vector<uint8_t> stateObjectGet(void *tPtr, ZT_StateObjectType type, const uint64_t id[2]);
 
 	/**
 	 * Store a state object
@@ -237,10 +271,10 @@ public:
 	 * @param data Data to store
 	 * @param len Length of data
 	 */
-	ZT_INLINE void stateObjectPut(void *const tPtr,ZT_StateObjectType type,const uint64_t id[2],const void *const data,const unsigned int len) noexcept
+	ZT_INLINE void stateObjectPut(void *const tPtr, ZT_StateObjectType type, const uint64_t id[2], const void *const data, const unsigned int len) noexcept
 	{
 		if (m_cb.statePutFunction)
-			m_cb.statePutFunction(reinterpret_cast<ZT_Node *>(this), m_uPtr, tPtr, type, id, data, (int)len);
+			m_cb.statePutFunction(reinterpret_cast<ZT_Node *>(this), m_uPtr, tPtr, type, id, data, (int) len);
 	}
 
 	/**
@@ -250,7 +284,7 @@ public:
 	 * @param type Object type to delete
 	 * @param id Object ID
 	 */
-	ZT_INLINE void stateObjectDelete(void *const tPtr,ZT_StateObjectType type,const uint64_t id[2]) noexcept
+	ZT_INLINE void stateObjectDelete(void *const tPtr, ZT_StateObjectType type, const uint64_t id[2]) noexcept
 	{
 		if (m_cb.statePutFunction)
 			m_cb.statePutFunction(reinterpret_cast<ZT_Node *>(this), m_uPtr, tPtr, type, id, nullptr, -1);
@@ -267,7 +301,7 @@ public:
 	 * @param remoteAddress Remote address
 	 * @return True if path should be used
 	 */
-	bool shouldUsePathForZeroTierTraffic(void *tPtr,const Identity &id,int64_t localSocket,const InetAddress &remoteAddress);
+	bool shouldUsePathForZeroTierTraffic(void *tPtr, const Identity &id, int64_t localSocket, const InetAddress &remoteAddress);
 
 	/**
 	 * Query callback for a physical address for a peer
@@ -278,17 +312,19 @@ public:
 	 * @param addr Buffer to store address (result paramter)
 	 * @return True if addr was filled with something
 	 */
-	bool externalPathLookup(void *tPtr,const Identity &id,int family,InetAddress &addr);
+	bool externalPathLookup(void *tPtr, const Identity &id, int family, InetAddress &addr);
 
 	/**
 	 * @return This node's identity
 	 */
-	ZT_INLINE const Identity &identity() const noexcept { return m_RR.identity; }
+	ZT_INLINE const Identity &identity() const noexcept
+	{ return m_RR.identity; }
 
 	/**
 	 * @return True if aggressive NAT-traversal mechanisms like scanning of <1024 ports are enabled
 	 */
-	ZT_INLINE bool natMustDie() const noexcept { return m_natMustDie; }
+	ZT_INLINE bool natMustDie() const noexcept
+	{ return m_natMustDie; }
 
 	/**
 	 * Check whether a local controller has authorized a member on a network
@@ -303,12 +339,14 @@ public:
 	 * @param addr Member address to check
 	 * @return True if member has been authorized
 	 */
-	bool localControllerHasAuthorized(int64_t now,uint64_t nwid,const Address &addr) const;
+	bool localControllerHasAuthorized(int64_t now, uint64_t nwid, const Address &addr) const;
 
 	// Implementation of NetworkController::Sender interface
-	virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig);
-	virtual void ncSendRevocation(const Address &destination,const Revocation &rev);
-	virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode);
+	virtual void ncSendConfig(uint64_t nwid, uint64_t requestPacketId, const Address &destination, const NetworkConfig &nc, bool sendLegacyFormatConfig);
+
+	virtual void ncSendRevocation(const Address &destination, const Revocation &rev);
+
+	virtual void ncSendError(uint64_t nwid, uint64_t requestPacketId, const Address &destination, NetworkController::ErrorCode errorCode);
 
 private:
 	RuntimeEnvironment m_RR;
@@ -329,23 +367,33 @@ private:
 	// weird place to put it.
 	struct p_LocalControllerAuth
 	{
-		uint64_t nwid,address;
-		ZT_INLINE p_LocalControllerAuth(const uint64_t nwid_, const Address &address_)  noexcept: nwid(nwid_), address(address_.toInt()) {}
-		ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)(nwid + address); }
-		ZT_INLINE bool operator==(const p_LocalControllerAuth &a) const noexcept { return ((a.nwid == nwid) && (a.address == address)); }
-		ZT_INLINE bool operator!=(const p_LocalControllerAuth &a) const noexcept { return ((a.nwid != nwid) || (a.address != address)); }
-		ZT_INLINE bool operator<(const p_LocalControllerAuth &a) const noexcept { return ((a.nwid < nwid) || ((a.nwid == nwid) && (a.address < address))); }
+		uint64_t nwid, address;
+		ZT_INLINE p_LocalControllerAuth(const uint64_t nwid_, const Address &address_) noexcept: nwid(nwid_), address(address_.toInt())
+		{}
+
+		ZT_INLINE unsigned long hashCode() const noexcept
+		{ return (unsigned long) (nwid + address); }
+
+		ZT_INLINE bool operator==(const p_LocalControllerAuth &a) const noexcept
+		{ return ((a.nwid == nwid) && (a.address == address)); }
+
+		ZT_INLINE bool operator!=(const p_LocalControllerAuth &a) const noexcept
+		{ return ((a.nwid != nwid) || (a.address != address)); }
+
+		ZT_INLINE bool operator<(const p_LocalControllerAuth &a) const noexcept
+		{ return ((a.nwid < nwid) || ((a.nwid == nwid) && (a.address < address))); }
 	};
-	Map<p_LocalControllerAuth,int64_t> m_localControllerAuthorizations;
+
+	Map<p_LocalControllerAuth, int64_t> m_localControllerAuthorizations;
 	Mutex m_localControllerAuthorizations_l;
 
 	// Locally joined networks by network ID.
-	Map< uint64_t,SharedPtr<Network> > m_networks;
+	Map<uint64_t, SharedPtr<Network> > m_networks;
 	RWMutex m_networks_l;
 
 	// These are local interface addresses that have been configured via the API
 	// and can be pushed to other nodes.
-	Vector< ZT_InterfaceAddress > m_localInterfaceAddresses;
+	Vector<ZT_InterfaceAddress> m_localInterfaceAddresses;
 	Mutex m_localInterfaceAddresses_m;
 
 	// This is locked while running processBackgroundTasks().

+ 1 - 1
node/Path.cpp

@@ -17,7 +17,7 @@
 
 namespace ZeroTier {
 
-bool Path::send(const RuntimeEnvironment *const RR,void *const tPtr,const void *const data,const unsigned int len,const int64_t now) noexcept
+bool Path::send(const RuntimeEnvironment *const RR, void *const tPtr, const void *const data, const unsigned int len, const int64_t now) noexcept
 {
 	if (RR->node->putPacket(tPtr, m_localSocket, m_addr, data, len)) {
 		m_lastOut = now;

+ 20 - 13
node/Path.hpp

@@ -26,7 +26,7 @@ namespace ZeroTier {
 
 class RuntimeEnvironment;
 
-template<unsigned int MF,unsigned int MFP,unsigned int GCT,unsigned int GCS,typename P>
+template<unsigned int MF, unsigned int MFP, unsigned int GCT, unsigned int GCS, typename P>
 class Defragmenter;
 
 /**
@@ -37,11 +37,12 @@ class Path
 	friend class SharedPtr<Path>;
 
 	// Allow defragmenter to access fragment-in-flight info stored in Path for performance reasons.
-	template<unsigned int MF,unsigned int MFP,unsigned int GCT,unsigned int GCS,typename P>
-	friend class Defragmenter;
+	template<unsigned int MF, unsigned int MFP, unsigned int GCT, unsigned int GCS, typename P>
+	friend
+	class Defragmenter;
 
 public:
-	ZT_INLINE Path(const int64_t l,const InetAddress &r) noexcept :
+	ZT_INLINE Path(const int64_t l, const InetAddress &r) noexcept:
 		m_localSocket(l),
 		m_lastIn(0),
 		m_lastOut(0),
@@ -60,7 +61,7 @@ public:
 	 * @param now Current time
 	 * @return True if transport reported success
 	 */
-	bool send(const RuntimeEnvironment *RR,void *tPtr,const void *data,unsigned int len,int64_t now) noexcept;
+	bool send(const RuntimeEnvironment *RR, void *tPtr, const void *data, unsigned int len, int64_t now) noexcept;
 
 	/**
 	 * Explicitly update last sent time
@@ -68,7 +69,7 @@ public:
 	 * @param now Time of send
 	 * @param bytes Bytes sent
 	 */
-	ZT_INLINE void sent(const int64_t now,const unsigned int bytes) noexcept
+	ZT_INLINE void sent(const int64_t now, const unsigned int bytes) noexcept
 	{
 		m_lastOut.store(now);
 		m_outMeter.log(now, bytes);
@@ -80,7 +81,7 @@ public:
 	 * @param now Time of receive
 	 * @param bytes Bytes received
 	 */
-	ZT_INLINE void received(const int64_t now,const unsigned int bytes) noexcept
+	ZT_INLINE void received(const int64_t now, const unsigned int bytes) noexcept
 	{
 		m_lastIn.store(now);
 		m_inMeter.log(now, bytes);
@@ -104,34 +105,40 @@ public:
 	/**
 	 * @return Latency in milliseconds or -1 if unknown
 	 */
-	ZT_INLINE int latency() const noexcept { return m_latency; }
+	ZT_INLINE int latency() const noexcept
+	{ return m_latency; }
 
 	/**
 	 * Check path aliveness
 	 *
 	 * @param now Current time
 	 */
-	ZT_INLINE bool alive(const int64_t now) const noexcept { return ((now - m_lastIn.load()) < ZT_PATH_ALIVE_TIMEOUT); }
+	ZT_INLINE bool alive(const int64_t now) const noexcept
+	{ return ((now - m_lastIn.load()) < ZT_PATH_ALIVE_TIMEOUT); }
 
 	/**
 	 * @return Physical address
 	 */
-	ZT_INLINE const InetAddress &address() const noexcept { return m_addr; }
+	ZT_INLINE const InetAddress &address() const noexcept
+	{ return m_addr; }
 
 	/**
 	 * @return Local socket as specified by external code
 	 */
-	ZT_INLINE int64_t localSocket() const noexcept { return m_localSocket; }
+	ZT_INLINE int64_t localSocket() const noexcept
+	{ return m_localSocket; }
 
 	/**
 	 * @return Last time we received anything
 	 */
-	ZT_INLINE int64_t lastIn() const noexcept { return m_lastIn.load(); }
+	ZT_INLINE int64_t lastIn() const noexcept
+	{ return m_lastIn.load(); }
 
 	/**
 	 * @return Last time we sent something
 	 */
-	ZT_INLINE int64_t lastOut() const noexcept { return m_lastOut.load(); }
+	ZT_INLINE int64_t lastOut() const noexcept
+	{ return m_lastOut.load(); }
 
 private:
 	const int64_t m_localSocket;

+ 17 - 51
node/Peer.cpp

@@ -129,10 +129,6 @@ void Peer::received(
 				// Re-prioritize paths to include the new one.
 				m_prioritizePaths(now);
 
-				// Remember most recently learned paths for future bootstrap attempts on restart.
-				Endpoint pathEndpoint(path->address());
-				m_bootstrap[pathEndpoint.type()] = pathEndpoint;
-
 				RR->t->learnedNewPath(tPtr, 0x582fabdd, packetId, m_id, path->address(), old);
 			} else {
 				path->sent(now,hello(tPtr,path->localSocket(),path->address(),now));
@@ -227,8 +223,6 @@ void Peer::pulse(void *const tPtr,const int64_t now,const bool isRoot)
 {
 	RWMutex::Lock l(m_lock);
 
-	// Determine if we need to send a full HELLO because we are refreshing ephemeral
-	// keys or it's simply been too long.
 	bool needHello = false;
 	if ( (m_vProto >= 11) && ( ((now - m_ephemeralPairTimestamp) >= (ZT_SYMMETRIC_KEY_TTL / 2)) || ((m_ephemeralKeys[0])&&(m_ephemeralKeys[0]->odometer() >= (ZT_SYMMETRIC_KEY_TTL_MESSAGES / 2))) ) ) {
 		m_ephemeralPair.generate();
@@ -237,9 +231,6 @@ void Peer::pulse(void *const tPtr,const int64_t now,const bool isRoot)
 		needHello = true;
 	}
 
-	// If we have no active paths and none queued to try, attempt any
-	// old paths we have cached in m_bootstrap or that external code
-	// supplies to the core via the optional API callback.
 	if (m_tryQueue.empty()&&(m_alivePathCount == 0)) {
 		InetAddress addr;
 		if (RR->node->externalPathLookup(tPtr, m_id, -1, addr)) {
@@ -248,27 +239,10 @@ void Peer::pulse(void *const tPtr,const int64_t now,const bool isRoot)
 				sent(now,m_sendProbe(tPtr,-1,addr,nullptr,0,now));
 			}
 		}
-
-		if (!m_bootstrap.empty()) {
-			unsigned int tryAtIndex = (unsigned int)Utils::random() % (unsigned int)m_bootstrap.size();
-			for(SortedMap< Endpoint::Type,Endpoint >::const_iterator i(m_bootstrap.begin());i != m_bootstrap.end();++i) {
-				if (tryAtIndex > 0) {
-					--tryAtIndex;
-				} else {
-					if ((i->second.isInetAddr())&&(!i->second.ip().ipsEqual(addr))) {
-						RR->t->tryingNewPath(tPtr, 0x0a009444, m_id, i->second.ip(), InetAddress::NIL, 0, 0, Identity::NIL);
-						sent(now,m_sendProbe(tPtr,-1,i->second.ip(),nullptr,0,now));
-						break;
-					}
-				}
-			}
-		}
 	}
 
-	// Sort paths and forget expired ones.
 	m_prioritizePaths(now);
 
-	// Attempt queued endpoints if they don't overlap with paths.
 	if (!m_tryQueue.empty()) {
 		for(int k=0;k<ZT_NAT_T_MAX_QUEUED_ATTEMPTS_PER_PULSE;++k) {
 			// This is a global circular pointer that iterates through the list of
@@ -398,7 +372,7 @@ void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddres
 	}
 	m_alivePathCount = pc;
 	while (pc < ZT_MAX_PEER_NETWORK_PATHS)
-		m_paths[pc].zero();
+		m_paths[pc++].zero();
 }
 
 bool Peer::directlyConnected(int64_t now)
@@ -464,17 +438,14 @@ int Peer::marshal(uint8_t data[ZT_PEER_MARSHAL_SIZE_MAX]) const noexcept
 		return -1;
 	p += s;
 
-	s = m_locator.marshal(data + p);
-	if (s <= 0)
-		return s;
-	p += s;
-
-	data[p++] = (uint8_t)m_bootstrap.size();
-	for(std::map< Endpoint::Type,Endpoint >::const_iterator i(m_bootstrap.begin());i != m_bootstrap.end();++i) { // NOLINT(modernize-loop-convert,hicpp-use-auto,modernize-use-auto)
-		s = i->second.marshal(data + p);
+	if (m_locator) {
+		data[p++] = 1;
+		s = m_locator->marshal(data + p);
 		if (s <= 0)
-			return -1;
+			return s;
 		p += s;
+	} else {
+		data[p++] = 0;
 	}
 
 	Utils::storeBigEndian(data + p,(uint16_t)m_vProto);
@@ -528,24 +499,19 @@ int Peer::unmarshal(const uint8_t *restrict data,const int len) noexcept
 		Utils::burn(k,sizeof(k));
 	}
 
-	s = m_locator.unmarshal(data + p, len - p);
-	if (s < 0)
-		return s;
-	p += s;
-
-	if (p >= len)
-		return -1;
-	const unsigned int bootstrapCount = data[p++];
-	if (bootstrapCount > ZT_MAX_PEER_NETWORK_PATHS)
-		return -1;
-	m_bootstrap.clear();
-	for(unsigned int i=0;i<bootstrapCount;++i) {
-		Endpoint tmp;
-		s = tmp.unmarshal(data + p,len - p);
+	if (data[p] == 0) {
+		++p;
+		m_locator.zero();
+	} else if (data[p] == 1) {
+		++p;
+		Locator *const loc = new Locator();
+		s = loc->unmarshal(data + p, len - p);
+		m_locator.set(loc);
 		if (s < 0)
 			return s;
 		p += s;
-		m_bootstrap[tmp.type()] = tmp;
+	} else {
+		return -1;
 	}
 
 	if ((p + 10) > len)

+ 80 - 63
node/Peer.hpp

@@ -32,7 +32,7 @@
 #include "SymmetricKey.hpp"
 #include "Containers.hpp"
 
-#define ZT_PEER_MARSHAL_SIZE_MAX (1 + ZT_ADDRESS_LENGTH + ZT_SYMMETRIC_KEY_SIZE + ZT_IDENTITY_MARSHAL_SIZE_MAX + ZT_LOCATOR_MARSHAL_SIZE_MAX + 1 + (ZT_MAX_PEER_NETWORK_PATHS * ZT_ENDPOINT_MARSHAL_SIZE_MAX) + (2*4) + 2)
+#define ZT_PEER_MARSHAL_SIZE_MAX (1 + ZT_ADDRESS_LENGTH + ZT_SYMMETRIC_KEY_SIZE + ZT_IDENTITY_MARSHAL_SIZE_MAX + 1 + ZT_LOCATOR_MARSHAL_SIZE_MAX + 1 + (ZT_MAX_PEER_NETWORK_PATHS * ZT_ENDPOINT_MARSHAL_SIZE_MAX) + (2*4) + 2)
 
 #define ZT_PEER_DEDUP_BUFFER_SIZE 1024
 #define ZT_PEER_DEDUP_BUFFER_MASK 1023U
@@ -47,6 +47,7 @@ class Topology;
 class Peer
 {
 	friend class SharedPtr<Peer>;
+
 	friend class Topology;
 
 public:
@@ -73,22 +74,44 @@ public:
 	/**
 	 * @return This peer's ZT address (short for identity().address())
 	 */
-	ZT_INLINE Address address() const noexcept { return m_id.address(); }
+	ZT_INLINE Address address() const noexcept
+	{ return m_id.address(); }
 
 	/**
 	 * @return This peer's identity
 	 */
-	ZT_INLINE const Identity &identity() const noexcept { return m_id; }
+	ZT_INLINE const Identity &identity() const noexcept
+	{ return m_id; }
 
 	/**
-	 * @return Copy of current locator
+	 * @return Current locator or NULL if no locator is known
 	 */
-	ZT_INLINE Locator locator() const noexcept
+	ZT_INLINE const SharedPtr<const Locator> &locator() const noexcept
 	{
 		RWMutex::RLock l(m_lock);
 		return m_locator;
 	}
 
+	/**
+	 * Set or update peer locator
+	 *
+	 * This checks the locator's timestamp against the current locator and
+	 * replace it if newer.
+	 *
+	 * SECURITY: note that this does NOT validate the locator's signature
+	 * or structural validity. This MUST be done before calling this.
+	 *
+	 * @param loc Locator update
+	 * @return New locator or previous if it was not replaced.
+	 */
+	ZT_INLINE SharedPtr<const Locator> setLocator(const SharedPtr<const Locator> &loc) noexcept
+	{
+		RWMutex::Lock l(m_lock);
+		if ((loc) && ((!m_locator) || (m_locator->timestamp() < loc->timestamp())))
+			m_locator = loc;
+		return m_locator;
+	}
+
 	/**
 	 * Log receipt of an authenticated packet
 	 *
@@ -117,7 +140,7 @@ public:
 	 * @param now Current time
 	 * @param bytes Number of bytes written
 	 */
-	ZT_INLINE void sent(const int64_t now,const unsigned int bytes) noexcept
+	ZT_INLINE void sent(const int64_t now, const unsigned int bytes) noexcept
 	{
 		m_lastSend = now;
 		m_outMeter.log(now, bytes);
@@ -129,7 +152,7 @@ public:
 	 * @param now Current time
 	 * @param bytes Number of bytes relayed
 	 */
-	ZT_INLINE void relayed(const int64_t now,const unsigned int bytes) noexcept
+	ZT_INLINE void relayed(const int64_t now, const unsigned int bytes) noexcept
 	{
 		m_relayedMeter.log(now, bytes);
 	}
@@ -163,10 +186,10 @@ public:
 	 * @param len Length in bytes
 	 * @param via Path over which to send data (may or may not be an already-learned path for this peer)
 	 */
-	ZT_INLINE void send(void *tPtr,int64_t now,const void *data,unsigned int len,const SharedPtr<Path> &via) noexcept
+	ZT_INLINE void send(void *tPtr, int64_t now, const void *data, unsigned int len, const SharedPtr<Path> &via) noexcept
 	{
-		via->send(RR,tPtr,data,len,now);
-		sent(now,len);
+		via->send(RR, tPtr, data, len, now);
+		sent(now, len);
 	}
 
 	/**
@@ -180,7 +203,7 @@ public:
 	 * @param data Data to send
 	 * @param len Length in bytes
 	 */
-	void send(void *tPtr,int64_t now,const void *data,unsigned int len) noexcept;
+	void send(void *tPtr, int64_t now, const void *data, unsigned int len) noexcept;
 
 	/**
 	 * Send a HELLO to this peer at a specified physical address.
@@ -191,7 +214,7 @@ public:
 	 * @param now Current time
 	 * @return Number of bytes sent
 	 */
-	unsigned int hello(void *tPtr,int64_t localSocket,const InetAddress &atAddress,int64_t now);
+	unsigned int hello(void *tPtr, int64_t localSocket, const InetAddress &atAddress, int64_t now);
 
 	/**
 	 * Ping this peer if needed and/or perform other periodic tasks.
@@ -200,7 +223,7 @@ public:
 	 * @param now Current time
 	 * @param isRoot True if this peer is a root
 	 */
-	void pulse(void *tPtr,int64_t now,bool isRoot);
+	void pulse(void *tPtr, int64_t now, bool isRoot);
 
 	/**
 	 * Attempt to contact this peer at a given endpoint.
@@ -210,7 +233,7 @@ public:
 	 * @param ep Endpoint to attempt to contact
 	 * @param bfg1024 Use BFG1024 brute force symmetric NAT busting algorithm if applicable
 	 */
-	void contact(void *tPtr,int64_t now,const Endpoint &ep,bool breakSymmetricBFG1024);
+	void contact(void *tPtr, int64_t now, const Endpoint &ep, bool breakSymmetricBFG1024);
 
 	/**
 	 * Reset paths within a given IP scope and address family
@@ -225,35 +248,13 @@ public:
 	 * @param inetAddressFamily Family e.g. AF_INET
 	 * @param now Current time
 	 */
-	void resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddressFamily,int64_t now);
-
-	/**
-	 * @return All currently memorized bootstrap endpoints
-	 */
-	ZT_INLINE FCV<Endpoint,ZT_MAX_PEER_NETWORK_PATHS> bootstrap() const noexcept
-	{
-		RWMutex::RLock l(m_lock);
-		FCV<Endpoint,ZT_MAX_PEER_NETWORK_PATHS> r;
-		for(SortedMap<Endpoint::Type,Endpoint>::const_iterator i(m_bootstrap.begin());i != m_bootstrap.end();++i) // NOLINT(hicpp-use-auto,modernize-use-auto,modernize-loop-convert)
-			r.push_back(i->second);
-		return r;
-	}
-
-	/**
-	 * Set bootstrap endpoint
-	 *
-	 * @param ep Bootstrap endpoint
-	 */
-	ZT_INLINE void setBootstrap(const Endpoint &ep) noexcept
-	{
-		RWMutex::Lock l(m_lock);
-		m_bootstrap[ep.type()] = ep;
-	}
+	void resetWithinScope(void *tPtr, InetAddress::IpScope scope, int inetAddressFamily, int64_t now);
 
 	/**
 	 * @return Time of last receive of anything, whether direct or relayed
 	 */
-	ZT_INLINE int64_t lastReceive() const noexcept { return m_lastReceive; }
+	ZT_INLINE int64_t lastReceive() const noexcept
+	{ return m_lastReceive; }
 
 	/**
 	 * @return Average latency of all direct paths or -1 if no direct paths or unknown
@@ -263,7 +264,7 @@ public:
 		int ltot = 0;
 		int lcnt = 0;
 		RWMutex::RLock l(m_lock);
-		for(unsigned int i=0;i < m_alivePathCount;++i) {
+		for (unsigned int i = 0;i < m_alivePathCount;++i) {
 			int lat = m_paths[i]->latency();
 			if (lat > 0) {
 				ltot += lat;
@@ -347,19 +348,28 @@ public:
 	 * @param vmin Minor version
 	 * @param vrev Revision
 	 */
-	ZT_INLINE void setRemoteVersion(unsigned int vproto,unsigned int vmaj,unsigned int vmin,unsigned int vrev) noexcept
+	ZT_INLINE void setRemoteVersion(unsigned int vproto, unsigned int vmaj, unsigned int vmin, unsigned int vrev) noexcept
 	{
-		m_vProto = (uint16_t)vproto;
-		m_vMajor = (uint16_t)vmaj;
-		m_vMinor = (uint16_t)vmin;
-		m_vRevision = (uint16_t)vrev;
+		m_vProto = (uint16_t) vproto;
+		m_vMajor = (uint16_t) vmaj;
+		m_vMinor = (uint16_t) vmin;
+		m_vRevision = (uint16_t) vrev;
 	}
 
-	ZT_INLINE unsigned int remoteVersionProtocol() const noexcept { return m_vProto; }
-	ZT_INLINE unsigned int remoteVersionMajor() const noexcept { return m_vMajor; }
-	ZT_INLINE unsigned int remoteVersionMinor() const noexcept { return m_vMinor; }
-	ZT_INLINE unsigned int remoteVersionRevision() const noexcept { return m_vRevision; }
-	ZT_INLINE bool remoteVersionKnown() const noexcept { return ((m_vMajor > 0) || (m_vMinor > 0) || (m_vRevision > 0)); }
+	ZT_INLINE unsigned int remoteVersionProtocol() const noexcept
+	{ return m_vProto; }
+
+	ZT_INLINE unsigned int remoteVersionMajor() const noexcept
+	{ return m_vMajor; }
+
+	ZT_INLINE unsigned int remoteVersionMinor() const noexcept
+	{ return m_vMinor; }
+
+	ZT_INLINE unsigned int remoteVersionRevision() const noexcept
+	{ return m_vRevision; }
+
+	ZT_INLINE bool remoteVersionKnown() const noexcept
+	{ return ((m_vMajor > 0) || (m_vMinor > 0) || (m_vRevision > 0)); }
 
 	/**
 	 * @return True if there is at least one alive direct path
@@ -380,9 +390,12 @@ public:
 
 	// NOTE: peer marshal/unmarshal only saves/restores the identity, locator, most
 	// recent bootstrap address, and version information.
-	static constexpr int marshalSizeMax() noexcept { return ZT_PEER_MARSHAL_SIZE_MAX; }
+	static constexpr int marshalSizeMax() noexcept
+	{ return ZT_PEER_MARSHAL_SIZE_MAX; }
+
 	int marshal(uint8_t data[ZT_PEER_MARSHAL_SIZE_MAX]) const noexcept;
-	int unmarshal(const uint8_t *restrict data,int len) noexcept;
+
+	int unmarshal(const uint8_t *restrict data, int len) noexcept;
 
 	/**
 	 * Rate limit gate for inbound WHOIS requests
@@ -413,7 +426,7 @@ public:
 	 */
 	ZT_INLINE bool rateGateProbeRequest(const int64_t now) noexcept
 	{
-		if((now - m_lastProbeReceived) > ZT_PEER_PROBE_RESPONSE_RATE_LIMIT) {
+		if ((now - m_lastProbeReceived) > ZT_PEER_PROBE_RESPONSE_RATE_LIMIT) {
 			m_lastProbeReceived = now;
 			return true;
 		}
@@ -432,12 +445,14 @@ public:
 	ZT_INLINE bool deduplicateIncomingPacket(const uint64_t packetId) noexcept
 	{
 		// TODO: should take instance ID into account too, but this isn't fully wired.
-		return m_dedup[Utils::hash32((uint32_t)packetId) & ZT_PEER_DEDUP_BUFFER_MASK].exchange(packetId) == packetId;
+		return m_dedup[Utils::hash32((uint32_t) packetId) & ZT_PEER_DEDUP_BUFFER_MASK].exchange(packetId) == packetId;
 	}
 
 private:
 	void m_prioritizePaths(int64_t now);
-	unsigned int m_sendProbe(void *tPtr,int64_t localSocket,const InetAddress &atAddress,const uint16_t *ports,unsigned int numPorts,int64_t now);
+
+	unsigned int m_sendProbe(void *tPtr, int64_t localSocket, const InetAddress &atAddress, const uint16_t *ports, unsigned int numPorts, int64_t now);
+
 	void m_deriveSecondaryIdentityKeys() noexcept;
 
 	ZT_INLINE SharedPtr<SymmetricKey> m_key() noexcept
@@ -468,7 +483,7 @@ private:
 	SharedPtr<SymmetricKey> m_ephemeralKeys[2];
 
 	Identity m_id;
-	Locator m_locator;
+	SharedPtr<const Locator> m_locator;
 
 	// the last time something was sent or received from this peer (direct or indirect).
 	std::atomic<int64_t> m_lastReceive;
@@ -500,24 +515,26 @@ private:
 	// Direct paths sorted in descending order of preference.
 	SharedPtr<Path> m_paths[ZT_MAX_PEER_NETWORK_PATHS];
 
-	// For SharedPtr<>
-	std::atomic<int> __refCount;
-
 	// Number of paths current alive (number of non-NULL entries in _paths).
 	unsigned int m_alivePathCount;
 
-	// Remembered addresses by endpoint type (std::map is smaller for only a few keys).
-	SortedMap<Endpoint::Type,Endpoint> m_bootstrap;
+	// For SharedPtr<>
+	std::atomic<int> __refCount;
 
 	// Addresses recieved via PUSH_DIRECT_PATHS etc. that we are scheduled to try.
 	struct p_TryQueueItem
 	{
-		ZT_INLINE p_TryQueueItem() : target(), ts(0), breakSymmetricBFG1024(false) {}
-		ZT_INLINE p_TryQueueItem(const int64_t now, const Endpoint &t, const bool bfg) : target(t), ts(now), breakSymmetricBFG1024(bfg) {}
+		ZT_INLINE p_TryQueueItem() : target(), ts(0), breakSymmetricBFG1024(false)
+		{}
+
+		ZT_INLINE p_TryQueueItem(const int64_t now, const Endpoint &t, const bool bfg) : target(t), ts(now), breakSymmetricBFG1024(bfg)
+		{}
+
 		Endpoint target;
 		int64_t ts;
 		bool breakSymmetricBFG1024;
 	};
+
 	List<p_TryQueueItem> m_tryQueue;
 	List<p_TryQueueItem>::iterator m_tryQueuePtr; // loops over _tryQueue like a circular buffer
 

+ 42 - 17
node/Revocation.hpp

@@ -40,9 +40,11 @@ class Revocation : public Credential
 	friend class Credential;
 
 public:
-	static constexpr ZT_CredentialType credentialType() noexcept { return ZT_CREDENTIAL_TYPE_REVOCATION; }
+	static constexpr ZT_CredentialType credentialType() noexcept
+	{ return ZT_CREDENTIAL_TYPE_REVOCATION; }
 
-	ZT_INLINE Revocation() noexcept { memoryZero(this); } // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
+	ZT_INLINE Revocation() noexcept
+	{ memoryZero(this); } // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
 
 	/**
 	 * @param i ID (arbitrary for revocations, currently random)
@@ -53,7 +55,7 @@ public:
 	 * @param tgt Target node whose credential(s) are being revoked
 	 * @param ct Credential type being revoked
 	 */
-	ZT_INLINE Revocation(const uint32_t i,const uint64_t nwid,const uint32_t cid,const uint64_t thr,const uint64_t fl,const Address &tgt,const ZT_CredentialType ct) noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
+	ZT_INLINE Revocation(const uint32_t i, const uint64_t nwid, const uint32_t cid, const uint64_t thr, const uint64_t fl, const Address &tgt, const ZT_CredentialType ct) noexcept: // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
 		m_id(i),
 		m_credentialId(cid),
 		m_networkId(nwid),
@@ -66,16 +68,35 @@ public:
 	{
 	}
 
-	ZT_INLINE uint32_t id() const noexcept { return m_id; }
-	ZT_INLINE uint32_t credentialId() const noexcept { return m_credentialId; }
-	ZT_INLINE uint64_t networkId() const noexcept { return m_networkId; }
-	ZT_INLINE int64_t threshold() const noexcept { return m_threshold; }
-	ZT_INLINE const Address &target() const noexcept { return m_target; }
-	ZT_INLINE const Address &signer() const noexcept { return m_signedBy; }
-	ZT_INLINE ZT_CredentialType typeBeingRevoked() const noexcept { return m_type; }
-	ZT_INLINE const uint8_t *signature() const noexcept { return m_signature; }
-	ZT_INLINE unsigned int signatureLength() const noexcept { return m_signatureLength; }
-	ZT_INLINE bool fastPropagate() const noexcept { return ((m_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0); }
+	ZT_INLINE uint32_t id() const noexcept
+	{ return m_id; }
+
+	ZT_INLINE uint32_t credentialId() const noexcept
+	{ return m_credentialId; }
+
+	ZT_INLINE uint64_t networkId() const noexcept
+	{ return m_networkId; }
+
+	ZT_INLINE int64_t threshold() const noexcept
+	{ return m_threshold; }
+
+	ZT_INLINE const Address &target() const noexcept
+	{ return m_target; }
+
+	ZT_INLINE const Address &signer() const noexcept
+	{ return m_signedBy; }
+
+	ZT_INLINE ZT_CredentialType typeBeingRevoked() const noexcept
+	{ return m_type; }
+
+	ZT_INLINE const uint8_t *signature() const noexcept
+	{ return m_signature; }
+
+	ZT_INLINE unsigned int signatureLength() const noexcept
+	{ return m_signatureLength; }
+
+	ZT_INLINE bool fastPropagate() const noexcept
+	{ return ((m_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0); }
 
 	/**
 	 * @param signer Signing identity, must have private key
@@ -89,11 +110,15 @@ public:
 	 * @param RR Runtime environment to provide for peer lookup, etc.
 	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
 	 */
-	ZT_INLINE Credential::VerifyResult verify(const RuntimeEnvironment *RR,void *tPtr) const noexcept { return _verify(RR,tPtr,*this); }
+	ZT_INLINE Credential::VerifyResult verify(const RuntimeEnvironment *RR, void *tPtr) const noexcept
+	{ return _verify(RR, tPtr, *this); }
+
+	static constexpr int marshalSizeMax() noexcept
+	{ return ZT_REVOCATION_MARSHAL_SIZE_MAX; }
+
+	int marshal(uint8_t data[ZT_REVOCATION_MARSHAL_SIZE_MAX], bool forSign = false) const noexcept;
 
-	static constexpr int marshalSizeMax() noexcept { return ZT_REVOCATION_MARSHAL_SIZE_MAX; }
-	int marshal(uint8_t data[ZT_REVOCATION_MARSHAL_SIZE_MAX],bool forSign = false) const noexcept;
-	int unmarshal(const uint8_t *restrict data,int len) noexcept;
+	int unmarshal(const uint8_t *restrict data, int len) noexcept;
 
 private:
 	uint32_t m_id;

+ 10 - 5
node/SHA512.cpp

@@ -251,19 +251,24 @@ void HMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],const void *msg,const u
 void KBKDFHMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],const char label,const char context,const uint32_t iter,uint8_t out[ZT_SYMMETRIC_KEY_SIZE])
 {
 	uint8_t kbkdfMsg[13];
-	uint8_t kbuf[48];
+
 	Utils::storeBigEndian<uint32_t>(kbkdfMsg,(uint32_t)iter);
+
 	kbkdfMsg[4] = (uint8_t)'Z';
 	kbkdfMsg[5] = (uint8_t)'T'; // preface our labels with something ZT-specific
 	kbkdfMsg[6] = (uint8_t)label;
 	kbkdfMsg[7] = 0;
+
 	kbkdfMsg[8] = (uint8_t)context;
+
+	// Output key length: 384 bits (as 32-bit big-endian value)
 	kbkdfMsg[9] = 0;
 	kbkdfMsg[10] = 0;
-	kbkdfMsg[11] = 1;
-	kbkdfMsg[12] = 0; // key length: 256 bits as big-endian 32-bit value
-	HMACSHA384(key,&kbkdfMsg,sizeof(kbkdfMsg),kbuf);
-	Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(out,kbuf);
+	kbkdfMsg[11] = 0x01;
+	kbkdfMsg[12] = 0x80;
+
+	static_assert(ZT_SYMMETRIC_KEY_SIZE == ZT_SHA384_DIGEST_SIZE,"sizeof(out) != ZT_SHA384_DIGEST_SIZE");
+	HMACSHA384(key,&kbkdfMsg,sizeof(kbkdfMsg),out);
 }
 
 } // namespace ZeroTier

+ 22 - 10
node/SelfAwareness.hpp

@@ -23,6 +23,7 @@
 namespace ZeroTier {
 
 class Identity;
+
 class RuntimeEnvironment;
 
 /**
@@ -45,7 +46,7 @@ public:
 	 * @param trusted True if this peer is trusted as an authority to inform us of external address changes
 	 * @param now Current time
 	 */
-	void iam(void *tPtr,const Identity &reporter,int64_t receivedOnLocalSocket,const InetAddress &reporterPhysicalAddress,const InetAddress &myPhysicalAddress,bool trusted,int64_t now);
+	void iam(void *tPtr, const Identity &reporter, int64_t receivedOnLocalSocket, const InetAddress &reporterPhysicalAddress, const InetAddress &myPhysicalAddress, bool trusted, int64_t now);
 
 	/**
 	 * Clean up database periodically
@@ -60,7 +61,7 @@ public:
 	 * @param now Current time
 	 * @return Map of count to IP/port representing how many endpoints reported each address
 	 */
-	MultiMap<unsigned int,InetAddress> externalAddresses(int64_t now) const;
+	MultiMap<unsigned int, InetAddress> externalAddresses(int64_t now) const;
 
 private:
 	struct p_PhySurfaceKey
@@ -70,13 +71,21 @@ private:
 		InetAddress reporterPhysicalAddress;
 		InetAddress::IpScope scope;
 
-		ZT_INLINE p_PhySurfaceKey() noexcept {}
-		ZT_INLINE p_PhySurfaceKey(const Address &r, const int64_t rol, const InetAddress &ra, InetAddress::IpScope s) noexcept : reporter(r), receivedOnLocalSocket(rol), reporterPhysicalAddress(ra), scope(s) {}
+		ZT_INLINE p_PhySurfaceKey() noexcept
+		{}
+
+		ZT_INLINE p_PhySurfaceKey(const Address &r, const int64_t rol, const InetAddress &ra, InetAddress::IpScope s) noexcept: reporter(r), receivedOnLocalSocket(rol), reporterPhysicalAddress(ra), scope(s)
+		{}
+
+		ZT_INLINE unsigned long hashCode() const noexcept
+		{ return ((unsigned long) reporter.toInt() + (unsigned long) receivedOnLocalSocket + (unsigned long) scope); }
 
-		ZT_INLINE unsigned long hashCode() const noexcept { return ((unsigned long)reporter.toInt() + (unsigned long)receivedOnLocalSocket + (unsigned long)scope); }
+		ZT_INLINE bool operator==(const p_PhySurfaceKey &k) const noexcept
+		{ return ((reporter == k.reporter) && (receivedOnLocalSocket == k.receivedOnLocalSocket) && (reporterPhysicalAddress == k.reporterPhysicalAddress) && (scope == k.scope)); }
+
+		ZT_INLINE bool operator!=(const p_PhySurfaceKey &k) const noexcept
+		{ return (!(*this == k)); }
 
-		ZT_INLINE bool operator==(const p_PhySurfaceKey &k) const noexcept { return ((reporter == k.reporter) && (receivedOnLocalSocket == k.receivedOnLocalSocket) && (reporterPhysicalAddress == k.reporterPhysicalAddress) && (scope == k.scope)); }
-		ZT_INLINE bool operator!=(const p_PhySurfaceKey &k) const noexcept { return (!(*this == k)); }
 		ZT_INLINE bool operator<(const p_PhySurfaceKey &k) const noexcept
 		{
 			if (reporter < k.reporter) {
@@ -102,12 +111,15 @@ private:
 		uint64_t ts;
 		bool trusted;
 
-		ZT_INLINE p_PhySurfaceEntry() noexcept : mySurface(), ts(0), trusted(false) {}
-		ZT_INLINE p_PhySurfaceEntry(const InetAddress &a, const uint64_t t) noexcept : mySurface(a), ts(t), trusted(false) {}
+		ZT_INLINE p_PhySurfaceEntry() noexcept: mySurface(), ts(0), trusted(false)
+		{}
+
+		ZT_INLINE p_PhySurfaceEntry(const InetAddress &a, const uint64_t t) noexcept: mySurface(a), ts(t), trusted(false)
+		{}
 	};
 
 	const RuntimeEnvironment *RR;
-	Map< p_PhySurfaceKey,p_PhySurfaceEntry > m_phy;
+	Map<p_PhySurfaceKey, p_PhySurfaceEntry> m_phy;
 	Mutex m_phy_l;
 };
 

+ 1 - 6
node/Tests.cpp

@@ -13,6 +13,7 @@
 
 #include "Tests.h"
 
+//#define ZT_ENABLE_TESTS
 #ifdef ZT_ENABLE_TESTS
 
 #include "Constants.hpp"
@@ -40,12 +41,6 @@
 #include "Fingerprint.hpp"
 #include "Containers.hpp"
 
-#include <cstdint>
-#include <cstring>
-#include <cstdio>
-#include <ctime>
-#include <set>
-
 #ifdef __UNIX_LIKE__
 #include <unistd.h>
 #include <sys/time.h>

+ 34 - 28
node/Topology.cpp

@@ -31,14 +31,16 @@ Topology::Topology(const RuntimeEnvironment *renv, void *tPtr) :
 			if ((l > 0)&&(id)) {
 				if ((drem -= l) <= 0)
 					break;
-				Locator loc;
-				l = loc.unmarshal(dptr, drem);
-				if ((l > 0)&&(loc)) {
-					m_roots[id] = loc;
+				Locator *const loc = new Locator();
+				l = loc->unmarshal(dptr, drem);
+				if (l > 0) {
+					m_roots[id].set(loc);
 					dptr += l;
 					ZT_SPEW("loaded root %s", id.address().toString().c_str());
 					if ((drem -= l) <= 0)
 						break;
+				} else {
+					delete loc;
 				}
 			}
 		}
@@ -71,9 +73,9 @@ struct p_RootSortComparisonOperator
 	}
 };
 
-bool Topology::addRoot(void *const tPtr, const Identity &id, const Locator &loc)
+bool Topology::addRoot(void *const tPtr, const Identity &id, const SharedPtr<const Locator> &loc)
 {
-	if ((id == RR->identity) || (!id) || (!loc) || (!loc.verify(id)) || (!id.locallyValidate()))
+	if ((id == RR->identity) || (!id) || (!loc) || (!loc->verify(id)) || (!id.locallyValidate()))
 		return false;
 	RWMutex::Lock l1(m_peers_l);
 	m_roots[id] = loc;
@@ -82,20 +84,17 @@ bool Topology::addRoot(void *const tPtr, const Identity &id, const Locator &loc)
 	return true;
 }
 
-bool Topology::removeRoot(void *const tPtr, const Fingerprint &fp)
+bool Topology::removeRoot(void *const tPtr, Address address)
 {
-	const bool hashIsZero = !fp.haveHash();
 	RWMutex::Lock l1(m_peers_l);
 	for(Vector< SharedPtr<Peer> >::const_iterator r(m_rootPeers.begin());r!=m_rootPeers.end();++r) {
-		if ((*r)->address() == fp.address()) {
-			if ((hashIsZero)||(fp == (*r)->identity().fingerprint())) {
-				Map<Identity,Locator>::iterator rr(m_roots.find((*r)->identity()));
-				if (rr != m_roots.end()) {
-					m_roots.erase(rr);
-					m_updateRootPeers(tPtr);
-					m_writeRootList(tPtr);
-					return true;
-				}
+		if ((*r)->address() == address) {
+			Map< Identity,SharedPtr<const Locator> >::iterator rr(m_roots.find((*r)->identity()));
+			if (rr != m_roots.end()) {
+				m_roots.erase(rr);
+				m_updateRootPeers(tPtr);
+				m_writeRootList(tPtr);
+				return true;
 			}
 		}
 	}
@@ -174,11 +173,11 @@ void Topology::m_writeRootList(void *tPtr)
 	uint8_t *const roots = (uint8_t *)malloc((ZT_IDENTITY_MARSHAL_SIZE_MAX + ZT_LOCATOR_MARSHAL_SIZE_MAX + 2) * m_roots.size());
 	if (roots) { // sanity check
 		int p = 0;
-		for (Map<Identity,Locator>::const_iterator r(m_roots.begin());r!=m_roots.end();++r) {
+		for (Map< Identity,SharedPtr<const Locator> >::const_iterator r(m_roots.begin());r!=m_roots.end();++r) {
 			int pp = r->first.marshal(roots + p, false);
 			if (pp > 0) {
 				p += pp;
-				pp = r->second.marshal(roots + p);
+				pp = r->second->marshal(roots + p);
 				if (pp > 0)
 					p += pp;
 			}
@@ -195,16 +194,23 @@ void Topology::m_updateRootPeers(void *tPtr)
 {
 	// assumes m_peers_l is locked for write
 	Vector< SharedPtr<Peer> > rp;
-	for (Map<Identity,Locator>::iterator r(m_roots.begin());r!=m_roots.end();++r) {
-		Map< Address,SharedPtr<Peer> >::iterator p(m_peers.find(r->first.address()));
-		if ((p == m_peers.end())||(p->second->identity() != r->first)) {
-			SharedPtr<Peer> np(new Peer(RR));
-			np->init(r->first);
-			m_peers[r->first.address()] = np;
-			rp.push_back(np);
-		} else {
-			rp.push_back(p->second);
+	for (Map< Identity,SharedPtr<const Locator> >::iterator r(m_roots.begin());r!=m_roots.end();++r) {
+		Map< Address,SharedPtr<Peer> >::iterator pp(m_peers.find(r->first.address()));
+		SharedPtr<Peer> p;
+		if (pp != m_peers.end())
+			p = pp->second;
+
+		if (!p)
+			m_loadCached(tPtr, r->first.address(), p);
+
+		if ((!p) || (p->identity() != r->first)) {
+			p.set(new Peer(RR));
+			p->init(r->first);
+			m_peers[r->first.address()] = p;
 		}
+
+		p->setLocator(r->second);
+		rp.push_back(p);
 	}
 	m_rootPeers.swap(rp);
 	std::sort(m_rootPeers.begin(), m_rootPeers.end(), p_RootSortComparisonOperator());

+ 4 - 4
node/Topology.hpp

@@ -193,16 +193,16 @@ public:
 	 * @param loc Root locator
 	 * @return True if identity and locator are valid and root was added / updated
 	 */
-	bool addRoot(void *tPtr,const Identity &id,const Locator &loc);
+	bool addRoot(void *tPtr,const Identity &id,const SharedPtr<const Locator> &loc);
 
 	/**
 	 * Remove a root server's identity from the root server set
 	 *
 	 * @param tPtr Thread pointer
-	 * @param fp Root identity
+	 * @param address Root address
 	 * @return True if root found and removed, false if not found
 	 */
-	bool removeRoot(void *tPtr,const Fingerprint &fp);
+	bool removeRoot(void *tPtr, Address address);
 
 	/**
 	 * Sort roots in ascending order of apparent latency
@@ -250,7 +250,7 @@ private:
 	RWMutex m_peers_l; // locks m_peers, m_roots, and m_rootPeers
 	Map< uint64_t,SharedPtr<Path> > m_paths;
 	Map< Address,SharedPtr<Peer> > m_peers;
-	Map< Identity,Locator > m_roots;
+	Map< Identity,SharedPtr<const Locator> > m_roots;
 	Vector< SharedPtr<Peer> > m_rootPeers;
 };