Compare commits

...

24 Commits

Author SHA1 Message Date
Sarah Jamie Lewis 0e96539f22 Merge pull request 'Store Messages and Send when Online' (#553) from offline-messages into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #553
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-04-16 18:35:02 +00:00
Sarah Jamie Lewis e55f342324
Updating Logging -> Debug
continuous-integration/drone/pr Build is passing Details
2024-02-26 13:40:47 -08:00
Sarah Jamie Lewis 89aca91b37
Store Messages and Send when Online
continuous-integration/drone/pr Build is passing Details
2024-02-26 13:18:38 -08:00
Sarah Jamie Lewis cd918c02ea Merge pull request 'Fix Error in ACL-V1 that Prevented ShareFiles (for some)' (#552) from acl-v2 into master
continuous-integration/drone/push Build is passing Details
Reviewed-on: #552
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-02-26 17:26:17 +00:00
Sarah Jamie Lewis 05a198c89f
Fix Error in ACL-V1 that Prevented ShareFiles (for some)
continuous-integration/drone/pr Build is passing Details
Also aligns model.DeserializeAttributes to best practice
2024-02-24 12:51:19 -08:00
Sarah Jamie Lewis 1d9202ff93 Don't reject text messages
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is pending Details
2024-02-12 22:02:35 +00:00
Sarah Jamie Lewis 0907af57d5 Merge pull request 'Introduce Channel/Overlay Mappings' (#549) from overlays into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #549
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-02-11 23:10:59 +00:00
Sarah Jamie Lewis 826ac40a5c Stream check in engine
continuous-integration/drone/pr Build is pending Details
2024-02-11 14:45:11 -08:00
Sarah Jamie Lewis 1a034953df Util Functions for MW
continuous-integration/drone/pr Build is pending Details
2024-02-11 14:44:18 -08:00
Sarah Jamie Lewis 3124f7b7c4 MessageOverlay time to pointer
continuous-integration/drone/pr Build is pending Details
2024-02-11 13:56:19 -08:00
Sarah Jamie Lewis 792e79dceb Introduce Channel/Overlay Mappings
continuous-integration/drone/pr Build is failing Details
- Map channel 7 to ephemeral / no ack
- Create model methods
- Introduce optional latency measurements into Cwtch
2024-02-11 12:14:07 -08:00
Sarah Jamie Lewis 3e0680943a Prevent Duplicate Queue Subscription
continuous-integration/drone/pr Build is pending Details
continuous-integration/drone/push Build is failing Details
2024-02-09 13:16:23 -08:00
Sarah Jamie Lewis 9cb62d269e Merge pull request 'Fix non-image/preview downloads in Android' (#547) from android_file_download_fix into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #547
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-02-09 21:06:25 +00:00
Sarah Jamie Lewis ec71e56d23 Fix non-image/preview downloads in Android
continuous-integration/drone/pr Build is pending Details
2024-02-09 11:33:25 -08:00
Sarah Jamie Lewis aaabb12b6c Merge pull request 'First Cut of Enhanced Permissions' (#543) from enhanced-permissions into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #543
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-01-15 18:04:34 +00:00
Sarah Jamie Lewis b0a87ee8d0 Move comment for better understanding
continuous-integration/drone/pr Build is pending Details
2024-01-11 10:06:08 -08:00
Sarah Jamie Lewis d66beb95e5 Update APIs, Formatting
continuous-integration/drone/pr Build is pending Details
2024-01-11 10:02:27 -08:00
Sarah Jamie Lewis 41b3e20aff Remove Flakey Queued Check in Contact Retry Plugin Test
continuous-integration/drone/pr Build is passing Details
2024-01-08 13:25:53 -08:00
Sarah Jamie Lewis 1c7003fb96 First Draft of Enhanced Permissions API
continuous-integration/drone/pr Build is pending Details
2024-01-08 13:22:38 -08:00
Dan Ballard cb3b0b4c46 add new setting themeImages and fix default themeing
continuous-integration/drone/pr Build is pending Details
continuous-integration/drone/push Build is pending Details
2024-01-06 12:04:47 -08:00
Sarah Jamie Lewis a18c19bbf2 Fix Contact Retry Failure to Restart (#541)
continuous-integration/drone/push Build is pending Details
commit daea5128c0 (HEAD -> post-stable-fixes, origin/post-stable-fixes)
Author: Sarah Jamie Lewis <sarah@openprivacy.ca>
Date:   Tue Jan 2 12:45:39 2024 -0800

    Fixup Connection Test to check reconnecting status

commit 347ac3cf48
Author: Sarah Jamie Lewis <sarah@openprivacy.ca>
Date:   Tue Jan 2 12:33:31 2024 -0800

    Fixup Formatting and Quality Script

    ineffassign and misspell are no longer compatible with previous
    go workflows and the latest versions do not work. Commenting for
    now with intent to replace with better tooling.

commit d9ce7737cc
Author: Sarah Jamie Lewis <sarah@openprivacy.ca>
Date:   Tue Jan 2 12:24:33 2024 -0800

    Fix Contact Retry Failure to Restart

    When toggling between connected and disconnected, the Contact Retry plugin
    could find itself in a state where the new event would never get requeued.

    Also: Make the unsigned nature of limit in GetMessage* Apis explicit.

Reviewed-on: #541
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-01-02 23:17:59 +00:00
Sarah Jamie Lewis be4230d16e Merge pull request 'Small fixes pass with upgraded staticcheck and nilaway' (#539) from fixups into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #539
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2024-01-02 20:46:09 +00:00
Sarah Jamie Lewis 34957f809b Update ChunkSpec initialization
continuous-integration/drone/pr Build is failing Details
2023-11-19 14:45:08 -08:00
Sarah Jamie Lewis 456a5f5c4d Small fixes pass with upgraded staticcheck and nilaway
continuous-integration/drone/pr Build is failing Details
2023-11-18 11:51:27 -08:00
37 changed files with 609 additions and 279 deletions

View File

@ -5,27 +5,29 @@ name: linux-test
steps:
- name: fetch
image: golang:1.19.1
image: golang:1.21.5
volumes:
- name: deps
path: /go
commands:
- go install honnef.co/go/tools/cmd/staticcheck@latest
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
- chmod a+x tor
- go get -u golang.org/x/lint/golint
- go install go.uber.org/nilaway/cmd/nilaway@latest
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-0.4.8.9-linux-x86_64.tar.gz -O tor.tar.gz
- tar -xzf tor.tar.gz
- chmod a+x Tor/tor
- export PATH=$PWD/Tor/:$PATH
- export LD_LIBRARY_PATH=$PWD/Tor/
- tor --version
- export GO111MODULE=on
- go mod vendor
- name: quality
image: golang:1.19.1
image: golang:1.21.5
volumes:
- name: deps
path: /go
commands:
- staticcheck ./...
- ./testing/quality.sh
- name: units-tests
image: golang:1.19.1
image: golang:1.21.5
volumes:
- name: deps
path: /go
@ -33,28 +35,32 @@ steps:
- export PATH=`pwd`:$PATH
- sh testing/tests.sh
- name: integ-test
image: golang:1.19.1
image: golang:1.21.5
volumes:
- name: deps
path: /go
commands:
- export PATH=`pwd`:$PATH
- export PATH=$PWD/Tor/:$PATH
- export LD_LIBRARY_PATH=$PWD/Tor/
- tor --version
- go test -timeout=30m -race -v cwtch.im/cwtch/testing/
- name: filesharing-integ-test
image: golang:1.19.1
image: golang:1.21.5
volumes:
- name: deps
path: /go
commands:
- export PATH=`pwd`:$PATH
- export PATH=$PWD/Tor/:$PATH
- export LD_LIBRARY_PATH=$PWD/Tor/
- go test -timeout=20m -race -v cwtch.im/cwtch/testing/filesharing
- name: filesharing-autodownload-integ-test
image: golang:1.19.1
image: golang:1.21.5
volumes:
- name: deps
path: /go
commands:
- export PATH=`pwd`:$PATH
- export PATH=$PWD/Tor/:$PATH
- export LD_LIBRARY_PATH=$PWD/Tor/
- go test -timeout=20m -race -v cwtch.im/cwtch/testing/autodownload
- name: notify-gogs
image: openpriv/drone-gogs

4
.gitignore vendored
View File

@ -33,4 +33,6 @@ data-dir-cwtchtool/
tokens
tordir/
testing/autodownload/download_dir
testing/autodownload/storage
testing/autodownload/storage
*.swp
testing/managerstorage/*

View File

@ -217,7 +217,7 @@ func (app *application) setupPeer(profile peer.CwtchPeer) {
// Initialize the Peer with the Given Event Bus
app.peers[profile.GetOnion()] = profile
profile.Init(app.eventBuses[profile.GetOnion()])
profile.Init(eventBus)
// Update the Peer with the Most Recent Experiment State...
globalSettings := app.settings.ReadGlobalSettings()
@ -259,7 +259,8 @@ func (app *application) DeleteProfile(onion string, password string) {
defer app.appmutex.Unlock()
// short circuit to prevent nil-pointer panic if this function is called twice (or incorrectly)
if app.peers[onion] == nil {
peer := app.peers[onion]
if peer == nil {
log.Errorf("shutdownPeer called with invalid onion %v", onion)
return
}
@ -269,11 +270,11 @@ func (app *application) DeleteProfile(onion string, password string) {
password = DefactoPasswordForUnencryptedProfiles
}
if app.peers[onion].CheckPassword(password) {
if peer.CheckPassword(password) {
// soft-shutdown
app.peers[onion].Shutdown()
peer.Shutdown()
// delete the underlying storage
app.peers[onion].Delete()
peer.Delete()
// hard shutdown / remove from app
app.shutdownPeer(onion)
@ -341,6 +342,7 @@ func (app *application) LoadProfiles(password string) {
cps, err := peer.CreateEncryptedStore(profileDirectory, password)
if err != nil {
log.Errorf("error creating encrypted store: %v", err)
continue
}
profile := peer.ImportLegacyProfile(legacyProfile, cps)
loaded = app.installProfile(profile)
@ -361,6 +363,7 @@ func (app *application) LoadProfiles(password string) {
func (app *application) registerHooks(profile peer.CwtchPeer) {
// Register Hooks
profile.RegisterHook(extensions.ProfileValueExtension{})
profile.RegisterHook(extensions.SendWhenOnlineExtension{})
profile.RegisterHook(new(filesharing.Functionality))
profile.RegisterHook(new(filesharing.ImagePreviewsFunctionality))
profile.RegisterHook(new(servers.Functionality))
@ -390,10 +393,23 @@ func (app *application) ActivatePeerEngine(onion string) {
profile := app.GetPeer(onion)
if profile != nil {
if _, exists := app.engines[onion]; !exists {
log.Debugf("restartFlow: Creating a New Protocol Engine...")
app.engines[profile.GetOnion()], _ = profile.GenerateProtocolEngine(app.acn, app.eventBuses[profile.GetOnion()], app.engineHooks)
app.eventBuses[profile.GetOnion()].Publish(event.NewEventList(event.ProtocolEngineCreated))
app.QueryACNStatus()
eventBus, exists := app.eventBuses[profile.GetOnion()]
if !exists {
// todo handle this case?
log.Errorf("cannot activate peer engine without an event bus")
return
}
engine, err := profile.GenerateProtocolEngine(app.acn, eventBus, app.engineHooks)
if err == nil {
log.Debugf("restartFlow: Creating a New Protocol Engine...")
app.engines[profile.GetOnion()] = engine
eventBus.Publish(event.NewEventList(event.ProtocolEngineCreated))
app.QueryACNStatus()
} else {
log.Errorf("corrupted profile detected for %v", onion)
}
}
}
}
@ -403,21 +419,24 @@ func (app *application) ConfigureConnections(onion string, listen bool, peers bo
profile := app.GetPeer(onion)
if profile != nil {
// if we are making a decision to ignore
if !peers || !servers {
app.eventBuses[profile.GetOnion()].Publish(event.NewEventList(event.PurgeRetries))
}
profileBus, exists := app.eventBuses[profile.GetOnion()]
if exists {
// if we are making a decision to ignore
if !peers || !servers {
profileBus.Publish(event.NewEventList(event.PurgeRetries))
}
// enable the engine if it doesn't exist...
// note: this function is idempotent
app.ActivatePeerEngine(onion)
if listen {
profile.Listen()
}
// enable the engine if it doesn't exist...
// note: this function is idempotent
app.ActivatePeerEngine(onion)
if listen {
profile.Listen()
}
app.eventBuses[profile.GetOnion()].Publish(event.NewEventList(event.ResumeRetries))
// do this in the background, for large contact lists it can take a long time...
go profile.StartConnections(peers, servers)
profileBus.Publish(event.NewEventList(event.ResumeRetries))
// do this in the background, for large contact lists it can take a long time...
go profile.StartConnections(peers, servers)
}
} else {
log.Errorf("profile does not exist %v", onion)
}
@ -532,21 +551,26 @@ func (app *application) ShutdownPeer(onion string) {
}
// shutdownPeer mutex unlocked helper shutdown peer
//
//nolint:nilaway
func (app *application) shutdownPeer(onion string) {
// short circuit to prevent nil-pointer panic if this function is called twice (or incorrectly)
if app.eventBuses[onion] == nil || app.peers[onion] == nil {
onionEventBus := app.eventBuses[onion]
onionPeer := app.peers[onion]
if onionEventBus == nil || onionPeer == nil {
log.Errorf("shutdownPeer called with invalid onion %v", onion)
return
}
// we are an internal locked method, app.eventBuses[onion] cannot fail...
onionEventBus.Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion))
onionEventBus.Shutdown()
app.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion))
app.eventBuses[onion].Shutdown()
delete(app.eventBuses, onion)
app.peers[onion].Shutdown()
onionPeer.Shutdown()
delete(app.peers, onion)
if _, ok := app.engines[onion]; ok {
app.engines[onion].Shutdown()
if onionEngine, ok := app.engines[onion]; ok {
onionEngine.Shutdown()
delete(app.engines, onion)
}
log.Debugf("shutting down plugins for %v", onion)

View File

@ -302,8 +302,7 @@ func (cr *contactRetry) run() {
cr.authorizedPeers.Store(id, true)
if c, ok := cr.connections.Load(id); ok {
contact := c.(*contact)
if contact.state == connections.DISCONNECTED && !contact.queued {
if contact.state == connections.DISCONNECTED {
// prioritize connections made in the last week
if time.Since(contact.lastSeen).Hours() < PriorityQueueTimeSinceQualifierHours {
cr.priorityQueue.insert(contact)
@ -461,10 +460,10 @@ func (cr *contactRetry) addConnection(id string, state connections.ConnectionSta
cr.connections.Store(id, p)
return
} else {
// we have rerequested this connnection. Force set the queued parameter to true.
p, _ := cr.connections.Load(id)
if !p.(*contact).queued {
p.(*contact).queued = true
// we have rerequested this connnection, probably via an explicit ask, update it's state
if c, ok := cr.connections.Load(id); ok {
contact := c.(*contact)
contact.state = state
}
}
}

View File

@ -36,19 +36,19 @@ func TestContactRetryQueue(t *testing.T) {
// progress...
setup := false
for !setup {
if pinf, exists := cr.connections.Load(testOnion); exists {
if pinf.(*contact).queued {
if _, exists := cr.authorizedPeers.Load(testOnion); exists {
t.Logf("authorized")
setup = true
}
if _, exists := cr.connections.Load(testOnion); exists {
if _, exists := cr.authorizedPeers.Load(testOnion); exists {
t.Logf("authorized")
setup = true
}
}
}
// We should very quickly become connecting...
time.Sleep(time.Second)
pinf, _ := cr.connections.Load(testOnion)
if pinf.(*contact).queued == false {
t.Fatalf("test connection should be queued, actually: %v", pinf.(*contact).queued)
if pinf.(*contact).state != 1 {
t.Fatalf("test connection should be in connecting after update, actually: %v", pinf.(*contact).state)
}
// Asset that "test" is authenticated
@ -63,19 +63,12 @@ func TestContactRetryQueue(t *testing.T) {
// Publish an unrelated event to trigger the Plugin to go through a queuing cycle
// If we didn't do this we would have to wait 30 seconds for a check-in
bus.Publish(event.NewEvent(event.PeerStateChange, map[event.Field]string{event.RemotePeer: "test2", event.ConnectionState: "Disconnected"}))
bus.Publish(event.NewEvent(event.QueuePeerRequest, map[event.Field]string{event.RemotePeer: testOnion, event.LastSeen: time.Now().Format(time.RFC3339Nano)}))
time.Sleep(time.Second)
if pinf.(*contact).queued != false {
t.Fatalf("test connection should not be queued, actually: %v", pinf.(*contact).queued)
}
// Publish a new peer request...
bus.Publish(event.NewEvent(event.QueuePeerRequest, map[event.Field]string{event.RemotePeer: testOnion}))
time.Sleep(time.Second) // yield for a second so the event can catch up...
// Peer test should be forced to queue....
pinf, _ = cr.connections.Load(testOnion)
if pinf.(*contact).queued != true {
t.Fatalf("test connection should be forced to queue after new queue peer request")
if pinf.(*contact).state != 1 {
t.Fatalf("test connection should be in connecting after update, actually: %v", pinf.(*contact).state)
}
cr.Shutdown()

View File

@ -15,6 +15,9 @@ func WaitGetPeer(app Application, name string) peer.CwtchPeer {
for {
for _, handle := range app.ListProfiles() {
peer := app.GetPeer(handle)
if peer == nil {
continue
}
localName, _ := peer.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
if localName == name {
return peer

View File

@ -95,6 +95,11 @@ func (em *manager) initialize() {
func (em *manager) Subscribe(eventType Type, queue Queue) {
em.mapMutex.Lock()
defer em.mapMutex.Unlock()
for _, sub := range em.subscribers[eventType] {
if sub == queue {
return // don't add the same queue for the same event twice...
}
}
em.subscribers[eventType] = append(em.subscribers[eventType], queue)
}

View File

@ -1,3 +1,4 @@
// nolint:nilaway - the infiniteBuffer function causes issues with static analysis because it is very unidomatic.
package event
/*

View File

@ -0,0 +1,66 @@
package extensions
import (
"strconv"
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/model/constants"
"cwtch.im/cwtch/peer"
"cwtch.im/cwtch/protocol/connections"
"cwtch.im/cwtch/settings"
"git.openprivacy.ca/openprivacy/log"
)
// SendWhenOnlineExtension implements automatic sending
// Some Considerations:
// - There are race conditions inherant in this approach e.g. a peer could go offline just after recieving a message and never sending an ack
// - In that case the next time we connect we will send a duplicate message.
// - Currently we do not include metadata like sent time in raw peer protocols (however Overlay does now have support for that information)
type SendWhenOnlineExtension struct {
}
func (soe SendWhenOnlineExtension) NotifySettingsUpdate(_ settings.GlobalSettings) {
}
func (soe SendWhenOnlineExtension) EventsToRegister() []event.Type {
return []event.Type{event.PeerStateChange}
}
func (soe SendWhenOnlineExtension) ExperimentsToRegister() []string {
return nil
}
func (soe SendWhenOnlineExtension) OnEvent(ev event.Event, profile peer.CwtchPeer) {
switch ev.EventType {
case event.PeerStateChange:
ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
if err == nil {
// if we have re-authenticated with thie peer then request their profile image...
if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.AUTHENTICATED {
// Check the last 100 messages, if any of them are pending, then send them now...
messsages, _ := profile.GetMostRecentMessages(ci.ID, 0, 0, uint(100))
for _, message := range messsages {
if message.Attr[constants.AttrAck] == constants.False {
body := message.Body
ev := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(ci.ID), event.RemotePeer: ci.Handle, event.Data: body})
ev.EventID = message.Signature // we need this ensure that we correctly ack this in the db when it comes back
// TODO: The EventBus is becoming very noisy...we may want to consider a one-way shortcut to Engine i.e. profile.Engine.SendMessageToPeer
log.Debugf("resending message that was sent when peer was offline")
profile.PublishEvent(ev)
}
}
}
}
}
}
// OnContactReceiveValue is nop for SendWhenOnnlineExtension
func (soe SendWhenOnlineExtension) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, szp attr.ScopedZonedPath, value string, exists bool) {
}
// OnContactRequestValue is nop for SendWhenOnnlineExtension
func (soe SendWhenOnlineExtension) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, szp attr.ScopedZonedPath) {
}

View File

@ -272,15 +272,21 @@ func (f *Functionality) DownloadFile(profile peer.CwtchPeer, conversationID int,
}
// Don't download files if the download file directory does not exist
if _, err := os.Stat(path.Dir(downloadFilePath)); os.IsNotExist(err) {
return errors.New("download directory does not exist")
}
// Unless we are on Android where the kernel wishes to keep us ignorant of the
// actual path and/or existence of the file. We handle this case further down
// the line when the manifest is received and protocol engine and the Android layer
// negotiate a temporary local file -> final file copy. We don't want to worry
// about that here...
if runtime.GOOS != "android" {
if _, err := os.Stat(path.Dir(downloadFilePath)); os.IsNotExist(err) {
return errors.New("download directory does not exist")
}
// Don't download files if the manifest file directory does not exist
if _, err := os.Stat(path.Dir(manifestFilePath)); os.IsNotExist(err) {
return errors.New("manifest directory does not exist")
// Don't download files if the manifest file directory does not exist
if _, err := os.Stat(path.Dir(manifestFilePath)); os.IsNotExist(err) {
return errors.New("manifest directory does not exist")
}
}
// Store local.filesharing.filekey.manifest as the location of the manifest
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key), manifestFilePath)

View File

@ -38,14 +38,14 @@ func (i *ImagePreviewsFunctionality) OnEvent(ev event.Event, profile peer.CwtchP
case event.NewMessageFromPeer:
ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
if err == nil {
if ci.Accepted {
if ci.GetPeerAC().RenderImages {
i.handleImagePreviews(profile, &ev, ci.ID, ci.ID)
}
}
case event.NewMessageFromGroup:
ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
if err == nil {
if ci.Accepted {
if ci.GetPeerAC().RenderImages {
i.handleImagePreviews(profile, &ev, ci.ID, ci.ID)
}
}
@ -90,7 +90,7 @@ func (i *ImagePreviewsFunctionality) OnContactReceiveValue(profile peer.CwtchPee
_, zone, path := path.GetScopeZonePath()
if exists && zone == attr.ProfileZone && path == constants.CustomProfileImageKey {
// We only download from accepted conversations
if conversation.Accepted {
if conversation.GetPeerAC().RenderImages {
fileKey := value
basepath := i.downloadFolder
fsf := FunctionalityGate()
@ -123,6 +123,16 @@ func (i *ImagePreviewsFunctionality) OnContactReceiveValue(profile peer.CwtchPee
// handleImagePreviews checks settings and, if appropriate, auto-downloads any images
func (i *ImagePreviewsFunctionality) handleImagePreviews(profile peer.CwtchPeer, ev *event.Event, conversationID, senderID int) {
if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
ci, err := profile.GetConversationInfo(senderID)
if err != nil {
log.Errorf("attempted to call handleImagePreviews with unknown conversation: %v", senderID)
return
}
if !ci.GetPeerAC().ShareFiles || !ci.GetPeerAC().RenderImages {
log.Infof("refusing to autodownload files from sender: %v. conversation AC does not permit image rendering", senderID)
return
}
// Short-circuit failures
// Don't auto-download images if the download path does not exist.
@ -142,7 +152,7 @@ func (i *ImagePreviewsFunctionality) handleImagePreviews(profile peer.CwtchPeer,
// Now look at the image preview experiment
var cm model.MessageWrapper
err := json.Unmarshal([]byte(ev.Data[event.Data]), &cm)
err = json.Unmarshal([]byte(ev.Data[event.Data]), &cm)
if err == nil && cm.Overlay == model.OverlayFileSharing {
log.Debugf("Received File Sharing Message")
var fm OverlayMessage

View File

@ -9,6 +9,8 @@ import (
"cwtch.im/cwtch/protocol/connections"
"cwtch.im/cwtch/settings"
"encoding/json"
"errors"
"git.openprivacy.ca/openprivacy/log"
)
const (
@ -98,7 +100,12 @@ func (f *Functionality) PublishServerUpdate(profile peer.CwtchPeer) error {
func (f *Functionality) GetServerInfoList(profile peer.CwtchPeer) []Server {
var servers []Server
for _, server := range profile.GetServers() {
servers = append(servers, f.GetServerInfo(profile, server))
server, err := f.GetServerInfo(profile, server)
if err != nil {
log.Errorf("profile server list is corrupted: %v", err)
continue
}
servers = append(servers, server)
}
return servers
}
@ -120,8 +127,11 @@ func (f *Functionality) DeleteServerInfo(profile peer.CwtchPeer, serverOnion str
// GetServerInfo compiles all the information the UI might need regarding a particular server including any verified
// cryptographic keys
func (f *Functionality) GetServerInfo(profile peer.CwtchPeer, serverOnion string) Server {
serverInfo, _ := profile.FetchConversationInfo(serverOnion)
func (f *Functionality) GetServerInfo(profile peer.CwtchPeer, serverOnion string) (Server, error) {
serverInfo, err := profile.FetchConversationInfo(serverOnion)
if err != nil {
return Server{}, errors.New("server not found")
}
keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
var serverKeys []ServerKey
@ -136,5 +146,5 @@ func (f *Functionality) GetServerInfo(profile peer.CwtchPeer, serverOnion string
recentTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncMostRecentMessageTime)).ToString()]
syncStatus := SyncStatus{startTimeStr, recentTimeStr}
return Server{Onion: serverOnion, Identifier: serverInfo.ID, Status: connections.ConnectionStateName[profile.GetPeerState(serverInfo.Handle)], Keys: serverKeys, Description: description, SyncProgress: syncStatus}
return Server{Onion: serverOnion, Identifier: serverInfo.ID, Status: connections.ConnectionStateName[profile.GetPeerState(serverInfo.Handle)], Keys: serverKeys, Description: description, SyncProgress: syncStatus}, nil
}

2
go.mod
View File

@ -1,6 +1,6 @@
module cwtch.im/cwtch
go 1.17
go 1.20
require (
git.openprivacy.ca/cwtch.im/tapir v0.6.0

97
go.sum
View File

@ -1,48 +1,23 @@
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.openprivacy.ca/cwtch.im/tapir v0.6.0 h1:TtnKjxitkIDMM7Qn0n/u+mOHRLJzuQUYjYRu5n0/QFY=
git.openprivacy.ca/cwtch.im/tapir v0.6.0/go.mod h1:iQIq4y7N+DuP3CxyG66WNEC/d6vzh+wXvvOmelB+KoY=
git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
git.openprivacy.ca/openprivacy/bine v0.0.5 h1:DJs5gqw3SkvLSgRDvroqJxZ7F+YsbxbBRg5t0rU5gYE=
git.openprivacy.ca/openprivacy/bine v0.0.5/go.mod h1:fwdeq6RO08WDkV0k7HfArsjRvurVULoUQmT//iaABZM=
git.openprivacy.ca/openprivacy/connectivity v1.8.6/go.mod h1:Hn1gpOx/bRZp5wvCtPQVJPXrfeUH0EGiG/Aoa0vjGLg=
git.openprivacy.ca/openprivacy/connectivity v1.11.0 h1:roASjaFtQLu+HdH5fa2wx6F00NL3YsUTlmXBJh8aLZk=
git.openprivacy.ca/openprivacy/connectivity v1.11.0/go.mod h1:OQO1+7OIz/jLxDrorEMzvZA6SEbpbDyLGpjoFqT3z1Y=
git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c h1:gkfmnY4Rlt3VINCo4uKdpvngiibQyoENVj5Q88sxXhE=
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c/go.mod h1:tDPFhGdt3hJWqtKwx57i9baiB1Cj0yAg22VOPUqm5vY=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -53,115 +28,43 @@ github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaG
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
github.com/mutecomm/go-sqlcipher/v4 v4.4.2 h1:eM10bFtI4UvibIsKr10/QT7Yfz+NADfjZYh0GKrXUNc=
github.com/mutecomm/go-sqlcipher/v4 v4.4.2/go.mod h1:mF2UmIpBnzFeBdu/ypTDb/LdbS0nk0dfSN1WUsWTjMA=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -67,3 +67,8 @@ const ProfileAttribute3 = "profile-attribute-3"
// Description is used on server contacts,
const Description = "description"
// Used to store the status of acl migrations
const ACLVersion = "acl-version"
const ACLVersionOne = "acl-v1"
const ACLVersionTwo = "acl-v2"

View File

@ -1,22 +1,36 @@
package model
import (
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/model/constants"
"encoding/json"
"time"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/model/constants"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log"
)
// AccessControl is a type determining client assigned authorization to a peer
// for a given conversation
type AccessControl struct {
Blocked bool // Any attempts from this handle to connect are blocked
Read bool // Allows a handle to access the conversation
Append bool // Allows a handle to append new messages to the conversation
Blocked bool // Any attempts from this handle to connect are blocked overrides all other settings
// Basic Conversation Rights
Read bool // Allows a handle to access the conversation
Append bool // Allows a handle to append new messages to the conversation
AutoConnect bool // Profile should automatically try to connect with peer
ExchangeAttributes bool // Profile should automatically exchange attributes like Name, Profile Image, etc.
// Extension Related Permissions
ShareFiles bool // Allows a handle to share files to a conversation
RenderImages bool // Indicates that certain filetypes should be autodownloaded and rendered when shared by this contact
}
// DefaultP2PAccessControl - because in the year 2021, go does not support constant structs...
// DefaultP2PAccessControl defaults to a semi-trusted peer with no access to special extensions.
func DefaultP2PAccessControl() AccessControl {
return AccessControl{Read: true, Append: true, Blocked: false}
return AccessControl{Read: true, Append: true, ExchangeAttributes: true, Blocked: false,
AutoConnect: true, ShareFiles: false, RenderImages: false}
}
// AccessControlList represents an access control list for a conversation. Mapping handles to conversation
@ -30,10 +44,10 @@ func (acl *AccessControlList) Serialize() []byte {
}
// DeserializeAccessControlList takes in JSON and returns an AccessControlList
func DeserializeAccessControlList(data []byte) AccessControlList {
func DeserializeAccessControlList(data []byte) (AccessControlList, error) {
var acl AccessControlList
json.Unmarshal(data, &acl)
return acl
err := json.Unmarshal(data, &acl)
return acl, err
}
// Attributes a type-driven encapsulation of an Attribute map.
@ -47,8 +61,12 @@ func (a *Attributes) Serialize() []byte {
// DeserializeAttributes converts a JSON struct into an Attributes map
func DeserializeAttributes(data []byte) Attributes {
var attributes Attributes
json.Unmarshal(data, &attributes)
attributes := make(Attributes)
err := json.Unmarshal(data, &attributes)
if err != nil {
log.Error("error deserializing attributes (this is likely a programming error): %v", err)
return make(Attributes)
}
return attributes
}
@ -60,7 +78,9 @@ type Conversation struct {
Handle string
Attributes Attributes
ACL AccessControlList
Accepted bool
// Deprecated, please use ACL for permissions related functions
Accepted bool
}
// GetAttribute is a helper function that fetches a conversation attribute by scope, zone and key
@ -71,6 +91,21 @@ func (ci *Conversation) GetAttribute(scope attr.Scope, zone attr.Zone, key strin
return "", false
}
// GetPeerAC returns a suitable Access Control object for a the given peer conversation
// If this is called for a group conversation, this method will error and return a safe default AC.
func (ci *Conversation) GetPeerAC() AccessControl {
if acl, exists := ci.ACL[ci.Handle]; exists {
return acl
}
log.Errorf("attempted to access a Peer Access Control object from %v but peer ACL is undefined. This is likely a programming error", ci.Handle)
return DefaultP2PAccessControl()
}
// IsCwtchPeer is a helper attribute that identifies whether a conversation is a cwtch peer
func (ci *Conversation) IsCwtchPeer() bool {
return tor.IsValidHostname(ci.Handle)
}
// IsGroup is a helper attribute that identifies whether a conversation is a legacy group
func (ci *Conversation) IsGroup() bool {
if _, exists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]; exists {

View File

@ -59,21 +59,26 @@ func NewGroup(server string) (*Group, error) {
// Derive Group ID from the group key and the server public key. This binds the group to a particular server
// and key.
group.GroupID = deriveGroupID(groupKey[:], server)
return group, nil
var err error
group.GroupID, err = deriveGroupID(groupKey[:], server)
return group, err
}
// CheckGroup returns true only if the ID of the group is cryptographically valid.
func (g *Group) CheckGroup() bool {
return g.GroupID == deriveGroupID(g.GroupKey[:], g.GroupServer)
id, _ := deriveGroupID(g.GroupKey[:], g.GroupServer)
return g.GroupID == id
}
// deriveGroupID hashes together the key and the hostname to create a bound identifier that can later
// be referenced and checked by profiles when they receive invites and messages.
func deriveGroupID(groupKey []byte, serverHostname string) string {
data, _ := base32.StdEncoding.DecodeString(strings.ToUpper(serverHostname))
func deriveGroupID(groupKey []byte, serverHostname string) (string, error) {
data, err := base32.StdEncoding.DecodeString(strings.ToUpper(serverHostname))
if err != nil {
return "", err
}
pubkey := data[0:ed25519.PublicKeySize]
return hex.EncodeToString(pbkdf2.Key(groupKey, pubkey, 4096, 16, sha512.New))
return hex.EncodeToString(pbkdf2.Key(groupKey, pubkey, 4096, 16, sha512.New)), nil
}
// Invite generates a invitation that can be sent to a cwtch peer
@ -148,7 +153,7 @@ func ValidateInvite(invite string) (*groups.GroupInvite, error) {
// Derive the servers public key (we can ignore the error checking here because it's already been
// done by IsValidHostname, and check that we derive the same groupID...
derivedGroupID := deriveGroupID(gci.SharedKey, gci.ServerHost)
derivedGroupID, _ := deriveGroupID(gci.SharedKey, gci.ServerHost)
if derivedGroupID != gci.GroupID {
return nil, errors.New("group id is invalid")
}
@ -166,7 +171,9 @@ func ValidateInvite(invite string) (*groups.GroupInvite, error) {
// If successful, adds the message to the group's timeline
func (g *Group) AttemptDecryption(ciphertext []byte, signature []byte) (bool, *groups.DecryptedGroupMessage) {
success, dgm := g.DecryptMessage(ciphertext)
if success {
// the second check here is not needed, but DecryptMessage violates the usual
// go calling convention and we want static analysis tools to pick it up
if success && dgm != nil {
// Attempt to serialize this message
serialized, err := json.Marshal(dgm)

View File

@ -9,7 +9,10 @@ import (
)
func TestGroup(t *testing.T) {
g, _ := NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
g, err := NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
if err != nil {
t.Fatalf("Group with real group server should not fail")
}
dgm := &groups.DecryptedGroupMessage{
Onion: "onion",
Text: "Hello World!",
@ -37,7 +40,7 @@ func TestGroup(t *testing.T) {
encMessage, _ := g.EncryptMessage(dgm)
ok, message := g.DecryptMessage(encMessage)
if !ok || message.Text != "Hello World!" {
if (!ok || message == nil) || message.Text != "Hello World!" {
t.Errorf("group encryption was invalid, or returned wrong message decrypted:%v message:%v", ok, message)
return
}
@ -73,7 +76,10 @@ func TestGroupValidation(t *testing.T) {
t.Logf("Error: %v", err)
// Generate a valid group but replace the group server...
group, _ = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
group, err = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
if err != nil {
t.Fatalf("Group with real group server should not fail")
}
group.GroupServer = "tcnkoch4nyr3cldkemejtkpqok342rbql6iclnjjs3ndgnjgufzyxvqd"
invite, _ = group.Invite()
_, err = ValidateInvite(invite)
@ -84,7 +90,10 @@ func TestGroupValidation(t *testing.T) {
t.Logf("Error: %v", err)
// Generate a valid group but replace the group key...
group, _ = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
group, err = NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
if err != nil {
t.Fatalf("Group with real group server should not fail")
}
group.GroupKey = sha256.Sum256([]byte{})
invite, _ = group.Invite()
_, err = ValidateInvite(invite)

View File

@ -3,6 +3,7 @@ package model
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
)
// CalculateContentHash derives a hash using the author and the message body. It is intended to be
@ -12,3 +13,13 @@ func CalculateContentHash(author string, messageBody string) string {
contentBasedHash := sha256.Sum256(content)
return base64.StdEncoding.EncodeToString(contentBasedHash[:])
}
func DeserializeMessage(message string) (*MessageWrapper, error) {
var cm MessageWrapper
err := json.Unmarshal([]byte(message), &cm)
if err != nil {
return nil, err
}
return &cm, err
}

View File

@ -1,9 +1,40 @@
package model
import (
"time"
)
// MessageWrapper is the canonical Cwtch overlay wrapper
type MessageWrapper struct {
Overlay int `json:"o"`
Data string `json:"d"`
// when the data was assembled
SendTime *time.Time `json:"s,omitempty"`
// when the data was transmitted (by protocol engine e.g. over Tor)
TransitTime *time.Time `json:"t,omitempty"`
// when the data was received
RecvTime *time.Time `json:"r,omitempty"`
}
// Channel is defined as being the last 3 bits of the overlay id
// Channel 0 is reserved for the main conversation
// Channel 2 is reserved for conversation admin (managed groups)
// Channel 7 is reserved for streams (no ack, no store)
func (mw MessageWrapper) Channel() int {
if mw.Overlay > 1024 {
return mw.Overlay & 0x07
}
// for backward compatibilty all overlays less than 0x400 i.e. 1024 are
// mapped to channel 0 regardless of their channel status.
return 0
}
// If Overlay is a Stream Message it should not be ackd, or stored.
func (mw MessageWrapper) IsStream() bool {
return mw.Channel() == 0x07
}
// OverlayChat is the canonical identifier for chat overlays

View File

@ -80,11 +80,19 @@ func (p *Profile) GetCopy(timeline bool) *Profile {
if timeline {
for groupID := range newp.Groups {
newp.Groups[groupID].Timeline = *p.Groups[groupID].Timeline.GetCopy()
if group, exists := newp.Groups[groupID]; exists {
if pGroup, exists := p.Groups[groupID]; exists {
group.Timeline = *(pGroup).Timeline.GetCopy()
}
}
}
for peerID := range newp.Contacts {
newp.Contacts[peerID].Timeline = *p.Contacts[peerID].Timeline.GetCopy()
if peer, exists := newp.Contacts[peerID]; exists {
if pPeer, exists := p.Contacts[peerID]; exists {
peer.Timeline = *(pPeer).Timeline.GetCopy()
}
}
}
}

View File

@ -3,11 +3,20 @@ package peer
import (
"context"
"crypto/rand"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/constants"
"cwtch.im/cwtch/protocol/groups"
"cwtch.im/cwtch/settings"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"git.openprivacy.ca/cwtch.im/tapir/primitives"
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"golang.org/x/crypto/ed25519"
"os"
path "path/filepath"
"sort"
@ -16,17 +25,7 @@ import (
"sync"
"time"
"cwtch.im/cwtch/model/constants"
"cwtch.im/cwtch/protocol/groups"
"cwtch.im/cwtch/settings"
"git.openprivacy.ca/cwtch.im/tapir/primitives"
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"golang.org/x/crypto/ed25519"
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/protocol/connections"
"git.openprivacy.ca/openprivacy/log"
@ -94,7 +93,7 @@ func (cp *cwtchPeer) EnhancedImportBundle(importString string) string {
return cp.ImportBundle(importString).Error()
}
func (cp *cwtchPeer) EnhancedGetMessages(conversation int, index int, count int) string {
func (cp *cwtchPeer) EnhancedGetMessages(conversation int, index int, count uint) string {
var emessages = make([]EnhancedMessage, count)
messages, err := cp.GetMostRecentMessages(conversation, 0, index, count)
@ -145,7 +144,7 @@ func (cp *cwtchPeer) EnhancedGetMessageByContentHash(conversation int, contentHa
offset, err := cp.GetChannelMessageByContentHash(conversation, 0, contentHash)
if err == nil {
messages, err := cp.GetMostRecentMessages(conversation, 0, offset, 1)
if err == nil {
if len(messages) > 0 && err == nil {
sentTime, _ := time.Parse(time.RFC3339Nano, messages[0].Attr[constants.AttrSentTimestamp])
message.Message = model.Message{
Message: messages[0].Body,
@ -310,7 +309,33 @@ func (cp *cwtchPeer) GenerateProtocolEngine(acn connectivity.ACN, bus event.Mana
authorizations := make(map[string]model.Authorization)
for _, conversation := range conversations {
if tor.IsValidHostname(conversation.Handle) {
// Only perform the following actions for Peer-type Conversaions...
if conversation.IsCwtchPeer() {
// if this profile does not have an ACL version, and the profile is accepted (OR the acl version is v1 and the profile is accepted...)
// then migrate the permissions to the v2 ACL
// migrate the old accepted AC to a new fine-grained one
// we only do this for previously trusted connections
// NOTE: this does not supercede global cwthch experiments settings
// if share files is turned off globally then acl.ShareFiles will be ignored
// Note: There was a bug in the original EP code that meant that some acl-v1 profiles did not get ShareFiles or RenderImages - this corrects that.
if version, exists := conversation.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.ACLVersion); !exists || version == constants.ACLVersionOne {
if conversation.Accepted {
if ac, exists := conversation.ACL[conversation.Handle]; exists {
ac.ShareFiles = true
ac.RenderImages = true
ac.AutoConnect = true
ac.ExchangeAttributes = true
conversation.ACL[conversation.Handle] = ac
}
// Update the ACL Version
cp.storage.SetConversationAttribute(conversation.ID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.ACLVersion)), constants.ACLVersionTwo)
// Store the updated ACL
cp.storage.SetConversationACL(conversation.ID, conversation.ACL)
}
}
if conversation.ACL[conversation.Handle].Blocked {
authorizations[conversation.Handle] = model.AuthBlocked
} else {
@ -410,12 +435,19 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) (int, error)
if tor.IsValidHostname(conversationInfo.Handle) {
ev := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationInfo.ID), event.RemotePeer: conversationInfo.Handle, event.Data: message})
onion, _ := cp.storage.LoadProfileKeyValue(TypeAttribute, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Onion)).ToString())
id := -1
// 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
id, 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 {
return -1, err
// check if we should store this message locally...
if cm, err := model.DeserializeMessage(message); err == nil {
if !cm.IsStream() {
// 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
id, 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 {
return -1, err
}
}
}
cp.eventBus.Publish(ev)
return id, nil
} else {
@ -682,13 +714,60 @@ func (cp *cwtchPeer) NewContactConversation(handle string, acl model.AccessContr
conversationInfo, _ := cp.storage.GetConversationByHandle(handle)
if conversationInfo == nil {
conversationID, err := cp.storage.NewConversation(handle, model.Attributes{event.SaveHistoryKey: event.DeleteHistoryDefault}, model.AccessControlList{handle: acl}, accepted)
if err != nil {
log.Errorf("unable to create a new contact conversation: %v", err)
return -1, err
}
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.AttrLastConnectionTime)), time.Now().Format(time.RFC3339Nano))
if accepted {
// If this call came from a trusted action (i.e. import bundle or accept button then accept the conversation)
// This assigns all permissions (and in v2 is currently the default state of trusted contacts)
// Accept conversation does PeerWithOnion
cp.AcceptConversation(conversationID)
}
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.ACLVersion)), constants.ACLVersionTwo)
cp.eventBus.Publish(event.NewEvent(event.ContactCreated, map[event.Field]string{event.ConversationID: strconv.Itoa(conversationID), event.RemotePeer: handle}))
return conversationID, err
}
return -1, fmt.Errorf("contact conversation already exists")
}
// UpdateConversationAccessControlList is a genric ACL update method
func (cp *cwtchPeer) UpdateConversationAccessControlList(id int, acl model.AccessControlList) error {
return cp.storage.SetConversationACL(id, acl)
}
// EnhancedUpdateConversationAccessControlList wraps UpdateConversationAccessControlList and allows updating via a serialized JSON struct
func (cp *cwtchPeer) EnhancedUpdateConversationAccessControlList(id int, json string) error {
_, err := cp.GetConversationInfo(id)
if err == nil {
acl, err := model.DeserializeAccessControlList([]byte(json))
if err == nil {
return cp.UpdateConversationAccessControlList(id, acl)
}
return err
}
return err
}
// GetConversationAccessControlList returns the access control list associated with the conversation
func (cp *cwtchPeer) GetConversationAccessControlList(id int) (model.AccessControlList, error) {
ci, err := cp.GetConversationInfo(id)
if err == nil {
return ci.ACL, nil
}
return nil, err
}
// EnhancedGetConversationAccessControlList serialzies the access control list associated with the conversation
func (cp *cwtchPeer) EnhancedGetConversationAccessControlList(id int) (string, error) {
ci, err := cp.GetConversationInfo(id)
if err == nil {
return string(ci.ACL.Serialize()), nil
}
return "", err
}
// AcceptConversation looks up a conversation by `handle` and sets the Accepted status to `true`
// This will cause Cwtch to auto connect to this conversation on start up
func (cp *cwtchPeer) AcceptConversation(id int) error {
@ -701,6 +780,21 @@ func (cp *cwtchPeer) AcceptConversation(id int) error {
log.Errorf("Could not get conversation for %v: %v", id, err)
return err
}
if ac, exists := ci.ACL[ci.Handle]; exists {
ac.ShareFiles = true
ac.AutoConnect = true
ac.RenderImages = true
ac.ExchangeAttributes = true
ci.ACL[ci.Handle] = ac
}
err = cp.storage.SetConversationACL(id, ci.ACL)
if err != nil {
log.Errorf("Could not set conversation acl for %v: %v", id, err)
return err
}
if !ci.IsGroup() && !ci.IsServer() {
cp.sendUpdateAuth(id, ci.Handle, ci.Accepted, ci.ACL[ci.Handle].Blocked)
cp.PeerWithOnion(ci.Handle)
@ -748,7 +842,7 @@ func (cp *cwtchPeer) UnblockConversation(id int) error {
// TODO at some point in the future engine needs to understand ACLs not just legacy auth status
cp.sendUpdateAuth(id, ci.Handle, ci.Accepted, ci.ACL[ci.Handle].Blocked)
if !ci.IsGroup() && !ci.IsServer() && ci.Accepted {
if !ci.IsGroup() && !ci.IsServer() && ci.GetPeerAC().AutoConnect {
cp.PeerWithOnion(ci.Handle)
}
@ -888,7 +982,7 @@ func (cp *cwtchPeer) GetChannelMessageCount(conversation int, channel int) (int,
}
// GetMostRecentMessages returns a selection of messages, ordered by most recently inserted
func (cp *cwtchPeer) GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error) {
func (cp *cwtchPeer) GetMostRecentMessages(conversation int, channel int, offset int, limit uint) ([]model.ConversationMessage, error) {
return cp.storage.GetMostRecentMessages(conversation, channel, offset, limit)
}
@ -1053,13 +1147,12 @@ func (cp *cwtchPeer) DisconnectFromServer(onion string) {
// QueuePeeringWithOnion sends the request to peer with an onion directly to the contact retry queue; this is a mechanism to not flood tor with circuit requests
// Status: Ready for 1.10
func (cp *cwtchPeer) QueuePeeringWithOnion(handle string) {
lastSeen := event.CwtchEpoch
ci, err := cp.FetchConversationInfo(handle)
if err == nil {
lastSeen = cp.GetConversationLastSeenTime(ci.ID)
}
if !ci.ACL[ci.Handle].Blocked && ci.Accepted {
cp.eventBus.Publish(event.NewEvent(event.QueuePeerRequest, map[event.Field]string{event.RemotePeer: handle, event.LastSeen: lastSeen.Format(time.RFC3339Nano)}))
lastSeen := cp.GetConversationLastSeenTime(ci.ID)
if !ci.ACL[ci.Handle].Blocked {
cp.eventBus.Publish(event.NewEvent(event.QueuePeerRequest, map[event.Field]string{event.RemotePeer: handle, event.LastSeen: lastSeen.Format(time.RFC3339Nano)}))
}
}
}
@ -1175,9 +1268,9 @@ func (cp *cwtchPeer) ImportBundle(importString string) error {
return ConstructResponse(constants.ImportBundlePrefix, "success")
} else if tor.IsValidHostname(importString) {
_, err := cp.NewContactConversation(importString, model.DefaultP2PAccessControl(), true)
// NOTE: Not NewContactConversation implictly does AcceptConversation AND PeerWithOnion if relevant so
// we no longer need to do it here...
if err == nil {
// Assuming all is good, we should peer with this contact.
cp.PeerWithOnion(importString)
return ConstructResponse(constants.ImportBundlePrefix, "success")
}
return ConstructResponse(constants.ImportBundlePrefix, err.Error())
@ -1317,7 +1410,7 @@ func (cp *cwtchPeer) getConnectionsSortedByLastSeen(doPeers, doServers bool) []*
continue
}
} else {
if !doPeers || !conversation.Accepted {
if !doPeers {
continue
}
}
@ -1340,7 +1433,9 @@ func (cp *cwtchPeer) StartConnections(doPeers, doServers bool) {
cp.QueueJoinServer(conversation.model.Handle)
} else {
log.Debugf(" QueuePeerWithOnion(%v)", conversation.model.Handle)
cp.QueuePeeringWithOnion(conversation.model.Handle)
if conversation.model.GetPeerAC().AutoConnect {
cp.QueuePeeringWithOnion(conversation.model.Handle)
}
}
time.Sleep(50 * time.Millisecond)
}
@ -1404,6 +1499,13 @@ func (cp *cwtchPeer) storeMessage(handle string, message string, sent time.Time)
}
}
// Don't store messages in channel 7
if cm, err := model.DeserializeMessage(message); err == nil {
if cm.IsStream() {
return -1, nil
}
}
// Generate a random number and use it as the signature
signature := event.GetRandNumber().String()
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))
@ -1509,7 +1611,7 @@ func (cp *cwtchPeer) eventHandler() {
log.Debugf("confo info lookup newgetval %v %v %v", onion, conversationInfo, err)
// only accepted contacts can look up information
if conversationInfo != nil && conversationInfo.Accepted {
if conversationInfo != nil && conversationInfo.GetPeerAC().ExchangeAttributes {
// Type Safe Scoped/Zoned Path
zscope := attr.IntoScope(scope)
zone, zpath := attr.ParseZone(zpath)
@ -1541,7 +1643,7 @@ func (cp *cwtchPeer) eventHandler() {
conversationInfo, _ := cp.FetchConversationInfo(handle)
// only accepted contacts can look up information
if conversationInfo != nil && conversationInfo.Accepted {
if conversationInfo != nil && conversationInfo.GetPeerAC().ExchangeAttributes {
// Type Safe Scoped/Zoned Path
zscope := attr.IntoScope(scope)
zone, zpath := attr.ParseZone(zpath)
@ -1581,6 +1683,7 @@ func (cp *cwtchPeer) eventHandler() {
timestamp := time.Now().Format(time.RFC3339Nano)
cp.SetConversationAttribute(cid, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.AttrLastConnectionTime)), timestamp)
} else if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.DISCONNECTED {
ci, err := cp.FetchConversationInfo(handle)
if err == nil {

View File

@ -376,7 +376,12 @@ func (cps *CwtchProfileStorage) GetConversationByHandle(handle string) (*model.C
}
rows.Close()
return &model.Conversation{ID: id, Handle: handle, ACL: model.DeserializeAccessControlList(acl), Attributes: model.DeserializeAttributes(attributes), Accepted: accepted}, nil
cacl, err := model.DeserializeAccessControlList(acl)
if err != nil {
log.Errorf("error deserializing ACL from database, database maybe corrupted: %v", err)
return nil, err
}
return &model.Conversation{ID: id, Handle: handle, ACL: cacl, Attributes: model.DeserializeAttributes(attributes), Accepted: accepted}, nil
}
// FetchConversations returns *all* active conversations. This method should only be called
@ -412,7 +417,13 @@ func (cps *CwtchProfileStorage) FetchConversations() ([]*model.Conversation, err
rows.Close()
return nil, err
}
conversations = append(conversations, &model.Conversation{ID: id, Handle: handle, ACL: model.DeserializeAccessControlList(acl), Attributes: model.DeserializeAttributes(attributes), Accepted: accepted})
cacl, err := model.DeserializeAccessControlList(acl)
if err != nil {
log.Errorf("error deserializing ACL from database, database maybe corrupted: %v", err)
return nil, err
}
conversations = append(conversations, &model.Conversation{ID: id, Handle: handle, ACL: cacl, Attributes: model.DeserializeAttributes(attributes), Accepted: accepted})
}
}
@ -445,7 +456,12 @@ func (cps *CwtchProfileStorage) GetConversation(id int) (*model.Conversation, er
}
rows.Close()
return &model.Conversation{ID: id, Handle: handle, ACL: model.DeserializeAccessControlList(acl), Attributes: model.DeserializeAttributes(attributes), Accepted: accepted}, nil
cacl, err := model.DeserializeAccessControlList(acl)
if err != nil {
log.Errorf("error deserializing ACL from database, database maybe corrupted: %v", err)
return nil, err
}
return &model.Conversation{ID: id, Handle: handle, ACL: cacl, Attributes: model.DeserializeAttributes(attributes), Accepted: accepted}, nil
}
// AcceptConversation sets the accepted status of a conversation to true in the backing datastore
@ -781,7 +797,7 @@ func (cps *CwtchProfileStorage) SearchMessages(conversation int, channel int, pa
}
// GetMostRecentMessages returns the most recent messages in a channel up to a given limit at a given offset
func (cps *CwtchProfileStorage) GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error) {
func (cps *CwtchProfileStorage) GetMostRecentMessages(conversation int, channel int, offset int, limit uint) ([]model.ConversationMessage, error) {
channelID := ChannelID{Conversation: conversation, Channel: channel}
cps.mutex.Lock()

View File

@ -120,9 +120,19 @@ type CwtchPeer interface {
ArchiveConversation(conversation int)
GetConversationInfo(conversation int) (*model.Conversation, error)
FetchConversationInfo(handle string) (*model.Conversation, error)
// API-level management of conversation access control
UpdateConversationAccessControlList(id int, acl model.AccessControlList) error
EnhancedUpdateConversationAccessControlList(conversation int, acjson string) error
GetConversationAccessControlList(conversation int) (model.AccessControlList, error)
EnhancedGetConversationAccessControlList(conversation int) (string, error)
// Convieniance Functions for ACL Management
AcceptConversation(conversation int) error
BlockConversation(conversation int) error
UnblockConversation(conversation int) error
SetConversationAttribute(conversation int, path attr.ScopedZonedPath, value string) error
GetConversationAttribute(conversation int, path attr.ScopedZonedPath) (string, error)
DeleteConversation(conversation int) error
@ -131,7 +141,7 @@ type CwtchPeer interface {
GetChannelMessage(conversation int, channel int, id int) (string, model.Attributes, 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 uint) ([]model.ConversationMessage, error)
UpdateMessageAttribute(conversation int, channel int, id int, key string, value string) error
SearchConversations(pattern string) string
@ -142,7 +152,7 @@ type CwtchPeer interface {
EnhancedGetMessageByContentHash(conversation int, hash string) string
// EnhancedGetMessages returns a set of json-encoded enhanced messages, suitable for rendering in a UI
EnhancedGetMessages(conversation int, index int, count int) string
EnhancedGetMessages(conversation int, index int, count uint) string
// Server Token APIS
// TODO move these to feature protected interfaces

View File

@ -749,6 +749,16 @@ func (e *engine) handlePeerMessage(hostname string, eventID string, context stri
// Fall through handler for the default text conversation.
e.eventManager.Publish(event.NewEvent(event.NewMessageFromPeerEngine, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: hostname, event.Data: string(message)}))
// Don't ack messages in channel 7
// Note: this code explictly doesn't care about malformed messages, we deal with them
// later on...we still want to ack the original send...(as some "malformed" messages
// may be future-ok)
if cm, err := model.DeserializeMessage(string(message)); err == nil {
if cm.IsStream() {
return
}
}
// Send an explicit acknowledgement
// Every other protocol should have an explicit acknowledgement message e.g. value lookups have responses, and file handling has an explicit flow
if err := e.sendPeerMessage(hostname, pmodel.PeerMessage{ID: eventID, Context: event.ContextAck, Data: []byte{}}); err != nil {

View File

@ -2,12 +2,14 @@ package connections
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
model2 "cwtch.im/cwtch/protocol/model"
"encoding/json"
"git.openprivacy.ca/cwtch.im/tapir"
"git.openprivacy.ca/cwtch.im/tapir/applications"
"git.openprivacy.ca/openprivacy/log"
"sync/atomic"
"time"
)
const cwtchCapability = tapir.Capability("cwtchCapability")
@ -133,6 +135,14 @@ func (pa *PeerApp) listen() {
pa.version.Store(Version2)
}
} else {
if cm, err := model.DeserializeMessage(string(packet.Data)); err == nil {
if cm.TransitTime != nil {
rt := time.Now().UTC()
cm.RecvTime = &rt
data, _ := json.Marshal(cm)
packet.Data = data
}
}
pa.MessageHandler(pa.connection.Hostname(), packet.ID, packet.Context, packet.Data)
}
}
@ -148,6 +158,15 @@ func (pa *PeerApp) SendMessage(message model2.PeerMessage) error {
var serialized []byte
var err error
if cm, err := model.DeserializeMessage(string(message.Data)); err == nil {
if cm.SendTime != nil {
tt := time.Now().UTC()
cm.TransitTime = &tt
data, _ := json.Marshal(cm)
message.Data = data
}
}
if pa.version.Load() == Version2 {
// treat data as a pre-serialized string, not as a byte array (which will be base64 encoded and bloat the packet size)
serialized = message.Serialize()

View File

@ -13,7 +13,7 @@ type ChunkSpec []uint64
// CreateChunkSpec given a full list of chunks with their downloaded status (true for downloaded, false otherwise)
// derives a list of identifiers of chunks that have not been downloaded yet
func CreateChunkSpec(progress []bool) ChunkSpec {
var chunks ChunkSpec
chunks := ChunkSpec{}
for i, p := range progress {
if !p {
chunks = append(chunks, uint64(i))

View File

@ -93,7 +93,12 @@ func TestManifestLarge(t *testing.T) {
}
// Prepare Download
cwtchPngOutManifest, _ := LoadManifest("testdata/cwtch.png.manifest")
cwtchPngOutManifest, err := LoadManifest("testdata/cwtch.png.manifest")
if err != nil {
t.Fatalf("could not prepare download %v", err)
}
cwtchPngOutManifest.FileName = "testdata/cwtch.out.png"
defer cwtchPngOutManifest.Close()

View File

@ -35,6 +35,7 @@ type GlobalSettings struct {
Locale string
Theme string
ThemeMode string
ThemeImages bool
PreviousPid int64
ExperimentsEnabled bool
Experiments map[string]bool
@ -62,7 +63,9 @@ type GlobalSettings struct {
var DefaultGlobalSettings = GlobalSettings{
Locale: "en",
Theme: "dark",
Theme: "cwtch",
ThemeMode: "dark",
ThemeImages: false,
PreviousPid: -1,
ExperimentsEnabled: false,
Experiments: map[string]bool{constants.MessageFormattingExperiment: true},

View File

@ -67,7 +67,9 @@ func (ps *ProfileStoreV1) load() error {
if contact.Attributes[event.SaveHistoryKey] == event.SaveHistoryConfirmed {
ss := NewStreamStore(ps.directory, contact.LocalID, ps.key)
cp.Contacts[contact.Onion].Timeline.SetMessages(ss.Read())
if contact, exists := cp.Contacts[contact.Onion]; exists {
contact.Timeline.SetMessages(ss.Read())
}
}
}
@ -78,8 +80,10 @@ func (ps *ProfileStoreV1) load() error {
continue
}
ss := NewStreamStore(ps.directory, group.LocalID, ps.key)
cp.Groups[gid].Timeline.SetMessages(ss.Read())
cp.Groups[gid].Timeline.Sort()
if group, exists := cp.Groups[gid]; exists {
group.Timeline.SetMessages(ss.Read())
group.Timeline.Sort()
}
}
}

View File

@ -64,7 +64,6 @@ func TestFileSharing(t *testing.T) {
os.MkdirAll(dataDir, 0700)
// we don't need real randomness for the port, just to avoid a possible conflict...
mrand.Seed(int64(time.Now().Nanosecond()))
socksPort := mrand.Intn(1000) + 9051
controlPort := mrand.Intn(1000) + 9052
@ -99,7 +98,10 @@ func TestFileSharing(t *testing.T) {
app := app2.NewApp(acn, "./storage", app2.LoadAppSettings("./storage"))
usr, _ := user.Current()
usr, err := user.Current()
if err != nil {
t.Fatalf("current user is undefined")
}
cwtchDir := path.Join(usr.HomeDir, ".cwtch")
os.Mkdir(cwtchDir, 0700)
os.RemoveAll(path.Join(cwtchDir, "testing"))
@ -143,10 +145,23 @@ func TestFileSharing(t *testing.T) {
alice.NewContactConversation(bob.GetOnion(), model.DefaultP2PAccessControl(), true)
alice.PeerWithOnion(bob.GetOnion())
json, err := alice.EnhancedGetConversationAccessControlList(1)
if err != nil {
t.Fatalf("Error!: %v", err)
}
t.Logf("alice<->bob ACL: %s", json)
t.Logf("Waiting for alice and Bob to peer...")
waitForPeerPeerConnection(t, alice, bob)
alice.AcceptConversation(1)
bob.AcceptConversation(1)
err = alice.AcceptConversation(1)
if err != nil {
t.Fatalf("Error!: %v", err)
}
err = bob.AcceptConversation(1)
if err != nil {
t.Fatalf("Error!: %v", err)
}
t.Logf("Alice and Bob are Connected!!")
filesharingFunctionality := filesharing.FunctionalityGate()

View File

@ -99,7 +99,6 @@ func TestCwtchPeerIntegration(t *testing.T) {
os.MkdirAll(dataDir, 0700)
// we don't need real randomness for the port, just to avoid a possible conflict...
mrand.Seed(int64(time.Now().Nanosecond()))
socksPort := mrand.Intn(1000) + 9051
controlPort := mrand.Intn(1000) + 9052

View File

@ -29,7 +29,6 @@ func TestEncryptedStorage(t *testing.T) {
os.MkdirAll(dataDir, 0700)
// we don't need real randomness for the port, just to avoid a possible conflict...
mrand.Seed(int64(time.Now().Nanosecond()))
socksPort := mrand.Intn(1000) + 9051
controlPort := mrand.Intn(1000) + 9052
@ -99,6 +98,10 @@ func TestEncryptedStorage(t *testing.T) {
ci, err = bob.FetchConversationInfo(alice.GetOnion())
}
if ci == nil {
t.Fatalf("could not fetch bobs conversation")
}
body, _, err := bob.GetChannelMessage(ci.ID, 0, 1)
if body != "Hello Bob" || err != nil {
t.Fatalf("unexpected message in conversation channel %v %v", body, err)

View File

@ -58,7 +58,7 @@ func TestFileSharing(t *testing.T) {
os.RemoveAll("cwtch.out.png")
os.RemoveAll("cwtch.out.png.manifest")
log.SetLevel(log.LevelInfo)
log.SetLevel(log.LevelDebug)
log.ExcludeFromPattern("tapir")
os.Mkdir("tordir", 0700)
@ -66,7 +66,6 @@ func TestFileSharing(t *testing.T) {
os.MkdirAll(dataDir, 0700)
// we don't need real randomness for the port, just to avoid a possible conflict...
mrand.Seed(int64(time.Now().Nanosecond()))
socksPort := mrand.Intn(1000) + 9051
controlPort := mrand.Intn(1000) + 9052
@ -101,7 +100,10 @@ func TestFileSharing(t *testing.T) {
app := app2.NewApp(acn, "./storage", app2.LoadAppSettings("./storage"))
usr, _ := user.Current()
usr, err := user.Current()
if err != nil {
t.Fatalf("current user is undefined")
}
cwtchDir := path.Join(usr.HomeDir, ".cwtch")
os.Mkdir(cwtchDir, 0700)
os.RemoveAll(path.Join(cwtchDir, "testing"))
@ -124,9 +126,17 @@ func TestFileSharing(t *testing.T) {
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
aliceQueueOracle := event.NewQueue()
app.GetEventBus(alice.GetOnion()).Subscribe(event.SearchResult, aliceQueueOracle)
aliceEb := app.GetEventBus(alice.GetOnion())
if aliceEb == nil {
t.Fatalf("alice's eventbus is undefined")
}
aliceEb.Subscribe(event.SearchResult, aliceQueueOracle)
queueOracle := event.NewQueue()
app.GetEventBus(bob.GetOnion()).Subscribe(event.FileDownloaded, queueOracle)
bobEb := app.GetEventBus(bob.GetOnion())
if bobEb == nil {
t.Fatalf("bob's eventbus is undefined")
}
bobEb.Subscribe(event.FileDownloaded, queueOracle)
// Turn on File Sharing Experiment...
settings := app.ReadSettings()
@ -141,13 +151,6 @@ func TestFileSharing(t *testing.T) {
bob.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
alice.NewContactConversation(bob.GetOnion(), model.DefaultP2PAccessControl(), true)
alice.PeerWithOnion(bob.GetOnion())
t.Logf("Waiting for alice and Bob to peer...")
waitForPeerPeerConnection(t, alice, bob)
alice.AcceptConversation(1)
t.Logf("Alice and Bob are Connected!!")
filesharingFunctionality := filesharing.FunctionalityGate()
@ -157,10 +160,10 @@ func TestFileSharing(t *testing.T) {
}
alice.SendMessage(1, fileSharingMessage)
bob.AcceptConversation(1)
// Wait for the messages to arrive...
time.Sleep(time.Second * 10)
// Ok this is fun...we just Sent a Message we may not have a connection yet...
// so this test will only pass if sending offline works...
waitForPeerPeerConnection(t, bob, alice)
bob.SendMessage(1, "this is a test message")
bob.SendMessage(1, "this is another test message")

View File

@ -4,18 +4,25 @@ echo "Checking code quality (you want to see no output here)"
echo ""
echo ""
echo "Linting:"
echo "Running staticcheck..."
staticcheck ./...
# In the future we should remove include-pkgs. However, there are a few false positives in the overall go stdlib that make this
# too noisy right now, specifically assigning nil to initialize slices (safe), and using go internal context channels assigned
# nil (also safe).
# We also have one file infinite_channel.go written in a way that static analysis cannot reason about easily. So it is explictly
# ignored.
echo "Running nilaway..."
nilaway -include-pkgs="cwtch.im/cwtch,cwtch.im/tapir,git.openprivacy.ca/openprivacy/connectivity" -exclude-file-docstrings="nolint:nilaway" ./...
echo "Time to format"
gofmt -l -s -w .
# ineffassign (https://github.com/gordonklaus/ineffassign)
echo "Checking for ineffectual assignment of errors (unchecked errors...)"
ineffassign ./..
# echo "Checking for ineffectual assignment of errors (unchecked errors...)"
# ineffassign .
# misspell (https://github.com/client9/misspell/cmd/misspell)
echo "Checking for misspelled words..."
misspell . | grep -v "testing/" | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
# echo "Checking for misspelled words..."
# misspell . | grep -v "testing/" | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"

View File

@ -18,7 +18,6 @@ import (
"os"
path "path/filepath"
"strings"
"time"
)
var tool = flag.String("tool", "", "the tool to use")
@ -86,7 +85,6 @@ func getTokens(bundle string) {
os.MkdirAll(dataDir, 0700)
// we don't need real randomness for the port, just to avoid a possible conflict...
mrand.Seed(int64(time.Now().Nanosecond()))
socksPort := mrand.Intn(1000) + 9051
controlPort := mrand.Intn(1000) + 9052

View File

@ -1,3 +1,4 @@
// nolint:nilaway - the context timeout here is reported as an error, even though it is a by-the-doc example
package utils
import (