FileSharing Experiments / Move Experiment Handling to App and Cwtch Peer

This commit is contained in:
Sarah Jamie Lewis 2023-01-25 12:32:26 -08:00
parent 26c5c11216
commit f246ea1e40
16 changed files with 391 additions and 79 deletions

View File

@ -31,6 +31,8 @@ type application struct {
engines map[string]connections.Engine
appBus event.Manager
appmutex sync.Mutex
settings *GlobalSettingsFile
}
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
@ -52,6 +54,9 @@ type Application interface {
ActivatePeerEngine(onion string, doListen, doPeers, doServers bool)
DeactivatePeerEngine(onion string)
ReadSettings() GlobalSettings
UpdateSettings(settings GlobalSettings)
ShutdownPeer(string)
Shutdown()
@ -67,7 +72,15 @@ func NewApp(acn connectivity.ACN, appDirectory string) Application {
log.Debugf("NewApp(%v)\n", appDirectory)
os.MkdirAll(path.Join(appDirectory, "profiles"), 0700)
app := &application{engines: make(map[string]connections.Engine), eventBuses: make(map[string]event.Manager), directory: appDirectory, appBus: event.NewEventManager()}
// Note: we basically presume this doesn't fail. If the file doesn't exist we create it, and as such the
// only plausible error conditions are related to file create e.g. low disk space. If that is the case then
// many other parts of Cwtch are likely to fail also.
settings, err := InitGlobalSettingsFile(appDirectory, DefactoPasswordForUnencryptedProfiles)
if err != nil {
log.Errorf("error initializing global settings file %. Global settings might not be loaded or saves", err)
}
app := &application{engines: make(map[string]connections.Engine), eventBuses: make(map[string]event.Manager), directory: appDirectory, appBus: event.NewEventManager(), settings: settings}
app.peers = make(map[string]peer.CwtchPeer)
app.acn = acn
@ -80,6 +93,26 @@ func NewApp(acn connectivity.ACN, appDirectory string) Application {
return app
}
func (app *application) ReadSettings() GlobalSettings {
app.appmutex.Lock()
defer app.appmutex.Unlock()
return app.settings.ReadGlobalSettings()
}
func (app *application) UpdateSettings(settings GlobalSettings) {
// don't allow any other application changes while settings update
app.appmutex.Lock()
defer app.appmutex.Unlock()
app.settings.WriteGlobalSettings(settings)
// we now need to propagate changes to all peers
app.peerLock.Lock()
defer app.peerLock.Unlock()
for _, profile := range app.peers {
profile.UpdateExperiments(settings.ExperimentsEnabled, settings.Experiments)
}
}
// ListProfiles returns a map of onions to their profile's Name
func (app *application) ListProfiles() []string {
var keys []string
@ -160,7 +193,7 @@ func (app *application) CreatePeer(name string, password string, attributes map[
}
func (app *application) DeletePeer(onion string, password string) {
log.Infof("DeletePeer called on %v\n", onion)
log.Debugf("DeletePeer called on %v\n", onion)
app.appmutex.Lock()
defer app.appmutex.Unlock()
@ -249,7 +282,6 @@ func (app *application) registerHooks(profile peer.CwtchPeer) {
// Register Hooks
profile.RegisterHook(extensions.ProfileValueExtension{})
profile.RegisterHook(filesharing.Functionality{})
}
// installProfile takes a profile and if it isn't loaded in the app, installs it and returns true

6
app/app_constants.go Normal file
View File

@ -0,0 +1,6 @@
package app
// We offer "un-passworded" profiles but our storage encrypts everything with a password. We need an agreed upon
// password to use in that case, that the app case use behind the scenes to password and unlock with
// https://docs.openprivacy.ca/cwtch-security-handbook/profile_encryption_and_storage.html
const DefactoPasswordForUnencryptedProfiles = "be gay do crime"

View File

@ -27,7 +27,7 @@ func (a antispam) Shutdown() {
}
func (a *antispam) run() {
log.Infof("running antispam trigger plugin")
log.Debugf("running antispam trigger plugin")
for {
select {
case <-time.After(antispamTickTime):

146
app/settings.go Normal file
View File

@ -0,0 +1,146 @@
package app
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model/constants"
"cwtch.im/cwtch/storage/v1"
"encoding/json"
"git.openprivacy.ca/openprivacy/log"
"os"
path "path/filepath"
)
const (
CwtchStarted = event.Type("CwtchStarted")
CwtchStartError = event.Type("CwtchStartError")
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
)
const GlobalSettingsFilename = "ui.globals"
const saltFile = "SALT"
type NotificationPolicy string
const (
NotificationPolicyMute = NotificationPolicy("NotificationPolicy.Mute")
NotificationPolicyOptIn = NotificationPolicy("NotificationPolicy.OptIn")
NotificationPolicyDefaultAll = NotificationPolicy("NotificationPolicy.DefaultAll")
)
type GlobalSettingsFile struct {
v1.FileStore
}
type GlobalSettings struct {
Locale string
Theme string
ThemeMode string
PreviousPid int64
ExperimentsEnabled bool
Experiments map[string]bool
BlockUnknownConnections bool
NotificationPolicy NotificationPolicy
NotificationContent string
StreamerMode bool
StateRootPane int
FirstTime bool
UIColumnModePortrait string
UIColumnModeLandscape string
DownloadPath string
AllowAdvancedTorConfig bool
CustomTorrc string
UseCustomTorrc bool
UseExternalTor bool
CustomSocksPort int
CustomControlPort int
UseTorCache bool
TorCacheDir string
}
var DefaultGlobalSettings = GlobalSettings{
Locale: "en",
Theme: "dark",
PreviousPid: -1,
ExperimentsEnabled: false,
Experiments: map[string]bool{constants.MessageFormattingExperiment: true},
StateRootPane: 0,
FirstTime: true,
BlockUnknownConnections: false,
StreamerMode: false,
UIColumnModePortrait: "DualpaneMode.Single",
UIColumnModeLandscape: "DualpaneMode.CopyPortrait",
NotificationPolicy: "NotificationPolicy.Mute",
NotificationContent: "NotificationContent.SimpleEvent",
DownloadPath: "",
AllowAdvancedTorConfig: false,
CustomTorrc: "",
UseCustomTorrc: false,
CustomSocksPort: -1,
CustomControlPort: -1,
UseTorCache: false,
TorCacheDir: "",
}
func InitGlobalSettingsFile(directory string, password string) (*GlobalSettingsFile, error) {
var key [32]byte
salt, err := os.ReadFile(path.Join(directory, saltFile))
if err != nil {
log.Infof("Could not find salt file: %v (creating a new settings file)", err)
var newSalt [128]byte
key, newSalt, err = v1.CreateKeySalt(password)
if err != nil {
log.Errorf("Could not initialize salt: %v", err)
return nil, err
}
os.Mkdir(directory, 0700)
err := os.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
if err != nil {
log.Errorf("Could not write salt file: %v", err)
return nil, err
}
} else {
key = v1.CreateKey(password, salt)
}
gsFile := v1.NewFileStore(directory, GlobalSettingsFilename, key)
log.Infof("initialized global settings file: %v", gsFile)
globalSettingsFile := GlobalSettingsFile{
gsFile,
}
return &globalSettingsFile, nil
}
func (globalSettingsFile *GlobalSettingsFile) ReadGlobalSettings() GlobalSettings {
settings := DefaultGlobalSettings
if globalSettingsFile == nil {
log.Errorf("Global Settings File was not Initialized Properly")
return settings
}
settingsBytes, err := globalSettingsFile.Read()
if err != nil {
log.Infof("Could not read global ui settings: %v (assuming this is a first time app deployment...)", err)
return settings //firstTime = true
}
err = json.Unmarshal(settingsBytes, &settings)
if err != nil {
log.Errorf("Could not parse global ui settings: %v\n", err)
// TODO if settings is corrupted, we probably want to alert the UI.
return settings //firstTime = true
}
log.Debugf("Settings: %#v", settings)
return settings
}
func (globalSettingsFile *GlobalSettingsFile) WriteGlobalSettings(globalSettings GlobalSettings) {
bytes, _ := json.Marshal(globalSettings)
// override first time setting
globalSettings.FirstTime = true
err := globalSettingsFile.Write(bytes)
if err != nil {
log.Errorf("Could not write global ui settings: %v\n", err)
}
}

View File

@ -41,7 +41,7 @@ func (pne ProfileValueExtension) OnContactReceiveValue(profile peer.CwtchPeer, c
// OnContactRequestValue for ProfileValueExtension handles returning Public Profile Values
func (pne ProfileValueExtension) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, szp attr.ScopedZonedPath) {
scope, zone, zpath := szp.GetScopeZonePath()
log.Infof("Looking up public | conversation scope/zone %v", szp.ToString())
log.Debugf("Looking up public | conversation scope/zone %v", szp.ToString())
if scope.IsPublic() || scope.IsConversation() {
val, exists := profile.GetScopedZonedAttribute(scope, zone, zpath)

View File

@ -40,66 +40,70 @@ func (f Functionality) RegisterExperiments() []string {
// OnEvent handles File Sharing Hooks like Manifest Received and FileDownloaded
func (f Functionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
switch ev.EventType {
case event.ManifestReceived:
log.Debugf("Manifest Received Event!: %v", ev)
handle := ev.Data[event.Handle]
fileKey := ev.Data[event.FileKey]
serializedManifest := ev.Data[event.SerializedManifest]
if profile.IsFeatureEnabled(constants.FileSharingExperiment) {
switch ev.EventType {
case event.ManifestReceived:
log.Debugf("Manifest Received Event!: %v", ev)
handle := ev.Data[event.Handle]
fileKey := ev.Data[event.FileKey]
serializedManifest := ev.Data[event.SerializedManifest]
manifestFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.manifest", fileKey))
if exists {
downloadFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.path", fileKey))
manifestFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.manifest", fileKey))
if exists {
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
var manifest files.Manifest
err := json.Unmarshal([]byte(serializedManifest), &manifest)
downloadFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.path", fileKey))
if exists {
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
var manifest files.Manifest
err := json.Unmarshal([]byte(serializedManifest), &manifest)
if err == nil {
// We only need to check the file size here, as manifest is sent to engine and the file created
// will be bound to the size advertised in manifest.
fileSizeLimitValue, fileSizeLimitExists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.limit", fileKey))
if fileSizeLimitExists {
fileSizeLimit, err := strconv.ParseUint(fileSizeLimitValue, 10, bits.UintSize)
if err == nil {
if manifest.FileSizeInBytes >= fileSizeLimit {
log.Errorf("could not download file, size %v greater than limit %v", manifest.FileSizeInBytes, fileSizeLimitValue)
} else {
manifest.Title = manifest.FileName
manifest.FileName = downloadFilePath
log.Debugf("saving manifest")
err = manifest.Save(manifestFilePath)
if err != nil {
log.Errorf("could not save manifest: %v", err)
if err == nil {
// We only need to check the file size here, as manifest is sent to engine and the file created
// will be bound to the size advertised in manifest.
fileSizeLimitValue, fileSizeLimitExists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.limit", fileKey))
if fileSizeLimitExists {
fileSizeLimit, err := strconv.ParseUint(fileSizeLimitValue, 10, bits.UintSize)
if err == nil {
if manifest.FileSizeInBytes >= fileSizeLimit {
log.Errorf("could not download file, size %v greater than limit %v", manifest.FileSizeInBytes, fileSizeLimitValue)
} else {
tempFile := ""
if runtime.GOOS == "android" {
tempFile = manifestFilePath[0 : len(manifestFilePath)-len(".manifest")]
log.Debugf("derived android temp path: %v", tempFile)
manifest.Title = manifest.FileName
manifest.FileName = downloadFilePath
log.Debugf("saving manifest")
err = manifest.Save(manifestFilePath)
if err != nil {
log.Errorf("could not save manifest: %v", err)
} else {
tempFile := ""
if runtime.GOOS == "android" {
tempFile = manifestFilePath[0 : len(manifestFilePath)-len(".manifest")]
log.Debugf("derived android temp path: %v", tempFile)
}
profile.PublishEvent(event.NewEvent(event.ManifestSaved, map[event.Field]string{
event.FileKey: fileKey,
event.Handle: handle,
event.SerializedManifest: string(manifest.Serialize()),
event.TempFile: tempFile,
event.NameSuggestion: manifest.Title,
}))
}
profile.PublishEvent(event.NewEvent(event.ManifestSaved, map[event.Field]string{
event.FileKey: fileKey,
event.Handle: handle,
event.SerializedManifest: string(manifest.Serialize()),
event.TempFile: tempFile,
event.NameSuggestion: manifest.Title,
}))
}
}
}
} else {
log.Errorf("error saving manifest: %v", err)
}
} else {
log.Errorf("error saving manifest: %v", err)
log.Errorf("found manifest path but not download path for %v", fileKey)
}
} else {
log.Errorf("found manifest path but not download path for %v", fileKey)
log.Errorf("no download path found for manifest: %v", fileKey)
}
} else {
log.Errorf("no download path found for manifest: %v", fileKey)
case event.FileDownloaded:
fileKey := ev.Data[event.FileKey]
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey), "true")
}
case event.FileDownloaded:
fileKey := ev.Data[event.FileKey]
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey), "true")
} else {
log.Errorf("profile called filesharing experiment OnContactReceiveValue even though file sharing was not enabled. This is likely a programming error.")
}
}
@ -108,21 +112,25 @@ func (f Functionality) OnContactRequestValue(profile peer.CwtchPeer, conversatio
}
func (f Functionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
scope, zone, zpath := path.GetScopeZonePath()
log.Infof("file sharing contact receive value")
if exists && scope.IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(zpath, ".manifest.size") {
fileKey := strings.Replace(zpath, ".manifest.size", "", 1)
size, err := strconv.Atoi(value)
// if size is valid and below the maximum size for a manifest
// this is to prevent malicious sharers from using large amounts of memory when distributing
// a manifest as we reconstruct this in-memory
if err == nil && size < files.MaxManifestSize {
profile.PublishEvent(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: value, event.Handle: conversation.Handle}))
} else {
profile.PublishEvent(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: conversation.Handle}))
// Profile should not call us if FileSharing is disabled
if profile.IsFeatureEnabled(constants.FileSharingExperiment) {
scope, zone, zpath := path.GetScopeZonePath()
log.Debugf("file sharing contact receive value")
if exists && scope.IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(zpath, ".manifest.size") {
fileKey := strings.Replace(zpath, ".manifest.size", "", 1)
size, err := strconv.Atoi(value)
// if size is valid and below the maximum size for a manifest
// this is to prevent malicious sharers from using large amounts of memory when distributing
// a manifest as we reconstruct this in-memory
if err == nil && size < files.MaxManifestSize {
profile.PublishEvent(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: value, event.Handle: conversation.Handle}))
} else {
profile.PublishEvent(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: conversation.Handle}))
}
}
} else {
log.Errorf("profile called filesharing experiment OnContactReceiveValue even though file sharing was not enabled. This is likely a programming error.")
}
}
// FunctionalityGate returns filesharing if enabled in the given experiment map
@ -174,6 +182,11 @@ func (om *OverlayMessage) ShouldAutoDL() bool {
// to downloadFilePath
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, conversationID int, downloadFilePath string, manifestFilePath string, key string, limit uint64) error {
// assert that we are allowed to download the file
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
return errors.New("filesharing functionality is not enabled")
}
// Don't download files if the download or manifest path is not set
if downloadFilePath == "" || manifestFilePath == "" {
return errors.New("download path or manifest path is empty")
@ -225,6 +238,12 @@ func (f *Functionality) startFileShare(profile peer.CwtchPeer, filekey string, m
// RestartFileShare takes in an existing filekey and, assuming the manifest exists, restarts sharing of the manifest
func (f *Functionality) RestartFileShare(profile peer.CwtchPeer, filekey string) error {
// assert that we are allowed to restart filesharing
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
return errors.New("filesharing functionality is not enabled")
}
// check that a manifest exists
manifest, manifestExists := profile.GetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", filekey))
if manifestExists {
@ -238,6 +257,12 @@ func (f *Functionality) RestartFileShare(profile peer.CwtchPeer, filekey string)
// ReShareFiles given a profile we iterate through all existing fileshares and re-share them
// if the time limit has not expired
func (f *Functionality) ReShareFiles(profile peer.CwtchPeer) error {
// assert that we are allowed to restart filesharing
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
return errors.New("filesharing functionality is not enabled")
}
keys, err := profile.GetScopedZonedAttributeKeys(attr.LocalScope, attr.FilesharingZone)
if err != nil {
return err
@ -304,6 +329,12 @@ func (f *Functionality) GetFileShareInfo(profile peer.CwtchPeer, filekey string)
// ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file
// at filepath
func (f *Functionality) ShareFile(filepath string, profile peer.CwtchPeer) (string, string, error) {
// assert that we are allowed to share files
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
return "", "", errors.New("filesharing functionality is not enabled")
}
manifest, err := files.CreateManifest(filepath)
if err != nil {
return "", "", err
@ -446,6 +477,7 @@ func GenerateDownloadPath(basePath, fileName string, overwrite bool) (filePath,
// StopFileShare sends a message to the ProtocolEngine to cease sharing a particular file
func (f *Functionality) StopFileShare(profile peer.CwtchPeer, fileKey string) {
// Note we do not do a permissions check here, as we are *always* permitted to stop sharing files.
// set the filekey status to inactive
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.active", fileKey), constants.False)
profile.PublishEvent(event.NewEvent(event.StopFileShare, map[event.Field]string{event.FileKey: fileKey}))
@ -453,5 +485,6 @@ func (f *Functionality) StopFileShare(profile peer.CwtchPeer, fileKey string) {
// StopAllFileShares sends a message to the ProtocolEngine to cease sharing all files
func (f *Functionality) StopAllFileShares(profile peer.CwtchPeer) {
// Note we do not do a permissions check here, as we are *always* permitted to stop sharing files.
profile.PublishEvent(event.NewEvent(event.StopAllFileShares, map[event.Field]string{}))
}

View File

@ -10,5 +10,7 @@ const ImagePreviewsExperiment = "filesharing-images"
// ImagePreviewMaxSizeInBytes Files up to this size will be autodownloaded using ImagePreviewsExperiment
const ImagePreviewMaxSizeInBytes = 20971520
const MessageFormattingExperiment = "message-formatting"
// AutoDLFileExts Files with these extensions will be autodownloaded using ImagePreviewsExperiment
var AutoDLFileExts = [...]string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}

32
model/experiments.go Normal file
View File

@ -0,0 +1,32 @@
package model
// Experiments are optional functionality that can be enabled/disabled by an application either completely or individually.
// examples of experiments include File Sharing, Profile Images and Groups.
type Experiments struct {
enabled bool
experiments map[string]bool
}
// InitExperiments encapsulates a set of experiments separate from their storage in GlobalSettings.
func InitExperiments(enabled bool, experiments map[string]bool) Experiments {
return Experiments{
enabled: enabled,
experiments: experiments,
}
}
// IsEnabled is a convenience function that takes in an experiment and returns true if it is enabled. Experiments
// are only enabled if both global experiments are turned on and if the specific experiment is also turned on.
// The one exception to this is experiments that have been promoted to default functionality which may be turned on
// even if experiments turned off globally. These experiments are defined by DefaultEnabledFunctionality.
func (e *Experiments) IsEnabled(experiment string) bool {
if !e.enabled {
// todo handle default-enabled functionality
return false
}
enabled, exists := e.experiments[experiment]
if !exists {
return false
}
return enabled
}

View File

@ -69,11 +69,30 @@ type cwtchPeer struct {
queue event.Queue
eventBus event.Manager
extensions []ProfileHook
extensionLock sync.Mutex // we don't want to hold up all of cwtch for managing thread safe access to extensions
extensions []ProfileHook
extensionLock sync.Mutex // we don't want to hold up all of cwtch for managing thread safe access to extensions
experiments model.Experiments
experimentsLock sync.Mutex
}
// IsFeatureEnabled returns true if the functionality defined by featureName has been enabled by the application, false otherwise.
// this function is intended to be used by ProfileHooks to determine if they should execute experimental functionality.
func (cp *cwtchPeer) IsFeatureEnabled(featureName string) bool {
cp.experimentsLock.Lock()
defer cp.experimentsLock.Unlock()
return cp.experiments.IsEnabled(featureName)
}
// UpdateExperiments notifies a Cwtch profile of a change in the nature of global experiments. The Cwtch Profile uses
// this information to update registered extensions.
func (cp *cwtchPeer) UpdateExperiments(enabled bool, experiments map[string]bool) {
cp.experimentsLock.Lock()
defer cp.experimentsLock.Unlock()
cp.experiments = model.InitExperiments(enabled, experiments)
}
func (cp *cwtchPeer) PublishEvent(resp event.Event) {
log.Debugf("Publishing Event: %v %v", resp.EventType, resp.Data)
cp.eventBus.Publish(resp)
}
@ -154,7 +173,7 @@ func (cp *cwtchPeer) ChangePassword(password string, newpassword string, newpass
// probably redundant but we like api safety
if newpassword == newpasswordAgain {
rekey := createKey(newpassword, salt)
log.Infof("rekeying database...")
log.Debugf("rekeying database...")
return cp.storage.Rekey(rekey)
}
return errors.New(constants.PasswordsDoNotMatchError)
@ -1286,9 +1305,15 @@ func (cp *cwtchPeer) eventHandler() {
// Safe Access to Extensions
cp.extensionLock.Lock()
log.Infof("checking extension...%v", cp.extensions)
log.Debugf("checking extension...%v", cp.extensions)
for _, extension := range cp.extensions {
log.Infof("checking extension...%v", extension)
log.Debugf("checking extension...%v", extension)
// check if the current map of experiments satisfies the extension requirements
if !cp.checkExtensionExperiment(extension) {
log.Debugf("skipping extension...not all experiments satisfied")
continue
}
extension.extension.OnContactRequestValue(cp, *conversationInfo, ev.EventID, scopedZonedPath)
}
cp.extensionLock.Unlock()
@ -1313,6 +1338,12 @@ func (cp *cwtchPeer) eventHandler() {
// Safe Access to Extensions
cp.extensionLock.Lock()
for _, extension := range cp.extensions {
log.Debugf("checking extension...%v", extension)
// check if the current map of experiments satisfies the extension requirements
if !cp.checkExtensionExperiment(extension) {
log.Debugf("skipping extension...not all experiments satisfied")
continue
}
extension.extension.OnContactReceiveValue(cp, *conversationInfo, scopedZonedPath, val, exists)
}
cp.extensionLock.Unlock()
@ -1406,6 +1437,13 @@ func (cp *cwtchPeer) eventHandler() {
processed := false
cp.extensionLock.Lock()
for _, extension := range cp.extensions {
// check if the current map of experiments satisfies the extension requirements
if !cp.checkExtensionExperiment(extension) {
log.Debugf("skipping extension...not all experiments satisfied")
continue
}
// if the extension is registered for this event type then process
if _, contains := extension.events[ev.EventType]; contains {
extension.extension.OnEvent(ev, cp)
@ -1421,6 +1459,17 @@ func (cp *cwtchPeer) eventHandler() {
}
}
func (cp *cwtchPeer) checkExtensionExperiment(hook ProfileHook) bool {
cp.experimentsLock.Lock()
defer cp.experimentsLock.Unlock()
for experiment := range hook.experiments {
if !cp.experiments.IsEnabled(experiment) {
return false
}
}
return true
}
// attemptInsertOrAcknowledgeLegacyGroupConversation is a convenience method that looks up the conversation
// by the given handle and attempts to mark the message as acknowledged. returns error on failure
// to either find the contact or the associated message

View File

@ -125,7 +125,9 @@ func NewCwtchProfileStorage(db *sql.DB, profileDirectory string) (*CwtchProfileS
insertProfileKeyValueStmt, err := db.Prepare(insertProfileKeySQLStmt)
if err != nil {
db.Close()
log.Errorf("error preparing query: %v %v", insertProfileKeySQLStmt, err)
// note: this is debug because we expect failure here when opening an encrypted database with an
// incorrect password. The rest are errors because failure is not expected.
log.Debugf("error preparing query: %v %v", insertProfileKeySQLStmt, err)
return nil, err
}
@ -772,7 +774,7 @@ func (cps *CwtchProfileStorage) PurgeNonSavedMessages() {
for _, conversation := range ci {
if !conversation.IsGroup() && !conversation.IsServer() {
if conversation.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)).ToString()] != event.SaveHistoryConfirmed {
log.Infof("purging conversation...")
log.Debugf("purging conversation...")
// TODO: At some point in the future this needs to iterate over channels and make a decision for each on..
cps.PurgeConversationChannel(conversation.ID, 0)
}

View File

@ -7,11 +7,10 @@ import (
)
type ProfileHooks interface {
// RegisterEvents returns a set of events that the extension is interested hooking
RegisterEvents() []event.Type
// RegisterExperiments RegisterExperiments returns a set of experiments that the extension is interested in being notified about
// RegisterExperiments returns a set of experiments that the extension is interested in being notified about
RegisterExperiments() []string
// OnEvent is called whenever an event Registered with RegisterEvents is called

View File

@ -132,4 +132,6 @@ type CwtchPeer interface {
Delete()
PublishEvent(resp event.Event)
RegisterHook(hook ProfileHooks)
UpdateExperiments(enabled bool, experiments map[string]bool)
IsFeatureEnabled(featureName string) bool
}

View File

@ -176,7 +176,7 @@ func ImportProfile(exportedCwtchFile string, profilesDir string, password string
log.Errorf("%s is an invalid cwtch backup file: %s", profileID, err)
return nil, err
}
log.Infof("%s is a valid cwtch backup file", profileID)
log.Debugf("%s is a valid cwtch backup file", profileID)
profileDBFile := filepath.Join(profilesDir, profileID, dbFile)
log.Debugf("checking %v", profileDBFile)

View File

@ -73,7 +73,7 @@ func (ps *ProfileStoreV1) load() error {
for gid, group := range cp.Groups {
if group.Version == 0 {
log.Infof("group %v is of unsupported version 0. dropping group...\n", group.GroupID)
log.Debugf("group %v is of unsupported version 0. dropping group...\n", group.GroupID)
delete(cp.Groups, gid)
continue
}

View File

@ -152,7 +152,7 @@ func (ss *streamStore) WriteN(messages []model.Message) {
ss.lock.Lock()
defer ss.lock.Unlock()
log.Infof("WriteN %v messages\n", len(messages))
log.Debugf("WriteN %v messages\n", len(messages))
i := 0
for _, m := range messages {
ss.updateBuffer(m)

View File

@ -118,13 +118,18 @@ func TestFileSharing(t *testing.T) {
app.ActivatePeerEngine(bob.GetOnion(), true, true, true)
alice.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer, event.ManifestReceived})
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
queueOracle := event.NewQueue()
app.GetEventBus(bob.GetOnion()).Subscribe(event.FileDownloaded, queueOracle)
t.Logf("** Launching Peers...")
// Turn on File Sharing Experiment...
settings := app.ReadSettings()
settings.ExperimentsEnabled = true
settings.Experiments[constants.FileSharingExperiment] = true
app.UpdateSettings(settings)
t.Logf("** Launching Peers...")
waitTime := time.Duration(30) * time.Second
t.Logf("** Waiting for Alice, Bob to connect with onion network... (%v)\n", waitTime)
time.Sleep(waitTime)
@ -163,7 +168,11 @@ func TestFileSharing(t *testing.T) {
// Restart
t.Logf("Restarting File Share")
filesharingFunctionality.ReShareFiles(alice)
err = filesharingFunctionality.ReShareFiles(alice)
if err != nil {
t.Fatalf("Error!: %v", err)
}
// run the same download test again...to check that we can actually download the file
testBobDownloadFile(t, bob, filesharingFunctionality, queueOracle)