2019-09-12 23:20:30 +00:00
when not ( compileOption ( " threads " ) ) :
{. fatal : " Please, compile this program with the --threads:on option! " . }
2020-07-07 11:14:11 +00:00
import tables , strformat , strutils , bearssl
2020-04-08 17:21:06 +00:00
import chronos # an efficient library for async
import .. / libp2p / [ switch , # manage transports, a single entry point for dialing and listening
2021-06-28 02:37:00 +00:00
builders , # helper to build the switch object
2020-04-08 17:21:06 +00:00
multistream , # tag stream with short header to identify it
2021-06-28 02:37:00 +00:00
multicodec , # multicodec utilities
2020-04-08 17:21:06 +00:00
crypto / crypto , # cryptographic functions
2020-04-21 01:24:42 +00:00
errors , # error handling utilities
2020-04-08 17:21:06 +00:00
protocols / identify , # identify the peer info of a peer
2020-06-19 17:29:43 +00:00
stream / connection , # create and close stream read / write connections
2020-04-08 17:21:06 +00:00
transports / transport , # listen and dial to other peers using p2p protocol
transports / tcptransport , # listen and dial to other peers using client-server protocol
multiaddress , # encode different addressing schemes. For example, /ip4/7.7.7.7/tcp/6543 means it is using IPv4 protocol and TCP
2020-05-08 20:58:23 +00:00
peerinfo , # manage the information of a peer, such as peer ID and public / private key
2020-07-01 06:25:09 +00:00
peerid , # Implement how peers interact
2020-04-08 17:21:06 +00:00
protocols / protocol , # define the protocol base type
protocols / secure / secure , # define the protocol of secure connection
protocols / secure / secio , # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS
muxers / muxer , # define an interface for stream multiplexing, allowing peers to offer many protocols over a single connection
2020-09-14 08:19:54 +00:00
muxers / mplex / mplex ] # define some contants and message types for stream multiplexing
2019-09-12 21:54:12 +00:00
const ChatCodec = " /nim-libp2p/chat/1.0.0 "
2019-09-13 00:05:20 +00:00
const DefaultAddr = " /ip4/127.0.0.1/tcp/55505 "
2019-09-12 23:20:30 +00:00
const Help = """
Commands : / [ ? | hep | connect | disconnect | exit ]
help : Prints this help
connect : dials a remote peer
disconnect : ends current session
exit : closes the chat
"""
2020-04-08 17:21:06 +00:00
type ChatProto = ref object of LPProtocol
switch : Switch # a single entry point for dialing and listening to peer
2020-05-08 20:58:23 +00:00
transp : StreamTransport # transport streams between read & write file descriptor
2020-04-08 17:21:06 +00:00
conn : Connection # create and close read & write stream
connected : bool # if the node is connected to another peer
started : bool # if the node has started
2019-09-13 00:05:20 +00:00
2021-06-28 02:37:00 +00:00
proc readAndPrint ( p : ChatProto ) {. async . } =
while true :
var strData = await p . conn . readLp ( 1024 )
strData & = ' \0 ' . uint8
var str = cast [ cstring ] ( addr strdata [ 0 ] )
echo $ p . switch . peerInfo . peerId & " : " & $ str
await sleepAsync ( 100 . millis )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc dialPeer ( p : ChatProto , address : string ) {. async . } =
2021-06-28 02:37:00 +00:00
let
multiAddr = MultiAddress . init ( address ) . tryGet ( )
# split the peerId part /p2p/...
peerIdBytes = multiAddr [ multiCodec ( " p2p " ) ]
. tryGet ( )
. protoAddress ( )
. tryGet ( )
remotePeer = PeerID . init ( peerIdBytes ) . tryGet ( )
# split the wire address
ip4Addr = multiAddr [ multiCodec ( " ip4 " ) ] . tryGet ( )
tcpAddr = multiAddr [ multiCodec ( " tcp " ) ] . tryGet ( )
wireAddr = ip4Addr & tcpAddr
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
echo & " dialing peer: {multiAddr} "
2021-06-28 02:37:00 +00:00
p . conn = await p . switch . dial ( remotePeer , @ [ wireAddr ] , ChatCodec )
2019-09-12 21:54:12 +00:00
p . connected = true
2021-06-28 02:37:00 +00:00
asyncSpawn p . readAndPrint ( )
2020-04-08 17:21:06 +00:00
proc writeAndPrint ( p : ChatProto ) {. async . } =
2019-09-12 21:54:12 +00:00
while true :
2019-09-13 00:05:20 +00:00
if not p . connected :
2019-09-12 21:54:12 +00:00
echo " type an address or wait for a connection: "
2019-09-12 23:20:30 +00:00
echo " type /[help|?] for help "
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
let line = await p . transp . readLine ( )
2019-09-12 23:20:30 +00:00
if line . startsWith ( " /help " ) or line . startsWith ( " /? " ) or not p . started :
echo Help
continue
2019-12-08 21:06:58 +00:00
2019-09-12 23:20:30 +00:00
if line . startsWith ( " /disconnect " ) :
echo " Ending current session "
2019-09-13 00:05:20 +00:00
if p . connected and p . conn . closed . not :
await p . conn . close ( )
2019-09-12 23:20:30 +00:00
p . connected = false
elif line . startsWith ( " /connect " ) :
if p . connected :
2019-09-14 13:54:09 +00:00
var yesno = " N "
2019-09-12 23:20:30 +00:00
echo " a session is already in progress, do you want end it [y/N]? "
2019-09-14 13:54:09 +00:00
yesno = await p . transp . readLine ( )
2019-09-12 23:20:30 +00:00
if yesno . cmpIgnoreCase ( " y " ) = = 0 :
await p . conn . close ( )
p . connected = false
2019-12-08 21:06:58 +00:00
elif yesno . cmpIgnoreCase ( " n " ) = = 0 :
2019-09-12 23:20:30 +00:00
continue
else :
echo " unrecognized response "
continue
echo " enter address of remote peer "
let address = await p . transp . readLine ( )
if address . len > 0 :
await p . dialPeer ( address )
elif line . startsWith ( " /exit " ) :
2019-09-13 00:05:20 +00:00
if p . connected and p . conn . closed . not :
await p . conn . close ( )
p . connected = false
await p . switch . stop ( )
echo " quitting... "
2019-09-12 23:20:30 +00:00
quit ( 0 )
2019-09-12 21:54:12 +00:00
else :
2019-09-12 23:20:30 +00:00
if p . connected :
await p . conn . writeLp ( line )
else :
try :
2021-06-28 02:37:00 +00:00
if line . startsWith ( " / " ) and " p2p " in line :
2019-09-12 23:20:30 +00:00
await p . dialPeer ( line )
except :
2019-10-04 21:20:19 +00:00
echo & " unable to dial remote peer {line} "
2020-04-08 17:21:06 +00:00
echo getCurrentExceptionMsg ( )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc readWriteLoop ( p : ChatProto ) {. async . } =
2021-06-28 02:37:00 +00:00
await p . writeAndPrint ( )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc newChatProto ( switch : Switch , transp : StreamTransport ) : ChatProto =
2020-09-21 09:16:29 +00:00
var chatproto = ChatProto ( switch : switch , transp : transp , codecs : @ [ ChatCodec ] )
2020-04-08 17:21:06 +00:00
# create handler for incoming connection
proc handle ( stream : Connection , proto : string ) {. async . } =
if chatproto . connected and not chatproto . conn . closed :
2019-09-12 23:20:30 +00:00
echo " a chat session is already in progress - disconnecting! "
await stream . close ( )
2019-12-08 21:06:58 +00:00
else :
2020-04-08 17:21:06 +00:00
chatproto . conn = stream
chatproto . connected = true
2021-06-28 02:37:00 +00:00
await chatproto . readAndPrint ( )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
# assign the new handler
chatproto . handler = handle
return chatproto
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc readInput ( wfd : AsyncFD ) {. thread . } =
2019-09-12 21:54:12 +00:00
## This procedure performs reading from `stdin` and sends data over
## pipe to main thread.
2020-04-08 17:21:06 +00:00
let transp = fromPipe ( wfd )
2019-12-08 21:06:58 +00:00
2019-09-12 21:54:12 +00:00
while true :
2020-04-08 17:21:06 +00:00
let line = stdin . readLine ( )
2019-09-25 19:33:50 +00:00
discard waitFor transp . write ( line & " \r \n " )
2019-09-12 21:54:12 +00:00
2020-07-07 11:14:11 +00:00
proc processInput ( rfd : AsyncFD , rng : ref BrHmacDrbgContext ) {. async . } =
2020-04-08 17:21:06 +00:00
let transp = fromPipe ( rfd )
2019-09-12 21:54:12 +00:00
2020-07-07 11:14:11 +00:00
let seckey = PrivateKey . random ( RSA , rng [ ] ) . get ( )
2019-09-13 00:05:20 +00:00
var localAddress = DefaultAddr
while true :
echo & " Type an address to bind to or Enter to use the default {DefaultAddr} "
let a = await transp . readLine ( )
try :
if a . len > 0 :
2021-06-28 02:37:00 +00:00
localAddress = a
2019-09-13 00:05:20 +00:00
break
2021-06-28 02:37:00 +00:00
# uise default
2019-09-13 00:05:20 +00:00
break
except :
echo " invalid address "
localAddress = DefaultAddr
continue
2021-06-28 02:37:00 +00:00
var switch = SwitchBuilder
. init ( )
. withRng ( rng )
. withPrivateKey ( seckey )
. withAddress ( MultiAddress . init ( localAddress ) . tryGet ( ) )
. build ( )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
let chatProto = newChatProto ( switch , transp )
2019-09-12 21:54:12 +00:00
switch . mount ( chatProto )
2020-04-08 17:21:06 +00:00
let libp2pFuts = await switch . start ( )
2019-09-12 23:20:30 +00:00
chatProto . started = true
2021-06-28 02:37:00 +00:00
let id = $ switch . peerInfo . peerId
2019-09-12 23:20:30 +00:00
echo " PeerID: " & id
2019-09-12 21:54:12 +00:00
echo " listening on: "
2021-06-28 02:37:00 +00:00
for a in switch . peerInfo . addrs :
echo & " {a}/p2p/{id} "
2019-09-12 21:54:12 +00:00
await chatProto . readWriteLoop ( )
2020-04-21 01:24:42 +00:00
await allFuturesThrowing ( libp2pFuts )
2019-09-12 21:54:12 +00:00
proc main ( ) {. async . } =
2020-07-07 11:14:11 +00:00
let rng = newRng ( ) # Singe random number source for the whole application
2020-04-08 17:21:06 +00:00
let ( rfd , wfd ) = createAsyncPipe ( )
2019-09-12 21:54:12 +00:00
if rfd = = asyncInvalidPipe or wfd = = asyncInvalidPipe :
raise newException ( ValueError , " Could not initialize pipe! " )
2019-12-08 21:06:58 +00:00
2019-09-12 21:54:12 +00:00
var thread : Thread [ AsyncFD ]
2020-04-08 17:21:06 +00:00
thread . createThread ( readInput , wfd )
2020-07-07 11:14:11 +00:00
await processInput ( rfd , rng )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
when isMainModule : # isMainModule = true when the module is compiled as the main file
2019-09-12 21:54:12 +00:00
waitFor ( main ( ) )