2019-09-12 23:20:30 +00:00
when not ( compileOption ( " threads " ) ) :
{. fatal : " Please, compile this program with the --threads:on option! " . }
2020-04-08 17:21:06 +00:00
import tables , strformat , strutils
import chronos # an efficient library for async
import .. / libp2p / [ switch , # manage transports, a single entry point for dialing and listening
multistream , # tag stream with short header to identify it
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
connection , # create and close stream read / write connections
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
peerinfo , # manage the information of a peer, such as peer ID and public / private key
peer , # Implement how peers interact
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
muxers / mplex / mplex , # implement stream multiplexing
muxers / mplex / types ] # 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
transp : StreamTransport # transport streams between read & write file descriptor
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
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc initAddress ( T : type MultiAddress , str : string ) : T =
let address = MultiAddress . init ( str )
if IPFS . match ( address ) and matchPartial ( multiaddress . TCP , address ) :
result = address
else :
raise newException ( MultiAddressError ,
" Invalid bootstrap node multi-address " )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc dialPeer ( p : ChatProto , address : string ) {. async . } =
let multiAddr = MultiAddress . initAddress ( address ) ;
let parts = address . split ( " / " )
let remotePeer = PeerInfo . init ( parts [ ^ 1 ] ,
[ multiAddr ] )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
echo & " dialing peer: {multiAddr} "
2019-09-12 21:54:12 +00:00
p . conn = await p . switch . dial ( remotePeer , ChatCodec )
p . connected = true
2020-04-08 17:21:06 +00:00
proc readAndPrint ( p : ChatProto ) {. async . } =
while true :
while p . connected :
# TODO: echo &"{p.id} -> "
echo cast [ string ] ( await p . conn . readLp ( ) )
await sleepAsync ( 100 . millis )
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 :
if line . startsWith ( " / " ) and " ipfs " in line :
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 . } =
asyncCheck p . writeAndPrint ( ) # execute the async function but does not block
2019-12-08 21:06:58 +00:00
asyncCheck p . readAndPrint ( )
2019-09-12 21:54:12 +00:00
2020-04-08 17:21:06 +00:00
proc newChatProto ( switch : Switch , transp : StreamTransport ) : ChatProto =
var chatproto = ChatProto ( switch : switch , transp : transp , codec : ChatCodec )
# 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
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-04-08 17:21:06 +00:00
proc processInput ( rfd : AsyncFD ) {. async . } =
let transp = fromPipe ( rfd )
2019-09-12 21:54:12 +00:00
let seckey = PrivateKey . random ( RSA )
2020-04-08 17:21:06 +00:00
let peerInfo = PeerInfo . init ( seckey )
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 :
peerInfo . addrs . add ( Multiaddress . init ( a ) )
break
peerInfo . addrs . add ( Multiaddress . init ( localAddress ) )
break
except :
echo " invalid address "
localAddress = DefaultAddr
continue
2020-04-08 17:21:06 +00:00
# a constructor for building different multiplexers under various connections
2019-09-12 21:54:12 +00:00
proc createMplex ( conn : Connection ) : Muxer =
result = newMplex ( conn )
2020-04-08 17:21:06 +00:00
let mplexProvider = newMuxerProvider ( createMplex , MplexCodec )
let transports = @ [ Transport ( newTransport ( TcpTransport ) ) ]
let muxers = [ ( MplexCodec , mplexProvider ) ] . toTable ( )
let identify = newIdentify ( peerInfo )
let secureManagers = [ ( SecioCodec , Secure ( newSecio ( seckey ) ) ) ] . toTable ( )
let switch = newSwitch ( peerInfo ,
2019-10-04 21:20:19 +00:00
transports ,
identify ,
muxers ,
2020-04-08 17:21:06 +00:00
secureManagers )
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
2019-12-10 20:50:35 +00:00
let id = peerInfo . peerId . pretty
2019-09-12 23:20:30 +00:00
echo " PeerID: " & id
2019-09-12 21:54:12 +00:00
echo " listening on: "
2019-09-12 23:20:30 +00:00
for a in peerInfo . addrs :
echo & " {a}/ipfs/{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-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 )
await processInput ( rfd )
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 ( ) )