Add core packages, configuration system, and initial application structure
This commit is contained in:
270
app/config/config.go
Normal file
270
app/config/config.go
Normal file
@@ -0,0 +1,270 @@
|
||||
// Package config provides a go-simpler.org/env configuration table and helpers
|
||||
// for working with the list of key/value lists stored in .env files.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"go-simpler.org/env"
|
||||
lol "lol.mleku.dev"
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/version"
|
||||
)
|
||||
|
||||
// C holds application configuration settings loaded from environment variables
|
||||
// and default values. It defines parameters for app behaviour, storage
|
||||
// locations, logging, and network settings used across the relay service.
|
||||
type C struct {
|
||||
AppName string `env:"ORLY_APP_NAME" usage:"set a name to display on information about the relay" default:"ORLY"`
|
||||
DataDir string `env:"ORLY_DATA_DIR" usage:"storage location for the event store" default:"~/.local/share/ORLY"`
|
||||
Listen string `env:"ORLY_LISTEN" default:"0.0.0.0" usage:"network listen address"`
|
||||
Port int `env:"ORLY_PORT" default:"3334" usage:"port to listen on"`
|
||||
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
|
||||
Pprof string `env:"ORLY_PPROF" usage:"enable pprof in modes: cpu,memory,allocation"`
|
||||
}
|
||||
|
||||
// New creates and initializes a new configuration object for the relay
|
||||
// application
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - cfg: A pointer to the initialized configuration struct containing default
|
||||
// or environment-provided values
|
||||
//
|
||||
// - err: An error object that is non-nil if any operation during
|
||||
// initialization fails
|
||||
//
|
||||
// # Expected Behaviour:
|
||||
//
|
||||
// Initializes a new configuration instance by loading environment variables and
|
||||
// checking for a .env file in the default configuration directory. Sets logging
|
||||
// levels based on configuration values and returns the populated configuration
|
||||
// or an error if any step fails
|
||||
func New() (cfg *C, err error) {
|
||||
cfg = &C{}
|
||||
if err = env.Load(cfg, &env.Options{SliceSep: ","}); chk.T(err) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err)
|
||||
}
|
||||
PrintHelp(cfg, os.Stderr)
|
||||
os.Exit(0)
|
||||
}
|
||||
if cfg.DataDir == "" || strings.Contains(cfg.DataDir, "~") {
|
||||
cfg.DataDir = filepath.Join(xdg.DataHome, cfg.AppName)
|
||||
}
|
||||
if GetEnv() {
|
||||
PrintEnv(cfg, os.Stdout)
|
||||
os.Exit(0)
|
||||
}
|
||||
if HelpRequested() {
|
||||
PrintHelp(cfg, os.Stderr)
|
||||
os.Exit(0)
|
||||
}
|
||||
lol.SetLogLevel(cfg.LogLevel)
|
||||
return
|
||||
}
|
||||
|
||||
// HelpRequested determines if the command line arguments indicate a request for help
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - help: A boolean value indicating true if a help flag was detected in the
|
||||
// command line arguments, false otherwise
|
||||
//
|
||||
// # Expected Behaviour
|
||||
//
|
||||
// The function checks the first command line argument for common help flags and
|
||||
// returns true if any of them are present. Returns false if no help flag is found
|
||||
func HelpRequested() (help bool) {
|
||||
if len(os.Args) > 1 {
|
||||
switch strings.ToLower(os.Args[1]) {
|
||||
case "help", "-h", "--h", "-help", "--help", "?":
|
||||
help = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetEnv checks if the first command line argument is "env" and returns
|
||||
// whether the environment configuration should be printed.
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - requested: A boolean indicating true if the 'env' argument was
|
||||
// provided, false otherwise.
|
||||
//
|
||||
// # Expected Behaviour
|
||||
//
|
||||
// The function returns true when the first command line argument is "env"
|
||||
// (case-insensitive), signalling that the environment configuration should be
|
||||
// printed. Otherwise, it returns false.
|
||||
func GetEnv() (requested bool) {
|
||||
if len(os.Args) > 1 {
|
||||
switch strings.ToLower(os.Args[1]) {
|
||||
case "env":
|
||||
requested = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// KV is a key/value pair.
|
||||
type KV struct{ Key, Value string }
|
||||
|
||||
// KVSlice is a sortable slice of key/value pairs, designed for managing
|
||||
// configuration data and enabling operations like merging and sorting based on
|
||||
// keys.
|
||||
type KVSlice []KV
|
||||
|
||||
func (kv KVSlice) Len() int { return len(kv) }
|
||||
func (kv KVSlice) Less(i, j int) bool { return kv[i].Key < kv[j].Key }
|
||||
func (kv KVSlice) Swap(i, j int) { kv[i], kv[j] = kv[j], kv[i] }
|
||||
|
||||
// Compose merges two KVSlice instances into a new slice where key-value pairs
|
||||
// from the second slice override any duplicate keys from the first slice.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - kv2: The second KVSlice whose entries will be merged with the receiver.
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - out: A new KVSlice containing all entries from both slices, with keys
|
||||
// from kv2 taking precedence over keys from the receiver.
|
||||
//
|
||||
// # Expected Behaviour
|
||||
//
|
||||
// The method returns a new KVSlice that combines the contents of the receiver
|
||||
// and kv2. If any key exists in both slices, the value from kv2 is used. The
|
||||
// resulting slice remains sorted by keys as per the KVSlice implementation.
|
||||
func (kv KVSlice) Compose(kv2 KVSlice) (out KVSlice) {
|
||||
// duplicate the initial KVSlice
|
||||
for _, p := range kv {
|
||||
out = append(out, p)
|
||||
}
|
||||
out:
|
||||
for i, p := range kv2 {
|
||||
for j, q := range out {
|
||||
// if the key is repeated, replace the value
|
||||
if p.Key == q.Key {
|
||||
out[j].Value = kv2[i].Value
|
||||
continue out
|
||||
}
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EnvKV generates key/value pairs from a configuration object's struct tags
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - cfg: A configuration object whose struct fields are processed for env tags
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - m: A KVSlice containing key/value pairs derived from the config's env tags
|
||||
//
|
||||
// # Expected Behaviour
|
||||
//
|
||||
// Processes each field of the config object, extracting values tagged with
|
||||
// "env" and converting them to strings. Skips fields without an "env" tag.
|
||||
// Handles various value types including strings, integers, booleans, durations,
|
||||
// and string slices by joining elements with commas.
|
||||
func EnvKV(cfg any) (m KVSlice) {
|
||||
t := reflect.TypeOf(cfg)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
k := t.Field(i).Tag.Get("env")
|
||||
v := reflect.ValueOf(cfg).Field(i).Interface()
|
||||
var val string
|
||||
switch v.(type) {
|
||||
case string:
|
||||
val = v.(string)
|
||||
case int, bool, time.Duration:
|
||||
val = fmt.Sprint(v)
|
||||
case []string:
|
||||
arr := v.([]string)
|
||||
if len(arr) > 0 {
|
||||
val = strings.Join(arr, ",")
|
||||
}
|
||||
}
|
||||
// this can happen with embedded structs
|
||||
if k == "" {
|
||||
continue
|
||||
}
|
||||
m = append(m, KV{k, val})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PrintEnv outputs sorted environment key/value pairs from a configuration object
|
||||
// to the provided writer
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - cfg: Pointer to the configuration object containing env tags
|
||||
//
|
||||
// - printer: Destination for the output, typically an io.Writer implementation
|
||||
//
|
||||
// # Expected Behaviour
|
||||
//
|
||||
// Outputs each environment variable derived from the config's struct tags in
|
||||
// sorted order, formatted as "key=value\n" to the specified writer
|
||||
func PrintEnv(cfg *C, printer io.Writer) {
|
||||
kvs := EnvKV(*cfg)
|
||||
sort.Sort(kvs)
|
||||
for _, v := range kvs {
|
||||
_, _ = fmt.Fprintf(printer, "%s=%s\n", v.Key, v.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintHelp prints help information including application version, environment
|
||||
// variable configuration, and details about .env file handling to the provided
|
||||
// writer
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - cfg: Configuration object containing app name and config directory path
|
||||
//
|
||||
// - printer: Output destination for the help text
|
||||
//
|
||||
// # Expected Behaviour
|
||||
//
|
||||
// Prints application name and version followed by environment variable
|
||||
// configuration details, explains .env file behaviour including automatic
|
||||
// loading and custom path options, and displays current configuration values
|
||||
// using PrintEnv. Outputs all information to the specified writer
|
||||
func PrintHelp(cfg *C, printer io.Writer) {
|
||||
_, _ = fmt.Fprintf(
|
||||
printer,
|
||||
"%s %s\n\n", cfg.AppName, version.V,
|
||||
)
|
||||
_, _ = fmt.Fprintf(
|
||||
printer,
|
||||
`Usage: %s [env|help]
|
||||
|
||||
- env: print environment variables configuring %s
|
||||
- help: print this help text
|
||||
|
||||
`,
|
||||
cfg.AppName, cfg.AppName,
|
||||
)
|
||||
_, _ = fmt.Fprintf(
|
||||
printer,
|
||||
"Environment variables that configure %s:\n\n", cfg.AppName,
|
||||
)
|
||||
env.Usage(cfg, printer, &env.Options{SliceSep: ","})
|
||||
fmt.Fprintf(printer, "\ncurrent configuration:\n\n")
|
||||
PrintEnv(cfg, printer)
|
||||
fmt.Fprintln(printer)
|
||||
return
|
||||
}
|
||||
23
app/main.go
Normal file
23
app/main.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, cfg *config.C) (quit chan struct{}) {
|
||||
// shutdown handler
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.I.F("shutting down")
|
||||
close(quit)
|
||||
}
|
||||
}()
|
||||
// start listener
|
||||
|
||||
quit = make(chan struct{})
|
||||
return
|
||||
}
|
||||
7
go.mod
7
go.mod
@@ -3,15 +3,22 @@ module next.orly.dev
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/pkg/profile v1.7.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b
|
||||
go-simpler.org/env v0.12.0
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
||||
lol.mleku.dev v1.0.2
|
||||
lukechampine.com/frand v1.5.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
|
||||
25
go.sum
25
go.sum
@@ -1,27 +1,52 @@
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
|
||||
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg=
|
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ=
|
||||
go-simpler.org/env v0.12.0 h1:kt/lBts0J1kjWJAnB740goNdvwNxt5emhYngL0Fzufs=
|
||||
go-simpler.org/env v0.12.0/go.mod h1:cc/5Md9JCUM7LVLtN0HYjPTDcI3Q8TDaPlNTAlDU+WI=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lol.mleku.dev v1.0.2 h1:bSV1hHnkmt1hq+9nSvRwN6wgcI7itbM3XRZ4dMB438c=
|
||||
lol.mleku.dev v1.0.2/go.mod h1:DQ0WnmkntA9dPLCXgvtIgYt5G0HSqx3wSTLolHgWeLA=
|
||||
lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w=
|
||||
lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q=
|
||||
|
||||
38
main.go
Normal file
38
main.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app"
|
||||
"next.orly.dev/app/config"
|
||||
"next.orly.dev/pkg/version"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
var cfg *config.C
|
||||
if cfg, err = config.New(); chk.T(err) {
|
||||
}
|
||||
log.I.F("starting %s %s", cfg.AppName, version.V)
|
||||
startProfiler(cfg.Pprof)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
quit := app.Run(ctx, cfg)
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt)
|
||||
for {
|
||||
select {
|
||||
case <-sigs:
|
||||
fmt.Printf("\r")
|
||||
cancel()
|
||||
case <-quit:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
47
pkg/encoders/event/event.go
Normal file
47
pkg/encoders/event/event.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package event
|
||||
|
||||
// E is the primary datatype of nostr. This is the form of the structure that
|
||||
// defines its JSON string-based format.
|
||||
type E struct {
|
||||
|
||||
// ID is the SHA256 hash of the canonical encoding of the event in binary format
|
||||
ID []byte
|
||||
|
||||
// Pubkey is the public key of the event creator in binary format
|
||||
Pubkey []byte
|
||||
|
||||
// CreatedAt is the UNIX timestamp of the event according to the event
|
||||
// creator (never trust a timestamp!)
|
||||
CreatedAt int64
|
||||
|
||||
// Kind is the nostr protocol code for the type of event. See kind.T
|
||||
Kind uint16
|
||||
|
||||
// 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.
|
||||
Tags [][]byte
|
||||
|
||||
// Content is an arbitrary string that can contain anything, but usually
|
||||
// conforming to a specification relating to the Kind and the Tags.
|
||||
Content []byte
|
||||
|
||||
// Sig is the signature on the ID hash that validates as coming from the
|
||||
// Pubkey in binary format.
|
||||
Sig []byte
|
||||
}
|
||||
|
||||
// S is an array of event.E that sorts in reverse chronological order.
|
||||
type S []*E
|
||||
|
||||
// Len returns the length of the event.Es.
|
||||
func (ev S) Len() int { return len(ev) }
|
||||
|
||||
// Less returns whether the first is newer than the second (larger unix
|
||||
// timestamp).
|
||||
func (ev S) Less(i, j int) bool { return ev[i].CreatedAt > ev[j].CreatedAt }
|
||||
|
||||
// Swap two indexes of the event.Es with each other.
|
||||
func (ev S) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] }
|
||||
|
||||
// C is a channel that carries event.E.
|
||||
type C chan *E
|
||||
10
pkg/encoders/event/examples/eventcache.go
Normal file
10
pkg/encoders/event/examples/eventcache.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Package examples is an embedded jsonl format of a collection of events
|
||||
// intended to be used to test an event codec.
|
||||
package examples
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed out.jsonl
|
||||
var Cache []byte
|
||||
11596
pkg/encoders/event/examples/out.jsonl
Normal file
11596
pkg/encoders/event/examples/out.jsonl
Normal file
File diff suppressed because one or more lines are too long
1
pkg/encoders/ints/base10k.txt
Normal file
1
pkg/encoders/ints/base10k.txt
Normal file
File diff suppressed because one or more lines are too long
20
pkg/encoders/ints/gen/pregen.go
Normal file
20
pkg/encoders/ints/gen/pregen.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Package main is a generator for the base10000 (4 digit) encoding of the ints
|
||||
// library.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, err := os.Create("pkg/ints/base10k.txt")
|
||||
if chk.E(err) {
|
||||
panic(err)
|
||||
}
|
||||
for i := range 10000 {
|
||||
fmt.Fprintf(fh, "%04d", i)
|
||||
}
|
||||
}
|
||||
135
pkg/encoders/ints/ints.go
Normal file
135
pkg/encoders/ints/ints.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Package ints is an optimised encoder for decimal numbers in ASCII format,
|
||||
// that simplifies and accelerates encoding and decoding decimal strings. It is
|
||||
// faster than strconv in part because it uses a base of 10000 and a lookup
|
||||
// table.
|
||||
package ints
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"io"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"lol.mleku.dev/errorf"
|
||||
)
|
||||
|
||||
// run this to regenerate (pointlessly) the base 10 array of 4 places per entry
|
||||
//go:generate go run ./gen/.
|
||||
|
||||
//go:embed base10k.txt
|
||||
var base10k []byte
|
||||
|
||||
const base = 10000
|
||||
|
||||
// T is an integer with a fast codec to decimal ASCII.
|
||||
type T struct {
|
||||
N uint64
|
||||
}
|
||||
|
||||
func New[V constraints.Integer](n V) *T {
|
||||
return &T{uint64(n)}
|
||||
}
|
||||
|
||||
// Uint64 returns the int.T as a uint64 (the base type)
|
||||
func (n *T) Uint64() uint64 { return n.N }
|
||||
|
||||
// Int64 returns an int64 from the base number (may cause truncation)
|
||||
func (n *T) Int64() int64 { return int64(n.N) }
|
||||
|
||||
// Uint16 returns an uint16 from the base number (may cause truncation)
|
||||
func (n *T) Uint16() uint16 { return uint16(n.N) }
|
||||
|
||||
var powers = []*T{
|
||||
{1},
|
||||
{1_0000},
|
||||
{1_0000_0000},
|
||||
{1_0000_0000_0000},
|
||||
{1_0000_0000_0000_0000},
|
||||
}
|
||||
|
||||
const zero = '0'
|
||||
const nine = '9'
|
||||
|
||||
// Marshal the int.T into a byte string.
|
||||
func (n *T) Marshal(dst []byte) (b []byte) {
|
||||
nn := n.N
|
||||
b = dst
|
||||
if n.N == 0 {
|
||||
b = append(b, '0')
|
||||
return
|
||||
}
|
||||
var i int
|
||||
var trimmed bool
|
||||
k := len(powers)
|
||||
for k > 0 {
|
||||
k--
|
||||
q := n.N / powers[k].N
|
||||
if !trimmed && q == 0 {
|
||||
continue
|
||||
}
|
||||
offset := q * 4
|
||||
bb := base10k[offset : offset+4]
|
||||
if !trimmed {
|
||||
for i = range bb {
|
||||
if bb[i] != '0' {
|
||||
bb = bb[i:]
|
||||
trimmed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
b = append(b, bb...)
|
||||
n.N = n.N - q*powers[k].N
|
||||
}
|
||||
n.N = nn
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal reads a string, which must be a positive integer no larger than math.MaxUint64,
|
||||
// skipping any non-numeric content before it.
|
||||
//
|
||||
// Note that leading zeros are not considered valid, but basically no such thing as machine
|
||||
// generated JSON integers with leading zeroes. Until this is disproven, this is the fastest way
|
||||
// to read a positive json integer, and a leading zero is decoded as a zero, and the remainder
|
||||
// returned.
|
||||
func (n *T) Unmarshal(b []byte) (r []byte, err error) {
|
||||
if len(b) < 1 {
|
||||
err = errorf.E("zero length number")
|
||||
return
|
||||
}
|
||||
var sLen int
|
||||
if b[0] == zero {
|
||||
r = b[1:]
|
||||
n.N = 0
|
||||
return
|
||||
}
|
||||
// skip non-number characters
|
||||
for i, v := range b {
|
||||
if v >= '0' && v <= '9' {
|
||||
b = b[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(b) == 0 {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
// count the digits
|
||||
for ; sLen < len(b) && b[sLen] >= zero && b[sLen] <= nine && b[sLen] != ','; sLen++ {
|
||||
}
|
||||
if sLen == 0 {
|
||||
err = errorf.E("zero length number")
|
||||
return
|
||||
}
|
||||
if sLen > 20 {
|
||||
err = errorf.E("too big number for uint64")
|
||||
return
|
||||
}
|
||||
// the length of the string found
|
||||
r = b[sLen:]
|
||||
b = b[:sLen]
|
||||
for _, ch := range b {
|
||||
ch -= zero
|
||||
n.N = n.N*10 + uint64(ch)
|
||||
}
|
||||
return
|
||||
}
|
||||
87
pkg/encoders/ints/ints_test.go
Normal file
87
pkg/encoders/ints/ints_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package ints
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
|
||||
"lukechampine.com/frand"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshal(t *testing.T) {
|
||||
b := make([]byte, 0, 8)
|
||||
var rem []byte
|
||||
var n *T
|
||||
var err error
|
||||
for _ = range 10000000 {
|
||||
n = New(uint64(frand.Intn(math.MaxInt64)))
|
||||
b = n.Marshal(b)
|
||||
m := New(0)
|
||||
if rem, err = m.Unmarshal(b); chk.E(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n.N != m.N {
|
||||
t.Fatalf("failed to convert to int64 at %d %s %d", n.N, b, m.N)
|
||||
}
|
||||
if len(rem) > 0 {
|
||||
t.Fatalf("leftover bytes after converting back: '%s'", rem)
|
||||
}
|
||||
b = b[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkByteStringToInt64(bb *testing.B) {
|
||||
b := make([]byte, 0, 19)
|
||||
var i int
|
||||
const nTests = 10000000
|
||||
testInts := make([]*T, nTests)
|
||||
for i = range nTests {
|
||||
testInts[i] = New(frand.Intn(math.MaxInt64))
|
||||
}
|
||||
bb.Run(
|
||||
"Marshal", func(bb *testing.B) {
|
||||
bb.ReportAllocs()
|
||||
for i = 0; i < bb.N; i++ {
|
||||
n := testInts[i%10000]
|
||||
b = n.Marshal(b)
|
||||
b = b[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
bb.Run(
|
||||
"Itoa", func(bb *testing.B) {
|
||||
bb.ReportAllocs()
|
||||
var s string
|
||||
for i = 0; i < bb.N; i++ {
|
||||
n := testInts[i%10000]
|
||||
s = strconv.Itoa(int(n.N))
|
||||
_ = s
|
||||
}
|
||||
},
|
||||
)
|
||||
bb.Run(
|
||||
"MarshalUnmarshal", func(bb *testing.B) {
|
||||
bb.ReportAllocs()
|
||||
m := New(0)
|
||||
for i = 0; i < bb.N; i++ {
|
||||
n := testInts[i%10000]
|
||||
b = m.Marshal(b)
|
||||
_, _ = n.Unmarshal(b)
|
||||
b = b[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
bb.Run(
|
||||
"ItoaAtoi", func(bb *testing.B) {
|
||||
bb.ReportAllocs()
|
||||
var s string
|
||||
for i = 0; i < bb.N; i++ {
|
||||
n := testInts[i%10000]
|
||||
s = strconv.Itoa(int(n.N))
|
||||
_, _ = strconv.Atoi(s)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
127
pkg/encoders/text/escape.go
Normal file
127
pkg/encoders/text/escape.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package text
|
||||
|
||||
// NostrEscape for JSON encoding according to RFC8259.
|
||||
//
|
||||
// This is the efficient implementation based on the NIP-01 specification:
|
||||
//
|
||||
// To prevent implementation differences from creating a different event ID for
|
||||
// the same event, the following rules MUST be followed while serializing:
|
||||
//
|
||||
// No whitespace, line breaks or other unnecessary formatting should be included
|
||||
// in the output JSON. No characters except the following should be escaped, and
|
||||
// instead should be included verbatim:
|
||||
//
|
||||
// - A line break, 0x0A, as \n
|
||||
// - A double quote, 0x22, as \"
|
||||
// - A backslash, 0x5C, as \\
|
||||
// - A carriage return, 0x0D, as \r
|
||||
// - A tab character, 0x09, as \t
|
||||
// - A backspace, 0x08, as \b
|
||||
// - A form feed, 0x0C, as \f
|
||||
//
|
||||
// UTF-8 should be used for encoding.
|
||||
func NostrEscape(dst, src []byte) []byte {
|
||||
l := len(src)
|
||||
for i := 0; i < l; i++ {
|
||||
c := src[i]
|
||||
switch {
|
||||
case c == '"':
|
||||
dst = append(dst, '\\', '"')
|
||||
case c == '\\':
|
||||
// if i+1 < l && src[i+1] == 'u' || i+1 < l && src[i+1] == '/' {
|
||||
if i+1 < l && src[i+1] == 'u' {
|
||||
dst = append(dst, '\\')
|
||||
} else {
|
||||
dst = append(dst, '\\', '\\')
|
||||
}
|
||||
case c == '\b':
|
||||
dst = append(dst, '\\', 'b')
|
||||
case c == '\t':
|
||||
dst = append(dst, '\\', 't')
|
||||
case c == '\n':
|
||||
dst = append(dst, '\\', 'n')
|
||||
case c == '\f':
|
||||
dst = append(dst, '\\', 'f')
|
||||
case c == '\r':
|
||||
dst = append(dst, '\\', 'r')
|
||||
default:
|
||||
dst = append(dst, c)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// NostrUnescape reverses the operation of NostrEscape except instead of
|
||||
// appending it to the provided slice, it rewrites it, eliminating a memory
|
||||
// copy. Keep in mind that the original JSON will be mangled by this operation,
|
||||
// but the resultant slices will cost zero allocations.
|
||||
func NostrUnescape(dst []byte) (b []byte) {
|
||||
var r, w int
|
||||
for ; r < len(dst); r++ {
|
||||
if dst[r] == '\\' {
|
||||
r++
|
||||
c := dst[r]
|
||||
switch {
|
||||
|
||||
// nip-01 specifies the following single letter C-style escapes for control
|
||||
// codes under 0x20.
|
||||
//
|
||||
// no others are specified but must be preserved, so only these can be
|
||||
// safely decoded at runtime as they must be re-encoded when marshalled.
|
||||
case c == '"':
|
||||
dst[w] = '"'
|
||||
w++
|
||||
case c == '\\':
|
||||
dst[w] = '\\'
|
||||
w++
|
||||
case c == 'b':
|
||||
dst[w] = '\b'
|
||||
w++
|
||||
case c == 't':
|
||||
dst[w] = '\t'
|
||||
w++
|
||||
case c == 'n':
|
||||
dst[w] = '\n'
|
||||
w++
|
||||
case c == 'f':
|
||||
dst[w] = '\f'
|
||||
w++
|
||||
case c == 'r':
|
||||
dst[w] = '\r'
|
||||
w++
|
||||
|
||||
// special cases for non-nip-01 specified json escapes (must be preserved for ID
|
||||
// generation).
|
||||
case c == 'u':
|
||||
dst[w] = '\\'
|
||||
w++
|
||||
dst[w] = 'u'
|
||||
w++
|
||||
case c == '/':
|
||||
dst[w] = '\\'
|
||||
w++
|
||||
dst[w] = '/'
|
||||
w++
|
||||
|
||||
// special case for octal escapes (must be preserved for ID generation).
|
||||
case c >= '0' && c <= '9':
|
||||
dst[w] = '\\'
|
||||
w++
|
||||
dst[w] = c
|
||||
w++
|
||||
|
||||
// anything else after a reverse solidus just preserve it.
|
||||
default:
|
||||
dst[w] = dst[r]
|
||||
w++
|
||||
dst[w] = c
|
||||
w++
|
||||
}
|
||||
} else {
|
||||
dst[w] = dst[r]
|
||||
w++
|
||||
}
|
||||
}
|
||||
b = dst[:w]
|
||||
return
|
||||
}
|
||||
459
pkg/encoders/text/escape_test.go
Normal file
459
pkg/encoders/text/escape_test.go
Normal file
@@ -0,0 +1,459 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/crypto/sha256"
|
||||
|
||||
"lukechampine.com/frand"
|
||||
)
|
||||
|
||||
func TestUnescapeByteString(t *testing.T) {
|
||||
b := make([]byte, 256)
|
||||
for i := range b {
|
||||
b[i] = byte(i)
|
||||
}
|
||||
escaped := NostrEscape(nil, b)
|
||||
unescaped := NostrUnescape(escaped)
|
||||
if string(b) != string(unescaped) {
|
||||
t.Log(b)
|
||||
t.Log(unescaped)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func GenRandString(l int, src *frand.RNG) (str []byte) {
|
||||
return src.Bytes(l)
|
||||
}
|
||||
|
||||
var seed = sha256.Sum256(
|
||||
[]byte(`
|
||||
The tao that can be told
|
||||
is not the eternal Tao
|
||||
The name that can be named
|
||||
is not the eternal Name
|
||||
|
||||
The unnamable is the eternally real
|
||||
Naming is the origin of all particular things
|
||||
|
||||
Free from desire, you realize the mystery
|
||||
Caught in desire, you see only the manifestations
|
||||
|
||||
Yet mystery and manifestations arise from the same source
|
||||
This source is called darkness
|
||||
|
||||
Darkness within darkness
|
||||
The gateway to all understanding
|
||||
`),
|
||||
)
|
||||
|
||||
var src = frand.NewCustom(seed[:], 32, 12)
|
||||
|
||||
func TestRandomEscapeByteString(t *testing.T) {
|
||||
// this is a kind of fuzz test, does a massive number of iterations of
|
||||
// random content that ensures the escaping is correct without creating a
|
||||
// fixed set of test vectors.
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
l := src.Intn(1<<8) + 32
|
||||
s1 := GenRandString(l, src)
|
||||
s2 := make([]byte, l)
|
||||
orig := make([]byte, l)
|
||||
copy(s2, s1)
|
||||
copy(orig, s1)
|
||||
|
||||
// first we are checking our implementation comports to the one from go-nostr.
|
||||
escapeStringVersion := NostrEscape([]byte{}, s1)
|
||||
escapeJSONStringAndWrapVersion := NostrEscape(nil, s2)
|
||||
if len(escapeJSONStringAndWrapVersion) != len(escapeStringVersion) {
|
||||
t.Logf(
|
||||
"escapeString\nlength: %d\n%s\n%v\n",
|
||||
len(escapeStringVersion), string(escapeStringVersion),
|
||||
escapeStringVersion,
|
||||
)
|
||||
t.Logf(
|
||||
"escapJSONStringAndWrap\nlength: %d\n%s\n%v\n",
|
||||
len(escapeJSONStringAndWrapVersion),
|
||||
escapeJSONStringAndWrapVersion,
|
||||
escapeJSONStringAndWrapVersion,
|
||||
)
|
||||
t.FailNow()
|
||||
}
|
||||
for i := range escapeStringVersion {
|
||||
if i > len(escapeJSONStringAndWrapVersion) {
|
||||
t.Fatal("escapeString version is shorter")
|
||||
}
|
||||
if escapeStringVersion[i] != escapeJSONStringAndWrapVersion[i] {
|
||||
t.Logf(
|
||||
"escapeString version differs at index %d from "+
|
||||
"escapeJSONStringAndWrap version\n%s\n%s\n%v\n%v", i,
|
||||
escapeStringVersion[i-4:],
|
||||
escapeJSONStringAndWrapVersion[i-4:],
|
||||
escapeStringVersion[i-4:],
|
||||
escapeJSONStringAndWrapVersion[i-4:],
|
||||
)
|
||||
t.Logf(
|
||||
"escapeString\nlength: %d %s\n",
|
||||
len(escapeStringVersion), escapeStringVersion,
|
||||
)
|
||||
t.Logf(
|
||||
"escapJSONStringAndWrap\nlength: %d %s\n",
|
||||
len(escapeJSONStringAndWrapVersion),
|
||||
escapeJSONStringAndWrapVersion,
|
||||
)
|
||||
t.Logf(
|
||||
"got '%s' %d expected '%s' %d\n",
|
||||
string(escapeJSONStringAndWrapVersion[i]),
|
||||
escapeJSONStringAndWrapVersion[i],
|
||||
string(escapeStringVersion[i]),
|
||||
escapeStringVersion[i],
|
||||
)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// next, unescape the output and see if it matches the original
|
||||
unescaped := NostrUnescape(escapeJSONStringAndWrapVersion)
|
||||
// t.Logf("unescaped: \n%s\noriginal: \n%s", unescaped, orig)
|
||||
if string(unescaped) != string(orig) {
|
||||
t.Fatalf(
|
||||
"\ngot %d %v\nexpected %d %v\n",
|
||||
len(unescaped),
|
||||
unescaped,
|
||||
len(orig),
|
||||
orig,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNostrEscapeNostrUnescape(b *testing.B) {
|
||||
const size = 65536
|
||||
b.Run(
|
||||
"frand64k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape64k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape64k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"frand32k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 2
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape32k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 2
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape32k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 2
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"frand16k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 4
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape16k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 4
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape16k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 4
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"frand8k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 8
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape8k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 8
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape8k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 8
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"frand4k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 16
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape4k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 16
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape4k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 16
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"frand2k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 32
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape2k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 32
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape2k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 32
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"frand1k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 64
|
||||
in := make([]byte, size)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscape1k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 64
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
b.Run(
|
||||
"NostrEscapeNostrUnescape1k", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
size := size / 64
|
||||
in := make([]byte, size)
|
||||
out := make([]byte, size*2)
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = frand.Read(in); chk.E(err) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
out = NostrEscape(out, in)
|
||||
in = in[:0]
|
||||
out = NostrUnescape(out)
|
||||
out = out[:0]
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
12
pkg/utils/units/units.go
Normal file
12
pkg/utils/units/units.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Package units is a convenient set of names designating data sizes in bytes
|
||||
// using common ISO names (base 10).
|
||||
package units
|
||||
|
||||
const (
|
||||
Kilobyte = 1000
|
||||
Kb = Kilobyte
|
||||
Megabyte = Kilobyte * Kilobyte
|
||||
Mb = Megabyte
|
||||
Gigabyte = Megabyte * Kilobyte
|
||||
Gb = Gigabyte
|
||||
)
|
||||
1
pkg/version/version
Normal file
1
pkg/version/version
Normal file
@@ -0,0 +1 @@
|
||||
v0.0.1
|
||||
12
pkg/version/version.go
Normal file
12
pkg/version/version.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed version
|
||||
var V string
|
||||
|
||||
var Description = "relay powered by the orly framework https://next.orly.dev"
|
||||
|
||||
var URL = "https://nextorly.dev"
|
||||
19
pprof.go
Normal file
19
pprof.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pkg/profile"
|
||||
)
|
||||
|
||||
func startProfiler(mode string) {
|
||||
switch mode {
|
||||
case "cpu":
|
||||
prof := profile.Start(profile.CPUProfile)
|
||||
defer prof.Stop()
|
||||
case "memory":
|
||||
prof := profile.Start(profile.MemProfile)
|
||||
defer prof.Stop()
|
||||
case "allocation":
|
||||
prof := profile.Start(profile.MemProfileAllocs)
|
||||
defer prof.Stop()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user