Fixes from Cwtch UI Integration
This commit is contained in:
parent
2caaa7eb87
commit
72ac4099d5
13
app/app.go
13
app/app.go
|
@ -160,21 +160,16 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
|
||||||
// Attempt to load an encrypted database
|
// Attempt to load an encrypted database
|
||||||
profileDirectory := path.Join(ac.directory, "profiles", file.Name())
|
profileDirectory := path.Join(ac.directory, "profiles", file.Name())
|
||||||
profile, err := peer.FromEncryptedDatabase(profileDirectory, password)
|
profile, err := peer.FromEncryptedDatabase(profileDirectory, password)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// return the load the profile...
|
// return the load the profile...
|
||||||
|
log.Infof("loading profile from new-type storage database...")
|
||||||
loadProfileFn(profile)
|
loadProfileFn(profile)
|
||||||
}
|
} else { // On failure attempt to load a legacy profile
|
||||||
|
profileStore, err := storage.LoadProfileWriterStore(profileDirectory, password)
|
||||||
// On failure attempt to load a legacy profile
|
|
||||||
if err != nil {
|
|
||||||
eventBus := event.NewEventManager()
|
|
||||||
|
|
||||||
profileStore, err := storage.LoadProfileWriterStore(eventBus, profileDirectory, password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
log.Infof("found legacy profile. importing to new database structure...")
|
||||||
legacyProfile := profileStore.GetProfileCopy(timeline)
|
legacyProfile := profileStore.GetProfileCopy(timeline)
|
||||||
|
|
||||||
cps, err := peer.CreateEncryptedStore(profileDirectory, password)
|
cps, err := peer.CreateEncryptedStore(profileDirectory, password)
|
||||||
|
|
|
@ -30,6 +30,9 @@ const True = "true"
|
||||||
// False - false
|
// False - false
|
||||||
const False = "false"
|
const False = "false"
|
||||||
|
|
||||||
|
// AttrAuthor - conversation attribute for author of the message - referenced by pub key rather than conversation id because of groups.
|
||||||
|
const AttrAuthor = "author"
|
||||||
|
|
||||||
// AttrAck - conversation attribute for acknowledgement status
|
// AttrAck - conversation attribute for acknowledgement status
|
||||||
const AttrAck = "ack"
|
const AttrAck = "ack"
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ type Group struct {
|
||||||
GroupName string
|
GroupName string
|
||||||
GroupKey [32]byte
|
GroupKey [32]byte
|
||||||
GroupServer string
|
GroupServer string
|
||||||
|
Attributes map[string]string //legacy to not use
|
||||||
Version int
|
Version int
|
||||||
Timeline Timeline `json:"-"`
|
Timeline Timeline `json:"-"`
|
||||||
LocalID string
|
LocalID string
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastKnownSignature = "LastKnowSignature"
|
const lastKnownSignature = "LastKnowSignature"
|
||||||
|
const lastReceivedSignature = "LastReceivedSignature"
|
||||||
|
|
||||||
var autoHandleableEvents = map[event.Type]bool{event.EncryptedGroupMessage: true, event.PeerStateChange: true,
|
var autoHandleableEvents = map[event.Type]bool{event.EncryptedGroupMessage: true, event.PeerStateChange: true,
|
||||||
event.ServerStateChange: true, event.NewGroupInvite: true, event.NewMessageFromPeer: true,
|
event.ServerStateChange: true, event.NewGroupInvite: true, event.NewMessageFromPeer: true,
|
||||||
|
@ -160,7 +161,7 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) error {
|
||||||
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{constants.AttrAck: event.False, constants.AttrSentTimestamp: time.Now().Format(time.RFC3339Nano)}, ev.EventID, model.CalculateContentHash(string(onion), message))
|
err := cp.storage.InsertMessage(conversationInfo.ID, 0, message, model.Attributes{constants.AttrAuthor: string(onion), 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
|
||||||
}
|
}
|
||||||
|
@ -192,7 +193,7 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the Group Message
|
// Insert the Group Message
|
||||||
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))
|
err = cp.storage.InsertMessage(conversationInfo.ID, 0, dm.Text, model.Attributes{constants.AttrAck: constants.False, "PreviousSignature": base64.StdEncoding.EncodeToString(dm.PreviousMessageSig), constants.AttrAuthor: dm.Onion, constants.AttrSentTimestamp: time.Now().Format(time.RFC3339Nano)}, base64.StdEncoding.EncodeToString(sig), model.CalculateContentHash(dm.Onion, dm.Text))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
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)})
|
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)
|
||||||
|
@ -264,7 +265,9 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
cp := new(cwtchPeer)
|
cp := new(cwtchPeer)
|
||||||
cp.shutdown = false
|
cp.shutdown = false
|
||||||
cp.storage = cps
|
cp.storage = cps
|
||||||
|
cp.eventBus = event.NewEventManager()
|
||||||
cp.queue = event.NewQueue()
|
cp.queue = event.NewQueue()
|
||||||
|
cp.state = make(map[string]connections.ConnectionState)
|
||||||
// Store all the Necessary Base Attributes In The Database
|
// Store all the Necessary Base Attributes In The Database
|
||||||
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, profile.Name)
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, profile.Name)
|
||||||
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, profile.Name)
|
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, profile.Name)
|
||||||
|
@ -274,7 +277,7 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
|
|
||||||
for k, v := range profile.Attributes {
|
for k, v := range profile.Attributes {
|
||||||
parts := strings.SplitN(k, ".", 2)
|
parts := strings.SplitN(k, ".", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) == 2 {
|
||||||
scope := attr.IntoScope(parts[0])
|
scope := attr.IntoScope(parts[0])
|
||||||
zone, path := attr.ParseZone(parts[1])
|
zone, path := attr.ParseZone(parts[1])
|
||||||
cp.SetScopedZonedAttribute(scope, zone, path, v)
|
cp.SetScopedZonedAttribute(scope, zone, path, v)
|
||||||
|
@ -297,10 +300,6 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for key, value := range contact.Attributes {
|
for key, value := range contact.Attributes {
|
||||||
switch key {
|
switch key {
|
||||||
case "name":
|
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), value)
|
|
||||||
case "local.profile.name":
|
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), value)
|
|
||||||
case event.SaveHistoryKey:
|
case event.SaveHistoryKey:
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)), value)
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)), value)
|
||||||
case string(model.BundleType):
|
case string(model.BundleType):
|
||||||
|
@ -311,13 +310,23 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
// ignore
|
// ignore
|
||||||
case string(model.KeyTypePrivacyPass):
|
case string(model.KeyTypePrivacyPass):
|
||||||
// ignore
|
// ignore
|
||||||
|
case lastKnownSignature:
|
||||||
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(lastReceivedSignature)), value)
|
||||||
default:
|
default:
|
||||||
log.Errorf("could not import conversation attribute %v %v", key, value)
|
log.Errorf("could not import conversation attribute %v %v", key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name, exists := contact.Attributes["local.name"]; exists {
|
||||||
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), name)
|
||||||
|
}
|
||||||
|
if name, exists := contact.Attributes["peer.name"]; exists {
|
||||||
|
cp.SetConversationAttribute(conversationID, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), name)
|
||||||
|
}
|
||||||
|
|
||||||
for _, message := range contact.Timeline.GetMessages() {
|
for _, message := range contact.Timeline.GetMessages() {
|
||||||
// By definition anything stored in legacy timelines in acknowledged
|
// By definition anything stored in legacy timelines in acknowledged
|
||||||
attr := model.Attributes{constants.AttrAck: event.True, constants.AttrSentTimestamp: message.Timestamp.Format(time.RFC3339Nano)}
|
attr := model.Attributes{constants.AttrAuthor: message.PeerID, constants.AttrAck: event.True, constants.AttrSentTimestamp: message.Timestamp.Format(time.RFC3339Nano)}
|
||||||
if message.Flags&0x01 == 0x01 {
|
if message.Flags&0x01 == 0x01 {
|
||||||
attr[constants.AttrRejected] = event.True
|
attr[constants.AttrRejected] = event.True
|
||||||
}
|
}
|
||||||
|
@ -330,15 +339,15 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, group := range profile.Groups {
|
for _, group := range profile.Groups {
|
||||||
|
group.GroupName = group.Attributes["local.name"]
|
||||||
invite, err := group.Invite()
|
invite, err := group.Invite()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Automatically grab all the important fields...
|
// Automatically grab all the important fields...
|
||||||
// Including Name...
|
|
||||||
conversationID, err := cp.ImportGroup(invite)
|
conversationID, err := cp.ImportGroup(invite)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, message := range group.Timeline.GetMessages() {
|
for _, message := range group.Timeline.GetMessages() {
|
||||||
// By definition anything stored in legacy timelines in acknowledged
|
// By definition anything stored in legacy timelines in acknowledged
|
||||||
attr := model.Attributes{constants.AttrAck: event.True, constants.AttrSentTimestamp: message.Timestamp.Format(time.RFC3339Nano)}
|
attr := model.Attributes{constants.AttrAuthor: message.PeerID, constants.AttrAck: event.True, constants.AttrSentTimestamp: message.Timestamp.Format(time.RFC3339Nano)}
|
||||||
if message.Flags&0x01 == 0x01 {
|
if message.Flags&0x01 == 0x01 {
|
||||||
attr[constants.AttrRejected] = event.True
|
attr[constants.AttrRejected] = event.True
|
||||||
}
|
}
|
||||||
|
@ -350,7 +359,7 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cp.eventBus.Shutdown() // We disregard all events from profile...
|
||||||
return cp
|
return cp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +455,7 @@ func (cp *cwtchPeer) ImportGroup(exportedInvite string) (int, error) {
|
||||||
cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)), gci.GroupID)
|
cp.SetConversationAttribute(groupConversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)), gci.GroupID)
|
||||||
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.ProfileZone.ConstructZonedPath(constants.Name)), gci.GroupName)
|
||||||
cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.ConversationID: 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
|
||||||
|
@ -570,7 +579,7 @@ func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) {
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)), group.GroupID)
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)), group.GroupID)
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)), group.GroupServer)
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)), group.GroupServer)
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupKey)), base64.StdEncoding.EncodeToString(group.GroupKey[:]))
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupKey)), base64.StdEncoding.EncodeToString(group.GroupKey[:]))
|
||||||
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.Name)), name)
|
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.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.ConversationID: strconv.Itoa(conversationID),
|
event.ConversationID: strconv.Itoa(conversationID),
|
||||||
|
@ -710,7 +719,7 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("group structure is malformed - no key")
|
return errors.New("group structure is malformed - no key")
|
||||||
}
|
}
|
||||||
groupName, ok := inviteConversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.Name)).ToString()]
|
groupName, ok := inviteConversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)).ToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("group structure is malformed - no name")
|
return errors.New("group structure is malformed - no name")
|
||||||
}
|
}
|
||||||
|
@ -804,7 +813,7 @@ func (cp *cwtchPeer) JoinServer(onion string) error {
|
||||||
tokenY, yExists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(model.KeyTypePrivacyPass))).ToString()]
|
tokenY, yExists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(model.KeyTypePrivacyPass))).ToString()]
|
||||||
tokenOnion, onionExists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(model.KeyTypeTokenOnion))).ToString()]
|
tokenOnion, onionExists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(model.KeyTypeTokenOnion))).ToString()]
|
||||||
if yExists && onionExists {
|
if yExists && onionExists {
|
||||||
signature, exists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(lastKnownSignature)).ToString()]
|
signature, exists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(lastReceivedSignature)).ToString()]
|
||||||
if !exists {
|
if !exists {
|
||||||
signature = base64.StdEncoding.EncodeToString([]byte{})
|
signature = base64.StdEncoding.EncodeToString([]byte{})
|
||||||
}
|
}
|
||||||
|
@ -908,7 +917,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, constants.AttrSentTimestamp: sent.Format(time.RFC3339Nano)}, signature, model.CalculateContentHash(handle, message))
|
return cp.storage.InsertMessage(ci.ID, 0, message, model.Attributes{constants.AttrAuthor: handle, 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
|
||||||
|
@ -1138,9 +1147,9 @@ func (cp *cwtchPeer) attemptInsertOrAcknowledgeLegacyGroupConversation(conversat
|
||||||
}
|
}
|
||||||
} 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, constants.AttrSentTimestamp: 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), constants.AttrAuthor: dm.Onion, constants.AttrSentTimestamp: time.Unix(int64(dm.Timestamp), 0).Format(time.RFC3339Nano)}, 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.ConversationID: 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.RemotePeer: dm.Onion, event.Index: strconv.Itoa(messageID)}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -1204,6 +1213,12 @@ func (cp *cwtchPeer) GetChannelMessageBySignature(conversationID int, channelID
|
||||||
return cp.storage.GetChannelMessageBySignature(conversationID, channelID, signature)
|
return cp.storage.GetChannelMessageBySignature(conversationID, channelID, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cp *cwtchPeer) GetChannelMessageByContentHash(conversationID int, channelID int, contenthash string) (int, error) {
|
||||||
|
cp.mutex.Lock()
|
||||||
|
defer cp.mutex.Unlock()
|
||||||
|
return cp.storage.GetChannelMessageByContentHash(conversationID, channelID, contenthash)
|
||||||
|
}
|
||||||
|
|
||||||
func (cp *cwtchPeer) constructGroupFromConversation(conversationInfo *model.Conversation) (*model.Group, error) {
|
func (cp *cwtchPeer) constructGroupFromConversation(conversationInfo *model.Conversation) (*model.Group, error) {
|
||||||
key := conversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupKey)).ToString()]
|
key := conversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupKey)).ToString()]
|
||||||
groupKey, err := base64.StdEncoding.DecodeString(key)
|
groupKey, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
|
|
@ -43,12 +43,13 @@ type CwtchProfileStorage struct {
|
||||||
setConversationAttributesStmt *sql.Stmt
|
setConversationAttributesStmt *sql.Stmt
|
||||||
setConversationACLStmt *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
|
channelGetCountStmts map[ChannelID]*sql.Stmt
|
||||||
channelGetMostRecentMessagesStmts map[ChannelID]*sql.Stmt
|
channelGetMostRecentMessagesStmts map[ChannelID]*sql.Stmt
|
||||||
|
channelGetMessageByContentHashStmts map[ChannelID]*sql.Stmt
|
||||||
|
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
@ -87,7 +88,7 @@ const getMessageFromConversationSQLStmt = `select Body, Attributes from channel_
|
||||||
const getMessageBySignatureFromConversationSQLStmt = `select ID from channel_%d_%d_chat where Signature=(?);`
|
const getMessageBySignatureFromConversationSQLStmt = `select ID from channel_%d_%d_chat where Signature=(?);`
|
||||||
|
|
||||||
// getMessageByContentHashFromConversationSQLStmt is a template for selecting conversation messages by content hash
|
// getMessageByContentHashFromConversationSQLStmt is a template for selecting conversation messages by content hash
|
||||||
const getMessageByContentHashFromConversationSQLStmt = `select ID from channel_%d_%d_chat where ContentHash=(?);`
|
const getMessageByContentHashFromConversationSQLStmt = `select ID from channel_%d_%d_chat where ContentHash=(?) order by ID desc limit 1;`
|
||||||
|
|
||||||
// getMessageCountFromConversationSQLStmt is a template for fetching the count of a messages in a conversation channel
|
// getMessageCountFromConversationSQLStmt is a template for fetching the count of a messages in a conversation channel
|
||||||
const getMessageCountFromConversationSQLStmt = `select count(*) from channel_%d_%d_chat;`
|
const getMessageCountFromConversationSQLStmt = `select count(*) from channel_%d_%d_chat;`
|
||||||
|
@ -164,22 +165,23 @@ func NewCwtchProfileStorage(db *sql.DB) (*CwtchProfileStorage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CwtchProfileStorage{db: db,
|
return &CwtchProfileStorage{db: db,
|
||||||
insertProfileKeyValueStmt: insertProfileKeyValueStmt,
|
insertProfileKeyValueStmt: insertProfileKeyValueStmt,
|
||||||
selectProfileKeyValueStmt: selectProfileKeyStmt,
|
selectProfileKeyValueStmt: selectProfileKeyStmt,
|
||||||
fetchAllConversationsStmt: fetchAllConversationsStmt,
|
fetchAllConversationsStmt: fetchAllConversationsStmt,
|
||||||
insertConversationStmt: insertConversationStmt,
|
insertConversationStmt: insertConversationStmt,
|
||||||
selectConversationStmt: selectConversationStmt,
|
selectConversationStmt: selectConversationStmt,
|
||||||
selectConversationByHandleStmt: selectConversationByHandleStmt,
|
selectConversationByHandleStmt: selectConversationByHandleStmt,
|
||||||
acceptConversationStmt: acceptConversationStmt,
|
acceptConversationStmt: acceptConversationStmt,
|
||||||
deleteConversationStmt: deleteConversationStmt,
|
deleteConversationStmt: deleteConversationStmt,
|
||||||
setConversationAttributesStmt: setConversationAttributesStmt,
|
setConversationAttributesStmt: setConversationAttributesStmt,
|
||||||
setConversationACLStmt: setConversationACLStmt,
|
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{},
|
channelGetMessageByContentHashStmts: map[ChannelID]*sql.Stmt{},
|
||||||
channelGetCountStmts: map[ChannelID]*sql.Stmt{}},
|
channelGetMostRecentMessagesStmts: map[ChannelID]*sql.Stmt{},
|
||||||
|
channelGetCountStmts: map[ChannelID]*sql.Stmt{}},
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +425,6 @@ func (cps *CwtchProfileStorage) InsertMessage(conversation int, channel int, bod
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("inserted message with signature: %v", signature)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,6 +490,43 @@ func (cps *CwtchProfileStorage) GetChannelMessageBySignature(conversation int, c
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChannelMessageByContentHash looks up a conversation message by hash instead of identifier.
|
||||||
|
func (cps *CwtchProfileStorage) GetChannelMessageByContentHash(conversation int, channel int, hash string) (int, error) {
|
||||||
|
channelID := ChannelID{Conversation: conversation, Channel: channel}
|
||||||
|
|
||||||
|
_, exists := cps.channelGetMessageByContentHashStmts[channelID]
|
||||||
|
if !exists {
|
||||||
|
conversationStmt, err := cps.db.Prepare(fmt.Sprintf(getMessageByContentHashFromConversationSQLStmt, conversation, channel))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error executing transaction: %v", err)
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
cps.channelGetMessageByContentHashStmts[channelID] = conversationStmt
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := cps.channelGetMessageByContentHashStmts[channelID].Query(hash)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error executing query: %v", err)
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := rows.Next()
|
||||||
|
|
||||||
|
if !result {
|
||||||
|
return -1, errors.New("no result found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var id int
|
||||||
|
err = rows.Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error fetching rows: %v", err)
|
||||||
|
rows.Close()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetChannelMessage looks up a channel message by conversation, channel and message id. On success it
|
// GetChannelMessage looks up a channel message by conversation, channel and message id. On success it
|
||||||
// returns the message body and the attributes associated with the message. Otherwise an error is returned.
|
// returns the message body and the attributes associated with the message. Otherwise an error is returned.
|
||||||
func (cps *CwtchProfileStorage) GetChannelMessage(conversation int, channel int, messageID int) (string, model.Attributes, error) {
|
func (cps *CwtchProfileStorage) GetChannelMessage(conversation int, channel int, messageID int) (string, model.Attributes, error) {
|
||||||
|
|
|
@ -114,6 +114,7 @@ type CwtchPeer interface {
|
||||||
// New Unified Conversation Channel Interfaces
|
// 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)
|
GetChannelMessageCount(conversation int, channel int) (int, error)
|
||||||
|
GetChannelMessageByContentHash(conversation int, channel int, contenthash string) (int, error)
|
||||||
GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error)
|
GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error)
|
||||||
|
|
||||||
ShareFile(fileKey string, serializedManifest string)
|
ShareFile(fileKey string, serializedManifest string)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package peer
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
@ -63,7 +64,7 @@ func initV2Directory(directory, password string) ([32]byte, [128]byte, error) {
|
||||||
return key, salt, nil
|
return key, salt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openEncryptedDatabase(profileDirectory string, password string) (*sql.DB, error) {
|
func openEncryptedDatabase(profileDirectory string, password string, createIfNotExists bool) (*sql.DB, error) {
|
||||||
salt, err := ioutil.ReadFile(path.Join(profileDirectory, saltFile))
|
salt, err := ioutil.ReadFile(path.Join(profileDirectory, saltFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -71,6 +72,13 @@ func openEncryptedDatabase(profileDirectory string, password string) (*sql.DB, e
|
||||||
|
|
||||||
key := createKey(password, salt)
|
key := createKey(password, salt)
|
||||||
dbPath := filepath.Join(profileDirectory, "db")
|
dbPath := filepath.Join(profileDirectory, "db")
|
||||||
|
|
||||||
|
if !createIfNotExists {
|
||||||
|
if _, err := os.Stat(dbPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dbname := fmt.Sprintf("%v?_pragma_key=x'%x'&_pragma_cipher_page_size=8192", dbPath, key)
|
dbname := fmt.Sprintf("%v?_pragma_key=x'%x'&_pragma_cipher_page_size=8192", dbPath, key)
|
||||||
db, err := sql.Open("sqlite3", dbname)
|
db, err := sql.Open("sqlite3", dbname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -89,7 +97,7 @@ func CreateEncryptedStorePeer(profileDirectory string, name string, password str
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Opening Encrypted Database")
|
log.Debugf("Opening Encrypted Database")
|
||||||
db, err := openEncryptedDatabase(profileDirectory, password)
|
db, err := openEncryptedDatabase(profileDirectory, password, true)
|
||||||
if db == nil || err != nil {
|
if db == nil || err != nil {
|
||||||
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
|
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -115,14 +123,9 @@ func CreateEncryptedStorePeer(profileDirectory string, name string, password str
|
||||||
|
|
||||||
// CreateEncryptedStore creates a encrypted datastore
|
// CreateEncryptedStore creates a encrypted datastore
|
||||||
func CreateEncryptedStore(profileDirectory string, password string) (*CwtchProfileStorage, error) {
|
func CreateEncryptedStore(profileDirectory string, password string) (*CwtchProfileStorage, error) {
|
||||||
log.Debugf("Initializing Encrypted Storage Directory")
|
|
||||||
_, _, err := initV2Directory(profileDirectory, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Opening Encrypted Database")
|
log.Debugf("Creating Encrypted Database")
|
||||||
db, err := openEncryptedDatabase(profileDirectory, password)
|
db, err := openEncryptedDatabase(profileDirectory, password, true)
|
||||||
if db == nil || err != nil {
|
if db == nil || err != nil {
|
||||||
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
|
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -148,8 +151,8 @@ func CreateEncryptedStore(profileDirectory string, password string) (*CwtchProfi
|
||||||
|
|
||||||
// FromEncryptedDatabase constructs a Cwtch Profile from an existing Encrypted Database
|
// FromEncryptedDatabase constructs a Cwtch Profile from an existing Encrypted Database
|
||||||
func FromEncryptedDatabase(profileDirectory string, password string) (CwtchPeer, error) {
|
func FromEncryptedDatabase(profileDirectory string, password string) (CwtchPeer, error) {
|
||||||
log.Debugf("Loading Encrypted Profile")
|
log.Infof("Loading Encrypted Profile: %v", profileDirectory)
|
||||||
db, err := openEncryptedDatabase(profileDirectory, password)
|
db, err := openEncryptedDatabase(profileDirectory, password, false)
|
||||||
if db == nil || err != nil {
|
if db == nil || err != nil {
|
||||||
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
|
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ func CreateProfileWriterStore(eventManager event.Manager, directory, password st
|
||||||
|
|
||||||
// LoadProfileWriterStore loads a profile store from filestore listening for events and saving them
|
// LoadProfileWriterStore loads a profile store from filestore listening for events and saving them
|
||||||
// directory should be $appDir/profiles/$rand
|
// directory should be $appDir/profiles/$rand
|
||||||
func LoadProfileWriterStore(eventManager event.Manager, directory, password string) (ProfileStore, error) {
|
func LoadProfileWriterStore(directory, password string) (ProfileStore, error) {
|
||||||
return v1.LoadProfileWriterStore(eventManager, directory, password)
|
return v1.LoadProfileWriterStore(directory, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadProfile reads a profile from storage and returns the profile
|
// ReadProfile reads a profile from storage and returns the profile
|
||||||
|
|
|
@ -68,7 +68,7 @@ func CreateProfileWriterStore(eventManager event.Manager, directory, password st
|
||||||
|
|
||||||
// LoadProfileWriterStore loads a profile store from filestore listening for events and saving them
|
// LoadProfileWriterStore loads a profile store from filestore listening for events and saving them
|
||||||
// directory should be $appDir/profiles/$rand
|
// directory should be $appDir/profiles/$rand
|
||||||
func LoadProfileWriterStore(eventManager event.Manager, directory, password string) (*ProfileStoreV1, error) {
|
func LoadProfileWriterStore(directory, password string) (*ProfileStoreV1, error) {
|
||||||
salt, err := ioutil.ReadFile(path.Join(directory, saltFile))
|
salt, err := ioutil.ReadFile(path.Join(directory, saltFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in New Issue