diff --git a/.gitignore b/.gitignore index 8a1e939..1b30163 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ pkg/database/testrealy /.idea/go.imports.xml /.idea/inspectionProfiles/Project_Default.xml /.idea/.name +/ctxproxy.config.yml diff --git a/app/config/config.go b/app/config/config.go index 3ca17af..d86901d 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -31,6 +31,7 @@ type C struct { DBLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"database log level: fatal error warn info debug trace"` Pprof string `env:"ORLY_PPROF" usage:"enable pprof in modes: cpu,memory,allocation"` IPWhitelist []string `env:"ORLY_IP_WHITELIST" usage:"comma-separated list of IP addresses to allow access from, matches on prefixes to allow private subnets, eg 10.0.0 = 10.0.0.0/8"` + Admins []string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"` } // New creates and initializes a new configuration object for the relay diff --git a/app/handle-close.go b/app/handle-close.go index a55cd19..7c8a747 100644 --- a/app/handle-close.go +++ b/app/handle-close.go @@ -11,9 +11,7 @@ import ( // HandleClose processes a CLOSE envelope by unmarshalling the request, // validates the presence of an field, and signals cancellation for // the associated listener through the server's publisher mechanism. -func (l *Listener) HandleClose( - req []byte, -) (err error) { +func (l *Listener) HandleClose(req []byte) (err error) { var rem []byte env := closeenvelope.New() if rem, err = env.Unmarshal(req); chk.E(err) { diff --git a/app/handle-delete.go b/app/handle-delete.go new file mode 100644 index 0000000..38a1c23 --- /dev/null +++ b/app/handle-delete.go @@ -0,0 +1,180 @@ +package app + +import ( + "fmt" + + database "database.orly" + "database.orly/indexes/types" + "encoders.orly/envelopes/eventenvelope" + "encoders.orly/event" + "encoders.orly/filter" + "encoders.orly/hex" + "encoders.orly/ints" + "encoders.orly/kind" + "encoders.orly/tag" + "encoders.orly/tag/atag" + "lol.mleku.dev/chk" + "lol.mleku.dev/log" + utils "utils.orly" +) + +func (l *Listener) GetSerialsFromFilter(f *filter.F) ( + sers types.Uint40s, err error, +) { + var idxs []database.Range + if idxs, err = database.GetIndexesFromFilter(f); chk.E(err) { + return + } + for _, idx := range idxs { + var s types.Uint40s + if s, err = l.GetSerialsByRange(idx); chk.E(err) { + continue + } + sers = append(sers, s...) + } + return +} + +func (l *Listener) HandleDelete(env *eventenvelope.Submission) { + log.T.C( + func() string { + return fmt.Sprintf( + "delete event\n%s", env.E.Serialize(), + ) + }, + ) + var ownerDelete bool + for _, pk := range l.Admins { + if utils.FastEqual(pk, env.E.Pubkey) { + ownerDelete = true + break + } + } + // process the tags in the delete event + var err error + for _, t := range *env.E.Tags { + // first search for a tags, as these are the simplest to process + if utils.FastEqual(t.Key(), []byte("a")) { + at := new(atag.T) + if _, err = at.Unmarshal(t.Value()); chk.E(err) { + continue + } + if ownerDelete || utils.FastEqual(env.E.Pubkey, at.Pubkey) { + // find the event and delete it + f := &filter.F{ + Authors: tag.NewFromBytesSlice(at.Pubkey), + Kinds: kind.NewS(at.Kind), + } + if len(at.DTag) > 0 { + f.Tags = tag.NewS( + tag.NewFromAny("d", at.DTag), + ) + } + var sers types.Uint40s + if sers, err = l.GetSerialsFromFilter(f); chk.E(err) { + continue + } + // if found, delete them + if len(sers) > 0 { + for _, s := range sers { + var ev *event.E + if ev, err = l.FetchEventBySerial(s); chk.E(err) { + continue + } + if !(kind.IsReplaceable(ev.Kind) && len(at.DTag) == 0) { + // skip a tags with no dtag if the kind is not + // replaceable. + continue + } + if err = l.DeleteEventBySerial( + l.Ctx, s, ev, + ); chk.E(err) { + continue + } + } + } + } + continue + } + // if e tags are found, delete them if the author is signer, or one of + // the owners is signer + if utils.FastEqual(t.Key(), []byte("e")) { + var dst []byte + if _, err = hex.DecBytes(dst, t.Value()); chk.E(err) { + continue + } + f := &filter.F{ + Ids: tag.NewFromBytesSlice(dst), + } + var sers types.Uint40s + if sers, err = l.GetSerialsFromFilter(f); chk.E(err) { + continue + } + // if found, delete them + if len(sers) > 0 { + // there should be only one event per serial, so we can just + // delete them all + for _, s := range sers { + var ev *event.E + if ev, err = l.FetchEventBySerial(s); chk.E(err) { + continue + } + // check that the author is the same as the signer of the + // delete, for the k tag case the author is the signer of + // the event. + if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) { + continue + } + // exclude delete events + if ev.Kind == kind.EventDeletion.K { + continue + } + if err = l.DeleteEventBySerial(l.Ctx, s, ev); chk.E(err) { + continue + } + } + continue + } + } + // if k tags are found, check they are replaceable + if utils.FastEqual(t.Key(), []byte("k")) { + ki := ints.New(0) + if _, err = ki.Unmarshal(t.Value()); chk.E(err) { + continue + } + kn := ki.Uint16() + // skip events that are delete events or that are not replaceable + if !kind.IsReplaceable(kn) || kn != kind.EventDeletion.K { + continue + } + f := &filter.F{ + Authors: tag.NewFromBytesSlice(env.E.Pubkey), + Kinds: kind.NewS(kind.New(kn)), + } + var sers types.Uint40s + if sers, err = l.GetSerialsFromFilter(f); chk.E(err) { + continue + } + // if found, delete them + if len(sers) > 0 { + // there should be only one event per serial because replaces + // delete old ones, so we can just delete them all + for _, s := range sers { + var ev *event.E + if ev, err = l.FetchEventBySerial(s); chk.E(err) { + continue + } + // check that the author is the same as the signer of the + // delete, for the k tag case the author is the signer of + // the event. + if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) { + continue + } + } + continue + } + } + continue + } + return +} diff --git a/app/handle-event.go b/app/handle-event.go index dcf1010..5278e6d 100644 --- a/app/handle-event.go +++ b/app/handle-event.go @@ -1,7 +1,6 @@ package app import ( - "context" "fmt" "strings" @@ -12,9 +11,7 @@ import ( utils "utils.orly" ) -func (l *Listener) HandleEvent(c context.Context, msg []byte) ( - err error, -) { +func (l *Listener) HandleEvent(msg []byte) (err error) { // decode the envelope env := eventenvelope.NewSubmission() if msg, err = env.Unmarshal(msg); chk.E(err) { @@ -57,24 +54,22 @@ func (l *Listener) HandleEvent(c context.Context, msg []byte) ( } // if the event is a delete, process the delete if env.E.Kind == kind.EventDeletion.K { - - } - // check if the event was deleted - // - // todo: the list of admin pubkeys should go in the second parameter when it - // is implemented to enable admins to delete events of other users. - if err = l.CheckForDeleted(env.E, nil); err != nil { - if strings.HasPrefix(err.Error(), "blocked:") { - errStr := err.Error()[len("blocked: "):len(err.Error())] - if err = Ok.Error( - l, env, errStr, - ); chk.E(err) { - return + l.HandleDelete(env) + } else { + // check if the event was deleted + if err = l.CheckForDeleted(env.E, l.Admins); err != nil { + if strings.HasPrefix(err.Error(), "blocked:") { + errStr := err.Error()[len("blocked: "):len(err.Error())] + if err = Ok.Error( + l, env, errStr, + ); chk.E(err) { + return + } } } } // store the event - if _, _, err = l.SaveEvent(c, env.E); chk.E(err) { + if _, _, err = l.SaveEvent(l.Ctx, env.E); chk.E(err) { return } l.publishers.Deliver(env.E) diff --git a/app/handle-message.go b/app/handle-message.go index 7fc7afe..047d6a4 100644 --- a/app/handle-message.go +++ b/app/handle-message.go @@ -29,10 +29,10 @@ func (l *Listener) HandleMessage(msg []byte, remote string) { switch t { case eventenvelope.L: log.D.F("eventenvelope: %s", rem) - err = l.HandleEvent(l.ctx, rem) + err = l.HandleEvent(rem) case reqenvelope.L: log.D.F("reqenvelope: %s", rem) - err = l.HandleReq(l.ctx, rem) + err = l.HandleReq(rem) case closeenvelope.L: log.D.F("closeenvelope: %s", rem) err = l.HandleClose(rem) diff --git a/app/handle-req.go b/app/handle-req.go index 106cf85..b9f74b3 100644 --- a/app/handle-req.go +++ b/app/handle-req.go @@ -1,7 +1,6 @@ package app import ( - "context" "errors" "encoders.orly/envelopes/closedenvelope" @@ -18,7 +17,7 @@ import ( "utils.orly/pointers" ) -func (l *Listener) HandleReq(c context.Context, msg []byte) ( +func (l *Listener) HandleReq(msg []byte) ( err error, ) { var rem []byte @@ -36,7 +35,7 @@ func (l *Listener) HandleReq(c context.Context, msg []byte) ( continue } } - if events, err = l.QueryEvents(c, f); chk.E(err) { + if events, err = l.QueryEvents(l.Ctx, f); chk.E(err) { if errors.Is(err, badger.ErrDBClosed) { return } diff --git a/app/main.go b/app/main.go index 57bfcd9..b8f43da 100644 --- a/app/main.go +++ b/app/main.go @@ -6,6 +6,7 @@ import ( "net/http" database "database.orly" + "encoders.orly/bech32encoding" "lol.mleku.dev/chk" "lol.mleku.dev/log" "next.orly.dev/app/config" @@ -23,12 +24,24 @@ func Run( close(quit) } }() + // get the admins + var err error + var adminKeys [][]byte + for _, admin := range cfg.Admins { + var pk []byte + if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(admin); chk.E(err) { + continue + } + adminKeys = append(adminKeys, pk) + } + // start listener l := &Server{ Ctx: ctx, Config: cfg, D: db, publishers: publish.New(NewPublisher(ctx)), + Admins: adminKeys, } addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port) log.I.F("starting listener on http://%s", addr) diff --git a/app/server.go b/app/server.go index ef548ad..3724a03 100644 --- a/app/server.go +++ b/app/server.go @@ -12,12 +12,13 @@ import ( ) type Server struct { - mux *http.ServeMux - Config *config.C - Ctx context.Context - remote string - *database.D + mux *http.ServeMux + Config *config.C + Ctx context.Context + remote string publishers *publish.S + Admins [][]byte + *database.D } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/convert/convert.go b/cmd/convert/convert.go new file mode 100644 index 0000000..984fdd2 --- /dev/null +++ b/cmd/convert/convert.go @@ -0,0 +1,143 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "crypto.orly/ec/schnorr" + "crypto.orly/ec/secp256k1" + b32 "encoders.orly/bech32encoding" + "encoders.orly/hex" +) + +func usage() { + fmt.Fprintf(os.Stderr, "Usage: convert [--secret] \n") + fmt.Fprintf( + os.Stderr, " can be hex (64 chars) or bech32 (npub/nsec).\n", + ) + fmt.Fprintf( + os.Stderr, + " --secret: interpret input key as a secret key; print both nsec and npub in hex and bech32.\n"+ + " --secret is implied if starts with nsec.\n", + ) +} + +func main() { + var isSecret bool + flag.BoolVar( + &isSecret, "secret", false, "interpret the input as a secret key", + ) + flag.Parse() + + if flag.NArg() < 1 { + usage() + os.Exit(2) + } + + input := strings.TrimSpace(flag.Arg(0)) + + // Auto-detect secret if input starts with nsec + if strings.HasPrefix(input, string(b32.SecHRP)) { + isSecret = true + } + + if isSecret { + if err := handleSecret(input); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + return + } + + if err := handlePublic(input); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} + +func handleSecret(input string) error { + // Accept nsec bech32 or 64-char hex as secret key + var sk *secp256k1.SecretKey + var err error + + if strings.HasPrefix(input, string(b32.SecHRP)) { // nsec... + if sk, err = b32.NsecToSecretKey([]byte(input)); err != nil { + return fmt.Errorf("failed to decode nsec: %w", err) + } + } else { + // Expect hex + if len(input) != b32.HexKeyLen { + return fmt.Errorf("secret key hex must be %d chars", b32.HexKeyLen) + } + var b []byte + if b, err = hex.Dec(input); err != nil { + return fmt.Errorf("invalid secret hex: %w", err) + } + sk = secp256k1.SecKeyFromBytes(b) + } + + // Prepare outputs for secret + nsec, err := b32.SecretKeyToNsec(sk) + if err != nil { + return fmt.Errorf("encode nsec: %w", err) + } + secHex := hex.EncAppend(nil, sk.Serialize()) + + // Derive public key + pk := sk.PubKey() + npub, err := b32.PublicKeyToNpub(pk) + if err != nil { + return fmt.Errorf("encode npub: %w", err) + } + pkBytes := schnorr.SerializePubKey(pk) + pkHex := hex.EncAppend(nil, pkBytes) + + // Print results + fmt.Printf("nsec (hex): %s\n", string(secHex)) + fmt.Printf("nsec (bech32): %s\n", string(nsec)) + fmt.Printf("npub (hex): %s\n", string(pkHex)) + fmt.Printf("npub (bech32): %s\n", string(npub)) + return nil +} + +func handlePublic(input string) error { + // Accept npub bech32, nsec bech32 (derive pub), or 64-char hex pubkey + var pubBytes []byte + var err error + + if strings.HasPrefix(input, string(b32.PubHRP)) { // npub... + if pubBytes, err = b32.NpubToBytes([]byte(input)); err != nil { + return fmt.Errorf("failed to decode npub: %w", err) + } + } else if strings.HasPrefix( + input, string(b32.SecHRP), + ) { // nsec without --secret: show pub only + var sk *secp256k1.SecretKey + if sk, err = b32.NsecToSecretKey([]byte(input)); err != nil { + return fmt.Errorf("failed to decode nsec: %w", err) + } + pubBytes = schnorr.SerializePubKey(sk.PubKey()) + } else { + // Expect hex pubkey + if len(input) != b32.HexKeyLen { + return fmt.Errorf("public key hex must be %d chars", b32.HexKeyLen) + } + if pubBytes, err = hex.Dec(input); err != nil { + return fmt.Errorf("invalid public hex: %w", err) + } + } + + // Compute encodings + npub, err := b32.BinToNpub(pubBytes) + if err != nil { + return fmt.Errorf("encode npub: %w", err) + } + pubHex := hex.EncAppend(nil, pubBytes) + + // Print only pubkey representations + fmt.Printf("npub (hex): %s\n", string(pubHex)) + fmt.Printf("npub (bech32): %s\n", string(npub)) + return nil +} diff --git a/pkg/database/query-for-deleted.go b/pkg/database/query-for-deleted.go index dfadd95..90bfdd1 100644 --- a/pkg/database/query-for-deleted.go +++ b/pkg/database/query-for-deleted.go @@ -31,7 +31,7 @@ func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) { t := ev.Tags.GetFirst([]byte("d")) a := atag.T{ Kind: kind.New(ev.Kind), - PubKey: ev.Pubkey, + Pubkey: ev.Pubkey, DTag: t.Value(), } at := a.Marshal(nil) @@ -135,7 +135,7 @@ func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) { // construct a tag a := atag.T{ Kind: kind.New(ev.Kind), - PubKey: ev.Pubkey, + Pubkey: ev.Pubkey, } at := a.Marshal(nil) if idxs, err = GetIndexesFromFilter( diff --git a/pkg/encoders/bech32encoding/keys.go b/pkg/encoders/bech32encoding/keys.go index cddedcd..65c5e70 100644 --- a/pkg/encoders/bech32encoding/keys.go +++ b/pkg/encoders/bech32encoding/keys.go @@ -2,6 +2,7 @@ package bech32encoding import ( "bytes" + "strings" "crypto.orly/ec" "crypto.orly/ec/bech32" @@ -11,6 +12,7 @@ import ( "lol.mleku.dev/chk" "lol.mleku.dev/log" "utils.orly" + "utils.orly/constraints" ) const ( @@ -67,7 +69,9 @@ func PublicKeyToNpub(pk *secp256k1.PublicKey) (encoded []byte, err error) { // NsecToSecretKey decodes a nostr secret key (nsec) and returns the secp256k1 // secret key. -func NsecToSecretKey(encoded []byte) (sk *secp256k1.SecretKey, err error) { +func NsecToSecretKey[V constraints.Bytes](encoded V) ( + sk *secp256k1.SecretKey, err error, +) { var b8 []byte if b8, err = NsecToBytes(encoded); chk.E(err) { return @@ -77,9 +81,9 @@ func NsecToSecretKey(encoded []byte) (sk *secp256k1.SecretKey, err error) { } // NsecToBytes converts a nostr bech32 encoded secret key to raw bytes. -func NsecToBytes(encoded []byte) (sk []byte, err error) { +func NsecToBytes[V constraints.Bytes](encoded V) (sk []byte, err error) { var b5, hrp []byte - if hrp, b5, err = bech32.Decode(encoded); chk.E(err) { + if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) { return } if !utils.FastEqual(hrp, SecHRP) { @@ -97,9 +101,9 @@ func NsecToBytes(encoded []byte) (sk []byte, err error) { } // NpubToBytes converts a bech32 encoded public key to raw bytes. -func NpubToBytes(encoded []byte) (pk []byte, err error) { +func NpubToBytes[V constraints.Bytes](encoded V) (pk []byte, err error) { var b5, hrp []byte - if hrp, b5, err = bech32.Decode(encoded); chk.E(err) { + if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) { return } if !utils.FastEqual(hrp, PubHRP) { @@ -118,9 +122,11 @@ func NpubToBytes(encoded []byte) (pk []byte, err error) { // NpubToPublicKey decodes an nostr public key (npub) and returns an secp256k1 // public key. -func NpubToPublicKey(encoded []byte) (pk *secp256k1.PublicKey, err error) { +func NpubToPublicKey[V constraints.Bytes](encoded V) ( + pk *secp256k1.PublicKey, err error, +) { var b5, b8, hrp []byte - if hrp, b5, err = bech32.Decode(encoded); chk.E(err) { + if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) { err = log.E.Err("ERROR: '%s'", err) return } @@ -134,14 +140,13 @@ func NpubToPublicKey(encoded []byte) (pk *secp256k1.PublicKey, err error) { if b8, err = ConvertFromBech32(b5); chk.E(err) { return } - return schnorr.ParsePubKey(b8[:schnorr.PubKeyBytesLen]) } // HexToPublicKey decodes a string that should be a 64 character long hex // encoded public key into a btcec.PublicKey that can be used to verify a // signature or encode to Bech32. -func HexToPublicKey(pk string) (p *btcec.PublicKey, err error) { +func HexToPublicKey[V constraints.Bytes](pk V) (p *btcec.PublicKey, err error) { if len(pk) != HexKeyLen { err = log.E.Err( "secret key is %d bytes, must be %d", len(pk), @@ -150,7 +155,7 @@ func HexToPublicKey(pk string) (p *btcec.PublicKey, err error) { return } var pb []byte - if pb, err = hex.Dec(pk); chk.D(err) { + if pb, err = hex.Dec(string(pk)); chk.D(err) { return } if p, err = schnorr.ParsePubKey(pb); chk.D(err) { @@ -159,17 +164,30 @@ func HexToPublicKey(pk string) (p *btcec.PublicKey, err error) { return } -func NpubOrHexToPublicKey(encoded []byte) (pk *btcec.PublicKey, err error) { - if !bytes.HasPrefix([]byte("npub"), encoded) && len(encoded) == HexKeyLen { - return HexToPublicKey(string(encoded)) +func NpubOrHexToPublicKey[V constraints.Bytes](encoded V) ( + pk *btcec.PublicKey, err error, +) { + if !strings.HasPrefix( + "npub", string(encoded), + ) && len(encoded) == HexKeyLen { + return HexToPublicKey(encoded) } return NpubToPublicKey(encoded) } +func NpubOrHexToPublicKeyBinary[V constraints.Bytes](enc V) ( + pkb []byte, err error, +) { + if bytes.HasPrefix([]byte(enc), []byte("npub")) { + return NpubToBytes(enc) + } + return hex.Dec(string(enc)) +} + // HexToSecretKey decodes a string that should be a 64 character long hex // encoded public key into a btcec.PublicKey that can be used to verify a // signature or encode to Bech32. -func HexToSecretKey(sk []byte) (s *btcec.SecretKey, err error) { +func HexToSecretKey[V constraints.Bytes](sk V) (s *btcec.SecretKey, err error) { if len(sk) != HexKeyLen { err = log.E.Err( "secret key is %d bytes, must be %d", len(sk), @@ -178,7 +196,7 @@ func HexToSecretKey(sk []byte) (s *btcec.SecretKey, err error) { return } pb := make([]byte, schnorr.PubKeyBytesLen) - if _, err = hex.DecBytes(pb, sk); chk.D(err) { + if _, err = hex.DecBytes(pb, []byte(sk)); chk.D(err) { return } if s = secp256k1.SecKeyFromBytes(pb); chk.D(err) { @@ -189,9 +207,9 @@ func HexToSecretKey(sk []byte) (s *btcec.SecretKey, err error) { // HexToNpub converts a raw 64 character hex encoded public key (as used in // standard nostr json events) to a bech32 encoded npub. -func HexToNpub(publicKeyHex []byte) (s []byte, err error) { +func HexToNpub[V constraints.Bytes](publicKeyHex V) (s []byte, err error) { b := make([]byte, schnorr.PubKeyBytesLen) - if _, err = hex.DecBytes(b, publicKeyHex); chk.D(err) { + if _, err = hex.DecBytes(b, []byte(publicKeyHex)); chk.D(err) { err = log.E.Err("failed to decode public key hex: %w", err) return } @@ -212,7 +230,7 @@ func BinToNpub(b []byte) (s []byte, err error) { } // HexToNsec converts a hex encoded secret key to a bech32 encoded nsec. -func HexToNsec(sk []byte) (nsec []byte, err error) { +func HexToNsec[V constraints.Bytes](sk V) (nsec []byte, err error) { var s *btcec.SecretKey if s, err = HexToSecretKey(sk); chk.E(err) { return @@ -235,13 +253,12 @@ func BinToNsec(sk []byte) (nsec []byte, err error) { // SecretKeyToHex converts a secret key to the hex encoding. func SecretKeyToHex(sk *btcec.SecretKey) (hexSec []byte) { - hex.EncBytes(hexSec, sk.Serialize()) - return + return hex.EncAppend(nil, sk.Serialize()) } // NsecToHex converts a bech32 encoded nostr secret key to a raw hexadecimal // string. -func NsecToHex(nsec []byte) (hexSec []byte, err error) { +func NsecToHex[V constraints.Bytes](nsec V) (hexSec []byte, err error) { var sk *secp256k1.SecretKey if sk, err = NsecToSecretKey(nsec); chk.E(err) { return diff --git a/pkg/encoders/bech32encoding/nip19_test.go b/pkg/encoders/bech32encoding/nip19_test.go index e67ce64..8a61a13 100644 --- a/pkg/encoders/bech32encoding/nip19_test.go +++ b/pkg/encoders/bech32encoding/nip19_test.go @@ -265,7 +265,7 @@ func TestEncodeDecodeNEventTestEncodeDecodeNEvent(t *testing.T) { } } -func MustDecode[V string | []byte](s V) (b []byte) { +func MustDecode[V constraints.Bytes](s V) (b []byte) { var err error if _, err = hex.Dec(string(s)); chk.E(err) { panic(err) diff --git a/pkg/encoders/envelopes/authenvelope/authenvelope.go b/pkg/encoders/envelopes/authenvelope/authenvelope.go index f3c41e1..5218be6 100644 --- a/pkg/encoders/envelopes/authenvelope/authenvelope.go +++ b/pkg/encoders/envelopes/authenvelope/authenvelope.go @@ -13,6 +13,7 @@ import ( "lol.mleku.dev/errorf" "lol.mleku.dev/log" "utils.orly/units" + "utils.orly/constraints" ) // L is the label associated with this type of codec.Envelope. @@ -30,7 +31,7 @@ var _ codec.Envelope = (*Challenge)(nil) func NewChallenge() *Challenge { return &Challenge{} } // NewChallengeWith creates a new authenvelope.Challenge with provided bytes. -func NewChallengeWith[V string | []byte](challenge V) *Challenge { +func NewChallengeWith[V constraints.Bytes](challenge V) *Challenge { return &Challenge{[]byte(challenge)} } diff --git a/pkg/encoders/envelopes/countenvelope/countenvelope.go b/pkg/encoders/envelopes/countenvelope/countenvelope.go index 5b3fc02..bd44cd3 100644 --- a/pkg/encoders/envelopes/countenvelope/countenvelope.go +++ b/pkg/encoders/envelopes/countenvelope/countenvelope.go @@ -13,6 +13,7 @@ import ( "interfaces.orly/codec" "lol.mleku.dev/chk" "lol.mleku.dev/errorf" + "utils.orly/constraints" ) // L is the label associated with this type of codec.Envelope. @@ -118,7 +119,7 @@ func NewResponse() *Response { return new(Response) } // NewResponseFrom creates a new countenvelope.Response with provided string for the // subscription.Id, a count and optional variadic approximate flag, which is // otherwise false and does not get rendered into the JSON. -func NewResponseFrom[V string | []byte]( +func NewResponseFrom[V constraints.Bytes]( s V, cnt int, approx ...bool, ) (res *Response, err error) { diff --git a/pkg/encoders/envelopes/eventenvelope/eventenvelope.go b/pkg/encoders/envelopes/eventenvelope/eventenvelope.go index d479f46..04347d7 100644 --- a/pkg/encoders/envelopes/eventenvelope/eventenvelope.go +++ b/pkg/encoders/envelopes/eventenvelope/eventenvelope.go @@ -13,6 +13,7 @@ import ( "lol.mleku.dev/errorf" "utils.orly/bufpool" "utils.orly/units" + "utils.orly/constraints" ) // L is the label associated with this type of codec.Envelope. @@ -103,7 +104,7 @@ func NewResult() *Result { return &Result{} } // NewResultWith creates a new eventenvelope.Result with a provided // subscription.Id string and event.E. -func NewResultWith[V string | []byte](s V, ev *event.E) ( +func NewResultWith[V constraints.Bytes](s V, ev *event.E) ( res *Result, err error, ) { if len(s) < 0 || len(s) > 64 { diff --git a/pkg/encoders/envelopes/noticeenvelope/noticeenvelope.go b/pkg/encoders/envelopes/noticeenvelope/noticeenvelope.go index bc008b6..a65a5b1 100644 --- a/pkg/encoders/envelopes/noticeenvelope/noticeenvelope.go +++ b/pkg/encoders/envelopes/noticeenvelope/noticeenvelope.go @@ -10,6 +10,7 @@ import ( "encoders.orly/text" "interfaces.orly/codec" "lol.mleku.dev/chk" + "utils.orly/constraints" ) // L is the label associated with this type of codec.Envelope. @@ -28,7 +29,7 @@ var _ codec.Envelope = (*T)(nil) func New() *T { return &T{} } // NewFrom creates a new noticeenvelope.T with a provided message. -func NewFrom[V string | []byte](msg V) *T { return &T{Message: []byte(msg)} } +func NewFrom[V constraints.Bytes](msg V) *T { return &T{Message: []byte(msg)} } // Label returns the label of a NOTICE envelope. func (en *T) Label() string { return L } diff --git a/pkg/encoders/envelopes/okenvelope/okenvelope.go b/pkg/encoders/envelopes/okenvelope/okenvelope.go index 2a3a60e..12857b9 100644 --- a/pkg/encoders/envelopes/okenvelope/okenvelope.go +++ b/pkg/encoders/envelopes/okenvelope/okenvelope.go @@ -14,6 +14,7 @@ import ( "lol.mleku.dev/chk" "lol.mleku.dev/errorf" "lol.mleku.dev/log" + "utils.orly/constraints" ) // L is the label associated with this type of codec.Envelope. @@ -34,7 +35,7 @@ func New() *T { return &T{} } // NewFrom creates a new okenvelope.T with a string for the subscription.Id and // the optional reason. -func NewFrom[V string | []byte](eid V, ok bool, msg ...V) *T { +func NewFrom[V constraints.Bytes](eid V, ok bool, msg ...V) *T { var m []byte if len(msg) > 0 { m = []byte(msg[0]) diff --git a/pkg/encoders/envelopes/reqenvelope/reqenvelope.go b/pkg/encoders/envelopes/reqenvelope/reqenvelope.go index fe6b046..dfeca27 100644 --- a/pkg/encoders/envelopes/reqenvelope/reqenvelope.go +++ b/pkg/encoders/envelopes/reqenvelope/reqenvelope.go @@ -10,6 +10,7 @@ import ( "encoders.orly/text" "interfaces.orly/codec" "lol.mleku.dev/chk" + "utils.orly/constraints" ) // L is the label associated with this type of codec.Envelope. @@ -39,7 +40,7 @@ func NewFrom(id []byte, ff *filter.S) *T { } } -func NewWithId[V string | []byte](id V, ff *filter.S) (sub *T) { +func NewWithId[V constraints.Bytes](id V, ff *filter.S) (sub *T) { return &T{ Subscription: []byte(id), Filters: ff, diff --git a/pkg/utils/constraints/constraints.go b/pkg/utils/constraints/constraints.go new file mode 100644 index 0000000..75d0ec2 --- /dev/null +++ b/pkg/utils/constraints/constraints.go @@ -0,0 +1,5 @@ +package constraints + +type Bytes interface { + ~string | ~[]byte +} diff --git a/pkg/utils/fastequal.go b/pkg/utils/fastequal.go index 9ef30f9..6d1c51a 100644 --- a/pkg/utils/fastequal.go +++ b/pkg/utils/fastequal.go @@ -1,6 +1,8 @@ package utils -func FastEqual[A string | []byte, B string | []byte](a A, b B) (same bool) { +import "utils.orly/constraints" + +func FastEqual[A constraints.Bytes, B constraints.Bytes](a A, b B) (same bool) { if len(a) != len(b) { return } diff --git a/pkg/utils/normalize/normalize.go b/pkg/utils/normalize/normalize.go index d51a7a5..8bace3e 100644 --- a/pkg/utils/normalize/normalize.go +++ b/pkg/utils/normalize/normalize.go @@ -11,6 +11,7 @@ import ( "encoders.orly/ints" "lol.mleku.dev/chk" "lol.mleku.dev/log" + "utils.orly/constraints" ) var ( @@ -29,7 +30,7 @@ var ( // - Adds ws:// to addresses with any other port // // - Converts http/s to ws/s -func URL[V string | []byte](v V) (b []byte) { +func URL[V constraints.Bytes](v V) (b []byte) { u := []byte(v) if len(u) == 0 { return nil