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.
|
|
|
|
|
|
|
|
## This module contains a Switch Building helper.
|
|
|
|
runnableExamples:
|
|
|
|
let switch =
|
|
|
|
SwitchBuilder.new()
|
|
|
|
.withRng(rng)
|
|
|
|
.withAddresses(multiaddress)
|
|
|
|
# etc
|
|
|
|
.build()
|
2021-05-21 16:27:01 +00:00
|
|
|
|
2022-08-03 11:33:19 +00:00
|
|
|
when (NimMajor, NimMinor) < (1, 4):
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
else:
|
|
|
|
{.push raises: [].}
|
2021-05-21 16:27:01 +00:00
|
|
|
|
2021-04-02 01:20:51 +00:00
|
|
|
import
|
2022-07-22 10:54:09 +00:00
|
|
|
options, tables, chronos, chronicles, sequtils,
|
2021-04-02 01:20:51 +00:00
|
|
|
switch, peerid, peerinfo, stream/connection, multiaddress,
|
|
|
|
crypto/crypto, transports/[transport, tcptransport],
|
2022-07-04 13:19:21 +00:00
|
|
|
muxers/[muxer, mplex/mplex, yamux/yamux],
|
2022-09-30 08:41:04 +00:00
|
|
|
protocols/[identify, secure/secure, secure/noise, rendezvous],
|
2023-01-06 10:14:38 +00:00
|
|
|
protocols/connectivity/[autonat/server, relay/relay, relay/client, relay/rtransport],
|
2021-05-24 17:55:33 +00:00
|
|
|
connmanager, upgrademngrs/muxedupgrade,
|
2021-08-18 07:40:12 +00:00
|
|
|
nameresolving/nameresolver,
|
2022-07-01 18:19:57 +00:00
|
|
|
errors, utility
|
2021-04-02 01:20:51 +00:00
|
|
|
|
|
|
|
export
|
2021-05-24 17:55:33 +00:00
|
|
|
switch, peerid, peerinfo, connection, multiaddress, crypto, errors
|
2021-04-02 01:20:51 +00:00
|
|
|
|
|
|
|
type
|
2022-07-01 18:19:57 +00:00
|
|
|
TransportProvider* {.public.} = proc(upgr: Upgrade): Transport {.gcsafe, raises: [Defect].}
|
2021-08-03 13:48:03 +00:00
|
|
|
|
2021-04-02 01:20:51 +00:00
|
|
|
SecureProtocol* {.pure.} = enum
|
|
|
|
Noise,
|
|
|
|
Secio {.deprecated.}
|
|
|
|
|
|
|
|
SwitchBuilder* = ref object
|
|
|
|
privKey: Option[PrivateKey]
|
2021-06-21 15:14:24 +00:00
|
|
|
addresses: seq[MultiAddress]
|
2021-04-02 01:20:51 +00:00
|
|
|
secureManagers: seq[SecureProtocol]
|
2022-07-22 10:54:09 +00:00
|
|
|
muxers: seq[MuxerProvider]
|
2021-08-03 13:48:03 +00:00
|
|
|
transports: seq[TransportProvider]
|
2022-06-16 08:08:52 +00:00
|
|
|
rng: ref HmacDrbgContext
|
2021-04-02 01:20:51 +00:00
|
|
|
maxConnections: int
|
|
|
|
maxIn: int
|
2022-03-14 08:39:30 +00:00
|
|
|
sendSignedPeerRecord: bool
|
2021-04-02 01:20:51 +00:00
|
|
|
maxOut: int
|
|
|
|
maxConnsPerPeer: int
|
2021-04-05 22:06:45 +00:00
|
|
|
protoVersion: string
|
|
|
|
agentVersion: string
|
2021-08-18 07:40:12 +00:00
|
|
|
nameResolver: NameResolver
|
2022-05-25 10:12:57 +00:00
|
|
|
peerStoreCapacity: Option[int]
|
2022-08-23 15:49:07 +00:00
|
|
|
autonat: bool
|
2022-08-01 12:31:22 +00:00
|
|
|
circuitRelay: Relay
|
2022-09-30 08:41:04 +00:00
|
|
|
rdv: RendezVous
|
2022-12-16 11:32:00 +00:00
|
|
|
services: seq[Service]
|
2021-04-02 01:20:51 +00:00
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc new*(T: type[SwitchBuilder]): T {.public.} =
|
|
|
|
## Creates a SwitchBuilder
|
2021-05-21 16:27:01 +00:00
|
|
|
|
|
|
|
let address = MultiAddress
|
|
|
|
.init("/ip4/127.0.0.1/tcp/0")
|
2021-05-22 18:27:30 +00:00
|
|
|
.expect("Should initialize to default")
|
2021-05-21 16:27:01 +00:00
|
|
|
|
2021-04-02 01:20:51 +00:00
|
|
|
SwitchBuilder(
|
|
|
|
privKey: none(PrivateKey),
|
2021-06-21 15:14:24 +00:00
|
|
|
addresses: @[address],
|
2021-04-02 01:20:51 +00:00
|
|
|
secureManagers: @[],
|
|
|
|
maxConnections: MaxConnections,
|
|
|
|
maxIn: -1,
|
|
|
|
maxOut: -1,
|
|
|
|
maxConnsPerPeer: MaxConnectionsPerPeer,
|
2021-04-05 22:06:45 +00:00
|
|
|
protoVersion: ProtoVersion,
|
2022-08-01 12:31:22 +00:00
|
|
|
agentVersion: AgentVersion)
|
2021-04-02 01:20:51 +00:00
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withPrivateKey*(b: SwitchBuilder, privateKey: PrivateKey): SwitchBuilder {.public.} =
|
|
|
|
## Set the private key of the switch. Will be used to
|
|
|
|
## generate a PeerId
|
|
|
|
|
2021-04-02 01:20:51 +00:00
|
|
|
b.privKey = some(privateKey)
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withAddress*(b: SwitchBuilder, address: MultiAddress): SwitchBuilder {.public.} =
|
|
|
|
## | Set the listening address of the switch
|
|
|
|
## | Calling it multiple time will override the value
|
|
|
|
|
2021-06-21 15:14:24 +00:00
|
|
|
b.addresses = @[address]
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withAddresses*(b: SwitchBuilder, addresses: seq[MultiAddress]): SwitchBuilder {.public.} =
|
|
|
|
## | Set the listening addresses of the switch
|
|
|
|
## | Calling it multiple time will override the value
|
|
|
|
|
2021-06-21 15:14:24 +00:00
|
|
|
b.addresses = addresses
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withSignedPeerRecord*(b: SwitchBuilder, sendIt = true): SwitchBuilder {.public.} =
|
2022-03-14 08:39:30 +00:00
|
|
|
b.sendSignedPeerRecord = sendIt
|
|
|
|
b
|
2021-06-21 15:14:24 +00:00
|
|
|
|
2022-05-25 15:59:42 +00:00
|
|
|
proc withMplex*(
|
|
|
|
b: SwitchBuilder,
|
|
|
|
inTimeout = 5.minutes,
|
|
|
|
outTimeout = 5.minutes,
|
2022-07-01 18:19:57 +00:00
|
|
|
maxChannCount = 200): SwitchBuilder {.public.} =
|
|
|
|
## | Uses `Mplex <https://docs.libp2p.io/concepts/stream-multiplexing/#mplex>`_ as a multiplexer
|
|
|
|
## | `Timeout` is the duration after which a inactive connection will be closed
|
2021-04-05 22:06:45 +00:00
|
|
|
proc newMuxer(conn: Connection): Muxer =
|
2021-10-25 08:26:32 +00:00
|
|
|
Mplex.new(
|
2021-04-05 22:06:45 +00:00
|
|
|
conn,
|
2022-05-25 15:59:42 +00:00
|
|
|
inTimeout,
|
|
|
|
outTimeout,
|
|
|
|
maxChannCount)
|
2021-04-05 22:06:45 +00:00
|
|
|
|
2022-07-22 10:54:09 +00:00
|
|
|
assert b.muxers.countIt(it.codec == MplexCodec) == 0, "Mplex build multiple times"
|
|
|
|
b.muxers.add(MuxerProvider.new(newMuxer, MplexCodec))
|
2022-07-04 13:19:21 +00:00
|
|
|
b
|
|
|
|
|
|
|
|
proc withYamux*(b: SwitchBuilder): SwitchBuilder =
|
|
|
|
proc newMuxer(conn: Connection): Muxer = Yamux.new(conn)
|
2021-04-02 01:20:51 +00:00
|
|
|
|
2022-07-22 10:54:09 +00:00
|
|
|
assert b.muxers.countIt(it.codec == YamuxCodec) == 0, "Yamux build multiple times"
|
|
|
|
b.muxers.add(MuxerProvider.new(newMuxer, YamuxCodec))
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withNoise*(b: SwitchBuilder): SwitchBuilder {.public.} =
|
2021-04-05 22:06:45 +00:00
|
|
|
b.secureManagers.add(SecureProtocol.Noise)
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withTransport*(b: SwitchBuilder, prov: TransportProvider): SwitchBuilder {.public.} =
|
|
|
|
## Use a custom transport
|
|
|
|
runnableExamples:
|
|
|
|
let switch =
|
|
|
|
SwitchBuilder.new()
|
|
|
|
.withTransport(proc(upgr: Upgrade): Transport = TcpTransport.new(flags, upgr))
|
|
|
|
.build()
|
2021-08-03 13:48:03 +00:00
|
|
|
b.transports.add(prov)
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withTcpTransport*(b: SwitchBuilder, flags: set[ServerFlags] = {}): SwitchBuilder {.public.} =
|
2021-08-03 13:48:03 +00:00
|
|
|
b.withTransport(proc(upgr: Upgrade): Transport = TcpTransport.new(flags, upgr))
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withRng*(b: SwitchBuilder, rng: ref HmacDrbgContext): SwitchBuilder {.public.} =
|
2021-04-05 22:06:45 +00:00
|
|
|
b.rng = rng
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withMaxConnections*(b: SwitchBuilder, maxConnections: int): SwitchBuilder {.public.} =
|
|
|
|
## Maximum concurrent connections of the switch. You should either use this, or
|
|
|
|
## `withMaxIn <#withMaxIn,SwitchBuilder,int>`_ & `withMaxOut<#withMaxOut,SwitchBuilder,int>`_
|
2021-04-02 01:20:51 +00:00
|
|
|
b.maxConnections = maxConnections
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withMaxIn*(b: SwitchBuilder, maxIn: int): SwitchBuilder {.public.} =
|
|
|
|
## Maximum concurrent incoming connections. Should be used with `withMaxOut<#withMaxOut,SwitchBuilder,int>`_
|
2021-04-02 01:20:51 +00:00
|
|
|
b.maxIn = maxIn
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withMaxOut*(b: SwitchBuilder, maxOut: int): SwitchBuilder {.public.} =
|
|
|
|
## Maximum concurrent outgoing connections. Should be used with `withMaxIn<#withMaxIn,SwitchBuilder,int>`_
|
2021-04-02 01:20:51 +00:00
|
|
|
b.maxOut = maxOut
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withMaxConnsPerPeer*(b: SwitchBuilder, maxConnsPerPeer: int): SwitchBuilder {.public.} =
|
2021-04-02 01:20:51 +00:00
|
|
|
b.maxConnsPerPeer = maxConnsPerPeer
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withPeerStore*(b: SwitchBuilder, capacity: int): SwitchBuilder {.public.} =
|
2022-05-25 10:12:57 +00:00
|
|
|
b.peerStoreCapacity = some(capacity)
|
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withProtoVersion*(b: SwitchBuilder, protoVersion: string): SwitchBuilder {.public.} =
|
2021-04-05 22:06:45 +00:00
|
|
|
b.protoVersion = protoVersion
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withAgentVersion*(b: SwitchBuilder, agentVersion: string): SwitchBuilder {.public.} =
|
2021-04-05 22:06:45 +00:00
|
|
|
b.agentVersion = agentVersion
|
2021-04-02 01:20:51 +00:00
|
|
|
b
|
|
|
|
|
2022-07-01 18:19:57 +00:00
|
|
|
proc withNameResolver*(b: SwitchBuilder, nameResolver: NameResolver): SwitchBuilder {.public.} =
|
2021-08-18 07:40:12 +00:00
|
|
|
b.nameResolver = nameResolver
|
|
|
|
b
|
|
|
|
|
2022-08-23 15:49:07 +00:00
|
|
|
proc withAutonat*(b: SwitchBuilder): SwitchBuilder =
|
|
|
|
b.autonat = true
|
|
|
|
b
|
|
|
|
|
2022-08-01 12:31:22 +00:00
|
|
|
proc withCircuitRelay*(b: SwitchBuilder, r: Relay = Relay.new()): SwitchBuilder =
|
|
|
|
b.circuitRelay = r
|
2022-05-18 08:19:37 +00:00
|
|
|
b
|
|
|
|
|
2022-09-30 08:41:04 +00:00
|
|
|
proc withRendezVous*(b: SwitchBuilder, rdv: RendezVous = RendezVous.new()): SwitchBuilder =
|
|
|
|
b.rdv = rdv
|
|
|
|
b
|
|
|
|
|
2022-12-16 11:32:00 +00:00
|
|
|
proc withServices*(b: SwitchBuilder, services: seq[Service]): SwitchBuilder =
|
|
|
|
b.services = services
|
|
|
|
b
|
|
|
|
|
2021-05-22 18:27:30 +00:00
|
|
|
proc build*(b: SwitchBuilder): Switch
|
2022-07-01 18:19:57 +00:00
|
|
|
{.raises: [Defect, LPError], public.} =
|
2021-05-22 18:27:30 +00:00
|
|
|
|
2021-04-02 01:20:51 +00:00
|
|
|
if b.rng == nil: # newRng could fail
|
2021-05-21 16:27:01 +00:00
|
|
|
raise newException(Defect, "Cannot initialize RNG")
|
2021-04-02 01:20:51 +00:00
|
|
|
|
2021-05-21 16:27:01 +00:00
|
|
|
let pkRes = PrivateKey.random(b.rng[])
|
2021-04-02 01:20:51 +00:00
|
|
|
let
|
2021-05-22 18:27:30 +00:00
|
|
|
seckey = b.privKey.get(otherwise = pkRes.expect("Expected default Private Key"))
|
2021-04-05 22:06:45 +00:00
|
|
|
|
|
|
|
var
|
|
|
|
secureManagerInstances: seq[Secure]
|
|
|
|
if SecureProtocol.Noise in b.secureManagers:
|
2021-06-07 07:32:08 +00:00
|
|
|
secureManagerInstances.add(Noise.new(b.rng, seckey).Secure)
|
2021-04-05 22:06:45 +00:00
|
|
|
|
|
|
|
let
|
2021-10-25 08:26:32 +00:00
|
|
|
peerInfo = PeerInfo.new(
|
2021-05-21 16:27:01 +00:00
|
|
|
seckey,
|
2021-06-21 15:14:24 +00:00
|
|
|
b.addresses,
|
2021-05-21 16:27:01 +00:00
|
|
|
protoVersion = b.protoVersion,
|
|
|
|
agentVersion = b.agentVersion)
|
2021-04-05 22:06:45 +00:00
|
|
|
|
|
|
|
let
|
2022-03-14 08:39:30 +00:00
|
|
|
identify = Identify.new(peerInfo, b.sendSignedPeerRecord)
|
2021-10-25 08:26:32 +00:00
|
|
|
connManager = ConnManager.new(b.maxConnsPerPeer, b.maxConnections, b.maxIn, b.maxOut)
|
2021-06-07 07:32:08 +00:00
|
|
|
ms = MultistreamSelect.new()
|
2023-03-08 11:30:19 +00:00
|
|
|
muxedUpgrade = MuxedUpgrade.new(b.muxers, secureManagerInstances, connManager, ms)
|
2021-04-05 22:06:45 +00:00
|
|
|
|
|
|
|
let
|
2021-04-02 01:20:51 +00:00
|
|
|
transports = block:
|
|
|
|
var transports: seq[Transport]
|
2021-08-03 13:48:03 +00:00
|
|
|
for tProvider in b.transports:
|
|
|
|
transports.add(tProvider(muxedUpgrade))
|
2021-04-02 01:20:51 +00:00
|
|
|
transports
|
|
|
|
|
|
|
|
if b.secureManagers.len == 0:
|
|
|
|
b.secureManagers &= SecureProtocol.Noise
|
|
|
|
|
2021-04-06 20:16:23 +00:00
|
|
|
if isNil(b.rng):
|
|
|
|
b.rng = newRng()
|
|
|
|
|
2022-05-25 10:12:57 +00:00
|
|
|
let peerStore =
|
|
|
|
if isSome(b.peerStoreCapacity):
|
2023-03-08 11:30:19 +00:00
|
|
|
PeerStore.new(identify, b.peerStoreCapacity.get())
|
2022-05-25 10:12:57 +00:00
|
|
|
else:
|
2023-03-08 11:30:19 +00:00
|
|
|
PeerStore.new(identify)
|
2022-05-25 10:12:57 +00:00
|
|
|
|
2021-06-02 13:39:10 +00:00
|
|
|
let switch = newSwitch(
|
|
|
|
peerInfo = peerInfo,
|
|
|
|
transports = transports,
|
|
|
|
secureManagers = secureManagerInstances,
|
2021-04-05 19:37:14 +00:00
|
|
|
connManager = connManager,
|
2021-08-18 07:40:12 +00:00
|
|
|
ms = ms,
|
2022-05-25 10:12:57 +00:00
|
|
|
nameResolver = b.nameResolver,
|
2022-12-16 11:32:00 +00:00
|
|
|
peerStore = peerStore,
|
|
|
|
services = b.services)
|
2021-04-02 01:20:51 +00:00
|
|
|
|
2023-03-08 11:30:19 +00:00
|
|
|
switch.mount(identify)
|
|
|
|
|
2022-08-23 15:49:07 +00:00
|
|
|
if b.autonat:
|
|
|
|
let autonat = Autonat.new(switch)
|
|
|
|
switch.mount(autonat)
|
|
|
|
|
2022-08-01 12:31:22 +00:00
|
|
|
if not isNil(b.circuitRelay):
|
|
|
|
if b.circuitRelay of RelayClient:
|
|
|
|
switch.addTransport(RelayTransport.new(RelayClient(b.circuitRelay), muxedUpgrade))
|
|
|
|
b.circuitRelay.setup(switch)
|
|
|
|
switch.mount(b.circuitRelay)
|
2022-05-18 08:19:37 +00:00
|
|
|
|
2022-09-30 08:41:04 +00:00
|
|
|
if not isNil(b.rdv):
|
|
|
|
b.rdv.setup(switch)
|
|
|
|
switch.mount(b.rdv)
|
|
|
|
|
2021-04-02 01:20:51 +00:00
|
|
|
return switch
|
|
|
|
|
2021-05-24 22:48:18 +00:00
|
|
|
proc newStandardSwitch*(
|
|
|
|
privKey = none(PrivateKey),
|
2021-11-24 20:01:12 +00:00
|
|
|
addrs: MultiAddress | seq[MultiAddress] = MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet(),
|
2021-12-16 10:05:20 +00:00
|
|
|
secureManagers: openArray[SecureProtocol] = [
|
2021-05-24 22:48:18 +00:00
|
|
|
SecureProtocol.Noise,
|
|
|
|
],
|
|
|
|
transportFlags: set[ServerFlags] = {},
|
|
|
|
rng = newRng(),
|
|
|
|
inTimeout: Duration = 5.minutes,
|
|
|
|
outTimeout: Duration = 5.minutes,
|
|
|
|
maxConnections = MaxConnections,
|
|
|
|
maxIn = -1,
|
|
|
|
maxOut = -1,
|
2021-08-18 07:40:12 +00:00
|
|
|
maxConnsPerPeer = MaxConnectionsPerPeer,
|
2022-03-14 08:39:30 +00:00
|
|
|
nameResolver: NameResolver = nil,
|
2022-05-25 10:12:57 +00:00
|
|
|
sendSignedPeerRecord = false,
|
|
|
|
peerStoreCapacity = 1000): Switch
|
2022-07-01 18:19:57 +00:00
|
|
|
{.raises: [Defect, LPError], public.} =
|
|
|
|
## Helper for common switch configurations.
|
2023-05-18 08:24:17 +00:00
|
|
|
{.push warning[Deprecated]:off.}
|
2021-04-05 22:06:45 +00:00
|
|
|
if SecureProtocol.Secio in secureManagers:
|
|
|
|
quit("Secio is deprecated!") # use of secio is unsafe
|
2023-05-18 08:24:17 +00:00
|
|
|
{.pop.}
|
2021-04-05 22:06:45 +00:00
|
|
|
|
2021-11-24 20:01:12 +00:00
|
|
|
let addrs = when addrs is MultiAddress: @[addrs] else: addrs
|
2021-04-02 01:20:51 +00:00
|
|
|
var b = SwitchBuilder
|
2021-04-06 20:16:23 +00:00
|
|
|
.new()
|
2021-11-24 20:01:12 +00:00
|
|
|
.withAddresses(addrs)
|
2021-04-02 01:20:51 +00:00
|
|
|
.withRng(rng)
|
2022-03-14 08:39:30 +00:00
|
|
|
.withSignedPeerRecord(sendSignedPeerRecord)
|
2021-04-02 01:20:51 +00:00
|
|
|
.withMaxConnections(maxConnections)
|
|
|
|
.withMaxIn(maxIn)
|
|
|
|
.withMaxOut(maxOut)
|
|
|
|
.withMaxConnsPerPeer(maxConnsPerPeer)
|
2022-05-25 10:12:57 +00:00
|
|
|
.withPeerStore(capacity=peerStoreCapacity)
|
2021-04-05 22:06:45 +00:00
|
|
|
.withMplex(inTimeout, outTimeout)
|
2021-04-02 01:20:51 +00:00
|
|
|
.withTcpTransport(transportFlags)
|
2021-08-18 07:40:12 +00:00
|
|
|
.withNameResolver(nameResolver)
|
2021-04-05 22:06:45 +00:00
|
|
|
.withNoise()
|
2021-04-02 01:20:51 +00:00
|
|
|
|
|
|
|
if privKey.isSome():
|
|
|
|
b = b.withPrivateKey(privKey.get())
|
|
|
|
|
|
|
|
b.build()
|