mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-05-02 14:50:13 +02:00
depressing sonic json decoder implementation, breakingly force COUNT to use a single filter, reorganize envelope tests.
This commit is contained in:
parent
ec55b1fac8
commit
c9411a3c5b
14
envelopes.go
14
envelopes.go
@ -164,7 +164,7 @@ func (v ReqEnvelope) MarshalJSON() ([]byte, error) {
|
||||
// CountEnvelope represents a COUNT message.
|
||||
type CountEnvelope struct {
|
||||
SubscriptionID string
|
||||
Filters
|
||||
Filter
|
||||
Count *int64
|
||||
HyperLogLog []byte
|
||||
}
|
||||
@ -198,12 +198,11 @@ func (v *CountEnvelope) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
v.Filters = make(Filters, len(arr)-2)
|
||||
f := 0
|
||||
for i := 2; i < len(arr); i++ {
|
||||
item := []byte(arr[i].Raw)
|
||||
|
||||
if err := easyjson.Unmarshal(item, &v.Filters[f]); err != nil {
|
||||
if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
|
||||
return fmt.Errorf("%w -- on filter %d", err, f)
|
||||
}
|
||||
|
||||
@ -217,9 +216,9 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{NoEscapeHTML: true}
|
||||
w.RawString(`["COUNT","`)
|
||||
w.RawString(v.SubscriptionID)
|
||||
w.RawString(`"`)
|
||||
w.RawString(`",`)
|
||||
if v.Count != nil {
|
||||
w.RawString(`,{"count":`)
|
||||
w.RawString(`{"count":`)
|
||||
w.RawString(strconv.FormatInt(*v.Count, 10))
|
||||
if v.HyperLogLog != nil {
|
||||
w.RawString(`,"hll":"`)
|
||||
@ -230,10 +229,7 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
w.RawString(`}`)
|
||||
} else {
|
||||
for _, filter := range v.Filters {
|
||||
w.RawString(`,`)
|
||||
filter.MarshalEasyJSON(&w)
|
||||
}
|
||||
v.Filter.MarshalEasyJSON(&w)
|
||||
}
|
||||
w.RawString(`]`)
|
||||
return w.BuildBytes()
|
||||
|
@ -11,59 +11,69 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkParseMessage(b *testing.B) {
|
||||
messages := generateTestMessages(2000)
|
||||
for _, name := range []string{"relay", "client"} {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
messages := generateTestMessages(name)
|
||||
|
||||
b.Run("stdlib", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
var v any
|
||||
stdlibjson.Unmarshal(msg, &v)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run("jsonstdlib", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
var v any
|
||||
stdlibjson.Unmarshal(msg, &v)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("easyjson", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
_ = ParseMessage(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run("easyjson", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
_ = ParseMessage(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("simdjson", func(b *testing.B) {
|
||||
smp := SIMDMessageParser{ParsedJSON: &simdjson.ParsedJson{}, AuxIter: &simdjson.Iter{}}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
_, _ = smp.ParseMessage(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run("simdjson", func(b *testing.B) {
|
||||
smp := SIMDMessageParser{ParsedJSON: &simdjson.ParsedJson{}, AuxIter: &simdjson.Iter{}}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
_, _ = smp.ParseMessage(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("sonic", func(b *testing.B) {
|
||||
smp := SonicMessageParser{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, msg := range messages {
|
||||
_, _ = smp.ParseMessage(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateTestMessages(count int) [][]byte {
|
||||
messages := make([][]byte, 0, count)
|
||||
func generateTestMessages(typ string) [][]byte {
|
||||
messages := make([][]byte, 0, 600)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
var msg []byte
|
||||
setup := map[string]map[int]func() []byte{
|
||||
"client": {
|
||||
500: generateEventMessage,
|
||||
5: generateEOSEMessage,
|
||||
9: generateNoticeMessage,
|
||||
14: generateCountMessage,
|
||||
20: generateOKMessage,
|
||||
},
|
||||
"relay": {
|
||||
500: generateReqMessage,
|
||||
10: generateCountMessage,
|
||||
},
|
||||
}[typ]
|
||||
|
||||
switch rand.IntN(12) {
|
||||
case 1:
|
||||
msg = generateAuthMessage()
|
||||
case 2:
|
||||
msg = generateNoticeMessage()
|
||||
case 3:
|
||||
msg = generateEOSEMessage()
|
||||
case 4:
|
||||
msg = generateOKMessage()
|
||||
case 5:
|
||||
msg = generateCountMessage()
|
||||
case 6:
|
||||
msg = generateReqMessage()
|
||||
default:
|
||||
msg = generateEventMessage()
|
||||
for count, generator := range setup {
|
||||
for range count {
|
||||
messages = append(messages, generator())
|
||||
}
|
||||
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
|
||||
return messages
|
||||
|
@ -129,13 +129,11 @@ func (smp *SIMDMessageParser) ParseMessage(message []byte) (Envelope, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var filter Filter
|
||||
smp.TargetObject, smp.TargetInternalArray, err = filter.UnmarshalSIMD(
|
||||
smp.TargetObject, smp.TargetInternalArray, err = v.Filter.UnmarshalSIMD(
|
||||
&iter, smp.TargetObject, smp.TargetInternalArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Filters = Filters{filter}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
@ -208,3 +206,184 @@ func (smp *SIMDMessageParser) ParseMessage(message []byte) (Envelope, error) {
|
||||
return nil, UnknownLabel
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
attrId = []byte("id")
|
||||
attrPubkey = []byte("pubkey")
|
||||
attrCreatedAt = []byte("created_at")
|
||||
attrKind = []byte("kind")
|
||||
attrContent = []byte("content")
|
||||
attrTags = []byte("tags")
|
||||
attrSig = []byte("sig")
|
||||
)
|
||||
|
||||
func (event *Event) UnmarshalSIMD(
|
||||
iter *simdjson.Iter,
|
||||
obj *simdjson.Object,
|
||||
arr *simdjson.Array,
|
||||
subArr *simdjson.Array,
|
||||
) (*simdjson.Object, *simdjson.Array, *simdjson.Array, error) {
|
||||
obj, err := iter.Object(obj)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, fmt.Errorf("unexpected at event: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
name, t, err := obj.NextElementBytes(iter)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
} else if t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(name, attrId):
|
||||
event.ID, err = iter.String()
|
||||
case bytes.Equal(name, attrPubkey):
|
||||
event.PubKey, err = iter.String()
|
||||
case bytes.Equal(name, attrContent):
|
||||
event.Content, err = iter.String()
|
||||
case bytes.Equal(name, attrSig):
|
||||
event.Sig, err = iter.String()
|
||||
case bytes.Equal(name, attrCreatedAt):
|
||||
var ts uint64
|
||||
ts, err = iter.Uint()
|
||||
event.CreatedAt = Timestamp(ts)
|
||||
case bytes.Equal(name, attrKind):
|
||||
var kind uint64
|
||||
kind, err = iter.Uint()
|
||||
event.Kind = int(kind)
|
||||
case bytes.Equal(name, attrTags):
|
||||
arr, err = iter.Array(arr)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
event.Tags = make(Tags, 0, 10)
|
||||
titer := arr.Iter()
|
||||
for {
|
||||
if t := titer.Advance(); t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
subArr, err = titer.Array(subArr)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
tag, err := subArr.AsString()
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
event.Tags = append(event.Tags, tag)
|
||||
}
|
||||
default:
|
||||
return obj, arr, subArr, fmt.Errorf("unexpected event field '%s'", name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, arr, subArr, nil
|
||||
}
|
||||
|
||||
var (
|
||||
attrIds = []byte("ids")
|
||||
attrAuthors = []byte("authors")
|
||||
attrKinds = []byte("kinds")
|
||||
attrLimit = []byte("limit")
|
||||
attrSince = []byte("since")
|
||||
attrUntil = []byte("until")
|
||||
attrSearch = []byte("search")
|
||||
)
|
||||
|
||||
func (filter *Filter) UnmarshalSIMD(
|
||||
iter *simdjson.Iter,
|
||||
obj *simdjson.Object,
|
||||
arr *simdjson.Array,
|
||||
) (*simdjson.Object, *simdjson.Array, error) {
|
||||
obj, err := iter.Object(obj)
|
||||
if err != nil {
|
||||
return obj, arr, fmt.Errorf("unexpected at filter: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
name, t, err := obj.NextElementBytes(iter)
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
} else if t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(name, attrIds):
|
||||
if arr, err = iter.Array(arr); err == nil {
|
||||
filter.IDs, err = arr.AsString()
|
||||
}
|
||||
case bytes.Equal(name, attrAuthors):
|
||||
if arr, err = iter.Array(arr); err == nil {
|
||||
filter.Authors, err = arr.AsString()
|
||||
}
|
||||
case bytes.Equal(name, attrKinds):
|
||||
if arr, err = iter.Array(arr); err == nil {
|
||||
i := arr.Iter()
|
||||
filter.Kinds = make([]int, 0, 6)
|
||||
for {
|
||||
t := i.Advance()
|
||||
if t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
if kind, err := i.Uint(); err != nil {
|
||||
return obj, arr, err
|
||||
} else {
|
||||
filter.Kinds = append(filter.Kinds, int(kind))
|
||||
}
|
||||
}
|
||||
}
|
||||
case bytes.Equal(name, attrSearch):
|
||||
filter.Search, err = iter.String()
|
||||
case bytes.Equal(name, attrSince):
|
||||
var tsu uint64
|
||||
tsu, err = iter.Uint()
|
||||
ts := Timestamp(tsu)
|
||||
filter.Since = &ts
|
||||
case bytes.Equal(name, attrUntil):
|
||||
var tsu uint64
|
||||
tsu, err = iter.Uint()
|
||||
ts := Timestamp(tsu)
|
||||
filter.Until = &ts
|
||||
case bytes.Equal(name, attrLimit):
|
||||
var limit uint64
|
||||
limit, err = iter.Uint()
|
||||
filter.Limit = int(limit)
|
||||
if limit == 0 {
|
||||
filter.LimitZero = true
|
||||
}
|
||||
default:
|
||||
if len(name) > 1 && name[0] == '#' {
|
||||
if filter.Tags == nil {
|
||||
filter.Tags = make(TagMap, 1)
|
||||
}
|
||||
|
||||
arr, err := iter.Array(arr)
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
}
|
||||
vals, err := arr.AsString()
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
}
|
||||
|
||||
filter.Tags[string(name[1:])] = vals
|
||||
continue
|
||||
}
|
||||
|
||||
return obj, arr, fmt.Errorf("unexpected filter field '%s'", name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, arr, nil
|
||||
}
|
||||
|
185
envelopes_sonic.go
Normal file
185
envelopes_sonic.go
Normal file
@ -0,0 +1,185 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/bytedance/sonic/ast"
|
||||
)
|
||||
|
||||
type SonicMessageParser struct{}
|
||||
|
||||
func (smp *SonicMessageParser) ParseMessage(message []byte) (Envelope, error) {
|
||||
var err error
|
||||
|
||||
tlarr, _ := ast.NewParser(string(message)).Parse()
|
||||
label, _ := tlarr.Index(0).StrictString()
|
||||
|
||||
var v Envelope
|
||||
switch label {
|
||||
case "EVENT":
|
||||
env := &EventEnvelope{}
|
||||
sndN := tlarr.Index(1)
|
||||
var evtN *ast.Node
|
||||
switch sndN.TypeSafe() {
|
||||
case ast.V_STRING:
|
||||
subId, _ := sndN.StrictString()
|
||||
env.SubscriptionID = &subId
|
||||
evtN = tlarr.Index(2)
|
||||
case ast.V_OBJECT:
|
||||
evtN = sndN
|
||||
}
|
||||
err = eventFromSonicAst(&env.Event, evtN)
|
||||
v = env
|
||||
case "REQ":
|
||||
env := &ReqEnvelope{}
|
||||
nodes, _ := tlarr.ArrayUseNode()
|
||||
env.SubscriptionID, _ = nodes[1].StrictString()
|
||||
env.Filters = make(Filters, len(nodes)-2)
|
||||
for i, node := range nodes[2:] {
|
||||
err = filterFromSonicAst(&env.Filters[i], &node)
|
||||
}
|
||||
v = env
|
||||
case "COUNT":
|
||||
env := &CountEnvelope{}
|
||||
env.SubscriptionID, _ = tlarr.Index(1).StrictString()
|
||||
trdN := tlarr.Index(2)
|
||||
if countN := trdN.Get("count"); countN.Exists() {
|
||||
count, _ := countN.Int64()
|
||||
env.Count = &count
|
||||
hll, _ := trdN.Get("hll").StrictString()
|
||||
if len(hll) == 512 {
|
||||
env.HyperLogLog, _ = hex.DecodeString(hll)
|
||||
}
|
||||
} else {
|
||||
err = filterFromSonicAst(&env.Filter, trdN)
|
||||
}
|
||||
v = env
|
||||
case "NOTICE":
|
||||
notice, _ := tlarr.Index(1).StrictString()
|
||||
env := NoticeEnvelope(notice)
|
||||
v = &env
|
||||
case "EOSE":
|
||||
subId, _ := tlarr.Index(1).StrictString()
|
||||
env := EOSEEnvelope(subId)
|
||||
v = &env
|
||||
case "OK":
|
||||
env := &OKEnvelope{}
|
||||
env.EventID, _ = tlarr.Index(1).StrictString()
|
||||
env.OK, _ = tlarr.Index(2).Bool()
|
||||
env.Reason, _ = tlarr.Index(3).StrictString()
|
||||
v = env
|
||||
case "AUTH":
|
||||
env := &AuthEnvelope{}
|
||||
sndN := tlarr.Index(1)
|
||||
switch sndN.TypeSafe() {
|
||||
case ast.V_STRING:
|
||||
challenge, _ := sndN.StrictString()
|
||||
env.Challenge = &challenge
|
||||
case ast.V_OBJECT:
|
||||
err = eventFromSonicAst(&env.Event, sndN)
|
||||
}
|
||||
v = env
|
||||
case "CLOSED":
|
||||
env := &ClosedEnvelope{}
|
||||
env.SubscriptionID, _ = tlarr.Index(1).StrictString()
|
||||
env.Reason, _ = tlarr.Index(2).StrictString()
|
||||
v = env
|
||||
case "CLOSE":
|
||||
reason, _ := tlarr.Index(1).StrictString()
|
||||
env := CloseEnvelope(reason)
|
||||
v = &env
|
||||
default:
|
||||
return nil, UnknownLabel
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func eventFromSonicAst(evt *Event, node *ast.Node) error {
|
||||
evt.ID, _ = node.Get("id").StrictString()
|
||||
evt.PubKey, _ = node.Get("pubkey").StrictString()
|
||||
evt.Content, _ = node.Get("content").StrictString()
|
||||
evt.Sig, _ = node.Get("sig").StrictString()
|
||||
kind, _ := node.Get("kind").Int64()
|
||||
evt.Kind = int(kind)
|
||||
createdAt, _ := node.Get("created_at").Int64()
|
||||
evt.CreatedAt = Timestamp(createdAt)
|
||||
tagsN, err := node.Get("tags").ArrayUseNode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tags: %w", err)
|
||||
}
|
||||
evt.Tags = make(Tags, len(tagsN))
|
||||
for i, tagN := range tagsN {
|
||||
itemsN, err := tagN.ArrayUseNode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tag: %w", err)
|
||||
}
|
||||
tag := make(Tag, len(itemsN))
|
||||
|
||||
for j, itemN := range itemsN {
|
||||
tag[j], _ = itemN.StrictString()
|
||||
}
|
||||
|
||||
evt.Tags[i] = tag
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterFromSonicAst(filter *Filter, node *ast.Node) error {
|
||||
var err error
|
||||
|
||||
node.ForEach(func(path ast.Sequence, node *ast.Node) bool {
|
||||
switch *path.Key {
|
||||
case "limit":
|
||||
limit, _ := node.Int64()
|
||||
filter.Limit = int(limit)
|
||||
filter.LimitZero = filter.Limit == 0
|
||||
case "since":
|
||||
since, _ := node.Int64()
|
||||
filter.Since = (*Timestamp)(&since)
|
||||
case "until":
|
||||
until, _ := node.Int64()
|
||||
filter.Until = (*Timestamp)(&until)
|
||||
case "search":
|
||||
filter.Search, _ = node.StrictString()
|
||||
case "ids":
|
||||
idsN, _ := node.ArrayUseNode()
|
||||
filter.IDs = make([]string, len(idsN))
|
||||
for i, idN := range idsN {
|
||||
filter.IDs[i], _ = idN.StrictString()
|
||||
}
|
||||
case "authors":
|
||||
authorsN, _ := node.ArrayUseNode()
|
||||
filter.Authors = make([]string, len(authorsN))
|
||||
for i, authorN := range authorsN {
|
||||
filter.Authors[i], _ = authorN.StrictString()
|
||||
}
|
||||
case "kinds":
|
||||
kindsN, _ := node.ArrayUseNode()
|
||||
filter.Kinds = make([]int, len(kindsN))
|
||||
for i, kindN := range kindsN {
|
||||
kind, _ := kindN.Int64()
|
||||
filter.Kinds[i] = int(kind)
|
||||
}
|
||||
default:
|
||||
if len(*path.Key) > 1 && (*path.Key)[0] == '#' {
|
||||
if filter.Tags == nil {
|
||||
filter.Tags = make(TagMap, 2)
|
||||
}
|
||||
tagsN, _ := node.ArrayUseNode()
|
||||
tags := make([]string, len(tagsN))
|
||||
for i, authorN := range tagsN {
|
||||
tags[i], _ = authorN.StrictString()
|
||||
}
|
||||
filter.Tags[(*path.Key)[1:]] = tags
|
||||
} else {
|
||||
err = fmt.Errorf("unexpected field '%s'", *path.Key)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
@ -8,120 +8,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEventEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
eventEnvelopes := []string{
|
||||
`["EVENT","_",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`,
|
||||
`["EVENT",{"kind":3,"id":"9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2","pubkey":"373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7","created_at":1644844224,"tags":[["p","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"],["p","75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"],["p","46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"]],"content":"{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}","sig":"811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}]`,
|
||||
}
|
||||
|
||||
for _, raw := range eventEnvelopes {
|
||||
var env EventEnvelope
|
||||
err := json.Unmarshal([]byte(raw), &env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, env.GetID(), env.ID)
|
||||
|
||||
ok, _ := env.CheckSignature()
|
||||
assert.True(t, ok)
|
||||
|
||||
asJSON, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, raw, string(asJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoticeEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
noticeEnv := `["NOTICE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`
|
||||
var env NoticeEnvelope
|
||||
err := json.Unmarshal([]byte(noticeEnv), &env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "kjasbdlasvdluiasvd\"kjasbdksab\\d", string(env))
|
||||
|
||||
res, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, noticeEnv, string(res))
|
||||
}
|
||||
|
||||
func TestEoseEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
eoseEnv := `["EOSE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`
|
||||
var env EOSEEnvelope
|
||||
err := json.Unmarshal([]byte(eoseEnv), &env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "kjasbdlasvdluiasvd\"kjasbdksab\\d", string(env))
|
||||
|
||||
res, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, eoseEnv, string(res))
|
||||
}
|
||||
|
||||
func TestCountEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
countEnv := `["COUNT","z",{"count":12}]`
|
||||
var env CountEnvelope
|
||||
err := json.Unmarshal([]byte(countEnv), &env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(12), *env.Count)
|
||||
|
||||
res, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, countEnv, string(res))
|
||||
}
|
||||
|
||||
func TestOKEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
okEnvelopes := []string{
|
||||
`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",false,"error: could not connect to the database"]`,
|
||||
`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",true,""]`,
|
||||
}
|
||||
|
||||
for _, raw := range okEnvelopes {
|
||||
var env OKEnvelope
|
||||
err := json.Unmarshal([]byte(raw), &env)
|
||||
assert.NoError(t, err)
|
||||
|
||||
asJSON, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, raw, string(asJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClosedEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
closeEnvelopes := []string{
|
||||
`["CLOSED","_","error: something went wrong"]`,
|
||||
`["CLOSED",":1","auth-required: take a selfie and send it to the CIA"]`,
|
||||
}
|
||||
|
||||
for _, raw := range closeEnvelopes {
|
||||
var env ClosedEnvelope
|
||||
err := json.Unmarshal([]byte(raw), &env)
|
||||
assert.NoError(t, err)
|
||||
assert.Condition(t, func() (success bool) {
|
||||
if env.SubscriptionID != "_" && env.SubscriptionID != ":1" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
res, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, raw, string(res))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthEnvelopeEncodingAndDecoding(t *testing.T) {
|
||||
authEnvelopes := []string{
|
||||
`["AUTH","kjsabdlasb aslkd kasndkad \"as.kdnbskadb"]`,
|
||||
`["AUTH",{"kind":1,"id":"ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1683660344,"tags":[],"content":"hello world","sig":"94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51"}]`,
|
||||
}
|
||||
|
||||
for _, raw := range authEnvelopes {
|
||||
var env AuthEnvelope
|
||||
err := json.Unmarshal([]byte(raw), &env)
|
||||
assert.NoError(t, err)
|
||||
|
||||
asJSON, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, raw, string(asJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMessage(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
@ -144,187 +30,142 @@ func TestParseMessage(t *testing.T) {
|
||||
ExpectedEnvelope: nil,
|
||||
},
|
||||
{
|
||||
Name: "CLOSED envelope",
|
||||
Message: []byte(`["CLOSED",":1","error: we are broken"]`),
|
||||
ExpectedEnvelope: &ClosedEnvelope{SubscriptionID: ":1", Reason: "error: we are broken"},
|
||||
Name: "EVENT envelope with subscription id",
|
||||
Message: []byte(`["EVENT","_",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`),
|
||||
ExpectedEnvelope: &EventEnvelope{SubscriptionID: ptr("_"), Event: Event{Kind: 1, ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", PubKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", CreatedAt: 1644271588, Tags: Tags{}, Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", Sig: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}},
|
||||
},
|
||||
{
|
||||
Name: "AUTH envelope",
|
||||
Message: []byte(`["AUTH","bisteka"]`),
|
||||
ExpectedEnvelope: &AuthEnvelope{Challenge: ptr("bisteka")},
|
||||
Name: "EVENT envelope without subscription id",
|
||||
Message: []byte(`["EVENT",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`),
|
||||
ExpectedEnvelope: &EventEnvelope{Event: Event{Kind: 1, ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", PubKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", CreatedAt: 1644271588, Tags: Tags{}, Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", Sig: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}},
|
||||
},
|
||||
{
|
||||
Name: "EVENT envelope with tags",
|
||||
Message: []byte(`["EVENT",{"kind":3,"id":"9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2","pubkey":"373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7","created_at":1644844224,"tags":[["p","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"],["p","75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"],["p","46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"]],"content":"{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}","sig":"811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}]`),
|
||||
ExpectedEnvelope: &EventEnvelope{Event: Event{Kind: 3, ID: "9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2", PubKey: "373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7", CreatedAt: 1644844224, Tags: Tags{Tag{"p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"}, Tag{"p", "75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"}, Tag{"p", "46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"}}, Content: "{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}", Sig: "811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}},
|
||||
},
|
||||
{
|
||||
Name: "NOTICE envelope",
|
||||
Message: []byte(`["NOTICE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`),
|
||||
ExpectedEnvelope: ptr(NoticeEnvelope("kjasbdlasvdluiasvd\"kjasbdksab\\d")),
|
||||
},
|
||||
{
|
||||
Name: "EOSE envelope",
|
||||
Message: []byte(`["EOSE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`),
|
||||
ExpectedEnvelope: ptr(EOSEEnvelope("kjasbdlasvdluiasvd\"kjasbdksab\\d")),
|
||||
},
|
||||
{
|
||||
Name: "COUNT envelope",
|
||||
Message: []byte(`["COUNT","z",{"count":12}]`),
|
||||
ExpectedEnvelope: &CountEnvelope{SubscriptionID: "z", Count: ptr(int64(12))},
|
||||
},
|
||||
{
|
||||
Name: "COUNT envelope with HLL",
|
||||
Message: []byte(`["COUNT","sub1",{"count":42, "hll": "0100000101000000000000040000000001020000000002000000000200000003000002040000000101020001010000000000000007000004010000000200040000020400000000000102000002000004010000010000000301000102030002000301000300010000070000000001000004000102010000000400010002000000000103000100010001000001040100020001000000000000010000020000000000030100000001000400010000000000000901010100000000040000000b030000010100010000010000010000000003000000000000010003000100020000000000010000010100000100000104000200030001000300000001000101000102"}]`),
|
||||
ExpectedEnvelope: &CountEnvelope{SubscriptionID: "sub1", Count: ptr(int64(42)), HyperLogLog: []byte{1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 2, 4, 0, 0, 0, 1, 1, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 4, 1, 0, 0, 0, 2, 0, 4, 0, 0, 2, 4, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 0, 0, 4, 1, 0, 0, 1, 0, 0, 0, 3, 1, 0, 1, 2, 3, 0, 2, 0, 3, 1, 0, 3, 0, 1, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0, 4, 0, 1, 2, 1, 0, 0, 0, 4, 0, 1, 0, 2, 0, 0, 0, 0, 1, 3, 0, 1, 0, 1, 0, 1, 0, 0, 1, 4, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 9, 1, 1, 1, 0, 0, 0, 0, 4, 0, 0, 0, 11, 3, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 4, 0, 2, 0, 3, 0, 1, 0, 3, 0, 0, 0, 1, 0, 1, 1, 0, 1, 2}},
|
||||
},
|
||||
{
|
||||
Name: "OK envelope success",
|
||||
Message: []byte(`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",true,""]`),
|
||||
ExpectedEnvelope: &OKEnvelope{EventID: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa", OK: true, Reason: ""},
|
||||
},
|
||||
{
|
||||
Name: "OK envelope failure",
|
||||
Message: []byte(`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",false,"error: could not connect to the database"]`),
|
||||
ExpectedEnvelope: &OKEnvelope{EventID: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa", OK: false, Reason: "error: could not connect to the database"},
|
||||
},
|
||||
{
|
||||
Name: "CLOSED envelope with underscore",
|
||||
Message: []byte(`["CLOSED","_","error: something went wrong"]`),
|
||||
ExpectedEnvelope: &ClosedEnvelope{SubscriptionID: "_", Reason: "error: something went wrong"},
|
||||
},
|
||||
{
|
||||
Name: "CLOSED envelope with colon",
|
||||
Message: []byte(`["CLOSED",":1","auth-required: take a selfie and send it to the CIA"]`),
|
||||
ExpectedEnvelope: &ClosedEnvelope{SubscriptionID: ":1", Reason: "auth-required: take a selfie and send it to the CIA"},
|
||||
},
|
||||
{
|
||||
Name: "AUTH envelope with challenge",
|
||||
Message: []byte(`["AUTH","kjsabdlasb aslkd kasndkad \"as.kdnbskadb"]`),
|
||||
ExpectedEnvelope: &AuthEnvelope{Challenge: ptr("kjsabdlasb aslkd kasndkad \"as.kdnbskadb")},
|
||||
},
|
||||
{
|
||||
Name: "AUTH envelope with event",
|
||||
Message: []byte(`["AUTH",{"kind":1,"id":"ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1683660344,"tags":[],"content":"hello world","sig":"94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51"}]`),
|
||||
ExpectedEnvelope: &AuthEnvelope{Event: Event{Kind: 1, ID: "ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69", PubKey: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", CreatedAt: 1683660344, Tags: Tags{}, Content: "hello world", Sig: "94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51"}},
|
||||
},
|
||||
{
|
||||
Name: "REQ envelope",
|
||||
Message: []byte(`["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko", "batuke"]}]`),
|
||||
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "million", Filters: Filters{{Kinds: []int{1}}, {Kinds: []int{30023}, Tags: TagMap{"d": []string{"buteko", "batuke"}}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
envelope := ParseMessage(testCase.Message)
|
||||
if testCase.ExpectedEnvelope == nil && envelope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if testCase.ExpectedEnvelope == nil {
|
||||
assert.NotNil(t, envelope, "expected nil but got %v\n", envelope)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.ExpectedEnvelope.String(), envelope.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMessageSIMD(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Message []byte
|
||||
ExpectedEnvelope Envelope
|
||||
ExpectedErrorSubstring string
|
||||
}{
|
||||
{
|
||||
Name: "nil",
|
||||
Message: nil,
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "parse failed",
|
||||
},
|
||||
{
|
||||
Name: "empty string",
|
||||
Message: []byte(""),
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "parse failed",
|
||||
},
|
||||
{
|
||||
Name: "invalid input",
|
||||
Message: []byte("invalid input"),
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "parse failed",
|
||||
},
|
||||
{
|
||||
Name: "invalid JSON",
|
||||
Message: []byte("{not valid json}"),
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "parse failed",
|
||||
},
|
||||
{
|
||||
Name: "invalid REQ",
|
||||
Message: []byte(`["REQ","zzz", {"authors": [23]}]`),
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "not string, but int",
|
||||
},
|
||||
{
|
||||
Name: "same invalid REQ from before, but now valid",
|
||||
Message: []byte(`["REQ","zzz", {"kinds": [23]}]`),
|
||||
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "zzz", Filters: Filters{{Kinds: []int{23}}}},
|
||||
},
|
||||
{
|
||||
Name: "different invalid REQ",
|
||||
Message: []byte(`["REQ","zzz", {"authors": "string"}]`),
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "next item is not",
|
||||
},
|
||||
{
|
||||
Name: "yet another",
|
||||
Message: []byte(`["REQ","zzz", {"unknownfield": "_"}]`),
|
||||
ExpectedEnvelope: nil,
|
||||
ExpectedErrorSubstring: "unexpected filter field 'unknownfield'",
|
||||
},
|
||||
{
|
||||
Name: "EVENT envelope with subscription id",
|
||||
Message: []byte(
|
||||
`["EVENT","_",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`,
|
||||
),
|
||||
ExpectedEnvelope: &EventEnvelope{SubscriptionID: ptr("_"), Event: Event{Kind: 1, ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", PubKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", CreatedAt: 1644271588, Tags: Tags{}, Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", Sig: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}},
|
||||
},
|
||||
{
|
||||
Name: "EVENT envelope without subscription id",
|
||||
Message: []byte(
|
||||
`["EVENT",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`,
|
||||
),
|
||||
ExpectedEnvelope: &EventEnvelope{Event: Event{Kind: 1, ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", PubKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", CreatedAt: 1644271588, Tags: Tags{}, Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", Sig: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}},
|
||||
},
|
||||
{
|
||||
Name: "AUTH envelope with challenge",
|
||||
Message: []byte(`["AUTH","challenge-string"]`),
|
||||
ExpectedEnvelope: &AuthEnvelope{Challenge: ptr("challenge-string")},
|
||||
},
|
||||
{
|
||||
Name: "AUTH envelope with event",
|
||||
Message: []byte(
|
||||
`["AUTH", {"kind":22242,"id":"9b86ca5d2a9b4aa09870e710438c2fd2fcdeca12a18b6f17ab9ebcdbc43f1d4a","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1740505646,"tags":[["relay","ws://localhost:7777","2"],["challenge","3027526784722639360"]],"content":"","sig":"eceb827c4bba1de0ab8ee43f3e98df71194f5bdde0af27b5cda38e5c4338b5f63d31961acb5e3c119fd00ecef8b469867d060b697dbaa6ecee1906b483bc307d"}]`,
|
||||
),
|
||||
ExpectedEnvelope: &AuthEnvelope{Event: Event{Kind: 22242, ID: "9b86ca5d2a9b4aa09870e710438c2fd2fcdeca12a18b6f17ab9ebcdbc43f1d4a", PubKey: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", CreatedAt: 1740505646, Tags: Tags{Tag{"relay", "ws://localhost:7777", "2"}, Tag{"challenge", "3027526784722639360"}}, Content: "", Sig: "eceb827c4bba1de0ab8ee43f3e98df71194f5bdde0af27b5cda38e5c4338b5f63d31961acb5e3c119fd00ecef8b469867d060b697dbaa6ecee1906b483bc307d"}},
|
||||
},
|
||||
{
|
||||
Name: "NOTICE envelope",
|
||||
Message: []byte(`["NOTICE","test notice message"]`),
|
||||
ExpectedEnvelope: ptr(NoticeEnvelope("test notice message")),
|
||||
},
|
||||
{
|
||||
Name: "EOSE envelope",
|
||||
Message: []byte(`["EOSE","subscription123"]`),
|
||||
ExpectedEnvelope: ptr(EOSEEnvelope("subscription123")),
|
||||
},
|
||||
{
|
||||
Name: "CLOSE envelope",
|
||||
Message: []byte(`["CLOSE","subscription123"]`),
|
||||
ExpectedEnvelope: ptr(CloseEnvelope("subscription123")),
|
||||
},
|
||||
{
|
||||
Name: "CLOSED envelope",
|
||||
Message: []byte(`["CLOSED","subscription123","reason: test closed"]`),
|
||||
ExpectedEnvelope: &ClosedEnvelope{SubscriptionID: "subscription123", Reason: "reason: test closed"},
|
||||
},
|
||||
{
|
||||
Name: "OK envelope",
|
||||
Message: []byte(`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",true,""]`),
|
||||
ExpectedEnvelope: &OKEnvelope{EventID: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa", OK: true, Reason: ""},
|
||||
},
|
||||
{
|
||||
Name: "COUNT envelope with just count",
|
||||
Message: []byte(`["COUNT","sub1",{"count":42}]`),
|
||||
ExpectedEnvelope: &CountEnvelope{SubscriptionID: "sub1", Count: ptr(int64(42))},
|
||||
},
|
||||
{
|
||||
Name: "COUNT envelope with count and hll",
|
||||
Message: []byte(`["COUNT","sub1",{"count":42, "hll": "0100000101000000000000040000000001020000000002000000000200000003000002040000000101020001010000000000000007000004010000000200040000020400000000000102000002000004010000010000000301000102030002000301000300010000070000000001000004000102010000000400010002000000000103000100010001000001040100020001000000000000010000020000000000030100000001000400010000000000000901010100000000040000000b030000010100010000010000010000000003000000000000010003000100020000000000010000010100000100000104000200030001000300000001000101000102"}]`),
|
||||
ExpectedEnvelope: &CountEnvelope{SubscriptionID: "sub1", Count: ptr(int64(42)), HyperLogLog: []byte{1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 2, 4, 0, 0, 0, 1, 1, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 4, 1, 0, 0, 0, 2, 0, 4, 0, 0, 2, 4, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 0, 0, 4, 1, 0, 0, 1, 0, 0, 0, 3, 1, 0, 1, 2, 3, 0, 2, 0, 3, 1, 0, 3, 0, 1, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0, 4, 0, 1, 2, 1, 0, 0, 0, 4, 0, 1, 0, 2, 0, 0, 0, 0, 1, 3, 0, 1, 0, 1, 0, 1, 0, 0, 1, 4, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 9, 1, 1, 1, 0, 0, 0, 0, 4, 0, 0, 0, 11, 3, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 4, 0, 2, 0, 3, 0, 1, 0, 3, 0, 0, 0, 1, 0, 1, 1, 0, 1, 2}},
|
||||
},
|
||||
{
|
||||
Name: "REQ envelope",
|
||||
Message: []byte(`["REQ","sub1", {"until": 999999, "kinds":[1]}]`),
|
||||
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "sub1", Filters: Filters{{Kinds: []int{1}, Until: ptr(Timestamp(999999))}}},
|
||||
},
|
||||
{
|
||||
Name: "bigger REQ envelope",
|
||||
Message: []byte(`["REQ","sub1z\\\"zzz", {"authors":["9b86ca5d2a9b4aa09870e710438c2fd2fcdeca12a18b6f17ab9ebcdbc43f1d4a","8eee10b2ce1162b040fdcfdadb4f888c64aaf87531dab28cc0c09fbdea1b663e","0deadebefb3c1a760f036952abf675076343dd8424efdeaa0f1d9803a359ed46"],"since":1740425099,"limit":2,"#x":["<","as"]}, {"kinds": [2345, 112], "#plic": ["a"], "#ploc": ["blblb", "wuwuw"]}]`),
|
||||
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "sub1z\\\"zzz", Filters: Filters{{Authors: []string{"9b86ca5d2a9b4aa09870e710438c2fd2fcdeca12a18b6f17ab9ebcdbc43f1d4a", "8eee10b2ce1162b040fdcfdadb4f888c64aaf87531dab28cc0c09fbdea1b663e", "0deadebefb3c1a760f036952abf675076343dd8424efdeaa0f1d9803a359ed46"}, Since: ptr(Timestamp(1740425099)), Limit: 2, Tags: TagMap{"x": []string{"<", "as"}}}, {Kinds: []int{2345, 112}, Tags: TagMap{"plic": []string{"a"}, "ploc": []string{"blblb", "wuwuw"}}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run("standard", func(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
envelope := ParseMessage(testCase.Message)
|
||||
if testCase.ExpectedEnvelope == nil && envelope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if testCase.ExpectedEnvelope == nil {
|
||||
assert.Nil(t, envelope, "expected nil but got %v", envelope)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotNil(t, envelope, "expected non-nil envelope but got nil")
|
||||
assert.Equal(t, testCase.ExpectedEnvelope.String(), envelope.String())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("simdjson", func(t *testing.T) {
|
||||
smp := SIMDMessageParser{AuxIter: &simdjson.Iter{}}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
envelope, err := smp.ParseMessage(testCase.Message)
|
||||
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
envelope, err := smp.ParseMessage(testCase.Message)
|
||||
if testCase.ExpectedEnvelope == nil && envelope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if testCase.ExpectedEnvelope == nil {
|
||||
require.Nil(t, envelope, "expected nil but got %v", envelope)
|
||||
return
|
||||
}
|
||||
|
||||
if testCase.ExpectedErrorSubstring == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Contains(t, err.Error(), testCase.ExpectedErrorSubstring)
|
||||
return
|
||||
}
|
||||
require.NotNil(t, envelope, "expected non-nil envelope but got nil")
|
||||
require.Equal(t, testCase.ExpectedEnvelope.String(), envelope.String())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if testCase.ExpectedEnvelope == nil {
|
||||
require.Nil(t, envelope, "expected nil but got %v", envelope)
|
||||
return
|
||||
}
|
||||
t.Run("sonic", func(t *testing.T) {
|
||||
smp := SonicMessageParser{}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
envelope, err := smp.ParseMessage(testCase.Message)
|
||||
|
||||
require.NotNil(t, envelope, "expected non-nil envelope but got nil")
|
||||
require.Equal(t, testCase.ExpectedEnvelope, envelope)
|
||||
})
|
||||
}
|
||||
if testCase.ExpectedEnvelope == nil && envelope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if testCase.ExpectedEnvelope == nil {
|
||||
require.Nil(t, envelope, "expected nil but got %v", envelope)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, envelope, "expected non-nil envelope but got nil")
|
||||
require.Equal(t, testCase.ExpectedEnvelope.String(), envelope.String())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func ptr[S any](s S) *S { return &s }
|
||||
|
@ -1,87 +0,0 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/minio/simdjson-go"
|
||||
)
|
||||
|
||||
var (
|
||||
attrId = []byte("id")
|
||||
attrPubkey = []byte("pubkey")
|
||||
attrCreatedAt = []byte("created_at")
|
||||
attrKind = []byte("kind")
|
||||
attrContent = []byte("content")
|
||||
attrTags = []byte("tags")
|
||||
attrSig = []byte("sig")
|
||||
)
|
||||
|
||||
func (event *Event) UnmarshalSIMD(
|
||||
iter *simdjson.Iter,
|
||||
obj *simdjson.Object,
|
||||
arr *simdjson.Array,
|
||||
subArr *simdjson.Array,
|
||||
) (*simdjson.Object, *simdjson.Array, *simdjson.Array, error) {
|
||||
obj, err := iter.Object(obj)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, fmt.Errorf("unexpected at event: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
name, t, err := obj.NextElementBytes(iter)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
} else if t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(name, attrId):
|
||||
event.ID, err = iter.String()
|
||||
case bytes.Equal(name, attrPubkey):
|
||||
event.PubKey, err = iter.String()
|
||||
case bytes.Equal(name, attrContent):
|
||||
event.Content, err = iter.String()
|
||||
case bytes.Equal(name, attrSig):
|
||||
event.Sig, err = iter.String()
|
||||
case bytes.Equal(name, attrCreatedAt):
|
||||
var ts uint64
|
||||
ts, err = iter.Uint()
|
||||
event.CreatedAt = Timestamp(ts)
|
||||
case bytes.Equal(name, attrKind):
|
||||
var kind uint64
|
||||
kind, err = iter.Uint()
|
||||
event.Kind = int(kind)
|
||||
case bytes.Equal(name, attrTags):
|
||||
arr, err = iter.Array(arr)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
event.Tags = make(Tags, 0, 10)
|
||||
titer := arr.Iter()
|
||||
for {
|
||||
if t := titer.Advance(); t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
subArr, err = titer.Array(subArr)
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
tag, err := subArr.AsString()
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
event.Tags = append(event.Tags, tag)
|
||||
}
|
||||
default:
|
||||
return obj, arr, subArr, fmt.Errorf("unexpected event field '%s'", name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return obj, arr, subArr, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, arr, subArr, nil
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/minio/simdjson-go"
|
||||
)
|
||||
|
||||
var (
|
||||
attrIds = []byte("ids")
|
||||
attrAuthors = []byte("authors")
|
||||
attrKinds = []byte("kinds")
|
||||
attrLimit = []byte("limit")
|
||||
attrSince = []byte("since")
|
||||
attrUntil = []byte("until")
|
||||
attrSearch = []byte("search")
|
||||
)
|
||||
|
||||
func (filter *Filter) UnmarshalSIMD(
|
||||
iter *simdjson.Iter,
|
||||
obj *simdjson.Object,
|
||||
arr *simdjson.Array,
|
||||
) (*simdjson.Object, *simdjson.Array, error) {
|
||||
obj, err := iter.Object(obj)
|
||||
if err != nil {
|
||||
return obj, arr, fmt.Errorf("unexpected at filter: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
name, t, err := obj.NextElementBytes(iter)
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
} else if t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(name, attrIds):
|
||||
if arr, err = iter.Array(arr); err == nil {
|
||||
filter.IDs, err = arr.AsString()
|
||||
}
|
||||
case bytes.Equal(name, attrAuthors):
|
||||
if arr, err = iter.Array(arr); err == nil {
|
||||
filter.Authors, err = arr.AsString()
|
||||
}
|
||||
case bytes.Equal(name, attrKinds):
|
||||
if arr, err = iter.Array(arr); err == nil {
|
||||
i := arr.Iter()
|
||||
filter.Kinds = make([]int, 0, 6)
|
||||
for {
|
||||
t := i.Advance()
|
||||
if t == simdjson.TypeNone {
|
||||
break
|
||||
}
|
||||
if kind, err := i.Uint(); err != nil {
|
||||
return obj, arr, err
|
||||
} else {
|
||||
filter.Kinds = append(filter.Kinds, int(kind))
|
||||
}
|
||||
}
|
||||
}
|
||||
case bytes.Equal(name, attrSearch):
|
||||
filter.Search, err = iter.String()
|
||||
case bytes.Equal(name, attrSince):
|
||||
var tsu uint64
|
||||
tsu, err = iter.Uint()
|
||||
ts := Timestamp(tsu)
|
||||
filter.Since = &ts
|
||||
case bytes.Equal(name, attrUntil):
|
||||
var tsu uint64
|
||||
tsu, err = iter.Uint()
|
||||
ts := Timestamp(tsu)
|
||||
filter.Until = &ts
|
||||
case bytes.Equal(name, attrLimit):
|
||||
var limit uint64
|
||||
limit, err = iter.Uint()
|
||||
filter.Limit = int(limit)
|
||||
if limit == 0 {
|
||||
filter.LimitZero = true
|
||||
}
|
||||
default:
|
||||
if len(name) > 1 && name[0] == '#' {
|
||||
if filter.Tags == nil {
|
||||
filter.Tags = make(TagMap, 1)
|
||||
}
|
||||
|
||||
arr, err := iter.Array(arr)
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
}
|
||||
vals, err := arr.AsString()
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
}
|
||||
|
||||
filter.Tags[string(name[1:])] = vals
|
||||
continue
|
||||
}
|
||||
|
||||
return obj, arr, fmt.Errorf("unexpected filter field '%s'", name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return obj, arr, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, arr, nil
|
||||
}
|
11
go.mod
11
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/bluekeyes/go-gitdiff v0.7.1
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||
github.com/btcsuite/btcd/btcutil v1.1.5
|
||||
github.com/bytedance/sonic v1.12.10
|
||||
github.com/coder/websocket v1.8.12
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||
github.com/dgraph-io/badger/v4 v4.5.0
|
||||
@ -22,6 +23,7 @@ require (
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/simdjson-go v0.4.5
|
||||
github.com/ncruces/go-sqlite3 v0.18.3
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
@ -29,6 +31,7 @@ require (
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20240916111504-922dfa87e1e6
|
||||
github.com/tyler-smith/go-bip32 v1.0.0
|
||||
github.com/tyler-smith/go-bip39 v1.1.0
|
||||
github.com/valyala/fasthttp v1.51.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
|
||||
golang.org/x/net v0.34.0
|
||||
@ -46,7 +49,9 @@ require (
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/btcsuite/btcd v0.24.2 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
|
||||
@ -63,7 +68,6 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/minio/simdjson-go v0.4.5 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
@ -76,11 +80,12 @@ require (
|
||||
github.com/tetratelabs/wazero v1.8.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
google.golang.org/protobuf v1.36.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
|
23
go.sum
23
go.sum
@ -49,10 +49,18 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/bytedance/sonic v1.12.10 h1:uVCQr6oS5669E9ZVW0HyksTLfNS7Q/9hV6IVS4nEMsI=
|
||||
github.com/bytedance/sonic v1.12.10/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw=
|
||||
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@ -150,12 +158,12 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM=
|
||||
@ -228,6 +236,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20240916111504-922dfa87e1e6 h1:bFxO2fsY5mHZRrVvhmrAo/O8Agi9HDAIMmmOClZMrkQ=
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20240916111504-922dfa87e1e6/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE=
|
||||
github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE=
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
||||
@ -240,6 +250,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -284,10 +296,8 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -365,3 +375,4 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
@ -42,3 +42,5 @@ func TestSubIdExtract(t *testing.T) {
|
||||
require.Equal(t, "xxz", extractSubID(data))
|
||||
}
|
||||
}
|
||||
|
||||
func ptr[S any](s S) *S { return &s }
|
||||
|
@ -167,8 +167,10 @@ func (sub *Subscription) Fire() error {
|
||||
var reqb []byte
|
||||
if sub.countResult == nil {
|
||||
reqb, _ = ReqEnvelope{sub.id, sub.Filters}.MarshalJSON()
|
||||
} else if len(sub.Filters) == 1 {
|
||||
reqb, _ = CountEnvelope{sub.id, sub.Filters[0], nil, nil}.MarshalJSON()
|
||||
} else {
|
||||
reqb, _ = CountEnvelope{sub.id, sub.Filters, nil, nil}.MarshalJSON()
|
||||
return fmt.Errorf("unexpected sub configuration")
|
||||
}
|
||||
|
||||
sub.live.Store(true)
|
||||
|
Loading…
x
Reference in New Issue
Block a user