From 330a344845aa8653772a9be6c085f4fdb602a028 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 11 Mar 2026 19:21:55 +0900 Subject: [PATCH] hst: improve doc comments These now read a lot better both in source and on pkgsite. Signed-off-by: Ophestra --- hst/config.go | 122 ++++++++++++++++++++++++++++------------------ hst/container.go | 51 ++++++++++++------- hst/dbus.go | 46 ++++++++--------- hst/enablement.go | 8 +-- hst/fs.go | 15 ++++-- hst/fslink.go | 4 +- hst/fsoverlay.go | 6 ++- hst/hst.go | 60 ++++++++++++++++++----- hst/instance.go | 15 ++++-- 9 files changed, 212 insertions(+), 115 deletions(-) diff --git a/hst/config.go b/hst/config.go index 0c66c27..51ac62e 100644 --- a/hst/config.go +++ b/hst/config.go @@ -8,74 +8,97 @@ import ( "hakurei.app/container/check" ) -// Config configures an application container, implemented in internal/app. +// Config configures an application container. type Config struct { // Reverse-DNS style configured arbitrary identifier string. - // Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy. + // + // This value is passed as is to Wayland security-context-v1 and used as + // part of defaults in D-Bus session proxy. The zero value causes a default + // value to be derived from the container instance. ID string `json:"id,omitempty"` // System services to make available in the container. Enablements *Enablements `json:"enablements,omitempty"` // Session D-Bus proxy configuration. - // If set to nil, session bus proxy assume built-in defaults. + // + // Has no effect if [EDBus] but is not set in Enablements. The zero value + // assumes built-in defaults derived from ID. SessionBus *BusConfig `json:"session_bus,omitempty"` // System D-Bus proxy configuration. - // If set to nil, system bus proxy is disabled. + // + // Has no effect if [EDBus] but is not set in Enablements. The zero value + // disables system bus proxy. SystemBus *BusConfig `json:"system_bus,omitempty"` - // Direct access to wayland socket, no attempt is made to attach security-context-v1 - // and the bare socket is made available to the container. + // Direct access to Wayland socket, no attempt is made to attach + // security-context-v1 and the bare socket is made available to the + // container. // - // This option is unsupported and most likely enables full control over the Wayland - // session. Do not set this to true unless you are sure you know what you are doing. + // This option is unsupported and will most likely enable full control over + // the Wayland session from within the container. Do not set this to true + // unless you are sure you know what you are doing. DirectWayland bool `json:"direct_wayland,omitempty"` - // Direct access to the PipeWire socket established via SecurityContext::Create, no - // attempt is made to start the pipewire-pulse server. + + // Direct access to the PipeWire socket established via SecurityContext::Create, + // no attempt is made to start the pipewire-pulse server. // - // The SecurityContext machinery is fatally flawed, it blindly sets read and execute - // bits on all objects for clients with the lowest achievable privilege level (by - // setting PW_KEY_ACCESS to "restricted"). This enables them to call any method - // targeting any object, and since Registry::Destroy checks for the read and execute bit, - // allows the destruction of any object other than PW_ID_CORE as well. This behaviour - // is implemented separately in media-session and wireplumber, with the wireplumber - // implementation in Lua via an embedded Lua vm. In all known setups, wireplumber is - // in use, and there is no known way to change its behaviour and set permissions - // differently without replacing the Lua script. Also, since PipeWire relies on these - // permissions to work, reducing them is not possible. + // The SecurityContext machinery is fatally flawed, it unconditionally sets + // read and execute bits on all objects for clients with the lowest achievable + // privilege level (by setting PW_KEY_ACCESS to "restricted" or by satisfying + // all conditions of [the /.flatpak-info hack]). This enables them to call + // any method targeting any object, and since Registry::Destroy checks for + // the read and execute bit, allows the destruction of any object other than + // PW_ID_CORE as well. // - // Currently, the only other sandboxed use case is flatpak, which is not aware of - // PipeWire and blindly exposes the bare PulseAudio socket to the container (behaves - // like DirectPulse). This socket is backed by the pipewire-pulse compatibility daemon, - // which obtains client pid via the SO_PEERCRED option. The PipeWire daemon, pipewire-pulse - // daemon and the session manager daemon then separately performs the /.flatpak-info hack - // described in https://git.gensokyo.uk/security/hakurei/issues/21. Under such use case, - // since the client has no direct access to PipeWire, insecure parts of the protocol are - // obscured by pipewire-pulse simply not implementing them, and thus hiding the flaws - // described above. + // This behaviour is implemented separately in media-session and wireplumber, + // with the wireplumber implementation in Lua via an embedded Lua vm. In all + // known setups, wireplumber is in use, and in that case, no option for + // configuring this behaviour exists, without replacing the Lua script. + // Also, since PipeWire relies on these permissions to work, reducing them + // was never possible in the first place. // - // Hakurei does not rely on the /.flatpak-info hack. Instead, a socket is sets up via - // SecurityContext. A pipewire-pulse server connected through it achieves the same - // permissions as flatpak does via the /.flatpak-info hack and is maintained for the - // life of the container. + // Currently, the only other sandboxed use case is flatpak, which is not + // aware of PipeWire and blindly exposes the bare PulseAudio socket to the + // container (behaves like DirectPulse). This socket is backed by the + // pipewire-pulse compatibility daemon, which obtains client pid via the + // SO_PEERCRED option. The PipeWire daemon, pipewire-pulse daemon and the + // session manager daemon then separately performs [the /.flatpak-info hack]. + // Under such use case, since the client has no direct access to PipeWire, + // insecure parts of the protocol are obscured by the absence of an + // equivalent API in PulseAudio, or pipewire-pulse simply not implementing + // them. + // + // Hakurei does not rely on [the /.flatpak-info hack]. Instead, a socket is + // sets up via SecurityContext. A pipewire-pulse server connected through it + // achieves the same permissions as flatpak does via [the /.flatpak-info hack] + // and is maintained for the life of the container. + // + // This option is unsupported and enables a denial-of-service attack as the + // sandboxed client is able to destroy any client object and thus + // disconnecting them from PipeWire, or destroy the SecurityContext object, + // preventing any further container creation. // - // This option is unsupported and enables a denial-of-service attack as the sandboxed - // client is able to destroy any client object and thus disconnecting them from PipeWire, - // or destroy the SecurityContext object preventing any further container creation. // Do not set this to true, it is insecure under any configuration. - DirectPipeWire bool `json:"direct_pipewire,omitempty"` - // Direct access to PulseAudio socket, no attempt is made to establish pipewire-pulse - // server via a PipeWire socket with a SecurityContext attached and the bare socket - // is made available to the container. // - // This option is unsupported and enables arbitrary code execution as the PulseAudio - // server. Do not set this to true, it is insecure under any configuration. + // [the /.flatpak-info hack]: https://git.gensokyo.uk/security/hakurei/issues/21 + DirectPipeWire bool `json:"direct_pipewire,omitempty"` + + // Direct access to PulseAudio socket, no attempt is made to establish + // pipewire-pulse server via a PipeWire socket with a SecurityContext + // attached, and the bare socket is made available to the container. + // + // This option is unsupported and enables arbitrary code execution as the + // PulseAudio server. + // + // Do not set this to true, it is insecure under any configuration. DirectPulse bool `json:"direct_pulse,omitempty"` // Extra acl updates to perform before setuid. ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"` - // Numerical application id, passed to hsu, used to derive init user namespace credentials. + // Numerical application id, passed to hsu, used to derive init user + // namespace credentials. Identity int `json:"identity"` // Init user namespace supplementary groups inherited by all container processes. Groups []string `json:"groups"` @@ -85,17 +108,20 @@ type Config struct { } var ( - // ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any - // field that must not be null. + // ErrConfigNull is returned by [Config.Validate] for an invalid configuration + // that contains a null value for any field that must not be null. ErrConfigNull = errors.New("unexpected null in config") - // ErrIdentityBounds is returned by [Config.Validate] for an out of bounds [Config.Identity] value. + // ErrIdentityBounds is returned by [Config.Validate] for an out of bounds + // [Config.Identity] value. ErrIdentityBounds = errors.New("identity out of bounds") - // ErrEnviron is returned by [Config.Validate] if an environment variable name contains '=' or NUL. + // ErrEnviron is returned by [Config.Validate] if an environment variable + // name contains '=' or NUL. ErrEnviron = errors.New("invalid environment variable name") - // ErrInsecure is returned by [Config.Validate] if the configuration is considered insecure. + // ErrInsecure is returned by [Config.Validate] if the configuration is + // considered insecure. ErrInsecure = errors.New("configuration is insecure") ) diff --git a/hst/container.go b/hst/container.go index f9cc95e..8696d85 100644 --- a/hst/container.go +++ b/hst/container.go @@ -16,18 +16,20 @@ const PrivateTmp = "/.hakurei" var AbsPrivateTmp = check.MustAbs(PrivateTmp) const ( - // WaitDelayDefault is used when WaitDelay has its zero value. + // WaitDelayDefault is used when WaitDelay has the zero value. WaitDelayDefault = 5 * time.Second - // WaitDelayMax is used if WaitDelay exceeds its value. + // WaitDelayMax is used when WaitDelay exceeds its value. WaitDelayMax = 30 * time.Second ) const ( // ExitFailure is returned if the container fails to start. ExitFailure = iota + 1 - // ExitCancel is returned if the container is terminated by a shim-directed signal which cancels its context. + // ExitCancel is returned if the container is terminated by a shim-directed + // signal which cancels its context. ExitCancel - // ExitOrphan is returned when the shim is orphaned before priv side delivers a signal. + // ExitOrphan is returned when the shim is orphaned before priv side process + // delivers a signal. ExitOrphan // ExitRequest is returned when the priv side process requests shim exit. @@ -38,10 +40,12 @@ const ( type Flags uintptr const ( - // FMultiarch unblocks syscalls required for multiarch to work on applicable targets. + // FMultiarch unblocks system calls required for multiarch to work on + // multiarch-enabled targets (amd64, arm64). FMultiarch Flags = 1 << iota - // FSeccompCompat changes emitted seccomp filter programs to be identical to that of Flatpak. + // FSeccompCompat changes emitted seccomp filter programs to be identical to + // that of Flatpak in enabled rulesets. FSeccompCompat // FDevel unblocks ptrace and friends. FDevel @@ -54,12 +58,15 @@ const ( // FTty unblocks dangerous terminal I/O (faking input). FTty - // FMapRealUID maps the target user uid to the privileged user uid in the container user namespace. - // Some programs fail to connect to dbus session running as a different uid, - // this option works around it by mapping priv-side caller uid in container. + // FMapRealUID maps the target user uid to the privileged user uid in the + // container user namespace. + // + // Some programs fail to connect to dbus session running as a different uid, + // this option works around it by mapping priv-side caller uid in container. FMapRealUID - // FDevice mount /dev/ from the init mount namespace as-is in the container mount namespace. + // FDevice mount /dev/ from the init mount namespace as is in the container + // mount namespace. FDevice // FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity. @@ -112,30 +119,37 @@ func (flags Flags) String() string { } } -// ContainerConfig describes the container configuration to be applied to an underlying [container]. +// ContainerConfig describes the container configuration to be applied to an +// underlying [container]. It is validated by [Config.Validate]. type ContainerConfig struct { // Container UTS namespace hostname. Hostname string `json:"hostname,omitempty"` // Duration in nanoseconds to wait for after interrupting the initial process. - // Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than [WaitDelayMax]. - // Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault]. + // + // Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than + // [WaitDelayMax]. Values lesser than zero is equivalent to zero, bypassing + // [WaitDelayDefault]. WaitDelay time.Duration `json:"wait_delay,omitempty"` // Initial process environment variables. Env map[string]string `json:"env"` - /* Container mount points. - - If the first element targets /, it is inserted early and excluded from path hiding. */ + // Container mount points. + // + // If the first element targets /, it is inserted early and excluded from + // path hiding. Otherwise, an anonymous instance of tmpfs is set up on /. Filesystem []FilesystemConfigJSON `json:"filesystem"` - // String used as the username of the emulated user, validated against the default NAME_REGEX from adduser. + // String used as the username of the emulated user, validated against the + // default NAME_REGEX from adduser. + // // Defaults to passwd name of target uid or chronos. Username string `json:"username,omitempty"` // Pathname of shell in the container filesystem to use for the emulated user. Shell *check.Absolute `json:"shell"` - // Directory in the container filesystem to enter and use as the home directory of the emulated user. + // Directory in the container filesystem to enter and use as the home + // directory of the emulated user. Home *check.Absolute `json:"home"` // Pathname to executable file in the container filesystem. @@ -148,6 +162,7 @@ type ContainerConfig struct { } // ContainerConfigF is [ContainerConfig] stripped of its methods. +// // The [ContainerConfig.Flags] field does not survive a [json] round trip. type ContainerConfigF ContainerConfig diff --git a/hst/dbus.go b/hst/dbus.go index 6dd1a2c..5c4620c 100644 --- a/hst/dbus.go +++ b/hst/dbus.go @@ -5,8 +5,26 @@ import ( "strings" ) -// BadInterfaceError is returned when Interface fails an undocumented check in xdg-dbus-proxy, -// which would have cause a silent failure. +// BadInterfaceError is returned when Interface fails an undocumented check in +// xdg-dbus-proxy, which would have cause a silent failure. +// +// xdg-dbus-proxy fails without output when this condition is not met: +// +// char *dot = strrchr (filter->interface, '.'); +// if (dot != NULL) +// { +// *dot = 0; +// if (strcmp (dot + 1, "*") != 0) +// filter->member = g_strdup (dot + 1); +// } +// +// trim ".*" since they are removed before searching for '.': +// +// if (g_str_has_suffix (name, ".*")) +// { +// name[strlen (name) - 2] = 0; +// wildcard = TRUE; +// } type BadInterfaceError struct { // Interface is the offending interface string. Interface string @@ -19,7 +37,8 @@ func (e *BadInterfaceError) Error() string { if e == nil { return "" } - return "bad interface string " + strconv.Quote(e.Interface) + " in " + e.Segment + " bus configuration" + return "bad interface string " + strconv.Quote(e.Interface) + + " in " + e.Segment + " bus configuration" } // BusConfig configures the xdg-dbus-proxy process. @@ -76,31 +95,14 @@ func (c *BusConfig) Interfaces(yield func(string) bool) { } } -// CheckInterfaces checks for invalid interface strings based on an undocumented check in xdg-dbus-error, -// returning [BadInterfaceError] if one is encountered. +// CheckInterfaces checks for invalid interface strings based on an undocumented +// check in xdg-dbus-error, returning [BadInterfaceError] if one is encountered. func (c *BusConfig) CheckInterfaces(segment string) error { if c == nil { return nil } for iface := range c.Interfaces { - /* - xdg-dbus-proxy fails without output when this condition is not met: - char *dot = strrchr (filter->interface, '.'); - if (dot != NULL) - { - *dot = 0; - if (strcmp (dot + 1, "*") != 0) - filter->member = g_strdup (dot + 1); - } - - trim ".*" since they are removed before searching for '.': - if (g_str_has_suffix (name, ".*")) - { - name[strlen (name) - 2] = 0; - wildcard = TRUE; - } - */ if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 { return &BadInterfaceError{iface, segment} } diff --git a/hst/enablement.go b/hst/enablement.go index 9024078..71ddeca 100644 --- a/hst/enablement.go +++ b/hst/enablement.go @@ -11,15 +11,17 @@ import ( type Enablement byte const ( - // EWayland exposes a wayland pathname socket via security-context-v1. + // EWayland exposes a Wayland pathname socket via security-context-v1. EWayland Enablement = 1 << iota - // EX11 adds the target user via X11 ChangeHosts and exposes the X11 pathname socket. + // EX11 adds the target user via X11 ChangeHosts and exposes the X11 + // pathname socket. EX11 // EDBus enables the per-container xdg-dbus-proxy daemon. EDBus // EPipeWire exposes a pipewire pathname socket via SecurityContext. EPipeWire - // EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the PulseAudio socket. + // EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the + // PulseAudio socket. EPulse // EM is a noop. diff --git a/hst/fs.go b/hst/fs.go index 3cecac8..3f0f601 100644 --- a/hst/fs.go +++ b/hst/fs.go @@ -24,7 +24,8 @@ type FilesystemConfig interface { fmt.Stringer } -// The Ops interface enables [FilesystemConfig] to queue container ops without depending on the container package. +// The Ops interface enables [FilesystemConfig] to queue container ops without +// depending on the container package. type Ops interface { // Tmpfs appends an op that mounts tmpfs on a container path. Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops @@ -41,12 +42,15 @@ type Ops interface { // Link appends an op that creates a symlink in the container filesystem. Link(target *check.Absolute, linkName string, dereference bool) Ops - // Root appends an op that expands a directory into a toplevel bind mount mirror on container root. + // Root appends an op that expands a directory into a toplevel bind mount + // mirror on container root. Root(host *check.Absolute, flags int) Ops - // Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics. + // Etc appends an op that expands host /etc into a toplevel symlink mirror + // with /etc semantics. Etc(host *check.Absolute, prefix string) Ops - // Daemon appends an op that starts a daemon in the container and blocks until target appears. + // Daemon appends an op that starts a daemon in the container and blocks + // until target appears. Daemon(target, path *check.Absolute, args ...string) Ops } @@ -61,7 +65,8 @@ type ApplyState struct { // ErrFSNull is returned by [json] on encountering a null [FilesystemConfig] value. var ErrFSNull = errors.New("unexpected null in mount point") -// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry with invalid type. +// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry +// with invalid type. type FSTypeError string func (f FSTypeError) Error() string { return fmt.Sprintf("invalid filesystem type %q", string(f)) } diff --git a/hst/fslink.go b/hst/fslink.go index 2a1e379..57c1ffc 100644 --- a/hst/fslink.go +++ b/hst/fslink.go @@ -18,7 +18,9 @@ type FSLink struct { Target *check.Absolute `json:"dst"` // Arbitrary linkname value store in the symlink. Linkname string `json:"linkname"` - // Whether to treat Linkname as an absolute pathname and dereference before creating the link. + + // Whether to treat Linkname as an absolute pathname and dereference before + // creating the link. Dereference bool `json:"dereference,omitempty"` } diff --git a/hst/fsoverlay.go b/hst/fsoverlay.go index de4e14d..72df1de 100644 --- a/hst/fsoverlay.go +++ b/hst/fsoverlay.go @@ -19,9 +19,11 @@ type FSOverlay struct { // Any filesystem, does not need to be on a writable filesystem, must not be nil. Lower []*check.Absolute `json:"lower"` - // The upperdir is normally on a writable filesystem, leave as nil to mount Lower readonly. + // The upperdir is normally on a writable filesystem, leave as nil to mount + // Lower readonly. Upper *check.Absolute `json:"upper,omitempty"` - // The workdir needs to be an empty directory on the same filesystem as Upper, must not be nil if Upper is populated. + // The workdir needs to be an empty directory on the same filesystem as + // Upper, must not be nil if Upper is populated. Work *check.Absolute `json:"work,omitempty"` } diff --git a/hst/hst.go b/hst/hst.go index 439f4b2..45f1eb8 100644 --- a/hst/hst.go +++ b/hst/hst.go @@ -44,11 +44,13 @@ func (e *AppError) Message() string { type Paths struct { // Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp]. TempDir *check.Absolute `json:"temp_dir"` - // Shared directory specific to the hsu userid, usually (`/tmp/hakurei.%d`, [Info.User]). + // Shared directory specific to the hsu userid, usually + // (`/tmp/hakurei.%d`, [Info.User]). SharePath *check.Absolute `json:"share_path"` // Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid). RuntimePath *check.Absolute `json:"runtime_path"` - // Shared directory specific to the hsu userid located in RuntimePath, usually (`/run/user/%d/hakurei`, uid). + // Shared directory specific to the hsu userid located in RuntimePath, + // usually (`/run/user/%d/hakurei`, uid). RunDirPath *check.Absolute `json:"run_dir_path"` } @@ -74,10 +76,23 @@ func Template() *Config { SessionBus: &BusConfig{ See: nil, - Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver", - "org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"}, - Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*", - "org.mpris.MediaPlayer2.chromium.*"}, + Talk: []string{ + "org.freedesktop.Notifications", + "org.freedesktop.FileManager1", + "org.freedesktop.ScreenSaver", + "org.freedesktop.secrets", + + "org.kde.kwalletd5", + "org.kde.kwalletd6", + + "org.gnome.SessionManager", + }, + Own: []string{ + "org.chromium.Chromium.*", + + "org.mpris.MediaPlayer2.org.chromium.Chromium.*", + "org.mpris.MediaPlayer2.chromium.*", + }, Call: map[string]string{"org.freedesktop.portal.*": "*"}, Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, Log: false, @@ -112,7 +127,12 @@ func Template() *Config { "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT", }, Filesystem: []FilesystemConfigJSON{ - {&FSBind{Target: fhs.AbsRoot, Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"), Write: true, Special: true}}, + {&FSBind{ + Target: fhs.AbsRoot, + Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"), + Write: true, + Special: true, + }}, {&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}}, {&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}}, {&FSOverlay{ @@ -121,11 +141,27 @@ func Template() *Config { Upper: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"), Work: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"), }}, - {&FSLink{Target: fhs.AbsRun.Append("current-system"), Linkname: "/run/current-system", Dereference: true}}, - {&FSLink{Target: fhs.AbsRun.Append("opengl-driver"), Linkname: "/run/opengl-driver", Dereference: true}}, - {&FSBind{Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"), - Target: check.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}}, - {&FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}}, + {&FSLink{ + Target: fhs.AbsRun.Append("current-system"), + Linkname: "/run/current-system", + Dereference: true, + }}, + {&FSLink{ + Target: fhs.AbsRun.Append("opengl-driver"), + Linkname: "/run/opengl-driver", + Dereference: true, + }}, + {&FSBind{ + Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"), + Target: check.MustAbs("/data/data/org.chromium.Chromium"), + Write: true, + Ensure: true, + }}, + {&FSBind{ + Source: fhs.AbsDev.Append("dri"), + Device: true, + Optional: true, + }}, }, Username: "chronos", diff --git a/hst/instance.go b/hst/instance.go index b59a768..282472a 100644 --- a/hst/instance.go +++ b/hst/instance.go @@ -12,10 +12,12 @@ import ( // An ID is a unique identifier held by a running hakurei container. type ID [16]byte -// ErrIdentifierLength is returned when encountering a [hex] representation of [ID] with unexpected length. +// ErrIdentifierLength is returned when encountering a [hex] representation of +// [ID] with unexpected length. var ErrIdentifierLength = errors.New("identifier string has unexpected length") -// IdentifierDecodeError is returned by [ID.UnmarshalText] to provide relevant error descriptions. +// IdentifierDecodeError is returned by [ID.UnmarshalText] to provide relevant +// error descriptions. type IdentifierDecodeError struct{ Err error } func (e IdentifierDecodeError) Unwrap() error { return e.Err } @@ -23,7 +25,10 @@ func (e IdentifierDecodeError) Error() string { var invalidByteError hex.InvalidByteError switch { case errors.As(e.Err, &invalidByteError): - return fmt.Sprintf("got invalid byte %#U in identifier", rune(invalidByteError)) + return fmt.Sprintf( + "got invalid byte %#U in identifier", + rune(invalidByteError), + ) case errors.Is(e.Err, hex.ErrLength): return "odd length identifier hex string" @@ -41,7 +46,9 @@ func (a *ID) CreationTime() time.Time { } // NewInstanceID creates a new unique [ID]. -func NewInstanceID(id *ID) error { return newInstanceID(id, uint64(time.Now().UnixNano())) } +func NewInstanceID(id *ID) error { + return newInstanceID(id, uint64(time.Now().UnixNano())) +} // newInstanceID creates a new unique [ID] with the specified timestamp. func newInstanceID(id *ID, p uint64) error {