a big bundle of conversions and other changes.

This commit is contained in:
fiatjaf
2025-04-15 17:13:57 -03:00
parent f493293be2
commit 2b5b646a62
92 changed files with 852 additions and 2136 deletions

View 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
}

File diff suppressed because one or more lines are too long

View 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
}

View 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)
}
}
}