feature complete app

This commit is contained in:
Sarah Jamie Lewis 2018-05-02 21:12:45 -07:00
parent 383828512a
commit eacce2113c
5 changed files with 253 additions and 27 deletions

View File

@ -1,33 +1,151 @@
package main
import (
"bufio"
"fmt"
app2 "git.mascherari.press/cwtch/app"
"os"
"strings"
"github.com/c-bata/go-prompt"
)
var app app2.Application
func completer(d prompt.Document) []prompt.Suggest {
s := []prompt.Suggest{}
if d.FindStartOfPreviousWord() == 0 {
s := []prompt.Suggest{
{Text: "new-profile", Description: "create a new profile"},
{Text: "load-profile", Description: "load a new profile"},
{Text: "quit", Description: "quit cwtch"},
{Text: "servers", Description: "retrieve a list of servers and their connection status"},
{Text: "peers", Description: "retrieve a list of peers and their connection status"},
{Text: "contacts", Description: "retrieve a list of contacts"},
{Text: "groups", Description: "retrieve a list of groups"},
{Text: "send", Description: "send a message to a group"},
{Text: "timeline", Description: "read the timeline of a given group"},
{Text: "accept-invite", Description: "accept the invite of a group"},
{Text: "invite", Description: "invite a new contact"},
{Text: "invite-to-group", Description: "invite an existing contact to join an existing group"},
{Text: "new-group", Description: "create a new group"},
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
w := d.CurrentLine()
if strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") {
s = []prompt.Suggest{}
groups := app.Peer.Profile.Groups
for _, group := range groups {
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
if strings.HasPrefix(w, "invite-to-group") {
if d.FindStartOfPreviousWordWithSpace() == 0 {
s = []prompt.Suggest{}
contacts := app.Peer.Profile.Contacts
for _, contact := range contacts {
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
} else {
s = []prompt.Suggest{}
groups := app.Peer.Profile.Groups
for _, group := range groups {
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)
}
}
if strings.HasPrefix(w, "accept-invite") {
s = []prompt.Suggest{}
groups := app.Peer.Profile.Groups
for _, group := range groups {
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)
}
return s
}
func main() {
quit := false
app := app2.Application{}
profilefile := ""
for !quit {
reader := bufio.NewReader(os.Stdin)
cwtch :=
`
#, #'
@@@@@@:
@@@@@@.
@'@@+#' @@@@+
''''''@ #+@ :
@''''+;+' . '
@''@' :+' , ; ##, +'
,@@ ;' #'#@''. #''@''#
# ''''''#:,,#'''''@
: @''''@ :+'''@
' @;+'@ @'#
.:# '#..# '# @
@@@@@@
@@@@@@
'@@@@
@# . .
+++, #'@+'@
''', ''''''#
.#+# ''', @'''+,
@''# ''', .#@
:; '@''# .;. ''', ' : ;. ,
@+'''@ '+'+ @++ @+'@+''''+@ #+'''#: ''';#''+@ @@@@ @@@@@@@@@ :@@@@#
#''''''# +''. +'': +'''''''''+ @'''''''# '''+'''''@ @@@@ @@@@@@@@@@@@@@@@:
@'''@@'''@ @''# ,'''@ ''+ @@''+#+ :'''@@+''' ''''@@'''' @@@@ @@@@@@@@@@@@@@@@@
'''# @''# +''@ @'''# ;''@ +''+ @''@ ,+'', '''@ #'''. @@@@ @@@@ '@@@# @@@@
;''' @@; '''# #'@'' @''@ @''+ +''# .@@ ''', '''. @@@@ @@@ @@@ .@@@
@''# #'' ''#''#@''. #''# '''. '''. +'', @@@@ @@@ @@@ @@@
@''# @''@'' #'@+'+ #''# '''. ''', +'', +@@@.@@@ @@@@ @@@, @@@ ,@@@
;''+ @, +''@'# @'+''@ @''# +''; '+ ''', +'', @@@@@@@@# @@@@ @@@. .@@@ .@@@
'''# ++'+ ''''@ ,''''# #''' @''@ '@''+ ''', ''', @@@@@@@@: @@@@ @@@; .@@@' ;@@@
@'''@@'''@ #'''. +'''' ;'''#@ :'''#@+''+ ''', ''', @@@@@@# @@@@ @@@+ ,@@@. @@@@
#''''''# @''+ @''+ +'''' @'''''''# ''', ''', #@@@. @@@@ @@@+ @@@ @@@@
@+''+@ '++@ ;++@ '#''@ ##'''@: +++, +++, :@ @@@@ @@@' @@@ '@@@
:' ' '`
fmt.Printf("%v\n\n", cwtch)
quit := false
app = app2.Application{}
profilefile := ""
var history []string
for !quit {
profile := "unset"
if app.Peer != nil {
profile = app.Peer.Profile.Name
}
fmt.Printf("cwtch [%v]> ", profile)
text, _ := reader.ReadString('\n')
commands := strings.Split(text[0:len(text)-1], " ")
prmpt := fmt.Sprintf("cwtch [%v]> ", profile)
text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple),
prompt.OptionDescriptionBGColor(prompt.White),
prompt.OptionHistory(history))
commands := strings.Split(text[0:len(text)], " ")
history = append(history,text)
switch commands[0] {
case "quit":
app.Peer.Save(profilefile)
quit = true
case "newprofile":
case "new-profile":
if len(commands) == 3 {
err := app.NewProfile(commands[1], commands[2])
if err == nil {
@ -38,7 +156,7 @@ func main() {
} else {
fmt.Printf("Error creating NewProfile, usage: newprofile [name] [filename]\n")
}
case "loadprofile":
case "load-profile":
if len(commands) == 2 {
err := app.SetProfile(commands[1])
profilefile = commands[1]
@ -55,7 +173,7 @@ func main() {
if app.Peer != nil {
fmt.Printf("Address cwtch:%v\n", app.Peer.Profile.Onion)
} else {
fmt.Printf("Profile needs to be set")
fmt.Printf("Profile needs to be set\n")
}
case "invite":
if len(commands) == 2 {
@ -69,33 +187,75 @@ func main() {
for p, s := range peers {
fmt.Printf("Name: %v Status: %v\n", p, s)
}
case "servers":
servers := app.Peer.GetServers()
for s, st := range servers {
fmt.Printf("Name: %v Status: %v\n", s, st)
}
case "contacts":
for _, c := range app.Peer.Profile.Contacts {
fmt.Printf("Name: %v, Onion: %v, Trusted: %v\n", c.Name, c.Onion, c.Trusted)
}
case "groups":
for gid, g := range app.Peer.Profile.Groups {
fmt.Printf("Group Id: %v, Owner: %v\n", gid, g.Owner)
fmt.Printf("Group Id: %v, Owner: %v Accepted:%v \n", gid, g.Owner, g.Accepted)
}
case "invitetogroup":
case "accept-invite":
if len(commands) == 2 {
groupID:= commands[1]
err := app.Peer.AcceptInvite(groupID)
if err == nil {
fmt.Printf("Error: %v\n", err)
}
} else {
fmt.Printf("Error accepting invite, usage: accept-invite [groupid]\n")
}
case "invite-to-group":
if len(commands) == 3 {
fmt.Printf("Inviting %v to %v\n", commands[1], commands[2])
err := app.Peer.InviteOnionToGroup(commands[1], commands[2])
if err == nil {
fmt.Printf("Error: %v", err)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
} else {
fmt.Printf("Error inviting peer to group, usage: invitetogroup [onion] [groupid]\n")
}
case "newgroup":
case "new-group":
if len(commands) == 2 {
fmt.Printf("Setting up a new group on server:%v\n", commands[1])
id, _ := app.Peer.Profile.StartGroup(commands[1])
fmt.Printf("New Group [%v] created for server %v", id, commands[1])
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
app.Peer.Save(profilefile)
} else {
fmt.Printf("Error inviting peer, usage: newgroup [server]\n")
}
case "send":
if len(commands) > 2 {
message := strings.Join(commands[2:], " ")
err := app.Peer.SendMessageToGroup(commands[1], message)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
} else {
fmt.Printf("Error sending message to group, usage: send [groupid] [message]\n")
}
case "timeline":
if len(commands) == 2 {
group := app.Peer.Profile.GetGroupByGroupId(commands[1])
if group == nil {
fmt.Printf("Error: group does not exist\n")
} else {
for _, m := range group.Timeline.Messages {
verified := "not-verified"
if m.Verified {
verified = "verified"
}
fmt.Printf("%v %v: %v [%s]\n", m.Timestamp, m.PeerID, m.Message, verified)
}
}
} else {
fmt.Printf("Error reading timeline from group, usage: timeline [groupid]\n")
}
case "save":
app.Peer.Save(profilefile)
}

View File

@ -58,6 +58,14 @@ func (t Timeline) Less(i, j int) bool {
func (t *Timeline) Insert(mi *Message) {
t.lock.Lock()
for _,m := range t.Messages {
if compareSignatures(m.Signature, mi.Signature) {
t.lock.Unlock()
return
}
}
t.Messages = append(t.Messages, *mi)
sort.Sort(t)
t.lock.Unlock()

View File

@ -81,6 +81,12 @@ func (p *Profile) AddContact(onion string, profile PublicProfile) {
// VerifyMessage confirms the authenticity of a message given an onion, message and signature.
func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, timestamp int32, signature []byte) bool {
if onion == p.Onion {
m := message + groupID + strconv.Itoa(int(timestamp))
return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature)
}
contact, found := p.Contacts[onion]
if found {
m := message + groupID + strconv.Itoa(int(timestamp))

View File

@ -22,19 +22,28 @@ func NewConnectionsManager() *Manager {
func (m *Manager) ManagePeerConnection(host string, profile *model.Profile) {
m.lock.Lock()
ppc := NewPeerPeerConnection(host, profile)
go ppc.Run()
m.peerConnections[host] = ppc
_,exists := m.peerConnections[host]
if !exists {
ppc := NewPeerPeerConnection(host, profile)
go ppc.Run()
m.peerConnections[host] = ppc
}
m.lock.Unlock()
}
func (m *Manager) ManageServerConnection(host string, handler func(string, *protocol.GroupMessage)) {
m.lock.Lock()
psc := NewPeerServerConnection(host)
go psc.Run()
psc.GroupMessageHandler = handler
m.serverConnections[host] = psc
_,exists := m.serverConnections[host]
if !exists {
psc := NewPeerServerConnection(host)
go psc.Run()
psc.GroupMessageHandler = handler
m.serverConnections[host] = psc
}
m.lock.Unlock()
}
@ -48,6 +57,17 @@ func (m *Manager) GetPeers() map[string]ConnectionState {
return rm
}
func (m *Manager) GetServers() map[string]ConnectionState {
rm := make(map[string]ConnectionState)
m.lock.Lock()
for onion, psc := range m.serverConnections {
rm[onion] = psc.GetState()
}
m.lock.Unlock()
return rm
}
func (m *Manager) GetPeerPeerConnectionForOnion(host string) (ppc *PeerPeerConnection) {
m.lock.Lock()
ppc = m.peerConnections[host]

View File

@ -46,6 +46,11 @@ func (cp *CwtchPeer) setup() {
cp.PeerWithOnion(onion)
}
for _,group := range cp.Profile.Groups {
if group.Accepted || group.Owner == "self" {
cp.JoinServer(group.GroupServer)
}
}
}
func NewCwtchPeer(name string) *CwtchPeer {
@ -87,6 +92,7 @@ func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
ppc := cp.connectionsManager.GetPeerPeerConnectionForOnion(onion)
fmt.Printf("Got connection for group: %v - Sending Invite\n", ppc)
ppc.SendGroupInvite(invite)
return nil
}
return errors.New("group id could not be found")
}
@ -99,20 +105,46 @@ func (cp *CwtchPeer) JoinServer(onion string) {
cp.connectionsManager.ManageServerConnection(onion, cp.ReceiveGroupMessage)
}
func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) {
func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) error {
group := cp.Profile.GetGroupByGroupId(groupid)
if group == nil {
return errors.New("group does not exit")
}
psc := cp.connectionsManager.GetPeerServerConnectionForOnion(group.GroupServer)
ct := cp.Profile.EncryptMessageToGroup(message, groupid)
gm := &protocol.GroupMessage{
Ciphertext: ct,
}
psc.SendGroupMessage(gm)
return nil
}
func (cp *CwtchPeer) GetPeers() map[string]connections.ConnectionState {
return cp.connectionsManager.GetPeers()
}
func (cp *CwtchPeer) GetServers() map[string]connections.ConnectionState {
return cp.connectionsManager.GetServers()
}
func (cp *CwtchPeer) AcceptInvite(groupID string) error {
g := cp.Profile.GetGroupByGroupId(groupID)
if g == nil {
return errors.New("group invite does not exit")
}
g.Accepted = true
return nil
}
func (cp *CwtchPeer) RejectInvite(groupID string) error {
g := cp.Profile.GetGroupByGroupId(groupID)
if g == nil {
return errors.New("group invite does not exit")
}
g.Accepted = false
return nil
}
func (cp *CwtchPeer) Listen() error {
cwtchpeer := new(application.RicochetApplication)
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", cp.Profile.OnionPrivateKey, 9878)