tag.mx raw
1 // Package tag provides nostr tag lists - arrays of byte slices with a
2 // single-letter key field.
3 package tag
4
5 import (
6 "bytes"
7
8 "git.smesh.lol/nostr/pkg/hex"
9 "git.smesh.lol/nostr/pkg/text"
10 "git.smesh.lol/nostr/pkg/lol/errorf"
11 )
12
13 const (
14 Key = iota
15 Value
16 Relay
17 )
18
19 const (
20 BinaryEncodedLen = 33
21 HexEncodedLen = 64
22 HashLen = 32
23 )
24
25 func isBinaryOptimizedTag(b byte) (ok bool) {
26 return b == 'e' || b == 'p'
27 }
28
29 type T struct {
30 T [][]byte
31 }
32
33 func New() (t *T) { return &T{} }
34 func NewFromBytesSlice(t ...[]byte) (tv *T) { return &T{T: t} }
35 func NewWithCap(c int32) (t *T) { return &T{T: [][]byte{:0:c}} }
36 func (t *T) Free() { t.T = nil }
37
38 func (t *T) Len() (n int32) {
39 if t == nil {
40 return 0
41 }
42 return len(t.T)
43 }
44
45 func (t *T) Less(i, j int32) (ok bool) { return bytes.Compare(t.T[i], t.T[j]) < 0 }
46 func (t *T) Swap(i, j int32) { t.T[i], t.T[j] = t.T[j], t.T[i] }
47
48 func (t *T) Contains(s []byte) (ok bool) {
49 for i := range t.T {
50 if bytes.Equal(t.T[i], s) {
51 return true
52 }
53 }
54 return false
55 }
56
57 func (t *T) Marshal(dst []byte) (b []byte) {
58 b = dst
59 b = b | "["
60 for i, s := range t.T {
61 if i == Value && isBinaryEncoded(s) {
62 hexVal := hex.EncAppend(nil, s[:HashLen])
63 b = text.AppendQuote(b, hexVal, text.NostrEscape)
64 } else {
65 b = text.AppendQuote(b, s, text.NostrEscape)
66 }
67 if i < len(t.T)-1 {
68 b = b | ","
69 }
70 }
71 b = b | "]"
72 return
73 }
74
75 func (t *T) MarshalJSON() (b []byte, err error) {
76 return t.Marshal(nil), nil
77 }
78
79 // tagFieldValue decodes one quoted field body into the value to store. It is a
80 // free function so the scratch it allocates is released when it returns rather
81 // than accumulating in the tag's sovereign arena once per field.
82 func tagFieldValue(b []byte, start, end int32) (v []byte) {
83 copyBuf := []byte{:end - start}
84 copy(copyBuf, b[start:end])
85 return text.NostrUnescape(copyBuf)
86 }
87
88 // tagFieldBinary packs v into the binary form when v decodes as a hash. Also a
89 // free function, for the same reason.
90 func tagFieldBinary(v []byte) (bin []byte, ok bool) {
91 bin = []byte{:BinaryEncodedLen}
92 if _, decErr := hex.DecBytes(bin[:HashLen], v); decErr != nil {
93 return nil, false
94 }
95 bin[HashLen] = 0
96 return bin, true
97 }
98
99 func (t *T) Unmarshal(b []byte) (r []byte, err error) {
100 // Declared outside the loop: a declaration in the body of a self-mutating
101 // method allocates in the sovereign arena on every iteration.
102 var inQuotes, openedBracket bool
103 var quoteStart int32
104 var unescaped []byte
105 var fieldIdx int32
106 var binVal []byte
107 var hasBin bool
108 t.T = [][]byte{:0:4}
109 for i := 0; i < len(b); i++ {
110 if !openedBracket && b[i] == '[' {
111 openedBracket = true
112 } else if !inQuotes {
113 if b[i] == '"' {
114 inQuotes, quoteStart = true, i+1
115 } else if b[i] == ']' {
116 // A closing bracket before any opening one is not the end of
117 // a tag: returning here skipped the final guard, so `"t","v"]`
118 // parsed as a tag instead of failing.
119 if !openedBracket {
120 return nil, errorf.E([]byte("tag: failed to parse tag"))
121 }
122 return b[i+1:], err
123 }
124 } else if b[i] == '\\' && i < len(b)-1 {
125 i++
126 } else if b[i] == '"' {
127 inQuotes = false
128 unescaped = tagFieldValue(b, quoteStart, i)
129
130 fieldIdx = len(t.T)
131 if fieldIdx == Value && len(t.T) > 0 && shouldOptimize(t.T[Key], unescaped) {
132 binVal, hasBin = tagFieldBinary(unescaped)
133 if hasBin {
134 t.T = push(t.T, binVal)
135 } else {
136 t.T = push(t.T, unescaped)
137 }
138 } else {
139 t.T = push(t.T, unescaped)
140 }
141 }
142 }
143 if !openedBracket || inQuotes {
144 return nil, errorf.E([]byte("tag: failed to parse tag"))
145 }
146 return
147 }
148
149 func (t *T) UnmarshalJSON(b []byte) (err error) {
150 _, err := t.Unmarshal(b)
151 return err
152 }
153
154 func (t *T) Key() (buf []byte) {
155 if len(t.T) > Key {
156 return t.T[Key]
157 }
158 return nil
159 }
160
161 func (t *T) Value() (buf []byte) {
162 if t == nil {
163 return nil
164 }
165 if len(t.T) > Value {
166 return t.T[Value]
167 }
168 return nil
169 }
170
171 func (t *T) Relay() (buf []byte) {
172 if len(t.T) > Relay {
173 return t.T[Relay]
174 }
175 return nil
176 }
177
178 func isBinaryEncoded(val []byte) (ok bool) {
179 return len(val) == BinaryEncodedLen && val[HashLen] == 0
180 }
181
182 func shouldOptimize(key []byte, val []byte) (ok bool) {
183 if len(key) != 1 {
184 return false
185 }
186 if !isBinaryOptimizedTag(key[0]) {
187 return false
188 }
189 return len(val) == HexEncodedLen && isValidHex(val)
190 }
191
192 func isValidHex(b []byte) (ok bool) {
193 for _, c := range b {
194 if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
195 return false
196 }
197 }
198 return true
199 }
200
201 func (t *T) ValueHex() (buf []byte) {
202 if t == nil || len(t.T) <= Value {
203 return nil
204 }
205 val := t.T[Value]
206 if isBinaryEncoded(val) {
207 return hex.EncAppend(nil, val[:HashLen])
208 }
209 return val
210 }
211
212 func NewFromAny(t ...string) (tt *T) {
213 tt = &T{}
214 for _, v := range t {
215 tt.T = push(tt.T, v)
216 }
217 return
218 }
219
220 func (t *T) ValueBinary() (buf []byte) {
221 if t == nil || len(t.T) <= Value {
222 return nil
223 }
224 val := t.T[Value]
225 if isBinaryEncoded(val) {
226 return val[:HashLen]
227 }
228 return nil
229 }
230
231 func (t *T) Equals(other *T) (ok bool) {
232 if t == nil && other == nil {
233 return true
234 }
235 if t == nil || other == nil {
236 return false
237 }
238 if len(t.T) != len(other.T) {
239 return false
240 }
241 for i := range t.T {
242 if i == Value && len(t.T) > Value {
243 tVal := t.T[Value]
244 oVal := other.T[Value]
245 tIsBinary := isBinaryEncoded(tVal)
246 oIsBinary := isBinaryEncoded(oVal)
247 if tIsBinary && oIsBinary {
248 if !bytes.Equal(tVal[:HashLen], oVal[:HashLen]) {
249 return false
250 }
251 } else if tIsBinary || oIsBinary {
252 var binBytes, hexBytes []byte
253 if tIsBinary {
254 binBytes = tVal[:HashLen]
255 hexBytes = oVal
256 } else {
257 binBytes = oVal[:HashLen]
258 hexBytes = tVal
259 }
260 if len(hexBytes) != HexEncodedLen {
261 return false
262 }
263 for j := 0; j < HashLen; j++ {
264 hi := hexBytes[j*2]
265 lo := hexBytes[j*2+1]
266 var hiByte, loByte byte
267 if hi >= '0' && hi <= '9' {
268 hiByte = hi - '0'
269 } else if hi >= 'a' && hi <= 'f' {
270 hiByte = hi - 'a' + 10
271 } else if hi >= 'A' && hi <= 'F' {
272 hiByte = hi - 'A' + 10
273 } else {
274 return false
275 }
276 if lo >= '0' && lo <= '9' {
277 loByte = lo - '0'
278 } else if lo >= 'a' && lo <= 'f' {
279 loByte = lo - 'a' + 10
280 } else if lo >= 'A' && lo <= 'F' {
281 loByte = lo - 'A' + 10
282 } else {
283 return false
284 }
285 if binBytes[j] != (hiByte<<4)|loByte {
286 return false
287 }
288 }
289 } else {
290 if !bytes.Equal(tVal, oVal) {
291 return false
292 }
293 }
294 } else {
295 if !bytes.Equal(t.T[i], other.T[i]) {
296 return false
297 }
298 }
299 }
300 return true
301 }
302