envelopes_sonic.go raw
1 //go:build sonic
2
3 package nostr
4
5 import (
6 "encoding/hex"
7 stdlibjson "encoding/json"
8 "fmt"
9 "unsafe"
10
11 "github.com/bytedance/sonic/ast"
12 )
13
14 type sonicVisitorPosition int
15
16 const (
17 inEnvelope sonicVisitorPosition = iota
18
19 inEvent
20 inReq
21 inOk
22 inEose
23 inCount
24 inAuth
25 inClose
26 inClosed
27 inNotice
28
29 inFilterObject
30 inEventObject
31 inCountObject
32
33 inSince
34 inLimit
35 inUntil
36 inIds
37 inAuthors
38 inKinds
39 inSearch
40 inAFilterTag
41
42 inId
43 inCreatedAt
44 inKind
45 inContent
46 inPubkey
47 inSig
48 inTags // we just saw the "tags" object key
49 inTagsList // we have just seen the first `[` of the tags
50 inAnEventTag // we are inside an actual tag, i.e we have just seen `[[`, or `].[`
51 )
52
53 func (spp sonicVisitorPosition) String() string {
54 switch spp {
55 case inEnvelope:
56 return "inEnvelope"
57 case inEvent:
58 return "inEvent"
59 case inReq:
60 return "inReq"
61 case inOk:
62 return "inOk"
63 case inEose:
64 return "inEose"
65 case inCount:
66 return "inCount"
67 case inAuth:
68 return "inAuth"
69 case inClose:
70 return "inClose"
71 case inClosed:
72 return "inClosed"
73 case inNotice:
74 return "inNotice"
75 case inFilterObject:
76 return "inFilterObject"
77 case inEventObject:
78 return "inEventObject"
79 case inCountObject:
80 return "inCountObject"
81 case inSince:
82 return "inSince"
83 case inLimit:
84 return "inLimit"
85 case inUntil:
86 return "inUntil"
87 case inIds:
88 return "inIds"
89 case inAuthors:
90 return "inAuthors"
91 case inKinds:
92 return "inKinds"
93 case inAFilterTag:
94 return "inAFilterTag"
95 case inId:
96 return "inId"
97 case inCreatedAt:
98 return "inCreatedAt"
99 case inKind:
100 return "inKind"
101 case inContent:
102 return "inContent"
103 case inPubkey:
104 return "inPubkey"
105 case inSig:
106 return "inSig"
107 case inTags:
108 return "inTags"
109 case inTagsList:
110 return "inTagsList"
111 case inAnEventTag:
112 return "inAnEventTag"
113 default:
114 return "<unexpected-spp>"
115 }
116 }
117
118 type sonicVisitor struct {
119 event *EventEnvelope
120 req *ReqEnvelope
121 ok *OKEnvelope
122 eose *EOSEEnvelope
123 count *CountEnvelope
124 auth *AuthEnvelope
125 close *CloseEnvelope
126 closed *ClosedEnvelope
127 notice *NoticeEnvelope
128
129 whereWeAre sonicVisitorPosition
130
131 currentEvent *Event
132 currentEventTag Tag
133
134 currentFilter *Filter
135 currentFilterTagList []string
136 currentFilterTagName string
137
138 smp *sonicMessageParser
139 mainEnvelope Envelope
140 }
141
142 func (sv *sonicVisitor) OnArrayBegin(capacity int) error {
143 // fmt.Println("***", "OnArrayBegin", "==", sv.whereWeAre)
144
145 switch sv.whereWeAre {
146 case inTags:
147 sv.whereWeAre = inTagsList
148 sv.currentEvent.Tags = sv.smp.reusableTagArray
149 case inTagsList:
150 sv.whereWeAre = inAnEventTag
151 sv.currentEventTag = sv.smp.reusableStringArray
152 case inAFilterTag:
153 // we have already created this
154 }
155
156 return nil
157 }
158
159 func (sv *sonicVisitor) OnArrayEnd() error {
160 // fmt.Println("***", "OnArrayEnd", "==", sv.whereWeAre)
161
162 switch sv.whereWeAre {
163 // envelopes
164 case inEvent:
165 sv.mainEnvelope = sv.event
166 case inReq:
167 sv.mainEnvelope = sv.req
168 sv.smp.doneWithFilterSlice(sv.req.Filters)
169 case inOk:
170 sv.mainEnvelope = sv.ok
171 case inEose:
172 sv.mainEnvelope = sv.eose
173 case inCount:
174 sv.mainEnvelope = sv.count
175 case inAuth:
176 sv.mainEnvelope = sv.auth
177 case inClose:
178 sv.mainEnvelope = sv.close
179 case inClosed:
180 sv.mainEnvelope = sv.closed
181 case inNotice:
182 sv.mainEnvelope = sv.notice
183
184 // filter object properties
185 case inIds:
186 sv.whereWeAre = inFilterObject
187 sv.smp.doneWithStringSlice(sv.currentFilter.IDs)
188 case inAuthors:
189 sv.whereWeAre = inFilterObject
190 sv.smp.doneWithStringSlice(sv.currentFilter.Authors)
191 case inKinds:
192 sv.whereWeAre = inFilterObject
193 sv.smp.doneWithIntSlice(sv.currentFilter.Kinds)
194 case inAFilterTag:
195 sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList
196 sv.whereWeAre = inFilterObject
197 sv.smp.doneWithStringSlice(sv.currentFilterTagList)
198
199 // event object properties
200 case inAnEventTag:
201 sv.currentEvent.Tags = append(sv.currentEvent.Tags, sv.currentEventTag)
202 sv.whereWeAre = inTagsList
203 sv.smp.doneWithStringSlice(sv.currentEventTag)
204 case inTags, inTagsList:
205 sv.whereWeAre = inEventObject
206 sv.smp.doneWithTagSlice(sv.currentEvent.Tags)
207
208 default:
209 return fmt.Errorf("unexpected array end at %v", sv.whereWeAre)
210 }
211 return nil
212 }
213
214 func (sv *sonicVisitor) OnObjectBegin(capacity int) error {
215 // fmt.Println("***", "OnObjectBegin", "==", sv.whereWeAre)
216
217 switch sv.whereWeAre {
218 case inEvent:
219 sv.whereWeAre = inEventObject
220 sv.currentEvent = &Event{}
221 case inAuth:
222 sv.whereWeAre = inEventObject
223 sv.currentEvent = &Event{}
224 case inReq:
225 sv.whereWeAre = inFilterObject
226 sv.currentFilter = &Filter{}
227 case inCount:
228 // set this temporarily, we will switch to a filterObject if we see "count" or "hll"
229 sv.whereWeAre = inFilterObject
230 sv.currentFilter = &Filter{}
231 default:
232 return fmt.Errorf("unexpected object begin at %v", sv.whereWeAre)
233 }
234
235 return nil
236 }
237
238 func (sv *sonicVisitor) OnObjectKey(key string) error {
239 // fmt.Println("***", "OnObjectKey", key, "==", sv.whereWeAre)
240
241 switch sv.whereWeAre {
242 case inEventObject:
243 switch key {
244 case "id":
245 sv.whereWeAre = inId
246 case "sig":
247 sv.whereWeAre = inSig
248 case "pubkey":
249 sv.whereWeAre = inPubkey
250 case "content":
251 sv.whereWeAre = inContent
252 case "created_at":
253 sv.whereWeAre = inCreatedAt
254 case "kind":
255 sv.whereWeAre = inKind
256 case "tags":
257 sv.whereWeAre = inTags
258 default:
259 return fmt.Errorf("unexpected event attr %s", key)
260 }
261 case inFilterObject:
262 switch key {
263 case "limit":
264 sv.whereWeAre = inLimit
265 case "since":
266 sv.whereWeAre = inSince
267 case "until":
268 sv.whereWeAre = inUntil
269 case "ids":
270 sv.whereWeAre = inIds
271 sv.currentFilter.IDs = sv.smp.reusableStringArray
272 case "authors":
273 sv.whereWeAre = inAuthors
274 sv.currentFilter.Authors = sv.smp.reusableStringArray
275 case "kinds":
276 sv.whereWeAre = inKinds
277 sv.currentFilter.Kinds = sv.smp.reusableIntArray
278 case "search":
279 sv.whereWeAre = inSearch
280 case "count", "hll":
281 // oops, switch to a countObject
282 sv.whereWeAre = inCountObject
283 default:
284 if len(key) > 1 && key[0] == '#' {
285 if sv.currentFilter.Tags == nil {
286 sv.currentFilter.Tags = make(TagMap, 1)
287 }
288 sv.currentFilterTagList = sv.smp.reusableStringArray
289 sv.currentFilterTagName = key[1:]
290 sv.whereWeAre = inAFilterTag
291 } else {
292 return fmt.Errorf("unexpected filter attr %s", key)
293 }
294 }
295 case inCountObject:
296 // we'll judge by the shape of the value so ignore this
297 default:
298 return fmt.Errorf("unexpected object key %s at %s", key, sv.whereWeAre)
299 }
300
301 return nil
302 }
303
304 func (sv *sonicVisitor) OnObjectEnd() error {
305 // fmt.Println("***", "OnObjectEnd", "==", sv.whereWeAre)
306
307 switch sv.whereWeAre {
308 case inEventObject:
309 if sv.event != nil {
310 sv.event.Event = *sv.currentEvent
311 sv.whereWeAre = inEvent
312 } else {
313 sv.auth.Event = *sv.currentEvent
314 sv.whereWeAre = inAuth
315 }
316 sv.currentEvent = nil
317 case inFilterObject:
318 if sv.req != nil {
319 sv.req.Filters = append(sv.req.Filters, *sv.currentFilter)
320 sv.whereWeAre = inReq
321 } else {
322 sv.count.Filter = *sv.currentFilter
323 sv.whereWeAre = inCount
324 }
325 sv.currentFilter = nil
326 case inCountObject:
327 sv.whereWeAre = inCount
328 default:
329 return fmt.Errorf("unexpected object end at %s", sv.whereWeAre)
330 }
331
332 return nil
333 }
334
335 func (sv *sonicVisitor) OnString(v string) error {
336 // fmt.Println("***", "OnString", v, "==", sv.whereWeAre)
337
338 switch sv.whereWeAre {
339 case inEnvelope:
340 switch v {
341 case "EVENT":
342 sv.event = &EventEnvelope{}
343 sv.whereWeAre = inEvent
344 case "REQ":
345 sv.req = &ReqEnvelope{Filters: sv.smp.reusableFilterArray}
346 sv.whereWeAre = inReq
347 case "OK":
348 sv.ok = &OKEnvelope{}
349 sv.whereWeAre = inOk
350 case "EOSE":
351 sv.whereWeAre = inEose
352 case "COUNT":
353 sv.count = &CountEnvelope{}
354 sv.whereWeAre = inCount
355 case "AUTH":
356 sv.auth = &AuthEnvelope{}
357 sv.whereWeAre = inAuth
358 case "CLOSE":
359 sv.whereWeAre = inClose
360 case "CLOSED":
361 sv.closed = &ClosedEnvelope{}
362 sv.whereWeAre = inClosed
363 case "NOTICE":
364 sv.whereWeAre = inNotice
365 default:
366 return UnknownLabel
367 }
368
369 // in an envelope
370 case inEvent:
371 sv.event.SubscriptionID = &v
372 case inReq:
373 sv.req.SubscriptionID = v
374 case inOk:
375 if sv.ok.EventID == "" {
376 sv.ok.EventID = v
377 } else {
378 sv.ok.Reason = v
379 }
380 case inEose:
381 sv.eose = (*EOSEEnvelope)(&v)
382 case inCount:
383 sv.count.SubscriptionID = v
384 case inAuth:
385 sv.auth.Challenge = &v
386 case inClose:
387 sv.close = (*CloseEnvelope)(&v)
388 case inClosed:
389 if sv.closed.SubscriptionID == "" {
390 sv.closed.SubscriptionID = v
391 } else {
392 sv.closed.Reason = v
393 }
394 case inNotice:
395 sv.notice = (*NoticeEnvelope)(&v)
396
397 // filter object properties
398 case inIds:
399 sv.currentFilter.IDs = append(sv.currentFilter.IDs, v)
400 case inAuthors:
401 sv.currentFilter.Authors = append(sv.currentFilter.Authors, v)
402 case inSearch:
403 sv.currentFilter.Search = v
404 sv.whereWeAre = inFilterObject
405 case inAFilterTag:
406 sv.currentFilterTagList = append(sv.currentFilterTagList, v)
407
408 // id object properties
409 case inId:
410 sv.currentEvent.ID = v
411 sv.whereWeAre = inEventObject
412 case inContent:
413 sv.currentEvent.Content = v
414 sv.whereWeAre = inEventObject
415 case inPubkey:
416 sv.currentEvent.PubKey = v
417 sv.whereWeAre = inEventObject
418 case inSig:
419 sv.currentEvent.Sig = v
420 sv.whereWeAre = inEventObject
421 case inAnEventTag:
422 sv.currentEventTag = append(sv.currentEventTag, v)
423
424 // count object properties
425 case inCountObject:
426 sv.count.HyperLogLog, _ = hex.DecodeString(v)
427
428 default:
429 return fmt.Errorf("unexpected string %s at %v", v, sv.whereWeAre)
430 }
431 return nil
432 }
433
434 func (sv *sonicVisitor) OnInt64(v int64, _ stdlibjson.Number) error {
435 // fmt.Println("***", "OnInt64", v, "==", sv.whereWeAre)
436
437 switch sv.whereWeAre {
438 // event object
439 case inCreatedAt:
440 sv.currentEvent.CreatedAt = Timestamp(v)
441 sv.whereWeAre = inEventObject
442 case inKind:
443 sv.currentEvent.Kind = int(v)
444 sv.whereWeAre = inEventObject
445
446 // filter object
447 case inLimit:
448 sv.currentFilter.Limit = int(v)
449 sv.currentFilter.LimitZero = v == 0
450 sv.whereWeAre = inFilterObject
451 case inSince:
452 sv.currentFilter.Since = (*Timestamp)(&v)
453 sv.whereWeAre = inFilterObject
454 case inUntil:
455 sv.currentFilter.Until = (*Timestamp)(&v)
456 sv.whereWeAre = inFilterObject
457 case inKinds:
458 sv.currentFilter.Kinds = append(sv.currentFilter.Kinds, int(v))
459
460 // count object
461 case inCountObject:
462 sv.count.Count = &v
463 }
464 return nil
465 }
466
467 func (sv *sonicVisitor) OnBool(v bool) error {
468 // fmt.Println("***", "OnBool", v, "==", sv.whereWeAre)
469
470 if sv.whereWeAre == inOk {
471 sv.ok.OK = v
472 return nil
473 } else {
474 return fmt.Errorf("unexpected boolean")
475 }
476 }
477
478 func (_ sonicVisitor) OnNull() error {
479 return fmt.Errorf("null shouldn't be anywhere in a message")
480 }
481
482 func (_ sonicVisitor) OnFloat64(v float64, n stdlibjson.Number) error {
483 return fmt.Errorf("float shouldn't be anywhere in a message")
484 }
485
486 type sonicMessageParser struct {
487 reusableFilterArray []Filter
488 reusableTagArray []Tag
489 reusableStringArray []string
490 reusableIntArray []int
491 }
492
493 // NewMessageParser returns a sonicMessageParser object that is intended to be reused many times.
494 // It is not goroutine-safe.
495 func NewMessageParser() sonicMessageParser {
496 return sonicMessageParser{
497 reusableFilterArray: make([]Filter, 0, 1000),
498 reusableTagArray: make([]Tag, 0, 10000),
499 reusableStringArray: make([]string, 0, 10000),
500 reusableIntArray: make([]int, 0, 10000),
501 }
502 }
503
504 var NewSonicMessageParser = NewMessageParser
505
506 func (smp *sonicMessageParser) doneWithFilterSlice(slice []Filter) {
507 if unsafe.SliceData(smp.reusableFilterArray) == unsafe.SliceData(slice) {
508 smp.reusableFilterArray = slice[len(slice):]
509 }
510
511 if cap(smp.reusableFilterArray) < 7 {
512 // create a new one
513 smp.reusableFilterArray = make([]Filter, 0, 1000)
514 }
515 }
516
517 func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) {
518 if unsafe.SliceData(smp.reusableTagArray) == unsafe.SliceData(slice) {
519 smp.reusableTagArray = slice[len(slice):]
520 }
521
522 if cap(smp.reusableTagArray) < 7 {
523 // create a new one
524 smp.reusableTagArray = make([]Tag, 0, 10000)
525 }
526 }
527
528 func (smp *sonicMessageParser) doneWithStringSlice(slice []string) {
529 if unsafe.SliceData(smp.reusableStringArray) == unsafe.SliceData(slice) {
530 smp.reusableStringArray = slice[len(slice):]
531 }
532
533 if cap(smp.reusableStringArray) < 15 {
534 // create a new one
535 smp.reusableStringArray = make([]string, 0, 10000)
536 }
537 }
538
539 func (smp *sonicMessageParser) doneWithIntSlice(slice []int) {
540 if unsafe.SliceData(smp.reusableIntArray) == unsafe.SliceData(slice) {
541 smp.reusableIntArray = slice[len(slice):]
542 }
543
544 if cap(smp.reusableIntArray) < 8 {
545 // create a new one
546 smp.reusableIntArray = make([]int, 0, 10000)
547 }
548 }
549
550 // ParseMessage parses a message like ["EVENT", ...] or ["REQ", ...] and returns an Envelope.
551 // The returned envelopes, filters and events' slices should not be appended to, otherwise stuff
552 // will break.
553 //
554 // When an unexpected message (like ["NEG-OPEN", ...]) is found, the error UnknownLabel will be
555 // returned. Other errors will be returned if the JSON is malformed or the objects are not exactly
556 // as they should.
557 func (smp sonicMessageParser) ParseMessage(message string) (Envelope, error) {
558 sv := &sonicVisitor{smp: &smp}
559 sv.whereWeAre = inEnvelope
560
561 err := ast.Preorder(message, sv, nil)
562
563 return sv.mainEnvelope, err
564 }
565