Compare commits

..

No commits in common. "main" and "reconnect-fixes" have entirely different histories.

18 changed files with 309 additions and 331 deletions

View File

@ -5,20 +5,15 @@ name: linux-android-windows-test
steps:
- name: fetch
image: openpriv/go-cross-compile:2024.02
image: golang:1.19.1
volumes:
- name: deps
path: /go
commands:
- go install honnef.co/go/tools/cmd/staticcheck@latest
- 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
- 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
- git fetch --tags
- go mod download
- echo `git describe --tags` > VERSION
@ -26,7 +21,7 @@ steps:
- export GOSUMDB="off"
- name: build-linux
image: openpriv/go-cross-compile:2024.02
image: golang:1.19.1
volumes:
- name: deps
path: /go
@ -36,7 +31,7 @@ steps:
- make linux
- name: build-android
image: openpriv/go-cross-compile:2024.02
image: openpriv/android-go-mobile:2023.02
volumes:
- name: deps
path: /go
@ -49,7 +44,7 @@ steps:
- make android
- name: build-windows
image: openpriv/go-cross-compile:2024.02
image: openpriv/mingw-go:2023.01
environment:
GOPATH: /go
volumes:
@ -60,7 +55,7 @@ steps:
- make windows
- name: deploy-buildfiles
image: openpriv/go-cross-compile:2024.02
image: kroniak/ssh-client
pull: if-not-exists
environment:
BUILDFILES_KEY:
@ -151,7 +146,7 @@ steps:
status: [ success ]
commands:
- echo $BUILDFILES_KEY > ~/id_rsab64
- base64 -d -i ~/id_rsab64 -o ~/id_rsa
- base64 -d ~/id_rsab64 > ~/id_rsa
- chmod 400 ~/id_rsa
- export DIR=libCwtch-autobindings-`cat COMMIT_DATE`-`cat VERSION`
- mkdir -p $DIR

1
.gitignore vendored
View File

@ -9,4 +9,3 @@ libCwtch.so
libCwtch.dylib
libCwtch.dll
ios/
build/

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) 2023 Open Privacy Research Society
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -7,28 +7,20 @@ DEFAULT_GOAL: linux
all: linux android windows
ARCH := $(shell uname -m)
ARM_X_CC := aarch64-linux-gnu-gcc
linux: EXPERIMENTS ?= serverExperiment
linux: libCwtch.so
# determine if the x64 cross arm64 compiler is available so we can add that target
LINUX_X_ARM=
ifneq (,$(shell which $(ARM_X_CC)))
LINUX_X_ARM = libCwtch.x.so
endif
linux: EXPERIMENTS ?= "serverExperiment"
linux: libCwtch.so $(LINUX_X_ARM)
macos: EXPERIMENTS ?= "serverExperiment"
macos: EXPERIMENTS ?= serverExperiment
macos: libCwtch.x64.dylib libCwtch.arm64.dylib
android: EXPERIMENTS ?= ""
android: EXPERIMENTS ?=
android: cwtch.aar
windows: EXPERIMENTS ?= "serverExperiment"
windows: EXPERIMENTS ?= serverExperiment
windows: libCwtch.dll
lib.go: generate/generate_bindings.go spec
go run generate/generate_bindings.go --experiments "$(EXPERIMENTS)"
@ -37,31 +29,18 @@ libCwtch.so: lib.go
./switch-ffi.sh
go build -trimpath -ldflags "-buildid=autobindings-$(shell git describe --tags) -X main.buildVer=autobindings-$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.so
mkdir -p build/linux
ifeq ($(ARCH),x86_64)
mv libCwtch.so build/linux/libCwtch.x64.so
else ifeq ($(ARCH),aarch64)
mv libCwtch.so build/linux/libCwtch.arm64.so
endif
mv libCwtch.h build/linux/libCwtch.h
libCwtch.x.so: lib.go
./switch-ffi.sh
mkdir -p build/linux
ifeq ($(ARCH),x86_64)
env CGO_ENABLED=1 CC=$(ARM_X_CC) GOARCH=arm64 go build -trimpath -ldflags "-buildid=autobindings-$(shell git describe --tags) -X main.buildVer=autobindings-$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.x.so
mv libCwtch.x.so build/linux/libCwtch.arm64.so
mv libCwtch.x.h build/linux/libCwtch.h
endif
mv libCwtch.so build/linux/
mv libCwtch.h build/linux/
libCwtch.x64.dylib: lib.go
./switch-ffi.sh
env CGO_CFLAGS="-mmacosx-version-min=10.12" CGO_LDFLAGS="-mmacosx-version-min=10.12" GOARCH=amd64 GOOS=darwin CGO_ENABLED=1 go build -trimpath -ldflags "-buildid=autobindings-$(shell git describe --tags) -X main.buildVer=autobindings-$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.x64.dylib
go build -trimpath -ldflags "-buildid=autobindings-$(shell git describe --tags) -X main.buildVer=autobindings-$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.x64.dylib
mkdir -p build/macos
mv libCwtch.x64.dylib build/macos/
libCwtch.arm64.dylib: lib.go
./switch-ffi.sh
env CGO_CFLAGS="-mmacosx-version-min=10.12" CGO_LDFLAGS="-mmacosx-version-min=10.12" GOARCH=arm64 GOOS=darwin CGO_ENABLED=1 go build -trimpath -ldflags "-buildid=$(shell git describe --tags) -X main.buildVer=$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.arm64.dylib
env GOARCH=arm64 GOOS=darwin CGO_ENABLED=1 go build -trimpath -ldflags "-buildid=$(shell git describe --tags) -X main.buildVer=$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.arm64.dylib
mkdir -p build/macos
mv libCwtch.arm64.dylib build/macos/
@ -87,7 +66,7 @@ clean-autobindings:
rm -f lib.go
clean:
rm -fr lib.go cwtch.aar cwtch_go.apk libCwtch.*.h libCwtch.*.so cwtch-sources.jar libCwtch.dll libCwtch.*.dylib build
rm -fr lib.go cwtch.aar cwtch_go.apk libCwtch.h libCwtch.so cwtch-sources.jar libCwtch.dll libCwtch.*.dylib build
# iOS - for testing purposes only for now, not officially supported

View File

@ -10,6 +10,8 @@ Note for the Flutter-based Cwtch UI Application see: https://git.openprivacy.ca/
go run generate/generate_bindings.go
make linux // alternatively windows, android, macos
**NOTE:** Autobindings currently depends on an unreleased version of Cwtch (`cwtch.im/cwtch v0.18.10-0.20230221235514-49e0d849fa3e`).
### Experiments
Autobindings allow for comile time selection of experiments. Default selections for each target platform are supplied in Makefile but can be overridden with:
@ -59,10 +61,6 @@ Other directives:
- `LD_LIBRARY_PATH` set to point to `libCwtch.so`
- or drop a symlink into `/usr/lib`
### Cross compile arm 64
Needs `sudo apt install gcc-aarch64-linux-gnu`
## Android
- copy `cwtch.aar` into `flutter_app/android/cwtch`

View File

@ -13,5 +13,8 @@ const PeerOnline = "peer-online"
const PeerAutostart = "autostart"
// Description is used on server contacts,
const Description = "description"
// ConversationNotificationPolicy is the attribute label for conversations. When App NotificationPolicy is OptIn a true value here opts in
const ConversationNotificationPolicy = "notification-policy"

View File

@ -1,4 +1,4 @@
package server_hosting
package servers
import (
"cwtch.im/cwtch/app"

View File

@ -0,0 +1,66 @@
package groups
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
constants2 "cwtch.im/cwtch/model/constants"
"cwtch.im/cwtch/peer"
"cwtch.im/cwtch/protocol/connections"
"fmt"
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/constants"
)
const groupExperiment = "tapir-groups-experiment"
const (
// ServerList is a json encoded list of servers
ServerList = event.Field("ServerList")
)
const (
// UpdateServerInfo is an event containing a ProfileOnion and a ServerList
UpdateServerInfo = event.Type("UpdateServerInfo")
)
// GroupFunctionality provides experiment gated server functionality
type GroupFunctionality struct {
}
// ExperimentGate returns GroupFunctionality if the experiment is enabled, and an error otherwise.
func ExperimentGate(experimentMap map[string]bool) (*GroupFunctionality, error) {
if experimentMap[groupExperiment] {
return new(GroupFunctionality), nil
}
return nil, fmt.Errorf("gated by %v", groupExperiment)
}
// GetServerInfoList compiles all the information the UI might need regarding all servers..
func (gf *GroupFunctionality) GetServerInfoList(profile peer.CwtchPeer) []Server {
var servers []Server
for _, server := range profile.GetServers() {
servers = append(servers, gf.GetServerInfo(server, profile))
}
return servers
}
// GetServerInfo compiles all the information the UI might need regarding a particular server including any verified
// cryptographic keys
func (gf *GroupFunctionality) GetServerInfo(serverOnion string, profile peer.CwtchPeer) Server {
serverInfo, _ := profile.FetchConversationInfo(serverOnion)
keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
var serverKeys []ServerKey
for _, keyType := range keyTypes {
if key, has := serverInfo.GetAttribute(attr.PublicScope, attr.ServerKeyZone, string(keyType)); has {
serverKeys = append(serverKeys, ServerKey{Type: string(keyType), Key: key})
}
}
description, _ := serverInfo.GetAttribute(attr.LocalScope, attr.ServerZone, constants.Description)
startTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants2.SyncPreLastMessageTime)).ToString()]
recentTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants2.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}
}

View File

@ -0,0 +1,23 @@
package groups
import "testing"
func TestGroupFunctionality_IsEnabled(t *testing.T) {
_, err := ExperimentGate(map[string]bool{})
if err == nil {
t.Fatalf("group functionality should be disabled")
}
_, err = ExperimentGate(map[string]bool{groupExperiment: true})
if err != nil {
t.Fatalf("group functionality should be enabled")
}
_, err = ExperimentGate(map[string]bool{groupExperiment: false})
if err == nil {
t.Fatalf("group functionality should be disabled")
}
}

20
features/groups/server.go Normal file
View File

@ -0,0 +1,20 @@
package groups
type ServerKey struct {
Type string `json:"type"`
Key string `json:"key"`
}
type SyncStatus struct {
StartTime string `json:"startTime"`
LastMessageTime string `json:"lastMessageTime"`
}
type Server struct {
Onion string `json:"onion"`
Identifier int `json:"identifier"`
Status string `json:"status"`
Description string `json:"description"`
Keys []ServerKey `json:"keys"`
SyncProgress SyncStatus `json:"syncProgress"`
}

View File

@ -96,9 +96,7 @@ func main() {
case "profile":
generatedBindings = generateProfileFunction(generatedBindings, fName, args)
case "(json)profile":
generatedBindings = generateJsonProfileFunction(generatedBindings, fName, args, false)
case "(json-err)profile":
generatedBindings = generateJsonProfileFunction(generatedBindings, fName, args, true)
generatedBindings = generateJsonProfileFunction(generatedBindings, fName, args)
case "@profile-experiment":
experiment := args[0]
generatedBindings = generateExperimentalProfileFunction(generatedBindings, experiment, fName, args[1:])
@ -148,10 +146,6 @@ func intArgPrototype(varName string) (string, string, string, string) {
return fmt.Sprintf(`%s C.int`, ToSnakeCase(varName)), fmt.Sprintf(`int(%v)`, ToSnakeCase(varName)), fmt.Sprintf(`%v int`, varName), varName
}
func uintArgPrototype(varName string) (string, string, string, string) {
return fmt.Sprintf(`%s C.uint`, ToSnakeCase(varName)), fmt.Sprintf(`int(%v)`, ToSnakeCase(varName)), fmt.Sprintf(`%v int`, varName), varName
}
func conversationArgPrototype(varName string) (string, string, string, string) {
return intArgPrototype(varName)
}
@ -241,7 +235,7 @@ func mapArgs(argsTypes []string) (string, string, string, string) {
gUse = append(gUse, c4)
case "int":
if len(argTypeParts) != 2 {
fmt.Printf("generic int arg must have have e.g. int:<name>\n")
fmt.Printf("generic bool arg must have have e.g. bool:<name>\n")
os.Exit(1)
}
c1, c2, c3, c4 := intArgPrototype(argTypeParts[1])
@ -249,20 +243,6 @@ func mapArgs(argsTypes []string) (string, string, string, string) {
c2GoArgs = append(c2GoArgs, c2)
goSpec = append(goSpec, c3)
gUse = append(gUse, c4)
case "uint":
if len(argTypeParts) != 2 {
fmt.Printf("generic uint arg must have have e.g. uint:<name>\n")
os.Exit(1)
}
c1, c2, c3, c4 := uintArgPrototype(argTypeParts[1])
cArgs = append(cArgs, c1)
c2GoArgs = append(c2GoArgs, c2)
goSpec = append(goSpec, c3)
// because of java/kotlin/android/gomobile inability to recognize unsigned integers
// we need to pretent this is a signed interface...so do the final cast here...
// this will cause bad behavior if a negative number is passed through the java
// interface...so...don't do that...
gUse = append(gUse, fmt.Sprintf("uint(%s)", c4))
case "string":
if len(argTypeParts) != 2 {
fmt.Printf("generic string arg must have have e.g. string:<name>\n")
@ -384,7 +364,7 @@ func {{FNAME}}({{GO_ARGS_SPEC}}) {
return bindings
}
func generateJsonProfileFunction(bindings string, name string, argsTypes []string, handleErr bool) string {
func generateJsonProfileFunction(bindings string, name string, argsTypes []string) string {
appPrototype := `
//export c_{{FNAME}}
func c_{{FNAME}}({{C_ARGS}}) *C.char {
@ -394,26 +374,14 @@ func c_{{FNAME}}({{C_ARGS}}) *C.char {
func {{FNAME}}({{GO_ARGS_SPEC}}) string {
cwtchProfile := application.GetPeer(profile)
if cwtchProfile != nil {
{{HANDLE_FUNC}}
return cwtchProfile.{{LIBNAME}}({{GO_ARG}})
}
return ""
}
`
noErrorPrototype := `return cwtchProfile.{{LIBNAME}}({{GO_ARG}})`
withErrorPrototype := `res,err := cwtchProfile.{{LIBNAME}}({{GO_ARG}})
if err != nil {
log.Errorf("could not {{FNAME}} %v", err)
}
return res`
functionPrototype := noErrorPrototype
if handleErr {
functionPrototype = withErrorPrototype
}
cArgs, c2GoArgs, goSpec, gUse := mapArgs(argsTypes)
appPrototype = strings.ReplaceAll(appPrototype, "{{HANDLE_FUNC}}", functionPrototype)
appPrototype = strings.ReplaceAll(appPrototype, "{{FNAME}}", strings.TrimPrefix(name, "Enhanced"))
appPrototype = strings.ReplaceAll(appPrototype, "{{LIBNAME}}", name)
// We need to prepend a set of profile handle arguments...
@ -439,10 +407,7 @@ func {{FNAME}}({{GO_ARGS_SPEC}}) {
if cwtchProfile != nil {
functionality := {{EXPERIMENT}}.FunctionalityGate()
if functionality != nil {
err := functionality.{{LIBNAME}}(cwtchProfile, {{GO_ARG}})
if err != nil {
log.Errorf("error calling experimental feature {{FNAME}}: %v", err)
}
functionality.{{LIBNAME}}(cwtchProfile, {{GO_ARG}})
}
}
}

8
go.mod
View File

@ -1,11 +1,11 @@
module git.openprivacy.ca/cwtch.im/cwtch-autobindings
go 1.20
go 1.19
require (
cwtch.im/cwtch v0.27.0
cwtch.im/cwtch v0.19.6
git.openprivacy.ca/cwtch.im/server v1.4.5
git.openprivacy.ca/openprivacy/connectivity v1.11.0
git.openprivacy.ca/openprivacy/connectivity v1.8.7
git.openprivacy.ca/openprivacy/log v1.0.3
github.com/mutecomm/go-sqlcipher/v4 v4.4.2
)
@ -19,7 +19,7 @@ require (
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
go.etcd.io/bbolt v1.3.6 // indirect
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect

12
go.sum
View File

@ -1,6 +1,6 @@
cwtch.im/cwtch v0.18.0/go.mod h1:StheazFFY7PKqBbEyDVLhzWW6WOat41zV0ckC240c5Y=
cwtch.im/cwtch v0.27.0 h1:MkNIZ+pT5ZwiGlKHk20GMUTlzBsmCWJEibbj6hr+WHw=
cwtch.im/cwtch v0.27.0/go.mod h1:A3i92aFuhyHI2DYO2Qnvl5iqEw0Cox22pdiypHnMOy4=
cwtch.im/cwtch v0.19.6 h1:YeMbsOmUeXXqG0J4j6YCFLU6yTg9v7wTEHARtbM60xs=
cwtch.im/cwtch v0.19.6/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
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=
@ -13,8 +13,8 @@ git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLy
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/connectivity v1.8.7 h1:/hJLAqLaTj41mWI9ACVEEoCGHWUp+Dxw5HXQeqKDoWI=
git.openprivacy.ca/openprivacy/connectivity v1.8.7/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=
@ -92,8 +92,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
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/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4=
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=

View File

@ -3,29 +3,22 @@
echo "Checking code quality (you want to see no output here)"
echo ""
echo "Vetting:"
go vet generate/*
echo ""
echo "Running staticcheck..."
echo "Linting:"
staticcheck lib.go
staticcheck ./utils/*
staticcheck ./generate
# 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="git.openprivacy.ca/cwtch.im/cwtch-autobindings,cwtch.im/cwtch,cwtch.im/tapir,git.openprivacy.ca/openprivacy/connectivity" -exclude-errors-in-files="./templates/lib_template.go" -exclude-file-docstrings="nolint:nilaway" lib.go
nilaway -include-pkgs="git.openprivacy.ca/cwtch.im/cwtch-autobindings,cwtch.im/cwtch,cwtch.im/tapir,git.openprivacy.ca/openprivacy/connectivity" -exclude-errors-in-files="./templates/lib_template.go" -exclude-file-docstrings="nolint:nilaway" ./utils/
echo "Time to format"
gofmt -l -s -w ./utils/*
gofmt -l -s -w ./generate/*
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"

24
spec
View File

@ -3,7 +3,6 @@
# Peer Engine
app ActivatePeerEngine profile
app DeactivatePeerEngine profile
app ConfigureConnections profile bool:listen bool:peers bool:servers
# Profile Management
app CreateProfile name password bool:autostart
@ -20,32 +19,17 @@ profile AcceptConversation conversation
profile BlockConversation conversation
profile UnblockConversation conversation
profile DeleteConversation conversation
profile PeerWithOnion string:handle
profile DisconnectFromPeer string:handle
(json-err)profile EnhancedGetConversationAccessControlList conversation
profile EnhancedUpdateConversationAccessControlList conversation string:json
# Search
(json)profile SearchConversations string:pattern
# Message Management
(json)profile EnhancedSendMessage conversation string:msg
(json)profile EnhancedGetMessageById conversation message
(json)profile EnhancedGetMessageByContentHash conversation string:contentHash
(json)profile EnhancedGetMessages conversation int:index uint:count
(json)profile EnhancedGetMessages conversation int:index int:count
(json)profile EnhancedSendInviteMessage conversation conversation:target
profile UpdateMessageAttribute conversation channel message string:attributeKey string:attributeValue
# Group Management
profile StartGroup string:name string:server
profile QueueJoinServer string:handle
profile DisconnectFromServer string:handle
## Server List Management...
import "cwtch.im/cwtch/functionality/servers"
@profile-experiment PublishServerUpdate servers
@profile-experiment GetServerInfoList servers
@profile-experiment DeleteServerInfo servers string:serverOnion
# Filesharing Management
import "cwtch.im/cwtch/functionality/filesharing"
@ -53,14 +37,14 @@ import "cwtch.im/cwtch/functionality/filesharing"
@profile-experiment RestartFileShare filesharing string:filekey
@profile-experiment StopFileShare filesharing string:filekey
@profile-experiment CheckDownloadStatus filesharing string:filekey
@profile-experiment VerifyOrResumeDownloadDefaultLimit filesharing conversation string:filekey
@profile-experiment VerifyOrResumeDownload filesharing conversation string:filekey
@(json)profile-experiment EnhancedShareFile filesharing conversation string:filepath
@(json)profile-experiment EnhancedGetSharedFiles filesharing conversation
# Server Hosting Experiment
!serverExperiment import "git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/server_hosting"
!serverExperiment global serverExperiment *server_hosting.ServersFunctionality server_hosting
!serverExperiment import "git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
!serverExperiment global serverExperiment *servers.ServersFunctionality servers
!serverExperiment exp CreateServer application password string:description bool:autostart
!serverExperiment exp SetServerAttribute application string:handle string:key string:val
!serverExperiment exp LoadServers application acn password

View File

@ -22,7 +22,6 @@ import (
"runtime"
"runtime/pprof"
"strings"
"strconv"
"time"
mrand "math/rand"
"crypto/rand"
@ -31,7 +30,6 @@ import (
_ "github.com/mutecomm/go-sqlcipher/v4"
"cwtch.im/cwtch/app"
"git.openprivacy.ca/openprivacy/connectivity"
"sync"
{{IMPORTS}}
)
@ -168,14 +166,18 @@ func _startCwtch(appDir string, torPath string) {
globalAppDir = appDir
globalTorPath = torPath
settingsFile := app.LoadAppSettings(appDir)
// start with an Error ACN
erracn := connectivity.NewErrorACN(fmt.Errorf("initializing tor"))
globalACN = connectivity.NewProxyACN(&erracn)
newACN, globalSettings := buildACN(settingsFile.ReadGlobalSettings(), globalTorPath, globalAppDir)
globalACN = connectivity.NewProxyACN(newACN)
settingsFile.WriteGlobalSettings(globalSettings)
application = app.NewApp(&globalACN, appDir, settingsFile)
// Subscribe to all App Events...
eventHandler.HandleApp(application)
// Settings may have changed...
globalSettings = settingsFile.ReadGlobalSettings()
settingsJson, _ := json.Marshal(globalSettings)
// FIXME: This code exists to allow the Splash Screen test in the new UI integration tests to pass
// it doesn't actually fix the problem in theory, and we should get around to ensuring that application
// is safe to access even if shutdown is called concurrently...
@ -184,19 +186,15 @@ func _startCwtch(appDir string, torPath string) {
return
}
// Send global settings to the UI...
globalSettings := application.ReadSettings()
settingsJson, _ := json.Marshal(globalSettings)
application.UpdateSettings(globalSettings)
application.GetPrimaryBus().Publish(event.NewEvent(settings.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
log.Infof("libcwtch-go application launched")
application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{}))
application.QueryACNVersion()
application.LoadProfiles(app.DefactoPasswordForUnencryptedProfiles)
{{EXPERIMENT_REGISTER}}
// Finally attempt to set up a proper Tor
// Note: ResetTor launches an internal goroutine so this is non-blocking...
ResetTor()
}
// the pointer returned from this function **must** be freed using c_Free
@ -247,7 +245,7 @@ func ReconnectCwtchForeground() {
settingsJson, _ := json.Marshal(application.ReadSettings())
application.GetPrimaryBus().Publish(event.NewEvent(settings.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{utils.ReloadEvent: event.True}))
application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{}))
application.QueryACNStatus()
application.QueryACNVersion()
}
@ -314,8 +312,6 @@ func SetProfileAttribute(profileOnion string, key string, value string) {
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileStatus, value)
} else if zone == attr.ProfileZone && key == constants.PeerAutostart {
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAutostart, value)
} else if zone == attr.ProfileZone && key == constants.PeerAppearOffline {
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAppearOffline, value)
} else {
log.Errorf("attempted to set an attribute with an unknown zone: %v", key)
}
@ -397,41 +393,21 @@ func c_ResetTor() {
ResetTor()
}
var torLock sync.Mutex
func ResetTor() {
go func() {
// prevent concurrent calls to this method...
torLock.Lock()
defer torLock.Unlock()
log.Infof("Replacing ACN with new Tor...")
settings := application.ReadSettings()
globalACN.Close() // we need to close first if dateDir is the same, otherwise buildACN can't launch tor.
newAcn, settings, err := buildACN(settings, globalTorPath, globalAppDir)
// only update settings if successful.
if err == nil {
// Only update Tor specific settings...
currentSettings := application.ReadSettings()
currentSettings.TorCacheDir = settings.TorCacheDir
currentSettings.CustomControlPort = settings.CustomControlPort
currentSettings.CustomSocksPort = settings.CustomSocksPort
currentSettings.CustomTorrc = settings.CustomTorrc
application.UpdateSettings(currentSettings)
// We need to update settings on reset as buildACN can alter settings, otherwise the next reset will be broken...
settings = application.ReadSettings()
settingsJson, _ := json.Marshal(settings)
application.GetPrimaryBus().Publish(event.NewEvent(UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
}
log.Infof("Replacing ACN with new Tor...")
settings := application.ReadSettings()
// replace ACN regardlesss
globalACN.ReplaceACN(newAcn)
application.QueryACNStatus()
application.QueryACNVersion()
log.Infof("Restarted")
}()
globalACN.Close() // we need to close first if dateDir is the same, otherwise buildACN can't launch tor.
newAcn, settings := buildACN(settings, globalTorPath, globalAppDir)
application.UpdateSettings(settings)
globalACN.ReplaceACN(newAcn)
application.QueryACNVersion()
// We need to update settings on reset as buildACN can alter settings, otherwise the next reset will be broken...
settings = application.ReadSettings()
settingsJson, _ := json.Marshal(settings)
application.GetPrimaryBus().Publish(event.NewEvent(UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
log.Infof("Restarted")
}
const (
@ -440,8 +416,9 @@ const (
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
)
func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir string) (connectivity.ACN, settings.GlobalSettings, error) {
func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir string) (connectivity.ACN, settings.GlobalSettings) {
mrand.Seed(int64(time.Now().Nanosecond()))
socksPort := mrand.Intn(1000) + 9600
controlPort := socksPort + 1
@ -458,8 +435,7 @@ func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir str
if err != nil {
log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
erracn := connectivity.NewErrorACN(err)
return &erracn, globalSettings, err
return &connectivity.ErrorACN{}, globalSettings
}
if globalSettings.AllowAdvancedTorConfig {
@ -470,30 +446,7 @@ func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir str
// Override Ports if on Tails...
if cwtchTails := os.Getenv("CWTCH_TAILS"); strings.ToLower(cwtchTails) == "true" {
log.Infof("CWTCH_TAILS environment variable set... overriding tor config...")
controlPort = 9051
// In tails 5.13 the control port was changed to 951
// so read the Tails Version File and if it exists...
b, err := os.ReadFile("/etc/amnesia/version")
if err == nil {
// the file should start with the version followed
// by a space...
versionEnd := strings.Index(string(b), " ")
versionStr := string(b)[:versionEnd]
version, err := strconv.ParseFloat(versionStr, 64)
if err == nil {
log.Infof("Confirming Tails Version: %v", version)
// assert the control port if we are at the dedicated version...
// we know this change happened sometime after 5.11
if version >= 5.13 {
controlPort = 951
}
} else {
log.Errorf("Unable to confirm Tails version. CWTCH_TAILS options may not function correctly.")
}
}
socksPort = 9050
globalSettings.CustomControlPort = controlPort
globalSettings.CustomSocksPort = socksPort
@ -517,8 +470,7 @@ func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir str
if err != nil {
log.Errorf("error constructing torrc: %v", err)
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
erracn := connectivity.NewErrorACN(err)
return &erracn, globalSettings, err
return &connectivity.ErrorACN{}, globalSettings
}
dataDir := globalSettings.TorCacheDir
@ -538,8 +490,7 @@ func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir str
if dataDir, err = os.MkdirTemp(torDir, "data-dir-"); err != nil {
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
erracn := connectivity.NewErrorACN(err)
return &erracn, globalSettings, err
return &connectivity.ErrorACN{}, globalSettings
}
}
@ -550,10 +501,9 @@ func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir str
if err != nil {
log.Errorf("Error connecting to Tor replacing with ErrorACN: %v\n", err)
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
erracn := connectivity.NewErrorACN(err)
acn = &erracn
acn = &connectivity.ErrorACN{}
}
return acn, globalSettings, err
return acn, globalSettings
}

View File

@ -4,7 +4,6 @@ import "cwtch.im/cwtch/model"
type Contact struct {
Name string `json:"name"`
LocalName string `json:"localname"`
Onion string `json:"onion"`
Status string `json:"status"`
Picture string `json:"picture"`

View File

@ -1,22 +1,23 @@
package utils
import (
"cwtch.im/cwtch/settings"
"encoding/json"
"fmt"
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
"os"
"strconv"
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/app/plugins"
"cwtch.im/cwtch/functionality/servers"
"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"
"encoding/json"
"fmt"
constants2 "git.openprivacy.ca/cwtch.im/cwtch-autobindings/constants"
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/server_hosting"
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/features/groups"
"git.openprivacy.ca/openprivacy/log"
"os"
"strconv"
"time"
@ -65,10 +66,10 @@ func (eh *EventHandler) HandleApp(application app.Application) {
application.GetPrimaryBus().Subscribe(event.ACNVersion, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(settings.UpdateGlobalSettings, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(settings.CwtchStarted, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(server_hosting.NewServer, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(server_hosting.ServerIntentUpdate, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(server_hosting.ServerDeleted, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(server_hosting.ServerStatsUpdate, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(servers.NewServer, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(servers.ServerIntentUpdate, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(servers.ServerDeleted, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(servers.ServerStatsUpdate, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.StartingStorageMiragtion, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.DoneStorageMigration, eh.appBusQueue)
}
@ -102,7 +103,30 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
if err != nil {
break
}
if newAcnStatus == 100 {
if acnStatus != 100 {
for _, onion := range eh.app.ListProfiles() {
profile := eh.app.GetPeer(onion)
if profile != nil {
autostart, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerAutostart)
if !exists || autostart == "true" {
eh.app.ActivatePeerEngine(onion)
}
}
}
//eh.api.LaunchServers()
}
} else {
if acnStatus == 100 {
// just fell offline
for _, onion := range eh.app.ListProfiles() {
eh.app.DeactivatePeerEngine(onion)
}
//eh.api.StopServers()
}
}
acnStatus = newAcnStatus
for _, module := range eh.modules {
module.OnACNStatusEvent(eh.app, e)
}
@ -145,15 +169,9 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
// Start up the Profile
if acnStatus == 100 {
autostart, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAutostart)
appearOffline, appearOfflineExists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAppearOffline)
autostart, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerAutostart)
if !exists || autostart == "true" {
eh.app.ActivatePeerEngine(onion)
if appearOfflineExists && appearOffline == "true" {
// don't configure any connections...
} else {
eh.app.ConfigureConnections(onion, true, true, true)
}
}
}
@ -167,13 +185,6 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
}
e.Data["autostart"] = autostart
appearOffline, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAppearOffline)
// legacy profiles should not appearOffline by default
if !exists {
appearOffline = "false"
}
e.Data["appearOffline"] = appearOffline
// Name always exists
e.Data[constants.Name], _ = profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
e.Data[constants2.DefaultProfilePicture] = RandomProfileImage(onion)
@ -186,7 +197,7 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
}
// Construct our conversations and our srever lists
var contacts []Contact
var knownServers []servers.Server
var servers []groups.Server
conversations, err := profile.FetchConversations()
@ -198,23 +209,20 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
// has been disabled and then is later re-enabled. As such we need to ensure that this list is
// re-fetched when the group experiment is enabled via a dedicated ListServerInfo event...
if conversationInfo.IsServer() {
groupHandler := servers.FunctionalityGate()
groupHandler, err := groups.ExperimentGate(eh.app.ReadSettings().Experiments)
if err == nil {
serverInfo, _ := groupHandler.GetServerInfo(profile, conversationInfo.Handle)
knownServers = append(knownServers, serverInfo)
servers = append(servers, groupHandler.GetServerInfo(conversationInfo.Handle, profile))
}
continue
}
// Prefer local override to public name...
localname, exists := conversationInfo.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
name, exists := conversationInfo.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
if !exists {
localname = ""
}
name, exists := conversationInfo.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
if !exists {
name = conversationInfo.Handle
name, exists = conversationInfo.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
if !exists {
name = conversationInfo.Handle
}
}
// Resolve the profile image of the contact
@ -224,7 +232,7 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
cpicPath = RandomGroupImage(conversationInfo.Handle)
defaultPath = RandomGroupImage(conversationInfo.Handle)
} else {
cpicPath = eh.GetProfileImage(profile, conversationInfo, settings.DownloadPath)
cpicPath = GetProfileImage(profile, conversationInfo, settings.DownloadPath)
defaultPath = RandomProfileImage(conversationInfo.Handle)
}
@ -295,7 +303,6 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
contacts = append(contacts, Contact{
Name: name,
LocalName: localname,
Identifier: conversationInfo.ID,
Onion: conversationInfo.Handle,
Status: connections.ConnectionStateName[state],
@ -322,8 +329,8 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
e.Data["ContactsJson"] = string(bytes)
// Marshal the server list into the new peer event...
serversListBytes, _ := json.Marshal(knownServers)
e.Data[servers.ServerList] = string(serversListBytes)
serversListBytes, _ := json.Marshal(servers)
e.Data[groups.ServerList] = string(serversListBytes)
log.Debugf("contactsJson %v", e.Data["ContactsJson"])
}
@ -333,17 +340,15 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
return string(json)
}
func (eh *EventHandler) GetProfileImage(profile peer.CwtchPeer, conversationInfo *model.Conversation, basepath string) string {
if eh.app.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
fileKey, err := profile.GetConversationAttribute(conversationInfo.ID, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.CustomProfileImageKey)))
if err == nil {
if value, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey)); exists && value == event.True {
fp, _ := filesharing.GenerateDownloadPath(basepath, fileKey, true)
// check if the file exists...if it does then set the path...
if _, err := os.Stat(fp); err == nil {
image, _ := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.path", fileKey))
return image
}
func GetProfileImage(profile peer.CwtchPeer, conversationInfo *model.Conversation, basepath string) string {
fileKey, err := profile.GetConversationAttribute(conversationInfo.ID, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.CustomProfileImageKey)))
if err == nil {
if value, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey)); exists && value == event.True {
fp, _ := filesharing.GenerateDownloadPath(basepath, fileKey, true)
// check if the file exists...if it does then set the path...
if _, err := os.Stat(fp); err == nil {
image, _ := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.path", fileKey))
return image
}
}
}
@ -359,10 +364,6 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
log.Errorf("eh.app == nil in handleProfileEvent... this shouldnt happen?")
} else {
profile := eh.app.GetPeer(ev.Profile)
if profile == nil {
log.Errorf("something has gone very wrong. profile is nil in handleProfileEvent")
return ""
}
log.Debugf("New Profile Event to Handle: %v", ev)
switch ev.Event.EventType {
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
@ -372,7 +373,7 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
if ci != nil && err == nil {
ev.Event.Data[event.ConversationID] = strconv.Itoa(ci.ID)
profile.SetConversationAttribute(ci.ID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants2.Archived)), event.False)
ev.Event.Data[constants2.Picture] = eh.GetProfileImage(profile, ci, eh.app.ReadSettings().DownloadPath)
ev.Event.Data[constants2.Picture] = GetProfileImage(profile, ci, eh.app.ReadSettings().DownloadPath)
} else {
// TODO This Conversation May Not Exist Yet...But we are not in charge of creating it...
log.Errorf("todo wait for contact to be added before processing this event...")
@ -401,17 +402,13 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
ev.Event.Data["Nick"] = ev.Event.Data["RemotePeer"]
}
}
ev.Event.Data[constants2.Picture] = eh.GetProfileImage(profile, ci, eh.app.ReadSettings().DownloadPath)
ev.Event.Data[constants2.Picture] = GetProfileImage(profile, ci, eh.app.ReadSettings().DownloadPath)
}
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
profile.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants2.Archived)), event.False)
gci, err := profile.GetConversationInfo(conversationID)
if err != nil {
log.Errorf("new message from non-existant group: %v", err)
break
}
gci, _ := profile.GetConversationInfo(conversationID)
groupServer := gci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)).ToString()]
state := profile.GetPeerState(groupServer)
// if syncing, don't flood with notifications
@ -430,13 +427,11 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
count, err := profile.GetChannelMessageCount(conversationID, 0)
if err != nil {
log.Errorf("error fetching channel message count %v %v", conversationID, err)
break
}
conversationInfo, err := profile.GetConversationInfo(conversationID)
if err != nil {
log.Errorf("error fetching conversation info for %v %v", conversationID, err)
break
}
blocked := constants.False
@ -449,11 +444,7 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
accepted = constants.True
}
acl, err := json.Marshal(conversationInfo.ACL)
if err != nil {
log.Errorf("received invalid ACL in conversation: %v", err)
break
}
acl, _ := json.Marshal(conversationInfo.ACL)
lastMessage, _ := profile.GetMostRecentMessages(conversationID, 0, 0, 1)
ev.Event.Data["unread"] = strconv.Itoa(count) // if this is a new contact with messages attached then by-definition these are unread...
@ -473,16 +464,8 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
groupPic := RandomGroupImage(ev.Event.Data[event.GroupID])
ev.Event.Data[constants2.Picture] = groupPic
conversationID, err := strconv.Atoi(ev.Event.Data[event.ConversationID])
if err != nil {
log.Errorf("invalid conversation id recieved %v", err)
break
}
conversationInfo, err := profile.GetConversationInfo(conversationID)
if err != nil {
log.Errorf("error fetching conversation info for %v %v", conversationID, err)
break
}
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
conversationInfo, _ := profile.GetConversationInfo(conversationID)
acl, _ := json.Marshal(conversationInfo.ACL)
ev.Event.Data["accessControlList"] = string(acl)
case event.NewGroup:
@ -493,35 +476,71 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
groupPic := RandomGroupImage(invite.GroupID)
ev.Event.Data[constants2.Picture] = groupPic
conversationID, err := strconv.Atoi(ev.Event.Data[event.ConversationID])
if err != nil {
log.Errorf("invalid conversation id recieved %v", err)
break
}
conversationInfo, err := profile.GetConversationInfo(conversationID)
if err != nil {
log.Errorf("error fetching conversation info for %v %v", conversationID, err)
break
}
acl, err := json.Marshal(conversationInfo.ACL)
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
conversationInfo, _ := profile.GetConversationInfo(conversationID)
acl, _ := json.Marshal(conversationInfo.ACL)
ev.Event.Data["accessControlList"] = string(acl)
if err != nil {
log.Errorf("received invalid ACL in conversation: %v", err)
break
}
} else {
log.Errorf("received a new group event which contained an invalid invite %v. this should never happen and likely means there is a bug in cwtch. Please file a ticket @ https://git.openprivacy.ca/cwtch.im/cwtch", err)
return ""
}
case event.PeerStateChange:
cxnState := connections.ConnectionStateToType()[ev.Event.Data[event.ConnectionState]]
// skip events the UI doesn't act on
if cxnState == connections.CONNECTING || cxnState == connections.CONNECTED {
return ""
}
contact, err := profile.FetchConversationInfo(ev.Event.Data[event.RemotePeer])
if ev.Event.Data[event.RemotePeer] == profile.GetOnion() {
return "" // suppress events from our own profile...
}
// We do not know who this is...don't send any event until we see a message from them
// (at that point the conversation will have been created...)
if contact == nil || err != nil || contact.ID == 0 {
return ""
}
// if we already know this state, suppress
if knownState, exists := contactStateCache[ev.Event.Data[event.RemotePeer]]; exists && cxnState == knownState {
return ""
}
contactStateCache[ev.Event.Data[event.RemotePeer]] = cxnState
case event.ServerStateChange:
cxnState := connections.ConnectionStateToType()[ev.Event.Data[event.ConnectionState]]
// skip events the UI doesn't act on
if cxnState == connections.CONNECTING || cxnState == connections.CONNECTED {
return ""
}
// if we already know this state, suppress
if knownState, exists := contactStateCache[ev.Event.Data[event.RemotePeer]]; exists && cxnState == knownState {
return ""
}
contactStateCache[ev.Event.Data[event.RemotePeer]] = cxnState
case event.NewRetValMessageFromPeer:
// auto handled event means the setting is already done, we're just deciding if we need to tell the UI
onion := ev.Event.Data[event.RemotePeer]
scope := ev.Event.Data[event.Scope]
path := ev.Event.Data[event.Path]
exists, _ := strconv.ParseBool(ev.Event.Data[event.Exists])
conversation, err := profile.FetchConversationInfo(onion)
if err == nil {
if exists && attr.IntoScope(scope) == attr.PublicScope {
zone, path := attr.ParseZone(path)
if val, err := profile.GetConversationAttribute(conversation.ID, attr.LocalScope.ConstructScopedZonedPath(zone.ConstructZonedPath(path))); err == nil || val != "" {
// we have a locally set override, don't pass this remote set public scope update to UI
return ""
}
}
}
case event.TokenManagerInfo:
conversations, err := profile.FetchConversations()
if err == nil {
@ -566,10 +585,6 @@ func unwrap(original *EventProfileEnvelope) *event.Event {
func (eh *EventHandler) startHandlingPeer(onion string) {
eventBus := eh.app.GetEventBus(onion)
if eventBus == nil {
log.Errorf("could not start handling peer events .. event bus is nil")
return
}
q := event.NewQueue()
eventBus.Subscribe(event.NetworkStatus, q)
@ -587,7 +602,8 @@ func (eh *EventHandler) startHandlingPeer(onion string) {
eventBus.Subscribe(event.NewGroup, q)
eventBus.Subscribe(event.ServerStateChange, q)
eventBus.Subscribe(event.PeerStateChange, q)
eventBus.Subscribe(event.UpdatedConversationAttribute, q)
eventBus.Subscribe(event.NewRetValMessageFromPeer, q)
eventBus.Subscribe(event.ShareManifest, q)
eventBus.Subscribe(event.ManifestSizeReceived, q)
eventBus.Subscribe(event.ManifestError, q)
eventBus.Subscribe(event.ManifestReceived, q)
@ -596,9 +612,6 @@ func (eh *EventHandler) startHandlingPeer(onion string) {
eventBus.Subscribe(event.FileDownloaded, q)
eventBus.Subscribe(event.TokenManagerInfo, q)
eventBus.Subscribe(event.ProtocolEngineCreated, q)
eventBus.Subscribe(event.SearchResult, q)
eventBus.Subscribe(event.SearchCancelled, q)
eventBus.Subscribe(servers.UpdateServerInfo, q)
go eh.forwardProfileMessages(onion, q)
}