eventbin.go raw
1 package store
2
3 import (
4 "encoding/binary"
5 "errors"
6
7 "git.smesh.lol/torly/pkg/nostr"
8 )
9
10 // Event encoding in the WAL:
11 //
12 // version(1) | kind(2) | created_at(8) | pubkey(32) | id(32) | sig(64)
13 // tag count(2) | per tag: field count(2), per field: len(4) + bytes
14 // content len(4) + bytes
15 //
16 // The encoding is compact and trivially decodable; the JSON wire form is
17 // regenerated from it when an event is served.
18 const eventVersion = 1
19
20 var errBadEventEncoding = errors.New("store: malformed event record")
21
22 func appendU16(dst []byte, v uint16) []byte { return append(dst, byte(v>>8), byte(v)) }
23 func appendU32(dst []byte, v uint32) []byte {
24 return append(dst, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
25 }
26 func appendU64(dst []byte, v uint64) []byte {
27 return append(dst, byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32),
28 byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
29 }
30
31 func encodeEvent(ev *nostr.Event) []byte {
32 size := 1 + 2 + 8 + 32 + 32 + 64 + 2 + 4 + len(ev.Content)
33 for _, tag := range ev.Tags {
34 size += 2
35 for _, field := range tag {
36 size += 4 + len(field)
37 }
38 }
39 out := make([]byte, 0, size)
40 out = append(out, eventVersion)
41 out = appendU16(out, uint16(ev.Kind))
42 out = appendU64(out, uint64(ev.CreatedAt))
43 out = append(out, ev.PubKey[:]...)
44 out = append(out, ev.ID[:]...)
45 out = append(out, ev.Sig[:]...)
46 out = appendU16(out, uint16(len(ev.Tags)))
47 for _, tag := range ev.Tags {
48 out = appendU16(out, uint16(len(tag)))
49 for _, field := range tag {
50 out = appendU32(out, uint32(len(field)))
51 out = append(out, field...)
52 }
53 }
54 out = appendU32(out, uint32(len(ev.Content)))
55 out = append(out, ev.Content...)
56 return out
57 }
58
59 type decoder struct {
60 data []byte
61 pos int
62 }
63
64 func (d *decoder) u8() (byte, bool) {
65 if d.pos+1 > len(d.data) {
66 return 0, false
67 }
68 v := d.data[d.pos]
69 d.pos++
70 return v, true
71 }
72
73 func (d *decoder) u16() (uint16, bool) {
74 if d.pos+2 > len(d.data) {
75 return 0, false
76 }
77 v := binary.BigEndian.Uint16(d.data[d.pos:])
78 d.pos += 2
79 return v, true
80 }
81
82 func (d *decoder) u32() (uint32, bool) {
83 if d.pos+4 > len(d.data) {
84 return 0, false
85 }
86 v := binary.BigEndian.Uint32(d.data[d.pos:])
87 d.pos += 4
88 return v, true
89 }
90
91 func (d *decoder) u64() (uint64, bool) {
92 if d.pos+8 > len(d.data) {
93 return 0, false
94 }
95 v := binary.BigEndian.Uint64(d.data[d.pos:])
96 d.pos += 8
97 return v, true
98 }
99
100 func (d *decoder) bytes(n int) ([]byte, bool) {
101 if n < 0 || d.pos+n > len(d.data) {
102 return nil, false
103 }
104 v := d.data[d.pos : d.pos+n]
105 d.pos += n
106 return v, true
107 }
108
109 func decodeEvent(data []byte) (*nostr.Event, error) {
110 d := &decoder{data: data}
111 ver, ok := d.u8()
112 if !ok || ver != eventVersion {
113 return nil, errBadEventEncoding
114 }
115 kind, ok := d.u16()
116 if !ok {
117 return nil, errBadEventEncoding
118 }
119 created, ok := d.u64()
120 if !ok {
121 return nil, errBadEventEncoding
122 }
123 pub, ok := d.bytes(32)
124 if !ok {
125 return nil, errBadEventEncoding
126 }
127 id, ok := d.bytes(32)
128 if !ok {
129 return nil, errBadEventEncoding
130 }
131 sig, ok := d.bytes(64)
132 if !ok {
133 return nil, errBadEventEncoding
134 }
135 ev := &nostr.Event{
136 CreatedAt: int64(created),
137 Kind: int(kind),
138 }
139 copy(ev.PubKey[:], pub)
140 copy(ev.ID[:], id)
141 copy(ev.Sig[:], sig)
142
143 tagN, ok := d.u16()
144 if !ok {
145 return nil, errBadEventEncoding
146 }
147 ev.Tags = make(nostr.Tags, 0, tagN)
148 for i := 0; i < int(tagN); i++ {
149 fieldN, ok := d.u16()
150 if !ok {
151 return nil, errBadEventEncoding
152 }
153 tag := make(nostr.Tag, 0, fieldN)
154 for j := 0; j < int(fieldN); j++ {
155 n, ok := d.u32()
156 if !ok || n > 1<<20 {
157 return nil, errBadEventEncoding
158 }
159 b, ok := d.bytes(int(n))
160 if !ok {
161 return nil, errBadEventEncoding
162 }
163 tag = append(tag, string(b))
164 }
165 ev.Tags = append(ev.Tags, tag)
166 }
167 clen, ok := d.u32()
168 if !ok || clen > 16<<20 {
169 return nil, errBadEventEncoding
170 }
171 content, ok := d.bytes(int(clen))
172 if !ok {
173 return nil, errBadEventEncoding
174 }
175 ev.Content = string(content)
176 if d.pos != len(d.data) {
177 return nil, errBadEventEncoding
178 }
179 return ev, nil
180 }
181