diff --git a/go.mod b/go.mod index 4e255d6..06767d8 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module forge.lthn.ai/core/go-p2p go 1.26.0 require ( - forge.lthn.ai/Snider/Borg v0.2.1 + forge.lthn.ai/Snider/Borg v0.3.1 forge.lthn.ai/Snider/Poindexter v0.0.2 + forge.lthn.ai/core/go-io v0.1.3 + forge.lthn.ai/core/go-log v0.0.4 github.com/adrg/xdg v0.5.3 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 @@ -17,11 +19,8 @@ require ( 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/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect golang.org/x/crypto v0.49.0 // indirect golang.org/x/sys v0.42.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e206a7e..19f1d33 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,19 @@ -forge.lthn.ai/Snider/Borg v0.2.1 h1:Uf/YtUJLL8jlxTCjvP4J+5GHe3LLeALGtbh7zj8d8Qc= -forge.lthn.ai/Snider/Borg v0.2.1/go.mod h1:MVfolb7F6/A2LOIijcbBhWImu5db5NSMcSjvShMoMCA= +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.2 h1:XXzSKFjO6MeftQAnB9qR+IkOTp9f57Tg4sIx8Qzi/II= forge.lthn.ai/Snider/Poindexter v0.0.2/go.mod h1:ddzGia98k3HKkR0gl58IDzqz+MmgW2cQJOCNLfuWPpo= +forge.lthn.ai/core/go-io v0.1.3 h1:2DeH60V2kRqYGjCEvdze2FvqiryscgkQm9zOgqes20o= +forge.lthn.ai/core/go-io v0.1.3/go.mod h1:PbNKW1Q25ywSOoQXeGdQHbV5aiIrTXvHIQ5uhplA//g= +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/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/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/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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -18,23 +22,20 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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= diff --git a/logging/logger.go b/logging/logger.go index 669add2..f9247a7 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -9,6 +9,8 @@ import ( "strings" "sync" "time" + + coreerr "forge.lthn.ai/core/go-log" ) // Level represents the severity of a log message. @@ -278,6 +280,6 @@ func ParseLevel(s string) (Level, error) { case "ERROR": return LevelError, nil default: - return LevelInfo, fmt.Errorf("unknown log level: %s", s) + return LevelInfo, coreerr.E("logging.ParseLevel", "unknown log level: "+s, nil) } } diff --git a/node/bundle.go b/node/bundle.go index ecc2513..386bb8f 100644 --- a/node/bundle.go +++ b/node/bundle.go @@ -6,13 +6,14 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" - "errors" - "fmt" "io" "os" "path/filepath" "strings" + coreio "forge.lthn.ai/core/go-io" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/Snider/Borg/pkg/datanode" "forge.lthn.ai/Snider/Borg/pkg/tim" ) @@ -49,14 +50,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, fmt.Errorf("failed to create TIM: %w", err) + return nil, coreerr.E("CreateProfileBundle", "failed to create TIM", err) } t.Config = profileJSON // Encrypt to STIM format stimData, err := t.ToSigil(password) if err != nil { - return nil, fmt.Errorf("failed to encrypt bundle: %w", err) + return nil, coreerr.E("CreateProfileBundle", "failed to encrypt bundle", err) } // Calculate checksum @@ -85,29 +86,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 - minerData, err := os.ReadFile(minerPath) + minerContent, err := coreio.Local.Read(minerPath) if err != nil { - return nil, fmt.Errorf("failed to read miner binary: %w", err) + return nil, coreerr.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, }) if err != nil { - return nil, fmt.Errorf("failed to create tarball: %w", err) + return nil, coreerr.E("CreateMinerBundle", "failed to create tarball", err) } // Create DataNode from tarball dn, err := datanode.FromTar(tarData) if err != nil { - return nil, fmt.Errorf("failed to create datanode: %w", err) + return nil, coreerr.E("CreateMinerBundle", "failed to create datanode", err) } // Create TIM from DataNode t, err := tim.FromDataNode(dn) if err != nil { - return nil, fmt.Errorf("failed to create TIM: %w", err) + return nil, coreerr.E("CreateMinerBundle", "failed to create TIM", err) } // Set profile as config if provided @@ -118,7 +120,7 @@ func CreateMinerBundle(minerPath string, profileJSON []byte, name string, passwo // Encrypt to STIM format stimData, err := t.ToSigil(password) if err != nil { - return nil, fmt.Errorf("failed to encrypt bundle: %w", err) + return nil, coreerr.E("CreateMinerBundle", "failed to encrypt bundle", err) } checksum := calculateChecksum(stimData) @@ -135,7 +137,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, errors.New("checksum mismatch - bundle may be corrupted") + return nil, coreerr.E("ExtractProfileBundle", "checksum mismatch - bundle may be corrupted", nil) } // If it's unencrypted JSON, just return it @@ -146,7 +148,7 @@ func ExtractProfileBundle(bundle *Bundle, password string) ([]byte, error) { // Decrypt STIM format t, err := tim.FromSigil(bundle.Data, password) if err != nil { - return nil, fmt.Errorf("failed to decrypt bundle: %w", err) + return nil, coreerr.E("ExtractProfileBundle", "failed to decrypt bundle", err) } return t.Config, nil @@ -156,25 +158,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, errors.New("checksum mismatch - bundle may be corrupted") + return "", nil, coreerr.E("ExtractMinerBundle", "checksum mismatch - bundle may be corrupted", nil) } // Decrypt STIM format t, err := tim.FromSigil(bundle.Data, password) if err != nil { - return "", nil, fmt.Errorf("failed to decrypt bundle: %w", err) + return "", nil, coreerr.E("ExtractMinerBundle", "failed to decrypt bundle", err) } // Convert rootfs to tarball and extract tarData, err := t.RootFS.ToTar() if err != nil { - return "", nil, fmt.Errorf("failed to convert rootfs to tar: %w", err) + return "", nil, coreerr.E("ExtractMinerBundle", "failed to convert rootfs to tar", err) } // Extract tarball to destination minerPath, err := extractTarball(tarData, destDir) if err != nil { - return "", nil, fmt.Errorf("failed to extract tarball: %w", err) + return "", nil, coreerr.E("ExtractMinerBundle", "failed to extract tarball", err) } return minerPath, t.Config, nil @@ -254,11 +256,11 @@ 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 "", fmt.Errorf("failed to resolve destination directory: %w", err) + return "", coreerr.E("extractTarball", "failed to resolve destination directory", err) } absDestDir = filepath.Clean(absDestDir) - if err := os.MkdirAll(absDestDir, 0755); err != nil { + if err := coreio.Local.EnsureDir(absDestDir); err != nil { return "", err } @@ -279,12 +281,12 @@ func extractTarball(tarData []byte, destDir string) (string, error) { // Reject absolute paths if filepath.IsAbs(cleanName) { - return "", fmt.Errorf("invalid tar entry: absolute path not allowed: %s", hdr.Name) + return "", coreerr.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 "", fmt.Errorf("invalid tar entry: path traversal attempt: %s", hdr.Name) + return "", coreerr.E("extractTarball", "invalid tar entry: path traversal attempt: "+hdr.Name, nil) } // Build the full path and verify it's within destDir @@ -293,17 +295,17 @@ func extractTarball(tarData []byte, destDir string) (string, error) { // Final security check: ensure the path is still within destDir if !strings.HasPrefix(fullPath, absDestDir+string(os.PathSeparator)) && fullPath != absDestDir { - return "", fmt.Errorf("invalid tar entry: path escape attempt: %s", hdr.Name) + return "", coreerr.E("extractTarball", "invalid tar entry: path escape attempt: "+hdr.Name, nil) } switch hdr.Typeflag { case tar.TypeDir: - if err := os.MkdirAll(fullPath, os.FileMode(hdr.Mode)); err != nil { + if err := coreio.Local.EnsureDir(fullPath); err != nil { return "", err } case tar.TypeReg: // Ensure parent directory exists - if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { + if err := coreio.Local.EnsureDir(filepath.Dir(fullPath)); err != nil { return "", err } @@ -321,8 +323,8 @@ func extractTarball(tarData []byte, destDir string) (string, error) { return "", err } if written > maxFileSize { - os.Remove(fullPath) - return "", fmt.Errorf("file %s exceeds maximum size of %d bytes", hdr.Name, maxFileSize) + coreio.Local.Delete(fullPath) + return "", coreerr.E("extractTarball", "file "+hdr.Name+" exceeds maximum size", nil) } // Track first executable diff --git a/node/controller.go b/node/controller.go index 7e833f1..2b50122 100644 --- a/node/controller.go +++ b/node/controller.go @@ -3,11 +3,11 @@ package node import ( "context" "encoding/json" - "errors" - "fmt" "sync" "time" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-p2p/logging" ) @@ -67,11 +67,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, fmt.Errorf("peer not found: %s", peerID) + return nil, coreerr.E("Controller.sendRequest", "peer not found: "+peerID, nil) } conn, err := c.transport.Connect(peer) if err != nil { - return nil, fmt.Errorf("failed to connect to peer: %w", err) + return nil, coreerr.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 +96,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, fmt.Errorf("failed to send message: %w", err) + return nil, coreerr.E("Controller.sendRequest", "failed to send message", err) } // Wait for response @@ -107,7 +107,7 @@ func (c *Controller) sendRequest(peerID string, msg *Message, timeout time.Durat case resp := <-respCh: return resp, nil case <-ctx.Done(): - return nil, errors.New("request timeout") + return nil, coreerr.E("Controller.sendRequest", "request timeout", nil) } } @@ -120,7 +120,7 @@ func (c *Controller) GetRemoteStats(peerID string) (*StatsPayload, error) { msg, err := NewMessage(MsgGetStats, identity.ID, peerID, nil) if err != nil { - return nil, fmt.Errorf("failed to create message: %w", err) + return nil, coreerr.E("Controller.GetRemoteStats", "failed to create message", err) } resp, err := c.sendRequest(peerID, msg, 10*time.Second) @@ -144,7 +144,7 @@ func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, confi } if minerType == "" { - return errors.New("miner type is required") + return coreerr.E("Controller.StartRemoteMiner", "miner type is required", nil) } payload := StartMinerPayload{ @@ -155,7 +155,7 @@ func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, confi msg, err := NewMessage(MsgStartMiner, identity.ID, peerID, payload) if err != nil { - return fmt.Errorf("failed to create message: %w", err) + return coreerr.E("Controller.StartRemoteMiner", "failed to create message", err) } resp, err := c.sendRequest(peerID, msg, 30*time.Second) @@ -169,7 +169,7 @@ func (c *Controller) StartRemoteMiner(peerID, minerType, profileID string, confi } if !ack.Success { - return fmt.Errorf("miner start failed: %s", ack.Error) + return coreerr.E("Controller.StartRemoteMiner", "miner start failed: "+ack.Error, nil) } return nil @@ -188,7 +188,7 @@ func (c *Controller) StopRemoteMiner(peerID, minerName string) error { msg, err := NewMessage(MsgStopMiner, identity.ID, peerID, payload) if err != nil { - return fmt.Errorf("failed to create message: %w", err) + return coreerr.E("Controller.StopRemoteMiner", "failed to create message", err) } resp, err := c.sendRequest(peerID, msg, 30*time.Second) @@ -202,7 +202,7 @@ func (c *Controller) StopRemoteMiner(peerID, minerName string) error { } if !ack.Success { - return fmt.Errorf("miner stop failed: %s", ack.Error) + return coreerr.E("Controller.StopRemoteMiner", "miner stop failed: "+ack.Error, nil) } return nil @@ -222,7 +222,7 @@ func (c *Controller) GetRemoteLogs(peerID, minerName string, lines int) ([]strin msg, err := NewMessage(MsgGetLogs, identity.ID, peerID, payload) if err != nil { - return nil, fmt.Errorf("failed to create message: %w", err) + return nil, coreerr.E("Controller.GetRemoteLogs", "failed to create message", err) } resp, err := c.sendRequest(peerID, msg, 10*time.Second) @@ -281,7 +281,7 @@ func (c *Controller) PingPeer(peerID string) (float64, error) { msg, err := NewMessage(MsgPing, identity.ID, peerID, payload) if err != nil { - return 0, fmt.Errorf("failed to create message: %w", err) + return 0, coreerr.E("Controller.PingPeer", "failed to create message", err) } resp, err := c.sendRequest(peerID, msg, 5*time.Second) @@ -309,7 +309,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 fmt.Errorf("peer not found: %s", peerID) + return coreerr.E("Controller.ConnectToPeer", "peer not found: "+peerID, nil) } _, err := c.transport.Connect(peer) @@ -320,7 +320,7 @@ func (c *Controller) ConnectToPeer(peerID string) error { func (c *Controller) DisconnectFromPeer(peerID string) error { conn := c.transport.GetConnection(peerID) if conn == nil { - return fmt.Errorf("peer not connected: %s", peerID) + return coreerr.E("Controller.DisconnectFromPeer", "peer not connected: "+peerID, nil) } return conn.Close() diff --git a/node/dispatcher.go b/node/dispatcher.go index 668700e..c7704a1 100644 --- a/node/dispatcher.go +++ b/node/dispatcher.go @@ -1,11 +1,12 @@ package node import ( - "errors" "fmt" "iter" "sync" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-p2p/logging" "forge.lthn.ai/core/go-p2p/ueps" ) @@ -133,12 +134,12 @@ func (d *Dispatcher) Dispatch(pkt *ueps.ParsedPacket) error { var ( // ErrThreatScoreExceeded is returned when a packet's ThreatScore exceeds // the safety threshold. - ErrThreatScoreExceeded = fmt.Errorf("packet rejected: threat score exceeds safety threshold (%d)", ThreatScoreThreshold) + ErrThreatScoreExceeded = coreerr.E("Dispatcher.Dispatch", fmt.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 = errors.New("packet dropped: unknown intent") + ErrUnknownIntent = coreerr.E("Dispatcher.Dispatch", "packet dropped: unknown intent", nil) // ErrNilPacket is returned when a nil packet is passed to Dispatch. - ErrNilPacket = errors.New("dispatch: nil packet") + ErrNilPacket = coreerr.E("Dispatcher.Dispatch", "nil packet", nil) ) diff --git a/node/errors.go b/node/errors.go index 34b32be..7128e07 100644 --- a/node/errors.go +++ b/node/errors.go @@ -1,14 +1,14 @@ package node -import "errors" +import coreerr "forge.lthn.ai/core/go-log" // 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 = errors.New("node identity not initialized") + ErrIdentityNotInitialized = coreerr.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 = errors.New("miner manager not configured") + ErrMinerManagerNotConfigured = coreerr.E("node", "miner manager not configured", nil) ) diff --git a/node/identity.go b/node/identity.go index 8d2bf45..53b07a3 100644 --- a/node/identity.go +++ b/node/identity.go @@ -8,12 +8,13 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" - "fmt" - "os" "path/filepath" "sync" "time" + coreio "forge.lthn.ai/core/go-io" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/Snider/Borg/pkg/stmf" "github.com/adrg/xdg" ) @@ -25,7 +26,7 @@ const ChallengeSize = 32 func GenerateChallenge() ([]byte, error) { challenge := make([]byte, ChallengeSize) if _, err := rand.Read(challenge); err != nil { - return nil, fmt.Errorf("failed to generate challenge: %w", err) + return nil, coreerr.E("GenerateChallenge", "failed to generate challenge", err) } return challenge, nil } @@ -79,12 +80,12 @@ type NodeManager struct { func NewNodeManager() (*NodeManager, error) { keyPath, err := xdg.DataFile("lethean-desktop/node/private.key") if err != nil { - return nil, fmt.Errorf("failed to get key path: %w", err) + return nil, coreerr.E("NodeManager.New", "failed to get key path", err) } configPath, err := xdg.ConfigFile("lethean-desktop/node.json") if err != nil { - return nil, fmt.Errorf("failed to get config path: %w", err) + return nil, coreerr.E("NodeManager.New", "failed to get config path", err) } return NewNodeManagerWithPaths(keyPath, configPath) @@ -134,7 +135,7 @@ func (n *NodeManager) GenerateIdentity(name string, role NodeRole) error { // Generate X25519 keypair using STMF keyPair, err := stmf.GenerateKeyPair() if err != nil { - return fmt.Errorf("failed to generate keypair: %w", err) + return coreerr.E("NodeManager.GenerateIdentity", "failed to generate keypair", err) } // Derive node ID from public key (first 16 bytes as hex = 32 char ID) @@ -155,12 +156,12 @@ func (n *NodeManager) GenerateIdentity(name string, role NodeRole) error { // Save private key if err := n.savePrivateKey(); err != nil { - return fmt.Errorf("failed to save private key: %w", err) + return coreerr.E("NodeManager.GenerateIdentity", "failed to save private key", err) } // Save identity config if err := n.saveIdentity(); err != nil { - return fmt.Errorf("failed to save identity: %w", err) + return coreerr.E("NodeManager.GenerateIdentity", "failed to save identity", err) } return nil @@ -179,19 +180,19 @@ func (n *NodeManager) DeriveSharedSecret(peerPubKeyBase64 string) ([]byte, error // Load peer's public key peerPubKey, err := stmf.LoadPublicKeyBase64(peerPubKeyBase64) if err != nil { - return nil, fmt.Errorf("failed to load peer public key: %w", err) + return nil, coreerr.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, fmt.Errorf("failed to load private key: %w", err) + return nil, coreerr.E("NodeManager.DeriveSharedSecret", "failed to load private key", err) } // Derive shared secret using ECDH sharedSecret, err := privateKey.ECDH(peerPubKey) if err != nil { - return nil, fmt.Errorf("failed to derive shared secret: %w", err) + return nil, coreerr.E("NodeManager.DeriveSharedSecret", "failed to derive shared secret", err) } // Hash the shared secret using SHA-256 (same pattern as Borg/trix) @@ -203,13 +204,13 @@ func (n *NodeManager) DeriveSharedSecret(peerPubKeyBase64 string) ([]byte, error func (n *NodeManager) savePrivateKey() error { // Ensure directory exists dir := filepath.Dir(n.keyPath) - if err := os.MkdirAll(dir, 0700); err != nil { - return fmt.Errorf("failed to create key directory: %w", err) + if err := coreio.Local.EnsureDir(dir); err != nil { + return coreerr.E("NodeManager.savePrivateKey", "failed to create key directory", err) } - // Write private key with restricted permissions (0600) - if err := os.WriteFile(n.keyPath, n.privateKey, 0600); err != nil { - return fmt.Errorf("failed to write private key: %w", 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) } return nil @@ -219,17 +220,17 @@ func (n *NodeManager) savePrivateKey() error { func (n *NodeManager) saveIdentity() error { // Ensure directory exists dir := filepath.Dir(n.configPath) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) + if err := coreio.Local.EnsureDir(dir); err != nil { + return coreerr.E("NodeManager.saveIdentity", "failed to create config directory", err) } data, err := json.MarshalIndent(n.identity, "", " ") if err != nil { - return fmt.Errorf("failed to marshal identity: %w", err) + return coreerr.E("NodeManager.saveIdentity", "failed to marshal identity", err) } - if err := os.WriteFile(n.configPath, data, 0644); err != nil { - return fmt.Errorf("failed to write identity: %w", err) + if err := coreio.Local.Write(n.configPath, string(data)); err != nil { + return coreerr.E("NodeManager.saveIdentity", "failed to write identity", err) } return nil @@ -238,26 +239,27 @@ func (n *NodeManager) saveIdentity() error { // loadIdentity loads the node identity from disk. func (n *NodeManager) loadIdentity() error { // Load identity config - data, err := os.ReadFile(n.configPath) + content, err := coreio.Local.Read(n.configPath) if err != nil { - return fmt.Errorf("failed to read identity: %w", err) + return coreerr.E("NodeManager.loadIdentity", "failed to read identity", err) } var identity NodeIdentity - if err := json.Unmarshal(data, &identity); err != nil { - return fmt.Errorf("failed to unmarshal identity: %w", err) + if err := json.Unmarshal([]byte(content), &identity); err != nil { + return coreerr.E("NodeManager.loadIdentity", "failed to unmarshal identity", err) } // Load private key - privateKey, err := os.ReadFile(n.keyPath) + keyContent, err := coreio.Local.Read(n.keyPath) if err != nil { - return fmt.Errorf("failed to read private key: %w", err) + return coreerr.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 fmt.Errorf("failed to load keypair: %w", err) + return coreerr.E("NodeManager.loadIdentity", "failed to load keypair", err) } n.identity = &identity @@ -272,14 +274,18 @@ func (n *NodeManager) Delete() error { n.mu.Lock() defer n.mu.Unlock() - // Remove private key - if err := os.Remove(n.keyPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to remove private key: %w", err) + // 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) + } } - // Remove identity config - if err := os.Remove(n.configPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to remove identity: %w", 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) + } } n.identity = nil diff --git a/node/levin/header.go b/node/levin/header.go index 4655b03..c65c151 100644 --- a/node/levin/header.go +++ b/node/levin/header.go @@ -7,7 +7,8 @@ package levin import ( "encoding/binary" - "errors" + + coreerr "forge.lthn.ai/core/go-log" ) // HeaderSize is the exact byte length of a serialised Levin header. @@ -42,8 +43,8 @@ const ( // Sentinel errors returned by DecodeHeader. var ( - ErrBadSignature = errors.New("levin: bad signature") - ErrPayloadTooBig = errors.New("levin: payload exceeds maximum size") + ErrBadSignature = coreerr.E("levin", "bad signature", nil) + ErrPayloadTooBig = coreerr.E("levin", "payload exceeds maximum size", nil) ) // Header is the 33-byte packed header that prefixes every Levin message. diff --git a/node/levin/storage.go b/node/levin/storage.go index b571b15..d9e7682 100644 --- a/node/levin/storage.go +++ b/node/levin/storage.go @@ -5,11 +5,12 @@ package levin import ( "encoding/binary" - "errors" "fmt" "maps" "math" "slices" + + coreerr "forge.lthn.ai/core/go-log" ) // Portable storage signatures and version (9-byte header). @@ -40,12 +41,12 @@ const ( // Sentinel errors for storage encoding and decoding. var ( - ErrStorageBadSignature = errors.New("levin: bad storage signature") - ErrStorageTruncated = errors.New("levin: truncated storage data") - ErrStorageBadVersion = errors.New("levin: unsupported storage version") - ErrStorageNameTooLong = errors.New("levin: entry name exceeds 255 bytes") - ErrStorageTypeMismatch = errors.New("levin: value type mismatch") - ErrStorageUnknownType = errors.New("levin: unknown type tag") + 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) ) // Section is an ordered map of named values forming a portable storage section. @@ -393,7 +394,7 @@ func encodeValue(buf []byte, v Value) ([]byte, error) { return encodeSection(buf, v.objectVal) default: - return nil, fmt.Errorf("%w: 0x%02x", ErrStorageUnknownType, v.Type) + return nil, coreerr.E("levin.encodeValue", fmt.Sprintf("unknown type tag: 0x%02x", v.Type), ErrStorageUnknownType) } } @@ -440,7 +441,7 @@ func encodeArray(buf []byte, v Value) ([]byte, error) { return buf, nil default: - return nil, fmt.Errorf("%w: array of 0x%02x", ErrStorageUnknownType, elemType) + return nil, coreerr.E("levin.encodeArray", fmt.Sprintf("unknown type tag: array of 0x%02x", elemType), ErrStorageUnknownType) } } @@ -475,7 +476,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, fmt.Errorf("section entry count: %w", err) + return nil, 0, coreerr.E("levin.decodeSection", "section entry count", err) } off := n @@ -506,7 +507,7 @@ func decodeSection(buf []byte) (Section, int, error) { // Value. val, consumed, err := decodeValue(buf[off:], tag) if err != nil { - return nil, 0, fmt.Errorf("field %q: %w", name, err) + return nil, 0, coreerr.E("levin.decodeSection", "field "+name, err) } off += consumed @@ -612,7 +613,7 @@ func decodeValue(buf []byte, tag uint8) (Value, int, error) { return Value{Type: TypeObject, objectVal: sec}, consumed, nil default: - return Value{}, 0, fmt.Errorf("%w: 0x%02x", ErrStorageUnknownType, tag) + return Value{}, 0, coreerr.E("levin.decodeValue", fmt.Sprintf("unknown type tag: 0x%02x", tag), ErrStorageUnknownType) } } @@ -680,6 +681,6 @@ func decodeArray(buf []byte, tag uint8) (Value, int, error) { return Value{Type: tag, objectArray: arr}, off, nil default: - return Value{}, 0, fmt.Errorf("%w: array of 0x%02x", ErrStorageUnknownType, elemType) + return Value{}, 0, coreerr.E("levin.decodeArray", fmt.Sprintf("unknown type tag: array of 0x%02x", elemType), ErrStorageUnknownType) } } diff --git a/node/levin/varint.go b/node/levin/varint.go index cb40d52..c895eb9 100644 --- a/node/levin/varint.go +++ b/node/levin/varint.go @@ -5,7 +5,8 @@ package levin import ( "encoding/binary" - "errors" + + coreerr "forge.lthn.ai/core/go-log" ) // Size-mark bits occupying the two lowest bits of the first byte. @@ -22,10 +23,10 @@ const ( ) // ErrVarintTruncated is returned when the buffer is too short. -var ErrVarintTruncated = errors.New("levin: truncated varint") +var ErrVarintTruncated = coreerr.E("levin", "truncated varint", nil) // ErrVarintOverflow is returned when the value is too large to encode. -var ErrVarintOverflow = errors.New("levin: varint overflow") +var ErrVarintOverflow = coreerr.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; diff --git a/node/peer.go b/node/peer.go index bea47db..6e7a115 100644 --- a/node/peer.go +++ b/node/peer.go @@ -2,11 +2,8 @@ package node import ( "encoding/json" - "errors" - "fmt" "iter" "maps" - "os" "path/filepath" "regexp" "slices" @@ -14,6 +11,8 @@ import ( "time" poindexter "forge.lthn.ai/Snider/Poindexter" + coreio "forge.lthn.ai/core/go-io" + coreerr "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/go-p2p/logging" "github.com/adrg/xdg" ) @@ -79,13 +78,13 @@ func validatePeerName(name string) error { return nil // Empty names are allowed (optional field) } if len(name) < PeerNameMinLength { - return fmt.Errorf("peer name too short (min %d characters)", PeerNameMinLength) + return coreerr.E("validatePeerName", "peer name too short", nil) } if len(name) > PeerNameMaxLength { - return fmt.Errorf("peer name too long (max %d characters)", PeerNameMaxLength) + return coreerr.E("validatePeerName", "peer name too long", nil) } if !peerNameRegex.MatchString(name) { - return errors.New("peer name contains invalid characters (use alphanumeric, hyphens, underscores, spaces)") + return coreerr.E("validatePeerName", "peer name contains invalid characters (use alphanumeric, hyphens, underscores, spaces)", nil) } return nil } @@ -123,7 +122,7 @@ var ( func NewPeerRegistry() (*PeerRegistry, error) { peersPath, err := xdg.ConfigFile("lethean-desktop/peers.json") if err != nil { - return nil, fmt.Errorf("failed to get peers path: %w", err) + return nil, coreerr.E("PeerRegistry.New", "failed to get peers path", err) } return NewPeerRegistryWithPath(peersPath) @@ -244,7 +243,7 @@ func (r *PeerRegistry) AddPeer(peer *Peer) error { if peer.ID == "" { r.mu.Unlock() - return errors.New("peer ID is required") + return coreerr.E("PeerRegistry.AddPeer", "peer ID is required", nil) } // Validate peer name (P2P-LOW-3) @@ -255,7 +254,7 @@ func (r *PeerRegistry) AddPeer(peer *Peer) error { if _, exists := r.peers[peer.ID]; exists { r.mu.Unlock() - return fmt.Errorf("peer %s already exists", peer.ID) + return coreerr.E("PeerRegistry.AddPeer", "peer "+peer.ID+" already exists", nil) } // Set defaults @@ -280,7 +279,7 @@ func (r *PeerRegistry) UpdatePeer(peer *Peer) error { if _, exists := r.peers[peer.ID]; !exists { r.mu.Unlock() - return fmt.Errorf("peer %s not found", peer.ID) + return coreerr.E("PeerRegistry.UpdatePeer", "peer "+peer.ID+" not found", nil) } r.peers[peer.ID] = peer @@ -297,7 +296,7 @@ func (r *PeerRegistry) RemovePeer(id string) error { if _, exists := r.peers[id]; !exists { r.mu.Unlock() - return fmt.Errorf("peer %s not found", id) + return coreerr.E("PeerRegistry.RemovePeer", "peer "+id+" not found", nil) } delete(r.peers, id) @@ -351,7 +350,7 @@ func (r *PeerRegistry) UpdateMetrics(id string, pingMS, geoKM float64, hops int) peer, exists := r.peers[id] if !exists { r.mu.Unlock() - return fmt.Errorf("peer %s not found", id) + return coreerr.E("PeerRegistry.UpdateMetrics", "peer "+id+" not found", nil) } peer.PingMS = pingMS @@ -373,7 +372,7 @@ func (r *PeerRegistry) UpdateScore(id string, score float64) error { peer, exists := r.peers[id] if !exists { r.mu.Unlock() - return fmt.Errorf("peer %s not found", id) + return coreerr.E("PeerRegistry.UpdateScore", "peer "+id+" not found", nil) } // Clamp score to 0-100 @@ -656,8 +655,8 @@ func (r *PeerRegistry) scheduleSave() { func (r *PeerRegistry) saveNow() error { // Ensure directory exists dir := filepath.Dir(r.path) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("failed to create peers directory: %w", err) + if err := coreio.Local.EnsureDir(dir); err != nil { + return coreerr.E("PeerRegistry.saveNow", "failed to create peers directory", err) } // Convert to slice for JSON @@ -665,18 +664,18 @@ func (r *PeerRegistry) saveNow() error { data, err := json.MarshalIndent(peers, "", " ") if err != nil { - return fmt.Errorf("failed to marshal peers: %w", err) + return coreerr.E("PeerRegistry.saveNow", "failed to marshal peers", err) } // Use atomic write pattern: write to temp file, then rename tmpPath := r.path + ".tmp" - if err := os.WriteFile(tmpPath, data, 0644); err != nil { - return fmt.Errorf("failed to write peers temp file: %w", err) + if err := coreio.Local.Write(tmpPath, string(data)); err != nil { + return coreerr.E("PeerRegistry.saveNow", "failed to write peers temp file", err) } - if err := os.Rename(tmpPath, r.path); err != nil { - os.Remove(tmpPath) // Clean up temp file - return fmt.Errorf("failed to rename peers file: %w", 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) } return nil @@ -718,14 +717,14 @@ func (r *PeerRegistry) save() error { // load reads peers from disk. func (r *PeerRegistry) load() error { - data, err := os.ReadFile(r.path) + content, err := coreio.Local.Read(r.path) if err != nil { - return fmt.Errorf("failed to read peers: %w", err) + return coreerr.E("PeerRegistry.load", "failed to read peers", err) } var peers []*Peer - if err := json.Unmarshal(data, &peers); err != nil { - return fmt.Errorf("failed to unmarshal peers: %w", err) + if err := json.Unmarshal([]byte(content), &peers); err != nil { + return coreerr.E("PeerRegistry.load", "failed to unmarshal peers", err) } r.peers = make(map[string]*Peer) diff --git a/node/protocol.go b/node/protocol.go index f75dce7..203e651 100644 --- a/node/protocol.go +++ b/node/protocol.go @@ -1,8 +1,9 @@ package node import ( - "errors" "fmt" + + coreerr "forge.lthn.ai/core/go-log" ) // ProtocolError represents an error from the remote peer. @@ -25,7 +26,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 errors.New("nil response") + return coreerr.E("ResponseHandler.ValidateResponse", "nil response", nil) } // Check for error response @@ -39,7 +40,7 @@ func (h *ResponseHandler) ValidateResponse(resp *Message, expectedType MessageTy // Check expected type if resp.Type != expectedType { - return fmt.Errorf("unexpected response type: expected %s, got %s", expectedType, resp.Type) + return coreerr.E("ResponseHandler.ValidateResponse", "unexpected response type: expected "+string(expectedType)+", got "+string(resp.Type), nil) } return nil @@ -54,7 +55,7 @@ func (h *ResponseHandler) ParseResponse(resp *Message, expectedType MessageType, if target != nil { if err := resp.ParsePayload(target); err != nil { - return fmt.Errorf("failed to parse %s payload: %w", expectedType, err) + return coreerr.E("ResponseHandler.ParseResponse", "failed to parse "+string(expectedType)+" payload", err) } } diff --git a/node/transport.go b/node/transport.go index 1bd9e7f..3f79aef 100644 --- a/node/transport.go +++ b/node/transport.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" - "errors" "fmt" "iter" "maps" @@ -16,6 +15,8 @@ import ( "sync/atomic" "time" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/Snider/Borg/pkg/smsg" "forge.lthn.ai/core/go-p2p/logging" "github.com/gorilla/websocket" @@ -289,7 +290,7 @@ func (t *Transport) Stop() error { defer cancel() if err := t.server.Shutdown(ctx); err != nil { - return fmt.Errorf("server shutdown error: %w", err) + return coreerr.E("Transport.Stop", "server shutdown error", err) } } @@ -320,7 +321,7 @@ func (t *Transport) Connect(peer *Peer) (*PeerConnection, error) { } conn, _, err := dialer.Dial(u.String(), nil) if err != nil { - return nil, fmt.Errorf("failed to connect to peer: %w", err) + return nil, coreerr.E("Transport.Connect", "failed to connect to peer", err) } pc := &PeerConnection{ @@ -335,7 +336,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, fmt.Errorf("handshake failed: %w", err) + return nil, coreerr.E("Transport.Connect", "handshake failed", err) } // Store connection using the real peer ID from handshake @@ -368,7 +369,7 @@ func (t *Transport) Send(peerID string, msg *Message) error { t.mu.RUnlock() if !exists { - return fmt.Errorf("peer %s not connected", peerID) + return coreerr.E("Transport.Send", "peer "+peerID+" not connected", nil) } return pc.Send(msg) @@ -628,7 +629,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 fmt.Errorf("generate challenge: %w", err) + return coreerr.E("Transport.performHandshake", "generate challenge", err) } payload := HandshakePayload{ @@ -639,41 +640,41 @@ func (t *Transport) performHandshake(pc *PeerConnection) error { msg, err := NewMessage(MsgHandshake, identity.ID, pc.Peer.ID, payload) if err != nil { - return fmt.Errorf("create handshake message: %w", err) + return coreerr.E("Transport.performHandshake", "create handshake message", err) } // First message is unencrypted (peer needs our public key) data, err := MarshalJSON(msg) if err != nil { - return fmt.Errorf("marshal handshake message: %w", err) + return coreerr.E("Transport.performHandshake", "marshal handshake message", err) } if err := pc.Conn.WriteMessage(websocket.TextMessage, data); err != nil { - return fmt.Errorf("send handshake: %w", err) + return coreerr.E("Transport.performHandshake", "send handshake", err) } // Wait for ack _, ackData, err := pc.Conn.ReadMessage() if err != nil { - return fmt.Errorf("read handshake ack: %w", err) + return coreerr.E("Transport.performHandshake", "read handshake ack", err) } var ackMsg Message if err := json.Unmarshal(ackData, &ackMsg); err != nil { - return fmt.Errorf("unmarshal handshake ack: %w", err) + return coreerr.E("Transport.performHandshake", "unmarshal handshake ack", err) } if ackMsg.Type != MsgHandshakeAck { - return fmt.Errorf("expected handshake_ack, got %s", ackMsg.Type) + return coreerr.E("Transport.performHandshake", "expected handshake_ack, got "+string(ackMsg.Type), nil) } var ackPayload HandshakeAckPayload if err := ackMsg.ParsePayload(&ackPayload); err != nil { - return fmt.Errorf("parse handshake ack payload: %w", err) + return coreerr.E("Transport.performHandshake", "parse handshake ack payload", err) } if !ackPayload.Accepted { - return fmt.Errorf("handshake rejected: %s", ackPayload.Reason) + return coreerr.E("Transport.performHandshake", "handshake rejected: "+ackPayload.Reason, nil) } // Update peer with the received identity info @@ -685,15 +686,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 fmt.Errorf("derive shared secret for challenge verification: %w", err) + return coreerr.E("Transport.performHandshake", "derive shared secret for challenge verification", err) } // Verify the server's response to our challenge if len(ackPayload.ChallengeResponse) == 0 { - return errors.New("server did not provide challenge response") + return coreerr.E("Transport.performHandshake", "server did not provide challenge response", nil) } if !VerifyChallenge(challenge, ackPayload.ChallengeResponse, sharedSecret) { - return errors.New("challenge response verification failed: server may not have matching private key") + return coreerr.E("Transport.performHandshake", "challenge response verification failed: server may not have matching private key", nil) } // Store the shared secret for later use @@ -840,7 +841,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 fmt.Errorf("failed to set write deadline: %w", err) + return coreerr.E("PeerConnection.Send", "failed to set write deadline", err) } defer pc.Conn.SetWriteDeadline(time.Time{}) // Reset deadline after send diff --git a/node/worker.go b/node/worker.go index 06da7b6..ffbb874 100644 --- a/node/worker.go +++ b/node/worker.go @@ -3,11 +3,11 @@ package node import ( "encoding/base64" "encoding/json" - "errors" - "fmt" "path/filepath" "time" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-p2p/logging" "github.com/adrg/xdg" ) @@ -119,7 +119,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, fmt.Errorf("invalid ping payload: %w", err) + return nil, coreerr.E("Worker.handlePing", "invalid ping payload", err) } pong := PongPayload{ @@ -202,12 +202,12 @@ func (w *Worker) handleStartMiner(msg *Message) (*Message, error) { var payload StartMinerPayload if err := msg.ParsePayload(&payload); err != nil { - return nil, fmt.Errorf("invalid start miner payload: %w", err) + return nil, coreerr.E("Worker.handleStartMiner", "invalid start miner payload", err) } // Validate miner type is provided if payload.MinerType == "" { - return nil, errors.New("miner type is required") + return nil, coreerr.E("Worker.handleStartMiner", "miner type is required", nil) } // Get the config from the profile or use the override @@ -217,11 +217,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, fmt.Errorf("profile not found: %s", payload.ProfileID) + return nil, coreerr.E("Worker.handleStartMiner", "profile not found: "+payload.ProfileID, nil) } config = profile } else { - return nil, errors.New("no config provided and no profile manager configured") + return nil, coreerr.E("Worker.handleStartMiner", "no config provided and no profile manager configured", nil) } // Start the miner @@ -249,7 +249,7 @@ func (w *Worker) handleStopMiner(msg *Message) (*Message, error) { var payload StopMinerPayload if err := msg.ParsePayload(&payload); err != nil { - return nil, fmt.Errorf("invalid stop miner payload: %w", err) + return nil, coreerr.E("Worker.handleStopMiner", "invalid stop miner payload", err) } err := w.minerManager.StopMiner(payload.MinerName) @@ -272,7 +272,7 @@ func (w *Worker) handleGetLogs(msg *Message) (*Message, error) { var payload GetLogsPayload if err := msg.ParsePayload(&payload); err != nil { - return nil, fmt.Errorf("invalid get logs payload: %w", err) + return nil, coreerr.E("Worker.handleGetLogs", "invalid get logs payload", err) } // Validate and limit the Lines parameter to prevent resource exhaustion @@ -283,7 +283,7 @@ func (w *Worker) handleGetLogs(msg *Message) (*Message, error) { miner, err := w.minerManager.GetMiner(payload.MinerName) if err != nil { - return nil, fmt.Errorf("miner not found: %s", payload.MinerName) + return nil, coreerr.E("Worker.handleGetLogs", "miner not found: "+payload.MinerName, nil) } lines := miner.GetConsoleHistory(payload.Lines) @@ -301,7 +301,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, fmt.Errorf("invalid deploy payload: %w", err) + return nil, coreerr.E("Worker.handleDeploy", "invalid deploy payload", err) } // Reconstruct Bundle object from payload @@ -321,19 +321,19 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err switch bundle.Type { case BundleProfile: if w.profileManager == nil { - return nil, errors.New("profile manager not configured") + return nil, coreerr.E("Worker.handleDeploy", "profile manager not configured", nil) } // Decrypt and extract profile data profileData, err := ExtractProfileBundle(bundle, password) if err != nil { - return nil, fmt.Errorf("failed to extract profile bundle: %w", err) + return nil, coreerr.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, fmt.Errorf("invalid profile data JSON: %w", err) + return nil, coreerr.E("Worker.handleDeploy", "invalid profile data JSON", err) } if err := w.profileManager.SaveProfile(profile); err != nil { @@ -366,7 +366,7 @@ 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, fmt.Errorf("failed to extract miner bundle: %w", err) + return nil, coreerr.E("Worker.handleDeploy", "failed to extract miner bundle", err) } // If the bundle contained a profile config, save it @@ -396,7 +396,7 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err return msg.Reply(MsgDeployAck, ack) default: - return nil, fmt.Errorf("unknown bundle type: %s", payload.BundleType) + return nil, coreerr.E("Worker.handleDeploy", "unknown bundle type: "+payload.BundleType, nil) } } diff --git a/ueps/packet.go b/ueps/packet.go index c8a481e..2f5c101 100644 --- a/ueps/packet.go +++ b/ueps/packet.go @@ -5,8 +5,9 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/binary" - "errors" "io" + + coreerr "forge.lthn.ai/core/go-log" ) // TLV Types @@ -104,7 +105,7 @@ 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 errors.New("TLV value too large for 2-byte length header") + return coreerr.E("ueps.writeTLV", "TLV value too large for 2-byte length header", nil) } if _, err := w.Write([]byte{tag}); err != nil { diff --git a/ueps/reader.go b/ueps/reader.go index e6c04fa..245f216 100644 --- a/ueps/reader.go +++ b/ueps/reader.go @@ -6,8 +6,9 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/binary" - "errors" "io" + + coreerr "forge.lthn.ai/core/go-log" ) // ParsedPacket holds the verified data @@ -92,7 +93,7 @@ func ReadAndVerify(r *bufio.Reader, sharedSecret []byte) (*ParsedPacket, error) verify: if len(signature) == 0 { - return nil, errors.New("UEPS packet missing HMAC signature") + return nil, coreerr.E("ueps.ReadAndVerify", "UEPS packet missing HMAC signature", nil) } // 5. Verify HMAC @@ -103,7 +104,7 @@ verify: expectedMAC := mac.Sum(nil) if !hmac.Equal(signature, expectedMAC) { - return nil, errors.New("integrity violation: HMAC mismatch (ThreatScore +100)") + return nil, coreerr.E("ueps.ReadAndVerify", "integrity violation: HMAC mismatch (ThreatScore +100)", nil) } return &ParsedPacket{