Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
0024611179
|
|||
|
699ba0554e
|
|||
|
c62d685fa4
|
|||
|
6935575654
|
|||
|
80043b46b3
|
|||
|
c68654dccc
|
|||
|
72c6d16739
|
|||
|
366d35ec28
|
|||
|
c36cec44c4
|
|||
|
c91a283520
|
|||
|
bb0693f455
|
|||
|
0d7943be89
|
|||
|
978d9b88cd
|
|||
|
bbfb9b7300
|
|||
|
5b06906673
|
|||
|
f5c3da9bc3
|
|||
|
c608e1075b
|
|||
|
5237fb1a1f
|
|||
|
6901950059
|
|||
|
251fc17933
|
|||
|
fdb9e18b03
|
|||
|
67552edf04
|
|||
|
f25b760d84
|
|||
|
bfa38822e0
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -103,3 +103,5 @@ pkg/database/testrealy
|
|||||||
/.idea/codeStyles/codeStyleConfig.xml
|
/.idea/codeStyles/codeStyleConfig.xml
|
||||||
/.idea/material_theme_project_new.xml
|
/.idea/material_theme_project_new.xml
|
||||||
/.idea/orly.iml
|
/.idea/orly.iml
|
||||||
|
/.idea/go.imports.xml
|
||||||
|
/.idea/inspectionProfiles/Project_Default.xml
|
||||||
|
|||||||
@@ -56,17 +56,17 @@ as:
|
|||||||
extensions and become active in place of the LetsEncrypt certificates
|
extensions and become active in place of the LetsEncrypt certificates
|
||||||
|
|
||||||
> Note that the match is greedy, so you can explicitly separately give a subdomain
|
> Note that the match is greedy, so you can explicitly separately give a subdomain
|
||||||
certificate and it will be selected even if there is a wildcard that also matches.
|
certificate, and it will be selected even if there is a wildcard that also matches.
|
||||||
|
|
||||||
# IMPORTANT
|
# IMPORTANT
|
||||||
|
|
||||||
With Comodo SSL (sectigo RSA) certificates you also need to append the intermediate certificate
|
With Comodo SSL (sectigo RSA) certificates you also need to append the intermediate certificate
|
||||||
to the `.crt` file in order to get it to work properly with openssl library based tools like
|
to the `.crt` file to get it to work properly with openssl library based tools like
|
||||||
wget, curl and the go tool, which is quite important if you want to do subdomains on a wildcard
|
wget, curl and the go tool, which is quite important if you want to do subdomains on a wildcard
|
||||||
certificate.
|
certificate.
|
||||||
|
|
||||||
Probably the same applies to some of the other certificate authorities. If you sometimes get
|
Probably the same applies to some of the other certificate authorities. If you sometimes get
|
||||||
issues with CLI tools refusing to accept these certificates on your web server or other, this
|
issues with CLI tools refusing to accept these certificates on your web server or other, this
|
||||||
may be the problem.
|
may be the problem.
|
||||||
|
|
||||||
## example mapping.txt
|
## example mapping.txt
|
||||||
|
|||||||
104
cmd/lerproxy/app/app.go
Normal file
104
cmd/lerproxy/app/app.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/context"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunArgs struct {
|
||||||
|
Addr string `arg:"-l,--listen" default:":https" help:"address to listen at"`
|
||||||
|
Conf string `arg:"-m,--map" default:"mapping.txt" help:"file with host/backend mapping"`
|
||||||
|
Cache string `arg:"-c,--cachedir" default:"/var/cache/letsencrypt" help:"path to directory to cache key and certificates"`
|
||||||
|
HSTS bool `arg:"-h,--hsts" help:"add Strict-Transport-Security header"`
|
||||||
|
Email string `arg:"-e,--email" help:"contact email address presented to letsencrypt CA"`
|
||||||
|
HTTP string `arg:"--http" default:":http" help:"optional address to serve http-to-https redirects and ACME http-01 challenge responses"`
|
||||||
|
RTO time.Duration `arg:"-r,--rto" default:"1m" help:"maximum duration before timing out read of the request"`
|
||||||
|
WTO time.Duration `arg:"-w,--wto" default:"5m" help:"maximum duration before timing out write of the response"`
|
||||||
|
Idle time.Duration `arg:"-i,--idle" help:"how long idle connection is kept before closing (set rto, wto to 0 to use this)"`
|
||||||
|
Certs []string `arg:"--cert,separate" help:"certificates and the domain they match: eg: orly.dev:/path/to/cert - this will indicate to load two, one with extension .key and one with .crt, each expected to be PEM encoded TLS private and public keys, respectively"`
|
||||||
|
// Rewrites string `arg:"-r,--rewrites" default:"rewrites.txt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(c context.T, args RunArgs) (err error) {
|
||||||
|
if args.Cache == "" {
|
||||||
|
err = log.E.Err("no cache specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var srv *http.Server
|
||||||
|
var httpHandler http.Handler
|
||||||
|
if srv, httpHandler, err = SetupServer(args); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv.ReadHeaderTimeout = 5 * time.Second
|
||||||
|
if args.RTO > 0 {
|
||||||
|
srv.ReadTimeout = args.RTO
|
||||||
|
}
|
||||||
|
if args.WTO > 0 {
|
||||||
|
srv.WriteTimeout = args.WTO
|
||||||
|
}
|
||||||
|
group, ctx := errgroup.WithContext(c)
|
||||||
|
if args.HTTP != "" {
|
||||||
|
httpServer := http.Server{
|
||||||
|
Addr: args.HTTP,
|
||||||
|
Handler: httpHandler,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
group.Go(
|
||||||
|
func() (err error) {
|
||||||
|
chk.E(httpServer.ListenAndServe())
|
||||||
|
return
|
||||||
|
},
|
||||||
|
)
|
||||||
|
group.Go(
|
||||||
|
func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
ctx, cancel := context.Timeout(
|
||||||
|
context.Bg(),
|
||||||
|
time.Second,
|
||||||
|
)
|
||||||
|
defer cancel()
|
||||||
|
return httpServer.Shutdown(ctx)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if srv.ReadTimeout != 0 || srv.WriteTimeout != 0 || args.Idle == 0 {
|
||||||
|
group.Go(
|
||||||
|
func() (err error) {
|
||||||
|
chk.E(srv.ListenAndServeTLS("", ""))
|
||||||
|
return
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
group.Go(
|
||||||
|
func() (err error) {
|
||||||
|
var ln net.Listener
|
||||||
|
if ln, err = net.Listen("tcp", srv.Addr); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
ln = Listener{
|
||||||
|
Duration: args.Idle,
|
||||||
|
TCPListener: ln.(*net.TCPListener),
|
||||||
|
}
|
||||||
|
err = srv.ServeTLS(ln, "", "")
|
||||||
|
chk.E(err)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
group.Go(
|
||||||
|
func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
ctx, cancel := context.Timeout(context.Bg(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
return srv.Shutdown(ctx)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return group.Wait()
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// Package buf implements a simple concurrent safe buffer pool for raw bytes.
|
package app
|
||||||
package buf
|
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// Package timeout provides a simple extension of a net.TCPConn with a
|
package app
|
||||||
// configurable read/write deadline.
|
|
||||||
package timeout
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
63
cmd/lerproxy/app/go-vanity.go
Normal file
63
cmd/lerproxy/app/go-vanity.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoVanity configures an HTTP handler for redirecting requests to vanity URLs
|
||||||
|
// based on the provided hostname and backend address.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - hn (string): The hostname associated with the vanity URL.
|
||||||
|
//
|
||||||
|
// - ba (string): The backend address, expected to be in the format
|
||||||
|
// "git+<repository-path>".
|
||||||
|
//
|
||||||
|
// - mux (*http.ServeMux): The HTTP serve multiplexer where the handler will be
|
||||||
|
// registered.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Splits the backend address to extract the repository path from the "git+" prefix.
|
||||||
|
//
|
||||||
|
// - If the split fails, logs an error and returns without registering a handler.
|
||||||
|
//
|
||||||
|
// - Generates an HTML redirect page containing metadata for Go import and
|
||||||
|
// redirects to the extracted repository path.
|
||||||
|
//
|
||||||
|
// - Registers a handler on the provided ServeMux that serves this redirect page
|
||||||
|
// when requests are made to the specified hostname.
|
||||||
|
func GoVanity(hn, ba string, mux *http.ServeMux) {
|
||||||
|
split := strings.Split(ba, "git+")
|
||||||
|
if len(split) != 2 {
|
||||||
|
log.E.Ln("invalid go vanity redirect: %s: %s", hn, ba)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirector := fmt.Sprintf(
|
||||||
|
`<html><head><meta name="go-import" content="%s git %s"/><meta http-equiv = "refresh" content = " 3 ; url = %s"/></head><body>redirecting to <a href="%s">%s</a></body></html>`,
|
||||||
|
hn, split[1], split[1], split[1], split[1],
|
||||||
|
)
|
||||||
|
mux.HandleFunc(
|
||||||
|
hn+"/",
|
||||||
|
func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.Header().Set(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
)
|
||||||
|
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
writer.Header().Set("Content-Type", "text/html")
|
||||||
|
writer.Header().Set(
|
||||||
|
"Content-Length", fmt.Sprint(len(redirector)),
|
||||||
|
)
|
||||||
|
writer.Header().Set(
|
||||||
|
"strict-transport-security",
|
||||||
|
"max-age=0; includeSubDomains",
|
||||||
|
)
|
||||||
|
fmt.Fprint(writer, redirector)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
// Package tcpkeepalive implements a net.TCPListener with a singleton set period
|
package app
|
||||||
// for a default 3 minute keep-aline.
|
|
||||||
package tcpkeepalive
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"orly.dev/cmd/lerproxy/timeout"
|
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Period can be changed prior to opening a Listener to alter its'
|
// Period can be changed before opening a Listener to alter its
|
||||||
// KeepAlivePeriod.
|
// KeepAlivePeriod.
|
||||||
var Period = 3 * time.Minute
|
var Period = 3 * time.Minute
|
||||||
|
|
||||||
// Listener sets TCP keep-alive timeouts on accepted connections.
|
// Listener sets TCP keep-alive timeouts on accepted connections.
|
||||||
// It's used by ListenAndServe and ListenAndServeTLS so dead TCP connections
|
// It is used by ListenAndServe and ListenAndServeTLS so dead TCP connections
|
||||||
// (e.g. closing laptop mid-download) eventually go away.
|
// (e.g. closing laptop mid-download) eventually go away.
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
time.Duration
|
time.Duration
|
||||||
@@ -33,7 +30,7 @@ func (ln Listener) Accept() (conn net.Conn, e error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ln.Duration != 0 {
|
if ln.Duration != 0 {
|
||||||
return timeout.Conn{Duration: ln.Duration, TCPConn: tc}, nil
|
return Conn{Duration: ln.Duration, TCPConn: tc}, nil
|
||||||
}
|
}
|
||||||
return tc, nil
|
return tc, nil
|
||||||
}
|
}
|
||||||
80
cmd/lerproxy/app/nostr-dns.go
Normal file
80
cmd/lerproxy/app/nostr-dns.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NostrJSON struct {
|
||||||
|
Names map[string]string `json:"names"`
|
||||||
|
Relays map[string][]string `json:"relays"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NostrDNS handles the configuration and registration of a Nostr DNS endpoint
|
||||||
|
// for a given hostname and backend address.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - hn (string): The hostname for which the Nostr DNS entry is being configured.
|
||||||
|
//
|
||||||
|
// - ba (string): The path to the JSON file containing the Nostr DNS data.
|
||||||
|
//
|
||||||
|
// - mux (*http.ServeMux): The HTTP serve multiplexer to which the Nostr DNS
|
||||||
|
// handler will be registered.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - err (error): An error if any step fails during the configuration or
|
||||||
|
// registration process.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Reads the JSON file specified by `ba` and parses its contents into a
|
||||||
|
// NostrJSON struct.
|
||||||
|
//
|
||||||
|
// - Registers a new HTTP handler on the provided `mux` for the
|
||||||
|
// `.well-known/nostr.json` endpoint under the specified hostname.
|
||||||
|
//
|
||||||
|
// - The handler serves the parsed Nostr DNS data with appropriate HTTP headers
|
||||||
|
// set for CORS and content type.
|
||||||
|
func NostrDNS(hn, ba string, mux *http.ServeMux) (err error) {
|
||||||
|
log.T.Ln(hn, ba)
|
||||||
|
var fb []byte
|
||||||
|
if fb, err = os.ReadFile(ba); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var v NostrJSON
|
||||||
|
if err = json.Unmarshal(fb, &v); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var jb []byte
|
||||||
|
if jb, err = json.Marshal(v); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nostrJSON := string(jb)
|
||||||
|
mux.HandleFunc(
|
||||||
|
hn+"/.well-known/nostr.json",
|
||||||
|
func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
log.T.Ln("serving nostr json to", hn)
|
||||||
|
writer.Header().Set(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
)
|
||||||
|
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
writer.Header().Set(
|
||||||
|
"Content-Length", fmt.Sprint(len(nostrJSON)),
|
||||||
|
)
|
||||||
|
writer.Header().Set(
|
||||||
|
"strict-transport-security",
|
||||||
|
"max-age=0; includeSubDomains",
|
||||||
|
)
|
||||||
|
fmt.Fprint(writer, nostrJSON)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
15
cmd/lerproxy/app/proxy.go
Normal file
15
cmd/lerproxy/app/proxy.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set(
|
||||||
|
"Strict-Transport-Security",
|
||||||
|
"max-age=31536000; includeSubDomains; preload",
|
||||||
|
)
|
||||||
|
p.Handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
62
cmd/lerproxy/app/read-mapping.go
Normal file
62
cmd/lerproxy/app/read-mapping.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadMapping reads a mapping file and returns a map of hostnames to backend
|
||||||
|
// addresses.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - file (string): The path to the mapping file to read.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - m (map[string]string): A map containing the hostname to backend address
|
||||||
|
// mappings parsed from the file.
|
||||||
|
//
|
||||||
|
// - err (error): An error if any step during reading or parsing fails.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Opens the specified file and reads its contents line by line.
|
||||||
|
//
|
||||||
|
// - Skips lines that are empty or start with a '#'.
|
||||||
|
//
|
||||||
|
// - Splits each valid line into two parts using the first colon as the
|
||||||
|
// separator.
|
||||||
|
//
|
||||||
|
// - Trims whitespace from both parts and adds them to the map.
|
||||||
|
//
|
||||||
|
// - Returns any error encountered during file operations or parsing.
|
||||||
|
func ReadMapping(file string) (m map[string]string, err error) {
|
||||||
|
var f *os.File
|
||||||
|
if f, err = os.Open(file); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m = make(map[string]string)
|
||||||
|
sc := bufio.NewScanner(f)
|
||||||
|
for sc.Scan() {
|
||||||
|
if b := sc.Bytes(); len(b) == 0 || b[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := strings.SplitN(sc.Text(), ":", 2)
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = fmt.Errorf("invalid line: %q", sc.Text())
|
||||||
|
log.E.Ln(err)
|
||||||
|
chk.E(f.Close())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m[strings.TrimSpace(s[0])] = strings.TrimSpace(s[1])
|
||||||
|
}
|
||||||
|
err = sc.Err()
|
||||||
|
chk.E(err)
|
||||||
|
chk.E(f.Close())
|
||||||
|
return
|
||||||
|
}
|
||||||
63
cmd/lerproxy/app/reverse.go
Normal file
63
cmd/lerproxy/app/reverse.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"orly.dev/cmd/lerproxy/utils"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSingleHostReverseProxy is a copy of httputil.NewSingleHostReverseProxy
|
||||||
|
// with the addition of forwarding headers:
|
||||||
|
//
|
||||||
|
// - Legacy X-Forwarded-* headers (X-Forwarded-Proto, X-Forwarded-For,
|
||||||
|
// X-Forwarded-Host)
|
||||||
|
//
|
||||||
|
// - Standardized Forwarded header according to RFC 7239
|
||||||
|
// (https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Forwarded)
|
||||||
|
func NewSingleHostReverseProxy(target *url.URL) (rp *httputil.ReverseProxy) {
|
||||||
|
targetQuery := target.RawQuery
|
||||||
|
director := func(req *http.Request) {
|
||||||
|
log.D.S(req)
|
||||||
|
req.URL.Scheme = target.Scheme
|
||||||
|
req.URL.Host = target.Host
|
||||||
|
req.URL.Path = utils.SingleJoiningSlash(target.Path, req.URL.Path)
|
||||||
|
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||||
|
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||||
|
} else {
|
||||||
|
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||||
|
}
|
||||||
|
if _, ok := req.Header["User-Agent"]; !ok {
|
||||||
|
req.Header.Set("User-Agent", "")
|
||||||
|
}
|
||||||
|
// Set X-Forwarded-* headers for backward compatibility
|
||||||
|
req.Header.Set("X-Forwarded-Proto", "https")
|
||||||
|
// Get client IP address
|
||||||
|
clientIP := req.RemoteAddr
|
||||||
|
if fwdFor := req.Header.Get("X-Forwarded-For"); fwdFor != "" {
|
||||||
|
clientIP = fwdFor + ", " + clientIP
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Forwarded-For", clientIP)
|
||||||
|
// Set X-Forwarded-Host if not already set
|
||||||
|
if _, exists := req.Header["X-Forwarded-Host"]; !exists {
|
||||||
|
req.Header.Set("X-Forwarded-Host", req.Host)
|
||||||
|
}
|
||||||
|
// Set standardized Forwarded header according to RFC 7239
|
||||||
|
// Format: Forwarded: by=<identifier>;for=<identifier>;host=<host>;proto=<http|https>
|
||||||
|
forwardedProto := "https"
|
||||||
|
forwardedHost := req.Host
|
||||||
|
forwardedFor := clientIP
|
||||||
|
// Build the Forwarded header value
|
||||||
|
forwardedHeader := "proto=" + forwardedProto
|
||||||
|
if forwardedFor != "" {
|
||||||
|
forwardedHeader += ";for=" + forwardedFor
|
||||||
|
}
|
||||||
|
if forwardedHost != "" {
|
||||||
|
forwardedHeader += ";host=" + forwardedHost
|
||||||
|
}
|
||||||
|
req.Header.Set("Forwarded", forwardedHeader)
|
||||||
|
}
|
||||||
|
rp = &httputil.ReverseProxy{Director: director}
|
||||||
|
return
|
||||||
|
}
|
||||||
124
cmd/lerproxy/app/set-proxy.go
Normal file
124
cmd/lerproxy/app/set-proxy.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
log2 "log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"orly.dev/pkg/utils/context"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetProxy creates an HTTP handler that routes incoming requests to specified
|
||||||
|
// backend addresses based on hostname mappings.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - mapping (map[string]string): A map where keys are hostnames and values are
|
||||||
|
// the corresponding backend addresses.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - h (http.Handler): The HTTP handler configured with the proxy settings.
|
||||||
|
// - err (error): An error if the mapping is empty or invalid.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Validates that the provided hostname to backend address mapping is not empty.
|
||||||
|
//
|
||||||
|
// - Creates a new ServeMux and configures it to route requests based on the
|
||||||
|
// specified hostnames and backend addresses.
|
||||||
|
//
|
||||||
|
// - Handles special cases such as vanity URLs, Nostr DNS entries, and Unix
|
||||||
|
// socket connections.
|
||||||
|
func SetProxy(mapping map[string]string) (h http.Handler, err error) {
|
||||||
|
if len(mapping) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty mapping")
|
||||||
|
}
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
for hostname, backendAddr := range mapping {
|
||||||
|
hn, ba := hostname, backendAddr
|
||||||
|
if strings.ContainsRune(hn, os.PathSeparator) {
|
||||||
|
err = log.E.Err("invalid hostname: %q", hn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
network := "tcp"
|
||||||
|
if ba != "" && ba[0] == '@' && runtime.GOOS == "linux" {
|
||||||
|
// append \0 to address so addrlen for connect(2) is calculated in a
|
||||||
|
// way compatible with some other implementations (i.e. uwsgi)
|
||||||
|
network, ba = "unix", ba+string(byte(0))
|
||||||
|
} else if strings.HasPrefix(ba, "git+") {
|
||||||
|
GoVanity(hn, ba, mux)
|
||||||
|
continue
|
||||||
|
} else if filepath.IsAbs(ba) {
|
||||||
|
network = "unix"
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(ba, string(os.PathSeparator)):
|
||||||
|
// path specified as directory with explicit trailing slash; add
|
||||||
|
// this path as static site
|
||||||
|
fs := http.FileServer(http.Dir(ba))
|
||||||
|
mux.Handle(hn+"/", fs)
|
||||||
|
continue
|
||||||
|
case strings.HasSuffix(ba, "nostr.json"):
|
||||||
|
if err = NostrDNS(hn, ba, mux); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if u, err := url.Parse(ba); err == nil {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "http", "https":
|
||||||
|
rp := NewSingleHostReverseProxy(u)
|
||||||
|
modifyCORSResponse := func(res *http.Response) error {
|
||||||
|
res.Header.Set(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
)
|
||||||
|
// res.Header.Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
res.Header.Set("Access-Control-Allow-Origin", "*")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rp.ModifyResponse = modifyCORSResponse
|
||||||
|
rp.ErrorLog = log2.New(
|
||||||
|
os.Stderr, "lerproxy", log2.Llongfile,
|
||||||
|
)
|
||||||
|
rp.BufferPool = Pool{}
|
||||||
|
mux.Handle(hn+"/", rp)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rp := &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = req.Host
|
||||||
|
req.Header.Set("X-Forwarded-Proto", "https")
|
||||||
|
req.Header.Set("X-Forwarded-For", req.RemoteAddr)
|
||||||
|
req.Header.Set(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
)
|
||||||
|
req.Header.Set("Access-Control-Allow-Origin", "*")
|
||||||
|
log.D.Ln(req.URL, req.RemoteAddr)
|
||||||
|
},
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: func(c context.T, n, addr string) (
|
||||||
|
net.Conn, error,
|
||||||
|
) {
|
||||||
|
return net.DialTimeout(network, ba, 5*time.Second)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ErrorLog: log2.New(io.Discard, "", 0),
|
||||||
|
BufferPool: Pool{},
|
||||||
|
}
|
||||||
|
mux.Handle(hn+"/", rp)
|
||||||
|
}
|
||||||
|
return mux, nil
|
||||||
|
}
|
||||||
81
cmd/lerproxy/app/setup-server.go
Normal file
81
cmd/lerproxy/app/setup-server.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"net/http"
|
||||||
|
"orly.dev/cmd/lerproxy/utils"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupServer configures and returns an HTTP server instance with proxy
|
||||||
|
// handling and automatic certificate management based on the provided RunArgs
|
||||||
|
// configuration.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - a (RunArgs): The configuration arguments containing settings for the server
|
||||||
|
// address, cache directory, mapping file, HSTS header, email, and certificates.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - s (*http.Server): The configured HTTP server instance.
|
||||||
|
//
|
||||||
|
// - h (http.Handler): The HTTP handler used for proxying requests and managing
|
||||||
|
// automatic certificate challenges.
|
||||||
|
//
|
||||||
|
// - err (error): An error if any step during setup fails.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Reads the hostname to backend address mapping from the specified
|
||||||
|
// configuration file.
|
||||||
|
//
|
||||||
|
// - Sets up a proxy handler that routes incoming requests based on the defined
|
||||||
|
// mappings.
|
||||||
|
//
|
||||||
|
// - Enables HSTS header support if enabled in the RunArgs.
|
||||||
|
//
|
||||||
|
// - Creates the cache directory for storing certificates and keys if it does not
|
||||||
|
// already exist.
|
||||||
|
//
|
||||||
|
// - Configures an autocert.Manager to handle automatic certificate management,
|
||||||
|
// including hostname whitelisting, email contact, and cache storage.
|
||||||
|
//
|
||||||
|
// - Initializes the HTTP server with proxy handler, address, and TLS
|
||||||
|
// configuration.
|
||||||
|
func SetupServer(a RunArgs) (s *http.Server, h http.Handler, err error) {
|
||||||
|
var mapping map[string]string
|
||||||
|
if mapping, err = ReadMapping(a.Conf); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var proxy http.Handler
|
||||||
|
if proxy, err = SetProxy(mapping); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if a.HSTS {
|
||||||
|
proxy = &Proxy{Handler: proxy}
|
||||||
|
}
|
||||||
|
if err = os.MkdirAll(a.Cache, 0700); chk.E(err) {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"cannot create cache directory %q: %v",
|
||||||
|
a.Cache, err,
|
||||||
|
)
|
||||||
|
chk.E(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
Cache: autocert.DirCache(a.Cache),
|
||||||
|
HostPolicy: autocert.HostWhitelist(utils.GetKeys(mapping)...),
|
||||||
|
Email: a.Email,
|
||||||
|
}
|
||||||
|
s = &http.Server{
|
||||||
|
Handler: proxy,
|
||||||
|
Addr: a.Addr,
|
||||||
|
TLSConfig: TLSConfig(&m, a.Certs...),
|
||||||
|
}
|
||||||
|
h = m.HTTPHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
87
cmd/lerproxy/app/tls-config.go
Normal file
87
cmd/lerproxy/app/tls-config.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSConfig creates a custom TLS configuration that combines automatic
|
||||||
|
// certificate management with explicitly provided certificates.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - m (*autocert.Manager): The autocert manager used for managing automatic
|
||||||
|
// certificate generation and retrieval.
|
||||||
|
//
|
||||||
|
// - certs (...string): A variadic list of certificate definitions in the format
|
||||||
|
// "domain:/path/to/cert", where each domain maps to a certificate file. The
|
||||||
|
// corresponding key file is expected to be at "/path/to/cert.key".
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - tc (*tls.Config): A new TLS configuration that prioritises explicitly
|
||||||
|
// provided certificates over automatically generated ones.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Loads all explicitly provided certificates and maps them to their
|
||||||
|
// respective domains.
|
||||||
|
//
|
||||||
|
// - Creates a custom GetCertificate function that checks if the requested
|
||||||
|
// domain matches any of the explicitly provided certificates, returning those
|
||||||
|
// first.
|
||||||
|
//
|
||||||
|
// - Falls back to the autocert manager's GetCertificate method if no explicit
|
||||||
|
// certificate is found for the requested domain.
|
||||||
|
func TLSConfig(m *autocert.Manager, certs ...string) (tc *tls.Config) {
|
||||||
|
certMap := make(map[string]*tls.Certificate)
|
||||||
|
var mx sync.Mutex
|
||||||
|
for _, cert := range certs {
|
||||||
|
split := strings.Split(cert, ":")
|
||||||
|
if len(split) != 2 {
|
||||||
|
log.E.F("invalid certificate parameter format: `%s`", cert)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var c tls.Certificate
|
||||||
|
if c, err = tls.LoadX509KeyPair(
|
||||||
|
split[1]+".crt", split[1]+".key",
|
||||||
|
); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
certMap[split[0]] = &c
|
||||||
|
}
|
||||||
|
tc = m.TLSConfig()
|
||||||
|
tc.GetCertificate = func(helo *tls.ClientHelloInfo) (
|
||||||
|
cert *tls.Certificate, err error,
|
||||||
|
) {
|
||||||
|
mx.Lock()
|
||||||
|
var own string
|
||||||
|
for i := range certMap {
|
||||||
|
// to also handle explicit subdomain certs, prioritize over a root
|
||||||
|
// wildcard.
|
||||||
|
if helo.ServerName == i {
|
||||||
|
own = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if it got to us and ends in the same-name dot tld assume the
|
||||||
|
// subdomain was redirected, or it is a wildcard certificate; thus
|
||||||
|
// only the ending needs to match.
|
||||||
|
if strings.HasSuffix(helo.ServerName, i) {
|
||||||
|
own = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if own != "" {
|
||||||
|
defer mx.Unlock()
|
||||||
|
return certMap[own], nil
|
||||||
|
}
|
||||||
|
mx.Unlock()
|
||||||
|
return m.GetCertificate(helo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Package hsts implements a HTTP handler that enforces HSTS.
|
|
||||||
package hsts
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type Proxy struct {
|
|
||||||
http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().
|
|
||||||
Set("Strict-Transport-Security",
|
|
||||||
"max-age=31536000; includeSubDomains; preload")
|
|
||||||
p.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
16
cmd/lerproxy/lerproxy.service
Normal file
16
cmd/lerproxy/lerproxy.service
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# systemd unit to run lerproxy as a service
|
||||||
|
[Unit]
|
||||||
|
Description=lerproxy
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=mleku
|
||||||
|
ExecStart=/home/mleku/.local/bin/lerproxy -m /home/mleku/mapping.txt
|
||||||
|
Restart=always
|
||||||
|
Wants=network-online.target
|
||||||
|
# waits for wireguard service to come up before starting, remove the wg-quick@wg0 section if running it directly on an
|
||||||
|
# internet routeable connection
|
||||||
|
After=network.target network-online.target wg-quick@wg0.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -1,402 +1,23 @@
|
|||||||
// Command lerproxy implements https reverse proxy with automatic LetsEncrypt
|
|
||||||
// usage for multiple hostnames/backends,your own SSL certificates, nostr NIP-05
|
|
||||||
// DNS verification hosting and Go vanity redirects.
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"orly.dev/cmd/lerproxy/app"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
stdLog "log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"orly.dev/cmd/lerproxy/buf"
|
|
||||||
"orly.dev/cmd/lerproxy/hsts"
|
|
||||||
"orly.dev/cmd/lerproxy/reverse"
|
|
||||||
"orly.dev/cmd/lerproxy/tcpkeepalive"
|
|
||||||
"orly.dev/cmd/lerproxy/util"
|
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"orly.dev/pkg/utils/context"
|
"orly.dev/pkg/utils/context"
|
||||||
"orly.dev/pkg/utils/log"
|
"orly.dev/pkg/utils/log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alexflint/go-arg"
|
"github.com/alexflint/go-arg"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type runArgs struct {
|
var args app.RunArgs
|
||||||
Addr string `arg:"-l,--listen" default:":https" help:"address to listen at"`
|
|
||||||
Conf string `arg:"-m,--map" default:"mapping.txt" help:"file with host/backend mapping"`
|
|
||||||
Cache string `arg:"-c,--cachedir" default:"/var/cache/letsencrypt" help:"path to directory to cache key and certificates"`
|
|
||||||
HSTS bool `arg:"-h,--hsts" help:"add Strict-Transport-Security header"`
|
|
||||||
Email string `arg:"-e,--email" help:"contact email address presented to letsencrypt CA"`
|
|
||||||
HTTP string `arg:"--http" default:":http" help:"optional address to serve http-to-https redirects and ACME http-01 challenge responses"`
|
|
||||||
RTO time.Duration `arg:"-r,--rto" default:"1m" help:"maximum duration before timing out read of the request"`
|
|
||||||
WTO time.Duration `arg:"-w,--wto" default:"5m" help:"maximum duration before timing out write of the response"`
|
|
||||||
Idle time.Duration `arg:"-i,--idle" help:"how long idle connection is kept before closing (set rto, wto to 0 to use this)"`
|
|
||||||
Certs []string `arg:"--cert,separate" help:"certificates and the domain they match: eg: orly.dev:/path/to/cert - this will indicate to load two, one with extension .key and one with .crt, each expected to be PEM encoded TLS private and public keys, respectively"`
|
|
||||||
// Rewrites string `arg:"-r,--rewrites" default:"rewrites.txt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var args runArgs
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
arg.MustParse(&args)
|
arg.MustParse(&args)
|
||||||
ctx, cancel := signal.NotifyContext(context.Bg(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Bg(), os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := run(ctx, args); chk.T(err) {
|
if err := app.Run(ctx, args); chk.T(err) {
|
||||||
log.F.Ln(err)
|
log.F.Ln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(c context.T, args runArgs) (err error) {
|
|
||||||
|
|
||||||
if args.Cache == "" {
|
|
||||||
err = log.E.Err("no cache specified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var srv *http.Server
|
|
||||||
var httpHandler http.Handler
|
|
||||||
if srv, httpHandler, err = setupServer(args); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srv.ReadHeaderTimeout = 5 * time.Second
|
|
||||||
if args.RTO > 0 {
|
|
||||||
srv.ReadTimeout = args.RTO
|
|
||||||
}
|
|
||||||
if args.WTO > 0 {
|
|
||||||
srv.WriteTimeout = args.WTO
|
|
||||||
}
|
|
||||||
group, ctx := errgroup.WithContext(c)
|
|
||||||
if args.HTTP != "" {
|
|
||||||
httpServer := http.Server{
|
|
||||||
Addr: args.HTTP,
|
|
||||||
Handler: httpHandler,
|
|
||||||
ReadTimeout: 10 * time.Second,
|
|
||||||
WriteTimeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
group.Go(
|
|
||||||
func() (err error) {
|
|
||||||
chk.E(httpServer.ListenAndServe())
|
|
||||||
return
|
|
||||||
},
|
|
||||||
)
|
|
||||||
group.Go(
|
|
||||||
func() error {
|
|
||||||
<-ctx.Done()
|
|
||||||
ctx, cancel := context.Timeout(
|
|
||||||
context.Bg(),
|
|
||||||
time.Second,
|
|
||||||
)
|
|
||||||
defer cancel()
|
|
||||||
return httpServer.Shutdown(ctx)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if srv.ReadTimeout != 0 || srv.WriteTimeout != 0 || args.Idle == 0 {
|
|
||||||
group.Go(
|
|
||||||
func() (err error) {
|
|
||||||
chk.E(srv.ListenAndServeTLS("", ""))
|
|
||||||
return
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
group.Go(
|
|
||||||
func() (err error) {
|
|
||||||
var ln net.Listener
|
|
||||||
if ln, err = net.Listen("tcp", srv.Addr); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer ln.Close()
|
|
||||||
ln = tcpkeepalive.Listener{
|
|
||||||
Duration: args.Idle,
|
|
||||||
TCPListener: ln.(*net.TCPListener),
|
|
||||||
}
|
|
||||||
err = srv.ServeTLS(ln, "", "")
|
|
||||||
chk.E(err)
|
|
||||||
return
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
group.Go(
|
|
||||||
func() error {
|
|
||||||
<-ctx.Done()
|
|
||||||
ctx, cancel := context.Timeout(context.Bg(), time.Second)
|
|
||||||
defer cancel()
|
|
||||||
return srv.Shutdown(ctx)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return group.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSConfig returns a TLSConfig that works with a LetsEncrypt automatic SSL cert issuer as well
|
|
||||||
// as any provided .pem certificates from providers.
|
|
||||||
//
|
|
||||||
// The certs are provided in the form "example.com:/path/to/cert.pem"
|
|
||||||
func TLSConfig(m *autocert.Manager, certs ...string) (tc *tls.Config) {
|
|
||||||
certMap := make(map[string]*tls.Certificate)
|
|
||||||
var mx sync.Mutex
|
|
||||||
for _, cert := range certs {
|
|
||||||
split := strings.Split(cert, ":")
|
|
||||||
if len(split) != 2 {
|
|
||||||
log.E.F("invalid certificate parameter format: `%s`", cert)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var c tls.Certificate
|
|
||||||
if c, err = tls.LoadX509KeyPair(
|
|
||||||
split[1]+".crt", split[1]+".key",
|
|
||||||
); chk.E(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
certMap[split[0]] = &c
|
|
||||||
}
|
|
||||||
tc = m.TLSConfig()
|
|
||||||
tc.GetCertificate = func(helo *tls.ClientHelloInfo) (
|
|
||||||
cert *tls.Certificate, err error,
|
|
||||||
) {
|
|
||||||
mx.Lock()
|
|
||||||
var own string
|
|
||||||
for i := range certMap {
|
|
||||||
// to also handle explicit subdomain certs, prioritize over a root wildcard.
|
|
||||||
if helo.ServerName == i {
|
|
||||||
own = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// if it got to us and ends in the same name dot tld assume the subdomain was
|
|
||||||
// redirected or it's a wildcard certificate, thus only the ending needs to match.
|
|
||||||
if strings.HasSuffix(helo.ServerName, i) {
|
|
||||||
own = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if own != "" {
|
|
||||||
defer mx.Unlock()
|
|
||||||
return certMap[own], nil
|
|
||||||
}
|
|
||||||
mx.Unlock()
|
|
||||||
return m.GetCertificate(helo)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupServer(a runArgs) (s *http.Server, h http.Handler, err error) {
|
|
||||||
var mapping map[string]string
|
|
||||||
if mapping, err = readMapping(a.Conf); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var proxy http.Handler
|
|
||||||
if proxy, err = setProxy(mapping); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if a.HSTS {
|
|
||||||
proxy = &hsts.Proxy{Handler: proxy}
|
|
||||||
}
|
|
||||||
if err = os.MkdirAll(a.Cache, 0700); chk.E(err) {
|
|
||||||
err = fmt.Errorf(
|
|
||||||
"cannot create cache directory %q: %v",
|
|
||||||
a.Cache, err,
|
|
||||||
)
|
|
||||||
chk.E(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m := autocert.Manager{
|
|
||||||
Prompt: autocert.AcceptTOS,
|
|
||||||
Cache: autocert.DirCache(a.Cache),
|
|
||||||
HostPolicy: autocert.HostWhitelist(util.GetKeys(mapping)...),
|
|
||||||
Email: a.Email,
|
|
||||||
}
|
|
||||||
s = &http.Server{
|
|
||||||
Handler: proxy,
|
|
||||||
Addr: a.Addr,
|
|
||||||
TLSConfig: TLSConfig(&m, a.Certs...),
|
|
||||||
}
|
|
||||||
h = m.HTTPHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type NostrJSON struct {
|
|
||||||
Names map[string]string `json:"names"`
|
|
||||||
Relays map[string][]string `json:"relays"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func setProxy(mapping map[string]string) (h http.Handler, err error) {
|
|
||||||
if len(mapping) == 0 {
|
|
||||||
return nil, fmt.Errorf("empty mapping")
|
|
||||||
}
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
for hostname, backendAddr := range mapping {
|
|
||||||
hn, ba := hostname, backendAddr
|
|
||||||
if strings.ContainsRune(hn, os.PathSeparator) {
|
|
||||||
err = log.E.Err("invalid hostname: %q", hn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
network := "tcp"
|
|
||||||
if ba != "" && ba[0] == '@' && runtime.GOOS == "linux" {
|
|
||||||
// append \0 to address so addrlen for connect(2) is calculated in a
|
|
||||||
// way compatible with some other implementations (i.e. uwsgi)
|
|
||||||
network, ba = "unix", ba+string(byte(0))
|
|
||||||
} else if strings.HasPrefix(ba, "git+") {
|
|
||||||
split := strings.Split(ba, "git+")
|
|
||||||
if len(split) != 2 {
|
|
||||||
log.E.Ln("invalid go vanity redirect: %s: %s", hn, ba)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
redirector := fmt.Sprintf(
|
|
||||||
`<html><head><meta name="go-import" content="%s git %s"/><meta http-equiv = "refresh" content = " 3 ; url = %s"/></head><body>redirecting to <a href="%s">%s</a></body></html>`,
|
|
||||||
hn, split[1], split[1], split[1], split[1],
|
|
||||||
)
|
|
||||||
mux.HandleFunc(
|
|
||||||
hn+"/",
|
|
||||||
func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
writer.Header().Set(
|
|
||||||
"Access-Control-Allow-Methods",
|
|
||||||
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
||||||
)
|
|
||||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
writer.Header().Set("Content-Type", "text/html")
|
|
||||||
writer.Header().Set(
|
|
||||||
"Content-Length", fmt.Sprint(len(redirector)),
|
|
||||||
)
|
|
||||||
writer.Header().Set(
|
|
||||||
"strict-transport-security",
|
|
||||||
"max-age=0; includeSubDomains",
|
|
||||||
)
|
|
||||||
fmt.Fprint(writer, redirector)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
} else if filepath.IsAbs(ba) {
|
|
||||||
network = "unix"
|
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(ba, string(os.PathSeparator)):
|
|
||||||
// path specified as directory with explicit trailing slash; add
|
|
||||||
// this path as static site
|
|
||||||
fs := http.FileServer(http.Dir(ba))
|
|
||||||
mux.Handle(hn+"/", fs)
|
|
||||||
continue
|
|
||||||
case strings.HasSuffix(ba, "nostr.json"):
|
|
||||||
log.I.Ln(hn, ba)
|
|
||||||
var fb []byte
|
|
||||||
if fb, err = os.ReadFile(ba); chk.E(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var v NostrJSON
|
|
||||||
if err = json.Unmarshal(fb, &v); chk.E(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var jb []byte
|
|
||||||
if jb, err = json.Marshal(v); chk.E(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nostrJSON := string(jb)
|
|
||||||
mux.HandleFunc(
|
|
||||||
hn+"/.well-known/nostr.json",
|
|
||||||
func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
log.I.Ln("serving nostr json to", hn)
|
|
||||||
writer.Header().Set(
|
|
||||||
"Access-Control-Allow-Methods",
|
|
||||||
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
||||||
)
|
|
||||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
writer.Header().Set("Content-Type", "application/json")
|
|
||||||
writer.Header().Set(
|
|
||||||
"Content-Length", fmt.Sprint(len(nostrJSON)),
|
|
||||||
)
|
|
||||||
writer.Header().Set(
|
|
||||||
"strict-transport-security",
|
|
||||||
"max-age=0; includeSubDomains",
|
|
||||||
)
|
|
||||||
fmt.Fprint(writer, nostrJSON)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if u, err := url.Parse(ba); err == nil {
|
|
||||||
switch u.Scheme {
|
|
||||||
case "http", "https":
|
|
||||||
rp := reverse.NewSingleHostReverseProxy(u)
|
|
||||||
modifyCORSResponse := func(res *http.Response) error {
|
|
||||||
res.Header.Set(
|
|
||||||
"Access-Control-Allow-Methods",
|
|
||||||
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
||||||
)
|
|
||||||
// res.Header.Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
res.Header.Set("Access-Control-Allow-Origin", "*")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rp.ModifyResponse = modifyCORSResponse
|
|
||||||
rp.ErrorLog = stdLog.New(
|
|
||||||
os.Stderr, "lerproxy", stdLog.Llongfile,
|
|
||||||
)
|
|
||||||
rp.BufferPool = buf.Pool{}
|
|
||||||
mux.Handle(hn+"/", rp)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rp := &httputil.ReverseProxy{
|
|
||||||
Director: func(req *http.Request) {
|
|
||||||
req.URL.Scheme = "http"
|
|
||||||
req.URL.Host = req.Host
|
|
||||||
req.Header.Set("X-Forwarded-Proto", "https")
|
|
||||||
req.Header.Set("X-Forwarded-For", req.RemoteAddr)
|
|
||||||
req.Header.Set(
|
|
||||||
"Access-Control-Allow-Methods",
|
|
||||||
"GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
||||||
)
|
|
||||||
// req.Header.Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
req.Header.Set("Access-Control-Allow-Origin", "*")
|
|
||||||
log.D.Ln(req.URL, req.RemoteAddr)
|
|
||||||
},
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: func(c context.T, n, addr string) (
|
|
||||||
net.Conn, error,
|
|
||||||
) {
|
|
||||||
return net.DialTimeout(network, ba, 5*time.Second)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ErrorLog: stdLog.New(io.Discard, "", 0),
|
|
||||||
BufferPool: buf.Pool{},
|
|
||||||
}
|
|
||||||
mux.Handle(hn+"/", rp)
|
|
||||||
}
|
|
||||||
return mux, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMapping(file string) (m map[string]string, err error) {
|
|
||||||
var f *os.File
|
|
||||||
if f, err = os.Open(file); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m = make(map[string]string)
|
|
||||||
sc := bufio.NewScanner(f)
|
|
||||||
for sc.Scan() {
|
|
||||||
if b := sc.Bytes(); len(b) == 0 || b[0] == '#' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s := strings.SplitN(sc.Text(), ":", 2)
|
|
||||||
if len(s) != 2 {
|
|
||||||
err = fmt.Errorf("invalid line: %q", sc.Text())
|
|
||||||
log.E.Ln(err)
|
|
||||||
chk.E(f.Close())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m[strings.TrimSpace(s[0])] = strings.TrimSpace(s[1])
|
|
||||||
}
|
|
||||||
err = sc.Err()
|
|
||||||
chk.E(err)
|
|
||||||
chk.E(f.Close())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
// Package reverse is a copy of httputil.NewSingleHostReverseProxy with addition
|
|
||||||
// of "X-Forwarded-Proto" header.
|
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"orly.dev/cmd/lerproxy/util"
|
|
||||||
"orly.dev/pkg/utils/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewSingleHostReverseProxy is a copy of httputil.NewSingleHostReverseProxy
|
|
||||||
// with addition of "X-Forwarded-Proto" header.
|
|
||||||
func NewSingleHostReverseProxy(target *url.URL) (rp *httputil.ReverseProxy) {
|
|
||||||
targetQuery := target.RawQuery
|
|
||||||
director := func(req *http.Request) {
|
|
||||||
log.D.S(req)
|
|
||||||
req.URL.Scheme = target.Scheme
|
|
||||||
req.URL.Host = target.Host
|
|
||||||
req.URL.Path = util.SingleJoiningSlash(target.Path, req.URL.Path)
|
|
||||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
|
||||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
|
||||||
} else {
|
|
||||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
|
||||||
}
|
|
||||||
if _, ok := req.Header["User-Agent"]; !ok {
|
|
||||||
req.Header.Set("User-Agent", "")
|
|
||||||
}
|
|
||||||
req.Header.Set("X-Forwarded-Proto", "https")
|
|
||||||
}
|
|
||||||
rp = &httputil.ReverseProxy{Director: director}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Package util provides some helpers for lerproxy, a tool to convert maps of
|
|
||||||
// strings to slices of the same strings, and a helper to avoid putting two / in
|
|
||||||
// a URL.
|
|
||||||
package util
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func GetKeys(m map[string]string) []string {
|
|
||||||
out := make([]string, 0, len(m))
|
|
||||||
for k := range m {
|
|
||||||
out = append(out, k)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func SingleJoiningSlash(a, b string) string {
|
|
||||||
suffixSlash := strings.HasSuffix(a, "/")
|
|
||||||
prefixSlash := strings.HasPrefix(b, "/")
|
|
||||||
switch {
|
|
||||||
case suffixSlash && prefixSlash:
|
|
||||||
return a + b[1:]
|
|
||||||
case !suffixSlash && !prefixSlash:
|
|
||||||
return a + "/" + b
|
|
||||||
}
|
|
||||||
return a + b
|
|
||||||
}
|
|
||||||
62
cmd/lerproxy/utils/utils.go
Normal file
62
cmd/lerproxy/utils/utils.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// GetKeys returns a slice containing all the keys from the provided map.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - m (map[string]string): The input map from which to extract keys.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - []string: A slice of strings representing the keys in the map.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - Iterates over each key in the map and appends it to a new slice.
|
||||||
|
//
|
||||||
|
// - Returns the slice containing all the keys.
|
||||||
|
func GetKeys(m map[string]string) []string {
|
||||||
|
out := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
out = append(out, k)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// SingleJoiningSlash joins two strings with a single slash between them,
|
||||||
|
// ensuring that the resulting path doesn't contain multiple consecutive
|
||||||
|
// slashes.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - a (string): The first string to join.
|
||||||
|
//
|
||||||
|
// - b (string): The second string to join.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - result (string): The joined string with a single slash between them if
|
||||||
|
// needed.
|
||||||
|
//
|
||||||
|
// # Expected behaviour
|
||||||
|
//
|
||||||
|
// - If both a and b start and end with a slash, the resulting string will have
|
||||||
|
// only one slash between them.
|
||||||
|
//
|
||||||
|
// - If neither a nor b starts or ends with a slash, the strings will be joined
|
||||||
|
// with a single slash in between.
|
||||||
|
//
|
||||||
|
// - Otherwise, the two strings are simply concatenated.
|
||||||
|
func SingleJoiningSlash(a, b string) string {
|
||||||
|
suffixSlash := strings.HasSuffix(a, "/")
|
||||||
|
prefixSlash := strings.HasPrefix(b, "/")
|
||||||
|
switch {
|
||||||
|
case suffixSlash && prefixSlash:
|
||||||
|
return a + b[1:]
|
||||||
|
case !suffixSlash && !prefixSlash:
|
||||||
|
return a + "/" + b
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
@@ -190,6 +190,5 @@ func Post(f string, ur *url.URL, sign signer.I) (err error) {
|
|||||||
if io.Copy(os.Stdout, res.Body); chk.E(err) {
|
if io.Copy(os.Stdout, res.Body); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"orly.dev/pkg/crypto/ec/bech32"
|
"orly.dev/pkg/crypto/ec/bech32"
|
||||||
"orly.dev/pkg/crypto/ec/schnorr"
|
|
||||||
"orly.dev/pkg/crypto/ec/secp256k1"
|
"orly.dev/pkg/crypto/ec/secp256k1"
|
||||||
|
"orly.dev/pkg/crypto/p256k"
|
||||||
"orly.dev/pkg/encoders/bech32encoding"
|
"orly.dev/pkg/encoders/bech32encoding"
|
||||||
"orly.dev/pkg/utils/atomic"
|
"orly.dev/pkg/utils/atomic"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"orly.dev/pkg/utils/interrupt"
|
"orly.dev/pkg/utils/interrupt"
|
||||||
"orly.dev/pkg/utils/log"
|
"orly.dev/pkg/utils/log"
|
||||||
|
"orly.dev/pkg/utils/lol"
|
||||||
"orly.dev/pkg/utils/qu"
|
"orly.dev/pkg/utils/qu"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -33,9 +34,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
sec *secp256k1.SecretKey
|
sec []byte
|
||||||
npub []byte
|
npub []byte
|
||||||
pub *secp256k1.PublicKey
|
pub []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
@@ -45,6 +46,7 @@ var args struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
lol.SetLogLevel("info")
|
||||||
arg.MustParse(&args)
|
arg.MustParse(&args)
|
||||||
if args.String == "" {
|
if args.String == "" {
|
||||||
_, _ = fmt.Fprintln(
|
_, _ = fmt.Fprintln(
|
||||||
@@ -79,7 +81,7 @@ Options:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Vanity(str string, where int, threads int) (e error) {
|
func Vanity(str string, where int, threads int) (err error) {
|
||||||
|
|
||||||
// check the string has valid bech32 ciphers
|
// check the string has valid bech32 ciphers
|
||||||
for i := range str {
|
for i := range str {
|
||||||
@@ -122,7 +124,7 @@ out:
|
|||||||
wm := workingFor % time.Second
|
wm := workingFor % time.Second
|
||||||
workingFor -= wm
|
workingFor -= wm
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"working for %v, attempts %d\n",
|
" working for %v, attempts %d",
|
||||||
workingFor, counter.Load(),
|
workingFor, counter.Load(),
|
||||||
)
|
)
|
||||||
case r := <-resC:
|
case r := <-resC:
|
||||||
@@ -142,20 +144,16 @@ out:
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"generated in %d attempts using %d threads, taking %v\n",
|
"\r# generated in %d attempts using %d threads, taking %v ",
|
||||||
counter.Load(), args.Threads, time.Now().Sub(started),
|
counter.Load(), args.Threads, time.Now().Sub(started),
|
||||||
)
|
)
|
||||||
secBytes := res.sec.Serialize()
|
fmt.Printf(
|
||||||
log.D.Ln(
|
"\nHSEC = %s\nHPUB = %s\n",
|
||||||
"generated key pair:\n"+
|
hex.EncodeToString(res.sec),
|
||||||
"\nhex:\n"+
|
hex.EncodeToString(res.pub),
|
||||||
"\tsecret: %s\n"+
|
|
||||||
"\tpublic: %s\n\n",
|
|
||||||
hex.EncodeToString(secBytes),
|
|
||||||
hex.EncodeToString(schnorr.SerializePubKey(res.pub)),
|
|
||||||
)
|
)
|
||||||
nsec, _ := bech32encoding.SecretKeyToNsec(res.sec)
|
nsec, _ := bech32encoding.BinToNsec(res.sec)
|
||||||
fmt.Printf("\nNSEC = %s\nNPUB = %s\n\n", nsec, res.npub)
|
fmt.Printf("NSEC = %s\nNPUB = %s\n", nsec, res.npub)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,16 +183,19 @@ out:
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
counter.Inc()
|
counter.Inc()
|
||||||
r.sec, r.pub, e = GenKeyPair()
|
// r.sec, r.pub, e = GenKeyPair()
|
||||||
|
r.sec, r.pub, e = Gen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.E.Ln("error generating key: '%v' worker stopping", e)
|
log.E.Ln("error generating key: '%v' worker stopping", e)
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
r.npub, e = bech32encoding.PublicKeyToNpub(r.pub)
|
// r.npub, e = bech32encoding.PublicKeyToNpub(r.pub)
|
||||||
if e != nil {
|
if r.npub, e = bech32encoding.BinToNpub(r.pub); e != nil {
|
||||||
log.E.Ln("fatal error generating npub: %s\n", e)
|
log.E.Ln("fatal error generating npub: %s\n", e)
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
fmt.Printf("\rgenerating key: %s", r.npub)
|
||||||
|
// log.I.F("%s", r.npub)
|
||||||
switch where {
|
switch where {
|
||||||
case PositionBeginning:
|
case PositionBeginning:
|
||||||
if bytes.HasPrefix(r.npub, append(prefix, []byte(str)...)) {
|
if bytes.HasPrefix(r.npub, append(prefix, []byte(str)...)) {
|
||||||
@@ -215,6 +216,11 @@ out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Gen() (skb, pkb []byte, err error) {
|
||||||
|
skb, pkb, _, _, err = p256k.Generate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GenKeyPair creates a fresh new key pair using the entropy source used by
|
// GenKeyPair creates a fresh new key pair using the entropy source used by
|
||||||
// crypto/rand (ie, /dev/random on posix systems).
|
// crypto/rand (ie, /dev/random on posix systems).
|
||||||
func GenKeyPair() (
|
func GenKeyPair() (
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ type C struct {
|
|||||||
SpiderSeeds []string `env:"ORLY_SPIDER_SEEDS" usage:"seeds to use for the spider (relays that are looked up initially to find owner relay lists) (comma separated)" default:"wss://relay.nostr.band/,wss://relay.damus.io/,wss://nostr.wine/,wss://nostr.land/,wss://theforest.nostr1.com/"`
|
SpiderSeeds []string `env:"ORLY_SPIDER_SEEDS" usage:"seeds to use for the spider (relays that are looked up initially to find owner relay lists) (comma separated)" default:"wss://relay.nostr.band/,wss://relay.damus.io/,wss://nostr.wine/,wss://nostr.land/,wss://theforest.nostr1.com/"`
|
||||||
Owners []string `env:"ORLY_OWNERS" usage:"list of users whose follow lists designate whitelisted users who can publish events, and who can read if public readable is false (comma separated)"`
|
Owners []string `env:"ORLY_OWNERS" usage:"list of users whose follow lists designate whitelisted users who can publish events, and who can read if public readable is false (comma separated)"`
|
||||||
Private bool `env:"ORLY_PRIVATE" usage:"do not spider for user metadata because the relay is private and this would leak relay memberships" default:"false"`
|
Private bool `env:"ORLY_PRIVATE" usage:"do not spider for user metadata because the relay is private and this would leak relay memberships" default:"false"`
|
||||||
|
Whitelist []string `env:"ORLY_WHITELIST" usage:"only allow connections from this list of IP addresses"`
|
||||||
|
RelaySecret string `env:"ORLY_SECRET_KEY" usage:"secret key for relay cluster replication authentication"`
|
||||||
|
PeerRelays []string `env:"ORLY_PEER_RELAYS" usage:"list of peer relays URLs that new events are pushed to in format <pubkey>|<url>"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates and initializes a new configuration object for the relay
|
// New creates and initializes a new configuration object for the relay
|
||||||
@@ -73,6 +76,9 @@ func New() (cfg *C, err error) {
|
|||||||
if cfg.State == "" || strings.Contains(cfg.State, "~") {
|
if cfg.State == "" || strings.Contains(cfg.State, "~") {
|
||||||
cfg.State = filepath.Join(xdg.StateHome, cfg.AppName)
|
cfg.State = filepath.Join(xdg.StateHome, cfg.AppName)
|
||||||
}
|
}
|
||||||
|
if len(cfg.Owners) > 0 {
|
||||||
|
cfg.AuthRequired = true
|
||||||
|
}
|
||||||
envPath := filepath.Join(cfg.Config, ".env")
|
envPath := filepath.Join(cfg.Config, ".env")
|
||||||
if apputil.FileExists(envPath) {
|
if apputil.FileExists(envPath) {
|
||||||
var e env2.Env
|
var e env2.Env
|
||||||
@@ -87,6 +93,17 @@ func New() (cfg *C, err error) {
|
|||||||
lol.SetLogLevel(cfg.LogLevel)
|
lol.SetLogLevel(cfg.LogLevel)
|
||||||
log.I.F("loaded configuration from %s", envPath)
|
log.I.F("loaded configuration from %s", envPath)
|
||||||
}
|
}
|
||||||
|
// if spider seeds has no elements, there still is a single entry with an
|
||||||
|
// empty string; and also if any of the fields are empty strings, they need
|
||||||
|
// to be removed.
|
||||||
|
var seeds []string
|
||||||
|
for _, u := range cfg.SpiderSeeds {
|
||||||
|
if u == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seeds = append(seeds, u)
|
||||||
|
}
|
||||||
|
cfg.SpiderSeeds = seeds
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,13 @@ func (s *Server) AcceptEvent(
|
|||||||
c context.T, ev *event.E, hr *http.Request, authedPubkey []byte,
|
c context.T, ev *event.E, hr *http.Request, authedPubkey []byte,
|
||||||
remote string,
|
remote string,
|
||||||
) (accept bool, notice string, afterSave func()) {
|
) (accept bool, notice string, afterSave func()) {
|
||||||
|
if !s.AuthRequired() {
|
||||||
|
accept = true
|
||||||
|
return
|
||||||
|
}
|
||||||
// if auth is required and the user is not authed, reject
|
// if auth is required and the user is not authed, reject
|
||||||
if s.AuthRequired() && len(authedPubkey) == 0 {
|
if s.AuthRequired() && len(authedPubkey) == 0 {
|
||||||
|
notice = "client isn't authed"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check if the authed user is on the lists
|
// check if the authed user is on the lists
|
||||||
@@ -53,6 +58,14 @@ func (s *Server) AcceptEvent(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// todo: check if event author is on owners' mute lists or block list
|
if !accept {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, u := range s.OwnersMuted() {
|
||||||
|
if bytes.Equal(u, authedPubkey) {
|
||||||
|
notice = "event author is banned from this relay"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
package relay
|
package relay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"orly.dev/pkg/crypto/ec/secp256k1"
|
||||||
|
"orly.dev/pkg/encoders/hex"
|
||||||
|
"orly.dev/pkg/protocol/httpauth"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
realy_lol "orly.dev/pkg/version"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -17,6 +27,21 @@ var (
|
|||||||
NIP20prefixmatcher = regexp.MustCompile(`^\w+: `)
|
NIP20prefixmatcher = regexp.MustCompile(`^\w+: `)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var userAgent = fmt.Sprintf("orly/%s", realy_lol.V)
|
||||||
|
|
||||||
|
type WriteCloser struct {
|
||||||
|
*bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteCloser) Close() error {
|
||||||
|
w.Buffer.Reset()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriteCloser(w []byte) *WriteCloser {
|
||||||
|
return &WriteCloser{bytes.NewBuffer(w)}
|
||||||
|
}
|
||||||
|
|
||||||
// AddEvent processes an incoming event, saves it if valid, and delivers it to
|
// AddEvent processes an incoming event, saves it if valid, and delivers it to
|
||||||
// subscribers.
|
// subscribers.
|
||||||
//
|
//
|
||||||
@@ -55,6 +80,7 @@ var (
|
|||||||
// relevant message.
|
// relevant message.
|
||||||
func (s *Server) AddEvent(
|
func (s *Server) AddEvent(
|
||||||
c context.T, rl relay.I, ev *event.E, hr *http.Request, origin string,
|
c context.T, rl relay.I, ev *event.E, hr *http.Request, origin string,
|
||||||
|
pubkeys [][]byte,
|
||||||
) (accepted bool, message []byte) {
|
) (accepted bool, message []byte) {
|
||||||
|
|
||||||
if ev == nil {
|
if ev == nil {
|
||||||
@@ -85,6 +111,79 @@ func (s *Server) AddEvent(
|
|||||||
}
|
}
|
||||||
// notify subscribers
|
// notify subscribers
|
||||||
s.listeners.Deliver(ev)
|
s.listeners.Deliver(ev)
|
||||||
|
// push the new event to replicas if replicas are configured, and the relay
|
||||||
|
// has an identity key.
|
||||||
|
var err error
|
||||||
|
if len(s.Peers.Addresses) > 0 &&
|
||||||
|
len(s.Peers.I.Sec()) == secp256k1.SecKeyBytesLen {
|
||||||
|
evb := ev.Marshal(nil)
|
||||||
|
var payload io.ReadCloser
|
||||||
|
payload = NewWriteCloser(evb)
|
||||||
|
replica:
|
||||||
|
for i, a := range s.Peers.Addresses {
|
||||||
|
// the peer address index is the same as the list of pubkeys
|
||||||
|
// (they're unpacked from a string containing both, appended at the
|
||||||
|
// same time), so if the pubkeys from the http event endpoint sent
|
||||||
|
// us here matches the index of this address, we can skip it.
|
||||||
|
log.I.S(pubkeys)
|
||||||
|
for _, pk := range pubkeys {
|
||||||
|
if bytes.Equal(s.Peers.Pubkeys[i], pk) {
|
||||||
|
log.I.F(
|
||||||
|
"not sending back to replica that just sent us this event %0x %s",
|
||||||
|
ev.ID, a,
|
||||||
|
)
|
||||||
|
continue replica
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.I.F("sending to replica %s", a)
|
||||||
|
var ur *url.URL
|
||||||
|
if ur, err = url.Parse(a + "/api/event"); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var r *http.Request
|
||||||
|
r = &http.Request{
|
||||||
|
Method: "POST",
|
||||||
|
URL: ur,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: make(http.Header),
|
||||||
|
Body: payload,
|
||||||
|
ContentLength: int64(len(evb)),
|
||||||
|
Host: ur.Host,
|
||||||
|
}
|
||||||
|
r.Header.Add("User-Agent", userAgent)
|
||||||
|
if err = httpauth.AddNIP98Header(
|
||||||
|
r, ur, "POST", "", s.Peers.I, 0,
|
||||||
|
); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// add this replica's pubkey to the list to prevent re-sending to
|
||||||
|
// other replicas more than twice
|
||||||
|
pubkeys = append(pubkeys, s.Peers.Pub())
|
||||||
|
var pubkeysHeader []byte
|
||||||
|
for j, pk := range pubkeys {
|
||||||
|
pubkeysHeader = hex.EncAppend(pubkeysHeader, pk)
|
||||||
|
if j < len(pubkeys)-1 {
|
||||||
|
pubkeysHeader = append(pubkeysHeader, ':')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Header.Add("X-Pubkeys", string(pubkeysHeader))
|
||||||
|
r.GetBody = func() (rc io.ReadCloser, err error) {
|
||||||
|
rc = payload
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := &http.Client{}
|
||||||
|
if _, err = client.Do(r); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.I.F(
|
||||||
|
"event pushed to replica %s\n%s",
|
||||||
|
ur.String(), evb,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
accepted = true
|
accepted = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ func (s *Server) AdminAuth(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, pk := range s.ownersPubkeys {
|
for _, pk := range s.ownersPubkeys {
|
||||||
|
|
||||||
if bytes.Equal(pk, pubkey) {
|
if bytes.Equal(pk, pubkey) {
|
||||||
authed = true
|
authed = true
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"orly.dev/pkg/utils/log"
|
"orly.dev/pkg/utils/log"
|
||||||
"orly.dev/pkg/utils/lol"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceURL constructs the service URL based on the incoming HTTP request. It
|
// ServiceURL constructs the service URL based on the incoming HTTP request. It
|
||||||
@@ -34,8 +33,6 @@ import (
|
|||||||
//
|
//
|
||||||
// - Returns the constructed URL string.
|
// - Returns the constructed URL string.
|
||||||
func (s *Server) ServiceURL(req *http.Request) (st string) {
|
func (s *Server) ServiceURL(req *http.Request) (st string) {
|
||||||
lol.Tracer("ServiceURL")
|
|
||||||
defer func() { lol.Tracer("end ServiceURL", st) }()
|
|
||||||
if !s.AuthRequired() {
|
if !s.AuthRequired() {
|
||||||
log.T.F("auth not required")
|
log.T.F("auth not required")
|
||||||
return
|
return
|
||||||
|
|||||||
10
pkg/app/relay/config.go
Normal file
10
pkg/app/relay/config.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package relay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"orly.dev/pkg/app/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) Config() (c *config.C) {
|
||||||
|
c = s.C
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -55,7 +55,8 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
Nips: supportedNIPs, Software: version.URL,
|
Nips: supportedNIPs, Software: version.URL,
|
||||||
Version: version.V,
|
Version: version.V,
|
||||||
Limitation: relayinfo.Limits{
|
Limitation: relayinfo.Limits{
|
||||||
AuthRequired: s.C.AuthRequired,
|
AuthRequired: s.C.AuthRequired,
|
||||||
|
RestrictedWrites: s.C.AuthRequired,
|
||||||
},
|
},
|
||||||
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png",
|
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png",
|
||||||
}
|
}
|
||||||
|
|||||||
39
pkg/app/relay/owners-followed-auth.go
Normal file
39
pkg/app/relay/owners-followed-auth.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package relay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"orly.dev/pkg/protocol/httpauth"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) OwnersFollowedAuth(
|
||||||
|
r *http.Request, remote string,
|
||||||
|
tolerance ...time.Duration,
|
||||||
|
) (authed bool, pubkey []byte) {
|
||||||
|
var valid bool
|
||||||
|
var err error
|
||||||
|
var tolerate time.Duration
|
||||||
|
if len(tolerance) > 0 {
|
||||||
|
tolerate = tolerance[0]
|
||||||
|
}
|
||||||
|
if valid, pubkey, err = httpauth.CheckAuth(r, tolerate); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
log.E.F(
|
||||||
|
"invalid auth %s from %s",
|
||||||
|
r.Header.Get("Authorization"), remote,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pk := range s.ownersFollowed {
|
||||||
|
if bytes.Equal(pk, pubkey) {
|
||||||
|
authed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
6
pkg/app/relay/owners-pubkeys.go
Normal file
6
pkg/app/relay/owners-pubkeys.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package relay
|
||||||
|
|
||||||
|
func (s *Server) OwnersPubkeys() (pks [][]byte) {
|
||||||
|
pks = s.ownersPubkeys
|
||||||
|
return
|
||||||
|
}
|
||||||
66
pkg/app/relay/peers.go
Normal file
66
pkg/app/relay/peers.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package relay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"orly.dev/pkg/crypto/p256k"
|
||||||
|
"orly.dev/pkg/encoders/bech32encoding"
|
||||||
|
"orly.dev/pkg/interfaces/signer"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/keys"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Peers is a structure that keeps the information required when peer
|
||||||
|
// replication is enabled.
|
||||||
|
//
|
||||||
|
// - Addresses are the relay addresses that will be pushed new events when
|
||||||
|
// accepted. From ORLY_PEER_RELAYS first field after the |.
|
||||||
|
//
|
||||||
|
// - Pubkeys are the relay peer public keys that we will send any event to
|
||||||
|
// including privileged type. From ORLY_PEER_RELAYS before the |.
|
||||||
|
//
|
||||||
|
// - I - the signer of this relay, generated from the nsec in
|
||||||
|
// ORLY_SECRET_KEY.
|
||||||
|
type Peers struct {
|
||||||
|
Addresses []string
|
||||||
|
Pubkeys [][]byte
|
||||||
|
signer.I
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init accepts the lists which will come from config.C for peer relay settings
|
||||||
|
// and populate the Peers with this data after decoding it.
|
||||||
|
func (p *Peers) Init(
|
||||||
|
addresses []string, sec string,
|
||||||
|
) (err error) {
|
||||||
|
for _, address := range addresses {
|
||||||
|
split := strings.Split(address, "@")
|
||||||
|
if len(split) != 2 {
|
||||||
|
log.E.F("invalid peer address: %s", address)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.Addresses = append(p.Addresses, split[1])
|
||||||
|
var pk []byte
|
||||||
|
if pk, err = keys.DecodeNpubOrHex(split[0]); chk.D(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.Pubkeys = append(p.Pubkeys, pk)
|
||||||
|
log.I.F("peer %s added; pubkey: %0x", split[1], pk)
|
||||||
|
}
|
||||||
|
p.I = &p256k.Signer{}
|
||||||
|
var s []byte
|
||||||
|
if s, err = keys.DecodeNsecOrHex(sec); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = p.I.InitSec(s); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var npub []byte
|
||||||
|
if npub, err = bech32encoding.BinToNpub(p.I.Pub()); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.I.F(
|
||||||
|
"relay peer initialized, relay's npub: %s",
|
||||||
|
npub,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
// Package publisher is a singleton package that keeps track of subscriptions in
|
|
||||||
// both websockets and http SSE, including managing the authentication state of
|
|
||||||
// a connection.
|
|
||||||
package publish
|
package publish
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"orly.dev/pkg/encoders/event"
|
"orly.dev/pkg/encoders/event"
|
||||||
"orly.dev/pkg/interfaces/publisher"
|
"orly.dev/pkg/interfaces/publisher"
|
||||||
|
"orly.dev/pkg/interfaces/typer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// S is the control structure for the subscription management scheme.
|
// S is the control structure for the subscription management scheme.
|
||||||
@@ -26,11 +24,10 @@ func (s *S) Type() string { return "publish" }
|
|||||||
func (s *S) Deliver(ev *event.E) {
|
func (s *S) Deliver(ev *event.E) {
|
||||||
for _, p := range s.Publishers {
|
for _, p := range s.Publishers {
|
||||||
p.Deliver(ev)
|
p.Deliver(ev)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) Receive(msg publisher.Message) {
|
func (s *S) Receive(msg typer.T) {
|
||||||
t := msg.Type()
|
t := msg.Type()
|
||||||
for _, p := range s.Publishers {
|
for _, p := range s.Publishers {
|
||||||
if p.Type() == t {
|
if p.Type() == t {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ import (
|
|||||||
"orly.dev/pkg/utils/normalize"
|
"orly.dev/pkg/utils/normalize"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Publish processes and stores an event in the server's storage. It handles different types of events: ephemeral, replaceable, and parameterized replaceable.
|
// Publish processes and stores an event in the server's storage. It handles
|
||||||
|
// different types of events: ephemeral, replaceable, and parameterized
|
||||||
|
// replaceable.
|
||||||
//
|
//
|
||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
@@ -60,8 +62,14 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
|
|||||||
log.T.F("found %d possible duplicate events", len(evs))
|
log.T.F("found %d possible duplicate events", len(evs))
|
||||||
for _, ev := range evs {
|
for _, ev := range evs {
|
||||||
del := true
|
del := true
|
||||||
if bytes.Equal(ev.Id, evt.Id) {
|
if bytes.Equal(ev.ID, evt.ID) {
|
||||||
continue
|
return errorf.W(
|
||||||
|
string(
|
||||||
|
normalize.Duplicate.F(
|
||||||
|
"event already in relay database",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
log.I.F(
|
log.I.F(
|
||||||
"maybe replace %s with %s", ev.Serialize(), evt.Serialize(),
|
"maybe replace %s with %s", ev.Serialize(), evt.Serialize(),
|
||||||
@@ -75,6 +83,12 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// not deleting these events because some clients are retarded
|
||||||
|
// and the query will pull the new one, but a backup can recover
|
||||||
|
// the data of old ones
|
||||||
|
if ev.Kind.IsDirectoryEvent() {
|
||||||
|
del = false
|
||||||
|
}
|
||||||
if evt.Kind.Equal(kind.FollowList) {
|
if evt.Kind.Equal(kind.FollowList) {
|
||||||
// if the event is from someone on ownersFollowed or
|
// if the event is from someone on ownersFollowed or
|
||||||
// followedFollows, for now add to this list so they're
|
// followedFollows, for now add to this list so they're
|
||||||
@@ -99,7 +113,7 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
|
|||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
// event has been saved and lists updated.
|
// event has been saved and lists updated.
|
||||||
return
|
// return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -121,7 +135,7 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
|
|||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
// event has been saved and lists updated.
|
// event has been saved and lists updated.
|
||||||
return
|
// return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,5 +241,10 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.T.C(
|
||||||
|
func() string {
|
||||||
|
return fmt.Sprintf("saved event:\n%s", evt.Serialize())
|
||||||
|
},
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"orly.dev/pkg/protocol/openapi"
|
||||||
"orly.dev/pkg/protocol/socketapi"
|
"orly.dev/pkg/protocol/socketapi"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"orly.dev/pkg/app/config"
|
"orly.dev/pkg/app/config"
|
||||||
@@ -37,6 +39,7 @@ type Server struct {
|
|||||||
listeners *publish.S
|
listeners *publish.S
|
||||||
*config.C
|
*config.C
|
||||||
*Lists
|
*Lists
|
||||||
|
*Peers
|
||||||
Mux *servemux.S
|
Mux *servemux.S
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +103,12 @@ func NewServer(
|
|||||||
options: op,
|
options: op,
|
||||||
C: sp.C,
|
C: sp.C,
|
||||||
Lists: new(Lists),
|
Lists: new(Lists),
|
||||||
|
Peers: new(Peers),
|
||||||
}
|
}
|
||||||
s.listeners = publish.New(socketapi.New(s))
|
chk.E(
|
||||||
|
s.Peers.Init(sp.C.PeerRelays, sp.C.RelaySecret),
|
||||||
|
)
|
||||||
|
s.listeners = publish.New(socketapi.New(s), openapi.NewPublisher(s))
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.relay.Init(); chk.E(err) {
|
if err := s.relay.Init(); chk.E(err) {
|
||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
@@ -133,6 +140,21 @@ func NewServer(
|
|||||||
//
|
//
|
||||||
// - For all other paths, delegates to the internal mux's ServeHTTP method.
|
// - For all other paths, delegates to the internal mux's ServeHTTP method.
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := s.Config()
|
||||||
|
remote := helpers.GetRemoteFromReq(r)
|
||||||
|
var whitelisted bool
|
||||||
|
if len(c.Whitelist) > 0 {
|
||||||
|
for _, addr := range c.Whitelist {
|
||||||
|
if strings.HasPrefix(remote, addr) {
|
||||||
|
whitelisted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
whitelisted = true
|
||||||
|
}
|
||||||
|
if !whitelisted {
|
||||||
|
return
|
||||||
|
}
|
||||||
// standard nostr protocol only governs the "root" path of the relay and
|
// standard nostr protocol only governs the "root" path of the relay and
|
||||||
// websockets
|
// websockets
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"orly.dev/pkg/encoders/hex"
|
"orly.dev/pkg/encoders/hex"
|
||||||
"orly.dev/pkg/encoders/kinds"
|
"orly.dev/pkg/encoders/kinds"
|
||||||
"orly.dev/pkg/encoders/tag"
|
"orly.dev/pkg/encoders/tag"
|
||||||
|
"orly.dev/pkg/encoders/timestamp"
|
||||||
"orly.dev/pkg/protocol/ws"
|
"orly.dev/pkg/protocol/ws"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"orly.dev/pkg/utils/context"
|
"orly.dev/pkg/utils/context"
|
||||||
@@ -16,6 +17,7 @@ import (
|
|||||||
"orly.dev/pkg/utils/log"
|
"orly.dev/pkg/utils/log"
|
||||||
"orly.dev/pkg/utils/lol"
|
"orly.dev/pkg/utils/lol"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IdPkTs is a map of event IDs to their id, pubkey, kind, and timestamp
|
// IdPkTs is a map of event IDs to their id, pubkey, kind, and timestamp
|
||||||
@@ -45,11 +47,15 @@ func (s *Server) SpiderFetch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var kindsList string
|
var kindsList string
|
||||||
for i, kk := range k.K {
|
if k != nil {
|
||||||
if i > 0 {
|
for i, kk := range k.K {
|
||||||
kindsList += ","
|
if i > 0 {
|
||||||
|
kindsList += ","
|
||||||
|
}
|
||||||
|
kindsList += kk.Name()
|
||||||
}
|
}
|
||||||
kindsList += kk.Name()
|
} else {
|
||||||
|
kindsList = "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query local database
|
// Query local database
|
||||||
@@ -70,7 +76,7 @@ func (s *Server) SpiderFetch(
|
|||||||
// If it doesn't exist or the new event is newer, store it
|
// If it doesn't exist or the new event is newer, store it
|
||||||
if !exists || ev.CreatedAtInt64() > existing.Timestamp {
|
if !exists || ev.CreatedAtInt64() > existing.Timestamp {
|
||||||
pkKindMap[pkKindKey] = &IdPkTs{
|
pkKindMap[pkKindKey] = &IdPkTs{
|
||||||
Id: ev.Id,
|
Id: ev.ID,
|
||||||
Pubkey: ev.Pubkey,
|
Pubkey: ev.Pubkey,
|
||||||
Kind: ev.Kind.ToU16(),
|
Kind: ev.Kind.ToU16(),
|
||||||
Timestamp: ev.CreatedAtInt64(),
|
Timestamp: ev.CreatedAtInt64(),
|
||||||
@@ -100,7 +106,7 @@ func (s *Server) SpiderFetch(
|
|||||||
|
|
||||||
log.I.F("%d events found of type %s", len(pkKindMap), kindsList)
|
log.I.F("%d events found of type %s", len(pkKindMap), kindsList)
|
||||||
|
|
||||||
if !noFetch {
|
if !noFetch && len(s.C.SpiderSeeds) > 0 {
|
||||||
// we need to search the spider seeds.
|
// we need to search the spider seeds.
|
||||||
// Break up pubkeys into batches of 128
|
// Break up pubkeys into batches of 128
|
||||||
for i := 0; i < len(pubkeys); i += 128 {
|
for i := 0; i < len(pubkeys); i += 128 {
|
||||||
@@ -115,12 +121,19 @@ func (s *Server) SpiderFetch(
|
|||||||
)
|
)
|
||||||
batchPkList := tag.New(batchPubkeys...)
|
batchPkList := tag.New(batchPubkeys...)
|
||||||
lim := uint(batchPkList.Len())
|
lim := uint(batchPkList.Len())
|
||||||
|
l := &lim
|
||||||
|
var since *timestamp.T
|
||||||
|
if k == nil {
|
||||||
|
since = timestamp.FromTime(time.Now().Add(-1 * time.Hour))
|
||||||
|
} else {
|
||||||
|
l = nil
|
||||||
|
}
|
||||||
batchFilter := &filter.F{
|
batchFilter := &filter.F{
|
||||||
Kinds: k,
|
Kinds: k,
|
||||||
Authors: batchPkList,
|
Authors: batchPkList,
|
||||||
Limit: &lim,
|
Since: since,
|
||||||
|
Limit: l,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, seed := range s.C.SpiderSeeds {
|
for _, seed := range s.C.SpiderSeeds {
|
||||||
select {
|
select {
|
||||||
case <-s.Ctx.Done():
|
case <-s.Ctx.Done():
|
||||||
@@ -148,6 +161,7 @@ func (s *Server) SpiderFetch(
|
|||||||
|
|
||||||
// Process each event immediately
|
// Process each event immediately
|
||||||
for i, ev := range evss {
|
for i, ev := range evss {
|
||||||
|
// log.I.S(ev)
|
||||||
// Create a key based on pubkey and kind for deduplication
|
// Create a key based on pubkey and kind for deduplication
|
||||||
pkKindKey := string(ev.Pubkey) + string(ev.Kind.Marshal(nil))
|
pkKindKey := string(ev.Pubkey) + string(ev.Kind.Marshal(nil))
|
||||||
|
|
||||||
@@ -157,8 +171,8 @@ func (s *Server) SpiderFetch(
|
|||||||
// If it doesn't exist or the new event is newer, store it and save to database
|
// If it doesn't exist or the new event is newer, store it and save to database
|
||||||
if !exists || ev.CreatedAtInt64() > existing.Timestamp {
|
if !exists || ev.CreatedAtInt64() > existing.Timestamp {
|
||||||
var ser *types.Uint40
|
var ser *types.Uint40
|
||||||
if ser, err = s.Storage().GetSerialById(ev.Id); err == nil && ser != nil {
|
if ser, err = s.Storage().GetSerialById(ev.ID); err == nil && ser != nil {
|
||||||
err = errorf.E("event already exists: %0x", ev.Id)
|
err = errorf.E("event already exists: %0x", ev.ID)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// verify the signature
|
// verify the signature
|
||||||
@@ -166,7 +180,7 @@ func (s *Server) SpiderFetch(
|
|||||||
if valid, err = ev.Verify(); chk.E(err) || !valid {
|
if valid, err = ev.Verify(); chk.E(err) || !valid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.I.F("event %0x is valid", ev.Id)
|
log.I.F("event %0x is valid", ev.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the event to the database
|
// Save the event to the database
|
||||||
@@ -185,12 +199,12 @@ func (s *Server) SpiderFetch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
log.I.F("saved event: %0x", ev.Id)
|
log.I.F("saved event: %0x", ev.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the essential information
|
// Store the essential information
|
||||||
pkKindMap[pkKindKey] = &IdPkTs{
|
pkKindMap[pkKindKey] = &IdPkTs{
|
||||||
Id: ev.Id,
|
Id: ev.ID,
|
||||||
Pubkey: ev.Pubkey,
|
Pubkey: ev.Pubkey,
|
||||||
Kind: ev.Kind.ToU16(),
|
Kind: ev.Kind.ToU16(),
|
||||||
Timestamp: ev.CreatedAtInt64(),
|
Timestamp: ev.CreatedAtInt64(),
|
||||||
|
|||||||
@@ -2,41 +2,19 @@ package relay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"orly.dev/pkg/crypto/ec/bech32"
|
|
||||||
"orly.dev/pkg/encoders/bech32encoding"
|
|
||||||
"orly.dev/pkg/encoders/hex"
|
|
||||||
"orly.dev/pkg/encoders/kind"
|
"orly.dev/pkg/encoders/kind"
|
||||||
"orly.dev/pkg/encoders/kinds"
|
"orly.dev/pkg/encoders/kinds"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/keys"
|
||||||
"orly.dev/pkg/utils/log"
|
"orly.dev/pkg/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) Spider(noFetch ...bool) (err error) {
|
func (s *Server) Spider(noFetch ...bool) (err error) {
|
||||||
var ownersPubkeys [][]byte
|
var ownersPubkeys [][]byte
|
||||||
for _, v := range s.C.Owners {
|
for _, v := range s.C.Owners {
|
||||||
var prf []byte
|
|
||||||
var pk []byte
|
var pk []byte
|
||||||
var bits5 []byte
|
if pk, err = keys.DecodeNpubOrHex(v); chk.E(err) {
|
||||||
if prf, bits5, err = bech32.DecodeNoLimit([]byte(v)); chk.D(err) {
|
continue
|
||||||
// try hex then
|
|
||||||
if _, err = hex.DecBytes(pk, []byte(v)); chk.E(err) {
|
|
||||||
log.W.F(
|
|
||||||
"owner key %s is neither bech32 npub nor hex",
|
|
||||||
v,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !bytes.Equal(prf, bech32encoding.NpubHRP) {
|
|
||||||
log.W.F(
|
|
||||||
"owner key %s is neither bech32 npub nor hex",
|
|
||||||
v,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pk, err = bech32.ConvertBits(bits5, 5, 8, false); chk.E(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// owners themselves are on the OwnersFollowed list as first level
|
// owners themselves are on the OwnersFollowed list as first level
|
||||||
ownersPubkeys = append(ownersPubkeys, pk)
|
ownersPubkeys = append(ownersPubkeys, pk)
|
||||||
@@ -118,15 +96,17 @@ func (s *Server) Spider(noFetch ...bool) (err error) {
|
|||||||
s.SetOwnersFollowed(ownersFollowed)
|
s.SetOwnersFollowed(ownersFollowed)
|
||||||
s.SetFollowedFollows(followedFollows)
|
s.SetFollowedFollows(followedFollows)
|
||||||
s.SetOwnersMuted(ownersMuted)
|
s.SetOwnersMuted(ownersMuted)
|
||||||
// lastly, update users profile metadata and relay lists in the background
|
// lastly, update all followed users new events in the background
|
||||||
if !dontFetch {
|
if !dontFetch {
|
||||||
go func() {
|
go func() {
|
||||||
everyone := append(ownersFollowed, followedFollows...)
|
everyone := append(ownersFollowed, followedFollows...)
|
||||||
s.SpiderFetch(
|
s.SpiderFetch(
|
||||||
kinds.New(
|
// kinds.New(
|
||||||
kind.ProfileMetadata, kind.RelayListMetadata,
|
// kind.ProfileMetadata, kind.RelayListMetadata,
|
||||||
kind.DMRelaysList,
|
// kind.DMRelaysList,
|
||||||
), false, true, everyone...,
|
// ),
|
||||||
|
nil,
|
||||||
|
false, true, everyone...,
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
50
pkg/app/relay/user-auth.go
Normal file
50
pkg/app/relay/user-auth.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package relay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"orly.dev/pkg/protocol/httpauth"
|
||||||
|
"orly.dev/pkg/utils/chk"
|
||||||
|
"orly.dev/pkg/utils/log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) UserAuth(
|
||||||
|
r *http.Request, remote string, tolerance ...time.Duration,
|
||||||
|
) (authed bool, pubkey []byte, super bool) {
|
||||||
|
var valid bool
|
||||||
|
var err error
|
||||||
|
var tolerate time.Duration
|
||||||
|
if len(tolerance) > 0 {
|
||||||
|
tolerate = tolerance[0]
|
||||||
|
}
|
||||||
|
if valid, pubkey, err = httpauth.CheckAuth(r, tolerate); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
log.E.F(
|
||||||
|
"invalid auth %s from %s",
|
||||||
|
r.Header.Get("Authorization"), remote,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pk := range append(s.ownersFollowed, s.followedFollows...) {
|
||||||
|
if bytes.Equal(pk, pubkey) {
|
||||||
|
authed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the client is one of the relay cluster replicas, also set the super
|
||||||
|
// flag to indicate that privilege checks can be bypassed.
|
||||||
|
if len(s.Peers.Pubkeys) > 0 {
|
||||||
|
for _, pk := range s.Peers.Pubkeys {
|
||||||
|
if bytes.Equal(pk, pubkey) {
|
||||||
|
authed = true
|
||||||
|
super = true
|
||||||
|
pubkey = pk
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ func TestSignerVerify(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if valid, err = signer.Verify(id, ev.Sig); chk.E(err) {
|
if valid, err = signer.Verify(id, ev.Sig); chk.E(err) {
|
||||||
t.Errorf("failed to verify: %s\n%0x", err, ev.Id)
|
t.Errorf("failed to verify: %s\n%0x", err, ev.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !valid {
|
if !valid {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (d *D) DeleteEvent(c context.T, eid *eventid.T) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ser == nil {
|
if ser == nil {
|
||||||
// Event not found, nothing to delete
|
// Event wasn't found, nothing to delete
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Fetch the event to get its data
|
// Fetch the event to get its data
|
||||||
@@ -33,7 +33,7 @@ func (d *D) DeleteEvent(c context.T, eid *eventid.T) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev == nil {
|
if ev == nil {
|
||||||
// Event not found, nothing to delete
|
// Event wasn't found, nothing to delete
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get all indexes for the event
|
// Get all indexes for the event
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"orly.dev/pkg/database/indexes"
|
"orly.dev/pkg/database/indexes"
|
||||||
"orly.dev/pkg/database/indexes/types"
|
"orly.dev/pkg/database/indexes/types"
|
||||||
"orly.dev/pkg/encoders/codecbuf"
|
|
||||||
"orly.dev/pkg/encoders/event"
|
"orly.dev/pkg/encoders/event"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"orly.dev/pkg/utils/context"
|
"orly.dev/pkg/utils/context"
|
||||||
@@ -22,8 +21,7 @@ func (d *D) Export(c context.T, w io.Writer, pubkeys ...[]byte) {
|
|||||||
if len(pubkeys) == 0 {
|
if len(pubkeys) == 0 {
|
||||||
if err = d.View(
|
if err = d.View(
|
||||||
func(txn *badger.Txn) (err error) {
|
func(txn *badger.Txn) (err error) {
|
||||||
buf := codecbuf.Get()
|
buf := new(bytes.Buffer)
|
||||||
defer codecbuf.Put(buf)
|
|
||||||
if err = indexes.EventEnc(nil).MarshalWrite(buf); chk.E(err) {
|
if err = indexes.EventEnc(nil).MarshalWrite(buf); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -61,8 +59,7 @@ func (d *D) Export(c context.T, w io.Writer, pubkeys ...[]byte) {
|
|||||||
for _, pubkey := range pubkeys {
|
for _, pubkey := range pubkeys {
|
||||||
if err = d.View(
|
if err = d.View(
|
||||||
func(txn *badger.Txn) (err error) {
|
func(txn *badger.Txn) (err error) {
|
||||||
pkBuf := codecbuf.Get()
|
pkBuf := new(bytes.Buffer)
|
||||||
defer codecbuf.Put(pkBuf)
|
|
||||||
ph := &types.PubHash{}
|
ph := &types.PubHash{}
|
||||||
if err = ph.FromPubkey(pubkey); chk.E(err) {
|
if err = ph.FromPubkey(pubkey); chk.E(err) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
"orly.dev/pkg/database/indexes"
|
"orly.dev/pkg/database/indexes"
|
||||||
"orly.dev/pkg/database/indexes/types"
|
"orly.dev/pkg/database/indexes/types"
|
||||||
"orly.dev/pkg/encoders/codecbuf"
|
|
||||||
"orly.dev/pkg/encoders/event"
|
"orly.dev/pkg/encoders/event"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
)
|
)
|
||||||
@@ -13,8 +12,7 @@ import (
|
|||||||
func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
|
func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
|
||||||
if err = d.View(
|
if err = d.View(
|
||||||
func(txn *badger.Txn) (err error) {
|
func(txn *badger.Txn) (err error) {
|
||||||
buf := codecbuf.Get()
|
buf := new(bytes.Buffer)
|
||||||
defer codecbuf.Put(buf)
|
|
||||||
if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) {
|
if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func TestFetchEventBySerial(t *testing.T) {
|
|||||||
var sers types.Uint40s
|
var sers types.Uint40s
|
||||||
sers, err = db.QueryForSerials(
|
sers, err = db.QueryForSerials(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(testEvent.Id),
|
Ids: tag.New(testEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,10 +102,10 @@ func TestFetchEventBySerial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify the fetched event has the same ID as the original event
|
// Verify the fetched event has the same ID as the original event
|
||||||
if !bytes.Equal(fetchedEvent.Id, testEvent.Id) {
|
if !bytes.Equal(fetchedEvent.ID, testEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Fetched event ID doesn't match original event ID. Got %x, expected %x",
|
"Fetched event ID doesn't match original event ID. Got %x, expected %x",
|
||||||
fetchedEvent.Id, testEvent.Id,
|
fetchedEvent.ID, testEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
"orly.dev/pkg/database/indexes"
|
"orly.dev/pkg/database/indexes"
|
||||||
"orly.dev/pkg/database/indexes/types"
|
"orly.dev/pkg/database/indexes/types"
|
||||||
"orly.dev/pkg/encoders/codecbuf"
|
|
||||||
"orly.dev/pkg/interfaces/store"
|
"orly.dev/pkg/interfaces/store"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
)
|
)
|
||||||
@@ -15,8 +14,7 @@ func (d *D) GetFullIdPubkeyBySerial(ser *types.Uint40) (
|
|||||||
) {
|
) {
|
||||||
if err = d.View(
|
if err = d.View(
|
||||||
func(txn *badger.Txn) (err error) {
|
func(txn *badger.Txn) (err error) {
|
||||||
buf := codecbuf.Get()
|
buf := new(bytes.Buffer)
|
||||||
defer codecbuf.Put(buf)
|
|
||||||
if err = indexes.FullIdPubkeyEnc(
|
if err = indexes.FullIdPubkeyEnc(
|
||||||
ser, nil, nil, nil,
|
ser, nil, nil, nil,
|
||||||
).MarshalWrite(buf); chk.E(err) {
|
).MarshalWrite(buf); chk.E(err) {
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ func GetIndexesForEvent(ev *event.E, serial uint64) (
|
|||||||
if err = ser.Set(serial); chk.E(err) {
|
if err = ser.Set(serial); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Id index
|
// ID index
|
||||||
idHash := new(IdHash)
|
idHash := new(IdHash)
|
||||||
if err = idHash.FromId(ev.Id); chk.E(err) {
|
if err = idHash.FromId(ev.ID); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
idIndex := indexes.IdEnc(idHash, ser)
|
idIndex := indexes.IdEnc(idHash, ser)
|
||||||
@@ -50,7 +50,7 @@ func GetIndexesForEvent(ev *event.E, serial uint64) (
|
|||||||
}
|
}
|
||||||
// FullIdPubkey index
|
// FullIdPubkey index
|
||||||
fullID := new(Id)
|
fullID := new(Id)
|
||||||
if err = fullID.FromId(ev.Id); chk.E(err) {
|
if err = fullID.FromId(ev.ID); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pubHash := new(PubHash)
|
pubHash := new(PubHash)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func testBasicEvent(t *testing.T) {
|
|||||||
for i := range id {
|
for i := range id {
|
||||||
id[i] = byte(i)
|
id[i] = byte(i)
|
||||||
}
|
}
|
||||||
ev.Id = id
|
ev.ID = id
|
||||||
|
|
||||||
// Set Pubkey
|
// Set Pubkey
|
||||||
pubkey := make([]byte, 32)
|
pubkey := make([]byte, 32)
|
||||||
@@ -92,7 +92,7 @@ func testBasicEvent(t *testing.T) {
|
|||||||
|
|
||||||
// Create and verify the expected indexes
|
// Create and verify the expected indexes
|
||||||
|
|
||||||
// 1. Id index
|
// 1. ID index
|
||||||
ser := new(types2.Uint40)
|
ser := new(types2.Uint40)
|
||||||
err = ser.Set(serial)
|
err = ser.Set(serial)
|
||||||
if chk.E(err) {
|
if chk.E(err) {
|
||||||
@@ -100,7 +100,7 @@ func testBasicEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idHash := new(types2.IdHash)
|
idHash := new(types2.IdHash)
|
||||||
err = idHash.FromId(ev.Id)
|
err = idHash.FromId(ev.ID)
|
||||||
if chk.E(err) {
|
if chk.E(err) {
|
||||||
t.Fatalf("Failed to create IdHash: %v", err)
|
t.Fatalf("Failed to create IdHash: %v", err)
|
||||||
}
|
}
|
||||||
@@ -109,9 +109,9 @@ func testBasicEvent(t *testing.T) {
|
|||||||
|
|
||||||
// 2. FullIdPubkey index
|
// 2. FullIdPubkey index
|
||||||
fullID := new(types2.Id)
|
fullID := new(types2.Id)
|
||||||
err = fullID.FromId(ev.Id)
|
err = fullID.FromId(ev.ID)
|
||||||
if chk.E(err) {
|
if chk.E(err) {
|
||||||
t.Fatalf("Failed to create Id: %v", err)
|
t.Fatalf("Failed to create ID: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pubHash := new(types2.PubHash)
|
pubHash := new(types2.PubHash)
|
||||||
@@ -156,7 +156,7 @@ func testEventWithTags(t *testing.T) {
|
|||||||
for i := range id {
|
for i := range id {
|
||||||
id[i] = byte(i)
|
id[i] = byte(i)
|
||||||
}
|
}
|
||||||
ev.Id = id
|
ev.ID = id
|
||||||
|
|
||||||
// Set Pubkey
|
// Set Pubkey
|
||||||
pubkey := make([]byte, 32)
|
pubkey := make([]byte, 32)
|
||||||
@@ -210,7 +210,7 @@ func testEventWithTags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idHash := new(types2.IdHash)
|
idHash := new(types2.IdHash)
|
||||||
err = idHash.FromId(ev.Id)
|
err = idHash.FromId(ev.ID)
|
||||||
if chk.E(err) {
|
if chk.E(err) {
|
||||||
t.Fatalf("Failed to create IdHash: %v", err)
|
t.Fatalf("Failed to create IdHash: %v", err)
|
||||||
}
|
}
|
||||||
@@ -268,7 +268,7 @@ func testErrorHandling(t *testing.T) {
|
|||||||
for i := range id {
|
for i := range id {
|
||||||
id[i] = byte(i)
|
id[i] = byte(i)
|
||||||
}
|
}
|
||||||
ev.Id = id
|
ev.ID = id
|
||||||
|
|
||||||
// Set Pubkey
|
// Set Pubkey
|
||||||
pubkey := make([]byte, 32)
|
pubkey := make([]byte, 32)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func CreatePubHashFromData(data []byte) (p *types2.PubHash, err error) {
|
|||||||
// complete set of combinations of all fields in the event, thus there is no
|
// complete set of combinations of all fields in the event, thus there is no
|
||||||
// need to decode events until they are to be delivered.
|
// need to decode events until they are to be delivered.
|
||||||
func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||||
// Id eid
|
// ID eid
|
||||||
//
|
//
|
||||||
// If there is any Ids in the filter, none of the other fields matter. It
|
// If there is any Ids in the filter, none of the other fields matter. It
|
||||||
// should be an error, but convention just ignores it.
|
// should be an error, but convention just ignores it.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
|
|
||||||
// TestGetIndexesFromFilter tests the GetIndexesFromFilter function
|
// TestGetIndexesFromFilter tests the GetIndexesFromFilter function
|
||||||
func TestGetIndexesFromFilter(t *testing.T) {
|
func TestGetIndexesFromFilter(t *testing.T) {
|
||||||
t.Run("Id", testIdFilter)
|
t.Run("ID", testIdFilter)
|
||||||
t.Run("Pubkey", testPubkeyFilter)
|
t.Run("Pubkey", testPubkeyFilter)
|
||||||
t.Run("CreatedAt", testCreatedAtFilter)
|
t.Run("CreatedAt", testCreatedAtFilter)
|
||||||
t.Run("CreatedAtUntil", testCreatedAtUntilFilter)
|
t.Run("CreatedAtUntil", testCreatedAtUntilFilter)
|
||||||
@@ -77,9 +77,9 @@ func verifyIndex(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Id filter
|
// Test ID filter
|
||||||
func testIdFilter(t *testing.T) {
|
func testIdFilter(t *testing.T) {
|
||||||
// Create a filter with an Id
|
// Create a filter with an ID
|
||||||
f := filter.New()
|
f := filter.New()
|
||||||
id := make([]byte, sha256.Size)
|
id := make([]byte, sha256.Size)
|
||||||
for i := range id {
|
for i := range id {
|
||||||
@@ -102,7 +102,7 @@ func testIdFilter(t *testing.T) {
|
|||||||
expectedIdx := indexes.IdEnc(idHash, nil)
|
expectedIdx := indexes.IdEnc(idHash, nil)
|
||||||
|
|
||||||
// Verify the generated index
|
// Verify the generated index
|
||||||
// For Id filter, both start and end indexes are the same
|
// For ID filter, both start and end indexes are the same
|
||||||
verifyIndex(t, idxs, expectedIdx, expectedIdx)
|
verifyIndex(t, idxs, expectedIdx, expectedIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func TestGetSerialById(t *testing.T) {
|
|||||||
testEvent := events[3] // Using the same event as in QueryForIds test
|
testEvent := events[3] // Using the same event as in QueryForIds test
|
||||||
|
|
||||||
// Get the serial by ID
|
// Get the serial by ID
|
||||||
serial, err := db.GetSerialById(testEvent.Id)
|
serial, err := db.GetSerialById(testEvent.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to get serial by ID: %v", err)
|
t.Fatalf("Failed to get serial by ID: %v", err)
|
||||||
}
|
}
|
||||||
@@ -82,10 +82,10 @@ func TestGetSerialById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test with a non-existent ID
|
// Test with a non-existent ID
|
||||||
nonExistentId := make([]byte, len(testEvent.Id))
|
nonExistentId := make([]byte, len(testEvent.ID))
|
||||||
// Ensure it's different from any real ID
|
// Ensure it's different from any real ID
|
||||||
for i := range nonExistentId {
|
for i := range nonExistentId {
|
||||||
nonExistentId[i] = ^testEvent.Id[i]
|
nonExistentId[i] = ^testEvent.ID[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
serial, err = db.GetSerialById(nonExistentId)
|
serial, err = db.GetSerialById(nonExistentId)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func TestGetSerialsByRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the serial for this event
|
// Get the serial for this event
|
||||||
serial, err := db.GetSerialById(ev.Id)
|
serial, err := db.GetSerialById(ev.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Failed to get serial for event #%d: %v", eventCount+1, err,
|
"Failed to get serial for event #%d: %v", eventCount+1, err,
|
||||||
@@ -73,7 +73,7 @@ func TestGetSerialsByRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serial != nil {
|
if serial != nil {
|
||||||
eventSerials[string(ev.Id)] = serial
|
eventSerials[string(ev.ID)] = serial
|
||||||
}
|
}
|
||||||
|
|
||||||
eventCount++
|
eventCount++
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ func EventDec(ser *types.Uint40) (enc *T) { return New(NewPrefix(), ser) }
|
|||||||
// Id contains a truncated 8-byte hash of an event index. This is the secondary
|
// Id contains a truncated 8-byte hash of an event index. This is the secondary
|
||||||
// key of an event, the primary key is the serial found in the Event.
|
// key of an event, the primary key is the serial found in the Event.
|
||||||
//
|
//
|
||||||
// 3 prefix|8 Id hash|5 serial
|
// 3 prefix|8 ID hash|5 serial
|
||||||
var Id = next()
|
var Id = next()
|
||||||
|
|
||||||
func IdVars() (id *types.IdHash, ser *types.Uint40) {
|
func IdVars() (id *types.IdHash, ser *types.Uint40) {
|
||||||
@@ -202,7 +202,7 @@ func IdDec(id *types.IdHash, ser *types.Uint40) (enc *T) {
|
|||||||
// FullIdPubkey is an index designed to enable sorting and filtering of
|
// FullIdPubkey is an index designed to enable sorting and filtering of
|
||||||
// results found via other indexes, without having to decode the event.
|
// results found via other indexes, without having to decode the event.
|
||||||
//
|
//
|
||||||
// 3 prefix|5 serial|32 Id|8 pubkey hash|8 timestamp
|
// 3 prefix|5 serial|32 ID|8 pubkey hash|8 timestamp
|
||||||
var FullIdPubkey = next()
|
var FullIdPubkey = next()
|
||||||
|
|
||||||
func FullIdPubkeyVars() (
|
func FullIdPubkeyVars() (
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func TestPrefixFunction(t *testing.T) {
|
|||||||
expected I
|
expected I
|
||||||
}{
|
}{
|
||||||
{"Event", Event, EventPrefix},
|
{"Event", Event, EventPrefix},
|
||||||
{"Id", Id, IdPrefix},
|
{"ID", Id, IdPrefix},
|
||||||
{"FullIdPubkey", FullIdPubkey, FullIdPubkeyPrefix},
|
{"FullIdPubkey", FullIdPubkey, FullIdPubkeyPrefix},
|
||||||
{"Pubkey", Pubkey, PubkeyPrefix},
|
{"Pubkey", Pubkey, PubkeyPrefix},
|
||||||
{"CreatedAt", CreatedAt, CreatedAtPrefix},
|
{"CreatedAt", CreatedAt, CreatedAtPrefix},
|
||||||
@@ -122,7 +122,7 @@ func TestIdentify(t *testing.T) {
|
|||||||
expected int
|
expected int
|
||||||
}{
|
}{
|
||||||
{"Event", EventPrefix, Event},
|
{"Event", EventPrefix, Event},
|
||||||
{"Id", IdPrefix, Id},
|
{"ID", IdPrefix, Id},
|
||||||
{"FullIdPubkey", FullIdPubkeyPrefix, FullIdPubkey},
|
{"FullIdPubkey", FullIdPubkeyPrefix, FullIdPubkey},
|
||||||
{"Pubkey", PubkeyPrefix, Pubkey},
|
{"Pubkey", PubkeyPrefix, Pubkey},
|
||||||
{"CreatedAt", CreatedAtPrefix, CreatedAt},
|
{"CreatedAt", CreatedAtPrefix, CreatedAt},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Id struct {
|
|||||||
func (fi *Id) FromId(id []byte) (err error) {
|
func (fi *Id) FromId(id []byte) (err error) {
|
||||||
if len(id) != IdLen {
|
if len(id) != IdLen {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"fullid.FromId: invalid Id length, got %d require %d", len(id),
|
"fullid.FromId: invalid ID length, got %d require %d", len(id),
|
||||||
IdLen,
|
IdLen,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func TestFromId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIdMarshalWriteUnmarshalRead(t *testing.T) {
|
func TestIdMarshalWriteUnmarshalRead(t *testing.T) {
|
||||||
// Create a Id with a known value
|
// Create a ID with a known value
|
||||||
fi1 := &Id{}
|
fi1 := &Id{}
|
||||||
validId := make([]byte, sha256.Size)
|
validId := make([]byte, sha256.Size)
|
||||||
for i := 0; i < sha256.Size; i++ {
|
for i := 0; i < sha256.Size; i++ {
|
||||||
@@ -80,7 +80,7 @@ func TestIdMarshalWriteUnmarshalRead(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIdUnmarshalReadWithCorruptedData(t *testing.T) {
|
func TestIdUnmarshalReadWithCorruptedData(t *testing.T) {
|
||||||
// Create a Id with a known value
|
// Create a ID with a known value
|
||||||
fi1 := &Id{}
|
fi1 := &Id{}
|
||||||
validId := make([]byte, sha256.Size)
|
validId := make([]byte, sha256.Size)
|
||||||
for i := 0; i < sha256.Size; i++ {
|
for i := 0; i < sha256.Size; i++ {
|
||||||
@@ -91,7 +91,7 @@ func TestIdUnmarshalReadWithCorruptedData(t *testing.T) {
|
|||||||
t.Fatalf("FromId failed: %v", err)
|
t.Fatalf("FromId failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a second Id with a different value
|
// Create a second ID with a different value
|
||||||
fi2 := &Id{}
|
fi2 := &Id{}
|
||||||
differentId := make([]byte, sha256.Size)
|
differentId := make([]byte, sha256.Size)
|
||||||
for i := 0; i < sha256.Size; i++ {
|
for i := 0; i < sha256.Size; i++ {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (i *IdHash) Set(idh []byte) {
|
|||||||
func (i *IdHash) FromId(id []byte) (err error) {
|
func (i *IdHash) FromId(id []byte) (err error) {
|
||||||
if len(id) != sha256.Size {
|
if len(id) != sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"FromId: invalid Id length, got %d require %d", len(id),
|
"FromId: invalid ID length, got %d require %d", len(id),
|
||||||
sha256.Size,
|
sha256.Size,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -43,7 +43,7 @@ func (i *IdHash) FromIdBase64(idb64 string) (err error) {
|
|||||||
// Check if the decoded ID has the correct length
|
// Check if the decoded ID has the correct length
|
||||||
if len(decoded) != sha256.Size {
|
if len(decoded) != sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"FromIdBase64: invalid Id length, got %d require %d", len(decoded),
|
"FromIdBase64: invalid ID length, got %d require %d", len(decoded),
|
||||||
sha256.Size,
|
sha256.Size,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -62,7 +62,7 @@ func (i *IdHash) FromIdHex(idh string) (err error) {
|
|||||||
}
|
}
|
||||||
if len(id) != sha256.Size {
|
if len(id) != sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"FromIdHex: invalid Id length, got %d require %d", len(id),
|
"FromIdHex: invalid ID length, got %d require %d", len(id),
|
||||||
sha256.Size,
|
sha256.Size,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package types
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"orly.dev/pkg/encoders/codecbuf"
|
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ func (ts *Timestamp) ToTimestamp() (timestamp int64) {
|
|||||||
func (ts *Timestamp) Bytes() (b []byte, err error) {
|
func (ts *Timestamp) Bytes() (b []byte, err error) {
|
||||||
v := new(Uint64)
|
v := new(Uint64)
|
||||||
v.Set(uint64(ts.val))
|
v.Set(uint64(ts.val))
|
||||||
buf := codecbuf.Get()
|
buf := new(bytes.Buffer)
|
||||||
if err = v.MarshalWrite(buf); chk.E(err) {
|
if err = v.MarshalWrite(buf); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"orly.dev/pkg/encoders/codecbuf"
|
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,8 +35,7 @@ func (w *Word) MarshalWrite(wr io.Writer) (err error) {
|
|||||||
|
|
||||||
// UnmarshalRead reads the word from the reader, stopping at the zero-byte marker
|
// UnmarshalRead reads the word from the reader, stopping at the zero-byte marker
|
||||||
func (w *Word) UnmarshalRead(r io.Reader) error {
|
func (w *Word) UnmarshalRead(r io.Reader) error {
|
||||||
buf := codecbuf.Get()
|
buf := new(bytes.Buffer)
|
||||||
defer codecbuf.Put(buf)
|
|
||||||
tmp := make([]byte, 1)
|
tmp := make([]byte, 1)
|
||||||
foundEndMarker := false
|
foundEndMarker := false
|
||||||
|
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the newest event
|
// Verify it's the newest event
|
||||||
if !bytes.Equal(evs[0].Id, newestEvent.Id) {
|
if !bytes.Equal(evs[0].ID, newestEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match the newest event. Got %x, expected %x",
|
"Event ID doesn't match the newest event. Got %x, expected %x",
|
||||||
evs[0].Id, newestEvent.Id,
|
evs[0].ID, newestEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
|
|||||||
// Query for the base event by ID
|
// Query for the base event by ID
|
||||||
evs, err = db.QueryEvents(
|
evs, err = db.QueryEvents(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(baseEvent.Id),
|
Ids: tag.New(baseEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -161,10 +161,10 @@ func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the base event
|
// Verify it's the base event
|
||||||
if !bytes.Equal(evs[0].Id, baseEvent.Id) {
|
if !bytes.Equal(evs[0].ID, baseEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match when querying for base event by ID. Got %x, expected %x",
|
"Event ID doesn't match when querying for base event by ID. Got %x, expected %x",
|
||||||
evs[0].Id, baseEvent.Id,
|
evs[0].ID, baseEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
|
|||||||
isIdInFilter := false
|
isIdInFilter := false
|
||||||
if f.Ids != nil && f.Ids.Len() > 0 {
|
if f.Ids != nil && f.Ids.Len() > 0 {
|
||||||
for i := 0; i < f.Ids.Len(); i++ {
|
for i := 0; i < f.Ids.Len(); i++ {
|
||||||
if bytes.Equal(ev.Id, f.Ids.B(i)) {
|
if bytes.Equal(ev.ID, f.Ids.B(i)) {
|
||||||
isIdInFilter = true
|
isIdInFilter = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func TestQueryEventsByID(t *testing.T) {
|
|||||||
|
|
||||||
evs, err := db.QueryEvents(
|
evs, err := db.QueryEvents(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(testEvent.Id),
|
Ids: tag.New(testEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,10 +103,10 @@ func TestQueryEventsByID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the correct event
|
// Verify it's the correct event
|
||||||
if !bytes.Equal(evs[0].Id, testEvent.Id) {
|
if !bytes.Equal(evs[0].ID, testEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match. Got %x, expected %x", evs[0].Id,
|
"Event ID doesn't match. Got %x, expected %x", evs[0].ID,
|
||||||
testEvent.Id,
|
testEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
// Query for the original event by ID
|
// Query for the original event by ID
|
||||||
evs, err := db.QueryEvents(
|
evs, err := db.QueryEvents(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(replaceableEvent.Id),
|
Ids: tag.New(replaceableEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -239,10 +239,10 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the original event
|
// Verify it's the original event
|
||||||
if !bytes.Equal(evs[0].Id, replaceableEvent.Id) {
|
if !bytes.Equal(evs[0].ID, replaceableEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match when querying for replaced event. Got %x, expected %x",
|
"Event ID doesn't match when querying for replaced event. Got %x, expected %x",
|
||||||
evs[0].Id, replaceableEvent.Id,
|
evs[0].ID, replaceableEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,10 +269,10 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the newer event
|
// Verify it's the newer event
|
||||||
if !bytes.Equal(evs[0].Id, newerEvent.Id) {
|
if !bytes.Equal(evs[0].ID, newerEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match when querying for replaceable events. Got %x, expected %x",
|
"Event ID doesn't match when querying for replaceable events. Got %x, expected %x",
|
||||||
evs[0].Id, newerEvent.Id,
|
evs[0].ID, newerEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
|
|
||||||
// Add an e-tag referencing the replaceable event
|
// Add an e-tag referencing the replaceable event
|
||||||
deletionEvent.Tags = deletionEvent.Tags.AppendTags(
|
deletionEvent.Tags = deletionEvent.Tags.AppendTags(
|
||||||
tag.New([]byte{'e'}, []byte(hex.Enc(replaceableEvent.Id))),
|
tag.New([]byte{'e'}, []byte(hex.Enc(replaceableEvent.ID))),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Save the deletion event
|
// Save the deletion event
|
||||||
@@ -319,17 +319,17 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's still the newer event
|
// Verify it's still the newer event
|
||||||
if !bytes.Equal(evs[0].Id, newerEvent.Id) {
|
if !bytes.Equal(evs[0].ID, newerEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match after deletion. Got %x, expected %x",
|
"Event ID doesn't match after deletion. Got %x, expected %x",
|
||||||
evs[0].Id, newerEvent.Id,
|
evs[0].ID, newerEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for the original event by ID
|
// Query for the original event by ID
|
||||||
evs, err = db.QueryEvents(
|
evs, err = db.QueryEvents(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(replaceableEvent.Id),
|
Ids: tag.New(replaceableEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,10 +345,10 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the original event
|
// Verify it's the original event
|
||||||
if !bytes.Equal(evs[0].Id, replaceableEvent.Id) {
|
if !bytes.Equal(evs[0].ID, replaceableEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match when querying for deleted event by ID. Got %x, expected %x",
|
"Event ID doesn't match when querying for deleted event by ID. Got %x, expected %x",
|
||||||
evs[0].Id, replaceableEvent.Id,
|
evs[0].ID, replaceableEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,7 +433,7 @@ func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
paramDeletionEvent2.Tags = tags.New()
|
paramDeletionEvent2.Tags = tags.New()
|
||||||
// Add an e-tag referencing the parameterized replaceable event
|
// Add an e-tag referencing the parameterized replaceable event
|
||||||
paramDeletionEvent2.Tags = paramDeletionEvent2.Tags.AppendTags(
|
paramDeletionEvent2.Tags = paramDeletionEvent2.Tags.AppendTags(
|
||||||
tag.New([]byte{'e'}, []byte(hex.Enc(paramEvent.Id))),
|
tag.New([]byte{'e'}, []byte(hex.Enc(paramEvent.ID))),
|
||||||
)
|
)
|
||||||
paramDeletionEvent2.Sign(sign)
|
paramDeletionEvent2.Sign(sign)
|
||||||
|
|
||||||
@@ -483,7 +483,7 @@ func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
// Query for the parameterized event by ID
|
// Query for the parameterized event by ID
|
||||||
evs, err = db.QueryEvents(
|
evs, err = db.QueryEvents(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(paramEvent.Id),
|
Ids: tag.New(paramEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -501,10 +501,10 @@ func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's the correct event
|
// Verify it's the correct event
|
||||||
if !bytes.Equal(evs[0].Id, paramEvent.Id) {
|
if !bytes.Equal(evs[0].ID, paramEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match when querying for deleted parameterized event by ID. Got %x, expected %x",
|
"Event ID doesn't match when querying for deleted parameterized event by ID. Got %x, expected %x",
|
||||||
evs[0].Id, paramEvent.Id,
|
evs[0].ID, paramEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func TestQueryForAuthorsTags(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
if !bytes.Equal(ev.Pubkey, testEvent.Pubkey) {
|
if !bytes.Equal(ev.Pubkey, testEvent.Pubkey) {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func TestQueryForCreatedAt(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ func TestQueryForCreatedAt(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ func TestQueryForCreatedAt(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,34 +86,34 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
len(idTsPk),
|
len(idTsPk),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(idTsPk[0].Id, events[5474].Id) {
|
if !bytes.Equal(idTsPk[0].Id, events[5474].ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"failed to get expected event, got %0x, expected %0x", idTsPk[0].Id,
|
"failed to get expected event, got %0x, expected %0x", idTsPk[0].Id,
|
||||||
events[5474].Id,
|
events[5474].ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(idTsPk[1].Id, events[272].Id) {
|
if !bytes.Equal(idTsPk[1].Id, events[272].ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"failed to get expected event, got %0x, expected %0x", idTsPk[1].Id,
|
"failed to get expected event, got %0x, expected %0x", idTsPk[1].Id,
|
||||||
events[272].Id,
|
events[272].ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(idTsPk[2].Id, events[1].Id) {
|
if !bytes.Equal(idTsPk[2].Id, events[1].ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"failed to get expected event, got %0x, expected %0x", idTsPk[2].Id,
|
"failed to get expected event, got %0x, expected %0x", idTsPk[2].Id,
|
||||||
events[1].Id,
|
events[1].ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(idTsPk[3].Id, events[80].Id) {
|
if !bytes.Equal(idTsPk[3].Id, events[80].ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"failed to get expected event, got %0x, expected %0x", idTsPk[3].Id,
|
"failed to get expected event, got %0x, expected %0x", idTsPk[3].Id,
|
||||||
events[80].Id,
|
events[80].ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(idTsPk[4].Id, events[123].Id) {
|
if !bytes.Equal(idTsPk[4].Id, events[123].ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"failed to get expected event, got %0x, expected %0x", idTsPk[4].Id,
|
"failed to get expected event, got %0x, expected %0x", idTsPk[4].Id,
|
||||||
events[123].Id,
|
events[123].ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testKind.K {
|
if ev.Kind.K != testKind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
@@ -207,7 +207,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
// Check if the event has the tag we're looking for
|
// Check if the event has the tag we're looking for
|
||||||
@@ -258,7 +258,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testKind.K {
|
if ev.Kind.K != testKind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
@@ -305,7 +305,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testEvent.Kind.K {
|
if ev.Kind.K != testEvent.Kind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
@@ -366,7 +366,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testEvent.Kind.K {
|
if ev.Kind.K != testEvent.Kind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
@@ -433,7 +433,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
if !bytes.Equal(ev.Pubkey, testEvent.Pubkey) {
|
if !bytes.Equal(ev.Pubkey, testEvent.Pubkey) {
|
||||||
@@ -506,7 +506,7 @@ func TestQueryForIds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func TestQueryForKindsAuthorsTags(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testKind.K {
|
if ev.Kind.K != testKind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func TestQueryForKindsAuthors(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testKind.K {
|
if ev.Kind.K != testKind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func TestQueryForKindsTags(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testKind.K {
|
if ev.Kind.K != testKind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func TestQueryForKinds(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
if ev.Kind.K != testKind.K {
|
if ev.Kind.K != testKind.K {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func TestQueryForSerials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the serial for this event
|
// Get the serial for this event
|
||||||
serial, err := db.GetSerialById(ev.Id)
|
serial, err := db.GetSerialById(ev.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Failed to get serial for event #%d: %v", eventCount+1, err,
|
"Failed to get serial for event #%d: %v", eventCount+1, err,
|
||||||
@@ -73,7 +73,7 @@ func TestQueryForSerials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serial != nil {
|
if serial != nil {
|
||||||
eventSerials[string(ev.Id)] = serial
|
eventSerials[string(ev.ID)] = serial
|
||||||
}
|
}
|
||||||
|
|
||||||
eventCount++
|
eventCount++
|
||||||
@@ -91,7 +91,7 @@ func TestQueryForSerials(t *testing.T) {
|
|||||||
|
|
||||||
serials, err := db.QueryForSerials(
|
serials, err := db.QueryForSerials(
|
||||||
ctx, &filter.F{
|
ctx, &filter.F{
|
||||||
Ids: tag.New(testEvent.Id),
|
Ids: tag.New(testEvent.ID),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,10 +110,10 @@ func TestQueryForSerials(t *testing.T) {
|
|||||||
t.Fatalf("Failed to fetch event for serial: %v", err)
|
t.Fatalf("Failed to fetch event for serial: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(ev.Id, testEvent.Id) {
|
if !bytes.Equal(ev.ID, testEvent.ID) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Event ID doesn't match. Got %x, expected %x",
|
"Event ID doesn't match. Got %x, expected %x",
|
||||||
ev.Id, testEvent.Id,
|
ev.ID, testEvent.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func TestQueryForTags(t *testing.T) {
|
|||||||
// Find the event with this ID
|
// Find the event with this ID
|
||||||
var found bool
|
var found bool
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if bytes.Equal(result.Id, ev.Id) {
|
if bytes.Equal(result.Id, ev.ID) {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
// Check if the event has the tag we're looking for
|
// Check if the event has the tag we're looking for
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func (d *D) SaveEvent(c context.T, ev *event.E, noVerify bool) (
|
|||||||
if !noVerify {
|
if !noVerify {
|
||||||
// check if the event already exists
|
// check if the event already exists
|
||||||
var ser *types.Uint40
|
var ser *types.Uint40
|
||||||
if ser, err = d.GetSerialById(ev.Id); err == nil && ser != nil {
|
if ser, err = d.GetSerialById(ev.ID); err == nil && ser != nil {
|
||||||
err = errorf.E("event already exists: %0x", ev.Id)
|
err = errorf.E("event already exists: %0x", ev.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func (d *D) SaveEvent(c context.T, ev *event.E, noVerify bool) (
|
|||||||
if ev.CreatedAt.I64() < idPkTss[0].Ts {
|
if ev.CreatedAt.I64() < idPkTss[0].Ts {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
|
"blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
|
||||||
ev.Id, at, ev.CreatedAt.I64(), idPkTss[0].Ts,
|
ev.ID, at, ev.CreatedAt.I64(), idPkTss[0].Ts,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func (d *D) SaveEvent(c context.T, ev *event.E, noVerify bool) (
|
|||||||
&filter.F{
|
&filter.F{
|
||||||
Authors: tag.New(ev.Pubkey),
|
Authors: tag.New(ev.Pubkey),
|
||||||
Kinds: kinds.New(kind.Deletion),
|
Kinds: kinds.New(kind.Deletion),
|
||||||
Tags: tags.New(tag.New([]byte("#e"), ev.Id)),
|
Tags: tags.New(tag.New([]byte("#e"), ev.ID)),
|
||||||
},
|
},
|
||||||
); chk.E(err) {
|
); chk.E(err) {
|
||||||
return
|
return
|
||||||
@@ -115,7 +115,7 @@ func (d *D) SaveEvent(c context.T, ev *event.E, noVerify bool) (
|
|||||||
// really there can only be one of these; the chances of an idhash
|
// really there can only be one of these; the chances of an idhash
|
||||||
// collision are basically zero in practice, at least, one in a
|
// collision are basically zero in practice, at least, one in a
|
||||||
// billion or more anyway, more than a human is going to create.
|
// billion or more anyway, more than a human is going to create.
|
||||||
err = errorf.E("blocked: %0x was deleted by event Id", ev.Id)
|
err = errorf.E("blocked: %0x was deleted by event ID", ev.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func TestDeletionEventWithETagRejection(t *testing.T) {
|
|||||||
|
|
||||||
// Add an e-tag referencing the regular event
|
// Add an e-tag referencing the regular event
|
||||||
deletionEvent.Tags = deletionEvent.Tags.AppendTags(
|
deletionEvent.Tags = deletionEvent.Tags.AppendTags(
|
||||||
tag.New([]byte{'e'}, []byte(hex.Enc(regularEvent.Id))),
|
tag.New([]byte{'e'}, []byte(hex.Enc(regularEvent.ID))),
|
||||||
)
|
)
|
||||||
|
|
||||||
deletionEvent.Sign(sign)
|
deletionEvent.Sign(sign)
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func EncodeEvent(
|
|||||||
return bech32.Encode(NeventHRP, bits5)
|
return bech32.Encode(NeventHRP, bits5)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeEntity encodes a pubkey, kind, event Id, and relay hints.
|
// EncodeEntity encodes a pubkey, kind, event ID, and relay hints.
|
||||||
func EncodeEntity(pk []byte, k *kind.T, id []byte, relays [][]byte) (
|
func EncodeEntity(pk []byte, k *kind.T, id []byte, relays [][]byte) (
|
||||||
s []byte, err error,
|
s []byte, err error,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type Profile struct {
|
|||||||
Relays [][]byte `json:"relays,omitempty"`
|
Relays [][]byte `json:"relays,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event pointer is the combination of an event Id, relay hints, author, pubkey,
|
// Event pointer is the combination of an event ID, relay hints, author, pubkey,
|
||||||
// and kind.
|
// and kind.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID *eventid.T `json:"id"`
|
ID *eventid.T `json:"id"`
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
# Codecbuf - Concurrent-Safe Bytes Buffer Pool
|
|
||||||
|
|
||||||
This package provides a concurrent-safe pool of `bytes.Buffer` objects for encoding data. It helps reduce memory allocations and improve performance by reusing buffers instead of creating new ones for each operation.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Get a buffer from the default pool
|
|
||||||
buf := codecbuf.Get()
|
|
||||||
|
|
||||||
// Use the buffer
|
|
||||||
buf.WriteString("Hello, World!")
|
|
||||||
// ... do more operations with the buffer ...
|
|
||||||
|
|
||||||
// Return the buffer to the pool when done
|
|
||||||
codecbuf.Put(buf)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using with defer
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ProcessData() {
|
|
||||||
// Get a buffer from the default pool
|
|
||||||
buf := codecbuf.Get()
|
|
||||||
|
|
||||||
// Return the buffer to the pool when the function exits
|
|
||||||
defer codecbuf.Put(buf)
|
|
||||||
|
|
||||||
// Use the buffer
|
|
||||||
buf.WriteString("Hello, World!")
|
|
||||||
// ... do more operations with the buffer ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating a Custom Pool
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Create a new buffer pool
|
|
||||||
pool := codecbuf.NewPool()
|
|
||||||
|
|
||||||
// Get a buffer from the custom pool
|
|
||||||
buf := pool.Get()
|
|
||||||
|
|
||||||
// Use the buffer
|
|
||||||
buf.WriteString("Hello, World!")
|
|
||||||
|
|
||||||
// Return the buffer to the custom pool
|
|
||||||
pool.Put(buf)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
Using a buffer pool can significantly improve performance in applications that frequently create and use byte buffers, especially in high-throughput scenarios. The pool reduces garbage collection pressure by reusing buffers instead of allocating new ones.
|
|
||||||
|
|
||||||
## Thread Safety
|
|
||||||
|
|
||||||
The buffer pool is safe for concurrent use by multiple goroutines. However, individual buffers obtained from the pool should not be used concurrently by multiple goroutines without additional synchronization.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Package codecbuf provides a concurrent-safe bytes buffer pool for encoding
|
|
||||||
// data.
|
|
||||||
package codecbuf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pool is a concurrent-safe pool of bytes.Buffer objects.
|
|
||||||
type Pool struct {
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPool creates a new buffer pool.
|
|
||||||
func NewPool() *Pool {
|
|
||||||
return &Pool{
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a buffer from the pool or creates a new one if the pool is empty.
|
|
||||||
func (p *Pool) Get() *bytes.Buffer {
|
|
||||||
return p.pool.Get().(*bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put returns a buffer to the pool after zeroing its bytes for security and resetting it.
|
|
||||||
func (p *Pool) Put(buf *bytes.Buffer) {
|
|
||||||
// Zero out the bytes for security
|
|
||||||
data := buf.Bytes()
|
|
||||||
for i := range data {
|
|
||||||
data[i] = 0
|
|
||||||
}
|
|
||||||
buf.Reset()
|
|
||||||
p.pool.Put(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultPool is the default buffer pool for the application.
|
|
||||||
var DefaultPool = NewPool()
|
|
||||||
|
|
||||||
// Get returns a buffer from the default pool.
|
|
||||||
func Get() *bytes.Buffer {
|
|
||||||
return DefaultPool.Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put returns a buffer to the default pool after zeroing its bytes for security.
|
|
||||||
func Put(buf *bytes.Buffer) {
|
|
||||||
DefaultPool.Put(buf)
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package codecbuf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPool(t *testing.T) {
|
|
||||||
// Create a new pool
|
|
||||||
pool := NewPool()
|
|
||||||
|
|
||||||
// Get a buffer from the pool
|
|
||||||
buf := pool.Get()
|
|
||||||
if buf == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write some data to the buffer
|
|
||||||
testData := "test data"
|
|
||||||
_, err := buf.WriteString(testData)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer contains the expected data
|
|
||||||
if buf.String() != testData {
|
|
||||||
t.Fatalf(
|
|
||||||
"Expected buffer to contain %q, got %q", testData, buf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the buffer back in the pool
|
|
||||||
pool.Put(buf)
|
|
||||||
|
|
||||||
// Get another buffer from the pool (should be the same one, reset)
|
|
||||||
buf2 := pool.Get()
|
|
||||||
if buf2 == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer is empty (was reset)
|
|
||||||
if buf2.Len() != 0 {
|
|
||||||
t.Fatalf("Expected empty buffer, got buffer with length %d", buf2.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write different data to the buffer
|
|
||||||
testData2 := "different data"
|
|
||||||
_, err = buf2.WriteString(testData2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer contains the new data
|
|
||||||
if buf2.String() != testData2 {
|
|
||||||
t.Fatalf(
|
|
||||||
"Expected buffer to contain %q, got %q", testData2, buf2.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultPool(t *testing.T) {
|
|
||||||
// Get a buffer from the default pool
|
|
||||||
buf := Get()
|
|
||||||
if buf == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from default pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write some data to the buffer
|
|
||||||
testData := "test data for default pool"
|
|
||||||
_, err := buf.WriteString(testData)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer contains the expected data
|
|
||||||
if buf.String() != testData {
|
|
||||||
t.Fatalf(
|
|
||||||
"Expected buffer to contain %q, got %q", testData, buf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the buffer back in the pool
|
|
||||||
Put(buf)
|
|
||||||
|
|
||||||
// Get another buffer from the pool (should be reset)
|
|
||||||
buf2 := Get()
|
|
||||||
if buf2 == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from default pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer is empty (was reset)
|
|
||||||
if buf2.Len() != 0 {
|
|
||||||
t.Fatalf("Expected empty buffer, got buffer with length %d", buf2.Len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestZeroBytes(t *testing.T) {
|
|
||||||
// Create a new pool
|
|
||||||
pool := NewPool()
|
|
||||||
|
|
||||||
// Get a buffer from the pool
|
|
||||||
buf := pool.Get()
|
|
||||||
if buf == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write some sensitive data to the buffer
|
|
||||||
sensitiveData := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
|
||||||
_, err := buf.Write(sensitiveData)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the capacity before putting it back
|
|
||||||
capacity := buf.Cap()
|
|
||||||
|
|
||||||
// Put the buffer back in the pool
|
|
||||||
pool.Put(buf)
|
|
||||||
|
|
||||||
// Get another buffer from the pool (should be the same one, reset)
|
|
||||||
buf2 := pool.Get()
|
|
||||||
if buf2 == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer is empty (was reset)
|
|
||||||
if buf2.Len() != 0 {
|
|
||||||
t.Fatalf("Expected empty buffer, got buffer with length %d", buf2.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the capacity is the same (should be the same buffer)
|
|
||||||
if buf2.Cap() != capacity {
|
|
||||||
t.Fatalf("Expected capacity %d, got %d", capacity, buf2.Cap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the underlying bytes directly
|
|
||||||
// We need to grow the buffer to the same size as before to access the same memory
|
|
||||||
buf2.Grow(len(sensitiveData))
|
|
||||||
|
|
||||||
// Write some new data to the buffer to expose the underlying memory
|
|
||||||
newData := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
|
|
||||||
_, err = buf2.Write(newData)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the buffer bytes
|
|
||||||
bufBytes := buf2.Bytes()
|
|
||||||
|
|
||||||
// Verify that the sensitive data was zeroed out
|
|
||||||
// The new data should be there, but no trace of the old data
|
|
||||||
for i, b := range bufBytes[:len(newData)] {
|
|
||||||
if b != newData[i] {
|
|
||||||
t.Fatalf("Expected byte %d to be %d, got %d", i, newData[i], b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultPoolZeroBytes(t *testing.T) {
|
|
||||||
// Get a buffer from the default pool
|
|
||||||
buf := Get()
|
|
||||||
if buf == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from default pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write some sensitive data to the buffer
|
|
||||||
sensitiveData := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
|
||||||
_, err := buf.Write(sensitiveData)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the capacity before putting it back
|
|
||||||
capacity := buf.Cap()
|
|
||||||
|
|
||||||
// Put the buffer back in the pool
|
|
||||||
Put(buf)
|
|
||||||
|
|
||||||
// Get another buffer from the pool (should be the same one, reset)
|
|
||||||
buf2 := Get()
|
|
||||||
if buf2 == nil {
|
|
||||||
t.Fatal("Expected non-nil buffer from default pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the buffer is empty (was reset)
|
|
||||||
if buf2.Len() != 0 {
|
|
||||||
t.Fatalf("Expected empty buffer, got buffer with length %d", buf2.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the capacity is the same (should be the same buffer)
|
|
||||||
if buf2.Cap() != capacity {
|
|
||||||
t.Fatalf("Expected capacity %d, got %d", capacity, buf2.Cap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the underlying bytes directly
|
|
||||||
// We need to grow the buffer to the same size as before to access the same memory
|
|
||||||
buf2.Grow(len(sensitiveData))
|
|
||||||
|
|
||||||
// Write some new data to the buffer to expose the underlying memory
|
|
||||||
newData := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
|
|
||||||
_, err = buf2.Write(newData)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to buffer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the buffer bytes
|
|
||||||
bufBytes := buf2.Bytes()
|
|
||||||
|
|
||||||
// Verify that the sensitive data was zeroed out
|
|
||||||
// The new data should be there, but no trace of the old data
|
|
||||||
for i, b := range bufBytes[:len(newData)] {
|
|
||||||
if b != newData[i] {
|
|
||||||
t.Fatalf("Expected byte %d to be %d, got %d", i, newData[i], b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWithPool(b *testing.B) {
|
|
||||||
pool := NewPool()
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
buf := pool.Get()
|
|
||||||
buf.WriteString("benchmark test data")
|
|
||||||
pool.Put(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWithoutPool(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.WriteString("benchmark test data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -171,7 +171,7 @@ func NewResponseWith(event *event.E) *Response { return &Response{Event: event}
|
|||||||
// Label returns the label of a auth Response envelope.
|
// Label returns the label of a auth Response envelope.
|
||||||
func (en *Response) Label() string { return L }
|
func (en *Response) Label() string { return L }
|
||||||
|
|
||||||
func (en *Response) Id() []byte { return en.Event.Id }
|
func (en *Response) Id() []byte { return en.Event.ID }
|
||||||
|
|
||||||
// Write the Response to a provided io.Writer.
|
// Write the Response to a provided io.Writer.
|
||||||
func (en *Response) Write(w io.Writer) (err error) {
|
func (en *Response) Write(w io.Writer) (err error) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func New() *T {
|
|||||||
return &T{Subscription: subscription.NewStd()}
|
return &T{Subscription: subscription.NewStd()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFrom creates a new closedenvelope.T populated with subscription Id and Reason.
|
// NewFrom creates a new closedenvelope.T populated with subscription ID and Reason.
|
||||||
func NewFrom(id *subscription.Id, msg []byte) *T {
|
func NewFrom(id *subscription.Id, msg []byte) *T {
|
||||||
return &T{
|
return &T{
|
||||||
Subscription: id, Reason: msg,
|
Subscription: id, Reason: msg,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func TestMarshalUnmarshal(t *testing.T) {
|
|||||||
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// log.I.Ln(req2.Id)
|
// log.I.Ln(req2.ID)
|
||||||
if len(rem) > 0 {
|
if len(rem) > 0 {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"unmarshal failed, remainder\n%d %s",
|
"unmarshal failed, remainder\n%d %s",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ var _ codec.Envelope = (*T)(nil)
|
|||||||
// New creates an empty new standard formatted closeenvelope.T.
|
// New creates an empty new standard formatted closeenvelope.T.
|
||||||
func New() *T { return &T{ID: subscription.NewStd()} }
|
func New() *T { return &T{ID: subscription.NewStd()} }
|
||||||
|
|
||||||
// NewFrom creates a new closeenvelope.T populated with subscription Id.
|
// NewFrom creates a new closeenvelope.T populated with subscription ID.
|
||||||
func NewFrom(id *subscription.Id) *T { return &T{ID: id} }
|
func NewFrom(id *subscription.Id) *T { return &T{ID: id} }
|
||||||
|
|
||||||
// Label returns the label of a closeenvelope.T.
|
// Label returns the label of a closeenvelope.T.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func TestMarshalUnmarshal(t *testing.T) {
|
|||||||
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// log.I.Ln(req2.Id)
|
// log.I.Ln(req2.ID)
|
||||||
if len(rem) > 0 {
|
if len(rem) > 0 {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"unmarshal failed, remainder\n%d %s",
|
"unmarshal failed, remainder\n%d %s",
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func (en *Response) Unmarshal(b []byte) (r []byte, err error) {
|
|||||||
r = b
|
r = b
|
||||||
var inID, inCount bool
|
var inID, inCount bool
|
||||||
for ; len(r) > 0; r = r[1:] {
|
for ; len(r) > 0; r = r[1:] {
|
||||||
// first we should be finding a subscription Id
|
// first we should be finding a subscription ID
|
||||||
if !inID && r[0] == '"' {
|
if !inID && r[0] == '"' {
|
||||||
r = r[1:]
|
r = r[1:]
|
||||||
// so we don't do this twice
|
// so we don't do this twice
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestMarshalUnmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
req := NewFrom(s)
|
req := NewFrom(s)
|
||||||
rb = req.Marshal(rb)
|
rb = req.Marshal(rb)
|
||||||
// log.I.Ln(req.Id)
|
// log.I.Ln(req.ID)
|
||||||
rb1 = rb1[:len(rb)]
|
rb1 = rb1[:len(rb)]
|
||||||
copy(rb1, rb)
|
copy(rb1, rb)
|
||||||
var rem []byte
|
var rem []byte
|
||||||
@@ -35,7 +35,7 @@ func TestMarshalUnmarshal(t *testing.T) {
|
|||||||
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// log.I.Ln(req2.Id)
|
// log.I.Ln(req2.ID)
|
||||||
if len(rem) > 0 {
|
if len(rem) > 0 {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"unmarshal failed, remainder\n%d %s",
|
"unmarshal failed, remainder\n%d %s",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func NewSubmissionWith(ev *event.E) *Submission { return &Submission{E: ev} }
|
|||||||
// Label returns the label of a event eventenvelope.Submission envelope.
|
// Label returns the label of a event eventenvelope.Submission envelope.
|
||||||
func (en *Submission) Label() string { return L }
|
func (en *Submission) Label() string { return L }
|
||||||
|
|
||||||
func (en *Submission) Id() []byte { return en.E.Id }
|
func (en *Submission) Id() []byte { return en.E.ID }
|
||||||
|
|
||||||
// Write the Submission to a provided io.Writer.
|
// Write the Submission to a provided io.Writer.
|
||||||
func (en *Submission) Write(w io.Writer) (err error) {
|
func (en *Submission) Write(w io.Writer) (err error) {
|
||||||
@@ -104,7 +104,7 @@ func NewResultWith[V string | []byte](s V, ev *event.E) (
|
|||||||
return &Result{subscription.MustNew(s), ev}, nil
|
return &Result{subscription.MustNew(s), ev}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (en *Result) Id() []byte { return en.Event.Id }
|
func (en *Result) Id() []byte { return en.Event.ID }
|
||||||
|
|
||||||
// Label returns the label of a event eventenvelope.Result envelope.
|
// Label returns the label of a event eventenvelope.Result envelope.
|
||||||
func (en *Result) Label() string { return L }
|
func (en *Result) Label() string { return L }
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestMarshalUnmarshal(t *testing.T) {
|
|||||||
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
if rem, err = req2.Unmarshal(rb); chk.E(err) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// log.I.Ln(req2.Id)
|
// log.I.Ln(req2.ID)
|
||||||
if len(rem) > 0 {
|
if len(rem) > 0 {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"unmarshal failed, remainder\n%d %s",
|
"unmarshal failed, remainder\n%d %s",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func NewFrom[V string | []byte](eid V, ok bool, msg ...V) *T {
|
|||||||
}
|
}
|
||||||
if len(eid) != sha256.Size {
|
if len(eid) != sha256.Size {
|
||||||
log.W.F(
|
log.W.F(
|
||||||
"event Id unexpected length, expect %d got %d",
|
"event ID unexpected length, expect %d got %d",
|
||||||
len(eid), sha256.Size,
|
len(eid), sha256.Size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func (en *T) Unmarshal(b []byte) (r []byte, err error) {
|
|||||||
}
|
}
|
||||||
if len(idHex) != sha256.Size {
|
if len(idHex) != sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"invalid size for Id, require %d got %d",
|
"invalid size for ID, require %d got %d",
|
||||||
len(idHex), sha256.Size,
|
len(idHex), sha256.Size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
// MarshalBinary writes a binary encoding of an event.
|
// MarshalBinary writes a binary encoding of an event.
|
||||||
//
|
//
|
||||||
// [ 32 bytes Id ]
|
// [ 32 bytes ID ]
|
||||||
// [ 32 bytes Pubkey ]
|
// [ 32 bytes Pubkey ]
|
||||||
// [ varint CreatedAt ]
|
// [ varint CreatedAt ]
|
||||||
// [ 2 bytes Kind ]
|
// [ 2 bytes Kind ]
|
||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
// [ varint Content length ]
|
// [ varint Content length ]
|
||||||
// [ 64 bytes Sig ]
|
// [ 64 bytes Sig ]
|
||||||
func (ev *E) MarshalBinary(w io.Writer) {
|
func (ev *E) MarshalBinary(w io.Writer) {
|
||||||
_, _ = w.Write(ev.Id)
|
_, _ = w.Write(ev.ID)
|
||||||
_, _ = w.Write(ev.Pubkey)
|
_, _ = w.Write(ev.Pubkey)
|
||||||
varint.Encode(w, uint64(ev.CreatedAt.V))
|
varint.Encode(w, uint64(ev.CreatedAt.V))
|
||||||
varint.Encode(w, uint64(ev.Kind.K))
|
varint.Encode(w, uint64(ev.Kind.K))
|
||||||
@@ -46,8 +46,8 @@ func (ev *E) MarshalBinary(w io.Writer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ev *E) UnmarshalBinary(r io.Reader) (err error) {
|
func (ev *E) UnmarshalBinary(r io.Reader) (err error) {
|
||||||
ev.Id = make([]byte, 32)
|
ev.ID = make([]byte, 32)
|
||||||
if _, err = r.Read(ev.Id); chk.E(err) {
|
if _, err = r.Read(ev.ID); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ev.Pubkey = make([]byte, 32)
|
ev.Pubkey = make([]byte, 32)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ToCanonical converts the event to the canonical encoding used to derive the
|
// ToCanonical converts the event to the canonical encoding used to derive the
|
||||||
// event Id.
|
// event ID.
|
||||||
func (ev *E) ToCanonical(dst []byte) (b []byte) {
|
func (ev *E) ToCanonical(dst []byte) (b []byte) {
|
||||||
b = dst
|
b = dst
|
||||||
b = append(b, "[0,\""...)
|
b = append(b, "[0,\""...)
|
||||||
@@ -88,8 +88,8 @@ func (ev *E) FromCanonical(b []byte) (rem []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// create the event, use the Id hash to populate the Id
|
// create the event, use the ID hash to populate the ID
|
||||||
ev.Id = id
|
ev.ID = id
|
||||||
// unwrap the pubkey
|
// unwrap the pubkey
|
||||||
if v, ok := x[1].(*json.Hex); !ok {
|
if v, ok := x[1].(*json.Hex); !ok {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
can := ev.ToCanonical(nil)
|
can := ev.ToCanonical(nil)
|
||||||
eh := event.Hash(can)
|
eh := event.Hash(can)
|
||||||
eq := bytes.Equal(ev.Id, eh)
|
eq := bytes.Equal(ev.ID, eh)
|
||||||
if !eq {
|
if !eq {
|
||||||
_, err = fmt.Fprintf(ids, "%s\n", ev.Serialize())
|
_, err = fmt.Fprintf(ids, "%s\n", ev.Serialize())
|
||||||
if chk.E(err) {
|
if chk.E(err) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package event provides a codec for nostr events, for the wire format (with Id
|
// Package event provides a codec for nostr events, for the wire format (with ID
|
||||||
// and signature), for the canonical form, that is hashed to generate the Id,
|
// and signature), for the canonical form, that is hashed to generate the ID,
|
||||||
// and a fast binary form that uses io.Reader/io.Writer.
|
// and a fast binary form that uses io.Reader/io.Writer.
|
||||||
package event
|
package event
|
||||||
|
|
||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"orly.dev/pkg/encoders/tags"
|
"orly.dev/pkg/encoders/tags"
|
||||||
"orly.dev/pkg/encoders/text"
|
"orly.dev/pkg/encoders/text"
|
||||||
"orly.dev/pkg/encoders/timestamp"
|
"orly.dev/pkg/encoders/timestamp"
|
||||||
"orly.dev/pkg/encoders/unix"
|
|
||||||
"orly.dev/pkg/interfaces/signer"
|
"orly.dev/pkg/interfaces/signer"
|
||||||
"orly.dev/pkg/utils/chk"
|
"orly.dev/pkg/utils/chk"
|
||||||
"orly.dev/pkg/utils/errorf"
|
"orly.dev/pkg/utils/errorf"
|
||||||
@@ -24,8 +23,8 @@ import (
|
|||||||
// defines its JSON string-based format.
|
// defines its JSON string-based format.
|
||||||
type E struct {
|
type E struct {
|
||||||
|
|
||||||
// Id is the SHA256 hash of the canonical encoding of the event in binary format
|
// ID is the SHA256 hash of the canonical encoding of the event in binary format
|
||||||
Id []byte
|
ID []byte
|
||||||
|
|
||||||
// Pubkey is the public key of the event creator in binary format
|
// Pubkey is the public key of the event creator in binary format
|
||||||
Pubkey []byte
|
Pubkey []byte
|
||||||
@@ -38,14 +37,14 @@ type E struct {
|
|||||||
Kind *kind.T
|
Kind *kind.T
|
||||||
|
|
||||||
// Tags are a list of tags, which are a list of strings usually structured
|
// Tags are a list of tags, which are a list of strings usually structured
|
||||||
// as a 3 layer scheme indicating specific features of an event.
|
// as a 3-layer scheme indicating specific features of an event.
|
||||||
Tags *tags.T
|
Tags *tags.T
|
||||||
|
|
||||||
// Content is an arbitrary string that can contain anything, but usually
|
// Content is an arbitrary string that can contain anything, but usually
|
||||||
// conforming to a specification relating to the Kind and the Tags.
|
// conforming to a specification relating to the Kind and the Tags.
|
||||||
Content []byte
|
Content []byte
|
||||||
|
|
||||||
// Sig is the signature on the Id hash that validates as coming from the
|
// Sig is the signature on the ID hash that validates as coming from the
|
||||||
// Pubkey in binary format.
|
// Pubkey in binary format.
|
||||||
Sig []byte
|
Sig []byte
|
||||||
}
|
}
|
||||||
@@ -77,19 +76,24 @@ func (ev *E) SerializeIndented() (b []byte) {
|
|||||||
return ev.MarshalWithWhitespace(nil, true)
|
return ev.MarshalWithWhitespace(nil, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventId returns the event.E Id as an eventid.T.
|
// EventId returns the event.E ID as an eventid.T.
|
||||||
func (ev *E) EventId() (eid *eventid.T) {
|
func (ev *E) EventId() (eid *eventid.T) {
|
||||||
return eventid.NewWith(ev.Id)
|
return eventid.NewWith(ev.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stringy/numbery functions for retarded other libraries
|
// stringy/numbery functions for retarded other libraries
|
||||||
|
|
||||||
// IdString returns the event Id as a hex-encoded string.
|
// IdString returns the event ID as a hex-encoded string.
|
||||||
func (ev *E) IdString() (s string) { return hex.Enc(ev.Id) }
|
func (ev *E) IdString() (s string) { return hex.Enc(ev.ID) }
|
||||||
|
|
||||||
|
func (ev *E) Id() []byte { return ev.ID }
|
||||||
|
|
||||||
// CreatedAtInt64 returns the created_at timestamp as a standard int64.
|
// CreatedAtInt64 returns the created_at timestamp as a standard int64.
|
||||||
func (ev *E) CreatedAtInt64() (i int64) { return ev.CreatedAt.I64() }
|
func (ev *E) CreatedAtInt64() (i int64) { return ev.CreatedAt.I64() }
|
||||||
|
|
||||||
|
// KindInt returns the kind as an int, as is often needed for JSON.
|
||||||
|
func (ev *E) KindInt() (i int) { return int(ev.Kind.K) }
|
||||||
|
|
||||||
// KindInt32 returns the kind as an int32, as is often needed for JSON.
|
// KindInt32 returns the kind as an int32, as is often needed for JSON.
|
||||||
func (ev *E) KindInt32() (i int32) { return int32(ev.Kind.K) }
|
func (ev *E) KindInt32() (i int32) { return int32(ev.Kind.K) }
|
||||||
|
|
||||||
@@ -107,13 +111,13 @@ func (ev *E) ContentString() (s string) { return string(ev.Content) }
|
|||||||
|
|
||||||
// J is an event.E encoded in more basic types than used in this library.
|
// J is an event.E encoded in more basic types than used in this library.
|
||||||
type J struct {
|
type J struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id" doc:"event id (SHA256 hash of canonical form of event, 64 characters hex)"`
|
||||||
Pubkey string `json:"pubkey"`
|
Pubkey string `json:"pubkey" doc:"public key of author of event, required to verify signature (BIP-340 Schnorr public key, 64 characters hex)"`
|
||||||
CreatedAt unix.Time `json:"created_at"`
|
CreatedAt int64 `json:"created_at" doc:"unix timestamp of time when event was created"`
|
||||||
Kind int32 `json:"kind"`
|
Kind int `json:"kind" doc:"kind number of event"`
|
||||||
Tags [][]string `json:"tags"`
|
Tags [][]string `json:"tags" doc:"tags that add metadata to the event"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content" doc:"content of event"`
|
||||||
Sig string `json:"sig"`
|
Sig string `json:"sig" doc:"signature of event (BIP-340 schnorr signature, 128 characters hex)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToEventJ converts an event.E into an event.J.
|
// ToEventJ converts an event.E into an event.J.
|
||||||
@@ -121,17 +125,17 @@ func (ev *E) ToEventJ() (j *J) {
|
|||||||
j = &J{}
|
j = &J{}
|
||||||
j.Id = ev.IdString()
|
j.Id = ev.IdString()
|
||||||
j.Pubkey = ev.PubKeyString()
|
j.Pubkey = ev.PubKeyString()
|
||||||
j.CreatedAt = unix.Time{ev.CreatedAt.Time()}
|
j.CreatedAt = ev.CreatedAt.I64()
|
||||||
j.Kind = ev.KindInt32()
|
j.Kind = ev.KindInt()
|
||||||
j.Content = ev.ContentString()
|
j.Content = ev.ContentString()
|
||||||
j.Tags = ev.Tags.ToStringsSlice()
|
j.Tags = ev.Tags.ToStringsSlice()
|
||||||
j.Sig = ev.SigString()
|
j.Sig = ev.SigString()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IdFromString decodes an event ID and loads it into an event.E Id.
|
// IdFromString decodes an event ID and loads it into an event.E ID.
|
||||||
func (ev *E) IdFromString(s string) (err error) {
|
func (ev *E) IdFromString(s string) (err error) {
|
||||||
ev.Id, err = hex.Dec(s)
|
ev.ID, err = hex.Dec(s)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +153,13 @@ func (ev *E) KindFromInt32(i int32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KindFromInt encodes an int representation of a kind.T into an event.E.
|
||||||
|
func (ev *E) KindFromInt(i int) {
|
||||||
|
ev.Kind = &kind.T{}
|
||||||
|
ev.Kind.K = uint16(i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// PubKeyFromString decodes a hex-encoded string into the event.E Pubkey field.
|
// PubKeyFromString decodes a hex-encoded string into the event.E Pubkey field.
|
||||||
func (ev *E) PubKeyFromString(s string) (err error) {
|
func (ev *E) PubKeyFromString(s string) (err error) {
|
||||||
if len(s) != 2*schnorr.PubKeyBytesLen {
|
if len(s) != 2*schnorr.PubKeyBytesLen {
|
||||||
@@ -198,8 +209,8 @@ func (e J) ToEvent() (ev *E, err error) {
|
|||||||
if err = ev.IdFromString(e.Id); chk.E(err) {
|
if err = ev.IdFromString(e.Id); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ev.CreatedAtFromInt64(e.CreatedAt.Unix())
|
ev.CreatedAtFromInt64(e.CreatedAt)
|
||||||
ev.KindFromInt32(e.Kind)
|
ev.KindFromInt(e.Kind)
|
||||||
if err = ev.PubKeyFromString(e.Pubkey); chk.E(err) {
|
if err = ev.PubKeyFromString(e.Pubkey); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (ev *E) Marshal(dst []byte) (b []byte) {
|
|||||||
func (ev *E) MarshalWithWhitespace(dst []byte, on bool) (b []byte) {
|
func (ev *E) MarshalWithWhitespace(dst []byte, on bool) (b []byte) {
|
||||||
// open parentheses
|
// open parentheses
|
||||||
dst = append(dst, '{')
|
dst = append(dst, '{')
|
||||||
// Id
|
// ID
|
||||||
if on {
|
if on {
|
||||||
dst = append(dst, '\n', '\t')
|
dst = append(dst, '\n', '\t')
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func (ev *E) MarshalWithWhitespace(dst []byte, on bool) (b []byte) {
|
|||||||
if on {
|
if on {
|
||||||
dst = append(dst, ' ')
|
dst = append(dst, ' ')
|
||||||
}
|
}
|
||||||
dst = text2.AppendQuote(dst, ev.Id, hex.EncAppend)
|
dst = text2.AppendQuote(dst, ev.ID, hex.EncAppend)
|
||||||
dst = append(dst, ',')
|
dst = append(dst, ',')
|
||||||
// Pubkey
|
// Pubkey
|
||||||
if on {
|
if on {
|
||||||
@@ -188,12 +188,12 @@ InVal:
|
|||||||
}
|
}
|
||||||
if len(id) != sha256.Size {
|
if len(id) != sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"invalid Id, require %d got %d", sha256.Size,
|
"invalid ID, require %d got %d", sha256.Size,
|
||||||
len(id),
|
len(id),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ev.Id = id
|
ev.ID = id
|
||||||
goto BetweenKV
|
goto BetweenKV
|
||||||
case jPubkey[0]:
|
case jPubkey[0]:
|
||||||
if !bytes.Equal(jPubkey, key) {
|
if !bytes.Equal(jPubkey, key) {
|
||||||
|
|||||||
@@ -58,177 +58,211 @@ func compareTags(t *testing.T, expected, actual *tags.T, context string) {
|
|||||||
// tags with fields containing escaped JSON that has been escaped using NostrEscape.
|
// tags with fields containing escaped JSON that has been escaped using NostrEscape.
|
||||||
func TestUnmarshalEscapedJSONInTags(t *testing.T) {
|
func TestUnmarshalEscapedJSONInTags(t *testing.T) {
|
||||||
// Test 1: Tag with a field containing escaped JSON
|
// Test 1: Tag with a field containing escaped JSON
|
||||||
t.Run("SimpleEscapedJSON", func(t *testing.T) {
|
t.Run(
|
||||||
// Create a tag with a field containing JSON that needs escaping
|
"SimpleEscapedJSON", func(t *testing.T) {
|
||||||
jsonContent := `{"key":"value","nested":{"array":[1,2,3]}}`
|
// Create a tag with a field containing JSON that needs escaping
|
||||||
|
jsonContent := `{"key":"value","nested":{"array":[1,2,3]}}`
|
||||||
|
|
||||||
// Create the event with the tag containing JSON
|
// Create the event with the tag containing JSON
|
||||||
originalEvent := &E{
|
originalEvent := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
Tags: tags.New(),
|
Tags: tags.New(),
|
||||||
Content: []byte("Event with JSON in tag"),
|
Content: []byte("Event with JSON in tag"),
|
||||||
Sig: bytes.Repeat([]byte{0x03}, 64),
|
Sig: bytes.Repeat([]byte{0x03}, 64),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a tag with JSON content
|
// Add a tag with JSON content
|
||||||
jsonTag := tag.New("j", jsonContent)
|
jsonTag := tag.New("j", jsonContent)
|
||||||
originalEvent.Tags.AppendTags(jsonTag)
|
originalEvent.Tags.AppendTags(jsonTag)
|
||||||
|
|
||||||
// Marshal the event
|
// Marshal the event
|
||||||
marshaled := originalEvent.Marshal(nil)
|
marshaled := originalEvent.Marshal(nil)
|
||||||
|
|
||||||
// Unmarshal back into a new event
|
// Unmarshal back into a new event
|
||||||
unmarshaledEvent := &E{}
|
unmarshaledEvent := &E{}
|
||||||
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to unmarshal event with JSON in tag: %v", err)
|
t.Fatalf("Failed to unmarshal event with JSON in tag: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the tag was correctly unmarshaled
|
// Verify the tag was correctly unmarshaled
|
||||||
if unmarshaledEvent.Tags.Len() != 1 {
|
if unmarshaledEvent.Tags.Len() != 1 {
|
||||||
t.Fatalf("Expected 1 tag, got %d", unmarshaledEvent.Tags.Len())
|
t.Fatalf("Expected 1 tag, got %d", unmarshaledEvent.Tags.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
||||||
if unmarshaledTag.Len() != 2 {
|
if unmarshaledTag.Len() != 2 {
|
||||||
t.Fatalf("Expected tag with 2 elements, got %d", unmarshaledTag.Len())
|
t.Fatalf(
|
||||||
}
|
"Expected tag with 2 elements, got %d", unmarshaledTag.Len(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if string(unmarshaledTag.B(0)) != "j" {
|
if string(unmarshaledTag.B(0)) != "j" {
|
||||||
t.Errorf("Expected tag key 'j', got '%s'", unmarshaledTag.B(0))
|
t.Errorf("Expected tag key 'j', got '%s'", unmarshaledTag.B(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(unmarshaledTag.B(1)) != jsonContent {
|
if string(unmarshaledTag.B(1)) != jsonContent {
|
||||||
t.Errorf("Expected tag value '%s', got '%s'", jsonContent, unmarshaledTag.B(1))
|
t.Errorf(
|
||||||
}
|
"Expected tag value '%s', got '%s'", jsonContent,
|
||||||
})
|
unmarshaledTag.B(1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Test 2: Tag with a field containing escaped JSON with special characters
|
// Test 2: Tag with a field containing escaped JSON with special characters
|
||||||
t.Run("EscapedJSONWithSpecialChars", func(t *testing.T) {
|
t.Run(
|
||||||
// JSON with characters that need escaping: quotes, backslashes, control chars
|
"EscapedJSONWithSpecialChars", func(t *testing.T) {
|
||||||
jsonContent := `{"text":"This has \"quotes\" and \\ backslashes","newlines":"\n\r\t"}`
|
// JSON with characters that need escaping: quotes, backslashes, control chars
|
||||||
|
jsonContent := `{"text":"This has \"quotes\" and \\ backslashes","newlines":"\n\r\t"}`
|
||||||
|
|
||||||
// Create the event with the tag containing JSON with special chars
|
// Create the event with the tag containing JSON with special chars
|
||||||
originalEvent := &E{
|
originalEvent := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
Tags: tags.New(),
|
Tags: tags.New(),
|
||||||
Content: []byte("Event with JSON containing special chars in tag"),
|
Content: []byte("Event with JSON containing special chars in tag"),
|
||||||
Sig: bytes.Repeat([]byte{0x03}, 64),
|
Sig: bytes.Repeat([]byte{0x03}, 64),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a tag with JSON content containing special chars
|
// Add a tag with JSON content containing special chars
|
||||||
jsonTag := tag.New("j", jsonContent)
|
jsonTag := tag.New("j", jsonContent)
|
||||||
originalEvent.Tags.AppendTags(jsonTag)
|
originalEvent.Tags.AppendTags(jsonTag)
|
||||||
|
|
||||||
// Marshal the event
|
// Marshal the event
|
||||||
marshaled := originalEvent.Marshal(nil)
|
marshaled := originalEvent.Marshal(nil)
|
||||||
|
|
||||||
// Unmarshal back into a new event
|
// Unmarshal back into a new event
|
||||||
unmarshaledEvent := &E{}
|
unmarshaledEvent := &E{}
|
||||||
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to unmarshal event with JSON containing special chars: %v", err)
|
t.Fatalf(
|
||||||
}
|
"Failed to unmarshal event with JSON containing special chars: %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the tag was correctly unmarshaled
|
// Verify the tag was correctly unmarshaled
|
||||||
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
||||||
if string(unmarshaledTag.B(1)) != jsonContent {
|
if string(unmarshaledTag.B(1)) != jsonContent {
|
||||||
t.Errorf("Expected tag value '%s', got '%s'", jsonContent, unmarshaledTag.B(1))
|
t.Errorf(
|
||||||
}
|
"Expected tag value '%s', got '%s'", jsonContent,
|
||||||
})
|
unmarshaledTag.B(1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Test 3: Tag with nested JSON that contains already escaped content
|
// Test 3: Tag with nested JSON that contains already escaped content
|
||||||
t.Run("NestedEscapedJSON", func(t *testing.T) {
|
t.Run(
|
||||||
// JSON with already escaped content
|
"NestedEscapedJSON", func(t *testing.T) {
|
||||||
jsonContent := `{"escaped":"This JSON contains \\\"already escaped\\\" content"}`
|
// JSON with already escaped content
|
||||||
|
jsonContent := `{"escaped":"This JSON contains \\\"already escaped\\\" content"}`
|
||||||
|
|
||||||
// Create the event with the tag containing nested escaped JSON
|
// Create the event with the tag containing nested escaped JSON
|
||||||
originalEvent := &E{
|
originalEvent := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
Tags: tags.New(),
|
Tags: tags.New(),
|
||||||
Content: []byte("Event with nested escaped JSON in tag"),
|
Content: []byte("Event with nested escaped JSON in tag"),
|
||||||
Sig: bytes.Repeat([]byte{0x03}, 64),
|
Sig: bytes.Repeat([]byte{0x03}, 64),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a tag with nested escaped JSON content
|
// Add a tag with nested escaped JSON content
|
||||||
jsonTag := tag.New("j", jsonContent)
|
jsonTag := tag.New("j", jsonContent)
|
||||||
originalEvent.Tags.AppendTags(jsonTag)
|
originalEvent.Tags.AppendTags(jsonTag)
|
||||||
|
|
||||||
// Marshal the event
|
// Marshal the event
|
||||||
marshaled := originalEvent.Marshal(nil)
|
marshaled := originalEvent.Marshal(nil)
|
||||||
|
|
||||||
// Unmarshal back into a new event
|
// Unmarshal back into a new event
|
||||||
unmarshaledEvent := &E{}
|
unmarshaledEvent := &E{}
|
||||||
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to unmarshal event with nested escaped JSON: %v", err)
|
t.Fatalf(
|
||||||
}
|
"Failed to unmarshal event with nested escaped JSON: %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the tag was correctly unmarshaled
|
// Verify the tag was correctly unmarshaled
|
||||||
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
||||||
if string(unmarshaledTag.B(1)) != jsonContent {
|
if string(unmarshaledTag.B(1)) != jsonContent {
|
||||||
t.Errorf("Expected tag value '%s', got '%s'", jsonContent, unmarshaledTag.B(1))
|
t.Errorf(
|
||||||
}
|
"Expected tag value '%s', got '%s'", jsonContent,
|
||||||
})
|
unmarshaledTag.B(1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Test 4: Tag with JSON that has been explicitly escaped using NostrEscape
|
// Test 4: Tag with JSON that has been explicitly escaped using NostrEscape
|
||||||
t.Run("ExplicitlyEscapedJSON", func(t *testing.T) {
|
t.Run(
|
||||||
// Original JSON with characters that need escaping
|
"ExplicitlyEscapedJSON", func(t *testing.T) {
|
||||||
originalJSON := []byte(`{"key":"value with "quotes"","nested":{"array":[1,2,3],"special":"\n\r\t"}}`)
|
// Original JSON with characters that need escaping
|
||||||
|
originalJSON := []byte(`{"key":"value with "quotes"","nested":{"array":[1,2,3],"special":"\n\r\t"}}`)
|
||||||
|
|
||||||
// Explicitly escape the JSON using NostrEscape
|
// Explicitly escape the JSON using NostrEscape
|
||||||
escapedJSON := make([]byte, 0, len(originalJSON)*2)
|
escapedJSON := make([]byte, 0, len(originalJSON)*2)
|
||||||
escapedJSON = text2.NostrEscape(escapedJSON, originalJSON)
|
escapedJSON = text2.NostrEscape(escapedJSON, originalJSON)
|
||||||
|
|
||||||
// Create the event with the tag containing explicitly escaped JSON
|
// Create the event with the tag containing explicitly escaped JSON
|
||||||
originalEvent := &E{
|
originalEvent := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
Tags: tags.New(),
|
Tags: tags.New(),
|
||||||
Content: []byte("Event with explicitly escaped JSON in tag"),
|
Content: []byte("Event with explicitly escaped JSON in tag"),
|
||||||
Sig: bytes.Repeat([]byte{0x03}, 64),
|
Sig: bytes.Repeat([]byte{0x03}, 64),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a tag with the explicitly escaped JSON content
|
// Add a tag with the explicitly escaped JSON content
|
||||||
jsonTag := tag.New("j", string(escapedJSON))
|
jsonTag := tag.New("j", string(escapedJSON))
|
||||||
originalEvent.Tags.AppendTags(jsonTag)
|
originalEvent.Tags.AppendTags(jsonTag)
|
||||||
|
|
||||||
// Marshal the event
|
// Marshal the event
|
||||||
marshaled := originalEvent.Marshal(nil)
|
marshaled := originalEvent.Marshal(nil)
|
||||||
|
|
||||||
// Unmarshal back into a new event
|
// Unmarshal back into a new event
|
||||||
unmarshaledEvent := &E{}
|
unmarshaledEvent := &E{}
|
||||||
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
_, err := unmarshaledEvent.Unmarshal(marshaled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to unmarshal event with explicitly escaped JSON: %v", err)
|
t.Fatalf(
|
||||||
}
|
"Failed to unmarshal event with explicitly escaped JSON: %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the tag was correctly unmarshaled
|
// Verify the tag was correctly unmarshaled
|
||||||
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
unmarshaledTag := unmarshaledEvent.Tags.GetTagElement(0)
|
||||||
if string(unmarshaledTag.B(1)) != string(escapedJSON) {
|
if string(unmarshaledTag.B(1)) != string(escapedJSON) {
|
||||||
t.Errorf("Expected tag value '%s', got '%s'", string(escapedJSON), unmarshaledTag.B(1))
|
t.Errorf(
|
||||||
}
|
"Expected tag value '%s', got '%s'", string(escapedJSON),
|
||||||
|
unmarshaledTag.B(1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Unescape the unmarshaled JSON to verify it matches the original
|
// Unescape the unmarshaled JSON to verify it matches the original
|
||||||
unescapedJSON := make([]byte, len(unmarshaledTag.B(1)))
|
unescapedJSON := make([]byte, len(unmarshaledTag.B(1)))
|
||||||
copy(unescapedJSON, unmarshaledTag.B(1))
|
copy(unescapedJSON, unmarshaledTag.B(1))
|
||||||
unescapedJSON = text2.NostrUnescape(unescapedJSON)
|
unescapedJSON = text2.NostrUnescape(unescapedJSON)
|
||||||
|
|
||||||
if string(unescapedJSON) != string(originalJSON) {
|
if string(unescapedJSON) != string(originalJSON) {
|
||||||
t.Errorf("Unescaped JSON doesn't match original. Expected '%s', got '%s'", string(originalJSON), string(unescapedJSON))
|
t.Errorf(
|
||||||
}
|
"Unescaped JSON doesn't match original. Expected '%s', got '%s'",
|
||||||
})
|
string(originalJSON), string(unescapedJSON),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalTags(t *testing.T) {
|
func TestUnmarshalTags(t *testing.T) {
|
||||||
@@ -238,7 +272,7 @@ func TestUnmarshalTags(t *testing.T) {
|
|||||||
jsonWithEmptyTags := []byte(`{"id":"0101010101010101010101010101010101010101010101010101010101010101","pubkey":"0202020202020202020202020202020202020202020202020202020202020202","created_at":1609459200,"kind":1,"tags":[],"content":"This is a test event","sig":"03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"}`)
|
jsonWithEmptyTags := []byte(`{"id":"0101010101010101010101010101010101010101010101010101010101010101","pubkey":"0202020202020202020202020202020202020202020202020202020202020202","created_at":1609459200,"kind":1,"tags":[],"content":"This is a test event","sig":"03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"}`)
|
||||||
|
|
||||||
expected := &E{
|
expected := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
@@ -263,7 +297,7 @@ func TestUnmarshalTags(t *testing.T) {
|
|||||||
jsonWithSimpleTags := []byte(`{"id":"0101010101010101010101010101010101010101010101010101010101010101","pubkey":"0202020202020202020202020202020202020202020202020202020202020202","created_at":1609459200,"kind":1,"tags":[["e","1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"],["p","abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"]],"content":"This is a test event","sig":"03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"}`)
|
jsonWithSimpleTags := []byte(`{"id":"0101010101010101010101010101010101010101010101010101010101010101","pubkey":"0202020202020202020202020202020202020202020202020202020202020202","created_at":1609459200,"kind":1,"tags":[["e","1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"],["p","abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"]],"content":"This is a test event","sig":"03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"}`)
|
||||||
|
|
||||||
expected := &E{
|
expected := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
@@ -299,7 +333,7 @@ func TestUnmarshalTags(t *testing.T) {
|
|||||||
jsonWithComplexTags := []byte(`{"id":"0101010101010101010101010101010101010101010101010101010101010101","pubkey":"0202020202020202020202020202020202020202020202020202020202020202","created_at":1609459200,"kind":1,"tags":[["e","1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","wss://relay.example.com","root"],["p","abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890","wss://relay.example.com"],["t","hashtag","topic"]],"content":"This is a test event","sig":"03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"}`)
|
jsonWithComplexTags := []byte(`{"id":"0101010101010101010101010101010101010101010101010101010101010101","pubkey":"0202020202020202020202020202020202020202020202020202020202020202","created_at":1609459200,"kind":1,"tags":[["e","1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","wss://relay.example.com","root"],["p","abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890","wss://relay.example.com"],["t","hashtag","topic"]],"content":"This is a test event","sig":"03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"}`)
|
||||||
|
|
||||||
expected := &E{
|
expected := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
@@ -346,7 +380,7 @@ func TestUnmarshalTags(t *testing.T) {
|
|||||||
}`)
|
}`)
|
||||||
|
|
||||||
expected := &E{
|
expected := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
@@ -393,7 +427,7 @@ func TestUnmarshalTags(t *testing.T) {
|
|||||||
}`)
|
}`)
|
||||||
|
|
||||||
expected := &E{
|
expected := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32),
|
ID: bytes.Repeat([]byte{0x01}, 32),
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
Pubkey: bytes.Repeat([]byte{0x02}, 32),
|
||||||
CreatedAt: timestamp.FromUnix(1609459200),
|
CreatedAt: timestamp.FromUnix(1609459200),
|
||||||
Kind: kind.TextNote,
|
Kind: kind.TextNote,
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
|
|
||||||
// compareEvents compares two events and reports any differences
|
// compareEvents compares two events and reports any differences
|
||||||
func compareEvents(t *testing.T, expected, actual *E, context string) {
|
func compareEvents(t *testing.T, expected, actual *E, context string) {
|
||||||
if !bytes.Equal(expected.Id, actual.Id) {
|
if !bytes.Equal(expected.ID, actual.ID) {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"%s: Id mismatch: expected %s, got %s", context,
|
"%s: ID mismatch: expected %s, got %s", context,
|
||||||
hex.Enc(expected.Id), hex.Enc(actual.Id),
|
hex.Enc(expected.ID), hex.Enc(actual.ID),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(expected.Pubkey, actual.Pubkey) {
|
if !bytes.Equal(expected.Pubkey, actual.Pubkey) {
|
||||||
@@ -52,7 +52,7 @@ func compareEvents(t *testing.T, expected, actual *E, context string) {
|
|||||||
func TestMarshalUnmarshalWithWhitespace(t *testing.T) {
|
func TestMarshalUnmarshalWithWhitespace(t *testing.T) {
|
||||||
// Create a sample event with predefined values
|
// Create a sample event with predefined values
|
||||||
original := &E{
|
original := &E{
|
||||||
Id: bytes.Repeat([]byte{0x01}, 32), // 32 bytes of 0x01
|
ID: bytes.Repeat([]byte{0x01}, 32), // 32 bytes of 0x01
|
||||||
Pubkey: bytes.Repeat([]byte{0x02}, 32), // 32 bytes of 0x02
|
Pubkey: bytes.Repeat([]byte{0x02}, 32), // 32 bytes of 0x02
|
||||||
CreatedAt: timestamp.FromUnix(1609459200), // 2021-01-01 00:00:00 UTC
|
CreatedAt: timestamp.FromUnix(1609459200), // 2021-01-01 00:00:00 UTC
|
||||||
Kind: kind.TextNote, // Kind 1 (text note)
|
Kind: kind.TextNote, // Kind 1 (text note)
|
||||||
@@ -73,7 +73,7 @@ func TestMarshalUnmarshalWithWhitespace(t *testing.T) {
|
|||||||
// Test 2: Manually created JSON with extra whitespace
|
// Test 2: Manually created JSON with extra whitespace
|
||||||
jsonWithExtraWhitespace := []byte(`
|
jsonWithExtraWhitespace := []byte(`
|
||||||
{
|
{
|
||||||
"id": "` + hex.Enc(original.Id) + `",
|
"id": "` + hex.Enc(original.ID) + `",
|
||||||
"pubkey": "` + hex.Enc(original.Pubkey) + `",
|
"pubkey": "` + hex.Enc(original.Pubkey) + `",
|
||||||
"created_at": 1609459200,
|
"created_at": 1609459200,
|
||||||
"kind": 1,
|
"kind": 1,
|
||||||
@@ -93,7 +93,7 @@ func TestMarshalUnmarshalWithWhitespace(t *testing.T) {
|
|||||||
|
|
||||||
// Test 3: JSON with mixed whitespace (spaces, tabs, newlines)
|
// Test 3: JSON with mixed whitespace (spaces, tabs, newlines)
|
||||||
jsonWithMixedWhitespace := []byte(`{
|
jsonWithMixedWhitespace := []byte(`{
|
||||||
"id" : "` + hex.Enc(original.Id) + `",
|
"id" : "` + hex.Enc(original.ID) + `",
|
||||||
"pubkey": "` + hex.Enc(original.Pubkey) + `",
|
"pubkey": "` + hex.Enc(original.Pubkey) + `",
|
||||||
"created_at": 1609459200 ,
|
"created_at": 1609459200 ,
|
||||||
"kind":1,
|
"kind":1,
|
||||||
@@ -115,7 +115,7 @@ func TestMarshalUnmarshalWithWhitespace(t *testing.T) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
"id" : "` + hex.Enc(original.Id) + `" ,
|
"id" : "` + hex.Enc(original.ID) + `" ,
|
||||||
"pubkey" : "` + hex.Enc(original.Pubkey) + `" ,
|
"pubkey" : "` + hex.Enc(original.Pubkey) + `" ,
|
||||||
"created_at" : 1609459200 ,
|
"created_at" : 1609459200 ,
|
||||||
"kind" : 1 ,
|
"kind" : 1 ,
|
||||||
|
|||||||
203
pkg/encoders/event/readwriter.go
Normal file
203
pkg/encoders/event/readwriter.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"orly.dev/pkg/encoders/hex"
|
||||||
|
text2 "orly.dev/pkg/encoders/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalWrite writes the JSON representation of an event to an io.Writer. This
|
||||||
|
// is a version of MarshalWithWhitespace that writes to an io.Writer instead of
|
||||||
|
// appending to a slice, with whitespace disabled by default. It implements the
|
||||||
|
// codec.I interface.
|
||||||
|
func (ev *E) MarshalWrite(w io.Writer) (err error) {
|
||||||
|
return ev.MarshalWriteWithWhitespace(w, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalWriteWithWhitespace writes the JSON representation of an event to an
|
||||||
|
// io.Writer with optional whitespace formatting. If the 'on' flag is set to
|
||||||
|
// true, it adds tabs and newlines to make the JSON more readable for humans.
|
||||||
|
// This is a version of MarshalWithWhitespace that writes to an io.Writer
|
||||||
|
// instead of appending to a slice.
|
||||||
|
func (ev *E) MarshalWriteWithWhitespace(w io.Writer, on bool) (err error) {
|
||||||
|
// open parentheses
|
||||||
|
if _, err = w.Write([]byte{'{'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buf []byte
|
||||||
|
buf = text2.JSONKey(buf, jId)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.AppendQuote(buf, ev.ID, hex.EncAppend)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{','}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.JSONKey(buf, jPubkey)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.AppendQuote(buf, ev.Pubkey, hex.EncAppend)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{','}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.JSONKey(buf, jCreatedAt)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = ev.CreatedAt.Marshal(buf)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{','}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.JSONKey(buf, jKind)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = ev.Kind.Marshal(buf)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{','}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.JSONKey(buf, jTags)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
if on {
|
||||||
|
buf = ev.Tags.MarshalWithWhitespace(buf)
|
||||||
|
} else {
|
||||||
|
buf = ev.Tags.Marshal(buf)
|
||||||
|
}
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{','}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.JSONKey(buf, jContent)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.AppendQuote(buf, ev.Content, text2.NostrEscape)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{','}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n', '\t'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.JSONKey(buf, jSig)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{' '}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = text2.AppendQuote(buf, ev.Sig, hex.EncAppend)
|
||||||
|
if _, err = w.Write(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
if _, err = w.Write([]byte{'\n'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err = w.Write([]byte{'}'}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ev *E) UnmarshalRead(r io.Reader) (err error) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
@@ -12,12 +12,12 @@ import (
|
|||||||
// Sign the event using the signer.I. Uses github.com/bitcoin-core/secp256k1 if
|
// Sign the event using the signer.I. Uses github.com/bitcoin-core/secp256k1 if
|
||||||
// available for much faster signatures.
|
// available for much faster signatures.
|
||||||
//
|
//
|
||||||
// Note that this only populates the Pubkey, Id and Sig. The caller must
|
// Note that this only populates the Pubkey, ID and Sig. The caller must
|
||||||
// set the CreatedAt timestamp as intended.
|
// set the CreatedAt timestamp as intended.
|
||||||
func (ev *E) Sign(keys signer.I) (err error) {
|
func (ev *E) Sign(keys signer.I) (err error) {
|
||||||
ev.Pubkey = keys.Pub()
|
ev.Pubkey = keys.Pub()
|
||||||
ev.Id = ev.GetIDBytes()
|
ev.ID = ev.GetIDBytes()
|
||||||
if ev.Sig, err = keys.Sign(ev.Id); chk.E(err) {
|
if ev.Sig, err = keys.Sign(ev.ID); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -30,17 +30,17 @@ func (ev *E) Verify() (valid bool, err error) {
|
|||||||
if err = keys.InitPub(ev.Pubkey); chk.E(err) {
|
if err = keys.InitPub(ev.Pubkey); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if valid, err = keys.Verify(ev.Id, ev.Sig); chk.T(err) {
|
if valid, err = keys.Verify(ev.ID, ev.Sig); chk.T(err) {
|
||||||
// check that this isn't because of a bogus Id
|
// check that this isn't because of a bogus ID
|
||||||
id := ev.GetIDBytes()
|
id := ev.GetIDBytes()
|
||||||
if !bytes.Equal(id, ev.Id) {
|
if !bytes.Equal(id, ev.ID) {
|
||||||
log.E.Ln("event Id incorrect")
|
log.E.Ln("event ID incorrect")
|
||||||
ev.Id = id
|
ev.ID = id
|
||||||
err = nil
|
err = nil
|
||||||
if valid, err = keys.Verify(ev.Id, ev.Sig); chk.E(err) {
|
if valid, err = keys.Verify(ev.ID, ev.Sig); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = errorf.W("event Id incorrect but signature is valid on correct Id")
|
err = errorf.W("event ID incorrect but signature is valid on correct ID")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (ei *T) Set(b []byte) (err error) {
|
|||||||
}
|
}
|
||||||
if len(b) != sha256.Size {
|
if len(b) != sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"Id bytes incorrect size, got %d require %d",
|
"ID bytes incorrect size, got %d require %d",
|
||||||
len(b), sha256.Size,
|
len(b), sha256.Size,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -45,7 +45,7 @@ func (ei *T) Set(b []byte) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromBytes creates a new eventid.T from the raw event Id hash.
|
// NewFromBytes creates a new eventid.T from the raw event ID hash.
|
||||||
func NewFromBytes(b []byte) (ei *T, err error) {
|
func NewFromBytes(b []byte) (ei *T, err error) {
|
||||||
ei = New()
|
ei = New()
|
||||||
if err = ei.Set(b); chk.E(err) {
|
if err = ei.Set(b); chk.E(err) {
|
||||||
@@ -64,6 +64,9 @@ func (ei *T) String() string {
|
|||||||
|
|
||||||
// ByteString renders an eventid.T as bytes in ASCII hex.
|
// ByteString renders an eventid.T as bytes in ASCII hex.
|
||||||
func (ei *T) ByteString(src []byte) (b []byte) {
|
func (ei *T) ByteString(src []byte) (b []byte) {
|
||||||
|
if ei == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return hex.EncAppend(src, ei[:])
|
return hex.EncAppend(src, ei[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@ func (ei *T) Unmarshal(b []byte) (rem []byte, err error) {
|
|||||||
b = b[1 : 2*sha256.Size+1]
|
b = b[1 : 2*sha256.Size+1]
|
||||||
if len(b) != 2*sha256.Size {
|
if len(b) != 2*sha256.Size {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
"event Id hex incorrect size, got %d require %d",
|
"event ID hex incorrect size, got %d require %d",
|
||||||
len(b), 2*sha256.Size,
|
len(b), 2*sha256.Size,
|
||||||
)
|
)
|
||||||
log.E.Ln(string(b))
|
log.E.Ln(string(b))
|
||||||
@@ -123,7 +126,7 @@ func (ei *T) Unmarshal(b []byte) (rem []byte, err error) {
|
|||||||
func NewFromString(s string) (ei *T, err error) {
|
func NewFromString(s string) (ei *T, err error) {
|
||||||
if len(s) != 2*sha256.Size {
|
if len(s) != 2*sha256.Size {
|
||||||
return nil, errorf.E(
|
return nil, errorf.E(
|
||||||
"event Id hex wrong size, got %d require %d",
|
"event ID hex wrong size, got %d require %d",
|
||||||
len(s), 2*sha256.Size,
|
len(s), 2*sha256.Size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -134,7 +137,7 @@ func NewFromString(s string) (ei *T, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gen creates a fake pseudorandom generated event Id for tests.
|
// Gen creates a fake pseudorandom generated event ID for tests.
|
||||||
func Gen() (ei *T) {
|
func Gen() (ei *T) {
|
||||||
b := frand.Bytes(sha256.Size)
|
b := frand.Bytes(sha256.Size)
|
||||||
ei = &T{}
|
ei = &T{}
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ func (f *F) Matches(ev *event.E) bool {
|
|||||||
// log.F.ToSliceOfBytes("nil event")
|
// log.F.ToSliceOfBytes("nil event")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if f.Ids.Len() > 0 && !f.Ids.Contains(ev.Id) {
|
if f.Ids.Len() > 0 && !f.Ids.Contains(ev.ID) {
|
||||||
// log.F.ToSliceOfBytes("no ids in filter match event\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String())
|
// log.F.ToSliceOfBytes("no ids in filter match event\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// S is a simplified filter that only covers the nip-01 REQ filter minus the
|
// S is a simplified filter that only covers the nip-01 REQ filter minus the
|
||||||
// separate and superseding Id list. The search field is from a different NIP,
|
// separate and superseding ID list. The search field is from a different NIP,
|
||||||
// but it is a separate API for which reason it is also not here.
|
// but it is a separate API for which reason it is also not here.
|
||||||
type S struct {
|
type S struct {
|
||||||
Kinds *kinds.T `json:"kinds,omitempty"`
|
Kinds *kinds.T `json:"kinds,omitempty"`
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewId[V string | []byte](s V) (*Id, error) {
|
|||||||
// remove invalid return value
|
// remove invalid return value
|
||||||
si.T = si.T[:0]
|
si.T = si.T[:0]
|
||||||
return si, errorf.E(
|
return si, errorf.E(
|
||||||
"invalid subscription Id - length %d < 1 or > 64", len(si.T),
|
"invalid subscription ID - length %d < 1 or > 64", len(si.T),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ func (si *Id) Marshal(dst []byte) (b []byte) {
|
|||||||
ue := text.NostrEscape(nil, si.T)
|
ue := text.NostrEscape(nil, si.T)
|
||||||
if len(ue) < 1 || len(ue) > 64 {
|
if len(ue) < 1 || len(ue) > 64 {
|
||||||
log.E.F(
|
log.E.F(
|
||||||
"invalid subscription Id, must be between 1 and 64 "+
|
"invalid subscription ID, must be between 1 and 64 "+
|
||||||
"characters, got %d (possibly due to escaping)", len(ue),
|
"characters, got %d (possibly due to escaping)", len(ue),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package text
|
|||||||
//
|
//
|
||||||
// This is the efficient implementation based on the NIP-01 specification:
|
// This is the efficient implementation based on the NIP-01 specification:
|
||||||
//
|
//
|
||||||
// To prevent implementation differences from creating a different event Id for
|
// To prevent implementation differences from creating a different event ID for
|
||||||
// the same event, the following rules MUST be followed while serializing:
|
// the same event, the following rules MUST be followed while serializing:
|
||||||
//
|
//
|
||||||
// No whitespace, line breaks or other unnecessary formatting should be included
|
// No whitespace, line breaks or other unnecessary formatting should be included
|
||||||
@@ -90,7 +90,7 @@ func NostrUnescape(dst []byte) (b []byte) {
|
|||||||
dst[w] = '\r'
|
dst[w] = '\r'
|
||||||
w++
|
w++
|
||||||
|
|
||||||
// special cases for non-nip-01 specified json escapes (must be preserved for Id
|
// special cases for non-nip-01 specified json escapes (must be preserved for ID
|
||||||
// generation).
|
// generation).
|
||||||
case c == 'u':
|
case c == 'u':
|
||||||
dst[w] = '\\'
|
dst[w] = '\\'
|
||||||
@@ -103,7 +103,7 @@ func NostrUnescape(dst []byte) (b []byte) {
|
|||||||
dst[w] = '/'
|
dst[w] = '/'
|
||||||
w++
|
w++
|
||||||
|
|
||||||
// special case for octal escapes (must be preserved for Id generation).
|
// special case for octal escapes (must be preserved for ID generation).
|
||||||
case c >= '0' && c <= '9':
|
case c >= '0' && c <= '9':
|
||||||
dst[w] = '\\'
|
dst[w] = '\\'
|
||||||
w++
|
w++
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user