safing-portmaster/spn/ships/testship.go

154 lines
4.3 KiB
Go

package ships
import (
"net"
"github.com/mr-tron/base58"
"github.com/tevino/abool"
"github.com/safing/portmaster/spn/hub"
)
// TestShip is a simulated ship that is used for testing higher level components.
type TestShip struct {
mine bool
secure bool
loadSize int
forward chan []byte
backward chan []byte
unloadTmp []byte
sinking *abool.AtomicBool
}
// NewTestShip returns a new TestShip for simulation.
func NewTestShip(secure bool, loadSize int) *TestShip {
return &TestShip{
mine: true,
secure: secure,
loadSize: loadSize,
forward: make(chan []byte, 100),
backward: make(chan []byte, 100),
sinking: abool.NewBool(false),
}
}
// String returns a human readable informational summary about the ship.
func (ship *TestShip) String() string {
if ship.mine {
return "<TestShip outbound>"
}
return "<TestShip inbound>"
}
// Transport returns the transport used for this ship.
func (ship *TestShip) Transport() *hub.Transport {
return &hub.Transport{
Protocol: "dummy",
}
}
// IsMine returns whether the ship was launched from here.
func (ship *TestShip) IsMine() bool {
return ship.mine
}
// IsSecure returns whether the ship provides transport security.
func (ship *TestShip) IsSecure() bool {
return ship.secure
}
// LoadSize returns the recommended data size that should be handed to Load().
// This value will be most likely somehow related to the connection's MTU.
// Alternatively, using a multiple of LoadSize is also recommended.
func (ship *TestShip) LoadSize() int {
return ship.loadSize
}
// Reverse creates a connected TestShip. This is used to simulate a connection instead of using a Pier.
func (ship *TestShip) Reverse() *TestShip {
return &TestShip{
mine: !ship.mine,
secure: ship.secure,
loadSize: ship.loadSize,
forward: ship.backward,
backward: ship.forward,
sinking: abool.NewBool(false),
}
}
// Load loads data into the ship - ie. sends the data via the connection.
// Returns ErrSunk if the ship has already sunk earlier.
func (ship *TestShip) Load(data []byte) error {
// Debugging:
// log.Debugf("spn/ship: loading %s", spew.Sdump(data))
// Check if ship is alive.
if ship.sinking.IsSet() {
return ErrSunk
}
// Empty load is used as a signal to cease operaetion.
if len(data) == 0 {
ship.Sink()
return nil
}
// Send all given data.
ship.forward <- data
return nil
}
// UnloadTo unloads data from the ship - ie. receives data from the
// connection - puts it into the buf. It returns the amount of data
// written and an optional error.
// Returns ErrSunk if the ship has already sunk earlier.
func (ship *TestShip) UnloadTo(buf []byte) (n int, err error) {
// Process unload tmp data, if there is any.
if ship.unloadTmp != nil {
// Copy as much data as possible.
copy(buf, ship.unloadTmp)
// If buf was too small, skip the copied section.
if len(buf) < len(ship.unloadTmp) {
ship.unloadTmp = ship.unloadTmp[len(buf):]
return len(buf), nil
}
// If everything was copied, unset the unloadTmp data.
n := len(ship.unloadTmp)
ship.unloadTmp = nil
return n, nil
}
// Receive data.
data := <-ship.backward
if len(data) == 0 {
return 0, ErrSunk
}
// Copy data, possibly save remainder for later.
copy(buf, data)
if len(buf) < len(data) {
ship.unloadTmp = data[len(buf):]
return len(buf), nil
}
return len(data), nil
}
// Sink closes the underlying connection and cleans up any related resources.
func (ship *TestShip) Sink() {
if ship.sinking.SetToIf(false, true) {
close(ship.forward)
}
}
// Dummy methods to conform to interface for testing.
func (ship *TestShip) LocalAddr() net.Addr { return nil } //nolint:golint
func (ship *TestShip) RemoteAddr() net.Addr { return nil } //nolint:golint
func (ship *TestShip) Public() bool { return true } //nolint:golint
func (ship *TestShip) MarkPublic() {} //nolint:golint
func (ship *TestShip) MaskAddress(addr net.Addr) string { return addr.String() } //nolint:golint
func (ship *TestShip) MaskIP(ip net.IP) string { return ip.String() } //nolint:golint
func (ship *TestShip) Mask(value []byte) string { return base58.Encode(value) } //nolint:golint