binary event encoding

This commit is contained in:
2025-05-31 00:32:41 +01:00
parent 73c8679a7c
commit 7ebea3e594
5 changed files with 241 additions and 0 deletions

106
event/binary.go Normal file
View File

@@ -0,0 +1,106 @@
package event
import (
"io"
"x.realy.lol/chk"
"x.realy.lol/ec/schnorr"
"x.realy.lol/hex"
"x.realy.lol/timestamp"
"x.realy.lol/varint"
)
// MarshalBinary writes a binary encoding of an event.
//
// [ 32 bytes Id ]
// [ 32 bytes Pubkey ]
// [ varint CreatedAt ]
// [ 2 bytes Kind ]
// [ varint Tags length ]
//
// [ varint tag length ]
// [ varint tag element length ]
// [ tag element data ]
// ...
//
// [ varint Content length ]
// [ 64 bytes Sig ]
func (ev *E) MarshalBinary(w io.Writer) {
_, _ = w.Write(ev.GetIdBytes())
_, _ = w.Write(ev.GetPubkeyBytes())
varint.Encode(w, uint64(ev.CreatedAt))
varint.Encode(w, uint64(ev.Kind))
varint.Encode(w, uint64(len(ev.Tags)))
for _, x := range ev.Tags {
varint.Encode(w, uint64(len(x)))
for _, y := range x {
varint.Encode(w, uint64(len(y)))
_, _ = w.Write([]byte(y))
}
}
varint.Encode(w, uint64(len(ev.Content)))
_, _ = w.Write([]byte(ev.Content))
_, _ = w.Write(ev.GetSigBytes())
return
}
func (ev *E) UnmarshalBinary(r io.Reader) (err error) {
id := make([]byte, 32)
if _, err = r.Read(id); chk.E(err) {
return
}
ev.Id = hex.Enc(id)
pubkey := make([]byte, 32)
if _, err = r.Read(pubkey); chk.E(err) {
return
}
ev.Pubkey = hex.Enc(pubkey)
var ca uint64
if ca, err = varint.Decode(r); chk.E(err) {
return
}
ev.CreatedAt = timestamp.New(ca)
var k uint64
if k, err = varint.Decode(r); chk.E(err) {
return
}
ev.Kind = int(k)
var nTags uint64
if nTags, err = varint.Decode(r); chk.E(err) {
return
}
for range nTags {
var nField uint64
if nField, err = varint.Decode(r); chk.E(err) {
return
}
var t []string
for range nField {
var lenField uint64
if lenField, err = varint.Decode(r); chk.E(err) {
return
}
field := make([]byte, lenField)
if _, err = r.Read(field); chk.E(err) {
return
}
t = append(t, string(field))
}
ev.Tags = append(ev.Tags, t)
}
var cLen uint64
if cLen, err = varint.Decode(r); chk.E(err) {
return
}
content := make([]byte, cLen)
if _, err = r.Read(content); chk.E(err) {
return
}
ev.Content = string(content)
sig := make([]byte, schnorr.SignatureSize)
if _, err = r.Read(sig); chk.E(err) {
return
}
ev.Sig = hex.Enc(sig)
return
}

43
event/binary_test.go Normal file
View File

@@ -0,0 +1,43 @@
package event
import (
"bufio"
"bytes"
"testing"
"time"
"x.realy.lol/chk"
"x.realy.lol/event/examples"
)
func TestTMarshalBinary_UnmarshalBinary(t *testing.T) {
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
var rem, out []byte
var err error
buf := new(bytes.Buffer)
ea, eb := New(), New()
now := time.Now()
var counter int
for scanner.Scan() {
b := scanner.Bytes()
c := make([]byte, 0, len(b))
c = append(c, b...)
if err = ea.Unmarshal(c); chk.E(err) {
t.Fatal(err)
}
if len(rem) != 0 {
t.Fatalf("some of input remaining after marshal/unmarshal: '%s'",
rem)
}
ea.MarshalBinary(buf)
buf2 := bytes.NewBuffer(buf.Bytes())
if err = eb.UnmarshalBinary(buf2); chk.E(err) {
t.Fatal(err)
}
counter++
out = out[:0]
}
t.Logf("unmarshaled json, marshaled binary, unmarshaled binary, "+
"%d events in %v av %v per event",
counter, time.Since(now), time.Since(now)/time.Duration(counter))
}

18
main.go
View File

@@ -3,8 +3,12 @@ package main
import (
"os"
"x.realy.lol/bech32encoding"
"x.realy.lol/chk"
"x.realy.lol/config"
"x.realy.lol/hex"
"x.realy.lol/log"
"x.realy.lol/p256k"
"x.realy.lol/version"
)
@@ -15,4 +19,18 @@ func main() {
os.Exit(1)
}
log.I.F("starting %s version %s", version.Name, version.Version)
a := cfg.Superuser
var err error
var dst []byte
if dst, err = bech32encoding.NpubToBytes([]byte(a)); chk.E(err) {
if _, err = hex.DecBytes(dst, []byte(a)); chk.E(err) {
log.F.F("SUPERUSER is invalid: %s", a)
os.Exit(1)
}
}
super := &p256k.Signer{}
if err = super.InitPub(dst); chk.E(err) {
return
}
}

45
varint/varint.go Normal file
View File

@@ -0,0 +1,45 @@
// Package varint is a variable integer encoding that works in reverse compared to the stdlib
// binary Varint. The terminal byte in the encoding is the one with the 8th bit set. This is
// basically like a base 128 encoding. It reads forward using an io.Reader and writes forward
// using an io.Writer.
package varint
import (
"io"
"x.realy.lol/chk"
)
func Encode(w io.Writer, v uint64) {
x := []byte{0}
for {
x[0] = byte(v) & 127
v >>= 7
if v == 0 {
x[0] |= 128
_, _ = w.Write(x)
break
} else {
_, _ = w.Write(x)
}
}
}
func Decode(r io.Reader) (v uint64, err error) {
x := []byte{0}
v += uint64(x[0])
var i uint64
for {
if _, err = r.Read(x); chk.E(err) {
return
}
if x[0] >= 128 {
v += uint64(x[0]&127) << (i * 7)
return
} else {
v += uint64(x[0]) << (i * 7)
i++
}
}
// }
}

29
varint/varint_test.go Normal file
View File

@@ -0,0 +1,29 @@
package varint
import (
"bytes"
"math"
"testing"
"lukechampine.com/frand"
"x.realy.lol/chk"
)
func TestEncode_Decode(t *testing.T) {
var v uint64
for range 10000000 {
v = uint64(frand.Intn(math.MaxInt64))
buf1 := new(bytes.Buffer)
Encode(buf1, v)
buf2 := bytes.NewBuffer(buf1.Bytes())
u, err := Decode(buf2)
if chk.E(err) {
t.Fatal(err)
}
if u != v {
t.Fatalf("expected %d got %d", v, u)
}
}
}