filter.mx raw

   1  package filter
   2  
   3  import (
   4  	"git.smesh.lol/moxie/pkg/mxutil"
   5  	"bytes"
   6  	"crypto/sha256"
   7  	"sort"
   8  
   9  	"crypto/secp256k1"
  10  	"git.smesh.lol/nostr/pkg/event"
  11  	"git.smesh.lol/nostr/pkg/ints"
  12  	"git.smesh.lol/nostr/pkg/kind"
  13  	"git.smesh.lol/nostr/pkg/tag"
  14  	"git.smesh.lol/nostr/pkg/text"
  15  	"git.smesh.lol/nostr/pkg/timestamp"
  16  	"git.smesh.lol/nostr/pkg/lol/chk"
  17  	"git.smesh.lol/nostr/pkg/lol/errorf"
  18  )
  19  
  20  // The taint flag records that Unmarshal saw undersized hex in ids/authors. It
  21  // is a package global behind a self-mutating type: a direct store to a global
  22  // is rejected outside init, and callers in other packages (morly's dbengine and
  23  // relay/server) must go through the methods, which is why the flag is no longer
  24  // an exported variable.
  25  type taintState struct{ on bool }
  26  
  27  func (t *taintState) set()           { t.on = true }
  28  func (t *taintState) clear()         { t.on = false }
  29  func (t *taintState) get() (ok bool) { return t.on }
  30  
  31  var taint taintState
  32  
  33  // SetTaint, ClearTaint and IsTainted are the cross-package API.
  34  func SetTaint()            { taint.set() }
  35  func ClearTaint()          { taint.clear() }
  36  func IsTainted() (ok bool) { return taint.get() }
  37  
  38  type F struct {
  39  	Ids     *tag.T       `json:"ids,omitempty"`
  40  	Kinds   *kind.S      `json:"kinds,omitempty"`
  41  	Authors *tag.T       `json:"authors,omitempty"`
  42  	Tags    *tag.S       `json:"-,omitempty"`
  43  	Since   *timestamp.T `json:"since,omitempty"`
  44  	Until   *timestamp.T `json:"until,omitempty"`
  45  	Search  []byte       `json:"search,omitempty"`
  46  	Limit   *uint32        `json:"limit,omitempty"`
  47  	Extra   map[string][]byte `json:"-"`
  48  }
  49  
  50  func New() (f *F) {
  51  	return &F{
  52  		Ids:     tag.NewWithCap(10),
  53  		Kinds:   kind.NewWithCap(10),
  54  		Authors: tag.NewWithCap(10),
  55  		Tags:    tag.NewSWithCap(10),
  56  		Since:   timestamp.New(),
  57  		Until:   timestamp.New(),
  58  	}
  59  }
  60  
  61  var (
  62  	IDs     []byte
  63  	Kinds   []byte
  64  	Authors []byte
  65  	Since   []byte
  66  	Until   []byte
  67  	Limit   []byte
  68  	Search  []byte
  69  )
  70  
  71  func init() {
  72  	IDs = []byte("ids")
  73  	Kinds = []byte("kinds")
  74  	Authors = []byte("authors")
  75  	Since = []byte("since")
  76  	Until = []byte("until")
  77  	Limit = []byte("limit")
  78  	Search = []byte("search")
  79  }
  80  
  81  func (f *F) Sort() {
  82  	if f.Ids != nil {
  83  		sort.Sort(f.Ids)
  84  	}
  85  	if f.Kinds != nil {
  86  		sort.Sort(f.Kinds)
  87  	}
  88  	if f.Authors != nil {
  89  		sort.Sort(f.Authors)
  90  	}
  91  	if f.Tags != nil {
  92  		for i, v := range f.Tags.T {
  93  			if len(v.T) > 2 {
  94  				// Insertion sort on tag values (v.T[1:]).
  95  				for ii := 2; ii < len(v.T); ii++ {
  96  					for jj := ii; jj > 1 && bytes.Compare(v.T[jj], v.T[jj-1]) < 0; jj-- {
  97  						v.T[jj], v.T[jj-1] = v.T[jj-1], v.T[jj]
  98  					}
  99  				}
 100  				f.Tags.T[i] = v
 101  			}
 102  		}
 103  		sort.Sort(f.Tags)
 104  	}
 105  }
 106  
 107  func (f *F) MatchesIgnoringTimestampConstraints(ev *event.E) (ok bool) {
 108  	if ev == nil {
 109  		return false
 110  	}
 111  	if f.Ids.Len() > 0 && !f.Ids.Contains(ev.ID) {
 112  		return false
 113  	}
 114  	if f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) {
 115  		return false
 116  	}
 117  	if f.Authors.Len() > 0 {
 118  		found := false
 119  		for _, author := range f.Authors.T {
 120  			if bytes.Equal(author, ev.Pubkey) {
 121  				found = true
 122  				break
 123  			}
 124  		}
 125  		if !found {
 126  			return false
 127  		}
 128  	}
 129  	if f.Tags.Len() > 0 {
 130  		for _, v := range f.Tags.T {
 131  			if v.Len() < 2 {
 132  				continue
 133  			}
 134  			key := v.Key()
 135  			values := v.T[1:]
 136  			if !ev.Tags.ContainsAny(key, values) {
 137  				return false
 138  			}
 139  		}
 140  	}
 141  	return true
 142  }
 143  
 144  func (f *F) Matches(ev *event.E) (match bool) {
 145  	if !f.MatchesIgnoringTimestampConstraints(ev) {
 146  		return
 147  	}
 148  	if f.Since.Int() != 0 && ev.CreatedAt < f.Since.I64() {
 149  		return
 150  	}
 151  	if f.Until.Int() != 0 && ev.CreatedAt > f.Until.I64() {
 152  		return
 153  	}
 154  	return true
 155  }
 156  
 157  func (f *F) Marshal(dst []byte) (b []byte) {
 158  	var first bool
 159  	if dst == nil {
 160  		dst = []byte{:0:256}
 161  	}
 162  	f.Sort()
 163  	b = dst
 164  	b = b | "{"
 165  	if f.Ids != nil && f.Ids.Len() > 0 {
 166  		first = true
 167  		b = text.JSONKey(b, IDs)
 168  		b = text.MarshalHexArray(b, f.Ids.T)
 169  	}
 170  	if f.Kinds.Len() > 0 {
 171  		if first {
 172  			b = b | ","
 173  		} else {
 174  			first = true
 175  		}
 176  		b = text.JSONKey(b, Kinds)
 177  		b = f.Kinds.Marshal(b)
 178  	}
 179  	if f.Authors.Len() > 0 {
 180  		if first {
 181  			b = b | ","
 182  		} else {
 183  			first = true
 184  		}
 185  		b = text.JSONKey(b, Authors)
 186  		b = text.MarshalHexArray(b, f.Authors.T)
 187  	}
 188  	if f.Tags != nil && f.Tags.Len() > 0 {
 189  		for _, tg := range f.Tags.T {
 190  			if tg == nil || tg.Len() < 2 {
 191  				continue
 192  			}
 193  			tKey := tg.T[0]
 194  			if len(tKey) != 1 ||
 195  				((tKey[0] < 'a' || tKey[0] > 'z') && (tKey[0] < 'A' || tKey[0] > 'Z')) {
 196  				continue
 197  			}
 198  			values := tg.T[1:]
 199  			if len(values) == 0 {
 200  				continue
 201  			}
 202  			if first {
 203  				b = b | ","
 204  			} else {
 205  				first = true
 206  			}
 207  			b = b | "\"#" | string([]byte{tKey[0]}) | "\":" 
 208  			b = b | "["
 209  			for i, value := range values {
 210  				b = text.AppendQuote(b, value, text.NostrEscape)
 211  				if i < len(values)-1 {
 212  					b = b | ","
 213  				}
 214  			}
 215  			b = b | "]"
 216  		}
 217  	}
 218  	if f.Since != nil && f.Since.U64() > 0 {
 219  		if first {
 220  			b = b | ","
 221  		} else {
 222  			first = true
 223  		}
 224  		b = text.JSONKey(b, Since)
 225  		b = f.Since.Marshal(b)
 226  	}
 227  	if f.Until != nil && f.Until.U64() > 0 {
 228  		if first {
 229  			b = b | ","
 230  		} else {
 231  			first = true
 232  		}
 233  		b = text.JSONKey(b, Until)
 234  		b = f.Until.Marshal(b)
 235  	}
 236  	if len(f.Search) > 0 {
 237  		if first {
 238  			b = b | ","
 239  		} else {
 240  			first = true
 241  		}
 242  		b = text.JSONKey(b, Search)
 243  		b = text.AppendQuote(b, f.Search, text.NostrEscape)
 244  	}
 245  	if f.Limit != nil {
 246  		if first {
 247  			b = b | ","
 248  		}
 249  		b = text.JSONKey(b, Limit)
 250  		b = ints.New(*f.Limit).Marshal(b)
 251  	}
 252  	b = b | "}"
 253  	return
 254  }
 255  
 256  func (f *F) Serialize() (b []byte) { return f.Marshal(nil) }
 257  
 258  const (
 259  	beforeOpen = iota
 260  	openParen
 261  	inKey
 262  	inKV
 263  	inVal
 264  	betweenKV
 265  	afterClose
 266  )
 267  
 268  func (f *F) Unmarshal(b []byte) (r []byte, err error) {
 269  	r = b
 270  	var key []byte
 271  	var state int32
 272  	// Declared outside the loop: a declaration in the body of a self-mutating
 273  	// method allocates in the sovereign arena on every iteration.
 274  	var klen int32
 275  	var tagKey []byte
 276  	var ff [][]byte
 277  
 278  	var sk bool
 279  	var untilInt, limitInt, sinceInt *ints.T
 280  	var limit uint32
 281  	var txt, val []byte
 282  	for ; len(r) > 0; r = r[1:] {
 283  		switch state {
 284  		case beforeOpen:
 285  			if r[0] == '{' {
 286  				state = openParen
 287  			}
 288  		case openParen:
 289  			if r[0] == '"' {
 290  				state = inKey
 291  			}
 292  		case inKey:
 293  			if r[0] == '"' {
 294  				state = inKV
 295  			} else {
 296  				if key == nil {
 297  					key = []byte{:0:16}
 298  				}
 299  				key = mxutil.Ensure(key, 1)
 300  				key = push(key, r[0])
 301  			}
 302  		case inKV:
 303  			if r[0] == ':' {
 304  				state = inVal
 305  			}
 306  		case inVal:
 307  			if len(key) < 1 {
 308  				err = errorf.E([]byte("filter key zero length: '%s'\n'%s"), b, r)
 309  				return
 310  			}
 311  			// Exact key match, not a first-byte switch with a length bound:
 312  			// any key sharing a leading byte with a field name was routed into
 313  			// that field's parser, so a valid but unknown key either failed the
 314  			// Unmarshal ("arr", "s") or was parsed as the wrong field
 315  			// ("iXXX" consumed the rest of the filter as ids and dropped a
 316  			// following "kinds"). Unknown keys belong in Extra, the same as
 317  			// every key that shares no leading byte.
 318  			if keyIs(key, IDs) {
 319  				if ff, r, sk, err = text.UnmarshalHexArray(r, sha256.Size); chk.E(err) {
 320  					return
 321  				}
 322  				if sk {
 323  					SetTaint()
 324  				}
 325  				f.Ids = mkTagT(ff)
 326  				state = betweenKV
 327  			} else if keyIs(key, Kinds) {
 328  				f.Kinds = kind.NewWithCap(0)
 329  				if r, err = f.Kinds.Unmarshal(r); chk.E(err) {
 330  					return
 331  				}
 332  				state = betweenKV
 333  			} else if keyIs(key, Authors) {
 334  				if ff, r, sk, err = text.UnmarshalHexArray(r, secp256k1.PubKeyBytesLen); chk.E(err) {
 335  					return
 336  				}
 337  				if sk {
 338  					SetTaint()
 339  				}
 340  				f.Authors = mkTagT(ff)
 341  				state = betweenKV
 342  			} else if keyIs(key, Until) {
 343  				untilInt = ints.New(0)
 344  				if r, err = untilInt.Unmarshal(r); chk.E(err) {
 345  					return
 346  				}
 347  				f.Until = timestamp.FromUnix(int64(untilInt.N))
 348  				state = betweenKV
 349  			} else if keyIs(key, Limit) {
 350  				limitInt = ints.New(0)
 351  				if r, err = limitInt.Unmarshal(r); chk.E(err) {
 352  					return
 353  				}
 354  				limit = uint32(limitInt.N)
 355  				f.Limit = mkU32(limit)
 356  				state = betweenKV
 357  			} else if keyIs(key, Search) {
 358  				if txt, r, err = text.UnmarshalQuoted(r); chk.E(err) {
 359  					return
 360  				}
 361  				f.Search = txt
 362  				state = betweenKV
 363  			} else if keyIs(key, Since) {
 364  				sinceInt = ints.New(0)
 365  				if r, err = sinceInt.Unmarshal(r); chk.E(err) {
 366  					return
 367  				}
 368  				f.Since = timestamp.FromUnix(int64(sinceInt.N))
 369  				state = betweenKV
 370  			} else if key[0] == '#' {
 371  				klen = len(key)
 372  				if klen != 2 {
 373  					err = errorf.E(
 374  						[]byte("filter tag keys can only be # and one alpha character: '%s'\n%s"),
 375  						key, b,
 376  					)
 377  					return
 378  				}
 379  				tagKey = []byte{:1}
 380  				tagKey[0] = key[1]
 381  				if ff, r, err = text.UnmarshalStringArray(r); chk.E(err) {
 382  					return
 383  				}
 384  				ff = [][]byte{tagKey} | ff
 385  				if f.Tags == nil {
 386  					f.Tags = tag.NewSWithCap(1)
 387  				}
 388  				// ff is already [][]byte: build the tag directly. Passing it to
 389  				// the variadic NewFromBytesSlice(t ...[]byte) wrapped the whole
 390  				// array as one element and produced a garbage tag.
 391  				f.Tags.T = push(f.Tags.T, mkTagT(ff))
 392  				state = betweenKV
 393  			} else {
 394  				if val, r, err = skipJSONValue(r); err != nil {
 395  					goto invalid
 396  				}
 397  				if f.Extra == nil {
 398  					f.Extra = mkByteMap()
 399  				}
 400  				f.Extra[string(key)] = val
 401  				state = betweenKV
 402  			}
 403  			key = key[:0]
 404  		case betweenKV:
 405  			if len(r) == 0 {
 406  				return
 407  			}
 408  			if r[0] == '}' {
 409  				state = afterClose
 410  			} else if r[0] == ',' {
 411  				state = openParen
 412  			} else if r[0] == '"' {
 413  				state = inKey
 414  			}
 415  		}
 416  		if len(r) == 0 {
 417  			return
 418  		}
 419  		if r[0] == '}' {
 420  			r = r[1:]
 421  			return
 422  		}
 423  	}
 424  invalid:
 425  	err = errorf.E([]byte("invalid key,\n'%s'\n'%s'"), string(b), string(r))
 426  	return
 427  }
 428  
 429  // mkTagT and mkU32 return pointers to values allocated in the CALLER's arena.
 430  // A mutating method must not store the address of its own local into the
 431  // receiver: the local lands in the receiver's sovereign data arena, which is
 432  // reclaimed at the method's batch boundary, so the stored pointer outlives
 433  // the value whenever the receiver is a temporary - which is how a parsed
 434  // filter ended up with a Limit pointing at unmapped memory. Handing the
 435  // value to a free function and returning its address makes the return codec
 436  // relocate the pointee into the caller's arena, so it lives as long as the
 437  // filter that holds it.
 438  func mkTagT(ff [][]byte) (p *tag.T) {
 439  	return &tag.T{T: ff}
 440  }
 441  
 442  func mkU32(v uint32) (p *uint32) {
 443  	x := v
 444  	return &x
 445  }
 446  
 447  // keyIs reports whether a filter key is exactly the field name want. Unmarshal
 448  // dispatches on it: matching only the first byte and a length bound sent every
 449  // unknown key that shared a leading byte into a known field's parser.
 450  func keyIs(key, want []byte) (ok bool) {
 451  	if len(key) != len(want) {
 452  		return false
 453  	}
 454  	for i := int32(0); i < len(key); i++ {
 455  		if key[i] != want[i] {
 456  			return false
 457  		}
 458  	}
 459  	return true
 460  }
 461  
 462  // mkByteMap returns a fresh map allocated in the caller's arena, for the same
 463  // reason as mkTagT: a composite literal written inside a mutating method is
 464  // allocated in the receiver's sovereign data arena, which is reclaimed at the
 465  // method's batch boundary.
 466  func mkByteMap() (m map[string][]byte) {
 467  	return map[string][]byte{}
 468  }
 469