go-nostr/envelopes.go
2025-03-04 11:42:44 -03:00

664 lines
15 KiB
Go

package nostr
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"strconv"
"github.com/mailru/easyjson"
jwriter "github.com/mailru/easyjson/jwriter"
"github.com/minio/simdjson-go"
"github.com/tidwall/gjson"
)
var (
labelEvent = []byte("EVENT")
labelReq = []byte("REQ")
labelCount = []byte("COUNT")
labelNotice = []byte("NOTICE")
labelEose = []byte("EOSE")
labelOk = []byte("OK")
labelAuth = []byte("AUTH")
labelClosed = []byte("CLOSED")
labelClose = []byte("CLOSE")
UnknownLabel = errors.New("unknown envelope label")
)
// ParseMessageSIMD parses a message using the experimental simdjson-go library.
func ParseMessageSIMD(message []byte, reuse *simdjson.ParsedJson) (Envelope, error) {
parsed, err := simdjson.Parse(message, reuse)
if err != nil {
return nil, fmt.Errorf("simdjson parse failed: %w", err)
}
iter := parsed.Iter()
iter.AdvanceInto()
if t := iter.Advance(); t != simdjson.TypeArray {
return nil, fmt.Errorf("top-level must be an array")
}
arr, _ := iter.Array(nil)
iter = arr.Iter()
iter.Advance()
label, _ := iter.StringBytes()
var v EnvelopeSIMD
switch {
case bytes.Equal(label, labelEvent):
v = &EventEnvelope{}
case bytes.Equal(label, labelReq):
v = &ReqEnvelope{}
case bytes.Equal(label, labelCount):
v = &CountEnvelope{}
case bytes.Equal(label, labelNotice):
x := NoticeEnvelope("")
v = &x
case bytes.Equal(label, labelEose):
x := EOSEEnvelope("")
v = &x
case bytes.Equal(label, labelOk):
v = &OKEnvelope{}
case bytes.Equal(label, labelAuth):
v = &AuthEnvelope{}
case bytes.Equal(label, labelClosed):
v = &ClosedEnvelope{}
case bytes.Equal(label, labelClose):
x := CloseEnvelope("")
v = &x
default:
return nil, UnknownLabel
}
err = v.UnmarshalSIMD(iter)
return v, err
}
// ParseMessage parses a message into an Envelope.
func ParseMessage(message []byte) Envelope {
firstComma := bytes.Index(message, []byte{','})
if firstComma == -1 {
return nil
}
label := message[0:firstComma]
var v Envelope
switch {
case bytes.Contains(label, labelEvent):
v = &EventEnvelope{}
case bytes.Contains(label, labelReq):
v = &ReqEnvelope{}
case bytes.Contains(label, labelCount):
v = &CountEnvelope{}
case bytes.Contains(label, labelNotice):
x := NoticeEnvelope("")
v = &x
case bytes.Contains(label, labelEose):
x := EOSEEnvelope("")
v = &x
case bytes.Contains(label, labelOk):
v = &OKEnvelope{}
case bytes.Contains(label, labelAuth):
v = &AuthEnvelope{}
case bytes.Contains(label, labelClosed):
v = &ClosedEnvelope{}
case bytes.Contains(label, labelClose):
x := CloseEnvelope("")
v = &x
default:
return nil
}
if err := v.UnmarshalJSON(message); err != nil {
return nil
}
return v
}
// Envelope is the interface for all nostr message envelopes.
type Envelope interface {
Label() string
UnmarshalJSON([]byte) error
MarshalJSON() ([]byte, error)
String() string
}
// EnvelopeSIMD extends Envelope with SIMD unmarshaling capability.
type EnvelopeSIMD interface {
Envelope
UnmarshalSIMD(simdjson.Iter) error
}
var (
_ Envelope = (*EventEnvelope)(nil)
_ Envelope = (*ReqEnvelope)(nil)
_ Envelope = (*CountEnvelope)(nil)
_ Envelope = (*NoticeEnvelope)(nil)
_ Envelope = (*EOSEEnvelope)(nil)
_ Envelope = (*CloseEnvelope)(nil)
_ Envelope = (*OKEnvelope)(nil)
_ Envelope = (*AuthEnvelope)(nil)
)
// EventEnvelope represents an EVENT message.
type EventEnvelope struct {
SubscriptionID *string
Event
}
func (_ EventEnvelope) Label() string { return "EVENT" }
func (v *EventEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
switch len(arr) {
case 2:
return easyjson.Unmarshal([]byte(arr[1].Raw), &v.Event)
case 3:
v.SubscriptionID = &arr[1].Str
return easyjson.Unmarshal([]byte(arr[2].Raw), &v.Event)
default:
return fmt.Errorf("failed to decode EVENT envelope")
}
}
func (v *EventEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
// we may or may not have a subscription ID, so peek
if iter.PeekNext() == simdjson.TypeString {
iter.Advance()
// we have a subscription ID
subID, err := iter.String()
if err != nil {
return err
}
v.SubscriptionID = &subID
}
// now get the event
if typ := iter.Advance(); typ == simdjson.TypeNone {
return fmt.Errorf("missing event")
}
return v.Event.UnmarshalSIMD(&iter)
}
func (v EventEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["EVENT",`)
if v.SubscriptionID != nil {
w.RawString(`"`)
w.RawString(*v.SubscriptionID)
w.RawString(`",`)
}
v.Event.MarshalEasyJSON(&w)
w.RawString(`]`)
return w.BuildBytes()
}
// ReqEnvelope represents a REQ message.
type ReqEnvelope struct {
SubscriptionID string
Filters
}
func (_ ReqEnvelope) Label() string { return "REQ" }
func (v *ReqEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
if len(arr) < 3 {
return fmt.Errorf("failed to decode REQ envelope: missing filters")
}
v.SubscriptionID = arr[1].Str
v.Filters = make(Filters, len(arr)-2)
f := 0
for i := 2; i < len(arr); i++ {
if err := easyjson.Unmarshal([]byte(arr[i].Raw), &v.Filters[f]); err != nil {
return fmt.Errorf("%w -- on filter %d", err, f)
}
f++
}
return nil
}
func (v *ReqEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
var err error
// we must have a subscription id
if typ := iter.Advance(); typ == simdjson.TypeString {
v.SubscriptionID, err = iter.String()
if err != nil {
return err
}
} else {
return fmt.Errorf("unexpected %s for REQ subscription id", typ)
}
// now get the filters
v.Filters = make(Filters, 0, 1)
tempIter := &simdjson.Iter{} // make a new iterator here because there may come multiple filters
for {
if typ, err := iter.AdvanceIter(tempIter); err != nil {
return err
} else if typ == simdjson.TypeNone {
break
} else {
}
var filter Filter
if err := filter.UnmarshalSIMD(tempIter); err != nil {
return err
}
v.Filters = append(v.Filters, filter)
}
if len(v.Filters) == 0 {
return fmt.Errorf("need at least one filter")
}
return nil
}
func (v ReqEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["REQ","`)
w.RawString(v.SubscriptionID)
w.RawString(`"`)
for _, filter := range v.Filters {
w.RawString(`,`)
filter.MarshalEasyJSON(&w)
}
w.RawString(`]`)
return w.BuildBytes()
}
// CountEnvelope represents a COUNT message.
type CountEnvelope struct {
SubscriptionID string
Filters
Count *int64
HyperLogLog []byte
}
func (_ CountEnvelope) Label() string { return "COUNT" }
func (c CountEnvelope) String() string {
v, _ := json.Marshal(c)
return string(v)
}
func (v *CountEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
if len(arr) < 3 {
return fmt.Errorf("failed to decode COUNT envelope: missing filters")
}
v.SubscriptionID = arr[1].Str
var countResult struct {
Count *int64 `json:"count"`
HLL string `json:"hll"`
}
if err := json.Unmarshal([]byte(arr[2].Raw), &countResult); err == nil && countResult.Count != nil {
v.Count = countResult.Count
if len(countResult.HLL) == 512 {
v.HyperLogLog, err = hex.DecodeString(countResult.HLL)
if err != nil {
return fmt.Errorf("invalid \"hll\" value in COUNT message: %w", err)
}
}
return nil
}
v.Filters = make(Filters, len(arr)-2)
f := 0
for i := 2; i < len(arr); i++ {
item := []byte(arr[i].Raw)
if err := easyjson.Unmarshal(item, &v.Filters[f]); err != nil {
return fmt.Errorf("%w -- on filter %d", err, f)
}
f++
}
return nil
}
func (v *CountEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
var err error
// this has two cases:
// in the first case (request from client) this is like REQ except with always one filter
// in the other (response from relay) we have a json object response
// but both cases start with a subscription id
if typ := iter.Advance(); typ == simdjson.TypeString {
v.SubscriptionID, err = iter.String()
if err != nil {
return err
}
} else {
return fmt.Errorf("unexpected %s for COUNT subscription id", typ)
}
// now get either a single filter or stuff from the json object
if typ := iter.Advance(); typ == simdjson.TypeNone {
return fmt.Errorf("missing json object")
}
if el, err := iter.FindElement(nil, "count"); err == nil {
c, _ := el.Iter.Uint()
count := int64(c)
v.Count = &count
if el, err = iter.FindElement(nil, "hll"); err == nil {
if hllHex, err := el.Iter.StringBytes(); err != nil || len(hllHex) != 512 {
return fmt.Errorf("hll is malformed")
} else {
v.HyperLogLog = make([]byte, 256)
if _, err := hex.Decode(v.HyperLogLog, hllHex); err != nil {
return fmt.Errorf("hll is invalid hex")
}
}
}
} else {
var filter Filter
if err := filter.UnmarshalSIMD(&iter); err != nil {
return err
}
v.Filters = Filters{filter}
}
return nil
}
func (v CountEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["COUNT","`)
w.RawString(v.SubscriptionID)
w.RawString(`"`)
if v.Count != nil {
w.RawString(`,{"count":`)
w.RawString(strconv.FormatInt(*v.Count, 10))
if v.HyperLogLog != nil {
w.RawString(`,"hll":"`)
hllHex := make([]byte, 512)
hex.Encode(hllHex, v.HyperLogLog)
w.Buffer.AppendBytes(hllHex)
w.RawString(`"`)
}
w.RawString(`}`)
} else {
for _, filter := range v.Filters {
w.RawString(`,`)
filter.MarshalEasyJSON(&w)
}
}
w.RawString(`]`)
return w.BuildBytes()
}
// NoticeEnvelope represents a NOTICE message.
type NoticeEnvelope string
func (_ NoticeEnvelope) Label() string { return "NOTICE" }
func (n NoticeEnvelope) String() string {
v, _ := json.Marshal(n)
return string(v)
}
func (v *NoticeEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
if len(arr) < 2 {
return fmt.Errorf("failed to decode NOTICE envelope")
}
*v = NoticeEnvelope(arr[1].Str)
return nil
}
func (v *NoticeEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
if typ := iter.Advance(); typ == simdjson.TypeString {
msg, _ := iter.String()
*v = NoticeEnvelope(msg)
}
return nil
}
func (v NoticeEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["NOTICE",`)
w.Raw(json.Marshal(string(v)))
w.RawString(`]`)
return w.BuildBytes()
}
// EOSEEnvelope represents an EOSE (End of Stored Events) message.
type EOSEEnvelope string
func (_ EOSEEnvelope) Label() string { return "EOSE" }
func (e EOSEEnvelope) String() string {
v, _ := json.Marshal(e)
return string(v)
}
func (v *EOSEEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
if len(arr) < 2 {
return fmt.Errorf("failed to decode EOSE envelope")
}
*v = EOSEEnvelope(arr[1].Str)
return nil
}
func (v *EOSEEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
if typ := iter.Advance(); typ == simdjson.TypeString {
msg, _ := iter.String()
*v = EOSEEnvelope(msg)
}
return nil
}
func (v EOSEEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["EOSE",`)
w.Raw(json.Marshal(string(v)))
w.RawString(`]`)
return w.BuildBytes()
}
// CloseEnvelope represents a CLOSE message.
type CloseEnvelope string
func (_ CloseEnvelope) Label() string { return "CLOSE" }
func (c CloseEnvelope) String() string {
v, _ := json.Marshal(c)
return string(v)
}
func (v *CloseEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
switch len(arr) {
case 2:
*v = CloseEnvelope(arr[1].Str)
return nil
default:
return fmt.Errorf("failed to decode CLOSE envelope")
}
}
func (v *CloseEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
if typ := iter.Advance(); typ == simdjson.TypeString {
msg, _ := iter.String()
*v = CloseEnvelope(msg)
}
return nil
}
func (v CloseEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["CLOSE",`)
w.Raw(json.Marshal(string(v)))
w.RawString(`]`)
return w.BuildBytes()
}
// ClosedEnvelope represents a CLOSED message.
type ClosedEnvelope struct {
SubscriptionID string
Reason string
}
func (_ ClosedEnvelope) Label() string { return "CLOSED" }
func (c ClosedEnvelope) String() string {
v, _ := json.Marshal(c)
return string(v)
}
func (v *ClosedEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
switch len(arr) {
case 3:
*v = ClosedEnvelope{arr[1].Str, arr[2].Str}
return nil
default:
return fmt.Errorf("failed to decode CLOSED envelope")
}
}
func (v *ClosedEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
if typ := iter.Advance(); typ == simdjson.TypeString {
v.SubscriptionID, _ = iter.String()
}
if typ := iter.Advance(); typ == simdjson.TypeString {
v.Reason, _ = iter.String()
}
return nil
}
func (v ClosedEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["CLOSED",`)
w.Raw(json.Marshal(string(v.SubscriptionID)))
w.RawString(`,`)
w.Raw(json.Marshal(v.Reason))
w.RawString(`]`)
return w.BuildBytes()
}
// OKEnvelope represents an OK message.
type OKEnvelope struct {
EventID string
OK bool
Reason string
}
func (_ OKEnvelope) Label() string { return "OK" }
func (o OKEnvelope) String() string {
v, _ := json.Marshal(o)
return string(v)
}
func (v *OKEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
if len(arr) < 4 {
return fmt.Errorf("failed to decode OK envelope: missing fields")
}
v.EventID = arr[1].Str
v.OK = arr[2].Raw == "true"
v.Reason = arr[3].Str
return nil
}
func (v *OKEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
if typ := iter.Advance(); typ == simdjson.TypeString {
v.EventID, _ = iter.String()
} else {
return fmt.Errorf("unexpected %s for OK id", typ)
}
if typ := iter.Advance(); typ == simdjson.TypeBool {
v.OK, _ = iter.Bool()
} else {
return fmt.Errorf("unexpected %s for OK status", typ)
}
if typ := iter.Advance(); typ == simdjson.TypeString {
v.Reason, _ = iter.String()
}
return nil
}
func (v OKEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["OK","`)
w.RawString(v.EventID)
w.RawString(`",`)
ok := "false"
if v.OK {
ok = "true"
}
w.RawString(ok)
w.RawString(`,`)
w.Raw(json.Marshal(v.Reason))
w.RawString(`]`)
return w.BuildBytes()
}
// AuthEnvelope represents an AUTH message.
type AuthEnvelope struct {
Challenge *string
Event Event
}
func (_ AuthEnvelope) Label() string { return "AUTH" }
func (a AuthEnvelope) String() string {
v, _ := json.Marshal(a)
return string(v)
}
func (v *AuthEnvelope) UnmarshalJSON(data []byte) error {
r := gjson.ParseBytes(data)
arr := r.Array()
if len(arr) < 2 {
return fmt.Errorf("failed to decode Auth envelope: missing fields")
}
if arr[1].IsObject() {
return easyjson.Unmarshal([]byte(arr[1].Raw), &v.Event)
} else {
v.Challenge = &arr[1].Str
}
return nil
}
func (v *AuthEnvelope) UnmarshalSIMD(iter simdjson.Iter) error {
if typ := iter.Advance(); typ == simdjson.TypeString {
// we have a challenge
subID, err := iter.String()
if err != nil {
return err
}
v.Challenge = &subID
return nil
} else {
// we have an event
return v.Event.UnmarshalSIMD(&iter)
}
}
func (v AuthEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["AUTH",`)
if v.Challenge != nil {
w.Raw(json.Marshal(*v.Challenge))
} else {
v.Event.MarshalEasyJSON(&w)
}
w.RawString(`]`)
return w.BuildBytes()
}