nim-libp2p/libp2p/switch.nim

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

370 lines
11 KiB
Nim
Raw Normal View History

2022-07-01 18:19:57 +00:00
# Nim-LibP2P
2023-01-20 14:47:40 +00:00
# Copyright (c) 2023 Status Research & Development GmbH
2022-07-01 18:19:57 +00:00
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
## The switch is the core of libp2p, which brings together the
## transports, the connection manager, the upgrader and other
## parts to allow programs to use libp2p
2019-08-27 21:46:12 +00:00
2023-06-07 11:12:49 +00:00
{.push raises: [].}
Connection limits (#384) * master merge * wip * avoid deadlocks * tcp limits * expose client field in chronosstream * limit incoming connections * update with new listen api * fix release * don't override peerinfo in connection * rework transport with accept * use semaphore to track resource ussage * rework with new transport accept api * move events to conn manager (#373) * use semaphore to track resource ussage * merge master * expose api to acquire conn slots * don't fail expensive metrics * allow tracking and updating connections * set global connection limits to 80 * add per peer connection limits * make sure conn is closed if tracking failed * more descriptive naming for handle * rework with new transport accept api * add `getStream` hide `selectConn` * add TransportClosedError * make nil explicit * don't make unnecessary copies of message * logging * error handling * cleanup semaphore * track connections properly * throw `TooManyConnections` when tracking outgoing * use proper exception and handle conventions * check onCloseHandle for nil * revert internalConnect changes * adding upgraded flag * await stream before closing * simplify tracking * wip * logging * split connection limits into incoming and outgoing * further streamline connection limits split counts * don't use closeWithEOF * move peer and conn event triggers from switch * wip * wip * wip * merge master * handle nil connections properly * add clarifying comment * don't raise exc on nil * no finally * add proper min/max connections logic * rebase master * merge master * master merge * remove request timeout should be addressed in separate PR * merge master * share semaphore when in/out limits arent enforced * merge master * use import * pass semaphore to trackConn * don't close last conn * use storeConn * merge master * use storeConn
2021-01-21 04:00:24 +00:00
import std/[tables, options, sequtils, sets, oids]
import chronos, chronicles, metrics
import
stream/connection,
2019-09-11 19:03:30 +00:00
transports/transport,
upgrademngrs/upgrade,
2019-09-11 19:03:30 +00:00
multistream,
2020-06-17 04:14:02 +00:00
multiaddress,
2019-09-06 07:13:47 +00:00
protocols/protocol,
2019-09-14 13:55:52 +00:00
protocols/secure/secure,
2019-09-11 19:03:30 +00:00
peerinfo,
utils/semaphore,
connmanager,
nameresolving/nameresolver,
peerid,
peerstore,
errors,
2022-07-01 18:19:57 +00:00
utility,
dialer
export connmanager, upgrade, dialer, peerstore
2020-09-23 14:07:16 +00:00
2019-09-12 02:10:38 +00:00
logScope:
2020-12-01 17:34:27 +00:00
topics = "libp2p switch"
2019-09-12 02:10:38 +00:00
#TODO: General note - use a finite state machine to manage the different
# steps of connections establishing and upgrading. This makes everything
# more robust and less prone to ordering attacks - i.e. muxing can come if
# and only if the channel has been secured (i.e. if a secure manager has been
# previously provided)
const ConcurrentUpgrades* = 4
type
2022-07-01 18:19:57 +00:00
Switch* {.public.} = ref object of Dial
2019-08-30 05:17:07 +00:00
peerInfo*: PeerInfo
connManager*: ConnManager
2019-08-30 05:17:07 +00:00
transports*: seq[Transport]
2020-02-04 16:16:21 +00:00
ms*: MultistreamSelect
acceptFuts: seq[Future[void]]
dialer*: Dial
peerStore*: PeerStore
nameResolver*: NameResolver
started: bool
services*: seq[Service]
Service* = ref object of RootObj
inUse: bool
method setup*(self: Service, switch: Switch): Future[bool] {.base, async.} =
if self.inUse:
warn "service setup has already been called"
return false
self.inUse = true
return true
method run*(self: Service, switch: Switch) {.base, async.} =
doAssert(false, "Not implemented!")
method stop*(self: Service, switch: Switch): Future[bool] {.base, async.} =
if not self.inUse:
warn "service is already stopped"
return false
self.inUse = false
return true
2019-08-27 21:46:12 +00:00
proc addConnEventHandler*(
2020-09-23 14:07:16 +00:00
s: Switch, handler: ConnEventHandler, kind: ConnEventKind
2022-07-01 18:19:57 +00:00
) {.public.} =
## Adds a ConnEventHandler, which will be triggered when
## a connection to a peer is created or dropped.
## There may be multiple connections per peer.
##
## The handler should not raise.
2020-09-23 14:07:16 +00:00
s.connManager.addConnEventHandler(handler, kind)
proc removeConnEventHandler*(
2020-09-23 14:07:16 +00:00
s: Switch, handler: ConnEventHandler, kind: ConnEventKind
2022-07-01 18:19:57 +00:00
) {.public.} =
2020-09-23 14:07:16 +00:00
s.connManager.removeConnEventHandler(handler, kind)
proc addPeerEventHandler*(
s: Switch, handler: PeerEventHandler, kind: PeerEventKind
2022-07-01 18:19:57 +00:00
) {.public.} =
## Adds a PeerEventHandler, which will be triggered when
## a peer connects or disconnects from us.
##
## The handler should not raise.
2020-09-23 14:07:16 +00:00
s.connManager.addPeerEventHandler(handler, kind)
proc removePeerEventHandler*(
s: Switch, handler: PeerEventHandler, kind: PeerEventKind
2022-07-01 18:19:57 +00:00
) {.public.} =
2020-09-23 14:07:16 +00:00
s.connManager.removePeerEventHandler(handler, kind)
2022-08-01 12:31:22 +00:00
method addTransport*(s: Switch, t: Transport) =
s.transports &= t
s.dialer.addTransport(t)
proc connectedPeers*(s: Switch, dir: Direction): seq[PeerId] =
s.connManager.connectedPeers(dir)
2022-07-01 18:19:57 +00:00
proc isConnected*(s: Switch, peerId: PeerId): bool {.public.} =
## returns true if the peer has one or more
2022-07-01 18:19:57 +00:00
## associated connections
##
peerId in s.connManager
2022-07-01 18:19:57 +00:00
proc disconnect*(s: Switch, peerId: PeerId): Future[void] {.gcsafe, public.} =
## Disconnect from a peer, waiting for the connection(s) to be dropped
s.connManager.dropPeer(peerId)
2019-09-28 19:54:32 +00:00
method connect*(
s: Switch,
2021-12-16 10:05:20 +00:00
peerId: PeerId,
2022-02-24 16:31:47 +00:00
addrs: seq[MultiAddress],
forceDial = false,
reuseConnection = true,
dir = Direction.Out,
): Future[void] {.public.} =
2022-07-01 18:19:57 +00:00
## Connects to a peer without opening a stream to it
s.dialer.connect(peerId, addrs, forceDial, reuseConnection, dir)
method connect*(
s: Switch, address: MultiAddress, allowUnknownPeerId = false
): Future[PeerId] =
## Connects to a peer and retrieve its PeerId
##
## If the P2P part is missing from the MA and `allowUnknownPeerId` is set
## to true, this will discover the PeerId while connecting. This exposes
## you to MiTM attacks, so it shouldn't be used without care!
s.dialer.connect(address, allowUnknownPeerId)
method dial*(
2022-07-01 18:19:57 +00:00
s: Switch, peerId: PeerId, protos: seq[string]
): Future[Connection] {.public.} =
## Open a stream to a connected peer with the specified `protos`
s.dialer.dial(peerId, protos)
Gossip one one (#240) * allow multiple codecs per protocol (without breaking things) * add 1.1 protocol to gossip * explicit peering part 1 * explicit peering part 2 * explicit peering part 3 * PeerInfo and ControlPrune protocols * fix encodePrune * validated always, even explicit peers * prune by score (score is stub still) * add a way to pass parameters to gossip * standard setup fixes * take into account explicit direct peers in publish * add floodPublish logic * small fixes, publish still half broken * make sure to waitsub in sparse test * use var semantics to optimize table access * wip... lvalues don't work properly sadly... * big publish refactor, replenish and balance * fix internal tests * use g.peers for fanout (todo: don't include flood peers) * exclude non gossip from fanout * internal test fixes * fix flood tests * fix test's trypublish * test interop fixes * make sure to not remove peers from gossip table * restore old replenishFanout * cleanups * restore utility module import * restore trace vs debug in gossip * improve fanout replenish behavior further * triage publish nil peers (issue is on master too but just hidden behind a if/in) * getGossipPeers fixes * remove topics from pubsubpeer (was unused) * simplify rebalanceMesh (following spec) and make it finally reach D_high * better diagnostics * merge new pubsubpeer, copy 1.1 to new module * fix up merge * conditional enable gossip11 module * add back topics in peers, re-enable flood publish * add more heartbeat locking to prevent races * actually lock the heartbeat * minor fixes * with sugar * merge 1.0 * remove assertion in publish * fix multistream 1.1 multi proto * Fix merge oops * wip * fix gossip 11 upstream * gossipsub11 -> gossipsub * support interop testing * tests fixing * fix directchat build * control prune updates (pb) * wip parameters * gossip internal tests fixes * parameters wip * finishup with params * cleanups/wip * small sugar * grafted and pruned procs * wip updateScores * wip * fix logging issue * pubsubpeer, chronicles explicit override * fix internal gossip tests * wip * tables troubleshooting * score wip * score wip * fixes * fix test utils generateNodes * don't delete while iterating in score update * fix grafted defect * add a handleConnect in subscribeTopic * pruning improvements * wip * score fixes * post merge - builds gossip tests * further merge fixes * rebalance improvements and opportunistic grafting * fix test for now * restore explicit peering * implement peer exchange graft message * add an hard cap to PX * backoff time management * IWANT cap/budget * Adaptive gossip dissemination * outbound mesh quota, internal tests fixing * oversub prune score based, finish outbound quota * finishup with score and ihave budget * use go daemon 0.3.0 * import fixes * byScore cleanup score sorting * remove pointless scaling in `/` Duration operator * revert using libp2p org for daemon * interop fixes * fixes and cleanup * remove heartbeat assertion, minor debug fixes * logging improvements and cleaning up * (to revert) add some traces * add explicit topic to gossip rpcs * pubsub merge fixes and type fix in switch * Revert "(to revert) add some traces" This reverts commit 4663eaab6cc336c81cee50bc54025cf0b7bcbd99. * cleanup some now irrelevant todo * shuffle peers anyway as score might be disabled * add missing shuffle * old merge fix * more merge fixes * debug improvements * re-enable gossip internal tests * add gossip10 fallback (dormant but tested) * split gossipsub internal tests into 1.0 and 1.1 Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
2020-09-21 09:16:29 +00:00
2022-07-01 18:19:57 +00:00
proc dial*(s: Switch, peerId: PeerId, proto: string): Future[Connection] {.public.} =
## Open a stream to a connected peer with the specified `proto`
Connection limits (#384) * master merge * wip * avoid deadlocks * tcp limits * expose client field in chronosstream * limit incoming connections * update with new listen api * fix release * don't override peerinfo in connection * rework transport with accept * use semaphore to track resource ussage * rework with new transport accept api * move events to conn manager (#373) * use semaphore to track resource ussage * merge master * expose api to acquire conn slots * don't fail expensive metrics * allow tracking and updating connections * set global connection limits to 80 * add per peer connection limits * make sure conn is closed if tracking failed * more descriptive naming for handle * rework with new transport accept api * add `getStream` hide `selectConn` * add TransportClosedError * make nil explicit * don't make unnecessary copies of message * logging * error handling * cleanup semaphore * track connections properly * throw `TooManyConnections` when tracking outgoing * use proper exception and handle conventions * check onCloseHandle for nil * revert internalConnect changes * adding upgraded flag * await stream before closing * simplify tracking * wip * logging * split connection limits into incoming and outgoing * further streamline connection limits split counts * don't use closeWithEOF * move peer and conn event triggers from switch * wip * wip * wip * merge master * handle nil connections properly * add clarifying comment * don't raise exc on nil * no finally * add proper min/max connections logic * rebase master * merge master * master merge * remove request timeout should be addressed in separate PR * merge master * share semaphore when in/out limits arent enforced * merge master * use import * pass semaphore to trackConn * don't close last conn * use storeConn * merge master * use storeConn
2021-01-21 04:00:24 +00:00
dial(s, peerId, @[proto])
method dial*(
s: Switch,
2021-12-16 10:05:20 +00:00
peerId: PeerId,
addrs: seq[MultiAddress],
2022-02-24 16:31:47 +00:00
protos: seq[string],
2022-07-01 18:19:57 +00:00
forceDial = false,
): Future[Connection] {.public.} =
## Connected to a peer and open a stream
## with the specified `protos`
2022-02-24 16:31:47 +00:00
s.dialer.dial(peerId, addrs, protos, forceDial)
proc dial*(
s: Switch, peerId: PeerId, addrs: seq[MultiAddress], proto: string
2022-07-01 18:19:57 +00:00
): Future[Connection] {.public.} =
## Connected to a peer and open a stream
## with the specified `proto`
dial(s, peerId, addrs, @[proto])
Gossip one one (#240) * allow multiple codecs per protocol (without breaking things) * add 1.1 protocol to gossip * explicit peering part 1 * explicit peering part 2 * explicit peering part 3 * PeerInfo and ControlPrune protocols * fix encodePrune * validated always, even explicit peers * prune by score (score is stub still) * add a way to pass parameters to gossip * standard setup fixes * take into account explicit direct peers in publish * add floodPublish logic * small fixes, publish still half broken * make sure to waitsub in sparse test * use var semantics to optimize table access * wip... lvalues don't work properly sadly... * big publish refactor, replenish and balance * fix internal tests * use g.peers for fanout (todo: don't include flood peers) * exclude non gossip from fanout * internal test fixes * fix flood tests * fix test's trypublish * test interop fixes * make sure to not remove peers from gossip table * restore old replenishFanout * cleanups * restore utility module import * restore trace vs debug in gossip * improve fanout replenish behavior further * triage publish nil peers (issue is on master too but just hidden behind a if/in) * getGossipPeers fixes * remove topics from pubsubpeer (was unused) * simplify rebalanceMesh (following spec) and make it finally reach D_high * better diagnostics * merge new pubsubpeer, copy 1.1 to new module * fix up merge * conditional enable gossip11 module * add back topics in peers, re-enable flood publish * add more heartbeat locking to prevent races * actually lock the heartbeat * minor fixes * with sugar * merge 1.0 * remove assertion in publish * fix multistream 1.1 multi proto * Fix merge oops * wip * fix gossip 11 upstream * gossipsub11 -> gossipsub * support interop testing * tests fixing * fix directchat build * control prune updates (pb) * wip parameters * gossip internal tests fixes * parameters wip * finishup with params * cleanups/wip * small sugar * grafted and pruned procs * wip updateScores * wip * fix logging issue * pubsubpeer, chronicles explicit override * fix internal gossip tests * wip * tables troubleshooting * score wip * score wip * fixes * fix test utils generateNodes * don't delete while iterating in score update * fix grafted defect * add a handleConnect in subscribeTopic * pruning improvements * wip * score fixes * post merge - builds gossip tests * further merge fixes * rebalance improvements and opportunistic grafting * fix test for now * restore explicit peering * implement peer exchange graft message * add an hard cap to PX * backoff time management * IWANT cap/budget * Adaptive gossip dissemination * outbound mesh quota, internal tests fixing * oversub prune score based, finish outbound quota * finishup with score and ihave budget * use go daemon 0.3.0 * import fixes * byScore cleanup score sorting * remove pointless scaling in `/` Duration operator * revert using libp2p org for daemon * interop fixes * fixes and cleanup * remove heartbeat assertion, minor debug fixes * logging improvements and cleaning up * (to revert) add some traces * add explicit topic to gossip rpcs * pubsub merge fixes and type fix in switch * Revert "(to revert) add some traces" This reverts commit 4663eaab6cc336c81cee50bc54025cf0b7bcbd99. * cleanup some now irrelevant todo * shuffle peers anyway as score might be disabled * add missing shuffle * old merge fix * more merge fixes * debug improvements * re-enable gossip internal tests * add gossip10 fallback (dormant but tested) * split gossipsub internal tests into 1.0 and 1.1 Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
2020-09-21 09:16:29 +00:00
proc mount*[T: LPProtocol](
s: Switch, proto: T, matcher: Matcher = nil
2023-06-07 11:12:49 +00:00
) {.gcsafe, raises: [LPError], public.} =
2022-07-01 18:19:57 +00:00
## mount a protocol to the switch
2019-08-31 17:58:49 +00:00
if isNil(proto.handler):
raise newException(LPError, "Protocol has to define a handle method or proc")
2019-08-27 21:46:12 +00:00
2019-09-06 07:13:47 +00:00
if proto.codec.len == 0:
raise newException(LPError, "Protocol has to define a codec string")
2019-08-31 18:52:56 +00:00
if s.started and not proto.started:
raise newException(LPError, "Protocol not started")
s.ms.addHandler(proto.codecs, proto, matcher)
s.peerInfo.protocols.add(proto.codec)
2019-08-28 02:30:53 +00:00
2023-03-08 11:30:19 +00:00
proc upgrader(switch: Switch, trans: Transport, conn: Connection) {.async.} =
let muxed = await trans.upgrade(conn, Opt.none(PeerId))
2023-03-08 11:30:19 +00:00
switch.connManager.storeMuxer(muxed)
await switch.peerStore.identify(muxed)
await switch.connManager.triggerPeerEvents(
muxed.connection.peerId, PeerEvent(kind: PeerEventKind.Identified, initiator: false)
)
2023-03-08 11:30:19 +00:00
trace "Connection upgrade succeeded"
proc upgradeMonitor(
switch: Switch, trans: Transport, conn: Connection, upgrades: AsyncSemaphore
) {.async.} =
try:
2023-03-08 11:30:19 +00:00
await switch.upgrader(trans, conn).wait(30.seconds)
except CatchableError as exc:
2023-03-08 11:30:19 +00:00
if exc isnot CancelledError:
libp2p_failed_upgrades_incoming.inc()
if not isNil(conn):
await conn.close()
trace "Exception awaiting connection upgrade", exc = exc.msg, conn
finally:
2023-03-08 11:30:19 +00:00
upgrades.release()
proc accept(s: Switch, transport: Transport) {.async.} = # noraises
## switch accept loop, ran for every transport
##
let upgrades = newAsyncSemaphore(ConcurrentUpgrades)
while transport.running:
var conn: Connection
2019-09-04 22:00:39 +00:00
try:
debug "About to accept incoming connection"
# remember to always release the slot when
# the upgrade succeeds or fails, this is
# currently done by the `upgradeMonitor`
Connection limits (#384) * master merge * wip * avoid deadlocks * tcp limits * expose client field in chronosstream * limit incoming connections * update with new listen api * fix release * don't override peerinfo in connection * rework transport with accept * use semaphore to track resource ussage * rework with new transport accept api * move events to conn manager (#373) * use semaphore to track resource ussage * merge master * expose api to acquire conn slots * don't fail expensive metrics * allow tracking and updating connections * set global connection limits to 80 * add per peer connection limits * make sure conn is closed if tracking failed * more descriptive naming for handle * rework with new transport accept api * add `getStream` hide `selectConn` * add TransportClosedError * make nil explicit * don't make unnecessary copies of message * logging * error handling * cleanup semaphore * track connections properly * throw `TooManyConnections` when tracking outgoing * use proper exception and handle conventions * check onCloseHandle for nil * revert internalConnect changes * adding upgraded flag * await stream before closing * simplify tracking * wip * logging * split connection limits into incoming and outgoing * further streamline connection limits split counts * don't use closeWithEOF * move peer and conn event triggers from switch * wip * wip * wip * merge master * handle nil connections properly * add clarifying comment * don't raise exc on nil * no finally * add proper min/max connections logic * rebase master * merge master * master merge * remove request timeout should be addressed in separate PR * merge master * share semaphore when in/out limits arent enforced * merge master * use import * pass semaphore to trackConn * don't close last conn * use storeConn * merge master * use storeConn
2021-01-21 04:00:24 +00:00
await upgrades.acquire() # first wait for an upgrade slot to become available
let slot = await s.connManager.getIncomingSlot()
conn =
try:
await transport.accept()
except CatchableError as exc:
slot.release()
raise exc
slot.trackConnection(conn)
if isNil(conn):
# A nil connection means that we might have hit a
# file-handle limit (or another non-fatal error),
# we can get one on the next try
debug "Unable to get a connection"
upgrades.release()
continue
# set the direction of this bottom level transport
# in order to be able to consume this information in gossipsub if required
# gossipsub gives priority to connections we make
conn.transportDir = Direction.In
debug "Accepted an incoming connection", conn
2023-03-08 11:30:19 +00:00
asyncSpawn s.upgradeMonitor(transport, conn, upgrades)
except CancelledError as exc:
trace "releasing semaphore on cancellation"
upgrades.release() # always release the slot
return
except CatchableError as exc:
error "Exception in accept loop, exiting", exc = exc.msg
upgrades.release() # always release the slot
if not isNil(conn):
await conn.close()
return
2022-07-01 18:19:57 +00:00
proc stop*(s: Switch) {.async, public.} =
## Stop listening on every transport, and
## close every active connections
trace "Stopping switch"
s.started = false
2023-01-17 15:18:38 +00:00
try:
# Stop accepting incoming connections
await allFutures(s.acceptFuts.mapIt(it.cancelAndWait())).wait(1.seconds)
except CatchableError as exc:
debug "Cannot cancel accepts", error = exc.msg
2023-01-17 15:18:38 +00:00
for service in s.services:
discard await service.stop(s)
# close and cleanup all connections
await s.connManager.close()
2019-09-06 07:13:47 +00:00
2021-12-16 10:05:20 +00:00
for transp in s.transports:
try:
2021-12-16 10:05:20 +00:00
await transp.stop()
except CancelledError as exc:
raise exc
except CatchableError as exc:
warn "error cleaning up transports", msg = exc.msg
await s.ms.stop()
trace "Switch stopped"
proc start*(s: Switch) {.async, public.} =
2022-07-01 18:19:57 +00:00
## Start listening on every transport
if s.started:
warn "Switch has already been started"
return
2023-05-16 12:59:02 +00:00
debug "starting switch for peer", peerInfo = s.peerInfo
var startFuts: seq[Future[void]]
for t in s.transports:
2022-10-20 10:22:28 +00:00
let addrs = s.peerInfo.listenAddrs.filterIt(t.handles(it))
2022-10-20 10:22:28 +00:00
s.peerInfo.listenAddrs.keepItIf(it notin addrs)
if addrs.len > 0 or t.running:
startFuts.add(t.start(addrs))
await allFutures(startFuts)
2022-09-15 07:06:32 +00:00
for fut in startFuts:
if fut.failed:
await s.stop()
raise fut.error
for t in s.transports: # for each transport
if t.addrs.len > 0 or t.running:
s.acceptFuts.add(s.accept(t))
2022-10-20 10:22:28 +00:00
s.peerInfo.listenAddrs &= t.addrs
for service in s.services:
discard await service.setup(s)
await s.peerInfo.update()
await s.ms.start()
s.started = true
debug "Started libp2p node", peer = s.peerInfo
2019-09-06 07:13:47 +00:00
proc newSwitch*(
peerInfo: PeerInfo,
transports: seq[Transport],
2021-12-16 10:05:20 +00:00
secureManagers: openArray[Secure] = [],
connManager: ConnManager,
ms: MultistreamSelect,
2023-03-08 11:30:19 +00:00
peerStore: PeerStore,
nameResolver: NameResolver = nil,
services = newSeq[Service](),
2023-06-07 11:12:49 +00:00
): Switch {.raises: [LPError].} =
if secureManagers.len == 0:
2021-05-24 17:55:33 +00:00
raise newException(LPError, "Provide at least one secure manager")
let switch = Switch(
peerInfo: peerInfo,
ms: ms,
transports: transports,
connManager: connManager,
peerStore: peerStore,
2023-03-08 11:30:19 +00:00
dialer:
Dialer.new(peerInfo.peerId, connManager, peerStore, transports, nameResolver),
nameResolver: nameResolver,
services: services,
)
2019-09-06 07:13:47 +00:00
switch.connManager.peerStore = peerStore
return switch