Skip to content
Snippets Groups Projects
Commit 4bda5f03 authored by Jonah Husson's avatar Jonah Husson
Browse files

Add anonymous chat example code

parents
No related branches found
No related tags found
No related merge requests found
.idea*
vendor*
# Shielded Help Demo
## Anonymous Chat Server
This repo demonstrates the use of the connections API to create an anonymous chat server,
where users can receive support anonymously
### Setup & Execution
To start the server, download or compile the appropriate binary for your system, set up a config file and run the following command:
`./chat.binary -c path/to/config.yaml`
This will start the chat server & output a contact file containing the server's identity to the configured path.
#### Compliation Steps:
```
go mod vendor -v
go mod tidy
# Linux 64 bit binary
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o chat.linux64 main.go
# Windows 64 bit binary
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o chat.win64 main.go
# Windows 32 big binary
GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/chat.win32 main.go
# Mac OSX 64 bit binary (intel)
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/chat.darwin64 main.go
```
#### Example Config
```yaml
# ==================================
# Anonymous Chat Server Configuration
# ==================================
# START YAML ===
# Verbose logging
logLevel: 1
# Path to log file
log: "/cmix/anonymous-chat.log"
# Path to NDF
ndf: ""
# Storage path and password
storageDir: ""
storagePass: ""
# Contact output path
contact: ""
# === END YAML
```
package chat
import (
"fmt"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/connect"
"gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/client/e2e/ratchet/partner"
"gitlab.com/elixxir/client/e2e/receive"
)
const (
defaultPayload = "This is a demo app of anonymous chat. The server does not know your identity. " +
"This can be used to allow a user in need to get help without revealing their identity."
)
// Listener is the anonymous chat listener, responds with a preset message
type Listener struct {
Partner partner.Manager
Conn connect.Connection
}
// Hear a message and respond
func (l *Listener) Hear(item receive.Message) {
jww.INFO.Printf("Chat listener heard a message from %+v, payload: \n%s\n", item.Sender, string(item.Payload))
rids, e2eID, t, err := l.Conn.SendE2E(1, []byte(defaultPayload), e2e.GetDefaultParams())
if err != nil {
fmt.Println(err)
return
}
jww.INFO.Printf("Sent response to %+v over rounds %+v at %+v [id: %+v]", l.Partner.PartnerId(), rids, t, e2eID)
}
// Name Returns a name, used for debugging
func (l *Listener) Name() string {
return fmt.Sprintf("Anonymous Chat listener [%s]", l.Partner.PartnerId().String())
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package cmd
import (
"encoding/json"
"fmt"
"git.xx.network/elixxir/shielded-help-demo/anonymous-chat/chat"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"gitlab.com/elixxir/client/api"
"gitlab.com/elixxir/client/connect"
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/diffieHellman"
"gitlab.com/xx_network/primitives/utils"
"os"
"time"
)
const (
IdentityKey = "IdentityKey"
)
var (
logPath, cfgFile string
validConfig bool
)
// Execute adds all child commands to the root command and sets flags
// appropriately. This is called by main.main(). It only needs to
// happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "chatserver",
Short: "Runs an anonymous chat server",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
initConfig()
initLog()
// Get & read in NDF
ndf := viper.GetString("ndf")
var ndfJSON []byte
if ep, err := utils.ExpandPath(ndf); err == nil {
ndfJSON, err = utils.ReadFile(ep)
if err != nil {
jww.FATAL.Panicf("Failed to read NDF at %s: %+v",
ep, err)
}
} else {
jww.FATAL.Panicf("Failed to expand given ndf path %s: %+v",
ndf, err)
}
// Get storage dir
storageDir := viper.GetString("storageDir")
storagePass := []byte(viper.GetString("storagePass"))
expandedStorageDir, err := utils.ExpandPath(storageDir)
if err != nil {
jww.FATAL.Panicf("Failed to expand given storage path %s: "+
"%+v", storageDir, err)
}
if storageDir == "" {
jww.FATAL.Panicf("Storage dir cannot be empty")
}
/* CLIENT INIT */
// Create new client if not there
if !utils.Exists(expandedStorageDir) {
// NewClient generates a transmission cryptographic identity
// registration & key negotiation occur in background on Login
err := api.NewClient(string(ndfJSON), expandedStorageDir,
storagePass, "")
if err != nil {
jww.FATAL.Panicf("Failed to init client: %+v", err)
}
}
// Start client
cl, err := api.Login(expandedStorageDir, storagePass,
api.GetDefaultParams())
if err != nil {
jww.FATAL.Panicf("Failed to open client: %+v", err)
}
/* IDENTITY */
var receptionIdentity api.Identity
identityObject, err := cl.GetStorage().Get(IdentityKey)
if err != nil {
jww.INFO.Println("Creating new identity")
// Create new identity
stream := cl.GetRng().GetStream()
receptionIdentity, err = api.MakeIdentity(stream,
cl.GetStorage().GetE2EGroup())
if err != nil {
jww.FATAL.Panicf("Failed to make identity: %+v", err)
}
stream.Close()
// Marshal and store identity
rawIdentity, err := json.Marshal(receptionIdentity)
if err != nil {
jww.FATAL.Panicf("Failed to marshal identity: %+v", err)
}
err = cl.GetStorage().Set(IdentityKey, &versioned.Object{
Version: 0,
Timestamp: time.Now(),
Data: rawIdentity,
})
if err != nil {
jww.FATAL.Panicf("Failed to store Identity: %+v", err)
}
} else {
err = json.Unmarshal(identityObject.Data, &receptionIdentity)
if err != nil {
jww.FATAL.Panicf("Failed to unmarshal identity: "+
"%+v", err)
}
}
// Add reception identity to tracking so
// that messages on this identity will be picked up
cl.GetCmix().AddIdentity(receptionIdentity.ID, time.Time{}, false)
// Create callback for incoming connections
cb := func(connection connect.Connection) {
// Register an example listener, which receives messages
// over this connection and responds with a set string
_ = connection.RegisterListener(1, // TODO: choose new message type
&chat.Listener{
Partner: connection.GetPartner(),
Conn: connection,
})
}
// Start connection server
err = connect.StartServer(cb, receptionIdentity.ID,
receptionIdentity.DHKeyPrivate, cl.GetRng(),
cl.GetStorage().GetE2EGroup(), cl.GetCmix(),
connect.GetDefaultParams())
contactPath := viper.GetString("contact")
// Create a contact object based on our reception identity
ct := contact.Contact{
ID: receptionIdentity.ID,
DhPubKey: diffieHellman.GeneratePublicKey( // Generate public key
receptionIdentity.DHKeyPrivate, cl.GetStorage().GetE2EGroup()),
}
// Output contact to file for distribution
if ep, err := utils.ExpandPath(contactPath); err == nil {
err := utils.WriteFile(ep, ct.Marshal(), os.ModePerm, os.ModePerm)
if err != nil {
jww.ERROR.Printf("Failed to write contact to "+
"expanded path %s: %+v", ep, err)
}
} else {
jww.ERROR.Printf("Failed to expand given contact "+
"path %s: %+v", contactPath, err)
}
/* START NETWORK FOLLOWER */
err = cl.StartNetworkFollower(time.Millisecond * 500)
if err != nil {
jww.FATAL.Panicf("Failed to start network follower: %+v",
err)
}
// Wait forever
var quitCh chan bool
select {
case <-quitCh:
cl.StopNetworkFollower()
break
}
},
}
// init is the initialization function for Cobra which defines commands
// and flags.
func init() {
// NOTE: The point of init() is to be declarative. There is
// one init in each sub command. Do not put variable
// declarations here, and ensure all the Flags are of the *P
// variety, unless there's a very good reason not to have them
// as local params to sub command."
rootCmd.Flags().StringVarP(&cfgFile, "config", "c",
"", "Sets a custom config file path")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
//Use default config location if none is passed
var err error
validConfig = true
if cfgFile == "" {
cfgFile, err = utils.SearchDefaultLocations("chat.yaml",
"xxnetwork")
if err != nil {
validConfig = false
jww.FATAL.Panicf("Failed to find config file: %+v", err)
}
} else {
cfgFile, err = utils.ExpandPath(cfgFile)
if err != nil {
validConfig = false
jww.FATAL.Panicf("Failed to expand config file path:"+
" %+v", err)
}
}
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
fmt.Printf("Unable to read config file (%s): %+v", cfgFile, err.Error())
validConfig = false
}
}
// initLog initializes logging thresholds and the log path.
func initLog() {
vipLogLevel := viper.GetUint("logLevel")
// Check the level of logs to display
if vipLogLevel > 1 {
// Set the GRPC log level
err := os.Setenv("GRPC_GO_LOG_SEVERITY_LEVEL", "info")
if err != nil {
jww.ERROR.Printf("Could not set "+
"GRPC_GO_LOG_SEVERITY_LEVEL: %+v", err)
}
err = os.Setenv("GRPC_GO_LOG_VERBOSITY_LEVEL", "99")
if err != nil {
jww.ERROR.Printf("Could not set "+
"GRPC_GO_LOG_VERBOSITY_LEVEL: %+v", err)
}
// Turn on trace logs
jww.SetLogThreshold(jww.LevelTrace)
jww.SetStdoutThreshold(jww.LevelTrace)
} else if vipLogLevel == 1 {
// Turn on debugging logs
jww.SetLogThreshold(jww.LevelDebug)
jww.SetStdoutThreshold(jww.LevelDebug)
} else {
// Turn on info logs
jww.SetLogThreshold(jww.LevelInfo)
jww.SetStdoutThreshold(jww.LevelInfo)
}
logPath = viper.GetString("log")
logFile, err := os.OpenFile(logPath,
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
0644)
if err != nil {
fmt.Printf("Could not open log file %s!\n", logPath)
} else {
jww.SetLogOutput(logFile)
}
}
go.mod 0 → 100644
module git.xx.network/elixxir/shielded-help-demo/anonymous-chat
go 1.17
require (
github.com/spf13/cobra v1.1.1
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/viper v1.7.1
gitlab.com/elixxir/client v1.5.1-0.20220605190044-41a9f645b4e5
gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b
gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e
)
require (
github.com/badoux/checkmail v1.2.1 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/elliotchance/orderedmap v1.4.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.0 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spf13/afero v1.5.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
github.com/ttacon/libphonenumber v1.2.1 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f // indirect
gitlab.com/elixxir/comms v0.0.4-0.20220323190139-9ed75f3a8b2c // indirect
gitlab.com/elixxir/ekv v0.1.7 // indirect
gitlab.com/elixxir/primitives v0.0.3-0.20220330212736-cce83b5f948f // indirect
gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac // indirect
gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce // indirect
gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93 // indirect
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
google.golang.org/grpc v1.42.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
go.sum 0 → 100644
This diff is collapsed.
package main
import "git.xx.network/elixxir/shielded-help-demo/anonymous-chat/cmd"
// main needs no introduction.
func main() {
cmd.Execute()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment