go-nostr/envelopes_sonic.go

565 lines
13 KiB
Go
Raw Permalink Normal View History

//go:build sonic
package nostr
import (
"encoding/hex"
stdlibjson "encoding/json"
"fmt"
"unsafe"
"github.com/bytedance/sonic/ast"
)
type sonicVisitorPosition int
const (
inEnvelope sonicVisitorPosition = iota
inEvent
inReq
inOk
inEose
inCount
inAuth
inClose
inClosed
inNotice
inFilterObject
inEventObject
inCountObject
inSince
inLimit
inUntil
inIds
inAuthors
inKinds
inSearch
inAFilterTag
inId
inCreatedAt
inKind
inContent
inPubkey
inSig
inTags // we just saw the "tags" object key
inTagsList // we have just seen the first `[` of the tags
inAnEventTag // we are inside an actual tag, i.e we have just seen `[[`, or `].[`
)
func (spp sonicVisitorPosition) String() string {
switch spp {
case inEnvelope:
return "inEnvelope"
case inEvent:
return "inEvent"
case inReq:
return "inReq"
case inOk:
return "inOk"
case inEose:
return "inEose"
case inCount:
return "inCount"
case inAuth:
return "inAuth"
case inClose:
return "inClose"
case inClosed:
return "inClosed"
case inNotice:
return "inNotice"
case inFilterObject:
return "inFilterObject"
case inEventObject:
return "inEventObject"
case inCountObject:
return "inCountObject"
case inSince:
return "inSince"
case inLimit:
return "inLimit"
case inUntil:
return "inUntil"
case inIds:
return "inIds"
case inAuthors:
return "inAuthors"
case inKinds:
return "inKinds"
case inAFilterTag:
return "inAFilterTag"
case inId:
return "inId"
case inCreatedAt:
return "inCreatedAt"
case inKind:
return "inKind"
case inContent:
return "inContent"
case inPubkey:
return "inPubkey"
case inSig:
return "inSig"
case inTags:
return "inTags"
case inTagsList:
return "inTagsList"
case inAnEventTag:
return "inAnEventTag"
default:
return "<unexpected-spp>"
}
}
type sonicVisitor struct {
event *EventEnvelope
req *ReqEnvelope
ok *OKEnvelope
eose *EOSEEnvelope
count *CountEnvelope
auth *AuthEnvelope
close *CloseEnvelope
closed *ClosedEnvelope
notice *NoticeEnvelope
whereWeAre sonicVisitorPosition
currentEvent *Event
currentEventTag Tag
currentFilter *Filter
currentFilterTagList []string
currentFilterTagName string
smp *sonicMessageParser
mainEnvelope Envelope
}
func (sv *sonicVisitor) OnArrayBegin(capacity int) error {
// fmt.Println("***", "OnArrayBegin", "==", sv.whereWeAre)
switch sv.whereWeAre {
case inTags:
sv.whereWeAre = inTagsList
sv.currentEvent.Tags = sv.smp.reusableTagArray
case inTagsList:
sv.whereWeAre = inAnEventTag
sv.currentEventTag = sv.smp.reusableStringArray
case inAFilterTag:
// we have already created this
}
return nil
}
func (sv *sonicVisitor) OnArrayEnd() error {
// fmt.Println("***", "OnArrayEnd", "==", sv.whereWeAre)
switch sv.whereWeAre {
// envelopes
case inEvent:
sv.mainEnvelope = sv.event
case inReq:
sv.mainEnvelope = sv.req
sv.smp.doneWithFilterSlice(sv.req.Filters)
case inOk:
sv.mainEnvelope = sv.ok
case inEose:
sv.mainEnvelope = sv.eose
case inCount:
sv.mainEnvelope = sv.count
case inAuth:
sv.mainEnvelope = sv.auth
case inClose:
sv.mainEnvelope = sv.close
case inClosed:
sv.mainEnvelope = sv.closed
case inNotice:
sv.mainEnvelope = sv.notice
// filter object properties
case inIds:
sv.whereWeAre = inFilterObject
sv.smp.doneWithStringSlice(sv.currentFilter.IDs)
case inAuthors:
sv.whereWeAre = inFilterObject
sv.smp.doneWithStringSlice(sv.currentFilter.Authors)
case inKinds:
sv.whereWeAre = inFilterObject
sv.smp.doneWithIntSlice(sv.currentFilter.Kinds)
case inAFilterTag:
sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList
sv.whereWeAre = inFilterObject
sv.smp.doneWithStringSlice(sv.currentFilterTagList)
// event object properties
case inAnEventTag:
sv.currentEvent.Tags = append(sv.currentEvent.Tags, sv.currentEventTag)
sv.whereWeAre = inTagsList
sv.smp.doneWithStringSlice(sv.currentEventTag)
case inTags, inTagsList:
sv.whereWeAre = inEventObject
sv.smp.doneWithTagSlice(sv.currentEvent.Tags)
default:
return fmt.Errorf("unexpected array end at %v", sv.whereWeAre)
}
return nil
}
func (sv *sonicVisitor) OnObjectBegin(capacity int) error {
// fmt.Println("***", "OnObjectBegin", "==", sv.whereWeAre)
switch sv.whereWeAre {
case inEvent:
sv.whereWeAre = inEventObject
sv.currentEvent = &Event{}
case inAuth:
sv.whereWeAre = inEventObject
sv.currentEvent = &Event{}
case inReq:
sv.whereWeAre = inFilterObject
sv.currentFilter = &Filter{}
case inCount:
// set this temporarily, we will switch to a filterObject if we see "count" or "hll"
sv.whereWeAre = inFilterObject
sv.currentFilter = &Filter{}
default:
return fmt.Errorf("unexpected object begin at %v", sv.whereWeAre)
}
return nil
}
func (sv *sonicVisitor) OnObjectKey(key string) error {
// fmt.Println("***", "OnObjectKey", key, "==", sv.whereWeAre)
switch sv.whereWeAre {
case inEventObject:
switch key {
case "id":
sv.whereWeAre = inId
case "sig":
sv.whereWeAre = inSig
case "pubkey":
sv.whereWeAre = inPubkey
case "content":
sv.whereWeAre = inContent
case "created_at":
sv.whereWeAre = inCreatedAt
case "kind":
sv.whereWeAre = inKind
case "tags":
sv.whereWeAre = inTags
default:
return fmt.Errorf("unexpected event attr %s", key)
}
case inFilterObject:
switch key {
case "limit":
sv.whereWeAre = inLimit
case "since":
sv.whereWeAre = inSince
case "until":
sv.whereWeAre = inUntil
case "ids":
sv.whereWeAre = inIds
sv.currentFilter.IDs = sv.smp.reusableStringArray
case "authors":
sv.whereWeAre = inAuthors
sv.currentFilter.Authors = sv.smp.reusableStringArray
case "kinds":
sv.whereWeAre = inKinds
sv.currentFilter.Kinds = sv.smp.reusableIntArray
case "search":
sv.whereWeAre = inSearch
case "count", "hll":
// oops, switch to a countObject
sv.whereWeAre = inCountObject
default:
if len(key) > 1 && key[0] == '#' {
if sv.currentFilter.Tags == nil {
sv.currentFilter.Tags = make(TagMap, 1)
}
sv.currentFilterTagList = sv.smp.reusableStringArray
sv.currentFilterTagName = key[1:]
sv.whereWeAre = inAFilterTag
} else {
return fmt.Errorf("unexpected filter attr %s", key)
}
}
case inCountObject:
// we'll judge by the shape of the value so ignore this
default:
return fmt.Errorf("unexpected object key %s at %s", key, sv.whereWeAre)
}
return nil
}
func (sv *sonicVisitor) OnObjectEnd() error {
// fmt.Println("***", "OnObjectEnd", "==", sv.whereWeAre)
switch sv.whereWeAre {
case inEventObject:
if sv.event != nil {
sv.event.Event = *sv.currentEvent
sv.whereWeAre = inEvent
} else {
sv.auth.Event = *sv.currentEvent
sv.whereWeAre = inAuth
}
sv.currentEvent = nil
case inFilterObject:
if sv.req != nil {
sv.req.Filters = append(sv.req.Filters, *sv.currentFilter)
sv.whereWeAre = inReq
} else {
sv.count.Filter = *sv.currentFilter
sv.whereWeAre = inCount
}
sv.currentFilter = nil
case inCountObject:
sv.whereWeAre = inCount
default:
return fmt.Errorf("unexpected object end at %s", sv.whereWeAre)
}
return nil
}
func (sv *sonicVisitor) OnString(v string) error {
// fmt.Println("***", "OnString", v, "==", sv.whereWeAre)
switch sv.whereWeAre {
case inEnvelope:
switch v {
case "EVENT":
sv.event = &EventEnvelope{}
sv.whereWeAre = inEvent
case "REQ":
sv.req = &ReqEnvelope{Filters: sv.smp.reusableFilterArray}
sv.whereWeAre = inReq
case "OK":
sv.ok = &OKEnvelope{}
sv.whereWeAre = inOk
case "EOSE":
sv.whereWeAre = inEose
case "COUNT":
sv.count = &CountEnvelope{}
sv.whereWeAre = inCount
case "AUTH":
sv.auth = &AuthEnvelope{}
sv.whereWeAre = inAuth
case "CLOSE":
sv.whereWeAre = inClose
case "CLOSED":
sv.closed = &ClosedEnvelope{}
sv.whereWeAre = inClosed
case "NOTICE":
sv.whereWeAre = inNotice
default:
return UnknownLabel
}
// in an envelope
case inEvent:
sv.event.SubscriptionID = &v
case inReq:
sv.req.SubscriptionID = v
case inOk:
if sv.ok.EventID == "" {
sv.ok.EventID = v
} else {
sv.ok.Reason = v
}
case inEose:
sv.eose = (*EOSEEnvelope)(&v)
case inCount:
sv.count.SubscriptionID = v
case inAuth:
sv.auth.Challenge = &v
case inClose:
sv.close = (*CloseEnvelope)(&v)
case inClosed:
if sv.closed.SubscriptionID == "" {
sv.closed.SubscriptionID = v
} else {
sv.closed.Reason = v
}
case inNotice:
sv.notice = (*NoticeEnvelope)(&v)
// filter object properties
case inIds:
sv.currentFilter.IDs = append(sv.currentFilter.IDs, v)
case inAuthors:
sv.currentFilter.Authors = append(sv.currentFilter.Authors, v)
case inSearch:
sv.currentFilter.Search = v
sv.whereWeAre = inFilterObject
case inAFilterTag:
sv.currentFilterTagList = append(sv.currentFilterTagList, v)
// id object properties
case inId:
sv.currentEvent.ID = v
sv.whereWeAre = inEventObject
case inContent:
sv.currentEvent.Content = v
sv.whereWeAre = inEventObject
case inPubkey:
sv.currentEvent.PubKey = v
sv.whereWeAre = inEventObject
case inSig:
sv.currentEvent.Sig = v
sv.whereWeAre = inEventObject
case inAnEventTag:
sv.currentEventTag = append(sv.currentEventTag, v)
// count object properties
case inCountObject:
sv.count.HyperLogLog, _ = hex.DecodeString(v)
default:
return fmt.Errorf("unexpected string %s at %v", v, sv.whereWeAre)
}
return nil
}
func (sv *sonicVisitor) OnInt64(v int64, _ stdlibjson.Number) error {
// fmt.Println("***", "OnInt64", v, "==", sv.whereWeAre)
switch sv.whereWeAre {
// event object
case inCreatedAt:
sv.currentEvent.CreatedAt = Timestamp(v)
sv.whereWeAre = inEventObject
case inKind:
sv.currentEvent.Kind = int(v)
sv.whereWeAre = inEventObject
// filter object
case inLimit:
sv.currentFilter.Limit = int(v)
sv.currentFilter.LimitZero = v == 0
sv.whereWeAre = inFilterObject
case inSince:
sv.currentFilter.Since = (*Timestamp)(&v)
sv.whereWeAre = inFilterObject
case inUntil:
sv.currentFilter.Until = (*Timestamp)(&v)
sv.whereWeAre = inFilterObject
case inKinds:
sv.currentFilter.Kinds = append(sv.currentFilter.Kinds, int(v))
// count object
case inCountObject:
sv.count.Count = &v
}
return nil
}
func (sv *sonicVisitor) OnBool(v bool) error {
// fmt.Println("***", "OnBool", v, "==", sv.whereWeAre)
if sv.whereWeAre == inOk {
sv.ok.OK = v
return nil
} else {
return fmt.Errorf("unexpected boolean")
}
}
func (_ sonicVisitor) OnNull() error {
return fmt.Errorf("null shouldn't be anywhere in a message")
}
func (_ sonicVisitor) OnFloat64(v float64, n stdlibjson.Number) error {
return fmt.Errorf("float shouldn't be anywhere in a message")
}
type sonicMessageParser struct {
reusableFilterArray []Filter
reusableTagArray []Tag
reusableStringArray []string
reusableIntArray []int
}
// NewMessageParser returns a sonicMessageParser object that is intended to be reused many times.
2025-03-07 21:21:43 -03:00
// It is not goroutine-safe.
func NewMessageParser() sonicMessageParser {
return sonicMessageParser{
reusableFilterArray: make([]Filter, 0, 1000),
reusableTagArray: make([]Tag, 0, 10000),
reusableStringArray: make([]string, 0, 10000),
reusableIntArray: make([]int, 0, 10000),
}
}
var NewSonicMessageParser = NewMessageParser
func (smp *sonicMessageParser) doneWithFilterSlice(slice []Filter) {
if unsafe.SliceData(smp.reusableFilterArray) == unsafe.SliceData(slice) {
smp.reusableFilterArray = slice[len(slice):]
}
if cap(smp.reusableFilterArray) < 7 {
// create a new one
smp.reusableFilterArray = make([]Filter, 0, 1000)
}
}
func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) {
if unsafe.SliceData(smp.reusableTagArray) == unsafe.SliceData(slice) {
smp.reusableTagArray = slice[len(slice):]
}
if cap(smp.reusableTagArray) < 7 {
// create a new one
smp.reusableTagArray = make([]Tag, 0, 10000)
}
}
func (smp *sonicMessageParser) doneWithStringSlice(slice []string) {
if unsafe.SliceData(smp.reusableStringArray) == unsafe.SliceData(slice) {
smp.reusableStringArray = slice[len(slice):]
}
if cap(smp.reusableStringArray) < 15 {
// create a new one
smp.reusableStringArray = make([]string, 0, 10000)
}
}
func (smp *sonicMessageParser) doneWithIntSlice(slice []int) {
if unsafe.SliceData(smp.reusableIntArray) == unsafe.SliceData(slice) {
smp.reusableIntArray = slice[len(slice):]
}
if cap(smp.reusableIntArray) < 8 {
// create a new one
smp.reusableIntArray = make([]int, 0, 10000)
}
}
2025-03-07 21:21:43 -03:00
// ParseMessage parses a message like ["EVENT", ...] or ["REQ", ...] and returns an Envelope.
// The returned envelopes, filters and events' slices should not be appended to, otherwise stuff
// will break.
//
// When an unexpected message (like ["NEG-OPEN", ...]) is found, the error UnknownLabel will be
// returned. Other errors will be returned if the JSON is malformed or the objects are not exactly
// as they should.
func (smp sonicMessageParser) ParseMessage(message string) (Envelope, error) {
sv := &sonicVisitor{smp: &smp}
sv.whereWeAre = inEnvelope
err := ast.Preorder(message, sv, nil)
return sv.mainEnvelope, err
}