diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..825c32f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/cmd/dsfxctl/main.go b/cmd/dsfxctl/main.go index 25b0d83..98f7af7 100644 --- a/cmd/dsfxctl/main.go +++ b/cmd/dsfxctl/main.go @@ -3,9 +3,9 @@ package main import ( "context" - "koti.casa/numenor-labs/dsfx/cmd/dsfxctl/client" - "koti.casa/numenor-labs/dsfx/pkg/disk" - "koti.casa/numenor-labs/dsfx/pkg/system" + "koti.casa/numenor-labs/dsfx/internal/client/client" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/system" ) func main() { diff --git a/cmd/dsfxnode/main.go b/cmd/dsfxnode/main.go index 490b7d1..7715c82 100644 --- a/cmd/dsfxnode/main.go +++ b/cmd/dsfxnode/main.go @@ -4,10 +4,11 @@ import ( "context" "log/slog" - "koti.casa/numenor-labs/dsfx/cmd/dsfxnode/node" - "koti.casa/numenor-labs/dsfx/pkg/disk" - "koti.casa/numenor-labs/dsfx/pkg/storage/scoped" - "koti.casa/numenor-labs/dsfx/pkg/system" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/system" + "koti.casa/numenor-labs/dsfx/internal/peer/node" + + "koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped" ) func main() { diff --git a/cmd/dsfxctl/client/app.go b/internal/client/client/app.go similarity index 90% rename from cmd/dsfxctl/client/app.go rename to internal/client/client/app.go index 44d3728..1e3702b 100644 --- a/cmd/dsfxctl/client/app.go +++ b/internal/client/client/app.go @@ -8,13 +8,14 @@ import ( "log/slog" "net" - "koti.casa/numenor-labs/dsfx/cmd/dsfxctl/conf" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" - "koti.casa/numenor-labs/dsfx/pkg/disk" - "koti.casa/numenor-labs/dsfx/pkg/logging" - "koti.casa/numenor-labs/dsfx/pkg/network" - "koti.casa/numenor-labs/dsfx/pkg/storage/scoped" - "koti.casa/numenor-labs/dsfx/pkg/system" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/logging" + "koti.casa/numenor-labs/dsfx/internal/lib/network" + "koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped" + "koti.casa/numenor-labs/dsfx/internal/lib/system" + + "koti.casa/numenor-labs/dsfx/internal/client/conf" ) // Client represents the client application for dsfxctl. diff --git a/cmd/dsfxctl/conf/conf.go b/internal/client/conf/conf.go similarity index 91% rename from cmd/dsfxctl/conf/conf.go rename to internal/client/conf/conf.go index f95748b..e7eea74 100644 --- a/cmd/dsfxctl/conf/conf.go +++ b/internal/client/conf/conf.go @@ -1,6 +1,6 @@ package conf -import "koti.casa/numenor-labs/dsfx/pkg/system" +import "koti.casa/numenor-labs/dsfx/internal/lib/system" const ( // DefaultConfigDir is the default directory for the dsfxctl configuration. diff --git a/pkg/assert/assert.go b/internal/lib/assert/assert.go similarity index 100% rename from pkg/assert/assert.go rename to internal/lib/assert/assert.go diff --git a/pkg/buffer/lenprefixed.go b/internal/lib/buffer/lenprefixed.go similarity index 100% rename from pkg/buffer/lenprefixed.go rename to internal/lib/buffer/lenprefixed.go diff --git a/pkg/crypto/encryption/aead.go b/internal/lib/crypto/encryption/aead.go similarity index 100% rename from pkg/crypto/encryption/aead.go rename to internal/lib/crypto/encryption/aead.go diff --git a/pkg/crypto/encryption/aead_test.go b/internal/lib/crypto/encryption/aead_test.go similarity index 90% rename from pkg/crypto/encryption/aead_test.go rename to internal/lib/crypto/encryption/aead_test.go index 7e37026..a48aaa3 100644 --- a/pkg/crypto/encryption/aead_test.go +++ b/internal/lib/crypto/encryption/aead_test.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "testing" - "koti.casa/numenor-labs/dsfx/pkg/crypto/encryption" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/encryption" ) func TestEncryptDecrypt(t *testing.T) { diff --git a/pkg/crypto/identity/ed25519.go b/internal/lib/crypto/identity/ed25519.go similarity index 100% rename from pkg/crypto/identity/ed25519.go rename to internal/lib/crypto/identity/ed25519.go diff --git a/pkg/crypto/keyexchange/ecdh.go b/internal/lib/crypto/keyexchange/ecdh.go similarity index 100% rename from pkg/crypto/keyexchange/ecdh.go rename to internal/lib/crypto/keyexchange/ecdh.go diff --git a/pkg/disk/default.go b/internal/lib/disk/default.go similarity index 100% rename from pkg/disk/default.go rename to internal/lib/disk/default.go diff --git a/pkg/disk/default_test.go b/internal/lib/disk/default_test.go similarity index 96% rename from pkg/disk/default_test.go rename to internal/lib/disk/default_test.go index 7862530..0e098db 100644 --- a/pkg/disk/default_test.go +++ b/internal/lib/disk/default_test.go @@ -3,7 +3,7 @@ package disk_test import ( "testing" - "koti.casa/numenor-labs/dsfx/pkg/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" ) func TestDefaultDisk(t *testing.T) { diff --git a/pkg/disk/disk.go b/internal/lib/disk/disk.go similarity index 100% rename from pkg/disk/disk.go rename to internal/lib/disk/disk.go diff --git a/pkg/frame/frame.go b/internal/lib/frame/frame.go similarity index 100% rename from pkg/frame/frame.go rename to internal/lib/frame/frame.go diff --git a/pkg/frame/frame_test.go b/internal/lib/frame/frame_test.go similarity index 96% rename from pkg/frame/frame_test.go rename to internal/lib/frame/frame_test.go index f969620..b95a562 100644 --- a/pkg/frame/frame_test.go +++ b/internal/lib/frame/frame_test.go @@ -5,7 +5,7 @@ import ( "bytes" "testing" - "koti.casa/numenor-labs/dsfx/pkg/frame" + "koti.casa/numenor-labs/dsfx/internal/lib/frame" ) func TestLenPrefixedWriteTo(t *testing.T) { diff --git a/internal/lib/handshake/bench.txt b/internal/lib/handshake/bench.txt new file mode 100644 index 0000000..d651bb1 --- /dev/null +++ b/internal/lib/handshake/bench.txt @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: koti.casa/numenor-labs/dsfx/internal/lib/handshake +cpu: Intel(R) Core(TM) Ultra 9 185H +BenchmarkHandshake 4508 285270 ns/op 12976 B/op 131 allocs/op +PASS +ok koti.casa/numenor-labs/dsfx/internal/lib/handshake 1.291s diff --git a/pkg/handshake/handshake.go b/internal/lib/handshake/handshake.go similarity index 97% rename from pkg/handshake/handshake.go rename to internal/lib/handshake/handshake.go index 119f728..1e6a0a7 100644 --- a/pkg/handshake/handshake.go +++ b/internal/lib/handshake/handshake.go @@ -10,12 +10,12 @@ import ( "io" "log/slog" - "koti.casa/numenor-labs/dsfx/pkg/assert" - "koti.casa/numenor-labs/dsfx/pkg/buffer" - "koti.casa/numenor-labs/dsfx/pkg/crypto/encryption" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" - "koti.casa/numenor-labs/dsfx/pkg/crypto/keyexchange" - "koti.casa/numenor-labs/dsfx/pkg/logging" + "koti.casa/numenor-labs/dsfx/internal/lib/assert" + "koti.casa/numenor-labs/dsfx/internal/lib/buffer" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/encryption" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/keyexchange" + "koti.casa/numenor-labs/dsfx/internal/lib/logging" ) const ( diff --git a/pkg/handshake/handshake_test.go b/internal/lib/handshake/handshake_test.go similarity index 96% rename from pkg/handshake/handshake_test.go rename to internal/lib/handshake/handshake_test.go index cb4d422..13d8586 100644 --- a/pkg/handshake/handshake_test.go +++ b/internal/lib/handshake/handshake_test.go @@ -10,8 +10,8 @@ import ( "sync" "testing" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" - "koti.casa/numenor-labs/dsfx/pkg/handshake" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/handshake" ) func TestHandshake(t *testing.T) { diff --git a/pkg/logging/logging.go b/internal/lib/logging/logging.go similarity index 100% rename from pkg/logging/logging.go rename to internal/lib/logging/logging.go diff --git a/pkg/network/addr.go b/internal/lib/network/addr.go similarity index 97% rename from pkg/network/addr.go rename to internal/lib/network/addr.go index bdecce0..61989e3 100644 --- a/pkg/network/addr.go +++ b/internal/lib/network/addr.go @@ -8,7 +8,7 @@ import ( "net" "strings" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" ) var ( diff --git a/pkg/network/conn.go b/internal/lib/network/conn.go similarity index 95% rename from pkg/network/conn.go rename to internal/lib/network/conn.go index 8b12595..9622738 100644 --- a/pkg/network/conn.go +++ b/internal/lib/network/conn.go @@ -5,8 +5,8 @@ import ( "net" "time" - "koti.casa/numenor-labs/dsfx/pkg/crypto/encryption" - "koti.casa/numenor-labs/dsfx/pkg/frame" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/encryption" + "koti.casa/numenor-labs/dsfx/internal/lib/frame" ) // Conn is a wrapper around net.TCPConn that encrypts and decrypts data as it is diff --git a/pkg/network/listener.go b/internal/lib/network/listener.go similarity index 88% rename from pkg/network/listener.go rename to internal/lib/network/listener.go index 95beb4b..9a26f8b 100644 --- a/pkg/network/listener.go +++ b/internal/lib/network/listener.go @@ -6,8 +6,8 @@ import ( "log/slog" "net" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" - "koti.casa/numenor-labs/dsfx/pkg/handshake" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/handshake" ) // Listener ... diff --git a/pkg/network/network.go b/internal/lib/network/network.go similarity index 88% rename from pkg/network/network.go rename to internal/lib/network/network.go index c1c4bfe..1ad9f1a 100644 --- a/pkg/network/network.go +++ b/internal/lib/network/network.go @@ -5,8 +5,8 @@ import ( "crypto/ed25519" "net" - "koti.casa/numenor-labs/dsfx/pkg/handshake" - "koti.casa/numenor-labs/dsfx/pkg/logging" + "koti.casa/numenor-labs/dsfx/internal/lib/handshake" + "koti.casa/numenor-labs/dsfx/internal/lib/logging" ) // Dial ... diff --git a/pkg/storage/scoped/scoped.go b/internal/lib/storage/scoped/scoped.go similarity index 97% rename from pkg/storage/scoped/scoped.go rename to internal/lib/storage/scoped/scoped.go index 57dbdb7..851b23c 100644 --- a/pkg/storage/scoped/scoped.go +++ b/internal/lib/storage/scoped/scoped.go @@ -4,7 +4,7 @@ import ( "io/fs" "path/filepath" - "koti.casa/numenor-labs/dsfx/pkg/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" ) // StorageScope is an interface that extends the disk.Disk interface by ensuring diff --git a/pkg/storage/scoped/scoped_test.go b/internal/lib/storage/scoped/scoped_test.go similarity index 95% rename from pkg/storage/scoped/scoped_test.go rename to internal/lib/storage/scoped/scoped_test.go index c0bce30..6bc573e 100644 --- a/pkg/storage/scoped/scoped_test.go +++ b/internal/lib/storage/scoped/scoped_test.go @@ -4,8 +4,8 @@ import ( "io/fs" "testing" - "koti.casa/numenor-labs/dsfx/pkg/disk" - "koti.casa/numenor-labs/dsfx/pkg/storage/scoped" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped" ) func TestScopedStorage_Scope(t *testing.T) { diff --git a/pkg/system/default.go b/internal/lib/system/default.go similarity index 97% rename from pkg/system/default.go rename to internal/lib/system/default.go index 8055ab0..fd0387b 100644 --- a/pkg/system/default.go +++ b/internal/lib/system/default.go @@ -3,7 +3,7 @@ package system import ( "os" - "koti.casa/numenor-labs/dsfx/pkg/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" ) // Default returns a default implementation of the System interface. diff --git a/pkg/system/system.go b/internal/lib/system/system.go similarity index 95% rename from pkg/system/system.go rename to internal/lib/system/system.go index 745724e..ea26225 100644 --- a/pkg/system/system.go +++ b/internal/lib/system/system.go @@ -1,7 +1,7 @@ package system import ( - "koti.casa/numenor-labs/dsfx/pkg/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" ) type System interface { diff --git a/cmd/dsfxnode/conf/conf.go b/internal/peer/conf/conf.go similarity index 93% rename from cmd/dsfxnode/conf/conf.go rename to internal/peer/conf/conf.go index 62a8ce9..df367a1 100644 --- a/cmd/dsfxnode/conf/conf.go +++ b/internal/peer/conf/conf.go @@ -1,6 +1,6 @@ package conf -import "koti.casa/numenor-labs/dsfx/pkg/system" +import "koti.casa/numenor-labs/dsfx/internal/lib/system" const ( // DefaultConfigDir is the default directory for the dsfxctl configuration. diff --git a/cmd/dsfxnode/node/node.go b/internal/peer/node/node.go similarity index 94% rename from cmd/dsfxnode/node/node.go rename to internal/peer/node/node.go index bb02da6..7076f68 100644 --- a/cmd/dsfxnode/node/node.go +++ b/internal/peer/node/node.go @@ -10,12 +10,13 @@ import ( "os" "strings" - "koti.casa/numenor-labs/dsfx/cmd/dsfxnode/conf" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" - "koti.casa/numenor-labs/dsfx/pkg/disk" - "koti.casa/numenor-labs/dsfx/pkg/logging" - "koti.casa/numenor-labs/dsfx/pkg/network" - "koti.casa/numenor-labs/dsfx/pkg/system" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/logging" + "koti.casa/numenor-labs/dsfx/internal/lib/network" + "koti.casa/numenor-labs/dsfx/internal/lib/system" + + "koti.casa/numenor-labs/dsfx/internal/peer/conf" ) type Node struct { diff --git a/internal/sim/disk.go b/internal/sim/disk.go new file mode 100644 index 0000000..0b6c448 --- /dev/null +++ b/internal/sim/disk.go @@ -0,0 +1,287 @@ +package sim + +import ( + "errors" + "io" + "io/fs" + "math/rand" + "sync" + "time" + + "koti.casa/numenor-labs/dsfx/internal/lib/disk" +) + +// Tolerance defines simulation tolerance parameters. +type Tolerance struct { + // Latency to wait before executing an operation. + Latency time.Duration + // FailureChance is the probability (0.0–1.0) that an operation fails. + FailureChance float64 + // CorruptionChance is the probability (0.0–1.0) that a write is corrupted. + CorruptionChance float64 +} + +// simEntry represents an entry in our in–memory file system. +type simEntry struct { + name string + isDir bool + perm fs.FileMode + modTime time.Time + + // For files, data holds the file contents. + data []byte + mu sync.Mutex +} + +// simFile is a simulated file object. It implements fs.File and io.Writer. +// The same underlying simEntry is shared among all handles to a given file. +type simFile struct { + entry *simEntry + // offset to simulate reading sequentially. + offset int + // readOnly indicates whether writes are allowed. + readOnly bool + // closed is set once Close() has been called. + closed bool + // You could add a mutex here if you want to protect concurrent access per handle. + mu sync.Mutex + + // tol copied from the SimDisk for per–file simulation. + tol Tolerance +} + +// Ensure simFile implements the interfaces. +var _ disk.File = (*simFile)(nil) + +// Read reads from the simulated file starting at the current offset. +func (sf *simFile) Read(p []byte) (int, error) { + sf.mu.Lock() + defer sf.mu.Unlock() + + if sf.closed { + return 0, errors.New("read from closed file") + } + + // Lock the underlying data. + sf.entry.mu.Lock() + defer sf.entry.mu.Unlock() + + if sf.offset >= len(sf.entry.data) { + return 0, io.EOF + } + n := copy(p, sf.entry.data[sf.offset:]) + sf.offset += n + return n, nil +} + +// Write writes the given bytes at the end of the file (for a writable file). +// It may simulate corruption. +func (sf *simFile) Write(p []byte) (int, error) { + if sf.readOnly { + return 0, errors.New("cannot write to read-only file") + } + sf.mu.Lock() + defer sf.mu.Unlock() + + if sf.closed { + return 0, errors.New("write to closed file") + } + + // simulate latency + time.Sleep(sf.tol.Latency) + // simulate failure + if rand.Float64() < sf.tol.FailureChance { + return 0, errors.New("simulated write failure") + } + + // prepare data to write + dataToWrite := make([]byte, len(p)) + copy(dataToWrite, p) + + // simulate corruption by flipping bits in the data (if triggered) + if rand.Float64() < sf.tol.CorruptionChance { + for i := range dataToWrite { + dataToWrite[i] = ^dataToWrite[i] // simple corruption: bitwise complement + } + } + + // lock the underlying entry and append data + sf.entry.mu.Lock() + defer sf.entry.mu.Unlock() + sf.entry.data = append(sf.entry.data, dataToWrite...) + // update modification time + sf.entry.modTime = time.Now() + return len(p), nil +} + +// Close marks the file as closed. +func (sf *simFile) Close() error { + sf.mu.Lock() + defer sf.mu.Unlock() + if sf.closed { + return errors.New("file already closed") + } + sf.closed = true + return nil +} + +// Stat returns file information for the simulated file. +func (sf *simFile) Stat() (fs.FileInfo, error) { + sf.mu.Lock() + defer sf.mu.Unlock() + if sf.closed { + return nil, errors.New("stat on closed file") + } + return &simFileInfo{entry: sf.entry}, nil +} + +// simFileInfo implements fs.FileInfo for a simEntry. +type simFileInfo struct { + entry *simEntry +} + +var _ fs.FileInfo = (*simFileInfo)(nil) + +func (fi *simFileInfo) Name() string { return fi.entry.name } +func (fi *simFileInfo) Size() int64 { + fi.entry.mu.Lock() + defer fi.entry.mu.Unlock() + return int64(len(fi.entry.data)) +} +func (fi *simFileInfo) Mode() fs.FileMode { return fi.entry.perm } +func (fi *simFileInfo) ModTime() time.Time { return fi.entry.modTime } +func (fi *simFileInfo) IsDir() bool { return fi.entry.isDir } +func (fi *simFileInfo) Sys() interface{} { return nil } + +// SimDisk is our in–memory simulation that implements the disk.Disk interface. +type SimDisk struct { + // tol holds the tolerance parameters to simulate latency, failures, corruption. + tol Tolerance + + // entries is a map from file or directory name to its corresponding simEntry. + entries map[string]*simEntry + + mu sync.Mutex +} + +// NewSimDisk returns a new simulated disk with the given tolerance parameters. +func NewSimDisk(tol Tolerance) disk.Disk { + return &SimDisk{ + tol: tol, + entries: make(map[string]*simEntry), + } +} + +// simulateOp sleeps for the configured latency and then returns an error if a failure is simulated. +func (sd *SimDisk) simulateOp() error { + time.Sleep(sd.tol.Latency) + if rand.Float64() < sd.tol.FailureChance { + return errors.New("simulated disk operation failure") + } + return nil +} + +// Mkdir creates a directory in the simulated file system. +func (sd *SimDisk) Mkdir(name string, perm fs.FileMode) error { + if err := sd.simulateOp(); err != nil { + return err + } + + sd.mu.Lock() + defer sd.mu.Unlock() + + if _, exists := sd.entries[name]; exists { + return errors.New("directory already exists") + } + sd.entries[name] = &simEntry{ + name: name, + isDir: true, + perm: perm, + modTime: time.Now(), + } + return nil +} + +// Create creates (or truncates) a file in the simulated file system and returns a writable file. +func (sd *SimDisk) Create(name string) (disk.File, error) { + if err := sd.simulateOp(); err != nil { + return nil, err + } + + sd.mu.Lock() + // For simplicity, Create always truncates (or creates new) + se := &simEntry{ + name: name, + isDir: false, + perm: 0644, + modTime: time.Now(), + data: []byte{}, + } + sd.entries[name] = se + sd.mu.Unlock() + + // Return a writable file handle + return &simFile{ + entry: se, + offset: 0, + readOnly: false, + tol: sd.tol, + }, nil +} + +// Open opens an existing file in the simulated file system. +// The returned file is "read-only" (write attempts will error), +func (sd *SimDisk) Open(name string) (disk.File, error) { + if err := sd.simulateOp(); err != nil { + return nil, err + } + + sd.mu.Lock() + se, exists := sd.entries[name] + sd.mu.Unlock() + if !exists { + return nil, errors.New("file does not exist") + } + if se.isDir { + return nil, errors.New("cannot open directory") + } + + // Return a new file handle starting at offset 0; marked as readOnly. + return &simFile{ + entry: se, + offset: 0, + readOnly: true, + tol: sd.tol, + }, nil +} + +// Stat returns information about the file or directory. +func (sd *SimDisk) Stat(name string) (fs.FileInfo, error) { + if err := sd.simulateOp(); err != nil { + return nil, err + } + + sd.mu.Lock() + se, exists := sd.entries[name] + sd.mu.Unlock() + if !exists { + return nil, errors.New("no such file or directory") + } + + return &simFileInfo{entry: se}, nil +} + +// Remove deletes a file or directory from the simulated file system. +func (sd *SimDisk) Remove(name string) error { + if err := sd.simulateOp(); err != nil { + return err + } + + sd.mu.Lock() + defer sd.mu.Unlock() + if _, exists := sd.entries[name]; !exists { + return errors.New("file or directory does not exist") + } + delete(sd.entries, name) + return nil +} diff --git a/internal/sim/system.go b/internal/sim/system.go new file mode 100644 index 0000000..f4e1a91 --- /dev/null +++ b/internal/sim/system.go @@ -0,0 +1,219 @@ +package sim + +import ( + "errors" + "math/rand" + "sync" + "time" + + "koti.casa/numenor-labs/dsfx/internal/lib/disk" + "koti.casa/numenor-labs/dsfx/internal/lib/system" +) + +// SimSystem is a simulated implementation of system.System. +// It allows the caller to set parameters for latency and failure chance (from tol) +// and provides in–memory values for command–line arguments, environment variables, +// and standard output/error. +type SimSystem struct { + // Simulation tolerance parameters (latency, failure chance, corruption chance). + tol Tolerance + + // Simulated command–line arguments. + args []string + + // In–memory environment variables. Protected by mu. + mu sync.Mutex + env map[string]string + + // Simulated directory paths. + homeDir string + configDir string + cacheDir string + tempDir string + + // Simulated standard output and error. + stdout disk.File + stderr disk.File +} + +// NewSimSystem returns a new simulated system that implements system.System. +// The caller provides a tolerance value and a slice of command–line arguments. +// Other fields (directories, environment) are pre–populated for simulation purposes. +func NewSimSystem(tol Tolerance, args []string) system.System { + s := &SimSystem{ + tol: tol, + args: args, + env: make(map[string]string), + homeDir: "/home/simuser", + configDir: "/home/simuser/.config", + cacheDir: "/home/simuser/.cache", + tempDir: "/tmp", + } + // Create simulated stdout and stderr. + s.stdout = newSimOutput("stdout", tol) + s.stderr = newSimOutput("stderr", tol) + return s +} + +// simulateOp applies the configured latency and possibly simulates an operation failure. +func (s *SimSystem) simulateOp() error { + time.Sleep(s.tol.Latency) + if rand.Float64() < s.tol.FailureChance { + return errors.New("simulated system operation failure") + } + return nil +} + +// Args returns the simulated command–line arguments (skipping the program name). +func (s *SimSystem) Args() []string { + // Simulate latency even for argument access. + _ = s.simulateOp() + // Return a copy so that callers cannot modify the underlying slice. + cpy := make([]string, len(s.args)) + copy(cpy, s.args) + return cpy +} + +// Arg returns the simulated command–line argument at index i. +// If the index is out–of–range, it returns an empty string. +func (s *SimSystem) Arg(i int) string { + _ = s.simulateOp() + if i < 0 || i >= len(s.args) { + return "" + } + return s.args[i] +} + +// UserHomeDir returns the simulated home directory. +func (s *SimSystem) UserHomeDir() (string, error) { + if err := s.simulateOp(); err != nil { + return "", err + } + return s.homeDir, nil +} + +// UserConfigDir returns the simulated configuration directory. +func (s *SimSystem) UserConfigDir() (string, error) { + if err := s.simulateOp(); err != nil { + return "", err + } + return s.configDir, nil +} + +// UserCacheDir returns the simulated cache directory. +func (s *SimSystem) UserCacheDir() (string, error) { + if err := s.simulateOp(); err != nil { + return "", err + } + return s.cacheDir, nil +} + +// TempDir returns the simulated temporary directory. +func (s *SimSystem) TempDir() string { + // We simulate latency even though TempDir cannot fail. + _ = s.simulateOp() + return s.tempDir +} + +// Stdout returns a simulated disk.File representing standard output. +func (s *SimSystem) Stdout() disk.File { + // In a real simulation you might add latency/failure to writes on stdout. + _ = s.simulateOp() + return s.stdout +} + +// Stderr returns a simulated disk.File representing standard error. +func (s *SimSystem) Stderr() disk.File { + _ = s.simulateOp() + return s.stderr +} + +// Exit simulates terminating the program with the given exit code. +// As with many tests, we simulate exit by panicing with a special error. +// This allows tests to catch the panic and inspect the exit code. +func (s *SimSystem) Exit(code int) { + _ = s.simulateOp() + panic(&SimExitError{Code: code}) +} + +// GetEnv retrieves the simulated value for the environment variable named by key. +func (s *SimSystem) GetEnv(key string) string { + _ = s.simulateOp() + s.mu.Lock() + defer s.mu.Unlock() + return s.env[key] +} + +// SetEnv sets the simulated environment variable named by key to value. +func (s *SimSystem) SetEnv(key, value string) error { + _ = s.simulateOp() + s.mu.Lock() + defer s.mu.Unlock() + s.env[key] = value + return nil +} + +// SimExitError is the error value used to simulate a program exit. +type SimExitError struct { + Code int +} + +// Error implements the error interface. +func (e *SimExitError) Error() string { + return "simulated program exit" +} + +// -------------------------------------------------------------------------- +// Helper functions and types for simulated standard output/error +// -------------------------------------------------------------------------- + +// newSimOutput creates a new simulated disk.File that acts as an output. +// Internally it creates a simulated file with an in–memory entry. +func newSimOutput(name string, tol Tolerance) disk.File { + entry := &simEntry{ + name: name, + isDir: false, + perm: 0644, + modTime: time.Now(), + data: []byte{}, + } + // Return a simFile which implements disk.File. + // (Note: simFile and simEntry are the types defined in dsfx/sim/disk.go.) + return &simFile{ + entry: entry, + offset: 0, + readOnly: false, + tol: tol, + } +} + +/* +Usage example: + + // Define a tolerance for simulation. + tol := sim.Tolerance{ + Latency: 10 * time.Millisecond, + FailureChance: 0.01, + CorruptionChance: 0.005, + } + + // Simulate a system with custom command–line arguments. + sys := sim.NewSimSystem(tol, []string{"--verbose", "--config=sim.conf"}) + + // Calling system methods: + args := sys.Args() + home, err := sys.UserHomeDir() + // ... etc. + + // To simulate an exit: + func run() { + defer func() { + if r := recover(); r != nil { + if exitErr, ok := r.(*sim.SimExitError); ok { + fmt.Printf("simulated exit(%d)\n", exitErr.Code) + } + } + }() + sys.Exit(3) + } +*/ diff --git a/tool/bench/main.go b/internal/tool/bench/main.go similarity index 83% rename from tool/bench/main.go rename to internal/tool/bench/main.go index 15451c2..3602c9e 100644 --- a/tool/bench/main.go +++ b/internal/tool/bench/main.go @@ -7,7 +7,7 @@ import ( ) func main() { - cmd := exec.Command("go", "test", "-bench=Handshake", "-cpu=1", "-benchmem", "./pkg/handshake/...") + cmd := exec.Command("go", "test", "-bench=Handshake", "-cpu=1", "-benchmem", "./internal/lib/handshake/...") output, err := cmd.Output() if err != nil { diff --git a/tool/genkey/main.go b/internal/tool/genkey/main.go similarity index 80% rename from tool/genkey/main.go rename to internal/tool/genkey/main.go index 6c91bd8..c61c0be 100644 --- a/tool/genkey/main.go +++ b/internal/tool/genkey/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "koti.casa/numenor-labs/dsfx/pkg/crypto/identity" + "koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity" ) func main() { diff --git a/pkg/handshake/bench.txt b/pkg/handshake/bench.txt deleted file mode 100644 index 1160fd2..0000000 --- a/pkg/handshake/bench.txt +++ /dev/null @@ -1,7 +0,0 @@ -goos: linux -goarch: amd64 -pkg: koti.casa/numenor-labs/dsfx/pkg/handshake -cpu: Intel(R) Core(TM) Ultra 9 185H -BenchmarkHandshake 4214 274337 ns/op 12976 B/op 131 allocs/op -PASS -ok koti.casa/numenor-labs/dsfx/pkg/handshake 1.163s diff --git a/revive.toml b/revive.toml deleted file mode 100644 index 08005fe..0000000 --- a/revive.toml +++ /dev/null @@ -1,41 +0,0 @@ -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -errorCode = 1 -warningCode = 1 - -[rule.bare-return] -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.empty-block] -[rule.empty-lines] -[rule.enforce-map-style] -[rule.enforce-slice-style] -[rule.error-naming] -[rule.error-return] -[rule.error-strings] -[rule.errorf] -[rule.exported] -[rule.filename-format] - # Override the default pattern to forbid .go files with uppercase letters and dashes. - arguments=["^[_a-z][_a-z0-9]*\\.go$"] -[rule.increment-decrement] -[rule.indent-error-flow] -[rule.line-length-limit] - arguments = [200] -# [rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.redefines-builtin-id] -[rule.superfluous-else] -[rule.time-naming] -[rule.unexported-naming] -[rule.unexported-return] -[rule.unreachable-code] -[rule.unused-parameter] -[rule.useless-break] -[rule.use-any] -[rule.var-declaration] -[rule.var-naming]