@@ -18,11 +18,14 @@ import ( | |||
"sync" | |||
) | |||
// AttributeTag is a const name for a peer attribute that can be set at creation time, for example for versioning info | |||
const AttributeTag = "tag" | |||
type applicationCore struct { | |||
eventBuses map[string]event.Manager | |||
directory string | |||
mutex sync.Mutex | |||
coremutex sync.Mutex | |||
} | |||
type application struct { | |||
@@ -30,16 +33,20 @@ type application struct { | |||
appletPeers | |||
appletACN | |||
appletPlugins | |||
storage map[string]storage.ProfileStore | |||
engines map[string]connections.Engine | |||
appBus event.Manager | |||
storage map[string]storage.ProfileStore | |||
engines map[string]connections.Engine | |||
appBus event.Manager | |||
appmutex sync.Mutex | |||
} | |||
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers | |||
type Application interface { | |||
LoadProfiles(password string) | |||
CreatePeer(name string, password string) | |||
CreateTaggedPeer(name string, password string, tag string) | |||
DeletePeer(onion string) | |||
AddPeerPlugin(onion string, pluginID plugins.PluginID) | |||
ChangePeerPassword(onion, oldpass, newpass string) | |||
LaunchPeers() | |||
GetPrimaryBus() event.Manager | |||
@@ -78,8 +85,8 @@ func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) { | |||
profile := storage.NewProfile(name) | |||
ac.mutex.Lock() | |||
defer ac.mutex.Unlock() | |||
ac.coremutex.Lock() | |||
defer ac.coremutex.Unlock() | |||
_, exists := ac.eventBuses[profile.Onion] | |||
if exists { | |||
@@ -92,8 +99,15 @@ func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) { | |||
return profile, nil | |||
} | |||
// CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine) | |||
func (app *application) CreatePeer(name string, password string) { | |||
func (ac *applicationCore) DeletePeer(onion string) { | |||
ac.coremutex.Lock() | |||
defer ac.coremutex.Unlock() | |||
ac.eventBuses[onion].Shutdown() | |||
delete(ac.eventBuses, onion) | |||
} | |||
func (app *application) CreateTaggedPeer(name string, password string, tag string) { | |||
profile, err := app.applicationCore.CreatePeer(name) | |||
if err != nil { | |||
app.appBus.Publish(event.NewEventList(event.PeerError, event.Error, err.Error())) | |||
@@ -115,9 +129,46 @@ func (app *application) CreatePeer(name string, password string) { | |||
app.peers[profile.Onion] = peer | |||
app.engines[profile.Onion] = engine | |||
if tag != "" { | |||
peer.SetAttribute(AttributeTag, tag) | |||
} | |||
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})) | |||
} | |||
// CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine) | |||
func (app *application) CreatePeer(name string, password string) { | |||
app.CreateTaggedPeer(name, password, "") | |||
} | |||
func (app *application) DeletePeer(onion string) { | |||
log.Infof("DeletePeer called on %v\n", onion) | |||
app.appmutex.Lock() | |||
defer app.appmutex.Unlock() | |||
app.appletPlugins.ShutdownPeer(onion) | |||
app.plugins.Delete(onion) | |||
app.peers[onion].Shutdown() | |||
delete(app.peers, onion) | |||
app.engines[onion].Shutdown() | |||
delete(app.engines, onion) | |||
app.storage[onion].Shutdown() | |||
app.storage[onion].Delete() | |||
delete(app.storage, onion) | |||
app.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion)) | |||
app.applicationCore.DeletePeer(onion) | |||
log.Debugf("Delete peer for %v Done\n", onion) | |||
} | |||
func (app *application) ChangePeerPassword(onion, oldpass, newpass string) { | |||
app.eventBuses[onion].Publish(event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass)) | |||
} | |||
func (app *application) AddPeerPlugin(onion string, pluginID plugins.PluginID) { | |||
app.AddPlugin(onion, pluginID, app.eventBuses[onion], app.acn) | |||
} | |||
@@ -147,9 +198,9 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf | |||
continue | |||
} | |||
ac.mutex.Lock() | |||
ac.coremutex.Lock() | |||
ac.eventBuses[profile.Onion] = eventBus | |||
ac.mutex.Unlock() | |||
ac.coremutex.Unlock() | |||
loadProfileFn(profile, profileStore) | |||
} | |||
@@ -166,11 +217,11 @@ func (app *application) LoadProfiles(password string) { | |||
blockedPeers := profile.BlockedPeers() | |||
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) | |||
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) | |||
app.mutex.Lock() | |||
app.appmutex.Lock() | |||
app.peers[profile.Onion] = peer | |||
app.storage[profile.Onion] = profileStore | |||
app.engines[profile.Onion] = engine | |||
app.mutex.Unlock() | |||
app.appmutex.Unlock() | |||
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})) | |||
count++ | |||
}) | |||
@@ -210,8 +261,8 @@ func (app *application) QueryACNStatus() { | |||
// ShutdownPeer shuts down a peer and removes it from the app's management | |||
func (app *application) ShutdownPeer(onion string) { | |||
app.mutex.Lock() | |||
defer app.mutex.Unlock() | |||
app.appmutex.Lock() | |||
defer app.appmutex.Unlock() | |||
app.eventBuses[onion].Shutdown() | |||
delete(app.eventBuses, onion) | |||
app.peers[onion].Shutdown() | |||
@@ -9,6 +9,7 @@ import ( | |||
"git.openprivacy.ca/openprivacy/libricochet-go/log" | |||
"path" | |||
"strconv" | |||
"sync" | |||
) | |||
type applicationClient struct { | |||
@@ -16,6 +17,7 @@ type applicationClient struct { | |||
appletPeers | |||
appBus event.Manager | |||
acmutex sync.Mutex | |||
} | |||
// NewAppClient returns an Application that acts as a client to a AppService, connected by the IPCBridge supplied | |||
@@ -43,6 +45,9 @@ func (ac *applicationClient) handleEvent(ev *event.Event) { | |||
password := ev.Data[event.Password] | |||
reload := ev.Data[event.Status] == "running" | |||
ac.newPeer(localID, password, reload) | |||
case event.DeletePeer: | |||
onion := ev.Data[event.Identity] | |||
ac.handleDeletedPeer(onion) | |||
case event.PeerError: | |||
ac.appBus.Publish(*ev) | |||
case event.AppError: | |||
@@ -73,8 +78,8 @@ func (ac *applicationClient) newPeer(localID, password string, reload bool) { | |||
peer := peer.FromProfile(profile) | |||
peer.Init(eventBus) | |||
ac.mutex.Lock() | |||
defer ac.mutex.Unlock() | |||
ac.acmutex.Lock() | |||
defer ac.acmutex.Unlock() | |||
ac.peers[profile.Onion] = peer | |||
ac.eventBuses[profile.Onion] = eventBus | |||
npEvent := event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}) | |||
@@ -90,11 +95,36 @@ func (ac *applicationClient) newPeer(localID, password string, reload bool) { | |||
// CreatePeer messages the service to create a new Peer with the given name | |||
func (ac *applicationClient) CreatePeer(name string, password string) { | |||
ac.CreateTaggedPeer(name, password, "") | |||
} | |||
func (ac *applicationClient) CreateTaggedPeer(name, password, tag string) { | |||
log.Infof("appClient CreatePeer %v\n", name) | |||
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.CreatePeer, map[event.Field]string{event.ProfileName: name, event.Password: password})} | |||
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.CreatePeer, map[event.Field]string{event.ProfileName: name, event.Password: password, event.Data: tag})} | |||
ac.bridge.Write(&message) | |||
} | |||
// DeletePeer messages tehe service to delete a peer | |||
func (ac *applicationClient) DeletePeer(onion string) { | |||
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.DeletePeer, map[event.Field]string{event.Identity: onion})} | |||
ac.bridge.Write(&message) | |||
} | |||
func (ac *applicationClient) ChangePeerPassword(onion, oldpass, newpass string) { | |||
message := event.IPCMessage{Dest: onion, Message: event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass)} | |||
ac.bridge.Write(&message) | |||
} | |||
func (ac *applicationClient) handleDeletedPeer(onion string) { | |||
ac.acmutex.Lock() | |||
defer ac.acmutex.Unlock() | |||
ac.peers[onion].Shutdown() | |||
delete(ac.peers, onion) | |||
ac.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion)) | |||
ac.applicationCore.DeletePeer(onion) | |||
} | |||
func (ac *applicationClient) AddPeerPlugin(onion string, pluginID plugins.PluginID) { | |||
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.AddPeerPlugin, map[event.Field]string{event.Identity: onion, event.Data: strconv.Itoa(int(pluginID))})} | |||
ac.bridge.Write(&message) | |||
@@ -113,8 +143,8 @@ func (ac *applicationClient) QueryACNStatus() { | |||
// ShutdownPeer shuts down a peer and removes it from the app's management | |||
func (ac *applicationClient) ShutdownPeer(onion string) { | |||
ac.mutex.Lock() | |||
defer ac.mutex.Unlock() | |||
ac.acmutex.Lock() | |||
defer ac.acmutex.Unlock() | |||
ac.eventBuses[onion].Shutdown() | |||
delete(ac.eventBuses, onion) | |||
ac.peers[onion].Shutdown() | |||
@@ -11,6 +11,7 @@ import ( | |||
"git.openprivacy.ca/openprivacy/libricochet-go/log" | |||
"path" | |||
"strconv" | |||
"sync" | |||
) | |||
type applicationService struct { | |||
@@ -20,6 +21,7 @@ type applicationService struct { | |||
storage map[string]storage.ProfileStore | |||
engines map[string]connections.Engine | |||
asmutex sync.Mutex | |||
} | |||
// ApplicationService is the back end of an application that manages engines and writing storage and communicates to an ApplicationClient by an IPCBridge | |||
@@ -46,7 +48,14 @@ func (as *applicationService) handleEvent(ev *event.Event) { | |||
case event.CreatePeer: | |||
profileName := ev.Data[event.ProfileName] | |||
password := ev.Data[event.Password] | |||
as.createPeer(profileName, password) | |||
tag := ev.Data[event.Data] | |||
as.createPeer(profileName, password, tag) | |||
case event.DeletePeer: | |||
onion := ev.Data[event.Identity] | |||
as.deletePeer(onion) | |||
message := event.IPCMessage{Dest: DestApp, Message: *ev} | |||
as.bridge.Write(&message) | |||
case event.AddPeerPlugin: | |||
onion := ev.Data[event.Identity] | |||
pluginID, _ := strconv.Atoi(ev.Data[event.Data]) | |||
@@ -79,7 +88,7 @@ func (as *applicationService) handleEvent(ev *event.Event) { | |||
} | |||
} | |||
func (as *applicationService) createPeer(name, password string) { | |||
func (as *applicationService) createPeer(name, password, tag string) { | |||
log.Infof("app Service create peer %v %v\n", name, password) | |||
profile, err := as.applicationCore.CreatePeer(name) | |||
as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion]) | |||
@@ -90,6 +99,10 @@ func (as *applicationService) createPeer(name, password string) { | |||
return | |||
} | |||
if tag != "" { | |||
profile.SetAttribute(AttributeTag, tag) | |||
} | |||
profileStore := storage.NewProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile) | |||
blockedPeers := profile.BlockedPeers() | |||
@@ -112,10 +125,10 @@ func (as *applicationService) loadProfiles(password string) { | |||
blockedPeers := profile.BlockedPeers() | |||
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) | |||
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) | |||
as.mutex.Lock() | |||
as.asmutex.Lock() | |||
as.storage[profile.Onion] = profileStore | |||
as.engines[profile.Onion] = engine | |||
as.mutex.Unlock() | |||
as.asmutex.Unlock() | |||
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.LocalID, event.Password: password})} | |||
as.bridge.Write(&message) | |||
count++ | |||
@@ -136,6 +149,23 @@ func (as *applicationService) getACNStatusHandler() func(int, string) { | |||
} | |||
} | |||
func (as *applicationService) deletePeer(onion string) { | |||
as.asmutex.Lock() | |||
defer as.asmutex.Unlock() | |||
as.appletPlugins.ShutdownPeer(onion) | |||
as.plugins.Delete(onion) | |||
as.engines[onion].Shutdown() | |||
delete(as.engines, onion) | |||
as.storage[onion].Shutdown() | |||
as.storage[onion].Delete() | |||
delete(as.storage, onion) | |||
as.applicationCore.DeletePeer(onion) | |||
} | |||
func (as *applicationService) ShutdownPeer(onion string) { | |||
as.engines[onion].Shutdown() | |||
delete(as.engines, onion) | |||
@@ -131,7 +131,7 @@ func main() { | |||
profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw) | |||
err = profileStore.Load() | |||
err = profileStore.Read() | |||
if err != nil { | |||
log.Errorln(err) | |||
os.Exit(1) | |||
@@ -82,6 +82,15 @@ const ( | |||
// RemotePeer [eg ""] | |||
PeerCreated = Type("PeerCreated") | |||
// Password, NewPassword | |||
ChangePassword = Type("ChangePassword") | |||
// Error(err), EventID | |||
ChangePasswordError = Type("ChangePasswordError") | |||
// EventID | |||
ChangePasswordSuccess = Type("ChangePasswordSuccess") | |||
// a group has been successfully added or newly created | |||
// attributes: | |||
// Data [serialized *model.Group] | |||
@@ -129,13 +138,16 @@ const ( | |||
/***** Application client / service messages *****/ | |||
// ProfileName, Password | |||
// ProfileName, Password, Data(tag) | |||
CreatePeer = Type("CreatePeer") | |||
// service -> client: Identity(localId), Password, [Status(new/default=blank || from reload='running')] | |||
// app -> Identity(onion) | |||
NewPeer = Type("NewPeer") | |||
// Identity(onion) | |||
DeletePeer = Type("DeletePeer") | |||
// Identity(onion), Data(pluginID) | |||
AddPeerPlugin = Type("AddPeerPlugin") | |||
@@ -156,6 +168,7 @@ const ( | |||
Shutdown = Type("Shutdown") | |||
// Error(err) | |||
// Error creating peer | |||
PeerError = Type("PeerError") | |||
// Error(err) | |||
@@ -197,6 +210,7 @@ const ( | |||
ProfileName = Field("ProfileName") | |||
Password = Field("Password") | |||
NewPassword = Field("NewPassword") | |||
ConnectionState = Field("ConnectionState") | |||
@@ -36,7 +36,7 @@ type Group struct { | |||
// NewGroup initializes a new group associated with a given CwtchServer | |||
func NewGroup(server string) (*Group, error) { | |||
group := new(Group) | |||
group.LocalID = generateRandomID() | |||
group.LocalID = GenerateRandomID() | |||
if utils.IsValidHostname(server) == false { | |||
return nil, errors.New("Server is not a valid v3 onion") | |||
@@ -44,7 +44,8 @@ type Profile struct { | |||
// TODO: Should this be per server? | |||
const MaxGroupMessageLength = 1800 | |||
func generateRandomID() string { | |||
// 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)) | |||
@@ -55,7 +56,7 @@ func (p *PublicProfile) init() { | |||
p.Attributes = make(map[string]string) | |||
} | |||
p.unacknowledgedMessages = make(map[string]Message) | |||
p.LocalID = generateRandomID() | |||
p.LocalID = GenerateRandomID() | |||
} | |||
// SetAttribute allows applications to store arbitrary configuration info at the profile level. | |||
@@ -371,7 +372,7 @@ func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, err | |||
if err == nil { | |||
group := new(Group) | |||
group.GroupID = gci.GetGroupName() | |||
group.LocalID = generateRandomID() | |||
group.LocalID = GenerateRandomID() | |||
group.SignedGroupID = gci.GetSignedGroupId() | |||
copy(group.GroupKey[:], gci.GetGroupSharedKey()[:]) | |||
group.GroupServer = gci.GetServerHost() | |||
@@ -1,7 +1,9 @@ | |||
package storage | |||
import ( | |||
"git.openprivacy.ca/openprivacy/libricochet-go/log" | |||
"io/ioutil" | |||
"os" | |||
"path" | |||
) | |||
@@ -14,8 +16,10 @@ type fileStore struct { | |||
// FileStore is a primitive around storing encrypted files | |||
type FileStore interface { | |||
Save([]byte) error | |||
Load() ([]byte, error) | |||
Write([]byte) error | |||
Read() ([]byte, error) | |||
Delete() | |||
ChangePassword(newpass string) | |||
} | |||
// NewFileStore instantiates a fileStore given a filename and a password | |||
@@ -27,8 +31,8 @@ func NewFileStore(directory string, filename string, password string) FileStore | |||
return filestore | |||
} | |||
// save serializes a cwtchPeer to a file | |||
func (fps *fileStore) Save(data []byte) error { | |||
// write serializes a cwtchPeer to a file | |||
func (fps *fileStore) Write(data []byte) error { | |||
key, salt, _ := createKey(fps.password) | |||
encryptedbytes, err := encryptFileData(data, key) | |||
if err != nil { | |||
@@ -39,9 +43,19 @@ func (fps *fileStore) Save(data []byte) error { | |||
encryptedbytes = append(salt[:], encryptedbytes...) | |||
err = ioutil.WriteFile(path.Join(fps.directory, fps.filename), encryptedbytes, 0600) | |||
return err | |||
} | |||
func (fps *fileStore) Load() ([]byte, error) { | |||
func (fps *fileStore) Read() ([]byte, error) { | |||
return readEncryptedFile(fps.directory, fps.filename, fps.password) | |||
} | |||
func (fps *fileStore) Delete() { | |||
err := os.Remove(path.Join(fps.directory, fps.filename)) | |||
if err != nil { | |||
log.Errorf("Deleting file %v\n", err) | |||
} | |||
} | |||
func (fps *fileStore) ChangePassword(newpass string) { | |||
fps.password = newpass | |||
} |
@@ -9,11 +9,13 @@ import ( | |||
"time" | |||
) | |||
const groupIDLen = 32 | |||
const peerIDLen = 56 | |||
const profileFilename = "profile" | |||
type profileStore struct { | |||
fs FileStore | |||
streamStores map[string]StreamStore | |||
streamStores map[string]StreamStore // map [groupId|onion] StreamStore | |||
directory string | |||
password string | |||
profile *model.Profile | |||
@@ -26,6 +28,7 @@ type profileStore struct { | |||
type ProfileStore interface { | |||
Load() error | |||
Shutdown() | |||
Delete() | |||
GetProfileCopy(timeline bool) *model.Profile | |||
GetNewPeerMessage() *event.Event | |||
GetStatusMessages() []*event.Event | |||
@@ -58,6 +61,7 @@ func NewProfileWriterStore(eventManager event.Manager, directory, password strin | |||
ps.eventManager.Subscribe(event.ServerStateChange, ps.queue) | |||
ps.eventManager.Subscribe(event.DeleteContact, ps.queue) | |||
ps.eventManager.Subscribe(event.DeleteGroup, ps.queue) | |||
ps.eventManager.Subscribe(event.ChangePassword, ps.queue) | |||
return ps | |||
} | |||
@@ -115,17 +119,64 @@ func (ps *profileStore) GetStatusMessages() []*event.Event { | |||
return messages | |||
} | |||
func (ps *profileStore) ChangePassword(oldpass, newpass, eventID string) { | |||
if oldpass != ps.password { | |||
ps.eventManager.Publish(event.NewEventList(event.ChangePasswordError, event.Error, "Supplied current password does not match", event.EventID, eventID)) | |||
return | |||
} | |||
newStreamStores := map[string]StreamStore{} | |||
idToNewLocalID := map[string]string{} | |||
// Generate all new StreamStores with the new password and write all the old StreamStore data into these ones | |||
for ssid, ss := range ps.streamStores { | |||
// New ss with new pass and new localID | |||
newlocalID := model.GenerateRandomID() | |||
idToNewLocalID[ssid] = newlocalID | |||
newSS := NewStreamStore(ps.directory, newlocalID, newpass) | |||
newStreamStores[ssid] = newSS | |||
// write whole store | |||
messages := ss.Read() | |||
newSS.WriteN(messages) | |||
} | |||
// Switch over | |||
oldStreamStores := ps.streamStores | |||
ps.streamStores = newStreamStores | |||
for ssid, newLocalID := range idToNewLocalID { | |||
if len(ssid) == groupIDLen { | |||
ps.profile.Groups[ssid].LocalID = newLocalID | |||
} else { | |||
ps.profile.Contacts[ssid].LocalID = newLocalID | |||
} | |||
} | |||
ps.password = newpass | |||
ps.fs.ChangePassword(newpass) | |||
ps.save() | |||
// Clean up | |||
for _, oldss := range oldStreamStores { | |||
oldss.Delete() | |||
} | |||
ps.eventManager.Publish(event.NewEventList(event.ChangePasswordSuccess, event.EventID, eventID)) | |||
return | |||
} | |||
func (ps *profileStore) save() error { | |||
if ps.writer { | |||
bytes, _ := json.Marshal(ps.profile) | |||
return ps.fs.Save(bytes) | |||
return ps.fs.Write(bytes) | |||
} | |||
return nil | |||
} | |||
// Load instantiates a cwtchPeer from the file store | |||
// Read instantiates a cwtchPeer from the file store | |||
func (ps *profileStore) Load() error { | |||
decrypted, err := ps.fs.Load() | |||
decrypted, err := ps.fs.Read() | |||
if err != nil { | |||
return err | |||
} | |||
@@ -136,6 +187,7 @@ func (ps *profileStore) Load() error { | |||
for gid, group := range cp.Groups { | |||
ss := NewStreamStore(ps.directory, group.LocalID, ps.password) | |||
cp.Groups[gid].Timeline.SetMessages(ss.Read()) | |||
ps.streamStores[group.GroupID] = ss | |||
} | |||
@@ -149,8 +201,11 @@ func (ps *profileStore) GetProfileCopy(timeline bool) *model.Profile { | |||
} | |||
func (ps *profileStore) eventHandler() { | |||
log.Infoln("eventHandler()!") | |||
for { | |||
ev := ps.queue.Next() | |||
log.Infof("eventHandler event %v\n", ev) | |||
switch ev.EventType { | |||
case event.BlockPeer: | |||
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer]) | |||
@@ -253,6 +308,10 @@ func (ps *profileStore) eventHandler() { | |||
ss.Delete() | |||
delete(ps.streamStores, groupID) | |||
} | |||
case event.ChangePassword: | |||
oldpass := ev.Data[event.Password] | |||
newpass := ev.Data[event.NewPassword] | |||
ps.ChangePassword(oldpass, newpass, ev.EventID) | |||
default: | |||
return | |||
} | |||
@@ -265,3 +324,18 @@ func (ps *profileStore) Shutdown() { | |||
ps.queue.Shutdown() | |||
} | |||
} | |||
func (ps *profileStore) Delete() { | |||
log.Debugf("Delete ProfileStore for %v\n", ps.profile.Onion) | |||
for _, ss := range ps.streamStores { | |||
ss.Delete() | |||
} | |||
ps.fs.Delete() | |||
err := os.RemoveAll(ps.directory) | |||
if err != nil { | |||
log.Errorf("ProfileStore Delete error on RemoveAll on %v was %v\n", ps.directory, err) | |||
} | |||
} |
@@ -4,6 +4,8 @@ package storage | |||
import ( | |||
"cwtch.im/cwtch/event" | |||
"fmt" | |||
"log" | |||
"os" | |||
"testing" | |||
"time" | |||
@@ -16,6 +18,7 @@ const testInitialMessage = "howdy" | |||
const testMessage = "Hello from storage" | |||
func TestProfileStoreWriteRead(t *testing.T) { | |||
log.Println("profile store test!") | |||
os.RemoveAll(testingDir) | |||
eventBus := event.NewEventManager() | |||
profile := NewProfile(testProfileName) | |||
@@ -68,3 +71,82 @@ func TestProfileStoreWriteRead(t *testing.T) { | |||
} | |||
} | |||
func TestProfileStoreChangePassword(t *testing.T) { | |||
os.RemoveAll(testingDir) | |||
eventBus := event.NewEventManager() | |||
queue := event.NewQueue() | |||
eventBus.Subscribe(event.ChangePasswordSuccess, queue) | |||
profile := NewProfile(testProfileName) | |||
ps1 := NewProfileWriterStore(eventBus, testingDir, password, profile) | |||
groupid, invite, err := profile.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") | |||
if err != nil { | |||
t.Errorf("Creating group: %v\n", err) | |||
} | |||
if err != nil { | |||
t.Errorf("Creating group invite: %v\n", err) | |||
} | |||
eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)})) | |||
time.Sleep(1 * time.Second) | |||
fmt.Println("Sending 200 messages...") | |||
for i := 0; i < 200; i++ { | |||
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{ | |||
event.GroupID: groupid, | |||
event.TimestampSent: time.Now().Format(time.RFC3339Nano), | |||
event.TimestampReceived: time.Now().Format(time.RFC3339Nano), | |||
event.RemotePeer: ps1.GetProfileCopy(true).Onion, | |||
event.Data: testMessage, | |||
})) | |||
} | |||
newPass := "qwerty123" | |||
fmt.Println("Sending Change Passwords event...") | |||
eventBus.Publish(event.NewEventList(event.ChangePassword, event.Password, password, event.NewPassword, newPass)) | |||
ev := queue.Next() | |||
if ev.EventType != event.ChangePasswordSuccess { | |||
t.Errorf("Unexpected event response detected %v\n", ev.EventType) | |||
return | |||
} | |||
fmt.Println("Sending 10 more messages...") | |||
for i := 0; i < 10; i++ { | |||
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{ | |||
event.GroupID: groupid, | |||
event.TimestampSent: time.Now().Format(time.RFC3339Nano), | |||
event.TimestampReceived: time.Now().Format(time.RFC3339Nano), | |||
event.RemotePeer: ps1.GetProfileCopy(true).Onion, | |||
event.Data: testMessage, | |||
})) | |||
} | |||
time.Sleep(3 * time.Second) | |||
fmt.Println("Shutdown profile store...") | |||
ps1.Shutdown() | |||
fmt.Println("New Profile store...") | |||
ps2 := NewProfileWriterStore(eventBus, testingDir, newPass, nil) | |||
err = ps2.Load() | |||
if err != nil { | |||
t.Errorf("Error createing new profileStore with new password: %v\n", err) | |||
return | |||
} | |||
profile2 := ps2.GetProfileCopy(true) | |||
if profile2.Groups[groupid] == nil { | |||
t.Errorf("Failed to load group %v\n", groupid) | |||
return | |||
} | |||
if len(profile2.Groups[groupid].Timeline.Messages) != 210 { | |||
t.Errorf("Failed to load group's 210 messages, instead got %v\n", len(profile2.Groups[groupid].Timeline.Messages)) | |||
} | |||
} |
@@ -33,8 +33,10 @@ type streamStore struct { | |||
// StreamStore provides a stream like interface to encrypted storage | |||
type StreamStore interface { | |||
Write(message model.Message) | |||
WriteN(messages []model.Message) | |||
Read() []model.Message | |||
Delete() | |||
ChangePassword(newpass string) | |||
} | |||
// NewStreamStore returns an initialized StreamStore ready for reading and writing | |||
@@ -138,7 +140,7 @@ func (ss *streamStore) Read() (messages []model.Message) { | |||
return resp | |||
} | |||
// AddMessage adds a GroupMessage to the store | |||
// Write adds a GroupMessage to the store | |||
func (ss *streamStore) Write(m model.Message) { | |||
ss.lock.Lock() | |||
defer ss.lock.Unlock() | |||
@@ -151,3 +153,21 @@ func (ss *streamStore) Write(m model.Message) { | |||
ss.initBuffer() | |||
} | |||
} | |||
func (ss *streamStore) WriteN(messages []model.Message) { | |||
ss.lock.Lock() | |||
defer ss.lock.Unlock() | |||
for _, m := range messages { | |||
ss.updateBuffer(m) | |||
if ss.bufferByteCount > bytesPerFile { | |||
ss.updateFile() | |||
log.Debugf("rotating log file") | |||
ss.rotateFileStore() | |||
ss.initBuffer() | |||
} | |||
} | |||
} | |||
func (ss *streamStore) ChangePassword(newpass string) {} |
@@ -2,7 +2,6 @@ package storage | |||
import ( | |||
"cwtch.im/cwtch/model" | |||
"git.openprivacy.ca/openprivacy/libricochet-go/log" | |||
"os" | |||
"testing" | |||
) | |||
@@ -13,9 +12,6 @@ const password = "asdfqwer" | |||
const line1 = "Hello from storage!" | |||
func TestStreamStoreWriteRead(t *testing.T) { | |||
log.SetLevel(log.LevelDebug) | |||
os.Remove(".test.json") | |||
os.RemoveAll(testingDir) | |||
os.Mkdir(testingDir, 0777) | |||
@@ -34,8 +30,6 @@ func TestStreamStoreWriteRead(t *testing.T) { | |||
} | |||
func TestStreamStoreWriteReadRotate(t *testing.T) { | |||
log.SetLevel(log.LevelDebug) | |||
os.Remove(".test.json") | |||
os.RemoveAll(testingDir) | |||
os.Mkdir(testingDir, 0777) | |||