Cleaning up and documenting examples

This commit is contained in:
Sarah Jamie Lewis 2019-01-23 10:48:58 -08:00
parent 8f00e26b81
commit 64ce11d436
8 changed files with 162 additions and 161 deletions

View File

@ -15,7 +15,7 @@ pipeline:
image: golang
commands:
- go list ./... | xargs go vet
- go list ./... | grep -v "/wire/" | grep -v "/examples/" | xargs golint -set_exit_status
- go list ./... | grep -v "/wire/" | xargs golint -set_exit_status
units-tests:
image: golang
commands:

3
.gitignore vendored
View File

@ -4,3 +4,6 @@ go-ricochet-coverage.out
.idea
.reviewboardrc
/vendor/
/testing/tor/
/connectivity/tor/
/tor/

View File

@ -0,0 +1,112 @@
package alicebot
import (
"crypto/rand"
"git.openprivacy.ca/openprivacy/libricochet-go"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"golang.org/x/crypto/ed25519"
"os"
"time"
)
// NewAliceBot creates a new AliceBot and establishes a connection to the given onion server.
func NewAliceBot(acn connectivity.ACN, onion string) AliceBot {
alice := new(alicebot)
alice.messages = make(map[uint32]string)
var err error
alice.pub, alice.priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Errorf("[alice] error generating key: %v", err)
os.Exit(1)
}
rc, err := goricochet.Open(acn, onion)
if err != nil {
log.Errorf("[alice] error connecting to echobot: %v", err)
os.Exit(1)
}
_, err = connection.HandleOutboundConnection(rc).ProcessAuthAsV3Client(identity.InitializeV3("alice", &alice.priv, &alice.pub))
if err != nil {
log.Errorf("[alice] failed to authenticate connection: %v", err)
os.Exit(1)
}
alice.rc = rc
ach := connection.AutoConnectionHandler{}
ach.Init()
ach.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
chat := new(channels.ChatChannel)
chat.Handler = alice
return chat
})
go alice.rc.Process(&ach)
log.Infof("[alice] requesting channel...")
alice.rc.Do(func() error {
chatchannel := channels.ChatChannel{}
chatchannel.Handler = alice
_, err := alice.rc.RequestOpenChannel("im.ricochet.chat", &chatchannel)
if err != nil {
log.Errorf("failed requestopenchannel: %v", err)
os.Exit(1)
}
return nil
})
return alice
}
type alicebot struct {
messages map[uint32]string
pub ed25519.PublicKey
priv ed25519.PrivateKey
mID int
rc *connection.Connection
}
// AliceBot is an interface for alicebot, allowing callers to send and receive messages.
type AliceBot interface {
channels.ChatChannelHandler
SendMessage(string)
}
// SendMessage can be called to send a message to EchoBot
func (ab *alicebot) SendMessage(message string) {
// The following code opens (or creates) a new im.ricochet.chat channel to the connected service
// and sends a message.
log.Infof("[alice] sending...")
ab.rc.Do(func() error {
channel := ab.rc.Channel("im.ricochet.chat", channels.Outbound)
id, err := channels.SendMessageOnChatChannel(channel, message)
if err == nil {
ab.messages[id] = message
}
return err
})
}
// OpenInbound is called when EchoBot attempts to open a channel with AliceBot
func (ab *alicebot) OpenInbound() {
log.Infof("[alice] inbound connection established")
}
// ChatMessage is called whenever AliceBot receives a message from EchoBot
func (ab *alicebot) ChatMessage(messageID uint32, when time.Time, message string) bool {
log.Infof("[alice] got message from echobot: %s", message)
return true
}
// ChatMessageAck is called whenever AliceBot received an acknowledgement of a previously sent message.
func (ab *alicebot) ChatMessageAck(messageID uint32, accepted bool) {
log.Infof("[alice] message \"%s\" ack'd", ab.messages[messageID])
}

View File

@ -2,10 +2,9 @@ package main
import (
"crypto/rand"
"git.openprivacy.ca/openprivacy/libricochet-go"
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/application/examples/echobot/alicebot"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"time"
@ -15,17 +14,20 @@ import (
"os"
)
// EchoBotInstance is an Instance of the EchoBot Application. One is created for every connected peer.
type EchoBotInstance struct {
rai *application.Instance
ra *application.RicochetApplication
}
// Init establishes an EchoBotInstance
func (ebi *EchoBotInstance) Init(rai *application.Instance, ra *application.RicochetApplication) {
ebi.rai = rai
ebi.ra = ra
}
// We always want bidirectional chat channels
// OpenInbound is called when AliceBot opens a ChatChannel. In this case, because we want EchoBot to respond we
// need to open a new channel in the other direction.
func (ebi *EchoBotInstance) OpenInbound() {
log.Debugln("OpenInbound() ChatChannel handler called...")
outboutChatChannel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
@ -40,39 +42,39 @@ func (ebi *EchoBotInstance) OpenInbound() {
}
}
// ChatMessage is called whenever a connected peer sends a message to EchoBot
func (ebi *EchoBotInstance) ChatMessage(messageID uint32, when time.Time, message string) bool {
log.Infof("message from %v - %v", ebi.rai.RemoteHostname, message)
ebi.SendChatMessage(ebi.rai, ebi.rai.RemoteHostname+" "+message)
return true
}
// ChatMessageAck is called whenever a connected peer acknolwedges a message that EchoBot sent.
func (ebi *EchoBotInstance) ChatMessageAck(messageID uint32, accepted bool) {
}
// SendChatMessage sends a chat message to the given echobot instance
func (ebi *EchoBotInstance) SendChatMessage(rai *application.Instance, message string) {
ebi.rai.Connection.Do(func() error {
channel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil {
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
if ok {
chatchannel.SendMessage(message)
}
} //TODO: else?
// We are swallowing the message id and the error here, in reality you will want to handle it.
channels.SendMessageOnChatChannel(channel, message)
return nil
})
}
// main() encapsulates an entire ricochet ecosystem, from starting tor, to generating onion service keys to managing
// the launching of both the echobot server and the alicebot peer.
// In most systems you will only be handling one or two of these subsystems at any given time so this example might seem
// bloated, but we have tried to highlight the most interesting aspects to allow easy application to new domains.
func main() {
////////////
// SERVER //
////////////
// Set up Logging.
log.SetLevel(log.LevelInfo)
log.AddEverythingFromPattern("connectivity")
echobot := new(application.RicochetApplication)
cpubk, cprivk, err := ed25519.GenerateKey(rand.Reader)
// Set up Tor
acn, err := connectivity.StartTor(".", "")
if err != nil {
log.Errorf("Unable to start Tor: %v", err)
@ -80,13 +82,19 @@ func main() {
}
defer acn.Close()
listenService, err := acn.Listen(cprivk, application.RicochetPort)
// Set up the Echobot Server
echobot := new(application.RicochetApplication)
cpubk, cprivk, err := ed25519.GenerateKey(rand.Reader)
// Turn on the echobot onion service in Tor.
listenService, err := acn.Listen(cprivk, application.RicochetPort)
if err != nil {
log.Errorf("error setting up onion service: %v", err)
os.Exit(1)
}
// This next section looks complicated (and it is a little), but all it is doing is allowing echobot to handle
// im.ricochet.chat type channels.
af := application.InstanceFactory{}
af.Init()
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) func() channels.Handler {
@ -99,116 +107,21 @@ func main() {
}
})
// Thee next few lines turn on echobot and make it available to listen to new connections.
// Note that we initialize a V3 identity for echobot.
echobot.Init(acn, "echobot", identity.InitializeV3("echobot", &cprivk, &cpubk), af, new(application.AcceptAllContactManager))
log.Infof("echobot listening on %v", listenService.AddressFull())
go echobot.Run(listenService)
// Now we wait a little bit for everything to wire itself together.
log.Infoln("counting to five ...")
time.Sleep(time.Second * 5)
////////////
// CLIENT //
////////////
//alicebot should nominally be in another package to prevent initializing it directly
alice := NewAliceBot(acn, listenService.AddressIdentity())
// Finally, in these last few lines we setup an AliceBot who simply sends messages to echobot
alice := alicebot.NewAliceBot(acn, listenService.AddressIdentity())
alice.SendMessage("be gay")
alice.SendMessage("do crime")
// stick around and see what happens
time.Sleep(time.Second * 30)
}
func NewAliceBot(acn connectivity.ACN, onion string) alicebot {
alice := alicebot{}
alice.messages = make(map[uint32]string)
var err error
alice.pub, alice.priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Errorf("[alice] error generating key: %v", err)
os.Exit(1)
}
rc, err := goricochet.Open(acn, onion)
if err != nil {
log.Errorf("[alice] error connecting to echobot: %v", err)
os.Exit(1)
}
_, err = connection.HandleOutboundConnection(rc).ProcessAuthAsV3Client(identity.InitializeV3("alice", &alice.priv, &alice.pub))
if err != nil {
log.Errorf("[alice] failed to authenticate connection: %v", err)
os.Exit(1)
}
alice.rc = rc
ach := connection.AutoConnectionHandler{}
ach.Init()
ach.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
chat := new(channels.ChatChannel)
chat.Handler = &alice
return chat
})
go alice.rc.Process(&ach)
log.Infof("[alice] requesting channel...")
alice.rc.Do(func() error {
chatchannel := channels.ChatChannel{}
chatchannel.Handler = &alice
_, err := alice.rc.RequestOpenChannel("im.ricochet.chat", &chatchannel)
if err != nil {
log.Errorf("failed requestopenchannel: %v", err)
os.Exit(1)
}
return nil
})
return alice
}
type alicebot struct {
messages map[uint32]string
pub ed25519.PublicKey
priv ed25519.PrivateKey
mID int
rc *connection.Connection
}
func (this *alicebot) SendMessage(message string) {
log.Infof("[alice] sending...")
this.rc.Do(func() error {
channel := this.rc.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil {
peerchannel, ok := channel.Handler.(*channels.ChatChannel)
if ok {
log.Infof("[alice] sending '%s' to echobot", message)
this.messages[peerchannel.SendMessage(message)] = message
} else {
log.Errorf("couldn't cast channel:")
os.Exit(1)
}
} else {
log.Errorf("couldn't create channel")
os.Exit(1)
}
return nil
})
}
func (this *alicebot) OpenInbound() {
log.Infof("[alice] inbound connection established")
}
func (this *alicebot) ChatMessage(messageID uint32, when time.Time, message string) bool {
log.Infof("[alice] got message from echobot: %s", message)
return true
}
func (this *alicebot) ChatMessageAck(messageID uint32, accepted bool) {
log.Infof("[alice] message \"%s\" ack'd", this.messages[messageID])
}

View File

@ -1,34 +0,0 @@
package main
import (
"crypto/rand"
"encoding/base32"
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"strings"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"os"
)
// An example of how to setup a v3 onion service in go
func main() {
log.SetLevel(log.LevelInfo)
tm, err := connectivity.StartTor(".", "")
if err != nil {
log.Errorf("Unable to start Tor: %v", err)
os.Exit(1)
}
defer tm.Close()
cpubk, cprivk, _ := ed25519.GenerateKey(rand.Reader)
onion, err := tm.Listen(cprivk, application.RicochetPort)
utils.CheckError(err)
defer onion.Close()
log.Infof("Got Listener %v", onion.AddressFull())
decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion.AddressIdentity()))
log.Infof("Decoded Public Key: %x %v", decodedPub[:32], err)
log.Infof("ed25519 Public Key: %x", cpubk)
}

View File

@ -1,6 +1,7 @@
package channels
import (
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
@ -57,6 +58,19 @@ func (cc *ChatChannel) SendMessageWithTime(message string, when time.Time) uint3
return messageID
}
// SendMessageOnChatChannel is a wrapper function which performs some necessary boilerplate
// to make sending messages easier.
func SendMessageOnChatChannel(channel *Channel, message string) (uint32, error) {
if channel != nil {
peerchannel, ok := channel.Handler.(*ChatChannel)
if ok {
return peerchannel.SendMessage(message), nil
}
return 0, errors.New("channel is not an im.ricochet.chat channel")
}
return 0, errors.New("channel pointer is nil")
}
// Acknowledge indicates that the given messageID was received, and whether
// it was accepted.
func (cc *ChatChannel) Acknowledge(messageID uint32, accepted bool) {

View File

@ -80,13 +80,8 @@ func SendMessage(rai *application.Instance, message string) error {
log.Infof("Finding Chat Channel")
channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil {
log.Infof("Found Chat Channel")
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
if ok {
chatchannel.SendMessage(message)
}
} else {
_, err := channels.SendMessageOnChatChannel(channel, message)
if err != nil {
log.Infof("Could not find chat channel")
}
return nil

View File

@ -12,6 +12,4 @@ echo ""
echo "Linting:"
# Ignore wire packages as they are autogenerated
# Ignore examples as they are illustrative
# TODO Consider Renaming ApplicationInstance and ApplicationInstanceFactory to remove the last grep
go list ./... | grep -v "/wire/" | grep -v "/examples/" | xargs golint
go list ./... | grep -v "/wire/" | xargs golint