Merge pull request '[agent/codex] Upgrade this package to dappco.re/go/core v0.8.0-alpha.1. Ru...' (#10) from agent/upgrade-to-core-v0-8-0-alpha-1 into dev
All checks were successful
Security Scan / security (push) Successful in 7s
Test / test (push) Successful in 2m3s

This commit is contained in:
Virgil 2026-03-26 14:37:52 +00:00
commit 0ab5d87ca2
21 changed files with 402 additions and 295 deletions

View file

@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project
`go-p2p` is the P2P networking layer for the Lethean network. Module path: `forge.lthn.ai/core/go-p2p`
`go-p2p` is the P2P networking layer for the Lethean network. Module path: `dappco.re/go/core/p2p`
## Prerequisites
@ -80,8 +80,8 @@ type ProfileManager interface {
- Licence: EUPL-1.2 — new files need `// SPDX-License-Identifier: EUPL-1.2`
- Security-first: do not weaken HMAC, challenge-response, Zip Slip defence, or rate limiting
- Use `logging` package only — no `fmt.Println` or `log.Printf` in library code
- Error handling: use `coreerr.E()` from `go-log` — never `fmt.Errorf` or `errors.New` in library code
- File I/O: use `coreio.Local` from `go-io` — never `os.ReadFile`/`os.WriteFile` in library code (exception: `os.OpenFile` for streaming writes where `coreio` lacks support)
- Error handling: use `core.E()` from `dappco.re/go/core` — never `fmt.Errorf` or `errors.New` in library code
- File I/O: use `dappco.re/go/core` filesystem helpers (package-level adapters in `node/` backed by `core.Fs`) — never `os.ReadFile`/`os.WriteFile` in library code (exception: `os.OpenFile` for streaming writes where filesystem helpers cannot preserve tar header mode bits)
- Hot-path debug logging uses sampling pattern: `if counter.Add(1)%interval == 0`
### Transport test helper

5
go.mod
View file

@ -3,8 +3,7 @@ module dappco.re/go/core/p2p
go 1.26.0
require (
dappco.re/go/core/io v0.2.0
dappco.re/go/core/log v0.1.0
dappco.re/go/core v0.8.0-alpha.1
forge.lthn.ai/Snider/Borg v0.3.1
forge.lthn.ai/Snider/Poindexter v0.0.3
github.com/adrg/xdg v0.5.3
@ -15,11 +14,11 @@ require (
require (
forge.lthn.ai/Snider/Enchantrix v0.0.4 // indirect
forge.lthn.ai/core/go-log v0.0.4 // indirect
github.com/ProtonMail/go-crypto v1.4.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/sys v0.42.0 // indirect

66
go.sum
View file

@ -1,45 +1,99 @@
dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4=
dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E=
dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc=
dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs=
dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk=
dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8=
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
forge.lthn.ai/Snider/Enchantrix v0.0.4 h1:biwpix/bdedfyc0iVeK15awhhJKH6TEMYOTXzHXx5TI=
forge.lthn.ai/Snider/Enchantrix v0.0.4/go.mod h1:OGCwuVeZPq3OPe2h6TX/ZbgEjHU6B7owpIBeXQGbSe0=
forge.lthn.ai/Snider/Poindexter v0.0.3 h1:cx5wRhuLRKBM8riIZyNVAT2a8rwRhn1dodFBktocsVE=
forge.lthn.ai/Snider/Poindexter v0.0.3/go.mod h1:ddzGia98k3HKkR0gl58IDzqz+MmgW2cQJOCNLfuWPpo=
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/clipperhouse/uax29/v2 v2.4.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -2,15 +2,13 @@
package logging
import (
"fmt"
"io"
"maps"
"os"
"strings"
"sync"
"time"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// Level represents the severity of a log message.
@ -115,7 +113,7 @@ func (l *Logger) log(level Level, msg string, fields Fields) {
}
// Build the log line
var sb strings.Builder
sb := core.NewBuilder()
timestamp := time.Now().Format("2006/01/02 15:04:05")
sb.WriteString(timestamp)
sb.WriteString(" [")
@ -138,12 +136,12 @@ func (l *Logger) log(level Level, msg string, fields Fields) {
sb.WriteString(" ")
sb.WriteString(k)
sb.WriteString("=")
sb.WriteString(fmt.Sprintf("%v", v))
sb.WriteString(core.Sprint(v))
}
}
sb.WriteString("\n")
fmt.Fprint(l.output, sb.String())
_, _ = l.output.Write([]byte(sb.String()))
}
// Debug logs a debug message.
@ -168,22 +166,22 @@ func (l *Logger) Error(msg string, fields ...Fields) {
// Debugf logs a formatted debug message.
func (l *Logger) Debugf(format string, args ...any) {
l.log(LevelDebug, fmt.Sprintf(format, args...), nil)
l.log(LevelDebug, core.Sprintf(format, args...), nil)
}
// Infof logs a formatted informational message.
func (l *Logger) Infof(format string, args ...any) {
l.log(LevelInfo, fmt.Sprintf(format, args...), nil)
l.log(LevelInfo, core.Sprintf(format, args...), nil)
}
// Warnf logs a formatted warning message.
func (l *Logger) Warnf(format string, args ...any) {
l.log(LevelWarn, fmt.Sprintf(format, args...), nil)
l.log(LevelWarn, core.Sprintf(format, args...), nil)
}
// Errorf logs a formatted error message.
func (l *Logger) Errorf(format string, args ...any) {
l.log(LevelError, fmt.Sprintf(format, args...), nil)
l.log(LevelError, core.Sprintf(format, args...), nil)
}
// mergeFields combines multiple Fields maps into one.
@ -270,7 +268,7 @@ func Errorf(format string, args ...any) {
// ParseLevel parses a string into a log level.
func ParseLevel(s string) (Level, error) {
switch strings.ToUpper(s) {
switch core.Upper(s) {
case "DEBUG":
return LevelDebug, nil
case "INFO":
@ -280,6 +278,6 @@ func ParseLevel(s string) (Level, error) {
case "ERROR":
return LevelError, nil
default:
return LevelInfo, coreerr.E("logging.ParseLevel", "unknown log level: "+s, nil)
return LevelInfo, core.E("logging.ParseLevel", "unknown log level: "+s, nil)
}
}

View file

@ -2,8 +2,9 @@ package node
import (
"bytes"
"encoding/json"
"sync"
core "dappco.re/go/core"
)
// bufferPool provides reusable byte buffers for JSON encoding.
@ -29,27 +30,22 @@ func putBuffer(buf *bytes.Buffer) {
}
}
// MarshalJSON encodes a value to JSON using a pooled buffer.
// MarshalJSON encodes a value to JSON using Core's JSON primitive and then
// restores the historical no-EscapeHTML behaviour expected by the node package.
// Returns a copy of the encoded bytes (safe to use after the function returns).
func MarshalJSON(v any) ([]byte, error) {
buf := getBuffer()
defer putBuffer(buf)
enc := json.NewEncoder(buf)
// Don't escape HTML characters (matches json.Marshal behavior for these use cases)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return nil, err
encoded := core.JSONMarshal(v)
if !encoded.OK {
return nil, encoded.Value.(error)
}
data := encoded.Value.([]byte)
// json.Encoder.Encode adds a newline; remove it to match json.Marshal
data := buf.Bytes()
if len(data) > 0 && data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}
data = bytes.ReplaceAll(data, []byte(`\u003c`), []byte("<"))
data = bytes.ReplaceAll(data, []byte(`\u003e`), []byte(">"))
data = bytes.ReplaceAll(data, []byte(`\u0026`), []byte("&"))
// Return a copy since the buffer will be reused
result := make([]byte, len(data))
copy(result, data)
return result, nil
// Return a copy since callers may retain the slice after subsequent calls.
out := make([]byte, len(data))
copy(out, data)
return out, nil
}

View file

@ -5,14 +5,10 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"forge.lthn.ai/Snider/Borg/pkg/datanode"
"forge.lthn.ai/Snider/Borg/pkg/tim"
@ -50,14 +46,14 @@ func CreateProfileBundle(profileJSON []byte, name string, password string) (*Bun
// Create a TIM with just the profile config
t, err := tim.New()
if err != nil {
return nil, coreerr.E("CreateProfileBundle", "failed to create TIM", err)
return nil, core.E("CreateProfileBundle", "failed to create TIM", err)
}
t.Config = profileJSON
// Encrypt to STIM format
stimData, err := t.ToSigil(password)
if err != nil {
return nil, coreerr.E("CreateProfileBundle", "failed to encrypt bundle", err)
return nil, core.E("CreateProfileBundle", "failed to encrypt bundle", err)
}
// Calculate checksum
@ -86,30 +82,30 @@ func CreateProfileBundleUnencrypted(profileJSON []byte, name string) (*Bundle, e
// CreateMinerBundle creates an encrypted bundle containing a miner binary and optional profile.
func CreateMinerBundle(minerPath string, profileJSON []byte, name string, password string) (*Bundle, error) {
// Read miner binary
minerContent, err := coreio.Local.Read(minerPath)
minerContent, err := fsRead(minerPath)
if err != nil {
return nil, coreerr.E("CreateMinerBundle", "failed to read miner binary", err)
return nil, core.E("CreateMinerBundle", "failed to read miner binary", err)
}
minerData := []byte(minerContent)
// Create a tarball with the miner binary
tarData, err := createTarball(map[string][]byte{
filepath.Base(minerPath): minerData,
core.PathBase(minerPath): minerData,
})
if err != nil {
return nil, coreerr.E("CreateMinerBundle", "failed to create tarball", err)
return nil, core.E("CreateMinerBundle", "failed to create tarball", err)
}
// Create DataNode from tarball
dn, err := datanode.FromTar(tarData)
if err != nil {
return nil, coreerr.E("CreateMinerBundle", "failed to create datanode", err)
return nil, core.E("CreateMinerBundle", "failed to create datanode", err)
}
// Create TIM from DataNode
t, err := tim.FromDataNode(dn)
if err != nil {
return nil, coreerr.E("CreateMinerBundle", "failed to create TIM", err)
return nil, core.E("CreateMinerBundle", "failed to create TIM", err)
}
// Set profile as config if provided
@ -120,7 +116,7 @@ func CreateMinerBundle(minerPath string, profileJSON []byte, name string, passwo
// Encrypt to STIM format
stimData, err := t.ToSigil(password)
if err != nil {
return nil, coreerr.E("CreateMinerBundle", "failed to encrypt bundle", err)
return nil, core.E("CreateMinerBundle", "failed to encrypt bundle", err)
}
checksum := calculateChecksum(stimData)
@ -137,7 +133,7 @@ func CreateMinerBundle(minerPath string, profileJSON []byte, name string, passwo
func ExtractProfileBundle(bundle *Bundle, password string) ([]byte, error) {
// Verify checksum first
if calculateChecksum(bundle.Data) != bundle.Checksum {
return nil, coreerr.E("ExtractProfileBundle", "checksum mismatch - bundle may be corrupted", nil)
return nil, core.E("ExtractProfileBundle", "checksum mismatch - bundle may be corrupted", nil)
}
// If it's unencrypted JSON, just return it
@ -148,7 +144,7 @@ func ExtractProfileBundle(bundle *Bundle, password string) ([]byte, error) {
// Decrypt STIM format
t, err := tim.FromSigil(bundle.Data, password)
if err != nil {
return nil, coreerr.E("ExtractProfileBundle", "failed to decrypt bundle", err)
return nil, core.E("ExtractProfileBundle", "failed to decrypt bundle", err)
}
return t.Config, nil
@ -158,25 +154,25 @@ func ExtractProfileBundle(bundle *Bundle, password string) ([]byte, error) {
func ExtractMinerBundle(bundle *Bundle, password string, destDir string) (string, []byte, error) {
// Verify checksum
if calculateChecksum(bundle.Data) != bundle.Checksum {
return "", nil, coreerr.E("ExtractMinerBundle", "checksum mismatch - bundle may be corrupted", nil)
return "", nil, core.E("ExtractMinerBundle", "checksum mismatch - bundle may be corrupted", nil)
}
// Decrypt STIM format
t, err := tim.FromSigil(bundle.Data, password)
if err != nil {
return "", nil, coreerr.E("ExtractMinerBundle", "failed to decrypt bundle", err)
return "", nil, core.E("ExtractMinerBundle", "failed to decrypt bundle", err)
}
// Convert rootfs to tarball and extract
tarData, err := t.RootFS.ToTar()
if err != nil {
return "", nil, coreerr.E("ExtractMinerBundle", "failed to convert rootfs to tar", err)
return "", nil, core.E("ExtractMinerBundle", "failed to convert rootfs to tar", err)
}
// Extract tarball to destination
minerPath, err := extractTarball(tarData, destDir)
if err != nil {
return "", nil, coreerr.E("ExtractMinerBundle", "failed to extract tarball", err)
return "", nil, core.E("ExtractMinerBundle", "failed to extract tarball", err)
}
return minerPath, t.Config, nil
@ -212,7 +208,7 @@ func createTarball(files map[string][]byte) ([]byte, error) {
for name, content := range files {
// Create parent directories if needed
dir := filepath.Dir(name)
dir := core.PathDir(name)
if dir != "." && !dirs[dir] {
hdr := &tar.Header{
Name: dir + "/",
@ -227,7 +223,7 @@ func createTarball(files map[string][]byte) ([]byte, error) {
// Determine file mode (executable for binaries in miners/)
mode := int64(0644)
if filepath.Dir(name) == "miners" || !isJSON(content) {
if core.PathDir(name) == "miners" || !isJSON(content) {
mode = 0755
}
@ -254,13 +250,18 @@ func createTarball(files map[string][]byte) ([]byte, error) {
// extractTarball extracts a tar archive to a directory, returns first executable found.
func extractTarball(tarData []byte, destDir string) (string, error) {
// Ensure destDir is an absolute, clean path for security checks
absDestDir, err := filepath.Abs(destDir)
if err != nil {
return "", coreerr.E("extractTarball", "failed to resolve destination directory", err)
absDestDir := destDir
if !core.PathIsAbs(absDestDir) {
cwd, err := os.Getwd()
if err != nil {
return "", core.E("extractTarball", "failed to resolve destination directory", err)
}
absDestDir = core.CleanPath(core.Concat(cwd, string(os.PathSeparator), absDestDir), string(os.PathSeparator))
} else {
absDestDir = core.CleanPath(absDestDir, string(os.PathSeparator))
}
absDestDir = filepath.Clean(absDestDir)
if err := coreio.Local.EnsureDir(absDestDir); err != nil {
if err := fsEnsureDir(absDestDir); err != nil {
return "", err
}
@ -277,44 +278,47 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
}
// Security: Sanitize the tar entry name to prevent path traversal (Zip Slip)
cleanName := filepath.Clean(hdr.Name)
cleanName := core.CleanPath(hdr.Name, "/")
// Reject absolute paths
if filepath.IsAbs(cleanName) {
return "", coreerr.E("extractTarball", "invalid tar entry: absolute path not allowed: "+hdr.Name, nil)
if core.PathIsAbs(cleanName) {
return "", core.E("extractTarball", "invalid tar entry: absolute path not allowed: "+hdr.Name, nil)
}
// Reject paths that escape the destination directory
if strings.HasPrefix(cleanName, ".."+string(os.PathSeparator)) || cleanName == ".." {
return "", coreerr.E("extractTarball", "invalid tar entry: path traversal attempt: "+hdr.Name, nil)
if core.HasPrefix(cleanName, "../") || cleanName == ".." {
return "", core.E("extractTarball", "invalid tar entry: path traversal attempt: "+hdr.Name, nil)
}
// Build the full path and verify it's within destDir
fullPath := filepath.Join(absDestDir, cleanName)
fullPath = filepath.Clean(fullPath)
fullPath := core.CleanPath(core.Concat(absDestDir, string(os.PathSeparator), cleanName), string(os.PathSeparator))
// Final security check: ensure the path is still within destDir
if !strings.HasPrefix(fullPath, absDestDir+string(os.PathSeparator)) && fullPath != absDestDir {
return "", coreerr.E("extractTarball", "invalid tar entry: path escape attempt: "+hdr.Name, nil)
allowedPrefix := core.Concat(absDestDir, string(os.PathSeparator))
if absDestDir == string(os.PathSeparator) {
allowedPrefix = absDestDir
}
if !core.HasPrefix(fullPath, allowedPrefix) && fullPath != absDestDir {
return "", core.E("extractTarball", "invalid tar entry: path escape attempt: "+hdr.Name, nil)
}
switch hdr.Typeflag {
case tar.TypeDir:
if err := coreio.Local.EnsureDir(fullPath); err != nil {
if err := fsEnsureDir(fullPath); err != nil {
return "", err
}
case tar.TypeReg:
// Ensure parent directory exists
if err := coreio.Local.EnsureDir(filepath.Dir(fullPath)); err != nil {
if err := fsEnsureDir(core.PathDir(fullPath)); err != nil {
return "", err
}
// os.OpenFile is used deliberately here instead of coreio.Local.Create/Write
// because coreio hardcodes file permissions (0644) and we need to preserve
// os.OpenFile is used deliberately here instead of core.Fs.Create/Write
// because the helper writes with fixed default permissions and we need to preserve
// the tar header's mode bits — executable binaries require 0755.
f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(hdr.Mode))
if err != nil {
return "", coreerr.E("extractTarball", "failed to create file "+hdr.Name, err)
return "", core.E("extractTarball", "failed to create file "+hdr.Name, err)
}
// Limit file size to prevent decompression bombs (100MB max per file)
@ -323,11 +327,11 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
written, err := io.Copy(f, limitedReader)
f.Close()
if err != nil {
return "", coreerr.E("extractTarball", "failed to write file "+hdr.Name, err)
return "", core.E("extractTarball", "failed to write file "+hdr.Name, err)
}
if written > maxFileSize {
coreio.Local.Delete(fullPath)
return "", coreerr.E("extractTarball", "file "+hdr.Name+" exceeds maximum size", nil)
fsDelete(fullPath)
return "", core.E("extractTarball", "file "+hdr.Name+" exceeds maximum size", nil)
}
// Track first executable
@ -346,16 +350,25 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
// StreamBundle writes a bundle to a writer (for large transfers).
func StreamBundle(bundle *Bundle, w io.Writer) error {
encoder := json.NewEncoder(w)
return encoder.Encode(bundle)
result := core.JSONMarshal(bundle)
if !result.OK {
return result.Value.(error)
}
_, err := w.Write(result.Value.([]byte))
return err
}
// ReadBundle reads a bundle from a reader.
func ReadBundle(r io.Reader) (*Bundle, error) {
var bundle Bundle
decoder := json.NewDecoder(r)
if err := decoder.Decode(&bundle); err != nil {
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
return nil, err
}
var bundle Bundle
result := core.JSONUnmarshal(buf.Bytes(), &bundle)
if !result.OK {
return nil, result.Value.(error)
}
return &bundle, nil
}

View file

@ -2,11 +2,10 @@ package node
import (
"context"
"encoding/json"
"sync"
"time"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"dappco.re/go/core/p2p/logging"
)
@ -67,11 +66,11 @@ func (c *Controller) sendRequest(peerID string, msg *Message, timeout time.Durat
if c.transport.GetConnection(peerID) == nil {
peer := c.peers.GetPeer(peerID)
if peer == nil {
return nil, coreerr.E("Controller.sendRequest", "peer not found: "+peerID, nil)
return nil, core.E("Controller.sendRequest", "peer not found: "+peerID, nil)
}
conn, err := c.transport.Connect(peer)
if err != nil {
return nil, coreerr.E("Controller.sendRequest", "failed to connect to peer", err)
return nil, core.E("Controller.sendRequest", "failed to connect to peer", err)
}
// Use the real peer ID after handshake (it may have changed)
actualPeerID = conn.Peer.ID
@ -96,7 +95,7 @@ func (c *Controller) sendRequest(peerID string, msg *Message, timeout time.Durat
// Send the message
if err := c.transport.Send(actualPeerID, msg); err != nil {
return nil, coreerr.E("Controller.sendRequest", "failed to send message", err)
return nil, core.E("Controller.sendRequest", "failed to send message", err)
}
// Wait for response
@ -107,7 +106,7 @@ func (c *Controller) sendRequest(peerID string, msg *Message, timeout time.Durat
case resp := <-respCh:
return resp, nil
case <-ctx.Done():
return nil, coreerr.E("Controller.sendRequest", "request timeout", nil)
return nil, core.E("Controller.sendRequest", "request timeout", nil)
}
}
@ -120,7 +119,7 @@ func (c *Controller) GetRemoteStats(peerID string) (*StatsPayload, error) {
msg, err := NewMessage(MsgGetStats, identity.ID, peerID, nil)
if err != nil {
return nil, coreerr.E("Controller.GetRemoteStats", "failed to create message", err)
return nil, core.E("Controller.GetRemoteStats", "failed to create message", err)
}
resp, err := c.sendRequest(peerID, msg, 10*time.Second)
@ -137,14 +136,14 @@ func (c *Controller) GetRemoteStats(peerID string) (*StatsPayload, error) {
}
// StartRemoteMiner requests a remote peer to start a miner with a given profile.
func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, configOverride json.RawMessage) error {
func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, configOverride RawMessage) error {
identity := c.node.GetIdentity()
if identity == nil {
return ErrIdentityNotInitialized
}
if minerType == "" {
return coreerr.E("Controller.StartRemoteMiner", "miner type is required", nil)
return core.E("Controller.StartRemoteMiner", "miner type is required", nil)
}
payload := StartMinerPayload{
@ -155,7 +154,7 @@ func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, confi
msg, err := NewMessage(MsgStartMiner, identity.ID, peerID, payload)
if err != nil {
return coreerr.E("Controller.StartRemoteMiner", "failed to create message", err)
return core.E("Controller.StartRemoteMiner", "failed to create message", err)
}
resp, err := c.sendRequest(peerID, msg, 30*time.Second)
@ -169,7 +168,7 @@ func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, confi
}
if !ack.Success {
return coreerr.E("Controller.StartRemoteMiner", "miner start failed: "+ack.Error, nil)
return core.E("Controller.StartRemoteMiner", "miner start failed: "+ack.Error, nil)
}
return nil
@ -188,7 +187,7 @@ func (c *Controller) StopRemoteMiner(peerID, minerName string) error {
msg, err := NewMessage(MsgStopMiner, identity.ID, peerID, payload)
if err != nil {
return coreerr.E("Controller.StopRemoteMiner", "failed to create message", err)
return core.E("Controller.StopRemoteMiner", "failed to create message", err)
}
resp, err := c.sendRequest(peerID, msg, 30*time.Second)
@ -202,7 +201,7 @@ func (c *Controller) StopRemoteMiner(peerID, minerName string) error {
}
if !ack.Success {
return coreerr.E("Controller.StopRemoteMiner", "miner stop failed: "+ack.Error, nil)
return core.E("Controller.StopRemoteMiner", "miner stop failed: "+ack.Error, nil)
}
return nil
@ -222,7 +221,7 @@ func (c *Controller) GetRemoteLogs(peerID, minerName string, lines int) ([]strin
msg, err := NewMessage(MsgGetLogs, identity.ID, peerID, payload)
if err != nil {
return nil, coreerr.E("Controller.GetRemoteLogs", "failed to create message", err)
return nil, core.E("Controller.GetRemoteLogs", "failed to create message", err)
}
resp, err := c.sendRequest(peerID, msg, 10*time.Second)
@ -281,7 +280,7 @@ func (c *Controller) PingPeer(peerID string) (float64, error) {
msg, err := NewMessage(MsgPing, identity.ID, peerID, payload)
if err != nil {
return 0, coreerr.E("Controller.PingPeer", "failed to create message", err)
return 0, core.E("Controller.PingPeer", "failed to create message", err)
}
resp, err := c.sendRequest(peerID, msg, 5*time.Second)
@ -309,7 +308,7 @@ func (c *Controller) PingPeer(peerID string) (float64, error) {
func (c *Controller) ConnectToPeer(peerID string) error {
peer := c.peers.GetPeer(peerID)
if peer == nil {
return coreerr.E("Controller.ConnectToPeer", "peer not found: "+peerID, nil)
return core.E("Controller.ConnectToPeer", "peer not found: "+peerID, nil)
}
_, err := c.transport.Connect(peer)
@ -320,7 +319,7 @@ func (c *Controller) ConnectToPeer(peerID string) error {
func (c *Controller) DisconnectFromPeer(peerID string) error {
conn := c.transport.GetConnection(peerID)
if conn == nil {
return coreerr.E("Controller.DisconnectFromPeer", "peer not connected: "+peerID, nil)
return core.E("Controller.DisconnectFromPeer", "peer not connected: "+peerID, nil)
}
return conn.Close()

53
node/core_fs.go Normal file
View file

@ -0,0 +1,53 @@
// SPDX-License-Identifier: EUPL-1.2
package node
import core "dappco.re/go/core"
var localFS = (&core.Fs{}).New("/")
func fsEnsureDir(path string) error {
return fsResultErr(localFS.EnsureDir(path))
}
func fsWrite(path, content string) error {
return fsResultErr(localFS.Write(path, content))
}
func fsRead(path string) (string, error) {
result := localFS.Read(path)
if !result.OK {
return "", fsResultErr(result)
}
content, ok := result.Value.(string)
if !ok {
return "", core.E("node.fsRead", "filesystem read returned non-string content", nil)
}
return content, nil
}
func fsDelete(path string) error {
return fsResultErr(localFS.Delete(path))
}
func fsRename(oldPath, newPath string) error {
return fsResultErr(localFS.Rename(oldPath, newPath))
}
func fsExists(path string) bool {
return localFS.Exists(path)
}
func fsResultErr(result core.Result) error {
if result.OK {
return nil
}
if err, ok := result.Value.(error); ok && err != nil {
return err
}
return core.E("node.fs", "filesystem operation failed", nil)
}

View file

@ -1,11 +1,10 @@
package node
import (
"fmt"
"iter"
"sync"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"dappco.re/go/core/p2p/logging"
"dappco.re/go/core/p2p/ueps"
@ -69,7 +68,7 @@ func (d *Dispatcher) RegisterHandler(intentID byte, handler IntentHandler) {
defer d.mu.Unlock()
d.handlers[intentID] = handler
d.log.Debug("handler registered", logging.Fields{
"intent_id": fmt.Sprintf("0x%02X", intentID),
"intent_id": core.Sprintf("0x%02X", intentID),
})
}
@ -108,7 +107,7 @@ func (d *Dispatcher) Dispatch(pkt *ueps.ParsedPacket) error {
d.log.Warn("packet dropped: threat score exceeds safety threshold", logging.Fields{
"threat_score": pkt.Header.ThreatScore,
"threshold": ThreatScoreThreshold,
"intent_id": fmt.Sprintf("0x%02X", pkt.Header.IntentID),
"intent_id": core.Sprintf("0x%02X", pkt.Header.IntentID),
"version": pkt.Header.Version,
})
return ErrThreatScoreExceeded
@ -121,7 +120,7 @@ func (d *Dispatcher) Dispatch(pkt *ueps.ParsedPacket) error {
if !exists {
d.log.Warn("packet dropped: unknown intent", logging.Fields{
"intent_id": fmt.Sprintf("0x%02X", pkt.Header.IntentID),
"intent_id": core.Sprintf("0x%02X", pkt.Header.IntentID),
"version": pkt.Header.Version,
})
return ErrUnknownIntent
@ -134,12 +133,12 @@ func (d *Dispatcher) Dispatch(pkt *ueps.ParsedPacket) error {
var (
// ErrThreatScoreExceeded is returned when a packet's ThreatScore exceeds
// the safety threshold.
ErrThreatScoreExceeded = coreerr.E("Dispatcher.Dispatch", fmt.Sprintf("packet rejected: threat score exceeds safety threshold (%d)", ThreatScoreThreshold), nil)
ErrThreatScoreExceeded = core.E("Dispatcher.Dispatch", core.Sprintf("packet rejected: threat score exceeds safety threshold (%d)", ThreatScoreThreshold), nil)
// ErrUnknownIntent is returned when no handler is registered for the
// packet's IntentID.
ErrUnknownIntent = coreerr.E("Dispatcher.Dispatch", "packet dropped: unknown intent", nil)
ErrUnknownIntent = core.E("Dispatcher.Dispatch", "packet dropped: unknown intent", nil)
// ErrNilPacket is returned when a nil packet is passed to Dispatch.
ErrNilPacket = coreerr.E("Dispatcher.Dispatch", "nil packet", nil)
ErrNilPacket = core.E("Dispatcher.Dispatch", "nil packet", nil)
)

View file

@ -1,14 +1,14 @@
package node
import coreerr "dappco.re/go/core/log"
import core "dappco.re/go/core"
// Sentinel errors shared across the node package.
var (
// ErrIdentityNotInitialized is returned when a node operation requires
// a node identity but none has been generated or loaded.
ErrIdentityNotInitialized = coreerr.E("node", "node identity not initialized", nil)
ErrIdentityNotInitialized = core.E("node", "node identity not initialized", nil)
// ErrMinerManagerNotConfigured is returned when a miner operation is
// attempted but no MinerManager has been set on the Worker.
ErrMinerManagerNotConfigured = coreerr.E("node", "miner manager not configured", nil)
ErrMinerManagerNotConfigured = core.E("node", "miner manager not configured", nil)
)

View file

@ -7,13 +7,10 @@ import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"path/filepath"
"sync"
"time"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"forge.lthn.ai/Snider/Borg/pkg/stmf"
"github.com/adrg/xdg"
@ -26,7 +23,7 @@ const ChallengeSize = 32
func GenerateChallenge() ([]byte, error) {
challenge := make([]byte, ChallengeSize)
if _, err := rand.Read(challenge); err != nil {
return nil, coreerr.E("GenerateChallenge", "failed to generate challenge", err)
return nil, core.E("GenerateChallenge", "failed to generate challenge", err)
}
return challenge, nil
}
@ -80,12 +77,12 @@ type NodeManager struct {
func NewNodeManager() (*NodeManager, error) {
keyPath, err := xdg.DataFile("lethean-desktop/node/private.key")
if err != nil {
return nil, coreerr.E("NodeManager.New", "failed to get key path", err)
return nil, core.E("NodeManager.New", "failed to get key path", err)
}
configPath, err := xdg.ConfigFile("lethean-desktop/node.json")
if err != nil {
return nil, coreerr.E("NodeManager.New", "failed to get config path", err)
return nil, core.E("NodeManager.New", "failed to get config path", err)
}
return NewNodeManagerWithPaths(keyPath, configPath)
@ -135,7 +132,7 @@ func (n *NodeManager) GenerateIdentity(name string, role NodeRole) error {
// Generate X25519 keypair using STMF
keyPair, err := stmf.GenerateKeyPair()
if err != nil {
return coreerr.E("NodeManager.GenerateIdentity", "failed to generate keypair", err)
return core.E("NodeManager.GenerateIdentity", "failed to generate keypair", err)
}
// Derive node ID from public key (first 16 bytes as hex = 32 char ID)
@ -156,12 +153,12 @@ func (n *NodeManager) GenerateIdentity(name string, role NodeRole) error {
// Save private key
if err := n.savePrivateKey(); err != nil {
return coreerr.E("NodeManager.GenerateIdentity", "failed to save private key", err)
return core.E("NodeManager.GenerateIdentity", "failed to save private key", err)
}
// Save identity config
if err := n.saveIdentity(); err != nil {
return coreerr.E("NodeManager.GenerateIdentity", "failed to save identity", err)
return core.E("NodeManager.GenerateIdentity", "failed to save identity", err)
}
return nil
@ -180,19 +177,19 @@ func (n *NodeManager) DeriveSharedSecret(peerPubKeyBase64 string) ([]byte, error
// Load peer's public key
peerPubKey, err := stmf.LoadPublicKeyBase64(peerPubKeyBase64)
if err != nil {
return nil, coreerr.E("NodeManager.DeriveSharedSecret", "failed to load peer public key", err)
return nil, core.E("NodeManager.DeriveSharedSecret", "failed to load peer public key", err)
}
// Load our private key
privateKey, err := ecdh.X25519().NewPrivateKey(n.privateKey)
if err != nil {
return nil, coreerr.E("NodeManager.DeriveSharedSecret", "failed to load private key", err)
return nil, core.E("NodeManager.DeriveSharedSecret", "failed to load private key", err)
}
// Derive shared secret using ECDH
sharedSecret, err := privateKey.ECDH(peerPubKey)
if err != nil {
return nil, coreerr.E("NodeManager.DeriveSharedSecret", "failed to derive shared secret", err)
return nil, core.E("NodeManager.DeriveSharedSecret", "failed to derive shared secret", err)
}
// Hash the shared secret using SHA-256 (same pattern as Borg/trix)
@ -203,14 +200,14 @@ func (n *NodeManager) DeriveSharedSecret(peerPubKeyBase64 string) ([]byte, error
// savePrivateKey saves the private key to disk with restricted permissions.
func (n *NodeManager) savePrivateKey() error {
// Ensure directory exists
dir := filepath.Dir(n.keyPath)
if err := coreio.Local.EnsureDir(dir); err != nil {
return coreerr.E("NodeManager.savePrivateKey", "failed to create key directory", err)
dir := core.PathDir(n.keyPath)
if err := fsEnsureDir(dir); err != nil {
return core.E("NodeManager.savePrivateKey", "failed to create key directory", err)
}
// Write private key
if err := coreio.Local.Write(n.keyPath, string(n.privateKey)); err != nil {
return coreerr.E("NodeManager.savePrivateKey", "failed to write private key", err)
if err := fsWrite(n.keyPath, string(n.privateKey)); err != nil {
return core.E("NodeManager.savePrivateKey", "failed to write private key", err)
}
return nil
@ -219,18 +216,19 @@ func (n *NodeManager) savePrivateKey() error {
// saveIdentity saves the public identity to the config file.
func (n *NodeManager) saveIdentity() error {
// Ensure directory exists
dir := filepath.Dir(n.configPath)
if err := coreio.Local.EnsureDir(dir); err != nil {
return coreerr.E("NodeManager.saveIdentity", "failed to create config directory", err)
dir := core.PathDir(n.configPath)
if err := fsEnsureDir(dir); err != nil {
return core.E("NodeManager.saveIdentity", "failed to create config directory", err)
}
data, err := json.MarshalIndent(n.identity, "", " ")
if err != nil {
return coreerr.E("NodeManager.saveIdentity", "failed to marshal identity", err)
result := core.JSONMarshal(n.identity)
if !result.OK {
return core.E("NodeManager.saveIdentity", "failed to marshal identity", result.Value.(error))
}
data := result.Value.([]byte)
if err := coreio.Local.Write(n.configPath, string(data)); err != nil {
return coreerr.E("NodeManager.saveIdentity", "failed to write identity", err)
if err := fsWrite(n.configPath, string(data)); err != nil {
return core.E("NodeManager.saveIdentity", "failed to write identity", err)
}
return nil
@ -239,27 +237,28 @@ func (n *NodeManager) saveIdentity() error {
// loadIdentity loads the node identity from disk.
func (n *NodeManager) loadIdentity() error {
// Load identity config
content, err := coreio.Local.Read(n.configPath)
content, err := fsRead(n.configPath)
if err != nil {
return coreerr.E("NodeManager.loadIdentity", "failed to read identity", err)
return core.E("NodeManager.loadIdentity", "failed to read identity", err)
}
var identity NodeIdentity
if err := json.Unmarshal([]byte(content), &identity); err != nil {
return coreerr.E("NodeManager.loadIdentity", "failed to unmarshal identity", err)
result := core.JSONUnmarshalString(content, &identity)
if !result.OK {
return core.E("NodeManager.loadIdentity", "failed to unmarshal identity", result.Value.(error))
}
// Load private key
keyContent, err := coreio.Local.Read(n.keyPath)
keyContent, err := fsRead(n.keyPath)
if err != nil {
return coreerr.E("NodeManager.loadIdentity", "failed to read private key", err)
return core.E("NodeManager.loadIdentity", "failed to read private key", err)
}
privateKey := []byte(keyContent)
// Reconstruct keypair from private key
keyPair, err := stmf.LoadKeyPair(privateKey)
if err != nil {
return coreerr.E("NodeManager.loadIdentity", "failed to load keypair", err)
return core.E("NodeManager.loadIdentity", "failed to load keypair", err)
}
n.identity = &identity
@ -275,16 +274,16 @@ func (n *NodeManager) Delete() error {
defer n.mu.Unlock()
// Remove private key (ignore if already absent)
if coreio.Local.Exists(n.keyPath) {
if err := coreio.Local.Delete(n.keyPath); err != nil {
return coreerr.E("NodeManager.Delete", "failed to remove private key", err)
if fsExists(n.keyPath) {
if err := fsDelete(n.keyPath); err != nil {
return core.E("NodeManager.Delete", "failed to remove private key", err)
}
}
// Remove identity config (ignore if already absent)
if coreio.Local.Exists(n.configPath) {
if err := coreio.Local.Delete(n.configPath); err != nil {
return coreerr.E("NodeManager.Delete", "failed to remove identity", err)
if fsExists(n.configPath) {
if err := fsDelete(n.configPath); err != nil {
return core.E("NodeManager.Delete", "failed to remove identity", err)
}
}

View file

@ -8,7 +8,7 @@ package levin
import (
"encoding/binary"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// HeaderSize is the exact byte length of a serialised Levin header.
@ -43,8 +43,8 @@ const (
// Sentinel errors returned by DecodeHeader.
var (
ErrBadSignature = coreerr.E("levin", "bad signature", nil)
ErrPayloadTooBig = coreerr.E("levin", "payload exceeds maximum size", nil)
ErrBadSignature = core.E("levin", "bad signature", nil)
ErrPayloadTooBig = core.E("levin", "payload exceeds maximum size", nil)
)
// Header is the 33-byte packed header that prefixes every Levin message.

View file

@ -5,12 +5,11 @@ package levin
import (
"encoding/binary"
"fmt"
"maps"
"math"
"slices"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// Portable storage signatures and version (9-byte header).
@ -41,12 +40,12 @@ const (
// Sentinel errors for storage encoding and decoding.
var (
ErrStorageBadSignature = coreerr.E("levin.storage", "bad storage signature", nil)
ErrStorageTruncated = coreerr.E("levin.storage", "truncated storage data", nil)
ErrStorageBadVersion = coreerr.E("levin.storage", "unsupported storage version", nil)
ErrStorageNameTooLong = coreerr.E("levin.storage", "entry name exceeds 255 bytes", nil)
ErrStorageTypeMismatch = coreerr.E("levin.storage", "value type mismatch", nil)
ErrStorageUnknownType = coreerr.E("levin.storage", "unknown type tag", nil)
ErrStorageBadSignature = core.E("levin.storage", "bad storage signature", nil)
ErrStorageTruncated = core.E("levin.storage", "truncated storage data", nil)
ErrStorageBadVersion = core.E("levin.storage", "unsupported storage version", nil)
ErrStorageNameTooLong = core.E("levin.storage", "entry name exceeds 255 bytes", nil)
ErrStorageTypeMismatch = core.E("levin.storage", "value type mismatch", nil)
ErrStorageUnknownType = core.E("levin.storage", "unknown type tag", nil)
)
// Section is an ordered map of named values forming a portable storage section.
@ -394,7 +393,7 @@ func encodeValue(buf []byte, v Value) ([]byte, error) {
return encodeSection(buf, v.objectVal)
default:
return nil, coreerr.E("levin.encodeValue", fmt.Sprintf("unknown type tag: 0x%02x", v.Type), ErrStorageUnknownType)
return nil, core.E("levin.encodeValue", core.Sprintf("unknown type tag: 0x%02x", v.Type), ErrStorageUnknownType)
}
}
@ -441,7 +440,7 @@ func encodeArray(buf []byte, v Value) ([]byte, error) {
return buf, nil
default:
return nil, coreerr.E("levin.encodeArray", fmt.Sprintf("unknown type tag: array of 0x%02x", elemType), ErrStorageUnknownType)
return nil, core.E("levin.encodeArray", core.Sprintf("unknown type tag: array of 0x%02x", elemType), ErrStorageUnknownType)
}
}
@ -476,7 +475,7 @@ func DecodeStorage(data []byte) (Section, error) {
func decodeSection(buf []byte) (Section, int, error) {
count, n, err := UnpackVarint(buf)
if err != nil {
return nil, 0, coreerr.E("levin.decodeSection", "section entry count", err)
return nil, 0, core.E("levin.decodeSection", "section entry count", err)
}
off := n
@ -507,7 +506,7 @@ func decodeSection(buf []byte) (Section, int, error) {
// Value.
val, consumed, err := decodeValue(buf[off:], tag)
if err != nil {
return nil, 0, coreerr.E("levin.decodeSection", "field "+name, err)
return nil, 0, core.E("levin.decodeSection", "field "+name, err)
}
off += consumed
@ -613,7 +612,7 @@ func decodeValue(buf []byte, tag uint8) (Value, int, error) {
return Value{Type: TypeObject, objectVal: sec}, consumed, nil
default:
return Value{}, 0, coreerr.E("levin.decodeValue", fmt.Sprintf("unknown type tag: 0x%02x", tag), ErrStorageUnknownType)
return Value{}, 0, core.E("levin.decodeValue", core.Sprintf("unknown type tag: 0x%02x", tag), ErrStorageUnknownType)
}
}
@ -681,6 +680,6 @@ func decodeArray(buf []byte, tag uint8) (Value, int, error) {
return Value{Type: tag, objectArray: arr}, off, nil
default:
return Value{}, 0, coreerr.E("levin.decodeArray", fmt.Sprintf("unknown type tag: array of 0x%02x", elemType), ErrStorageUnknownType)
return Value{}, 0, core.E("levin.decodeArray", core.Sprintf("unknown type tag: array of 0x%02x", elemType), ErrStorageUnknownType)
}
}

View file

@ -6,27 +6,27 @@ package levin
import (
"encoding/binary"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// Size-mark bits occupying the two lowest bits of the first byte.
const (
varintMask = 0x03
varintMark1 = 0x00 // 1 byte, max 63
varintMark2 = 0x01 // 2 bytes, max 16,383
varintMark4 = 0x02 // 4 bytes, max 1,073,741,823
varintMark8 = 0x03 // 8 bytes, max 4,611,686,018,427,387,903
varintMax1 = 63
varintMax2 = 16_383
varintMax4 = 1_073_741_823
varintMax8 = 4_611_686_018_427_387_903
varintMask = 0x03
varintMark1 = 0x00 // 1 byte, max 63
varintMark2 = 0x01 // 2 bytes, max 16,383
varintMark4 = 0x02 // 4 bytes, max 1,073,741,823
varintMark8 = 0x03 // 8 bytes, max 4,611,686,018,427,387,903
varintMax1 = 63
varintMax2 = 16_383
varintMax4 = 1_073_741_823
varintMax8 = 4_611_686_018_427_387_903
)
// ErrVarintTruncated is returned when the buffer is too short.
var ErrVarintTruncated = coreerr.E("levin", "truncated varint", nil)
var ErrVarintTruncated = core.E("levin", "truncated varint", nil)
// ErrVarintOverflow is returned when the value is too large to encode.
var ErrVarintOverflow = coreerr.E("levin", "varint overflow", nil)
var ErrVarintOverflow = core.E("levin", "varint overflow", nil)
// PackVarint encodes v using the epee portable-storage varint scheme.
// The low two bits of the first byte indicate the total encoded width;

View file

@ -5,6 +5,7 @@ import (
"slices"
"time"
core "dappco.re/go/core"
"github.com/google/uuid"
)
@ -20,6 +21,9 @@ const (
// Used for version negotiation during handshake.
var SupportedProtocolVersions = []string{"1.0"}
// RawMessage is the message payload byte slice used for deferred JSON decoding.
type RawMessage = json.RawMessage
// IsProtocolVersionSupported checks if a given version is supported.
func IsProtocolVersionSupported(version string) bool {
return slices.Contains(SupportedProtocolVersions, version)
@ -57,18 +61,18 @@ const (
// Message represents a P2P message between nodes.
type Message struct {
ID string `json:"id"` // UUID
Type MessageType `json:"type"`
From string `json:"from"` // Sender node ID
To string `json:"to"` // Recipient node ID (empty for broadcast)
Timestamp time.Time `json:"ts"`
Payload json.RawMessage `json:"payload"`
ReplyTo string `json:"replyTo,omitempty"` // ID of message being replied to
ID string `json:"id"` // UUID
Type MessageType `json:"type"`
From string `json:"from"` // Sender node ID
To string `json:"to"` // Recipient node ID (empty for broadcast)
Timestamp time.Time `json:"ts"`
Payload RawMessage `json:"payload"`
ReplyTo string `json:"replyTo,omitempty"` // ID of message being replied to
}
// NewMessage creates a new message with a generated ID and timestamp.
func NewMessage(msgType MessageType, from, to string, payload any) (*Message, error) {
var payloadBytes json.RawMessage
var payloadBytes RawMessage
if payload != nil {
data, err := MarshalJSON(payload)
if err != nil {
@ -102,7 +106,11 @@ func (m *Message) ParsePayload(v any) error {
if m.Payload == nil {
return nil
}
return json.Unmarshal(m.Payload, v)
result := core.JSONUnmarshal(m.Payload, v)
if !result.OK {
return result.Value.(error)
}
return nil
}
// --- Payload Types ---
@ -135,9 +143,9 @@ type PongPayload struct {
// StartMinerPayload requests starting a miner.
type StartMinerPayload struct {
MinerType string `json:"minerType"` // Required: miner type (e.g., "xmrig", "tt-miner")
ProfileID string `json:"profileId,omitempty"`
Config json.RawMessage `json:"config,omitempty"` // Override profile config
MinerType string `json:"minerType"` // Required: miner type (e.g., "xmrig", "tt-miner")
ProfileID string `json:"profileId,omitempty"`
Config RawMessage `json:"config,omitempty"` // Override profile config
}
// StopMinerPayload requests stopping a miner.

View file

@ -1,17 +1,14 @@
package node
import (
"encoding/json"
"iter"
"maps"
"path/filepath"
"regexp"
"slices"
"sync"
"time"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"dappco.re/go/core/p2p/logging"
poindexter "forge.lthn.ai/Snider/Poindexter"
@ -79,13 +76,13 @@ func validatePeerName(name string) error {
return nil // Empty names are allowed (optional field)
}
if len(name) < PeerNameMinLength {
return coreerr.E("validatePeerName", "peer name too short", nil)
return core.E("validatePeerName", "peer name too short", nil)
}
if len(name) > PeerNameMaxLength {
return coreerr.E("validatePeerName", "peer name too long", nil)
return core.E("validatePeerName", "peer name too long", nil)
}
if !peerNameRegex.MatchString(name) {
return coreerr.E("validatePeerName", "peer name contains invalid characters (use alphanumeric, hyphens, underscores, spaces)", nil)
return core.E("validatePeerName", "peer name contains invalid characters (use alphanumeric, hyphens, underscores, spaces)", nil)
}
return nil
}
@ -123,7 +120,7 @@ var (
func NewPeerRegistry() (*PeerRegistry, error) {
peersPath, err := xdg.ConfigFile("lethean-desktop/peers.json")
if err != nil {
return nil, coreerr.E("PeerRegistry.New", "failed to get peers path", err)
return nil, core.E("PeerRegistry.New", "failed to get peers path", err)
}
return NewPeerRegistryWithPath(peersPath)
@ -244,7 +241,7 @@ func (r *PeerRegistry) AddPeer(peer *Peer) error {
if peer.ID == "" {
r.mu.Unlock()
return coreerr.E("PeerRegistry.AddPeer", "peer ID is required", nil)
return core.E("PeerRegistry.AddPeer", "peer ID is required", nil)
}
// Validate peer name (P2P-LOW-3)
@ -255,7 +252,7 @@ func (r *PeerRegistry) AddPeer(peer *Peer) error {
if _, exists := r.peers[peer.ID]; exists {
r.mu.Unlock()
return coreerr.E("PeerRegistry.AddPeer", "peer "+peer.ID+" already exists", nil)
return core.E("PeerRegistry.AddPeer", "peer "+peer.ID+" already exists", nil)
}
// Set defaults
@ -280,7 +277,7 @@ func (r *PeerRegistry) UpdatePeer(peer *Peer) error {
if _, exists := r.peers[peer.ID]; !exists {
r.mu.Unlock()
return coreerr.E("PeerRegistry.UpdatePeer", "peer "+peer.ID+" not found", nil)
return core.E("PeerRegistry.UpdatePeer", "peer "+peer.ID+" not found", nil)
}
r.peers[peer.ID] = peer
@ -297,7 +294,7 @@ func (r *PeerRegistry) RemovePeer(id string) error {
if _, exists := r.peers[id]; !exists {
r.mu.Unlock()
return coreerr.E("PeerRegistry.RemovePeer", "peer "+id+" not found", nil)
return core.E("PeerRegistry.RemovePeer", "peer "+id+" not found", nil)
}
delete(r.peers, id)
@ -351,7 +348,7 @@ func (r *PeerRegistry) UpdateMetrics(id string, pingMS, geoKM float64, hops int)
peer, exists := r.peers[id]
if !exists {
r.mu.Unlock()
return coreerr.E("PeerRegistry.UpdateMetrics", "peer "+id+" not found", nil)
return core.E("PeerRegistry.UpdateMetrics", "peer "+id+" not found", nil)
}
peer.PingMS = pingMS
@ -373,7 +370,7 @@ func (r *PeerRegistry) UpdateScore(id string, score float64) error {
peer, exists := r.peers[id]
if !exists {
r.mu.Unlock()
return coreerr.E("PeerRegistry.UpdateScore", "peer "+id+" not found", nil)
return core.E("PeerRegistry.UpdateScore", "peer "+id+" not found", nil)
}
// Clamp score to 0-100
@ -655,28 +652,29 @@ func (r *PeerRegistry) scheduleSave() {
// Must be called with r.mu held (at least RLock).
func (r *PeerRegistry) saveNow() error {
// Ensure directory exists
dir := filepath.Dir(r.path)
if err := coreio.Local.EnsureDir(dir); err != nil {
return coreerr.E("PeerRegistry.saveNow", "failed to create peers directory", err)
dir := core.PathDir(r.path)
if err := fsEnsureDir(dir); err != nil {
return core.E("PeerRegistry.saveNow", "failed to create peers directory", err)
}
// Convert to slice for JSON
peers := slices.Collect(maps.Values(r.peers))
data, err := json.MarshalIndent(peers, "", " ")
if err != nil {
return coreerr.E("PeerRegistry.saveNow", "failed to marshal peers", err)
result := core.JSONMarshal(peers)
if !result.OK {
return core.E("PeerRegistry.saveNow", "failed to marshal peers", result.Value.(error))
}
data := result.Value.([]byte)
// Use atomic write pattern: write to temp file, then rename
tmpPath := r.path + ".tmp"
if err := coreio.Local.Write(tmpPath, string(data)); err != nil {
return coreerr.E("PeerRegistry.saveNow", "failed to write peers temp file", err)
if err := fsWrite(tmpPath, string(data)); err != nil {
return core.E("PeerRegistry.saveNow", "failed to write peers temp file", err)
}
if err := coreio.Local.Rename(tmpPath, r.path); err != nil {
coreio.Local.Delete(tmpPath) // Clean up temp file
return coreerr.E("PeerRegistry.saveNow", "failed to rename peers file", err)
if err := fsRename(tmpPath, r.path); err != nil {
fsDelete(tmpPath) // Clean up temp file
return core.E("PeerRegistry.saveNow", "failed to rename peers file", err)
}
return nil
@ -718,14 +716,15 @@ func (r *PeerRegistry) save() error {
// load reads peers from disk.
func (r *PeerRegistry) load() error {
content, err := coreio.Local.Read(r.path)
content, err := fsRead(r.path)
if err != nil {
return coreerr.E("PeerRegistry.load", "failed to read peers", err)
return core.E("PeerRegistry.load", "failed to read peers", err)
}
var peers []*Peer
if err := json.Unmarshal([]byte(content), &peers); err != nil {
return coreerr.E("PeerRegistry.load", "failed to unmarshal peers", err)
result := core.JSONUnmarshalString(content, &peers)
if !result.OK {
return core.E("PeerRegistry.load", "failed to unmarshal peers", result.Value.(error))
}
r.peers = make(map[string]*Peer)

View file

@ -1,9 +1,7 @@
package node
import (
"fmt"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// ProtocolError represents an error from the remote peer.
@ -13,7 +11,7 @@ type ProtocolError struct {
}
func (e *ProtocolError) Error() string {
return fmt.Sprintf("remote error (%d): %s", e.Code, e.Message)
return core.Sprintf("remote error (%d): %s", e.Code, e.Message)
}
// ResponseHandler provides helpers for handling protocol responses.
@ -26,7 +24,7 @@ type ResponseHandler struct{}
// 3. If response type matches expected (returns error if not)
func (h *ResponseHandler) ValidateResponse(resp *Message, expectedType MessageType) error {
if resp == nil {
return coreerr.E("ResponseHandler.ValidateResponse", "nil response", nil)
return core.E("ResponseHandler.ValidateResponse", "nil response", nil)
}
// Check for error response
@ -40,7 +38,7 @@ func (h *ResponseHandler) ValidateResponse(resp *Message, expectedType MessageTy
// Check expected type
if resp.Type != expectedType {
return coreerr.E("ResponseHandler.ValidateResponse", "unexpected response type: expected "+string(expectedType)+", got "+string(resp.Type), nil)
return core.E("ResponseHandler.ValidateResponse", "unexpected response type: expected "+string(expectedType)+", got "+string(resp.Type), nil)
}
return nil
@ -55,7 +53,7 @@ func (h *ResponseHandler) ParseResponse(resp *Message, expectedType MessageType,
if target != nil {
if err := resp.ParsePayload(target); err != nil {
return coreerr.E("ResponseHandler.ParseResponse", "failed to parse "+string(expectedType)+" payload", err)
return core.E("ResponseHandler.ParseResponse", "failed to parse "+string(expectedType)+" payload", err)
}
}

View file

@ -4,8 +4,6 @@ import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"iter"
"maps"
"net/http"
@ -15,7 +13,7 @@ import (
"sync/atomic"
"time"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"dappco.re/go/core/p2p/logging"
"forge.lthn.ai/Snider/Borg/pkg/smsg"
@ -290,7 +288,7 @@ func (t *Transport) Stop() error {
defer cancel()
if err := t.server.Shutdown(ctx); err != nil {
return coreerr.E("Transport.Stop", "server shutdown error", err)
return core.E("Transport.Stop", "server shutdown error", err)
}
}
@ -321,7 +319,7 @@ func (t *Transport) Connect(peer *Peer) (*PeerConnection, error) {
}
conn, _, err := dialer.Dial(u.String(), nil)
if err != nil {
return nil, coreerr.E("Transport.Connect", "failed to connect to peer", err)
return nil, core.E("Transport.Connect", "failed to connect to peer", err)
}
pc := &PeerConnection{
@ -336,7 +334,7 @@ func (t *Transport) Connect(peer *Peer) (*PeerConnection, error) {
// This also derives and stores the shared secret in pc.SharedSecret
if err := t.performHandshake(pc); err != nil {
conn.Close()
return nil, coreerr.E("Transport.Connect", "handshake failed", err)
return nil, core.E("Transport.Connect", "handshake failed", err)
}
// Store connection using the real peer ID from handshake
@ -369,7 +367,7 @@ func (t *Transport) Send(peerID string, msg *Message) error {
t.mu.RUnlock()
if !exists {
return coreerr.E("Transport.Send", "peer "+peerID+" not connected", nil)
return core.E("Transport.Send", "peer "+peerID+" not connected", nil)
}
return pc.Send(msg)
@ -457,7 +455,7 @@ func (t *Transport) handleWSUpgrade(w http.ResponseWriter, r *http.Request) {
// Decode handshake message (not encrypted yet, contains public key)
var msg Message
if err := json.Unmarshal(data, &msg); err != nil {
if result := core.JSONUnmarshal(data, &msg); !result.OK {
conn.Close()
return
}
@ -485,7 +483,7 @@ func (t *Transport) handleWSUpgrade(w http.ResponseWriter, r *http.Request) {
rejectPayload := HandshakeAckPayload{
Identity: *identity,
Accepted: false,
Reason: fmt.Sprintf("incompatible protocol version %s, supported: %v", payload.Version, SupportedProtocolVersions),
Reason: core.Sprintf("incompatible protocol version %s, supported: %v", payload.Version, SupportedProtocolVersions),
}
rejectMsg, _ := NewMessage(MsgHandshakeAck, identity.ID, payload.Identity.ID, rejectPayload)
if rejectData, err := MarshalJSON(rejectMsg); err == nil {
@ -629,7 +627,7 @@ func (t *Transport) performHandshake(pc *PeerConnection) error {
// Generate challenge for the server to prove it has the matching private key
challenge, err := GenerateChallenge()
if err != nil {
return coreerr.E("Transport.performHandshake", "generate challenge", err)
return core.E("Transport.performHandshake", "generate challenge", err)
}
payload := HandshakePayload{
@ -640,41 +638,41 @@ func (t *Transport) performHandshake(pc *PeerConnection) error {
msg, err := NewMessage(MsgHandshake, identity.ID, pc.Peer.ID, payload)
if err != nil {
return coreerr.E("Transport.performHandshake", "create handshake message", err)
return core.E("Transport.performHandshake", "create handshake message", err)
}
// First message is unencrypted (peer needs our public key)
data, err := MarshalJSON(msg)
if err != nil {
return coreerr.E("Transport.performHandshake", "marshal handshake message", err)
return core.E("Transport.performHandshake", "marshal handshake message", err)
}
if err := pc.Conn.WriteMessage(websocket.TextMessage, data); err != nil {
return coreerr.E("Transport.performHandshake", "send handshake", err)
return core.E("Transport.performHandshake", "send handshake", err)
}
// Wait for ack
_, ackData, err := pc.Conn.ReadMessage()
if err != nil {
return coreerr.E("Transport.performHandshake", "read handshake ack", err)
return core.E("Transport.performHandshake", "read handshake ack", err)
}
var ackMsg Message
if err := json.Unmarshal(ackData, &ackMsg); err != nil {
return coreerr.E("Transport.performHandshake", "unmarshal handshake ack", err)
if result := core.JSONUnmarshal(ackData, &ackMsg); !result.OK {
return core.E("Transport.performHandshake", "unmarshal handshake ack", result.Value.(error))
}
if ackMsg.Type != MsgHandshakeAck {
return coreerr.E("Transport.performHandshake", "expected handshake_ack, got "+string(ackMsg.Type), nil)
return core.E("Transport.performHandshake", "expected handshake_ack, got "+string(ackMsg.Type), nil)
}
var ackPayload HandshakeAckPayload
if err := ackMsg.ParsePayload(&ackPayload); err != nil {
return coreerr.E("Transport.performHandshake", "parse handshake ack payload", err)
return core.E("Transport.performHandshake", "parse handshake ack payload", err)
}
if !ackPayload.Accepted {
return coreerr.E("Transport.performHandshake", "handshake rejected: "+ackPayload.Reason, nil)
return core.E("Transport.performHandshake", "handshake rejected: "+ackPayload.Reason, nil)
}
// Update peer with the received identity info
@ -686,15 +684,15 @@ func (t *Transport) performHandshake(pc *PeerConnection) error {
// Verify challenge response - derive shared secret first using the peer's public key
sharedSecret, err := t.node.DeriveSharedSecret(pc.Peer.PublicKey)
if err != nil {
return coreerr.E("Transport.performHandshake", "derive shared secret for challenge verification", err)
return core.E("Transport.performHandshake", "derive shared secret for challenge verification", err)
}
// Verify the server's response to our challenge
if len(ackPayload.ChallengeResponse) == 0 {
return coreerr.E("Transport.performHandshake", "server did not provide challenge response", nil)
return core.E("Transport.performHandshake", "server did not provide challenge response", nil)
}
if !VerifyChallenge(challenge, ackPayload.ChallengeResponse, sharedSecret) {
return coreerr.E("Transport.performHandshake", "challenge response verification failed: server may not have matching private key", nil)
return core.E("Transport.performHandshake", "challenge response verification failed: server may not have matching private key", nil)
}
// Store the shared secret for later use
@ -841,7 +839,7 @@ func (pc *PeerConnection) Send(msg *Message) error {
// Set write deadline to prevent blocking forever
if err := pc.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
return coreerr.E("PeerConnection.Send", "failed to set write deadline", err)
return core.E("PeerConnection.Send", "failed to set write deadline", err)
}
defer pc.Conn.SetWriteDeadline(time.Time{}) // Reset deadline after send
@ -932,8 +930,8 @@ func (t *Transport) decryptMessage(data []byte, sharedSecret []byte) (*Message,
// Parse message from JSON
var msg Message
if err := json.Unmarshal([]byte(smsgMsg.Body), &msg); err != nil {
return nil, err
if result := core.JSONUnmarshalString(smsgMsg.Body, &msg); !result.OK {
return nil, result.Value.(error)
}
return &msg, nil

View file

@ -2,11 +2,9 @@ package node
import (
"encoding/base64"
"encoding/json"
"path/filepath"
"time"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
"dappco.re/go/core/p2p/logging"
"github.com/adrg/xdg"
@ -55,7 +53,6 @@ func NewWorker(node *NodeManager, transport *Transport) *Worker {
}
}
// SetMinerManager sets the miner manager for handling miner operations.
func (w *Worker) SetMinerManager(manager MinerManager) {
w.minerManager = manager
@ -119,7 +116,7 @@ func (w *Worker) HandleMessage(conn *PeerConnection, msg *Message) {
func (w *Worker) handlePing(msg *Message) (*Message, error) {
var ping PingPayload
if err := msg.ParsePayload(&ping); err != nil {
return nil, coreerr.E("Worker.handlePing", "invalid ping payload", err)
return nil, core.E("Worker.handlePing", "invalid ping payload", err)
}
pong := PongPayload{
@ -202,12 +199,12 @@ func (w *Worker) handleStartMiner(msg *Message) (*Message, error) {
var payload StartMinerPayload
if err := msg.ParsePayload(&payload); err != nil {
return nil, coreerr.E("Worker.handleStartMiner", "invalid start miner payload", err)
return nil, core.E("Worker.handleStartMiner", "invalid start miner payload", err)
}
// Validate miner type is provided
if payload.MinerType == "" {
return nil, coreerr.E("Worker.handleStartMiner", "miner type is required", nil)
return nil, core.E("Worker.handleStartMiner", "miner type is required", nil)
}
// Get the config from the profile or use the override
@ -217,11 +214,11 @@ func (w *Worker) handleStartMiner(msg *Message) (*Message, error) {
} else if w.profileManager != nil {
profile, err := w.profileManager.GetProfile(payload.ProfileID)
if err != nil {
return nil, coreerr.E("Worker.handleStartMiner", "profile not found: "+payload.ProfileID, nil)
return nil, core.E("Worker.handleStartMiner", "profile not found: "+payload.ProfileID, nil)
}
config = profile
} else {
return nil, coreerr.E("Worker.handleStartMiner", "no config provided and no profile manager configured", nil)
return nil, core.E("Worker.handleStartMiner", "no config provided and no profile manager configured", nil)
}
// Start the miner
@ -249,7 +246,7 @@ func (w *Worker) handleStopMiner(msg *Message) (*Message, error) {
var payload StopMinerPayload
if err := msg.ParsePayload(&payload); err != nil {
return nil, coreerr.E("Worker.handleStopMiner", "invalid stop miner payload", err)
return nil, core.E("Worker.handleStopMiner", "invalid stop miner payload", err)
}
err := w.minerManager.StopMiner(payload.MinerName)
@ -272,7 +269,7 @@ func (w *Worker) handleGetLogs(msg *Message) (*Message, error) {
var payload GetLogsPayload
if err := msg.ParsePayload(&payload); err != nil {
return nil, coreerr.E("Worker.handleGetLogs", "invalid get logs payload", err)
return nil, core.E("Worker.handleGetLogs", "invalid get logs payload", err)
}
// Validate and limit the Lines parameter to prevent resource exhaustion
@ -283,7 +280,7 @@ func (w *Worker) handleGetLogs(msg *Message) (*Message, error) {
miner, err := w.minerManager.GetMiner(payload.MinerName)
if err != nil {
return nil, coreerr.E("Worker.handleGetLogs", "miner not found: "+payload.MinerName, nil)
return nil, core.E("Worker.handleGetLogs", "miner not found: "+payload.MinerName, nil)
}
lines := miner.GetConsoleHistory(payload.Lines)
@ -301,7 +298,7 @@ func (w *Worker) handleGetLogs(msg *Message) (*Message, error) {
func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, error) {
var payload DeployPayload
if err := msg.ParsePayload(&payload); err != nil {
return nil, coreerr.E("Worker.handleDeploy", "invalid deploy payload", err)
return nil, core.E("Worker.handleDeploy", "invalid deploy payload", err)
}
// Reconstruct Bundle object from payload
@ -321,19 +318,19 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err
switch bundle.Type {
case BundleProfile:
if w.profileManager == nil {
return nil, coreerr.E("Worker.handleDeploy", "profile manager not configured", nil)
return nil, core.E("Worker.handleDeploy", "profile manager not configured", nil)
}
// Decrypt and extract profile data
profileData, err := ExtractProfileBundle(bundle, password)
if err != nil {
return nil, coreerr.E("Worker.handleDeploy", "failed to extract profile bundle", err)
return nil, core.E("Worker.handleDeploy", "failed to extract profile bundle", err)
}
// Unmarshal into interface{} to pass to ProfileManager
var profile any
if err := json.Unmarshal(profileData, &profile); err != nil {
return nil, coreerr.E("Worker.handleDeploy", "invalid profile data JSON", err)
if result := core.JSONUnmarshal(profileData, &profile); !result.OK {
return nil, core.E("Worker.handleDeploy", "invalid profile data JSON", result.Value.(error))
}
if err := w.profileManager.SaveProfile(profile); err != nil {
@ -354,8 +351,8 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err
case BundleMiner, BundleFull:
// Determine installation directory
// We use w.DataDir/lethean-desktop/miners/<bundle_name>
minersDir := filepath.Join(w.DataDir, "lethean-desktop", "miners")
installDir := filepath.Join(minersDir, payload.Name)
minersDir := core.JoinPath(w.DataDir, "lethean-desktop", "miners")
installDir := core.JoinPath(minersDir, payload.Name)
logging.Info("deploying miner bundle", logging.Fields{
"name": payload.Name,
@ -366,14 +363,14 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err
// Extract miner bundle
minerPath, profileData, err := ExtractMinerBundle(bundle, password, installDir)
if err != nil {
return nil, coreerr.E("Worker.handleDeploy", "failed to extract miner bundle", err)
return nil, core.E("Worker.handleDeploy", "failed to extract miner bundle", err)
}
// If the bundle contained a profile config, save it
if len(profileData) > 0 && w.profileManager != nil {
var profile any
if err := json.Unmarshal(profileData, &profile); err != nil {
logging.Warn("failed to parse profile from miner bundle", logging.Fields{"error": err})
if result := core.JSONUnmarshal(profileData, &profile); !result.OK {
logging.Warn("failed to parse profile from miner bundle", logging.Fields{"error": result.Value.(error)})
} else {
if err := w.profileManager.SaveProfile(profile); err != nil {
logging.Warn("failed to save profile from miner bundle", logging.Fields{"error": err})
@ -396,7 +393,7 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err
return msg.Reply(MsgDeployAck, ack)
default:
return nil, coreerr.E("Worker.handleDeploy", "unknown bundle type: "+payload.BundleType, nil)
return nil, core.E("Worker.handleDeploy", "unknown bundle type: "+payload.BundleType, nil)
}
}

View file

@ -7,7 +7,7 @@ import (
"encoding/binary"
"io"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// TLV Types
@ -23,7 +23,7 @@ const (
// UEPSHeader represents the conscious routing metadata
type UEPSHeader struct {
Version uint8 // Default 0x09
Version uint8 // Default 0x09
CurrentLayer uint8
TargetLayer uint8
IntentID uint8 // Semantic Token
@ -68,7 +68,7 @@ func (p *PacketBuilder) MarshalAndSign(sharedSecret []byte) ([]byte, error) {
if err := writeTLV(buf, TagIntent, []byte{p.Header.IntentID}); err != nil {
return nil, err
}
// Threat Score is uint16, needs binary packing
tsBuf := make([]byte, 2)
binary.BigEndian.PutUint16(tsBuf, p.Header.ThreatScore)
@ -105,22 +105,21 @@ func (p *PacketBuilder) MarshalAndSign(sharedSecret []byte) ([]byte, error) {
func writeTLV(w io.Writer, tag uint8, value []byte) error {
// Check length constraint (2 byte length = max 65535 bytes)
if len(value) > 65535 {
return coreerr.E("ueps.writeTLV", "TLV value too large for 2-byte length header", nil)
return core.E("ueps.writeTLV", "TLV value too large for 2-byte length header", nil)
}
if _, err := w.Write([]byte{tag}); err != nil {
return err
}
lenBuf := make([]byte, 2)
binary.BigEndian.PutUint16(lenBuf, uint16(len(value)))
if _, err := w.Write(lenBuf); err != nil {
return err
}
if _, err := w.Write(value); err != nil {
return err
}
return nil
}

View file

@ -8,7 +8,7 @@ import (
"encoding/binary"
"io"
coreerr "dappco.re/go/core/log"
core "dappco.re/go/core"
)
// ParsedPacket holds the verified data
@ -93,7 +93,7 @@ func ReadAndVerify(r *bufio.Reader, sharedSecret []byte) (*ParsedPacket, error)
verify:
if len(signature) == 0 {
return nil, coreerr.E("ueps.ReadAndVerify", "UEPS packet missing HMAC signature", nil)
return nil, core.E("ueps.ReadAndVerify", "UEPS packet missing HMAC signature", nil)
}
// 5. Verify HMAC
@ -104,7 +104,7 @@ verify:
expectedMAC := mac.Sum(nil)
if !hmac.Equal(signature, expectedMAC) {
return nil, coreerr.E("ueps.ReadAndVerify", "integrity violation: HMAC mismatch (ThreatScore +100)", nil)
return nil, core.E("ueps.ReadAndVerify", "integrity violation: HMAC mismatch (ThreatScore +100)", nil)
}
return &ParsedPacket{
@ -112,4 +112,3 @@ verify:
Payload: payload,
}, nil
}