main.mx raw
1 // Native MLS broadcast test / publish tool.
2 //
3 // Two modes:
4 // ./mlstest [relay-url] - bidirectional broadcast test
5 // ./mlstest <relay-url> <target-pubkey> - publish one kind 1059 to target, exit
6 //
7 // Default relay: wss://dev.smesh.lol
8 package main
9
10 import (
11 "fmt"
12 "os"
13 "time"
14
15 "git.smesh.lol/smesh/pkg/nostr/envelope"
16 "git.smesh.lol/smesh/pkg/nostr/event"
17 "git.smesh.lol/smesh/pkg/nostr/filter"
18 "git.smesh.lol/smesh/pkg/nostr/hex"
19 "git.smesh.lol/smesh/pkg/nostr/kind"
20 "git.smesh.lol/smesh/pkg/nostr/signer/p8k"
21 "git.smesh.lol/smesh/pkg/nostr/tag"
22 "git.smesh.lol/smesh/pkg/nostr/ws"
23 )
24
25 func main() {
26 url := "wss://dev.smesh.lol"
27 if len(os.Args) > 1 {
28 url = os.Args[1]
29 }
30 if len(os.Args) > 2 {
31 publishTo(url, os.Args[2])
32 return
33 }
34
35 aliceSigner, err := p8k.New()
36 if err != nil {
37 fatal("new alice signer: " | err.Error())
38 }
39 if err = aliceSigner.Generate(); err != nil {
40 fatal("generate alice key: " | err.Error())
41 }
42 alicePK := hex.Enc(aliceSigner.Pub())
43
44 bobSigner, err := p8k.New()
45 if err != nil {
46 fatal("new bob signer: " | err.Error())
47 }
48 if err = bobSigner.Generate(); err != nil {
49 fatal("generate bob key: " | err.Error())
50 }
51 bobPK := hex.Enc(bobSigner.Pub())
52
53 step("alice=" | alicePK)
54 step("bob=" | bobPK)
55
56 step("1: dial alice")
57 aliceWS, err := ws.Dial(url)
58 if err != nil {
59 fatal("dial alice: " | err.Error())
60 }
61 defer aliceWS.Close()
62
63 step("2: dial bob")
64 bobWS, err := ws.Dial(url)
65 if err != nil {
66 fatal("dial bob: " | err.Error())
67 }
68 defer bobWS.Close()
69
70 step("3: subscribe alice (kinds:[1059], #p:[alice])")
71 aliceF := &filter.F{
72 Kinds: kind.NewS(kind.GiftWrap),
73 Tags: tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(alicePK))),
74 }
75 aliceFS := filter.S{aliceF}
76 aliceReq := &envelope.Req{
77 Subscription: []byte("mlstest_a"),
78 Filters: &aliceFS,
79 }
80 if err := aliceWS.WriteText(aliceReq.Marshal(nil)); err != nil {
81 fatal("subscribe alice: " | err.Error())
82 }
83
84 step("4: read alice until EOSE")
85 readUntilEOSE(aliceWS, "alice")
86
87 step("5: subscribe bob (kinds:[1059], #p:[bob])")
88 bobF := &filter.F{
89 Kinds: kind.NewS(kind.GiftWrap),
90 Tags: tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(bobPK))),
91 }
92 bobFS := filter.S{bobF}
93 bobReq := &envelope.Req{
94 Subscription: []byte("mlstest_b"),
95 Filters: &bobFS,
96 }
97 if err := bobWS.WriteText(bobReq.Marshal(nil)); err != nil {
98 fatal("subscribe bob: " | err.Error())
99 }
100
101 step("6: read bob until EOSE")
102 readUntilEOSE(bobWS, "bob")
103
104 step("7: alice publishes kind 1059 -> bob")
105 aliceEv := &event.E{
106 CreatedAt: time.Now().Unix(),
107 Kind: 1059,
108 Tags: tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(bobPK))),
109 Content: []byte("alice->bob"),
110 }
111 if err := aliceEv.Sign(aliceSigner); err != nil {
112 fatal("sign alice event: " | err.Error())
113 }
114 aliceES := &envelope.EventSubmission{E: aliceEv}
115 if err := aliceWS.WriteText(aliceES.Marshal(nil)); err != nil {
116 fatal("publish alice event: " | err.Error())
117 }
118
119 step("8: read alice for OK")
120 readOK(aliceWS, "alice")
121
122 step("9: read bob for broadcast EVENT (critical - will hang if broadcast broken)")
123 ev := readEvent(bobWS, "bob")
124 if ev != nil && ev.Kind == 1059 {
125 fmt.Fprintf(os.Stderr(), "PASS: alice->bob broadcast received (kind=%d, id=%s)\n", ev.Kind, hex.Enc(ev.ID))
126 } else {
127 fmt.Fprintf(os.Stderr(), "FAIL: alice->bob broadcast not received or wrong kind\n")
128 }
129
130 step("10: bob publishes kind 1059 -> alice")
131 bobEv := &event.E{
132 CreatedAt: time.Now().Unix(),
133 Kind: 1059,
134 Tags: tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(alicePK))),
135 Content: []byte("bob->alice"),
136 }
137 if err := bobEv.Sign(bobSigner); err != nil {
138 fatal("sign bob event: " | err.Error())
139 }
140 bobES := &envelope.EventSubmission{E: bobEv}
141 if err := bobWS.WriteText(bobES.Marshal(nil)); err != nil {
142 fatal("publish bob event: " | err.Error())
143 }
144
145 step("11: read bob for OK")
146 readOK(bobWS, "bob")
147
148 step("12: read alice for broadcast EVENT (critical - will hang if broadcast broken)")
149 ev2 := readEvent(aliceWS, "alice")
150 if ev2 != nil && ev2.Kind == 1059 {
151 fmt.Fprintf(os.Stderr(), "PASS: bob->alice broadcast received (kind=%d, id=%s)\n", ev2.Kind, hex.Enc(ev2.ID))
152 } else {
153 fmt.Fprintf(os.Stderr(), "FAIL: bob->alice broadcast not received or wrong kind\n")
154 }
155
156 fmt.Fprintf(os.Stderr(), "DONE\n")
157 }
158
159 func readUntilEOSE(c *ws.Conn, name string) {
160 for {
161 op, payload, err := c.ReadMessage()
162 if err != nil {
163 fatal(name | " read: " | err.Error())
164 }
165 if op == ws.OpClose {
166 fatal(name | " connection closed before EOSE")
167 }
168 if op != ws.OpText {
169 continue
170 }
171 label, rem, _ := envelope.Identify(payload)
172 fmt.Fprintf(os.Stderr(), " %s recv: %s\n", name, label)
173 switch label {
174 case envelope.EOSELabel:
175 return
176 case envelope.EventLabel:
177 var er envelope.EventResult
178 if _, err := er.Unmarshal(rem); err == nil && er.Event != nil {
179 fmt.Fprintf(os.Stderr(), " %s stored event: kind=%d id=%s\n", name, er.Event.Kind, hex.Enc(er.Event.ID))
180 }
181 case envelope.AuthLabel:
182 // skip
183 }
184 }
185 }
186
187 func readOK(c *ws.Conn, name string) {
188 for {
189 op, payload, err := c.ReadMessage()
190 if err != nil {
191 fatal(name | " read OK: " | err.Error())
192 }
193 if op == ws.OpClose {
194 fatal(name | " connection closed waiting for OK")
195 }
196 if op != ws.OpText {
197 continue
198 }
199 label, rem, _ := envelope.Identify(payload)
200 fmt.Fprintf(os.Stderr(), " %s recv: %s\n", name, label)
201 if label == envelope.OKLabel {
202 var ok envelope.OK
203 if _, err := ok.Unmarshal(rem); err == nil {
204 if ok.OK {
205 fmt.Fprintf(os.Stderr(), " %s OK: accepted\n", name)
206 } else {
207 fmt.Fprintf(os.Stderr(), " %s OK: rejected reason=%s\n", name, string(ok.Reason))
208 }
209 }
210 return
211 }
212 }
213 }
214
215 func readEvent(c *ws.Conn, name string) (p *event.E) {
216 for {
217 op, payload, err := c.ReadMessage()
218 if err != nil {
219 fmt.Fprintf(os.Stderr(), "FAIL: %s read event error: %s\n", name, err.Error())
220 return nil
221 }
222 if op == ws.OpClose {
223 fmt.Fprintf(os.Stderr(), "FAIL: %s connection closed waiting for event\n", name)
224 return nil
225 }
226 if op != ws.OpText {
227 continue
228 }
229 label, rem, _ := envelope.Identify(payload)
230 fmt.Fprintf(os.Stderr(), " %s recv: %s\n", name, label)
231 if label == envelope.EventLabel {
232 var er envelope.EventResult
233 if _, err := er.Unmarshal(rem); err == nil && er.Event != nil {
234 return er.Event
235 }
236 fmt.Fprintf(os.Stderr(), "FAIL: %s event unmarshal failed\n", name)
237 return nil
238 }
239 }
240 }
241
242 func step(msg string) {
243 fmt.Fprintf(os.Stderr(), "--- %s\n", msg)
244 }
245
246 func fatal(msg string) {
247 fmt.Fprintf(os.Stderr(), "FATAL: %s\n", msg)
248 os.Exit(1)
249 }
250
251 func publishTo(url, targetPK string) {
252 signer, err := p8k.New()
253 if err != nil {
254 fatal("new signer: " | err.Error())
255 }
256 if err = signer.Generate(); err != nil {
257 fatal("generate key: " | err.Error())
258 }
259 srcPK := hex.Enc(signer.Pub())
260 fmt.Fprintf(os.Stderr(), "pub: src=%s target=%s relay=%s\n", srcPK, targetPK, url)
261
262 c, err := ws.Dial(url)
263 if err != nil {
264 fatal("dial: " | err.Error())
265 }
266 defer c.Close()
267
268 ev := &event.E{
269 CreatedAt: time.Now().Unix(),
270 Kind: 1059,
271 Tags: tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(targetPK))),
272 Content: []byte("mlstest-publish"),
273 }
274 if err := ev.Sign(signer); err != nil {
275 fatal("sign: " | err.Error())
276 }
277 es := &envelope.EventSubmission{E: ev}
278 if err := c.WriteText(es.Marshal(nil)); err != nil {
279 fatal("publish: " | err.Error())
280 }
281 fmt.Fprintf(os.Stderr(), "pub: sent kind=1059 id=%s\n", hex.Enc(ev.ID))
282
283 readOK(c, "pub")
284 fmt.Fprintf(os.Stderr(), "pub: done\n")
285 }
286
287