nx_ice_agent.nx source
↩ module page · 144 lines · 5900 B
1// nx_ice_agent.nx -- SOVEREIGN ICE AGENT state machine (RFC 8445 6), NishiLang, composing nx_ice.
2//
3// The orchestration on top of the check-exchange primitives: an agent holds its LOCAL candidates and the
4// peer's REMOTE candidates, forms candidate PAIRS (same component), orders them by candidate-pair priority
5// (RFC 8445 6.1.2.3, via the overflow-safe comparator), schedules connectivity checks highest-priority
6// first, records each check's result, and NOMINATES the best valid pair as the selected path. Pure logic
7// over the primitives -- the actual check send/recv is nx_ice's ic_build_check / ic_verify_* over UDP; this
8// module decides WHICH pair to check next and WHICH becomes the path. Zero third-party code.
9// license_tier: INDEPENDENT_REDERIVE genealogy_id: international-research-sources/ietf/rfc_8445
10import "nx_syscalls.nx"
11import "nx_ice.nx"
12const AG_MAGIC_4096: i64 = 4096
13
14const AG_MAX_LOCAL: i64 = 8
15const AG_MAX_REMOTE: i64 = 8
16const AG_MAX_PAIRS: i64 = 64
17
18// agent header (i64 slots)
19const AG_NLOCAL: i64 = 0
20const AG_NREMOTE: i64 = 1
21const AG_NPAIRS: i64 = 2
22const AG_CONTROLLING: i64 = 3
23const AG_NOMINATED: i64 = 4 // pair index of the SELECTED pair, or -1
24// candidate + pair tables (i64 offsets); each candidate = 4 slots (ip, port, prio, component),
25// each pair = 4 slots (local_idx, remote_idx, state, spare)
26const AG_LOCAL0: i64 = 8
27const AG_REMOTE0: i64 = 40 // AG_LOCAL0 + AG_MAX_LOCAL*4
28const AG_PAIRS0: i64 = 72 // AG_REMOTE0 + AG_MAX_REMOTE*4
29
30// pair states
31const AG_ST_WAITING: i64 = 0
32const AG_ST_INPROGRESS: i64 = 1
33const AG_ST_SUCCEEDED: i64 = 2
34const AG_ST_FAILED: i64 = 3
35const AG_ST_SELECTED: i64 = 4
36
37func ag_new(controlling: i64) -> *i64 {
38 let a: *i64 = sys_mmap(AG_MAGIC_4096) as *i64
39 a[AG_NLOCAL]=0; a[AG_NREMOTE]=0; a[AG_NPAIRS]=0
40 a[AG_CONTROLLING]=controlling; a[AG_NOMINATED]=0-1
41 return a
42}
43func ag_add_local(a: *i64, ip: i64, port: i64, prio: i64, comp: i64) -> i64 {
44 let i: i64 = a[AG_NLOCAL]
45 if i >= AG_MAX_LOCAL { return 0-1 }
46 let b: i64 = AG_LOCAL0 + i*4
47 a[b]=ip; a[b+1]=port; a[b+2]=prio; a[b+3]=comp
48 a[AG_NLOCAL]=i+1
49 return i
50}
51func ag_add_remote(a: *i64, ip: i64, port: i64, prio: i64, comp: i64) -> i64 {
52 let i: i64 = a[AG_NREMOTE]
53 if i >= AG_MAX_REMOTE { return 0-1 }
54 let b: i64 = AG_REMOTE0 + i*4
55 a[b]=ip; a[b+1]=port; a[b+2]=prio; a[b+3]=comp
56 a[AG_NREMOTE]=i+1
57 return i
58}
59func ag_local_prio(a: *i64, i: i64) -> i64 { return a[AG_LOCAL0 + i*4 + 2] }
60func ag_local_comp(a: *i64, i: i64) -> i64 { return a[AG_LOCAL0 + i*4 + 3] }
61func ag_remote_prio(a: *i64, i: i64) -> i64 { return a[AG_REMOTE0 + i*4 + 2] }
62func ag_remote_comp(a: *i64, i: i64) -> i64 { return a[AG_REMOTE0 + i*4 + 3] }
63func ag_pair_local(a: *i64, p: i64) -> i64 { return a[AG_PAIRS0 + p*4] }
64func ag_pair_remote(a: *i64, p: i64) -> i64 { return a[AG_PAIRS0 + p*4 + 1] }
65func ag_pair_state(a: *i64, p: i64) -> i64 { return a[AG_PAIRS0 + p*4 + 2] }
66
67// (G,D) for a pair: controlling agent -> G=local prio, D=remote prio; controlled -> swapped (RFC 8445 6.1.2.3)
68func ag_pair_G(a: *i64, p: i64) -> i64 { if a[AG_CONTROLLING]==1 { return ag_local_prio(a, ag_pair_local(a,p)) } return ag_remote_prio(a, ag_pair_remote(a,p)) }
69func ag_pair_D(a: *i64, p: i64) -> i64 { if a[AG_CONTROLLING]==1 { return ag_remote_prio(a, ag_pair_remote(a,p)) } return ag_local_prio(a, ag_pair_local(a,p)) }
70
71// swap two pair rows
72func ag_swap(a: *i64, p: i64, q: i64) -> i64 {
73 var k: i64 = 0
74 while k < 4 {
75 let tmp: i64 = a[AG_PAIRS0 + p*4 + k]
76 a[AG_PAIRS0 + p*4 + k] = a[AG_PAIRS0 + q*4 + k]
77 a[AG_PAIRS0 + q*4 + k] = tmp
78 k = k + 1
79 }
80 return 0
81}
82// form pairs (matching component) then sort HIGHEST candidate-pair priority first (selection sort; n<=64).
83func ag_form_pairs(a: *i64) -> i64 {
84 var np: i64 = 0
85 var li: i64 = 0
86 while li < a[AG_NLOCAL] {
87 var ri: i64 = 0
88 while ri < a[AG_NREMOTE] {
89 if ag_local_comp(a, li) == ag_remote_comp(a, ri) {
90 if np < AG_MAX_PAIRS {
91 let b: i64 = AG_PAIRS0 + np*4
92 a[b]=li; a[b+1]=ri; a[b+2]=AG_ST_WAITING; a[b+3]=0
93 np = np + 1
94 }
95 }
96 ri = ri + 1
97 }
98 li = li + 1
99 }
100 a[AG_NPAIRS] = np
101 // selection sort by ic_pair_cmp descending
102 var i: i64 = 0
103 while i < np {
104 var best: i64 = i
105 var j: i64 = i + 1
106 while j < np {
107 if ic_pair_cmp(ag_pair_G(a,j), ag_pair_D(a,j), ag_pair_G(a,best), ag_pair_D(a,best)) > 0 { best = j }
108 j = j + 1
109 }
110 if best != i { ag_swap(a, i, best) }
111 i = i + 1
112 }
113 return np
114}
115// the next pair to check = the highest-priority pair still WAITING; marks it IN-PROGRESS. -1 if none left.
116func ag_next_check(a: *i64) -> i64 {
117 var i: i64 = 0
118 while i < a[AG_NPAIRS] {
119 if ag_pair_state(a, i) == AG_ST_WAITING { a[AG_PAIRS0 + i*4 + 2] = AG_ST_INPROGRESS; return i }
120 i = i + 1
121 }
122 return 0-1
123}
124// record a check result: success -> SUCCEEDED, else FAILED.
125func ag_result(a: *i64, p: i64, success: i64) -> i64 {
126 if p < 0 { return 0-1 }
127 if p >= a[AG_NPAIRS] { return 0-1 }
128 if success == 1 { a[AG_PAIRS0 + p*4 + 2] = AG_ST_SUCCEEDED } else { a[AG_PAIRS0 + p*4 + 2] = AG_ST_FAILED }
129 return 0
130}
131// NOMINATE: the highest-priority SUCCEEDED pair becomes the selected path (pairs are priority-ordered, so
132// the first succeeded one is the best). Marks it SELECTED, records AG_NOMINATED, returns its index or -1.
133func ag_nominate(a: *i64) -> i64 {
134 var i: i64 = 0
135 while i < a[AG_NPAIRS] {
136 if ag_pair_state(a, i) == AG_ST_SUCCEEDED {
137 a[AG_PAIRS0 + i*4 + 2] = AG_ST_SELECTED
138 a[AG_NOMINATED] = i
139 return i
140 }
141 i = i + 1
142 }
143 return 0-1
144}