a big bundle of conversions and other changes.
This commit is contained in:
138
eventstore/codec/betterbinary/codec.go
Normal file
138
eventstore/codec/betterbinary/codec.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package betterbinary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxKind = math.MaxUint16
|
||||
MaxCreatedAt = math.MaxUint32
|
||||
MaxContentSize = math.MaxUint16
|
||||
MaxTagCount = math.MaxUint16
|
||||
MaxTagItemCount = math.MaxUint8
|
||||
MaxTagItemSize = math.MaxUint16
|
||||
)
|
||||
|
||||
func Measure(evt nostr.Event) int {
|
||||
n := 135 // static base
|
||||
|
||||
n += 2 + // tag section length
|
||||
2 + // number of tags
|
||||
len(evt.Tags)*3 // each tag offset + each tag item count
|
||||
for _, tag := range evt.Tags {
|
||||
n += len(tag) * 2 // item length for each item in this tag
|
||||
for _, item := range tag {
|
||||
n += len(item) // actual tag item
|
||||
}
|
||||
}
|
||||
|
||||
// content length and actual content
|
||||
n += 2 + len(evt.Content)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func Marshal(evt nostr.Event, buf []byte) error {
|
||||
buf[0] = 0
|
||||
|
||||
if evt.Kind > MaxKind {
|
||||
return fmt.Errorf("kind is too big: %d, max is %d", evt.Kind, MaxKind)
|
||||
}
|
||||
binary.LittleEndian.PutUint16(buf[1:3], uint16(evt.Kind))
|
||||
|
||||
if evt.CreatedAt > MaxCreatedAt {
|
||||
return fmt.Errorf("created_at is too big: %d, max is %d", evt.CreatedAt, MaxCreatedAt)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(buf[3:7], uint32(evt.CreatedAt))
|
||||
|
||||
copy(buf[7:39], evt.ID[:])
|
||||
copy(buf[39:71], evt.PubKey[:])
|
||||
copy(buf[71:135], evt.Sig[:])
|
||||
|
||||
tagBase := 135
|
||||
// buf[135:137] (tagsSectionLength) will be set later when we know the absolute size of the tags section
|
||||
|
||||
ntags := len(evt.Tags)
|
||||
if ntags > MaxTagCount {
|
||||
return fmt.Errorf("can't encode too many tags: %d, max is %d", ntags, MaxTagCount)
|
||||
}
|
||||
binary.LittleEndian.PutUint16(buf[137:139], uint16(ntags))
|
||||
|
||||
tagOffset := 2 + 2 + ntags*2
|
||||
for t, tag := range evt.Tags {
|
||||
binary.LittleEndian.PutUint16(buf[tagBase+2+2+t*2:], uint16(tagOffset))
|
||||
|
||||
itemCount := len(tag)
|
||||
if itemCount > MaxTagItemCount {
|
||||
return fmt.Errorf("can't encode a tag with so many items: %d, max is %d", itemCount, MaxTagItemCount)
|
||||
}
|
||||
buf[tagBase+tagOffset] = uint8(itemCount)
|
||||
|
||||
itemOffset := 1
|
||||
for _, item := range tag {
|
||||
itemSize := len(item)
|
||||
if itemSize > MaxTagItemSize {
|
||||
return fmt.Errorf("tag item is too large: %d, max is %d", itemSize, MaxTagItemSize)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(buf[tagBase+tagOffset+itemOffset:], uint16(itemSize))
|
||||
copy(buf[tagBase+tagOffset+itemOffset+2:], []byte(item))
|
||||
itemOffset += 2 + len(item)
|
||||
}
|
||||
tagOffset += itemOffset
|
||||
}
|
||||
|
||||
tagsSectionLength := tagOffset
|
||||
binary.LittleEndian.PutUint16(buf[tagBase:], uint16(tagsSectionLength))
|
||||
|
||||
// content
|
||||
if contentLength := len(evt.Content); contentLength > MaxContentSize {
|
||||
return fmt.Errorf("content is too large: %d, max is %d", contentLength, MaxContentSize)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint16(buf[tagBase+tagsSectionLength:], uint16(contentLength))
|
||||
}
|
||||
copy(buf[tagBase+tagsSectionLength+2:], []byte(evt.Content))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Unmarshal(data []byte, evt *nostr.Event) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("failed to decode binary: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
evt.Kind = uint16(binary.LittleEndian.Uint16(data[1:3]))
|
||||
evt.CreatedAt = nostr.Timestamp(binary.LittleEndian.Uint32(data[3:7]))
|
||||
evt.ID = nostr.ID(data[7:39])
|
||||
evt.PubKey = nostr.PubKey(data[39:71])
|
||||
evt.Sig = [64]byte(data[71:135])
|
||||
|
||||
const tagbase = 135
|
||||
tagsSectionLength := binary.LittleEndian.Uint16(data[tagbase:])
|
||||
ntags := binary.LittleEndian.Uint16(data[tagbase+2:])
|
||||
evt.Tags = make(nostr.Tags, ntags)
|
||||
for t := range evt.Tags {
|
||||
offset := binary.LittleEndian.Uint16(data[tagbase+4+t*2:])
|
||||
nitems := int(data[tagbase+offset])
|
||||
tag := make(nostr.Tag, nitems)
|
||||
|
||||
curr := tagbase + offset + 1
|
||||
for i := range tag {
|
||||
length := binary.LittleEndian.Uint16(data[curr:])
|
||||
tag[i] = string(data[curr+2 : curr+2+length])
|
||||
curr += 2 + length
|
||||
}
|
||||
evt.Tags[t] = tag
|
||||
}
|
||||
|
||||
contentLength := binary.LittleEndian.Uint16(data[tagbase+tagsSectionLength:])
|
||||
evt.Content = string(data[tagbase+tagsSectionLength+2 : tagbase+tagsSectionLength+2+contentLength])
|
||||
|
||||
return err
|
||||
}
|
||||
182
eventstore/codec/betterbinary/codec_test.go
Normal file
182
eventstore/codec/betterbinary/codec_test.go
Normal file
File diff suppressed because one or more lines are too long
33
eventstore/codec/betterbinary/filtering.go
Normal file
33
eventstore/codec/betterbinary/filtering.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package betterbinary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func TagMatches(evtb []byte, key string, vals []string) bool {
|
||||
matches := make([][]byte, 0, len(vals))
|
||||
for _, val := range vals {
|
||||
match := append([]byte{1, 0, key[0], uint8(len(val)), 0}, val...)
|
||||
matches = append(matches, match)
|
||||
}
|
||||
|
||||
ntags := binary.LittleEndian.Uint16(evtb[137:])
|
||||
var t uint16
|
||||
for t = 0; t < ntags; t++ {
|
||||
offset := int(binary.LittleEndian.Uint16(evtb[139+t*2:]))
|
||||
nitems := evtb[135+offset]
|
||||
if nitems >= 2 {
|
||||
for _, match := range matches {
|
||||
if slices.Equal(evtb[135+offset+1:135+offset+1+len(match)], match) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func KindMatches(evtb []byte, kind uint16) bool {
|
||||
return binary.LittleEndian.Uint16(evtb[1:3]) == kind
|
||||
}
|
||||
51
eventstore/codec/betterbinary/filtering_test.go
Normal file
51
eventstore/codec/betterbinary/filtering_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package betterbinary
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func TestTagFiltering(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
json string
|
||||
tagKey string
|
||||
tagValues []string
|
||||
matches bool
|
||||
}{
|
||||
{
|
||||
`{"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"kind":1,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","content":"He got snowed in"}`,
|
||||
"x",
|
||||
[]string{"sadjqw", ""},
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"kind":1,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","content":"He got snowed in","tags":[["client","gossip"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]]}`,
|
||||
"e",
|
||||
[]string{"2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{"id":"3f551da67788c7aae15360d025595dc2d391f10bb7e759ee5d9b2ad7d64392e4","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1712715433,"kind":1,"tags":[["-"],["askdasds"],["t","spam"],["t","nada"]],"content":"ggsgsgsg","sig":"43431f4cf8bd015305c2d484841e5730d261beeb375a86c57a61df3d26e820ce8d6712d2a3c89e3f2298597f14abf58079954e9e658ba59bfc2d7ce6384f25c7"}`,
|
||||
"t",
|
||||
[]string{"nothing", "nada"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{"id":"3f551da67788c7aae15360d025595dc2d391f10bb7e759ee5d9b2ad7d64392e4","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1712715433,"kind":1,"tags":[["-"],["askdasds"],["t","spam"],["t","nada"]],"content":"ggsgsgsg","sig":"43431f4cf8bd015305c2d484841e5730d261beeb375a86c57a61df3d26e820ce8d6712d2a3c89e3f2298597f14abf58079954e9e658ba59bfc2d7ce6384f25c7"}`,
|
||||
"z",
|
||||
[]string{"nothing", "nada"},
|
||||
false,
|
||||
},
|
||||
} {
|
||||
var evt nostr.Event
|
||||
easyjson.Unmarshal([]byte(tc.json), &evt)
|
||||
bin := make([]byte, Measure(evt))
|
||||
Marshal(evt, bin)
|
||||
|
||||
if res := TagMatches(bin, tc.tagKey, tc.tagValues); res != tc.matches {
|
||||
t.Fatalf("matched incorrectly: %v=>%v over %s was %v, expected %v", tc.tagKey, tc.tagValues, tc.json, res, tc.matches)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user