binary event encoding
This commit is contained in:
106
event/binary.go
Normal file
106
event/binary.go
Normal 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
43
event/binary_test.go
Normal 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
18
main.go
@@ -3,8 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"x.realy.lol/bech32encoding"
|
||||||
|
"x.realy.lol/chk"
|
||||||
"x.realy.lol/config"
|
"x.realy.lol/config"
|
||||||
|
"x.realy.lol/hex"
|
||||||
"x.realy.lol/log"
|
"x.realy.lol/log"
|
||||||
|
"x.realy.lol/p256k"
|
||||||
"x.realy.lol/version"
|
"x.realy.lol/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,4 +19,18 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
log.I.F("starting %s version %s", version.Name, version.Version)
|
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
45
varint/varint.go
Normal 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
29
varint/varint_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user