envelopes.go raw

   1  package nostr
   2  
   3  import (
   4  	"encoding/hex"
   5  	"errors"
   6  	"fmt"
   7  	"strconv"
   8  	"strings"
   9  	"unsafe"
  10  
  11  	"github.com/mailru/easyjson"
  12  	jwriter "github.com/mailru/easyjson/jwriter"
  13  	"github.com/tidwall/gjson"
  14  )
  15  
  16  var UnknownLabel = errors.New("unknown envelope label")
  17  
  18  type MessageParser interface {
  19  	// ParseMessage parses a message into an Envelope.
  20  	ParseMessage(string) (Envelope, error)
  21  }
  22  
  23  // Deprecated: use NewMessageParser instead
  24  func ParseMessage(message string) Envelope {
  25  	firstQuote := strings.IndexRune(message, '"')
  26  	if firstQuote == -1 {
  27  		return nil
  28  	}
  29  	secondQuote := strings.IndexRune(message[firstQuote+1:], '"')
  30  	if secondQuote == -1 {
  31  		return nil
  32  	}
  33  	label := message[firstQuote+1 : firstQuote+1+secondQuote]
  34  
  35  	var v Envelope
  36  	switch label {
  37  	case "EVENT":
  38  		v = &EventEnvelope{}
  39  	case "REQ":
  40  		v = &ReqEnvelope{}
  41  	case "COUNT":
  42  		v = &CountEnvelope{}
  43  	case "NOTICE":
  44  		x := NoticeEnvelope("")
  45  		v = &x
  46  	case "EOSE":
  47  		x := EOSEEnvelope("")
  48  		v = &x
  49  	case "OK":
  50  		v = &OKEnvelope{}
  51  	case "AUTH":
  52  		v = &AuthEnvelope{}
  53  	case "CLOSED":
  54  		v = &ClosedEnvelope{}
  55  	case "CLOSE":
  56  		x := CloseEnvelope("")
  57  		v = &x
  58  	default:
  59  		return nil
  60  	}
  61  
  62  	if err := v.FromJSON(message); err != nil {
  63  		return nil
  64  	}
  65  
  66  	return v
  67  }
  68  
  69  // Envelope is the interface for all nostr message envelopes.
  70  type Envelope interface {
  71  	Label() string
  72  	FromJSON(string) error
  73  	MarshalJSON() ([]byte, error)
  74  	String() string
  75  }
  76  
  77  var (
  78  	_ Envelope = (*EventEnvelope)(nil)
  79  	_ Envelope = (*ReqEnvelope)(nil)
  80  	_ Envelope = (*CountEnvelope)(nil)
  81  	_ Envelope = (*NoticeEnvelope)(nil)
  82  	_ Envelope = (*EOSEEnvelope)(nil)
  83  	_ Envelope = (*CloseEnvelope)(nil)
  84  	_ Envelope = (*OKEnvelope)(nil)
  85  	_ Envelope = (*AuthEnvelope)(nil)
  86  )
  87  
  88  // EventEnvelope represents an EVENT message.
  89  type EventEnvelope struct {
  90  	SubscriptionID *string
  91  	Event
  92  }
  93  
  94  func (_ EventEnvelope) Label() string { return "EVENT" }
  95  
  96  func (v *EventEnvelope) FromJSON(data string) error {
  97  	r := gjson.Parse(data)
  98  	arr := r.Array()
  99  	switch len(arr) {
 100  	case 2:
 101  		return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[1].Raw), len(arr[1].Raw)), &v.Event)
 102  	case 3:
 103  		subid := string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
 104  		v.SubscriptionID = &subid
 105  		return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &v.Event)
 106  	default:
 107  		return fmt.Errorf("failed to decode EVENT envelope")
 108  	}
 109  }
 110  
 111  func (v EventEnvelope) MarshalJSON() ([]byte, error) {
 112  	w := jwriter.Writer{NoEscapeHTML: true}
 113  	w.RawString(`["EVENT",`)
 114  	if v.SubscriptionID != nil {
 115  		w.RawString(`"`)
 116  		w.RawString(*v.SubscriptionID)
 117  		w.RawString(`",`)
 118  	}
 119  	v.Event.MarshalEasyJSON(&w)
 120  	w.RawString(`]`)
 121  	return w.BuildBytes()
 122  }
 123  
 124  // ReqEnvelope represents a REQ message.
 125  type ReqEnvelope struct {
 126  	SubscriptionID string
 127  	Filters
 128  }
 129  
 130  func (_ ReqEnvelope) Label() string { return "REQ" }
 131  
 132  func (v *ReqEnvelope) FromJSON(data string) error {
 133  	r := gjson.Parse(data)
 134  	arr := r.Array()
 135  	if len(arr) < 3 {
 136  		return fmt.Errorf("failed to decode REQ envelope: missing filters")
 137  	}
 138  	v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
 139  	v.Filters = make(Filters, len(arr)-2)
 140  	f := 0
 141  	for i := 2; i < len(arr); i++ {
 142  		if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw)), &v.Filters[f]); err != nil {
 143  			return fmt.Errorf("%w -- on filter %d", err, f)
 144  		}
 145  		f++
 146  	}
 147  
 148  	return nil
 149  }
 150  
 151  func (v ReqEnvelope) MarshalJSON() ([]byte, error) {
 152  	w := jwriter.Writer{NoEscapeHTML: true}
 153  	w.RawString(`["REQ","`)
 154  	w.RawString(v.SubscriptionID)
 155  	w.RawString(`"`)
 156  	for _, filter := range v.Filters {
 157  		w.RawString(`,`)
 158  		filter.MarshalEasyJSON(&w)
 159  	}
 160  	w.RawString(`]`)
 161  	return w.BuildBytes()
 162  }
 163  
 164  // CountEnvelope represents a COUNT message.
 165  type CountEnvelope struct {
 166  	SubscriptionID string
 167  	Filter
 168  	Count       *int64
 169  	HyperLogLog []byte
 170  }
 171  
 172  func (_ CountEnvelope) Label() string { return "COUNT" }
 173  func (c CountEnvelope) String() string {
 174  	v, _ := json.Marshal(c)
 175  	return string(v)
 176  }
 177  
 178  func (v *CountEnvelope) FromJSON(data string) error {
 179  	r := gjson.Parse(data)
 180  	arr := r.Array()
 181  	if len(arr) < 3 {
 182  		return fmt.Errorf("failed to decode COUNT envelope: missing filters")
 183  	}
 184  	v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
 185  
 186  	var countResult struct {
 187  		Count *int64 `json:"count"`
 188  		HLL   string `json:"hll"`
 189  	}
 190  	if err := json.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &countResult); err == nil && countResult.Count != nil {
 191  		v.Count = countResult.Count
 192  		if len(countResult.HLL) == 512 {
 193  			v.HyperLogLog, err = hex.DecodeString(countResult.HLL)
 194  			if err != nil {
 195  				return fmt.Errorf("invalid \"hll\" value in COUNT message: %w", err)
 196  			}
 197  		}
 198  		return nil
 199  	}
 200  
 201  	f := 0
 202  	for i := 2; i < len(arr); i++ {
 203  		item := unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw))
 204  
 205  		if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
 206  			return fmt.Errorf("%w -- on filter %d", err, f)
 207  		}
 208  
 209  		f++
 210  	}
 211  
 212  	return nil
 213  }
 214  
 215  func (v CountEnvelope) MarshalJSON() ([]byte, error) {
 216  	w := jwriter.Writer{NoEscapeHTML: true}
 217  	w.RawString(`["COUNT","`)
 218  	w.RawString(v.SubscriptionID)
 219  	w.RawString(`",`)
 220  	if v.Count != nil {
 221  		w.RawString(`{"count":`)
 222  		w.RawString(strconv.FormatInt(*v.Count, 10))
 223  		if v.HyperLogLog != nil {
 224  			w.RawString(`,"hll":"`)
 225  			hllHex := make([]byte, 512)
 226  			hex.Encode(hllHex, v.HyperLogLog)
 227  			w.Buffer.AppendBytes(hllHex)
 228  			w.RawString(`"`)
 229  		}
 230  		w.RawString(`}`)
 231  	} else {
 232  		v.Filter.MarshalEasyJSON(&w)
 233  	}
 234  	w.RawString(`]`)
 235  	return w.BuildBytes()
 236  }
 237  
 238  // NoticeEnvelope represents a NOTICE message.
 239  type NoticeEnvelope string
 240  
 241  func (_ NoticeEnvelope) Label() string { return "NOTICE" }
 242  func (n NoticeEnvelope) String() string {
 243  	v, _ := json.Marshal(n)
 244  	return string(v)
 245  }
 246  
 247  func (v *NoticeEnvelope) FromJSON(data string) error {
 248  	r := gjson.Parse(data)
 249  	arr := r.Array()
 250  	if len(arr) < 2 {
 251  		return fmt.Errorf("failed to decode NOTICE envelope")
 252  	}
 253  	*v = NoticeEnvelope(string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))))
 254  	return nil
 255  }
 256  
 257  func (v NoticeEnvelope) MarshalJSON() ([]byte, error) {
 258  	w := jwriter.Writer{NoEscapeHTML: true}
 259  	w.RawString(`["NOTICE",`)
 260  	w.Raw(json.Marshal(string(v)))
 261  	w.RawString(`]`)
 262  	return w.BuildBytes()
 263  }
 264  
 265  // EOSEEnvelope represents an EOSE (End of Stored Events) message.
 266  type EOSEEnvelope string
 267  
 268  func (_ EOSEEnvelope) Label() string { return "EOSE" }
 269  func (e EOSEEnvelope) String() string {
 270  	v, _ := json.Marshal(e)
 271  	return string(v)
 272  }
 273  
 274  func (v *EOSEEnvelope) FromJSON(data string) error {
 275  	r := gjson.Parse(data)
 276  	arr := r.Array()
 277  	if len(arr) < 2 {
 278  		return fmt.Errorf("failed to decode EOSE envelope")
 279  	}
 280  	*v = EOSEEnvelope(string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))))
 281  	return nil
 282  }
 283  
 284  func (v EOSEEnvelope) MarshalJSON() ([]byte, error) {
 285  	w := jwriter.Writer{NoEscapeHTML: true}
 286  	w.RawString(`["EOSE",`)
 287  	w.Raw(json.Marshal(string(v)))
 288  	w.RawString(`]`)
 289  	return w.BuildBytes()
 290  }
 291  
 292  // CloseEnvelope represents a CLOSE message.
 293  type CloseEnvelope string
 294  
 295  func (_ CloseEnvelope) Label() string { return "CLOSE" }
 296  func (c CloseEnvelope) String() string {
 297  	v, _ := json.Marshal(c)
 298  	return string(v)
 299  }
 300  
 301  func (v *CloseEnvelope) FromJSON(data string) error {
 302  	r := gjson.Parse(data)
 303  	arr := r.Array()
 304  	switch len(arr) {
 305  	case 2:
 306  		*v = CloseEnvelope(string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))))
 307  		return nil
 308  	default:
 309  		return fmt.Errorf("failed to decode CLOSE envelope")
 310  	}
 311  }
 312  
 313  func (v CloseEnvelope) MarshalJSON() ([]byte, error) {
 314  	w := jwriter.Writer{NoEscapeHTML: true}
 315  	w.RawString(`["CLOSE",`)
 316  	w.Raw(json.Marshal(string(v)))
 317  	w.RawString(`]`)
 318  	return w.BuildBytes()
 319  }
 320  
 321  // ClosedEnvelope represents a CLOSED message.
 322  type ClosedEnvelope struct {
 323  	SubscriptionID string
 324  	Reason         string
 325  }
 326  
 327  func (_ ClosedEnvelope) Label() string { return "CLOSED" }
 328  func (c ClosedEnvelope) String() string {
 329  	v, _ := json.Marshal(c)
 330  	return string(v)
 331  }
 332  
 333  func (v *ClosedEnvelope) FromJSON(data string) error {
 334  	r := gjson.Parse(data)
 335  	arr := r.Array()
 336  	switch len(arr) {
 337  	case 3:
 338  		*v = ClosedEnvelope{
 339  			string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))),
 340  			string(unsafe.Slice(unsafe.StringData(arr[2].Str), len(arr[2].Str))),
 341  		}
 342  		return nil
 343  	default:
 344  		return fmt.Errorf("failed to decode CLOSED envelope")
 345  	}
 346  }
 347  
 348  func (v ClosedEnvelope) MarshalJSON() ([]byte, error) {
 349  	w := jwriter.Writer{NoEscapeHTML: true}
 350  	w.RawString(`["CLOSED",`)
 351  	w.Raw(json.Marshal(string(v.SubscriptionID)))
 352  	w.RawString(`,`)
 353  	w.Raw(json.Marshal(v.Reason))
 354  	w.RawString(`]`)
 355  	return w.BuildBytes()
 356  }
 357  
 358  // OKEnvelope represents an OK message.
 359  type OKEnvelope struct {
 360  	EventID string
 361  	OK      bool
 362  	Reason  string
 363  }
 364  
 365  func (_ OKEnvelope) Label() string { return "OK" }
 366  func (o OKEnvelope) String() string {
 367  	v, _ := json.Marshal(o)
 368  	return string(v)
 369  }
 370  
 371  func (v *OKEnvelope) FromJSON(data string) error {
 372  	r := gjson.Parse(data)
 373  	arr := r.Array()
 374  	if len(arr) < 4 {
 375  		return fmt.Errorf("failed to decode OK envelope: missing fields")
 376  	}
 377  	v.EventID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
 378  	v.OK = arr[2].Raw == "true"
 379  	v.Reason = string(unsafe.Slice(unsafe.StringData(arr[3].Str), len(arr[3].Str)))
 380  
 381  	return nil
 382  }
 383  
 384  func (v OKEnvelope) MarshalJSON() ([]byte, error) {
 385  	w := jwriter.Writer{NoEscapeHTML: true}
 386  	w.RawString(`["OK","`)
 387  	w.RawString(v.EventID)
 388  	w.RawString(`",`)
 389  	ok := "false"
 390  	if v.OK {
 391  		ok = "true"
 392  	}
 393  	w.RawString(ok)
 394  	w.RawString(`,`)
 395  	w.Raw(json.Marshal(v.Reason))
 396  	w.RawString(`]`)
 397  	return w.BuildBytes()
 398  }
 399  
 400  // AuthEnvelope represents an AUTH message.
 401  type AuthEnvelope struct {
 402  	Challenge *string
 403  	Event     Event
 404  }
 405  
 406  func (_ AuthEnvelope) Label() string { return "AUTH" }
 407  func (a AuthEnvelope) String() string {
 408  	v, _ := json.Marshal(a)
 409  	return string(v)
 410  }
 411  
 412  func (v *AuthEnvelope) FromJSON(data string) error {
 413  	r := gjson.Parse(data)
 414  	arr := r.Array()
 415  	if len(arr) < 2 {
 416  		return fmt.Errorf("failed to decode Auth envelope: missing fields")
 417  	}
 418  	if arr[1].IsObject() {
 419  		return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[1].Raw), len(arr[1].Raw)), &v.Event)
 420  	} else {
 421  		challenge := string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
 422  		v.Challenge = &challenge
 423  	}
 424  	return nil
 425  }
 426  
 427  func (v AuthEnvelope) MarshalJSON() ([]byte, error) {
 428  	w := jwriter.Writer{NoEscapeHTML: true}
 429  	w.RawString(`["AUTH",`)
 430  	if v.Challenge != nil {
 431  		w.Raw(json.Marshal(*v.Challenge))
 432  	} else {
 433  		v.Event.MarshalEasyJSON(&w)
 434  	}
 435  	w.RawString(`]`)
 436  	return w.BuildBytes()
 437  }
 438