helpers.go raw
1 package nostr
2
3 import (
4 "strconv"
5 "strings"
6 "sync"
7 "unsafe"
8
9 jsoniter "github.com/json-iterator/go"
10 "golang.org/x/exp/constraints"
11 )
12
13 const MAX_LOCKS = 50
14
15 var (
16 namedMutexPool = make([]sync.Mutex, MAX_LOCKS)
17 json = jsoniter.ConfigFastest
18 )
19
20 //go:noescape
21 //go:linkname memhash runtime.memhash
22 func memhash(p unsafe.Pointer, h, s uintptr) uintptr
23
24 func namedLock(name string) (unlock func()) {
25 sptr := unsafe.StringData(name)
26 idx := uint64(memhash(unsafe.Pointer(sptr), 0, uintptr(len(name)))) % MAX_LOCKS
27 namedMutexPool[idx].Lock()
28 return namedMutexPool[idx].Unlock
29 }
30
31 func similar[E constraints.Ordered](as, bs []E) bool {
32 if len(as) != len(bs) {
33 return false
34 }
35
36 for _, a := range as {
37 for _, b := range bs {
38 if b == a {
39 goto next
40 }
41 }
42 // didn't find a B that corresponded to the current A
43 return false
44
45 next:
46 continue
47 }
48
49 return true
50 }
51
52 // Escaping strings for JSON encoding according to RFC8259.
53 // Also encloses result in quotation marks "".
54 func escapeString(dst []byte, s string) []byte {
55 dst = append(dst, '"')
56 for i := 0; i < len(s); i++ {
57 c := s[i]
58 switch {
59 case c == '"':
60 // quotation mark
61 dst = append(dst, []byte{'\\', '"'}...)
62 case c == '\\':
63 // reverse solidus
64 dst = append(dst, []byte{'\\', '\\'}...)
65 case c >= 0x20:
66 // default, rest below are control chars
67 dst = append(dst, c)
68 case c == 0x08:
69 dst = append(dst, []byte{'\\', 'b'}...)
70 case c < 0x09:
71 dst = append(dst, []byte{'\\', 'u', '0', '0', '0', '0' + c}...)
72 case c == 0x09:
73 dst = append(dst, []byte{'\\', 't'}...)
74 case c == 0x0a:
75 dst = append(dst, []byte{'\\', 'n'}...)
76 case c == 0x0c:
77 dst = append(dst, []byte{'\\', 'f'}...)
78 case c == 0x0d:
79 dst = append(dst, []byte{'\\', 'r'}...)
80 case c < 0x10:
81 dst = append(dst, []byte{'\\', 'u', '0', '0', '0', 0x57 + c}...)
82 case c < 0x1a:
83 dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x20 + c}...)
84 case c < 0x20:
85 dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x47 + c}...)
86 }
87 }
88 dst = append(dst, '"')
89 return dst
90 }
91
92 func arePointerValuesEqual[V comparable](a *V, b *V) bool {
93 if a == nil && b == nil {
94 return true
95 }
96 if a != nil && b != nil {
97 return *a == *b
98 }
99 return false
100 }
101
102 func subIdToSerial(subId string) int64 {
103 n := strings.Index(subId, ":")
104 if n < 0 || n > len(subId) {
105 return -1
106 }
107 serialId, _ := strconv.ParseInt(subId[0:n], 10, 64)
108 return serialId
109 }
110
111 func isLowerHex(thing string) bool {
112 for _, charNumber := range thing {
113 if (charNumber >= 48 && charNumber <= 57) || (charNumber >= 97 && charNumber <= 102) {
114 continue
115 }
116 return false
117 }
118 return true
119 }
120
121 func extractSubID(jsonStr string) string {
122 // look for "EVENT" pattern
123 start := strings.Index(jsonStr, `"EVENT"`)
124 if start == -1 {
125 return ""
126 }
127
128 // move to the next quote
129 offset := strings.Index(jsonStr[start+7:], `"`)
130 if offset == -1 {
131 return ""
132 }
133
134 start += 7 + offset + 1
135
136 // find the ending quote
137 end := strings.Index(jsonStr[start:], `"`)
138
139 // get the contents
140 return jsonStr[start : start+end]
141 }
142
143 func extractEventID(jsonStr string) string {
144 // look for "id" pattern
145 start := strings.Index(jsonStr, `"id"`)
146 if start == -1 {
147 return ""
148 }
149
150 // move to the next quote
151 offset := strings.IndexRune(jsonStr[start+4:], '"')
152 start += 4 + offset + 1
153
154 // get 64 characters of the id
155 return jsonStr[start : start+64]
156 }
157
158 func extractEventPubKey(jsonStr string) string {
159 // look for "pubkey" pattern
160 start := strings.Index(jsonStr, `"pubkey"`)
161 if start == -1 {
162 return ""
163 }
164
165 // move to the next quote
166 offset := strings.IndexRune(jsonStr[start+8:], '"')
167 start += 8 + offset + 1
168
169 // get 64 characters of the pubkey
170 return jsonStr[start : start+64]
171 }
172
173 func extractDTag(jsonStr string) string {
174 // look for ["d", pattern
175 start := strings.Index(jsonStr, `["d"`)
176 if start == -1 {
177 return ""
178 }
179
180 // move to the next quote
181 offset := strings.IndexRune(jsonStr[start+4:], '"')
182 start += 4 + offset + 1
183
184 // find the ending quote
185 end := strings.IndexRune(jsonStr[start:], '"')
186 if end == -1 {
187 return ""
188 }
189
190 // get the contents
191 return jsonStr[start : start+end]
192 }
193
194 func extractTimestamp(jsonStr string) Timestamp {
195 // look for "created_at": pattern
196 start := strings.Index(jsonStr, `"created_at"`)
197 if start == -1 {
198 return 0
199 }
200
201 // move to the next number
202 offset := strings.IndexAny(jsonStr[start+12:], "9876543210")
203 if offset == -1 {
204 return 0
205 }
206 start += 12 + offset
207
208 // find the end
209 end := strings.IndexAny(jsonStr[start:], ",} ")
210 if end == -1 {
211 return 0
212 }
213
214 // get the contents
215 ts, _ := strconv.ParseInt(jsonStr[start:start+end], 10, 64)
216 return Timestamp(ts)
217 }
218