90 lines
2.9 KiB
Nim
90 lines
2.9 KiB
Nim
|
import chronos # an efficient library for async
|
||
|
import stew/byteutils # various utils
|
||
|
import libp2p
|
||
|
|
||
|
##
|
||
|
# Create our custom protocol
|
||
|
##
|
||
|
const TestCodec = "/test/proto/1.0.0" # custom protocol string identifier
|
||
|
|
||
|
type
|
||
|
TestProto = ref object of LPProtocol # declare a custom protocol
|
||
|
|
||
|
proc new(T: typedesc[TestProto]): T =
|
||
|
|
||
|
# every incoming connections will be in handled in this closure
|
||
|
proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
|
||
|
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||
|
await conn.writeLp("Roger p2p!")
|
||
|
|
||
|
# We must close the connections ourselves when we're done with it
|
||
|
await conn.close()
|
||
|
|
||
|
return T(codecs: @[TestCodec], handler: handle)
|
||
|
|
||
|
##
|
||
|
# Helper to create a switch/node
|
||
|
##
|
||
|
proc createSwitch(ma: MultiAddress, rng: ref HmacDrbgContext): Switch =
|
||
|
var switch = SwitchBuilder
|
||
|
.new()
|
||
|
.withRng(rng) # Give the application RNG
|
||
|
.withAddress(ma) # Our local address(es)
|
||
|
.withTcpTransport() # Use TCP as transport
|
||
|
.withMplex() # Use Mplex as muxer
|
||
|
.withNoise() # Use Noise as secure manager
|
||
|
.build()
|
||
|
|
||
|
result = switch
|
||
|
|
||
|
##
|
||
|
# The actual application
|
||
|
##
|
||
|
proc main() {.async, gcsafe.} =
|
||
|
let
|
||
|
rng = newRng() # Single random number source for the whole application
|
||
|
# port 0 will take a random available port
|
||
|
# `tryGet` will throw an exception if the MultiAddress failed
|
||
|
# (for instance, if the address is not well formatted)
|
||
|
ma1 = MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
|
||
|
ma2 = MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
|
||
|
|
||
|
# setup the custom proto
|
||
|
let testProto = TestProto.new()
|
||
|
|
||
|
# setup the two nodes
|
||
|
let
|
||
|
switch1 = createSwitch(ma1, rng) #Create the two switches
|
||
|
switch2 = createSwitch(ma2, rng)
|
||
|
|
||
|
# mount the proto on switch1
|
||
|
# the node will now listen for this proto
|
||
|
# and call the handler everytime a client request it
|
||
|
switch1.mount(testProto)
|
||
|
|
||
|
# Start the nodes. This will start the transports
|
||
|
# and listen on each local addresses
|
||
|
await switch1.start()
|
||
|
await switch2.start()
|
||
|
|
||
|
# the node addrs is populated with it's
|
||
|
# actual port during the start
|
||
|
|
||
|
# use the second node to dial the first node
|
||
|
# using the first node peerid and address
|
||
|
# and specify our custom protocol codec
|
||
|
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||
|
|
||
|
# conn is now a fully setup connection, we talk directly to the node1 custom protocol handler
|
||
|
await conn.writeLp("Hello p2p!") # writeLp send a length prefixed buffer over the wire
|
||
|
|
||
|
# readLp reads length prefixed bytes and returns a buffer without the prefix
|
||
|
echo "Remote responded with - ", string.fromBytes(await conn.readLp(1024))
|
||
|
|
||
|
# We must close the connection ourselves when we're done with it
|
||
|
await conn.close()
|
||
|
|
||
|
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||
|
|
||
|
waitFor(main())
|