 min2) {
			return -1;
		} else {
			if (rev1 > rev2) {
				return 1;
			} else if (rev1 < rev2) {
				return -1;
			} else {
				if (b1 > b2) {
					return 1;
				} else if (b1 < b2) {
					return -1;
				} else {
					return 0;
				}
			}
		}
	}
}

SoftwareUpdater::SoftwareUpdater(Node &node,const std::string &homePath) :
	_node(node),
	_lastCheckTime(0),
	_homePath(homePath),
	_channel(ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL),
	_distLog((FILE *)0),
	_latestValid(false),
	_downloadLength(0)
{
	OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str());
}

SoftwareUpdater::~SoftwareUpdater()
{
	if (_distLog)
		fclose(_distLog);
}

void SoftwareUpdater::setUpdateDistribution(bool distribute)
{
	_dist.clear();
	if (distribute) {
		_distLog = fopen((_homePath + ZT_PATH_SEPARATOR_S "update-dist.log").c_str(),"a");

		const std::string udd(_homePath + ZT_PATH_SEPARATOR_S "update-dist.d");
		const std::vector<std::string> ud(OSUtils::listDirectory(udd.c_str()));
		for(std::vector<std::string>::const_iterator u(ud.begin());u!=ud.end();++u) {
			// Each update has a companion.json file describing it. Other files are ignored.
			if ((u->length() > 5)&&(u->substr(u->length() - 5,5) == ".json")) {

				std::string buf;
				if (OSUtils::readFile((udd + ZT_PATH_SEPARATOR_S + *u).c_str(),buf)) {
					try {
						_D d;
						d.meta = OSUtils::jsonParse(buf); // throws on invalid JSON

						// If update meta is called e.g. foo.exe.json, then foo.exe is the update itself
						const std::string binPath(udd + ZT_PATH_SEPARATOR_S + u->substr(0,u->length() - 5));
						const std::string metaHash(OSUtils::jsonBinFromHex(d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH]));
						if ((metaHash.length() == 64)&&(OSUtils::readFile(binPath.c_str(),d.bin))) {
							std::array<uint8_t,64> sha512;
							SHA512(sha512.data(),d.bin.data(),(unsigned int)d.bin.length());
							if (!memcmp(sha512.data(),metaHash.data(),64)) { // double check that hash in JSON is correct
								d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE] = d.bin.length(); // override with correct value -- setting this in meta json is optional
								std::array<uint8_t,16> shakey;
								memcpy(shakey.data(),sha512.data(),16);
								_dist[shakey] = d;
								if (_distLog) {
									fprintf(_distLog,".......... INIT: DISTRIBUTING %s (%u bytes)" ZT_EOL_S,binPath.c_str(),(unsigned int)d.bin.length());
									fflush(_distLog);
								}
							}
						}
					} catch (... ) {} // ignore bad meta JSON, etc.
				}

			}
		}
	} else {
		if (_distLog) {
			fclose(_distLog);
			_distLog = (FILE *)0;
		}
	}
}

void SoftwareUpdater::handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len)
{
	if (!len) return;
	const MessageVerb v = (MessageVerb)reinterpret_cast<const uint8_t *>(data)[0];
	try {
		switch(v) {

			case VERB_GET_LATEST:
			case VERB_LATEST: {
				nlohmann::json req = OSUtils::jsonParse(std::string(reinterpret_cast<const char *>(data) + 1,len - 1)); // throws on invalid JSON
				if (req.is_object()) {
					const unsigned int rvMaj = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0);
					const unsigned int rvMin = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0);
					const unsigned int rvRev = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0);
					const unsigned int rvBld = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0);
					const unsigned int rvPlatform = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0);
					const unsigned int rvArch = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE],0);
					const unsigned int rvVendor = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0);
					const std::string rvChannel(OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],""));

					if (v == VERB_GET_LATEST) {

						if (!_dist.empty()) {
							const nlohmann::json *latest = (const nlohmann::json *)0;
							const std::string expectedSigner = OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY],"");
							unsigned int bestVMaj = rvMaj;
							unsigned int bestVMin = rvMin;
							unsigned int bestVRev = rvRev;
							unsigned int bestVBld = rvBld;
							for(std::map< std::array<uint8_t,16>,_D >::const_iterator d(_dist.begin());d!=_dist.end();++d) {
								// The arch field in update description.json files can be an array for e.g. multi-arch update files
								const nlohmann::json &dvArch2 = d->second.meta[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE];
								std::vector<unsigned int> dvArch;
								if (dvArch2.is_array()) {
									for(unsigned long i=0;i<dvArch2.size();++i)
										dvArch.push_back((unsigned int)OSUtils::jsonInt(dvArch2[i],0));
								} else {
									dvArch.push_back((unsigned int)OSUtils::jsonInt(dvArch2,0));
								}

								if ((OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0) == rvPlatform)&&
								    (std::find(dvArch.begin(),dvArch.end(),rvArch)!= dvArch.end())&&
								    (OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0) == rvVendor)&&
								    (OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],"") == rvChannel)&&
								    (OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == expectedSigner)) {
									const unsigned int dvMaj = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0);
									const unsigned int dvMin = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0);
									const unsigned int dvRev = (unsigned int)OSUtils::jsonInt(