package event import ( "bytes" "crypto/sha256" "encoding/hex" "fmt" "io" "smesh.lol/pkg/nostr/ec/schnorr" "smesh.lol/pkg/nostr/ints" "smesh.lol/pkg/nostr/kind" "smesh.lol/pkg/nostr/tag" "smesh.lol/pkg/nostr/text" "smesh.lol/pkg/lol/chk" "smesh.lol/pkg/lol/errorf" ) // E is the primary datatype of nostr. type E struct { ID []byte Pubkey []byte CreatedAt int64 Kind uint16 Tags *tag.S Content []byte Sig []byte } var ( jId = []byte("id") jPubkey = []byte("pubkey") jCreatedAt = []byte("created_at") jKind = []byte("kind") jTags = []byte("tags") jContent = []byte("content") jSig = []byte("sig") ) func New() *E { return &E{} } func (ev *E) Free() { ev.ID = nil ev.Pubkey = nil ev.Tags = nil ev.Content = nil ev.Sig = nil } func (ev *E) Clone() *E { clone := &E{CreatedAt: ev.CreatedAt, Kind: ev.Kind} if ev.ID != nil { clone.ID = []byte{:len(ev.ID)} copy(clone.ID, ev.ID) } if ev.Pubkey != nil { clone.Pubkey = []byte{:len(ev.Pubkey)} copy(clone.Pubkey, ev.Pubkey) } if ev.Content != nil { clone.Content = []byte{:len(ev.Content)} copy(clone.Content, ev.Content) } if ev.Sig != nil { clone.Sig = []byte{:len(ev.Sig)} copy(clone.Sig, ev.Sig) } if ev.Tags != nil { clone.Tags = tag.NewS() for _, tg := range *ev.Tags { if tg != nil { newTag := tag.NewWithCap(len(tg.T)) for _, element := range tg.T { e := []byte{:len(element)} copy(e, element) newTag.T = append(newTag.T, e) } clone.Tags.Append(newTag) } } } return clone } func (ev *E) EstimateSize() (size int) { size = len(ev.ID)*2 + len(ev.Pubkey)*2 + len(ev.Sig)*2 + len(ev.Content)*2 if ev.Tags == nil { return } for _, v := range *ev.Tags { for _, w := range (*v).T { size += len(w) * 2 } } return } func (ev *E) Marshal(dst []byte) (b []byte) { b = dst if b == nil { b = []byte{:0:ev.EstimateSize()+100} } b = append(b, '{') b = append(b, '"') b = append(b, jId...) b = append(b, `":"`...) hexStart := len(b) b = append(b, []byte{:2*sha256.Size}...) hex.Encode(b[hexStart:], ev.ID) b = append(b, `","`...) b = append(b, jPubkey...) b = append(b, `":"`...) hexStart = len(b) b = append(b, []byte{:2*schnorr.PubKeyBytesLen}...) hex.Encode(b[hexStart:], ev.Pubkey) b = append(b, `","`...) b = append(b, jCreatedAt...) b = append(b, `":`...) b = ints.New(ev.CreatedAt).Marshal(b) b = append(b, `,"`...) b = append(b, jKind...) b = append(b, `":`...) b = ints.New(ev.Kind).Marshal(b) b = append(b, `,"`...) b = append(b, jTags...) b = append(b, `":`...) if ev.Tags != nil { b = ev.Tags.Marshal(b) } else { b = append(b, '[', ']') } b = append(b, `,"`...) b = append(b, jContent...) b = append(b, `":"`...) b = text.NostrEscape(b, ev.Content) b = append(b, `","`...) b = append(b, jSig...) b = append(b, `":"`...) if len(ev.Sig) > 0 { hexStart = len(b) b = append(b, []byte{:2*schnorr.SignatureSize}...) hex.Encode(b[hexStart:], ev.Sig) } b = append(b, `"}`...) return } func (ev *E) MarshalJSON() (b []byte, err error) { b = ev.Marshal(nil) return } func (ev *E) Serialize() (b []byte) { return ev.Marshal(nil) } func (ev *E) Unmarshal(b []byte) (rem []byte, err error) { key := []byte{:0:9} for ; len(b) > 0; b = b[1:] { if isWhitespace(b[0]) { continue } if b[0] == '{' { b = b[1:] goto BetweenKeys } } goto eof BetweenKeys: for ; len(b) > 0; b = b[1:] { if isWhitespace(b[0]) { continue } if b[0] == '"' { b = b[1:] goto InKey } } goto eof InKey: for ; len(b) > 0; b = b[1:] { if b[0] == '"' { b = b[1:] goto InKV } key = append(key, b[0]) } goto eof InKV: for ; len(b) > 0; b = b[1:] { if isWhitespace(b[0]) { continue } if b[0] == ':' { b = b[1:] goto InVal } } goto eof InVal: for len(b) > 0 && isWhitespace(b[0]) { b = b[1:] } switch key[0] { case jId[0]: if !bytes.Equal(jId, key) { goto invalid } var id []byte if id, b, err = text.UnmarshalHex(b); chk.E(err) { return } if len(id) != sha256.Size { err = errorf.E( []byte("invalid id, require %d got %d"), sha256.Size, len(id), ) return } ev.ID = id goto BetweenKV case jPubkey[0]: if !bytes.Equal(jPubkey, key) { goto invalid } var pk []byte if pk, b, err = text.UnmarshalHex(b); chk.E(err) { return } if len(pk) != schnorr.PubKeyBytesLen { err = errorf.E( []byte("invalid pubkey, require %d got %d"), schnorr.PubKeyBytesLen, len(pk), ) return } ev.Pubkey = pk goto BetweenKV case jKind[0]: if !bytes.Equal(jKind, key) { goto invalid } k := kind.New(0) if b, err = k.Unmarshal(b); chk.E(err) { return } ev.Kind = k.ToU16() goto BetweenKV case jTags[0]: if !bytes.Equal(jTags, key) { goto invalid } ev.Tags = tag.NewS() if b, err = ev.Tags.Unmarshal(b); chk.E(err) { return } goto BetweenKV case jSig[0]: if !bytes.Equal(jSig, key) { goto invalid } var sig []byte if sig, b, err = text.UnmarshalHex(b); chk.E(err) { return } if len(sig) != 0 && len(sig) != schnorr.SignatureSize { sig = nil } ev.Sig = sig goto BetweenKV case jContent[0]: if key[1] == jContent[1] { if !bytes.Equal(jContent, key) { goto invalid } if ev.Content, b, err = text.UnmarshalQuoted(b); chk.T(err) { return } goto BetweenKV } else if key[1] == jCreatedAt[1] { if !bytes.Equal(jCreatedAt, key) { goto invalid } i := ints.New(0) if b, err = i.Unmarshal(b); chk.T(err) { return } ev.CreatedAt = i.Int64() goto BetweenKV } else { goto invalid } default: goto invalid } BetweenKV: key = key[:0] for ; len(b) > 0; b = b[1:] { if isWhitespace(b[0]) { continue } switch { case len(b) == 0: return case b[0] == '}': b = b[1:] goto AfterClose case b[0] == ',': b = b[1:] goto BetweenKeys case b[0] == '"': b = b[1:] goto InKey } } goto AfterClose AfterClose: rem = b return invalid: err = fmt.Errorf( "invalid key,\n'%s'\n'%s'\n'%s'", string(b), string(b[:]), string(b), ) return eof: err = io.EOF return } func (ev *E) UnmarshalJSON(b []byte) (err error) { _, err = ev.Unmarshal(b) return } func isWhitespace(b byte) bool { return b == ' ' || b == '\t' || b == '\n' || b == '\r' } // S is an array of event.E that sorts in reverse chronological order. type S []*E func (ev S) Len() int { return len(ev) } func (ev S) Less(i, j int) bool { return ev[i].CreatedAt > ev[j].CreatedAt } func (ev S) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] } type C chan *E