package store import ( "encoding/binary" "errors" "git.smesh.lol/torly/pkg/nostr" ) // Event encoding in the WAL: // // version(1) | kind(2) | created_at(8) | pubkey(32) | id(32) | sig(64) // tag count(2) | per tag: field count(2), per field: len(4) + bytes // content len(4) + bytes // // The encoding is compact and trivially decodable; the JSON wire form is // regenerated from it when an event is served. const eventVersion = 1 var errBadEventEncoding = errors.New("store: malformed event record") func appendU16(dst []byte, v uint16) []byte { return append(dst, byte(v>>8), byte(v)) } func appendU32(dst []byte, v uint32) []byte { return append(dst, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } func appendU64(dst []byte, v uint64) []byte { return append(dst, byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } func encodeEvent(ev *nostr.Event) []byte { size := 1 + 2 + 8 + 32 + 32 + 64 + 2 + 4 + len(ev.Content) for _, tag := range ev.Tags { size += 2 for _, field := range tag { size += 4 + len(field) } } out := make([]byte, 0, size) out = append(out, eventVersion) out = appendU16(out, uint16(ev.Kind)) out = appendU64(out, uint64(ev.CreatedAt)) out = append(out, ev.PubKey[:]...) out = append(out, ev.ID[:]...) out = append(out, ev.Sig[:]...) out = appendU16(out, uint16(len(ev.Tags))) for _, tag := range ev.Tags { out = appendU16(out, uint16(len(tag))) for _, field := range tag { out = appendU32(out, uint32(len(field))) out = append(out, field...) } } out = appendU32(out, uint32(len(ev.Content))) out = append(out, ev.Content...) return out } type decoder struct { data []byte pos int } func (d *decoder) u8() (byte, bool) { if d.pos+1 > len(d.data) { return 0, false } v := d.data[d.pos] d.pos++ return v, true } func (d *decoder) u16() (uint16, bool) { if d.pos+2 > len(d.data) { return 0, false } v := binary.BigEndian.Uint16(d.data[d.pos:]) d.pos += 2 return v, true } func (d *decoder) u32() (uint32, bool) { if d.pos+4 > len(d.data) { return 0, false } v := binary.BigEndian.Uint32(d.data[d.pos:]) d.pos += 4 return v, true } func (d *decoder) u64() (uint64, bool) { if d.pos+8 > len(d.data) { return 0, false } v := binary.BigEndian.Uint64(d.data[d.pos:]) d.pos += 8 return v, true } func (d *decoder) bytes(n int) ([]byte, bool) { if n < 0 || d.pos+n > len(d.data) { return nil, false } v := d.data[d.pos : d.pos+n] d.pos += n return v, true } func decodeEvent(data []byte) (*nostr.Event, error) { d := &decoder{data: data} ver, ok := d.u8() if !ok || ver != eventVersion { return nil, errBadEventEncoding } kind, ok := d.u16() if !ok { return nil, errBadEventEncoding } created, ok := d.u64() if !ok { return nil, errBadEventEncoding } pub, ok := d.bytes(32) if !ok { return nil, errBadEventEncoding } id, ok := d.bytes(32) if !ok { return nil, errBadEventEncoding } sig, ok := d.bytes(64) if !ok { return nil, errBadEventEncoding } ev := &nostr.Event{ CreatedAt: int64(created), Kind: int(kind), } copy(ev.PubKey[:], pub) copy(ev.ID[:], id) copy(ev.Sig[:], sig) tagN, ok := d.u16() if !ok { return nil, errBadEventEncoding } ev.Tags = make(nostr.Tags, 0, tagN) for i := 0; i < int(tagN); i++ { fieldN, ok := d.u16() if !ok { return nil, errBadEventEncoding } tag := make(nostr.Tag, 0, fieldN) for j := 0; j < int(fieldN); j++ { n, ok := d.u32() if !ok || n > 1<<20 { return nil, errBadEventEncoding } b, ok := d.bytes(int(n)) if !ok { return nil, errBadEventEncoding } tag = append(tag, string(b)) } ev.Tags = append(ev.Tags, tag) } clen, ok := d.u32() if !ok || clen > 16<<20 { return nil, errBadEventEncoding } content, ok := d.bytes(int(clen)) if !ok { return nil, errBadEventEncoding } ev.Content = string(content) if d.pos != len(d.data) { return nil, errBadEventEncoding } return ev, nil }