forked from cwtch.im/cwtch
Tapir Server Refactor
This commit is contained in:
parent
c2114018f8
commit
0550a71244
618
app/cli/main.go
618
app/cli/main.go
|
@ -1,618 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
app2 "cwtch.im/cwtch/app"
|
|
||||||
"cwtch.im/cwtch/event"
|
|
||||||
peer2 "cwtch.im/cwtch/peer"
|
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"cwtch.im/cwtch/model"
|
|
||||||
"fmt"
|
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
|
||||||
"github.com/c-bata/go-prompt"
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var app app2.Application
|
|
||||||
var peer peer2.CwtchPeer
|
|
||||||
var group *model.Group
|
|
||||||
var groupFollowBreakChan chan bool
|
|
||||||
var prmpt string
|
|
||||||
|
|
||||||
var suggestionsBase = []prompt.Suggest{
|
|
||||||
{Text: "/new-profile", Description: "create a new profile"},
|
|
||||||
{Text: "/load-profiles", Description: "loads profiles with a password"},
|
|
||||||
{Text: "/list-profiles", Description: "list active profiles"},
|
|
||||||
{Text: "/select-profile", Description: "selects an active profile to use"},
|
|
||||||
{Text: "/help", Description: "print list of commands"},
|
|
||||||
{Text: "/quit", Description: "quit cwtch"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var suggestionsSelectedProfile = []prompt.Suggest{
|
|
||||||
{Text: "/info", Description: "show user info"},
|
|
||||||
{Text: "/list-contacts", Description: "retrieve a list of contacts"},
|
|
||||||
{Text: "/list-groups", Description: "retrieve a list of groups"},
|
|
||||||
{Text: "/new-group", Description: "create a new group on a server"},
|
|
||||||
{Text: "/select-group", Description: "selects a group to follow"},
|
|
||||||
{Text: "/unselect-group", Description: "stop following the current group"},
|
|
||||||
{Text: "/invite", Description: "invite a new contact"},
|
|
||||||
{Text: "/invite-to-group", Description: "invite an existing contact to join an existing group"},
|
|
||||||
{Text: "/accept-invite", Description: "accept the invite of a group"},
|
|
||||||
/*{Text: "/list-servers", Description: "retrieve a list of servers and their connection status"},
|
|
||||||
{Text: "/list-peers", Description: "retrieve a list of peers and their connection status"},*/
|
|
||||||
{Text: "/export-group", Description: "export a group invite: prints as a string"},
|
|
||||||
{Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var suggestions = suggestionsBase
|
|
||||||
|
|
||||||
var usages = map[string]string{
|
|
||||||
"/new-profile": "/new-profile [name]",
|
|
||||||
"/load-profiles": "/load-profiles",
|
|
||||||
"/list-profiles": "",
|
|
||||||
"/select-profile": "/select-profile [onion]",
|
|
||||||
"/quit": "",
|
|
||||||
/* "/list-servers": "",
|
|
||||||
"/list-peers": "",*/
|
|
||||||
"/list-contacts": "",
|
|
||||||
"/list-groups": "",
|
|
||||||
"/select-group": "/select-group [groupid]",
|
|
||||||
"/unselect-group": "",
|
|
||||||
"/export-group": "/export-group [groupid]",
|
|
||||||
"/info": "",
|
|
||||||
"/send": "/send [groupid] [message]",
|
|
||||||
"/timeline": "/timeline [groupid]",
|
|
||||||
"/accept-invite": "/accept-invite [groupid]",
|
|
||||||
"/invite": "/invite [peerid]",
|
|
||||||
"/invite-to-group": "/invite-to-group [groupid] [peerid]",
|
|
||||||
"/new-group": "/new-group [server]",
|
|
||||||
"/help": "",
|
|
||||||
"/trust": "/trust [peerid]",
|
|
||||||
"/block": "/block [peerid]",
|
|
||||||
}
|
|
||||||
|
|
||||||
func printMessage(m model.Message) {
|
|
||||||
p := peer.GetContact(m.PeerID)
|
|
||||||
name := "unknown"
|
|
||||||
if p != nil {
|
|
||||||
name = p.Name
|
|
||||||
} else if peer.GetOnion() == m.PeerID {
|
|
||||||
name = peer.GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%v %v (%v): %v\n", m.Timestamp, name, m.PeerID, m.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func startGroupFollow() {
|
|
||||||
groupFollowBreakChan = make(chan bool)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
l := len(group.Timeline.GetMessages())
|
|
||||||
select {
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
if group == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gms := group.Timeline.GetMessages()
|
|
||||||
if len(gms) != l {
|
|
||||||
fmt.Printf("\n")
|
|
||||||
for ; l < len(gms); l++ {
|
|
||||||
printMessage(gms[l])
|
|
||||||
}
|
|
||||||
fmt.Printf(prmpt)
|
|
||||||
}
|
|
||||||
case <-groupFollowBreakChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopGroupFollow() {
|
|
||||||
if group != nil {
|
|
||||||
groupFollowBreakChan <- true
|
|
||||||
group = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func completer(d prompt.Document) []prompt.Suggest {
|
|
||||||
|
|
||||||
var s []prompt.Suggest
|
|
||||||
|
|
||||||
if d.FindStartOfPreviousWord() == 0 {
|
|
||||||
return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := d.CurrentLine()
|
|
||||||
|
|
||||||
// Suggest a profile id
|
|
||||||
if strings.HasPrefix(w, "/select-profile") {
|
|
||||||
s = []prompt.Suggest{}
|
|
||||||
peerlist := app.ListPeers()
|
|
||||||
for onion, peername := range peerlist {
|
|
||||||
s = append(s, prompt.Suggest{Text: onion, Description: peername})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suggest groupid
|
|
||||||
if /*strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") ||*/ strings.HasPrefix(w, "/export-group") || strings.HasPrefix(w, "/select-group") {
|
|
||||||
s = []prompt.Suggest{}
|
|
||||||
groups := peer.GetGroups()
|
|
||||||
for _, groupID := range groups {
|
|
||||||
group := peer.GetGroup(groupID)
|
|
||||||
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
|
||||||
}
|
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suggest unaccepted group
|
|
||||||
if strings.HasPrefix(w, "/accept-invite") {
|
|
||||||
s = []prompt.Suggest{}
|
|
||||||
groups := peer.GetGroups()
|
|
||||||
for _, groupID := range groups {
|
|
||||||
group := peer.GetGroup(groupID)
|
|
||||||
if group.Accepted == false {
|
|
||||||
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// suggest groupid AND peerid
|
|
||||||
if strings.HasPrefix(w, "/invite-to-group") {
|
|
||||||
|
|
||||||
if d.FindStartOfPreviousWordWithSpace() == 0 {
|
|
||||||
s = []prompt.Suggest{}
|
|
||||||
groups := peer.GetGroups()
|
|
||||||
for _, groupID := range groups {
|
|
||||||
group := peer.GetGroup(groupID)
|
|
||||||
if group.Owner == "self" {
|
|
||||||
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
s = []prompt.Suggest{}
|
|
||||||
contacts := peer.GetContacts()
|
|
||||||
for _, onion := range contacts {
|
|
||||||
contact := peer.GetContact(onion)
|
|
||||||
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
|
||||||
}
|
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suggest contact onion / peerid
|
|
||||||
if strings.HasPrefix(w, "/block") || strings.HasPrefix(w, "/trust") || strings.HasPrefix(w, "/invite") {
|
|
||||||
s = []prompt.Suggest{}
|
|
||||||
contacts := peer.GetContacts()
|
|
||||||
for _, onion := range contacts {
|
|
||||||
contact := peer.GetContact(onion)
|
|
||||||
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
|
||||||
}
|
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleAppEvents(em event.Manager) {
|
|
||||||
queue := event.NewQueue()
|
|
||||||
em.Subscribe(event.NewPeer, queue)
|
|
||||||
em.Subscribe(event.PeerError, queue)
|
|
||||||
|
|
||||||
for {
|
|
||||||
ev := queue.Next()
|
|
||||||
switch ev.EventType {
|
|
||||||
case event.NewPeer:
|
|
||||||
onion := ev.Data[event.Identity]
|
|
||||||
p := app.GetPeer(onion)
|
|
||||||
app.LaunchPeers()
|
|
||||||
|
|
||||||
fmt.Printf("\nLoaded profile %v (%v)\n", p.GetName(), p.GetOnion())
|
|
||||||
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
|
||||||
|
|
||||||
profiles := app.ListPeers()
|
|
||||||
fmt.Printf("\n%v profiles active now\n", len(profiles))
|
|
||||||
fmt.Printf("You should run `select-profile` to use a profile or `list-profiles` to view loaded profiles\n")
|
|
||||||
case event.PeerError:
|
|
||||||
err := ev.Data[event.Error]
|
|
||||||
fmt.Printf("\nError creating profile: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
cwtch :=
|
|
||||||
`
|
|
||||||
#, #'
|
|
||||||
@@@@@@:
|
|
||||||
@@@@@@.
|
|
||||||
@'@@+#' @@@@+
|
|
||||||
''''''@ #+@ :
|
|
||||||
@''''+;+' . '
|
|
||||||
@''@' :+' , ; ##, +'
|
|
||||||
,@@ ;' #'#@''. #''@''#
|
|
||||||
# ''''''#:,,#'''''@
|
|
||||||
: @''''@ :+'''@
|
|
||||||
' @;+'@ @'#
|
|
||||||
.:# '#..# '# @
|
|
||||||
@@@@@@
|
|
||||||
@@@@@@
|
|
||||||
'@@@@
|
|
||||||
@# . .
|
|
||||||
+++, #'@+'@
|
|
||||||
''', ''''''#
|
|
||||||
.#+# ''', @'''+,
|
|
||||||
@''# ''', .#@
|
|
||||||
:; '@''# .;. ''', ' : ;. ,
|
|
||||||
@+'''@ '+'+ @++ @+'@+''''+@ #+'''#: ''';#''+@ @@@@ @@@@@@@@@ :@@@@#
|
|
||||||
#''''''# +''. +'': +'''''''''+ @'''''''# '''+'''''@ @@@@ @@@@@@@@@@@@@@@@:
|
|
||||||
@'''@@'''@ @''# ,'''@ ''+ @@''+#+ :'''@@+''' ''''@@'''' @@@@ @@@@@@@@@@@@@@@@@
|
|
||||||
'''# @''# +''@ @'''# ;''@ +''+ @''@ ,+'', '''@ #'''. @@@@ @@@@ '@@@# @@@@
|
|
||||||
;''' @@; '''# #'@'' @''@ @''+ +''# .@@ ''', '''. @@@@ @@@ @@@ .@@@
|
|
||||||
@''# #'' ''#''#@''. #''# '''. '''. +'', @@@@ @@@ @@@ @@@
|
|
||||||
@''# @''@'' #'@+'+ #''# '''. ''', +'', +@@@.@@@ @@@@ @@@, @@@ ,@@@
|
|
||||||
;''+ @, +''@'# @'+''@ @''# +''; '+ ''', +'', @@@@@@@@# @@@@ @@@. .@@@ .@@@
|
|
||||||
'''# ++'+ ''''@ ,''''# #''' @''@ '@''+ ''', ''', @@@@@@@@: @@@@ @@@; .@@@' ;@@@
|
|
||||||
@'''@@'''@ #'''. +'''' ;'''#@ :'''#@+''+ ''', ''', @@@@@@# @@@@ @@@+ ,@@@. @@@@
|
|
||||||
#''''''# @''+ @''+ +'''' @'''''''# ''', ''', #@@@. @@@@ @@@+ @@@ @@@@
|
|
||||||
@+''+@ '++@ ;++@ '#''@ ##'''@: +++, +++, :@ @@@@ @@@' @@@ '@@@
|
|
||||||
:' ' '`
|
|
||||||
fmt.Printf("%v\n\n", cwtch)
|
|
||||||
quit := false
|
|
||||||
|
|
||||||
usr, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("\nError: could not load current user: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
acn, err := tor.NewTorACN(path.Join(usr.HomeDir, ".cwtch"), "")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("\nError connecting to Tor: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
app = app2.NewApp(acn, path.Join(usr.HomeDir, ".cwtch"))
|
|
||||||
go handleAppEvents(app.GetPrimaryBus())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error initializing application: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
log.SetLevel(log.LevelDebug)
|
|
||||||
fmt.Printf("\nWelcome to Cwtch!\n")
|
|
||||||
fmt.Printf("If this if your first time you should create a profile by running `/new-profile`\n")
|
|
||||||
fmt.Printf("`/load-profiles` will prompt you for a password and load profiles from storage\n")
|
|
||||||
fmt.Printf("`/help` will show you other available commands\n")
|
|
||||||
fmt.Printf("There is full [TAB] completion support\n\n")
|
|
||||||
|
|
||||||
var history []string
|
|
||||||
for !quit {
|
|
||||||
|
|
||||||
prmpt = "cwtch> "
|
|
||||||
if group != nil {
|
|
||||||
prmpt = fmt.Sprintf("cwtch %v (%v) [%v] say> ", peer.GetName(), peer.GetOnion(), group.GroupID)
|
|
||||||
} else if peer != nil {
|
|
||||||
prmpt = fmt.Sprintf("cwtch %v (%v)> ", peer.GetName(), peer.GetOnion())
|
|
||||||
}
|
|
||||||
|
|
||||||
text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple),
|
|
||||||
prompt.OptionDescriptionBGColor(prompt.White),
|
|
||||||
prompt.OptionPrefixTextColor(prompt.White),
|
|
||||||
prompt.OptionInputTextColor(prompt.Purple),
|
|
||||||
prompt.OptionHistory(history))
|
|
||||||
|
|
||||||
commands := strings.Split(text[0:], " ")
|
|
||||||
history = append(history, text)
|
|
||||||
|
|
||||||
if peer == nil {
|
|
||||||
if commands[0] != "/help" && commands[0] != "/quit" && commands[0] != "/new-profile" && commands[0] != "/load-profiles" && commands[0] != "/select-profile" && commands[0] != "/list-profiles" {
|
|
||||||
fmt.Printf("Profile needs to be set\n")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send
|
|
||||||
if group != nil && !strings.HasPrefix(commands[0], "/") {
|
|
||||||
err := peer.SendMessageToGroup(group.GroupID, text)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error sending message: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch commands[0] {
|
|
||||||
case "/quit":
|
|
||||||
quit = true
|
|
||||||
case "/new-profile":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
name := strings.Trim(commands[1], " ")
|
|
||||||
if name == "" {
|
|
||||||
fmt.Printf("Error creating profile, usage: %v\n", usages[commands[0]])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n")
|
|
||||||
|
|
||||||
password := ""
|
|
||||||
failcount := 0
|
|
||||||
for ; failcount < 3; failcount++ {
|
|
||||||
fmt.Print("Enter a password to encrypt the profile: ")
|
|
||||||
bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
|
|
||||||
if string(bytePassword) == "" {
|
|
||||||
fmt.Print("\nBlank password not allowed.")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Print("\nRe-enter password: ")
|
|
||||||
bytePassword2, _ := terminal.ReadPassword(int(syscall.Stdin))
|
|
||||||
if bytes.Equal(bytePassword, bytePassword2) {
|
|
||||||
password = string(bytePassword)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
fmt.Print("\nPASSWORDS DIDN'T MATCH! Try again.\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if failcount >= 3 {
|
|
||||||
fmt.Printf("Error creating profile for %v: Your password entries must match!\n", name)
|
|
||||||
} else {
|
|
||||||
app.CreatePeer(name, password)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error creating New Profile, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/load-profiles":
|
|
||||||
fmt.Print("Enter a password to decrypt the profile: ")
|
|
||||||
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("\nError loading profiles: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
app.LoadProfiles(string(bytePassword))
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\nError loading profiles: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "/list-profiles":
|
|
||||||
peerlist := app.ListPeers()
|
|
||||||
for onion, peername := range peerlist {
|
|
||||||
fmt.Printf(" %v\t%v\n", onion, peername)
|
|
||||||
}
|
|
||||||
case "/select-profile":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
p := app.GetPeer(commands[1])
|
|
||||||
if p == nil {
|
|
||||||
fmt.Printf("Error: profile '%v' does not exist\n", commands[1])
|
|
||||||
} else {
|
|
||||||
stopGroupFollow()
|
|
||||||
peer = p
|
|
||||||
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto cwtchPeer / Join Server
|
|
||||||
// TODO There are some privacy implications with this that we should
|
|
||||||
// think over.
|
|
||||||
for _, name := range p.GetContacts() {
|
|
||||||
profile := p.GetContact(name)
|
|
||||||
if profile.Authorization == model.AuthApproved {
|
|
||||||
p.PeerWithOnion(profile.Onion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, groupid := range p.GetGroups() {
|
|
||||||
group := p.GetGroup(groupid)
|
|
||||||
if group.Accepted || group.Owner == "self" {
|
|
||||||
p.JoinServer(group.GroupServer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error selecting profile, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/info":
|
|
||||||
if peer != nil {
|
|
||||||
fmt.Printf("Address cwtch:%v\n", peer.GetOnion())
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Profile needs to be set\n")
|
|
||||||
}
|
|
||||||
case "/invite":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
fmt.Printf("Inviting cwtch:%v\n", commands[1])
|
|
||||||
peer.PeerWithOnion(commands[1])
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error inviting peer, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
/*case "/list-peers":
|
|
||||||
peers := peer.GetPeers()
|
|
||||||
for p, s := range peers {
|
|
||||||
fmt.Printf("Name: %v Status: %v\n", p, connections.ConnectionStateName[s])
|
|
||||||
}
|
|
||||||
case "/list-servers":
|
|
||||||
servers := peer.GetServers()
|
|
||||||
for s, st := range servers {
|
|
||||||
fmt.Printf("Name: %v Status: %v\n", s, connections.ConnectionStateName[st])
|
|
||||||
}*/
|
|
||||||
case "/list-contacts":
|
|
||||||
contacts := peer.GetContacts()
|
|
||||||
for _, onion := range contacts {
|
|
||||||
c := peer.GetContact(onion)
|
|
||||||
fmt.Printf("Name: %v Onion: %v Authorization: %v\n", c.Name, c.Onion, c.Authorization)
|
|
||||||
}
|
|
||||||
case "/list-groups":
|
|
||||||
for _, gid := range peer.GetGroups() {
|
|
||||||
g := peer.GetGroup(gid)
|
|
||||||
fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted)
|
|
||||||
}
|
|
||||||
case "/block":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
peer.SetContactAuthorization(commands[1], model.AuthBlocked)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/accept-invite":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
groupID := commands[1]
|
|
||||||
err := peer.AcceptInvite(groupID)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
} else {
|
|
||||||
group := peer.GetGroup(groupID)
|
|
||||||
if group == nil {
|
|
||||||
fmt.Printf("Error: group does not exist\n")
|
|
||||||
} else {
|
|
||||||
peer.JoinServer(group.GroupServer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error accepting invite, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/invite-to-group":
|
|
||||||
if len(commands) == 3 {
|
|
||||||
fmt.Printf("Inviting %v to %v\n", commands[1], commands[2])
|
|
||||||
err := peer.InviteOnionToGroup(commands[2], commands[1])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error inviting peer to group, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/new-group":
|
|
||||||
if len(commands) == 2 && commands[1] != "" {
|
|
||||||
fmt.Printf("Setting up a new group on server:%v\n", commands[1])
|
|
||||||
id, _, err := peer.StartGroup(commands[1])
|
|
||||||
if err == nil {
|
|
||||||
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
|
|
||||||
group := peer.GetGroup(id)
|
|
||||||
if group == nil {
|
|
||||||
fmt.Printf("Error: group does not exist\n")
|
|
||||||
} else {
|
|
||||||
peer.JoinServer(group.GroupServer)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error creating new group: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error creating a new group, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/select-group":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
g := peer.GetGroup(commands[1])
|
|
||||||
if g == nil {
|
|
||||||
fmt.Printf("Error: group %s not found!\n", commands[1])
|
|
||||||
} else {
|
|
||||||
stopGroupFollow()
|
|
||||||
group = g
|
|
||||||
|
|
||||||
fmt.Printf("--------------- %v ---------------\n", group.GroupID)
|
|
||||||
gms := group.Timeline.GetMessages()
|
|
||||||
max := 20
|
|
||||||
if len(gms) < max {
|
|
||||||
max = len(gms)
|
|
||||||
}
|
|
||||||
for i := len(gms) - max; i < len(gms); i++ {
|
|
||||||
printMessage(gms[i])
|
|
||||||
}
|
|
||||||
fmt.Printf("------------------------------\n")
|
|
||||||
|
|
||||||
startGroupFollow()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error selecting a group, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/unselect-group":
|
|
||||||
stopGroupFollow()
|
|
||||||
case "/export-group":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
group := peer.GetGroup(commands[1])
|
|
||||||
if group == nil {
|
|
||||||
fmt.Printf("Error: group does not exist\n")
|
|
||||||
} else {
|
|
||||||
invite, _ := peer.ExportGroup(commands[1])
|
|
||||||
fmt.Printf("Invite: %v\n", invite)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error exporting group, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/import-group":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
err := peer.ImportGroup(commands[1])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error importing group: %v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Imported group!\n")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%v", commands)
|
|
||||||
fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
|
|
||||||
}
|
|
||||||
case "/help":
|
|
||||||
for _, command := range suggestions {
|
|
||||||
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
|
||||||
}
|
|
||||||
case "/sendlots":
|
|
||||||
if len(commands) == 2 {
|
|
||||||
group := peer.GetGroup(commands[1])
|
|
||||||
if group == nil {
|
|
||||||
fmt.Printf("Error: group does not exist\n")
|
|
||||||
} else {
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
fmt.Printf("Sending message: %v\n", i)
|
|
||||||
err := peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("could not send message %v because %v\n", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("Waiting 5 seconds for message to process...\n")
|
|
||||||
time.Sleep(time.Second * 5)
|
|
||||||
timeline := group.GetTimeline()
|
|
||||||
totalLatency := time.Duration(0)
|
|
||||||
maxLatency := time.Duration(0)
|
|
||||||
totalMessages := 0
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
found := false
|
|
||||||
for _, m := range timeline {
|
|
||||||
if m.Message == fmt.Sprintf("this is message %v", i) && m.PeerID == peer.GetOnion() {
|
|
||||||
found = true
|
|
||||||
latency := m.Received.Sub(m.Timestamp)
|
|
||||||
fmt.Printf("Latency for Message %v was %v\n", i, latency)
|
|
||||||
totalLatency = totalLatency + latency
|
|
||||||
if maxLatency < latency {
|
|
||||||
maxLatency = latency
|
|
||||||
}
|
|
||||||
totalMessages++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
fmt.Printf("message %v was never received\n", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Average Latency for %v messages was: %vms\n", totalMessages, time.Duration(int64(totalLatency)/int64(totalMessages)))
|
|
||||||
fmt.Printf("Max Latency for %v messages was: %vms\n", totalMessages, maxLatency)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Shutdown()
|
|
||||||
acn.Close()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
|
@ -221,6 +221,8 @@ const (
|
||||||
|
|
||||||
GroupID = Field("GroupID")
|
GroupID = Field("GroupID")
|
||||||
GroupServer = Field("GroupServer")
|
GroupServer = Field("GroupServer")
|
||||||
|
ServerTokenY = Field("ServerTokenY")
|
||||||
|
ServerTokenOnion = Field("ServerTokenOnion")
|
||||||
GroupInvite = Field("GroupInvite")
|
GroupInvite = Field("GroupInvite")
|
||||||
|
|
||||||
ProfileName = Field("ProfileName")
|
ProfileName = Field("ProfileName")
|
||||||
|
|
15
go.mod
15
go.mod
|
@ -3,22 +3,23 @@ module cwtch.im/cwtch
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cwtch.im/tapir v0.1.18
|
cwtch.im/tapir v0.2.0
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.1.2
|
git.openprivacy.ca/openprivacy/connectivity v1.2.0
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.1
|
git.openprivacy.ca/openprivacy/log v1.0.1
|
||||||
github.com/c-bata/go-prompt v0.2.3
|
github.com/c-bata/go-prompt v0.2.3 // indirect
|
||||||
github.com/golang/protobuf v1.3.5
|
github.com/golang/protobuf v1.3.5 // indirect
|
||||||
github.com/google/go-cmp v0.4.0 // indirect
|
github.com/google/go-cmp v0.4.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
github.com/gtank/ristretto255 v0.1.2
|
||||||
|
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/mattn/go-tty v0.0.3 // indirect
|
github.com/mattn/go-tty v0.0.3 // indirect
|
||||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 // indirect
|
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 // indirect
|
||||||
github.com/struCoder/pidusage v0.1.3
|
github.com/struCoder/pidusage v0.1.3
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20200420104511-884d27f42877
|
golang.org/x/crypto v0.0.0-20200420104511-884d27f42877
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
)
|
)
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1,10 +1,15 @@
|
||||||
|
cwtch.im v0.3.16 h1:uXJO1tUlKVDVQ/0wa4n8C8fzcW2Kol8IYg+tsNFmZOU=
|
||||||
cwtch.im/tapir v0.1.18 h1:Fs/jL9ZRyel/A1D/BYzIPEVQau8y5BJg44yA+GQDbSM=
|
cwtch.im/tapir v0.1.18 h1:Fs/jL9ZRyel/A1D/BYzIPEVQau8y5BJg44yA+GQDbSM=
|
||||||
cwtch.im/tapir v0.1.18/go.mod h1:/IrAI6CBHfgzsfgRT8WHVb1P9fCCz7+45hfsdkKn8Zg=
|
cwtch.im/tapir v0.1.18/go.mod h1:/IrAI6CBHfgzsfgRT8WHVb1P9fCCz7+45hfsdkKn8Zg=
|
||||||
|
cwtch.im/tapir v0.2.0 h1:7MkoR5+uEuPW34/O0GZRidnIjq/01Cfm8nl5IRuqpGc=
|
||||||
|
cwtch.im/tapir v0.2.0/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ=
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
|
git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w=
|
git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w=
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
|
git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.1.2 h1:Bk8ul3+4/awpQGvskfLpp7/K3Lj8OAxBwlmQqeZy3Ok=
|
git.openprivacy.ca/openprivacy/connectivity v1.1.2 h1:Bk8ul3+4/awpQGvskfLpp7/K3Lj8OAxBwlmQqeZy3Ok=
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.1.2/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
|
git.openprivacy.ca/openprivacy/connectivity v1.1.2/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
|
||||||
|
git.openprivacy.ca/openprivacy/connectivity v1.2.0 h1:dbZ5CRl11vg3BNHdzRKSlDP8OUtDB+mf6FkxMVf73qw=
|
||||||
|
git.openprivacy.ca/openprivacy/connectivity v1.2.0/go.mod h1:B7vzuVmChJtSKoh0ezph5vu6DQ0gIk0zHUNG6IgXCcA=
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 h1:Z86uL9K47onznY1wP1P/wWfWMbbyvk6xnCp94R180os=
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 h1:Z86uL9K47onznY1wP1P/wWfWMbbyvk6xnCp94R180os=
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13/go.mod h1:ZUuX1SOrgV4K18IEcp0hQJNPKszRr2oGb3UeK2iYe5U=
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13/go.mod h1:ZUuX1SOrgV4K18IEcp0hQJNPKszRr2oGb3UeK2iYe5U=
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y=
|
git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y=
|
||||||
|
@ -13,6 +18,7 @@ git.openprivacy.ca/openprivacy/log v1.0.1 h1:NWV5oBTatvlSzUE6wtB+UQCulgyMOtm4BXG
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
||||||
github.com/c-bata/go-prompt v0.2.3 h1:jjCS+QhG/sULBhAaBdjb2PlMRVaKXQgn+4yzaauvs2s=
|
github.com/c-bata/go-prompt v0.2.3 h1:jjCS+QhG/sULBhAaBdjb2PlMRVaKXQgn+4yzaauvs2s=
|
||||||
github.com/c-bata/go-prompt v0.2.3/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
|
github.com/c-bata/go-prompt v0.2.3/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw=
|
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw=
|
||||||
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
|
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -20,6 +26,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||||
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
||||||
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
|
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
|
||||||
github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
|
github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
|
||||||
|
@ -30,6 +37,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||||
|
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
@ -84,6 +93,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200420001825-978e26b7c37c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200420001825-978e26b7c37c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -2,12 +2,12 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -89,29 +89,29 @@ func (g *Group) Invite(initialMessage []byte) ([]byte, error) {
|
||||||
|
|
||||||
g.InitialMessage = initialMessage[:]
|
g.InitialMessage = initialMessage[:]
|
||||||
|
|
||||||
gci := &protocol.GroupChatInvite{
|
gci := &groups.GroupInvite{
|
||||||
GroupName: g.GroupID,
|
GroupName: g.GroupID,
|
||||||
GroupSharedKey: g.GroupKey[:],
|
SharedKey: g.GroupKey[:],
|
||||||
ServerHost: g.GroupServer,
|
ServerHost: g.GroupServer,
|
||||||
SignedGroupId: g.SignedGroupID[:],
|
SignedGroupID: g.SignedGroupID[:],
|
||||||
InitialMessage: initialMessage[:],
|
InitialMessage: initialMessage[:],
|
||||||
}
|
}
|
||||||
|
|
||||||
invite, err := proto.Marshal(gci)
|
invite, err := json.Marshal(gci)
|
||||||
return invite, err
|
return invite, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
// AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
||||||
func (g *Group) AddSentMessage(message *protocol.DecryptedGroupMessage, sig []byte) Message {
|
func (g *Group) AddSentMessage(message *groups.DecryptedGroupMessage, sig []byte) Message {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
timelineMessage := Message{
|
timelineMessage := Message{
|
||||||
Message: message.GetText(),
|
Message: message.Text,
|
||||||
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
Timestamp: time.Unix(int64(message.Timestamp), 0),
|
||||||
Received: time.Unix(0, 0),
|
Received: time.Unix(0, 0),
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
PeerID: message.GetOnion(),
|
PeerID: message.Onion,
|
||||||
PreviousMessageSig: message.GetPreviousMessageSig(),
|
PreviousMessageSig: message.PreviousMessageSig,
|
||||||
ReceivedByServer: false,
|
ReceivedByServer: false,
|
||||||
}
|
}
|
||||||
g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage)
|
g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage)
|
||||||
|
@ -139,7 +139,7 @@ func (g *Group) ErrorSentMessage(sig []byte, error string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
||||||
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) {
|
func (g *Group) AddMessage(message *groups.DecryptedGroupMessage, sig []byte) (*Message, bool) {
|
||||||
|
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
|
@ -153,12 +153,12 @@ func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineMessage := &Message{
|
timelineMessage := &Message{
|
||||||
Message: message.GetText(),
|
Message: message.Text,
|
||||||
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
Timestamp: time.Unix(int64(message.Timestamp), 0),
|
||||||
Received: time.Now(),
|
Received: time.Now(),
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
PeerID: message.GetOnion(),
|
PeerID: message.Onion,
|
||||||
PreviousMessageSig: message.GetPreviousMessageSig(),
|
PreviousMessageSig: message.PreviousMessageSig,
|
||||||
ReceivedByServer: true,
|
ReceivedByServer: true,
|
||||||
Error: "",
|
Error: "",
|
||||||
}
|
}
|
||||||
|
@ -175,13 +175,13 @@ func (g *Group) GetTimeline() (timeline []Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//EncryptMessage takes a message and encrypts the message under the group key.
|
//EncryptMessage takes a message and encrypts the message under the group key.
|
||||||
func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) ([]byte, error) {
|
func (g *Group) EncryptMessage(message *groups.DecryptedGroupMessage) ([]byte, error) {
|
||||||
var nonce [24]byte
|
var nonce [24]byte
|
||||||
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
||||||
log.Errorf("Cannot read from random: %v\n", err)
|
log.Errorf("Cannot read from random: %v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
wire, err := proto.Marshal(message)
|
wire, err := json.Marshal(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -191,14 +191,14 @@ func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) ([]byte,
|
||||||
|
|
||||||
// DecryptMessage takes a ciphertext and returns true and the decrypted message if the
|
// DecryptMessage takes a ciphertext and returns true and the decrypted message if the
|
||||||
// cipher text can be successfully decrypted,else false.
|
// cipher text can be successfully decrypted,else false.
|
||||||
func (g *Group) DecryptMessage(ciphertext []byte) (bool, *protocol.DecryptedGroupMessage) {
|
func (g *Group) DecryptMessage(ciphertext []byte) (bool, *groups.DecryptedGroupMessage) {
|
||||||
if len(ciphertext) > 24 {
|
if len(ciphertext) > 24 {
|
||||||
var decryptNonce [24]byte
|
var decryptNonce [24]byte
|
||||||
copy(decryptNonce[:], ciphertext[:24])
|
copy(decryptNonce[:], ciphertext[:24])
|
||||||
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
|
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
|
||||||
if ok {
|
if ok {
|
||||||
dm := &protocol.DecryptedGroupMessage{}
|
dm := &groups.DecryptedGroupMessage{}
|
||||||
err := proto.Unmarshal(decrypted, dm)
|
err := json.Unmarshal(decrypted, dm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true, dm
|
return true, dm
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGroup(t *testing.T) {
|
func TestGroup(t *testing.T) {
|
||||||
g, _ := NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
|
g, _ := NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
|
||||||
dgm := &protocol.DecryptedGroupMessage{
|
dgm := &groups.DecryptedGroupMessage{
|
||||||
Onion: proto.String("onion"),
|
Onion: "onion",
|
||||||
Text: proto.String("Hello World!"),
|
Text: "Hello World!",
|
||||||
Timestamp: proto.Int32(int32(time.Now().Unix())),
|
Timestamp: uint64(time.Now().Unix()),
|
||||||
SignedGroupId: []byte{},
|
SignedGroupID: []byte{},
|
||||||
PreviousMessageSig: []byte{},
|
PreviousMessageSig: []byte{},
|
||||||
Padding: []byte{},
|
Padding: []byte{},
|
||||||
}
|
}
|
||||||
encMessage, _ := g.EncryptMessage(dgm)
|
encMessage, _ := g.EncryptMessage(dgm)
|
||||||
ok, message := g.DecryptMessage(encMessage)
|
ok, message := g.DecryptMessage(encMessage)
|
||||||
if !ok || message.GetText() != "Hello World!" {
|
if !ok || message.Text != "Hello World!" {
|
||||||
t.Errorf("group encryption was invalid, or returned wrong message decrypted:%v message:%v", ok, message)
|
t.Errorf("group encryption was invalid, or returned wrong message decrypted:%v message:%v", ok, message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// KeyTypeOnion - a cwtch address
|
||||||
|
KeyTypeOnion = "onion" // bulletin board
|
||||||
|
|
||||||
|
// KeyTypeTokenOnion - a cwtch peer with a PoW based token protocol
|
||||||
|
KeyTypeTokenOnion = "token_onion"
|
||||||
|
|
||||||
|
//KeyTypePrivacyPass - a privacy pass based token server
|
||||||
|
KeyTypePrivacyPass = "pp_key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key provides a wrapper for a generic public key identifier (could be an onion address, a zcash address etc.)
|
||||||
|
type Key string
|
||||||
|
|
||||||
|
// KeyBundle manages a collection of related keys for various different services.
|
||||||
|
type KeyBundle struct {
|
||||||
|
Keys map[string]Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasKey returns true if the bundle has a public key of a given type.
|
||||||
|
func (kb *KeyBundle) HasKey(name string) bool {
|
||||||
|
_, exists := kb.Keys[name]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey retrieves a key with a given type from the bundle
|
||||||
|
func (kb *KeyBundle) GetKey(name string) (Key, error) {
|
||||||
|
key, exists := kb.Keys[name]
|
||||||
|
if exists {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("no such key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttributeBundle returns a map that can be used as part of a peer attribute bundle
|
||||||
|
func (kb *KeyBundle) AttributeBundle() map[string]string {
|
||||||
|
ab := make(map[string]string)
|
||||||
|
for k, v := range kb.Keys {
|
||||||
|
ab[k] = string(v)
|
||||||
|
}
|
||||||
|
return ab
|
||||||
|
}
|
|
@ -2,14 +2,13 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -354,16 +353,16 @@ func (p *Profile) GetGroup(groupID string) (g *Group) {
|
||||||
|
|
||||||
// ProcessInvite adds a new group invite to the profile. returns the new group ID
|
// ProcessInvite adds a new group invite to the profile. returns the new group ID
|
||||||
func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, error) {
|
func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, error) {
|
||||||
var gci protocol.GroupChatInvite
|
var gci groups.GroupInvite
|
||||||
err := proto.Unmarshal([]byte(invite), &gci)
|
err := json.Unmarshal([]byte(invite), &gci)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
group := new(Group)
|
group := new(Group)
|
||||||
group.GroupID = gci.GetGroupName()
|
group.GroupID = gci.GroupName
|
||||||
group.LocalID = GenerateRandomID()
|
group.LocalID = GenerateRandomID()
|
||||||
group.SignedGroupID = gci.GetSignedGroupId()
|
group.SignedGroupID = gci.SignedGroupID
|
||||||
copy(group.GroupKey[:], gci.GetGroupSharedKey()[:])
|
copy(group.GroupKey[:], gci.SharedKey[:])
|
||||||
group.GroupServer = gci.GetServerHost()
|
group.GroupServer = gci.ServerHost
|
||||||
group.InitialMessage = gci.GetInitialMessage()[:]
|
group.InitialMessage = []byte(gci.InitialMessage)
|
||||||
group.Accepted = false
|
group.Accepted = false
|
||||||
group.Owner = peerHostname
|
group.Owner = peerHostname
|
||||||
group.Attributes = make(map[string]string)
|
group.Attributes = make(map[string]string)
|
||||||
|
@ -389,7 +388,7 @@ func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool,
|
||||||
for _, group := range p.Groups {
|
for _, group := range p.Groups {
|
||||||
success, dgm := group.DecryptMessage(ciphertext)
|
success, dgm := group.DecryptMessage(ciphertext)
|
||||||
if success {
|
if success {
|
||||||
verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), ciphertext, signature)
|
verified := p.VerifyGroupMessage(dgm.Onion, group.GroupID, dgm.Text, int32(dgm.Timestamp), ciphertext, signature)
|
||||||
|
|
||||||
// So we have a message that has a valid group key, but the signature can't be verified.
|
// So we have a message that has a valid group key, but the signature can't be verified.
|
||||||
// The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious)
|
// The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious)
|
||||||
|
@ -436,11 +435,11 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte,
|
||||||
padding := make([]byte, lenPadding)
|
padding := make([]byte, lenPadding)
|
||||||
getRandomness(&padding)
|
getRandomness(&padding)
|
||||||
|
|
||||||
dm := &protocol.DecryptedGroupMessage{
|
dm := &groups.DecryptedGroupMessage{
|
||||||
Onion: proto.String(p.Onion),
|
Onion: p.Onion,
|
||||||
Text: proto.String(message),
|
Text: message,
|
||||||
SignedGroupId: group.SignedGroupID[:],
|
SignedGroupID: group.SignedGroupID[:],
|
||||||
Timestamp: proto.Int32(int32(timestamp)),
|
Timestamp: uint64(timestamp),
|
||||||
PreviousMessageSig: prevSig,
|
PreviousMessageSig: prevSig,
|
||||||
Padding: padding[:],
|
Padding: padding[:],
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ type CwtchPeer interface {
|
||||||
DeleteContact(string)
|
DeleteContact(string)
|
||||||
DeleteGroup(string)
|
DeleteGroup(string)
|
||||||
|
|
||||||
|
AddServer(string)
|
||||||
JoinServer(string)
|
JoinServer(string)
|
||||||
SendMessageToGroup(string, string) error
|
SendMessageToGroup(string, string) error
|
||||||
SendMessageToGroupTracked(string, string) (string, error)
|
SendMessageToGroupTracked(string, string) (string, error)
|
||||||
|
@ -204,6 +205,38 @@ func (cp *cwtchPeer) AddContact(nick, onion string, authorization model.Authoriz
|
||||||
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault))
|
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cp *cwtchPeer) AddServer(serverSpecification string) {
|
||||||
|
keyBundle := new(model.KeyBundle)
|
||||||
|
json.Unmarshal([]byte(serverSpecification), &keyBundle)
|
||||||
|
|
||||||
|
log.Debugf("Got new key bundle %v", keyBundle)
|
||||||
|
|
||||||
|
if keyBundle.HasKey(model.KeyTypeOnion) {
|
||||||
|
onionKey, _ := keyBundle.GetKey(model.KeyTypeOnion)
|
||||||
|
onion := string(onionKey)
|
||||||
|
decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
|
||||||
|
ab := keyBundle.AttributeBundle()
|
||||||
|
ab["nick"] = onion
|
||||||
|
pp := &model.PublicProfile{Name: onion, Ed25519PublicKey: decodedPub, Authorization: model.AuthUnknown, Onion: onion, Attributes: ab}
|
||||||
|
|
||||||
|
cp.Profile.AddContact(onion, pp)
|
||||||
|
pd, _ := json.Marshal(pp)
|
||||||
|
cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{
|
||||||
|
event.Data: string(pd),
|
||||||
|
event.RemotePeer: onion,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Publish every key as an attribute
|
||||||
|
for k, v := range ab {
|
||||||
|
log.Debugf("Server (%v) has %v key %v", onion, k, v)
|
||||||
|
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to Deleting Peer History
|
||||||
|
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetContacts returns an unordered list of onions
|
// GetContacts returns an unordered list of onions
|
||||||
func (cp *cwtchPeer) GetContacts() []string {
|
func (cp *cwtchPeer) GetContacts() []string {
|
||||||
cp.mutex.Lock()
|
cp.mutex.Lock()
|
||||||
|
@ -298,7 +331,14 @@ func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
|
||||||
|
|
||||||
// JoinServer manages a new server connection with the given onion address
|
// JoinServer manages a new server connection with the given onion address
|
||||||
func (cp *cwtchPeer) JoinServer(onion string) {
|
func (cp *cwtchPeer) JoinServer(onion string) {
|
||||||
cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion}))
|
if cp.GetContact(onion) != nil {
|
||||||
|
tokenY, yExists := cp.GetContact(onion).GetAttribute(model.KeyTypePrivacyPass)
|
||||||
|
tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(model.KeyTypeTokenOnion)
|
||||||
|
if yExists && onionExists {
|
||||||
|
cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion, event.ServerTokenY: tokenY, event.ServerTokenOnion: tokenOnion}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO HANDLE ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessageToGroup attempts to sent the given message to the given group id.
|
// SendMessageToGroup attempts to sent the given message to the given group id.
|
||||||
|
|
|
@ -1,327 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// source: ControlChannel.proto
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package protocol is a generated protocol buffer package.
|
|
||||||
|
|
||||||
It is generated from these files:
|
|
||||||
ControlChannel.proto
|
|
||||||
cwtch-profile.proto
|
|
||||||
group_message.proto
|
|
||||||
|
|
||||||
It has these top-level messages:
|
|
||||||
Packet
|
|
||||||
OpenChannel
|
|
||||||
ChannelResult
|
|
||||||
KeepAlive
|
|
||||||
EnableFeatures
|
|
||||||
FeaturesEnabled
|
|
||||||
CwtchPeerPacket
|
|
||||||
CwtchIdentity
|
|
||||||
GroupChatInvite
|
|
||||||
CwtchServerPacket
|
|
||||||
FetchMessage
|
|
||||||
GroupMessage
|
|
||||||
DecryptedGroupMessage
|
|
||||||
*/
|
|
||||||
package protocol
|
|
||||||
|
|
||||||
import proto "github.com/golang/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
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the proto package it is being compiled against.
|
|
||||||
// A compilation error at this line likely means your copy of the
|
|
||||||
// proto package needs to be updated.
|
|
||||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
|
||||||
|
|
||||||
type ChannelResult_CommonError int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ChannelResult_GenericError ChannelResult_CommonError = 0
|
|
||||||
ChannelResult_UnknownTypeError ChannelResult_CommonError = 1
|
|
||||||
ChannelResult_UnauthorizedError ChannelResult_CommonError = 2
|
|
||||||
ChannelResult_BadUsageError ChannelResult_CommonError = 3
|
|
||||||
ChannelResult_FailedError ChannelResult_CommonError = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
var ChannelResult_CommonError_name = map[int32]string{
|
|
||||||
0: "GenericError",
|
|
||||||
1: "UnknownTypeError",
|
|
||||||
2: "UnauthorizedError",
|
|
||||||
3: "BadUsageError",
|
|
||||||
4: "FailedError",
|
|
||||||
}
|
|
||||||
var ChannelResult_CommonError_value = map[string]int32{
|
|
||||||
"GenericError": 0,
|
|
||||||
"UnknownTypeError": 1,
|
|
||||||
"UnauthorizedError": 2,
|
|
||||||
"BadUsageError": 3,
|
|
||||||
"FailedError": 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x ChannelResult_CommonError) Enum() *ChannelResult_CommonError {
|
|
||||||
p := new(ChannelResult_CommonError)
|
|
||||||
*p = x
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (x ChannelResult_CommonError) String() string {
|
|
||||||
return proto.EnumName(ChannelResult_CommonError_name, int32(x))
|
|
||||||
}
|
|
||||||
func (x *ChannelResult_CommonError) UnmarshalJSON(data []byte) error {
|
|
||||||
value, err := proto.UnmarshalJSONEnum(ChannelResult_CommonError_value, data, "ChannelResult_CommonError")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*x = ChannelResult_CommonError(value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) {
|
|
||||||
return fileDescriptor0, []int{2, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Packet struct {
|
|
||||||
// Must contain exactly one field
|
|
||||||
OpenChannel *OpenChannel `protobuf:"bytes,1,opt,name=open_channel,json=openChannel" json:"open_channel,omitempty"`
|
|
||||||
ChannelResult *ChannelResult `protobuf:"bytes,2,opt,name=channel_result,json=channelResult" json:"channel_result,omitempty"`
|
|
||||||
KeepAlive *KeepAlive `protobuf:"bytes,3,opt,name=keep_alive,json=keepAlive" json:"keep_alive,omitempty"`
|
|
||||||
EnableFeatures *EnableFeatures `protobuf:"bytes,4,opt,name=enable_features,json=enableFeatures" json:"enable_features,omitempty"`
|
|
||||||
FeaturesEnabled *FeaturesEnabled `protobuf:"bytes,5,opt,name=features_enabled,json=featuresEnabled" json:"features_enabled,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Packet) Reset() { *m = Packet{} }
|
|
||||||
func (m *Packet) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Packet) ProtoMessage() {}
|
|
||||||
func (*Packet) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
|
||||||
|
|
||||||
func (m *Packet) GetOpenChannel() *OpenChannel {
|
|
||||||
if m != nil {
|
|
||||||
return m.OpenChannel
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Packet) GetChannelResult() *ChannelResult {
|
|
||||||
if m != nil {
|
|
||||||
return m.ChannelResult
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Packet) GetKeepAlive() *KeepAlive {
|
|
||||||
if m != nil {
|
|
||||||
return m.KeepAlive
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Packet) GetEnableFeatures() *EnableFeatures {
|
|
||||||
if m != nil {
|
|
||||||
return m.EnableFeatures
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Packet) GetFeaturesEnabled() *FeaturesEnabled {
|
|
||||||
if m != nil {
|
|
||||||
return m.FeaturesEnabled
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenChannel struct {
|
|
||||||
ChannelIdentifier *int32 `protobuf:"varint,1,req,name=channel_identifier,json=channelIdentifier" json:"channel_identifier,omitempty"`
|
|
||||||
ChannelType *string `protobuf:"bytes,2,req,name=channel_type,json=channelType" json:"channel_type,omitempty"`
|
|
||||||
proto.XXX_InternalExtensions `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *OpenChannel) Reset() { *m = OpenChannel{} }
|
|
||||||
func (m *OpenChannel) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*OpenChannel) ProtoMessage() {}
|
|
||||||
func (*OpenChannel) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
|
||||||
|
|
||||||
var extRange_OpenChannel = []proto.ExtensionRange{
|
|
||||||
{Start: 100, End: 536870911},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange {
|
|
||||||
return extRange_OpenChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *OpenChannel) GetChannelIdentifier() int32 {
|
|
||||||
if m != nil && m.ChannelIdentifier != nil {
|
|
||||||
return *m.ChannelIdentifier
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *OpenChannel) GetChannelType() string {
|
|
||||||
if m != nil && m.ChannelType != nil {
|
|
||||||
return *m.ChannelType
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChannelResult struct {
|
|
||||||
ChannelIdentifier *int32 `protobuf:"varint,1,req,name=channel_identifier,json=channelIdentifier" json:"channel_identifier,omitempty"`
|
|
||||||
Opened *bool `protobuf:"varint,2,req,name=opened" json:"opened,omitempty"`
|
|
||||||
CommonError *ChannelResult_CommonError `protobuf:"varint,3,opt,name=common_error,json=commonError,enum=protocol.ChannelResult_CommonError" json:"common_error,omitempty"`
|
|
||||||
proto.XXX_InternalExtensions `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ChannelResult) Reset() { *m = ChannelResult{} }
|
|
||||||
func (m *ChannelResult) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*ChannelResult) ProtoMessage() {}
|
|
||||||
func (*ChannelResult) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
|
||||||
|
|
||||||
var extRange_ChannelResult = []proto.ExtensionRange{
|
|
||||||
{Start: 100, End: 536870911},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange {
|
|
||||||
return extRange_ChannelResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ChannelResult) GetChannelIdentifier() int32 {
|
|
||||||
if m != nil && m.ChannelIdentifier != nil {
|
|
||||||
return *m.ChannelIdentifier
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ChannelResult) GetOpened() bool {
|
|
||||||
if m != nil && m.Opened != nil {
|
|
||||||
return *m.Opened
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ChannelResult) GetCommonError() ChannelResult_CommonError {
|
|
||||||
if m != nil && m.CommonError != nil {
|
|
||||||
return *m.CommonError
|
|
||||||
}
|
|
||||||
return ChannelResult_GenericError
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeepAlive struct {
|
|
||||||
ResponseRequested *bool `protobuf:"varint,1,req,name=response_requested,json=responseRequested" json:"response_requested,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *KeepAlive) Reset() { *m = KeepAlive{} }
|
|
||||||
func (m *KeepAlive) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*KeepAlive) ProtoMessage() {}
|
|
||||||
func (*KeepAlive) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
|
||||||
|
|
||||||
func (m *KeepAlive) GetResponseRequested() bool {
|
|
||||||
if m != nil && m.ResponseRequested != nil {
|
|
||||||
return *m.ResponseRequested
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type EnableFeatures struct {
|
|
||||||
Feature []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"`
|
|
||||||
proto.XXX_InternalExtensions `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *EnableFeatures) Reset() { *m = EnableFeatures{} }
|
|
||||||
func (m *EnableFeatures) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*EnableFeatures) ProtoMessage() {}
|
|
||||||
func (*EnableFeatures) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
|
||||||
|
|
||||||
var extRange_EnableFeatures = []proto.ExtensionRange{
|
|
||||||
{Start: 100, End: 536870911},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange {
|
|
||||||
return extRange_EnableFeatures
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *EnableFeatures) GetFeature() []string {
|
|
||||||
if m != nil {
|
|
||||||
return m.Feature
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeaturesEnabled struct {
|
|
||||||
Feature []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"`
|
|
||||||
proto.XXX_InternalExtensions `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FeaturesEnabled) Reset() { *m = FeaturesEnabled{} }
|
|
||||||
func (m *FeaturesEnabled) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*FeaturesEnabled) ProtoMessage() {}
|
|
||||||
func (*FeaturesEnabled) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
|
||||||
|
|
||||||
var extRange_FeaturesEnabled = []proto.ExtensionRange{
|
|
||||||
{Start: 100, End: 536870911},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange {
|
|
||||||
return extRange_FeaturesEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FeaturesEnabled) GetFeature() []string {
|
|
||||||
if m != nil {
|
|
||||||
return m.Feature
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proto.RegisterType((*Packet)(nil), "protocol.Packet")
|
|
||||||
proto.RegisterType((*OpenChannel)(nil), "protocol.OpenChannel")
|
|
||||||
proto.RegisterType((*ChannelResult)(nil), "protocol.ChannelResult")
|
|
||||||
proto.RegisterType((*KeepAlive)(nil), "protocol.KeepAlive")
|
|
||||||
proto.RegisterType((*EnableFeatures)(nil), "protocol.EnableFeatures")
|
|
||||||
proto.RegisterType((*FeaturesEnabled)(nil), "protocol.FeaturesEnabled")
|
|
||||||
proto.RegisterEnum("protocol.ChannelResult_CommonError", ChannelResult_CommonError_name, ChannelResult_CommonError_value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { proto.RegisterFile("ControlChannel.proto", fileDescriptor0) }
|
|
||||||
|
|
||||||
var fileDescriptor0 = []byte{
|
|
||||||
// 461 bytes of a gzipped FileDescriptorProto
|
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0x4d, 0x8f, 0xd3, 0x30,
|
|
||||||
0x10, 0x25, 0xe9, 0xee, 0x92, 0x4e, 0xfa, 0x91, 0x9a, 0x5d, 0x30, 0xb7, 0x12, 0x2e, 0x15, 0x12,
|
|
||||||
0x3d, 0x54, 0x20, 0x21, 0x0e, 0x48, 0x4b, 0xd9, 0x22, 0xc4, 0x01, 0x64, 0xd1, 0x73, 0x64, 0x92,
|
|
||||||
0x29, 0x1b, 0x35, 0x6b, 0x1b, 0xc7, 0x05, 0x2d, 0xa7, 0xfe, 0x0e, 0xfe, 0x0c, 0x7f, 0x0d, 0xc5,
|
|
||||||
0x89, 0x9b, 0x14, 0x09, 0x09, 0x4e, 0xc9, 0x9b, 0xf7, 0xde, 0x8c, 0xfc, 0x66, 0xe0, 0x7c, 0x29,
|
|
||||||
0x85, 0xd1, 0xb2, 0x58, 0x5e, 0x73, 0x21, 0xb0, 0x98, 0x2b, 0x2d, 0x8d, 0x24, 0x81, 0xfd, 0xa4,
|
|
||||||
0xb2, 0x88, 0x7f, 0xf9, 0x70, 0xf6, 0x91, 0xa7, 0x5b, 0x34, 0xe4, 0x05, 0x0c, 0xa4, 0x42, 0x91,
|
|
||||||
0xa4, 0xb5, 0x94, 0x7a, 0x53, 0x6f, 0x16, 0x2e, 0x2e, 0xe6, 0x4e, 0x3b, 0xff, 0xa0, 0x50, 0x34,
|
|
||||||
0x7d, 0x58, 0x28, 0x5b, 0x40, 0x5e, 0xc1, 0xa8, 0x31, 0x25, 0x1a, 0xcb, 0x5d, 0x61, 0xa8, 0x6f,
|
|
||||||
0xbd, 0x0f, 0x5a, 0xaf, 0xf3, 0x59, 0x9a, 0x0d, 0xd3, 0x2e, 0x24, 0x0b, 0x80, 0x2d, 0xa2, 0x4a,
|
|
||||||
0x78, 0x91, 0x7f, 0x43, 0xda, 0xb3, 0xde, 0x7b, 0xad, 0xf7, 0x3d, 0xa2, 0xba, 0xac, 0x28, 0xd6,
|
|
||||||
0xdf, 0xba, 0x5f, 0x72, 0x09, 0x63, 0x14, 0xfc, 0x73, 0x81, 0xc9, 0x06, 0xb9, 0xd9, 0x69, 0x2c,
|
|
||||||
0xe9, 0x89, 0x35, 0xd2, 0xd6, 0x78, 0x65, 0x05, 0xab, 0x86, 0x67, 0x23, 0x3c, 0xc2, 0xe4, 0x0d,
|
|
||||||
0x44, 0xce, 0x9b, 0xd4, 0x54, 0x46, 0x4f, 0x6d, 0x8f, 0x87, 0x6d, 0x0f, 0xa7, 0xae, 0x7b, 0x65,
|
|
||||||
0x6c, 0xbc, 0x39, 0x2e, 0xc4, 0x39, 0x84, 0x9d, 0x60, 0xc8, 0x53, 0x20, 0x2e, 0x8b, 0x3c, 0x43,
|
|
||||||
0x61, 0xf2, 0x4d, 0x8e, 0x9a, 0x7a, 0x53, 0x7f, 0x76, 0xca, 0x26, 0x0d, 0xf3, 0xee, 0x40, 0x90,
|
|
||||||
0x47, 0x30, 0x70, 0x72, 0x73, 0xab, 0x90, 0xfa, 0x53, 0x7f, 0xd6, 0x67, 0x61, 0x53, 0xfb, 0x74,
|
|
||||||
0xab, 0xf0, 0x49, 0x10, 0x64, 0xd1, 0x7e, 0xbf, 0xdf, 0xfb, 0xf1, 0x4f, 0x1f, 0x86, 0x47, 0x41,
|
|
||||||
0xfe, 0xef, 0xb4, 0xfb, 0x70, 0x56, 0xed, 0x0d, 0x33, 0x3b, 0x27, 0x60, 0x0d, 0x22, 0x2b, 0x18,
|
|
||||||
0xa4, 0xf2, 0xe6, 0x46, 0x8a, 0x04, 0xb5, 0x96, 0xda, 0xae, 0x60, 0xb4, 0x78, 0xfc, 0x97, 0xf5,
|
|
||||||
0xcd, 0x97, 0x56, 0x7b, 0x55, 0x49, 0x59, 0x98, 0xb6, 0x20, 0x56, 0x10, 0x76, 0x38, 0x12, 0xc1,
|
|
||||||
0xe0, 0x2d, 0x0a, 0xd4, 0x79, 0x6a, 0x71, 0x74, 0x87, 0x9c, 0x43, 0xb4, 0x16, 0x5b, 0x21, 0xbf,
|
|
||||||
0x8b, 0xea, 0x69, 0x75, 0xd5, 0x23, 0x17, 0x30, 0x59, 0x0b, 0xbe, 0x33, 0xd7, 0x52, 0xe7, 0x3f,
|
|
||||||
0x30, 0xab, 0xcb, 0x3e, 0x99, 0xc0, 0xf0, 0x35, 0xcf, 0xd6, 0x25, 0xff, 0xd2, 0x28, 0x7b, 0x64,
|
|
||||||
0x0c, 0xe1, 0x8a, 0xe7, 0x85, 0xd3, 0x9c, 0x74, 0xc2, 0x79, 0x09, 0xfd, 0xc3, 0xa1, 0x54, 0xb9,
|
|
||||||
0x68, 0x2c, 0x95, 0x14, 0x25, 0x26, 0x1a, 0xbf, 0xee, 0xb0, 0x34, 0x98, 0xd9, 0x5c, 0x02, 0x36,
|
|
||||||
0x71, 0x0c, 0x73, 0x44, 0xfc, 0x0c, 0x46, 0xc7, 0xb7, 0x42, 0x28, 0xdc, 0x6d, 0x16, 0x4d, 0xbd,
|
|
||||||
0x69, 0x6f, 0xd6, 0x67, 0x0e, 0x76, 0x26, 0x3e, 0x87, 0xf1, 0x1f, 0xd7, 0xf1, 0x2f, 0xb6, 0xdf,
|
|
||||||
0x01, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x32, 0x16, 0x1e, 0x93, 0x03, 0x00, 0x00,
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
syntax = "proto2";
|
|
||||||
package protocol;
|
|
||||||
|
|
||||||
message Packet {
|
|
||||||
// Must contain exactly one field
|
|
||||||
optional OpenChannel open_channel = 1;
|
|
||||||
optional ChannelResult channel_result = 2;
|
|
||||||
optional KeepAlive keep_alive = 3;
|
|
||||||
optional EnableFeatures enable_features = 4;
|
|
||||||
optional FeaturesEnabled features_enabled = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OpenChannel {
|
|
||||||
required int32 channel_identifier = 1; // Arbitrary unique identifier for this channel instance
|
|
||||||
required string channel_type = 2; // String identifying channel type; e.g. im.ricochet.chat
|
|
||||||
|
|
||||||
// It is valid to extend the OpenChannel message to add fields specific
|
|
||||||
// to the requested channel_type.
|
|
||||||
extensions 100 to max;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ChannelResult {
|
|
||||||
required int32 channel_identifier = 1; // Matching the value from OpenChannel
|
|
||||||
required bool opened = 2; // If the channel is now open
|
|
||||||
|
|
||||||
enum CommonError {
|
|
||||||
GenericError = 0;
|
|
||||||
UnknownTypeError = 1;
|
|
||||||
UnauthorizedError = 2;
|
|
||||||
BadUsageError = 3;
|
|
||||||
FailedError = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
optional CommonError common_error = 3;
|
|
||||||
|
|
||||||
// As with OpenChannel, it is valid to extend this message with fields specific
|
|
||||||
// to the channel type.
|
|
||||||
extensions 100 to max;
|
|
||||||
}
|
|
||||||
|
|
||||||
message KeepAlive {
|
|
||||||
required bool response_requested = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EnableFeatures {
|
|
||||||
repeated string feature = 1;
|
|
||||||
extensions 100 to max;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FeaturesEnabled {
|
|
||||||
repeated string feature = 1;
|
|
||||||
extensions 100 to max;
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
|
||||||
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Manager encapsulates all the logic necessary to manage outgoing peer and server connections.
|
|
||||||
type Manager struct {
|
|
||||||
serverConnections map[string]*PeerServerConnection
|
|
||||||
lock sync.Mutex
|
|
||||||
acn connectivity.ACN
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnectionsManager creates a new instance of Manager.
|
|
||||||
func NewConnectionsManager(acn connectivity.ACN) *Manager {
|
|
||||||
m := new(Manager)
|
|
||||||
m.acn = acn
|
|
||||||
m.serverConnections = make(map[string]*PeerServerConnection)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManageServerConnection creates a new ServerConnection for Host with the given callback handler.
|
|
||||||
// If there is an establish connection, it is replaced with a new one, assuming this came from
|
|
||||||
// a new JoinServer from a new Group being joined. If it is still connecting to a server, the second request will be abandonded
|
|
||||||
func (m *Manager) ManageServerConnection(host string, engine Engine, messageHandler func(string, *protocol.GroupMessage)) {
|
|
||||||
m.lock.Lock()
|
|
||||||
defer m.lock.Unlock()
|
|
||||||
|
|
||||||
psc, exists := m.serverConnections[host]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
if psc.GetState() == DISCONNECTED || psc.GetState() == CONNECTING || psc.GetState() == CONNECTED {
|
|
||||||
log.Infof("Already connecting to %v, abandoning fresh attempt\n", host)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newPsc := NewPeerServerConnection(engine, host)
|
|
||||||
newPsc.GroupMessageHandler = messageHandler
|
|
||||||
go newPsc.Run()
|
|
||||||
m.serverConnections[host] = newPsc
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
log.Infof("Closing connection to %v, replacing with this one\n", host)
|
|
||||||
psc.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetServerSynced is a helper for peerserver connections and engine to call when a Fetch is done to set the state of the connection to SYNCED
|
|
||||||
func (m *Manager) SetServerSynced(onion string) {
|
|
||||||
m.serverConnections[onion].setState(SYNCED)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerServerConnectionForOnion safely returns a given host connection
|
|
||||||
func (m *Manager) GetPeerServerConnectionForOnion(host string) (psc *PeerServerConnection) {
|
|
||||||
m.lock.Lock()
|
|
||||||
psc = m.serverConnections[host]
|
|
||||||
m.lock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown closes all connections under management (freeing their goroutines)
|
|
||||||
func (m *Manager) Shutdown() {
|
|
||||||
m.lock.Lock()
|
|
||||||
for onion, psc := range m.serverConnections {
|
|
||||||
psc.Close()
|
|
||||||
delete(m.serverConnections, onion)
|
|
||||||
}
|
|
||||||
m.lock.Unlock()
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConnectionsManager(t *testing.T) {
|
|
||||||
// TODO We need to encapsulate connections behind a well defined interface for tesintg
|
|
||||||
NewConnectionsManager(connectivity.NewLocalACN())
|
|
||||||
}
|
|
|
@ -3,15 +3,17 @@ package connections
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/event"
|
"cwtch.im/cwtch/event"
|
||||||
"cwtch.im/cwtch/model"
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
"cwtch.im/tapir"
|
"cwtch.im/tapir"
|
||||||
"cwtch.im/tapir/networks/tor"
|
"cwtch.im/tapir/networks/tor"
|
||||||
"cwtch.im/tapir/primitives"
|
"cwtch.im/tapir/primitives"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
"git.openprivacy.ca/openprivacy/connectivity"
|
||||||
torProdider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
torProdider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
"github.com/gtank/ristretto255"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -20,7 +22,6 @@ import (
|
||||||
|
|
||||||
type engine struct {
|
type engine struct {
|
||||||
queue event.Queue
|
queue event.Queue
|
||||||
connectionsManager *Manager
|
|
||||||
|
|
||||||
// Engine Attributes
|
// Engine Attributes
|
||||||
identity primitives.Identity
|
identity primitives.Identity
|
||||||
|
@ -41,6 +42,9 @@ type engine struct {
|
||||||
// Nextgen Tapir Service
|
// Nextgen Tapir Service
|
||||||
service tapir.Service
|
service tapir.Service
|
||||||
|
|
||||||
|
// Nextgen Tapir Service
|
||||||
|
ephemeralServices sync.Map // string(onion) => tapir.Service
|
||||||
|
|
||||||
// Required for listen(), inaccessible from identity
|
// Required for listen(), inaccessible from identity
|
||||||
privateKey ed25519.PrivateKey
|
privateKey ed25519.PrivateKey
|
||||||
|
|
||||||
|
@ -64,7 +68,6 @@ func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateK
|
||||||
go engine.eventHandler()
|
go engine.eventHandler()
|
||||||
|
|
||||||
engine.acn = acn
|
engine.acn = acn
|
||||||
engine.connectionsManager = NewConnectionsManager(engine.acn)
|
|
||||||
|
|
||||||
// Init the Server running the Simple App.
|
// Init the Server running the Simple App.
|
||||||
engine.service = new(tor.BaseOnionService)
|
engine.service = new(tor.BaseOnionService)
|
||||||
|
@ -122,7 +125,7 @@ func (e *engine) eventHandler() {
|
||||||
case event.InvitePeerToGroup:
|
case event.InvitePeerToGroup:
|
||||||
e.sendMessageToPeer(ev.EventID, ev.Data[event.RemotePeer], event.ContextInvite, []byte(ev.Data[event.GroupInvite]))
|
e.sendMessageToPeer(ev.EventID, ev.Data[event.RemotePeer], event.ContextInvite, []byte(ev.Data[event.GroupInvite]))
|
||||||
case event.JoinServer:
|
case event.JoinServer:
|
||||||
e.joinServer(ev.Data[event.GroupServer])
|
e.peerWithTokenServer(ev.Data[event.GroupServer], ev.Data[event.ServerTokenOnion], ev.Data[event.ServerTokenY])
|
||||||
case event.DeleteContact:
|
case event.DeleteContact:
|
||||||
onion := ev.Data[event.RemotePeer]
|
onion := ev.Data[event.RemotePeer]
|
||||||
// We remove this peer from out blocklist which will prevent them from contacting us if we have "block unknown peers" turned on.
|
// We remove this peer from out blocklist which will prevent them from contacting us if we have "block unknown peers" turned on.
|
||||||
|
@ -213,7 +216,6 @@ func (e *engine) listenFn() {
|
||||||
// Shutdown tears down the eventHandler goroutine
|
// Shutdown tears down the eventHandler goroutine
|
||||||
func (e *engine) Shutdown() {
|
func (e *engine) Shutdown() {
|
||||||
e.shuttingDown = true
|
e.shuttingDown = true
|
||||||
e.connectionsManager.Shutdown()
|
|
||||||
e.service.Shutdown()
|
e.service.Shutdown()
|
||||||
e.queue.Shutdown()
|
e.queue.Shutdown()
|
||||||
}
|
}
|
||||||
|
@ -244,6 +246,38 @@ func (e *engine) peerWithOnion(onion string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// peerWithTokenServer is the entry point for cwtchPeer - server relationships
|
||||||
|
// needs to be run in a goroutine as will block on Open.
|
||||||
|
func (e *engine) peerWithTokenServer(onion string, tokenServerOnion string, tokenServerY string) {
|
||||||
|
e.ignoreOnShutdown(e.serverConnecting)(onion)
|
||||||
|
|
||||||
|
// Create a new ephemeral service for this connection
|
||||||
|
ephemeralService := new(tor.BaseOnionService)
|
||||||
|
eid, epk := primitives.InitializeEphemeralIdentity()
|
||||||
|
ephemeralService.Init(e.acn, epk, &eid)
|
||||||
|
|
||||||
|
Y := ristretto255.NewElement()
|
||||||
|
Y.UnmarshalText([]byte(tokenServerY))
|
||||||
|
connected, err := ephemeralService.Connect(onion, NewTokenBoardClient(e.acn, Y, tokenServerOnion, e.receiveGroupMessage, e.serverSynced))
|
||||||
|
e.ephemeralServices.Store(onion, ephemeralService)
|
||||||
|
// If we are already connected...check if we are authed and issue an auth event
|
||||||
|
// (This allows the ui to be stateless)
|
||||||
|
if connected && err != nil {
|
||||||
|
conn, err := ephemeralService.GetConnection(onion)
|
||||||
|
if err == nil {
|
||||||
|
if conn.HasCapability(groups.CwtchServerSyncedCapability) {
|
||||||
|
e.ignoreOnShutdown(e.serverConnected)(onion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only issue a disconnected error if we are disconnected (Connect will fail if a connection already exists)
|
||||||
|
if !connected && err != nil {
|
||||||
|
e.ignoreOnShutdown(e.serverDisconnected)(onion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *engine) ignoreOnShutdown(f func(string)) func(string) {
|
func (e *engine) ignoreOnShutdown(f func(string)) func(string) {
|
||||||
return func(x string) {
|
return func(x string) {
|
||||||
if !e.shuttingDown {
|
if !e.shuttingDown {
|
||||||
|
@ -278,6 +312,35 @@ func (e *engine) peerConnecting(onion string) {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *engine) serverConnecting(onion string) {
|
||||||
|
e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{
|
||||||
|
event.GroupServer: string(onion),
|
||||||
|
event.ConnectionState: ConnectionStateName[CONNECTING],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *engine) serverConnected(onion string) {
|
||||||
|
e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{
|
||||||
|
event.GroupServer: onion,
|
||||||
|
event.ConnectionState: ConnectionStateName[CONNECTED],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *engine) serverSynced(onion string) {
|
||||||
|
log.Debugf("SERVER SYNCED: %v", onion)
|
||||||
|
e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{
|
||||||
|
event.GroupServer: onion,
|
||||||
|
event.ConnectionState: ConnectionStateName[SYNCED],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *engine) serverDisconnected(onion string) {
|
||||||
|
e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{
|
||||||
|
event.GroupServer: onion,
|
||||||
|
event.ConnectionState: ConnectionStateName[DISCONNECTED],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (e *engine) peerAck(onion string, eventID string) {
|
func (e *engine) peerAck(onion string, eventID string) {
|
||||||
e.eventManager.Publish(event.NewEvent(event.PeerAcknowledgement, map[event.Field]string{
|
e.eventManager.Publish(event.NewEvent(event.PeerAcknowledgement, map[event.Field]string{
|
||||||
event.EventID: eventID,
|
event.EventID: eventID,
|
||||||
|
@ -335,32 +398,31 @@ func (e *engine) deleteConnection(id string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// receiveGroupMessage is a callback function that processes GroupMessages from a given server
|
// receiveGroupMessage is a callback function that processes GroupMessages from a given server
|
||||||
func (e *engine) receiveGroupMessage(server string, gm *protocol.GroupMessage) {
|
func (e *engine) receiveGroupMessage(server string, gm *groups.EncryptedGroupMessage) {
|
||||||
// Publish Event so that a Profile Engine can deal with it.
|
// Publish Event so that a Profile Engine can deal with it.
|
||||||
// Note: This technically means that *multiple* Profile Engines could listen to the same ProtocolEngine!
|
// Note: This technically means that *multiple* Profile Engines could listen to the same ProtocolEngine!
|
||||||
e.eventManager.Publish(event.NewEvent(event.EncryptedGroupMessage, map[event.Field]string{event.Ciphertext: string(gm.GetCiphertext()), event.Signature: string(gm.GetSignature())}))
|
e.eventManager.Publish(event.NewEvent(event.EncryptedGroupMessage, map[event.Field]string{event.Ciphertext: string(gm.Ciphertext), event.Signature: string(gm.Signature)}))
|
||||||
}
|
|
||||||
|
|
||||||
// joinServer manages a new server connection with the given onion address
|
|
||||||
func (e *engine) joinServer(onion string) {
|
|
||||||
e.connectionsManager.ManageServerConnection(onion, e, e.receiveGroupMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendMessageToGroup attempts to sent the given message to the given group id.
|
// sendMessageToGroup attempts to sent the given message to the given group id.
|
||||||
func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) {
|
func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) error {
|
||||||
psc := e.connectionsManager.GetPeerServerConnectionForOnion(server)
|
es, ok := e.ephemeralServices.Load(server)
|
||||||
if psc == nil {
|
if !ok {
|
||||||
e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: server, event.Signature: string(sig), event.Error: "server is offline or the connection has yet to finalize"}))
|
return fmt.Errorf("no service exists for group %v", server)
|
||||||
}
|
}
|
||||||
gm := &protocol.GroupMessage{
|
ephemeralService := es.(tapir.Service)
|
||||||
Ciphertext: ct,
|
conn, err := ephemeralService.WaitForCapabilityOrClose(server, groups.CwtchServerSyncedCapability)
|
||||||
Signature: sig,
|
if err == nil {
|
||||||
|
tokenApp, ok := (conn.App()).(*TokenBoardClient)
|
||||||
|
if ok {
|
||||||
|
for tokenApp.Post(ct, sig) == false {
|
||||||
|
tokenApp.MakePayment()
|
||||||
}
|
}
|
||||||
err := psc.SendGroupMessage(gm)
|
return nil
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: server, event.Signature: string(sig), event.Error: err.Error()}))
|
|
||||||
}
|
}
|
||||||
|
return errors.New("failed type assertion conn.App != TokenBoardClientApp")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *engine) handlePeerMessage(hostname string, eventID string, context string, message []byte) {
|
func (e *engine) handlePeerMessage(hostname string, eventID string, context string, message []byte) {
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
package fetch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CwtchPeerFetchChannel is the peer implementation of the im.cwtch.server.fetch
|
|
||||||
// channel.
|
|
||||||
type CwtchPeerFetchChannel struct {
|
|
||||||
channel *channels.Channel
|
|
||||||
Handler CwtchPeerFetchChannelHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// CwtchPeerFetchChannelHandler should be implemented by peers to receive new messages.
|
|
||||||
type CwtchPeerFetchChannelHandler interface {
|
|
||||||
HandleGroupMessage(*protocol.GroupMessage)
|
|
||||||
HandleFetchDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type string for this channel, e.g. "im.ricochet.server.fetch)
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) Type() string {
|
|
||||||
return "im.cwtch.server.fetch"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed is called when the channel is closed for any reason.
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) Closed(err error) {
|
|
||||||
cpfc.Handler.HandleFetchDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyClientCanOpen - for Cwtch server channels only client can open
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) OnlyClientCanOpen() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton - for Cwtch channels there can only be one instance per direction
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) Singleton() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bidirectional - for Cwtch channels are not bidrectional
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) Bidirectional() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresAuthentication - Cwtch server channels require no auth.
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) RequiresAuthentication() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInbound - cwtch server peer implementations shouldnever respond to inbound requests
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
|
||||||
return nil, errors.New("client does not receive inbound listen channels")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutbound sets up a new cwtch fetch channel
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
|
||||||
cpfc.channel = channel
|
|
||||||
messageBuilder := new(utils.MessageBuilder)
|
|
||||||
return messageBuilder.OpenChannel(channel.ID, cpfc.Type()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutboundResult confirms a previous open channel request
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
|
||||||
if err == nil {
|
|
||||||
if crm.GetOpened() {
|
|
||||||
cpfc.channel.Pending = false
|
|
||||||
cpfc.FetchRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchRequest sends a FetchMessage to the Server.
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) FetchRequest() error {
|
|
||||||
if cpfc.channel.Pending == false {
|
|
||||||
fm := &protocol.FetchMessage{}
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
FetchMessage: fm,
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
cpfc.channel.SendMessage(packet)
|
|
||||||
} else {
|
|
||||||
return errors.New("channel isn't set up yet")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet is called for each raw packet received on this channel.
|
|
||||||
func (cpfc *CwtchPeerFetchChannel) Packet(data []byte) {
|
|
||||||
csp := &protocol.CwtchServerPacket{}
|
|
||||||
err := proto.Unmarshal(data, csp)
|
|
||||||
if err == nil {
|
|
||||||
if csp.GetGroupMessage() != nil {
|
|
||||||
gm := csp.GetGroupMessage()
|
|
||||||
// We create a new go routine here to avoid leaking any information about processing time
|
|
||||||
// TODO Server can probably try to use this to DoS a peer
|
|
||||||
cpfc.Handler.HandleGroupMessage(gm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package fetch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestHandler struct {
|
|
||||||
Received bool
|
|
||||||
Closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (th *TestHandler) HandleGroupMessage(m *protocol.GroupMessage) {
|
|
||||||
th.Received = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (th *TestHandler) HandleFetchDone() {
|
|
||||||
th.Closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerFetchChannelAttributes(t *testing.T) {
|
|
||||||
cssc := new(CwtchPeerFetchChannel)
|
|
||||||
if cssc.Type() != "im.cwtch.server.fetch" {
|
|
||||||
t.Errorf("cwtch channel type is incorrect %v", cssc.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.OnlyClientCanOpen() {
|
|
||||||
t.Errorf("only clients should be able to open im.cwtch.server.Fetch channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.Bidirectional() {
|
|
||||||
t.Errorf("im.cwtch.server.fetch should not be bidirectional")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.Singleton() {
|
|
||||||
t.Errorf("im.cwtch.server.fetch should be a Singleton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.RequiresAuthentication() != "none" {
|
|
||||||
t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestPeerFetchChannelOpenInbound(t *testing.T) {
|
|
||||||
cssc := new(CwtchPeerFetchChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
_, err := cssc.OpenInbound(channel, nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("client implementation of im.cwtch.server.Fetch should never open an inbound channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerFetchChannel(t *testing.T) {
|
|
||||||
pfc := new(CwtchPeerFetchChannel)
|
|
||||||
th := new(TestHandler)
|
|
||||||
pfc.Handler = th
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 3
|
|
||||||
channel.SendMessage = func([]byte) {}
|
|
||||||
channel.CloseChannel = func() {}
|
|
||||||
result, err := pfc.OpenOutbound(channel)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected result but also got non-nil error: result:%v, err: %v", result, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := &Protocol_Data_Control.ChannelResult{
|
|
||||||
ChannelIdentifier: proto.Int32(3),
|
|
||||||
Opened: proto.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
pfc.OpenOutboundResult(nil, cr)
|
|
||||||
if channel.Pending {
|
|
||||||
t.Errorf("once opened channel should no longer be pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: &protocol.GroupMessage{
|
|
||||||
Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
|
|
||||||
pfc.Packet(packet)
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 2)
|
|
||||||
|
|
||||||
if th.Received != true {
|
|
||||||
t.Errorf("group message should not have been received")
|
|
||||||
}
|
|
||||||
|
|
||||||
pfc.Closed(nil)
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package listen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CwtchPeerListenChannel is the peer implementation of im.cwtch.server.listen
|
|
||||||
type CwtchPeerListenChannel struct {
|
|
||||||
channel *channels.Channel
|
|
||||||
Handler CwtchPeerSendChannelHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// CwtchPeerSendChannelHandler is implemented by peers who want to listen to new messages
|
|
||||||
type CwtchPeerSendChannelHandler interface {
|
|
||||||
HandleGroupMessage(*protocol.GroupMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type string for this channel, e.g. "im.ricochet.server.listen".
|
|
||||||
func (cplc *CwtchPeerListenChannel) Type() string {
|
|
||||||
return "im.cwtch.server.listen"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed is called when the channel is closed for any reason.
|
|
||||||
func (cplc *CwtchPeerListenChannel) Closed(err error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyClientCanOpen - for Cwtch server channels can only be opened by peers
|
|
||||||
func (cplc *CwtchPeerListenChannel) OnlyClientCanOpen() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton - for Cwtch channels there can only be one instance per direction
|
|
||||||
func (cplc *CwtchPeerListenChannel) Singleton() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bidirectional - for Cwtch channels are not bidrectional
|
|
||||||
func (cplc *CwtchPeerListenChannel) Bidirectional() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresAuthentication - Cwtch channels require no auth channels
|
|
||||||
func (cplc *CwtchPeerListenChannel) RequiresAuthentication() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInbound - peers should never respond to open inbound requests from servers
|
|
||||||
func (cplc *CwtchPeerListenChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
|
||||||
return nil, errors.New("client does not receive inbound listen channels")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutbound sets up a new server listen channel
|
|
||||||
func (cplc *CwtchPeerListenChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
|
||||||
cplc.channel = channel
|
|
||||||
messageBuilder := new(utils.MessageBuilder)
|
|
||||||
return messageBuilder.OpenChannel(channel.ID, cplc.Type()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutboundResult confirms a previous open channel request
|
|
||||||
func (cplc *CwtchPeerListenChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
|
||||||
if err == nil {
|
|
||||||
if crm.GetOpened() {
|
|
||||||
cplc.channel.Pending = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet is called for each server packet received on this channel.
|
|
||||||
func (cplc *CwtchPeerListenChannel) Packet(data []byte) {
|
|
||||||
csp := &protocol.CwtchServerPacket{}
|
|
||||||
err := proto.Unmarshal(data, csp)
|
|
||||||
if err == nil {
|
|
||||||
if csp.GetGroupMessage() != nil {
|
|
||||||
gm := csp.GetGroupMessage()
|
|
||||||
cplc.Handler.HandleGroupMessage(gm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
package listen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestHandler struct {
|
|
||||||
Received bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (th *TestHandler) HandleGroupMessage(m *protocol.GroupMessage) {
|
|
||||||
th.Received = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerListenChannelAttributes(t *testing.T) {
|
|
||||||
cssc := new(CwtchPeerListenChannel)
|
|
||||||
if cssc.Type() != "im.cwtch.server.listen" {
|
|
||||||
t.Errorf("cwtch channel type is incorrect %v", cssc.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.OnlyClientCanOpen() {
|
|
||||||
t.Errorf("only clients should be able to open im.cwtch.server.listen channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.Bidirectional() {
|
|
||||||
t.Errorf("im.cwtch.server.listen should not be bidirectional")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.Singleton() {
|
|
||||||
t.Errorf("im.cwtch.server.listen should be a Singleton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.RequiresAuthentication() != "none" {
|
|
||||||
t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestPeerListenChannelOpenInbound(t *testing.T) {
|
|
||||||
cssc := new(CwtchPeerListenChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
_, err := cssc.OpenInbound(channel, nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("client implementation of im.cwtch.server.Listen should never open an inbound channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerListenChannel(t *testing.T) {
|
|
||||||
pfc := new(CwtchPeerListenChannel)
|
|
||||||
th := new(TestHandler)
|
|
||||||
pfc.Handler = th
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 3
|
|
||||||
result, err := pfc.OpenOutbound(channel)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected result but also got non-nil error: result:%v, err: %v", result, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := &Protocol_Data_Control.ChannelResult{
|
|
||||||
ChannelIdentifier: proto.Int32(3),
|
|
||||||
Opened: proto.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
pfc.OpenOutboundResult(nil, cr)
|
|
||||||
if channel.Pending {
|
|
||||||
t.Errorf("once opened channel should no longer be pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: &protocol.GroupMessage{Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}},
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
|
|
||||||
pfc.Packet(packet)
|
|
||||||
|
|
||||||
// Wait for goroutine to run
|
|
||||||
time.Sleep(time.Second * 1)
|
|
||||||
|
|
||||||
if !th.Received {
|
|
||||||
t.Errorf("group message should have been received")
|
|
||||||
}
|
|
||||||
|
|
||||||
pfc.Closed(nil)
|
|
||||||
|
|
||||||
}
|
|
|
@ -83,7 +83,7 @@ func (pa *PeerApp) listen() {
|
||||||
for {
|
for {
|
||||||
message := pa.connection.Expect()
|
message := pa.connection.Expect()
|
||||||
if len(message) == 0 {
|
if len(message) == 0 {
|
||||||
log.Errorf("0 byte read, socket has likely failed. Closing the listen goroutine")
|
log.Debugf("0 byte read, socket has likely failed. Closing the listen goroutine")
|
||||||
pa.OnClose(pa.connection.Hostname())
|
pa.OnClose(pa.connection.Hostname())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
package connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"cwtch.im/cwtch/event"
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/fetch"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/listen"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/send"
|
|
||||||
"errors"
|
|
||||||
"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/identity"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PeerServerConnection encapsulates a single Peer->Server connection
|
|
||||||
type PeerServerConnection struct {
|
|
||||||
connection.AutoConnectionHandler
|
|
||||||
Server string
|
|
||||||
stateMutex sync.Mutex
|
|
||||||
state ConnectionState
|
|
||||||
connection *connection.Connection
|
|
||||||
protocolEngine Engine
|
|
||||||
|
|
||||||
GroupMessageHandler func(string, *protocol.GroupMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPeerServerConnection creates a new Peer->Server outbound connection
|
|
||||||
func NewPeerServerConnection(engine Engine, serverhostname string) *PeerServerConnection {
|
|
||||||
psc := new(PeerServerConnection)
|
|
||||||
psc.protocolEngine = engine
|
|
||||||
psc.Server = serverhostname
|
|
||||||
psc.setState(DISCONNECTED)
|
|
||||||
psc.Init()
|
|
||||||
return psc
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current connection state
|
|
||||||
func (psc *PeerServerConnection) GetState() ConnectionState {
|
|
||||||
psc.stateMutex.Lock()
|
|
||||||
defer psc.stateMutex.Unlock()
|
|
||||||
return psc.state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (psc *PeerServerConnection) setState(state ConnectionState) {
|
|
||||||
log.Debugf("Setting State to %v for %v\n", ConnectionStateName[state], psc.Server)
|
|
||||||
psc.stateMutex.Lock()
|
|
||||||
defer psc.stateMutex.Unlock()
|
|
||||||
psc.state = state
|
|
||||||
psc.protocolEngine.EventManager().Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{
|
|
||||||
event.GroupServer: string(psc.Server),
|
|
||||||
event.ConnectionState: ConnectionStateName[state],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitTilSynced waits until the underlying connection is authenticated
|
|
||||||
func (psc *PeerServerConnection) WaitTilSynced() {
|
|
||||||
for {
|
|
||||||
if psc.GetState() == SYNCED {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run manages the setup and teardown of a peer server connection
|
|
||||||
func (psc *PeerServerConnection) Run() error {
|
|
||||||
log.Infof("Connecting to %v", psc.Server)
|
|
||||||
psc.setState(CONNECTING)
|
|
||||||
|
|
||||||
rc, err := goricochet.Open(psc.protocolEngine.ACN(), psc.Server)
|
|
||||||
if err == nil {
|
|
||||||
psc.connection = rc
|
|
||||||
if psc.GetState() == KILLED {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
psc.setState(CONNECTED)
|
|
||||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
||||||
if err == nil {
|
|
||||||
_, err := connection.HandleOutboundConnection(psc.connection).ProcessAuthAsV3Client(identity.InitializeV3("cwtchpeer", &priv, &pub))
|
|
||||||
if err == nil {
|
|
||||||
if psc.GetState() == KILLED {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
psc.setState(AUTHENTICATED)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
psc.connection.Do(func() error {
|
|
||||||
psc.connection.RequestOpenChannel("im.cwtch.server.fetch", &fetch.CwtchPeerFetchChannel{Handler: psc})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
psc.connection.Do(func() error {
|
|
||||||
psc.connection.RequestOpenChannel("im.cwtch.server.listen", &listen.CwtchPeerListenChannel{Handler: psc})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
psc.connection.Process(psc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
psc.setState(FAILED)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break makes Run() return and prevents processing, but doesn't close the connection.
|
|
||||||
func (psc *PeerServerConnection) Break() error {
|
|
||||||
return psc.connection.Break()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupMessage sends the given protocol message to the Server.
|
|
||||||
func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) error {
|
|
||||||
if psc.state != SYNCED {
|
|
||||||
return errors.New("peer is not yet connected & authenticated & synced to server cannot send message")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := psc.connection.Do(func() error {
|
|
||||||
psc.connection.RequestOpenChannel("im.cwtch.server.send", &send.CwtchPeerSendChannel{})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
errCount := 0
|
|
||||||
for errCount < 5 {
|
|
||||||
time.Sleep(time.Second * time.Duration(errCount+1)) // back off retry
|
|
||||||
err = psc.connection.Do(func() error {
|
|
||||||
channel := psc.connection.Channel("im.cwtch.server.send", channels.Outbound)
|
|
||||||
if channel == nil {
|
|
||||||
return errors.New("no channel found")
|
|
||||||
}
|
|
||||||
sendchannel, ok := channel.Handler.(*send.CwtchPeerSendChannel)
|
|
||||||
if ok {
|
|
||||||
return sendchannel.SendGroupMessage(gm)
|
|
||||||
}
|
|
||||||
return errors.New("channel is not a peer send channel (this should definitely not happen)")
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errCount++
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close shuts down the connection (freeing the handler goroutines)
|
|
||||||
func (psc *PeerServerConnection) Close() {
|
|
||||||
psc.setState(KILLED)
|
|
||||||
if psc.connection != nil {
|
|
||||||
psc.connection.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGroupMessage passes the given group message back to the profile.
|
|
||||||
func (psc *PeerServerConnection) HandleGroupMessage(gm *protocol.GroupMessage) {
|
|
||||||
psc.GroupMessageHandler(psc.Server, gm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFetchDone calls the supplied callback for when a fetch connection is closed
|
|
||||||
func (psc *PeerServerConnection) HandleFetchDone() {
|
|
||||||
psc.setState(SYNCED)
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
package connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/event"
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/server/fetch"
|
|
||||||
"cwtch.im/cwtch/server/send"
|
|
||||||
"cwtch.im/tapir/primitives"
|
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
|
|
||||||
identityOld "git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ServerAuthValid(hostname string, key ed25519.PublicKey) (allowed, known bool) {
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestServer struct {
|
|
||||||
connection.AutoConnectionHandler
|
|
||||||
Received chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *TestServer) HandleGroupMessage(gm *protocol.GroupMessage) {
|
|
||||||
ts.Received <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *TestServer) HandleFetchRequest() []*protocol.GroupMessage {
|
|
||||||
return []*protocol.GroupMessage{{Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}, {Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runtestserver(t *testing.T, ts *TestServer, priv ed25519.PrivateKey, identity primitives.Identity, listenChan chan bool) {
|
|
||||||
ln, _ := net.Listen("tcp", "127.0.0.1:5451")
|
|
||||||
listenChan <- true
|
|
||||||
conn, _ := ln.Accept()
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
rc, err := goricochet.NegotiateVersionInbound(conn)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Negotiate Version Error: %v", err)
|
|
||||||
}
|
|
||||||
// TODO switch from old identity to new tapir identity.
|
|
||||||
pub := identity.PublicKey()
|
|
||||||
err = connection.HandleInboundConnection(rc).ProcessAuthAsV3Server(identityOld.InitializeV3("", &priv, &pub), ServerAuthValid)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ServerAuth Error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.RegisterChannelHandler("im.cwtch.server.send", func() channels.Handler {
|
|
||||||
server := new(send.CwtchServerSendChannel)
|
|
||||||
server.Handler = ts
|
|
||||||
return server
|
|
||||||
})
|
|
||||||
|
|
||||||
ts.RegisterChannelHandler("im.cwtch.server.fetch", func() channels.Handler {
|
|
||||||
server := new(fetch.CwtchServerFetchChannel)
|
|
||||||
server.Handler = ts
|
|
||||||
return server
|
|
||||||
})
|
|
||||||
|
|
||||||
rc.Process(ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerServerConnection(t *testing.T) {
|
|
||||||
identity, priv := primitives.InitializeEphemeralIdentity()
|
|
||||||
t.Logf("Launching Server....\n")
|
|
||||||
ts := new(TestServer)
|
|
||||||
ts.Init()
|
|
||||||
ts.Received = make(chan bool)
|
|
||||||
listenChan := make(chan bool)
|
|
||||||
go runtestserver(t, ts, priv, identity, listenChan)
|
|
||||||
<-listenChan
|
|
||||||
onionAddr := identity.Hostname()
|
|
||||||
|
|
||||||
manager := event.NewEventManager()
|
|
||||||
engine := NewProtocolEngine(identity, priv, connectivity.NewLocalACN(), manager, nil)
|
|
||||||
|
|
||||||
psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr)
|
|
||||||
numcalls := 0
|
|
||||||
psc.GroupMessageHandler = func(s string, gm *protocol.GroupMessage) {
|
|
||||||
numcalls++
|
|
||||||
}
|
|
||||||
state := psc.GetState()
|
|
||||||
if state != DISCONNECTED {
|
|
||||||
t.Errorf("new connections should start in disconnected state")
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 1)
|
|
||||||
go psc.Run()
|
|
||||||
psc.WaitTilSynced()
|
|
||||||
|
|
||||||
gm := &protocol.GroupMessage{Ciphertext: []byte("hello"), Signature: []byte{}}
|
|
||||||
psc.SendGroupMessage(gm)
|
|
||||||
|
|
||||||
// Wait until message is received
|
|
||||||
<-ts.Received
|
|
||||||
|
|
||||||
if numcalls != 2 {
|
|
||||||
t.Errorf("Should have received 2 calls from fetch request, instead received %v", numcalls)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
package send
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/spam"
|
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CwtchPeerSendChannel is the peer implementation of im.cwtch.server.send
|
|
||||||
type CwtchPeerSendChannel struct {
|
|
||||||
channel *channels.Channel
|
|
||||||
spamGuard spam.Guard
|
|
||||||
challenge []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type string for this channel, e.g. "im.ricochet.server.send".
|
|
||||||
func (cpsc *CwtchPeerSendChannel) Type() string {
|
|
||||||
return "im.cwtch.server.send"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed is called when the channel is closed for any reason.
|
|
||||||
func (cpsc *CwtchPeerSendChannel) Closed(err error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyClientCanOpen - for Cwtch server channels only peers may open.
|
|
||||||
func (cpsc *CwtchPeerSendChannel) OnlyClientCanOpen() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton - for Cwtch channels there can only be one instance per direction
|
|
||||||
func (cpsc *CwtchPeerSendChannel) Singleton() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bidirectional - for Cwtch channels are not bidrectional
|
|
||||||
func (cpsc *CwtchPeerSendChannel) Bidirectional() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresAuthentication - Cwtch channels require no auth
|
|
||||||
func (cpsc *CwtchPeerSendChannel) RequiresAuthentication() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInbound should never be called on peers.
|
|
||||||
func (cpsc *CwtchPeerSendChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
|
||||||
return nil, errors.New("client does not receive inbound listen channels")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutbound is used to set up a new send channel and initialize spamguard
|
|
||||||
func (cpsc *CwtchPeerSendChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
|
||||||
cpsc.spamGuard.Difficulty = 2
|
|
||||||
cpsc.channel = channel
|
|
||||||
messageBuilder := new(utils.MessageBuilder)
|
|
||||||
return messageBuilder.OpenChannel(channel.ID, cpsc.Type()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutboundResult confirms the open channel request and sets the spamguard challenge
|
|
||||||
func (cpsc *CwtchPeerSendChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
|
||||||
if err == nil {
|
|
||||||
if crm.GetOpened() {
|
|
||||||
ce, _ := proto.GetExtension(crm, protocol.E_ServerNonce)
|
|
||||||
cpsc.challenge = ce.([]byte)[:]
|
|
||||||
cpsc.channel.Pending = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupMessage performs the spamguard proof of work and sends a message.
|
|
||||||
func (cpsc *CwtchPeerSendChannel) SendGroupMessage(gm *protocol.GroupMessage) error {
|
|
||||||
if cpsc.channel.Pending == false {
|
|
||||||
sgsolve := cpsc.spamGuard.SolveChallenge(cpsc.challenge, gm.GetCiphertext())
|
|
||||||
gm.Spamguard = sgsolve[:]
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: gm,
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
cpsc.channel.SendMessage(packet)
|
|
||||||
cpsc.channel.CloseChannel()
|
|
||||||
} else {
|
|
||||||
return errors.New("channel isn't set up yet")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet should never be
|
|
||||||
func (cpsc *CwtchPeerSendChannel) Packet(data []byte) {
|
|
||||||
// If we receive a packet on this channel, close the connection
|
|
||||||
cpsc.channel.CloseChannel()
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package send
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/spam"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPeerSendChannelAttributes(t *testing.T) {
|
|
||||||
cssc := new(CwtchPeerSendChannel)
|
|
||||||
if cssc.Type() != "im.cwtch.server.send" {
|
|
||||||
t.Errorf("cwtch channel type is incorrect %v", cssc.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.OnlyClientCanOpen() {
|
|
||||||
t.Errorf("only clients should be able to open im.cwtch.server.send channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.Bidirectional() {
|
|
||||||
t.Errorf("im.cwtch.server.listen should not be bidirectional")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.Singleton() {
|
|
||||||
t.Errorf("im.cwtch.server.listen should be a Singleton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.RequiresAuthentication() != "none" {
|
|
||||||
t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerSendChannelOpenInbound(t *testing.T) {
|
|
||||||
cssc := new(CwtchPeerSendChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
_, err := cssc.OpenInbound(channel, nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("client implementation of im.cwtch.server.Listen should never open an inbound channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerSendChannelClosesOnPacket(t *testing.T) {
|
|
||||||
pfc := new(CwtchPeerSendChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
closed := false
|
|
||||||
channel.CloseChannel = func() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
pfc.OpenOutbound(channel)
|
|
||||||
pfc.Packet([]byte{})
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("send channel should close if server attempts to send packets")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerSendChannel(t *testing.T) {
|
|
||||||
pfc := new(CwtchPeerSendChannel)
|
|
||||||
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 3
|
|
||||||
success := false
|
|
||||||
|
|
||||||
var sg spam.Guard
|
|
||||||
sg.Difficulty = 2
|
|
||||||
|
|
||||||
closed := false
|
|
||||||
channel.CloseChannel = func() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.SendMessage = func(message []byte) {
|
|
||||||
packet := new(protocol.CwtchServerPacket)
|
|
||||||
proto.Unmarshal(message[:], packet)
|
|
||||||
if packet.GetGroupMessage() != nil {
|
|
||||||
success = sg.ValidateChallenge(packet.GetGroupMessage().GetCiphertext(), packet.GetGroupMessage().GetSpamguard())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err := pfc.OpenOutbound(channel)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected result but also got non-nil error: result:%v, err: %v", result, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
challenge := sg.GenerateChallenge(3)
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(challenge[:], control)
|
|
||||||
|
|
||||||
pfc.OpenOutboundResult(nil, control.GetChannelResult())
|
|
||||||
if channel.Pending {
|
|
||||||
t.Errorf("once opened channel should no longer be pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
gm := &protocol.GroupMessage{Ciphertext: []byte("hello")}
|
|
||||||
pfc.SendGroupMessage(gm)
|
|
||||||
if !success {
|
|
||||||
t.Errorf("send channel should have successfully sent a valid group message")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("send channel should have successfully closed after a valid group message")
|
|
||||||
}
|
|
||||||
|
|
||||||
pfc.Closed(nil)
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
package spam
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"io"
|
|
||||||
//"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Guard implements a spam protection mechanism for Cwtch Servers.
|
|
||||||
type Guard struct {
|
|
||||||
Difficulty int
|
|
||||||
nonce [24]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRandomness(arr *[24]byte) {
|
|
||||||
if _, err := io.ReadFull(rand.Reader, arr[:]); err != nil {
|
|
||||||
utils.CheckError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//GenerateChallenge returns a channel result packet with a spamguard challenge nonce
|
|
||||||
func (sg *Guard) GenerateChallenge(channelID int32) []byte {
|
|
||||||
|
|
||||||
cr := &Protocol_Data_Control.ChannelResult{
|
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
|
||||||
Opened: proto.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
var nonce [24]byte
|
|
||||||
getRandomness(&nonce)
|
|
||||||
sg.nonce = nonce
|
|
||||||
err := proto.SetExtension(cr, protocol.E_ServerNonce, sg.nonce[:])
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
|
||||||
ChannelResult: cr,
|
|
||||||
}
|
|
||||||
ret, err := proto.Marshal(pc)
|
|
||||||
utils.CheckError(err)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// SolveChallenge takes in a challenge and a message and returns a solution
|
|
||||||
// The solution is a 24 byte nonce which when hashed with the challenge and the message
|
|
||||||
// produces a sha256 hash with Difficulty leading 0s
|
|
||||||
func (sg *Guard) SolveChallenge(challenge []byte, message []byte) []byte {
|
|
||||||
solved := false
|
|
||||||
var spamguard [24]byte
|
|
||||||
var sum [32]byte
|
|
||||||
solve := make([]byte, len(challenge)+len(message)+len(spamguard))
|
|
||||||
for !solved {
|
|
||||||
|
|
||||||
getRandomness(&spamguard)
|
|
||||||
|
|
||||||
copy(solve[0:], challenge[:])
|
|
||||||
copy(solve[len(challenge):], message[:])
|
|
||||||
copy(solve[len(challenge)+len(message):], spamguard[:])
|
|
||||||
|
|
||||||
sum = sha256.Sum256(solve)
|
|
||||||
|
|
||||||
solved = true
|
|
||||||
for i := 0; i < sg.Difficulty; i++ {
|
|
||||||
if sum[i] != 0x00 {
|
|
||||||
solved = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//fmt.Printf("[SOLVED] %x\n",sha256.Sum256(solve))
|
|
||||||
return spamguard[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateChallenge returns true if the message and spamguard pass the challenge
|
|
||||||
func (sg *Guard) ValidateChallenge(message []byte, spamguard []byte) bool {
|
|
||||||
if len(spamguard) != 24 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the message is too large just throw it away.
|
|
||||||
if len(message) > 2048 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
solve := make([]byte, len(sg.nonce)+len(message)+len(spamguard))
|
|
||||||
copy(solve[0:], sg.nonce[:])
|
|
||||||
copy(solve[len(sg.nonce):], message[:])
|
|
||||||
copy(solve[len(sg.nonce)+len(message):], spamguard[:])
|
|
||||||
sum := sha256.Sum256(solve)
|
|
||||||
|
|
||||||
for i := 0; i < sg.Difficulty; i++ {
|
|
||||||
if sum[i] != 0x00 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package spam
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSpamGuard(t *testing.T) {
|
|
||||||
var spamGuard Guard
|
|
||||||
spamGuard.Difficulty = 2
|
|
||||||
challenge := spamGuard.GenerateChallenge(3)
|
|
||||||
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(challenge[:], control)
|
|
||||||
|
|
||||||
if control.GetChannelResult() != nil {
|
|
||||||
ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce)
|
|
||||||
challenge := ce.([]byte)[:]
|
|
||||||
|
|
||||||
sgsolve := spamGuard.SolveChallenge(challenge, []byte("Hello"))
|
|
||||||
t.Logf("Solved: %v %v", challenge, sgsolve)
|
|
||||||
result := spamGuard.ValidateChallenge([]byte("Hello"), sgsolve)
|
|
||||||
if result != true {
|
|
||||||
t.Errorf("Validating Guard Failed")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Errorf("Failed SpamGaurd")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpamGuardBadLength(t *testing.T) {
|
|
||||||
var spamGuard Guard
|
|
||||||
spamGuard.Difficulty = 2
|
|
||||||
spamGuard.GenerateChallenge(3)
|
|
||||||
result := spamGuard.ValidateChallenge([]byte("test"), []byte{0x00, 0x00})
|
|
||||||
if result {
|
|
||||||
t.Errorf("Validating Guard should have failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpamGuardFail(t *testing.T) {
|
|
||||||
var spamGuard Guard
|
|
||||||
spamGuard.Difficulty = 2
|
|
||||||
challenge := spamGuard.GenerateChallenge(3)
|
|
||||||
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(challenge[:], control)
|
|
||||||
|
|
||||||
if control.GetChannelResult() != nil {
|
|
||||||
ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce)
|
|
||||||
challenge := ce.([]byte)[:]
|
|
||||||
|
|
||||||
var spamGuard2 Guard
|
|
||||||
spamGuard2.Difficulty = 1
|
|
||||||
sgsolve := spamGuard2.SolveChallenge(challenge, []byte("Hello"))
|
|
||||||
t.Logf("Solved: %v %v", challenge, sgsolve)
|
|
||||||
result := spamGuard.ValidateChallenge([]byte("Hello"), sgsolve)
|
|
||||||
if result {
|
|
||||||
t.Errorf("Validating Guard successes")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Errorf("Failed SpamGaurd")
|
|
||||||
}
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
package connections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
|
"cwtch.im/tapir"
|
||||||
|
"cwtch.im/tapir/applications"
|
||||||
|
"cwtch.im/tapir/networks/tor"
|
||||||
|
"cwtch.im/tapir/primitives"
|
||||||
|
"cwtch.im/tapir/primitives/privacypass"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"git.openprivacy.ca/openprivacy/connectivity"
|
||||||
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
"github.com/gtank/ristretto255"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTokenBoardClient generates a new Client for Token Board
|
||||||
|
func NewTokenBoardClient(acn connectivity.ACN, Y *ristretto255.Element, tokenServiceOnion string, groupMessageHandler func(server string, gm *groups.EncryptedGroupMessage), serverSyncedHandler func(server string)) tapir.Application {
|
||||||
|
tba := new(TokenBoardClient)
|
||||||
|
tba.acn = acn
|
||||||
|
tba.tokenService = privacypass.NewTokenServer()
|
||||||
|
tba.tokenService.Y = Y
|
||||||
|
tba.tokenServiceOnion = tokenServiceOnion
|
||||||
|
tba.receiveGroupMessageHandler = groupMessageHandler
|
||||||
|
tba.serverSyncedHandler = serverSyncedHandler
|
||||||
|
return tba
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenBoardClient defines a client for the TokenBoard server
|
||||||
|
type TokenBoardClient struct {
|
||||||
|
applications.AuthApp
|
||||||
|
connection tapir.Connection
|
||||||
|
receiveGroupMessageHandler func(server string, gm *groups.EncryptedGroupMessage)
|
||||||
|
serverSyncedHandler func(server string)
|
||||||
|
|
||||||
|
// Token service handling
|
||||||
|
acn connectivity.ACN
|
||||||
|
tokens []*privacypass.Token
|
||||||
|
tokenService *privacypass.TokenServer
|
||||||
|
tokenServiceOnion string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInstance Client a new TokenBoardApp
|
||||||
|
func (ta *TokenBoardClient) NewInstance() tapir.Application {
|
||||||
|
tba := new(TokenBoardClient)
|
||||||
|
tba.serverSyncedHandler = ta.serverSyncedHandler
|
||||||
|
tba.receiveGroupMessageHandler = ta.receiveGroupMessageHandler
|
||||||
|
tba.acn = ta.acn
|
||||||
|
tba.tokenService = ta.tokenService
|
||||||
|
tba.tokenServiceOnion = ta.tokenServiceOnion
|
||||||
|
return tba
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes the cryptographic TokenBoardApp
|
||||||
|
func (ta *TokenBoardClient) Init(connection tapir.Connection) {
|
||||||
|
ta.AuthApp.Init(connection)
|
||||||
|
if connection.HasCapability(applications.AuthCapability) {
|
||||||
|
ta.connection = connection
|
||||||
|
ta.connection.SetCapability(groups.CwtchServerSyncedCapability)
|
||||||
|
log.Debugf("Successfully Initialized Connection")
|
||||||
|
go ta.Listen()
|
||||||
|
ta.Replay()
|
||||||
|
} else {
|
||||||
|
connection.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen processes the messages for this application
|
||||||
|
func (ta *TokenBoardClient) Listen() {
|
||||||
|
for {
|
||||||
|
log.Debugf("Client waiting...")
|
||||||
|
data := ta.connection.Expect()
|
||||||
|
if len(data) == 0 {
|
||||||
|
log.Debugf("Server closed the connection...")
|
||||||
|
return // connection is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always expect the server to follow protocol, and the second it doesn't we close the connection
|
||||||
|
// TODO issue an error so the client is aware
|
||||||
|
var message groups.Message
|
||||||
|
if err := json.Unmarshal(data, &message); err != nil {
|
||||||
|
log.Debugf("Server sent an unexpected message, closing the connection: %v", err)
|
||||||
|
ta.connection.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch message.MessageType {
|
||||||
|
case groups.NewMessageMessage:
|
||||||
|
if message.NewMessage != nil {
|
||||||
|
ta.receiveGroupMessageHandler(ta.connection.Hostname(), &message.NewMessage.EGM)
|
||||||
|
} else {
|
||||||
|
// TODO: Send this error to the UI
|
||||||
|
log.Debugf("Server sent an unexpected NewMessage, closing the connection: %s", data)
|
||||||
|
ta.connection.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case groups.PostResultMessage:
|
||||||
|
// TODO handle failure
|
||||||
|
case groups.ReplayResultMessage:
|
||||||
|
if message.ReplayResult != nil {
|
||||||
|
log.Debugf("Replaying %v Messages...", message.ReplayResult.NumMessages)
|
||||||
|
for i := 0; i < message.ReplayResult.NumMessages; i++ {
|
||||||
|
data := ta.connection.Expect()
|
||||||
|
egm := &groups.EncryptedGroupMessage{}
|
||||||
|
if err := json.Unmarshal(data, egm); err == nil {
|
||||||
|
ta.receiveGroupMessageHandler(ta.connection.Hostname(), egm)
|
||||||
|
} else {
|
||||||
|
// TODO: Send this error to the UI
|
||||||
|
log.Debugf("Server sent an unexpected EncryptedGroupMessage, closing the connection: %v", err)
|
||||||
|
ta.connection.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ta.serverSyncedHandler(ta.connection.Hostname())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay posts a Replay Message to the server.
|
||||||
|
func (ta *TokenBoardClient) Replay() {
|
||||||
|
// TODO - Allow configurable ranges
|
||||||
|
data, _ := json.Marshal(groups.Message{MessageType: groups.ReplayRequestMessage, ReplayRequest: &groups.ReplayRequest{LastCommit: []byte{}}})
|
||||||
|
ta.connection.Send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurchaseTokens purchases the given number of tokens from the server (using the provided payment handler)
|
||||||
|
func (ta *TokenBoardClient) PurchaseTokens() {
|
||||||
|
ta.MakePayment()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post sends a Post Request to the server
|
||||||
|
func (ta *TokenBoardClient) Post(ct []byte, sig []byte) bool {
|
||||||
|
egm := groups.EncryptedGroupMessage{Ciphertext: ct, Signature: sig}
|
||||||
|
token, err := ta.NextToken(egm.ToBytes(), ta.connection.Hostname())
|
||||||
|
if err == nil {
|
||||||
|
data, _ := json.Marshal(groups.Message{MessageType: groups.PostRequestMessage, PostRequest: &groups.PostRequest{EGM: egm, Token: token}})
|
||||||
|
log.Debugf("Message Length: %s %v", data, len(data))
|
||||||
|
ta.connection.Send(data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log.Debugf("No Valid Tokens: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePayment uses the PoW based token protocol to obtain more tokens
|
||||||
|
func (ta *TokenBoardClient) MakePayment() {
|
||||||
|
id, sk := primitives.InitializeEphemeralIdentity()
|
||||||
|
var client tapir.Service
|
||||||
|
client = new(tor.BaseOnionService)
|
||||||
|
client.Init(ta.acn, sk, &id)
|
||||||
|
|
||||||
|
tokenApplication := new(applications.TokenApplication)
|
||||||
|
tokenApplication.TokenService = ta.tokenService
|
||||||
|
powTokenApp := new(applications.ApplicationChain).
|
||||||
|
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
||||||
|
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
||||||
|
client.Connect(ta.tokenServiceOnion, powTokenApp)
|
||||||
|
conn, err := client.WaitForCapabilityOrClose(ta.tokenServiceOnion, applications.HasTokensCapability)
|
||||||
|
if err == nil {
|
||||||
|
powtapp, _ := conn.App().(*applications.TokenApplication)
|
||||||
|
ta.tokens = append(ta.tokens, powtapp.Tokens...)
|
||||||
|
log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("Error making payment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextToken retrieves the next token
|
||||||
|
func (ta *TokenBoardClient) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) {
|
||||||
|
if len(ta.tokens) == 0 {
|
||||||
|
return privacypass.SpentToken{}, errors.New("No more tokens")
|
||||||
|
}
|
||||||
|
token := ta.tokens[0]
|
||||||
|
ta.tokens = ta.tokens[1:]
|
||||||
|
return token.SpendToken(append(data, hostname...)), nil
|
||||||
|
}
|
|
@ -1,135 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// source: cwtch-profile.proto
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package protocol is a generated protocol buffer package.
|
|
||||||
|
|
||||||
It is generated from these files:
|
|
||||||
cwtch-profile.proto
|
|
||||||
|
|
||||||
It has these top-level messages:
|
|
||||||
CwtchPeerPacket
|
|
||||||
CwtchIdentity
|
|
||||||
GroupChatInvite
|
|
||||||
*/
|
|
||||||
package protocol
|
|
||||||
|
|
||||||
import proto "github.com/golang/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
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the proto package it is being compiled against.
|
|
||||||
// A compilation error at this line likely means your copy of the
|
|
||||||
// proto package needs to be updated.
|
|
||||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
|
||||||
|
|
||||||
type CwtchPeerPacket struct {
|
|
||||||
CwtchIdentify *CwtchIdentity `protobuf:"bytes,1,opt,name=cwtch_identify,json=cwtchIdentify" json:"cwtch_identify,omitempty"`
|
|
||||||
GroupChatInvite *GroupChatInvite `protobuf:"bytes,2,opt,name=group_chat_invite,json=groupChatInvite" json:"group_chat_invite,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *CwtchPeerPacket) Reset() { *m = CwtchPeerPacket{} }
|
|
||||||
func (m *CwtchPeerPacket) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*CwtchPeerPacket) ProtoMessage() {}
|
|
||||||
func (*CwtchPeerPacket) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
|
||||||
|
|
||||||
func (m *CwtchPeerPacket) GetCwtchIdentify() *CwtchIdentity {
|
|
||||||
if m != nil {
|
|
||||||
return m.CwtchIdentify
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *CwtchPeerPacket) GetGroupChatInvite() *GroupChatInvite {
|
|
||||||
if m != nil {
|
|
||||||
return m.GroupChatInvite
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CwtchIdentity struct {
|
|
||||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
|
||||||
Ed25519PublicKey []byte `protobuf:"bytes,2,opt,name=ed25519_public_key,json=ed25519PublicKey,proto3" json:"ed25519_public_key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *CwtchIdentity) Reset() { *m = CwtchIdentity{} }
|
|
||||||
func (m *CwtchIdentity) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*CwtchIdentity) ProtoMessage() {}
|
|
||||||
func (*CwtchIdentity) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
|
||||||
|
|
||||||
func (m *CwtchIdentity) GetName() string {
|
|
||||||
if m != nil {
|
|
||||||
return m.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *CwtchIdentity) GetEd25519PublicKey() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.Ed25519PublicKey
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// [name] has invited you to join a group chat: [message]
|
|
||||||
type GroupChatInvite struct {
|
|
||||||
GroupName string `protobuf:"bytes,1,opt,name=group_name,json=groupName" json:"group_name,omitempty"`
|
|
||||||
GroupSharedKey []byte `protobuf:"bytes,2,opt,name=group_shared_key,json=groupSharedKey,proto3" json:"group_shared_key,omitempty"`
|
|
||||||
ServerHost string `protobuf:"bytes,3,opt,name=server_host,json=serverHost" json:"server_host,omitempty"`
|
|
||||||
SignedGroupId []byte `protobuf:"bytes,4,opt,name=signed_group_id,json=signedGroupId,proto3" json:"signed_group_id,omitempty"`
|
|
||||||
InitialMessage []byte `protobuf:"bytes,5,opt,name=initial_message,json=initialMessage,proto3" json:"initial_message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupChatInvite) Reset() { *m = GroupChatInvite{} }
|
|
||||||
func (m *GroupChatInvite) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*GroupChatInvite) ProtoMessage() {}
|
|
||||||
func (*GroupChatInvite) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
|
||||||
|
|
||||||
func (m *GroupChatInvite) GetGroupName() string {
|
|
||||||
if m != nil {
|
|
||||||
return m.GroupName
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupChatInvite) GetGroupSharedKey() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.GroupSharedKey
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupChatInvite) GetServerHost() string {
|
|
||||||
if m != nil {
|
|
||||||
return m.ServerHost
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupChatInvite) GetSignedGroupId() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.SignedGroupId
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupChatInvite) GetInitialMessage() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.InitialMessage
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proto.RegisterType((*CwtchPeerPacket)(nil), "protocol.CwtchPeerPacket")
|
|
||||||
proto.RegisterType((*CwtchIdentity)(nil), "protocol.CwtchIdentity")
|
|
||||||
proto.RegisterType((*GroupChatInvite)(nil), "protocol.GroupChatInvite")
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { proto.RegisterFile("cwtch-profile.proto", fileDescriptor0) }
|
|
|
@ -1,21 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package protocol;
|
|
||||||
|
|
||||||
message CwtchPeerPacket {
|
|
||||||
CwtchIdentity cwtch_identify = 1;
|
|
||||||
GroupChatInvite group_chat_invite = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CwtchIdentity {
|
|
||||||
string name = 1;
|
|
||||||
bytes ed25519_public_key = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [name] has invited you to join a group chat: [message]
|
|
||||||
message GroupChatInvite {
|
|
||||||
string group_name = 1;
|
|
||||||
bytes group_shared_key = 2;
|
|
||||||
string server_host = 3;
|
|
||||||
bytes signed_group_id = 4;
|
|
||||||
bytes initial_message = 5;
|
|
||||||
}
|
|
|
@ -1,188 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// source: group_message.proto
|
|
||||||
|
|
||||||
package protocol
|
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
|
||||||
import fmt "fmt"
|
|
||||||
import math "math"
|
|
||||||
import control "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
|
||||||
var _ = proto.Marshal
|
|
||||||
var _ = fmt.Errorf
|
|
||||||
var _ = math.Inf
|
|
||||||
|
|
||||||
type CwtchServerPacket struct {
|
|
||||||
GroupMessage *GroupMessage `protobuf:"bytes,1,opt,name=group_message,json=groupMessage" json:"group_message,omitempty"`
|
|
||||||
FetchMessage *FetchMessage `protobuf:"bytes,2,opt,name=fetch_message,json=fetchMessage" json:"fetch_message,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *CwtchServerPacket) Reset() { *m = CwtchServerPacket{} }
|
|
||||||
func (m *CwtchServerPacket) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*CwtchServerPacket) ProtoMessage() {}
|
|
||||||
func (*CwtchServerPacket) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
|
|
||||||
|
|
||||||
func (m *CwtchServerPacket) GetGroupMessage() *GroupMessage {
|
|
||||||
if m != nil {
|
|
||||||
return m.GroupMessage
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *CwtchServerPacket) GetFetchMessage() *FetchMessage {
|
|
||||||
if m != nil {
|
|
||||||
return m.FetchMessage
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchMessage struct {
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FetchMessage) Reset() { *m = FetchMessage{} }
|
|
||||||
func (m *FetchMessage) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*FetchMessage) ProtoMessage() {}
|
|
||||||
func (*FetchMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} }
|
|
||||||
|
|
||||||
type GroupMessage struct {
|
|
||||||
Ciphertext []byte `protobuf:"bytes,1,req,name=ciphertext" json:"ciphertext,omitempty"`
|
|
||||||
Spamguard []byte `protobuf:"bytes,2,req,name=spamguard" json:"spamguard,omitempty"`
|
|
||||||
Signature []byte `protobuf:"bytes,3,req,name=signature" json:"signature,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupMessage) Reset() { *m = GroupMessage{} }
|
|
||||||
func (m *GroupMessage) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*GroupMessage) ProtoMessage() {}
|
|
||||||
func (*GroupMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} }
|
|
||||||
|
|
||||||
func (m *GroupMessage) GetCiphertext() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.Ciphertext
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupMessage) GetSpamguard() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.Spamguard
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GroupMessage) GetSignature() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.Signature
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptedGroupMessage is *never* sent in the clear on the wire
|
|
||||||
// and is only ever sent when encrypted in the ciphertext parameter of
|
|
||||||
// GroupMessage
|
|
||||||
type DecryptedGroupMessage struct {
|
|
||||||
Onion *string `protobuf:"bytes,1,req,name=onion" json:"onion,omitempty"`
|
|
||||||
Timestamp *int32 `protobuf:"varint,2,req,name=timestamp" json:"timestamp,omitempty"`
|
|
||||||
Text *string `protobuf:"bytes,3,req,name=text" json:"text,omitempty"`
|
|
||||||
SignedGroupId []byte `protobuf:"bytes,4,req,name=signed_group_id,json=signedGroupId" json:"signed_group_id,omitempty"`
|
|
||||||
PreviousMessageSig []byte `protobuf:"bytes,5,req,name=previous_message_sig,json=previousMessageSig" json:"previous_message_sig,omitempty"`
|
|
||||||
// Used to prevent analysis on text length, length is 1024 - len(text)
|
|
||||||
Padding []byte `protobuf:"bytes,6,req,name=padding" json:"padding,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) Reset() { *m = DecryptedGroupMessage{} }
|
|
||||||
func (m *DecryptedGroupMessage) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*DecryptedGroupMessage) ProtoMessage() {}
|
|
||||||
func (*DecryptedGroupMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} }
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) GetOnion() string {
|
|
||||||
if m != nil && m.Onion != nil {
|
|
||||||
return *m.Onion
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) GetTimestamp() int32 {
|
|
||||||
if m != nil && m.Timestamp != nil {
|
|
||||||
return *m.Timestamp
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) GetText() string {
|
|
||||||
if m != nil && m.Text != nil {
|
|
||||||
return *m.Text
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) GetSignedGroupId() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.SignedGroupId
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) GetPreviousMessageSig() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.PreviousMessageSig
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DecryptedGroupMessage) GetPadding() []byte {
|
|
||||||
if m != nil {
|
|
||||||
return m.Padding
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var E_ServerNonce = &proto.ExtensionDesc{
|
|
||||||
ExtendedType: (*control.ChannelResult)(nil),
|
|
||||||
ExtensionType: ([]byte)(nil),
|
|
||||||
Field: 8200,
|
|
||||||
Name: "protocol.server_nonce",
|
|
||||||
Tag: "bytes,8200,opt,name=server_nonce,json=serverNonce",
|
|
||||||
Filename: "group_message.proto",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proto.RegisterType((*CwtchServerPacket)(nil), "protocol.CwtchServerPacket")
|
|
||||||
proto.RegisterType((*FetchMessage)(nil), "protocol.FetchMessage")
|
|
||||||
proto.RegisterType((*GroupMessage)(nil), "protocol.GroupMessage")
|
|
||||||
proto.RegisterType((*DecryptedGroupMessage)(nil), "protocol.DecryptedGroupMessage")
|
|
||||||
proto.RegisterExtension(E_ServerNonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { proto.RegisterFile("group_message.proto", fileDescriptor2) }
|
|
||||||
|
|
||||||
var fileDescriptor2 = []byte{
|
|
||||||
// 360 bytes of a gzipped FileDescriptorProto
|
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0x4d, 0x6e, 0xdb, 0x30,
|
|
||||||
0x10, 0x85, 0x21, 0xff, 0xb4, 0xf5, 0x58, 0x6e, 0x51, 0xd6, 0x6d, 0x89, 0xa2, 0x28, 0x0c, 0x2d,
|
|
||||||
0x0a, 0xaf, 0x8c, 0x20, 0xcb, 0x78, 0x13, 0xc0, 0x41, 0x82, 0x2c, 0x12, 0x04, 0xf2, 0x01, 0x04,
|
|
||||||
0x42, 0x1a, 0x53, 0x4c, 0x24, 0x92, 0x20, 0x29, 0x27, 0xb9, 0x41, 0x36, 0x39, 0x5d, 0x2e, 0x14,
|
|
||||||
0x88, 0xb2, 0x6c, 0x39, 0x2b, 0x69, 0xde, 0x37, 0x6f, 0xde, 0x80, 0x24, 0xfc, 0xe0, 0x46, 0x55,
|
|
||||||
0x3a, 0x29, 0xd1, 0x5a, 0xc6, 0x71, 0xa1, 0x8d, 0x72, 0x8a, 0x7c, 0xf1, 0x9f, 0x54, 0x15, 0x7f,
|
|
||||||
0xa6, 0x2b, 0x25, 0x9d, 0x51, 0xc5, 0x2a, 0x67, 0x52, 0x62, 0xd1, 0xf0, 0xe8, 0x35, 0x80, 0xef,
|
|
||||||
0xab, 0x47, 0x97, 0xe6, 0x6b, 0x34, 0x5b, 0x34, 0x77, 0x2c, 0x7d, 0x40, 0x47, 0x96, 0x30, 0x39,
|
|
||||||
0x1a, 0x46, 0x83, 0x59, 0x30, 0x1f, 0x9f, 0xfe, 0x5a, 0xb4, 0xd3, 0x16, 0x57, 0x35, 0xbe, 0x69,
|
|
||||||
0x68, 0x1c, 0xf2, 0x4e, 0x55, 0x9b, 0x37, 0xe8, 0xd2, 0x7c, 0x6f, 0xee, 0x7d, 0x34, 0x5f, 0xd6,
|
|
||||||
0x78, 0x6f, 0xde, 0x74, 0xaa, 0xe8, 0x2b, 0x84, 0x5d, 0x1a, 0xdd, 0x43, 0xd8, 0x8d, 0x22, 0xff,
|
|
||||||
0x00, 0x52, 0xa1, 0x73, 0x34, 0x0e, 0x9f, 0x1c, 0x0d, 0x66, 0xbd, 0x79, 0x18, 0x77, 0x14, 0xf2,
|
|
||||||
0x17, 0x46, 0x56, 0xb3, 0x92, 0x57, 0xcc, 0x64, 0xb4, 0xe7, 0xf1, 0x41, 0xf0, 0x54, 0x70, 0xc9,
|
|
||||||
0x5c, 0x65, 0x90, 0xf6, 0x77, 0xb4, 0x15, 0xa2, 0xb7, 0x00, 0x7e, 0x5e, 0x60, 0x6a, 0x9e, 0xb5,
|
|
||||||
0xc3, 0xec, 0x28, 0x75, 0x0a, 0x43, 0x25, 0x85, 0x92, 0x3e, 0x70, 0x14, 0x37, 0x45, 0x3d, 0xcd,
|
|
||||||
0x89, 0x12, 0xad, 0x63, 0xa5, 0xf6, 0x59, 0xc3, 0xf8, 0x20, 0x10, 0x02, 0x03, 0xbf, 0x63, 0xdf,
|
|
||||||
0x5b, 0xfc, 0x3f, 0xf9, 0x0f, 0xdf, 0xea, 0x38, 0xcc, 0x92, 0xe6, 0x78, 0x45, 0x46, 0x07, 0x7e,
|
|
||||||
0x8b, 0x49, 0x23, 0xfb, 0xd0, 0xeb, 0x8c, 0x9c, 0xc0, 0x54, 0x1b, 0xdc, 0x0a, 0x55, 0xd9, 0xf6,
|
|
||||||
0x14, 0x13, 0x2b, 0x38, 0x1d, 0xfa, 0x66, 0xd2, 0xb2, 0xdd, 0x7a, 0x6b, 0xc1, 0x09, 0x85, 0xcf,
|
|
||||||
0x9a, 0x65, 0x99, 0x90, 0x9c, 0x7e, 0xf2, 0x4d, 0x6d, 0x79, 0xb6, 0x84, 0xd0, 0xfa, 0xbb, 0x4d,
|
|
||||||
0xa4, 0x92, 0x29, 0x92, 0xdf, 0x87, 0x7b, 0xd8, 0x3d, 0x85, 0x18, 0x6d, 0x55, 0x38, 0xfa, 0x72,
|
|
||||||
0x3e, 0x0b, 0xe6, 0x61, 0x3c, 0x6e, 0xba, 0x6f, 0xeb, 0xe6, 0xf7, 0x00, 0x00, 0x00, 0xff, 0xff,
|
|
||||||
0x33, 0x30, 0xca, 0x8b, 0x54, 0x02, 0x00, 0x00,
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
syntax = "proto2";
|
|
||||||
package protocol;
|
|
||||||
|
|
||||||
import "ControlChannel.proto";
|
|
||||||
|
|
||||||
message CwtchServerPacket {
|
|
||||||
optional GroupMessage group_message = 1;
|
|
||||||
optional FetchMessage fetch_message = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
extend protocol.ChannelResult {
|
|
||||||
optional bytes server_nonce = 8200; // 32 random bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message FetchMessage {
|
|
||||||
}
|
|
||||||
|
|
||||||
message GroupMessage {
|
|
||||||
required bytes ciphertext = 1;
|
|
||||||
required bytes spamguard = 2;
|
|
||||||
required bytes signature = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptedGroupMessage is *never* sent in the clear on the wire
|
|
||||||
// and is only ever sent when encrypted in the ciphertext parameter of
|
|
||||||
// GroupMessage
|
|
||||||
message DecryptedGroupMessage {
|
|
||||||
required string onion = 1;
|
|
||||||
required int32 timestamp = 2;
|
|
||||||
required string text = 3;
|
|
||||||
required bytes signed_group_id = 4;
|
|
||||||
required bytes previous_message_sig =5;
|
|
||||||
// Used to prevent analysis on text length, length is 1024 - len(text)
|
|
||||||
required bytes padding = 6;
|
|
||||||
}
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package groups
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/tapir"
|
||||||
|
"cwtch.im/tapir/primitives/privacypass"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CwtchServerSyncedCapability is used to indicate that a given cwtch server is synced
|
||||||
|
const CwtchServerSyncedCapability = tapir.Capability("CwtchServerSyncedCapability")
|
||||||
|
|
||||||
|
// GroupInvite provides a structured type for communicating group information to peers
|
||||||
|
type GroupInvite struct {
|
||||||
|
GroupName string
|
||||||
|
SignedGroupID []byte
|
||||||
|
Timestamp uint64
|
||||||
|
SharedKey []byte
|
||||||
|
ServerHost string
|
||||||
|
InitialMessage []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptedGroupMessage is the main encapsulation of group message data
|
||||||
|
type DecryptedGroupMessage struct {
|
||||||
|
Text string
|
||||||
|
Onion string
|
||||||
|
Timestamp uint64
|
||||||
|
SignedGroupID []byte
|
||||||
|
PreviousMessageSig []byte
|
||||||
|
Padding []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptedGroupMessage provides an encapsulation of the encrypted group message stored on the server
|
||||||
|
type EncryptedGroupMessage struct {
|
||||||
|
Ciphertext []byte
|
||||||
|
Signature []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBytes converts the encrypted group message to a set of bytes for serialization
|
||||||
|
func (egm EncryptedGroupMessage) ToBytes() []byte {
|
||||||
|
data, _ := json.Marshal(egm)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType defines the enum for TokenBoard messages
|
||||||
|
type MessageType int
|
||||||
|
|
||||||
|
// Message Types
|
||||||
|
const (
|
||||||
|
ReplayRequestMessage MessageType = iota
|
||||||
|
ReplayResultMessage
|
||||||
|
PostRequestMessage
|
||||||
|
PostResultMessage
|
||||||
|
NewMessageMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message encapsulates the application protocol
|
||||||
|
type Message struct {
|
||||||
|
MessageType MessageType
|
||||||
|
PostRequest *PostRequest `json:",omitempty"`
|
||||||
|
PostResult *PostResult `json:",omitempty"`
|
||||||
|
NewMessage *NewMessage `json:",omitempty"`
|
||||||
|
ReplayRequest *ReplayRequest `json:",omitempty"`
|
||||||
|
ReplayResult *ReplayResult `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplayRequest requests a reply from the given Commit
|
||||||
|
type ReplayRequest struct {
|
||||||
|
LastCommit []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostRequest requests to post the message to the board with the given token
|
||||||
|
type PostRequest struct {
|
||||||
|
Token privacypass.SpentToken
|
||||||
|
EGM EncryptedGroupMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostResult returns the success of a given post attempt
|
||||||
|
type PostResult struct {
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplayResult is sent by the server before a stream of replayed messages
|
||||||
|
type ReplayResult struct {
|
||||||
|
NumMessages int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessage is used to send a new bulletin board message to interested peers.
|
||||||
|
type NewMessage struct {
|
||||||
|
//Token privacypass.SpentToken
|
||||||
|
EGM EncryptedGroupMessage
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -19,7 +18,7 @@ func main() {
|
||||||
|
|
||||||
serverConfig := cwtchserver.LoadConfig(configDir, serverConfigFile)
|
serverConfig := cwtchserver.LoadConfig(configDir, serverConfigFile)
|
||||||
|
|
||||||
acn, err := tor.NewTorACN(path.Join(configDir, "tor"), "")
|
acn, err := tor.NewTorACNWithAuth(".", "", 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("\nError connecting to Tor: %v\n", err)
|
log.Errorf("\nError connecting to Tor: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -31,5 +30,6 @@ func main() {
|
||||||
|
|
||||||
// TODO load params from .cwtch/server.conf or command line flag
|
// TODO load params from .cwtch/server.conf or command line flag
|
||||||
// TODO: respond to HUP so t.Close is gracefully called
|
// TODO: respond to HUP so t.Close is gracefully called
|
||||||
server.Run(acn, serverConfig)
|
server.Setup(serverConfig)
|
||||||
|
server.Run(acn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
package fetch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CwtchServerFetchChannel implements the ChannelHandler interface for a channel of
|
|
||||||
// type "im.cwtch.server.fetch" - this implementation only handles server side logic.
|
|
||||||
type CwtchServerFetchChannel struct {
|
|
||||||
Handler CwtchServerFetchHandler
|
|
||||||
channel *channels.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// CwtchServerFetchHandler defines the interface for interacting with this Channel
|
|
||||||
type CwtchServerFetchHandler interface {
|
|
||||||
HandleFetchRequest() []*protocol.GroupMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type string for this channel, e.g. "im.ricochet.Cwtch".
|
|
||||||
func (cc *CwtchServerFetchChannel) Type() string {
|
|
||||||
return "im.cwtch.server.fetch"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed is called when the channel is closed for any reason.
|
|
||||||
func (cc *CwtchServerFetchChannel) Closed(err error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyClientCanOpen - for Cwtch channels any side can open
|
|
||||||
func (cc *CwtchServerFetchChannel) OnlyClientCanOpen() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton - for Cwtch channels there can only be one instance per direction
|
|
||||||
func (cc *CwtchServerFetchChannel) Singleton() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bidirectional - for Cwtch channels are not bidrectional
|
|
||||||
func (cc *CwtchServerFetchChannel) Bidirectional() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresAuthentication - Cwtch channels require hidden service auth
|
|
||||||
func (cc *CwtchServerFetchChannel) RequiresAuthentication() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInbound is the first method called for an inbound channel request.
|
|
||||||
// If an error is returned, the channel is rejected. If a RawMessage is
|
|
||||||
// returned, it will be sent as the ChannelResult message.
|
|
||||||
func (cc *CwtchServerFetchChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
|
||||||
cc.channel = channel
|
|
||||||
messageBuilder := new(utils.MessageBuilder)
|
|
||||||
return messageBuilder.AckOpenChannel(channel.ID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutbound is the first method called for an outbound channel request.
|
|
||||||
// If an error is returned, the channel is not opened. If a RawMessage is
|
|
||||||
// returned, it will be sent as the OpenChannel message.
|
|
||||||
func (cc *CwtchServerFetchChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
|
||||||
return nil, errors.New("server does not open Fetch channels")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutboundResult is called when a response is received for an
|
|
||||||
// outbound OpenChannel request. If `err` is non-nil, the channel was
|
|
||||||
// rejected and Closed will be called immediately afterwards. `raw`
|
|
||||||
// contains the raw protocol message including any extension data.
|
|
||||||
func (cc *CwtchServerFetchChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
|
||||||
// NOTE: Should never be called
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupMessages sends a series of group messages to the client.
|
|
||||||
func (cc *CwtchServerFetchChannel) SendGroupMessages(gms []*protocol.GroupMessage) {
|
|
||||||
for _, gm := range gms {
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: gm,
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
cc.channel.SendMessage(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet is called for each raw packet received on this channel.
|
|
||||||
func (cc *CwtchServerFetchChannel) Packet(data []byte) {
|
|
||||||
csp := &protocol.CwtchServerPacket{}
|
|
||||||
err := proto.Unmarshal(data, csp)
|
|
||||||
if err == nil {
|
|
||||||
if csp.GetFetchMessage() != nil {
|
|
||||||
cc.SendGroupMessages(cc.Handler.HandleFetchRequest())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we receive a packet on this channel, close the connection
|
|
||||||
cc.channel.CloseChannel()
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
package fetch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServerFetchChannelAttributes(t *testing.T) {
|
|
||||||
cslc := new(CwtchServerFetchChannel)
|
|
||||||
if cslc.Type() != "im.cwtch.server.fetch" {
|
|
||||||
t.Errorf("cwtch channel type is incorrect %v", cslc.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cslc.OnlyClientCanOpen() {
|
|
||||||
t.Errorf("only clients should be able to open im.cwtch.server.fetch channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cslc.Bidirectional() {
|
|
||||||
t.Errorf("im.cwtch.server.fetch should not be bidirectional")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cslc.Singleton() {
|
|
||||||
t.Errorf("im.cwtch.server.fetch should be a Singleton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cslc.RequiresAuthentication() != "none" {
|
|
||||||
t.Errorf("cwtch channel required auth is incorrect %v", cslc.RequiresAuthentication())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestServerFetchChannelOpenOutbound(t *testing.T) {
|
|
||||||
cslc := new(CwtchServerFetchChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
_, err := cslc.OpenOutbound(channel)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("server implementation of im.cwtch.server.fetch should never open an outbound channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestHandler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (th *TestHandler) HandleFetchRequest() []*protocol.GroupMessage {
|
|
||||||
gm := &protocol.GroupMessage{
|
|
||||||
Ciphertext: []byte("Hello"),
|
|
||||||
Spamguard: []byte{},
|
|
||||||
}
|
|
||||||
return []*protocol.GroupMessage{gm}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerFetchChannel(t *testing.T) {
|
|
||||||
cslc := new(CwtchServerFetchChannel)
|
|
||||||
th := new(TestHandler)
|
|
||||||
cslc.Handler = th
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 1
|
|
||||||
closed := false
|
|
||||||
channel.CloseChannel = func() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
gotgm := false
|
|
||||||
channel.SendMessage = func([]byte) {
|
|
||||||
gotgm = true
|
|
||||||
}
|
|
||||||
|
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
|
||||||
ChannelIdentifier: proto.Int32(1),
|
|
||||||
ChannelType: proto.String(cslc.Type()),
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cslc.OpenInbound(channel, oc)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("OpenInbound for im.cwtch.server.Fetch should have succeeded, instead: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(resp[:], control)
|
|
||||||
|
|
||||||
if control.GetChannelResult() != nil {
|
|
||||||
|
|
||||||
fm := &protocol.FetchMessage{}
|
|
||||||
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
FetchMessage: fm,
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
cslc.Packet(packet)
|
|
||||||
|
|
||||||
if !gotgm {
|
|
||||||
t.Errorf("Did not receive packet on wire as expected in Fetch channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("Fetch channel should be cosed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("Fetch channel should be closed after incorrect packet received")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
t.Errorf("Expected ChannelResult from im.cwtch.server.Fetch, instead: %v", control)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
package listen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CwtchServerListenChannel implements the ChannelHandler interface for a channel of
|
|
||||||
// type "im.cwtch.server.listen" - this implementation only handles server side logic.
|
|
||||||
type CwtchServerListenChannel struct {
|
|
||||||
channel *channels.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type string for this channel, e.g. "im.ricochet.Cwtch".
|
|
||||||
func (cc *CwtchServerListenChannel) Type() string {
|
|
||||||
return "im.cwtch.server.listen"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed is called when the channel is closed for any reason.
|
|
||||||
func (cc *CwtchServerListenChannel) Closed(err error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyClientCanOpen - for Cwtch channels any side can open
|
|
||||||
func (cc *CwtchServerListenChannel) OnlyClientCanOpen() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton - for Cwtch channels there can only be one instance per direction
|
|
||||||
func (cc *CwtchServerListenChannel) Singleton() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bidirectional - for Cwtch channels are not bidrectional
|
|
||||||
func (cc *CwtchServerListenChannel) Bidirectional() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresAuthentication - Cwtch channels require hidden service auth
|
|
||||||
func (cc *CwtchServerListenChannel) RequiresAuthentication() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInbound is the first method called for an inbound channel request.
|
|
||||||
// If an error is returned, the channel is rejected. If a RawMessage is
|
|
||||||
// returned, it will be sent as the ChannelResult message.
|
|
||||||
func (cc *CwtchServerListenChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
|
||||||
cc.channel = channel
|
|
||||||
messageBuilder := new(utils.MessageBuilder)
|
|
||||||
return messageBuilder.AckOpenChannel(channel.ID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutbound is the first method called for an outbound channel request.
|
|
||||||
// If an error is returned, the channel is not opened. If a RawMessage is
|
|
||||||
// returned, it will be sent as the OpenChannel message.
|
|
||||||
func (cc *CwtchServerListenChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
|
||||||
return nil, errors.New("server does not open listen channels")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutboundResult is called when a response is received for an
|
|
||||||
// outbound OpenChannel request. If `err` is non-nil, the channel was
|
|
||||||
// rejected and Closed will be called immediately afterwards. `raw`
|
|
||||||
// contains the raw protocol message including any extension data.
|
|
||||||
func (cc *CwtchServerListenChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
|
||||||
// NOTE: Should never be called
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupMessage sends a single group message to the peer
|
|
||||||
func (cc *CwtchServerListenChannel) SendGroupMessage(gm *protocol.GroupMessage) {
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: gm,
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
cc.channel.SendMessage(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet is called for each raw packet received on this channel.
|
|
||||||
func (cc *CwtchServerListenChannel) Packet(data []byte) {
|
|
||||||
// If we receive a packet on this channel, close the connection
|
|
||||||
cc.channel.CloseChannel()
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package listen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServerListenChannelAttributes(t *testing.T) {
|
|
||||||
cslc := new(CwtchServerListenChannel)
|
|
||||||
if cslc.Type() != "im.cwtch.server.listen" {
|
|
||||||
t.Errorf("cwtch channel type is incorrect %v", cslc.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cslc.OnlyClientCanOpen() {
|
|
||||||
t.Errorf("only clients should be able to open im.cwtch.server.listen channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cslc.Bidirectional() {
|
|
||||||
t.Errorf("im.cwtch.server.listen should not be bidirectional")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cslc.Singleton() {
|
|
||||||
t.Errorf("im.cwtch.server.listen should be a Singleton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cslc.RequiresAuthentication() != "none" {
|
|
||||||
t.Errorf("cwtch channel required auth is incorrect %v", cslc.RequiresAuthentication())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestServerListenChannelOpenOutbound(t *testing.T) {
|
|
||||||
cslc := new(CwtchServerListenChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
_, err := cslc.OpenOutbound(channel)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("server implementation of im.cwtch.server.listen should never open an outbound channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerListenChannel(t *testing.T) {
|
|
||||||
cslc := new(CwtchServerListenChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 1
|
|
||||||
closed := false
|
|
||||||
channel.CloseChannel = func() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
gotgm := false
|
|
||||||
channel.SendMessage = func([]byte) {
|
|
||||||
gotgm = true
|
|
||||||
}
|
|
||||||
|
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
|
||||||
ChannelIdentifier: proto.Int32(1),
|
|
||||||
ChannelType: proto.String(cslc.Type()),
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cslc.OpenInbound(channel, oc)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("OpenInbound for im.cwtch.server.listen should have succeeded, instead: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(resp[:], control)
|
|
||||||
|
|
||||||
if control.GetChannelResult() != nil {
|
|
||||||
gm := &protocol.GroupMessage{
|
|
||||||
Ciphertext: []byte("Hello"),
|
|
||||||
Signature: []byte{},
|
|
||||||
Spamguard: []byte{},
|
|
||||||
}
|
|
||||||
cslc.SendGroupMessage(gm)
|
|
||||||
if !gotgm {
|
|
||||||
t.Errorf("Did not receive packet on wire as expected in listen channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if closed {
|
|
||||||
t.Errorf("listen channel should not be cosed")
|
|
||||||
}
|
|
||||||
|
|
||||||
cslc.Packet(nil)
|
|
||||||
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("listen channel should be closed after incorrect packet received")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
t.Errorf("Expected ChannelResult from im.cwtch.server.listen, instead: %v", control)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@ package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"cwtch.im/tapir"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"github.com/struCoder/pidusage"
|
"github.com/struCoder/pidusage"
|
||||||
"os"
|
"os"
|
||||||
|
@ -30,7 +30,7 @@ type Monitors struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start initializes a Monitors's monitors
|
// Start initializes a Monitors's monitors
|
||||||
func (mp *Monitors) Start(ra *application.RicochetApplication, configDir string, log bool) {
|
func (mp *Monitors) Start(ts tapir.Service, configDir string, log bool) {
|
||||||
mp.log = log
|
mp.log = log
|
||||||
mp.configDir = configDir
|
mp.configDir = configDir
|
||||||
mp.starttime = time.Now()
|
mp.starttime = time.Now()
|
||||||
|
@ -50,7 +50,9 @@ func (mp *Monitors) Start(ra *application.RicochetApplication, configDir string,
|
||||||
sysInfo, _ := pidusage.GetStat(os.Getpid())
|
sysInfo, _ := pidusage.GetStat(os.Getpid())
|
||||||
return float64(sysInfo.Memory)
|
return float64(sysInfo.Memory)
|
||||||
})
|
})
|
||||||
mp.ClientConns = NewMonitorHistory(Count, Average, func() float64 { return float64(ra.ConnectionCount()) })
|
|
||||||
|
// TODO: replace with ts.
|
||||||
|
mp.ClientConns = NewMonitorHistory(Count, Average, func() float64 { return float64(ts.Metrics().ConnectionCount) })
|
||||||
|
|
||||||
if mp.log {
|
if mp.log {
|
||||||
go mp.run()
|
go mp.run()
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
package send
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/spam"
|
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CwtchServerSendChannel implements the ChannelHandler interface for a channel of
|
|
||||||
// type "im.cwtch.server.send - this implementation only handles server-side logic.
|
|
||||||
type CwtchServerSendChannel struct {
|
|
||||||
// Methods of Handler are called for Cwtch events on this channel
|
|
||||||
Handler CwtchServerSendChannelHandler
|
|
||||||
channel *channels.Channel
|
|
||||||
spamguard spam.Guard
|
|
||||||
}
|
|
||||||
|
|
||||||
// CwtchServerSendChannelHandler defines the interface needed to interact with this channel
|
|
||||||
type CwtchServerSendChannelHandler interface {
|
|
||||||
HandleGroupMessage(*protocol.GroupMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type string for this channel, e.g. "im.ricochet.Cwtch".
|
|
||||||
func (cc *CwtchServerSendChannel) Type() string {
|
|
||||||
return "im.cwtch.server.send"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed is called when the channel is closed for any reason.
|
|
||||||
func (cc *CwtchServerSendChannel) Closed(err error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyClientCanOpen - for Cwtch channels any side can open
|
|
||||||
func (cc *CwtchServerSendChannel) OnlyClientCanOpen() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton - for Cwtch channels there can only be one instance per direction
|
|
||||||
func (cc *CwtchServerSendChannel) Singleton() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bidirectional - for Cwtch channels are not bidrectional
|
|
||||||
func (cc *CwtchServerSendChannel) Bidirectional() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresAuthentication - Cwtch channels require hidden service auth
|
|
||||||
func (cc *CwtchServerSendChannel) RequiresAuthentication() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInbound is the first method called for an inbound channel request.
|
|
||||||
// If an error is returned, the channel is rejected. If a RawMessage is
|
|
||||||
// returned, it will be sent as the ChannelResult message.
|
|
||||||
func (cc *CwtchServerSendChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
|
||||||
cc.channel = channel
|
|
||||||
cc.spamguard.Difficulty = 2
|
|
||||||
return cc.spamguard.GenerateChallenge(channel.ID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutbound is the first method called for an outbound channel request.
|
|
||||||
// If an error is returned, the channel is not opened. If a RawMessage is
|
|
||||||
// returned, it will be sent as the OpenChannel message.
|
|
||||||
func (cc *CwtchServerSendChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
|
||||||
return nil, errors.New("server does not open send channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOutboundResult is called when a response is received for an
|
|
||||||
// outbound OpenChannel request. If `err` is non-nil, the channel was
|
|
||||||
// rejected and Closed will be called immediately afterwards. `raw`
|
|
||||||
// contains the raw protocol message including any extension data.
|
|
||||||
func (cc *CwtchServerSendChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
|
||||||
// NOTE: Should never be called
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet is called for each raw packet received on this channel.
|
|
||||||
func (cc *CwtchServerSendChannel) Packet(data []byte) {
|
|
||||||
csp := &protocol.CwtchServerPacket{}
|
|
||||||
err := proto.Unmarshal(data, csp)
|
|
||||||
if err == nil {
|
|
||||||
if csp.GetGroupMessage() != nil {
|
|
||||||
gm := csp.GetGroupMessage()
|
|
||||||
ok := cc.spamguard.ValidateChallenge(gm.GetCiphertext(), gm.GetSpamguard())
|
|
||||||
if ok {
|
|
||||||
cc.Handler.HandleGroupMessage(gm)
|
|
||||||
} else {
|
|
||||||
log.Errorf("Failed to validate spamguard %v\n", gm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Errorf("Failed to decode packet on SEND channel %v\n", err)
|
|
||||||
}
|
|
||||||
cc.channel.CloseChannel()
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
package send
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/protocol/connections/spam"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestHandler struct {
|
|
||||||
Received bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (th *TestHandler) HandleGroupMessage(m *protocol.GroupMessage) {
|
|
||||||
th.Received = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerSendChannelAttributes(t *testing.T) {
|
|
||||||
cssc := new(CwtchServerSendChannel)
|
|
||||||
if cssc.Type() != "im.cwtch.server.send" {
|
|
||||||
t.Errorf("cwtch channel type is incorrect %v", cssc.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.OnlyClientCanOpen() {
|
|
||||||
t.Errorf("only clients should be able to open im.cwtch.server.send channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.Bidirectional() {
|
|
||||||
t.Errorf("im.cwtch.server.send should not be bidirectional")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cssc.Singleton() {
|
|
||||||
t.Errorf("im.cwtch.server.send should be a Singleton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cssc.RequiresAuthentication() != "none" {
|
|
||||||
t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestServerSendChannelOpenOutbound(t *testing.T) {
|
|
||||||
cssc := new(CwtchServerSendChannel)
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
_, err := cssc.OpenOutbound(channel)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("server implementation of im.cwtch.server.send should never open an outbound channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerSendChannel(t *testing.T) {
|
|
||||||
cssc := new(CwtchServerSendChannel)
|
|
||||||
th := new(TestHandler)
|
|
||||||
cssc.Handler = th
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 1
|
|
||||||
closed := false
|
|
||||||
channel.CloseChannel = func() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
|
||||||
ChannelIdentifier: proto.Int32(1),
|
|
||||||
ChannelType: proto.String(cssc.Type()),
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cssc.OpenInbound(channel, oc)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("OpenInbound for im.cwtch.server.send should have succeeded, instead: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(resp[:], control)
|
|
||||||
|
|
||||||
if control.GetChannelResult() != nil {
|
|
||||||
|
|
||||||
var spamguard spam.Guard
|
|
||||||
spamguard.Difficulty = 2
|
|
||||||
|
|
||||||
ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce)
|
|
||||||
challenge := ce.([]byte)[:]
|
|
||||||
|
|
||||||
sgsolve := spamguard.SolveChallenge(challenge, []byte("Hello"))
|
|
||||||
//t.Logf("Solved: %x", sgsolve)
|
|
||||||
|
|
||||||
gm := &protocol.GroupMessage{
|
|
||||||
Ciphertext: []byte("Hello"),
|
|
||||||
Signature: []byte{},
|
|
||||||
Spamguard: sgsolve,
|
|
||||||
}
|
|
||||||
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: gm,
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
|
|
||||||
cssc.Packet(packet)
|
|
||||||
|
|
||||||
if !th.Received {
|
|
||||||
t.Errorf("group message should have been received")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("im.cwtch.server.send should have been closed after use")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("Expected ChannelResult from im.cwtch.server.send, instead: %v", control)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerSendChannelNoSpamGuard(t *testing.T) {
|
|
||||||
cssc := new(CwtchServerSendChannel)
|
|
||||||
th := new(TestHandler)
|
|
||||||
th.Received = false
|
|
||||||
cssc.Handler = th
|
|
||||||
channel := new(channels.Channel)
|
|
||||||
channel.ID = 1
|
|
||||||
closed := false
|
|
||||||
channel.CloseChannel = func() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
|
||||||
ChannelIdentifier: proto.Int32(1),
|
|
||||||
ChannelType: proto.String(cssc.Type()),
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cssc.OpenInbound(channel, oc)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("OpenInbound for im.cwtch.server.send should have succeeded, instead: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
control := new(Protocol_Data_Control.Packet)
|
|
||||||
proto.Unmarshal(resp[:], control)
|
|
||||||
|
|
||||||
if control.GetChannelResult() != nil {
|
|
||||||
|
|
||||||
var spamguard spam.Guard
|
|
||||||
spamguard.Difficulty = 2
|
|
||||||
|
|
||||||
ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce)
|
|
||||||
challenge := ce.([]byte)[:]
|
|
||||||
|
|
||||||
sgsolve := spamguard.SolveChallenge(challenge, []byte("4234"))
|
|
||||||
//t.Logf("Solved: %x", sgsolve)
|
|
||||||
|
|
||||||
gm := &protocol.GroupMessage{
|
|
||||||
Ciphertext: []byte("hello"),
|
|
||||||
Signature: []byte{},
|
|
||||||
Spamguard: sgsolve,
|
|
||||||
}
|
|
||||||
|
|
||||||
csp := &protocol.CwtchServerPacket{
|
|
||||||
GroupMessage: gm,
|
|
||||||
}
|
|
||||||
packet, _ := proto.Marshal(csp)
|
|
||||||
|
|
||||||
cssc.Packet(packet)
|
|
||||||
|
|
||||||
if th.Received == true {
|
|
||||||
t.Errorf("group message should not have been received")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !closed {
|
|
||||||
t.Errorf("im.cwtch.server.send should have been closed after use")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
t.Errorf("Expected ChannelResult from im.cwtch.server.send, instead: %v", control)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
108
server/server.go
108
server/server.go
|
@ -1,89 +1,80 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/server/fetch"
|
"crypto/ed25519"
|
||||||
"cwtch.im/cwtch/server/listen"
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/server/metrics"
|
"cwtch.im/cwtch/server/metrics"
|
||||||
"cwtch.im/cwtch/server/send"
|
|
||||||
"cwtch.im/cwtch/server/storage"
|
"cwtch.im/cwtch/server/storage"
|
||||||
|
"cwtch.im/tapir"
|
||||||
|
"cwtch.im/tapir/applications"
|
||||||
|
tor2 "cwtch.im/tapir/networks/tor"
|
||||||
|
"cwtch.im/tapir/primitives"
|
||||||
|
"cwtch.im/tapir/primitives/privacypass"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
"git.openprivacy.ca/openprivacy/connectivity"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server encapsulates a complete, compliant Cwtch server.
|
// Server encapsulates a complete, compliant Cwtch server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
app *application.RicochetApplication
|
service tapir.Service
|
||||||
config Config
|
config Config
|
||||||
metricsPack metrics.Monitors
|
metricsPack metrics.Monitors
|
||||||
closed bool
|
closed bool
|
||||||
|
tokenTapirService tapir.Service
|
||||||
|
tokenServer *privacypass.TokenServer
|
||||||
|
tokenService primitives.Identity
|
||||||
|
tokenServicePrivKey ed25519.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup initialized a server from a given configuration
|
||||||
|
func (s *Server) Setup(serverConfig Config) {
|
||||||
|
s.config = serverConfig
|
||||||
|
s.tokenServer = privacypass.NewTokenServer()
|
||||||
|
s.tokenService, s.tokenServicePrivKey = primitives.InitializeEphemeralIdentity()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts a server with the given privateKey
|
// Run starts a server with the given privateKey
|
||||||
// TODO: surface errors
|
// TODO: surface errors
|
||||||
// TODO: handle HUP/KILL signals to exit and close Tor gracefully
|
// TODO: handle HUP/KILL signals to exit and close Tor gracefully
|
||||||
// TODO: handle user input to exit
|
// TODO: handle user input to exit
|
||||||
func (s *Server) Run(acn connectivity.ACN, serverConfig Config) {
|
func (s *Server) Run(acn connectivity.ACN) {
|
||||||
s.closed = false
|
s.closed = false
|
||||||
s.config = serverConfig
|
|
||||||
cwtchserver := new(application.RicochetApplication)
|
|
||||||
s.metricsPack.Start(cwtchserver, serverConfig.ConfigDir, s.config.ServerReporting.LogMetricsToFile)
|
|
||||||
|
|
||||||
af := application.InstanceFactory{}
|
addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey)
|
||||||
af.Init()
|
|
||||||
|
//tokenService := privacypass.NewTokenServer()
|
||||||
|
identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey)
|
||||||
|
var service tapir.Service
|
||||||
|
service = new(tor2.BaseOnionService)
|
||||||
|
service.Init(acn, s.config.PrivateKey, &identity)
|
||||||
|
s.service = service
|
||||||
|
log.Infof("cwtch server running on cwtch:%s\n", addressIdentity+".onion:")
|
||||||
|
s.metricsPack.Start(service, s.config.ConfigDir, s.config.ServerReporting.LogMetricsToFile)
|
||||||
|
|
||||||
ms := new(storage.MessageStore)
|
ms := new(storage.MessageStore)
|
||||||
err := ms.Init(serverConfig.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter)
|
err := ms.Init(s.config.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
acn.Close()
|
acn.Close()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
af.AddHandler("im.cwtch.server.listen", func(rai *application.Instance) func() channels.Handler {
|
|
||||||
return func() channels.Handler {
|
|
||||||
cslc := new(listen.CwtchServerListenChannel)
|
|
||||||
return cslc
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
af.AddHandler("im.cwtch.server.fetch", func(rai *application.Instance) func() channels.Handler {
|
go func() {
|
||||||
si := new(Instance)
|
s.tokenTapirService = new(tor2.BaseOnionService)
|
||||||
si.Init(rai, cwtchserver, ms)
|
s.tokenTapirService.Init(acn, s.tokenServicePrivKey, &s.tokenService)
|
||||||
return func() channels.Handler {
|
tokenApplication := new(applications.TokenApplication)
|
||||||
cssc := new(fetch.CwtchServerFetchChannel)
|
tokenApplication.TokenService = s.tokenServer
|
||||||
cssc.Handler = si
|
powTokenApp := new(applications.ApplicationChain).
|
||||||
return cssc
|
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
||||||
}
|
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
||||||
})
|
s.tokenTapirService.Listen(powTokenApp)
|
||||||
|
}()
|
||||||
af.AddHandler("im.cwtch.server.send", func(rai *application.Instance) func() channels.Handler {
|
|
||||||
si := new(Instance)
|
|
||||||
si.Init(rai, cwtchserver, ms)
|
|
||||||
return func() channels.Handler {
|
|
||||||
cssc := new(send.CwtchServerSendChannel)
|
|
||||||
cssc.Handler = si
|
|
||||||
return cssc
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey)
|
|
||||||
cwtchserver.Init(acn, "cwtch server for "+addressIdentity, s.config.Identity(), af, new(application.AcceptAllContactManager))
|
|
||||||
port := strconv.Itoa(application.RicochetPort)
|
|
||||||
log.Infof("cwtch server running on cwtch:%s\n", addressIdentity+".onion:"+port)
|
|
||||||
|
|
||||||
s.app = cwtchserver
|
|
||||||
|
|
||||||
for true {
|
for true {
|
||||||
listenService, err := acn.Listen(s.config.PrivateKey, application.RicochetPort)
|
s.service.Listen(NewTokenBoardServer(ms, s.tokenServer))
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Listen() error setting up onion service: %v\n", err)
|
|
||||||
} else {
|
|
||||||
s.app.Run(listenService)
|
|
||||||
}
|
|
||||||
if s.closed {
|
if s.closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -91,9 +82,20 @@ func (s *Server) Run(acn connectivity.ACN, serverConfig Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyBundle provides the keybundle of the server (mostly used for testing)
|
||||||
|
func (s *Server) KeyBundle() model.KeyBundle {
|
||||||
|
kb := model.KeyBundle{Keys: make(map[string]model.Key)}
|
||||||
|
identity := s.config.Identity()
|
||||||
|
kb.Keys[model.KeyTypeOnion] = model.Key(identity.Hostname())
|
||||||
|
kb.Keys[model.KeyTypeTokenOnion] = model.Key(tor.GetTorV3Hostname(s.tokenService.PublicKey()))
|
||||||
|
kb.Keys[model.KeyTypePrivacyPass] = model.Key(s.tokenServer.Y.String())
|
||||||
|
return kb
|
||||||
|
}
|
||||||
|
|
||||||
// Shutdown kills the app closing all connections and freeing all goroutines
|
// Shutdown kills the app closing all connections and freeing all goroutines
|
||||||
func (s *Server) Shutdown() {
|
func (s *Server) Shutdown() {
|
||||||
s.closed = true
|
s.closed = true
|
||||||
s.app.Shutdown()
|
s.service.Shutdown()
|
||||||
|
s.tokenTapirService.Shutdown()
|
||||||
s.metricsPack.Stop()
|
s.metricsPack.Stop()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,10 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"cwtch.im/tapir/primitives"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
"github.com/gtank/ristretto255"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
|
@ -23,12 +24,14 @@ type Config struct {
|
||||||
MaxBufferLines int `json:"maxBufferLines"`
|
MaxBufferLines int `json:"maxBufferLines"`
|
||||||
PublicKey ed25519.PublicKey `json:"publicKey"`
|
PublicKey ed25519.PublicKey `json:"publicKey"`
|
||||||
PrivateKey ed25519.PrivateKey `json:"privateKey"`
|
PrivateKey ed25519.PrivateKey `json:"privateKey"`
|
||||||
|
PrivacyPassPublicKey ristretto255.Element `json:"privacyPassPublicKey"`
|
||||||
|
PrivacyPassPrivateKey ristretto255.Scalar `json:"privacyPassPrivateKey"`
|
||||||
ServerReporting Reporting `json:"serverReporting"`
|
ServerReporting Reporting `json:"serverReporting"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identity returns an encapsulation of the servers keys for running ricochet
|
// Identity returns an encapsulation of the servers keys for running ricochet
|
||||||
func (config *Config) Identity() identity.Identity {
|
func (config *Config) Identity() primitives.Identity {
|
||||||
return identity.InitializeV3("", &config.PrivateKey, &config.PublicKey)
|
return primitives.InitializeIdentity("", &config.PrivateKey, &config.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save dumps the latest version of the config to a json file given by filename
|
// Save dumps the latest version of the config to a json file given by filename
|
||||||
|
@ -44,7 +47,7 @@ func LoadConfig(configDir, filename string) Config {
|
||||||
config := Config{}
|
config := Config{}
|
||||||
config.ConfigDir = configDir
|
config.ConfigDir = configDir
|
||||||
config.MaxBufferLines = 100000
|
config.MaxBufferLines = 100000
|
||||||
config.ServerReporting.LogMetricsToFile = false
|
config.ServerReporting.LogMetricsToFile = true
|
||||||
raw, err := ioutil.ReadFile(path.Join(configDir, filename))
|
raw, err := ioutil.ReadFile(path.Join(configDir, filename))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = json.Unmarshal(raw, &config)
|
err = json.Unmarshal(raw, &config)
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/server/listen"
|
|
||||||
"cwtch.im/cwtch/server/storage"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Instance encapsulates the Ricochet application.
|
|
||||||
type Instance struct {
|
|
||||||
rai *application.Instance
|
|
||||||
ra *application.RicochetApplication
|
|
||||||
msi storage.MessageStoreInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init sets up a Server Instance
|
|
||||||
func (si *Instance) Init(rai *application.Instance, ra *application.RicochetApplication, msi storage.MessageStoreInterface) {
|
|
||||||
si.rai = rai
|
|
||||||
si.ra = ra
|
|
||||||
si.msi = msi
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFetchRequest returns a list of all messages in the servers buffer
|
|
||||||
func (si *Instance) HandleFetchRequest() []*protocol.GroupMessage {
|
|
||||||
return si.msi.FetchMessages()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGroupMessage takes in a group message and distributes it to all listening peers
|
|
||||||
func (si *Instance) HandleGroupMessage(gm *protocol.GroupMessage) {
|
|
||||||
si.msi.AddMessage(*gm)
|
|
||||||
go si.ra.Broadcast(func(rai *application.Instance) {
|
|
||||||
rai.Connection.Do(func() error {
|
|
||||||
channel := rai.Connection.Channel("im.cwtch.server.listen", channels.Inbound)
|
|
||||||
if channel != nil {
|
|
||||||
cslc, ok := channel.Handler.(*listen.CwtchServerListenChannel)
|
|
||||||
if ok {
|
|
||||||
cslc.SendGroupMessage(gm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/cwtch/protocol"
|
|
||||||
"cwtch.im/cwtch/server/metrics"
|
|
||||||
"cwtch.im/cwtch/server/storage"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServerInstance(t *testing.T) {
|
|
||||||
si := new(Instance)
|
|
||||||
ai := new(application.Instance)
|
|
||||||
ra := new(application.RicochetApplication)
|
|
||||||
msi := new(storage.MessageStore)
|
|
||||||
os.RemoveAll("messages")
|
|
||||||
msi.Init(".", 5, metrics.NewCounter())
|
|
||||||
gm := protocol.GroupMessage{
|
|
||||||
Ciphertext: []byte("Hello this is a fairly average length message that we are writing here."),
|
|
||||||
Spamguard: []byte{},
|
|
||||||
}
|
|
||||||
|
|
||||||
si.Init(ai, ra, msi)
|
|
||||||
msi.AddMessage(gm)
|
|
||||||
res := si.HandleFetchRequest()
|
|
||||||
|
|
||||||
if len(res) != 1 {
|
|
||||||
t.Errorf("Expected 1 Group messages Instead got %v", res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ra.HandleApplicationInstance(ai)
|
|
||||||
si.HandleGroupMessage(&gm)
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 2)
|
|
||||||
}
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
|
"cwtch.im/cwtch/server/storage"
|
||||||
|
"cwtch.im/tapir"
|
||||||
|
"cwtch.im/tapir/applications"
|
||||||
|
"cwtch.im/tapir/primitives/privacypass"
|
||||||
|
"encoding/json"
|
||||||
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTokenBoardServer generates new Server for Token Board
|
||||||
|
func NewTokenBoardServer(store storage.MessageStoreInterface, tokenService *privacypass.TokenServer) tapir.Application {
|
||||||
|
tba := new(TokenboardServer)
|
||||||
|
tba.TokenService = tokenService
|
||||||
|
tba.LegacyMessageStore = store
|
||||||
|
return tba
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenboardServer defines the token board server
|
||||||
|
type TokenboardServer struct {
|
||||||
|
applications.AuthApp
|
||||||
|
connection tapir.Connection
|
||||||
|
TokenService *privacypass.TokenServer
|
||||||
|
LegacyMessageStore storage.MessageStoreInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInstance creates a new TokenBoardApp
|
||||||
|
func (ta *TokenboardServer) NewInstance() tapir.Application {
|
||||||
|
tba := new(TokenboardServer)
|
||||||
|
tba.TokenService = ta.TokenService
|
||||||
|
tba.LegacyMessageStore = ta.LegacyMessageStore
|
||||||
|
return tba
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes the cryptographic TokenBoardApp
|
||||||
|
func (ta *TokenboardServer) Init(connection tapir.Connection) {
|
||||||
|
ta.AuthApp.Init(connection)
|
||||||
|
if connection.HasCapability(applications.AuthCapability) {
|
||||||
|
ta.connection = connection
|
||||||
|
ta.connection.SetCapability(groups.CwtchServerSyncedCapability)
|
||||||
|
go ta.Listen()
|
||||||
|
} else {
|
||||||
|
connection.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen processes the messages for this application
|
||||||
|
func (ta *TokenboardServer) Listen() {
|
||||||
|
for {
|
||||||
|
data := ta.connection.Expect()
|
||||||
|
if len(data) == 0 {
|
||||||
|
log.Debugf("Server Closing Connection")
|
||||||
|
ta.connection.Close()
|
||||||
|
return // connection is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
var message groups.Message
|
||||||
|
if err := json.Unmarshal(data, &message); err != nil {
|
||||||
|
log.Debugf("Server Closing Connection Because of Malformed Client Packet %v", err)
|
||||||
|
ta.connection.Close()
|
||||||
|
return // connection is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
switch message.MessageType {
|
||||||
|
case groups.PostRequestMessage:
|
||||||
|
if message.PostRequest != nil {
|
||||||
|
postrequest := *message.PostRequest
|
||||||
|
log.Debugf("Received a Post Message Request: %v", ta.connection.Hostname())
|
||||||
|
ta.postMessageRequest(postrequest)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Server Closing Connection Because of PostRequestMessage Client Packet")
|
||||||
|
ta.connection.Close()
|
||||||
|
return // connection is closed
|
||||||
|
}
|
||||||
|
case groups.ReplayRequestMessage:
|
||||||
|
if message.ReplayRequest != nil {
|
||||||
|
log.Debugf("Received Replay Request %v", message.ReplayRequest)
|
||||||
|
messages := ta.LegacyMessageStore.FetchMessages()
|
||||||
|
response, _ := json.Marshal(groups.Message{MessageType: groups.ReplayResultMessage, ReplayResult: &groups.ReplayResult{NumMessages: len(messages)}})
|
||||||
|
log.Debugf("Sending Replay Response %v", groups.ReplayResult{NumMessages: len(messages)})
|
||||||
|
ta.connection.Send(response)
|
||||||
|
for _, message := range messages {
|
||||||
|
data, _ = json.Marshal(message)
|
||||||
|
ta.connection.Send(data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debugf("Server Closing Connection Because of Malformed ReplayRequestMessage Packet")
|
||||||
|
ta.connection.Close()
|
||||||
|
return // connection is closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ta *TokenboardServer) postMessageRequest(pr groups.PostRequest) {
|
||||||
|
if err := ta.TokenService.SpendToken(pr.Token, append(pr.EGM.ToBytes(), ta.connection.ID().Hostname()...)); err == nil {
|
||||||
|
log.Debugf("Token is valid")
|
||||||
|
ta.LegacyMessageStore.AddMessage(pr.EGM)
|
||||||
|
data, _ := json.Marshal(groups.Message{MessageType: groups.PostResultMessage, PostResult: &groups.PostResult{Success: true}})
|
||||||
|
ta.connection.Send(data)
|
||||||
|
data, _ = json.Marshal(groups.Message{MessageType: groups.NewMessageMessage, NewMessage: &groups.NewMessage{EGM: pr.EGM}})
|
||||||
|
ta.connection.Broadcast(data, groups.CwtchServerSyncedCapability)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Attempt to spend an invalid token: %v", err)
|
||||||
|
data, _ := json.Marshal(groups.Message{MessageType: groups.PostResultMessage, PostResult: &groups.PostResult{Success: false}})
|
||||||
|
ta.connection.Send(data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
"cwtch.im/cwtch/server/metrics"
|
"cwtch.im/cwtch/server/metrics"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -20,8 +20,8 @@ const (
|
||||||
|
|
||||||
// MessageStoreInterface defines an interface to interact with a store of cwtch messages.
|
// MessageStoreInterface defines an interface to interact with a store of cwtch messages.
|
||||||
type MessageStoreInterface interface {
|
type MessageStoreInterface interface {
|
||||||
AddMessage(protocol.GroupMessage)
|
AddMessage(groups.EncryptedGroupMessage)
|
||||||
FetchMessages() []*protocol.GroupMessage
|
FetchMessages() []*groups.EncryptedGroupMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageStore is a file-backed implementation of MessageStoreInterface
|
// MessageStore is a file-backed implementation of MessageStoreInterface
|
||||||
|
@ -30,7 +30,7 @@ type MessageStore struct {
|
||||||
filePos int
|
filePos int
|
||||||
storeDirectory string
|
storeDirectory string
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
messages []*protocol.GroupMessage
|
messages []*groups.EncryptedGroupMessage
|
||||||
messageCounter metrics.Counter
|
messageCounter metrics.Counter
|
||||||
maxBufferLines int
|
maxBufferLines int
|
||||||
bufferPos int
|
bufferPos int
|
||||||
|
@ -45,7 +45,7 @@ func (ms *MessageStore) Close() {
|
||||||
ms.lock.Unlock()
|
ms.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MessageStore) updateBuffer(gm *protocol.GroupMessage) {
|
func (ms *MessageStore) updateBuffer(gm *groups.EncryptedGroupMessage) {
|
||||||
ms.messages[ms.bufferPos] = gm
|
ms.messages[ms.bufferPos] = gm
|
||||||
ms.bufferPos++
|
ms.bufferPos++
|
||||||
if ms.bufferPos == ms.maxBufferLines {
|
if ms.bufferPos == ms.maxBufferLines {
|
||||||
|
@ -70,7 +70,7 @@ func (ms *MessageStore) initAndLoadFiles() error {
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
gms := scanner.Text()
|
gms := scanner.Text()
|
||||||
ms.filePos++
|
ms.filePos++
|
||||||
gm := &protocol.GroupMessage{}
|
gm := &groups.EncryptedGroupMessage{}
|
||||||
err := json.Unmarshal([]byte(gms), gm)
|
err := json.Unmarshal([]byte(gms), gm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ms.updateBuffer(gm)
|
ms.updateBuffer(gm)
|
||||||
|
@ -83,7 +83,7 @@ func (ms *MessageStore) initAndLoadFiles() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MessageStore) updateFile(gm *protocol.GroupMessage) {
|
func (ms *MessageStore) updateFile(gm *groups.EncryptedGroupMessage) {
|
||||||
s, err := json.Marshal(gm)
|
s, err := json.Marshal(gm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to unmarshal group message %v\n", err)
|
log.Errorf("Failed to unmarshal group message %v\n", err)
|
||||||
|
@ -118,7 +118,7 @@ func (ms *MessageStore) Init(appDirectory string, maxBufferLines int, messageCou
|
||||||
|
|
||||||
ms.bufferPos = 0
|
ms.bufferPos = 0
|
||||||
ms.maxBufferLines = maxBufferLines
|
ms.maxBufferLines = maxBufferLines
|
||||||
ms.messages = make([]*protocol.GroupMessage, maxBufferLines)
|
ms.messages = make([]*groups.EncryptedGroupMessage, maxBufferLines)
|
||||||
ms.bufferRotated = false
|
ms.bufferRotated = false
|
||||||
ms.messageCounter = messageCounter
|
ms.messageCounter = messageCounter
|
||||||
|
|
||||||
|
@ -127,13 +127,13 @@ func (ms *MessageStore) Init(appDirectory string, maxBufferLines int, messageCou
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchMessages returns all messages from the backing file.
|
// FetchMessages returns all messages from the backing file.
|
||||||
func (ms *MessageStore) FetchMessages() (messages []*protocol.GroupMessage) {
|
func (ms *MessageStore) FetchMessages() (messages []*groups.EncryptedGroupMessage) {
|
||||||
ms.lock.Lock()
|
ms.lock.Lock()
|
||||||
if !ms.bufferRotated {
|
if !ms.bufferRotated {
|
||||||
messages = make([]*protocol.GroupMessage, ms.bufferPos)
|
messages = make([]*groups.EncryptedGroupMessage, ms.bufferPos)
|
||||||
copy(messages, ms.messages[0:ms.bufferPos])
|
copy(messages, ms.messages[0:ms.bufferPos])
|
||||||
} else {
|
} else {
|
||||||
messages = make([]*protocol.GroupMessage, ms.maxBufferLines)
|
messages = make([]*groups.EncryptedGroupMessage, ms.maxBufferLines)
|
||||||
copy(messages, ms.messages[ms.bufferPos:ms.maxBufferLines])
|
copy(messages, ms.messages[ms.bufferPos:ms.maxBufferLines])
|
||||||
copy(messages[ms.bufferPos:], ms.messages[0:ms.bufferPos])
|
copy(messages[ms.bufferPos:], ms.messages[0:ms.bufferPos])
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ func (ms *MessageStore) FetchMessages() (messages []*protocol.GroupMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddMessage adds a GroupMessage to the store
|
// AddMessage adds a GroupMessage to the store
|
||||||
func (ms *MessageStore) AddMessage(gm protocol.GroupMessage) {
|
func (ms *MessageStore) AddMessage(gm groups.EncryptedGroupMessage) {
|
||||||
ms.messageCounter.Add(1)
|
ms.messageCounter.Add(1)
|
||||||
ms.lock.Lock()
|
ms.lock.Lock()
|
||||||
ms.updateBuffer(&gm)
|
ms.updateBuffer(&gm)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
"cwtch.im/cwtch/server/metrics"
|
"cwtch.im/cwtch/server/metrics"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -14,9 +14,8 @@ func TestMessageStore(t *testing.T) {
|
||||||
counter := metrics.NewCounter()
|
counter := metrics.NewCounter()
|
||||||
ms.Init("./", 1000, counter)
|
ms.Init("./", 1000, counter)
|
||||||
for i := 0; i < 499; i++ {
|
for i := 0; i < 499; i++ {
|
||||||
gm := protocol.GroupMessage{
|
gm := groups.EncryptedGroupMessage{
|
||||||
Ciphertext: []byte("Hello this is a fairly average length message that we are writing here. " + strconv.Itoa(i)),
|
Ciphertext: []byte("Hello this is a fairly average length message that we are writing here. " + strconv.Itoa(i)),
|
||||||
Spamguard: []byte{},
|
|
||||||
}
|
}
|
||||||
ms.AddMessage(gm)
|
ms.AddMessage(gm)
|
||||||
}
|
}
|
||||||
|
@ -33,9 +32,8 @@ func TestMessageStore(t *testing.T) {
|
||||||
counter.Reset()
|
counter.Reset()
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
gm := protocol.GroupMessage{
|
gm := groups.EncryptedGroupMessage{
|
||||||
Ciphertext: []byte("Hello this is a fairly average length message that we are writing here. " + strconv.Itoa(i)),
|
Ciphertext: []byte("Hello this is a fairly average length message that we are writing here. " + strconv.Itoa(i)),
|
||||||
Spamguard: []byte{},
|
|
||||||
}
|
}
|
||||||
ms.AddMessage(gm)
|
ms.AddMessage(gm)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/peer"
|
||||||
"cwtch.im/cwtch/protocol/connections"
|
"cwtch.im/cwtch/protocol/connections"
|
||||||
cwtchserver "cwtch.im/cwtch/server"
|
cwtchserver "cwtch.im/cwtch/server"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
@ -115,7 +116,7 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
log.ExcludeFromPattern("pipeBridge")
|
log.ExcludeFromPattern("pipeBridge")
|
||||||
log.ExcludeFromPattern("tapir")
|
log.ExcludeFromPattern("tapir")
|
||||||
os.RemoveAll("tor")
|
os.RemoveAll("tor")
|
||||||
acn, err := tor.NewTorACN(".", "")
|
acn, err := tor.NewTorACNWithAuth(".", "", 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not start Tor: %v", err)
|
t.Fatalf("Could not start Tor: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -125,7 +126,7 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
|
|
||||||
serverOnline := false
|
serverOnline := false
|
||||||
var serverAddr string
|
var serverAddr string
|
||||||
|
var serverKeyBundle []byte
|
||||||
if !serverOnline {
|
if !serverOnline {
|
||||||
// launch app with new key
|
// launch app with new key
|
||||||
fmt.Println("No server found!")
|
fmt.Println("No server found!")
|
||||||
|
@ -135,7 +136,10 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
config := cwtchserver.LoadConfig(".", "server-test.json")
|
config := cwtchserver.LoadConfig(".", "server-test.json")
|
||||||
identity := config.Identity()
|
identity := config.Identity()
|
||||||
serverAddr = identity.Hostname()
|
serverAddr = identity.Hostname()
|
||||||
go server.Run(acn, config)
|
server.Setup(config)
|
||||||
|
serverKeyBundle, _ = json.Marshal(server.KeyBundle())
|
||||||
|
log.Debugf("server key bundle %s", serverKeyBundle)
|
||||||
|
go server.Run(acn)
|
||||||
|
|
||||||
// let tor get established
|
// let tor get established
|
||||||
fmt.Printf("Establishing Tor hidden service: %v...\n", serverAddr)
|
fmt.Printf("Establishing Tor hidden service: %v...\n", serverAddr)
|
||||||
|
@ -197,6 +201,7 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
// ***** Peering, server joining, group creation / invite *****
|
// ***** Peering, server joining, group creation / invite *****
|
||||||
|
|
||||||
fmt.Println("Alice joining server...")
|
fmt.Println("Alice joining server...")
|
||||||
|
alice.AddServer(string(serverKeyBundle))
|
||||||
alice.JoinServer(serverAddr)
|
alice.JoinServer(serverAddr)
|
||||||
|
|
||||||
fmt.Println("Alice peering with Bob...")
|
fmt.Println("Alice peering with Bob...")
|
||||||
|
@ -222,10 +227,12 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
// Normal flow would be Bob app monitors for the new connection (a new connection state change to Auth
|
// Normal flow would be Bob app monitors for the new connection (a new connection state change to Auth
|
||||||
// and the adds the user to peer, and then approves or blocks it
|
// and the adds the user to peer, and then approves or blocks it
|
||||||
bob.AddContact("alice?", alice.GetOnion(), model.AuthApproved)
|
bob.AddContact("alice?", alice.GetOnion(), model.AuthApproved)
|
||||||
|
bob.AddServer(string(serverKeyBundle))
|
||||||
bob.SetContactAuthorization(alice.GetOnion(), model.AuthApproved)
|
bob.SetContactAuthorization(alice.GetOnion(), model.AuthApproved)
|
||||||
|
|
||||||
waitForPeerPeerConnection(t, alice, carol)
|
waitForPeerPeerConnection(t, alice, carol)
|
||||||
carol.AddContact("alice?", alice.GetOnion(), model.AuthApproved)
|
carol.AddContact("alice?", alice.GetOnion(), model.AuthApproved)
|
||||||
|
carol.AddServer(string(serverKeyBundle))
|
||||||
carol.SetContactAuthorization(alice.GetOnion(), model.AuthApproved)
|
carol.SetContactAuthorization(alice.GetOnion(), model.AuthApproved)
|
||||||
|
|
||||||
fmt.Println("Alice and Bob getVal public.name...")
|
fmt.Println("Alice and Bob getVal public.name...")
|
||||||
|
@ -315,7 +322,7 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error for Alice inviting Carol to group: %v", err)
|
t.Fatalf("Error for Alice inviting Carol to group: %v", err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second * 10)
|
time.Sleep(time.Second * 60) // Account for some token acquisition in Alice and Bob flows.
|
||||||
fmt.Println("Carol examining groups and accepting invites...")
|
fmt.Println("Carol examining groups and accepting invites...")
|
||||||
for _, groupID := range carol.GetGroups() {
|
for _, groupID := range carol.GetGroups() {
|
||||||
group := carol.GetGroup(groupID)
|
group := carol.GetGroup(groupID)
|
||||||
|
@ -338,16 +345,16 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
|
|
||||||
fmt.Printf("%v> %v", bobName, bobLines[2])
|
fmt.Printf("%v> %v", bobName, bobLines[2])
|
||||||
bob.SendMessageToGroup(groupID, bobLines[2])
|
bob.SendMessageToGroup(groupID, bobLines[2])
|
||||||
time.Sleep(time.Second * 10)
|
time.Sleep(time.Second * 60) // we need to account for spam-based token acquisition
|
||||||
|
|
||||||
fmt.Printf("%v> %v", carolName, carolLines[0])
|
fmt.Printf("%v> %v", carolName, carolLines[0])
|
||||||
carol.SendMessageToGroup(groupID, carolLines[0])
|
carol.SendMessageToGroup(groupID, carolLines[0])
|
||||||
time.Sleep(time.Second * 10)
|
time.Sleep(time.Second * 60) // we need to account for spam-based token acquisition
|
||||||
|
|
||||||
// ***** Verify Test *****
|
// ***** Verify Test *****
|
||||||
|
|
||||||
fmt.Println("Final syncing time...")
|
fmt.Println("Final syncing time...")
|
||||||
time.Sleep(time.Second * 30)
|
time.Sleep(time.Second * 60)
|
||||||
|
|
||||||
alicesGroup := alice.GetGroup(groupID)
|
alicesGroup := alice.GetGroup(groupID)
|
||||||
if alicesGroup == nil {
|
if alicesGroup == nil {
|
||||||
|
@ -369,14 +376,14 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
fmt.Printf("Bob's TimeLine:\n")
|
fmt.Printf("Bob's TimeLine:\n")
|
||||||
bobVerified := printAndCountVerifedTimeline(t, bobsGroup.GetTimeline())
|
bobVerified := printAndCountVerifedTimeline(t, bobsGroup.GetTimeline())
|
||||||
if bobVerified != 6 {
|
if bobVerified != 6 {
|
||||||
t.Errorf("Bob did not have 5 verified messages")
|
t.Errorf("Bob did not have 6 verified messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
carolsGroup := carol.GetGroup(groupID)
|
carolsGroup := carol.GetGroup(groupID)
|
||||||
fmt.Printf("Carol's TimeLine:\n")
|
fmt.Printf("Carol's TimeLine:\n")
|
||||||
carolVerified := printAndCountVerifedTimeline(t, carolsGroup.GetTimeline())
|
carolVerified := printAndCountVerifedTimeline(t, carolsGroup.GetTimeline())
|
||||||
if carolVerified != 6 {
|
if carolVerified != 6 {
|
||||||
t.Errorf("Carol did not have 3 verified messages")
|
t.Errorf("Carol did not have 6 verified messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(alicesGroup.GetTimeline()) != 4 {
|
if len(alicesGroup.GetTimeline()) != 4 {
|
||||||
|
|
|
@ -9,16 +9,8 @@ go test -race ${1} -coverprofile=storage.v0.cover.out -v ./storage/v0
|
||||||
go test -race ${1} -coverprofile=storage.v1.cover.out -v ./storage/v1
|
go test -race ${1} -coverprofile=storage.v1.cover.out -v ./storage/v1
|
||||||
go test -race ${1} -coverprofile=storage.cover.out -v ./storage
|
go test -race ${1} -coverprofile=storage.cover.out -v ./storage
|
||||||
go test -race ${1} -coverprofile=peer.connections.cover.out -v ./protocol/connections
|
go test -race ${1} -coverprofile=peer.connections.cover.out -v ./protocol/connections
|
||||||
go test -race ${1} -coverprofile=protocol.spam.cover.out -v ./protocol/connections/spam
|
|
||||||
go test -race ${1} -coverprofile=peer.fetch.cover.out -v ./protocol/connections/fetch
|
|
||||||
go test -race ${1} -coverprofile=peer.listen.cover.out -v ./protocol/connections/listen
|
|
||||||
go test -race ${1} -coverprofile=peer.send.cover.out -v ./protocol/connections/send
|
|
||||||
go test -race ${1} -coverprofile=peer.cover.out -v ./peer
|
go test -race ${1} -coverprofile=peer.cover.out -v ./peer
|
||||||
go test -race ${1} -coverprofile=server.fetch.cover.out -v ./server/fetch
|
|
||||||
go test -race ${1} -coverprofile=server.listen.cover.out -v ./server/listen
|
|
||||||
go test -race ${1} -coverprofile=server.send.cover.out -v ./server/send
|
|
||||||
go test -race ${1} -coverprofile=server.metrics.cover.out -v ./server/metrics
|
go test -race ${1} -coverprofile=server.metrics.cover.out -v ./server/metrics
|
||||||
go test -race ${1} -coverprofile=server.cover.out -v ./server
|
|
||||||
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
||||||
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
||||||
rm -rf *.cover.out
|
rm -rf *.cover.out
|
||||||
|
|
Loading…
Reference in New Issue