final version functioning

This commit is contained in:
shinya 2026-05-02 22:09:19 +02:00
commit 858d8877ff
30 changed files with 6953 additions and 0 deletions

82
README.md Normal file
View File

@ -0,0 +1,82 @@
# whspbrd
Simple end-to-end encrypted terminal chat with a relay server.
## Build
```bash
go build -o whspbrd
```
## Server
```bash
./whspbrd --mode server --listen :9090
```
## Client
First run once to print your identity (share with others):
```bash
./whspbrd --mode client --show-identity
```
Then connect (you need the peer fingerprint and base64 public key):
```bash
./whspbrd --mode client --connect 127.0.0.1:9090 --peer <peer-fingerprint> --peer-key <base64-pubkey>
## TUI
```bash
./whspbrd --mode client --tui --connect 127.0.0.1:9090
```
TUI commands:
- `/add <name> <pubkey>` add a contact.
- `/rename <name|fingerprint> <new-name>` rename contact.
- `/remove <name|fingerprint>` remove contact.
- `/trust <pubkey>` trust a peer key.
- `/sendfile <path>` send a file.
- `/whoami` show your fingerprint.
- `/pubkey` show your public key.
- `/help` list commands.
TUI navigation:
- `Tab` switch focus between Users and Input.
- `Enter` send message.
- `Up/Down` select contact in Users.
- `PgUp/PgDn` scroll chat history.
- `Ctrl+U` / `Ctrl+D` page up/down.
- `Home` / `End` jump to top/bottom of chat.
## Self-test
```bash
./whspbrd --selftest
```
```
During chat, commands:
- `/whoami` shows your fingerprint.
- `/pubkey` shows your base64 public key.
- `/trust <base64-pubkey>` stores a peer key (TOFU).
- `/sendfile <path>` sends a file (images supported).
- `/quit` exits.
- `/list` shows last 50 messages (stored locally).
## Data locations
- Config: `$XDG_CONFIG_HOME/whspbrd` (fallback `~/.config/whspbrd`)
- Data: `$XDG_DATA_HOME/whspbrd` (fallback `~/.local/share/whspbrd`)
Files:
- `identity.key` long-term private key.
- `peers.json` trusted peers for TOFU.
- received files are written into the data directory.
- `messages.db` stores chat history (SQLite).

108
colors.go Normal file
View File

@ -0,0 +1,108 @@
package main
import (
"fmt"
"github.com/jroimartin/gocui"
)
// Color holds the numeric part of an ANSI sequence (e.g. 31, 32, 313, ...)
type Color struct{ code int }
func NewColor(code int) Color { return Color{code} }
type tuiColors struct {
UserFg gocui.Attribute
ChatFg gocui.Attribute
StatusFg gocui.Attribute
InputFg gocui.Attribute
SelFg gocui.Attribute
SelBg gocui.Attribute
}
var TuiColors = tuiColors{
UserFg: gocui.ColorWhite,
ChatFg: gocui.ColorWhite,
StatusFg: gocui.ColorYellow,
InputFg: gocui.ColorGreen,
SelFg: gocui.ColorBlack,
SelBg: gocui.ColorCyan,
}
// Text returns the basic foreground sequence, e.g. "\033[31m"
func (c Color) Text() string { return fmt.Sprintf("\033[%dm", c.code) }
// Underline returns the sequence with underline: "\033[31;4m"
func (c Color) Underline() string { return fmt.Sprintf("\033[%d;4m", c.code) }
// Blink returns the sequence with blink: "\033[31;5m" (kept for completeness)
func (c Color) Blink() string { return fmt.Sprintf("\033[%d;5m", c.code) }
// Inverse returns the sequence with inverse/swap: "\033[31;7m"
// (this matches your original usage of ;7m as "background"/inverse)
func (c Color) Inverse() string { return fmt.Sprintf("\033[%d;7m", c.code) }
// Bg returns a real background color sequence (code + 10 -> 40..47 etc).
// e.g. if code==31 -> Bg() -> "\033[41m"
func (c Color) Bg() string { return fmt.Sprintf("\033[%dm", c.code+10) }
// Attr lets you compose any extra attribute numbers (e.g. 1 for bold)
func (c Color) Attr(attrs ...int) string {
if len(attrs) == 0 {
return c.Text()
}
s := fmt.Sprintf("\033[%d", c.code)
for _, a := range attrs {
s += fmt.Sprintf(";%d", a)
}
s += "m"
return s
}
// String implements fmt.Stringer (defaults to Text())
func (c Color) String() string { return c.Text() }
type Palette struct {
Base00 Color
Base01 Color
Base02 Color
Base03 Color
Base04 Color
Base05 Color
Base06 Color
Base07 Color
Base08 Color
Base09 Color
Base10 Color
Base11 Color
Base12 Color
Base13 Color
Base14 Color
Base15 Color
Reset string // usually reset
}
// Helper methods on the palette for convenience:
func (p Palette) Background(c Color) string { return c.Inverse() } // keeps your current ;7m semantics
func (p Palette) Underlined(c Color) string { return c.Underline() } // convenience
func (p Palette) Text(c Color) string { return c.Text() }
var Colors = Palette{
Base00: NewColor(31),
Base01: NewColor(32),
Base02: NewColor(33),
Base03: NewColor(34),
Base04: NewColor(35),
Base05: NewColor(36),
Base06: NewColor(37),
Base07: NewColor(38),
Base08: NewColor(39),
Base09: NewColor(310),
Base10: NewColor(311),
Base11: NewColor(312),
Base12: NewColor(313),
Base13: NewColor(314),
Base14: NewColor(315),
Base15: NewColor(316),
Reset: "\033[0m", // reset
}

82
flake.lock Normal file
View File

@ -0,0 +1,82 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770585520,
"narHash": "sha256-yBz9Ozd5Wb56i3e3cHZ8WcbzCQ9RlVaiW18qDYA/AzA=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "1201ddd1279c35497754f016ef33d5e060f3da8d",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1735563628,
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

37
go.mod Normal file
View File

@ -0,0 +1,37 @@
module whspbrd
go 1.22
require (
github.com/integrii/flaggy v1.6.0
github.com/jroimartin/gocui v0.5.0
golang.org/x/crypto v0.22.0
modernc.org/sqlite v1.29.0
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/getlantern/systray v1.2.1 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nsf/termbox-go v1.1.1 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.19.0 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)

71
go.sum Normal file
View File

@ -0,0 +1,71 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/integrii/flaggy v1.6.0 h1:uqCN1mDnbux18JENxqi/VNaAwStTssEa/DjPVh6vE4g=
github.com/integrii/flaggy v1.6.0/go.mod h1:ir+1vTHHG4iOyQ4u9ovPA1TPQ+l3pU7Z8c/fd07G4Lg=
github.com/jroimartin/gocui v0.5.0 h1:DCZc97zY9dMnHXJSJLLmx9VqiEnAj0yh0eTNpuEtG/4=
github.com/jroimartin/gocui v0.5.0/go.mod h1:l7Hz8DoYoL6NoYnlnaX6XCNR62G7J5FfSW5jEogzaxE=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.0 h1:lQVw+ZsFM3aRG5m4myG70tbXpr3S/J1ej0KHIP4EvjM=
modernc.org/sqlite v1.29.0/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

2257
main.go Normal file

File diff suppressed because it is too large Load Diff

123
menc/menc.go Normal file
View File

@ -0,0 +1,123 @@
package menc
// 15% AI generated code
// mostly human made; AI comments and details
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"errors"
)
var (
ErrCipherTextTooShort = errors.New("ciphertext too short")
)
type AESGCM_AutoNonce struct {
gcm cipher.AEAD
nonceSeq uint32
}
func NewAESGCM_AutoNonce(key []byte) (*AESGCM_AutoNonce, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return &AESGCM_AutoNonce{
gcm: gcm,
}, nil
}
/*
Encrypt encrypts plaintext with optional AAD.
Nonce is sequencialy produced and auto managed.
Output format: nonce || ciphertext
*/
func (a *AESGCM_AutoNonce) Encrypt(plaintext, aad []byte) ([]byte, error) {
ns := a.gcm.NonceSize()
out := make([]byte, ns, ns+len(plaintext)+a.gcm.Overhead())
nonce := out[:ns]
binary.BigEndian.PutUint32(nonce[ns-4:], a.nonceSeq)
a.nonceSeq++
return a.gcm.Seal(out, nonce, plaintext, aad), nil
}
/*
Decrypt decrypts data produced by some AESGCM encrypt.
Expects input format: nonce || ciphertext
*/
func (a *AESGCM_AutoNonce) Decrypt(ciphertext, aad []byte) ([]byte, error) {
ns := a.gcm.NonceSize()
if len(ciphertext) < ns {
return nil, ErrCipherTextTooShort
}
nonce := ciphertext[:ns]
data := ciphertext[ns:]
return a.gcm.Open(nil, nonce, data, aad)
}
/*
Encrypt encrypts plaintext with optional AAD.
A random nonce is geneated automaticaly.
Output format: nonce || ciphertext
*/
func AESGCM_Quick_Encrypt(key, plaintext, aad []byte) ([]byte, error) {
gcm, err := helper_AESGCM_Quick(key)
if err != nil {
return nil, err
}
ns := gcm.NonceSize()
out := make([]byte, ns, ns+len(plaintext)+gcm.Overhead())
nonce := out[:ns]
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
out = gcm.Seal(out, nonce, plaintext, aad)
return out, nil
}
/*
Decrypt decrypts data produced by some AESGCM encrypt.
Expects input format: nonce || ciphertext
*/
func AESGCM_Quick_Decrypt(key, ciphertext, aad []byte) ([]byte, error) {
gcm, err := helper_AESGCM_Quick(key)
if err != nil {
return nil, err
}
ns := gcm.NonceSize()
if len(ciphertext) < ns {
return nil, ErrCipherTextTooShort
}
nonce := ciphertext[:ns]
data := ciphertext[ns:]
return gcm.Open(nil, nonce, data, aad)
}
func helper_AESGCM_Quick(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(block)
}

214
owner/owner.go Normal file
View File

@ -0,0 +1,214 @@
package owner
// 5% of AI generated code
// human made; AI details
// FINAL-FINAL
import (
"WhspBrd/typio/bit"
"WhspBrd/typio/yum"
"bytes"
"crypto/mlkem"
"crypto/sha256"
"errors"
"github.com/AeonDave/cryptonite-go/sig"
)
var mldsa87 = sig.NewDeterministicMLDSA87()
var (
ErrInvalidSignature = errors.New("invalid signature")
ErrDestroyedSecret = errors.New("secret has been destroyed")
)
const requiredSeedSizeForMLDSA87 = 32
const signaturePubPartSize = sig.MLDSA87PublicKeySize
const signatureSignSize = sig.MLDSA87SignatureSize
const SignatureSize = signaturePubPartSize + signatureSignSize
const IdentitySize = bit.Size256b_B
type Identity = bit.Sha256
func IdentityEq(id, other Identity) bool {
return bytes.Equal(id[:], other[:])
}
func pubHash(pubKey []byte) Identity {
return sha256.Sum256(pubKey)
}
type Secret interface {
Identity() Identity
PulicKey() []byte
EncapKey() []byte
Decapsulate(ciphertext []byte) ([]byte, error)
Sign(data []byte) (signedData []byte, err error)
Save(path string, password []byte) error
Destroy()
SaveAndDestroy(path string, password []byte) error
}
type secret struct {
seed []byte
pubKey []byte
encap []byte
identity Identity
decap *mlkem.DecapsulationKey1024
privKey []byte
destroyed bool
}
func Encapsulate(secret Secret, encapKey []byte) (sharedkey []byte, ciphertext []byte, err error) {
encap, err := mlkem.NewEncapsulationKey1024(encapKey)
if err != nil {
return
}
sharedkey, ciphertext = encap.Encapsulate()
return
}
func NewSecret() (Secret, error) {
seed := make([]byte, requiredSeedSizeForMLDSA87)
if err := yum.YumSeed(seed); err != nil {
return nil, err
}
pubKey, privKey, err := sig.GenerateDeterministicKeyMLDSA87(seed)
if err != nil {
return nil, err
}
decap, err := mlkem.NewDecapsulationKey1024(append(seed, seed...))
if err != nil {
return nil, err
}
encap := decap.EncapsulationKey().Bytes()
return &secret{
seed: seed,
encap: encap,
decap: decap,
pubKey: pubKey,
identity: pubHash(pubKey),
privKey: privKey,
}, nil
}
func LoadSecret(path string, password []byte) (Secret, error) {
seed, err := yum.YumLoad(path, password)
if err != nil {
return nil, err
}
pubKey, privKey, err := sig.GenerateDeterministicKeyMLDSA87(seed)
if err != nil {
return nil, err
}
decap, err := mlkem.NewDecapsulationKey1024(append(seed, seed...))
if err != nil {
return nil, err
}
encap := decap.EncapsulationKey().Bytes()
return &secret{
seed: seed,
encap: encap,
decap: decap,
pubKey: pubKey,
identity: pubHash(pubKey),
privKey: privKey,
}, nil
}
func (s *secret) Identity() Identity {
return s.identity
}
func (s *secret) PulicKey() []byte {
pubCopy := make([]byte, len(s.pubKey))
copy(pubCopy, s.pubKey)
return pubCopy
}
func (s *secret) EncapKey() []byte {
encapCopy := make([]byte, len(s.encap))
copy(encapCopy, s.encap)
return encapCopy
}
func (s *secret) Decapsulate(ciphertext []byte) ([]byte, error) {
if s.destroyed {
return nil, ErrDestroyedSecret
}
return s.decap.Decapsulate(ciphertext)
}
func (s *secret) Sign(data []byte) (sign []byte, err error) {
if s.destroyed {
err = ErrDestroyedSecret
return
}
signPart, err := mldsa87.Sign(s.privKey, data)
if err != nil {
return
}
sign = append(s.pubKey, signPart...)
return
}
func (s *secret) Save(path string, password []byte) error {
if s.destroyed {
return ErrDestroyedSecret
}
return yum.YumSave(path, s.seed, password)
}
func Verify(data []byte, sign []byte) (Identity, error) {
if len(sign) < SignatureSize {
return Identity{}, ErrInvalidSignature
}
pubPart := sign[:signaturePubPartSize]
signPart := sign[signaturePubPartSize:]
ok := mldsa87.Verify(pubPart, data, signPart)
if !ok {
return Identity{}, ErrInvalidSignature
}
return pubHash(pubPart), nil
}
// Destroy zeroes out the secret's sensitive data and marks it as destroyed.
func (s *secret) Destroy() {
if s.destroyed {
return
}
for i := range s.seed {
s.seed[i] = 0
}
for i := range s.privKey {
s.privKey[i] = 0
}
for i := range s.encap {
s.encap[i] = 0
}
s.destroyed = true
/*for i := range s.pubKey {
s.pubKey[i] = 0
}
for i := range s.identity {
s.identity[i] = 0
}*/ // not needed for added security, but could be done if desired
}
func (s *secret) SaveAndDestroy(path string, password []byte) error {
if s.destroyed {
return ErrDestroyedSecret
}
if err := s.Save(path, password); err != nil {
return err
}
s.Destroy()
return nil
}

View File

@ -0,0 +1,48 @@
//go:build !windows
package cell_size
import (
"fmt"
"os"
"syscall"
"unsafe"
)
type winsize struct {
Rows uint16
Cols uint16
Xpixels uint16
Ypixels uint16
}
func GetTerminalCellSizePixels() (widthPx int, heightPx int, err error) {
ws := &winsize{}
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
os.Stdout.Fd(),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return 0, 0, errno
}
if ws.Cols == 0 || ws.Rows == 0 {
return 0, 0, fmt.Errorf("terminal rows or columns is zero")
}
widthPx = int(ws.Xpixels) / int(ws.Cols)
heightPx = int(ws.Ypixels) / int(ws.Rows)
return
}
func GetConsoleSize() (int, int) {
var sz struct {
rows uint16
cols uint16
xpixels uint16
ypixels uint16
}
_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
uintptr(syscall.Stdout), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
return int(sz.cols), int(sz.rows)
}

View File

@ -0,0 +1,107 @@
//go:build windows
package cell_size
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type coord struct {
X int16
Y int16
}
type consoleFontInfoEx struct {
cbSize uint32
nFont uint32
dwFontSize coord
fontFamily uint32
fontWeight uint32
faceName [32]uint16
}
var (
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
procGetCurrentConsoleFontEx = kernel32.NewProc("GetCurrentConsoleFontEx")
)
func GetTerminalCellSizePixels() (widthPx int, heightPx int, err error) {
var fontInfo consoleFontInfoEx
fontInfo.cbSize = uint32(unsafe.Sizeof(fontInfo))
stdOutHandle := windows.Handle(syscall.Stdout)
ret, _, err := procGetCurrentConsoleFontEx.Call(
uintptr(stdOutHandle),
uintptr(0), // bMaximumWindow = false
uintptr(unsafe.Pointer(&fontInfo)),
)
if ret == 0 {
return 0, 0, err
}
return int(fontInfo.dwFontSize.X), int(fontInfo.dwFontSize.Y), nil
}
type (
short int16
word uint16
smallRect struct {
Left short
Top short
Right short
Bottom short
}
consoleScreenBufferInfo struct {
Size coord
CursorPosition coord
Attributes word
Window smallRect
MaximumWindowSize coord
}
)
var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
var getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
// GetConsoleSize returns the current number of columns and rows in the active console window.
// The return value of this function is in the order of cols, rows.
func GetConsoleSize() (int, int) {
stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE)
var info, err = getConsoleScreenBufferInfo(stdoutHandle)
if err != nil {
return 0, 0
}
return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1)
}
func getError(r1, r2 uintptr, lastErr error) error {
// If the function fails, the return value is zero.
if r1 == 0 {
if lastErr != nil {
return lastErr
}
return syscall.EINVAL
}
return nil
}
func getStdHandle(stdhandle int) uintptr {
handle, err := syscall.GetStdHandle(stdhandle)
if err != nil {
panic(fmt.Errorf("could not get standard io handle %d", stdhandle))
}
return uintptr(handle)
}
func getConsoleScreenBufferInfo(handle uintptr) (*consoleScreenBufferInfo, error) {
var info consoleScreenBufferInfo
if err := getError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)); err != nil {
return nil, err
}
return &info, nil
}

View File

@ -0,0 +1,257 @@
package cleanimage
import (
"fmt"
"strings"
)
// KittyImageCleaner provides methods to generate Kitty graphics protocol
// commands for deleting images.
type KittyImageCleaner struct{}
// NewKittyImageCleaner creates a new instance of KittyImageCleaner.
func NewKittyImageCleaner() *KittyImageCleaner {
return &KittyImageCleaner{}
}
// buildCommand constructs the base Kitty graphics protocol command.
func (kic *KittyImageCleaner) buildCommand(params map[string]string) string {
var sb strings.Builder
sb.WriteString("\033_Ga=d") // Start with the delete action
if len(params) > 0 {
var paramStrings []string
for key, value := range params {
paramStrings = append(paramStrings, fmt.Sprintf("%s=%s", key, value))
}
sb.WriteString(",")
sb.WriteString(strings.Join(paramStrings, ","))
}
sb.WriteString("\033\\") // End the command
return sb.String()
}
// DeleteAllVisiblePlacements deletes all images visible on screen.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteAllVisiblePlacements(freeData bool) string {
if freeData {
return kic.buildCommand(map[string]string{"d": "A"})
}
return kic.buildCommand(map[string]string{"d": "a"})
}
// DeleteByID deletes images with a specific ID.
// 'imageID' is the ID of the image to delete.
// 'placementID' is an optional placement ID. If 0, all placements with the imageID are deleted.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByID(imageID int, placementID int, freeData bool) string {
params := make(map[string]string)
if freeData {
params["d"] = "I"
} else {
params["d"] = "i"
}
params["i"] = fmt.Sprintf("%d", imageID)
if placementID != 0 {
params["p"] = fmt.Sprintf("%d", placementID)
}
return kic.buildCommand(params)
}
// DeleteNewestByID deletes the newest image with a specified number (ID).
// 'imageNumber' is the number (ID) of the newest image to delete.
// 'placementID' is an optional placement ID. If 0, all placements with the imageNumber are deleted.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteNewestByID(imageNumber int, placementID int, freeData bool) string {
params := make(map[string]string)
if freeData {
params["d"] = "N"
} else {
params["d"] = "n"
}
params["I"] = fmt.Sprintf("%d", imageNumber) // Note: Kitty uses 'I' for number here
if placementID != 0 {
params["p"] = fmt.Sprintf("%d", placementID)
}
return kic.buildCommand(params)
}
// DeleteByCursorPosition deletes all placements that intersect with the current cursor position.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByCursorPosition(freeData bool) string {
if freeData {
return kic.buildCommand(map[string]string{"d": "C"})
}
return kic.buildCommand(map[string]string{"d": "c"})
}
// DeleteAnimationFrames deletes animation frames.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteAnimationFrames(freeData bool) string {
if freeData {
return kic.buildCommand(map[string]string{"d": "F"})
}
return kic.buildCommand(map[string]string{"d": "f"})
}
// DeleteByCellPosition deletes all placements that intersect a specific cell.
// 'x', 'y' are the coordinates of the cell (1-indexed).
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByCellPosition(x, y int, freeData bool) string {
params := map[string]string{
"x": fmt.Sprintf("%d", x),
"y": fmt.Sprintf("%d", y),
}
if freeData {
params["d"] = "P"
} else {
params["d"] = "p"
}
return kic.buildCommand(params)
}
// DeleteByCellAndZIndex deletes all placements that intersect a specific cell
// and have a specific z-index.
// 'x', 'y' are the coordinates of the cell (1-indexed).
// 'zIndex' is the z-index of the placements to delete.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByCellAndZIndex(x, y, zIndex int, freeData bool) string {
params := map[string]string{
"x": fmt.Sprintf("%d", x),
"y": fmt.Sprintf("%d", y),
"z": fmt.Sprintf("%d", zIndex),
}
if freeData {
params["d"] = "Q"
} else {
params["d"] = "q"
}
return kic.buildCommand(params)
}
// DeleteByIDRange deletes all images whose ID is within a specified range.
// 'minID' is the minimum ID (inclusive).
// 'maxID' is the maximum ID (inclusive).
// 'freeData' determines if the underlying image data should also be freed.
// (Requires Kitty version 0.33.0 or later)
func (kic *KittyImageCleaner) DeleteByIDRange(minID, maxID int, freeData bool) string {
params := map[string]string{
"x": fmt.Sprintf("%d", minID),
"y": fmt.Sprintf("%d", maxID),
}
if freeData {
params["d"] = "R"
} else {
params["d"] = "r"
}
return kic.buildCommand(params)
}
// DeleteByColumn deletes all placements that intersect the specified column.
// 'column' is the column number (1-indexed).
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByColumn(column int, freeData bool) string {
params := map[string]string{
"x": fmt.Sprintf("%d", column),
}
if freeData {
params["d"] = "X"
} else {
params["d"] = "x"
}
return kic.buildCommand(params)
}
// DeleteByRow deletes all placements that intersect the specified row.
// 'row' is the row number (1-indexed).
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByRow(row int, freeData bool) string {
params := map[string]string{
"y": fmt.Sprintf("%d", row),
}
if freeData {
params["d"] = "Y"
} else {
params["d"] = "y"
}
return kic.buildCommand(params)
}
// DeleteByZIndex deletes all placements that have the specified z-index.
// 'zIndex' is the z-index of the placements to delete.
// 'freeData' determines if the underlying image data should also be freed.
func (kic *KittyImageCleaner) DeleteByZIndex(zIndex int, freeData bool) string {
params := map[string]string{
"z": fmt.Sprintf("%d", zIndex),
}
if freeData {
params["d"] = "Z"
} else {
params["d"] = "z"
}
return kic.buildCommand(params)
}
func main() {
cleaner := NewKittyImageCleaner()
// Example usage:
fmt.Println("Kitty Image Cleaning Commands:")
fmt.Println("-------------------------------")
// <ESC>_Ga=d<ESC>\ # delete all visible placements
fmt.Println("Delete all visible placements (no data free):",
cleaner.DeleteAllVisiblePlacements(false))
// <ESC>_Ga=d,d=A<ESC>\ # delete all visible placements, freeing data
fmt.Println("Delete all visible placements (with data free):",
cleaner.DeleteAllVisiblePlacements(true))
// <ESC>_Ga=d,d=i,i=10<ESC>\ # delete the image with id=10, without freeing data
fmt.Println("Delete image with ID 10 (no data free):",
cleaner.DeleteByID(10, 0, false))
// <ESC>_Ga=d,d=I,i=10<ESC>\ # delete the image with id=10, freeing data
fmt.Println("Delete image with ID 10 (with data free):",
cleaner.DeleteByID(10, 0, true))
// <ESC>_Ga=d,d=i,i=10,p=7<ESC>\ # delete the image with id=10 and placement id=7, without freeing data
fmt.Println("Delete placement 7 of image ID 10 (no data free):",
cleaner.DeleteByID(10, 7, false))
// <ESC>_Ga=d,d=I,i=10,p=7<ESC>\ # delete the image with id=10 and placement id=7, freeing data
fmt.Println("Delete placement 7 of image ID 10 (with data free):",
cleaner.DeleteByID(10, 7, true))
// <ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the placements with z-index -1, also freeing up image data
fmt.Println("Delete placements with z-index -1 (with data free):",
cleaner.DeleteByZIndex(-1, true))
// <ESC>_Ga=d,d=z,z=0<ESC>\ # delete the placements with z-index 0, without freeing data
fmt.Println("Delete placements with z-index 0 (no data free):",
cleaner.DeleteByZIndex(0, false))
// <ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all placements that intersect the cell at (3, 4), without freeing data
fmt.Println("Delete placements at cell (3,4) (no data free):",
cleaner.DeleteByCellPosition(3, 4, false))
// <ESC>_Ga=d,d=P,x=5,y=6<ESC>\ # delete all placements that intersect the cell at (5, 6), freeing data
fmt.Println("Delete placements at cell (5,6) (with data free):",
cleaner.DeleteByCellPosition(5, 6, true))
fmt.Println("Delete placements intersecting cursor (no data free):",
cleaner.DeleteByCursorPosition(false))
fmt.Println("Delete placements intersecting column 10 (with data free):",
cleaner.DeleteByColumn(10, true))
fmt.Println("Delete placements intersecting row 5 (no data free):",
cleaner.DeleteByRow(5, false))
fmt.Println("Delete images with ID range 100-200 (with data free):",
cleaner.DeleteByIDRange(100, 200, true))
fmt.Println("Delete placements at cell (1,1) with z-index 10 (no data free):",
cleaner.DeleteByCellAndZIndex(1, 1, 10, false))
}

196
pkg/icons/icon_unix.go Normal file
View File

@ -0,0 +1,196 @@
//go:build linux || darwin
// +build linux darwin
// File generated by 2goarray (http://github.com/cratonica/2goarray)
package icon
var Data []byte = []byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20,
0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a, 0xf4, 0x00, 0x00, 0x00,
0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72,
0x65, 0x00, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67,
0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x00, 0x00,
0x03, 0x66, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f,
0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65,
0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf,
0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70,
0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54,
0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78,
0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c,
0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a,
0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a,
0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65,
0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e,
0x30, 0x2d, 0x63, 0x30, 0x36, 0x30, 0x20, 0x36, 0x31, 0x2e, 0x31, 0x33,
0x34, 0x37, 0x37, 0x37, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x30, 0x2f, 0x30,
0x32, 0x2f, 0x31, 0x32, 0x2d, 0x31, 0x37, 0x3a, 0x33, 0x32, 0x3a, 0x30,
0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20,
0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c,
0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70,
0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72,
0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32,
0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d,
0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44,
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72,
0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20,
0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d,
0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61,
0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70,
0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d,
0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68,
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f,
0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31,
0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78,
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74,
0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62,
0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e,
0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x4f, 0x72,
0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65,
0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69,
0x64, 0x3a, 0x36, 0x37, 0x32, 0x34, 0x42, 0x45, 0x31, 0x35, 0x45, 0x44,
0x32, 0x30, 0x36, 0x38, 0x31, 0x31, 0x38, 0x38, 0x43, 0x36, 0x46, 0x32,
0x38, 0x31, 0x35, 0x44, 0x41, 0x33, 0x43, 0x35, 0x35, 0x35, 0x22, 0x20,
0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65,
0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69,
0x64, 0x3a, 0x41, 0x33, 0x42, 0x34, 0x46, 0x42, 0x36, 0x36, 0x33, 0x41,
0x41, 0x38, 0x31, 0x31, 0x45, 0x32, 0x42, 0x32, 0x43, 0x41, 0x39, 0x37,
0x42, 0x44, 0x33, 0x34, 0x34, 0x31, 0x45, 0x46, 0x33, 0x32, 0x22, 0x20,
0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69,
0x64, 0x3a, 0x41, 0x33, 0x42, 0x34, 0x46, 0x42, 0x36, 0x35, 0x33, 0x41,
0x41, 0x38, 0x31, 0x31, 0x45, 0x32, 0x42, 0x32, 0x43, 0x41, 0x39, 0x37,
0x42, 0x44, 0x33, 0x34, 0x34, 0x31, 0x45, 0x46, 0x33, 0x32, 0x22, 0x20,
0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54,
0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50,
0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x53, 0x35,
0x20, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x22, 0x3e,
0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69,
0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65,
0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44,
0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x45, 0x36,
0x38, 0x31, 0x34, 0x43, 0x36, 0x41, 0x45, 0x45, 0x32, 0x30, 0x36, 0x38,
0x31, 0x31, 0x38, 0x38, 0x43, 0x36, 0x46, 0x32, 0x38, 0x31, 0x35, 0x44,
0x41, 0x33, 0x43, 0x35, 0x35, 0x35, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65,
0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44,
0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x36, 0x37,
0x32, 0x34, 0x42, 0x45, 0x31, 0x35, 0x45, 0x44, 0x32, 0x30, 0x36, 0x38,
0x31, 0x31, 0x38, 0x38, 0x43, 0x36, 0x46, 0x32, 0x38, 0x31, 0x35, 0x44,
0x41, 0x33, 0x43, 0x35, 0x35, 0x35, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f,
0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52,
0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d,
0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b,
0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e,
0x5d, 0xed, 0x35, 0xe2, 0x00, 0x00, 0x04, 0xee, 0x49, 0x44, 0x41, 0x54,
0x78, 0xda, 0xc4, 0x57, 0xcf, 0x6f, 0x55, 0x45, 0x18, 0x3d, 0xf3, 0xe3,
0xfe, 0xea, 0x7b, 0xaf, 0xa5, 0x6d, 0x0a, 0xd8, 0x34, 0xbe, 0x16, 0x83,
0x69, 0x8c, 0x2e, 0x04, 0xe2, 0x86, 0xb8, 0x70, 0xe1, 0x06, 0x35, 0x18,
0x13, 0x5d, 0x60, 0x8c, 0xd1, 0x68, 0xe2, 0xca, 0xb8, 0x33, 0x31, 0xf1,
0x6f, 0x70, 0x67, 0x5c, 0xb1, 0x62, 0xe1, 0x46, 0x42, 0x8c, 0x0b, 0xe3,
0x46, 0x34, 0x25, 0x11, 0x41, 0x14, 0xa4, 0x24, 0xa4, 0x08, 0x58, 0x0a,
0x29, 0x14, 0x0a, 0x6d, 0xe9, 0xeb, 0xbb, 0xef, 0xce, 0x9d, 0xf1, 0xcc,
0xbd, 0xaf, 0xa5, 0x44, 0x63, 0x49, 0xee, 0x4b, 0x78, 0xc9, 0xf7, 0xee,
0x9d, 0x3b, 0x33, 0x77, 0xce, 0x77, 0xbe, 0xf3, 0x7d, 0x33, 0x57, 0x38,
0xe7, 0xf0, 0x38, 0x7f, 0x7a, 0xab, 0x01, 0xe2, 0xd9, 0x37, 0xff, 0xeb,
0xb1, 0xa2, 0x7d, 0x46, 0xdb, 0xeb, 0x87, 0xd0, 0x8e, 0xd3, 0xbe, 0xf8,
0xd7, 0x28, 0x6b, 0xe1, 0x2e, 0x7c, 0xf3, 0xbf, 0xef, 0x97, 0x5b, 0x42,
0x34, 0x06, 0xf0, 0x2c, 0x6d, 0x36, 0xe0, 0x43, 0xda, 0x88, 0x90, 0xf2,
0x90, 0xea, 0x6f, 0xbc, 0x0b, 0x21, 0x9e, 0x67, 0xfb, 0x8d, 0xa2, 0xcf,
0x76, 0xc7, 0x70, 0x5e, 0xff, 0x40, 0x7f, 0x75, 0x06, 0xc6, 0x27, 0x9a,
0xb8, 0x76, 0x63, 0xbe, 0x70, 0x53, 0xf0, 0x2f, 0xe7, 0x02, 0xd6, 0xda,
0x71, 0x36, 0xaf, 0xd1, 0x0e, 0xcb, 0x38, 0x16, 0x5c, 0xf4, 0x7a, 0xbe,
0xbc, 0xd2, 0x14, 0x71, 0x04, 0x19, 0x68, 0xf8, 0xb0, 0x4a, 0xda, 0x2e,
0xce, 0xad, 0x0c, 0x60, 0xf7, 0x53, 0xbb, 0x90, 0x87, 0x11, 0x76, 0x8f,
0xee, 0x40, 0xa8, 0x35, 0xfe, 0x9a, 0xbf, 0x35, 0x36, 0x73, 0xf9, 0xea,
0x24, 0xd2, 0xf4, 0x20, 0x5d, 0x2d, 0xbc, 0x15, 0x49, 0x0c, 0x1d, 0x86,
0xdf, 0x09, 0x25, 0x7f, 0x80, 0x14, 0xd3, 0x8e, 0x20, 0x93, 0x30, 0x44,
0xa3, 0xd1, 0xd8, 0x12, 0x80, 0xdc, 0x3a, 0x02, 0x06, 0xa4, 0xba, 0xb0,
0x5a, 0x12, 0x2b, 0x2e, 0xf4, 0x35, 0xb4, 0x3e, 0x58, 0x84, 0xde, 0xb3,
0x91, 0xa6, 0x64, 0x86, 0xf7, 0x4a, 0xbe, 0x4a, 0xcf, 0x8f, 0xe4, 0x26,
0x0f, 0x42, 0xa5, 0xf0, 0xcc, 0xe8, 0x13, 0x50, 0xfe, 0x79, 0x65, 0x11,
0xf2, 0x1d, 0x86, 0x62, 0x9a, 0x9a, 0xbe, 0x88, 0xa7, 0x77, 0x8e, 0x04,
0x9d, 0x34, 0x1b, 0x45, 0xda, 0x81, 0xf7, 0xde, 0x53, 0x9d, 0x77, 0x32,
0x04, 0x49, 0x82, 0x88, 0x1e, 0x67, 0xb9, 0xa9, 0x37, 0xe2, 0x44, 0x3c,
0x39, 0x3c, 0x84, 0xa8, 0x1b, 0x8a, 0xca, 0x00, 0xba, 0xbf, 0x28, 0x35,
0xe6, 0xc0, 0x9f, 0x17, 0x2f, 0x7d, 0x20, 0xad, 0x6d, 0xc2, 0xe4, 0xd8,
0x10, 0x45, 0x10, 0x20, 0xd0, 0x01, 0xfa, 0x09, 0xa2, 0x16, 0x85, 0x13,
0xf5, 0x28, 0x3a, 0x1a, 0x28, 0x75, 0x98, 0x4b, 0x7f, 0xcf, 0xde, 0x56,
0xe5, 0x10, 0xf0, 0xf7, 0x36, 0x6d, 0x4a, 0x0a, 0x71, 0x14, 0x4a, 0x1d,
0xb0, 0x7e, 0xce, 0x3a, 0xb5, 0xbc, 0x2a, 0xea, 0x22, 0x50, 0x92, 0x11,
0x90, 0xd0, 0x8a, 0xdc, 0x0b, 0xbc, 0xc2, 0x1e, 0x9f, 0x7b, 0xbf, 0xd0,
0x3e, 0xea, 0x05, 0x80, 0x8f, 0x69, 0xfb, 0x7c, 0x76, 0x81, 0x8e, 0x23,
0x75, 0xa5, 0x75, 0x31, 0x68, 0x0f, 0x80, 0x66, 0xac, 0x44, 0x9a, 0x09,
0x38, 0x5b, 0xbe, 0x92, 0xdd, 0xcf, 0xd1, 0xde, 0xab, 0x1c, 0x82, 0x95,
0x54, 0xdf, 0x58, 0x5a, 0xe3, 0xab, 0x3a, 0x0e, 0xf5, 0xc8, 0x61, 0x72,
0x4c, 0x53, 0x5c, 0x0e, 0x27, 0x67, 0xb2, 0x62, 0x76, 0x28, 0x55, 0x51,
0x97, 0xf6, 0x8c, 0x4b, 0xf4, 0x45, 0xc0, 0xad, 0x7b, 0xe4, 0xbd, 0x23,
0xb0, 0xda, 0x21, 0x51, 0x10, 0x57, 0x2a, 0x03, 0x38, 0xb4, 0xf7, 0xef,
0x99, 0xe0, 0x85, 0x35, 0x34, 0x87, 0x15, 0x26, 0x77, 0x0e, 0xa3, 0x39,
0x5e, 0x73, 0xc7, 0x7e, 0x5a, 0xc5, 0x5b, 0x9f, 0xcf, 0x09, 0xd4, 0x15,
0x19, 0x50, 0x94, 0x84, 0xc4, 0xfb, 0x2f, 0x25, 0x78, 0x6d, 0x9f, 0xc4,
0xed, 0x85, 0x0c, 0x73, 0xf7, 0x52, 0xcc, 0x2e, 0x38, 0x5c, 0x5f, 0x74,
0xe7, 0x2a, 0x03, 0xf8, 0xe4, 0xe5, 0x9b, 0xe7, 0xa0, 0xb7, 0xd1, 0xc9,
0x41, 0x0a, 0xbf, 0x8f, 0x2e, 0xf7, 0x09, 0xa9, 0x38, 0x4d, 0xcc, 0x16,
0x9e, 0xfb, 0xb0, 0x07, 0x5a, 0x50, 0x8b, 0x75, 0x48, 0x1d, 0x61, 0x64,
0xb0, 0x8d, 0xed, 0x43, 0x6d, 0xec, 0x99, 0xa0, 0xfe, 0x4c, 0x6b, 0xba,
0x32, 0x80, 0xd4, 0xec, 0xb8, 0x2c, 0x5c, 0x9d, 0x45, 0xbd, 0x21, 0x21,
0x6b, 0x2c, 0x46, 0x35, 0xa6, 0x9d, 0x7d, 0xa0, 0x01, 0x0a, 0x30, 0x24,
0x0b, 0x51, 0x5c, 0x67, 0x23, 0x41, 0x26, 0x42, 0x6a, 0xc5, 0x9b, 0x36,
0x70, 0xe1, 0xb5, 0xb0, 0x72, 0x1d, 0x08, 0x86, 0x66, 0xa1, 0xe2, 0x25,
0xe8, 0x81, 0x41, 0xa1, 0x6a, 0x64, 0xa0, 0x1f, 0x41, 0xdc, 0x59, 0xef,
0xa5, 0xfa, 0x15, 0x42, 0x0f, 0xa2, 0x00, 0xe0, 0x59, 0x08, 0xe0, 0xf8,
0xcc, 0x09, 0x71, 0x9b, 0x20, 0x66, 0xab, 0xd7, 0x01, 0x99, 0xdc, 0xa4,
0xe7, 0x57, 0x84, 0x6e, 0x0c, 0xd2, 0x98, 0xf7, 0x83, 0xf4, 0x76, 0x6d,
0x23, 0x7f, 0x7c, 0x0a, 0xfa, 0x10, 0xc4, 0x31, 0xfb, 0x14, 0x37, 0x1f,
0x4d, 0xf1, 0x51, 0x13, 0xac, 0x42, 0x97, 0x9d, 0x50, 0x8b, 0xd5, 0x2b,
0x61, 0x30, 0x90, 0x41, 0x86, 0x97, 0xe8, 0xfd, 0x9e, 0x02, 0x80, 0xda,
0x46, 0x6f, 0x57, 0x8b, 0x52, 0xe0, 0x33, 0xd3, 0xe7, 0x3f, 0x0b, 0x0f,
0xa2, 0x64, 0x80, 0x8d, 0x3a, 0x84, 0x66, 0x85, 0x2c, 0xc2, 0x93, 0xcf,
0x08, 0x17, 0xd9, 0xea, 0x75, 0x40, 0x51, 0x78, 0x32, 0xfa, 0x83, 0x61,
0x80, 0xf0, 0xf7, 0x5c, 0x24, 0x8c, 0x06, 0x20, 0x65, 0x39, 0xd5, 0xd7,
0xfb, 0x52, 0x03, 0x04, 0xa7, 0xfd, 0xd8, 0x84, 0xe0, 0x22, 0xee, 0x1d,
0xd1, 0x19, 0x7f, 0xad, 0xce, 0x80, 0xf2, 0x2f, 0x91, 0x67, 0x79, 0xe3,
0xe9, 0xf0, 0x60, 0x10, 0x84, 0x0d, 0x36, 0x65, 0xb1, 0x1f, 0x48, 0xd1,
0x65, 0x20, 0x68, 0xa0, 0x18, 0x23, 0x03, 0x7f, 0x65, 0x47, 0x78, 0x1e,
0xc2, 0x55, 0xdf, 0x0d, 0xfd, 0x82, 0x7c, 0xe9, 0x79, 0xde, 0x2d, 0x95,
0xdb, 0xaf, 0x45, 0x18, 0xf8, 0xf4, 0x2b, 0xa7, 0x7a, 0x22, 0x22, 0xb6,
0x83, 0x90, 0x0b, 0x93, 0xfb, 0x32, 0x39, 0xe4, 0x02, 0x41, 0x9c, 0x2f,
0xc0, 0xf4, 0xa0, 0x14, 0x7b, 0x4f, 0xe7, 0xe0, 0xb2, 0x0b, 0xb0, 0x54,
0x7f, 0xbe, 0x46, 0x00, 0xe9, 0x03, 0x00, 0x3e, 0x04, 0xdc, 0xf9, 0x22,
0xc5, 0xca, 0xc8, 0x7e, 0xe7, 0x7c, 0xbd, 0xce, 0x09, 0x58, 0x2c, 0x6c,
0xe4, 0x6a, 0x25, 0x00, 0x8e, 0x2f, 0x76, 0xc6, 0x72, 0xe3, 0x3f, 0xed,
0xf2, 0x55, 0x96, 0xe4, 0x65, 0xc4, 0xaa, 0xc5, 0xb8, 0xcb, 0x82, 0x10,
0x81, 0x32, 0x0b, 0x22, 0xc1, 0xcc, 0x30, 0x34, 0xdb, 0x26, 0x88, 0xec,
0xd7, 0x62, 0x2f, 0x76, 0xb6, 0x3a, 0x00, 0x97, 0x67, 0xa5, 0x99, 0xd6,
0x94, 0x33, 0xcb, 0x04, 0x70, 0x17, 0x7d, 0x62, 0x85, 0x27, 0x9e, 0x2e,
0x80, 0x42, 0x84, 0xac, 0x07, 0xee, 0x3e, 0x01, 0xac, 0x70, 0x6c, 0xcb,
0x33, 0x71, 0x82, 0x13, 0x50, 0x58, 0xe5, 0x3a, 0x60, 0x56, 0xd7, 0xa1,
0x9c, 0x76, 0xb6, 0x73, 0x1f, 0xc2, 0xd4, 0x7d, 0x75, 0xeb, 0x5b, 0x07,
0x00, 0x0f, 0x80, 0xd9, 0x60, 0xb9, 0xb8, 0x3f, 0xc0, 0x9a, 0xb5, 0x3b,
0x44, 0x71, 0xa6, 0x67, 0xc7, 0x72, 0x97, 0xaf, 0xac, 0xdf, 0x5e, 0x45,
0xee, 0xce, 0x5a, 0xb4, 0xf7, 0x2b, 0xab, 0x91, 0x6c, 0xe8, 0x8b, 0x00,
0x28, 0x7a, 0x91, 0x2f, 0x77, 0x99, 0x4a, 0x7f, 0x23, 0xb1, 0x37, 0x7a,
0x06, 0x00, 0xd9, 0xbd, 0x8d, 0x53, 0xbe, 0x3f, 0x98, 0x38, 0xdb, 0xda,
0xaf, 0x84, 0x46, 0xac, 0x5d, 0xa9, 0x7a, 0x86, 0x40, 0x4b, 0x02, 0x30,
0x4b, 0x3c, 0x2d, 0xb3, 0xfc, 0x5b, 0x73, 0xbc, 0xa7, 0x1f, 0x26, 0x96,
0x9e, 0x6d, 0xa2, 0xe3, 0x47, 0x61, 0xe5, 0xa7, 0x42, 0x05, 0x3c, 0x03,
0x5a, 0x94, 0x25, 0xcf, 0x33, 0xc0, 0xfb, 0x9c, 0x59, 0x9a, 0x53, 0xac,
0xce, 0xfd, 0xfc, 0x28, 0xea, 0x7f, 0xf4, 0x10, 0x64, 0xf3, 0x9b, 0x9b,
0xa7, 0xb8, 0xc9, 0xcc, 0x91, 0x81, 0xb1, 0x7a, 0x68, 0x4a, 0x00, 0xc2,
0xef, 0x86, 0x06, 0x8a, 0xa1, 0xe2, 0xfa, 0x97, 0xa8, 0x86, 0xdf, 0x7b,
0xca, 0x80, 0xcb, 0x16, 0x36, 0x37, 0x17, 0x73, 0x67, 0x4e, 0xf2, 0x14,
0x34, 0x56, 0x8f, 0x92, 0xf2, 0x7c, 0x40, 0x6f, 0x7d, 0x0d, 0x10, 0xf9,
0x5d, 0x8e, 0x75, 0x27, 0x18, 0x8c, 0x56, 0x6f, 0x01, 0x98, 0xf9, 0x87,
0xdb, 0xce, 0x1c, 0x81, 0x69, 0xbf, 0x58, 0x8b, 0x9b, 0xdb, 0x81, 0x7a,
0x91, 0xc9, 0xa1, 0x6c, 0x51, 0x03, 0x77, 0xce, 0xb8, 0x5c, 0x7f, 0xe5,
0x8a, 0xcf, 0xc6, 0x1e, 0x02, 0x78, 0x38, 0x9e, 0xfe, 0xde, 0x1c, 0x83,
0x5d, 0x38, 0x55, 0x0b, 0x87, 0x5f, 0x67, 0xfb, 0x1d, 0x3e, 0x68, 0x2b,
0x61, 0xbe, 0x84, 0x6b, 0x7f, 0xeb, 0x50, 0x6b, 0x97, 0x63, 0x1e, 0xfd,
0x8b, 0x5b, 0x3c, 0xee, 0xcf, 0xf3, 0x7f, 0x04, 0x18, 0x00, 0xe0, 0x6e,
0xdd, 0x63, 0x24, 0x57, 0x80, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
}

367
pkg/icons/icon_win.go Normal file
View File

@ -0,0 +1,367 @@
//go:build windows
// +build windows
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
package icon
var Data []byte = []byte{
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
0x20, 0x00, 0xa8, 0x10, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09, 0xc6, 0xf5, 0x1b, 0x34, 0x69, 0x66, 0x6b, 0x5a, 0x24,
0x00, 0xc8, 0x8a, 0x5a, 0x30, 0xc0, 0x7a, 0x88, 0x7d, 0x56, 0x40, 0xbf,
0xdf, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xc6, 0xf2, 0x50, 0x22, 0xcf,
0xfe, 0xe9, 0x42, 0x64, 0x5e, 0xff, 0x5b, 0x25, 0x00, 0xff, 0x8b, 0x5b,
0x30, 0xff, 0x7f, 0x8a, 0x7b, 0xff, 0x5d, 0xd6, 0xfd, 0xcd, 0x5e, 0xd0,
0xf5, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x43, 0xc9, 0xf4, 0xac, 0x46, 0xd4, 0xff, 0xff, 0x4f, 0x5c,
0x53, 0xff, 0x59, 0x25, 0x00, 0xff, 0x89, 0x5b, 0x30, 0xff, 0x86, 0x88,
0x75, 0xff, 0x71, 0xdb, 0xfe, 0xff, 0x6f, 0xd3, 0xf7, 0x9e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xc6, 0xf1, 0x12, 0x5c, 0xd1,
0xf7, 0xe3, 0x61, 0xd8, 0xfc, 0xff, 0x57, 0x55, 0x47, 0xff, 0x58, 0x26,
0x00, 0xff, 0x88, 0x5b, 0x30, 0xff, 0x8b, 0x85, 0x6e, 0xff, 0x82, 0xde,
0xfd, 0xff, 0x7f, 0xd8, 0xf7, 0xd9, 0x7f, 0xcc, 0xe5, 0x0a, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x6f, 0xd4, 0xf4, 0x47, 0x73, 0xd7, 0xfa, 0xff, 0x78, 0xda,
0xf9, 0xff, 0x5b, 0x4f, 0x3b, 0xff, 0x57, 0x27, 0x00, 0xff, 0x86, 0x5a,
0x30, 0xff, 0x8f, 0x81, 0x68, 0xff, 0x93, 0xe1, 0xfa, 0xff, 0x8e, 0xdd,
0xfa, 0xfd, 0x8a, 0xd8, 0xf6, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xd8,
0xf7, 0x88, 0x87, 0xdc, 0xfc, 0xff, 0x8b, 0xdd, 0xf8, 0xff, 0x5d, 0x49,
0x34, 0xff, 0x56, 0x28, 0x00, 0xff, 0x85, 0x5a, 0x30, 0xff, 0x91, 0x7e,
0x62, 0xff, 0xa2, 0xe4, 0xfa, 0xff, 0x9e, 0xe2, 0xfb, 0xff, 0x98, 0xdd,
0xf6, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x02, 0x90, 0xdc, 0xf7, 0xc4, 0x9a, 0xe3,
0xfd, 0xff, 0x9e, 0xe0, 0xf5, 0xff, 0x5e, 0x45, 0x2c, 0xff, 0x55, 0x29,
0x00, 0xff, 0x84, 0x59, 0x2f, 0xff, 0x92, 0x7c, 0x5e, 0xff, 0xb1, 0xe8,
0xf9, 0xff, 0xac, 0xe8, 0xfd, 0xff, 0xa4, 0xe1, 0xf7, 0xb9, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0xd9,
0xf7, 0x22, 0xa0, 0xe1, 0xf8, 0xf3, 0xad, 0xe9, 0xff, 0xff, 0xad, 0xe2,
0xf2, 0xff, 0x5e, 0x40, 0x23, 0xff, 0x55, 0x29, 0x00, 0xff, 0x83, 0x59,
0x2f, 0xff, 0x93, 0x79, 0x58, 0xff, 0xbf, 0xeb, 0xf8, 0xff, 0xbb, 0xed,
0xfe, 0xff, 0xb1, 0xe6, 0xf9, 0xec, 0xa7, 0xe2, 0xf5, 0x1a, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xe1, 0xf6, 0x56, 0xb1, 0xe8,
0xfb, 0xff, 0xbf, 0xef, 0xff, 0xff, 0xbc, 0xe2, 0xec, 0xff, 0x5c, 0x3a,
0x19, 0xff, 0x55, 0x2a, 0x00, 0xff, 0x82, 0x59, 0x2e, 0xff, 0x93, 0x75,
0x52, 0xff, 0xcc, 0xed, 0xf5, 0xff, 0xc9, 0xf1, 0xff, 0xff, 0xbe, 0xec,
0xfb, 0xff, 0xb5, 0xe7, 0xf8, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xb0, 0xe6, 0xfa, 0x92, 0xbf, 0xec, 0xfc, 0xff, 0xd0, 0xf5,
0xff, 0xff, 0xc9, 0xe1, 0xe2, 0xff, 0x5a, 0x34, 0x10, 0xff, 0x55, 0x2b,
0x00, 0xff, 0x81, 0x58, 0x2d, 0xff, 0x92, 0x71, 0x4c, 0xff, 0xd8, 0xee,
0xf2, 0xff, 0xd6, 0xf5, 0xff, 0xff, 0xc8, 0xef, 0xfc, 0xff, 0xbb, 0xea,
0xf9, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x02, 0xb8, 0xe9,
0xf9, 0xc6, 0xca, 0xef, 0xfd, 0xff, 0xde, 0xfb, 0xff, 0xff, 0xd3, 0xde,
0xda, 0xff, 0x58, 0x30, 0x0a, 0xff, 0x55, 0x2c, 0x00, 0xff, 0x7f, 0x58,
0x2c, 0xff, 0x91, 0x6d, 0x47, 0xff, 0xe1, 0xed, 0xeb, 0xff, 0xdf, 0xf9,
0xff, 0xff, 0xd0, 0xf1, 0xfc, 0xff, 0xc1, 0xea, 0xfb, 0xbd, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xb2, 0xe5, 0xf6, 0x1e, 0xbd, 0xeb, 0xfa, 0xf0, 0xd0, 0xf1,
0xfd, 0xff, 0xe7, 0xfe, 0xff, 0xff, 0xda, 0xd9, 0xd4, 0xff, 0x56, 0x2d,
0x07, 0xff, 0x55, 0x2c, 0x00, 0xff, 0x7e, 0x57, 0x2a, 0xff, 0x8d, 0x68,
0x41, 0xff, 0xe6, 0xea, 0xe4, 0xff, 0xe5, 0xfb, 0xff, 0xff, 0xd3, 0xf2,
0xfd, 0xff, 0xc4, 0xeb, 0xfa, 0xe9, 0xb9, 0xe8, 0xf3, 0x16, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0xe3,
0xf8, 0x4a, 0xbe, 0xec, 0xfb, 0xff, 0xd1, 0xf1, 0xfd, 0xff, 0xe9, 0xff,
0xff, 0xff, 0xd7, 0xd4, 0xce, 0xff, 0x54, 0x2a, 0x04, 0xff, 0x55, 0x2c,
0x00, 0xff, 0x7e, 0x55, 0x28, 0xff, 0x8a, 0x63, 0x3a, 0xff, 0xe1, 0xe4,
0xdf, 0xff, 0xe4, 0xfc, 0xff, 0xff, 0xd3, 0xf2, 0xfc, 0xff, 0xc2, 0xec,
0xfb, 0xfe, 0xb6, 0xe8, 0xf7, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xe3, 0xf8, 0x79, 0xbb, 0xeb,
0xfb, 0xff, 0xcc, 0xf0, 0xfd, 0xff, 0xe2, 0xfd, 0xff, 0xff, 0xca, 0xce,
0xc9, 0xff, 0x52, 0x27, 0x03, 0xff, 0x56, 0x2c, 0x00, 0xff, 0x7c, 0x54,
0x26, 0xff, 0x87, 0x60, 0x35, 0xff, 0xd4, 0xdf, 0xd8, 0xff, 0xdd, 0xfb,
0xff, 0xff, 0xce, 0xf0, 0xfc, 0xff, 0xc0, 0xec, 0xfc, 0xff, 0xb0, 0xe8,
0xf8, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xa0, 0xe1, 0xf9, 0xa9, 0xb3, 0xe8, 0xfb, 0xff, 0xc2, 0xed,
0xfc, 0xff, 0xd6, 0xfa, 0xff, 0xff, 0xbb, 0xc7, 0xc3, 0xff, 0x50, 0x25,
0x01, 0xff, 0x56, 0x2c, 0x00, 0xff, 0x7b, 0x53, 0x23, 0xff, 0x84, 0x5c,
0x2f, 0xff, 0xc7, 0xd7, 0xd1, 0xff, 0xd3, 0xf7, 0xff, 0xff, 0xc5, 0xee,
0xfc, 0xff, 0xb9, 0xeb, 0xfb, 0xff, 0xab, 0xe5, 0xf9, 0xa0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xcc, 0xcc, 0x05, 0x96, 0xdd,
0xf7, 0xcd, 0xa8, 0xe5, 0xfa, 0xff, 0xb5, 0xe9, 0xfb, 0xff, 0xc6, 0xf7,
0xff, 0xff, 0xab, 0xbf, 0xbd, 0xff, 0x50, 0x24, 0x00, 0xff, 0x56, 0x2c,
0x00, 0xff, 0x79, 0x51, 0x20, 0xff, 0x81, 0x59, 0x2a, 0xff, 0xb8, 0xce,
0xca, 0xff, 0xc6, 0xf4, 0xff, 0xff, 0xb9, 0xea, 0xfb, 0xff, 0xaf, 0xe7,
0xfb, 0xff, 0xa0, 0xe1, 0xf8, 0xc5, 0x7f, 0x7f, 0x7f, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7f, 0xd1, 0xf6, 0x1c, 0x8b, 0xdb, 0xf7, 0xee, 0x9b, 0xe1,
0xf9, 0xff, 0xa6, 0xe4, 0xfa, 0xff, 0xb4, 0xf2, 0xff, 0xff, 0x9b, 0xb7,
0xb6, 0xff, 0x50, 0x22, 0x00, 0xff, 0x56, 0x2c, 0x00, 0xff, 0x77, 0x4e,
0x1b, 0xff, 0x80, 0x55, 0x25, 0xff, 0xab, 0xc6, 0xc2, 0xff, 0xb7, 0xf1,
0xff, 0xff, 0xad, 0xe6, 0xfb, 0xff, 0xa3, 0xe4, 0xfa, 0xff, 0x98, 0xdf,
0xf7, 0xe7, 0x8c, 0xd9, 0xf2, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xd3,
0xf6, 0x3a, 0x7d, 0xd7, 0xf8, 0xfd, 0x8b, 0xdc, 0xf9, 0xff, 0x96, 0xde,
0xf9, 0xff, 0xa1, 0xec, 0xff, 0xff, 0x8c, 0xaf, 0xb1, 0xff, 0x50, 0x22,
0x00, 0xff, 0x55, 0x2c, 0x00, 0xff, 0x74, 0x4c, 0x18, 0xff, 0x7e, 0x52,
0x1f, 0xff, 0x9f, 0xbe, 0xbc, 0xff, 0xa8, 0xed, 0xff, 0xff, 0xa0, 0xe2,
0xf9, 0xff, 0x98, 0xdf, 0xf9, 0xff, 0x8d, 0xda, 0xf8, 0xfb, 0x85, 0xd6,
0xf5, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xd5, 0xfc, 0x5c, 0x6b, 0xdd,
0xff, 0xff, 0x7a, 0xe0, 0xff, 0xff, 0x84, 0xdf, 0xff, 0xff, 0x8e, 0xe8,
0xff, 0xff, 0x7d, 0xa6, 0xac, 0xff, 0x50, 0x21, 0x00, 0xff, 0x55, 0x2c,
0x00, 0xff, 0x72, 0x49, 0x14, 0xff, 0x7b, 0x4e, 0x19, 0xff, 0x93, 0xb6,
0xb5, 0xff, 0x9a, 0xe9, 0xff, 0xff, 0x92, 0xe1, 0xfe, 0xff, 0x8a, 0xe2,
0xff, 0xff, 0x80, 0xe1, 0xff, 0xff, 0x74, 0xdb, 0xfc, 0x4f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x71, 0x92, 0x8e, 0x8a, 0x69, 0x97, 0x94, 0xff, 0x67, 0xaa,
0xb3, 0xff, 0x6e, 0xc2, 0xdb, 0xff, 0x78, 0xe1, 0xf9, 0xfe, 0x6d, 0xa2,
0xa9, 0xfe, 0x52, 0x20, 0x00, 0xff, 0x55, 0x2c, 0x00, 0xff, 0x6f, 0x46,
0x11, 0xff, 0x7a, 0x4a, 0x13, 0xff, 0x87, 0xb1, 0xb0, 0xff, 0x89, 0xe5,
0xfd, 0xff, 0x84, 0xd0, 0xe9, 0xff, 0x80, 0xc0, 0xcd, 0xff, 0x7f, 0xab,
0xaf, 0xff, 0x7d, 0x96, 0x92, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x60,
0x34, 0xaf, 0x77, 0x45, 0x0b, 0xff, 0x64, 0x35, 0x00, 0xff, 0x5b, 0x37,
0x0c, 0xff, 0x5a, 0x4c, 0x34, 0xff, 0x59, 0x4d, 0x37, 0xff, 0x54, 0x28,
0x00, 0xff, 0x55, 0x2c, 0x00, 0xff, 0x6c, 0x42, 0x0d, 0xff, 0x78, 0x4d,
0x17, 0xff, 0x7e, 0x70, 0x4e, 0xff, 0x82, 0x77, 0x58, 0xff, 0x87, 0x67,
0x42, 0xff, 0x8a, 0x61, 0x38, 0xff, 0x8d, 0x5f, 0x33, 0xff, 0x8d, 0x5e,
0x34, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x67, 0x41, 0xc9, 0x76, 0x4e,
0x1a, 0xff, 0x65, 0x3b, 0x00, 0xff, 0x5c, 0x30, 0x00, 0xff, 0x56, 0x27,
0x00, 0xff, 0x54, 0x24, 0x00, 0xff, 0x54, 0x2c, 0x00, 0xff, 0x55, 0x2c,
0x00, 0xff, 0x69, 0x40, 0x07, 0xff, 0x75, 0x4d, 0x17, 0xff, 0x7c, 0x50,
0x1c, 0xff, 0x82, 0x55, 0x25, 0xff, 0x87, 0x5d, 0x33, 0xff, 0x89, 0x62,
0x3a, 0xff, 0x8b, 0x65, 0x3e, 0xff, 0x8a, 0x65, 0x3f, 0xc9, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x8c, 0x65, 0x40, 0x5b, 0x75, 0x4c, 0x19, 0xcb, 0x65, 0x3c,
0x02, 0xff, 0x5c, 0x33, 0x00, 0xff, 0x57, 0x2e, 0x00, 0xff, 0x55, 0x2b,
0x00, 0xff, 0x54, 0x2b, 0x00, 0xff, 0x55, 0x2c, 0x00, 0xff, 0x65, 0x3c,
0x03, 0xff, 0x71, 0x49, 0x12, 0xff, 0x7a, 0x52, 0x20, 0xff, 0x81, 0x5a,
0x2c, 0xff, 0x85, 0x5f, 0x34, 0xff, 0x89, 0x62, 0x3a, 0xff, 0x8a, 0x63,
0x3d, 0xd2, 0x89, 0x64, 0x3d, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x6d, 0x49, 0x00, 0x07, 0x61, 0x39, 0x00, 0x51, 0x57, 0x2e,
0x00, 0xb5, 0x56, 0x2d, 0x01, 0xfd, 0x54, 0x2a, 0x00, 0xff, 0x54, 0x2b,
0x00, 0xff, 0x54, 0x2b, 0x00, 0xff, 0x61, 0x38, 0x00, 0xff, 0x6f, 0x46,
0x0d, 0xff, 0x78, 0x50, 0x1d, 0xff, 0x7d, 0x56, 0x27, 0xfc, 0x84, 0x5d,
0x33, 0xb4, 0x86, 0x60, 0x38, 0x52, 0x7f, 0x4c, 0x33, 0x0a, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x40, 0x20, 0x08, 0x77, 0x4f,
0x22, 0xde, 0x59, 0x2f, 0x00, 0xff, 0x54, 0x2b, 0x00, 0xff, 0x53, 0x2a,
0x00, 0xff, 0x5c, 0x33, 0x00, 0xff, 0x6a, 0x42, 0x08, 0xff, 0x6f, 0x47,
0x0f, 0xff, 0x6a, 0x44, 0x09, 0xdb, 0x55, 0x2a, 0x00, 0x06, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x55, 0x55, 0x00, 0x03, 0x7f, 0x58, 0x28, 0xc4, 0x5f, 0x36,
0x00, 0xff, 0x55, 0x2b, 0x00, 0xff, 0x54, 0x2b, 0x01, 0xff, 0x61, 0x39,
0x03, 0xff, 0x69, 0x41, 0x05, 0xff, 0x68, 0x40, 0x04, 0xff, 0x66, 0x3e,
0x04, 0xcf, 0x33, 0x33, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x76, 0x52, 0x24, 0x1c, 0x5a, 0x31, 0x00, 0xb0, 0x54, 0x2b,
0x00, 0xff, 0x55, 0x2d, 0x01, 0xff, 0x68, 0x40, 0x05, 0xff, 0x68, 0x40,
0x04, 0xff, 0x65, 0x3c, 0x03, 0xb3, 0x65, 0x3b, 0x00, 0x2b, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x52, 0x29, 0x00, 0x88, 0x53, 0x29, 0x00, 0x75, 0x55, 0x2c,
0x00, 0xa8, 0x68, 0x3f, 0x05, 0xa9, 0x5f, 0x36, 0x02, 0x8e, 0x51, 0x29,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x2a,
0x00, 0x68, 0x52, 0x2a, 0x00, 0x92, 0x4c, 0x19, 0x00, 0x0a, 0x60, 0x30,
0x00, 0x10, 0x53, 0x2a, 0x00, 0xa2, 0x51, 0x28, 0x00, 0x52, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x54, 0x29,
0x00, 0x7d, 0x52, 0x2a, 0x00, 0xb0, 0x53, 0x2a, 0x00, 0xb1, 0x53, 0x29,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0,
0x03, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xc0,
0x03, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80,
0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80,
0x01, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xfa,
0x3f, 0xff, 0xff, 0xfd, 0xbf, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff,
0xff, 0xff,
}

95
pkg/mpris/mpris.go Normal file
View File

@ -0,0 +1,95 @@
package mpris
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/Endg4meZer0/go-mpris"
"github.com/godbus/dbus/v5"
)
type PlayerInfo struct {
Name string
Title string
Artist []string
Album string
ArtURL string
}
func Mpris() ([]PlayerInfo, error) {
conn, err := dbus.SessionBus()
if err != nil {
return nil, err
}
names, err := mpris.List(conn)
if err != nil {
return nil, err
}
if len(names) == 0 {
return nil, fmt.Errorf("no MPRIS players found")
}
var players []PlayerInfo
for _, name := range names {
player := mpris.New(conn, name)
metadata, err := player.GetMetadata()
if err != nil {
return nil, err
}
title, _ := metadata["xesam:title"].Value().(string)
artist, _ := metadata["xesam:artist"].Value().([]string)
album, _ := metadata["xesam:album"].Value().(string)
var artURL string
if val, ok := metadata["mpris:artUrl"]; ok {
if url, ok := val.Value().(string); ok {
artURL = url
}
}
players = append(players, PlayerInfo{
Name: name,
Title: title,
Artist: artist,
Album: album,
ArtURL: artURL,
})
}
return players, nil
}
func IsRemoteArt(uri string) bool {
return strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://")
}
func DownloadArt(url, dest string) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("failed to fetch art: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
out, err := os.Create(dest)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return fmt.Errorf("failed to save art: %w", err)
}
return nil
}

View File

@ -0,0 +1,75 @@
package render_image
import (
"encoding/base64"
"fmt"
"image"
"image/draw"
_ "image/jpeg"
_ "image/png"
"os"
"whspbrd/pkg/resize_image"
)
func LoadImage(filePath string) (image.Image, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
img, _, err := image.Decode(f)
return img, err
}
func ConvertToRGBA(img image.Image) *image.RGBA {
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
return rgba
}
func EncodeImageToBase64RGBA(rgba *image.RGBA) string {
return base64.StdEncoding.EncodeToString(rgba.Pix)
}
func RenderImage(filepath string, row int, col int, width int, height int, units bool) {
img, err := LoadImage(filepath)
if err != nil {
fmt.Printf("Error loading image: %v\n", err)
return
}
rgba := ConvertToRGBA(img)
if units {
rgba, _ = resize_image.ResizeInTerminal(*rgba, width, height)
} else {
rgba, _ = resize_image.Resize(*rgba, width, height)
}
encoded := EncodeImageToBase64RGBA(rgba)
imgWidth := rgba.Rect.Dx()
imgHeight := rgba.Rect.Dy()
fmt.Printf("\033[s\033[%d;%dH", row, col)
chunkSize := 4096
pos := 0
first := true
for pos < len(encoded) {
fmt.Print("\033_G")
if first {
fmt.Printf("q=2,a=T,f=32,s=%d,v=%d,", imgWidth, imgHeight)
first = false
}
chunkLen := len(encoded) - pos
if chunkLen > chunkSize {
chunkLen = chunkSize
}
if pos+chunkLen < len(encoded) {
fmt.Print("m=1")
}
fmt.Printf(";%s\033\\", encoded[pos:pos+chunkLen])
pos += chunkLen
}
fmt.Print("\033[u")
}

View File

@ -0,0 +1,68 @@
package resize_image
import (
"image"
"image/color"
"whspbrd/pkg/cell_size"
)
func Resize(img image.RGBA, width int, height int) (*image.RGBA, error) {
originalWidth := img.Bounds().Dx()
originalHeight := img.Bounds().Dy()
if width <= 0 && height <= 0 {
return nil, nil
} else if width <= 0 {
width = int(float64(height) * float64(originalWidth) / float64(originalHeight))
} else if height <= 0 {
height = int(float64(width) * float64(originalHeight) / float64(originalWidth))
}
newImg := image.NewRGBA(image.Rect(0, 0, width, height))
scaleX := float64(originalWidth) / float64(width)
scaleY := float64(originalHeight) / float64(height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
srcX := int(float64(x) * scaleX)
srcY := int(float64(y) * scaleY)
r, g, b, a := getPixel(img, srcX, srcY)
newImg.Set(x, y, color.RGBA{r, g, b, a})
}
}
return newImg, nil
}
func ResizeInTerminal(img image.RGBA, columns int, rows int) (*image.RGBA, error) {
if columns <= 0 && rows <= 0 {
return nil, nil
}
width := convertColumnsToPixels(columns)
height := convertRowsToPixels(rows)
return Resize(img, width, height)
}
func getPixel(img image.RGBA, x int, y int) (uint8, uint8, uint8, uint8) {
index := img.PixOffset(x, y)
return img.Pix[index], img.Pix[index+1], img.Pix[index+2], img.Pix[index+3]
}
func convertColumnsToPixels(columns int) int {
w, _, err := cell_size.GetTerminalCellSizePixels()
if err != nil {
w = 8
}
return columns * w
}
func convertRowsToPixels(rows int) int {
_, h, err := cell_size.GetTerminalCellSizePixels()
if err != nil {
h = 16
}
return rows * h
}

View File

@ -0,0 +1,66 @@
package term_image
import (
"encoding/base64"
"fmt"
"image"
"image/draw"
"os"
)
func LoadImage(filePath string) (image.Image, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
img, _, err := image.Decode(f)
return img, err
}
func convertToRGBA(img image.Image) *image.RGBA {
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
return rgba
}
func encodeImageToBase64RGBA(rgba *image.RGBA) string {
return base64.StdEncoding.EncodeToString(rgba.Pix)
}
func RenderImage(filepath string, row int, col int) {
img, err := LoadImage(filepath)
if err != nil {
fmt.Printf("Error loading image: %v\n", err)
return
}
rgba := convertToRGBA(img)
encoded := encodeImageToBase64RGBA(rgba)
width := rgba.Rect.Dx()
height := rgba.Rect.Dy()
fmt.Printf("\033[s\033[%d;%dH", row, col)
chunkSize := 4096
pos := 0
first := true
for pos < len(encoded) {
fmt.Print("\033_G")
if first {
fmt.Printf("q=2,a=T,f=32,s=%d,v=%d,", width, height)
first = false
}
chunkLen := len(encoded) - pos
if chunkLen > chunkSize {
chunkLen = chunkSize
}
if pos+chunkLen < len(encoded) {
fmt.Print("m=1")
}
fmt.Printf(";%s\033\\", encoded[pos:pos+chunkLen])
pos += chunkLen
}
fmt.Print("\033[u")
}

205
relay/client.go Normal file
View File

@ -0,0 +1,205 @@
package relay
import (
"WhspBrd/menc"
"WhspBrd/owner"
"WhspBrd/thrembio"
"WhspBrd/typio/splco"
"crypto/mlkem"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"net"
"strconv"
"time"
)
type Client interface {
Register(tokenId uint32, token []byte) error
Login() error
PublishKem() error
GetKem(whos owner.Identity) (*mlkem.EncapsulationKey1024, error)
Send(to owner.Identity, encap *mlkem.EncapsulationKey1024, data []byte) error
MessageLen() uint32
MessageGet(id uint32) (sender owner.Identity, timestamp uint32, data []byte, err error)
}
type client struct {
th thrembio.Client
sc owner.Secret
spuha owner.Identity
}
func NewClient(addr string, secret owner.Secret, spuha owner.Identity) (Client, error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("invalid address, expected ip:port: %w", err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port: %w", err)
}
ip := net.ParseIP(host)
if ip == nil {
return nil, fmt.Errorf("invalid ip: %s", host)
}
th, err := thrembio.NewClient(&net.UDPAddr{IP: ip, Port: port}, secret)
if err != nil {
return nil, err
}
return &client{
th: th,
sc: secret,
spuha: spuha,
}, nil
}
func (c *client) Register(tokenId uint32, token []byte) error {
return c.th.Register(tokenId, token)
}
func (c *client) Login() error {
return c.th.Login()
}
func (c *client) PublishKem() error {
// [serverPuha] [kek] [osign(serverPuha | kek)]
data := append(c.spuha[:], c.sc.EncapKey()...)
osign, err := c.sc.Sign(data)
if err != nil {
return err
}
data = append(data, osign...)
data = append([]byte("pubkem"), data...)
err = c.th.Write(data)
if err != nil {
return err
}
res, err := c.th.Read()
if err != nil {
return err
}
if string(res) != "done" {
return err
}
return nil
}
func (c *client) GetKem(whos owner.Identity) (*mlkem.EncapsulationKey1024, error) {
// [receiverPuha]
// <- ([serverPuha] [kek] [osign(serverPuha | kek)])(of that receiverPuha)
err := c.th.Write(append([]byte("getkem"), whos[:]...))
if err != nil {
return nil, err
}
res, err := c.th.Read()
if err != nil {
return nil, err
}
if len(res) < owner.IdentitySize+mlkem.EncapsulationKeySize1024 {
return nil, errors.New("invalid response length")
}
serverPuha := res[:owner.IdentitySize]
kek := res[owner.IdentitySize : owner.IdentitySize+mlkem.EncapsulationKeySize1024]
osign := res[owner.IdentitySize+mlkem.EncapsulationKeySize1024:]
whosver, err := owner.Verify(append(serverPuha, kek...), osign)
if err != nil {
return nil, err
}
if whosver != whos {
return nil, errors.New("not signed by the expected owner")
}
return mlkem.NewEncapsulationKey1024(kek)
}
func (c *client) Send(to owner.Identity, encap *mlkem.EncapsulationKey1024, data []byte) error {
// -> [serverPuha] [receiverPuha] [kct] [time] [encData] [osign(serverPuha | receiverPuha | kct | time | encData)]
// <- "send"
shared, kct := encap.Encapsulate()
key := sha256.Sum256(shared)
timee := make([]byte, 8)
binary.BigEndian.PutUint64(timee, uint64(time.Now().Unix()))
encData, err := menc.AESGCM_Quick_Encrypt(key[:], data, timee)
if err != nil {
return err
}
payload := splco.Append(c.spuha[:], to[:], kct, timee, encData)
osign, err := c.sc.Sign(payload)
if err != nil {
return err
}
payload = append(payload, osign...)
err = c.th.Write(append([]byte("send"), payload...))
if err != nil {
return err
}
res, err := c.th.Read()
if err != nil {
return err
}
if string(res) != "send" {
return errors.New("unexpected response")
}
return nil
}
func (c *client) MessageLen() uint32 {
// -> "msglen"
// <- [len]
err := c.th.Write([]byte("msglen"))
if err != nil {
return 0
}
res, err := c.th.Read()
if err != nil {
return 0
}
if len(res) != 4 {
return 0
}
return binary.BigEndian.Uint32(res)
}
func (c *client) MessageGet(id uint32) (sender owner.Identity, timestamp uint32, data []byte, err error) {
// -> [id]
// <- [serverPuha] [receiverPuha] [kct] [time] [encData] [osign(serverPuha | senderPuha | kct | time | encData)]
idB := make([]byte, 4)
binary.BigEndian.PutUint32(idB, id)
err = c.th.Write(append([]byte("msgget"), idB...))
if err != nil {
return owner.Identity{}, 0, nil, err
}
res, err := c.th.Read()
if err != nil {
return owner.Identity{}, 0, nil, err
}
if len(res) < owner.IdentitySize*2+mlkem.CiphertextSize1024+8 {
return owner.Identity{}, 0, nil, errors.New("invalid response length")
}
serverPuha := owner.Identity(res[:owner.IdentitySize])
//receiverPuha := owner.Identity(res[owner.IdentitySize : owner.IdentitySize*2])
kct := res[owner.IdentitySize*2 : owner.IdentitySize*2+mlkem.CiphertextSize1024]
timee := res[owner.IdentitySize*2+mlkem.CiphertextSize1024 : owner.IdentitySize*2+mlkem.CiphertextSize1024+8]
encData := res[owner.IdentitySize*2+mlkem.CiphertextSize1024+8 : owner.IdentitySize*2+mlkem.CiphertextSize1024+8+len(res)-owner.IdentitySize*2-mlkem.CiphertextSize1024-8]
osign := res[len(res)-owner.IdentitySize:]
verData := res[:len(res)-owner.IdentitySize]
whosver, err := owner.Verify(verData, osign)
if err != nil {
return owner.Identity{}, 0, nil, err
}
shared, err := c.sc.Decapsulate(kct)
if err != nil {
return owner.Identity{}, 0, nil, err
}
key := sha256.Sum256(shared)
data, err = menc.AESGCM_Quick_Decrypt(key[:], encData, timee)
if err != nil {
return owner.Identity{}, 0, nil, err
}
if owner.IdentityEq(c.spuha, serverPuha) {
return owner.Identity{}, 0, nil, errors.New("unexpected sender")
}
return whosver, binary.BigEndian.Uint32(timee), data, nil
}

242
relay/server.go Normal file
View File

@ -0,0 +1,242 @@
package relay
import (
"WhspBrd/owner"
"WhspBrd/thrembio"
"crypto/mlkem"
"encoding/binary"
"sync"
)
type Server interface {
Start() error
Close()
GetId() owner.Identity
AddRegisterToken(tokenId uint32, token []byte) error
GetRegisterTokens() map[uint32][]byte
}
type server struct {
db thrembio.ServerDB
th thrembio.Server
sc owner.Secret
messages map[owner.Identity][][]byte
userKems map[owner.Identity]*mlkem.EncapsulationKey1024
mu sync.RWMutex
}
func NewServer(port uint16, secret owner.Secret, path string) (Server, error) {
db, err := thrembio.NewSQLiteDB(path)
if err != nil {
return nil, err
}
th, err := thrembio.NewServer(port, secret, db)
if err != nil {
return nil, err
}
th.SetFlags(thrembio.SF_NoUserErrorLog | thrembio.SF_NoRegisterLog | thrembio.SF_NoLoginLog)
return &server{
db: db,
th: th,
sc: secret,
messages: make(map[owner.Identity][][]byte),
userKems: make(map[owner.Identity]*mlkem.EncapsulationKey1024),
}, nil
}
func (s *server) Start() error {
err := s.th.Open()
if err != nil {
return err
}
return s.handleData()
}
func (s *server) handleData() error {
for {
rt, user, _, data, err := s.th.Read()
if err != nil {
if rt == thrembio.RT_UserError {
continue
}
return err
}
switch rt {
case thrembio.RT_Register:
// New user registered
continue
case thrembio.RT_Login:
// User logged in
continue
case thrembio.RT_Data:
// Parse relay protocol command
if len(data) < 6 {
s.th.Write([]byte("error"), user)
continue
}
cmd := string(data[:6])
payload := data[6:]
switch cmd {
case "pubkem":
s.handlePublishKem(user, payload)
case "getkem":
s.handleGetKem(user, payload)
case "send":
s.handleSend(user, payload)
case "msglen":
s.handleMessageLen(user)
case "msgget":
s.handleMessageGet(user, payload)
default:
s.th.Write([]byte("error"), user)
}
case thrembio.RT_UserError, thrembio.RT_InsideError:
// Handle errors or continue
continue
}
}
}
func (s *server) handlePublishKem(user owner.Identity, payload []byte) {
// Expected: [serverPuha] [kek] [osign(serverPuha | kek)]
if len(payload) < owner.IdentitySize+mlkem.EncapsulationKeySize1024+owner.SignatureSize {
s.th.Write([]byte("error"), user)
return
}
serverPuha := owner.Identity(payload[:owner.IdentitySize])
kek := payload[owner.IdentitySize : owner.IdentitySize+mlkem.EncapsulationKeySize1024]
osign := payload[owner.IdentitySize+mlkem.EncapsulationKeySize1024:]
// Verify signature signed by the user
whosver, err := owner.Verify(append(serverPuha[:], kek...), osign)
if err != nil || !owner.IdentityEq(whosver, user) {
s.th.Write([]byte("error"), user)
return
}
// Store KEM for this user
kemKey, err := mlkem.NewEncapsulationKey1024(kek)
if err != nil {
s.th.Write([]byte("error"), user)
return
}
s.mu.Lock()
s.userKems[user] = kemKey
s.mu.Unlock()
s.th.Write([]byte("done"), user)
}
func (s *server) handleGetKem(user owner.Identity, payload []byte) {
// Expected: [receiverPuha]
if len(payload) < owner.IdentitySize {
s.th.Write([]byte("error"), user)
return
}
receiverPuha := owner.Identity(payload[:owner.IdentitySize])
// Get receiver's KEM
s.mu.RLock()
kemKey, exists := s.userKems[receiverPuha]
s.mu.RUnlock()
if !exists || kemKey == nil {
s.th.Write([]byte("error"), user)
return
}
// Return [serverPuha] [kek] [osign(serverPuha | kek)]
id := s.sc.Identity()
responseData := append(id[:], kemKey.Bytes()...)
osign, err := s.sc.Sign(responseData)
if err != nil {
s.th.Write([]byte("error"), user)
return
}
responseData = append(responseData, osign...)
s.th.Write(responseData, user)
}
func (s *server) handleSend(user owner.Identity, payload []byte) {
// Expected: [serverPuha] [receiverPuha] [kct] [time] [encData] [osign(...)]
// Minimum: owner.IdentitySize*2 + mlkem.CiphertextSize1024 + 8 + owner.SignatureSize
if len(payload) < owner.IdentitySize*2+mlkem.CiphertextSize1024+8+owner.SignatureSize {
s.th.Write([]byte("error"), user)
return
}
// Extract receiverPuha (sender is verified by thrembio layer)
receiverPuha := owner.Identity(payload[owner.IdentitySize : owner.IdentitySize*2])
// Store the full message payload for the receiver
s.mu.Lock()
s.messages[receiverPuha] = append(s.messages[receiverPuha], payload)
s.mu.Unlock()
// Acknowledge
s.th.Write([]byte("send"), user)
}
func (s *server) handleMessageLen(user owner.Identity) {
// Return message count as 4-byte big-endian
s.mu.RLock()
msgCount := len(s.messages[user])
s.mu.RUnlock()
lenB := make([]byte, 4)
binary.BigEndian.PutUint32(lenB, uint32(msgCount))
s.th.Write(lenB, user)
}
func (s *server) handleMessageGet(user owner.Identity, payload []byte) {
// Expected: [id] (4 bytes)
if len(payload) < 4 {
s.th.Write([]byte("error"), user)
return
}
msgId := binary.BigEndian.Uint32(payload[:4])
s.mu.RLock()
msgs := s.messages[user]
if msgId >= uint32(len(msgs)) {
s.mu.RUnlock()
s.th.Write([]byte("error"), user)
return
}
// Return the full message payload: [serverPuha] [senderPuha] [kct] [time] [encData] [osign(...)]
msg := msgs[msgId]
s.mu.RUnlock()
s.th.Write(msg, user)
}
func (s *server) Close() {
s.th.Close()
}
func (s *server) GetId() owner.Identity {
return s.sc.Identity()
}
func (s *server) AddRegisterToken(tokenId uint32, token []byte) error {
return s.db.SetRegisterToken(tokenId, token)
}
func (s *server) GetRegisterTokens() map[uint32][]byte {
tokens, err := s.db.GetRegisterTokens()
if err != nil {
return nil
}
return tokens
}

243
sfudp/sfudp.go Normal file
View File

@ -0,0 +1,243 @@
// SFUDP = Safely Fragmented UDP
package sfudp
// 80% of AI generated code
// human designed; not human reviewed
import (
"encoding/binary"
"errors"
"net"
"sync"
"sync/atomic"
"time"
)
const (
MaxFragmentSize = 1024
HeaderSize = 8 // [4B ID][2B Index][1B Count][1B Reserved]
MaxPayload = MaxFragmentSize - HeaderSize
MaxFragments = 255
FragmentTTL = 20 * time.Second
)
// fragmentSet holds state for a fragmented message.
type fragmentSet struct {
frags [][]byte
received int
total int
totalSize int
timestamp time.Time
}
// SFUDPConn = classic UDPConn + transparent fragmentation.
type SFUDPConn struct {
udp *net.UDPConn
fragments map[uint32]*fragmentSet
mu sync.Mutex
nextID uint32
stopGC chan struct{}
readBuf []byte
}
func (c *SFUDPConn) GetUDP() *net.UDPConn { return c.udp }
func ListenSFUDP(network string, laddr *net.UDPAddr) (*SFUDPConn, error) {
u, err := net.ListenUDP(network, laddr)
if err != nil {
return nil, err
}
return newConn(u), nil
}
func DialSFUDP(network string, laddr, raddr *net.UDPAddr) (*SFUDPConn, error) {
u, err := net.DialUDP(network, laddr, raddr)
if err != nil {
return nil, err
}
return newConn(u), nil
}
func newConn(u *net.UDPConn) *SFUDPConn {
c := &SFUDPConn{
udp: u,
fragments: make(map[uint32]*fragmentSet),
stopGC: make(chan struct{}),
readBuf: make([]byte, MaxFragmentSize),
}
go c.gcLoop()
return c
}
func (c *SFUDPConn) Close() error {
close(c.stopGC)
return c.udp.Close()
}
// periodic cleanup of stale fragment sets
func (c *SFUDPConn) gcLoop() {
t := time.NewTicker(FragmentTTL / 2)
defer t.Stop()
for {
select {
case <-t.C:
now := time.Now()
c.mu.Lock()
for id, fs := range c.fragments {
if now.Sub(fs.timestamp) > FragmentTTL {
delete(c.fragments, id)
}
}
c.mu.Unlock()
case <-c.stopGC:
return
}
}
}
// --- Writing ---
func (c *SFUDPConn) Write(data []byte) (int, error) {
return c.writeFragments(data, nil)
}
func (c *SFUDPConn) WriteToSFUDP(data []byte, addr *net.UDPAddr) (int, error) {
return c.writeFragments(data, addr)
}
func (c *SFUDPConn) writeFragments(data []byte, addr *net.UDPAddr) (int, error) {
if len(data) == 0 {
return 0, nil
}
write := func(b []byte) (int, error) {
if addr != nil {
return c.udp.WriteToUDP(b, addr)
}
return c.udp.Write(b)
}
// single packet
if len(data) <= MaxPayload {
pkt := make([]byte, HeaderSize+len(data))
binary.LittleEndian.PutUint32(pkt[0:4], 0)
binary.LittleEndian.PutUint16(pkt[4:6], 0)
pkt[6] = 1
copy(pkt[HeaderSize:], data)
_, err := write(pkt)
return len(data), err
}
fragCount := (len(data) + MaxPayload - 1) / MaxPayload
if fragCount > MaxFragments {
return 0, errors.New("message too large")
}
id := atomic.AddUint32(&c.nextID, 1)
total := 0
for i := 0; i < fragCount; i++ {
start := i * MaxPayload
end := start + MaxPayload
if end > len(data) {
end = len(data)
}
pkt := make([]byte, HeaderSize+(end-start))
binary.LittleEndian.PutUint32(pkt[0:4], id)
binary.LittleEndian.PutUint16(pkt[4:6], uint16(i))
pkt[6] = uint8(fragCount)
copy(pkt[HeaderSize:], data[start:end])
if _, err := write(pkt); err != nil {
return total, err
}
total += end - start
}
return total, nil
}
// --- Reading ---
func (c *SFUDPConn) Read(b []byte) (int, error) {
n, _, err := c.ReadFromSFUDP(b)
return n, err
}
func (c *SFUDPConn) ReadFromSFUDP(b []byte) (int, *net.UDPAddr, error) {
for {
n, addr, err := c.udp.ReadFromUDP(c.readBuf)
if err != nil {
return 0, nil, err
}
if n < HeaderSize {
continue
}
h := c.readBuf[:HeaderSize]
id := binary.LittleEndian.Uint32(h[0:4])
index := binary.LittleEndian.Uint16(h[4:6])
count := h[6]
if count == 0 || int(index) >= int(count) {
continue
}
payload := c.readBuf[HeaderSize:n]
// single fragment
if id == 0 && count == 1 {
if len(payload) > len(b) {
return 0, addr, errors.New("buffer too small")
}
copy(b, payload)
return len(payload), addr, nil
}
c.mu.Lock()
fs := c.fragments[id]
if fs == nil {
fs = &fragmentSet{
frags: make([][]byte, count),
total: int(count),
timestamp: time.Now(),
}
c.fragments[id] = fs
}
fs.timestamp = time.Now()
c.mu.Unlock()
if fs.frags[index] == nil {
cp := make([]byte, len(payload))
copy(cp, payload)
fs.frags[index] = cp
fs.received++
fs.totalSize += len(cp)
}
if fs.received == fs.total {
if fs.totalSize > len(b) {
c.mu.Lock()
delete(c.fragments, id)
c.mu.Unlock()
return 0, addr, errors.New("buffer too small")
}
off := 0
for _, f := range fs.frags {
copy(b[off:], f)
off += len(f)
}
c.mu.Lock()
delete(c.fragments, id)
c.mu.Unlock()
return off, addr, nil
}
}
}

264
sfudp/sfudp_test.go Normal file
View File

@ -0,0 +1,264 @@
package sfudp_test
// 100% of AI generated code
// not reviewed at all
import (
"WhspBrd/sfudp"
"bytes"
"encoding/binary"
"errors"
"net"
"sync"
"testing"
"time"
)
const testTimeout = 2 * time.Second
// --- helpers ---
func newPair(t *testing.T) (*sfudp.SFUDPConn, *sfudp.SFUDPConn) {
t.Helper()
s, err := sfudp.ListenSFUDP("udp", &net.UDPAddr{Port: 0})
if err != nil {
t.Fatal(err)
}
c, err := sfudp.DialSFUDP("udp", nil, s.GetUDP().LocalAddr().(*net.UDPAddr))
if err != nil {
s.Close()
t.Fatal(err)
}
_ = s.GetUDP().SetReadDeadline(time.Now().Add(testTimeout))
_ = c.GetUDP().SetReadDeadline(time.Now().Add(testTimeout))
return s, c
}
func mustWrite(t *testing.T, c *sfudp.SFUDPConn, b []byte) {
t.Helper()
n, err := c.Write(b)
if err != nil {
t.Fatal(err)
}
if n != len(b) {
t.Fatalf("short write: %d != %d", n, len(b))
}
}
func mustRead(t *testing.T, s *sfudp.SFUDPConn, expected []byte) {
t.Helper()
buf := make([]byte, len(expected))
n, err := s.Read(buf)
if err != nil {
t.Fatal(err)
}
if n != len(expected) {
t.Fatalf("size mismatch: %d != %d", n, len(expected))
}
if !bytes.Equal(buf, expected) {
t.Fatal("payload mismatch")
}
}
// --- tests ---
func TestMessageSizes(t *testing.T) {
s, c := newPair(t)
defer s.Close()
defer c.Close()
tests := []int{
1,
100,
sfudp.MaxPayload - 1,
sfudp.MaxPayload,
sfudp.MaxPayload + 1,
sfudp.MaxPayload*3 + 123,
}
for _, size := range tests {
t.Run("size_"+itoa(size), func(t *testing.T) {
msg := make([]byte, size)
for i := range msg {
msg[i] = byte(i)
}
mustWrite(t, c, msg)
mustRead(t, s, msg)
})
}
}
func TestTooLargeMessage(t *testing.T) {
s, c := newPair(t)
defer s.Close()
defer c.Close()
msg := make([]byte, sfudp.MaxPayload*sfudp.MaxFragments+1)
_, err := c.Write(msg)
if err == nil {
t.Fatal("expected error")
}
}
func TestBufferTooSmall(t *testing.T) {
s, c := newPair(t)
defer s.Close()
defer c.Close()
msg := make([]byte, sfudp.MaxPayload*2)
mustWrite(t, c, msg)
small := make([]byte, 10)
_, err := s.Read(small)
if err == nil {
t.Fatal("expected buffer too small error")
}
}
func TestConcurrentWrites(t *testing.T) {
s, c := newPair(t)
defer s.Close()
defer c.Close()
const count = 20
var wg sync.WaitGroup
for i := 0; i < count; i++ {
wg.Add(1)
go func(v int) {
defer wg.Done()
msg := bytes.Repeat([]byte{byte(v)}, sfudp.MaxPayload+50)
mustWrite(t, c, msg)
}(i)
}
go func() {
wg.Wait()
}()
for i := 0; i < count; i++ {
buf := make([]byte, sfudp.MaxPayload+50)
_, err := s.Read(buf)
if err != nil {
t.Fatal(err)
}
}
}
func TestFragmentTimeoutGC(t *testing.T) {
s, _ := newPair(t)
defer s.Close()
raw, err := net.DialUDP("udp", nil, s.GetUDP().LocalAddr().(*net.UDPAddr))
if err != nil {
t.Fatal(err)
}
defer raw.Close()
// send incomplete fragment set
pkt := make([]byte, sfudp.HeaderSize+10)
binary.LittleEndian.PutUint32(pkt[0:4], 999)
binary.LittleEndian.PutUint16(pkt[4:6], 0)
pkt[6] = 2
_, _ = raw.Write(pkt)
time.Sleep(sfudp.FragmentTTL + time.Second)
// IMPORTANT: reset deadline after long sleep
_ = s.GetUDP().SetReadDeadline(time.Now().Add(testTimeout))
// send valid single fragment
valid := []byte("ok")
pkt = make([]byte, sfudp.HeaderSize+len(valid))
pkt[6] = 1
copy(pkt[sfudp.HeaderSize:], valid)
_, _ = raw.Write(pkt)
buf := make([]byte, 10)
n, err := s.Read(buf)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf[:n], valid) {
t.Fatal("unexpected payload")
}
}
func TestInvalidFragmentIgnored(t *testing.T) {
s, _ := newPair(t)
defer s.Close()
raw, err := net.DialUDP("udp", nil, s.GetUDP().LocalAddr().(*net.UDPAddr))
if err != nil {
t.Fatal(err)
}
defer raw.Close()
// invalid index >= count
pkt := make([]byte, sfudp.HeaderSize+10)
binary.LittleEndian.PutUint32(pkt[0:4], 111)
binary.LittleEndian.PutUint16(pkt[4:6], 5)
pkt[6] = 2
_, _ = raw.Write(pkt)
// valid single packet
valid := []byte("good")
pkt = make([]byte, sfudp.HeaderSize+len(valid))
pkt[6] = 1
copy(pkt[sfudp.HeaderSize:], valid)
_, _ = raw.Write(pkt)
buf := make([]byte, 16)
n, err := s.Read(buf)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf[:n], valid) {
t.Fatal("invalid fragment was not ignored")
}
}
func TestEmptyWrite(t *testing.T) {
s, c := newPair(t)
defer s.Close()
defer c.Close()
n, err := c.Write(nil)
if err != nil || n != 0 {
t.Fatal("empty write failed")
}
buf := make([]byte, 16)
_, err = s.Read(buf)
if err == nil {
t.Fatal("expected timeout")
}
if !errors.Is(err, net.ErrClosed) && !isTimeout(err) {
t.Fatal("unexpected error:", err)
}
}
// small int to string helper without fmt
func itoa(v int) string {
if v == 0 {
return "0"
}
var b [20]byte
i := len(b)
for v > 0 {
i--
b[i] = byte('0' + v%10)
v /= 10
}
return string(b[i:])
}
func isTimeout(err error) bool {
nerr, ok := err.(net.Error)
return ok && nerr.Timeout()
}

375
thrembio/client.go Normal file
View File

@ -0,0 +1,375 @@
package thrembio
// 80% of AI generated code
// human designed & reviewed-ish
import (
"WhspBrd/menc"
"WhspBrd/owner"
"WhspBrd/sfudp"
"WhspBrd/typio/bit"
"bytes"
"crypto/mlkem"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"os"
"strconv"
"time"
)
type Client interface {
Close() error
ping() error
GetServerId() owner.Identity
Register(tokenId uint32, token []byte) error
Login() error
Write(data []byte) error
Read() ([]byte, error)
}
type client struct {
conn *sfudp.SFUDPConn
secret owner.Secret
serverId owner.Identity
aes *menc.AESGCM_AutoNonce
sequence uint32
last []byte
debug bool
}
func NewClient(addr *net.UDPAddr, secret owner.Secret) (Client, error) {
if secret == nil {
return nil, ErrSecretCantBeNil
}
conn, err := sfudp.DialSFUDP("udp", nil, addr)
if err != nil {
return nil, err
}
c := &client{
conn: conn,
secret: secret,
debug: os.Getenv("WHSPBRD_DEBUG") != "",
}
err = c.ping()
if err != nil {
return nil, err
}
return c, nil
}
func (c *client) Close() error {
return c.conn.Close()
}
func (c *client) ping() error {
err := c.rawWrite(c.rawHeader(Rq_Ping), none)
if err != nil {
return err
}
t, _, id, err := c.receive()
if err != nil {
return err
}
if t != Rs_Ack {
return fmt.Errorf("expected ack")
}
if len(id) <= 0 {
return fmt.Errorf("expected valid id")
}
c.serverId = id
return nil
}
func (c *client) receive() (PacketResType, []byte, owner.Identity, error) {
buf := make([]byte, 16384)
if err := c.setReadDeadline(); err != nil {
return 0, nil, owner.Identity{}, err
}
n, err := c.conn.Read(buf)
if err != nil {
return 0, nil, owner.Identity{}, err
}
if c.debug {
log.Printf("thrembio receive bytes=%d", n)
}
return c.rawRead(buf[:n])
}
func (c *client) setReadDeadline() error {
ms := readTimeoutMs()
if ms <= 0 {
return nil
}
return c.conn.GetUDP().SetReadDeadline(time.Now().Add(time.Duration(ms) * time.Millisecond))
}
func readTimeoutMs() int {
val := os.Getenv("WHSPBRD_TIMEOUT_MS")
if val == "" {
return 5000
}
ms, err := strconv.Atoi(val)
if err != nil {
return 5000
}
return ms
}
func (c *client) GetServerId() owner.Identity {
return c.serverId
}
func (c *client) Register(tokenId uint32, token []byte) error {
header := c.rawHeader(Rq_Register)
tokenIdB := make([]byte, bit.SizeUint32_B)
binary.BigEndian.PutUint32(tokenIdB, tokenId)
identity := c.secret.Identity()
checkHash := sha256.Sum256(append(append(append(header, tokenIdB...), token...), identity[:]...))
signData := make([]byte, 0, len(header)+len(tokenIdB)+len(checkHash))
signData = append(signData, header...)
signData = append(signData, tokenIdB...)
signData = append(signData, checkHash[:]...)
sign, err := c.secret.Sign(signData)
if err != nil {
return err
}
payload := make([]byte, 0, len(tokenIdB)+len(checkHash)+len(sign))
payload = append(payload, tokenIdB...)
payload = append(payload, checkHash[:]...)
payload = append(payload, sign...)
if err := c.rawWrite(header, payload); err != nil {
return err
}
t, data, id, err := c.receive()
if err != nil {
return err
}
if len(id) > 0 {
c.serverId = id
}
switch t {
case Rs_Ack:
return nil
case Rs_Error:
return fmt.Errorf("register failed: %s", string(data))
default:
return fmt.Errorf("unexpected response type %d", t)
}
}
func (c *client) Login() error {
decKey, err := mlkem.GenerateKey1024()
if err != nil {
return err
}
encapKey := decKey.EncapsulationKey().Bytes()
header := c.rawHeader(Rq_Login)
signData := make([]byte, 0, len(header)+len(encapKey))
signData = append(signData, header...)
signData = append(signData, encapKey...)
sign, err := c.secret.Sign(signData)
if err != nil {
return err
}
payload := make([]byte, 0, len(encapKey)+len(sign))
payload = append(payload, encapKey...)
payload = append(payload, sign...)
if err := c.rawWrite(header, payload); err != nil {
return err
}
t, data, id, err := c.receive()
if err != nil {
return err
}
if len(id) > 0 {
c.serverId = id
}
switch t {
case Rs_Error:
return fmt.Errorf("login failed: %s", string(data))
case Rs_Data:
shared, err := decKey.Decapsulate(data)
if err != nil {
return err
}
key := sha256.Sum256(shared)
c.aes, err = menc.NewAESGCM_AutoNonce(key[:])
if err != nil {
return err
}
c.sequence = 0
return nil
default:
return fmt.Errorf("unexpected response type %d", t)
}
}
func (c *client) Write(data []byte) error {
if c.aes == nil {
return errors.New("not logged in")
}
header := c.rawHeader(Rq_Data)
userID := c.secret.Identity()
seqB := make([]byte, bit.SizeUint32_B)
binary.BigEndian.PutUint32(seqB, c.sequence)
aad := make([]byte, 0, len(header)+len(seqB))
aad = append(aad, header...)
aad = append(aad, seqB...)
ciphertext, err := c.aes.Encrypt(data, aad)
if err != nil {
return err
}
payload := make([]byte, 0, len(userID)+len(seqB)+len(ciphertext))
payload = append(payload, userID[:]...)
payload = append(payload, seqB...)
payload = append(payload, ciphertext...)
if err := c.rawWrite(header, payload); err != nil {
return err
}
t, data, id, err := c.receive()
if err != nil {
return err
}
if len(id) > 0 {
c.serverId = id
}
switch t {
case Rs_Ack:
c.sequence++
return nil
case Rs_Error:
return fmt.Errorf("write failed: %s", string(data))
default:
return fmt.Errorf("unexpected response type %d", t)
}
}
func (c *client) Read() ([]byte, error) {
if c.aes == nil {
return nil, errors.New("not logged in")
}
t, data, id, err := c.receive()
if err != nil {
return nil, err
}
if len(id) > 0 {
c.serverId = id
}
switch t {
case Rs_Data:
if len(c.last) == 0 {
return nil, errors.New("missing last request for decryption")
}
plain, err := c.aes.Decrypt(data, c.last)
if err != nil {
return nil, err
}
return plain, nil
case Rs_Error:
return nil, fmt.Errorf("server error: %s", string(data))
default:
return nil, fmt.Errorf("unexpected response type %d", t)
}
}
func (c *client) rawHeader(reqType PacketReqType) []byte {
buf := make([]byte, commonHeaderSize)
buf[0] = magicBytes[0]
buf[1] = magicBytes[1]
buf[2] = magicBytes[2]
buf[3] = version
nowMs := uint64(time.Now().UnixMilli())
binary.LittleEndian.PutUint64(buf[4:12], nowMs)
buf[12] = byte(reqType)
return buf
}
func (c *client) rawWrite(header, data []byte) error {
last := append(header, data...)
_, err := c.conn.Write(last)
c.last = last
return err
}
func (c *client) rawRead(p []byte) (PacketResType, []byte, owner.Identity, error) {
if len(p) < 37 ||
p[0] != magicBytes[0] ||
p[1] != magicBytes[1] ||
p[2] != magicBytes[2] ||
p[3] != version {
return 0, nil, owner.Identity{}, fmt.Errorf("bad packet")
}
tp := PacketResType(p[4])
if tp >= Rs_Unknown {
return 0, nil, owner.Identity{}, fmt.Errorf("bad type")
}
hash := p[5:37]
data := p[37:]
expectedHash := sha256.Sum256(c.last)
if !bytes.Equal(hash, expectedHash[:]) {
return 0, nil, owner.Identity{}, fmt.Errorf("hash mismatch")
}
if len(data) >= owner.SignatureSize {
payload := data[:len(data)-owner.SignatureSize]
sig := data[len(data)-owner.SignatureSize:]
buf := make([]byte, len(hash)+len(payload))
copy(buf, hash)
copy(buf[len(hash):], payload)
id, err := owner.Verify(buf, sig)
if err == nil {
return tp, payload, id, nil
}
}
if tp == Rs_Data {
return tp, data, owner.Identity{}, nil
}
return 0, nil, owner.Identity{}, fmt.Errorf("bad packet")
}

46
thrembio/global.go Normal file
View File

@ -0,0 +1,46 @@
package thrembio
// 0% of AI generated code
import (
"WhspBrd/owner"
"WhspBrd/typio/bit"
"crypto/mlkem"
"crypto/sha256"
"errors"
)
var (
ErrSecretCantBeNil = errors.New("secret cant be nil")
)
var magicBytes = []byte{0x70, 0x03, 0xEA} // 'p' + 'Ϫ' (U+03EA)
const version byte = 3
var magicWithVersion = append(magicBytes, version)
type PacketReqType byte
type PacketResType byte
const (
Rq_Ping PacketReqType = 0
Rq_Register PacketReqType = 1
Rq_Login PacketReqType = 2
Rq_Data PacketReqType = 3
Rq_Unknown PacketReqType = 4
Rs_Ack PacketResType = 0
Rs_Data PacketResType = 1
Rs_Error PacketResType = 2
Rs_Unknown PacketResType = 3
)
var (
notLoggedB = []byte("not_logged")
notRegisteredB = []byte("not_registered")
none = []byte{0}
)
var commonHeaderSize = len(magicWithVersion) + bit.Size64b_B + 1
var registerRestSize = bit.Size64b_B + sha256.BlockSize + owner.SignatureSize
var loginRestSize = mlkem.EncapsulationKeySize1024 + owner.SignatureSize
var dataRestMinSize = sha256.BlockSize + bit.Size32b_B + 1

603
thrembio/server.go Normal file
View File

@ -0,0 +1,603 @@
package thrembio
// 10% of AI generated code
// human made; AI details/comments
// FINAL
import (
"WhspBrd/menc"
"WhspBrd/owner"
"WhspBrd/sfudp"
"WhspBrd/thrembio/taskpool"
"WhspBrd/typio/bit"
"bytes"
"crypto/mlkem"
"crypto/sha256"
"encoding/binary"
"errors"
"log"
"net"
"os"
"sync"
"time"
)
var (
ErrServerAlreadyOpen = errors.New("server already open")
ErrMalformedPacket = errors.New("user provided malformed packet")
ErrNoAssociatedRegisterTokenFound = errors.New("user provided register token ID that doesn't exist")
ErrInvalidRegisterCheckHash = errors.New("user provided invalid register check hash (binds token + identity)")
ErrUserAlreadyRegistered = errors.New("user is already registered")
ErrNotRegisteredTriedToLogin = errors.New("user that is not registered tried to login")
ErrReplayAttackSuspected = errors.New("replay attack suspected due to repeated packet")
ErrUserNotLoggedIn = errors.New("user not logged in tried to send data")
)
type ReadType bit.Bit8
const (
RT_None ReadType = iota
// New user registered
RT_Register
// User logged in
RT_Login
// Data from authenticated user
RT_Data
// Client-related error
RT_UserError
// Internal server error
RT_InsideError
)
type serverFlag bit.Bit8
const (
SF_None serverFlag = 0
// Don't log user errors (like invalid register token, invalid login, etc.)
SF_NoUserErrorLog serverFlag = 1 << 0
// Don't log register events (successful ones)
SF_NoRegisterLog serverFlag = 1 << 1
// Don't log login events (successful ones)
SF_NoLoginLog serverFlag = 1 << 2
)
func (f *serverFlag) has(flag serverFlag) bool {
return (*f)&flag != 0
}
type reqPacket struct {
from net.UDPAddr // The address of the client that sent the request.
time uint64
reqType PacketReqType // The type of the request (register, login, data, etc.)
fullPacket []byte // The full packet that was sent by the client, including everything.
header []byte
packet []byte // The packet without the common header (magic, version, timestamp, type), so just the payload-ish.
// register
_got_tokenID_B []byte
_got_checkHash []byte
_got_sign []byte
_got_tokenID uint32
_token []byte
// login
_got_encapKey []byte
//_got_sign []byte
// data
_got_user []byte
_got_seq_B []byte
_got_payload []byte
_got_seq uint32
_sesh [32]byte
// temps
__tempBool bool
__tempBytes []byte
// for auto parts
__offset int
}
const rP_ReadAll = -1
func (pkt *reqPacket) startParts() {
pkt.__offset = 0
}
func (pkt *reqPacket) nextPart(size int) []byte {
start := pkt.__offset
if start > len(pkt.packet) {
return nil
}
var end int
switch {
case size == rP_ReadAll:
end = len(pkt.packet)
case size >= 0 && start+size <= len(pkt.packet):
end = start + size
default:
return nil
}
pkt.__offset = end
return pkt.packet[start:end]
}
type readPacket struct {
rt ReadType
u owner.Identity
fa net.UDPAddr
d []byte
e error
}
type Server interface {
Open() error
Close()
SetFlags(flags serverFlag)
/*
Read waits for incoming packets until a valid one is processed.
Invalid packets are ignored.
Returns one of:
RT_Register:
(RT_Register, newUser, fromAddr, nil, nil)
Ignored if SF_NoRegisterLog is enabled.
RT_Login:
(RT_Login, user, fromAddr, nil, nil)
Ignored if SF_NoLoginLog is enabled.
RT_Data:
(RT_Data, user, fromAddr, requestData, nil)
RT_UserError:
(RT_UserError, ?user, fromAddr, nil, error)
Ignored if SF_NoUserError is enabled.
RT_InsideError:
(RT_InsideError, emptyUser, emptyAddr, nil, error)
*/
Read() (readType ReadType, user owner.Identity, fromAddr net.UDPAddr, data []byte, err error)
Write(data []byte, to owner.Identity) error
}
type server struct {
port uint16
secret owner.Secret
db ServerDB
flags serverFlag
listener *sfudp.SFUDPConn
debug bool
bufPool sync.Pool
taskPool *taskpool.Pool[reqPacket]
packets chan readPacket
done chan struct{}
doneMu sync.Once
}
func NewServer(port uint16, secret owner.Secret, db ServerDB) (Server, error) {
if secret == nil {
return nil, ErrSecretCantBeNil
}
srv := &server{
port: port,
secret: secret,
db: db,
listener: nil,
taskPool: taskpool.New[reqPacket](0, 0),
bufPool: sync.Pool{
New: func() any {
b := make([]byte, 16384)
return &b
},
},
debug: os.Getenv("WHSPBRD_DEBUG") != "",
}
return srv, nil
}
func (s *server) SetFlags(flags serverFlag) {
s.flags = flags
}
func (s *server) Open() error {
if s.listener != nil {
return ErrServerAlreadyOpen
}
l, err := sfudp.ListenSFUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: int(s.port)})
if err != nil {
return err
}
s.listener = l
s.packets = make(chan readPacket, 1024)
s.done = make(chan struct{})
s.taskPool.Open()
go s.serve()
return nil
}
func (s *server) serve() {
defer close(s.packets)
for {
select {
case <-s.done:
return
default:
}
bufPtr := s.bufPool.Get().(*[]byte)
buf := *bufPtr
length, addr, err := s.listener.ReadFromSFUDP(buf[:])
if err != nil {
s.bufPool.Put(bufPtr)
return
}
if length < commonHeaderSize ||
buf[0] != magicBytes[0] ||
buf[1] != magicBytes[1] ||
buf[2] != magicBytes[2] ||
buf[3] != version {
continue
}
ts := binary.LittleEndian.Uint64(buf[4:12])
now := time.Now()
nowMs := uint64(now.UnixMilli())
if ts > nowMs || nowMs-ts > 8_000 {
continue
}
reqType := PacketReqType(buf[12])
if reqType >= Rq_Unknown {
continue
}
pkt := reqPacket{
from: *addr,
time: ts,
reqType: reqType,
fullPacket: buf[:length],
}
pkt.header = pkt.fullPacket[:13]
pkt.packet = pkt.fullPacket[13:]
s.taskPool.Dispatch(taskpool.Task[reqPacket]{
Value: pkt,
Handle: s.handlePacket,
})
}
}
func (s *server) handlePacket(pkt reqPacket) {
defer s.bufPool.Put(&pkt.fullPacket)
var (
user owner.Identity
data []byte
userErr error
err error
)
switch pkt.reqType {
case Rq_Ping:
s.rawWriteAck(&pkt.from, pkt.fullPacket, true)
return
case Rq_Register:
user, userErr, err = s.handleRegister(pkt)
case Rq_Login:
user, userErr, err = s.handleLogin(pkt)
case Rq_Data:
user, data, userErr, err = s.handleData(pkt)
default:
return
}
s.emitResult(pkt, user, data, userErr, err)
}
func (s *server) emitResult(
pkt reqPacket,
user owner.Identity,
data []byte,
userErr error,
err error,
) {
var rp readPacket
if err != nil {
rp = readPacket{RT_InsideError, owner.Identity{}, net.UDPAddr{}, nil, err}
} else if userErr != nil {
if s.debug {
log.Printf("user error: %v", userErr)
}
if err == nil {
_ = s.rawWriteError(&pkt.from, pkt.fullPacket, userErrPayload(userErr), true)
}
if s.flags.has(SF_NoUserErrorLog) {
return
}
rp = readPacket{RT_UserError, owner.Identity{}, pkt.from, nil, userErr}
} else {
switch pkt.reqType {
case Rq_Register:
if s.flags.has(SF_NoRegisterLog) {
return
}
rp = readPacket{RT_Register, user, pkt.from, nil, nil}
case Rq_Login:
if s.flags.has(SF_NoLoginLog) {
return
}
rp = readPacket{RT_Login, user, pkt.from, nil, nil}
case Rq_Data:
rp = readPacket{RT_Data, user, pkt.from, data, nil}
}
s.db.SetLastIp(user, &pkt.from)
}
select {
case s.packets <- rp:
case <-s.done:
}
}
func (s *server) Close() {
s.doneMu.Do(func() {
if s.listener == nil {
return
}
close(s.done)
s.listener.Close()
s.taskPool.Close()
s.listener = nil
})
}
func (s *server) Read() (ReadType, owner.Identity, net.UDPAddr, []byte, error) {
p, ok := <-s.packets
if !ok {
return RT_InsideError, owner.Identity{}, net.UDPAddr{}, nil, net.ErrClosed
}
return p.rt, p.u, p.fa, p.d, p.e
}
func (s *server) Write(data []byte, user owner.Identity) error {
ip, ex := s.db.GetLastIp(user)
if !ex {
return errors.New("no known IP for user")
}
req, ex := s.db.GetLastReq(user)
if !ex {
return errors.New("no known last request for user")
}
key, ex, err := s.db.GetActiveSession(user)
if err != nil {
return err
}
if !ex {
return errors.New("no active session for user")
}
ciphertext, err := menc.AESGCM_Quick_Encrypt(key[:], data, req)
if err != nil {
return err
}
return s.rawWriteData(ip, req, ciphertext, false)
}
func (s *server) handleRegister(pkt reqPacket) (newUser owner.Identity, userErr error, err error) {
pkt.startParts()
pkt._got_tokenID_B = pkt.nextPart(bit.SizeUint32_B)
pkt._got_checkHash = pkt.nextPart(bit.SizeSha256_B)
pkt._got_sign = pkt.nextPart(owner.SignatureSize)
pkt.__tempBytes = pkt.nextPart(rP_ReadAll)
if pkt._got_tokenID_B == nil || pkt._got_checkHash == nil || pkt._got_sign == nil || len(pkt.__tempBytes) != 0 {
userErr = ErrMalformedPacket
return
}
pkt._got_tokenID = binary.BigEndian.Uint32(pkt._got_tokenID_B)
// --- ATOMIC token consumption ---
pkt._token, pkt.__tempBool, err = s.db.ConsumeRegisterToken(pkt._got_tokenID)
if err != nil {
return
} else if !pkt.__tempBool {
userErr = ErrNoAssociatedRegisterTokenFound
return
}
// --- verify identity signature ---
newUser, userErr = owner.Verify(
pkt.fullPacket[:len(pkt.fullPacket)-owner.SignatureSize],
pkt._got_sign,
)
if userErr != nil {
return
}
// --- check hash binds token + identity ---
sha := sha256.New()
sha.Write(pkt.header)
sha.Write(pkt._got_tokenID_B)
sha.Write(pkt._token)
sha.Write(newUser[:])
if !bytes.Equal(sha.Sum(nil), pkt._got_checkHash) {
userErr = ErrInvalidRegisterCheckHash
return
}
// --- ATOMIC register user ---
alreadyExists, err := s.db.AddRegisteredUserAtomic(newUser)
if err != nil {
return
} else if alreadyExists {
userErr = ErrUserAlreadyRegistered
return
}
err = s.rawWriteAck(&pkt.from, pkt.fullPacket, true)
return
}
func (s *server) handleLogin(pkt reqPacket) (user owner.Identity, userErr error, err error) {
pkt.startParts()
pkt._got_encapKey = pkt.nextPart(mlkem.EncapsulationKeySize1024)
pkt._got_sign = pkt.nextPart(owner.SignatureSize)
pkt.__tempBytes = pkt.nextPart(rP_ReadAll)
if pkt._got_encapKey == nil || pkt._got_sign == nil || len(pkt.__tempBytes) != 0 {
userErr = ErrMalformedPacket
return
}
// --- verify user signature ---
user, userErr = owner.Verify(
pkt.fullPacket[:len(pkt.fullPacket)-owner.SignatureSize],
pkt._got_sign,
)
if userErr != nil {
return
}
// --- check registered user ---
pkt.__tempBool, err = s.db.HasRegisteredUser(user)
if err != nil {
return
} else if !pkt.__tempBool {
err = s.rawWriteError(&pkt.from, pkt.fullPacket, notRegisteredB, true)
userErr = ErrNotRegisteredTriedToLogin
return
}
// --- ATOMIC anti replay check ---
pkt.__tempBool, err = s.db.CheckAndSetLastReq(user, pkt.fullPacket)
if err != nil {
return
} else if pkt.__tempBool {
userErr = ErrReplayAttackSuspected
return
}
// --- generate encapsulation key ---
encKey, userErr := mlkem.NewEncapsulationKey1024(pkt._got_encapKey)
if userErr != nil {
return
}
shared, ciphertext := encKey.Encapsulate()
// --- set active session ---
err = s.db.SetActiveSession(user, sha256.Sum256(shared))
if err != nil {
return
}
err = s.rawWriteData(&pkt.from, pkt.fullPacket, ciphertext, true)
return
}
func (s *server) handleData(pkt reqPacket) (user owner.Identity, data []byte, userErr error, err error) {
pkt.startParts()
pkt._got_user = pkt.nextPart(owner.IdentitySize)
pkt._got_seq_B = pkt.nextPart(bit.SizeUint32_B)
pkt._got_payload = pkt.nextPart(rP_ReadAll)
if pkt._got_user == nil || pkt._got_seq_B == nil || pkt._got_payload == nil {
userErr = ErrMalformedPacket
return
}
pkt._got_seq = binary.BigEndian.Uint32(pkt._got_seq_B)
// --- check logged in user ---
pkt._sesh, pkt.__tempBool, err = s.db.GetActiveSession(owner.Identity(pkt._got_user))
if err != nil {
return
} else if !pkt.__tempBool {
err = s.rawWriteError(&pkt.from, pkt.fullPacket, notLoggedB, true)
userErr = ErrUserNotLoggedIn
return
}
data, userErr = menc.AESGCM_Quick_Decrypt(pkt._sesh[:], pkt._got_payload, append(pkt.header, pkt._got_seq_B...))
if userErr != nil {
return
}
// --- ATOMIC anti replay check ---
pkt.__tempBool, err = s.db.CheckAndSetLastReq(user, pkt.fullPacket)
if err != nil {
return
} else if pkt.__tempBool {
userErr = ErrReplayAttackSuspected
return
}
err = s.rawWriteAck(&pkt.from, pkt.fullPacket, true)
return
}
func (s *server) rawWriteAck(to *net.UDPAddr, tohash []byte, signed bool) error {
return s.rawWrite(to, Rs_Ack, tohash, none, signed)
}
func (s *server) rawWriteData(to *net.UDPAddr, tohash []byte, data []byte, signed bool) error {
return s.rawWrite(to, Rs_Data, tohash, data, signed)
}
func (s *server) rawWriteError(to *net.UDPAddr, tohash []byte, err []byte, signed bool) error {
return s.rawWrite(to, Rs_Error, tohash, err, signed)
}
func (s *server) rawWrite(to *net.UDPAddr, tp PacketResType, tohash []byte, payload []byte, signed bool) error {
hash := sha256.Sum256(tohash)
data := append(hash[:], payload...)
if signed {
signature, err := s.secret.Sign(data)
if err != nil {
panic("klokotek")
}
data = append(data, signature...)
}
data = append(magicWithVersion, append([]byte{byte(tp)}, data...)...)
_, err := s.listener.WriteToSFUDP(data, to)
return err
}
func userErrPayload(err error) []byte {
switch {
case errors.Is(err, ErrMalformedPacket):
return []byte("malformed")
case errors.Is(err, ErrNoAssociatedRegisterTokenFound):
return []byte("no_token")
case errors.Is(err, ErrInvalidRegisterCheckHash):
return []byte("invalid_check")
case errors.Is(err, ErrUserAlreadyRegistered):
return []byte("already_registered")
case errors.Is(err, ErrNotRegisteredTriedToLogin):
return []byte("not_registered")
case errors.Is(err, ErrUserNotLoggedIn):
return []byte("not_logged")
case errors.Is(err, ErrReplayAttackSuspected):
return []byte("replay")
default:
return []byte("user_error")
}
}

310
thrembio/serverDb.go Normal file
View File

@ -0,0 +1,310 @@
package thrembio
// 90% of AI generated code
// human designed & reviewed
import (
"WhspBrd/owner"
"bytes"
"database/sql"
"net"
"sync"
_ "github.com/mattn/go-sqlite3"
)
type ServerDB interface {
// Registration tokens (atomic usage)
GetRegisterTokens() (map[uint32][]byte, error) // optional read-only snapshot
SetRegisterToken(id uint32, token []byte) error // add new token
ConsumeRegisterToken(id uint32) ([]byte, bool, error) // atomic get + remove
// Registered users
GetRegisteredUsers() ([]owner.Identity, error) // optional snapshot
HasRegisteredUser(user owner.Identity) (bool, error)
AddRegisteredUserAtomic(user owner.Identity) (alreadyExists bool, err error) // atomic insert
RemoveRegisteredUser(user owner.Identity) error
// Active sessions
GetActiveSessions() (map[owner.Identity][32]byte, error)
SetActiveSession(user owner.Identity, secret [32]byte) error
GetActiveSession(user owner.Identity) ([32]byte, bool, error)
RemoveActiveSession(user owner.Identity) error
/*
Replay prevention (atomic)
Not db persistent.
*/
CheckAndSetLastReq(user owner.Identity, reqData []byte) (isReplay bool, err error)
CheckAndSetLastSeq(user owner.Identity, seq uint32) (isReplay bool, err error)
GetLastReq(user owner.Identity) ([]byte, bool)
/*
Not db persistent
*/
SetLastIp(user owner.Identity, ip *net.UDPAddr)
GetLastIp(user owner.Identity) (*net.UDPAddr, bool)
// Close DB
Close() error
}
type serverDB struct {
db *sql.DB
lastReqsMu sync.Mutex
lastReqs map[owner.Identity][]byte
lastSeqsMu sync.Mutex
lastSeqs map[owner.Identity]uint32
lastIpsMu sync.Mutex
lastIps map[owner.Identity]*net.UDPAddr
}
func NewSQLiteDB(path string) (ServerDB, error) {
db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
if err := db.Ping(); err != nil {
return nil, err
}
s := &serverDB{
db: db,
lastReqs: make(map[owner.Identity][]byte),
lastSeqs: make(map[owner.Identity]uint32),
lastIps: make(map[owner.Identity]*net.UDPAddr),
}
if err := s.init(); err != nil {
return nil, err
}
return s, nil
}
func (s *serverDB) init() error {
_, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS register_tokens (
id INTEGER PRIMARY KEY,
token BLOB NOT NULL
);
CREATE TABLE IF NOT EXISTS registered_users (
username BLOB PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS active_sessions (
username BLOB PRIMARY KEY,
session_id BLOB NOT NULL
);`)
return err
}
// --- helpers ---
func idToBytes(id owner.Identity) []byte {
b := make([]byte, len(id))
copy(b, id[:])
return b
}
func bytesToID(b []byte) owner.Identity {
var id owner.Identity
copy(id[:], b)
return id
}
// --- register tokens ---
func (s *serverDB) GetRegisterTokens() (map[uint32][]byte, error) {
rows, err := s.db.Query(`SELECT id, token FROM register_tokens`)
if err != nil {
return nil, err
}
defer rows.Close()
tokens := make(map[uint32][]byte)
for rows.Next() {
var id uint32
var token []byte
if err := rows.Scan(&id, &token); err != nil {
return nil, err
}
tokens[id] = token
}
return tokens, nil
}
func (s *serverDB) SetRegisterToken(id uint32, token []byte) error {
_, err := s.db.Exec(`
INSERT INTO register_tokens(id, token)
VALUES(?, ?)
ON CONFLICT(id) DO UPDATE SET token = excluded.token
`, id, token)
return err
}
// --- ATOMIC: consume token ---
func (s *serverDB) ConsumeRegisterToken(id uint32) ([]byte, bool, error) {
row := s.db.QueryRow(`
DELETE FROM register_tokens
WHERE id = ?
RETURNING token
`, id)
var token []byte
err := row.Scan(&token)
if err == sql.ErrNoRows {
return nil, false, nil
}
return token, err == nil, err
}
// --- registered users ---
func (s *serverDB) GetRegisteredUsers() ([]owner.Identity, error) {
rows, err := s.db.Query(`SELECT username FROM registered_users`)
if err != nil {
return nil, err
}
defer rows.Close()
var users []owner.Identity
for rows.Next() {
var u []byte
if err := rows.Scan(&u); err != nil {
return nil, err
}
users = append(users, bytesToID(u))
}
return users, nil
}
func (s *serverDB) HasRegisteredUser(user owner.Identity) (bool, error) {
var exists int
err := s.db.QueryRow(
`SELECT EXISTS(SELECT 1 FROM registered_users WHERE username = ?)`,
idToBytes(user),
).Scan(&exists)
return exists == 1, err
}
// --- ATOMIC: add user if not exists ---
func (s *serverDB) AddRegisteredUserAtomic(user owner.Identity) (alreadyExists bool, err error) {
res, err := s.db.Exec(`
INSERT OR IGNORE INTO registered_users(username) VALUES(?)
`, idToBytes(user))
if err != nil {
return false, err
}
rows, err := res.RowsAffected()
if err != nil {
return false, err
}
return rows == 0, nil
}
func (s *serverDB) RemoveRegisteredUser(user owner.Identity) error {
_, err := s.db.Exec(`DELETE FROM registered_users WHERE username = ?`, idToBytes(user))
return err
}
// --- active sessions ---
func (s *serverDB) GetActiveSessions() (map[owner.Identity][32]byte, error) {
rows, err := s.db.Query(`SELECT username, session_id FROM active_sessions`)
if err != nil {
return nil, err
}
defer rows.Close()
sessions := make(map[owner.Identity][32]byte)
for rows.Next() {
var username []byte
var key [32]byte
if err := rows.Scan(&username, &key); err != nil {
return nil, err
}
sessions[bytesToID(username)] = key
}
return sessions, nil
}
func (s *serverDB) SetActiveSession(user owner.Identity, key [32]byte) error {
_, err := s.db.Exec(`
INSERT INTO active_sessions(username, session_id)
VALUES(?, ?)
ON CONFLICT(username) DO UPDATE SET session_id = excluded.session_id
`, idToBytes(user), key[:])
return err
}
func (s *serverDB) GetActiveSession(user owner.Identity) ([32]byte, bool, error) {
var key [32]byte
err := s.db.QueryRow(
`SELECT session_id FROM active_sessions WHERE username = ?`,
idToBytes(user),
).Scan(&key)
if err == sql.ErrNoRows {
return [32]byte{}, false, nil
}
return key, err == nil, err
}
func (s *serverDB) RemoveActiveSession(user owner.Identity) error {
_, err := s.db.Exec(`DELETE FROM active_sessions WHERE username = ?`, idToBytes(user))
return err
}
// --- ATOMIC: login replay prevention ---
func (s *serverDB) CheckAndSetLastReq(user owner.Identity, reqData []byte) (isReplay bool, err error) {
s.lastReqsMu.Lock()
defer s.lastReqsMu.Unlock()
last, exists := s.lastReqs[user]
if exists && bytes.Equal(last, reqData) {
return true, nil
}
s.lastReqs[user] = reqData
return false, nil
}
func (s *serverDB) CheckAndSetLastSeq(user owner.Identity, seq uint32) (isReplay bool, err error) {
s.lastSeqsMu.Lock()
defer s.lastSeqsMu.Unlock()
last, exists := s.lastSeqs[user]
if exists && seq <= last {
return true, nil
}
s.lastSeqs[user] = seq
return false, nil
}
func (s *serverDB) GetLastReq(user owner.Identity) ([]byte, bool) {
s.lastReqsMu.Lock()
defer s.lastReqsMu.Unlock()
req, exists := s.lastReqs[user]
return req, exists
}
// just ip save
func (s *serverDB) SetLastIp(user owner.Identity, ip *net.UDPAddr) {
s.lastIpsMu.Lock()
defer s.lastIpsMu.Unlock()
s.lastIps[user] = ip
}
func (s *serverDB) GetLastIp(user owner.Identity) (*net.UDPAddr, bool) {
s.lastIpsMu.Lock()
defer s.lastIpsMu.Unlock()
ip, exists := s.lastIps[user]
return ip, exists
}
// --- close ---
func (s *serverDB) Close() error { return s.db.Close() }

View File

@ -0,0 +1,127 @@
// taskpool: simple bounded worker pool with safe shutdown + fast dispatch
package taskpool
// 80% of AI generated code
// human designed & reviewed
import (
"runtime"
"sync"
)
// Task = value + handler
type Task[T any] struct {
Value T
Handle func(T)
}
// Pool manages worker goroutines processing tasks from a queue.
type Pool[T any] struct {
workers int // number of worker goroutines
queueSize int // channel capacity
mu sync.RWMutex // RLock for dispatch, Lock for open/close
tasks chan Task[T] // task queue (nil when closed)
wg sync.WaitGroup
closed bool // prevents sends after close
}
// New creates a pool.
// nWorkers <= 0 → NumCPU
// queueSize <= 0 → 256
func New[T any](nWorkers, queueSize int) *Pool[T] {
if nWorkers <= 0 {
nWorkers = runtime.NumCPU()
}
if queueSize <= 0 {
queueSize = 256
}
return &Pool[T]{workers: nWorkers, queueSize: queueSize}
}
// Open starts workers (idempotent, restartable after Close).
func (p *Pool[T]) Open() {
p.mu.Lock()
defer p.mu.Unlock()
if !p.closed && p.tasks != nil { // already running
return
}
p.tasks = make(chan Task[T], p.queueSize)
p.closed = false
for i := 0; i < p.workers; i++ {
p.wg.Add(1)
go func() {
defer p.wg.Done()
for t := range p.tasks { // exits on close
t.Handle(t.Value)
}
}()
}
}
// Dispatch: try queue → fallback inline.
// Non-blocking, safe under concurrent Close.
func (p *Pool[T]) Dispatch(t Task[T]) {
p.mu.RLock()
tasks := p.tasks
closed := p.closed
if closed || tasks == nil {
p.mu.RUnlock()
t.Handle(t.Value) // pool unavailable → run inline
return
}
select {
case tasks <- t: // fast path
p.mu.RUnlock()
default:
p.mu.RUnlock()
t.Handle(t.Value) // queue full → backpressure
}
}
// TryDispatch: enqueue only, no fallback.
func (p *Pool[T]) TryDispatch(t Task[T]) bool {
p.mu.RLock()
tasks := p.tasks
closed := p.closed
if closed || tasks == nil {
p.mu.RUnlock()
return false
}
select {
case tasks <- t:
p.mu.RUnlock()
return true
default:
p.mu.RUnlock()
return false
}
}
// Close stops workers and waits for completion.
// Safe to call multiple times.
func (p *Pool[T]) Close() {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
return
}
p.closed = true
tasks := p.tasks
p.tasks = nil // prevent future sends
p.mu.Unlock()
if tasks != nil {
close(tasks) // safe: no send can happen (RLock prevents race)
}
p.wg.Wait() // wait for workers to drain
}

69
typio/base58/base58.go Normal file
View File

@ -0,0 +1,69 @@
package base58
// 20% of AI generated code
// human made; AI enhanced
import (
"errors"
"math"
"math/big"
)
var (
ErrInvalidBase58Character = errors.New("invalid character in base58 string")
)
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
var revAlphabet = func() map[byte]int {
m := make(map[byte]int)
for i := range len(alphabet) {
m[alphabet[i]] = i
}
return m
}()
var b58 = big.NewInt(58)
var b256 = big.NewInt(256)
var estimate = math.Log(256) / math.Log(58)
func Encode(input []byte) string {
number := new(big.Int).SetBytes(input)
if number.Sign() == 0 {
return string(alphabet[0])
}
bufCap := int(float64(len(input))*estimate) + 8
buf := make([]byte, 0, bufCap)
mod := new(big.Int)
for number.Sign() > 0 {
number.DivMod(number, b58, mod)
if cap(buf) > len(buf) {
buf = buf[:len(buf)+1]
copy(buf[1:], buf[:len(buf)-1+1])
buf[0] = alphabet[mod.Int64()]
} else {
tmp := make([]byte, 1, cap(buf)+1)
tmp[0] = alphabet[mod.Int64()]
buf = append(tmp, buf...)
}
}
return string(buf)
}
func Decode(input string) ([]byte, error) {
number := big.NewInt(0)
for i := 0; i < len(input); i++ {
c := input[i]
val, ok := revAlphabet[c]
if !ok {
return nil, ErrInvalidBase58Character
}
number.Mul(number, b58)
number.Add(number, big.NewInt(int64(val)))
}
return number.Bytes(), nil
}

55
typio/bit/bit.go Normal file
View File

@ -0,0 +1,55 @@
package bit
// 0% of AI generated code
// simple helper
const (
Size8b_B = 1
Size16b_B = 2
Size32b_B = 4
Size64b_B = 8
Size128b_B = 16
Size256b_B = 32
Size512b_B = 64
SizeUint8_B = Size8b_B
SizeUint16_B = Size16b_B
SizeUint32_B = Size32b_B
SizeUint64_B = Size64b_B
SizeSha128_B = Size128b_B
SizeSha256_B = Size256b_B
SizeSha512_B = Size512b_B
Size1B_b = 8
Size2B_b = 16
Size4B_b = 32
Size8B_b = 64
Size16B_b = 128
Size32B_b = 256
Size64B_b = 512
Size1KB_b = 1024
Size2KB_b = 2048
Size4KB_b = 4096
Size8KB_b = 8192
Size16KB_b = 16384
Size32KB_b = 32768
Size64KB_b = 65536
)
type (
Bit8 = uint8
Bit16 = uint16
Bit32 = uint32
Bit64 = uint64
Bit128 = [16]byte
Bit256 = [32]byte
Bit384 = [48]byte
Bit512 = [64]byte
Sha128 = Bit128
Sha256 = Bit256
Sha384 = Bit384
Sha512 = Bit512
)

47
typio/splco/splco.go Normal file
View File

@ -0,0 +1,47 @@
package splco
// why doesnt Go have this built in
// 30% of AI generated code
// simple helper
import "errors"
var (
ErrDataTooShort = errors.New("data too short for specified sizes")
ErrInvalidDataLength = errors.New("data length does not match sum of specified sizes")
)
func Append(data ...[]byte) []byte {
expLen := 0
for _, b := range data {
expLen += len(b)
}
return AppendWithSize(expLen, data...)
}
func AppendWithSize(size int, data ...[]byte) []byte {
return AppendInto(make([]byte, 0, size), data...)
}
func AppendInto(dest []byte, data ...[]byte) []byte {
for _, b := range data {
dest = append(dest, b...)
}
return dest
}
func Split(data []byte, sizes ...int) ([][]byte, error) {
result := make([][]byte, len(sizes))
offset := 0
for i, size := range sizes {
if offset+size > len(data) {
return nil, ErrDataTooShort
}
result[i] = data[offset : offset+size]
offset += size
}
if offset != len(data) {
return nil, ErrInvalidDataLength
}
return result, nil
}

114
typio/yum/yum.go Normal file
View File

@ -0,0 +1,114 @@
package yum
// 95% of AI generated code
// human designed & reviewed
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"os"
"golang.org/x/crypto/argon2"
)
const (
saltSize = 16
keySize = 32
nonceSize = 12
// Argon2 params (tune if needed)
argonTime = 1
argonMemory = 64 * 1024 // 64 MB
argonThreads = 4
)
// YumSave encrypts and saves data securely to disk.
func YumSave(path string, data []byte, password []byte) error {
// Generate salt
salt := make([]byte, saltSize)
if _, err := rand.Read(salt); err != nil {
return err
}
// Derive key
key := argon2.IDKey(password, salt, argonTime, argonMemory, argonThreads, keySize)
// Create AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// Generate nonce
nonce := make([]byte, nonceSize)
if _, err := rand.Read(nonce); err != nil {
return err
}
// Encrypt
ciphertext := gcm.Seal(nil, nonce, data, nil)
// File format: [salt | nonce | ciphertext]
fileData := append(salt, nonce...)
fileData = append(fileData, ciphertext...)
return os.WriteFile(path, fileData, 0600)
}
// YumLoad loads and decrypts data from disk.
func YumLoad(path string, password []byte) ([]byte, error) {
fileData, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if len(fileData) < saltSize+nonceSize {
return nil, errors.New("file too short")
}
// Extract components
salt := fileData[:saltSize]
nonce := fileData[saltSize : saltSize+nonceSize]
ciphertext := fileData[saltSize+nonceSize:]
// Derive key
key := argon2.IDKey(password, salt, argonTime, argonMemory, argonThreads, keySize)
// Recreate cipher
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Decrypt
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, errors.New("decryption failed (wrong password or corrupted data)")
}
return plaintext, nil
}
func YumSeed(buf []byte) error {
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
for i := range buf {
buf[i] = 0
}
return err
}
return nil
}