safing-portmaster/spn/terminal/errors.go
Daniel Hååvi 80664d1a27
Restructure modules ()
* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
2024-08-09 18:15:48 +03:00

221 lines
5.8 KiB
Go

package terminal
import (
"context"
"errors"
"fmt"
"github.com/safing/structures/varint"
)
// Error is a terminal error.
type Error struct {
// id holds the internal error ID.
id uint8
// external signifies if the error was received from the outside.
external bool
// err holds the wrapped error or the default error message.
err error
}
// ID returns the internal ID of the error.
func (e *Error) ID() uint8 {
return e.id
}
// Error returns the human readable format of the error.
func (e *Error) Error() string {
if e.external {
return "[ext] " + e.err.Error()
}
return e.err.Error()
}
// IsExternal returns whether the error occurred externally.
func (e *Error) IsExternal() bool {
if e == nil {
return false
}
return e.external
}
// Is returns whether the given error is of the same type.
func (e *Error) Is(target error) bool {
if e == nil || target == nil {
return false
}
t, ok := target.(*Error) //nolint:errorlint // Error implementation, not usage.
if !ok {
return false
}
return e.id == t.id
}
// Unwrap returns the wrapped error.
func (e *Error) Unwrap() error {
if e == nil || e.err == nil {
return nil
}
return e.err
}
// With adds context and details where the error occurred. The provided
// message is appended to the error.
// A new error with the same ID is returned and must be compared with
// errors.Is().
func (e *Error) With(format string, a ...interface{}) *Error {
// Return nil if error is nil.
if e == nil {
return nil
}
return &Error{
id: e.id,
err: fmt.Errorf(e.Error()+": "+format, a...),
}
}
// Wrap adds context higher up in the call chain. The provided message is
// prepended to the error.
// A new error with the same ID is returned and must be compared with
// errors.Is().
func (e *Error) Wrap(format string, a ...interface{}) *Error {
// Return nil if error is nil.
if e == nil {
return nil
}
return &Error{
id: e.id,
err: fmt.Errorf(format+": "+e.Error(), a...),
}
}
// AsExternal creates and returns an external version of the error.
func (e *Error) AsExternal() *Error {
// Return nil if error is nil.
if e == nil {
return nil
}
return &Error{
id: e.id,
err: e.err,
external: true,
}
}
// Pack returns the serialized internal error ID. The additional message is
// lost and is replaced with the default message upon parsing.
func (e *Error) Pack() []byte {
// Return nil slice if error is nil.
if e == nil {
return nil
}
return varint.Pack8(e.id)
}
// ParseExternalError parses an external error.
func ParseExternalError(id []byte) (*Error, error) {
// Return nil for an empty error.
if len(id) == 0 {
return ErrStopping.AsExternal(), nil
}
parsedID, _, err := varint.Unpack8(id)
if err != nil {
return nil, fmt.Errorf("failed to unpack error ID: %w", err)
}
return NewExternalError(parsedID), nil
}
// NewExternalError creates an external error based on the given ID.
func NewExternalError(id uint8) *Error {
err, ok := errorRegistry[id]
if ok {
return err.AsExternal()
}
return ErrUnknownError.AsExternal()
}
var errorRegistry = make(map[uint8]*Error)
func registerError(id uint8, err error) *Error {
// Check for duplicate.
_, ok := errorRegistry[id]
if ok {
panic(fmt.Sprintf("error with id %d already registered", id))
}
newErr := &Error{
id: id,
err: err,
}
errorRegistry[id] = newErr
return newErr
}
// func (e *Error) IsSpecial() bool {
// if e == nil {
// return false
// }
// return e.id > 0 && e.id < 8
// }
// IsOK returns if the error represents a "OK" or success status.
func (e *Error) IsOK() bool {
return !e.IsError()
}
// IsError returns if the error represents an erronous condition.
func (e *Error) IsError() bool {
if e == nil || e.err == nil {
return false
}
if e.id == 0 || e.id >= 8 {
return true
}
return false
}
// Terminal Errors.
var (
// ErrUnknownError is the default error.
ErrUnknownError = registerError(0, errors.New("unknown error"))
// Error IDs 1-7 are reserved for special "OK" values.
ErrStopping = registerError(2, errors.New("stopping"))
ErrExplicitAck = registerError(3, errors.New("explicit ack"))
ErrNoActivity = registerError(4, errors.New("no activity"))
// Errors IDs 8 and up are for regular errors.
ErrInternalError = registerError(8, errors.New("internal error"))
ErrMalformedData = registerError(9, errors.New("malformed data"))
ErrUnexpectedMsgType = registerError(10, errors.New("unexpected message type"))
ErrUnknownOperationType = registerError(11, errors.New("unknown operation type"))
ErrUnknownOperationID = registerError(12, errors.New("unknown operation id"))
ErrPermissionDenied = registerError(13, errors.New("permission denied"))
ErrIntegrity = registerError(14, errors.New("integrity violated"))
ErrInvalidOptions = registerError(15, errors.New("invalid options"))
ErrHubNotReady = registerError(16, errors.New("hub not ready"))
ErrRateLimited = registerError(24, errors.New("rate limited"))
ErrIncorrectUsage = registerError(22, errors.New("incorrect usage"))
ErrTimeout = registerError(62, errors.New("timed out"))
ErrUnsupportedVersion = registerError(93, errors.New("unsupported version"))
ErrHubUnavailable = registerError(101, errors.New("hub unavailable"))
ErrAbandonedTerminal = registerError(102, errors.New("terminal is being abandoned"))
ErrShipSunk = registerError(108, errors.New("ship sunk"))
ErrDestinationUnavailable = registerError(113, errors.New("destination unavailable"))
ErrTryAgainLater = registerError(114, errors.New("try again later"))
ErrConnectionError = registerError(121, errors.New("connection error"))
ErrQueueOverflow = registerError(122, errors.New("queue overflowed"))
ErrCanceled = registerError(125, context.Canceled)
)