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