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