mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
refactor(project): rethink directory structure
This commit is contained in:
parent
1876033e13
commit
330fa5dd37
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Changelog
|
@ -3,9 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/cmd/dsfxctl/client"
|
"koti.casa/numenor-labs/dsfx/internal/client/client"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/system"
|
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -4,10 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/cmd/dsfxnode/node"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/storage/scoped"
|
"koti.casa/numenor-labs/dsfx/internal/peer/node"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/system"
|
|
||||||
|
"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -8,13 +8,14 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/cmd/dsfxctl/conf"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
"koti.casa/numenor-labs/dsfx/internal/lib/network"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/network"
|
"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/storage/scoped"
|
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/system"
|
|
||||||
|
"koti.casa/numenor-labs/dsfx/internal/client/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client represents the client application for dsfxctl.
|
// Client represents the client application for dsfxctl.
|
@ -1,6 +1,6 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import "koti.casa/numenor-labs/dsfx/pkg/system"
|
import "koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultConfigDir is the default directory for the dsfxctl configuration.
|
// DefaultConfigDir is the default directory for the dsfxctl configuration.
|
@ -4,7 +4,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/encryption"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncryptDecrypt(t *testing.T) {
|
func TestEncryptDecrypt(t *testing.T) {
|
@ -3,7 +3,7 @@ package disk_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultDisk(t *testing.T) {
|
func TestDefaultDisk(t *testing.T) {
|
@ -5,7 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/frame"
|
"koti.casa/numenor-labs/dsfx/internal/lib/frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLenPrefixedWriteTo(t *testing.T) {
|
func TestLenPrefixedWriteTo(t *testing.T) {
|
7
internal/lib/handshake/bench.txt
Normal file
7
internal/lib/handshake/bench.txt
Normal file
@ -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
|
@ -10,12 +10,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/assert"
|
"koti.casa/numenor-labs/dsfx/internal/lib/assert"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/buffer"
|
"koti.casa/numenor-labs/dsfx/internal/lib/buffer"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/encryption"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/keyexchange"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/keyexchange"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
@ -10,8 +10,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
"koti.casa/numenor-labs/dsfx/internal/lib/handshake"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandshake(t *testing.T) {
|
func TestHandshake(t *testing.T) {
|
@ -8,7 +8,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
@ -5,8 +5,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/encryption"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/frame"
|
"koti.casa/numenor-labs/dsfx/internal/lib/frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Conn is a wrapper around net.TCPConn that encrypts and decrypts data as it is
|
// Conn is a wrapper around net.TCPConn that encrypts and decrypts data as it is
|
@ -6,8 +6,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
"koti.casa/numenor-labs/dsfx/internal/lib/handshake"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Listener ...
|
// Listener ...
|
@ -5,8 +5,8 @@ import (
|
|||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
"koti.casa/numenor-labs/dsfx/internal/lib/handshake"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dial ...
|
// Dial ...
|
@ -4,7 +4,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"path/filepath"
|
"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
|
// StorageScope is an interface that extends the disk.Disk interface by ensuring
|
@ -4,8 +4,8 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/storage/scoped"
|
"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScopedStorage_Scope(t *testing.T) {
|
func TestScopedStorage_Scope(t *testing.T) {
|
@ -3,7 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"os"
|
"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.
|
// Default returns a default implementation of the System interface.
|
@ -1,7 +1,7 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type System interface {
|
type System interface {
|
@ -1,6 +1,6 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import "koti.casa/numenor-labs/dsfx/pkg/system"
|
import "koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultConfigDir is the default directory for the dsfxctl configuration.
|
// DefaultConfigDir is the default directory for the dsfxctl configuration.
|
@ -10,12 +10,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/cmd/dsfxnode/conf"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
"koti.casa/numenor-labs/dsfx/internal/lib/network"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/network"
|
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/system"
|
|
||||||
|
"koti.casa/numenor-labs/dsfx/internal/peer/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
287
internal/sim/disk.go
Normal file
287
internal/sim/disk.go
Normal file
@ -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
|
||||||
|
}
|
219
internal/sim/system.go
Normal file
219
internal/sim/system.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
*/
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
@ -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
|
|
41
revive.toml
41
revive.toml
@ -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]
|
|
Loading…
x
Reference in New Issue
Block a user