1-- Prosody IM
2-- Copyright (C) 2011-2013 Florian Zeitz
3--
4-- This project is MIT/X11 licensed. Please see the
5-- COPYING file in the source package for more information.
6--
7
8-- This is used to sort destination addresses by preference
9-- during S2S connections.
10-- We can't hand this off to getaddrinfo, since it blocks
11
12local ip_commonPrefixLength = require"util.ip".commonPrefixLength
13
14local function commonPrefixLength(ipA, ipB)
15	local len = ip_commonPrefixLength(ipA, ipB);
16	return len < 64 and len or 64;
17end
18
19local function t_sort(t, comp)
20	for i = 1, (#t - 1) do
21		for j = (i + 1), #t do
22			local a, b = t[i], t[j];
23			if not comp(a,b) then
24				t[i], t[j] = b, a;
25			end
26		end
27	end
28end
29
30local function source(dest, candidates)
31	local function comp(ipA, ipB)
32		-- Rule 1: Prefer same address
33		if dest == ipA then
34			return true;
35		elseif dest == ipB then
36			return false;
37		end
38
39		-- Rule 2: Prefer appropriate scope
40		if ipA.scope < ipB.scope then
41			if ipA.scope < dest.scope then
42				return false;
43			else
44				return true;
45			end
46		elseif ipA.scope > ipB.scope then
47			if ipB.scope < dest.scope then
48				return true;
49			else
50				return false;
51			end
52		end
53
54		-- Rule 3: Avoid deprecated addresses
55		-- XXX: No way to determine this
56		-- Rule 4: Prefer home addresses
57		-- XXX: Mobility Address related, no way to determine this
58		-- Rule 5: Prefer outgoing interface
59		-- XXX: Interface to address relation. No way to determine this
60		-- Rule 6: Prefer matching label
61		if ipA.label == dest.label and ipB.label ~= dest.label then
62			return true;
63		elseif ipB.label == dest.label and ipA.label ~= dest.label then
64			return false;
65		end
66
67		-- Rule 7: Prefer temporary addresses (over public ones)
68		-- XXX: No way to determine this
69		-- Rule 8: Use longest matching prefix
70		if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then
71			return true;
72		else
73			return false;
74		end
75	end
76
77	t_sort(candidates, comp);
78	return candidates[1];
79end
80
81local function destination(candidates, sources)
82	local sourceAddrs = {};
83	local function comp(ipA, ipB)
84		local ipAsource = sourceAddrs[ipA];
85		local ipBsource = sourceAddrs[ipB];
86		-- Rule 1: Avoid unusable destinations
87		-- XXX: No such information
88		-- Rule 2: Prefer matching scope
89		if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then
90			return true;
91		elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then
92			return false;
93		end
94
95		-- Rule 3: Avoid deprecated addresses
96		-- XXX: No way to determine this
97		-- Rule 4: Prefer home addresses
98		-- XXX: Mobility Address related, no way to determine this
99		-- Rule 5: Prefer matching label
100		if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then
101			return true;
102		elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then
103			return false;
104		end
105
106		-- Rule 6: Prefer higher precedence
107		if ipA.precedence > ipB.precedence then
108			return true;
109		elseif ipA.precedence < ipB.precedence then
110			return false;
111		end
112
113		-- Rule 7: Prefer native transport
114		-- XXX: No way to determine this
115		-- Rule 8: Prefer smaller scope
116		if ipA.scope < ipB.scope then
117			return true;
118		elseif ipA.scope > ipB.scope then
119			return false;
120		end
121
122		-- Rule 9: Use longest matching prefix
123		if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then
124			return true;
125		elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then
126			return false;
127		end
128
129		-- Rule 10: Otherwise, leave order unchanged
130		return true;
131	end
132	for _, ip in ipairs(candidates) do
133		sourceAddrs[ip] = source(ip, sources);
134	end
135
136	t_sort(candidates, comp);
137	return candidates;
138end
139
140return {source = source,
141	destination = destination};
142