18 changed files with 12889 additions and 0 deletions
@ -0,0 +1,270 @@
@@ -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 |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -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 |
||||
} |
||||
@ -1,27 +1,52 @@
@@ -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= |
||||
|
||||
@ -0,0 +1,38 @@
@@ -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 |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,47 @@
@@ -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 |
||||
@ -0,0 +1,10 @@
@@ -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 |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,20 @@
@@ -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) |
||||
} |
||||
} |
||||
@ -0,0 +1,135 @@
@@ -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 |
||||
} |
||||
@ -0,0 +1,87 @@
@@ -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) |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
@ -0,0 +1,127 @@
@@ -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 |
||||
} |
||||
@ -0,0 +1,459 @@
@@ -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] |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -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 |
||||
) |
||||
@ -0,0 +1,12 @@
@@ -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" |
||||
@ -0,0 +1,19 @@
@@ -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() |
||||
} |
||||
} |
||||
Loading…
Reference in new issue