diff --git a/event/common.go b/event/common.go index e67d484..aa3910d 100644 --- a/event/common.go +++ b/event/common.go @@ -46,6 +46,10 @@ const ( // Imported NewGroupInvite = Type("NewGroupInvite") + // Inform the UI about a new group + // GroupID: groupID (allows them to fetch from the peer) + NewGroup = Type("NewGroup") + // GroupID AcceptGroupInvite = Type("AcceptGroupInvite") diff --git a/model/group.go b/model/group.go index 8583b3b..04d04e8 100644 --- a/model/group.go +++ b/model/group.go @@ -2,6 +2,7 @@ package model import ( "crypto/rand" + "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/protocol/groups" "encoding/json" "errors" @@ -64,6 +65,9 @@ func NewGroup(server string) (*Group, error) { copy(group.GroupKey[:], groupKey[:]) group.Owner = "self" group.Attributes = make(map[string]string) + // By default we set the "name" of the group to a random string, we can override this later, but to simplify the + // codes around invite, we assume that this is always set. + group.Attributes[attr.GetLocalScope("name")] = group.GroupID return group, nil } @@ -95,7 +99,8 @@ func (g *Group) Invite(initialMessage []byte) ([]byte, error) { g.InitialMessage = initialMessage[:] gci := &groups.GroupInvite{ - GroupName: g.GroupID, + GroupID: g.GroupID, + GroupName: g.Attributes[attr.GetLocalScope("name")], SharedKey: g.GroupKey[:], ServerHost: g.GroupServer, SignedGroupID: g.SignedGroupID[:], diff --git a/model/profile.go b/model/profile.go index a59f80a..983644c 100644 --- a/model/profile.go +++ b/model/profile.go @@ -361,13 +361,13 @@ func (p *Profile) GetGroup(groupID string) (g *Group) { } // 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, string, error) { var gci groups.GroupInvite err := json.Unmarshal([]byte(invite), &gci) if err == nil { group := new(Group) group.Version = CurrentGroupVersion - group.GroupID = gci.GroupName + group.GroupID = gci.GroupID group.LocalID = GenerateRandomID() group.SignedGroupID = gci.SignedGroupID copy(group.GroupKey[:], gci.SharedKey[:]) @@ -377,9 +377,9 @@ func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, err group.Owner = peerHostname group.Attributes = make(map[string]string) p.AddGroup(group) - return group.GroupID, nil + return group.GroupID, gci.GroupName, nil } - return "", err + return "", "", err } // AddGroup is a convenience method for adding a group to a profile. diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 614f6e5..4e3ad37 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -55,7 +55,7 @@ type CwtchPeer interface { StoreMessage(onion string, messageTxt string, sent time.Time) SetContactAuthorization(string, model.Authorization) error - ProcessInvite(string, string) (string, error) + ProcessInvite(string, string) (string, string, error) AcceptInvite(string) error RejectInvite(string) DeleteContact(string) @@ -63,6 +63,7 @@ type CwtchPeer interface { AddServer(string) error JoinServer(string) error + GetServers() []string SendMessageToGroupTracked(string, string) (string, error) GetName() string @@ -294,6 +295,18 @@ func (cp *cwtchPeer) GetContacts() []string { return cp.Profile.GetContacts() } +// GetServers returns an unordered list of servers +func (cp *cwtchPeer) GetServers() []string { + contacts := cp.Profile.GetContacts() + var servers []string + for _, contact := range contacts { + if cp.GetContact(contact).IsServer() { + servers = append(servers, contact) + } + } + return servers +} + // GetContact returns a given contact, nil is no such contact exists func (cp *cwtchPeer) GetContact(onion string) *model.PublicProfile { cp.mutex.Lock() @@ -440,7 +453,7 @@ func (cp *cwtchPeer) SetContactAuthorization(peer string, authorization model.Au } // ProcessInvite adds a new group invite to the profile. returns the new group ID -func (cp *cwtchPeer) ProcessInvite(invite string, remotePeer string) (string, error) { +func (cp *cwtchPeer) ProcessInvite(invite string, remotePeer string) (string, string, error) { cp.mutex.Lock() defer cp.mutex.Unlock() return cp.Profile.ProcessInvite(invite, remotePeer) @@ -656,17 +669,19 @@ func (cp *cwtchPeer) eventHandler() { } case event.NewGroupInvite: cp.mutex.Lock() - group, err := cp.Profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) + group, groupName, err := cp.Profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) if err == nil { if ev.Data[event.Imported] == "true" { cp.Profile.GetGroup(group).Accepted = true cp.mutex.Unlock() // TODO...seriously need a better way of handling these cases + cp.SetGroupAttribute(group, attr.GetLocalScope("name"), groupName) err = cp.JoinServer(cp.Profile.GetGroup(group).GroupServer) cp.mutex.Lock() if err != nil { log.Errorf("Joining Server should have worked %v", err) } } + cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.GroupID: group})) } cp.mutex.Unlock() case event.PeerStateChange: diff --git a/protocol/fuzzing/groups/fuzz.go b/protocol/fuzzing/groups/fuzz.go index ab25d82..df0082b 100644 --- a/protocol/fuzzing/groups/fuzz.go +++ b/protocol/fuzzing/groups/fuzz.go @@ -10,7 +10,7 @@ import ( // Fuzz various group related functions func Fuzz(data []byte) int { profile := model.GenerateNewProfile("fuzz") - inviteid, err := profile.ProcessInvite(string(data), profile.Onion) + inviteid, _, err := profile.ProcessInvite(string(data), profile.Onion) if err != nil { if inviteid != "" { diff --git a/protocol/groups/common.go b/protocol/groups/common.go index ddc4a9e..bb52ee0 100644 --- a/protocol/groups/common.go +++ b/protocol/groups/common.go @@ -11,6 +11,7 @@ const CwtchServerSyncedCapability = tapir.Capability("CwtchServerSyncedCapabilit // GroupInvite provides a structured type for communicating group information to peers type GroupInvite struct { + GroupID string GroupName string SignedGroupID []byte Timestamp uint64 diff --git a/server/app/main.go b/server/app/main.go index 1fee860..01fdeee 100644 --- a/server/app/main.go +++ b/server/app/main.go @@ -82,4 +82,7 @@ func main() { bundle := server.KeyBundle().Serialize() log.Infof("Server Config: server:%s", base64.StdEncoding.EncodeToString(bundle)) server.Run(acn) + for { + time.Sleep(time.Second) + } } diff --git a/server/server.go b/server/server.go index db3bf77..524995c 100644 --- a/server/server.go +++ b/server/server.go @@ -32,7 +32,7 @@ type Server struct { onionServiceStopped bool running bool existingMessageCount int - lock sync.RWMutex + lock sync.RWMutex } // Setup initialized a server from a given configuration diff --git a/storage/v0/profile_store.go b/storage/v0/profile_store.go index e7bb165..f1bbe0b 100644 --- a/storage/v0/profile_store.go +++ b/storage/v0/profile_store.go @@ -52,9 +52,9 @@ func ReadProfile(directory, password string) (*model.Profile, error) { /********************************************************************************************/ -// AddGroup For testing, adds a group to the profile (and startsa stream store) +// AddGroup For testing, adds a group to the profile (and starts a stream store) func (ps *ProfileStoreV0) AddGroup(invite []byte, peer string) { - gid, err := ps.profile.ProcessInvite(string(invite), peer) + gid, _, err := ps.profile.ProcessInvite(string(invite), peer) if err == nil { ps.save() group := ps.profile.Groups[gid] diff --git a/storage/v1/profile_store.go b/storage/v1/profile_store.go index 709ffe5..dab50fd 100644 --- a/storage/v1/profile_store.go +++ b/storage/v1/profile_store.go @@ -373,7 +373,7 @@ func (ps *ProfileStoreV1) eventHandler() { log.Errorf("error accepting group invite") } case event.NewGroupInvite: - gid, err := ps.profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) + gid, _, err := ps.profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) if err == nil { ps.save() group := ps.profile.Groups[gid]