diff --git a/.gitignore b/.gitignore
index cd7b1ceab449fd773f54ce34c3bb998c63da400c..895adf13948865ec49abdb8341f3ee692dfb1002 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
*.swp
examples/echo/echo
examples/multicodecs/multicodecs
+.idea
\ No newline at end of file
diff --git a/README.md b/README.md
index 1e60e3979446ee639aeea7ce072611ab8c009dbb..b44b25dec3513099e66976052fbdadc8c50d49f1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+
diff --git a/examples/README.md b/examples/README.md
index 6a1a8ef6df59081423ebb66d698d77bd686c6dd7..f2e1bd52dc1531ce8069b15d0101aa4c062192ed 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,3 +1,4 @@
+
# `go-libp2p` examples and tutorials
In this folder, you can find a variety of examples to help you get started in using go-libp2p. Every example as a specific purpose and some of each incorporate a full tutorial that you can follow through, helping you expand your knowledge about libp2p and p2p networks in general.
@@ -10,3 +11,4 @@ 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
diff --git a/examples/multipro/AUTHORS b/examples/multipro/AUTHORS
new file mode 100644
index 0000000000000000000000000000000000000000..70a81f0a5dde9671ce3c48994d9eb55a777193c6
--- /dev/null
+++ b/examples/multipro/AUTHORS
@@ -0,0 +1,3 @@
+# This is the official list of authors for copyright purposes.
+
+Aviv Eyal
diff --git a/examples/multipro/LICENSE b/examples/multipro/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..df71bca6adf2a477071a6a1858529eecf004e4c3
--- /dev/null
+++ b/examples/multipro/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Aviv Eyal
+
+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.
diff --git a/examples/multipro/README.md b/examples/multipro/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c60da8b173730ec4d5cfe4e19a606502adf44195
--- /dev/null
+++ b/examples/multipro/README.md
@@ -0,0 +1,62 @@
+# Protocol Multiplexing using rpc-style multicodecs, protobufs with libp2p
+
+This examples shows how to use multicodecs (i.e. protobufs) to encode and transmit information between LibP2P hosts using LibP2P Streams.
+Multicodecs present a common interface, making it very easy to swap the codec implementation if needed.
+This example expects that you area already familiar with the [echo example](https://github.com/libp2p/go-libp2p/tree/master/examples/echo).
+
+## Build
+
+Install gx:
+```sh
+> go get -u github.com/whyrusleeping/gx
+
+```
+
+Run GX from the root libp2p source dir:
+```sh
+>gx install
+```
+
+Build libp2p:
+```sh
+> make deps
+> make
+```
+
+Run from `multipro` directory
+
+```sh
+> go build
+```
+
+
+## Usage
+
+```sh
+> ./multipro
+
+```
+
+## Details
+
+The example creates two LibP2P Hosts supporting 2 protocols: ping and echo.
+
+Each protocol consists RPC-style requests and responses and each request and response is a typed protobufs message (and a go data object).
+
+This is a different pattern then defining a whole p2p protocol as one protobuf message with lots of optional fields (as can be observed in various p2p-lib protocols using protobufs such as dht).
+
+The example shows how to match async received responses with their requests. This is useful when processing a response requires access to the request data.
+
+The idea is to use lib-p2p protocol multiplexing on a per-message basis.
+
+### Features
+1. 2 fully implemented protocols using an RPC-like request-response pattern - Ping and Echo
+2. Scaffolding for quickly implementing new app-level versioned RPC-like protocols
+3. Full authentication of incoming message data by author (who might not be the message's sender peer)
+4. Base p2p format in protobufs with fields shared by all protocol messages
+5. Full access to request data when processing a response.
+
+## Author
+@avive
+
+
diff --git a/examples/multipro/echo.go b/examples/multipro/echo.go
new file mode 100644
index 0000000000000000000000000000000000000000..dd281bd0ef5bfe660635767b9e82eb8f58e6aa62
--- /dev/null
+++ b/examples/multipro/echo.go
@@ -0,0 +1,157 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "log"
+
+ inet "github.com/libp2p/go-libp2p-net"
+
+ "github.com/libp2p/go-libp2p-host"
+ p2p "github.com/libp2p/go-libp2p/examples/multipro/pb"
+ protobufCodec "github.com/multiformats/go-multicodec/protobuf"
+ uuid "github.com/satori/go.uuid"
+)
+
+// pattern: /protocol-name/request-or-response-message/version
+const echoRequest = "/echo/echoreq/0.0.1"
+const echoResponse = "/echo/echoresp/0.0.1"
+
+type EchoProtocol struct {
+ node *Node // local host
+ requests map[string]*p2p.EchoRequest // used to access request data from response handlers
+ done chan bool // only for demo purposes to hold main from terminating
+}
+
+func NewEchoProtocol(node *Node, done chan bool) *EchoProtocol {
+ e := EchoProtocol{node: node, requests: make(map[string]*p2p.EchoRequest), done: done}
+ node.SetStreamHandler(echoRequest, e.onEchoRequest)
+ node.SetStreamHandler(echoResponse, e.onEchoResponse)
+
+ // design note: to implement fire-and-forget style messages you may just skip specifying a response callback.
+ // a fire-and-forget message will just include a request and not specify a response object
+
+ return &e
+}
+
+// remote peer requests handler
+func (e *EchoProtocol) onEchoRequest(s inet.Stream) {
+ // get request data
+ data := &p2p.EchoRequest{}
+ decoder := protobufCodec.Multicodec(nil).Decoder(bufio.NewReader(s))
+ err := decoder.Decode(data)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ log.Printf("%s: Received echo request from %s. Message: %s", s.Conn().LocalPeer(), s.Conn().RemotePeer(), data.Message)
+
+ valid := e.node.authenticateMessage(data, data.MessageData)
+
+ if !valid {
+ log.Println("Failed to authenticate message")
+ return
+ }
+
+ log.Printf("%s: Sending echo response to %s. Message id: %s...", s.Conn().LocalPeer(), s.Conn().RemotePeer(), data.MessageData.Id)
+
+ // send response to the request using the message string he provided
+
+ resp := &p2p.EchoResponse{
+ MessageData: e.node.NewMessageData(data.MessageData.Id, false),
+ Message: data.Message}
+
+ // sign the data
+ signature, err := e.node.signProtoMessage(resp)
+ if err != nil {
+ log.Println("failed to sign response")
+ return
+ }
+
+ // add the signature to the message
+ resp.MessageData.Sign = string(signature)
+
+ s, respErr := e.node.NewStream(context.Background(), s.Conn().RemotePeer(), echoResponse)
+ if respErr != nil {
+ log.Println(respErr)
+ return
+ }
+
+ ok := e.node.sendProtoMessage(resp, s)
+
+ if ok {
+ log.Printf("%s: Echo response to %s sent.", s.Conn().LocalPeer().String(), s.Conn().RemotePeer().String())
+ }
+}
+
+// remote echo response handler
+func (e *EchoProtocol) onEchoResponse(s inet.Stream) {
+ data := &p2p.EchoResponse{}
+ decoder := protobufCodec.Multicodec(nil).Decoder(bufio.NewReader(s))
+ err := decoder.Decode(data)
+ if err != nil {
+ return
+ }
+
+ // authenticate message content
+ valid := e.node.authenticateMessage(data, data.MessageData)
+
+ if !valid {
+ log.Println("Failed to authenticate message")
+ return
+ }
+
+ // locate request data and remove it if found
+ req, ok := e.requests[data.MessageData.Id]
+ if ok {
+ // remove request from map as we have processed it here
+ delete(e.requests, data.MessageData.Id)
+ } else {
+ log.Println("Failed to locate request data boject for response")
+ return
+ }
+
+ if req.Message != data.Message {
+ log.Fatalln("Expected echo to respond with request message")
+ }
+
+ log.Printf("%s: Received echo response from %s. Message id:%s. Message: %s.", s.Conn().LocalPeer(), s.Conn().RemotePeer(), data.MessageData.Id, data.Message)
+ e.done <- true
+}
+
+func (e *EchoProtocol) Echo(host host.Host) bool {
+ log.Printf("%s: Sending echo to: %s....", e.node.ID(), host.ID())
+
+ // create message data
+ req := &p2p.EchoRequest{
+ MessageData: e.node.NewMessageData(uuid.Must(uuid.NewV4()).String(), false),
+ Message: fmt.Sprintf("Echo from %s", e.node.ID())}
+
+ signature, err := e.node.signProtoMessage(req)
+ if err != nil {
+ log.Println("failed to sign message")
+ return false
+ }
+
+ // add the signature to the message
+ req.MessageData.Sign = string(signature)
+
+ s, err := e.node.NewStream(context.Background(), host.ID(), echoRequest)
+ if err != nil {
+ log.Println(err)
+ return false
+ }
+
+ ok := e.node.sendProtoMessage(req, s)
+
+ if !ok {
+ return false
+ }
+
+ // store request so response handler has access to it
+ e.requests[req.MessageData.Id] = req
+ log.Printf("%s: Echo to: %s was sent. Message Id: %s, Message: %s", e.node.ID(), host.ID(), req.MessageData.Id, req.Message)
+ return true
+}
diff --git a/examples/multipro/main.go b/examples/multipro/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..4f9e144c2ab03c9466a26c5ad8778ea9efbc6c9b
--- /dev/null
+++ b/examples/multipro/main.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "math/rand"
+
+ crypto "github.com/libp2p/go-libp2p-crypto"
+ peer "github.com/libp2p/go-libp2p-peer"
+ ps "github.com/libp2p/go-libp2p-peerstore"
+ swarm "github.com/libp2p/go-libp2p-swarm"
+ bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
+ ma "github.com/multiformats/go-multiaddr"
+)
+
+// helper method - create a lib-p2p host to listen on a port
+func makeRandomNode(port int, done chan bool) *Node {
+ // Ignoring most errors for brevity
+ // See echo example for more details and better implementation
+ priv, pub, _ := crypto.GenerateKeyPair(crypto.Secp256k1, 256)
+ pid, _ := peer.IDFromPublicKey(pub)
+ listen, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port))
+ peerStore := ps.NewPeerstore()
+ peerStore.AddPrivKey(pid, priv)
+ peerStore.AddPubKey(pid, pub)
+ n, _ := swarm.NewNetwork(context.Background(), []ma.Multiaddr{listen}, pid, peerStore, nil)
+ host := bhost.New(n)
+
+ return NewNode(host, done)
+}
+
+func main() {
+ // Choose random ports between 10000-10100
+ rand.Seed(666)
+ port1 := rand.Intn(100) + 10000
+ port2 := port1 + 1
+
+ done := make(chan bool, 1)
+
+ // Make 2 hosts
+ h1 := makeRandomNode(port1, done)
+ h2 := makeRandomNode(port2, done)
+ h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), ps.PermanentAddrTTL)
+ h2.Peerstore().AddAddrs(h1.ID(), h1.Addrs(), ps.PermanentAddrTTL)
+
+ log.Printf("This is a conversation between %s and %s\n", h1.ID(), h2.ID())
+
+ // send messages using the protocols
+ h1.Ping(h2.Host)
+ h2.Ping(h1.Host)
+ h1.Echo(h2.Host)
+ h2.Echo(h1.Host)
+
+ // block until all responses have been processed
+ for i := 0; i < 4; i++ {
+ <-done
+ }
+}
diff --git a/examples/multipro/node.go b/examples/multipro/node.go
new file mode 100644
index 0000000000000000000000000000000000000000..58b42330e13fcd428e47fe38efad423afeb82f25
--- /dev/null
+++ b/examples/multipro/node.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "bufio"
+ "log"
+ "time"
+
+ "github.com/gogo/protobuf/proto"
+ crypto "github.com/libp2p/go-libp2p-crypto"
+ host "github.com/libp2p/go-libp2p-host"
+ inet "github.com/libp2p/go-libp2p-net"
+ peer "github.com/libp2p/go-libp2p-peer"
+ p2p "github.com/libp2p/go-libp2p/examples/multipro/pb"
+ protobufCodec "github.com/multiformats/go-multicodec/protobuf"
+)
+
+// node client version
+const clientVersion = "go-p2p-node/0.0.1"
+
+// Node type - a p2p host implementing one or more p2p protocols
+type Node struct {
+ host.Host // lib-p2p host
+ *PingProtocol // ping protocol impl
+ *EchoProtocol // echo protocol impl
+ // add other protocols here...
+}
+
+// Create a new node with its implemented protocols
+func NewNode(host host.Host, done chan bool) *Node {
+ node := &Node{Host: host}
+ node.PingProtocol = NewPingProtocol(node, done)
+ node.EchoProtocol = NewEchoProtocol(node, done)
+ return node
+}
+
+// Authenticate incoming p2p message
+// message: a protobufs go data object
+// data: common p2p message data
+func (n *Node) authenticateMessage(message proto.Message, data *p2p.MessageData) bool {
+ // store a temp ref to signature and remove it from message data
+ // sign is a string to allow easy reset to zero-value (empty string)
+ sign := data.Sign
+ data.Sign = ""
+
+ // marshall data without the signature to protobufs3 binary format
+ bin, err := proto.Marshal(message)
+ if err != nil {
+ log.Println(err, "failed to marshal pb message")
+ return false
+ }
+
+ // restore sig in message data (for possible future use)
+ data.Sign = sign
+
+ // restore peer id binary format from base58 encoded node id data
+ peerId, err := peer.IDB58Decode(data.NodeId)
+ if err != nil {
+ log.Println(err, "Failed to decode node id from base58")
+ return false
+ }
+
+ // verify the data was authored by the signing peer identified by the public key
+ // and signature included in the message
+ return n.verifyData(bin, []byte(sign), peerId, data.NodePubKey)
+}
+
+// sign an outgoing p2p message payload
+func (n *Node) signProtoMessage(message proto.Message) ([]byte, error) {
+ data, err := proto.Marshal(message)
+ if err != nil {
+ return nil, err
+ }
+ return n.signData(data)
+}
+
+// sign binary data using the local node's private key
+func (n *Node) signData(data []byte) ([]byte, error) {
+ key := n.Peerstore().PrivKey(n.ID())
+ res, err := key.Sign(data)
+ return res, err
+}
+
+// Verify incoming p2p message data integrity
+// data: data to verify
+// signature: author signature provided in the message payload
+// peerId: author peer id from the message payload
+// pubKeyData: author public key from the message payload
+func (n *Node) verifyData(data []byte, signature []byte, peerId peer.ID, pubKeyData []byte) bool {
+ key, err := crypto.UnmarshalPublicKey(pubKeyData)
+ if err != nil {
+ log.Println(err, "Failed to extract key from message key data")
+ return false
+ }
+
+ // extract node id from the provided public key
+ idFromKey, err := peer.IDFromPublicKey(key)
+
+ if err != nil {
+ log.Println(err, "Failed to extract peer id from public key")
+ return false
+ }
+
+ // verify that message author node id matches the provided node public key
+ if idFromKey != peerId {
+ log.Println(err, "Node id and provided public key mismatch")
+ return false
+ }
+
+ res, err := key.Verify(data, signature)
+ if err != nil {
+ log.Println(err, "Error authenticating data")
+ return false
+ }
+
+ return res
+}
+
+// helper method - generate message data shared between all node's p2p protocols
+// messageId: unique for requests, copied from request for responses
+func (n *Node) NewMessageData(messageId string, gossip bool) *p2p.MessageData {
+ // Add protobufs bin data for message author public key
+ // this is useful for authenticating messages forwarded by a node authored by another node
+ nodePubKey, err := n.Peerstore().PubKey(n.ID()).Bytes()
+
+ if err != nil {
+ panic("Failed to get public key for sender from local peer store.")
+ }
+
+ return &p2p.MessageData{ClientVersion: clientVersion,
+ NodeId: peer.IDB58Encode(n.ID()),
+ NodePubKey: nodePubKey,
+ Timestamp: time.Now().Unix(),
+ Id: messageId,
+ Gossip: gossip}
+}
+
+// helper method - writes a protobuf go data object to a network stream
+// data: reference of protobuf go data object to send (not the object itself)
+// s: network stream to write the data to
+func (n *Node) sendProtoMessage(data proto.Message, s inet.Stream) bool {
+ writer := bufio.NewWriter(s)
+ enc := protobufCodec.Multicodec(nil).Encoder(writer)
+ err := enc.Encode(data)
+ if err != nil {
+ log.Println(err)
+ return false
+ }
+ writer.Flush()
+ return true
+}
diff --git a/examples/multipro/pb/p2p.pb.go b/examples/multipro/pb/p2p.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..091ef4afec89702fe832217c469123955a804dee
--- /dev/null
+++ b/examples/multipro/pb/p2p.pb.go
@@ -0,0 +1,121 @@
+// Code generated by protoc-gen-gogo.
+// source: p2p.proto
+// DO NOT EDIT!
+
+/*
+Package protocols_p2p is a generated protocol buffer package.
+
+It is generated from these files:
+ p2p.proto
+
+It has these top-level messages:
+ MessageData
+ PingRequest
+ PingResponse
+ EchoRequest
+ EchoResponse
+*/
+package protocols_p2p
+
+import proto "github.com/gogo/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// designed to be shared between all app protocols
+type MessageData struct {
+ // shared between all requests
+ ClientVersion string `protobuf:"bytes,1,opt,name=clientVersion,proto3" json:"clientVersion,omitempty"`
+ Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+ Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
+ Gossip bool `protobuf:"varint,4,opt,name=gossip,proto3" json:"gossip,omitempty"`
+ NodeId string `protobuf:"bytes,5,opt,name=nodeId,proto3" json:"nodeId,omitempty"`
+ NodePubKey []byte `protobuf:"bytes,6,opt,name=nodePubKey,proto3" json:"nodePubKey,omitempty"`
+ Sign string `protobuf:"bytes,7,opt,name=sign,proto3" json:"sign,omitempty"`
+}
+
+func (m *MessageData) Reset() { *m = MessageData{} }
+func (m *MessageData) String() string { return proto.CompactTextString(m) }
+func (*MessageData) ProtoMessage() {}
+
+// a protocol define a set of reuqest and responses
+type PingRequest struct {
+ MessageData *MessageData `protobuf:"bytes,1,opt,name=messageData" json:"messageData,omitempty"`
+ // method specific data
+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (m *PingRequest) Reset() { *m = PingRequest{} }
+func (m *PingRequest) String() string { return proto.CompactTextString(m) }
+func (*PingRequest) ProtoMessage() {}
+
+func (m *PingRequest) GetMessageData() *MessageData {
+ if m != nil {
+ return m.MessageData
+ }
+ return nil
+}
+
+type PingResponse struct {
+ MessageData *MessageData `protobuf:"bytes,1,opt,name=messageData" json:"messageData,omitempty"`
+ // response specific data
+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (m *PingResponse) Reset() { *m = PingResponse{} }
+func (m *PingResponse) String() string { return proto.CompactTextString(m) }
+func (*PingResponse) ProtoMessage() {}
+
+func (m *PingResponse) GetMessageData() *MessageData {
+ if m != nil {
+ return m.MessageData
+ }
+ return nil
+}
+
+// a protocol define a set of reuqest and responses
+type EchoRequest struct {
+ MessageData *MessageData `protobuf:"bytes,1,opt,name=messageData" json:"messageData,omitempty"`
+ // method specific data
+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (m *EchoRequest) Reset() { *m = EchoRequest{} }
+func (m *EchoRequest) String() string { return proto.CompactTextString(m) }
+func (*EchoRequest) ProtoMessage() {}
+
+func (m *EchoRequest) GetMessageData() *MessageData {
+ if m != nil {
+ return m.MessageData
+ }
+ return nil
+}
+
+type EchoResponse struct {
+ MessageData *MessageData `protobuf:"bytes,1,opt,name=messageData" json:"messageData,omitempty"`
+ // response specific data
+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (m *EchoResponse) Reset() { *m = EchoResponse{} }
+func (m *EchoResponse) String() string { return proto.CompactTextString(m) }
+func (*EchoResponse) ProtoMessage() {}
+
+func (m *EchoResponse) GetMessageData() *MessageData {
+ if m != nil {
+ return m.MessageData
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterType((*MessageData)(nil), "protocols.p2p.MessageData")
+ proto.RegisterType((*PingRequest)(nil), "protocols.p2p.PingRequest")
+ proto.RegisterType((*PingResponse)(nil), "protocols.p2p.PingResponse")
+ proto.RegisterType((*EchoRequest)(nil), "protocols.p2p.EchoRequest")
+ proto.RegisterType((*EchoResponse)(nil), "protocols.p2p.EchoResponse")
+}
diff --git a/examples/multipro/pb/p2p.proto b/examples/multipro/pb/p2p.proto
new file mode 100644
index 0000000000000000000000000000000000000000..53e652154b213b2f5d73de604c014a99ad09da86
--- /dev/null
+++ b/examples/multipro/pb/p2p.proto
@@ -0,0 +1,56 @@
+syntax = "proto3";
+
+package protocols.p2p;
+
+// designed to be shared between all app protocols
+message MessageData {
+ // shared between all requests
+ string clientVersion = 1; // client version
+ int64 timestamp = 2; // unix time
+ string id = 3; // allows requesters to use request data when processing a response
+ bool gossip = 4; // true to have receiver peer gossip the message to neighbors
+ string nodeId = 5; // id of node that created the message (not the peer that may have sent it). =base58(mh(sha256(nodePubKey)))
+ bytes nodePubKey = 6; // Authoring node Secp256k1 public key (32bytes) - protobufs serielized
+ string sign = 7; // signature of message data + method specific data by message authoring node. format: string([]bytes)
+}
+
+//// ping protocol
+
+// a protocol define a set of reuqest and responses
+message PingRequest {
+ MessageData messageData = 1;
+
+ // method specific data
+ string message = 2;
+ // add any data here....
+}
+
+message PingResponse {
+ MessageData messageData = 1;
+
+ // response specific data
+ string message = 2;
+
+ // ... add any additional message data here
+}
+
+//// echo protocol
+
+// a protocol define a set of reuqest and responses
+message EchoRequest {
+ MessageData messageData = 1;
+
+ // method specific data
+ string message = 2;
+
+ // add any additional message data here....
+}
+
+message EchoResponse {
+ MessageData messageData = 1;
+
+ // response specific data
+ string message = 2;
+
+ // ... add any additional message data here....
+}
diff --git a/examples/multipro/pb/readme.md b/examples/multipro/pb/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..ec7114f6add8544d129918ed3dc55167103002e2
--- /dev/null
+++ b/examples/multipro/pb/readme.md
@@ -0,0 +1,4 @@
+# building p2p.pb.go:
+protoc --gogo_out=. --proto_path=../../../../../../:/usr/local/opt/protobuf/include:. *.proto
+
+
diff --git a/examples/multipro/ping.go b/examples/multipro/ping.go
new file mode 100644
index 0000000000000000000000000000000000000000..bcc63dc1cf50918f45e01e3047862ec184394932
--- /dev/null
+++ b/examples/multipro/ping.go
@@ -0,0 +1,148 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/libp2p/go-libp2p-host"
+ inet "github.com/libp2p/go-libp2p-net"
+ p2p "github.com/libp2p/go-libp2p/examples/multipro/pb"
+ protobufCodec "github.com/multiformats/go-multicodec/protobuf"
+ uuid "github.com/satori/go.uuid"
+)
+
+// pattern: /protocol-name/request-or-response-message/version
+const pingRequest = "/ping/pingreq/0.0.1"
+const pingResponse = "/ping/pingresp/0.0.1"
+
+// PingProtocol type
+type PingProtocol struct {
+ node *Node // local host
+ requests map[string]*p2p.PingRequest // used to access request data from response handlers
+ done chan bool // only for demo purposes to stop main from terminating
+}
+
+func NewPingProtocol(node *Node, done chan bool) *PingProtocol {
+ p := &PingProtocol{node: node, requests: make(map[string]*p2p.PingRequest), done: done}
+ node.SetStreamHandler(pingRequest, p.onPingRequest)
+ node.SetStreamHandler(pingResponse, p.onPingResponse)
+ return p
+}
+
+// remote peer requests handler
+func (p *PingProtocol) onPingRequest(s inet.Stream) {
+
+ // get request data
+ data := &p2p.PingRequest{}
+ decoder := protobufCodec.Multicodec(nil).Decoder(bufio.NewReader(s))
+ err := decoder.Decode(data)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ log.Printf("%s: Received ping request from %s. Message: %s", s.Conn().LocalPeer(), s.Conn().RemotePeer(), data.Message)
+
+ valid := p.node.authenticateMessage(data, data.MessageData)
+
+ if !valid {
+ log.Println("Failed to authenticate message")
+ return
+ }
+
+ // generate response message
+ log.Printf("%s: Sending ping response to %s. Message id: %s...", s.Conn().LocalPeer(), s.Conn().RemotePeer(), data.MessageData.Id)
+
+ resp := &p2p.PingResponse{MessageData: p.node.NewMessageData(data.MessageData.Id, false),
+ Message: fmt.Sprintf("Ping response from %s", p.node.ID())}
+
+ // sign the data
+ signature, err := p.node.signProtoMessage(resp)
+ if err != nil {
+ log.Println("failed to sign response")
+ return
+ }
+
+ // add the signature to the message
+ resp.MessageData.Sign = string(signature)
+
+ // send the response
+ s, respErr := p.node.NewStream(context.Background(), s.Conn().RemotePeer(), pingResponse)
+ if respErr != nil {
+ log.Println(respErr)
+ return
+ }
+
+ ok := p.node.sendProtoMessage(resp, s)
+
+ if ok {
+ log.Printf("%s: Ping response to %s sent.", s.Conn().LocalPeer().String(), s.Conn().RemotePeer().String())
+ }
+}
+
+// remote ping response handler
+func (p *PingProtocol) onPingResponse(s inet.Stream) {
+ data := &p2p.PingResponse{}
+ decoder := protobufCodec.Multicodec(nil).Decoder(bufio.NewReader(s))
+ err := decoder.Decode(data)
+ if err != nil {
+ return
+ }
+
+ valid := p.node.authenticateMessage(data, data.MessageData)
+
+ if !valid {
+ log.Println("Failed to authenticate message")
+ return
+ }
+
+ // locate request data and remove it if found
+ _, ok := p.requests[data.MessageData.Id]
+ if ok {
+ // remove request from map as we have processed it here
+ delete(p.requests, data.MessageData.Id)
+ } else {
+ log.Println("Failed to locate request data boject for response")
+ return
+ }
+
+ log.Printf("%s: Received ping response from %s. Message id:%s. Message: %s.", s.Conn().LocalPeer(), s.Conn().RemotePeer(), data.MessageData.Id, data.Message)
+ p.done <- true
+}
+
+func (p *PingProtocol) Ping(host host.Host) bool {
+ log.Printf("%s: Sending ping to: %s....", p.node.ID(), host.ID())
+
+ // create message data
+ req := &p2p.PingRequest{MessageData: p.node.NewMessageData(uuid.Must(uuid.NewV4()).String(), false),
+ Message: fmt.Sprintf("Ping from %s", p.node.ID())}
+
+ // sign the data
+ signature, err := p.node.signProtoMessage(req)
+ if err != nil {
+ log.Println("failed to sign pb data")
+ return false
+ }
+
+ // add the signature to the message
+ req.MessageData.Sign = string(signature)
+
+ s, err := p.node.NewStream(context.Background(), host.ID(), pingRequest)
+ if err != nil {
+ log.Println(err)
+ return false
+ }
+
+ ok := p.node.sendProtoMessage(req, s)
+
+ if !ok {
+ return false
+ }
+
+ // store ref request so response handler has access to it
+ p.requests[req.MessageData.Id] = req
+ log.Printf("%s: Ping to: %s was sent. Message Id: %s, Message: %s", p.node.ID(), host.ID(), req.MessageData.Id, req.Message)
+ return true
+}
diff --git a/examples/protocol-multiplexing-with-multicodecs/README.md b/examples/protocol-multiplexing-with-multicodecs/README.md
index 217933876d5784ce974a23b003373e2a9b5eabc4..0b873f51a6ebd00e0333ea9342e85eac9d6caf92 100644
--- a/examples/protocol-multiplexing-with-multicodecs/README.md
+++ b/examples/protocol-multiplexing-with-multicodecs/README.md
@@ -1,3 +1,5 @@
+
+
# Protocol Multiplexing using multicodecs with libp2p
This examples shows how to use multicodecs (i.e. json) to encode and transmit information between LibP2P hosts using LibP2P Streams.
diff --git a/package.json b/package.json
index d2ef7c52a2a4868d2fa6d42a60a8d0da389d42a9..d2d817b90b66714d2451fa1f8f97ebebd56bafb8 100644
--- a/package.json
+++ b/package.json
@@ -281,6 +281,18 @@
"hash": "Qmc14vuKyGqX27RvBhekYytxSFJpaEgQVuVJgKSm69MEix",
"name": "go-smux-multiplex",
"version": "3.0.5"
+ },
+ {
+ "author": "multiformats",
+ "hash": "QmRDePEiL4Yupq5EkcK3L3ko3iMgYaqUdLu7xc1kqs7dnV",
+ "name": "go-multicodec",
+ "version": "0.1.5"
+ },
+ {
+ "author": "satori",
+ "hash": "QmcBWojPoNh4qm7zvv4qiepvCnnc7ALS9qcp7TNwwxT1gT",
+ "name": "go.uuid",
+ "version": "1.1.0"
}
],
"gxVersion": "0.4.0",