/*
 * A JavaScript class based model for the ZeroTier controller microservice API
 * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 *
 * --
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial closed-source software that incorporates or links
 * directly against ZeroTier software without disclosing the source code
 * of your own application.
 */
'use strict';
/**
 * @param {string} IP with optional /netmask|port section
 * @return 4, 6, or 0 if invalid
 */
function ipClassify(ip)
{
	if ((!ip)||(typeof ip !== 'string'))
		return 0;
	let ips = ip.split('/');
	if (ips.length > 0) {
		if (ips.length > 1) {
			if (ips[1].length === 0)
				return 0;
			for(let i=0;i 0) {
			for(let i=0;i 0) {
			for(let i=0;i= 0) {
			r += c;
			if (r.length === l)
				break;
		}
	}
	while (r.length < l)
		r = '0' + r;
	return r;
};
exports.formatZeroTierIdentifier = formatZeroTierIdentifier;
/**
 * Goes through a rule set array and makes sure it's valid, returning a canonicalized version
 *
 * @param {array[object]} rules Array of ZeroTier rules
 * @return New array of canonicalized rules
 * @throws {Error} Rule set is invalid
 */
function formatRuleSetArray(rules)
{
	let r = [];
	if ((rules)&&(Array.isArray(rules))) {
		for(let a=0;a= 0)
					this._authTokens[k] = exp;
			}
		}
		return this._authTokens;
	}
	get capabilities() { return this._capabilities; }
	set capabilities(c)
	{
		let ca = [];
		let ids = {};
		if ((c)&&(Array.isArray(c))) {
			for(let a=0;a= 0)&&(capId <= 0xffffffff)&&(!ids[capId])) {
						ids[capId] = true;
						let capDefault = !!cap['default'];
						let capRules = formatRuleSetArray(cap.rules);
						ca.push({
							id: capId,
							'default': capDefault,
							rules: capRules
						});
					}
				}
			}
		}
		ca.sort(function(a,b) {
			a = a.id;
			b = b.id;
			return ((a > b) ? 1 : ((a < b) ? -1 : 0));
		});
		this._capabilities = ca;
		return ca;
	}
	get ipAssignmentPools() { return this._ipAssignmentPools; }
	set ipAssignmentPools(ipp)
	{
		let pa = [];
		let ranges = {};
		if ((ipp)&&(Array.isArray(ipp))) {
			for(let a=0;a 0)&&(stype === ipClassify(end))&&(!ranges[start+'_'+end])) {
							ranges[start+'_'+end] = true;
							pa.push({ ipRangeStart: start, ipRangeEnd: end });
						}
					}
				}
			}
		}
		pa.sort(function(a,b) { return a.ipRangeStart.localeCompare(b.ipRangeStart); });
		this._ipAssignmentPools = pa;
		return pa;
	}
	get multicastLimit() { return this._multicastLimit; }
	set multicastLimit(n)
	{
		try {
			let nn = parseInt(n)||0;
			this._multicastLimit = (nn >= 0) ? nn : 0;
		} catch (e) {
			this._multicastLimit = 0;
		}
		return this._multicastLimit;
	}
	get routes() { return this._routes; }
	set routes(r)
	{
		let ra = [];
		let targets = {};
		if ((r)&&(Array.isArray(r))) {
			for(let a=0;a 0)&&((routeVia === null)||(ipClassify(routeVia) === rtt))&&(!targets[routeTarget])) {
						targets[routeTarget] = true;
						ra.push({ target: routeTarget, via: routeVia });
					}
				}
			}
		}
		ra.sort(function(a,b) { return a.routeTarget.localeCompare(b.routeTarget); });
		this._routes = ra;
		return ra;
	}
	get tags() { return this._tags; }
	set tags(t)
	{
		let ta = [];
		if ((t)&&(Array.isArray(t))) {
			for(let a=0;a= 0)||(tagId <= 0xffffffff)) {
						let tagDefault = tag.default;
						if (typeof tagDefault !== 'number')
							tagDefault = parseInt(tagDefault)||null;
						if ((tagDefault < 0)||(tagDefault > 0xffffffff))
							tagDefault = null;
						ta.push({ 'id': tagId, 'default': tagDefault });
					}
				}
			}
		}
		ta.sort(function(a,b) {
			a = a.id;
			b = b.id;
			return ((a > b) ? 1 : ((a < b) ? -1 : 0));
		});
		this._tags = ta;
		return ta;
	}
	get v4AssignMode() { return this._v4AssignMode; }
	set v4AssignMode(m)
	{
		if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) {
			this._v4AssignMode.zt = m.zt;
		} else if (m === 'zt') { // legacy
			this._v4AssignMode.zt = true;
		} else {
			this._v4AssignMode.zt = false;
		}
	}
	get v6AssignMode() { return this._v6AssignMode; }
	set v6AssignMode(m)
	{
		if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) {
			this._v6AssignMode.zt = m.zt;
			this._v6AssignMode.rfc4193 = m.rfc4193;
			this._v6AssignMode['6plane'] = m['6plane'];
		} else if (typeof m === 'string') { // legacy
			let ms = m.split(',');
			this._v6AssignMode.zt = false;
			this._v6AssignMode.rfc4193 = false;
			this._v6AssignMode['6plane'] = false;
			for(let i=0;i= 10000) mtu = 10000; // maximum as per ZT spec
		this._mtu = mtu;
	}
	get name() { return this._name; }
	set name(n)
	{
		if (typeof n === 'string')
			this._name = n;
		else if (typeof n === 'number')
			this._name = n.toString();
		else this._name = '';
	}
	get private() { return this._private; }
	set private(b)
	{
		// This is really meaningful for security, so make true unless explicitly set to false.
		this._private = (b !== false);
	}
	get activeMemberCount() { return this.__activeMemberCount; }
	get authorizedMemberCount() { return this.__authorizedMemberCount; }
	get totalMemberCount() { return this.__totalMemberCount; }
	get clock() { return this.__clock; }
	get creationTime() { return this.__creationTime; }
	get revision() { return this.__revision; }
	toJSONExcludeControllerGenerated()
	{
		return {
			id: this.id,
			objtype: 'network',
			nwid: this.nwid,
			authTokens: this.authTokens,
			capabilities: this.capabilities,
			ipAssignmentPools: this.ipAssignmentPools,
			multicastLimit: this.multicastLimit,
			routes: this.routes,
			tags: this.tags,
			v4AssignMode: this._v4AssignMode.toJSON(),
			v6AssignMode: this._v6AssignMode.toJSON(),
			rules: this.rules,
			enableBroadcast: this.enableBroadcast,
			mtu: this.mtu,
			name: this.name,
			'private': this['private']
		};
	}
	toJSON()
	{
		var j = this.toJSONExcludeControllerGenerated();
		j.activeMemberCount = this.activeMemberCount;
		j.authorizedMemberCount = this.authorizedMemberCount;
		j.totalMemberCount = this.totalMemberCount;
		j.clock = this.clock;
		j.creationTime = this.creationTime;
		j.revision = this.revision;
		return j;
	}
	clear()
	{
		this._id = '';
		this._authTokens = {};
		this._capabilities = [];
		this._ipAssignmentPools = [];
		this._multicastLimit = 32;
		this._routes = [];
		this._tags = [];
		this._v4AssignMode = new _V4AssignMode();
		this._v6AssignMode = new _v6AssignMode();
		this._rules = [];
		this._enableBroadcast = true;
		this._mtu = 2800;
		this._name = '';
		this._private = true;
		this.__activeMemberCount = 0;
		this.__authorizedMemberCount = 0;
		this.__totalMemberCount = 0;
		this.__clock = 0;
		this.__creationTime = 0;
		this.__revision = 0;
	}
	patch(obj)
	{
		if (obj instanceof Network)
			obj = obj.toJSON();
		if ((obj)&&(typeof obj === 'object')&&(!Array.isArray(obj))) {
			for(var k in obj) {
				try {
					switch(k) {
						case 'id':
						case 'authTokens':
						case 'capabilities':
						case 'ipAssignmentPools':
						case 'multicastLimit':
						case 'routes':
						case 'tags':
						case 'rules':
						case 'enableBroadcast':
						case 'mtu':
						case 'name':
						case 'private':
						case 'v4AssignMode':
						case 'v6AssignMode':
							this[k] = obj[k];
							break;
						case 'activeMemberCount':
						case 'authorizedMemberCount':
						case 'totalMemberCount':
						case 'clock':
						case 'creationTime':
						case 'revision':
							this['__'+k] = parseInt(obj[k])||0;
							break;
					}
				} catch (e) {}
			}
		}
	}
};
exports.Network = Network;
class Member
{
	constructor(obj)
	{
		this.clear();
		this.patch(obj);
	}
	get objtype() { return 'member'; }
	get id() { return this._id; }
	set id(x) { this._id = formatZeroTierIdentifier((typeof x === 'number') ? x.toString(16) : x,10); }
	get address() { return this._id; } // legacy
	get nwid() { return this._nwid; }
	set nwid(x) { this._nwid = formatZeroTierIdentifier(x,16); }
	get controllerId() { return this.nwid.substr(0,10); }
	get authorized() { return this._authorized; }
	set authorized(b) { this._authorized = (b === true); } // security critical so require explicit set to true
	get activeBridge() { return this._activeBridge; }
	set activeBridge(b) { this._activeBridge = !!b; }
	get capabilities() { return this._capabilities; }
	set capabilities(c)
	{
		let caps = {};
		let ca = [];
		if ((c)&&(Array.isArray(c))) {
			for(let a=0;a= 0)&&(capId <= 0xffffffff)&&(!caps[capId])) {
					caps[capId] = true;
					ca.push(capId);
				}
			}
		}
		ca.sort();
		this._capabilities = ca;
		return ca;
	}
	get identity() { return this._identity; }
	set identity(istr)
	{
		if ((istr)&&(typeof istr === 'string'))
			this._identity = istr;
		else this._identity = null;
	}
	get ipAssignments() { return this._ipAssignments; }
	set ipAssignments(ipa)
	{
		let ips = {};
		if ((ipa)&&(Array.isArray(ipa))) {
			for(let a=0;a 0)
					ips[ip] = true;
			}
		}
		this._ipAssignments = Object.keys(ips);
		this._ipAssignments.sort();
		return this._ipAssignments;
	}
	get noAutoAssignIps() { return this._noAutoAssignIps; }
	set noAutoAssignIps(b) { this._noAutoAssignIps = !!b; }
	get tags() { return this._tags; }
	set tags(t)
	{
		let ta = [];
		let pairs = {};
		if ((t)&&(Array.isArray(t))) {
			for(let a=0;a= 0)&&(tagId <= 0xffffffff)&&(tagValue >= 0)&&(tagValue <= 0xffffffff)&&(!pairs[pk])) {
						pairs[pk] = true;
						ta.push([ tagId,tagValue ]);
					}
				}
			}
		}
		ta.sort(function(a,b) {
			return ((a[0] < b[0]) ? -1 : ((a[0] > b[0]) ? 1 : 0));
		});
		this._tags = ta;
		return ta;
	}
	get creationTime() { return this.__creationTime; }
	get lastAuthorizedTime() { return this.__lastAuthorizedTime; }
	get lastAuthorizedCredentialType() { return this.__lastAuthorizedCredentialType; }
	get lastAuthorizedCredential() { return this.__lastAuthorizedCredential; }
	get lastDeauthorizedTime() { return this.__lastDeauthorizedTime; }
	get physicalAddr() { return this.__physicalAddr; }
	get revision() { return this.__revision; }
	get vMajor() { return this.__vMajor; }
	get vMinor() { return this.__vMinor; }
	get vRev() { return this.__vRev; }
	get vProto() { return this.__vProto; }
	toJSONExcludeControllerGenerated()
	{
		return {
			id: this.id,
			nwid: this.nwid,
			objtype: 'member',
			address: this.id,
			authorized: this.authorized,
			activeBridge: this.activeBridge,
			capabilities: this.capabilities,
			identity: this.identity,
			ipAssignments: this.ipAssignments,
			noAutoAssignIps: this.noAutoAssignIps,
			tags: this.tags
		};
	}
	toJSON()
	{
		let j = this.toJSONExcludeControllerGenerated();
		j.creationTime = this.creationTime;
		j.lastAuthorizedTime = this.lastAuthorizedTime;
		j.lastAuthorizedCredentialType = this.lastAuthorizedCredentialType;
		j.lastAuthorizedCredential = this.lastAuthorizedCredential;
		j.lastDeauthorizedTime = this.lastDeauthorizedTime;
		j.physicalAddr = this.physicalAddr;
		j.revision = this.revision;
		j.vMajor = this.vMajor;
		j.vMinor = this.vMinor;
		j.vRev = this.vRev;
		j.vProto = this.vProto;
		return j;
	}
	clear()
	{
		this._id = '';
		this._nwid = '';
		this._authorized = false;
		this._activeBridge = false;
		this._capabilities = [];
		this._identity = '';
		this._ipAssignments = [];
		this._noAutoAssignIps = false;
		this._tags = [];
		this.__creationTime = 0;
		this.__lastAuthorizedTime = 0;
		this.__lastAuthorizedCredentialType = null;
		this.__lastAuthorizedCredential = null;
		this.__lastDeauthorizedTime = 0;
		this.__physicalAddr = '';
		this.__revision = 0;
		this.__vMajor = 0;
		this.__vMinor = 0;
		this.__vRev = 0;
		this.__vProto = 0;
	}
	patch(obj)
	{
		if (obj instanceof Member)
			obj = obj.toJSON();
		if ((obj)&&(typeof obj === 'object')&&(!Array.isArray(obj))) {
			for(var k in obj) {
				try {
					switch(k) {
						case 'id':
						case 'nwid':
						case 'authorized':
						case 'activeBridge':
						case 'capabilities':
						case 'identity':
						case 'ipAssignments':
						case 'noAutoAssignIps':
						case 'tags':
							this[k] = obj[k];
							break;
						case 'creationTime':
						case 'lastAuthorizedTime':
						case 'lastAuthorizedCredentialType':
						case 'lastAuthorizedCredential':
						case 'lastDeauthorizedTime':
						case 'physicalAddr':
						case 'revision':
						case 'vMajor':
						case 'vMinor':
						case 'vRev':
						case 'vProto':
							this['__'+k] = parseInt(obj[k])||0;
							break;
					}
				} catch (e) {}
			}
		}
	}
};
exports.Member = Member;