libcwtch-go first cut integration / message timelines etc
continuous-integration/drone/push Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2021-11-17 14:34:13 -08:00
parent 5c47dd789a
commit e296c30818
15 changed files with 331 additions and 166 deletions

View File

@ -126,8 +126,7 @@ const (
// a peer contact has been added // a peer contact has been added
// attributes: // attributes:
// RemotePeer [eg ""] // RemotePeer [eg ""]
// Authorization ContactCreated = Type("ContactCreated")
PeerCreated = Type("PeerCreated")
// Password, NewPassword // Password, NewPassword
ChangePassword = Type("ChangePassword") ChangePassword = Type("ChangePassword")
@ -273,12 +272,12 @@ const (
Identity = Field("Identity") Identity = Field("Identity")
GroupConversationID = Field("GroupConversationID") ConversationID = Field("ConversationID")
GroupID = Field("GroupID") GroupID = Field("GroupID")
GroupServer = Field("GroupServer") GroupServer = Field("GroupServer")
ServerTokenY = Field("ServerTokenY") ServerTokenY = Field("ServerTokenY")
ServerTokenOnion = Field("ServerTokenOnion") ServerTokenOnion = Field("ServerTokenOnion")
GroupInvite = Field("GroupInvite") GroupInvite = Field("GroupInvite")
ProfileName = Field("ProfileName") ProfileName = Field("ProfileName")
Password = Field("Password") Password = Field("Password")

View File

@ -40,7 +40,7 @@ type OverlayMessage struct {
// DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process // DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process
// to downloadFilePath // to downloadFilePath
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, handle string, downloadFilePath string, manifestFilePath string, key string) { func (f *Functionality) DownloadFile(profile peer.CwtchPeer, conversationID int, downloadFilePath string, manifestFilePath string, key string) {
// Store local.filesharing.filekey.manifest as the location of the manifest // Store local.filesharing.filekey.manifest as the location of the manifest
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key), manifestFilePath) profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key), manifestFilePath)
@ -49,7 +49,7 @@ func (f *Functionality) DownloadFile(profile peer.CwtchPeer, handle string, down
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, key, downloadFilePath) profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, key, downloadFilePath)
// Get the value of conversation.filesharing.filekey.manifest.size from `handle` // Get the value of conversation.filesharing.filekey.manifest.size from `handle`
profile.SendScopedZonedGetValToContact(handle, attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key)) profile.SendScopedZonedGetValToContact(conversationID, attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key))
} }
// ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file // ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file

View File

@ -35,3 +35,6 @@ const AttrAck = "ack"
// AttrErr - conversation attribute for errored status // AttrErr - conversation attribute for errored status
const AttrErr = "error" const AttrErr = "error"
// AttrSentTimestamp - conversation attribute for the time the message was (nominally) sent
const AttrSentTimestamp = "sent"

View File

@ -1,6 +1,10 @@
package model package model
import "encoding/json" import (
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/model/constants"
"encoding/json"
)
// AccessControl is a type determining client assigned authorization to a peer // AccessControl is a type determining client assigned authorization to a peer
type AccessControl struct { type AccessControl struct {
@ -57,3 +61,32 @@ type Conversation struct {
ACL AccessControlList ACL AccessControlList
Accepted bool Accepted bool
} }
func (ci *Conversation) GetAttribute(scope attr.Scope, zone attr.Zone, key string) (string, bool) {
if value, exists := ci.Attributes[scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)).ToString()]; exists {
return value, true
}
return "", false
}
func (ci *Conversation) IsGroup() bool {
if _, exists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]; exists {
return true
}
return false
}
func (ci *Conversation) IsServer() bool {
if _, exists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(BundleType))).ToString()]; exists {
return true
}
return false
}
type ConversationMessage struct {
ID int
Body string
Attr Attributes
Signature string
ContentHash string
}

View File

@ -30,6 +30,7 @@ const GroupInvitePrefix = "torv3"
type Group struct { type Group struct {
// GroupID is now derived from the GroupKey and the GroupServer // GroupID is now derived from the GroupKey and the GroupServer
GroupID string GroupID string
GroupName string
GroupKey [32]byte GroupKey [32]byte
GroupServer string GroupServer string
Version int Version int
@ -73,11 +74,11 @@ func deriveGroupID(groupKey []byte, serverHostname string) string {
} }
// Invite generates a invitation that can be sent to a cwtch peer // Invite generates a invitation that can be sent to a cwtch peer
func (g *Group) Invite(name string) (string, error) { func (g *Group) Invite() (string, error) {
gci := &groups.GroupInvite{ gci := &groups.GroupInvite{
GroupID: g.GroupID, GroupID: g.GroupID,
GroupName: name, GroupName: g.GroupName,
SharedKey: g.GroupKey[:], SharedKey: g.GroupKey[:],
ServerHost: g.GroupServer, ServerHost: g.GroupServer,
} }

View File

@ -19,7 +19,7 @@ func TestGroup(t *testing.T) {
Padding: []byte{}, Padding: []byte{},
} }
invite, err := g.Invite("name") invite, err := g.Invite()
if err != nil { if err != nil {
t.Fatalf("error creating group invite: %v", err) t.Fatalf("error creating group invite: %v", err)
@ -64,7 +64,7 @@ func TestGroupValidation(t *testing.T) {
Version: 0, Version: 0,
} }
invite, _ := group.Invite("name") invite, _ := group.Invite()
_, err := ValidateInvite(invite) _, err := ValidateInvite(invite)
if err == nil { if err == nil {
@ -75,7 +75,7 @@ func TestGroupValidation(t *testing.T) {
// Generate a valid group but replace the group server... // Generate a valid group but replace the group server...
group, _ = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") group, _ = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
group.GroupServer = "tcnkoch4nyr3cldkemejtkpqok342rbql6iclnjjs3ndgnjgufzyxvqd" group.GroupServer = "tcnkoch4nyr3cldkemejtkpqok342rbql6iclnjjs3ndgnjgufzyxvqd"
invite, _ = group.Invite("name") invite, _ = group.Invite()
_, err = ValidateInvite(invite) _, err = ValidateInvite(invite)
if err == nil { if err == nil {
@ -86,7 +86,7 @@ func TestGroupValidation(t *testing.T) {
// Generate a valid group but replace the group key... // Generate a valid group but replace the group key...
group, _ = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") group, _ = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
group.GroupKey = sha256.Sum256([]byte{}) group.GroupKey = sha256.Sum256([]byte{})
invite, _ = group.Invite("name") invite, _ = group.Invite()
_, err = ValidateInvite(invite) _, err = ValidateInvite(invite)
if err == nil { if err == nil {

View File

@ -10,6 +10,7 @@ import (
"git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/cwtch.im/tapir/primitives"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"io" "io"
"path/filepath"
"sync" "sync"
"time" "time"
) )
@ -63,6 +64,13 @@ func getRandomness(arr *[]byte) {
} }
} }
// GenerateRandomID generates a random 16 byte hex id code
func GenerateRandomID() string {
randBytes := make([]byte, 16)
rand.Read(randBytes)
return filepath.Join(hex.EncodeToString(randBytes))
}
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and // EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
// profile // profile
func EncryptMessageToGroup(message string, author primitives.Identity, group *Group) ([]byte, []byte, *groups.DecryptedGroupMessage, error) { func EncryptMessageToGroup(message string, author primitives.Identity, group *Group) ([]byte, []byte, *groups.DecryptedGroupMessage, error) {

View File

@ -98,9 +98,14 @@ func (cp *cwtchPeer) GenerateProtocolEngine(acn connectivity.ACN, bus event.Mana
// SendScopedZonedGetValToContact // SendScopedZonedGetValToContact
// Status: No change in 1.5 // Status: No change in 1.5
func (cp *cwtchPeer) SendScopedZonedGetValToContact(handle string, scope attr.Scope, zone attr.Zone, path string) { func (cp *cwtchPeer) SendScopedZonedGetValToContact(conversationID int, scope attr.Scope, zone attr.Zone, path string) {
ev := event.NewEventList(event.SendGetValMessageToPeer, event.RemotePeer, handle, event.Scope, string(scope), event.Path, string(zone.ConstructZonedPath(path))) ci, err := cp.GetConversationInfo(conversationID)
cp.eventBus.Publish(ev) if err == nil {
ev := event.NewEventList(event.SendGetValMessageToPeer, event.RemotePeer, ci.Handle, event.Scope, string(scope), event.Path, string(zone.ConstructZonedPath(path)))
cp.eventBus.Publish(ev)
} else {
log.Errorf("Error sending scoped zone to contact %v %v", conversationID, err)
}
} }
// GetScopedZonedAttribute // GetScopedZonedAttribute
@ -151,11 +156,11 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) error {
if conversationInfo != nil && err == nil { if conversationInfo != nil && err == nil {
if tor.IsValidHostname(conversationInfo.Handle) { if tor.IsValidHostname(conversationInfo.Handle) {
ev := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.RemotePeer: conversationInfo.Handle, event.Data: message}) ev := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationInfo.ID), event.RemotePeer: conversationInfo.Handle, event.Data: message})
onion, _ := cp.storage.LoadProfileKeyValue(TypeAttribute, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Onion)).ToString()) onion, _ := cp.storage.LoadProfileKeyValue(TypeAttribute, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Onion)).ToString())
// For p2p messages we store the event id of the message as the "signature" we can then look this up in the database later for acks // For p2p messages we store the event id of the message as the "signature" we can then look this up in the database later for acks
err := cp.storage.InsertMessage(conversationInfo.ID, 0, message, model.Attributes{"ack": event.False, "sent": time.Now().String()}, ev.EventID, model.CalculateContentHash(string(onion), message)) err := cp.storage.InsertMessage(conversationInfo.ID, 0, message, model.Attributes{constants.AttrAck: event.False, constants.AttrSentTimestamp: time.Now().Format(time.RFC3339Nano)}, ev.EventID, model.CalculateContentHash(string(onion), message))
if err != nil { if err != nil {
return err return err
} }
@ -187,10 +192,13 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) error {
} }
// Insert the Group Message // Insert the Group Message
cp.storage.InsertMessage(conversationInfo.ID, 0, dm.Text, model.Attributes{constants.AttrAck: constants.False, "PreviousSignature": base64.StdEncoding.EncodeToString(dm.PreviousMessageSig), "Author": dm.Onion, "Sent": strconv.Itoa(int(dm.Timestamp))}, base64.StdEncoding.EncodeToString(sig), model.CalculateContentHash(dm.Onion, dm.Text)) err = cp.storage.InsertMessage(conversationInfo.ID, 0, dm.Text, model.Attributes{constants.AttrAck: constants.False, "PreviousSignature": base64.StdEncoding.EncodeToString(dm.PreviousMessageSig), "Author": dm.Onion, constants.AttrSentTimestamp: strconv.Itoa(int(dm.Timestamp))}, base64.StdEncoding.EncodeToString(sig), model.CalculateContentHash(dm.Onion, dm.Text))
if err == nil {
ev := event.NewEvent(event.SendMessageToGroup, map[event.Field]string{event.GroupID: conversationInfo.Handle, event.GroupServer: group.GroupServer, event.Ciphertext: base64.StdEncoding.EncodeToString(ct), event.Signature: base64.StdEncoding.EncodeToString(sig)}) ev := event.NewEvent(event.SendMessageToGroup, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationInfo.ID), event.GroupID: conversationInfo.Handle, event.GroupServer: group.GroupServer, event.Ciphertext: base64.StdEncoding.EncodeToString(ct), event.Signature: base64.StdEncoding.EncodeToString(sig)})
cp.eventBus.Publish(ev) cp.eventBus.Publish(ev)
} else {
return err
}
} }
return nil return nil
} }
@ -355,7 +363,7 @@ func (cp *cwtchPeer) ImportGroup(exportedInvite string) (int, error) {
cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)), gci.ServerHost) cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)), gci.ServerHost)
cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupKey)), base64.StdEncoding.EncodeToString(gci.SharedKey)) cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupKey)), base64.StdEncoding.EncodeToString(gci.SharedKey))
cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.Name)), gci.GroupName) cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.Name)), gci.GroupName)
cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.GroupConversationID: strconv.Itoa(groupConversationID), event.GroupServer: gci.ServerHost, event.GroupInvite: exportedInvite})) cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.ConversationID: strconv.Itoa(groupConversationID), event.GroupServer: gci.ServerHost, event.GroupInvite: exportedInvite}))
} }
return groupConversationID, err return groupConversationID, err
} }
@ -364,7 +372,9 @@ func (cp *cwtchPeer) ImportGroup(exportedInvite string) (int, error) {
func (cp *cwtchPeer) NewContactConversation(handle string, acl model.AccessControl, accepted bool) (int, error) { func (cp *cwtchPeer) NewContactConversation(handle string, acl model.AccessControl, accepted bool) (int, error) {
cp.mutex.Lock() cp.mutex.Lock()
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
return cp.storage.NewConversation(handle, model.Attributes{event.SaveHistoryKey: event.DeleteHistoryDefault}, model.AccessControlList{handle: acl}, accepted) conversationID, err := cp.storage.NewConversation(handle, model.Attributes{event.SaveHistoryKey: event.DeleteHistoryDefault}, model.AccessControlList{handle: acl}, accepted)
cp.eventBus.Publish(event.NewEvent(event.ContactCreated, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationID), event.RemotePeer: handle}))
return conversationID, err
} }
// AcceptConversation looks up a conversation by `handle` and sets the Accepted status to `true` // AcceptConversation looks up a conversation by `handle` and sets the Accepted status to `true`
@ -375,6 +385,23 @@ func (cp *cwtchPeer) AcceptConversation(id int) error {
return cp.storage.AcceptConversation(id) return cp.storage.AcceptConversation(id)
} }
// BlockConversation looks up a conversation by `handle` and sets the Accepted status to `true`
// This will cause Cwtch to auto connect to this conversation on start up
func (cp *cwtchPeer) BlockConversation(id int) error {
cp.mutex.Lock()
defer cp.mutex.Unlock()
ci, err := cp.storage.GetConversation(id)
if err != nil {
return err
}
// p2p conversations have a single ACL referencing the remote peer. Set this to blocked...
ci.ACL[ci.Handle] = model.AccessControl{Blocked: true, Read: false, Append: false}
// Send an event in any case to block the protocol engine...
// TODO at some point in the future engine needs to understand ACLs not just legacy auth status
cp.eventBus.Publish(event.NewEvent(event.SetPeerAuthorization, map[event.Field]string{event.RemotePeer: ci.Handle, event.Authorization: string(model.AuthBlocked)}))
return cp.storage.SetConversationACL(id, ci.ACL)
}
func (cp *cwtchPeer) FetchConversations() ([]*model.Conversation, error) { func (cp *cwtchPeer) FetchConversations() ([]*model.Conversation, error) {
cp.mutex.Lock() cp.mutex.Lock()
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
@ -424,12 +451,29 @@ func (cp *cwtchPeer) GetConversationAttribute(id int, path attr.ScopedZonedPath)
return val, nil return val, nil
} }
// GetChannelMessage returns a message from a conversation channel referenced by the absolute ID.
// Note: This should note be used to index a list as the ID is not expected to be tied to absolute position
// in the table (e.g. deleted messages, expired messages, etc.)
func (cp *cwtchPeer) GetChannelMessage(conversation int, channel int, id int) (string, model.Attributes, error) { func (cp *cwtchPeer) GetChannelMessage(conversation int, channel int, id int) (string, model.Attributes, error) {
cp.mutex.Lock() cp.mutex.Lock()
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
return cp.storage.GetChannelMessage(conversation, channel, id) return cp.storage.GetChannelMessage(conversation, channel, id)
} }
// GetChannelMessageCount returns the absolute number of messages in a given conversation channel
func (cp *cwtchPeer) GetChannelMessageCount(conversation int, channel int) (int, error) {
cp.mutex.Lock()
defer cp.mutex.Unlock()
return cp.storage.GetChannelMessageCount(conversation, channel)
}
// GetMostRecentMessages returns a selection of messages, ordered by most recently inserted
func (cp *cwtchPeer) GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error) {
cp.mutex.Lock()
defer cp.mutex.Unlock()
return cp.storage.GetMostRecentMessages(conversation, channel, offset, limit)
}
// StartGroup create a new group linked to the given server and returns the group ID, an invite or an error. // StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
// Status: TODO change server handle to conversation id...? // Status: TODO change server handle to conversation id...?
func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) { func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) {
@ -445,8 +489,9 @@ func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) {
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.Name)), name) cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.Name)), name)
cp.eventBus.Publish(event.NewEvent(event.GroupCreated, map[event.Field]string{ cp.eventBus.Publish(event.NewEvent(event.GroupCreated, map[event.Field]string{
event.GroupID: group.GroupID, event.ConversationID: strconv.Itoa(conversationID),
event.GroupServer: group.GroupServer, event.GroupID: group.GroupID,
event.GroupServer: group.GroupServer,
})) }))
return conversationID, nil return conversationID, nil
} }
@ -480,7 +525,10 @@ func (cp *cwtchPeer) AddServer(serverSpecification string) error {
// Add the contact if we don't already have it // Add the contact if we don't already have it
conversationInfo, _ := cp.FetchConversationInfo(onion) conversationInfo, _ := cp.FetchConversationInfo(onion)
if conversationInfo == nil { if conversationInfo == nil {
cp.NewContactConversation(onion, model.DefaultP2PAccessControl(), true) _, err := cp.NewContactConversation(onion, model.DefaultP2PAccessControl(), true)
if err != nil {
return err
}
} }
conversationInfo, err = cp.FetchConversationInfo(onion) conversationInfo, err = cp.FetchConversationInfo(onion)
@ -530,13 +578,13 @@ func (cp *cwtchPeer) GetOnion() string {
// GetPeerState // GetPeerState
// Status: Ready for 1.5 // Status: Ready for 1.5
func (cp *cwtchPeer) GetPeerState(handle string) (connections.ConnectionState, bool) { func (cp *cwtchPeer) GetPeerState(handle string) connections.ConnectionState {
cp.mutex.Lock() cp.mutex.Lock()
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
if state, ok := cp.state[handle]; ok { if state, ok := cp.state[handle]; ok {
return state, ok return state
} }
return connections.DISCONNECTED, false return connections.DISCONNECTED
} }
// PeerWithOnion initiates a request to the Protocol Engine to set up Cwtch Session with a given tor v3 onion // PeerWithOnion initiates a request to the Protocol Engine to set up Cwtch Session with a given tor v3 onion
@ -593,11 +641,12 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa
group := model.Group{ group := model.Group{
GroupID: groupID, GroupID: groupID,
GroupName: groupName,
GroupKey: groupKeyFixed, GroupKey: groupKeyFixed,
GroupServer: groupServer, GroupServer: groupServer,
} }
groupInvite, err := group.Invite(groupName) groupInvite, err := group.Invite()
if err != nil { if err != nil {
return errors.New("group invite is malformed") return errors.New("group invite is malformed")
} }
@ -773,7 +822,7 @@ func (cp *cwtchPeer) storeMessage(handle string, message string, sent time.Time)
// Generate a random number and use it as the signature // Generate a random number and use it as the signature
signature := event.GetRandNumber().String() signature := event.GetRandNumber().String()
return cp.storage.InsertMessage(ci.ID, 0, message, model.Attributes{constants.AttrAck: event.True, "sent": sent.String()}, signature, model.CalculateContentHash(handle, message)) return cp.storage.InsertMessage(ci.ID, 0, message, model.Attributes{constants.AttrAck: event.True, constants.AttrSentTimestamp: sent.Format(time.RFC3339Nano)}, signature, model.CalculateContentHash(handle, message))
} }
// ShareFile begins hosting the given serialized manifest // ShareFile begins hosting the given serialized manifest
@ -870,7 +919,7 @@ func (cp *cwtchPeer) eventHandler() {
val, exists = cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name) val, exists = cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
} }
resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Exists: strconv.FormatBool(exists)}) resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationInfo.ID), event.RemotePeer: onion, event.Exists: strconv.FormatBool(exists)})
resp.EventID = ev.EventID resp.EventID = ev.EventID
if exists { if exists {
resp.Data[event.Data] = val resp.Data[event.Data] = val
@ -998,14 +1047,14 @@ func (cp *cwtchPeer) attemptInsertOrAcknowledgeLegacyGroupConversation(conversat
attr[constants.AttrAck] = constants.True attr[constants.AttrAck] = constants.True
cp.storage.UpdateMessageAttributes(conversationID, 0, messageID, attr) cp.storage.UpdateMessageAttributes(conversationID, 0, messageID, attr)
cp.mutex.Unlock() cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(event.IndexedAcknowledgement, map[event.Field]string{event.GroupConversationID: strconv.Itoa(conversationID), event.Index: strconv.Itoa(messageID)})) cp.eventBus.Publish(event.NewEvent(event.IndexedAcknowledgement, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationID), event.Index: strconv.Itoa(messageID)}))
return nil return nil
} }
} else { } else {
cp.mutex.Lock() cp.mutex.Lock()
cp.storage.InsertMessage(conversationID, 0, dm.Text, model.Attributes{constants.AttrAck: constants.True, "PreviousSignature": base64.StdEncoding.EncodeToString(dm.PreviousMessageSig), "Author": dm.Onion, "Sent": strconv.Itoa(int(dm.Timestamp))}, signature, model.CalculateContentHash(dm.Onion, dm.Text)) cp.storage.InsertMessage(conversationID, 0, dm.Text, model.Attributes{constants.AttrAck: constants.True, "PreviousSignature": base64.StdEncoding.EncodeToString(dm.PreviousMessageSig), "Author": dm.Onion, constants.AttrSentTimestamp: strconv.Itoa(int(dm.Timestamp))}, signature, model.CalculateContentHash(dm.Onion, dm.Text))
cp.mutex.Unlock() cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{event.GroupConversationID: strconv.Itoa(conversationID), event.Index: strconv.Itoa(messageID)})) cp.eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationID), event.Index: strconv.Itoa(messageID)}))
return nil return nil
} }
return err return err
@ -1027,7 +1076,7 @@ func (cp *cwtchPeer) attemptAcknowledgeP2PConversation(handle string, signature
attr[constants.AttrAck] = constants.True attr[constants.AttrAck] = constants.True
cp.storage.UpdateMessageAttributes(ci.ID, 0, id, attr) cp.storage.UpdateMessageAttributes(ci.ID, 0, id, attr)
cp.mutex.Unlock() cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(event.IndexedAcknowledgement, map[event.Field]string{event.RemotePeer: handle, event.Index: strconv.Itoa(id)})) cp.eventBus.Publish(event.NewEvent(event.IndexedAcknowledgement, map[event.Field]string{event.ConversationID: strconv.Itoa(ci.ID), event.RemotePeer: handle, event.Index: strconv.Itoa(id)}))
return nil return nil
} }
return err return err
@ -1053,7 +1102,7 @@ func (cp *cwtchPeer) attemptErrorConversationMessage(handle string, signature st
attr[constants.AttrErr] = constants.True attr[constants.AttrErr] = constants.True
cp.storage.UpdateMessageAttributes(ci.ID, 0, id, attr) cp.storage.UpdateMessageAttributes(ci.ID, 0, id, attr)
cp.mutex.Unlock() cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(eventType, map[event.Field]string{event.RemotePeer: handle, event.Error: error, event.Index: strconv.Itoa(id)})) cp.eventBus.Publish(event.NewEvent(eventType, map[event.Field]string{event.ConversationID: strconv.Itoa(ci.ID), event.RemotePeer: handle, event.Error: error, event.Index: strconv.Itoa(id)}))
return nil return nil
} }
return err return err

View File

@ -41,11 +41,14 @@ type CwtchProfileStorage struct {
acceptConversationStmt *sql.Stmt acceptConversationStmt *sql.Stmt
deleteConversationStmt *sql.Stmt deleteConversationStmt *sql.Stmt
setConversationAttributesStmt *sql.Stmt setConversationAttributesStmt *sql.Stmt
setConversationACLStmt *sql.Stmt
channelInsertStmts map[ChannelID]*sql.Stmt channelInsertStmts map[ChannelID]*sql.Stmt
channelUpdateMessageStmts map[ChannelID]*sql.Stmt channelUpdateMessageStmts map[ChannelID]*sql.Stmt
channelGetMessageStmts map[ChannelID]*sql.Stmt channelGetMessageStmts map[ChannelID]*sql.Stmt
channelGetMessageBySignatureStmts map[ChannelID]*sql.Stmt channelGetMessageBySignatureStmts map[ChannelID]*sql.Stmt
channelGetCountStmts map[ChannelID]*sql.Stmt
channelGetMostRecentMessagesStmts map[ChannelID]*sql.Stmt
db *sql.DB db *sql.DB
} }
@ -65,6 +68,7 @@ const selectConversationSQLStmt = `select ID, Handle, Attributes, ACL, Accepted
const selectConversationByHandleSQLStmt = `select ID, Handle, Attributes, ACL, Accepted from conversations where Handle=(?);` const selectConversationByHandleSQLStmt = `select ID, Handle, Attributes, ACL, Accepted from conversations where Handle=(?);`
const acceptedConversationSQLStmt = `update conversations set Accepted=true where ID=(?);` const acceptedConversationSQLStmt = `update conversations set Accepted=true where ID=(?);`
const setConversationAttributesSQLStmt = `update conversations set Attributes=(?) where ID=(?) ;` const setConversationAttributesSQLStmt = `update conversations set Attributes=(?) where ID=(?) ;`
const setConversationACLSQLStmt = `update conversations set ACL=(?) where ID=(?) ;`
const deleteConversationSQLStmt = `delete from conversations where ID=(?);` const deleteConversationSQLStmt = `delete from conversations where ID=(?);`
// createTableConversationMessagesSQLStmt is a template for creating conversation based tables... // createTableConversationMessagesSQLStmt is a template for creating conversation based tables...
@ -85,6 +89,12 @@ const getMessageBySignatureFromConversationSQLStmt = `select ID from channel_%d_
// getMessageByContentHashFromConversationSQLStmt is a template for creating conversation based tables... // getMessageByContentHashFromConversationSQLStmt is a template for creating conversation based tables...
const getMessageByContentHashFromConversationSQLStmt = `select ID from channel_%d_%d_chat where ContentHash=(?);` const getMessageByContentHashFromConversationSQLStmt = `select ID from channel_%d_%d_chat where ContentHash=(?);`
// getMessageCountFromConversationSqlStmt
const getMessageCountFromConversationSqlStmt = `select count(*) from channel_%d_%d_chat;`
// getMostRecentMessagesFromSqlStmt
const getMostRecentMessagesSqlStmt = `select ID, Body, Attributes, Signature, ContentHash from channel_%d_%d_chat order by ID desc limit (?) offset (?);`
// NewCwtchProfileStorage constructs a new CwtchProfileStorage from a database. It is also responsible for // NewCwtchProfileStorage constructs a new CwtchProfileStorage from a database. It is also responsible for
// Preparing commonly used SQL Statements // Preparing commonly used SQL Statements
func NewCwtchProfileStorage(db *sql.DB) (*CwtchProfileStorage, error) { func NewCwtchProfileStorage(db *sql.DB) (*CwtchProfileStorage, error) {
@ -147,6 +157,12 @@ func NewCwtchProfileStorage(db *sql.DB) (*CwtchProfileStorage, error) {
return nil, err return nil, err
} }
setConversationACLStmt, err := db.Prepare(setConversationACLSQLStmt)
if err != nil {
log.Errorf("error preparing query: %v %v", setConversationACLSQLStmt, err)
return nil, err
}
return &CwtchProfileStorage{db: db, return &CwtchProfileStorage{db: db,
insertProfileKeyValueStmt: insertProfileKeyValueStmt, insertProfileKeyValueStmt: insertProfileKeyValueStmt,
selectProfileKeyValueStmt: selectProfileKeyStmt, selectProfileKeyValueStmt: selectProfileKeyStmt,
@ -157,10 +173,13 @@ func NewCwtchProfileStorage(db *sql.DB) (*CwtchProfileStorage, error) {
acceptConversationStmt: acceptConversationStmt, acceptConversationStmt: acceptConversationStmt,
deleteConversationStmt: deleteConversationStmt, deleteConversationStmt: deleteConversationStmt,
setConversationAttributesStmt: setConversationAttributesStmt, setConversationAttributesStmt: setConversationAttributesStmt,
setConversationACLStmt: setConversationACLStmt,
channelInsertStmts: map[ChannelID]*sql.Stmt{}, channelInsertStmts: map[ChannelID]*sql.Stmt{},
channelUpdateMessageStmts: map[ChannelID]*sql.Stmt{}, channelUpdateMessageStmts: map[ChannelID]*sql.Stmt{},
channelGetMessageStmts: map[ChannelID]*sql.Stmt{}, channelGetMessageStmts: map[ChannelID]*sql.Stmt{},
channelGetMessageBySignatureStmts: map[ChannelID]*sql.Stmt{}}, channelGetMessageBySignatureStmts: map[ChannelID]*sql.Stmt{},
channelGetMostRecentMessagesStmts: map[ChannelID]*sql.Stmt{},
channelGetCountStmts: map[ChannelID]*sql.Stmt{}},
nil nil
} }
@ -358,6 +377,16 @@ func (cps *CwtchProfileStorage) DeleteConversation(id int) error {
return nil return nil
} }
// SetConversationACL sets a new ACL on a given conversation.
func (cps *CwtchProfileStorage) SetConversationACL(id int, acl model.AccessControlList) error {
_, err := cps.setConversationACLStmt.Exec(acl, id)
if err != nil {
log.Errorf("error executing query: %v", err)
return err
}
return nil
}
// SetConversationAttribute sets a new attribute on a given conversation. // SetConversationAttribute sets a new attribute on a given conversation.
func (cps *CwtchProfileStorage) SetConversationAttribute(id int, path attr.ScopedZonedPath, value string) error { func (cps *CwtchProfileStorage) SetConversationAttribute(id int, path attr.ScopedZonedPath, value string) error {
ci, err := cps.GetConversation(id) ci, err := cps.GetConversation(id)
@ -501,6 +530,68 @@ func (cps *CwtchProfileStorage) GetChannelMessage(conversation int, channel int,
return body, model.DeserializeAttributes(attributes), nil return body, model.DeserializeAttributes(attributes), nil
} }
// GetChannelMessageCount returns the number of messages in a channel
func (cps *CwtchProfileStorage) GetChannelMessageCount(conversation int, channel int) (int, error) {
channelID := ChannelID{Conversation: conversation, Channel: channel}
_, exists := cps.channelGetCountStmts[channelID]
if !exists {
conversationStmt, err := cps.db.Prepare(fmt.Sprintf(getMessageCountFromConversationSqlStmt, conversation, channel))
if err != nil {
log.Errorf("error executing transaction: %v", err)
return -1, err
}
cps.channelGetCountStmts[channelID] = conversationStmt
}
var count int
err := cps.channelGetCountStmts[channelID].QueryRow().Scan(&count)
if err != nil {
log.Errorf("error executing query: %v", err)
return -1, err
}
return count, nil
}
// GetChannelMessageCount returns the number of messages in a channel
func (cps *CwtchProfileStorage) GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error) {
channelID := ChannelID{Conversation: conversation, Channel: channel}
_, exists := cps.channelGetMostRecentMessagesStmts[channelID]
if !exists {
conversationStmt, err := cps.db.Prepare(fmt.Sprintf(getMostRecentMessagesSqlStmt, conversation, channel))
if err != nil {
log.Errorf("error executing transaction: %v", err)
return nil, err
}
cps.channelGetMostRecentMessagesStmts[channelID] = conversationStmt
}
rows, err := cps.channelGetMostRecentMessagesStmts[channelID].Query(limit, offset)
if err != nil {
log.Errorf("error executing query: %v", err)
return nil, err
}
var conversationMessages []model.ConversationMessage
defer rows.Close()
for {
result := rows.Next()
if !result {
return conversationMessages, nil
}
var id int
var body string
var attributes []byte
var sig string
var contenthash string
err = rows.Scan(&id, &body, &attributes, &sig, &contenthash)
if err != nil {
return conversationMessages, err
}
conversationMessages = append(conversationMessages, model.ConversationMessage{ID: id, Body: body, Attr: model.DeserializeAttributes(attributes), Signature: sig, ContentHash: contenthash})
}
}
// Close closes the underlying database and prepared statements // Close closes the underlying database and prepared statements
func (cps *CwtchProfileStorage) Close() { func (cps *CwtchProfileStorage) Close() {
if cps.db != nil { if cps.db != nil {

View File

@ -10,7 +10,7 @@ import (
// AccessPeeringState provides access to functions relating to the underlying connections of a peer. // AccessPeeringState provides access to functions relating to the underlying connections of a peer.
type AccessPeeringState interface { type AccessPeeringState interface {
GetPeerState(string) (connections.ConnectionState, bool) GetPeerState(string) connections.ConnectionState
} }
// ModifyPeeringState is a meta-interface intended to restrict callers to modify-only access to connection peers // ModifyPeeringState is a meta-interface intended to restrict callers to modify-only access to connection peers
@ -48,7 +48,7 @@ type ModifyServers interface {
type SendMessages interface { type SendMessages interface {
SendMessage(conversation int, message string) error SendMessage(conversation int, message string) error
SendInviteToConversation(conversationID int, inviteConversationID int) error SendInviteToConversation(conversationID int, inviteConversationID int) error
SendScopedZonedGetValToContact(handle string, scope attr.Scope, zone attr.Zone, key string) SendScopedZonedGetValToContact(conversationID int, scope attr.Scope, zone attr.Zone, key string)
} }
// ModifyMessages enables a caller to modify the messages in a timeline // ModifyMessages enables a caller to modify the messages in a timeline
@ -106,11 +106,15 @@ type CwtchPeer interface {
GetConversationInfo(conversation int) (*model.Conversation, error) GetConversationInfo(conversation int) (*model.Conversation, error)
FetchConversationInfo(handle string) (*model.Conversation, error) FetchConversationInfo(handle string) (*model.Conversation, error)
AcceptConversation(conversation int) error AcceptConversation(conversation int) error
BlockConversation(conversation int) error
SetConversationAttribute(conversation int, path attr.ScopedZonedPath, value string) error SetConversationAttribute(conversation int, path attr.ScopedZonedPath, value string) error
GetConversationAttribute(conversation int, path attr.ScopedZonedPath) (string, error) GetConversationAttribute(conversation int, path attr.ScopedZonedPath) (string, error)
DeleteConversation(conversation int) error DeleteConversation(conversation int) error
// New Unified Conversation Channel Interfaces
GetChannelMessage(conversation int, channel int, id int) (string, model.Attributes, error) GetChannelMessage(conversation int, channel int, id int) (string, model.Attributes, error)
GetChannelMessageCount(conversation int, channel int) (int, error)
GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error)
ShareFile(fileKey string, serializedManifest string) ShareFile(fileKey string, serializedManifest string)
} }

View File

@ -5,8 +5,6 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"git.openprivacy.ca/openprivacy/log" "git.openprivacy.ca/openprivacy/log"
// Import SQL Cipher
_ "github.com/mutecomm/go-sqlcipher/v4"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
"io" "io"

View File

@ -36,10 +36,4 @@ func ReadProfile(directory string, key [32]byte, salt [128]byte) (*model.Profile
return v1.ReadProfile(directory, key, salt) return v1.ReadProfile(directory, key, salt)
} }
// NewProfile creates a new profile for use in the profile store.
func NewProfile(name string) *model.Profile {
profile := model.GenerateNewProfile(name)
return profile
}
// ********* Versioning and upgrade ********** // ********* Versioning and upgrade **********

View File

@ -1,6 +1,7 @@
package testing package testing
import ( import (
// Import SQL Cipher
"crypto/rand" "crypto/rand"
app2 "cwtch.im/cwtch/app" app2 "cwtch.im/cwtch/app"
"cwtch.im/cwtch/app/utils" "cwtch.im/cwtch/app/utils"
@ -15,6 +16,7 @@ import (
"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/mutecomm/go-sqlcipher/v4"
mrand "math/rand" mrand "math/rand"
"os" "os"
"os/user" "os/user"
@ -31,62 +33,28 @@ var (
carolLines = []string{"Howdy, thanks!"} carolLines = []string{"Howdy, thanks!"}
) )
func printAndCountVerifedTimeline(t *testing.T, timeline []model.Message) int { func waitForConnection(t *testing.T, peer peer.CwtchPeer, addr string, target connections.ConnectionState) {
numVerified := 0
for _, message := range timeline {
fmt.Printf("%v %v> %s\n", message.Timestamp, message.PeerID, message.Message)
numVerified++
}
return numVerified
}
func waitForPeerGroupConnection(t *testing.T, peer peer.CwtchPeer, serverAddr string) {
peerName, _ := peer.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name) peerName, _ := peer.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
for { for {
fmt.Printf("%v checking group connection...\n", peerName) fmt.Printf("%v checking connection...\n", peerName)
state, ok := peer.GetPeerState(serverAddr) state := peer.GetPeerState(addr)
if ok { fmt.Printf("Waiting for Peer %v to %v - state: %v\n", peerName, addr, state)
fmt.Printf("Waiting for Peer %v to join group %v - state: %v\n", peerName, serverAddr, state) if state == connections.FAILED {
if state == connections.FAILED { t.Fatalf("%v could not connect to %v", peer.GetOnion(), addr)
t.Fatalf("%v could not connect to %v", peer.GetOnion(), serverAddr) }
} if state != target {
if state != connections.SYNCED { fmt.Printf("peer %v %v waiting connect %v, currently: %v\n", peerName, peer.GetOnion(), addr, connections.ConnectionStateName[state])
fmt.Printf("peer %v %v waiting connect to group %v, currently: %v\n", peerName, peer.GetOnion(), serverAddr, connections.ConnectionStateName[state]) time.Sleep(time.Second * 5)
time.Sleep(time.Second * 5) continue
continue } else {
} else { fmt.Printf("peer %v %v CONNECTED to %v\n", peerName, peer.GetOnion(), addr)
fmt.Printf("peer %v %v CONNECTED to group %v\n", peerName, peer.GetOnion(), serverAddr) break
break
}
} }
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
} }
return return
} }
func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.CwtchPeer) {
for {
state, ok := peera.GetPeerState(peerb.GetOnion())
if ok {
//log.Infof("Waiting for Peer %v to peer with peer: %v - state: %v\n", peera.GetProfile().Name, peerb.GetProfile().Name, state)
if state == connections.FAILED {
t.Fatalf("%v could not connect to %v", peera.GetOnion(), peerb.GetOnion())
}
if state != connections.AUTHENTICATED {
fmt.Printf("peer %v waiting connect to peer %v, currently: %v\n", peera.GetOnion(), peerb.GetOnion(), connections.ConnectionStateName[state])
time.Sleep(time.Second * 5)
continue
} else {
peerAName, _ := peera.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
peerBName, _ := peerb.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName)
break
}
}
}
return
}
func TestCwtchPeerIntegration(t *testing.T) { func TestCwtchPeerIntegration(t *testing.T) {
numGoRoutinesStart := runtime.NumGoroutine() numGoRoutinesStart := runtime.NumGoroutine()
@ -179,65 +147,37 @@ func TestCwtchPeerIntegration(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error adding conversaiton %v", alice2bobConversationID) t.Fatalf("error adding conversaiton %v", alice2bobConversationID)
} }
alice.PeerWithOnion(bob.GetOnion()) bob2aliceConversationID, err := bob.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
if err != nil {
t.Fatalf("error adding conversaiton %v", bob2aliceConversationID)
}
fmt.Println("Alice peering with Carol...") t.Logf("Alice peering with Carol...")
// Simulate Alice Adding Carol // Simulate Alice Adding Carol
alice2carolConversationID, err := alice.NewContactConversation(carol.GetOnion(), model.DefaultP2PAccessControl(), true) alice2carolConversationID, err := alice.NewContactConversation(carol.GetOnion(), model.DefaultP2PAccessControl(), true)
if err != nil { if err != nil {
t.Fatalf("error adding conversaiton %v", alice2carolConversationID) t.Fatalf("error adding conversaiton %v", alice2carolConversationID)
} }
alice.PeerWithOnion(carol.GetOnion())
// Simulate Alice Creating a Group
fmt.Println("Alice joining server...")
if err := alice.AddServer(string(serverKeyBundle)); err != nil {
t.Fatalf("Failed to Add Server Bundle %v", err)
}
err = alice.JoinServer(ServerAddr)
if err != nil {
t.Fatalf("alice cannot join server %v %v", ServerAddr, err)
}
fmt.Println("Creating group on ", ServerAddr, "...")
aliceGroupConversationID, err := alice.StartGroup("Our Cool Testing Group", ServerAddr)
fmt.Printf("Created group: %v!\n", aliceGroupConversationID)
if err != nil {
t.Errorf("Failed to init group: %v", err)
return
}
fmt.Println("Waiting for alice to join server...")
waitForPeerGroupConnection(t, alice, ServerAddr)
fmt.Println("Waiting for alice and Bob to peer...")
waitForPeerPeerConnection(t, alice, bob)
// Need to add contact else SetContactAuth fails on peer peer doesnt exist
// 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
// Simulate Bob adding Alice
bob2aliceConversationID, err := bob.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
if err != nil {
t.Fatalf("error adding conversaiton %v", bob2aliceConversationID)
}
bob.AddServer(string(serverKeyBundle))
waitForPeerPeerConnection(t, alice, carol)
// Simulate Carol adding Alice
carol2aliceConversationID, err := carol.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true) carol2aliceConversationID, err := carol.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
if err != nil { if err != nil {
t.Fatalf("error adding conversaiton %v", carol2aliceConversationID) t.Fatalf("error adding conversaiton %v", carol2aliceConversationID)
} }
carol.AddServer(string(serverKeyBundle))
fmt.Println("Alice and Bob getVal public.name...") alice.PeerWithOnion(bob.GetOnion())
alice.PeerWithOnion(carol.GetOnion())
alice.SendScopedZonedGetValToContact(bob.GetOnion(), attr.PublicScope, attr.ProfileZone, constants.Name) waitForConnection(t, alice, bob.GetOnion(), connections.AUTHENTICATED)
bob.SendScopedZonedGetValToContact(alice.GetOnion(), attr.PublicScope, attr.ProfileZone, constants.Name) waitForConnection(t, alice, carol.GetOnion(), connections.AUTHENTICATED)
waitForConnection(t, bob, alice.GetOnion(), connections.AUTHENTICATED)
waitForConnection(t, carol, alice.GetOnion(), connections.AUTHENTICATED)
alice.SendScopedZonedGetValToContact(carol.GetOnion(), attr.PublicScope, attr.ProfileZone, constants.Name) t.Logf("Alice and Bob getVal public.name...")
carol.SendScopedZonedGetValToContact(alice.GetOnion(), attr.PublicScope, attr.ProfileZone, constants.Name)
alice.SendScopedZonedGetValToContact(alice2bobConversationID, attr.PublicScope, attr.ProfileZone, constants.Name)
bob.SendScopedZonedGetValToContact(bob2aliceConversationID, attr.PublicScope, attr.ProfileZone, constants.Name)
alice.SendScopedZonedGetValToContact(alice2carolConversationID, attr.PublicScope, attr.ProfileZone, constants.Name)
carol.SendScopedZonedGetValToContact(carol2aliceConversationID, attr.PublicScope, attr.ProfileZone, constants.Name)
// This used to be 10, but increasing it to 30 because this is now causing frequent issues // This used to be 10, but increasing it to 30 because this is now causing frequent issues
// Probably related to latency/throughput problems in the underlying tor network. // Probably related to latency/throughput problems in the underlying tor network.
@ -266,6 +206,35 @@ func TestCwtchPeerIntegration(t *testing.T) {
} }
fmt.Printf("Alice has carol's name as '%v'\n", carolName) fmt.Printf("Alice has carol's name as '%v'\n", carolName)
// Group Testing
// Simulate Alice Creating a Group
fmt.Println("Alice joining server...")
if err := alice.AddServer(string(serverKeyBundle)); err != nil {
t.Fatalf("Failed to Add Server Bundle %v", err)
}
bob.AddServer(string(serverKeyBundle))
carol.AddServer(string(serverKeyBundle))
err = alice.JoinServer(ServerAddr)
if err != nil {
t.Fatalf("alice cannot join server %v %v", ServerAddr, err)
}
waitForConnection(t, alice, ServerAddr, connections.AUTHENTICATED)
// Creating a Group
fmt.Println("Creating group on ", ServerAddr, "...")
aliceGroupConversationID, err := alice.StartGroup("Our Cool Testing Group", ServerAddr)
fmt.Printf("Created group: %v!\n", aliceGroupConversationID)
if err != nil {
t.Errorf("Failed to init group: %v", err)
return
}
fmt.Println("Waiting for alice to join server...")
// Invites
fmt.Println("Alice inviting Bob to group...") fmt.Println("Alice inviting Bob to group...")
err = alice.SendInviteToConversation(alice2bobConversationID, aliceGroupConversationID) err = alice.SendInviteToConversation(alice2bobConversationID, aliceGroupConversationID)
if err != nil { if err != nil {
@ -289,7 +258,7 @@ func TestCwtchPeerIntegration(t *testing.T) {
t.Fatalf("alice cannot join server %v %v", ServerAddr, err) t.Fatalf("alice cannot join server %v %v", ServerAddr, err)
} }
bobGroupConversationID := 3 bobGroupConversationID := 3
waitForPeerGroupConnection(t, bob, ServerAddr) waitForConnection(t, bob, ServerAddr, connections.SYNCED)
numGoRoutinesPostServerConnect := runtime.NumGoroutine() numGoRoutinesPostServerConnect := runtime.NumGoroutine()

View File

@ -1,6 +1,7 @@
package encryptedstorage package encryptedstorage
import ( import (
// Import SQL Cipher
"crypto/rand" "crypto/rand"
app2 "cwtch.im/cwtch/app" app2 "cwtch.im/cwtch/app"
"cwtch.im/cwtch/app/utils" "cwtch.im/cwtch/app/utils"
@ -11,6 +12,7 @@ import (
"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/mutecomm/go-sqlcipher/v4"
mrand "math/rand" mrand "math/rand"
"os" "os"
"path" "path"
@ -73,7 +75,7 @@ func TestEncryptedStorage(t *testing.T) {
alice.PeerWithOnion(bob.GetOnion()) alice.PeerWithOnion(bob.GetOnion())
time.Sleep(time.Second * 30) time.Sleep(time.Second * 40)
alice.SendMessage(2, "Hello Bob") alice.SendMessage(2, "Hello Bob")
if err != nil { if err != nil {
@ -104,6 +106,20 @@ func TestEncryptedStorage(t *testing.T) {
t.Fatalf("Alices message should have been acknowledged.") t.Fatalf("Alices message should have been acknowledged.")
} }
if count, err := alice.GetChannelMessageCount(2, 0); err != nil || count != 1 {
t.Fatalf("Channel should have a single message in it. Instead returned %v %v", count, err)
}
messages, err := alice.GetMostRecentMessages(2, 0, 0, 10)
if err != nil {
t.Fatalf("fetching messages over offset should not result in error: %v", err)
}
if len(messages) != 1 || len(messages) > 0 && messages[0].Body != "Hello Bob" {
t.Fatalf("expeced GetMostRecentMessages to return 1, instead returned: %v %v", len(messages), messages)
}
} }
// Sub Test testing that Alice can add Bob, delete the conversation associated with Bob, and then add Bob again // Sub Test testing that Alice can add Bob, delete the conversation associated with Bob, and then add Bob again

View File

@ -18,6 +18,8 @@ import (
"fmt" "fmt"
"git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log" "git.openprivacy.ca/openprivacy/log"
// Import SQL Cipher
_ "github.com/mutecomm/go-sqlcipher/v4"
mrand "math/rand" mrand "math/rand"
"os" "os"
"os/user" "os/user"
@ -30,22 +32,20 @@ import (
func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.CwtchPeer) { func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.CwtchPeer) {
for { for {
state, ok := peera.GetPeerState(peerb.GetOnion()) state := peera.GetPeerState(peerb.GetOnion())
if ok { //log.Infof("Waiting for Peer %v to peer with peer: %v - state: %v\n", peera.GetProfile().Name, peerb.GetProfile().Name, state)
//log.Infof("Waiting for Peer %v to peer with peer: %v - state: %v\n", peera.GetProfile().Name, peerb.GetProfile().Name, state) if state == connections.FAILED {
if state == connections.FAILED { t.Fatalf("%v could not connect to %v", peera.GetOnion(), peerb.GetOnion())
t.Fatalf("%v could not connect to %v", peera.GetOnion(), peerb.GetOnion()) }
} if state != connections.AUTHENTICATED {
if state != connections.AUTHENTICATED { fmt.Printf("peer %v waiting connect to peer %v, currently: %v\n", peera.GetOnion(), peerb.GetOnion(), connections.ConnectionStateName[state])
fmt.Printf("peer %v waiting connect to peer %v, currently: %v\n", peera.GetOnion(), peerb.GetOnion(), connections.ConnectionStateName[state]) time.Sleep(time.Second * 5)
time.Sleep(time.Second * 5) continue
continue } else {
} else { peerAName, _ := peera.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
peerAName, _ := peera.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name) peerBName, _ := peerb.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
peerBName, _ := peerb.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name) fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName)
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName) break
break
}
} }
} }
return return
@ -146,7 +146,7 @@ func TestFileSharing(t *testing.T) {
err := json.Unmarshal([]byte(messageWrapper.Data), &fileMessageOverlay) err := json.Unmarshal([]byte(messageWrapper.Data), &fileMessageOverlay)
if err == nil { if err == nil {
filesharingFunctionality.DownloadFile(bob, alice.GetOnion(), "cwtch.out.png", "cwtch.out.png.manifest", fmt.Sprintf("%s.%s", fileMessageOverlay.Hash, fileMessageOverlay.Nonce)) filesharingFunctionality.DownloadFile(bob, 1, "cwtch.out.png", "cwtch.out.png.manifest", fmt.Sprintf("%s.%s", fileMessageOverlay.Hash, fileMessageOverlay.Nonce))
} }
} }