Commit d91fc254 authored by Abhishek Upperwal's avatar Abhishek Upperwal
Browse files

Added example "chat" with README

parent bd3e85cb
......@@ -11,4 +11,5 @@ Let us know if you find any issue or if you want to contribute and add a new tut
- [Building an http proxy with libp2p](./http-proxy)
- [Protocol Multiplexing with multicodecs](./protocol-multiplexing-with-multicodecs)
- [An echo host](./echo)
- [Multicodecs with protobufs](./multipro)
\ No newline at end of file
- [Multicodecs with protobufs](./multipro)
- [P2P chat application](./chat)
\ No newline at end of file
# p2p chat app with libp2p
This program demonstrates a simple p2p chat application. It can work between two peers if
1. Both have private IP address (same network).
2. At least one of them has a public IP address.
Assume if 'A' and 'B' are on different networks host 'A' may or may not have a public IP address but host 'B' has one.
Usage: Run `./chat -sp <SOURCE_PORT>` on host 'B' where <SOURCE_PORT> can be any port number. Now run `./chat -d <MULTIADDR_B>` on node 'A' [`<MULTIADDR_B>` is multiaddress of host 'B' which can be obtained from host 'B' console].
## Build
To build the example, first run `make deps` in the root directory.
```
> make deps
> go build ./examples/chat
```
## Usage
On node 'B'
```
> ./chat -sp 3001
Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
2018/02/27 01:21:32 Got a new stream!
> hi (received messages in green colour)
> hello (sent messages in white colour)
> no
```
On node 'A'. Replace 127.0.0.1 with <PUBLIC_IP> if node 'B' has one.
```
> ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
This node's multiaddress:
/ip4/0.0.0.0/tcp/0/ipfs/QmWVx9NwsgaVWMRHNCpesq1WQAw2T3JurjGDNeVNWifPS7
> hi
> hello
```
**NOTE: debug mode is enabled by default, debug mode will always generate same node id (on each node) on every execution. Disable debug using `--debug false` flag while running your executable.**
## Authors
1. Abhishek Upperwal
\ No newline at end of file
/*
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Juan Batiz-Benet
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* This program demonstrate a simple chat application using p2p communication.
*
*/
package main
import (
"bufio"
"context"
"crypto/rand"
"flag"
"fmt"
"io"
"log"
mrand "math/rand"
"os"
"github.com/libp2p/go-libp2p-crypto"
"github.com/libp2p/go-libp2p-host"
"github.com/libp2p/go-libp2p-net"
"github.com/libp2p/go-libp2p-peer"
"github.com/libp2p/go-libp2p-peerstore"
"github.com/libp2p/go-libp2p-swarm"
"github.com/libp2p/go-libp2p/p2p/host/basic"
"github.com/multiformats/go-multiaddr"
)
/*
* addAddrToPeerstore parses a peer multiaddress and adds
* it to the given host's peerstore, so it knows how to
* contact it. It returns the peer ID of the remote peer.
* @credit examples/http-proxy/proxy.go
*/
func addAddrToPeerstore(h host.Host, addr string) peer.ID {
// The following code extracts target's the peer ID from the
// given multiaddress
ipfsaddr, err := multiaddr.NewMultiaddr(addr)
if err != nil {
log.Fatalln(err)
}
pid, err := ipfsaddr.ValueForProtocol(multiaddr.P_IPFS)
if err != nil {
log.Fatalln(err)
}
peerid, err := peer.IDB58Decode(pid)
if err != nil {
log.Fatalln(err)
}
// Decapsulate the /ipfs/<peerID> part from the target
// /ip4/<a.b.c.d>/ipfs/<peer> becomes /ip4/<a.b.c.d>
targetPeerAddr, _ := multiaddr.NewMultiaddr(
fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid)))
targetAddr := ipfsaddr.Decapsulate(targetPeerAddr)
// We have a peer ID and a targetAddr so we add
// it to the peerstore so LibP2P knows how to contact it
h.Peerstore().AddAddr(peerid, targetAddr, peerstore.PermanentAddrTTL)
return peerid
}
func handleStream(s net.Stream) {
log.Println("Got a new stream!")
// Create a buffer stream for non blocking read and write.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
go readData(rw)
go writeData(rw)
// stream 's' will stay open until you close it (or the other side closes it).
}
func readData(rw *bufio.ReadWriter) {
for {
str, _ := rw.ReadString('\n')
if str == "" {
return
}
if str != "\n" {
// Green console colour: \x1b[32m
// Reset console colour: \x1b[0m
fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
}
}
}
func writeData(rw *bufio.ReadWriter) {
stdReader := bufio.NewReader(os.Stdin)
for {
fmt.Print("> ")
sendData, err := stdReader.ReadString('\n')
if err != nil {
panic(err)
}
rw.WriteString(fmt.Sprintf("%s\n", sendData))
rw.Flush()
}
}
func main() {
sourcePort := flag.Int("sp", 0, "Source port number")
dest := flag.String("d", "", "Dest MultiAddr String")
help := flag.Bool("help", false, "Display Help")
debug := flag.Bool("debug", true, "Debug generated same node id on every execution.")
flag.Parse()
if *help {
fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n")
fmt.Printf("Usage: Run './chat -sp <SOURCE_PORT>' where <SOURCE_PORT> can be any port number. Now run './chat -d <MULTIADDR>' where <MULTIADDR> is multiaddress of previous listener host.\n")
os.Exit(0)
}
// If debug is enabled used constant random source else cryptographic randomness.
var r io.Reader
if *debug {
// Constant random source. This will always generate the same host ID on multiple execution.
// Don't do this in production code.
r = mrand.New(mrand.NewSource(int64(*sourcePort)))
} else {
r = rand.Reader
}
// Creates a new RSA key pair for this host
prvKey, pubKey, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
if err != nil {
panic(err)
}
// Getting host ID from public key.
// host ID is the hash of public key
nodeID, _ := peer.IDFromPublicKey(pubKey)
// 0.0.0.0 will listen on any interface device
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort))
// Adding self to the peerstore.
ps := peerstore.NewPeerstore()
ps.AddPrivKey(nodeID, prvKey)
ps.AddPubKey(nodeID, pubKey)
// Creating a new Swarm network.
network, err := swarm.NewNetwork(context.Background(), []multiaddr.Multiaddr{sourceMultiAddr}, nodeID, ps, nil)
if err != nil {
panic(err)
}
// NewHost constructs a new *BasicHost and activates it by attaching its
// stream and connection handlers to the given inet.Network (network).
// Other options like NATManager can also be added here.
// See docs: https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#HostOpts
host := basichost.New(network)
if *dest == "" {
// Set a function as stream handler.
// This function is called when a peer initiate a connection and starts a stream with this peer.
// Only applicable on the receiving side.
host.SetStreamHandler("/chat/1.0.0", handleStream)
fmt.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%d/ipfs/%s' on another console.\n You can replace 127.0.0.1 with public IP as well.\n", *sourcePort, host.ID().Pretty())
fmt.Printf("\nWaiting for incoming connection\n\n")
// Hang forever
<-make(chan struct{})
} else {
// Add destination peer multiaddress in the peerstore.
// This will be used during connection and stream creation by libp2p.
peerID := addAddrToPeerstore(host, *dest)
fmt.Println("This node's multiaddress: ")
// IP will be 0.0.0.0 (listen on any interface) and port will be 0 (choose one for me).
// Although this node will not listen for any connection. It will just initiate a connect with
// one of its peer and use that stream to communicate.
fmt.Printf("%s/ipfs/%s\n", sourceMultiAddr, host.ID().Pretty())
// Start a stream with peer with peer Id: 'peerId'.
// Multiaddress of the destination peer is fetched from the peerstore using 'peerId'.
s, err := host.NewStream(context.Background(), peerID, "/chat/1.0.0")
if err != nil {
panic(err)
}
// Create a buffered stream so that read and writes are non blocking.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
// Create a thread to read and write data.
go writeData(rw)
go readData(rw)
// Hang forever.
select {}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment