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