code wiki / _hdl_build / nx_connect_contacts_gate.nx
nx_connect_contacts_gate.nx source
↩ module page · 161 lines · 8533 B
1// nx_connect_contacts_gate.nx -- proves the CONNECT contact list (the "we don't even have a contact list"
2// basic): add/list/remove keyed to the signed-in identity, set semantics (idempotent, no self-add, bounded),
3// the page is auth-gated (signed-out shows a prompt, never someone's list), the "has an end-to-end key"
4// badge is driven live by the shared pubkey directory, and the list SURVIVES a serialize->deserialize round
5// trip (so it persists across restarts). license_tier: ORIGINAL expect_exit: 0
6import "nx_connect_accounts.nx" // acc_serialize / acc_deserialize + cs_* (transitive)
7
8const CG_BUF: i64 = 262144
9
10func cg_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
11func cg_check(pass: i64, label: *u8, fails: *i64) -> i64 {
12 cg_w(" " as *u8); cg_w(label); cg_w(": " as *u8)
13 if pass==1 { cg_w("PASS\n" as *u8) } else { cg_w("FAIL\n" as *u8); fails[0]=fails[0]+1 }
14 return 0
15}
16func cg_has(buf: *u8, n: i64, needle: *u8) -> i64 {
17 let nl: i64 = cs_slen(needle)
18 if nl==0 { return 0 }
19 var i: i64=0
20 while i+nl<=n { var k: i64=0; var hit: i64=1; while k<nl { if buf[i+k]!=needle[k] { hit=0; k=nl } else { k=k+1 } } if hit==1 { return 1 } i=i+1 }
21 return 0
22}
23func cg_count(buf: *u8, n: i64, needle: *u8) -> i64 {
24 let nl: i64 = cs_slen(needle)
25 if nl==0 { return 0 }
26 var hits: i64=0
27 var i: i64=0
28 while i+nl<=n { var k: i64=0; var hit: i64=1; while k<nl { if buf[i+k]!=needle[k] { hit=0; k=nl } else { k=k+1 } } if hit==1 { hits=hits+1; i=i+nl } else { i=i+1 } }
29 return hits
30}
31func cg_get(dst: *u8, path: *u8) -> i64 {
32 var o: i64 = cs_cat(dst, 0, "GET " as *u8)
33 o = cs_cat(dst, o, path)
34 o = cs_cat(dst, o, " HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
35 return o
36}
37func cg_post(dst: *u8, path: *u8, bodys: *u8) -> i64 {
38 let bl: i64 = cs_slen(bodys)
39 var o: i64 = cs_cat(dst, 0, "POST " as *u8)
40 o = cs_cat(dst, o, path)
41 o = cs_cat(dst, o, " HTTP/1.1\r\nHost: x\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8)
42 o = cs_catn(dst, o, bl)
43 o = cs_cat(dst, o, "\r\n\r\n" as *u8)
44 o = cs_cat(dst, o, bodys)
45 return o
46}
47
48func main() -> i64 {
49 let fails: *i64 = sys_mmap(16) as *i64
50 fails[0]=0
51 let req: *u8 = sys_mmap(65536)
52 let out: *u8 = sys_mmap(CG_BUF)
53 let ctx: *i64 = cs_world_new()
54 cg_w("=== nx_connect_contacts_gate -- identity-keyed contact list ===\n" as *u8)
55
56 // ---- T1: signed OUT -> the contacts page prompts to sign in, never shows a list ----
57 ctx[CS_AUTH]=0; ctx[CS_HANDLE]=0
58 var rn: i64 = cg_get(req, "/connect/contacts" as *u8)
59 var n: i64 = cs_handle(ctx, req, rn, out, CG_BUF)
60 var t1: i64=1
61 if cg_has(out, n, "Sign in to keep a list" as *u8)==0 { t1=0 }
62 if cg_has(out, n, "action=\"/connect/addcontact\"" as *u8)==1 { t1=0 } // no add form when signed out
63 cg_check(t1, "T1 signed-out /contacts prompts sign-in, exposes no list or add form" as *u8, fails)
64
65 // ---- sign in as 'vera' for the rest ----
66 ctx[CS_AUTH]=1; ctx[CS_HANDLE]="vera\x00" as *u8 as i64
67
68 // ---- T2: an add POST is REFUSED while signed out (auth gate on the mutation, not just the page) ----
69 var t2:i64=1
70 ctx[CS_AUTH]=0
71 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=piotr" as *u8)
72 n = cs_handle(ctx, req, rn, out, CG_BUF)
73 if ctx[CS_NCONTACTS]!=0 { t2=0 } // signed-out add must not mutate
74 ctx[CS_AUTH]=1
75 cg_check(t2, "T2 add is auth-gated: a signed-out POST /addcontact does not mutate the list" as *u8, fails)
76
77 // ---- T3: add two contacts; the list renders them, empty state gone ----
78 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=piotr" as *u8)
79 n = cs_handle(ctx, req, rn, out, CG_BUF)
80 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=mia_k" as *u8)
81 n = cs_handle(ctx, req, rn, out, CG_BUF)
82 var t3:i64=1
83 if ctx[CS_NCONTACTS]!=2 { t3=0 }
84 if cg_has(out, n, "Contact added" as *u8)==0 { t3=0 }
85 if cg_has(out, n, ">@piotr</b>" as *u8)==0 { t3=0 }
86 if cg_has(out, n, ">@mia_k</b>" as *u8)==0 { t3=0 }
87 cg_check(t3, "T3 add two contacts: both render in the list; count is 2" as *u8, fails)
88
89 // ---- T4: set semantics -- a duplicate add does not grow the list; self-add is refused ----
90 var t4:i64=1
91 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=piotr" as *u8)
92 n = cs_handle(ctx, req, rn, out, CG_BUF)
93 if ctx[CS_NCONTACTS]!=2 { t4=0 }
94 if cg_has(out, n, "Already in your contacts" as *u8)==0 { t4=0 }
95 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=vera" as *u8) // yourself
96 n = cs_handle(ctx, req, rn, out, CG_BUF)
97 if ctx[CS_NCONTACTS]!=2 { t4=0 }
98 if cg_has(out, n, "That’s you" as *u8)==0 { t4=0 }
99 // an invalid handle is refused
100 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=Bad%20Name" as *u8)
101 n = cs_handle(ctx, req, rn, out, CG_BUF)
102 if ctx[CS_NCONTACTS]!=2 { t4=0 }
103 if cg_has(out, n, "not valid" as *u8)==0 { t4=0 }
104 cg_check(t4, "T4 set semantics: duplicate + self-add + invalid all refused; count stays 2" as *u8, fails)
105
106 // ---- T5: the 'has an end-to-end key' badge is driven LIVE by the shared directory ----
107 var t5:i64=1
108 let sh: *i64 = cs_shared_new()
109 cs_sh_pub_put(sh, "piotr" as *u8, "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a" as *u8)
110 ctx[CS_SH]=sh as i64
111 rn = cg_get(req, "/connect/contacts" as *u8)
112 n = cs_handle2(ctx, sh, req, rn, out, CG_BUF)
113 // piotr has a key -> "e2e ready"; mia_k does not -> "no key". Exactly one of each.
114 if cg_count(out, n, ">e2e ready<" as *u8)!=1 { t5=0 }
115 if cg_count(out, n, ">no key<" as *u8)!=1 { t5=0 }
116 cg_check(t5, "T5 e2e-key badge is live: exactly the contact with a published key shows 'e2e ready'" as *u8, fails)
117
118 // ---- T6: remove a contact -> compacted, count drops, banner shown ----
119 var t6:i64=1
120 rn = cg_post(req, "/connect/rmcontact" as *u8, "handle=piotr" as *u8)
121 n = cs_handle2(ctx, sh, req, rn, out, CG_BUF)
122 if ctx[CS_NCONTACTS]!=1 { t6=0 }
123 if cg_has(out, n, "Contact removed" as *u8)==0 { t6=0 }
124 if cg_has(out, n, ">@piotr</b>" as *u8)==1 { t6=0 }
125 if cg_has(out, n, ">@mia_k</b>" as *u8)==0 { t6=0 }
126 cg_check(t6, "T6 remove: the contact is gone, the rest remain, count is 1" as *u8, fails)
127
128 // ---- T7: PERSISTENCE -- serialize the world, deserialize into a FRESH world, contacts survive ----
129 var t7:i64=1
130 rn = cg_post(req, "/connect/addcontact" as *u8, "handle=lena" as *u8) // now {mia_k, lena}
131 n = cs_handle(ctx, req, rn, out, CG_BUF)
132 let snap: *u8 = sys_mmap(CG_BUF)
133 let sn: i64 = acc_serialize(ctx, 42, snap)
134 let ctx2: *i64 = cs_world_new()
135 if acc_deserialize(ctx2, snap, sn)!=1 { t7=0 }
136 if ctx2[CS_NCONTACTS]!=2 { t7=0 }
137 if cs_contact_find(ctx2, "mia_k" as *u8)<0 { t7=0 }
138 if cs_contact_find(ctx2, "lena" as *u8)<0 { t7=0 }
139 if cs_contact_find(ctx2, "piotr" as *u8)>=0 { t7=0 } // removed one stays removed
140 cg_check(t7, "T7 persistence: contacts survive serialize->deserialize into a fresh world (restart-safe)" as *u8, fails)
141
142 // ---- T8: back-compat -- a snapshot with NO contact tail still loads (old .cst), 0 contacts. The caller
143 // contract is world_reset (NCONTACTS=0) THEN deserialize -- cs_world_new does the reset -- so a tail-less
144 // snapshot leaves the default 0 standing (the optional-tail `if o+8<=n` guard never fires).
145 var t8:i64=1
146 let ctx3: *i64 = cs_world_new()
147 let old: *u8 = sys_mmap(CG_BUF)
148 let oldctx: *i64 = cs_world_new()
149 let oldn: i64 = acc_serialize(oldctx, 7, old) // oldctx has 0 contacts -> tail = just the count word 0
150 if acc_deserialize(ctx3, old, oldn - 8)!=1 { t8=0 } // chop the trailing count word -> tail absent
151 if ctx3[CS_NCONTACTS]!=0 { t8=0 }
152 cg_check(t8, "T8 back-compat: a snapshot without the contact tail loads with 0 contacts (additive-safe)" as *u8, fails)
153
154 cg_w(" fails=" as *u8)
155 let nb: *u8=sys_mmap(28); var m: i64=fails[0]; var kk: i64=0; if m==0{nb[0]=48 as u8;kk=1} while m>0{nb[kk]=(48+(m%10)) as u8;m=m/10;kk=kk+1} let bb: *u8=sys_mmap(28); var j: i64=0; while j<kk{bb[j]=nb[kk-1-j];j=j+1} sys_write(1,bb,kk)
156 cg_w("\n" as *u8)
157 if fails[0]==0 { cg_w("VERDICT: verdict=GREEN (identity-keyed contact list: auth-gated add/remove, set semantics, live e2e badge, persists)\n" as *u8); sys_exit(0) }
158 cg_w("VERDICT: verdict=RED\n" as *u8)
159 sys_exit(1)
160 return 1
161}