mirror of
https://github.com/mautrix/signal.git
synced 2026-05-14 13:16:54 -04:00
Compare commits
1 commit
main
...
tulir/stat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92ff478b1f |
121 changed files with 4491 additions and 10979 deletions
14
.github/ISSUE_TEMPLATE/bug.md
vendored
14
.github/ISSUE_TEMPLATE/bug.md
vendored
|
|
@ -7,12 +7,10 @@ type: Bug
|
|||
|
||||
---
|
||||
|
||||
<!-- Include relevant logs, the bridge version and other important details here -->
|
||||
<!--
|
||||
Remember to include relevant logs, the bridge version and any other details.
|
||||
|
||||
### Checklist
|
||||
|
||||
<!-- All items below are mandatory. Issues not following the rules may be closed without comment. -->
|
||||
|
||||
* [ ] This is an actual bug, not just a setup issue (see the [troubleshooting docs](https://docs.mau.fi/bridges/general/troubleshooting.html) or ask in the Matrix room for setup help).
|
||||
* [ ] I am certain that sufficient information is included. Ask in the Matrix room first if not.
|
||||
* [ ] The bug is still present on the main branch. The `!signal version` command output is: ``
|
||||
It's always best to ask in the Matrix room first, especially if you aren't sure
|
||||
what details are needed. Issues with insufficient detail will likely just be
|
||||
ignored or closed immediately.
|
||||
-->
|
||||
|
|
|
|||
16
.github/workflows/go.yml
vendored
16
.github/workflows/go.yml
vendored
|
|
@ -11,14 +11,14 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ["1.25", "1.26"]
|
||||
name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
||||
go-version: ["1.24", "1.25"]
|
||||
name: Lint ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
|
|
@ -40,14 +40,14 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ["1.25", "1.26"]
|
||||
name: Test ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
||||
go-version: ["1.24", "1.25"]
|
||||
name: Test ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
|
|
|
|||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
lock-stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
- uses: dessant/lock-threads@v5
|
||||
id: lock
|
||||
with:
|
||||
issue-inactive-days: 90
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ repos:
|
|||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.4
|
||||
rev: v1.0.0-rc.2
|
||||
hooks:
|
||||
- id: go-imports
|
||||
exclude: "pb\\.go$"
|
||||
|
|
|
|||
62
CHANGELOG.md
62
CHANGELOG.md
|
|
@ -1,68 +1,8 @@
|
|||
# v26.04
|
||||
# v25.11 (unreleased)
|
||||
|
||||
* Updated libsignal to v0.92.1
|
||||
* Added support for admin message deletes from Signal.
|
||||
* Added support for binary service IDs in storage service.
|
||||
* Fixed `private_chat_portal_meta` option not setting DM room names correctly.
|
||||
* Fixed panic if user is logged out during initial chat sync.
|
||||
* Fixed avatar upload failing when creating new Signal group.
|
||||
|
||||
# v26.03
|
||||
|
||||
* Switched to sending binary service ID fields in outgoing messages.
|
||||
* Added support for roundtripping large attachments via disk to avoid keeping
|
||||
the entire file in memory during en/decryption.
|
||||
|
||||
# v26.02.2
|
||||
|
||||
* Added support for more new binary service ID fields.
|
||||
|
||||
# v26.02.1
|
||||
|
||||
* Updated libsignal to v0.87.5.
|
||||
* Added support for new binary service ID fields that Signal 8.0 switched to.
|
||||
|
||||
# v26.02
|
||||
|
||||
* Bumped minimum Go version to 1.25.
|
||||
* Updated libsignal to v0.87.1.
|
||||
* Added automatic recovery for the session not found error from libsignal.
|
||||
* Fixed sender key state not being cleared on logout properly.
|
||||
|
||||
# v26.01
|
||||
|
||||
* Updated libsignal to v0.86.12.
|
||||
* Changed automatic contact list sync option to only sync every 3 days rather
|
||||
than on every restart.
|
||||
* Fixed sending messages to groups with no other registered members.
|
||||
* Fixed sender key sends failing if some users had changed devices.
|
||||
* Fixed timestamps of outgoing typing notifications in DMs.
|
||||
|
||||
# v25.12
|
||||
|
||||
* Updated libsignal to v0.86.8.
|
||||
* Updated Docker image to Alpine 3.23.
|
||||
* Added support for dropping incoming DMs from blocked contacts on Signal.
|
||||
* Added support for sender key encryption when sending to groups, which makes
|
||||
sending much faster and enables sending typing notifications.
|
||||
* Added support for encryption retry receipts.
|
||||
* Fixed bugs with handling poll votes.
|
||||
* Fixed history transfer option not showing up when pairing with Signal Android.
|
||||
* Fixed nicknames being cleared not being bridged
|
||||
(thanks to [@Enzime] in [#623]).
|
||||
|
||||
[#623]: https://github.com/mautrix/signal/pull/623
|
||||
[@Enzime]: https://github.com/Enzime
|
||||
|
||||
# v25.11
|
||||
|
||||
* Updated libsignal to v0.86.4.
|
||||
* Added support for bridging invite state in groups for phone number invites.
|
||||
* Added support for polls.
|
||||
* Fixed PNI signature not being sent when replying to message requests.
|
||||
* Fixed unnecessary repeating error notices when Signal is down.
|
||||
* Fixed sticker size metadata on Matrix not matching how native Signal Desktop
|
||||
renders them.
|
||||
|
||||
# v25.10
|
||||
|
||||
|
|
|
|||
22
Dockerfile
22
Dockerfile
|
|
@ -1,17 +1,18 @@
|
|||
# -- Build libsignal (with Rust) --
|
||||
FROM rust:1-alpine AS rust-builder
|
||||
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev protobuf-dev
|
||||
FROM rust:1-alpine as rust-builder
|
||||
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev
|
||||
|
||||
WORKDIR /build
|
||||
# Copy all files needed for Rust build, and no Go files
|
||||
COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/.
|
||||
COPY build-rust.sh .
|
||||
|
||||
ARG DBG=0
|
||||
RUN ./build-rust.sh
|
||||
|
||||
# -- Build mautrix-signal (with Go) --
|
||||
FROM golang:1-alpine3.23 AS go-builder
|
||||
RUN apk add --no-cache git ca-certificates build-base olm-dev zlib-dev
|
||||
FROM golang:1-alpine3.22 AS go-builder
|
||||
RUN apk add --no-cache git ca-certificates build-base olm-dev
|
||||
|
||||
WORKDIR /build
|
||||
# Copy all files needed for Go build, and no Rust files
|
||||
|
|
@ -25,14 +26,20 @@ COPY pkg/connector/. pkg/connector/.
|
|||
COPY cmd/. cmd/.
|
||||
COPY .git .git
|
||||
|
||||
ARG DBG=0
|
||||
ENV LIBRARY_PATH=.
|
||||
COPY --from=rust-builder /build/pkg/libsignalgo/libsignal/target/*/libsignal_ffi.a ./
|
||||
RUN <<EOF
|
||||
if [ "$DBG" = 1 ]; then
|
||||
go install github.com/go-delve/delve/cmd/dlv@latest
|
||||
else
|
||||
touch /go/bin/dlv
|
||||
fi
|
||||
EOF
|
||||
RUN ./build-go.sh
|
||||
|
||||
# -- Run mautrix-signal --
|
||||
FROM alpine:3.23
|
||||
FROM alpine:3.22
|
||||
|
||||
ENV UID=1337 \
|
||||
GID=1337
|
||||
|
|
@ -41,6 +48,11 @@ RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go olm
|
|||
|
||||
COPY --from=go-builder /build/mautrix-signal /usr/bin/mautrix-signal
|
||||
COPY --from=go-builder /build/docker-run.sh /docker-run.sh
|
||||
COPY --from=go-builder /go/bin/dlv /usr/bin/dlv
|
||||
VOLUME /data
|
||||
|
||||
ARG DBG
|
||||
ARG DBGWAIT=0
|
||||
ENV DBG=${DBG} DBGWAIT=${DBGWAIT}
|
||||
RUN echo "Debug mode: DBG=${DBG} DBGWAIT=${DBGWAIT}"
|
||||
CMD ["/docker-run.sh"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
ARG DOCKER_HUB="docker.io"
|
||||
|
||||
FROM ${DOCKER_HUB}/alpine:3.23
|
||||
FROM ${DOCKER_HUB}/alpine:3.22
|
||||
|
||||
ENV UID=1337 \
|
||||
GID=1337
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
* [x] Text
|
||||
* [x] Formatting
|
||||
* [x] Mentions
|
||||
* [x] Polls
|
||||
* [x] Media
|
||||
* [x] Images
|
||||
* [x] Audio files
|
||||
|
|
@ -35,7 +34,6 @@
|
|||
* [x] Text
|
||||
* [x] Formatting
|
||||
* [x] Mentions
|
||||
* [x] Polls
|
||||
* [ ] Media
|
||||
* [x] Images
|
||||
* [x] Voice notes
|
||||
|
|
@ -67,8 +65,8 @@
|
|||
* [ ] Delivery receipts (there's no good way to bridge these)
|
||||
* [x] Disappearing messages
|
||||
* Misc
|
||||
* [x] Automatic portal creation
|
||||
* [x] After login
|
||||
* [ ] Automatic portal creation
|
||||
* [ ] After login
|
||||
* [x] When receiving message
|
||||
* [x] Linking as secondary device
|
||||
* [ ] Registering as primary device
|
||||
|
|
|
|||
|
|
@ -1,2 +1,9 @@
|
|||
#!/bin/sh
|
||||
BINARY_NAME=mautrix-signal go tool maubuild "$@"
|
||||
MAUTRIX_VERSION=$(cat go.mod | grep 'maunium.net/go/mautrix ' | awk '{ print $2 }')
|
||||
GO_LDFLAGS="-X main.Tag=$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$(git rev-parse HEAD) -X 'main.BuildTime=`date -Iseconds`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'"
|
||||
if [ "$DBG" = 1 ]; then
|
||||
GO_GCFLAGS='all=-N -l'
|
||||
else
|
||||
GO_LDFLAGS="-s -w ${GO_LDFLAGS}"
|
||||
fi
|
||||
go build -gcflags="$GO_GCFLAGS" -ldflags="$GO_LDFLAGS" -o mautrix-signal "$@" ./cmd/mautrix-signal
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
#!/bin/sh
|
||||
# TODO fix linking with debug library
|
||||
#if [ "$DBG" != 1 ]; then
|
||||
# RUST_PROFILE=release
|
||||
#else
|
||||
# RUST_PROFILE=dev
|
||||
#fi
|
||||
RUST_PROFILE=release
|
||||
git submodule update --init
|
||||
cd pkg/libsignalgo/libsignal && RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER="" cargo build -p libsignal-ffi --profile=release
|
||||
cd pkg/libsignalgo/libsignal && RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER="" cargo build -p libsignal-ffi --profile=$RUST_PROFILE
|
||||
|
|
|
|||
1
build.sh
1
build.sh
|
|
@ -1,5 +1,4 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
./build-rust.sh
|
||||
cp -f pkg/libsignalgo/libsignal/target/release/libsignal_ffi.a .
|
||||
LIBRARY_PATH=.:$LIBRARY_PATH ./build-go.sh
|
||||
|
|
|
|||
|
|
@ -17,12 +17,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/connector"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||
)
|
||||
|
||||
// Information to find out exactly which commit the bridge was built from.
|
||||
|
|
@ -37,14 +34,13 @@ var m = mxmain.BridgeMain{
|
|||
Name: "mautrix-signal",
|
||||
URL: "https://github.com/mautrix/signal",
|
||||
Description: "A Matrix-Signal puppeting bridge.",
|
||||
Version: "26.04",
|
||||
Version: "25.10",
|
||||
SemCalVer: true,
|
||||
|
||||
Connector: &connector.SignalConnector{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
web.UserAgent = fmt.Sprintf("mautrix-signal/%s %s", m.Version, web.BaseUserAgent)
|
||||
m.PostStart = func() {
|
||||
if m.Matrix.Provisioning != nil {
|
||||
m.Matrix.Provisioning.Router.HandleFunc("GET /v2/resolve_identifier/{phonenum}", legacyProvResolveIdentifier)
|
||||
|
|
|
|||
41
go.mod
41
go.mod
|
|
@ -1,51 +1,48 @@
|
|||
module go.mau.fi/mautrix-signal
|
||||
|
||||
go 1.25.0
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.26.2
|
||||
|
||||
tool go.mau.fi/util/cmd/maubuild
|
||||
toolchain go1.25.3
|
||||
|
||||
require (
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mattn/go-pointer v0.0.1
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/sync v0.20.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
go.mau.fi/util v0.9.2
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b
|
||||
golang.org/x/net v0.46.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4
|
||||
maunium.net/go/mautrix v0.25.3-0.20251027130059-9f2669025f28
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lib/pq v1.12.3 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.44 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/yuin/goldmark v1.8.2 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
go.mau.fi/zeroconfig v0.2.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
maunium.net/go/mauflag v1.0.0 // indirect
|
||||
|
|
|
|||
76
go.sum
76
go.sum
|
|
@ -1,16 +1,17 @@
|
|||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg=
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
|
@ -22,19 +23,23 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
|
||||
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
|
||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM=
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 h1:QTvNkZ5ylY0PGgA+Lih+GdboMLY/G9SEGLMEGVjTVA4=
|
||||
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
|
|
@ -42,8 +47,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
|
|||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
|
|
@ -51,37 +56,36 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
|
|||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 h1:YPEmc+li7TF6C9AdRTcSLMb6yCHdF27/wNT7kFLIVNg=
|
||||
go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM=
|
||||
go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU=
|
||||
go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
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=
|
||||
|
|
@ -91,5 +95,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||
maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4 h1:zNC9eVAhw8FhKpM3AxNAh/iy75UEYX91uJUvqqAYlvo=
|
||||
maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4/go.mod h1:3sOGhXi3P1V6/NruTA0gujkvTypXVUraWktCuTGyDuM=
|
||||
maunium.net/go/mautrix v0.25.3-0.20251027130059-9f2669025f28 h1:BG0IPWqlVR2mxAQM8jhOhHa168si5iXKeTv11nCGj58=
|
||||
maunium.net/go/mautrix v0.25.3-0.20251027130059-9f2669025f28/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI=
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
|||
if dm == nil {
|
||||
continue
|
||||
}
|
||||
cm := s.Main.MsgConv.ToMatrix(ctx, s.Client, params.Portal, senderACI, s.Main.Bridge.Bot, dm, attMap)
|
||||
cm := s.Main.MsgConv.ToMatrix(ctx, s.Client, params.Portal, s.Main.Bridge.Bot, dm, attMap)
|
||||
convertedReactions := make([]*bridgev2.BackfillReaction, 0, len(reactions))
|
||||
for _, reaction := range reactions {
|
||||
reactionSenderACI, err := getRecipientACI(reaction.AuthorId)
|
||||
|
|
@ -187,7 +187,7 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
|||
CompleteCallback: func() {
|
||||
// When reaching the last backwards backfill batch, delete the chat from the backup store.
|
||||
// If backwards backfilling isn't enabled, delete immediately after the first backfill request.
|
||||
if (!params.Forward && len(items) < params.Count) || !s.Main.Bridge.Config.Backfill.Queue.AnyEnabled() {
|
||||
if (!params.Forward && len(items) < params.Count) || (!s.Main.Bridge.Config.Backfill.Queue.Enabled && !s.Main.Bridge.Config.Backfill.WillPaginateManually) {
|
||||
err := s.Client.Store.BackupStore.DeleteBackupChat(ctx, chat.Id)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete chat from backup store")
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
|||
}
|
||||
|
||||
func capID() string {
|
||||
base := "fi.mau.signal.capabilities.2026_05_12"
|
||||
base := "fi.mau.signal.capabilities.2025_10_27"
|
||||
if ffmpeg.Supported() {
|
||||
return base + "+ffmpeg"
|
||||
}
|
||||
|
|
@ -111,8 +111,7 @@ var signalCaps = &event.RoomFeatures{
|
|||
},
|
||||
event.CapMsgSticker: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
// Signal clients will only render static webp, so apng is preferred
|
||||
"image/webp": event.CapLevelPartialSupport,
|
||||
"image/webp": event.CapLevelFullySupported,
|
||||
"image/png": event.CapLevelFullySupported,
|
||||
"image/apng": event.CapLevelFullySupported,
|
||||
"image/gif": supportedIfFFmpeg(),
|
||||
|
|
@ -139,10 +138,10 @@ var signalCaps = &event.RoomFeatures{
|
|||
},
|
||||
},
|
||||
State: event.StateFeatureMap{
|
||||
event.StateRoomName.Type: {Level: event.CapLevelFullySupported},
|
||||
event.StateRoomAvatar.Type: {Level: event.CapLevelFullySupported},
|
||||
event.StateTopic.Type: {Level: event.CapLevelFullySupported},
|
||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
||||
event.StateRoomName.Type: event.CapLevelFullySupported,
|
||||
event.StateRoomAvatar.Type: event.CapLevelFullySupported,
|
||||
event.StateTopic.Type: event.CapLevelFullySupported,
|
||||
event.StateBeeperDisappearingTimer.Type: event.CapLevelFullySupported,
|
||||
},
|
||||
MemberActions: event.MemberFeatureMap{
|
||||
event.MemberActionInvite: event.CapLevelFullySupported,
|
||||
|
|
@ -170,12 +169,6 @@ var signalCaps = &event.RoomFeatures{
|
|||
CustomEmojiReactions: false,
|
||||
ReadReceipts: true,
|
||||
TypingNotifications: true,
|
||||
|
||||
DeleteChat: true,
|
||||
MessageRequest: &event.MessageRequestFeatures{
|
||||
AcceptWithMessage: event.CapLevelPartialSupport,
|
||||
AcceptWithButton: event.CapLevelFullySupported,
|
||||
},
|
||||
}
|
||||
|
||||
var signalDisappearingCap = &event.DisappearingTimerCapability{
|
||||
|
|
@ -190,7 +183,7 @@ func init() {
|
|||
signalCapsDM.ID = capID() + "+dm"
|
||||
signalCapsDM.MemberActions = nil
|
||||
signalCapsDM.State = event.StateFeatureMap{
|
||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
||||
event.StateBeeperDisappearingTimer.Type: event.CapLevelFullySupported,
|
||||
}
|
||||
signalCapsNoteToSelf = ptr.Clone(signalCapsDM)
|
||||
signalCapsNoteToSelf.EditMaxAge = nil
|
||||
|
|
@ -212,7 +205,6 @@ var signalGeneralCaps = &bridgev2.NetworkGeneralCapabilities{
|
|||
AggressiveUpdateInfo: true,
|
||||
ImplicitReadReceipts: true,
|
||||
Provisioning: bridgev2.ProvisioningCapabilities{
|
||||
ImagePackImport: true,
|
||||
ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{
|
||||
CreateDM: true,
|
||||
LookupPhone: true,
|
||||
|
|
@ -237,5 +229,5 @@ func (s *SignalConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities
|
|||
}
|
||||
|
||||
func (s *SignalConnector) GetBridgeInfoVersion() (info, capabilities int) {
|
||||
return 1, 8
|
||||
return 1, 6
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, _ b
|
|||
e164String := fmt.Sprintf("+%d", e164Number)
|
||||
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
||||
return nil, fmt.Errorf("error looking up number in local contact list: %w", err)
|
||||
} else if recipient != nil && (recipient.ACI == uuid.Nil || !s.Client.Store.RecipientStore.IsUnregistered(ctx, libsignalgo.NewACIServiceID(recipient.ACI))) {
|
||||
} else if recipient != nil {
|
||||
aci = recipient.ACI
|
||||
pni = recipient.PNI
|
||||
} else if resp, err := s.Client.LookupPhone(ctx, e164Number); err != nil {
|
||||
|
|
@ -228,9 +228,6 @@ func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, _ b
|
|||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save recipient entry after looking up phone")
|
||||
}
|
||||
aci, pni = recipient.ACI, recipient.PNI
|
||||
if aci != uuid.Nil {
|
||||
s.Client.Store.RecipientStore.MarkUnregistered(ctx, libsignalgo.NewACIServiceID(aci), false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
aci, pni = serviceID.ToACIAndPNI()
|
||||
|
|
@ -326,13 +323,13 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
|||
}
|
||||
var avatarBytes []byte
|
||||
var avatarMXC id.ContentURIString
|
||||
if params.Avatar != nil && params.Avatar.URL != "" {
|
||||
if params.Avatar != nil {
|
||||
avatarMXC = params.Avatar.URL
|
||||
avatarBytes, err = s.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, nil)
|
||||
avatarBytes, err = s.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, params.Avatar.MSC3414File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download avatar: %w", err)
|
||||
}
|
||||
group.AvatarPath, err = s.Client.UploadGroupAvatar(ctx, avatarBytes, group.GroupIdentifier, group.GroupMasterKey)
|
||||
group.AvatarPath, err = s.Client.UploadGroupAvatar(ctx, avatarBytes, group.GroupIdentifier)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload avatar: %w", err)
|
||||
}
|
||||
|
|
@ -362,7 +359,7 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
|||
return nil, fmt.Errorf("failed to set portal room ID: %w", err)
|
||||
}
|
||||
}
|
||||
resp, err := s.Client.CreateGroup(ctx, group)
|
||||
resp, err := s.Client.CreateGroup(ctx, group, avatarBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create group: %w", err)
|
||||
}
|
||||
|
|
@ -414,7 +411,7 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI
|
|||
}
|
||||
|
||||
func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *types.Recipient, backupChat *store.BackupChat) *bridgev2.CreateChatResponse {
|
||||
namePtr := bridgev2.DefaultChatName
|
||||
name := ""
|
||||
topic := PrivateChatTopic
|
||||
selfUser := s.makeEventSender(s.Client.Store.ACI)
|
||||
members := &bridgev2.ChatMemberList{
|
||||
|
|
@ -441,7 +438,7 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
|||
var serviceID libsignalgo.ServiceID
|
||||
var avatar *bridgev2.Avatar
|
||||
if recipient.ACI == uuid.Nil {
|
||||
namePtr = ptr.Ptr(s.Main.Config.FormatDisplayname(recipient))
|
||||
name = s.Main.Config.FormatDisplayname(recipient)
|
||||
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
||||
} else {
|
||||
if backupChat == nil {
|
||||
|
|
@ -453,7 +450,7 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
|||
}
|
||||
members.OtherUserID = signalid.MakeUserID(recipient.ACI)
|
||||
if recipient.ACI == s.Client.Store.ACI {
|
||||
namePtr = ptr.Ptr(NoteToSelfName)
|
||||
name = NoteToSelfName
|
||||
avatar = &bridgev2.Avatar{
|
||||
ID: networkid.AvatarID(s.Main.Config.NoteToSelfAvatar),
|
||||
Remove: len(s.Main.Config.NoteToSelfAvatar) == 0,
|
||||
|
|
@ -474,15 +471,15 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
|||
return &bridgev2.CreateChatResponse{
|
||||
PortalKey: s.makeDMPortalKey(serviceID),
|
||||
PortalInfo: &bridgev2.ChatInfo{
|
||||
Name: namePtr,
|
||||
Name: &name,
|
||||
Avatar: avatar,
|
||||
Topic: &topic,
|
||||
Members: members,
|
||||
Type: ptr.Ptr(database.RoomTypeDM),
|
||||
|
||||
MessageRequest: ptr.Ptr(recipient.ACI != uuid.Nil && recipient.ProbablyMessageRequest()),
|
||||
CanBackfill: backupChat != nil,
|
||||
ExtraUpdates: updatePortalSyncMeta,
|
||||
CanBackfill: backupChat != nil,
|
||||
|
||||
ExtraUpdates: updatePortalSyncMeta,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,19 +32,10 @@ import (
|
|||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||
)
|
||||
|
||||
func (s *SignalClient) stopChatSync() {
|
||||
if cancel := s.cancelChatSync.Swap(nil); cancel != nil {
|
||||
(*cancel)()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc) {
|
||||
defer cancel()
|
||||
|
||||
func (s *SignalClient) syncChats(ctx context.Context) {
|
||||
if s.UserLogin.Metadata.(*signalid.UserLoginMetadata).ChatsSynced {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Client.Store.EphemeralBackupKey != nil {
|
||||
zerolog.Ctx(ctx).Info().Msg("Fetching transfer archive before syncing chats")
|
||||
meta, err := s.Client.WaitForTransfer(ctx)
|
||||
|
|
@ -74,22 +65,10 @@ func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc)
|
|||
}
|
||||
zerolog.Ctx(ctx).Info().Int("chat_count", len(chats)).Msg("Fetched chats to sync from database")
|
||||
for _, chat := range chats {
|
||||
if ctx.Err() != nil {
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
AnErr("ctx_err", ctx.Err()).
|
||||
Msg("Context cancelled while syncing chats, stopping")
|
||||
return
|
||||
}
|
||||
recipient, err := s.Client.Store.BackupStore.GetBackupRecipient(ctx, chat.RecipientId)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get recipient for chat")
|
||||
continue
|
||||
} else if recipient == nil {
|
||||
zerolog.Ctx(ctx).Warn().
|
||||
Uint64("backup_chat_id", chat.Id).
|
||||
Uint64("backup_recipient_id", chat.RecipientId).
|
||||
Msg("No recipient found for chat")
|
||||
continue
|
||||
}
|
||||
resyncEvt := &simplevent.ChatResync{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package connector
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
|
@ -27,7 +26,6 @@ import (
|
|||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/bridgev2/status"
|
||||
"maunium.net/go/mautrix/event"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
||||
|
|
@ -41,13 +39,11 @@ type SignalClient struct {
|
|||
Ghost *bridgev2.Ghost
|
||||
|
||||
queueEmptyWaiter *exsync.Event
|
||||
cancelChatSync atomic.Pointer[context.CancelFunc]
|
||||
}
|
||||
|
||||
var (
|
||||
_ bridgev2.NetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.BackgroundSyncingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.StickerImportingNetworkAPI = (*SignalClient)(nil)
|
||||
)
|
||||
|
||||
var pushCfg = &bridgev2.PushConfig{
|
||||
|
|
@ -78,27 +74,18 @@ func (s *SignalClient) RegisterPushNotifications(ctx context.Context, pushType b
|
|||
}
|
||||
}
|
||||
|
||||
func (s *SignalClient) DownloadImagePack(ctx context.Context, url string) (*bridgev2.ImportedImagePack, error) {
|
||||
return s.Main.MsgConv.DownloadImagePack(ctx, url)
|
||||
}
|
||||
|
||||
func (s *SignalClient) ListImagePacks(ctx context.Context) ([]*event.ImagePackMetadata, error) {
|
||||
return []*event.ImagePackMetadata{}, nil
|
||||
}
|
||||
|
||||
func (s *SignalClient) LogoutRemote(ctx context.Context) {
|
||||
if s.Client == nil {
|
||||
return
|
||||
}
|
||||
s.stopChatSync()
|
||||
err := s.Client.Unlink(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to unlink device")
|
||||
}
|
||||
err = s.Client.StopReceiveLoops()
|
||||
err := s.Client.StopReceiveLoops()
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to stop receive loops for logout")
|
||||
}
|
||||
err = s.Client.Unlink(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to unlink device")
|
||||
}
|
||||
err = s.Main.Store.DeleteDevice(context.TODO(), &s.Client.Store.DeviceData)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete device from store")
|
||||
|
|
@ -189,7 +176,6 @@ func (s *SignalClient) bridgeStateLoop(statusChan <-chan signalmeow.SignalConnec
|
|||
}
|
||||
|
||||
case signalmeow.SignalConnectionEventLoggedOut:
|
||||
s.stopChatSync()
|
||||
s.UserLogin.Log.Debug().Msg("Sending BadCredentials BridgeState")
|
||||
if err == nil {
|
||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: "You have been logged out of Signal, please reconnect"})
|
||||
|
|
@ -288,7 +274,6 @@ func (s *SignalClient) Disconnect() {
|
|||
if s.Client == nil {
|
||||
return
|
||||
}
|
||||
s.stopChatSync()
|
||||
err := s.Client.StopReceiveLoops()
|
||||
if err != nil {
|
||||
s.UserLogin.Log.Err(err).Msg("Failed to stop receive loops")
|
||||
|
|
@ -296,20 +281,28 @@ func (s *SignalClient) Disconnect() {
|
|||
}
|
||||
|
||||
func (s *SignalClient) postLoginConnect() {
|
||||
ctx := s.UserLogin.Log.WithContext(s.Main.Bridge.BackgroundCtx)
|
||||
ctx := s.UserLogin.Log.WithContext(context.Background())
|
||||
// TODO it would be more proper to only connect after syncing,
|
||||
// but currently syncing will fetch group info online, so it has to be connected.
|
||||
s.tryConnect(ctx, 0, false)
|
||||
if s.Client.Store.EphemeralBackupKey != nil {
|
||||
go func() {
|
||||
s.syncChats(ctx)
|
||||
if s.Client.Store.MasterKey != nil {
|
||||
s.Client.SyncStorage(ctx)
|
||||
}
|
||||
}()
|
||||
} else if s.Client.Store.MasterKey != nil {
|
||||
go s.Client.SyncStorage(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, noLoginSync bool) {
|
||||
if ctx.Err() != nil {
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Int("retry_count", retryCount).
|
||||
AnErr("ctx_err", ctx.Err()).
|
||||
Msg("Context is canceled, not trying to connect")
|
||||
return
|
||||
}
|
||||
if retryCount == 0 {
|
||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting})
|
||||
func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, doSync bool) {
|
||||
err := s.Client.RegisterCapabilities(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to register capabilities")
|
||||
} else {
|
||||
zerolog.Ctx(ctx).Debug().Msg("Successfully registered capabilities")
|
||||
}
|
||||
ch, err := s.Client.StartReceiveLoops(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -320,39 +313,12 @@ func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, noLoginSy
|
|||
retryInSeconds = 150
|
||||
}
|
||||
zerolog.Ctx(ctx).Debug().Int("retry_in_seconds", retryInSeconds).Msg("Sleeping and retrying connection")
|
||||
select {
|
||||
case <-time.After(time.Duration(retryInSeconds) * time.Second):
|
||||
case <-ctx.Done():
|
||||
zerolog.Ctx(ctx).Info().Msg("Context canceled, exit tryConnect")
|
||||
return
|
||||
}
|
||||
s.tryConnect(ctx, retryCount+1, noLoginSync)
|
||||
return
|
||||
}
|
||||
syncCtx, cancel := context.WithCancel(ctx)
|
||||
if oldCancel := s.cancelChatSync.Swap(&cancel); oldCancel != nil {
|
||||
(*oldCancel)()
|
||||
}
|
||||
go s.bridgeStateLoop(ch)
|
||||
if noLoginSync {
|
||||
go s.syncChats(syncCtx, cancel)
|
||||
time.Sleep(time.Duration(retryInSeconds) * time.Second)
|
||||
s.tryConnect(ctx, retryCount+1, doSync)
|
||||
} else {
|
||||
// TODO it would be more proper to only connect after syncing,
|
||||
// but currently syncing will fetch group info online, so it has to be connected.
|
||||
if s.Client.Store.EphemeralBackupKey != nil {
|
||||
go func() {
|
||||
if s.Client.Store.MasterKey != nil {
|
||||
s.Client.SyncStorage(ctx)
|
||||
} else {
|
||||
s.UserLogin.Log.Warn().Msg("No master key for storage sync before backup sync")
|
||||
}
|
||||
s.syncChats(syncCtx, cancel)
|
||||
}()
|
||||
} else {
|
||||
cancel()
|
||||
if s.Client.Store.MasterKey != nil {
|
||||
go s.Client.SyncStorage(ctx)
|
||||
}
|
||||
go s.bridgeStateLoop(ch)
|
||||
if doSync {
|
||||
go s.syncChats(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
// mautrix-signal - A Matrix-Signal puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/commands"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||
)
|
||||
|
||||
var CmdDiscardSenderKey = &commands.FullHandler{
|
||||
Func: fnDiscardSenderKey,
|
||||
Name: "discard-sender-key",
|
||||
Help: commands.HelpMeta{
|
||||
Section: commands.HelpSectionChats,
|
||||
Description: "Discard the Signal-side sender key in the current group",
|
||||
Args: "[_login ID_]",
|
||||
},
|
||||
RequiresPortal: true,
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnDiscardSenderKey(ce *commands.Event) {
|
||||
_, groupID, _ := signalid.ParsePortalID(ce.Portal.ID)
|
||||
if groupID == "" {
|
||||
ce.Reply("This command can only be used in group chat portals")
|
||||
return
|
||||
}
|
||||
var login *bridgev2.UserLogin
|
||||
if len(ce.Args) > 0 {
|
||||
login = ce.Bridge.GetCachedUserLoginByID(networkid.UserLoginID(ce.Args[0]))
|
||||
if login == nil || login.UserMXID != ce.User.MXID {
|
||||
ce.Reply("Login not found")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
login, _, err = ce.Portal.FindPreferredLogin(ce.Ctx, ce.User, false)
|
||||
if errors.Is(err, bridgev2.ErrNotLoggedIn) {
|
||||
ce.Reply("You're not logged in in this portal")
|
||||
return
|
||||
} else if err != nil {
|
||||
ce.Log.Err(err).Msg("Failed to find preferred login for portal")
|
||||
ce.Reply("Failed to find preferred login for portal")
|
||||
return
|
||||
}
|
||||
}
|
||||
distributionID, err := login.Client.(*SignalClient).Client.ResetSenderKey(ce.Ctx, groupID)
|
||||
if err != nil {
|
||||
ce.Log.Err(err).Msg("Failed to reset sender key")
|
||||
ce.Reply("Failed to reset sender key")
|
||||
} else {
|
||||
ce.Reply("Reset sender key with distribution ID %s", distributionID)
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,6 @@ type SignalConfig struct {
|
|||
NoteToSelfAvatar id.ContentURIString `yaml:"note_to_self_avatar"`
|
||||
LocationFormat string `yaml:"location_format"`
|
||||
DisappearViewOnce bool `yaml:"disappear_view_once"`
|
||||
ExtEvPolls bool `yaml:"extev_polls"`
|
||||
|
||||
displaynameTemplate *template.Template `yaml:"-"`
|
||||
}
|
||||
|
|
@ -104,7 +103,6 @@ func upgradeConfig(helper up.Helper) {
|
|||
helper.Copy(up.Str, "note_to_self_avatar")
|
||||
helper.Copy(up.Str, "location_format")
|
||||
helper.Copy(up.Bool, "disappear_view_once")
|
||||
helper.Copy(up.Bool, "extev_polls")
|
||||
}
|
||||
|
||||
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
||||
|
|
|
|||
|
|
@ -24,19 +24,15 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"go.mau.fi/util/dbutil"
|
||||
"go.mau.fi/util/exhttp"
|
||||
"go.mau.fi/util/exsync"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/commands"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/msgconv"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/store"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||
)
|
||||
|
||||
type SignalConnector struct {
|
||||
|
|
@ -67,8 +63,6 @@ func (s *SignalConnector) Init(bridge *bridgev2.Bridge) {
|
|||
s.MsgConv = msgconv.NewMessageConverter(bridge)
|
||||
s.MsgConv.LocationFormat = s.Config.LocationFormat
|
||||
s.MsgConv.DisappearViewOnce = s.Config.DisappearViewOnce
|
||||
s.MsgConv.ExtEvPolls = s.Config.ExtEvPolls
|
||||
bridge.Commands.(*commands.Processor).AddHandlers(CmdDiscardSenderKey)
|
||||
}
|
||||
|
||||
func (s *SignalConnector) SetMaxFileSize(maxSize int64) {
|
||||
|
|
@ -76,7 +70,6 @@ func (s *SignalConnector) SetMaxFileSize(maxSize int64) {
|
|||
}
|
||||
|
||||
func (s *SignalConnector) Start(ctx context.Context) error {
|
||||
s.ResetHTTPTransport()
|
||||
err := s.Store.Upgrade(ctx)
|
||||
if err != nil {
|
||||
return bridgev2.DBUpgradeError{Err: err, Section: "signalmeow"}
|
||||
|
|
@ -84,26 +77,6 @@ func (s *SignalConnector) Start(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *SignalConnector) ResetHTTPTransport() {
|
||||
settings := exhttp.SensibleClientSettings
|
||||
hs, ok := s.Bridge.Matrix.(bridgev2.MatrixConnectorWithHTTPSettings)
|
||||
if ok {
|
||||
settings = hs.GetHTTPClientSettings()
|
||||
}
|
||||
oldClient := web.SignalHTTPClient
|
||||
web.SignalHTTPClient = settings.WithTLSConfig(web.SignalTLSConfig).Compile()
|
||||
oldClient.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (s *SignalConnector) ResetNetworkConnections() {
|
||||
for _, login := range s.Bridge.GetAllCachedUserLogins() {
|
||||
c := login.Client.(*SignalClient)
|
||||
if c.Client != nil {
|
||||
c.Client.ForceReconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SignalConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error {
|
||||
aci, err := uuid.Parse(string(login.ID))
|
||||
if err != nil {
|
||||
|
|
@ -120,13 +93,13 @@ func (s *SignalConnector) LoadUserLogin(ctx context.Context, login *bridgev2.Use
|
|||
queueEmptyWaiter: exsync.NewEvent(),
|
||||
}
|
||||
if device != nil {
|
||||
sc.Client = signalmeow.NewClient(
|
||||
device,
|
||||
sc.UserLogin.Log.With().Str("component", "signalmeow").Logger(),
|
||||
sc.handleSignalEvent,
|
||||
)
|
||||
sc.Client.SyncContactsOnConnect = s.Config.SyncContactsOnStartup &&
|
||||
time.Since(login.Metadata.(*signalid.UserLoginMetadata).LastContactSync.Time) > 3*24*time.Hour
|
||||
sc.Client = &signalmeow.Client{
|
||||
Store: device,
|
||||
Log: sc.UserLogin.Log.With().Str("component", "signalmeow").Logger(),
|
||||
EventHandler: sc.handleSignalEvent,
|
||||
|
||||
SyncContactsOnConnect: s.Config.SyncContactsOnStartup,
|
||||
}
|
||||
}
|
||||
login.Client = sc
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"io"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
|
|
@ -29,7 +29,6 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
|||
return nil, fmt.Errorf("failed to parse direct media id: %w", err)
|
||||
}
|
||||
|
||||
var rawDataResp []byte
|
||||
switch info := info.(type) {
|
||||
case *signalid.DirectMediaAttachment:
|
||||
log.Info().
|
||||
|
|
@ -42,15 +41,18 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
|||
Uint32("size", info.Size).
|
||||
Msg("Direct downloading attachment")
|
||||
|
||||
return &mediaproxy.GetMediaResponseFile{
|
||||
Callback: func(w *os.File) (*mediaproxy.FileMeta, error) {
|
||||
_, err := signalmeow.DownloadAttachment(
|
||||
ctx, info.CDNID, info.CDNKey, info.CDNNumber, info.Key, info.Digest, info.PlaintextDigest, info.Size, w,
|
||||
return &mediaproxy.GetMediaResponseCallback{
|
||||
Callback: func(w io.Writer) (int64, error) {
|
||||
data, err := signalmeow.DownloadAttachment(
|
||||
ctx, info.CDNID, info.CDNKey, info.CDNNumber, info.Key, info.Digest, info.PlaintextDigest, info.Size,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Err(err).Msg("Direct download failed")
|
||||
return 0, err
|
||||
}
|
||||
return &mediaproxy.FileMeta{}, nil
|
||||
|
||||
_, err = w.Write(data)
|
||||
return int64(info.Size), err
|
||||
},
|
||||
}, nil
|
||||
case *signalid.DirectMediaGroupAvatar:
|
||||
|
|
@ -76,11 +78,18 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
|||
return nil, fmt.Errorf("failed to to get group master key: %w", err)
|
||||
}
|
||||
|
||||
rawDataResp, err = client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Direct download failed")
|
||||
return nil, err
|
||||
}
|
||||
return &mediaproxy.GetMediaResponseCallback{
|
||||
Callback: func(w io.Writer) (int64, error) {
|
||||
data, err := client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Direct download failed")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = w.Write(data)
|
||||
return int64(len(data)), err
|
||||
},
|
||||
}, nil
|
||||
case *signalid.DirectMediaProfileAvatar:
|
||||
log.Info().
|
||||
Stringer("user_id", info.UserID).
|
||||
|
|
@ -104,27 +113,19 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
|||
return nil, fmt.Errorf("profile key not found")
|
||||
}
|
||||
|
||||
rawDataResp, err = client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Direct download failed")
|
||||
return nil, err
|
||||
}
|
||||
case *signalid.DirectMediaSticker:
|
||||
log.Info().
|
||||
Hex("pack_id", info.PackID).
|
||||
Uint32("sticker_id", info.StickerID).
|
||||
Msg("Direct downloading sticker")
|
||||
return &mediaproxy.GetMediaResponseCallback{
|
||||
Callback: func(w io.Writer) (int64, error) {
|
||||
data, err := client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Direct download failed")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rawDataResp, err = signalmeow.DownloadStickerPackItem(ctx, info.PackID, info.PackKey, info.StickerID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Direct download failed")
|
||||
return nil, err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return int64(len(data)), err
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("no downloader for direct media type: %T", info)
|
||||
}
|
||||
if rawDataResp == nil {
|
||||
return nil, fmt.Errorf("unexpected fallthrough with no data")
|
||||
}
|
||||
return mediaproxy.GetMediaResponseRawData(rawDataResp), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,5 +24,3 @@ note_to_self_avatar: mxc://maunium.net/REBIVrqjZwmaWpssCZpBlmlL
|
|||
location_format: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
||||
# Should view-once messages disappear shortly after sending a read receipt on Matrix?
|
||||
disappear_view_once: false
|
||||
# Should polls be sent using unstable MSC3381 event types?
|
||||
extev_polls: false
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func inviteLinkToJoinRule(inviteLinkAccess signalmeow.AccessControl) event.JoinR
|
|||
}
|
||||
|
||||
func (s *SignalClient) getGroupInfo(ctx context.Context, groupID types.GroupIdentifier, minRevision uint32, backupChat *store.BackupChat) (*bridgev2.ChatInfo, error) {
|
||||
groupInfo, _, err := s.Client.RetrieveGroupByID(ctx, groupID, minRevision)
|
||||
groupInfo, err := s.Client.RetrieveGroupByID(ctx, groupID, minRevision)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve group by id: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
|
@ -43,19 +42,16 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
_ bridgev2.EditHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.ReactionHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RedactionHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.ReadReceiptHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.TypingHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RoomNameHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RoomAvatarHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RoomTopicHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.ChatViewingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.DisappearTimerChangingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.DeleteChatHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.PollHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.MessageRequestAcceptingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.EditHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.ReactionHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RedactionHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.ReadReceiptHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.TypingHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RoomNameHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RoomAvatarHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.RoomTopicHandlingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.ChatViewingNetworkAPI = (*SignalClient)(nil)
|
||||
_ bridgev2.DisappearTimerChangingNetworkAPI = (*SignalClient)(nil)
|
||||
)
|
||||
|
||||
func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.PortalID, content *signalpb.Content) error {
|
||||
|
|
@ -74,17 +70,17 @@ func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.Porta
|
|||
Int("failed_to_send_to_count", len(result.FailedToSendTo)).
|
||||
Int("successfully_sent_to_count", len(result.SuccessfullySentTo)).
|
||||
Logger()
|
||||
if len(result.FailedToSendTo) > 0 {
|
||||
log.Error().Msg("Failed to send event to some members of Signal group")
|
||||
}
|
||||
if len(result.SuccessfullySentTo) == 0 && len(result.FailedToSendTo) == 0 {
|
||||
log.Debug().Msg("No successes or failures - Probably sent to myself")
|
||||
} else if len(result.SuccessfullySentTo) == 0 {
|
||||
log.Error().Msg("Failed to send event to all members of Signal group")
|
||||
return errors.New("failed to send to any members of Signal group")
|
||||
|
||||
} else if len(result.SuccessfullySentTo) < totalRecipients {
|
||||
if len(result.FailedToSendTo) > 0 {
|
||||
log.Warn().Msg("Failed to send event to some members of Signal group")
|
||||
} else {
|
||||
log.Warn().Msg("Only sent event to some members of Signal group")
|
||||
}
|
||||
log.Warn().Msg("Only sent event to some members of Signal group")
|
||||
} else {
|
||||
log.Debug().Msg("Sent event to all members of Signal group")
|
||||
}
|
||||
|
|
@ -113,31 +109,16 @@ func getTimestampForEvent(txnID networkid.RawTransactionID, evt *event.Event, or
|
|||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (message *bridgev2.MatrixMessageResponse, err error) {
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
converted, err := s.Main.MsgConv.ToSignal(
|
||||
ctx, s.Client, msg.Portal, msg.Event, msg.Content, msg.OrigSender != nil, msg.ReplyTo,
|
||||
ctx, s.Client, msg.Portal, msg.Event, msg.Content, ts, msg.OrigSender != nil, msg.ReplyTo,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.doSendMessage(ctx, msg, converted, &signalid.MessageMetadata{
|
||||
ContainsAttachments: len(converted.Attachments) > 0,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SignalClient) doSendMessage(
|
||||
ctx context.Context,
|
||||
msg *bridgev2.MatrixMessage,
|
||||
converted *signalpb.DataMessage,
|
||||
meta *signalid.MessageMetadata,
|
||||
) (*bridgev2.MatrixMessageResponse, error) {
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
converted.Timestamp = &ts
|
||||
if meta == nil {
|
||||
meta = &signalid.MessageMetadata{}
|
||||
}
|
||||
msgID := signalid.MakeMessageID(s.Client.Store.ACI, ts)
|
||||
msg.AddPendingToIgnore(networkid.TransactionID(msgID))
|
||||
err := s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(converted))
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, &signalpb.Content{DataMessage: converted})
|
||||
if err != nil {
|
||||
return nil, bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
||||
}
|
||||
|
|
@ -145,7 +126,9 @@ func (s *SignalClient) doSendMessage(
|
|||
ID: msgID,
|
||||
SenderID: signalid.MakeUserID(s.Client.Store.ACI),
|
||||
Timestamp: time.UnixMilli(int64(ts)),
|
||||
Metadata: meta,
|
||||
Metadata: &signalid.MessageMetadata{
|
||||
ContainsAttachments: len(converted.Attachments) > 0,
|
||||
},
|
||||
}
|
||||
return &bridgev2.MatrixMessageResponse{
|
||||
DB: dbMsg,
|
||||
|
|
@ -167,16 +150,15 @@ func (s *SignalClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.Matri
|
|||
return fmt.Errorf("failed to get message reply target: %w", err)
|
||||
}
|
||||
}
|
||||
converted, err := s.Main.MsgConv.ToSignal(ctx, s.Client, msg.Portal, msg.Event, msg.Content, msg.OrigSender != nil, replyTo)
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
converted, err := s.Main.MsgConv.ToSignal(ctx, s.Client, msg.Portal, msg.Event, msg.Content, ts, msg.OrigSender != nil, replyTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
converted.Timestamp = &ts
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapEditMessage(&signalpb.EditMessage{
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, &signalpb.Content{EditMessage: &signalpb.EditMessage{
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
DataMessage: converted,
|
||||
}))
|
||||
}})
|
||||
if err != nil {
|
||||
return bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
||||
}
|
||||
|
|
@ -200,16 +182,19 @@ func (s *SignalClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.M
|
|||
return nil, fmt.Errorf("failed to parse target message ID: %w", err)
|
||||
}
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
||||
Timestamp: proto.Uint64(ts),
|
||||
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
||||
Reaction: &signalpb.DataMessage_Reaction{
|
||||
Emoji: proto.String(msg.PreHandleResp.Emoji),
|
||||
Remove: proto.Bool(false),
|
||||
TargetAuthorAciBinary: targetAuthorACI[:],
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
wrappedContent := &signalpb.Content{
|
||||
DataMessage: &signalpb.DataMessage{
|
||||
Timestamp: proto.Uint64(ts),
|
||||
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
||||
Reaction: &signalpb.DataMessage_Reaction{
|
||||
Emoji: proto.String(msg.PreHandleResp.Emoji),
|
||||
Remove: proto.Bool(false),
|
||||
TargetAuthorAci: proto.String(targetAuthorACI.String()),
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, wrappedContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -222,16 +207,19 @@ func (s *SignalClient) HandleMatrixReactionRemove(ctx context.Context, msg *brid
|
|||
return fmt.Errorf("failed to parse target message ID: %w", err)
|
||||
}
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
||||
Timestamp: proto.Uint64(ts),
|
||||
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
||||
Reaction: &signalpb.DataMessage_Reaction{
|
||||
Emoji: proto.String(msg.TargetReaction.Emoji),
|
||||
Remove: proto.Bool(true),
|
||||
TargetAuthorAciBinary: targetAuthorACI[:],
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
wrappedContent := &signalpb.Content{
|
||||
DataMessage: &signalpb.DataMessage{
|
||||
Timestamp: proto.Uint64(ts),
|
||||
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
||||
Reaction: &signalpb.DataMessage_Reaction{
|
||||
Emoji: proto.String(msg.TargetReaction.Emoji),
|
||||
Remove: proto.Bool(true),
|
||||
TargetAuthorAci: proto.String(targetAuthorACI.String()),
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, wrappedContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -246,12 +234,15 @@ func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridg
|
|||
return fmt.Errorf("cannot delete other people's messages")
|
||||
}
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
||||
Timestamp: proto.Uint64(ts),
|
||||
Delete: &signalpb.DataMessage_Delete{
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
wrappedContent := &signalpb.Content{
|
||||
DataMessage: &signalpb.DataMessage{
|
||||
Timestamp: proto.Uint64(ts),
|
||||
Delete: &signalpb.DataMessage_Delete{
|
||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
err = s.sendMessage(ctx, msg.Portal.ID, wrappedContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -311,21 +302,18 @@ func (s *SignalClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bri
|
|||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixTyping(ctx context.Context, typing *bridgev2.MatrixTyping) error {
|
||||
userID, groupID, err := signalid.ParsePortalID(typing.Portal.ID)
|
||||
userID, _, err := signalid.ParsePortalID(typing.Portal.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
typingMessage := signalmeow.TypingMessage(typing.IsTyping)
|
||||
// Only send typing notifications in DMs for now
|
||||
// Sending efficiently to groups requires implementing the proper SenderKey stuff first
|
||||
if !userID.IsEmpty() && userID.Type == libsignalgo.ServiceIDTypeACI {
|
||||
typingMessage := signalmeow.TypingMessage(typing.IsTyping)
|
||||
result := s.Client.SendMessage(ctx, userID, typingMessage)
|
||||
if !result.WasSuccessful {
|
||||
return result.Error
|
||||
}
|
||||
} else if groupID != "" {
|
||||
_, err = s.Client.SendGroupMessage(ctx, groupID, typingMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -378,7 +366,7 @@ func (s *SignalClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2
|
|||
return false, fmt.Errorf("failed to download avatar: %w", err)
|
||||
}
|
||||
avatarHash = sha256.Sum256(data)
|
||||
avatarPath, err = s.Client.UploadGroupAvatar(ctx, data, groupID, "")
|
||||
avatarPath, err = s.Client.UploadGroupAvatar(ctx, data, groupID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to reupload avatar: %w", err)
|
||||
}
|
||||
|
|
@ -397,24 +385,22 @@ func (s *SignalClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.
|
|||
}, nil)
|
||||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) {
|
||||
if msg.Type.IsSelf && msg.OrigSender != nil {
|
||||
return nil, nil
|
||||
}
|
||||
func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (bool, error) {
|
||||
var targetIntent bridgev2.MatrixAPI
|
||||
var targetSignalID libsignalgo.ServiceID
|
||||
var err error
|
||||
if msg.Portal.RoomType == database.RoomTypeDM {
|
||||
//TODO: this probably needs to revert some changes and clean up the portal on leaves
|
||||
switch msg.Type {
|
||||
case bridgev2.Invite:
|
||||
return nil, fmt.Errorf("cannot invite additional user to dm")
|
||||
return false, fmt.Errorf("cannot invite additional user to dm")
|
||||
default:
|
||||
return nil, nil
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
targetSignalID, err = signalid.ParseGhostOrUserLoginID(msg.Target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse target signal id: %w", err)
|
||||
return false, fmt.Errorf("failed to parse target signal id: %w", err)
|
||||
}
|
||||
switch target := msg.Target.(type) {
|
||||
case *bridgev2.Ghost:
|
||||
|
|
@ -424,12 +410,12 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
|||
if targetIntent == nil {
|
||||
ghost, err := s.Main.Bridge.GetGhostByID(ctx, networkid.UserID(target.ID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get ghost for user: %w", err)
|
||||
return false, fmt.Errorf("failed to get ghost for user: %w", err)
|
||||
}
|
||||
targetIntent = ghost.Intent
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot get target intent: unknown type: %T", target)
|
||||
return false, fmt.Errorf("cannot get target intent: unknown type: %T", target)
|
||||
}
|
||||
log := zerolog.Ctx(ctx).With().
|
||||
Str("From Membership", string(msg.Type.From)).
|
||||
|
|
@ -449,7 +435,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
|||
switch msg.Type {
|
||||
case bridgev2.AcceptInvite:
|
||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return nil, fmt.Errorf("can't accept invite for non-ACI service ID")
|
||||
return false, fmt.Errorf("can't accept invite for non-ACI service ID")
|
||||
}
|
||||
gc.PromotePendingMembers = []*signalmeow.PromotePendingMember{{
|
||||
ACI: targetSignalID.UUID,
|
||||
|
|
@ -458,7 +444,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
|||
gc.DeletePendingMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
||||
case bridgev2.Leave, bridgev2.Kick:
|
||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return nil, fmt.Errorf("can't kick non-ACI service ID")
|
||||
return false, fmt.Errorf("can't kick non-ACI service ID")
|
||||
}
|
||||
gc.DeleteMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||
case bridgev2.Invite:
|
||||
|
|
@ -494,7 +480,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
|||
// }}
|
||||
case bridgev2.AcceptKnock:
|
||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return nil, fmt.Errorf("can't accept knock from non-ACI service ID")
|
||||
return false, fmt.Errorf("can't accept knock from non-ACI service ID")
|
||||
}
|
||||
gc.PromoteRequestingMembers = []*signalmeow.RoleMember{{
|
||||
ACI: targetSignalID.UUID,
|
||||
|
|
@ -502,7 +488,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
|||
}}
|
||||
case bridgev2.RetractKnock, bridgev2.RejectKnock:
|
||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return nil, fmt.Errorf("can't reject knock from non-ACI service ID")
|
||||
return false, fmt.Errorf("can't reject knock from non-ACI service ID")
|
||||
}
|
||||
gc.DeleteRequestingMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||
case bridgev2.BanKnocked, bridgev2.BanInvited, bridgev2.BanJoined, bridgev2.BanLeft:
|
||||
|
|
@ -513,39 +499,40 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
|||
switch msg.Type {
|
||||
case bridgev2.BanJoined:
|
||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return nil, fmt.Errorf("can't ban joined non-ACI service ID")
|
||||
return false, fmt.Errorf("can't ban joined non-ACI service ID")
|
||||
}
|
||||
gc.DeleteMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||
case bridgev2.BanInvited:
|
||||
gc.DeletePendingMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
||||
case bridgev2.BanKnocked:
|
||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return nil, fmt.Errorf("can't ban knocked non-ACI service ID")
|
||||
return false, fmt.Errorf("can't ban knocked non-ACI service ID")
|
||||
}
|
||||
gc.DeleteRequestingMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||
}
|
||||
case bridgev2.Unban:
|
||||
gc.DeleteBannedMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported membership change: %s -> %s", msg.Type.From, msg.Type.To)
|
||||
log.Debug().Msg("unsupported membership change")
|
||||
return false, nil
|
||||
}
|
||||
_, groupID, err := signalid.ParsePortalID(msg.Portal.ID)
|
||||
if err != nil || groupID == "" {
|
||||
return nil, err
|
||||
return false, err
|
||||
}
|
||||
gc.Revision = msg.Portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
||||
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return false, err
|
||||
}
|
||||
if msg.Type == bridgev2.Invite && targetSignalID.Type != libsignalgo.ServiceIDTypePNI {
|
||||
err = targetIntent.EnsureJoined(ctx, msg.Portal.MXID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
msg.Portal.Metadata.(*signalid.PortalMetadata).Revision = revision
|
||||
return nil, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func plToRole(pl int) signalmeow.GroupMemberRole {
|
||||
|
|
@ -679,11 +666,13 @@ func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *b
|
|||
})
|
||||
} else {
|
||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||
res := s.Client.SendMessage(ctx, userID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
||||
Timestamp: ptr.Ptr(ts),
|
||||
Flags: ptr.Ptr(uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE)),
|
||||
ExpireTimer: ptr.Ptr(uint32(msg.Content.Timer.Seconds())),
|
||||
}))
|
||||
res := s.Client.SendMessage(ctx, userID, &signalpb.Content{
|
||||
DataMessage: &signalpb.DataMessage{
|
||||
Timestamp: ptr.Ptr(ts),
|
||||
Flags: ptr.Ptr(uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE)),
|
||||
ExpireTimer: ptr.Ptr(uint32(msg.Content.Timer.Seconds())),
|
||||
},
|
||||
})
|
||||
if !res.WasSuccessful {
|
||||
return false, res.Error
|
||||
}
|
||||
|
|
@ -691,222 +680,3 @@ func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *b
|
|||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) error {
|
||||
userID, groupID, err := signalid.ParsePortalID(msg.Portal.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse portal ID: %w", err)
|
||||
}
|
||||
|
||||
if msg.Content.FromMessageRequest {
|
||||
// TODO block and delete support?
|
||||
err = s.syncMessageRequestResponse(ctx, msg.Portal, signalpb.SyncMessage_MessageRequestResponse_DELETE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send message request delete sync: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build ConversationIdentifier based on portal type
|
||||
var conversationID *signalpb.ConversationIdentifier
|
||||
if groupID == "" {
|
||||
conversationID = &signalpb.ConversationIdentifier{
|
||||
Identifier: &signalpb.ConversationIdentifier_ThreadServiceIdBinary{
|
||||
ThreadServiceIdBinary: userID.Bytes(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
gid, err := groupID.Bytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse group ID: %w", err)
|
||||
}
|
||||
conversationID = &signalpb.ConversationIdentifier{
|
||||
Identifier: &signalpb.ConversationIdentifier_ThreadGroupId{
|
||||
ThreadGroupId: gid[:],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve most recent messages from the portal
|
||||
var mostRecentMessages []*signalpb.AddressableMessage
|
||||
dbMessages, err := s.Main.Bridge.DB.Message.GetMessagesBetweenTimeQuery(
|
||||
ctx,
|
||||
msg.Portal.PortalKey,
|
||||
time.Now().Add(-30*24*time.Hour), // Last 30 days
|
||||
time.Now(),
|
||||
)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get recent messages for conversation delete")
|
||||
} else if len(dbMessages) > 0 {
|
||||
// Limit to the 5 most recent messages overall
|
||||
limit := 5
|
||||
startIdx := 0
|
||||
if len(dbMessages) > limit {
|
||||
startIdx = len(dbMessages) - limit
|
||||
}
|
||||
|
||||
// Create AddressableMessage for most recent messages
|
||||
for _, dbMsg := range dbMessages[startIdx:] {
|
||||
senderACI, timestamp, err := signalid.ParseMessageID(dbMsg.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mostRecentMessages = append(mostRecentMessages, &signalpb.AddressableMessage{
|
||||
Author: &signalpb.AddressableMessage_AuthorServiceIdBinary{
|
||||
AuthorServiceIdBinary: senderACI[:],
|
||||
},
|
||||
SentTimestamp: proto.Uint64(timestamp),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
recipientID := s.Client.Store.ACIServiceID()
|
||||
// Send DeleteForMe sync message to self
|
||||
result := s.Client.SendMessage(ctx, recipientID, signalmeow.WrapSyncMessage(&signalpb.SyncMessage{
|
||||
Content: &signalpb.SyncMessage_DeleteForMe_{
|
||||
DeleteForMe: &signalpb.SyncMessage_DeleteForMe{
|
||||
ConversationDeletes: []*signalpb.SyncMessage_DeleteForMe_ConversationDelete{{
|
||||
Conversation: conversationID,
|
||||
MostRecentMessages: mostRecentMessages,
|
||||
IsFullDelete: proto.Bool(true),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Str("portal_id", string(msg.Portal.ID)).
|
||||
Int("recent_messages_count", len(mostRecentMessages)).
|
||||
Msg("Sent conversation deletion to Signal")
|
||||
|
||||
if !result.WasSuccessful {
|
||||
return fmt.Errorf("failed to send delete conversation sync message: %w %s %s", result.Error, userID, groupID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) {
|
||||
optionNames := make([]string, len(msg.Content.PollStart.Answers))
|
||||
optionIDs := make([]string, len(msg.Content.PollStart.Answers))
|
||||
for i, option := range msg.Content.PollStart.Answers {
|
||||
optionNames[i] = option.Text
|
||||
optionIDs[i] = option.ID
|
||||
}
|
||||
converted := &signalpb.DataMessage{
|
||||
PollCreate: &signalpb.DataMessage_PollCreate{
|
||||
Question: ptr.Ptr(msg.Content.PollStart.Question.Text),
|
||||
AllowMultiple: ptr.Ptr(msg.Content.PollStart.MaxSelections != 1),
|
||||
Options: optionNames,
|
||||
},
|
||||
RequiredProtocolVersion: ptr.Ptr(uint32(signalpb.DataMessage_POLLS)),
|
||||
}
|
||||
return s.doSendMessage(ctx, &msg.MatrixMessage, converted, &signalid.MessageMetadata{
|
||||
MatrixPollOptionIDs: optionIDs,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev2.MatrixPollVote) (*bridgev2.MatrixMessageResponse, error) {
|
||||
senderACI, msgTS, err := signalid.ParseMessageID(msg.VoteTo.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mxOptions := msg.VoteTo.Metadata.(*signalid.MessageMetadata).MatrixPollOptionIDs
|
||||
optionIndexes := make([]uint32, len(msg.Content.Response.Answers))
|
||||
for i, answer := range msg.Content.Response.Answers {
|
||||
if idx := slices.Index(mxOptions, answer); idx >= 0 {
|
||||
optionIndexes[i] = uint32(idx)
|
||||
} else if idx, err = strconv.Atoi(answer); err == nil && idx >= 0 {
|
||||
optionIndexes[i] = uint32(idx)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown poll answer ID: %s", answer)
|
||||
}
|
||||
}
|
||||
converted := &signalpb.DataMessage{
|
||||
PollVote: &signalpb.DataMessage_PollVote{
|
||||
TargetAuthorAciBinary: senderACI[:],
|
||||
TargetSentTimestamp: &msgTS,
|
||||
OptionIndexes: optionIndexes,
|
||||
VoteCount: proto.Uint32(1), // TODO
|
||||
},
|
||||
RequiredProtocolVersion: proto.Uint32(0),
|
||||
}
|
||||
return s.doSendMessage(ctx, &msg.MatrixMessage, converted, nil)
|
||||
}
|
||||
|
||||
func (s *SignalClient) syncMessageRequestResponse(
|
||||
ctx context.Context,
|
||||
portal *bridgev2.Portal,
|
||||
respType signalpb.SyncMessage_MessageRequestResponse_Type,
|
||||
) error {
|
||||
userID, groupID, err := signalid.ParsePortalID(portal.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accept := &signalpb.SyncMessage_MessageRequestResponse{
|
||||
Type: respType.Enum(),
|
||||
}
|
||||
if groupID != "" {
|
||||
gidBytes, err := groupID.Bytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse group ID: %w", err)
|
||||
}
|
||||
accept.GroupId = gidBytes[:]
|
||||
} else if userID.Type == libsignalgo.ServiceIDTypeACI {
|
||||
accept.ThreadAciBinary = userID.UUID[:]
|
||||
} else {
|
||||
return fmt.Errorf("invalid portal ID for message request response: %s", portal.ID)
|
||||
}
|
||||
res := s.Client.SendMessage(ctx, libsignalgo.NewACIServiceID(s.Client.Store.ACI), signalmeow.WrapSyncMessage(&signalpb.SyncMessage{
|
||||
Content: &signalpb.SyncMessage_MessageRequestResponse_{
|
||||
MessageRequestResponse: accept,
|
||||
},
|
||||
}))
|
||||
if !res.WasSuccessful {
|
||||
return res.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SignalClient) HandleMatrixAcceptMessageRequest(ctx context.Context, msg *bridgev2.MatrixAcceptMessageRequest) error {
|
||||
userID, _, err := signalid.ParsePortalID(msg.Portal.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.syncMessageRequestResponse(ctx, msg.Portal, signalpb.SyncMessage_MessageRequestResponse_ACCEPT)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sync message request acceptance: %w", err)
|
||||
}
|
||||
if userID.Type == libsignalgo.ServiceIDTypeACI {
|
||||
profileKey, err := s.Client.ProfileKeyForSignalID(ctx, s.Client.Store.ACI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get own profile key: %w", err)
|
||||
}
|
||||
var pniSig *signalpb.PniSignatureMessage
|
||||
if s.Client.Store.AccountRecord.GetPhoneNumberSharingMode() == signalpb.AccountRecord_EVERYBODY {
|
||||
sig, err := s.Client.Store.PNIIdentityKeyPair.SignAlternateIdentity(s.Client.Store.ACIIdentityKeyPair.GetIdentityKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate PNI signature: %w", err)
|
||||
}
|
||||
pniSig = &signalpb.PniSignatureMessage{
|
||||
Pni: s.Client.Store.PNI[:],
|
||||
Signature: sig,
|
||||
}
|
||||
}
|
||||
res := s.Client.SendMessage(ctx, userID, &signalpb.Content{
|
||||
Content: &signalpb.Content_DataMessage{DataMessage: &signalpb.DataMessage{
|
||||
Flags: proto.Uint32(uint32(signalpb.DataMessage_PROFILE_KEY_UPDATE)),
|
||||
ProfileKey: profileKey.Slice(),
|
||||
Timestamp: proto.Uint64(getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)),
|
||||
|
||||
RequiredProtocolVersion: proto.Uint32(0),
|
||||
}},
|
||||
PniSignatureMessage: pniSig,
|
||||
})
|
||||
if !res.WasSuccessful {
|
||||
return fmt.Errorf("failed to share profile key to accept message request: %w", res.Error)
|
||||
}
|
||||
// TODO send read receipts too?
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exzerolog"
|
||||
"go.mau.fi/util/jsontime"
|
||||
"go.mau.fi/util/ptr"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/database"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
|
|
@ -37,7 +35,6 @@ import (
|
|||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/events"
|
||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||
|
|
@ -55,8 +52,6 @@ func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
|
|||
return s.handleSignalReadSelf(evt)
|
||||
case *events.DeleteForMe:
|
||||
return s.handleSignalDeleteForMe(evt)
|
||||
case *events.MessageRequestResponse:
|
||||
return s.handleSignalMessageRequestResponse(evt)
|
||||
case *events.Call:
|
||||
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapCallEvent(evt)).Success
|
||||
case *events.ContactList:
|
||||
|
|
@ -103,9 +98,6 @@ func convertCallEvent(ctx context.Context, portal *bridgev2.Portal, intent bridg
|
|||
if userID, _, _ := signalid.ParsePortalID(portal.ID); !userID.IsEmpty() {
|
||||
content.MsgType = event.MsgText
|
||||
}
|
||||
content.BeeperActionMessage = &event.BeeperActionMessage{
|
||||
Type: event.BeeperActionMessageCall,
|
||||
}
|
||||
} else {
|
||||
content.Body = "Call ended"
|
||||
}
|
||||
|
|
@ -175,7 +167,7 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
|||
case *signalpb.DataMessage:
|
||||
switch {
|
||||
case innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
|
||||
innerEvt.Payment != nil, innerEvt.GiftBadge != nil, innerEvt.PollCreate != nil, innerEvt.PollVote != nil,
|
||||
innerEvt.Payment != nil, innerEvt.GiftBadge != nil,
|
||||
innerEvt.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
|
||||
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
||||
return bridgev2.RemoteEventMessage
|
||||
|
|
@ -184,7 +176,7 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
|||
return bridgev2.RemoteEventReactionRemove
|
||||
}
|
||||
return bridgev2.RemoteEventReaction
|
||||
case innerEvt.Delete != nil, innerEvt.AdminDelete != nil:
|
||||
case innerEvt.Delete != nil:
|
||||
return bridgev2.RemoteEventMessageRemove
|
||||
case innerEvt.GetGroupV2().GetGroupChange() != nil:
|
||||
return bridgev2.RemoteEventChatInfoChange
|
||||
|
|
@ -293,21 +285,16 @@ func (evt *Bv2ChatEvent) GetTimestamp() time.Time {
|
|||
}
|
||||
|
||||
func (evt *Bv2ChatEvent) GetTargetMessage() networkid.MessageID {
|
||||
var targetAuthorACI uuid.UUID
|
||||
var targetAuthorACI string
|
||||
var targetSentTS uint64
|
||||
switch innerEvt := evt.Event.(type) {
|
||||
case *signalpb.DataMessage:
|
||||
switch {
|
||||
case innerEvt.Reaction != nil:
|
||||
targetAuthorACI, _ = signalmeow.ParseStringOrBinaryUUID(innerEvt.Reaction.GetTargetAuthorAci(), innerEvt.Reaction.GetTargetAuthorAciBinary())
|
||||
targetAuthorACI = innerEvt.Reaction.GetTargetAuthorAci()
|
||||
targetSentTS = innerEvt.Reaction.GetTargetSentTimestamp()
|
||||
case innerEvt.Delete != nil:
|
||||
targetSentTS = innerEvt.Delete.GetTargetSentTimestamp()
|
||||
case innerEvt.AdminDelete != nil:
|
||||
if len(innerEvt.AdminDelete.GetTargetAuthorAciBinary()) == 16 {
|
||||
targetAuthorACI = uuid.UUID(innerEvt.AdminDelete.GetTargetAuthorAciBinary())
|
||||
}
|
||||
targetSentTS = innerEvt.AdminDelete.GetTargetSentTimestamp()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
@ -316,10 +303,11 @@ func (evt *Bv2ChatEvent) GetTargetMessage() networkid.MessageID {
|
|||
default:
|
||||
return ""
|
||||
}
|
||||
if targetAuthorACI == uuid.Nil {
|
||||
targetAuthorACI = evt.Info.Sender
|
||||
targetAuthorUUID := evt.Info.Sender
|
||||
if targetAuthorACI != "" {
|
||||
targetAuthorUUID, _ = uuid.Parse(targetAuthorACI)
|
||||
}
|
||||
return signalid.MakeMessageID(targetAuthorACI, targetSentTS)
|
||||
return signalid.MakeMessageID(targetAuthorUUID, targetSentTS)
|
||||
}
|
||||
|
||||
func (evt *Bv2ChatEvent) GetReactionEmoji() (string, networkid.EmojiID) {
|
||||
|
|
@ -339,7 +327,7 @@ func (evt *Bv2ChatEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Po
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("ConvertMessage() called for non-DataMessage event")
|
||||
}
|
||||
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, evt.Info.Sender, intent, dataMsg, nil)
|
||||
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, intent, dataMsg, nil)
|
||||
if converted.Disappear.Type != "" {
|
||||
evtTS := evt.GetTimestamp()
|
||||
if !dataMsg.GetIsViewOnce() {
|
||||
|
|
@ -364,7 +352,7 @@ func (evt *Bv2ChatEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Porta
|
|||
return nil, fmt.Errorf("ConvertEdit() called for non-EditMessage event")
|
||||
}
|
||||
// TODO tell converter about existing parts to avoid reupload?
|
||||
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, evt.Info.Sender, intent, editMsg.GetDataMessage(), nil)
|
||||
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, intent, editMsg.GetDataMessage(), nil)
|
||||
// TODO can anything other than the text be edited?
|
||||
editPart := converted.Parts[len(converted.Parts)-1].ToEditPart(existing[len(existing)-1])
|
||||
editPart.Part.EditCount++
|
||||
|
|
@ -426,7 +414,7 @@ func (b *Bv2Receipt) GetReadUpTo() time.Time {
|
|||
return time.Time{}
|
||||
}
|
||||
|
||||
var _ bridgev2.RemoteReadReceipt = (*Bv2Receipt)(nil)
|
||||
var _ bridgev2.RemoteReceipt = (*Bv2Receipt)(nil)
|
||||
|
||||
func convertReceipts[T any](ctx context.Context, input []T, getMessageFunc func(ctx context.Context, msgID T) (*database.Message, error)) map[networkid.PortalKey]*Bv2Receipt {
|
||||
log := zerolog.Ctx(ctx)
|
||||
|
|
@ -472,7 +460,7 @@ func (s *SignalClient) handleSignalReceipt(evt *events.Receipt) bool {
|
|||
Stringer("sender_id", evt.Sender).
|
||||
Stringer("receipt_type", evt.Content.GetType()).
|
||||
Logger()
|
||||
ctx := log.WithContext(s.Main.Bridge.BackgroundCtx)
|
||||
ctx := log.WithContext(context.TODO())
|
||||
receipts := convertReceipts(ctx, evt.Content.Timestamp, func(ctx context.Context, msgTS uint64) (*database.Message, error) {
|
||||
return s.Main.Bridge.DB.Message.GetFirstPartByID(ctx, s.UserLogin.ID, signalid.MakeMessageID(s.Client.Store.ACI, msgTS))
|
||||
})
|
||||
|
|
@ -483,9 +471,9 @@ func (s *SignalClient) handleSignalReadSelf(evt *events.ReadSelf) bool {
|
|||
log := s.UserLogin.Log.With().
|
||||
Str("action", "handle signal read self").
|
||||
Logger()
|
||||
ctx := log.WithContext(s.Main.Bridge.BackgroundCtx)
|
||||
ctx := log.WithContext(context.TODO())
|
||||
receipts := convertReceipts(ctx, evt.Messages, func(ctx context.Context, msgInfo *signalpb.SyncMessage_Read) (*database.Message, error) {
|
||||
aciUUID, err := signalmeow.ParseStringOrBinaryUUID(msgInfo.GetSenderAci(), msgInfo.GetSenderAciBinary())
|
||||
aciUUID, err := uuid.Parse(msgInfo.GetSenderAci())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -504,13 +492,6 @@ func (s *SignalClient) conversationIDToPortalKey(ctx context.Context, cid *signa
|
|||
return networkid.PortalKey{}, false
|
||||
}
|
||||
return s.makeDMPortalKey(serviceID), true
|
||||
case *signalpb.ConversationIdentifier_ThreadServiceIdBinary:
|
||||
serviceID, err := libsignalgo.ServiceIDFromBytes(ident.ThreadServiceIdBinary)
|
||||
if err != nil {
|
||||
log.Err(err).Hex("chat_id", ident.ThreadServiceIdBinary).Msg("Failed to parse delete for me conversation ID")
|
||||
return networkid.PortalKey{}, false
|
||||
}
|
||||
return s.makeDMPortalKey(serviceID), true
|
||||
case *signalpb.ConversationIdentifier_ThreadGroupId:
|
||||
if len(ident.ThreadGroupId) != libsignalgo.GroupIdentifierLength {
|
||||
log.Error().
|
||||
|
|
@ -549,22 +530,6 @@ func (s *SignalClient) addressableMessageToID(ctx context.Context, portalKey net
|
|||
return ""
|
||||
}
|
||||
return signalid.MakeMessageID(serviceID.UUID, am.GetSentTimestamp())
|
||||
case *signalpb.AddressableMessage_AuthorServiceIdBinary:
|
||||
serviceID, err := libsignalgo.ServiceIDFromBytes(typedAuthor.AuthorServiceIdBinary)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Object("portal_key", portalKey).
|
||||
Hex("author_service_id_binary", typedAuthor.AuthorServiceIdBinary).
|
||||
Msg("Failed to parse delete for me message author service ID")
|
||||
return ""
|
||||
} else if serviceID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
log.Warn().
|
||||
Object("portal_key", portalKey).
|
||||
Hex("author_service_id_binary", typedAuthor.AuthorServiceIdBinary).
|
||||
Msg("Dropping delete for me message with unsupported service ID type")
|
||||
return ""
|
||||
}
|
||||
return signalid.MakeMessageID(serviceID.UUID, am.GetSentTimestamp())
|
||||
case *signalpb.AddressableMessage_AuthorE164:
|
||||
log.Warn().
|
||||
Object("portal_key", portalKey).
|
||||
|
|
@ -655,45 +620,13 @@ func (s *SignalClient) handleSignalDeleteForMe(evt *events.DeleteForMe) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (s *SignalClient) handleSignalMessageRequestResponse(evt *events.MessageRequestResponse) bool {
|
||||
if evt.Type != signalpb.SyncMessage_MessageRequestResponse_ACCEPT {
|
||||
// TODO do we need to do anything with blocks/deletes here or are they sent as normal delete events?
|
||||
return true
|
||||
}
|
||||
var portalKey networkid.PortalKey
|
||||
if evt.GroupID != nil {
|
||||
portalKey = s.makePortalKey(evt.GroupID.String())
|
||||
} else if evt.ThreadACI != uuid.Nil {
|
||||
portalKey = s.makeDMPortalKey(libsignalgo.NewACIServiceID(evt.ThreadACI))
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
res := s.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventChatInfoChange,
|
||||
PortalKey: portalKey,
|
||||
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
|
||||
StreamOrder: int64(evt.Timestamp),
|
||||
LogContext: func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str("action", "unmark message request").Str("source", "sync message")
|
||||
},
|
||||
},
|
||||
ChatInfoChange: &bridgev2.ChatInfoChange{
|
||||
ChatInfo: &bridgev2.ChatInfo{
|
||||
MessageRequest: ptr.Ptr(false),
|
||||
},
|
||||
},
|
||||
})
|
||||
return res.Success
|
||||
}
|
||||
|
||||
func (s *SignalClient) handleSignalACIFound(evt *events.ACIFound) {
|
||||
log := s.UserLogin.Log.With().
|
||||
Str("action", "handle aci found").
|
||||
Stringer("aci", evt.ACI).
|
||||
Stringer("pni", evt.PNI).
|
||||
Logger()
|
||||
ctx := log.WithContext(s.Main.Bridge.BackgroundCtx)
|
||||
ctx := log.WithContext(context.TODO())
|
||||
pniPortalKey := s.makeDMPortalKey(evt.PNI)
|
||||
aciPortalKey := s.makeDMPortalKey(evt.ACI)
|
||||
result, portal, err := s.Main.Bridge.ReIDPortal(ctx, pniPortalKey, aciPortalKey)
|
||||
|
|
@ -713,7 +646,7 @@ func (s *SignalClient) handleSignalACIFound(evt *events.ACIFound) {
|
|||
|
||||
func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
|
||||
log := s.UserLogin.Log.With().Str("action", "handle contact list").Logger()
|
||||
ctx := log.WithContext(s.Main.Bridge.BackgroundCtx)
|
||||
ctx := log.WithContext(context.TODO())
|
||||
for _, contact := range evt.Contacts {
|
||||
if contact.ACI == uuid.Nil {
|
||||
continue
|
||||
|
|
@ -741,33 +674,6 @@ func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
|
|||
if contact.ACI == s.Client.Store.ACI {
|
||||
s.updateRemoteProfile(ctx, true)
|
||||
}
|
||||
if ptr.Val(contact.Whitelisted) {
|
||||
portal, err := s.Main.Bridge.GetExistingPortalByKey(ctx, s.makeDMPortalKey(libsignalgo.NewACIServiceID(contact.ACI)))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to get existing portal to update contact info")
|
||||
continue
|
||||
} else if portal != nil && portal.MessageRequest {
|
||||
s.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventChatInfoChange,
|
||||
LogContext: func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str("action", "unmark message request").Str("source", "contact list")
|
||||
},
|
||||
PortalKey: portal.PortalKey,
|
||||
},
|
||||
ChatInfoChange: &bridgev2.ChatInfoChange{
|
||||
ChatInfo: &bridgev2.ChatInfo{
|
||||
MessageRequest: ptr.Ptr(false),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
s.UserLogin.Metadata.(*signalid.UserLoginMetadata).LastContactSync = jsontime.UnixMilliNow()
|
||||
err := s.UserLogin.Save(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update last contact sync time")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ type QRLogin struct {
|
|||
cancelChan context.CancelFunc
|
||||
ProvChan chan signalmeow.ProvisioningResponse
|
||||
newQRCount int
|
||||
|
||||
ProvData *store.DeviceData
|
||||
}
|
||||
|
||||
var _ bridgev2.LoginProcessDisplayAndWait = (*QRLogin)(nil)
|
||||
|
|
@ -75,7 +77,7 @@ func (qr *QRLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
|||
Str("action", "login").
|
||||
Stringer("user_id", qr.User.MXID).
|
||||
Logger()
|
||||
provCtx, cancel := context.WithCancel(log.WithContext(qr.Main.Bridge.BackgroundCtx))
|
||||
provCtx, cancel := context.WithCancel(log.WithContext(context.Background()))
|
||||
qr.cancelChan = cancel
|
||||
// Don't use the start context here: the channel will outlive the start request.
|
||||
qr.ProvChan = signalmeow.PerformProvisioning(
|
||||
|
|
@ -110,6 +112,14 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
|||
return nil, fmt.Errorf("login not started")
|
||||
}
|
||||
|
||||
if qr.ProvData == nil {
|
||||
return qr.qrWait(ctx)
|
||||
} else {
|
||||
return qr.processingWait(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (qr *QRLogin) qrWait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||
select {
|
||||
case resp := <-qr.ProvChan:
|
||||
if resp.Err != nil {
|
||||
|
|
@ -122,7 +132,15 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
|||
qr.cancelChan()
|
||||
return nil, fmt.Errorf("no signal account ID received")
|
||||
}
|
||||
return qr.loginComplete(ctx, resp.ProvisioningData)
|
||||
qr.ProvData = resp.ProvisioningData
|
||||
return &bridgev2.LoginStep{
|
||||
Type: bridgev2.LoginStepTypeDisplayAndWait,
|
||||
StepID: LoginStepProcess,
|
||||
Instructions: fmt.Sprintf("Processing login as %s...", resp.ProvisioningData.Number),
|
||||
DisplayAndWaitParams: &bridgev2.LoginDisplayAndWaitParams{
|
||||
Type: bridgev2.LoginDisplayTypeNothing,
|
||||
},
|
||||
}, nil
|
||||
|
||||
// Server will timeout the request after 60 seconds, but Signal Desktop opens
|
||||
// a new socket and gets a new QR code after 45 seconds. We should do the same.
|
||||
|
|
@ -140,13 +158,26 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (qr *QRLogin) loginComplete(ctx context.Context, provData *store.DeviceData) (*bridgev2.LoginStep, error) {
|
||||
func (qr *QRLogin) processingWait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||
defer qr.cancelChan()
|
||||
newLoginID := signalid.MakeUserLoginID(qr.ProvData.ACI)
|
||||
|
||||
select {
|
||||
case resp := <-qr.ProvChan:
|
||||
if resp.Err != nil {
|
||||
return nil, resp.Err
|
||||
} else if resp.State != signalmeow.StateProvisioningPreKeysRegistered {
|
||||
return nil, fmt.Errorf("unexpected state %v", resp.State)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
ul, err := qr.User.NewLogin(ctx, &database.UserLogin{
|
||||
ID: signalid.MakeUserLoginID(provData.ACI),
|
||||
RemoteName: provData.Number,
|
||||
ID: newLoginID,
|
||||
RemoteName: qr.ProvData.Number,
|
||||
RemoteProfile: status.RemoteProfile{
|
||||
Phone: provData.Number,
|
||||
Phone: qr.ProvData.Number,
|
||||
},
|
||||
Metadata: &signalid.UserLoginMetadata{},
|
||||
}, &bridgev2.NewLoginParams{
|
||||
|
|
@ -159,7 +190,7 @@ func (qr *QRLogin) loginComplete(ctx context.Context, provData *store.DeviceData
|
|||
return &bridgev2.LoginStep{
|
||||
Type: bridgev2.LoginStepTypeComplete,
|
||||
StepID: LoginStepComplete,
|
||||
Instructions: fmt.Sprintf("Successfully logged in as %s / %s", provData.Number, provData.ACI),
|
||||
Instructions: fmt.Sprintf("Successfully logged in as %s / %s", qr.ProvData.Number, qr.ProvData.ACI),
|
||||
CompleteParams: &bridgev2.LoginCompleteParams{
|
||||
UserLoginID: ul.ID,
|
||||
UserLogin: ul,
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ package libsignalgo
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
|
@ -44,22 +42,6 @@ func BytesToBuffer(data []byte) C.SignalBorrowedBuffer {
|
|||
return buf
|
||||
}
|
||||
|
||||
func ManyBytesToBuffer[T ~[]byte](datas []T) (C.SignalBorrowedSliceOfBuffers, func()) {
|
||||
buffers := make([]C.SignalBorrowedBuffer, len(datas))
|
||||
var pinner runtime.Pinner
|
||||
for i, data := range datas {
|
||||
if len(data) == 0 {
|
||||
panic(fmt.Errorf("empty slice passed to ManyBytesToBuffer at index %d", i))
|
||||
}
|
||||
pinner.Pin(&data[0])
|
||||
buffers[i] = BytesToBuffer(data)
|
||||
}
|
||||
return C.SignalBorrowedSliceOfBuffers{
|
||||
base: unsafe.SliceData(buffers),
|
||||
length: C.size_t(len(buffers)),
|
||||
}, pinner.Unpin
|
||||
}
|
||||
|
||||
func EmptyBorrowedBuffer() C.SignalBorrowedBuffer {
|
||||
return C.SignalBorrowedBuffer{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package libsignalgo
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lsignal_ffi -ldl -lm -lz -lstdc++
|
||||
#cgo LDFLAGS: -lsignal_ffi -ldl -lm -lz
|
||||
*/
|
||||
import "C"
|
||||
|
|
|
|||
|
|
@ -39,17 +39,3 @@ func CopySignalOwnedBufferToBytes(buffer C.SignalOwnedBuffer) (b []byte) {
|
|||
C.signal_free_buffer(buffer.base, buffer.length)
|
||||
return
|
||||
}
|
||||
|
||||
func CopySignalBytestringArray[T ~[]byte](buffer C.SignalBytestringArray) (b []T) {
|
||||
concatted := C.GoBytes(unsafe.Pointer(buffer.bytes.base), C.int(buffer.bytes.length))
|
||||
b = make([]T, int(buffer.lengths.length))
|
||||
sizeTSize := unsafe.Sizeof(C.size_t(0))
|
||||
offset := 0
|
||||
for i := 0; i < int(buffer.lengths.length); i++ {
|
||||
length := int(*(*C.size_t)(unsafe.Add(unsafe.Pointer(buffer.lengths.base), uintptr(i)*sizeTSize)))
|
||||
b[i] = concatted[offset : offset+length]
|
||||
offset += length
|
||||
}
|
||||
C.signal_free_bytestring_array(buffer)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ package libsignalgo
|
|||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DecryptionErrorMessage struct {
|
||||
|
|
@ -48,7 +49,7 @@ func DeserializeDecryptionErrorMessage(messageBytes []byte) (*DecryptionErrorMes
|
|||
return wrapDecryptionErrorMessage(dem.raw), nil
|
||||
}
|
||||
|
||||
func DecryptionErrorMessageForOriginalMessage(originalBytes []byte, originalType CiphertextMessageType, originalTs uint64, originalSenderDeviceID uint) (*DecryptionErrorMessage, error) {
|
||||
func DecryptionErrorMessageForOriginalMessage(originalBytes []byte, originalType uint8, originalTs uint64, originalSenderDeviceID uint) (*DecryptionErrorMessage, error) {
|
||||
var dem C.SignalMutPointerDecryptionErrorMessage
|
||||
signalFfiError := C.signal_decryption_error_message_for_original_message(
|
||||
&dem,
|
||||
|
|
@ -111,14 +112,14 @@ func (dem *DecryptionErrorMessage) Serialize() ([]byte, error) {
|
|||
return CopySignalOwnedBufferToBytes(serialized), nil
|
||||
}
|
||||
|
||||
func (dem *DecryptionErrorMessage) GetTimestamp() (uint64, error) {
|
||||
func (dem *DecryptionErrorMessage) GetTimestamp() (time.Time, error) {
|
||||
var ts C.uint64_t
|
||||
signalFfiError := C.signal_decryption_error_message_get_timestamp(&ts, dem.constPtr())
|
||||
runtime.KeepAlive(dem)
|
||||
if signalFfiError != nil {
|
||||
return 0, wrapError(signalFfiError)
|
||||
return time.Time{}, wrapError(signalFfiError)
|
||||
}
|
||||
return uint64(ts), nil
|
||||
return time.UnixMilli(int64(ts)), nil
|
||||
}
|
||||
|
||||
func (dem *DecryptionErrorMessage) GetDeviceID() (uint32, error) {
|
||||
|
|
|
|||
|
|
@ -26,91 +26,34 @@ import (
|
|||
|
||||
type ErrorCode int
|
||||
|
||||
func (e ErrorCode) Error() string {
|
||||
return fmt.Sprintf("libsignalgo.ErrorCode(%d)", int(e))
|
||||
}
|
||||
|
||||
const (
|
||||
ErrorCodeUnknownError ErrorCode = 1
|
||||
ErrorCodeInvalidState ErrorCode = 2
|
||||
ErrorCodeInternalError ErrorCode = 3
|
||||
ErrorCodeNullParameter ErrorCode = 4
|
||||
ErrorCodeInvalidArgument ErrorCode = 5
|
||||
ErrorCodeInvalidType ErrorCode = 6
|
||||
ErrorCodeInvalidUtf8String ErrorCode = 7
|
||||
ErrorCodeCancelled ErrorCode = 8
|
||||
ErrorCodeProtobufError ErrorCode = 10
|
||||
ErrorCodeLegacyCiphertextVersion ErrorCode = 21
|
||||
ErrorCodeUnknownCiphertextVersion ErrorCode = 22
|
||||
ErrorCodeUnrecognizedMessageVersion ErrorCode = 23
|
||||
ErrorCodeInvalidMessage ErrorCode = 30
|
||||
ErrorCodeSealedSenderSelfSend ErrorCode = 31
|
||||
ErrorCodeInvalidKey ErrorCode = 40
|
||||
ErrorCodeInvalidSignature ErrorCode = 41
|
||||
ErrorCodeInvalidAttestationData ErrorCode = 42
|
||||
ErrorCodeFingerprintVersionMismatch ErrorCode = 51
|
||||
ErrorCodeFingerprintParsingError ErrorCode = 52
|
||||
ErrorCodeUntrustedIdentity ErrorCode = 60
|
||||
ErrorCodeInvalidKeyIdentifier ErrorCode = 70
|
||||
ErrorCodeSessionNotFound ErrorCode = 80
|
||||
ErrorCodeInvalidRegistrationId ErrorCode = 81
|
||||
ErrorCodeInvalidSession ErrorCode = 82
|
||||
ErrorCodeInvalidSenderKeySession ErrorCode = 83
|
||||
ErrorCodeInvalidProtocolAddress ErrorCode = 84
|
||||
ErrorCodeDuplicatedMessage ErrorCode = 90
|
||||
ErrorCodeCallbackError ErrorCode = 100
|
||||
ErrorCodeVerificationFailure ErrorCode = 110
|
||||
ErrorCodeUsernameCannotBeEmpty ErrorCode = 120
|
||||
ErrorCodeUsernameCannotStartWithDigit ErrorCode = 121
|
||||
ErrorCodeUsernameMissingSeparator ErrorCode = 122
|
||||
ErrorCodeUsernameBadDiscriminatorCharacter ErrorCode = 123
|
||||
ErrorCodeUsernameBadNicknameCharacter ErrorCode = 124
|
||||
ErrorCodeUsernameTooShort ErrorCode = 125
|
||||
ErrorCodeUsernameTooLong ErrorCode = 126
|
||||
ErrorCodeUsernameLinkInvalidEntropyDataLength ErrorCode = 127
|
||||
ErrorCodeUsernameLinkInvalid ErrorCode = 128
|
||||
ErrorCodeUsernameDiscriminatorCannotBeEmpty ErrorCode = 130
|
||||
ErrorCodeUsernameDiscriminatorCannotBeZero ErrorCode = 131
|
||||
ErrorCodeUsernameDiscriminatorCannotBeSingleDigit ErrorCode = 132
|
||||
ErrorCodeUsernameDiscriminatorCannotHaveLeadingZeros ErrorCode = 133
|
||||
ErrorCodeUsernameDiscriminatorTooLarge ErrorCode = 134
|
||||
ErrorCodeIoError ErrorCode = 140
|
||||
ErrorCodeInvalidMediaInput ErrorCode = 141
|
||||
ErrorCodeUnsupportedMediaInput ErrorCode = 142
|
||||
ErrorCodeConnectionTimedOut ErrorCode = 143
|
||||
ErrorCodeNetworkProtocol ErrorCode = 144
|
||||
ErrorCodeRateLimited ErrorCode = 145
|
||||
ErrorCodeWebSocket ErrorCode = 146
|
||||
ErrorCodeCdsiInvalidToken ErrorCode = 147
|
||||
ErrorCodeConnectionFailed ErrorCode = 148
|
||||
ErrorCodeChatServiceInactive ErrorCode = 149
|
||||
ErrorCodeRequestTimedOut ErrorCode = 150
|
||||
ErrorCodeRateLimitChallenge ErrorCode = 151
|
||||
ErrorCodePossibleCaptiveNetwork ErrorCode = 152
|
||||
ErrorCodeSvrDataMissing ErrorCode = 160
|
||||
ErrorCodeSvrRestoreFailed ErrorCode = 161
|
||||
ErrorCodeSvrRotationMachineTooManySteps ErrorCode = 162
|
||||
ErrorCodeSvrRequestFailed ErrorCode = 163
|
||||
ErrorCodeAppExpired ErrorCode = 170
|
||||
ErrorCodeDeviceDeregistered ErrorCode = 171
|
||||
ErrorCodeConnectionInvalidated ErrorCode = 172
|
||||
ErrorCodeConnectedElsewhere ErrorCode = 173
|
||||
ErrorCodeBackupValidation ErrorCode = 180
|
||||
ErrorCodeRegistrationInvalidSessionId ErrorCode = 190
|
||||
ErrorCodeRegistrationUnknown ErrorCode = 192
|
||||
ErrorCodeRegistrationSessionNotFound ErrorCode = 193
|
||||
ErrorCodeRegistrationNotReadyForVerification ErrorCode = 194
|
||||
ErrorCodeRegistrationSendVerificationCodeFailed ErrorCode = 195
|
||||
ErrorCodeRegistrationCodeNotDeliverable ErrorCode = 196
|
||||
ErrorCodeRegistrationSessionUpdateRejected ErrorCode = 197
|
||||
ErrorCodeRegistrationCredentialsCouldNotBeParsed ErrorCode = 198
|
||||
ErrorCodeRegistrationDeviceTransferPossible ErrorCode = 199
|
||||
ErrorCodeRegistrationRecoveryVerificationFailed ErrorCode = 200
|
||||
ErrorCodeRegistrationLock ErrorCode = 201
|
||||
ErrorCodeKeyTransparencyError ErrorCode = 210
|
||||
ErrorCodeKeyTransparencyVerificationFailed ErrorCode = 211
|
||||
ErrorCodeRequestUnauthorized ErrorCode = 220
|
||||
ErrorCodeMismatchedDevices ErrorCode = 221
|
||||
ErrorCodeUnknownError ErrorCode = 1
|
||||
ErrorCodeInvalidState ErrorCode = 2
|
||||
ErrorCodeInternalError ErrorCode = 3
|
||||
ErrorCodeNullParameter ErrorCode = 4
|
||||
ErrorCodeInvalidArgument ErrorCode = 5
|
||||
ErrorCodeInvalidType ErrorCode = 6
|
||||
ErrorCodeInvalidUtf8String ErrorCode = 7
|
||||
ErrorCodeProtobufError ErrorCode = 10
|
||||
ErrorCodeLegacyCiphertextVersion ErrorCode = 21
|
||||
ErrorCodeUnknownCiphertextVersion ErrorCode = 22
|
||||
ErrorCodeUnrecognizedMessageVersion ErrorCode = 23
|
||||
ErrorCodeInvalidMessage ErrorCode = 30
|
||||
ErrorCodeSealedSenderSelfSend ErrorCode = 31
|
||||
ErrorCodeInvalidKey ErrorCode = 40
|
||||
ErrorCodeInvalidSignature ErrorCode = 41
|
||||
ErrorCodeInvalidAttestationData ErrorCode = 42
|
||||
ErrorCodeFingerprintVersionMismatch ErrorCode = 51
|
||||
ErrorCodeFingerprintParsingError ErrorCode = 52
|
||||
ErrorCodeUntrustedIdentity ErrorCode = 60
|
||||
ErrorCodeInvalidKeyIdentifier ErrorCode = 70
|
||||
ErrorCodeSessionNotFound ErrorCode = 80
|
||||
ErrorCodeInvalidRegistrationId ErrorCode = 81
|
||||
ErrorCodeInvalidSession ErrorCode = 82
|
||||
ErrorCodeInvalidSenderKeySession ErrorCode = 83
|
||||
ErrorCodeDuplicatedMessage ErrorCode = 90
|
||||
ErrorCodeCallbackError ErrorCode = 100
|
||||
ErrorCodeVerificationFailure ErrorCode = 110
|
||||
)
|
||||
|
||||
type SignalError struct {
|
||||
|
|
@ -122,10 +65,6 @@ func (e *SignalError) Error() string {
|
|||
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func (e *SignalError) Unwrap() error {
|
||||
return e.Code
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapError(signalError *C.SignalFfiError) error {
|
||||
if signalError == nil {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func GroupEncrypt(ctx context.Context, ptext []byte, sender *Address, distributi
|
|||
signalFfiError := C.signal_group_encrypt_message(
|
||||
&ciphertextMessage,
|
||||
sender.constPtr(),
|
||||
*(*C.SignalUuid)(unsafe.Pointer(&distributionID)),
|
||||
(*[C.SignalUUID_LEN]C.uchar)(unsafe.Pointer(&distributionID)),
|
||||
BytesToBuffer(ptext),
|
||||
callbackCtx.wrapSenderKeyStore(store))
|
||||
runtime.KeepAlive(ptext)
|
||||
|
|
|
|||
|
|
@ -76,10 +76,6 @@ func (gmk GroupMasterKey) GroupIdentifier() (*GroupIdentifier, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (gmk GroupMasterKey) SecretParams() (GroupSecretParams, error) {
|
||||
return DeriveGroupSecretParamsFromMasterKey(gmk)
|
||||
}
|
||||
|
||||
func GenerateGroupSecretParamsWithRandomness(randomness Randomness) (GroupSecretParams, error) {
|
||||
var params [C.SignalGROUP_SECRET_PARAMS_LEN]C.uchar
|
||||
signalFfiError := C.signal_group_secret_params_generate_deterministic(¶ms, (*[C.SignalRANDOMNESS_LEN]C.uint8_t)(unsafe.Pointer(&randomness)))
|
||||
|
|
|
|||
|
|
@ -1,215 +0,0 @@
|
|||
// mautrix-signal - A Matrix-signal puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package libsignalgo
|
||||
|
||||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type GroupSendFullToken []byte
|
||||
|
||||
func (gsft GroupSendFullToken) String() string {
|
||||
return base64.StdEncoding.EncodeToString(gsft)
|
||||
}
|
||||
|
||||
func (gsft GroupSendFullToken) CheckValidContents() error {
|
||||
signalFfiError := C.signal_group_send_full_token_check_valid_contents(
|
||||
BytesToBuffer(gsft),
|
||||
)
|
||||
runtime.KeepAlive(gsft)
|
||||
if signalFfiError != nil {
|
||||
return wrapError(signalFfiError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gsft GroupSendFullToken) GetExpiration() (time.Time, error) {
|
||||
var expiration C.uint64_t
|
||||
signalFfiError := C.signal_group_send_full_token_get_expiration(
|
||||
&expiration,
|
||||
BytesToBuffer(gsft),
|
||||
)
|
||||
runtime.KeepAlive(gsft)
|
||||
if signalFfiError != nil {
|
||||
return time.Time{}, wrapError(signalFfiError)
|
||||
}
|
||||
return time.Unix(int64(expiration), 0), nil
|
||||
}
|
||||
|
||||
type GroupSendToken []byte
|
||||
|
||||
func (gst GroupSendToken) CheckValidContents() error {
|
||||
signalFfiError := C.signal_group_send_token_check_valid_contents(
|
||||
BytesToBuffer(gst),
|
||||
)
|
||||
runtime.KeepAlive(gst)
|
||||
if signalFfiError != nil {
|
||||
return wrapError(signalFfiError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gst GroupSendToken) ToFullToken(expiration time.Time) (GroupSendFullToken, error) {
|
||||
var fullToken C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
signalFfiError := C.signal_group_send_token_to_full_token(
|
||||
&fullToken,
|
||||
BytesToBuffer(gst),
|
||||
C.uint64_t(expiration.Unix()),
|
||||
)
|
||||
runtime.KeepAlive(gst)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
return CopySignalOwnedBufferToBytes(fullToken), nil
|
||||
}
|
||||
|
||||
type GroupSendEndorsement []byte
|
||||
|
||||
func (gse GroupSendEndorsement) ToToken(groupSecretParams *GroupSecretParams) (GroupSendToken, error) {
|
||||
var token C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
signalFfiError := C.signal_group_send_endorsement_to_token(
|
||||
&token,
|
||||
BytesToBuffer(gse),
|
||||
(*[C.SignalGROUP_SECRET_PARAMS_LEN]C.uint8_t)(unsafe.Pointer(groupSecretParams)),
|
||||
)
|
||||
runtime.KeepAlive(gse)
|
||||
runtime.KeepAlive(groupSecretParams)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
return CopySignalOwnedBufferToBytes(token), nil
|
||||
}
|
||||
|
||||
func (gse GroupSendEndorsement) ToFullToken(params *GroupSecretParams, expiration time.Time) (GroupSendFullToken, error) {
|
||||
token, err := gse.ToToken(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return token.ToFullToken(expiration)
|
||||
}
|
||||
|
||||
func (gse GroupSendEndorsement) CheckValidContents() error {
|
||||
signalFfiError := C.signal_group_send_endorsement_check_valid_contents(
|
||||
BytesToBuffer(gse),
|
||||
)
|
||||
runtime.KeepAlive(gse)
|
||||
if signalFfiError != nil {
|
||||
return wrapError(signalFfiError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gse GroupSendEndorsement) Remove(other GroupSendEndorsement) (GroupSendEndorsement, error) {
|
||||
var result C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
signalFfiError := C.signal_group_send_endorsement_remove(
|
||||
&result,
|
||||
BytesToBuffer(gse),
|
||||
BytesToBuffer(other),
|
||||
)
|
||||
runtime.KeepAlive(gse)
|
||||
runtime.KeepAlive(other)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
return CopySignalOwnedBufferToBytes(result), nil
|
||||
}
|
||||
|
||||
func GroupSendEndorsementCombine(endorsements ...GroupSendEndorsement) (GroupSendEndorsement, error) {
|
||||
var result C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
cEndorsements, unpin := ManyBytesToBuffer(endorsements)
|
||||
defer unpin()
|
||||
signalFfiError := C.signal_group_send_endorsement_combine(
|
||||
&result,
|
||||
cEndorsements,
|
||||
)
|
||||
runtime.KeepAlive(endorsements)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
return CopySignalOwnedBufferToBytes(result), nil
|
||||
}
|
||||
|
||||
type GroupSendEndorsementsResponse []byte
|
||||
|
||||
func (gser GroupSendEndorsementsResponse) GetExpiration() (time.Time, error) {
|
||||
var expiration C.uint64_t
|
||||
signalFfiError := C.signal_group_send_endorsements_response_get_expiration(
|
||||
&expiration,
|
||||
BytesToBuffer(gser),
|
||||
)
|
||||
runtime.KeepAlive(gser)
|
||||
if signalFfiError != nil {
|
||||
return time.Time{}, wrapError(signalFfiError)
|
||||
}
|
||||
return time.Unix(int64(expiration), 0), nil
|
||||
}
|
||||
|
||||
func (gser GroupSendEndorsementsResponse) CheckValidContents() error {
|
||||
signalFfiError := C.signal_group_send_endorsements_response_check_valid_contents(
|
||||
BytesToBuffer(gser),
|
||||
)
|
||||
runtime.KeepAlive(gser)
|
||||
if signalFfiError != nil {
|
||||
return wrapError(signalFfiError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gser GroupSendEndorsementsResponse) ReceiveWithServiceIDs(
|
||||
groupMembers []ServiceID, localUser ServiceID, params *GroupSecretParams, spp *ServerPublicParams,
|
||||
) (GroupSendEndorsement, map[ServiceID]GroupSendEndorsement, error) {
|
||||
var out C.SignalBytestringArray = C.SignalBytestringArray{}
|
||||
concatenatedMembers := make([]byte, len(groupMembers)*17)
|
||||
for i, member := range groupMembers {
|
||||
copy(concatenatedMembers[i*17:(i+1)*17], member.FixedBytes()[:])
|
||||
}
|
||||
signalFfiError := C.signal_group_send_endorsements_response_receive_and_combine_with_service_ids(
|
||||
&out,
|
||||
BytesToBuffer(gser),
|
||||
BytesToBuffer(concatenatedMembers),
|
||||
localUser.CFixedBytes(),
|
||||
C.uint64_t(time.Now().Unix()),
|
||||
(*[C.SignalGROUP_SECRET_PARAMS_LEN]C.uint8_t)(unsafe.Pointer(params)),
|
||||
C.SignalConstPointerServerPublicParams{spp},
|
||||
)
|
||||
runtime.KeepAlive(gser)
|
||||
runtime.KeepAlive(concatenatedMembers)
|
||||
runtime.KeepAlive(params)
|
||||
runtime.KeepAlive(spp)
|
||||
if signalFfiError != nil {
|
||||
return nil, nil, wrapError(signalFfiError)
|
||||
}
|
||||
endorsements := CopySignalBytestringArray[GroupSendEndorsement](out)
|
||||
memberEndorsements := make(map[ServiceID]GroupSendEndorsement, len(groupMembers))
|
||||
for i, member := range groupMembers {
|
||||
if len(endorsements) > i && len(endorsements[i]) > 0 {
|
||||
memberEndorsements[member] = endorsements[i]
|
||||
}
|
||||
}
|
||||
combined, err := GroupSendEndorsementCombine(endorsements...)
|
||||
if err != nil {
|
||||
return nil, memberEndorsements, err
|
||||
}
|
||||
return combined, memberEndorsements, nil
|
||||
}
|
||||
|
|
@ -84,7 +84,8 @@ func (i *IdentityKey) VerifyAlternateIdentity(other *IdentityKey, signature []by
|
|||
}
|
||||
|
||||
func (i *IdentityKey) Equal(other *IdentityKey) (bool, error) {
|
||||
return i.publicKey.Equal(other.publicKey)
|
||||
result, err := i.publicKey.Compare(other.publicKey)
|
||||
return result == 0, err
|
||||
}
|
||||
|
||||
type IdentityKeyPair struct {
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ package libsignalgo
|
|||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
|
||||
extern int signal_get_identity_key_pair_callback(void *store_ctx, SignalPairOfMutPointerPrivateKeyMutPointerPublicKey *keyp);
|
||||
typedef const SignalProtocolAddress const_address;
|
||||
typedef const SignalPublicKey const_public_key;
|
||||
|
||||
extern int signal_get_identity_key_pair_callback(void *store_ctx, SignalPrivateKey **keyp);
|
||||
extern int signal_get_local_registration_id_callback(void *store_ctx, uint32_t *idp);
|
||||
extern int signal_save_identity_key_callback(void *store_ctx, uint8_t *out, SignalMutPointerProtocolAddress address, SignalMutPointerPublicKey public_key);
|
||||
extern int signal_get_identity_key_callback(void *store_ctx, SignalMutPointerPublicKey *public_keyp, SignalMutPointerProtocolAddress address);
|
||||
extern int signal_is_trusted_identity_callback(void *store_ctx, bool *out, SignalMutPointerProtocolAddress address, SignalMutPointerPublicKey public_key, uint32_t direction);
|
||||
extern void signal_destroy_identity_key_store_callback(void *store_ctx);
|
||||
extern int signal_save_identity_key_callback(void *store_ctx, const_address *address, const_public_key *public_key);
|
||||
extern int signal_get_identity_key_callback(void *store_ctx, SignalPublicKey **public_keyp, const_address *address);
|
||||
extern int signal_is_trusted_identity_callback(void *store_ctx, const_address *address, const_public_key *public_key, unsigned int direction);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -49,29 +51,22 @@ type IdentityKeyStore interface {
|
|||
}
|
||||
|
||||
//export signal_get_identity_key_pair_callback
|
||||
func signal_get_identity_key_pair_callback(storeCtx unsafe.Pointer, keyp *C.SignalPairOfMutPointerPrivateKeyMutPointerPublicKey) C.int {
|
||||
func signal_get_identity_key_pair_callback(storeCtx unsafe.Pointer, keyp **C.SignalPrivateKey) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
||||
key, err := store.GetIdentityKeyPair(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if key == nil {
|
||||
keyp.first.raw = nil
|
||||
keyp.second.raw = nil
|
||||
return nil
|
||||
*keyp = nil
|
||||
} else {
|
||||
clone, err := key.privateKey.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clone.CancelFinalizer()
|
||||
*keyp = clone.ptr
|
||||
}
|
||||
privClone, err := key.privateKey.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubClone, err := key.publicKey.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privClone.CancelFinalizer()
|
||||
pubClone.CancelFinalizer()
|
||||
keyp.first.raw = privClone.ptr
|
||||
keyp.second.raw = pubClone.ptr
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
|
@ -88,17 +83,17 @@ func signal_get_local_registration_id_callback(storeCtx unsafe.Pointer, idp *C.u
|
|||
}
|
||||
|
||||
//export signal_save_identity_key_callback
|
||||
func signal_save_identity_key_callback(storeCtx unsafe.Pointer, out *C.uint8_t, address C.SignalMutPointerProtocolAddress, publicKey C.SignalMutPointerPublicKey) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
||||
publicKeyStruct := PublicKey{ptr: publicKey.raw}
|
||||
func signal_save_identity_key_callback(storeCtx unsafe.Pointer, address *C.const_address, publicKey *C.const_public_key) C.int {
|
||||
return wrapStoreCallbackCustomReturn(storeCtx, func(store IdentityKeyStore, ctx context.Context) (int, error) {
|
||||
publicKeyStruct := PublicKey{ptr: (*C.SignalPublicKey)(unsafe.Pointer(publicKey))}
|
||||
cloned, err := publicKeyStruct.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
addr := &Address{ptr: address.raw}
|
||||
addr := &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}
|
||||
theirServiceID, err := addr.NameServiceID()
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
replaced, err := store.SaveIdentityKey(
|
||||
ctx,
|
||||
|
|
@ -106,21 +101,20 @@ func signal_save_identity_key_callback(storeCtx unsafe.Pointer, out *C.uint8_t,
|
|||
&IdentityKey{cloned},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
if replaced {
|
||||
*out = 1
|
||||
return 1, nil
|
||||
} else {
|
||||
*out = 0
|
||||
return 0, nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_get_identity_key_callback
|
||||
func signal_get_identity_key_callback(storeCtx unsafe.Pointer, public_keyp *C.SignalMutPointerPublicKey, address C.SignalMutPointerProtocolAddress) C.int {
|
||||
func signal_get_identity_key_callback(storeCtx unsafe.Pointer, public_keyp **C.SignalPublicKey, address *C.const_address) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
||||
addr := &Address{ptr: address.raw}
|
||||
addr := &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}
|
||||
theirServiceID, err := addr.NameServiceID()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -128,42 +122,39 @@ func signal_get_identity_key_callback(storeCtx unsafe.Pointer, public_keyp *C.Si
|
|||
key, err := store.GetIdentityKey(ctx, theirServiceID)
|
||||
if err == nil && key != nil {
|
||||
key.publicKey.CancelFinalizer()
|
||||
public_keyp.raw = key.publicKey.ptr
|
||||
*public_keyp = key.publicKey.ptr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_is_trusted_identity_callback
|
||||
func signal_is_trusted_identity_callback(storeCtx unsafe.Pointer, out *C.bool, address C.SignalMutPointerProtocolAddress, public_key C.SignalMutPointerPublicKey, direction C.uint32_t) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
||||
addr := &Address{ptr: address.raw}
|
||||
func signal_is_trusted_identity_callback(storeCtx unsafe.Pointer, address *C.const_address, public_key *C.const_public_key, direction C.uint) C.int {
|
||||
return wrapStoreCallbackCustomReturn(storeCtx, func(store IdentityKeyStore, ctx context.Context) (int, error) {
|
||||
addr := &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}
|
||||
theirServiceID, err := addr.NameServiceID()
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
trusted, err := store.IsTrustedIdentity(ctx, theirServiceID, &IdentityKey{&PublicKey{ptr: public_key.raw}}, SignalDirection(direction))
|
||||
trusted, err := store.IsTrustedIdentity(ctx, theirServiceID, &IdentityKey{&PublicKey{ptr: (*C.SignalPublicKey)(unsafe.Pointer(public_key))}}, SignalDirection(direction))
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
if trusted {
|
||||
return 1, nil
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
*out = C.bool(trusted)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_destroy_identity_key_store_callback
|
||||
func signal_destroy_identity_key_store_callback(storeCtx unsafe.Pointer) {
|
||||
// No-op: Go's garbage collector handles cleanup
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapIdentityKeyStore(store IdentityKeyStore) C.SignalConstPointerFfiIdentityKeyStoreStruct {
|
||||
return C.SignalConstPointerFfiIdentityKeyStoreStruct{&C.SignalIdentityKeyStore{
|
||||
ctx: wrapStore(ctx, store),
|
||||
get_local_identity_key_pair: C.SignalFfiIdentityKeyStoreGetLocalIdentityKeyPair(C.signal_get_identity_key_pair_callback),
|
||||
get_local_registration_id: C.SignalFfiIdentityKeyStoreGetLocalRegistrationId(C.signal_get_local_registration_id_callback),
|
||||
get_identity_key: C.SignalFfiIdentityKeyStoreGetIdentityKey(C.signal_get_identity_key_callback),
|
||||
save_identity_key: C.SignalFfiIdentityKeyStoreSaveIdentityKey(C.signal_save_identity_key_callback),
|
||||
is_trusted_identity: C.SignalFfiIdentityKeyStoreIsTrustedIdentity(C.signal_is_trusted_identity_callback),
|
||||
destroy: C.SignalFfiIdentityKeyStoreDestroy(C.signal_destroy_identity_key_store_callback),
|
||||
ctx: wrapStore(ctx, store),
|
||||
get_identity_key_pair: C.SignalGetIdentityKeyPair(C.signal_get_identity_key_pair_callback),
|
||||
get_local_registration_id: C.SignalGetLocalRegistrationId(C.signal_get_local_registration_id_callback),
|
||||
save_identity: C.SignalSaveIdentityKey(C.signal_save_identity_key_callback),
|
||||
get_identity: C.SignalGetIdentityKey(C.signal_get_identity_key_callback),
|
||||
is_trusted_identity: C.SignalIsTrustedIdentity(C.signal_is_trusted_identity_callback),
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ package libsignalgo
|
|||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
|
||||
extern int signal_load_kyber_pre_key_callback(void *store_ctx, SignalMutPointerKyberPreKeyRecord *recordp, uint32_t id);
|
||||
extern int signal_store_kyber_pre_key_callback(void *store_ctx, uint32_t id, SignalMutPointerKyberPreKeyRecord record);
|
||||
extern int signal_mark_kyber_pre_key_used_callback(void *store_ctx, uint32_t id, uint32_t ec_prekey_id, SignalMutPointerPublicKey base_key);
|
||||
extern void signal_destroy_kyber_pre_key_store_callback(void *store_ctx);
|
||||
typedef const SignalKyberPreKeyRecord const_kyber_pre_key_record;
|
||||
|
||||
extern int signal_load_kyber_pre_key_callback(void *store_ctx, SignalKyberPreKeyRecord **recordp, uint32_t id);
|
||||
extern int signal_store_kyber_pre_key_callback(void *store_ctx, uint32_t id, const_kyber_pre_key_record *record);
|
||||
extern int signal_mark_kyber_pre_key_used_callback(void *store_ctx, uint32_t id);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -38,21 +39,21 @@ type KyberPreKeyStore interface {
|
|||
}
|
||||
|
||||
//export signal_load_kyber_pre_key_callback
|
||||
func signal_load_kyber_pre_key_callback(storeCtx unsafe.Pointer, keyp *C.SignalMutPointerKyberPreKeyRecord, id C.uint32_t) C.int {
|
||||
func signal_load_kyber_pre_key_callback(storeCtx unsafe.Pointer, keyp **C.SignalKyberPreKeyRecord, id C.uint32_t) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store KyberPreKeyStore, ctx context.Context) error {
|
||||
key, err := store.LoadKyberPreKey(ctx, uint32(id))
|
||||
if err == nil && key != nil {
|
||||
key.CancelFinalizer()
|
||||
keyp.raw = key.ptr
|
||||
*keyp = key.ptr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_store_kyber_pre_key_callback
|
||||
func signal_store_kyber_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord C.SignalMutPointerKyberPreKeyRecord) C.int {
|
||||
func signal_store_kyber_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord *C.const_kyber_pre_key_record) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store KyberPreKeyStore, ctx context.Context) error {
|
||||
record := KyberPreKeyRecord{ptr: preKeyRecord.raw}
|
||||
record := KyberPreKeyRecord{ptr: (*C.SignalKyberPreKeyRecord)(unsafe.Pointer(preKeyRecord))}
|
||||
cloned, err := record.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -62,24 +63,18 @@ func signal_store_kyber_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t,
|
|||
}
|
||||
|
||||
//export signal_mark_kyber_pre_key_used_callback
|
||||
func signal_mark_kyber_pre_key_used_callback(storeCtx unsafe.Pointer, id C.uint32_t, ecPrekeyID C.uint32_t, baseKey C.SignalMutPointerPublicKey) C.int {
|
||||
func signal_mark_kyber_pre_key_used_callback(storeCtx unsafe.Pointer, id C.uint32_t) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store KyberPreKeyStore, ctx context.Context) error {
|
||||
// TODO use ecPrekeyID and baseKey?
|
||||
return store.MarkKyberPreKeyUsed(ctx, uint32(id))
|
||||
err := store.MarkKyberPreKeyUsed(ctx, uint32(id))
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_destroy_kyber_pre_key_store_callback
|
||||
func signal_destroy_kyber_pre_key_store_callback(storeCtx unsafe.Pointer) {
|
||||
// No-op: Go's garbage collector handles cleanup
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapKyberPreKeyStore(store KyberPreKeyStore) C.SignalConstPointerFfiKyberPreKeyStoreStruct {
|
||||
return C.SignalConstPointerFfiKyberPreKeyStoreStruct{&C.SignalKyberPreKeyStore{
|
||||
ctx: wrapStore(ctx, store),
|
||||
load_kyber_pre_key: C.SignalFfiKyberPreKeyStoreLoadKyberPreKey(C.signal_load_kyber_pre_key_callback),
|
||||
store_kyber_pre_key: C.SignalFfiKyberPreKeyStoreStoreKyberPreKey(C.signal_store_kyber_pre_key_callback),
|
||||
mark_kyber_pre_key_used: C.SignalFfiKyberPreKeyStoreMarkKyberPreKeyUsed(C.signal_mark_kyber_pre_key_used_callback),
|
||||
destroy: C.SignalFfiKyberPreKeyStoreDestroy(C.signal_destroy_kyber_pre_key_store_callback),
|
||||
load_kyber_pre_key: C.SignalLoadKyberPreKey(C.signal_load_kyber_pre_key_callback),
|
||||
store_kyber_pre_key: C.SignalStoreKyberPreKey(C.signal_store_kyber_pre_key_callback),
|
||||
mark_kyber_pre_key_used: C.SignalMarkKyberPreKeyUsed(C.signal_mark_kyber_pre_key_used_callback),
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit bbc16886cae2feab1cd1fe271ccc651e8860ce96
|
||||
Subproject commit c02859f57552477680fb7928c3c3426193040313
|
||||
|
|
@ -29,8 +29,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
#define SignalBackupId_LEN 16
|
||||
|
||||
#define SignalCallLinkSecretParams_ROOT_KEY_MAX_BYTES_FOR_SHO 16
|
||||
|
||||
#define SignalNUM_AUTH_CRED_ATTRIBUTES 3
|
||||
|
||||
#define SignalNUM_PROFILE_KEY_CRED_ATTRIBUTES 4
|
||||
|
|
@ -235,7 +233,6 @@ typedef enum {
|
|||
SignalErrorCodeChatServiceInactive = 149,
|
||||
SignalErrorCodeRequestTimedOut = 150,
|
||||
SignalErrorCodeRateLimitChallenge = 151,
|
||||
SignalErrorCodePossibleCaptiveNetwork = 152,
|
||||
SignalErrorCodeSvrDataMissing = 160,
|
||||
SignalErrorCodeSvrRestoreFailed = 161,
|
||||
SignalErrorCodeSvrRotationMachineTooManySteps = 162,
|
||||
|
|
@ -258,10 +255,6 @@ typedef enum {
|
|||
SignalErrorCodeRegistrationLock = 201,
|
||||
SignalErrorCodeKeyTransparencyError = 210,
|
||||
SignalErrorCodeKeyTransparencyVerificationFailed = 211,
|
||||
SignalErrorCodeRequestUnauthorized = 220,
|
||||
SignalErrorCodeMismatchedDevices = 221,
|
||||
SignalErrorCodeServiceIdNotFound = 222,
|
||||
SignalErrorCodeUploadTooLarge = 223,
|
||||
} SignalErrorCode;
|
||||
|
||||
enum SignalSvr2CredentialsResult {
|
||||
|
|
@ -346,8 +339,6 @@ typedef struct SignalPrivateKey SignalPrivateKey;
|
|||
*/
|
||||
typedef struct SignalProtocolAddress SignalProtocolAddress;
|
||||
|
||||
typedef struct SignalProvisioningChatConnection SignalProvisioningChatConnection;
|
||||
|
||||
typedef struct SignalPublicKey SignalPublicKey;
|
||||
|
||||
typedef struct SignalRegisterAccountRequest SignalRegisterAccountRequest;
|
||||
|
|
@ -512,59 +503,15 @@ typedef struct {
|
|||
const SignalAuthenticatedChatConnection *raw;
|
||||
} SignalConstPointerAuthenticatedChatConnection;
|
||||
|
||||
/**
|
||||
* A type alias to be used with [`OwnedBufferOf`], so that `OwnedBufferOf<c_char>` and
|
||||
* `OwnedBufferOf<*const c_char>` get distinct names.
|
||||
*/
|
||||
typedef const char *SignalCStringPtr;
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
*/
|
||||
typedef struct {
|
||||
SignalCStringPtr *base;
|
||||
/**
|
||||
* The number of elements in the buffer (not necessarily the number of bytes).
|
||||
*/
|
||||
size_t length;
|
||||
} SignalOwnedBufferOfCStringPtr;
|
||||
|
||||
typedef struct {
|
||||
uint32_t cdn;
|
||||
SignalCStringPtr key;
|
||||
SignalOwnedBufferOfCStringPtr header_keys;
|
||||
SignalOwnedBufferOfCStringPtr header_values;
|
||||
SignalCStringPtr signed_upload_url;
|
||||
} SignalFfiUploadForm;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
*
|
||||
* cbindgen will produce independent C types like `SignalCPromisei32` and
|
||||
* `SignalCPromiseProtocolAddress`.
|
||||
*
|
||||
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
|
||||
* completed once.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*complete)(SignalFfiError *error, const SignalFfiUploadForm *result, const void *context);
|
||||
const void *context;
|
||||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseFfiUploadForm;
|
||||
|
||||
typedef SignalConnectionInfo SignalChatConnectionInfo;
|
||||
|
||||
typedef struct {
|
||||
SignalChatConnectionInfo *raw;
|
||||
} SignalMutPointerChatConnectionInfo;
|
||||
|
||||
typedef struct {
|
||||
SignalServerMessageAck *raw;
|
||||
} SignalMutPointerServerMessageAck;
|
||||
typedef void (*SignalReceivedIncomingMessage)(void *ctx, SignalOwnedBuffer envelope, uint64_t timestamp_millis, SignalServerMessageAck *cleanup);
|
||||
|
||||
typedef int (*SignalFfiChatListenerReceivedIncomingMessage)(void *ctx, SignalOwnedBuffer envelope, uint64_t timestamp, SignalMutPointerServerMessageAck ack);
|
||||
|
||||
typedef int (*SignalFfiChatListenerReceivedQueueEmpty)(void *ctx);
|
||||
typedef void (*SignalReceivedQueueEmpty)(void *ctx);
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
|
|
@ -584,25 +531,57 @@ typedef struct {
|
|||
|
||||
typedef SignalBytestringArray SignalStringArray;
|
||||
|
||||
typedef int (*SignalFfiChatListenerReceivedAlerts)(void *ctx, SignalStringArray alerts);
|
||||
typedef void (*SignalReceivedAlerts)(void *ctx, SignalStringArray alerts);
|
||||
|
||||
typedef int (*SignalFfiChatListenerConnectionInterrupted)(void *ctx, SignalFfiError *disconnect_cause);
|
||||
typedef void (*SignalConnectionInterrupted)(void *ctx, SignalFfiError *error);
|
||||
|
||||
typedef void (*SignalFfiChatListenerDestroy)(void *ctx);
|
||||
typedef void (*SignalDestroyChatListener)(void *ctx);
|
||||
|
||||
/**
|
||||
* Callbacks for [`ChatListener`].
|
||||
*
|
||||
* Callbacks will be serialized (i.e. two calls will not come in at the same time), but may not
|
||||
* always happen on the same thread. Calls should be responded to promptly to avoid blocking later
|
||||
* messages.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* This type contains raw pointers. Code that constructs an instance of this type must ensure
|
||||
* memory safety assuming that
|
||||
* - the callback function pointer fields are called with `ctx` as an argument;
|
||||
* - the `destroy` function pointer field is called with `ctx` as an argument;
|
||||
* - no function pointer fields are called after `destroy` is called.
|
||||
*/
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiChatListenerReceivedIncomingMessage received_incoming_message;
|
||||
SignalFfiChatListenerReceivedQueueEmpty received_queue_empty;
|
||||
SignalFfiChatListenerReceivedAlerts received_alerts;
|
||||
SignalFfiChatListenerConnectionInterrupted connection_interrupted;
|
||||
SignalFfiChatListenerDestroy destroy;
|
||||
SignalReceivedIncomingMessage received_incoming_message;
|
||||
SignalReceivedQueueEmpty received_queue_empty;
|
||||
SignalReceivedAlerts received_alerts;
|
||||
SignalConnectionInterrupted connection_interrupted;
|
||||
SignalDestroyChatListener destroy;
|
||||
} SignalFfiChatListenerStruct;
|
||||
|
||||
typedef struct {
|
||||
const SignalFfiChatListenerStruct *raw;
|
||||
} SignalConstPointerFfiChatListenerStruct;
|
||||
|
||||
/**
|
||||
* A type alias to be used with [`OwnedBufferOf`], so that `OwnedBufferOf<c_char>` and
|
||||
* `OwnedBufferOf<*const c_char>` get distinct names.
|
||||
*/
|
||||
typedef const char *SignalCStringPtr;
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
*/
|
||||
typedef struct {
|
||||
SignalCStringPtr *base;
|
||||
/**
|
||||
* The number of elements in the buffer (not necessarily the number of bytes).
|
||||
*/
|
||||
size_t length;
|
||||
} SignalOwnedBufferOfCStringPtr;
|
||||
|
||||
typedef struct {
|
||||
uint16_t status;
|
||||
const char *message;
|
||||
|
|
@ -636,27 +615,6 @@ typedef struct {
|
|||
*/
|
||||
typedef uint8_t SignalServiceIdFixedWidthBinaryBytes[17];
|
||||
|
||||
typedef struct {
|
||||
const uint32_t *base;
|
||||
size_t length;
|
||||
} SignalBorrowedSliceOfu32;
|
||||
|
||||
typedef struct {
|
||||
const SignalCiphertextMessage *raw;
|
||||
} SignalConstPointerCiphertextMessage;
|
||||
|
||||
typedef struct {
|
||||
const SignalConstPointerCiphertextMessage *base;
|
||||
size_t length;
|
||||
} SignalBorrowedSliceOfConstPointerCiphertextMessage;
|
||||
|
||||
/**
|
||||
* A wrapper type for raw UUIDs, because C treats arrays specially in argument position.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t bytes[16];
|
||||
} SignalUuid;
|
||||
|
||||
typedef struct {
|
||||
SignalPrivateKey *raw;
|
||||
} SignalMutPointerPrivateKey;
|
||||
|
|
@ -768,6 +726,10 @@ typedef struct {
|
|||
const SignalPlaintextContent *raw;
|
||||
} SignalConstPointerPlaintextContent;
|
||||
|
||||
typedef struct {
|
||||
const SignalCiphertextMessage *raw;
|
||||
} SignalConstPointerCiphertextMessage;
|
||||
|
||||
typedef struct {
|
||||
SignalConnectionInfo *raw;
|
||||
} SignalMutPointerConnectionInfo;
|
||||
|
|
@ -792,52 +754,49 @@ typedef struct {
|
|||
SignalSessionRecord *raw;
|
||||
} SignalMutPointerSessionRecord;
|
||||
|
||||
typedef int (*SignalFfiSessionStoreLoadSession)(void *ctx, SignalMutPointerSessionRecord *out, SignalMutPointerProtocolAddress address);
|
||||
typedef int (*SignalLoadSession)(void *store_ctx, SignalMutPointerSessionRecord *recordp, SignalConstPointerProtocolAddress address);
|
||||
|
||||
typedef int (*SignalFfiSessionStoreStoreSession)(void *ctx, SignalMutPointerProtocolAddress address, SignalMutPointerSessionRecord record);
|
||||
typedef struct {
|
||||
const SignalSessionRecord *raw;
|
||||
} SignalConstPointerSessionRecord;
|
||||
|
||||
typedef void (*SignalFfiSessionStoreDestroy)(void *ctx);
|
||||
typedef int (*SignalStoreSession)(void *store_ctx, SignalConstPointerProtocolAddress address, SignalConstPointerSessionRecord record);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiSessionStoreLoadSession load_session;
|
||||
SignalFfiSessionStoreStoreSession store_session;
|
||||
SignalFfiSessionStoreDestroy destroy;
|
||||
SignalLoadSession load_session;
|
||||
SignalStoreSession store_session;
|
||||
} SignalSessionStore;
|
||||
|
||||
typedef struct {
|
||||
const SignalSessionStore *raw;
|
||||
} SignalConstPointerFfiSessionStoreStruct;
|
||||
|
||||
typedef int (*SignalGetIdentityKeyPair)(void *store_ctx, SignalMutPointerPrivateKey *keyp);
|
||||
|
||||
typedef int (*SignalGetLocalRegistrationId)(void *store_ctx, uint32_t *idp);
|
||||
|
||||
typedef struct {
|
||||
const SignalPublicKey *raw;
|
||||
} SignalConstPointerPublicKey;
|
||||
|
||||
typedef int (*SignalSaveIdentityKey)(void *store_ctx, SignalConstPointerProtocolAddress address, SignalConstPointerPublicKey public_key);
|
||||
|
||||
typedef struct {
|
||||
SignalPublicKey *raw;
|
||||
} SignalMutPointerPublicKey;
|
||||
|
||||
typedef struct {
|
||||
SignalMutPointerPrivateKey first;
|
||||
SignalMutPointerPublicKey second;
|
||||
} SignalPairOfMutPointerPrivateKeyMutPointerPublicKey;
|
||||
typedef int (*SignalGetIdentityKey)(void *store_ctx, SignalMutPointerPublicKey *public_keyp, SignalConstPointerProtocolAddress address);
|
||||
|
||||
typedef int (*SignalFfiIdentityKeyStoreGetLocalIdentityKeyPair)(void *ctx, SignalPairOfMutPointerPrivateKeyMutPointerPublicKey *out);
|
||||
|
||||
typedef int (*SignalFfiIdentityKeyStoreGetLocalRegistrationId)(void *ctx, uint32_t *out);
|
||||
|
||||
typedef int (*SignalFfiIdentityKeyStoreGetIdentityKey)(void *ctx, SignalMutPointerPublicKey *out, SignalMutPointerProtocolAddress address);
|
||||
|
||||
typedef int (*SignalFfiIdentityKeyStoreSaveIdentityKey)(void *ctx, uint8_t *out, SignalMutPointerProtocolAddress address, SignalMutPointerPublicKey public_key);
|
||||
|
||||
typedef int (*SignalFfiIdentityKeyStoreIsTrustedIdentity)(void *ctx, bool *out, SignalMutPointerProtocolAddress address, SignalMutPointerPublicKey public_key, uint32_t direction);
|
||||
|
||||
typedef void (*SignalFfiIdentityKeyStoreDestroy)(void *ctx);
|
||||
typedef int (*SignalIsTrustedIdentity)(void *store_ctx, SignalConstPointerProtocolAddress address, SignalConstPointerPublicKey public_key, unsigned int direction);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiIdentityKeyStoreGetLocalIdentityKeyPair get_local_identity_key_pair;
|
||||
SignalFfiIdentityKeyStoreGetLocalRegistrationId get_local_registration_id;
|
||||
SignalFfiIdentityKeyStoreGetIdentityKey get_identity_key;
|
||||
SignalFfiIdentityKeyStoreSaveIdentityKey save_identity_key;
|
||||
SignalFfiIdentityKeyStoreIsTrustedIdentity is_trusted_identity;
|
||||
SignalFfiIdentityKeyStoreDestroy destroy;
|
||||
SignalGetIdentityKeyPair get_identity_key_pair;
|
||||
SignalGetLocalRegistrationId get_local_registration_id;
|
||||
SignalSaveIdentityKey save_identity;
|
||||
SignalGetIdentityKey get_identity;
|
||||
SignalIsTrustedIdentity is_trusted_identity;
|
||||
} SignalIdentityKeyStore;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -852,20 +811,21 @@ typedef struct {
|
|||
SignalPreKeyRecord *raw;
|
||||
} SignalMutPointerPreKeyRecord;
|
||||
|
||||
typedef int (*SignalFfiPreKeyStoreLoadPreKey)(void *ctx, SignalMutPointerPreKeyRecord *out, uint32_t id);
|
||||
typedef int (*SignalLoadPreKey)(void *store_ctx, SignalMutPointerPreKeyRecord *recordp, uint32_t id);
|
||||
|
||||
typedef int (*SignalFfiPreKeyStoreStorePreKey)(void *ctx, uint32_t id, SignalMutPointerPreKeyRecord record);
|
||||
typedef struct {
|
||||
const SignalPreKeyRecord *raw;
|
||||
} SignalConstPointerPreKeyRecord;
|
||||
|
||||
typedef int (*SignalFfiPreKeyStoreRemovePreKey)(void *ctx, uint32_t id);
|
||||
typedef int (*SignalStorePreKey)(void *store_ctx, uint32_t id, SignalConstPointerPreKeyRecord record);
|
||||
|
||||
typedef void (*SignalFfiPreKeyStoreDestroy)(void *ctx);
|
||||
typedef int (*SignalRemovePreKey)(void *store_ctx, uint32_t id);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiPreKeyStoreLoadPreKey load_pre_key;
|
||||
SignalFfiPreKeyStoreStorePreKey store_pre_key;
|
||||
SignalFfiPreKeyStoreRemovePreKey remove_pre_key;
|
||||
SignalFfiPreKeyStoreDestroy destroy;
|
||||
SignalLoadPreKey load_pre_key;
|
||||
SignalStorePreKey store_pre_key;
|
||||
SignalRemovePreKey remove_pre_key;
|
||||
} SignalPreKeyStore;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -876,17 +836,18 @@ typedef struct {
|
|||
SignalSignedPreKeyRecord *raw;
|
||||
} SignalMutPointerSignedPreKeyRecord;
|
||||
|
||||
typedef int (*SignalFfiSignedPreKeyStoreLoadSignedPreKey)(void *ctx, SignalMutPointerSignedPreKeyRecord *out, uint32_t id);
|
||||
typedef int (*SignalLoadSignedPreKey)(void *store_ctx, SignalMutPointerSignedPreKeyRecord *recordp, uint32_t id);
|
||||
|
||||
typedef int (*SignalFfiSignedPreKeyStoreStoreSignedPreKey)(void *ctx, uint32_t id, SignalMutPointerSignedPreKeyRecord record);
|
||||
typedef struct {
|
||||
const SignalSignedPreKeyRecord *raw;
|
||||
} SignalConstPointerSignedPreKeyRecord;
|
||||
|
||||
typedef void (*SignalFfiSignedPreKeyStoreDestroy)(void *ctx);
|
||||
typedef int (*SignalStoreSignedPreKey)(void *store_ctx, uint32_t id, SignalConstPointerSignedPreKeyRecord record);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiSignedPreKeyStoreLoadSignedPreKey load_signed_pre_key;
|
||||
SignalFfiSignedPreKeyStoreStoreSignedPreKey store_signed_pre_key;
|
||||
SignalFfiSignedPreKeyStoreDestroy destroy;
|
||||
SignalLoadSignedPreKey load_signed_pre_key;
|
||||
SignalStoreSignedPreKey store_signed_pre_key;
|
||||
} SignalSignedPreKeyStore;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -897,20 +858,21 @@ typedef struct {
|
|||
SignalKyberPreKeyRecord *raw;
|
||||
} SignalMutPointerKyberPreKeyRecord;
|
||||
|
||||
typedef int (*SignalFfiKyberPreKeyStoreLoadKyberPreKey)(void *ctx, SignalMutPointerKyberPreKeyRecord *out, uint32_t id);
|
||||
typedef int (*SignalLoadKyberPreKey)(void *store_ctx, SignalMutPointerKyberPreKeyRecord *recordp, uint32_t id);
|
||||
|
||||
typedef int (*SignalFfiKyberPreKeyStoreStoreKyberPreKey)(void *ctx, uint32_t id, SignalMutPointerKyberPreKeyRecord record);
|
||||
typedef struct {
|
||||
const SignalKyberPreKeyRecord *raw;
|
||||
} SignalConstPointerKyberPreKeyRecord;
|
||||
|
||||
typedef int (*SignalFfiKyberPreKeyStoreMarkKyberPreKeyUsed)(void *ctx, uint32_t id, uint32_t ec_prekey_id, SignalMutPointerPublicKey base_key);
|
||||
typedef int (*SignalStoreKyberPreKey)(void *store_ctx, uint32_t id, SignalConstPointerKyberPreKeyRecord record);
|
||||
|
||||
typedef void (*SignalFfiKyberPreKeyStoreDestroy)(void *ctx);
|
||||
typedef int (*SignalMarkKyberPreKeyUsed)(void *store_ctx, uint32_t id, uint32_t signed_prekey_id, SignalConstPointerPublicKey base_key);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiKyberPreKeyStoreLoadKyberPreKey load_kyber_pre_key;
|
||||
SignalFfiKyberPreKeyStoreStoreKyberPreKey store_kyber_pre_key;
|
||||
SignalFfiKyberPreKeyStoreMarkKyberPreKeyUsed mark_kyber_pre_key_used;
|
||||
SignalFfiKyberPreKeyStoreDestroy destroy;
|
||||
SignalLoadKyberPreKey load_kyber_pre_key;
|
||||
SignalStoreKyberPreKey store_kyber_pre_key;
|
||||
SignalMarkKyberPreKeyUsed mark_kyber_pre_key_used;
|
||||
} SignalKyberPreKeyStore;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -935,45 +897,11 @@ typedef struct {
|
|||
uint32_t second;
|
||||
} SignalPairOfc_charu32;
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t *base;
|
||||
/**
|
||||
* The number of elements in the buffer (not necessarily the number of bytes).
|
||||
*/
|
||||
size_t length;
|
||||
} SignalOwnedBufferOfu32;
|
||||
|
||||
typedef struct {
|
||||
SignalServiceIdFixedWidthBinaryBytes account;
|
||||
SignalOwnedBufferOfu32 missing_devices;
|
||||
SignalOwnedBufferOfu32 extra_devices;
|
||||
SignalOwnedBufferOfu32 stale_devices;
|
||||
} SignalFfiMismatchedDevicesError;
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
*/
|
||||
typedef struct {
|
||||
SignalFfiMismatchedDevicesError *base;
|
||||
/**
|
||||
* The number of elements in the buffer (not necessarily the number of bytes).
|
||||
*/
|
||||
size_t length;
|
||||
} SignalOwnedBufferOfFfiMismatchedDevicesError;
|
||||
|
||||
typedef struct {
|
||||
const char *first;
|
||||
SignalOwnedBuffer second;
|
||||
} SignalPairOfc_charOwnedBufferOfc_uchar;
|
||||
|
||||
typedef struct {
|
||||
SignalPairOfc_charOwnedBufferOfc_uchar first;
|
||||
int64_t second;
|
||||
} SignalPairOfPairOfc_charOwnedBufferOfc_uchari64;
|
||||
|
||||
typedef struct {
|
||||
const char *first;
|
||||
bool second;
|
||||
|
|
@ -987,10 +915,6 @@ typedef struct {
|
|||
const SignalFingerprint *raw;
|
||||
} SignalConstPointerFingerprint;
|
||||
|
||||
typedef struct {
|
||||
const SignalPublicKey *raw;
|
||||
} SignalConstPointerPublicKey;
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* The badge ID.
|
||||
|
|
@ -1017,47 +941,22 @@ typedef struct {
|
|||
size_t length;
|
||||
} SignalOwnedBufferOfFfiRegisterResponseBadge;
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
*/
|
||||
typedef struct {
|
||||
SignalServiceIdFixedWidthBinaryBytes *base;
|
||||
/**
|
||||
* The number of elements in the buffer (not necessarily the number of bytes).
|
||||
*/
|
||||
size_t length;
|
||||
} SignalOwnedBufferOfServiceIdFixedWidthBinaryBytes;
|
||||
|
||||
typedef struct {
|
||||
SignalPreKeyBundle *raw;
|
||||
} SignalMutPointerPreKeyBundle;
|
||||
|
||||
/**
|
||||
* A representation of a array allocated on the Rust heap for use in C code.
|
||||
*/
|
||||
typedef struct {
|
||||
SignalMutPointerPreKeyBundle *base;
|
||||
/**
|
||||
* The number of elements in the buffer (not necessarily the number of bytes).
|
||||
*/
|
||||
size_t length;
|
||||
} SignalOwnedBufferOfMutPointerPreKeyBundle;
|
||||
|
||||
typedef struct {
|
||||
SignalSenderKeyRecord *raw;
|
||||
} SignalMutPointerSenderKeyRecord;
|
||||
|
||||
typedef int (*SignalFfiSenderKeyStoreLoadSenderKey)(void *ctx, SignalMutPointerSenderKeyRecord *out, SignalMutPointerProtocolAddress sender, SignalUuid distribution_id);
|
||||
typedef int (*SignalLoadSenderKey)(void *store_ctx, SignalMutPointerSenderKeyRecord*, SignalConstPointerProtocolAddress, const uint8_t (*distribution_id)[16]);
|
||||
|
||||
typedef int (*SignalFfiSenderKeyStoreStoreSenderKey)(void *ctx, SignalMutPointerProtocolAddress sender, SignalUuid distribution_id, SignalMutPointerSenderKeyRecord record);
|
||||
typedef struct {
|
||||
const SignalSenderKeyRecord *raw;
|
||||
} SignalConstPointerSenderKeyRecord;
|
||||
|
||||
typedef void (*SignalFfiSenderKeyStoreDestroy)(void *ctx);
|
||||
typedef int (*SignalStoreSenderKey)(void *store_ctx, SignalConstPointerProtocolAddress, const uint8_t (*distribution_id)[16], SignalConstPointerSenderKeyRecord);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiSenderKeyStoreLoadSenderKey load_sender_key;
|
||||
SignalFfiSenderKeyStoreStoreSenderKey store_sender_key;
|
||||
SignalFfiSenderKeyStoreDestroy destroy;
|
||||
SignalLoadSenderKey load_sender_key;
|
||||
SignalStoreSenderKey store_sender_key;
|
||||
} SignalSenderKeyStore;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -1102,23 +1001,15 @@ typedef struct {
|
|||
SignalIncrementalMac *raw;
|
||||
} SignalMutPointerIncrementalMac;
|
||||
|
||||
typedef int (*SignalFfiLoggerLog)(void *ctx, SignalLogLevel level, const char *file, uint32_t line, const char *message);
|
||||
typedef void (*SignalLogCallback)(void *ctx, SignalLogLevel level, const char *file, uint32_t line, const char *message);
|
||||
|
||||
typedef int (*SignalFfiLoggerFlush)(void *ctx);
|
||||
|
||||
typedef void (*SignalFfiLoggerDestroy)(void *ctx);
|
||||
typedef void (*SignalLogFlushCallback)(void *ctx);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiLoggerLog log;
|
||||
SignalFfiLoggerFlush flush;
|
||||
SignalFfiLoggerDestroy destroy;
|
||||
} SignalFfiLoggerStruct;
|
||||
|
||||
typedef struct {
|
||||
SignalOwnedBuffer first;
|
||||
SignalOwnedBuffer second;
|
||||
} SignalPairOfOwnedBufferOfc_ucharOwnedBufferOfc_uchar;
|
||||
SignalLogCallback log;
|
||||
SignalLogFlushCallback flush;
|
||||
} SignalFfiLogger;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
|
|
@ -1130,10 +1021,10 @@ typedef struct {
|
|||
* completed once.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*complete)(SignalFfiError *error, const SignalPairOfOwnedBufferOfc_ucharOwnedBufferOfc_uchar *result, const void *context);
|
||||
void (*complete)(SignalFfiError *error, const SignalOwnedBuffer *result, const void *context);
|
||||
const void *context;
|
||||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromisePairOfOwnedBufferOfc_ucharOwnedBufferOfc_uchar;
|
||||
} SignalCPromiseOwnedBufferOfc_uchar;
|
||||
|
||||
typedef struct {
|
||||
const SignalUnauthenticatedChatConnection *raw;
|
||||
|
|
@ -1171,10 +1062,6 @@ typedef struct {
|
|||
SignalKyberSecretKey *raw;
|
||||
} SignalMutPointerKyberSecretKey;
|
||||
|
||||
typedef struct {
|
||||
const SignalKyberPreKeyRecord *raw;
|
||||
} SignalConstPointerKyberPreKeyRecord;
|
||||
|
||||
typedef struct {
|
||||
const SignalKyberPublicKey *raw;
|
||||
} SignalConstPointerKyberPublicKey;
|
||||
|
|
@ -1203,17 +1090,14 @@ typedef struct {
|
|||
const SignalMessageBackupValidationOutcome *raw;
|
||||
} SignalConstPointerMessageBackupValidationOutcome;
|
||||
|
||||
typedef int (*SignalFfiInputStreamRead)(void *ctx, size_t *out, SignalBorrowedMutableBuffer buf);
|
||||
typedef int (*SignalRead)(void *ctx, uint8_t *buf, size_t buf_len, size_t *amount_read);
|
||||
|
||||
typedef int (*SignalFfiInputStreamSkip)(void *ctx, uint64_t amount);
|
||||
|
||||
typedef void (*SignalFfiInputStreamDestroy)(void *ctx);
|
||||
typedef int (*SignalSkip)(void *ctx, uint64_t amount);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiInputStreamRead read;
|
||||
SignalFfiInputStreamSkip skip;
|
||||
SignalFfiInputStreamDestroy destroy;
|
||||
SignalRead read;
|
||||
SignalSkip skip;
|
||||
} SignalInputStream;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -1245,12 +1129,12 @@ typedef struct {
|
|||
} SignalMutPointerPlaintextContent;
|
||||
|
||||
typedef struct {
|
||||
const SignalPreKeyBundle *raw;
|
||||
} SignalConstPointerPreKeyBundle;
|
||||
SignalPreKeyBundle *raw;
|
||||
} SignalMutPointerPreKeyBundle;
|
||||
|
||||
typedef struct {
|
||||
const SignalPreKeyRecord *raw;
|
||||
} SignalConstPointerPreKeyRecord;
|
||||
const SignalPreKeyBundle *raw;
|
||||
} SignalConstPointerPreKeyBundle;
|
||||
|
||||
typedef struct {
|
||||
SignalPreKeySignalMessage *raw;
|
||||
|
|
@ -1260,49 +1144,6 @@ typedef struct {
|
|||
const SignalSenderKeyDistributionMessage *raw;
|
||||
} SignalConstPointerSenderKeyDistributionMessage;
|
||||
|
||||
typedef struct {
|
||||
SignalProvisioningChatConnection *raw;
|
||||
} SignalMutPointerProvisioningChatConnection;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
*
|
||||
* cbindgen will produce independent C types like `SignalCPromisei32` and
|
||||
* `SignalCPromiseProtocolAddress`.
|
||||
*
|
||||
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
|
||||
* completed once.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*complete)(SignalFfiError *error, const SignalMutPointerProvisioningChatConnection *result, const void *context);
|
||||
const void *context;
|
||||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseMutPointerProvisioningChatConnection;
|
||||
|
||||
typedef struct {
|
||||
const SignalProvisioningChatConnection *raw;
|
||||
} SignalConstPointerProvisioningChatConnection;
|
||||
|
||||
typedef int (*SignalFfiProvisioningListenerReceivedAddress)(void *ctx, const char *address, SignalMutPointerServerMessageAck send_ack);
|
||||
|
||||
typedef int (*SignalFfiProvisioningListenerReceivedEnvelope)(void *ctx, SignalOwnedBuffer envelope, SignalMutPointerServerMessageAck send_ack);
|
||||
|
||||
typedef int (*SignalFfiProvisioningListenerConnectionInterrupted)(void *ctx, SignalFfiError *disconnect_cause);
|
||||
|
||||
typedef void (*SignalFfiProvisioningListenerDestroy)(void *ctx);
|
||||
|
||||
typedef struct {
|
||||
void *ctx;
|
||||
SignalFfiProvisioningListenerReceivedAddress received_address;
|
||||
SignalFfiProvisioningListenerReceivedEnvelope received_envelope;
|
||||
SignalFfiProvisioningListenerConnectionInterrupted connection_interrupted;
|
||||
SignalFfiProvisioningListenerDestroy destroy;
|
||||
} SignalFfiProvisioningListenerStruct;
|
||||
|
||||
typedef struct {
|
||||
const SignalFfiProvisioningListenerStruct *raw;
|
||||
} SignalConstPointerFfiProvisioningListenerStruct;
|
||||
|
||||
typedef struct {
|
||||
SignalRegisterAccountRequest *raw;
|
||||
} SignalMutPointerRegisterAccountRequest;
|
||||
|
|
@ -1326,10 +1167,7 @@ typedef struct {
|
|||
const SignalRegisterAccountResponse *raw;
|
||||
} SignalConstPointerRegisterAccountResponse;
|
||||
|
||||
typedef struct {
|
||||
bool present;
|
||||
uint8_t bytes[16];
|
||||
} SignalOptionalUuid;
|
||||
typedef uint8_t SignalOptionalUuid[17];
|
||||
|
||||
typedef SignalAccountAttributes SignalRegistrationAccountAttributes;
|
||||
|
||||
|
|
@ -1444,10 +1282,6 @@ typedef struct {
|
|||
size_t length;
|
||||
} SignalBorrowedSliceOfConstPointerProtocolAddress;
|
||||
|
||||
typedef struct {
|
||||
const SignalSessionRecord *raw;
|
||||
} SignalConstPointerSessionRecord;
|
||||
|
||||
typedef struct {
|
||||
const SignalConstPointerSessionRecord *base;
|
||||
size_t length;
|
||||
|
|
@ -1527,8 +1361,8 @@ typedef struct {
|
|||
} SignalConstPointerSenderKeyMessage;
|
||||
|
||||
typedef struct {
|
||||
const SignalSenderKeyRecord *raw;
|
||||
} SignalConstPointerSenderKeyRecord;
|
||||
SignalServerMessageAck *raw;
|
||||
} SignalMutPointerServerMessageAck;
|
||||
|
||||
typedef struct {
|
||||
const SignalServerMessageAck *raw;
|
||||
|
|
@ -1546,10 +1380,6 @@ typedef struct {
|
|||
const SignalSgxClientState *raw;
|
||||
} SignalConstPointerSgxClientState;
|
||||
|
||||
typedef struct {
|
||||
const SignalSignedPreKeyRecord *raw;
|
||||
} SignalConstPointerSignedPreKeyRecord;
|
||||
|
||||
typedef struct {
|
||||
SignalTokioAsyncContext *raw;
|
||||
} SignalMutPointerTokioAsyncContext;
|
||||
|
|
@ -1573,26 +1403,6 @@ typedef struct {
|
|||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseMutPointerUnauthenticatedChatConnection;
|
||||
|
||||
typedef struct {
|
||||
SignalMutPointerPublicKey identity_key;
|
||||
SignalOwnedBufferOfMutPointerPreKeyBundle pre_key_bundles;
|
||||
} SignalFfiPreKeysResponse;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
*
|
||||
* cbindgen will produce independent C types like `SignalCPromisei32` and
|
||||
* `SignalCPromiseProtocolAddress`.
|
||||
*
|
||||
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
|
||||
* completed once.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*complete)(SignalFfiError *error, const SignalFfiPreKeysResponse *result, const void *context);
|
||||
const void *context;
|
||||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseFfiPreKeysResponse;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
*
|
||||
|
|
@ -1608,42 +1418,6 @@ typedef struct {
|
|||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseOptionalUuid;
|
||||
|
||||
typedef struct {
|
||||
bool present;
|
||||
const char *first;
|
||||
uint8_t second[32];
|
||||
} SignalOptionalPairOfc_charu832;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
*
|
||||
* cbindgen will produce independent C types like `SignalCPromisei32` and
|
||||
* `SignalCPromiseProtocolAddress`.
|
||||
*
|
||||
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
|
||||
* completed once.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*complete)(SignalFfiError *error, const SignalOptionalPairOfc_charu832 *result, const void *context);
|
||||
const void *context;
|
||||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseOptionalPairOfc_charu832;
|
||||
|
||||
/**
|
||||
* A C callback used to report the results of Rust futures.
|
||||
*
|
||||
* cbindgen will produce independent C types like `SignalCPromisei32` and
|
||||
* `SignalCPromiseProtocolAddress`.
|
||||
*
|
||||
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
|
||||
* completed once.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*complete)(SignalFfiError *error, const SignalOwnedBufferOfServiceIdFixedWidthBinaryBytes *result, const void *context);
|
||||
const void *context;
|
||||
SignalCancellationId cancellation_id;
|
||||
} SignalCPromiseOwnedBufferOfServiceIdFixedWidthBinaryBytes;
|
||||
|
||||
typedef struct {
|
||||
SignalValidatingMac *raw;
|
||||
} SignalMutPointerValidatingMac;
|
||||
|
|
@ -1658,8 +1432,6 @@ typedef uint8_t SignalRandomnessBytes[SignalRANDOMNESS_LEN];
|
|||
|
||||
typedef uint8_t SignalUnidentifiedAccessKey[SignalACCESS_KEY_LEN];
|
||||
|
||||
|
||||
|
||||
SignalFfiError *signal_account_entropy_pool_derive_backup_key(uint8_t (*out)[SignalBACKUP_KEY_LEN], const char *account_entropy);
|
||||
|
||||
SignalFfiError *signal_account_entropy_pool_derive_svr_key(uint8_t (*out)[SignalSVR_KEY_LEN], const char *account_entropy);
|
||||
|
|
@ -1726,8 +1498,6 @@ SignalFfiError *signal_authenticated_chat_connection_destroy(SignalMutPointerAut
|
|||
|
||||
SignalFfiError *signal_authenticated_chat_connection_disconnect(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat);
|
||||
|
||||
SignalFfiError *signal_authenticated_chat_connection_get_upload_form(SignalCPromiseFfiUploadForm *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, uint64_t upload_length);
|
||||
|
||||
SignalFfiError *signal_authenticated_chat_connection_info(SignalMutPointerChatConnectionInfo *out, SignalConstPointerAuthenticatedChatConnection chat);
|
||||
|
||||
SignalFfiError *signal_authenticated_chat_connection_init_listener(SignalConstPointerAuthenticatedChatConnection chat, SignalConstPointerFfiChatListenerStruct listener);
|
||||
|
|
@ -1736,10 +1506,6 @@ SignalFfiError *signal_authenticated_chat_connection_preconnect(SignalCPromisebo
|
|||
|
||||
SignalFfiError *signal_authenticated_chat_connection_send(SignalCPromiseFfiChatResponse *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, SignalConstPointerHttpRequest http_request, uint32_t timeout_millis);
|
||||
|
||||
SignalFfiError *signal_authenticated_chat_connection_send_message(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, const SignalServiceIdFixedWidthBinaryBytes *destination, uint64_t timestamp, SignalBorrowedSliceOfu32 device_ids, SignalBorrowedSliceOfu32 registration_ids, SignalBorrowedSliceOfConstPointerCiphertextMessage contents, bool online_only, bool is_urgent);
|
||||
|
||||
SignalFfiError *signal_authenticated_chat_connection_send_sync_message(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, uint64_t timestamp, SignalBorrowedSliceOfu32 device_ids, SignalBorrowedSliceOfu32 registration_ids, SignalBorrowedSliceOfConstPointerCiphertextMessage contents, bool is_urgent);
|
||||
|
||||
SignalFfiError *signal_backup_auth_credential_check_valid_contents(SignalBorrowedBuffer params_bytes);
|
||||
|
||||
SignalFfiError *signal_backup_auth_credential_get_backup_id(uint8_t (*out)[16], SignalBorrowedBuffer credential_bytes);
|
||||
|
|
@ -1760,7 +1526,7 @@ SignalFfiError *signal_backup_auth_credential_request_context_check_valid_conten
|
|||
|
||||
SignalFfiError *signal_backup_auth_credential_request_context_get_request(SignalOwnedBuffer *out, SignalBorrowedBuffer context_bytes);
|
||||
|
||||
SignalFfiError *signal_backup_auth_credential_request_context_new(SignalOwnedBuffer *out, const uint8_t (*backup_key)[32], SignalUuid uuid);
|
||||
SignalFfiError *signal_backup_auth_credential_request_context_new(SignalOwnedBuffer *out, const uint8_t (*backup_key)[32], const uint8_t (*uuid)[16]);
|
||||
|
||||
SignalFfiError *signal_backup_auth_credential_request_context_receive_response(SignalOwnedBuffer *out, SignalBorrowedBuffer context_bytes, SignalBorrowedBuffer response_bytes, uint64_t expected_redemption_time, SignalBorrowedBuffer params_bytes);
|
||||
|
||||
|
|
@ -1900,9 +1666,9 @@ SignalFfiError *signal_create_call_link_credential_request_issue_deterministic(S
|
|||
|
||||
SignalFfiError *signal_create_call_link_credential_response_check_valid_contents(SignalBorrowedBuffer response_bytes);
|
||||
|
||||
SignalFfiError *signal_decrypt_message(SignalOwnedBuffer *out, SignalConstPointerSignalMessage message, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerProtocolAddress local_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store);
|
||||
SignalFfiError *signal_decrypt_message(SignalOwnedBuffer *out, SignalConstPointerSignalMessage message, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store);
|
||||
|
||||
SignalFfiError *signal_decrypt_pre_key_message(SignalOwnedBuffer *out, SignalConstPointerPreKeySignalMessage message, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerProtocolAddress local_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store, SignalConstPointerFfiPreKeyStoreStruct prekey_store, SignalConstPointerFfiSignedPreKeyStoreStruct signed_prekey_store, SignalConstPointerFfiKyberPreKeyStoreStruct kyber_prekey_store);
|
||||
SignalFfiError *signal_decrypt_pre_key_message(SignalOwnedBuffer *out, SignalConstPointerPreKeySignalMessage message, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store, SignalConstPointerFfiPreKeyStoreStruct prekey_store, SignalConstPointerFfiSignedPreKeyStoreStruct signed_prekey_store, SignalConstPointerFfiKyberPreKeyStoreStruct kyber_prekey_store);
|
||||
|
||||
SignalFfiError *signal_decryption_error_message_clone(SignalMutPointerDecryptionErrorMessage *new_obj, SignalConstPointerDecryptionErrorMessage obj);
|
||||
|
||||
|
|
@ -1928,7 +1694,7 @@ SignalFfiError *signal_device_transfer_generate_private_key(SignalOwnedBuffer *o
|
|||
|
||||
SignalFfiError *signal_device_transfer_generate_private_key_with_format(SignalOwnedBuffer *out, uint8_t key_format);
|
||||
|
||||
SignalFfiError *signal_encrypt_message(SignalMutPointerCiphertextMessage *out, SignalBorrowedBuffer ptext, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerProtocolAddress local_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store, uint64_t now);
|
||||
SignalFfiError *signal_encrypt_message(SignalMutPointerCiphertextMessage *out, SignalBorrowedBuffer ptext, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store, uint64_t now);
|
||||
|
||||
void signal_error_free(SignalFfiError *err);
|
||||
|
||||
|
|
@ -1938,11 +1704,9 @@ SignalFfiError *signal_error_get_invalid_protocol_address(SignalPairOfc_charu32
|
|||
|
||||
SignalFfiError *signal_error_get_message(const char **out, SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
SignalFfiError *signal_error_get_mismatched_device_errors(SignalOwnedBufferOfFfiMismatchedDevicesError *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
SignalFfiError *signal_error_get_our_fingerprint_version(uint32_t *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
SignalFfiError *signal_error_get_rate_limit_challenge(SignalPairOfPairOfc_charOwnedBufferOfc_uchari64 *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
SignalFfiError *signal_error_get_rate_limit_challenge(SignalPairOfc_charOwnedBufferOfc_uchar *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
SignalFfiError *signal_error_get_registration_error_not_deliverable(SignalPairOfc_charbool *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
|
|
@ -1958,7 +1722,7 @@ uint32_t signal_error_get_type(const SignalFfiError *err);
|
|||
|
||||
SignalFfiError *signal_error_get_unknown_fields(SignalStringArray *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
SignalFfiError *signal_error_get_uuid(SignalUuid *out, SignalUnwindSafeArgSignalFfiError err);
|
||||
SignalFfiError *signal_error_get_uuid(uint8_t (*out)[16], SignalUnwindSafeArgSignalFfiError err);
|
||||
|
||||
SignalFfiError *signal_expiring_profile_key_credential_check_valid_contents(SignalBorrowedBuffer buffer);
|
||||
|
||||
|
|
@ -1982,23 +1746,12 @@ void signal_free_buffer(const unsigned char *buf, size_t buf_len);
|
|||
|
||||
void signal_free_bytestring_array(SignalBytestringArray array);
|
||||
|
||||
void signal_free_list_of_mismatched_device_errors(SignalOwnedBufferOfFfiMismatchedDevicesError buffer);
|
||||
|
||||
void signal_free_list_of_register_response_badges(SignalOwnedBufferOfFfiRegisterResponseBadge buffer);
|
||||
|
||||
void signal_free_list_of_service_ids(SignalOwnedBufferOfServiceIdFixedWidthBinaryBytes buffer);
|
||||
|
||||
void signal_free_list_of_strings(SignalOwnedBufferOfCStringPtr buffer);
|
||||
|
||||
void signal_free_lookup_response_entry_list(SignalOwnedBufferOfFfiCdsiLookupResponseEntry buffer);
|
||||
|
||||
/**
|
||||
* This frees a buffer of PreKeyBundle pointers, and _does not_ free the
|
||||
* pointers within the buffer. This _only_ frees the buffer containing
|
||||
* the pointers.
|
||||
*/
|
||||
void signal_free_outer_buffer_list_of_prekey_bundles(SignalOwnedBufferOfMutPointerPreKeyBundle buffer);
|
||||
|
||||
void signal_free_string(const char *buf);
|
||||
|
||||
SignalFfiError *signal_generic_server_public_params_check_valid_contents(SignalBorrowedBuffer params_bytes);
|
||||
|
|
@ -2011,7 +1764,7 @@ SignalFfiError *signal_generic_server_secret_params_get_public_params(SignalOwne
|
|||
|
||||
SignalFfiError *signal_group_decrypt_message(SignalOwnedBuffer *out, SignalConstPointerProtocolAddress sender, SignalBorrowedBuffer message, SignalConstPointerFfiSenderKeyStoreStruct store);
|
||||
|
||||
SignalFfiError *signal_group_encrypt_message(SignalMutPointerCiphertextMessage *out, SignalConstPointerProtocolAddress sender, SignalUuid distribution_id, SignalBorrowedBuffer message, SignalConstPointerFfiSenderKeyStoreStruct store);
|
||||
SignalFfiError *signal_group_encrypt_message(SignalMutPointerCiphertextMessage *out, SignalConstPointerProtocolAddress sender, const uint8_t (*distribution_id)[16], SignalBorrowedBuffer message, SignalConstPointerFfiSenderKeyStoreStruct store);
|
||||
|
||||
SignalFfiError *signal_group_master_key_check_valid_contents(SignalBorrowedBuffer buffer);
|
||||
|
||||
|
|
@ -2117,14 +1870,18 @@ SignalFfiError *signal_incremental_mac_initialize(SignalMutPointerIncrementalMac
|
|||
|
||||
SignalFfiError *signal_incremental_mac_update(SignalOwnedBuffer *out, SignalMutPointerIncrementalMac mac, SignalBorrowedBuffer bytes, uint32_t offset, uint32_t length);
|
||||
|
||||
bool signal_init_logger(SignalLogLevel max_level, SignalFfiLoggerStruct logger);
|
||||
bool signal_init_logger(SignalLogLevel max_level, SignalFfiLogger logger);
|
||||
|
||||
SignalFfiError *signal_key_transparency_aci_search_key(SignalOwnedBuffer *out, const SignalServiceIdFixedWidthBinaryBytes *aci);
|
||||
|
||||
SignalFfiError *signal_key_transparency_check(SignalCPromisePairOfOwnedBufferOfc_ucharOwnedBufferOfc_uchar *promise, SignalConstPointerTokioAsyncContext async_runtime, uint8_t environment, SignalConstPointerUnauthenticatedChatConnection chat_connection, const SignalServiceIdFixedWidthBinaryBytes *aci, SignalConstPointerPublicKey aci_identity_key, const char *e164, SignalOptionalBorrowedSliceOfc_uchar unidentified_access_key, SignalOptionalBorrowedSliceOfc_uchar username_hash, SignalOptionalBorrowedSliceOfc_uchar account_data, SignalOptionalBorrowedSliceOfc_uchar last_distinguished_tree_head, bool is_self_check, bool is_e164_discoverable);
|
||||
SignalFfiError *signal_key_transparency_distinguished(SignalCPromiseOwnedBufferOfc_uchar *promise, SignalConstPointerTokioAsyncContext async_runtime, uint8_t environment, SignalConstPointerUnauthenticatedChatConnection chat_connection, SignalOptionalBorrowedSliceOfc_uchar last_distinguished_tree_head);
|
||||
|
||||
SignalFfiError *signal_key_transparency_e164_search_key(SignalOwnedBuffer *out, const char *e164);
|
||||
|
||||
SignalFfiError *signal_key_transparency_monitor(SignalCPromiseOwnedBufferOfc_uchar *promise, SignalConstPointerTokioAsyncContext async_runtime, uint8_t environment, SignalConstPointerUnauthenticatedChatConnection chat_connection, const SignalServiceIdFixedWidthBinaryBytes *aci, SignalConstPointerPublicKey aci_identity_key, const char *e164, SignalOptionalBorrowedSliceOfc_uchar unidentified_access_key, SignalOptionalBorrowedSliceOfc_uchar username_hash, SignalOptionalBorrowedSliceOfc_uchar account_data, SignalBorrowedBuffer last_distinguished_tree_head, bool is_self_monitor);
|
||||
|
||||
SignalFfiError *signal_key_transparency_search(SignalCPromiseOwnedBufferOfc_uchar *promise, SignalConstPointerTokioAsyncContext async_runtime, uint8_t environment, SignalConstPointerUnauthenticatedChatConnection chat_connection, const SignalServiceIdFixedWidthBinaryBytes *aci, SignalConstPointerPublicKey aci_identity_key, const char *e164, SignalOptionalBorrowedSliceOfc_uchar unidentified_access_key, SignalOptionalBorrowedSliceOfc_uchar username_hash, SignalOptionalBorrowedSliceOfc_uchar account_data, SignalBorrowedBuffer last_distinguished_tree_head);
|
||||
|
||||
SignalFfiError *signal_key_transparency_username_hash_search_key(SignalOwnedBuffer *out, SignalBorrowedBuffer hash);
|
||||
|
||||
SignalFfiError *signal_kyber_key_pair_clone(SignalMutPointerKyberKeyPair *new_obj, SignalConstPointerKyberKeyPair obj);
|
||||
|
|
@ -2355,7 +2112,7 @@ SignalFfiError *signal_privatekey_serialize(SignalOwnedBuffer *out, SignalConstP
|
|||
|
||||
SignalFfiError *signal_privatekey_sign(SignalOwnedBuffer *out, SignalConstPointerPrivateKey key, SignalBorrowedBuffer message);
|
||||
|
||||
SignalFfiError *signal_process_prekey_bundle(SignalConstPointerPreKeyBundle bundle, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerProtocolAddress local_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store, uint64_t now);
|
||||
SignalFfiError *signal_process_prekey_bundle(SignalConstPointerPreKeyBundle bundle, SignalConstPointerProtocolAddress protocol_address, SignalConstPointerFfiSessionStoreStruct session_store, SignalConstPointerFfiIdentityKeyStoreStruct identity_key_store, uint64_t now);
|
||||
|
||||
SignalFfiError *signal_process_sender_key_distribution_message(SignalConstPointerProtocolAddress sender, SignalConstPointerSenderKeyDistributionMessage sender_key_distribution_message, SignalConstPointerFfiSenderKeyStoreStruct store);
|
||||
|
||||
|
|
@ -2383,18 +2140,10 @@ SignalFfiError *signal_profile_key_get_commitment(unsigned char (*out)[SignalPRO
|
|||
|
||||
SignalFfiError *signal_profile_key_get_profile_key_version(uint8_t (*out)[SignalPROFILE_KEY_VERSION_ENCODED_LEN], const unsigned char (*profile_key)[SignalPROFILE_KEY_LEN], const SignalServiceIdFixedWidthBinaryBytes *user_id);
|
||||
|
||||
SignalFfiError *signal_provisioning_chat_connection_connect(SignalCPromiseMutPointerProvisioningChatConnection *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerConnectionManager connection_manager);
|
||||
|
||||
SignalFfiError *signal_provisioning_chat_connection_destroy(SignalMutPointerProvisioningChatConnection p);
|
||||
|
||||
SignalFfiError *signal_provisioning_chat_connection_disconnect(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerProvisioningChatConnection chat);
|
||||
|
||||
SignalFfiError *signal_provisioning_chat_connection_info(SignalMutPointerChatConnectionInfo *out, SignalConstPointerProvisioningChatConnection chat);
|
||||
|
||||
SignalFfiError *signal_provisioning_chat_connection_init_listener(SignalConstPointerProvisioningChatConnection chat, SignalConstPointerFfiProvisioningListenerStruct listener);
|
||||
|
||||
SignalFfiError *signal_publickey_clone(SignalMutPointerPublicKey *new_obj, SignalConstPointerPublicKey obj);
|
||||
|
||||
SignalFfiError *signal_publickey_compare(int32_t *out, SignalConstPointerPublicKey key1, SignalConstPointerPublicKey key2);
|
||||
|
||||
SignalFfiError *signal_publickey_deserialize(SignalMutPointerPublicKey *out, SignalBorrowedBuffer data);
|
||||
|
||||
SignalFfiError *signal_publickey_destroy(SignalMutPointerPublicKey p);
|
||||
|
|
@ -2567,7 +2316,7 @@ SignalFfiError *signal_sender_certificate_validate(bool *out, SignalConstPointer
|
|||
|
||||
SignalFfiError *signal_sender_key_distribution_message_clone(SignalMutPointerSenderKeyDistributionMessage *new_obj, SignalConstPointerSenderKeyDistributionMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_create(SignalMutPointerSenderKeyDistributionMessage *out, SignalConstPointerProtocolAddress sender, SignalUuid distribution_id, SignalConstPointerFfiSenderKeyStoreStruct store);
|
||||
SignalFfiError *signal_sender_key_distribution_message_create(SignalMutPointerSenderKeyDistributionMessage *out, SignalConstPointerProtocolAddress sender, const uint8_t (*distribution_id)[16], SignalConstPointerFfiSenderKeyStoreStruct store);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_deserialize(SignalMutPointerSenderKeyDistributionMessage *out, SignalBorrowedBuffer data);
|
||||
|
||||
|
|
@ -2577,13 +2326,13 @@ SignalFfiError *signal_sender_key_distribution_message_get_chain_id(uint32_t *ou
|
|||
|
||||
SignalFfiError *signal_sender_key_distribution_message_get_chain_key(SignalOwnedBuffer *out, SignalConstPointerSenderKeyDistributionMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_get_distribution_id(SignalUuid *out, SignalConstPointerSenderKeyDistributionMessage obj);
|
||||
SignalFfiError *signal_sender_key_distribution_message_get_distribution_id(uint8_t (*out)[16], SignalConstPointerSenderKeyDistributionMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_get_iteration(uint32_t *out, SignalConstPointerSenderKeyDistributionMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_get_signature_key(SignalMutPointerPublicKey *out, SignalConstPointerSenderKeyDistributionMessage m);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_new(SignalMutPointerSenderKeyDistributionMessage *out, uint8_t message_version, SignalUuid distribution_id, uint32_t chain_id, uint32_t iteration, SignalBorrowedBuffer chainkey, SignalConstPointerPublicKey pk);
|
||||
SignalFfiError *signal_sender_key_distribution_message_new(SignalMutPointerSenderKeyDistributionMessage *out, uint8_t message_version, const uint8_t (*distribution_id)[16], uint32_t chain_id, uint32_t iteration, SignalBorrowedBuffer chainkey, SignalConstPointerPublicKey pk);
|
||||
|
||||
SignalFfiError *signal_sender_key_distribution_message_serialize(SignalOwnedBuffer *out, SignalConstPointerSenderKeyDistributionMessage obj);
|
||||
|
||||
|
|
@ -2597,11 +2346,11 @@ SignalFfiError *signal_sender_key_message_get_chain_id(uint32_t *out, SignalCons
|
|||
|
||||
SignalFfiError *signal_sender_key_message_get_cipher_text(SignalOwnedBuffer *out, SignalConstPointerSenderKeyMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_message_get_distribution_id(SignalUuid *out, SignalConstPointerSenderKeyMessage obj);
|
||||
SignalFfiError *signal_sender_key_message_get_distribution_id(uint8_t (*out)[16], SignalConstPointerSenderKeyMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_message_get_iteration(uint32_t *out, SignalConstPointerSenderKeyMessage obj);
|
||||
|
||||
SignalFfiError *signal_sender_key_message_new(SignalMutPointerSenderKeyMessage *out, uint8_t message_version, SignalUuid distribution_id, uint32_t chain_id, uint32_t iteration, SignalBorrowedBuffer ciphertext, SignalConstPointerPrivateKey pk);
|
||||
SignalFfiError *signal_sender_key_message_new(SignalMutPointerSenderKeyMessage *out, uint8_t message_version, const uint8_t (*distribution_id)[16], uint32_t chain_id, uint32_t iteration, SignalBorrowedBuffer ciphertext, SignalConstPointerPrivateKey pk);
|
||||
|
||||
SignalFfiError *signal_sender_key_message_serialize(SignalOwnedBuffer *out, SignalConstPointerSenderKeyMessage obj);
|
||||
|
||||
|
|
@ -2755,38 +2504,20 @@ SignalFfiError *signal_tokio_async_context_destroy(SignalMutPointerTokioAsyncCon
|
|||
|
||||
SignalFfiError *signal_tokio_async_context_new(SignalMutPointerTokioAsyncContext *out);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_account_exists(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, const SignalServiceIdFixedWidthBinaryBytes *account);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_backup_get_media_upload_form(SignalCPromiseFfiUploadForm *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalBorrowedBuffer credential, SignalBorrowedBuffer server_keys, SignalConstPointerPrivateKey signing_key, uint64_t upload_size, int64_t rng);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_backup_get_upload_form(SignalCPromiseFfiUploadForm *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalBorrowedBuffer credential, SignalBorrowedBuffer server_keys, SignalConstPointerPrivateKey signing_key, uint64_t upload_size, int64_t rng);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_connect(SignalCPromiseMutPointerUnauthenticatedChatConnection *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerConnectionManager connection_manager, SignalBorrowedBytestringArray languages);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_destroy(SignalMutPointerUnauthenticatedChatConnection p);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_disconnect(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_get_pre_keys_access_key_auth(SignalCPromiseFfiPreKeysResponse *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, const uint8_t (*auth)[16], const SignalServiceIdFixedWidthBinaryBytes *target, int32_t device);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_get_pre_keys_group_auth(SignalCPromiseFfiPreKeysResponse *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalBorrowedBuffer auth, const SignalServiceIdFixedWidthBinaryBytes *target, int32_t device);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_get_pre_keys_unrestricted_auth(SignalCPromiseFfiPreKeysResponse *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, const SignalServiceIdFixedWidthBinaryBytes *target, int32_t device);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_info(SignalMutPointerChatConnectionInfo *out, SignalConstPointerUnauthenticatedChatConnection chat);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_init_listener(SignalConstPointerUnauthenticatedChatConnection chat, SignalConstPointerFfiChatListenerStruct listener);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_look_up_username_hash(SignalCPromiseOptionalUuid *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalBorrowedBuffer hash);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_look_up_username_link(SignalCPromiseOptionalPairOfc_charu832 *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalUuid uuid, SignalBorrowedBuffer entropy);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_send(SignalCPromiseFfiChatResponse *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalConstPointerHttpRequest http_request, uint32_t timeout_millis);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_send_message(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, const SignalServiceIdFixedWidthBinaryBytes *destination, uint64_t timestamp, SignalBorrowedSliceOfu32 device_ids, SignalBorrowedSliceOfu32 registration_ids, SignalBorrowedSliceOfBuffers contents, uint8_t auth_kind, SignalOptionalBorrowedSliceOfc_uchar auth_buffer, bool online_only, bool is_urgent);
|
||||
|
||||
SignalFfiError *signal_unauthenticated_chat_connection_send_multi_recipient_message(SignalCPromiseOwnedBufferOfServiceIdFixedWidthBinaryBytes *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalBorrowedBuffer payload, uint64_t timestamp, SignalBorrowedBuffer auth, bool online_only, bool is_urgent);
|
||||
|
||||
SignalFfiError *signal_unidentified_sender_message_content_deserialize(SignalMutPointerUnidentifiedSenderMessageContent *out, SignalBorrowedBuffer data);
|
||||
|
||||
SignalFfiError *signal_unidentified_sender_message_content_destroy(SignalMutPointerUnidentifiedSenderMessageContent p);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ package libsignalgo
|
|||
|
||||
extern void signal_log_callback(void *ctx, SignalLogLevel level, char *file, uint32_t line, char *message);
|
||||
extern void signal_log_flush_callback(void *ctx);
|
||||
extern void signal_log_destroy_callback(void *ctx);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -41,11 +40,6 @@ func signal_log_flush_callback(ctx unsafe.Pointer) {
|
|||
ffiLogger.Flush()
|
||||
}
|
||||
|
||||
//export signal_log_destroy_callback
|
||||
func signal_log_destroy_callback(ctx unsafe.Pointer) {
|
||||
ffiLogger.Destroy()
|
||||
}
|
||||
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
|
|
@ -59,14 +53,12 @@ const (
|
|||
type Logger interface {
|
||||
Log(level LogLevel, file string, line uint, message string)
|
||||
Flush()
|
||||
Destroy()
|
||||
}
|
||||
|
||||
func InitLogger(level LogLevel, logger Logger) {
|
||||
ffiLogger = logger
|
||||
C.signal_init_logger(C.SignalLogLevel(level), C.SignalFfiLoggerStruct{
|
||||
log: C.SignalFfiLoggerLog(C.signal_log_callback),
|
||||
flush: C.SignalFfiLoggerFlush(C.signal_log_flush_callback),
|
||||
destroy: C.SignalFfiLoggerDestroy(C.signal_log_destroy_callback),
|
||||
C.signal_init_logger(C.SignalLogLevel(level), C.SignalFfiLogger{
|
||||
log: C.SignalLogCallback(C.signal_log_callback),
|
||||
flush: C.SignalLogFlushCallback(C.signal_log_flush_callback),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func Encrypt(ctx context.Context, plaintext []byte, forAddress, localAddress *Address, sessionStore SessionStore, identityKeyStore IdentityKeyStore) (*CiphertextMessage, error) {
|
||||
func Encrypt(ctx context.Context, plaintext []byte, forAddress *Address, sessionStore SessionStore, identityKeyStore IdentityKeyStore) (*CiphertextMessage, error) {
|
||||
var ciphertextMessage C.SignalMutPointerCiphertextMessage
|
||||
var now C.uint64_t = C.uint64_t(time.Now().Unix())
|
||||
callbackCtx := NewCallbackContext(ctx)
|
||||
|
|
@ -36,7 +36,6 @@ func Encrypt(ctx context.Context, plaintext []byte, forAddress, localAddress *Ad
|
|||
&ciphertextMessage,
|
||||
BytesToBuffer(plaintext),
|
||||
forAddress.constPtr(),
|
||||
localAddress.constPtr(),
|
||||
callbackCtx.wrapSessionStore(sessionStore),
|
||||
callbackCtx.wrapIdentityKeyStore(identityKeyStore),
|
||||
now,
|
||||
|
|
@ -49,7 +48,7 @@ func Encrypt(ctx context.Context, plaintext []byte, forAddress, localAddress *Ad
|
|||
return wrapCiphertextMessage(ciphertextMessage.raw), nil
|
||||
}
|
||||
|
||||
func Decrypt(ctx context.Context, message *Message, fromAddress, localAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore) ([]byte, error) {
|
||||
func Decrypt(ctx context.Context, message *Message, fromAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore) ([]byte, error) {
|
||||
callbackCtx := NewCallbackContext(ctx)
|
||||
defer callbackCtx.Unref()
|
||||
var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
|
|
@ -57,7 +56,6 @@ func Decrypt(ctx context.Context, message *Message, fromAddress, localAddress *A
|
|||
&decrypted,
|
||||
message.constPtr(),
|
||||
fromAddress.constPtr(),
|
||||
localAddress.constPtr(),
|
||||
callbackCtx.wrapSessionStore(sessionStore),
|
||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"runtime"
|
||||
)
|
||||
|
||||
func DecryptPreKey(ctx context.Context, preKeyMessage *PreKeyMessage, fromAddress, localAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore, preKeyStore PreKeyStore, signedPreKeyStore SignedPreKeyStore, kyberPreKeyStore KyberPreKeyStore) ([]byte, error) {
|
||||
func DecryptPreKey(ctx context.Context, preKeyMessage *PreKeyMessage, fromAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore, preKeyStore PreKeyStore, signedPreKeyStore SignedPreKeyStore, kyberPreKeyStore KyberPreKeyStore) ([]byte, error) {
|
||||
callbackCtx := NewCallbackContext(ctx)
|
||||
defer callbackCtx.Unref()
|
||||
var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
|
|
@ -34,7 +34,6 @@ func DecryptPreKey(ctx context.Context, preKeyMessage *PreKeyMessage, fromAddres
|
|||
&decrypted,
|
||||
preKeyMessage.constPtr(),
|
||||
fromAddress.constPtr(),
|
||||
localAddress.constPtr(),
|
||||
callbackCtx.wrapSessionStore(sessionStore),
|
||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||
callbackCtx.wrapPreKeyStore(preKeyStore),
|
||||
|
|
|
|||
|
|
@ -27,14 +27,13 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func ProcessPreKeyBundle(ctx context.Context, bundle *PreKeyBundle, forAddress, localAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore) error {
|
||||
func ProcessPreKeyBundle(ctx context.Context, bundle *PreKeyBundle, forAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore) error {
|
||||
callbackCtx := NewCallbackContext(ctx)
|
||||
defer callbackCtx.Unref()
|
||||
var now C.uint64_t = C.uint64_t(time.Now().Unix())
|
||||
signalFfiError := C.signal_process_prekey_bundle(
|
||||
bundle.constPtr(),
|
||||
forAddress.constPtr(),
|
||||
localAddress.constPtr(),
|
||||
callbackCtx.wrapSessionStore(sessionStore),
|
||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||
now,
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ package libsignalgo
|
|||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
|
||||
extern int signal_load_pre_key_callback(void *store_ctx, SignalMutPointerPreKeyRecord *recordp, uint32_t id);
|
||||
extern int signal_store_pre_key_callback(void *store_ctx, uint32_t id, SignalMutPointerPreKeyRecord record);
|
||||
typedef const SignalPreKeyRecord const_pre_key_record;
|
||||
|
||||
extern int signal_load_pre_key_callback(void *store_ctx, SignalPreKeyRecord **recordp, uint32_t id);
|
||||
extern int signal_store_pre_key_callback(void *store_ctx, uint32_t id, const_pre_key_record *record);
|
||||
extern int signal_remove_pre_key_callback(void *store_ctx, uint32_t id);
|
||||
extern void signal_destroy_pre_key_store_callback(void *store_ctx);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -38,21 +39,21 @@ type PreKeyStore interface {
|
|||
}
|
||||
|
||||
//export signal_load_pre_key_callback
|
||||
func signal_load_pre_key_callback(storeCtx unsafe.Pointer, keyp *C.SignalMutPointerPreKeyRecord, id C.uint32_t) C.int {
|
||||
func signal_load_pre_key_callback(storeCtx unsafe.Pointer, keyp **C.SignalPreKeyRecord, id C.uint32_t) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store PreKeyStore, ctx context.Context) error {
|
||||
key, err := store.LoadPreKey(ctx, uint32(id))
|
||||
if err == nil && key != nil {
|
||||
key.CancelFinalizer()
|
||||
keyp.raw = key.ptr
|
||||
*keyp = key.ptr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_store_pre_key_callback
|
||||
func signal_store_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord C.SignalMutPointerPreKeyRecord) C.int {
|
||||
func signal_store_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord *C.const_pre_key_record) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store PreKeyStore, ctx context.Context) error {
|
||||
record := PreKeyRecord{ptr: preKeyRecord.raw}
|
||||
record := PreKeyRecord{ptr: (*C.SignalPreKeyRecord)(unsafe.Pointer(preKeyRecord))}
|
||||
cloned, err := record.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -68,17 +69,11 @@ func signal_remove_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t) C.in
|
|||
})
|
||||
}
|
||||
|
||||
//export signal_destroy_pre_key_store_callback
|
||||
func signal_destroy_pre_key_store_callback(storeCtx unsafe.Pointer) {
|
||||
// No-op: Go's garbage collector handles cleanup
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapPreKeyStore(store PreKeyStore) C.SignalConstPointerFfiPreKeyStoreStruct {
|
||||
return C.SignalConstPointerFfiPreKeyStoreStruct{&C.SignalPreKeyStore{
|
||||
ctx: wrapStore(ctx, store),
|
||||
load_pre_key: C.SignalFfiPreKeyStoreLoadPreKey(C.signal_load_pre_key_callback),
|
||||
store_pre_key: C.SignalFfiPreKeyStoreStorePreKey(C.signal_store_pre_key_callback),
|
||||
remove_pre_key: C.SignalFfiPreKeyStoreRemovePreKey(C.signal_remove_pre_key_callback),
|
||||
destroy: C.SignalFfiPreKeyStoreDestroy(C.signal_destroy_pre_key_store_callback),
|
||||
load_pre_key: C.SignalLoadPreKey(C.signal_load_pre_key_callback),
|
||||
store_pre_key: C.SignalStorePreKey(C.signal_store_pre_key_callback),
|
||||
remove_pre_key: C.SignalRemovePreKey(C.signal_remove_pre_key_callback),
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ package libsignalgo
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
|
@ -55,6 +54,10 @@ func (pk *ProfileKey) IsEmpty() bool {
|
|||
return pk == nil || *pk == blankProfileKey
|
||||
}
|
||||
|
||||
func (ak *AccessKey) String() string {
|
||||
return string(ak[:])
|
||||
}
|
||||
|
||||
func (pv *ProfileKeyVersion) String() string {
|
||||
return string(pv[:])
|
||||
}
|
||||
|
|
@ -66,23 +69,6 @@ func (pk *ProfileKey) Slice() []byte {
|
|||
return pk[:]
|
||||
}
|
||||
|
||||
func (ak *AccessKey) Xor(other *AccessKey) *AccessKey {
|
||||
if ak == nil {
|
||||
return other
|
||||
} else if other == nil {
|
||||
return ak
|
||||
}
|
||||
var result AccessKey
|
||||
for i := 0; i < C.SignalACCESS_KEY_LEN; i++ {
|
||||
result[i] = ak[i] ^ other[i]
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
func (ak *AccessKey) String() string {
|
||||
return base64.StdEncoding.EncodeToString(ak[:])
|
||||
}
|
||||
|
||||
func (pk *ProfileKey) GetCommitment(u uuid.UUID) (*ProfileKeyCommitment, error) {
|
||||
c_result := [C.SignalPROFILE_KEY_COMMITMENT_LEN]C.uchar{}
|
||||
c_profileKey := (*[C.SignalPROFILE_KEY_LEN]C.uchar)(unsafe.Pointer(pk))
|
||||
|
|
|
|||
|
|
@ -29,9 +29,6 @@ type PublicKey struct {
|
|||
}
|
||||
|
||||
func wrapPublicKey(ptr *C.SignalPublicKey) *PublicKey {
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
publicKey := &PublicKey{ptr: ptr}
|
||||
runtime.SetFinalizer(publicKey, (*PublicKey).Destroy)
|
||||
return publicKey
|
||||
|
|
@ -84,15 +81,15 @@ func (k *PublicKey) CancelFinalizer() {
|
|||
runtime.SetFinalizer(k, nil)
|
||||
}
|
||||
|
||||
func (k *PublicKey) Equal(other *PublicKey) (bool, error) {
|
||||
var comparison C.bool
|
||||
signalFfiError := C.signal_publickey_equals(&comparison, k.constPtr(), other.constPtr())
|
||||
func (k *PublicKey) Compare(other *PublicKey) (int, error) {
|
||||
var comparison C.int
|
||||
signalFfiError := C.signal_publickey_compare(&comparison, k.constPtr(), other.constPtr())
|
||||
runtime.KeepAlive(k)
|
||||
runtime.KeepAlive(other)
|
||||
if signalFfiError != nil {
|
||||
return false, wrapError(signalFfiError)
|
||||
return 0, wrapError(signalFfiError)
|
||||
}
|
||||
return bool(comparison), nil
|
||||
return int(comparison), nil
|
||||
}
|
||||
|
||||
func (k *PublicKey) Bytes() ([]byte, error) {
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ package libsignalgo
|
|||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
|
@ -44,17 +42,8 @@ func NewSealedSenderAddress(e164 string, uuid uuid.UUID, deviceID uint32) *Seale
|
|||
}
|
||||
}
|
||||
|
||||
func SealedSenderEncryptPlaintext(
|
||||
ctx context.Context,
|
||||
message []byte,
|
||||
contentHint UnidentifiedSenderMessageContentHint,
|
||||
forAddress, localAddress *Address,
|
||||
fromSenderCert *SenderCertificate,
|
||||
sessionStore SessionStore,
|
||||
identityStore IdentityKeyStore,
|
||||
groupID *GroupIdentifier,
|
||||
) ([]byte, error) {
|
||||
ciphertextMessage, err := Encrypt(ctx, message, forAddress, localAddress, sessionStore, identityStore)
|
||||
func SealedSenderEncryptPlaintext(ctx context.Context, message []byte, contentHint UnidentifiedSenderMessageContentHint, forAddress *Address, fromSenderCert *SenderCertificate, sessionStore SessionStore, identityStore IdentityKeyStore) ([]byte, error) {
|
||||
ciphertextMessage, err := Encrypt(ctx, message, forAddress, sessionStore, identityStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -63,7 +52,7 @@ func SealedSenderEncryptPlaintext(
|
|||
ciphertextMessage,
|
||||
fromSenderCert,
|
||||
contentHint,
|
||||
groupID,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -89,51 +78,8 @@ func SealedSenderEncrypt(ctx context.Context, usmc *UnidentifiedSenderMessageCon
|
|||
return CopySignalOwnedBufferToBytes(encrypted), nil
|
||||
}
|
||||
|
||||
type SessionAddressTuple struct {
|
||||
ServiceID ServiceID
|
||||
DeviceID int
|
||||
Address *Address
|
||||
Record *SessionRecord
|
||||
}
|
||||
|
||||
func SealedSenderMultiRecipientEncrypt(
|
||||
ctx context.Context,
|
||||
usmc *UnidentifiedSenderMessageContent,
|
||||
recipients []SessionAddressTuple,
|
||||
identityStore IdentityKeyStore,
|
||||
) ([]byte, error) {
|
||||
var encrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
callbackCtx := NewCallbackContext(ctx)
|
||||
defer callbackCtx.Unref()
|
||||
recipientAddresses := make([]C.SignalConstPointerProtocolAddress, len(recipients))
|
||||
recipientSessions := make([]C.SignalConstPointerSessionRecord, len(recipients))
|
||||
|
||||
for i, recipient := range recipients {
|
||||
recipientAddresses[i] = recipient.Address.constPtr()
|
||||
recipientSessions[i] = recipient.Record.constPtr()
|
||||
}
|
||||
signalFfiError := C.signal_sealed_sender_multi_recipient_encrypt(
|
||||
&encrypted,
|
||||
C.SignalBorrowedSliceOfConstPointerProtocolAddress{
|
||||
base: unsafe.SliceData(recipientAddresses),
|
||||
length: C.size_t(len(recipientAddresses)),
|
||||
},
|
||||
C.SignalBorrowedSliceOfConstPointerSessionRecord{
|
||||
base: unsafe.SliceData(recipientSessions),
|
||||
length: C.size_t(len(recipientSessions)),
|
||||
},
|
||||
BytesToBuffer(nil),
|
||||
usmc.constPtr(),
|
||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||
)
|
||||
runtime.KeepAlive(usmc)
|
||||
runtime.KeepAlive(recipients)
|
||||
runtime.KeepAlive(recipientAddresses)
|
||||
runtime.KeepAlive(recipientSessions)
|
||||
if signalFfiError != nil {
|
||||
return nil, callbackCtx.wrapError(signalFfiError)
|
||||
}
|
||||
return CopySignalOwnedBufferToBytes(encrypted), nil
|
||||
func SealedSenderMultiRecipientEncrypt(messageContent *UnidentifiedSenderMessageContent, forRecipients []*Address, identityStore IdentityKeyStore, sessionStore SessionStore, ctx *CallbackContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type SealedSenderResult struct {
|
||||
|
|
@ -180,22 +126,18 @@ func wrapUnidentifiedSenderMessageContent(ptr *C.SignalUnidentifiedSenderMessage
|
|||
return messageContent
|
||||
}
|
||||
|
||||
func NewUnidentifiedSenderMessageContent(message *CiphertextMessage, senderCertificate *SenderCertificate, contentHint UnidentifiedSenderMessageContentHint, groupID *GroupIdentifier) (*UnidentifiedSenderMessageContent, error) {
|
||||
func NewUnidentifiedSenderMessageContent(message *CiphertextMessage, senderCertificate *SenderCertificate, contentHint UnidentifiedSenderMessageContentHint, groupID []byte) (*UnidentifiedSenderMessageContent, error) {
|
||||
var usmc C.SignalMutPointerUnidentifiedSenderMessageContent
|
||||
var groupIDBytes []byte
|
||||
if groupID != nil {
|
||||
groupIDBytes = groupID[:]
|
||||
}
|
||||
signalFfiError := C.signal_unidentified_sender_message_content_new(
|
||||
&usmc,
|
||||
message.constPtr(),
|
||||
senderCertificate.constPtr(),
|
||||
C.uint32_t(contentHint),
|
||||
BytesToBuffer(groupIDBytes),
|
||||
BytesToBuffer(groupID),
|
||||
)
|
||||
runtime.KeepAlive(message)
|
||||
runtime.KeepAlive(senderCertificate)
|
||||
runtime.KeepAlive(groupIDBytes)
|
||||
runtime.KeepAlive(groupID)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
|
|
@ -267,21 +209,18 @@ func (usmc *UnidentifiedSenderMessageContent) GetContents() ([]byte, error) {
|
|||
return CopySignalOwnedBufferToBytes(contents), nil
|
||||
}
|
||||
|
||||
func (usmc *UnidentifiedSenderMessageContent) GetGroupID() (*GroupIdentifier, error) {
|
||||
var contents C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||
signalFfiError := C.signal_unidentified_sender_message_content_get_group_id_or_empty(&contents, usmc.constPtr())
|
||||
runtime.KeepAlive(usmc)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
bytes := CopySignalOwnedBufferToBytes(contents)
|
||||
if len(bytes) == 0 {
|
||||
return nil, nil
|
||||
} else if len(bytes) != GroupIdentifierLength {
|
||||
return nil, fmt.Errorf("unexpected group ID length: %d", len(bytes))
|
||||
}
|
||||
return (*GroupIdentifier)(bytes), nil
|
||||
}
|
||||
//func (usmc *UnidentifiedSenderMessageContent) GetGroupID() ([]byte, error) {
|
||||
// var groupID *C.uchar
|
||||
// var length C.ulong
|
||||
// signalFfiError := C.signal_unidentified_sender_message_content_get_group_id(&groupID, &length, usmc.ptr)
|
||||
// if signalFfiError != nil {
|
||||
// return nil, wrapError(signalFfiError)
|
||||
// }
|
||||
// if groupID == nil {
|
||||
// return nil, nil
|
||||
// }
|
||||
// return CopyBufferToBytes(groupID, length), nil
|
||||
//}
|
||||
|
||||
func (usmc *UnidentifiedSenderMessageContent) GetSenderCertificate() (*SenderCertificate, error) {
|
||||
var senderCertificate C.SignalMutPointerSenderCertificate
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func NewSenderKeyDistributionMessage(ctx context.Context, sender *Address, distr
|
|||
signalFfiError := C.signal_sender_key_distribution_message_create(
|
||||
&skdm,
|
||||
sender.constPtr(),
|
||||
*(*C.SignalUuid)(unsafe.Pointer(&distributionID)),
|
||||
(*[C.SignalUUID_LEN]C.uchar)(unsafe.Pointer(&distributionID)),
|
||||
callbackCtx.wrapSenderKeyStore(store),
|
||||
)
|
||||
runtime.KeepAlive(sender)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,13 @@ package libsignalgo
|
|||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
|
||||
extern int signal_load_sender_key_callback(void *store_ctx, SignalMutPointerSenderKeyRecord *out, SignalMutPointerProtocolAddress sender, SignalUuid distribution_id);
|
||||
extern int signal_store_sender_key_callback(void *store_ctx, SignalMutPointerProtocolAddress sender, SignalUuid distribution_id, SignalMutPointerSenderKeyRecord record);
|
||||
extern void signal_destroy_sender_key_store_callback(void *store_ctx);
|
||||
typedef const SignalProtocolAddress const_address;
|
||||
|
||||
typedef const SignalSenderKeyRecord const_sender_key_record;
|
||||
typedef const uint8_t const_uuid_bytes[16];
|
||||
|
||||
extern int signal_load_sender_key_callback(void *store_ctx, SignalSenderKeyRecord**, const_address*, const_uuid_bytes*);
|
||||
extern int signal_store_sender_key_callback(void *store_ctx, const_address*, const_uuid_bytes*, const_sender_key_record*);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -38,40 +42,36 @@ type SenderKeyStore interface {
|
|||
}
|
||||
|
||||
//export signal_load_sender_key_callback
|
||||
func signal_load_sender_key_callback(storeCtx unsafe.Pointer, recordp *C.SignalMutPointerSenderKeyRecord, address C.SignalMutPointerProtocolAddress, distributionID C.SignalUuid) C.int {
|
||||
func signal_load_sender_key_callback(storeCtx unsafe.Pointer, recordp **C.SignalSenderKeyRecord, address *C.const_address, distributionIDBytes *C.const_uuid_bytes) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store SenderKeyStore, ctx context.Context) error {
|
||||
record, err := store.LoadSenderKey(ctx, &Address{ptr: address.raw}, *(*uuid.UUID)(unsafe.Pointer(&distributionID)))
|
||||
distributionID := uuid.UUID(*(*[16]byte)(unsafe.Pointer(distributionIDBytes)))
|
||||
record, err := store.LoadSenderKey(ctx, &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, distributionID)
|
||||
if err == nil && record != nil {
|
||||
record.CancelFinalizer()
|
||||
recordp.raw = record.ptr
|
||||
*recordp = record.ptr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_store_sender_key_callback
|
||||
func signal_store_sender_key_callback(storeCtx unsafe.Pointer, address C.SignalMutPointerProtocolAddress, distributionID C.SignalUuid, senderKeyRecord C.SignalMutPointerSenderKeyRecord) C.int {
|
||||
func signal_store_sender_key_callback(storeCtx unsafe.Pointer, address *C.const_address, distributionIDBytes *C.const_uuid_bytes, senderKeyRecord *C.const_sender_key_record) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store SenderKeyStore, ctx context.Context) error {
|
||||
record := SenderKeyRecord{ptr: senderKeyRecord.raw}
|
||||
distributionID := uuid.UUID(*(*[16]byte)(unsafe.Pointer(distributionIDBytes)))
|
||||
record := SenderKeyRecord{ptr: (*C.SignalSenderKeyRecord)(unsafe.Pointer(senderKeyRecord))}
|
||||
cloned, err := record.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.StoreSenderKey(ctx, &Address{ptr: address.raw}, *(*uuid.UUID)(unsafe.Pointer(&distributionID)), cloned)
|
||||
return store.StoreSenderKey(ctx, &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, distributionID, cloned)
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_destroy_sender_key_store_callback
|
||||
func signal_destroy_sender_key_store_callback(storeCtx unsafe.Pointer) {
|
||||
// No-op: Go's garbage collector handles cleanup
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapSenderKeyStore(store SenderKeyStore) C.SignalConstPointerFfiSenderKeyStoreStruct {
|
||||
return C.SignalConstPointerFfiSenderKeyStoreStruct{&C.SignalSenderKeyStore{
|
||||
ctx: wrapStore(ctx, store),
|
||||
load_sender_key: C.SignalFfiSenderKeyStoreLoadSenderKey(C.signal_load_sender_key_callback),
|
||||
store_sender_key: C.SignalFfiSenderKeyStoreStoreSenderKey(C.signal_store_sender_key_callback),
|
||||
destroy: C.SignalFfiSenderKeyStoreDestroy(C.signal_destroy_sender_key_store_callback),
|
||||
load_sender_key: C.SignalLoadSenderKey(C.signal_load_sender_key_callback),
|
||||
store_sender_key: C.SignalStoreSenderKey(C.signal_store_sender_key_callback),
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if C.SignalUUID_LEN != 16 {
|
||||
panic("libsignal-ffi uuid type size mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
type ServiceIDType byte
|
||||
|
||||
const (
|
||||
|
|
@ -87,9 +93,6 @@ func (s ServiceID) IsEmpty() bool {
|
|||
}
|
||||
|
||||
func (s ServiceID) Address(deviceID uint) (*Address, error) {
|
||||
if s.IsEmpty() {
|
||||
return nil, fmt.Errorf("cannot create address from empty ServiceID")
|
||||
}
|
||||
return newAddress(s.String(), deviceID)
|
||||
}
|
||||
|
||||
|
|
@ -115,19 +118,6 @@ func (s ServiceID) GoString() string {
|
|||
return fmt.Sprintf(`libsignalgo.ServiceID{Type: %#v, UUID: uuid.MustParse("%s")}`, s.Type, s.UUID)
|
||||
}
|
||||
|
||||
func (s ServiceID) MarshalText() ([]byte, error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
func (s *ServiceID) UnmarshalText(text []byte) error {
|
||||
parsed, err := ServiceIDFromString(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ServiceID) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Stringer("type", s.Type)
|
||||
e.Stringer("uuid", s.UUID)
|
||||
|
|
@ -161,18 +151,6 @@ func ServiceIDFromString(val string) (ServiceID, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func ServiceIDFromBytes(bytes []byte) (ServiceID, error) {
|
||||
if len(bytes) == 16 {
|
||||
return NewACIServiceID(uuid.UUID(bytes)), nil
|
||||
} else if len(bytes) == 17 {
|
||||
return ServiceID{
|
||||
Type: ServiceIDType(bytes[0]),
|
||||
UUID: uuid.UUID(bytes[1:]),
|
||||
}, nil
|
||||
}
|
||||
return EmptyServiceID, fmt.Errorf("invalid ServiceID byte length: %d (expected 16 or 17)", len(bytes))
|
||||
}
|
||||
|
||||
func ServiceIDFromCFixedBytes(serviceID *C.SignalServiceIdFixedWidthBinaryBytes) ServiceID {
|
||||
var id ServiceID
|
||||
fixedBytes := (*ServiceIDFixedBytes)(unsafe.Pointer(serviceID))
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import (
|
|||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
)
|
||||
|
||||
func initializeSessions(t *testing.T, aliceStore, bobStore *InMemorySignalProtocolStore, bobAddress, aliceAddress *libsignalgo.Address) {
|
||||
func initializeSessions(t *testing.T, aliceStore, bobStore *InMemorySignalProtocolStore, bobAddress *libsignalgo.Address) {
|
||||
ctx := context.TODO()
|
||||
|
||||
bobPreKey, err := libsignalgo.GeneratePrivateKey()
|
||||
|
|
@ -86,7 +86,7 @@ func initializeSessions(t *testing.T, aliceStore, bobStore *InMemorySignalProtoc
|
|||
assert.NoError(t, err)
|
||||
|
||||
// Alice processes the bundle
|
||||
err = libsignalgo.ProcessPreKeyBundle(ctx, bobBundle, bobAddress, aliceAddress, aliceStore, aliceStore)
|
||||
err = libsignalgo.ProcessPreKeyBundle(ctx, bobBundle, bobAddress, aliceStore, aliceStore)
|
||||
assert.NoError(t, err)
|
||||
|
||||
record, err := aliceStore.LoadSession(ctx, bobAddress)
|
||||
|
|
@ -132,11 +132,11 @@ func TestSessionCipher(t *testing.T) {
|
|||
aliceStore := NewInMemorySignalProtocolStore()
|
||||
bobStore := NewInMemorySignalProtocolStore()
|
||||
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||
|
||||
alicePlaintext := []byte{8, 6, 7, 5, 3, 0, 9}
|
||||
|
||||
aliceCiphertext, err := libsignalgo.Encrypt(ctx, alicePlaintext, bobAddress, aliceAddress, aliceStore, aliceStore)
|
||||
aliceCiphertext, err := libsignalgo.Encrypt(ctx, alicePlaintext, bobAddress, aliceStore, aliceStore)
|
||||
assert.NoError(t, err)
|
||||
aliceCiphertextMessageType, err := aliceCiphertext.MessageType()
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -147,13 +147,13 @@ func TestSessionCipher(t *testing.T) {
|
|||
bobCiphertext, err := libsignalgo.DeserializePreKeyMessage(aliceCiphertextSerialized)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bobPlaintext, err := libsignalgo.DecryptPreKey(ctx, bobCiphertext, aliceAddress, bobAddress, bobStore, bobStore, bobStore, bobStore, bobStore)
|
||||
bobPlaintext, err := libsignalgo.DecryptPreKey(ctx, bobCiphertext, aliceAddress, bobStore, bobStore, bobStore, bobStore, bobStore)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, alicePlaintext, bobPlaintext)
|
||||
|
||||
bobPlaintext2 := []byte{23}
|
||||
|
||||
bobCiphertext2, err := libsignalgo.Encrypt(ctx, bobPlaintext2, aliceAddress, bobAddress, bobStore, bobStore)
|
||||
bobCiphertext2, err := libsignalgo.Encrypt(ctx, bobPlaintext2, aliceAddress, bobStore, bobStore)
|
||||
assert.NoError(t, err)
|
||||
bobCiphertext2MessageType, err := bobCiphertext2.MessageType()
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -163,7 +163,7 @@ func TestSessionCipher(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
aliceCiphertext2, err := libsignalgo.DeserializeMessage(bobCiphertext2Serialized)
|
||||
assert.NoError(t, err)
|
||||
alicePlaintext2, err := libsignalgo.Decrypt(ctx, aliceCiphertext2, bobAddress, aliceAddress, aliceStore, aliceStore)
|
||||
alicePlaintext2, err := libsignalgo.Decrypt(ctx, aliceCiphertext2, bobAddress, aliceStore, aliceStore)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bobPlaintext2, alicePlaintext2)
|
||||
}
|
||||
|
|
@ -183,11 +183,11 @@ func TestSessionCipherWithBadStore(t *testing.T) {
|
|||
aliceStore := NewInMemorySignalProtocolStore()
|
||||
bobStore := &BadInMemorySignalProtocolStore{NewInMemorySignalProtocolStore()}
|
||||
|
||||
initializeSessions(t, aliceStore, bobStore.InMemorySignalProtocolStore, bobAddress, aliceAddress)
|
||||
initializeSessions(t, aliceStore, bobStore.InMemorySignalProtocolStore, bobAddress)
|
||||
|
||||
alicePlaintext := []byte{8, 6, 7, 5, 3, 0, 9}
|
||||
|
||||
aliceCiphertext, err := libsignalgo.Encrypt(ctx, alicePlaintext, bobAddress, aliceAddress, aliceStore, aliceStore)
|
||||
aliceCiphertext, err := libsignalgo.Encrypt(ctx, alicePlaintext, bobAddress, aliceStore, aliceStore)
|
||||
assert.NoError(t, err)
|
||||
aliceCiphertextMessageType, err := aliceCiphertext.MessageType()
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -198,7 +198,7 @@ func TestSessionCipherWithBadStore(t *testing.T) {
|
|||
bobCiphertext, err := libsignalgo.DeserializePreKeyMessage(aliceCiphertextSerialized)
|
||||
assert.NoError(t, err)
|
||||
t.Skip("This test is broken") // TODO fix
|
||||
_, err = libsignalgo.DecryptPreKey(ctx, bobCiphertext, aliceAddress, bobAddress, bobStore, bobStore, bobStore, bobStore, bobStore)
|
||||
_, err = libsignalgo.DecryptPreKey(ctx, bobCiphertext, aliceAddress, bobStore, bobStore, bobStore, bobStore, bobStore)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "Test error", err.Error())
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ func TestSealedSenderEncrypt_Repeated(t *testing.T) {
|
|||
aliceStore := NewInMemorySignalProtocolStore()
|
||||
bobStore := NewInMemorySignalProtocolStore()
|
||||
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||
|
||||
trustRoot, err := libsignalgo.GenerateIdentityKeyPair()
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -241,7 +241,7 @@ func TestSealedSenderEncrypt_Repeated(t *testing.T) {
|
|||
}()
|
||||
for i := 0; i < 100; i++ {
|
||||
message := []byte(fmt.Sprintf("%04d vision", i))
|
||||
ciphertext, err := libsignalgo.SealedSenderEncryptPlaintext(ctx, message, libsignalgo.UnidentifiedSenderMessageContentHintDefault, bobAddress, aliceAddress, senderCert, aliceStore, aliceStore, nil)
|
||||
ciphertext, err := libsignalgo.SealedSenderEncryptPlaintext(ctx, message, libsignalgo.UnidentifiedSenderMessageContentHintDefault, bobAddress, senderCert, aliceStore, aliceStore)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, ciphertext)
|
||||
}
|
||||
|
|
@ -252,18 +252,15 @@ func TestArchiveSession(t *testing.T) {
|
|||
ctx := context.TODO()
|
||||
setupLogging()
|
||||
|
||||
aliceACI := uuid.New()
|
||||
bobACI := uuid.New()
|
||||
|
||||
aliceAddress, err := libsignalgo.NewACIServiceID(aliceACI).Address(1)
|
||||
assert.NoError(t, err)
|
||||
bobAddress, err := libsignalgo.NewACIServiceID(bobACI).Address(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
aliceStore := NewInMemorySignalProtocolStore()
|
||||
bobStore := NewInMemorySignalProtocolStore()
|
||||
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||
|
||||
session, err := aliceStore.LoadSession(ctx, bobAddress)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -318,7 +315,7 @@ func TestSealedSenderGroupCipher(t *testing.T) {
|
|||
|
||||
bobStore := NewInMemorySignalProtocolStore()
|
||||
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
||||
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||
|
||||
trustRoot, err := libsignalgo.GenerateIdentityKeyPair()
|
||||
assert.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -83,9 +83,6 @@ func (sr *SessionRecord) ArchiveCurrentState() error {
|
|||
}
|
||||
|
||||
func (sr *SessionRecord) CurrentRatchetKeyMatches(key *PublicKey) (bool, error) {
|
||||
if sr == nil || key == nil {
|
||||
return false, nil
|
||||
}
|
||||
var result C.bool
|
||||
signalFfiError := C.signal_session_record_current_ratchet_key_matches(
|
||||
&result,
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ package libsignalgo
|
|||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
|
||||
extern int signal_load_session_callback(void *store_ctx, SignalMutPointerSessionRecord *recordp, SignalMutPointerProtocolAddress address);
|
||||
extern int signal_store_session_callback(void *store_ctx, SignalMutPointerProtocolAddress address, SignalMutPointerSessionRecord record);
|
||||
extern void signal_destroy_session_store_callback(void *store_ctx);
|
||||
typedef const SignalSessionRecord const_session_record;
|
||||
typedef const SignalProtocolAddress const_address;
|
||||
|
||||
extern int signal_load_session_callback(void *store_ctx, SignalSessionRecord **recordp, const_address *address);
|
||||
extern int signal_store_session_callback(void *store_ctx, const_address *address, const_session_record *record);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -36,39 +38,33 @@ type SessionStore interface {
|
|||
}
|
||||
|
||||
//export signal_load_session_callback
|
||||
func signal_load_session_callback(storeCtx unsafe.Pointer, recordp *C.SignalMutPointerSessionRecord, address C.SignalMutPointerProtocolAddress) C.int {
|
||||
func signal_load_session_callback(storeCtx unsafe.Pointer, recordp **C.SignalSessionRecord, address *C.const_address) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store SessionStore, ctx context.Context) error {
|
||||
record, err := store.LoadSession(ctx, &Address{ptr: address.raw})
|
||||
record, err := store.LoadSession(ctx, &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))})
|
||||
if err == nil && record != nil {
|
||||
record.CancelFinalizer()
|
||||
recordp.raw = record.ptr
|
||||
*recordp = record.ptr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_store_session_callback
|
||||
func signal_store_session_callback(storeCtx unsafe.Pointer, address C.SignalMutPointerProtocolAddress, sessionRecord C.SignalMutPointerSessionRecord) C.int {
|
||||
func signal_store_session_callback(storeCtx unsafe.Pointer, address *C.const_address, sessionRecord *C.const_session_record) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store SessionStore, ctx context.Context) error {
|
||||
record := SessionRecord{ptr: sessionRecord.raw}
|
||||
record := SessionRecord{ptr: (*C.SignalSessionRecord)(unsafe.Pointer(sessionRecord))}
|
||||
cloned, err := record.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.StoreSession(ctx, &Address{ptr: address.raw}, cloned)
|
||||
return store.StoreSession(ctx, &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, cloned)
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_destroy_session_store_callback
|
||||
func signal_destroy_session_store_callback(storeCtx unsafe.Pointer) {
|
||||
// No-op: Go's garbage collector handles cleanup
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapSessionStore(store SessionStore) C.SignalConstPointerFfiSessionStoreStruct {
|
||||
return C.SignalConstPointerFfiSessionStoreStruct{&C.SignalSessionStore{
|
||||
ctx: wrapStore(ctx, store),
|
||||
load_session: C.SignalFfiSessionStoreLoadSession(C.signal_load_session_callback),
|
||||
store_session: C.SignalFfiSessionStoreStoreSession(C.signal_store_session_callback),
|
||||
destroy: C.SignalFfiSessionStoreDestroy(C.signal_destroy_session_store_callback),
|
||||
load_session: C.SignalLoadSession(C.signal_load_session_callback),
|
||||
store_session: C.SignalStoreSession(C.signal_store_session_callback),
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ func (FFILogger) Log(level libsignalgo.LogLevel, file string, line uint, message
|
|||
|
||||
func (FFILogger) Flush() {}
|
||||
|
||||
func (FFILogger) Destroy() {}
|
||||
|
||||
var loggingSetup = false
|
||||
|
||||
func setupLogging() {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,10 @@ package libsignalgo
|
|||
/*
|
||||
#include "./libsignal-ffi.h"
|
||||
|
||||
extern int signal_load_signed_pre_key_callback(void *store_ctx, SignalMutPointerSignedPreKeyRecord *recordp, uint32_t id);
|
||||
extern int signal_store_signed_pre_key_callback(void *store_ctx, uint32_t id, SignalMutPointerSignedPreKeyRecord record);
|
||||
extern void signal_destroy_signed_pre_key_store_callback(void *store_ctx);
|
||||
typedef const SignalSignedPreKeyRecord const_signed_pre_key_record;
|
||||
|
||||
extern int signal_load_signed_pre_key_callback(void *store_ctx, SignalSignedPreKeyRecord **recordp, uint32_t id);
|
||||
extern int signal_store_signed_pre_key_callback(void *store_ctx, uint32_t id, const_signed_pre_key_record *record);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -36,21 +37,21 @@ type SignedPreKeyStore interface {
|
|||
}
|
||||
|
||||
//export signal_load_signed_pre_key_callback
|
||||
func signal_load_signed_pre_key_callback(storeCtx unsafe.Pointer, keyp *C.SignalMutPointerSignedPreKeyRecord, id C.uint32_t) C.int {
|
||||
func signal_load_signed_pre_key_callback(storeCtx unsafe.Pointer, keyp **C.SignalSignedPreKeyRecord, id C.uint32_t) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store SignedPreKeyStore, ctx context.Context) error {
|
||||
key, err := store.LoadSignedPreKey(ctx, uint32(id))
|
||||
if err == nil && key != nil {
|
||||
key.CancelFinalizer()
|
||||
keyp.raw = key.ptr
|
||||
*keyp = key.ptr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//export signal_store_signed_pre_key_callback
|
||||
func signal_store_signed_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord C.SignalMutPointerSignedPreKeyRecord) C.int {
|
||||
func signal_store_signed_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord *C.const_signed_pre_key_record) C.int {
|
||||
return wrapStoreCallback(storeCtx, func(store SignedPreKeyStore, ctx context.Context) error {
|
||||
record := SignedPreKeyRecord{ptr: preKeyRecord.raw}
|
||||
record := SignedPreKeyRecord{ptr: (*C.SignalSignedPreKeyRecord)(unsafe.Pointer(preKeyRecord))}
|
||||
cloned, err := record.Clone()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -59,16 +60,10 @@ func signal_store_signed_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t
|
|||
})
|
||||
}
|
||||
|
||||
//export signal_destroy_signed_pre_key_store_callback
|
||||
func signal_destroy_signed_pre_key_store_callback(storeCtx unsafe.Pointer) {
|
||||
// No-op: Go's garbage collector handles cleanup
|
||||
}
|
||||
|
||||
func (ctx *CallbackContext) wrapSignedPreKeyStore(store SignedPreKeyStore) C.SignalConstPointerFfiSignedPreKeyStoreStruct {
|
||||
return C.SignalConstPointerFfiSignedPreKeyStoreStruct{&C.SignalSignedPreKeyStore{
|
||||
ctx: wrapStore(ctx, store),
|
||||
load_signed_pre_key: C.SignalFfiSignedPreKeyStoreLoadSignedPreKey(C.signal_load_signed_pre_key_callback),
|
||||
store_signed_pre_key: C.SignalFfiSignedPreKeyStoreStoreSignedPreKey(C.signal_store_signed_pre_key_callback),
|
||||
destroy: C.SignalFfiSignedPreKeyStoreDestroy(C.signal_destroy_signed_pre_key_store_callback),
|
||||
load_signed_pre_key: C.SignalLoadSignedPreKey(C.signal_load_signed_pre_key_callback),
|
||||
store_signed_pre_key: C.SignalStoreSignedPreKey(C.signal_store_signed_pre_key_callback),
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
cd /data
|
||||
export RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER=""
|
||||
apk add --no-cache git make cmake protobuf-dev musl-dev g++ clang-dev cbindgen
|
||||
apk add --no-cache git make cmake protoc musl-dev g++ clang-dev cbindgen
|
||||
cd libsignal
|
||||
cargo build -p libsignal-ffi --release
|
||||
cbindgen --profile release rust/bridge/ffi -o libsignal-ffi.h
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
package libsignalgo
|
||||
|
||||
const Version = "v0.93.2"
|
||||
const Version = "v0.84.0"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ func (mc *MessageConverter) ToSignal(
|
|||
portal *bridgev2.Portal,
|
||||
evt *event.Event,
|
||||
content *event.MessageEventContent,
|
||||
timestamp uint64,
|
||||
relaybotFormatted bool,
|
||||
replyTo *database.Message,
|
||||
) (*signalpb.DataMessage, error) {
|
||||
|
|
@ -54,15 +55,16 @@ func (mc *MessageConverter) ToSignal(
|
|||
}
|
||||
|
||||
dm := &signalpb.DataMessage{
|
||||
Preview: mc.convertURLPreviewToSignal(ctx, content),
|
||||
Timestamp: ×tamp,
|
||||
Preview: mc.convertURLPreviewToSignal(ctx, content),
|
||||
}
|
||||
if replyTo != nil {
|
||||
authorACI, messageID, err := signalid.ParseMessageID(replyTo.ID)
|
||||
if err == nil {
|
||||
dm.Quote = &signalpb.DataMessage_Quote{
|
||||
Id: proto.Uint64(messageID),
|
||||
AuthorAciBinary: authorACI[:],
|
||||
Type: signalpb.DataMessage_Quote_NORMAL.Enum(),
|
||||
Id: proto.Uint64(messageID),
|
||||
AuthorAci: proto.String(authorACI.String()),
|
||||
Type: signalpb.DataMessage_Quote_NORMAL.Enum(),
|
||||
}
|
||||
if replyTo.Metadata.(*signalid.MessageMetadata).ContainsAttachments {
|
||||
dm.Quote.Attachments = make([]*signalpb.DataMessage_Quote_QuotedAttachment, 1)
|
||||
|
|
@ -110,24 +112,21 @@ func (mc *MessageConverter) ToSignal(
|
|||
return nil, fmt.Errorf("failed to convert sticker: %w", err)
|
||||
}
|
||||
att.Flags = proto.Uint32(uint32(signalpb.AttachmentPointer_BORDERLESS))
|
||||
|
||||
dm.Sticker = ParseStickerMeta(content.Info.BridgedSticker)
|
||||
if dm.Sticker == nil {
|
||||
var emoji *string
|
||||
// TODO check for single grapheme cluster?
|
||||
if len([]rune(content.Body)) == 1 {
|
||||
emoji = proto.String(variationselector.Remove(content.Body))
|
||||
}
|
||||
dm.Sticker = &signalpb.DataMessage_Sticker{
|
||||
// Signal iOS validates that pack id/key are of the correct length.
|
||||
// Android is fine with any non-nil values (like a zero-length byte string).
|
||||
PackId: make([]byte, 16),
|
||||
PackKey: make([]byte, 32),
|
||||
StickerId: proto.Uint32(0),
|
||||
Emoji: emoji,
|
||||
}
|
||||
var emoji *string
|
||||
// TODO check for single grapheme cluster?
|
||||
if len([]rune(content.Body)) == 1 {
|
||||
emoji = proto.String(variationselector.Remove(content.Body))
|
||||
}
|
||||
dm.Sticker = &signalpb.DataMessage_Sticker{
|
||||
// Signal iOS validates that pack id/key are of the correct length.
|
||||
// Android is fine with any non-nil values (like a zero-length byte string).
|
||||
PackId: make([]byte, 16),
|
||||
PackKey: make([]byte, 32),
|
||||
StickerId: proto.Uint32(0),
|
||||
|
||||
Data: att,
|
||||
Emoji: emoji,
|
||||
}
|
||||
dm.Sticker.Data = att
|
||||
case event.MsgLocation:
|
||||
lat, lon, err := parseGeoURI(content.GeoURI)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -81,16 +81,6 @@ func BackupToDataMessage(ci *backuppb.ChatItem, attMap AttachmentMap) (*signalpb
|
|||
Emoji: ti.StickerMessage.Sticker.Emoji,
|
||||
Data: backupToSignalAttachment(ti.StickerMessage.Sticker.Data, 0, uuid.New(), attMap),
|
||||
}
|
||||
case *backuppb.ChatItem_Poll:
|
||||
dm.PollCreate = &signalpb.DataMessage_PollCreate{
|
||||
Question: &ti.Poll.Question,
|
||||
AllowMultiple: &ti.Poll.AllowMultiple,
|
||||
Options: exslices.CastFunc(ti.Poll.Options, func(from *backuppb.Poll_PollOption) string {
|
||||
return from.Option
|
||||
}),
|
||||
}
|
||||
// TODO handle votes
|
||||
// TODO handle hasEnded somehow?
|
||||
case *backuppb.ChatItem_RemoteDeletedMessage:
|
||||
// TODO handle some other way? (also disappeared view-once messages)
|
||||
return nil, nil
|
||||
|
|
@ -248,7 +238,11 @@ func backupToSignalBodyRange(from *backuppb.BodyRange) *signalpb.BodyRange {
|
|||
out.Length = &from.Length
|
||||
switch av := from.AssociatedValue.(type) {
|
||||
case *backuppb.BodyRange_MentionAci:
|
||||
out.AssociatedValue = &signalpb.BodyRange_MentionAciBinary{MentionAciBinary: av.MentionAci}
|
||||
// TODO confirm this is correct
|
||||
if len(av.MentionAci) != 16 {
|
||||
return nil
|
||||
}
|
||||
out.AssociatedValue = &signalpb.BodyRange_MentionAci{MentionAci: uuid.UUID(av.MentionAci).String()}
|
||||
case *backuppb.BodyRange_Style_:
|
||||
out.AssociatedValue = &signalpb.BodyRange_Style_{Style: signalpb.BodyRange_Style(av.Style)}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ import (
|
|||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -54,7 +51,7 @@ func calculateLength(dm *signalpb.DataMessage) int {
|
|||
if dm.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0 {
|
||||
return 1
|
||||
}
|
||||
if dm.Sticker != nil || dm.PollVote != nil || dm.PollCreate != nil || dm.PollTerminate != nil {
|
||||
if dm.Sticker != nil {
|
||||
return 1
|
||||
}
|
||||
length := len(dm.Attachments) + len(dm.Contact)
|
||||
|
|
@ -78,13 +75,11 @@ func CanConvertSignal(dm *signalpb.DataMessage) bool {
|
|||
}
|
||||
|
||||
const ViewOnceDisappearTimer = 5 * time.Minute
|
||||
const matrixTextMaxLength = 30000 // approximate value to avoid hitting 64 KiB PDU size limit with HTML duplication
|
||||
|
||||
func (mc *MessageConverter) ToMatrix(
|
||||
ctx context.Context,
|
||||
client *signalmeow.Client,
|
||||
portal *bridgev2.Portal,
|
||||
sender uuid.UUID,
|
||||
intent bridgev2.MatrixAPI,
|
||||
dm *signalpb.DataMessage,
|
||||
attMap AttachmentMap,
|
||||
|
|
@ -113,20 +108,8 @@ func (mc *MessageConverter) ToMatrix(
|
|||
// Don't allow any other parts in a sticker message
|
||||
return cm
|
||||
}
|
||||
if dm.PollVote != nil {
|
||||
cm.Parts = append(cm.Parts, mc.convertPollVoteToMatrix(ctx, dm.PollVote))
|
||||
return cm
|
||||
}
|
||||
if dm.PollCreate != nil {
|
||||
cm.Parts = append(cm.Parts, mc.convertPollCreateToMatrix(dm.PollCreate))
|
||||
return cm
|
||||
}
|
||||
if dm.PollTerminate != nil {
|
||||
cm.Parts = append(cm.Parts, mc.convertPollTerminateToMatrix(ctx, sender, dm.PollTerminate))
|
||||
return cm
|
||||
}
|
||||
for i, att := range dm.GetAttachments() {
|
||||
if att.GetContentType() != "text/x-signal-plain" || att.GetSize() > matrixTextMaxLength {
|
||||
if att.GetContentType() != "text/x-signal-plain" {
|
||||
cm.Parts = append(cm.Parts, mc.convertAttachmentToMatrix(ctx, i, att, attMap))
|
||||
} else {
|
||||
longBody, err := mc.downloadSignalLongText(ctx, att, attMap)
|
||||
|
|
@ -177,12 +160,9 @@ func (mc *MessageConverter) ToMatrix(
|
|||
}
|
||||
}
|
||||
if dm.Quote != nil {
|
||||
authorACI, err := signalmeow.ParseStringOrBinaryUUID(dm.Quote.GetAuthorAci(), dm.Quote.GetAuthorAciBinary())
|
||||
authorACI, err := uuid.Parse(dm.Quote.GetAuthorAci())
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).
|
||||
Str("author_aci", dm.Quote.GetAuthorAci()).
|
||||
Hex("author_aci_binary", dm.Quote.GetAuthorAciBinary()).
|
||||
Msg("Failed to parse quote author ACI")
|
||||
zerolog.Ctx(ctx).Err(err).Str("author_aci", dm.Quote.GetAuthorAci()).Msg("Failed to parse quote author ACI")
|
||||
} else {
|
||||
cm.ReplyTo = &networkid.MessageOptionalPartID{
|
||||
MessageID: signalid.MakeMessageID(authorACI, dm.Quote.GetId()),
|
||||
|
|
@ -343,7 +323,7 @@ func (mc *MessageConverter) convertContactToVCard(ctx context.Context, contact *
|
|||
card.Add(vcard.FieldTelephone, &field)
|
||||
}
|
||||
if contact.GetAvatar().GetAvatar() != nil {
|
||||
avatarData, err := mc.downloadAttachment(ctx, contact.GetAvatar().GetAvatar(), attMap, nil)
|
||||
avatarData, err := mc.downloadAttachment(ctx, contact.GetAvatar().GetAvatar(), attMap)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to download contact avatar")
|
||||
} else {
|
||||
|
|
@ -461,28 +441,31 @@ func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker
|
|||
},
|
||||
}
|
||||
}
|
||||
// Signal stickers are 512x512, so tell Matrix clients to render them as 200x200 to match Signal
|
||||
// https://github.com/signalapp/Signal-Desktop/blob/v7.77.0-beta.1/ts/components/conversation/Message.dom.tsx#L135
|
||||
// Signal stickers are 512x512, so tell Matrix clients to render them as 256x256
|
||||
if converted.Content.Info.Width == 512 && converted.Content.Info.Height == 512 {
|
||||
converted.Content.Info.Width = 200
|
||||
converted.Content.Info.Height = 200
|
||||
converted.Content.Info.Width = 256
|
||||
converted.Content.Info.Height = 256
|
||||
}
|
||||
converted.Content.Body = sticker.GetEmoji()
|
||||
if len(sticker.GetPackId()) == PackIDLength && len(sticker.GetPackKey()) == PackKeyLength && !bytes.Equal(sticker.GetPackId(), zeroPackID) {
|
||||
converted.Content.Info.BridgedSticker = &event.BridgedSticker{
|
||||
Network: StickerSourceID,
|
||||
ID: strconv.FormatUint(uint64(sticker.GetStickerId()), 10),
|
||||
Emoji: sticker.GetEmoji(),
|
||||
PackURL: fmt.Sprintf(PackURLFormat, sticker.GetPackId(), sticker.GetPackKey()),
|
||||
}
|
||||
}
|
||||
converted.Type = event.EventSticker
|
||||
converted.Content.MsgType = ""
|
||||
if converted.Extra == nil {
|
||||
converted.Extra = map[string]any{}
|
||||
}
|
||||
// TODO fetch full pack metadata like the old bridge did?
|
||||
converted.Extra["fi.mau.signal.sticker"] = map[string]any{
|
||||
"id": sticker.GetStickerId(),
|
||||
"emoji": sticker.GetEmoji(),
|
||||
"pack": map[string]any{
|
||||
"id": sticker.GetPackId(),
|
||||
"key": sticker.GetPackKey(),
|
||||
},
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) downloadSignalLongText(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*string, error) {
|
||||
data, err := mc.downloadAttachment(ctx, att, attMap, nil)
|
||||
data, err := mc.downloadAttachment(ctx, att, attMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -508,9 +491,7 @@ func checkIfAttachmentExists(att *signalpb.AttachmentPointer, attMap AttachmentM
|
|||
return nil
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) downloadAttachment(
|
||||
ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap, into *os.File,
|
||||
) ([]byte, error) {
|
||||
func (mc *MessageConverter) downloadAttachment(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) ([]byte, error) {
|
||||
if err := checkIfAttachmentExists(att, attMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -521,19 +502,19 @@ func (mc *MessageConverter) downloadAttachment(
|
|||
plaintextHash = target.GetPlaintextHash()
|
||||
}
|
||||
}
|
||||
return signalmeow.DownloadAttachmentWithPointer(ctx, att, plaintextHash, into)
|
||||
return signalmeow.DownloadAttachmentWithPointer(ctx, att, plaintextHash)
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*bridgev2.ConvertedMessagePart, error) {
|
||||
fileName := att.GetFileName()
|
||||
content := &event.MessageEventContent{
|
||||
Body: att.GetFileName(),
|
||||
Info: &event.FileInfo{
|
||||
MimeType: att.GetContentType(),
|
||||
Width: int(att.GetWidth()),
|
||||
Height: int(att.GetHeight()),
|
||||
Size: int(att.GetSize()),
|
||||
Width: int(att.GetWidth()),
|
||||
Height: int(att.GetHeight()),
|
||||
Size: int(att.GetSize()),
|
||||
},
|
||||
}
|
||||
mimeType := att.GetContentType()
|
||||
if err := checkIfAttachmentExists(att, attMap); err != nil {
|
||||
return nil, err
|
||||
} else if mc.DirectMedia {
|
||||
|
|
@ -560,7 +541,25 @@ func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalp
|
|||
}
|
||||
content.URL, err = mc.Bridge.Matrix.GenerateContentURI(ctx, mediaID)
|
||||
} else {
|
||||
err = mc.actuallyReuploadAttachment(ctx, content, att, attMap)
|
||||
data, err := mc.downloadAttachment(ctx, att, attMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mimeType == "" {
|
||||
mimeType = http.DetectContentType(data)
|
||||
}
|
||||
if att.GetFlags()&uint32(signalpb.AttachmentPointer_VOICE_MESSAGE) != 0 && ffmpeg.Supported() {
|
||||
data, err = ffmpeg.ConvertBytes(ctx, data, ".ogg", []string{}, []string{"-c:a", "libopus"}, mimeType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert audio to ogg/opus: %w", err)
|
||||
}
|
||||
fileName += ".ogg"
|
||||
mimeType = "audio/ogg"
|
||||
content.MSC3245Voice = &event.MSC3245Voice{}
|
||||
// TODO include duration here (and in info) if there's some easy way to extract it with ffmpeg
|
||||
//content.MSC1767Audio = &event.MSC1767Audio{}
|
||||
}
|
||||
content.URL, content.File, err = getIntent(ctx).UploadMedia(ctx, getPortal(ctx).MXID, data, fileName, mimeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -569,7 +568,7 @@ func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalp
|
|||
content.Info.Blurhash = att.GetBlurHash()
|
||||
content.Info.AnoaBlurhash = att.GetBlurHash()
|
||||
}
|
||||
switch strings.Split(content.Info.MimeType, "/")[0] {
|
||||
switch strings.Split(mimeType, "/")[0] {
|
||||
case "image":
|
||||
content.MsgType = event.MsgImage
|
||||
case "video":
|
||||
|
|
@ -591,8 +590,10 @@ func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalp
|
|||
},
|
||||
}
|
||||
}
|
||||
content.Body = fileName
|
||||
content.Info.MimeType = mimeType
|
||||
if content.Body == "" {
|
||||
content.Body = strings.TrimPrefix(string(content.MsgType), "m.") + exmime.ExtensionFromMimetype(content.Info.MimeType)
|
||||
content.Body = strings.TrimPrefix(string(content.MsgType), "m.") + exmime.ExtensionFromMimetype(mimeType)
|
||||
}
|
||||
return &bridgev2.ConvertedMessagePart{
|
||||
Type: event.EventMessage,
|
||||
|
|
@ -600,186 +601,3 @@ func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalp
|
|||
Extra: extra,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) actuallyReuploadAttachment(
|
||||
ctx context.Context,
|
||||
content *event.MessageEventContent,
|
||||
att *signalpb.AttachmentPointer,
|
||||
attMap AttachmentMap,
|
||||
) (err error) {
|
||||
convertVoice := att.GetFlags()&uint32(signalpb.AttachmentPointer_VOICE_MESSAGE) != 0 && ffmpeg.Supported()
|
||||
requireFile := convertVoice
|
||||
content.URL, content.File, err = getIntent(ctx).UploadMediaStream(ctx, getPortal(ctx).MXID, int64(att.GetSize()), requireFile, func(file io.Writer) (*bridgev2.FileStreamResult, error) {
|
||||
osFile, ok := file.(*os.File)
|
||||
inMemData, err := mc.downloadAttachment(ctx, att, attMap, osFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !ok {
|
||||
if content.Info.MimeType == "" {
|
||||
content.Info.MimeType = http.DetectContentType(inMemData)
|
||||
}
|
||||
_, err = file.Write(inMemData)
|
||||
return &bridgev2.FileStreamResult{
|
||||
FileName: content.Body,
|
||||
MimeType: content.Info.MimeType,
|
||||
}, err
|
||||
}
|
||||
if content.Info.MimeType == "" {
|
||||
header := make([]byte, 512)
|
||||
_, err = osFile.ReadAt(header, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file header for MIME type detection: %w", err)
|
||||
} else {
|
||||
content.Info.MimeType = http.DetectContentType(header)
|
||||
}
|
||||
}
|
||||
var replFile string
|
||||
if att.GetFlags()&uint32(signalpb.AttachmentPointer_VOICE_MESSAGE) != 0 && ffmpeg.Supported() {
|
||||
replFile, err = ffmpeg.ConvertPath(ctx, osFile.Name(), ".ogg", []string{}, []string{"-c:a", "libopus"}, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert audio to ogg/opus: %w", err)
|
||||
}
|
||||
if content.Body == "" {
|
||||
content.Body = "Voice message.ogg"
|
||||
} else {
|
||||
content.Body += ".ogg"
|
||||
}
|
||||
content.Info.MimeType = "audio/ogg"
|
||||
content.MSC3245Voice = &event.MSC3245Voice{}
|
||||
// TODO include duration here (and in info) if there's some easy way to extract it with ffmpeg
|
||||
//content.MSC1767Audio = &event.MSC1767Audio{}
|
||||
}
|
||||
return &bridgev2.FileStreamResult{
|
||||
ReplacementFile: replFile,
|
||||
FileName: content.Body,
|
||||
MimeType: content.Info.MimeType,
|
||||
}, nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) convertPollCreateToMatrix(create *signalpb.DataMessage_PollCreate) *bridgev2.ConvertedMessagePart {
|
||||
evtType := event.EventMessage
|
||||
if mc.ExtEvPolls {
|
||||
evtType = event.EventUnstablePollStart
|
||||
}
|
||||
maxChoices := 1
|
||||
if create.GetAllowMultiple() {
|
||||
maxChoices = len(create.GetOptions())
|
||||
}
|
||||
msc3381Answers := make([]map[string]any, len(create.GetOptions()))
|
||||
optionsListText := make([]string, len(create.GetOptions()))
|
||||
optionsListHTML := make([]string, len(create.GetOptions()))
|
||||
for i, option := range create.GetOptions() {
|
||||
msc3381Answers[i] = map[string]any{
|
||||
"id": strconv.Itoa(i),
|
||||
"org.matrix.msc1767.text": option,
|
||||
}
|
||||
optionsListText[i] = fmt.Sprintf("%d. %s\n", i+1, option)
|
||||
optionsListHTML[i] = fmt.Sprintf("<li>%s</li>", event.TextToHTML(option))
|
||||
}
|
||||
body := fmt.Sprintf("%s\n\n%s\n\n(This message is a poll. Please open Signal to vote.)", create.GetQuestion(), strings.Join(optionsListText, "\n"))
|
||||
formattedBody := fmt.Sprintf("<p>%s</p><ol>%s</ol><p>(This message is a poll. Please open Signal to vote.)</p>", event.TextToHTML(create.GetQuestion()), strings.Join(optionsListHTML, ""))
|
||||
return &bridgev2.ConvertedMessagePart{
|
||||
Type: evtType,
|
||||
Content: &event.MessageEventContent{
|
||||
MsgType: event.MsgText,
|
||||
Body: body,
|
||||
Format: event.FormatHTML,
|
||||
FormattedBody: formattedBody,
|
||||
},
|
||||
Extra: map[string]any{
|
||||
"fi.mau.signal.poll": map[string]any{
|
||||
"question": create.GetQuestion(),
|
||||
"allow_multiple": create.GetAllowMultiple(),
|
||||
"options": create.GetOptions(),
|
||||
},
|
||||
"org.matrix.msc1767.message": []map[string]any{
|
||||
{"mimetype": "text/html", "body": formattedBody},
|
||||
{"mimetype": "text/plain", "body": body},
|
||||
},
|
||||
"org.matrix.msc3381.poll.start": map[string]any{
|
||||
"kind": "org.matrix.msc3381.poll.disclosed",
|
||||
"max_selections": maxChoices,
|
||||
"question": map[string]any{
|
||||
"org.matrix.msc1767.text": create.GetQuestion(),
|
||||
},
|
||||
"answers": msc3381Answers,
|
||||
},
|
||||
},
|
||||
DBMetadata: nil,
|
||||
DontBridge: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) convertPollTerminateToMatrix(ctx context.Context, senderACI uuid.UUID, terminate *signalpb.DataMessage_PollTerminate) *bridgev2.ConvertedMessagePart {
|
||||
pollMessageID := signalid.MakeMessageID(senderACI, terminate.GetTargetSentTimestamp())
|
||||
pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "")
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get poll terminate target message")
|
||||
return &bridgev2.ConvertedMessagePart{
|
||||
Type: event.EventUnstablePollEnd,
|
||||
Content: &event.MessageEventContent{},
|
||||
DontBridge: true,
|
||||
}
|
||||
}
|
||||
return &bridgev2.ConvertedMessagePart{
|
||||
Type: event.EventUnstablePollEnd,
|
||||
Content: &event.MessageEventContent{
|
||||
RelatesTo: &event.RelatesTo{
|
||||
Type: event.RelReference,
|
||||
EventID: pollMessage.MXID,
|
||||
},
|
||||
},
|
||||
Extra: map[string]any{
|
||||
"org.matrix.msc3381.poll.end": map[string]any{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var invalidPollVote = &bridgev2.ConvertedMessagePart{
|
||||
Type: event.EventUnstablePollResponse,
|
||||
Content: &event.MessageEventContent{},
|
||||
DontBridge: true,
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) convertPollVoteToMatrix(ctx context.Context, vote *signalpb.DataMessage_PollVote) *bridgev2.ConvertedMessagePart {
|
||||
if len(vote.GetTargetAuthorAciBinary()) != 16 {
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Str("author_aci_b64", base64.StdEncoding.EncodeToString(vote.GetTargetAuthorAciBinary())).
|
||||
Msg("Invalid author ACI in poll vote")
|
||||
return invalidPollVote
|
||||
}
|
||||
pollMessageID := signalid.MakeMessageID(uuid.UUID(vote.GetTargetAuthorAciBinary()), vote.GetTargetSentTimestamp())
|
||||
pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "")
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get poll vote target message")
|
||||
return invalidPollVote
|
||||
} else if pollMessage == nil {
|
||||
zerolog.Ctx(ctx).Warn().Msg("Poll vote target message not found")
|
||||
return invalidPollVote
|
||||
}
|
||||
mxOptionIDs := pollMessage.Metadata.(*signalid.MessageMetadata).MatrixPollOptionIDs
|
||||
optionIDs := make([]string, len(vote.GetOptionIndexes()))
|
||||
for i, optionIndex := range vote.GetOptionIndexes() {
|
||||
if int(optionIndex) < len(mxOptionIDs) {
|
||||
optionIDs[i] = mxOptionIDs[optionIndex]
|
||||
} else {
|
||||
optionIDs[i] = strconv.Itoa(int(optionIndex))
|
||||
}
|
||||
}
|
||||
return &bridgev2.ConvertedMessagePart{
|
||||
Type: event.EventUnstablePollResponse,
|
||||
Content: &event.MessageEventContent{
|
||||
RelatesTo: &event.RelatesTo{
|
||||
Type: event.RelReference,
|
||||
EventID: pollMessage.MXID,
|
||||
},
|
||||
},
|
||||
Extra: map[string]any{
|
||||
"org.matrix.msc3381.poll.response": map[string]any{
|
||||
"answers": optionIDs,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
// mautrix-signal - A Matrix-Signal puppeting bridge.
|
||||
// Copyright (C) 2026 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package msgconv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.mau.fi/util/emojishortcodes"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/database"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||
)
|
||||
|
||||
const StickerSourceID = "signal"
|
||||
const PackURLFormat = "https://signal.art/addstickers/#pack_id=%x&pack_key=%x"
|
||||
|
||||
const PackIDLength = 16
|
||||
const PackKeyLength = 32
|
||||
const PackURLLength = len(PackURLFormat) - len("%x")*2 + PackIDLength*2 + PackKeyLength*2
|
||||
|
||||
var zeroPackID = make([]byte, PackIDLength)
|
||||
|
||||
func ParseStickerMeta(info *event.BridgedSticker) *signalpb.DataMessage_Sticker {
|
||||
if info == nil || info.Network != StickerSourceID || len(info.PackURL) != PackURLLength {
|
||||
return nil
|
||||
}
|
||||
stickerID, err := strconv.ParseUint(info.ID, 10, 32)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
packID, packKey, err := parsePackURL(info.PackURL)
|
||||
if err != nil || len(packID) != PackIDLength || len(packKey) != PackKeyLength || bytes.Equal(packID, zeroPackID) {
|
||||
return nil
|
||||
}
|
||||
return &signalpb.DataMessage_Sticker{
|
||||
PackId: packID,
|
||||
PackKey: packKey,
|
||||
StickerId: proto.Uint32(uint32(stickerID)),
|
||||
Emoji: &info.Emoji,
|
||||
}
|
||||
}
|
||||
|
||||
func parsePackURL(rawURL string) (packID, packKey []byte, err error) {
|
||||
parsed, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid URL: %w", err)
|
||||
} else if parsed.Host != "signal.art" || !strings.HasPrefix(parsed.Path, "/addstickers") {
|
||||
return nil, nil, fmt.Errorf("invalid host or path in URL")
|
||||
}
|
||||
q, err := url.ParseQuery(parsed.Fragment)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid URL fragment: %w", err)
|
||||
}
|
||||
packID, err = hex.DecodeString(q.Get("pack_id"))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid pack ID in URL: %w", err)
|
||||
}
|
||||
packKey, err = hex.DecodeString(q.Get("pack_key"))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid pack key in URL: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) DownloadImagePack(ctx context.Context, url string) (*bridgev2.ImportedImagePack, error) {
|
||||
packID, packKey, err := parsePackURL(url)
|
||||
if err != nil {
|
||||
return nil, bridgev2.WrapRespErr(err, mautrix.MNotFound)
|
||||
}
|
||||
manifest, err := signalmeow.DownloadStickerPackManifest(ctx, packID, packKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download sticker pack manifest: %w", err)
|
||||
}
|
||||
topLevelExtra := map[string]any{
|
||||
"fi.mau.signal.stickerpack": map[string]any{
|
||||
"pack_id": hex.EncodeToString(packID),
|
||||
"pack_key": hex.EncodeToString(packKey),
|
||||
},
|
||||
}
|
||||
content := &event.ImagePackEventContent{
|
||||
Images: make(map[string]*event.ImagePackImage, len(manifest.Stickers)),
|
||||
Metadata: event.ImagePackMetadata{
|
||||
DisplayName: manifest.GetTitle(),
|
||||
AvatarURL: "",
|
||||
Usage: []event.ImagePackUsage{event.ImagePackUsageSticker},
|
||||
Attribution: manifest.GetAuthor(),
|
||||
BridgedPack: &event.BridgedStickerPack{
|
||||
Network: StickerSourceID,
|
||||
URL: fmt.Sprintf(PackURLFormat, packID, packKey),
|
||||
},
|
||||
},
|
||||
}
|
||||
imagesByID := make(map[uint32]id.ContentURIString, len(manifest.Stickers))
|
||||
uploadImage := func(sticker *signalpb.Pack_Sticker) (id.ContentURIString, error) {
|
||||
stickerID := sticker.GetId()
|
||||
existing, ok := imagesByID[stickerID]
|
||||
if ok {
|
||||
return existing, nil
|
||||
}
|
||||
var mxc id.ContentURIString
|
||||
if mc.DirectMedia {
|
||||
mediaID, err := signalid.DirectMediaSticker{
|
||||
PackID: packID,
|
||||
PackKey: packKey,
|
||||
StickerID: stickerID,
|
||||
}.AsMediaID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create media ID for sticker %d: %w", stickerID, err)
|
||||
}
|
||||
mxc, err = mc.Bridge.Matrix.GenerateContentURI(ctx, mediaID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate content URI for sticker %d: %w", stickerID, err)
|
||||
}
|
||||
} else {
|
||||
dbKey := database.Key(fmt.Sprintf("stickercache:%x:%d", packID, stickerID))
|
||||
if cached := mc.Bridge.DB.KV.Get(ctx, dbKey); cached != "" {
|
||||
mxc = id.ContentURIString(cached)
|
||||
imagesByID[stickerID] = mxc
|
||||
return mxc, nil
|
||||
}
|
||||
data, err := signalmeow.DownloadStickerPackItem(ctx, packID, packKey, stickerID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to download sticker %d: %w", stickerID, err)
|
||||
}
|
||||
mxc, _, err = mc.Bridge.Bot.UploadMedia(ctx, "", data, "", sticker.GetContentType())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to upload sticker %d: %w", stickerID, err)
|
||||
}
|
||||
mc.Bridge.DB.KV.Set(ctx, dbKey, string(mxc))
|
||||
}
|
||||
imagesByID[stickerID] = mxc
|
||||
return mxc, nil
|
||||
}
|
||||
for _, sticker := range manifest.Stickers {
|
||||
mxc, err := uploadImage(sticker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shortcode := emojishortcodes.Get(sticker.GetEmoji())
|
||||
realShortcode := shortcode
|
||||
i := 2
|
||||
for _, alreadyExists := content.Images[realShortcode]; alreadyExists; i++ {
|
||||
realShortcode = fmt.Sprintf("%s_%d", shortcode, i)
|
||||
}
|
||||
content.Images[realShortcode] = &event.ImagePackImage{
|
||||
URL: mxc,
|
||||
Body: sticker.GetEmoji(),
|
||||
Info: &event.FileInfo{
|
||||
MimeType: sticker.GetContentType(),
|
||||
Width: 200,
|
||||
Height: 200,
|
||||
BridgedSticker: &event.BridgedSticker{
|
||||
Network: StickerSourceID,
|
||||
ID: strconv.FormatUint(uint64(sticker.GetId()), 10),
|
||||
Emoji: sticker.GetEmoji(),
|
||||
PackURL: content.Metadata.BridgedPack.URL,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if manifest.Cover != nil {
|
||||
content.Metadata.AvatarURL, err = uploadImage(manifest.Cover)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload sticker pack cover: %w", err)
|
||||
}
|
||||
}
|
||||
return &bridgev2.ImportedImagePack{
|
||||
Content: content,
|
||||
Extra: topLevelExtra,
|
||||
Shortcode: hex.EncodeToString(packID),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -48,7 +48,6 @@ type MessageConverter struct {
|
|||
LocationFormat string
|
||||
DisappearViewOnce bool
|
||||
DirectMedia bool
|
||||
ExtEvPolls bool
|
||||
}
|
||||
|
||||
func NewMessageConverter(br *bridgev2.Bridge) *MessageConverter {
|
||||
|
|
@ -78,7 +77,7 @@ func NewMessageConverter(br *bridgev2.Bridge) *MessageConverter {
|
|||
GetUUIDFromMXID: func(ctx context.Context, userID id.UserID) uuid.UUID {
|
||||
parsed, ok := br.Matrix.ParseGhostMXID(userID)
|
||||
if ok {
|
||||
u, _ := signalid.ParseUserID(parsed)
|
||||
u, _ := uuid.Parse(string(parsed))
|
||||
return u
|
||||
}
|
||||
user, _ := br.GetExistingUserByMXID(ctx, userID)
|
||||
|
|
@ -86,7 +85,7 @@ func NewMessageConverter(br *bridgev2.Bridge) *MessageConverter {
|
|||
if user != nil {
|
||||
preferredLogin, _, _ := getPortal(ctx).FindPreferredLogin(ctx, user, true)
|
||||
if preferredLogin != nil {
|
||||
u, _ := signalid.ParseUserLoginID(preferredLogin.ID)
|
||||
u, _ := uuid.Parse(string(preferredLogin.ID))
|
||||
return u
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/exp/maps"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
|
@ -86,27 +85,15 @@ func Parse(ctx context.Context, message string, ranges []*signalpb.BodyRange, pa
|
|||
Start: int(*r.Start),
|
||||
Length: int(*r.Length),
|
||||
}.TruncateEnd(maxLength)
|
||||
var mentionACI uuid.UUID
|
||||
switch rv := r.GetAssociatedValue().(type) {
|
||||
case *signalpb.BodyRange_Style_:
|
||||
br.Value = Style(rv.Style)
|
||||
case *signalpb.BodyRange_MentionAci:
|
||||
var err error
|
||||
mentionACI, err = uuid.Parse(rv.MentionAci)
|
||||
parsed, err := uuid.Parse(rv.MentionAci)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
case *signalpb.BodyRange_MentionAciBinary:
|
||||
if len(rv.MentionAciBinary) != 16 {
|
||||
continue
|
||||
}
|
||||
mentionACI = uuid.UUID(rv.MentionAciBinary)
|
||||
default:
|
||||
zerolog.Ctx(ctx).Warn().Type("value_type", rv).Msg("Unsupported body range type")
|
||||
continue
|
||||
}
|
||||
if mentionACI != uuid.Nil {
|
||||
userInfo := params.GetUserInfo(ctx, mentionACI)
|
||||
userInfo := params.GetUserInfo(ctx, parsed)
|
||||
if userInfo.MXID == "" {
|
||||
continue
|
||||
}
|
||||
|
|
@ -115,7 +102,7 @@ func Parse(ctx context.Context, message string, ranges []*signalpb.BodyRange, pa
|
|||
// Maybe use NewUTF16String and do index replacements for the plaintext body too,
|
||||
// or just replace the plaintext body by parsing the generated HTML.
|
||||
content.Body = strings.Replace(content.Body, "\uFFFC", userInfo.Name, 1)
|
||||
br.Value = Mention{UserInfo: userInfo, UUID: mentionACI}
|
||||
br.Value = Mention{UserInfo: userInfo, UUID: parsed}
|
||||
}
|
||||
lrt.Add(br)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ func (m Mention) String() string {
|
|||
}
|
||||
|
||||
func (m Mention) Proto() signalpb.BodyRangeAssociatedValue {
|
||||
return &signalpb.BodyRange_MentionAciBinary{
|
||||
MentionAciBinary: m.UUID[:],
|
||||
return &signalpb.BodyRange_MentionAci{
|
||||
MentionAci: m.UUID.String(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,11 @@ type PortalMetadata struct {
|
|||
}
|
||||
|
||||
type MessageMetadata struct {
|
||||
ContainsAttachments bool `json:"contains_attachments,omitempty"`
|
||||
MatrixPollOptionIDs []string `json:"matrix_poll_option_ids,omitempty"`
|
||||
ContainsAttachments bool `json:"contains_attachments,omitempty"`
|
||||
}
|
||||
|
||||
type UserLoginMetadata struct {
|
||||
ChatsSynced bool `json:"chats_synced,omitempty"`
|
||||
LastContactSync jsontime.UnixMilli `json:"last_contact_sync,omitempty"`
|
||||
ChatsSynced bool `json:"chats_synced,omitempty"`
|
||||
}
|
||||
|
||||
type GhostMetadata struct {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ const (
|
|||
directMediaTypeGroupAvatar directMediaType = 1
|
||||
directMediaTypeProfileAvatar directMediaType = 2
|
||||
directMediaTypePlaintextDigestAttachment directMediaType = 3
|
||||
directMediaTypeSticker directMediaType = 4
|
||||
)
|
||||
|
||||
type DirectMediaInfo interface {
|
||||
|
|
@ -45,7 +44,6 @@ var (
|
|||
_ DirectMediaInfo = (*DirectMediaAttachment)(nil)
|
||||
_ DirectMediaInfo = (*DirectMediaGroupAvatar)(nil)
|
||||
_ DirectMediaInfo = (*DirectMediaProfileAvatar)(nil)
|
||||
_ DirectMediaInfo = (*DirectMediaSticker)(nil)
|
||||
)
|
||||
|
||||
type DirectMediaAttachment struct {
|
||||
|
|
@ -129,30 +127,6 @@ func (m DirectMediaProfileAvatar) AsMediaID() (mediaID networkid.MediaID, err er
|
|||
return networkid.MediaID(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
type DirectMediaSticker struct {
|
||||
PackID []byte
|
||||
PackKey []byte
|
||||
StickerID uint32
|
||||
}
|
||||
|
||||
const packIDLen = 16
|
||||
const packKeyLen = 32
|
||||
const directMediaStickerLen = 1 + packIDLen + packKeyLen + 4
|
||||
|
||||
func (m DirectMediaSticker) AsMediaID() (mediaID networkid.MediaID, err error) {
|
||||
if len(m.PackID) != packIDLen {
|
||||
return nil, fmt.Errorf("invalid pack ID length: %d", len(m.PackID))
|
||||
} else if len(m.PackKey) != packKeyLen {
|
||||
return nil, fmt.Errorf("invalid pack key length: %d", len(m.PackKey))
|
||||
}
|
||||
mediaID = make(networkid.MediaID, directMediaStickerLen)
|
||||
mediaID[0] = byte(directMediaTypeSticker)
|
||||
copy(mediaID[1:], m.PackID)
|
||||
copy(mediaID[1+packIDLen:], m.PackKey)
|
||||
binary.BigEndian.PutUint32(mediaID[1+packIDLen+packKeyLen:], m.StickerID)
|
||||
return mediaID, nil
|
||||
}
|
||||
|
||||
func ParseDirectMediaInfo(mediaID networkid.MediaID) (_ DirectMediaInfo, err error) {
|
||||
mediaIDLen := len(mediaID)
|
||||
if mediaIDLen == 0 {
|
||||
|
|
@ -226,15 +200,6 @@ func ParseDirectMediaInfo(mediaID networkid.MediaID) (_ DirectMediaInfo, err err
|
|||
info.ProfileAvatarPath = string(profileAvatarPath)
|
||||
}
|
||||
return &info, nil
|
||||
case directMediaTypeSticker:
|
||||
var info DirectMediaSticker
|
||||
if len(mediaID) != directMediaStickerLen {
|
||||
return info, fmt.Errorf("invalid media ID length for sticker: %d", len(mediaID))
|
||||
}
|
||||
info.PackID = mediaID[1 : 1+packIDLen]
|
||||
info.PackKey = mediaID[1+packIDLen : 1+packIDLen+packKeyLen]
|
||||
info.StickerID = binary.BigEndian.Uint32(mediaID[1+packIDLen+packKeyLen:])
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid direct media type %d", mediaType)
|
||||
|
|
|
|||
|
|
@ -31,11 +31,8 @@ import (
|
|||
"math"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/fallocate"
|
||||
"go.mau.fi/util/pkcs7"
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
|
|
@ -62,54 +59,26 @@ var ErrInvalidMACForAttachment = errors.New("invalid MAC for attachment")
|
|||
var ErrInvalidDigestForAttachment = errors.New("invalid digest for attachment")
|
||||
var ErrAttachmentNotFound = errors.New("attachment not found on server")
|
||||
|
||||
func DownloadAttachmentWithPointer(ctx context.Context, a *signalpb.AttachmentPointer, plaintextHash []byte, into *os.File) ([]byte, error) {
|
||||
func DownloadAttachmentWithPointer(ctx context.Context, a *signalpb.AttachmentPointer, plaintextHash []byte) ([]byte, error) {
|
||||
digest := a.GetDigest()
|
||||
plaintextDigest := false
|
||||
if digest == nil && plaintextHash != nil {
|
||||
digest = plaintextHash
|
||||
plaintextDigest = true
|
||||
}
|
||||
return DownloadAttachment(
|
||||
ctx, a.GetCdnId(), a.GetCdnKey(), a.GetCdnNumber(), a.Key, digest, plaintextDigest, a.GetSize(), into,
|
||||
)
|
||||
return DownloadAttachment(ctx, a.GetCdnId(), a.GetCdnKey(), a.GetCdnNumber(), a.Key, digest, plaintextDigest, a.GetSize())
|
||||
}
|
||||
|
||||
func DownloadAttachment(
|
||||
ctx context.Context,
|
||||
cdnID uint64,
|
||||
cdnKey string,
|
||||
cdnNumber uint32,
|
||||
key, digest []byte,
|
||||
plaintextDigest bool,
|
||||
size uint32,
|
||||
into *os.File,
|
||||
) ([]byte, error) {
|
||||
resp, err := web.GetAttachment(ctx, getAttachmentPath(cdnID, cdnKey), cdnNumber)
|
||||
func DownloadAttachment(ctx context.Context, cdnID uint64, cdnKey string, cdnNumber uint32, key, digest []byte, plaintextDigest bool, size uint32) ([]byte, error) {
|
||||
path := getAttachmentPath(cdnID, cdnKey)
|
||||
resp, err := web.GetAttachment(ctx, path, cdnNumber, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
bodyReader := resp.Body
|
||||
defer bodyReader.Close()
|
||||
|
||||
var body []byte
|
||||
var downloadedSize int64
|
||||
if resp.StatusCode > 400 {
|
||||
body, err = io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
} else if into == nil {
|
||||
if resp.ContentLength > 0 {
|
||||
body = make([]byte, resp.ContentLength)
|
||||
_, err = io.ReadFull(resp.Body, body)
|
||||
} else {
|
||||
body, err = io.ReadAll(http.MaxBytesReader(nil, resp.Body, max(int64(size), 32*1024)*2))
|
||||
}
|
||||
} else {
|
||||
err = fallocate.Fallocate(into, int(resp.ContentLength))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pre-allocate file for attachment: %w", err)
|
||||
}
|
||||
downloadedSize, err = io.Copy(into, resp.Body)
|
||||
}
|
||||
body, err := io.ReadAll(bodyReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -125,27 +94,12 @@ func DownloadAttachment(
|
|||
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
if into != nil {
|
||||
if _, err = into.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("failed to seek attachment file after downloading: %w", err)
|
||||
}
|
||||
return nil, decryptAttachmentFile(into, downloadedSize, key, digest, plaintextDigest, size)
|
||||
}
|
||||
return decryptAttachment(body, key, digest, plaintextDigest, size)
|
||||
}
|
||||
|
||||
const MACLength = 32
|
||||
const IVLength = 16
|
||||
|
||||
func macAndAESDecrypt(body, key []byte) ([]byte, error) {
|
||||
l := len(body) - MACLength
|
||||
if !verifyMAC(key[MACLength:], body[:l], body[l:]) {
|
||||
return nil, ErrInvalidMACForAttachment
|
||||
}
|
||||
|
||||
return aesDecrypt(key[:MACLength], body[:l])
|
||||
}
|
||||
|
||||
func decryptAttachment(body, key, digest []byte, plaintextDigest bool, size uint32) ([]byte, error) {
|
||||
if !plaintextDigest {
|
||||
hash := sha256.Sum256(body)
|
||||
|
|
@ -153,7 +107,12 @@ func decryptAttachment(body, key, digest []byte, plaintextDigest bool, size uint
|
|||
return nil, ErrInvalidDigestForAttachment
|
||||
}
|
||||
}
|
||||
decrypted, err := macAndAESDecrypt(body, key)
|
||||
l := len(body) - MACLength
|
||||
if !verifyMAC(key[MACLength:], body[:l], body[l:]) {
|
||||
return nil, ErrInvalidMACForAttachment
|
||||
}
|
||||
|
||||
decrypted, err := aesDecrypt(key[:MACLength], body[:l])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -170,59 +129,6 @@ func decryptAttachment(body, key, digest []byte, plaintextDigest bool, size uint
|
|||
return decrypted, nil
|
||||
}
|
||||
|
||||
func decryptAttachmentFile(file *os.File, downloadedSize int64, key, digest []byte, plaintextDigest bool, size uint32) error {
|
||||
if !plaintextDigest {
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(hasher, file); err != nil {
|
||||
return fmt.Errorf("failed to hash attachment file: %w", err)
|
||||
} else if !hmac.Equal(hasher.Sum(nil), digest) {
|
||||
return ErrInvalidDigestForAttachment
|
||||
} else if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek attachment file after hashing: %w", err)
|
||||
}
|
||||
}
|
||||
mac := make([]byte, MACLength)
|
||||
n, err := file.ReadAt(mac, downloadedSize-MACLength)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read MAC from attachment file: %w", err)
|
||||
} else if n != MACLength {
|
||||
return fmt.Errorf("unexpected MAC length read from attachment file: %d", n)
|
||||
}
|
||||
hasher := hmac.New(sha256.New, key[MACLength:])
|
||||
_, err = io.CopyN(hasher, file, downloadedSize-MACLength)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to hash attachment file for MAC verification: %w", err)
|
||||
} else if !hmac.Equal(hasher.Sum(nil), mac) {
|
||||
return ErrInvalidMACForAttachment
|
||||
} else if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek attachment file after verifying mac: %w", err)
|
||||
}
|
||||
|
||||
decryptedSize, err := aesDecryptFile(key[:MACLength], file, downloadedSize-MACLength)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if decryptedSize < int64(size) {
|
||||
return fmt.Errorf("decrypted attachment length %d < expected %d", decryptedSize, size)
|
||||
} else if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek attachment file after decrypting: %w", err)
|
||||
}
|
||||
err = file.Truncate(int64(size))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to truncate attachment file to expected size: %w", err)
|
||||
}
|
||||
if plaintextDigest {
|
||||
hasher = sha256.New()
|
||||
if _, err = io.Copy(hasher, file); err != nil {
|
||||
return fmt.Errorf("failed to hash decrypted attachment file: %w", err)
|
||||
} else if !hmac.Equal(hasher.Sum(nil), digest) {
|
||||
return fmt.Errorf("%w (plaintext hash)", ErrInvalidDigestForAttachment)
|
||||
} else if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek attachment file after hashing plaintext: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type attachmentV4UploadAttributes struct {
|
||||
Cdn uint32 `json:"cdn"`
|
||||
Key string `json:"key"`
|
||||
|
|
@ -245,14 +151,6 @@ func extend(data []byte, paddedLen int) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
func macAndAESEncrypt(keys, plaintext []byte) ([]byte, error) {
|
||||
encrypted, err := aesEncrypt(keys[:32], plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appendMAC(keys[32:], encrypted), nil
|
||||
}
|
||||
|
||||
func (cli *Client) UploadAttachment(ctx context.Context, body []byte) (*signalpb.AttachmentPointer, error) {
|
||||
log := zerolog.Ctx(ctx).With().Str("func", "upload attachment").Logger()
|
||||
keys := random.Bytes(64) // combined AES and MAC keys
|
||||
|
|
@ -268,20 +166,23 @@ func (cli *Client) UploadAttachment(ctx context.Context, body []byte) (*signalpb
|
|||
}
|
||||
body = extend(body, paddedLen)
|
||||
|
||||
encryptedWithMAC, err := macAndAESEncrypt(keys, body)
|
||||
encrypted, err := aesEncrypt(keys[:32], body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedWithMAC := appendMAC(keys[32:], encrypted)
|
||||
|
||||
// Get upload attributes from Signal server
|
||||
attributesPath := "/v4/attachments/form/upload"
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodGet, attributesPath, nil, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
opts := &web.HTTPReqOpt{Username: &username, Password: &password}
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, attributesPath, opts)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to request upload attributes")
|
||||
return nil, fmt.Errorf("failed to request upload attributes: %w", err)
|
||||
}
|
||||
var uploadAttributes attachmentV4UploadAttributes
|
||||
err = web.DecodeWSResponseBody(ctx, &uploadAttributes, resp)
|
||||
err = web.DecodeHTTPResponseBody(ctx, &uploadAttributes, resp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to decode upload attributes")
|
||||
return nil, fmt.Errorf("failed to decode upload attributes: %w", err)
|
||||
|
|
@ -291,7 +192,7 @@ func (cli *Client) UploadAttachment(ctx context.Context, body []byte) (*signalpb
|
|||
err = cli.uploadAttachmentTUS(ctx, uploadAttributes, encryptedWithMAC)
|
||||
} else {
|
||||
log.Trace().Msg("Using legacy upload")
|
||||
err = cli.uploadAttachmentLegacy(ctx, uploadAttributes, encryptedWithMAC)
|
||||
err = cli.uploadAttachmentLegacy(ctx, uploadAttributes, encryptedWithMAC, username, password)
|
||||
}
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to upload attachment")
|
||||
|
|
@ -317,17 +218,17 @@ func (cli *Client) uploadAttachmentLegacy(
|
|||
ctx context.Context,
|
||||
uploadAttributes attachmentV4UploadAttributes,
|
||||
encryptedWithMAC []byte,
|
||||
username string,
|
||||
password string,
|
||||
) error {
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
// Allocate attachment on CDN
|
||||
resp, err := web.SendHTTPRequest(ctx, "", http.MethodPost, "", &web.HTTPReqOpt{
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPost, "", &web.HTTPReqOpt{
|
||||
OverrideURL: uploadAttributes.SignedUploadLocation,
|
||||
ContentType: web.ContentTypeOctetStream,
|
||||
Headers: uploadAttributes.Headers,
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
})
|
||||
web.CloseBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send allocate request: %w", err)
|
||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
|
|
@ -335,14 +236,13 @@ func (cli *Client) uploadAttachmentLegacy(
|
|||
}
|
||||
|
||||
// Upload attachment to CDN
|
||||
resp, err = web.SendHTTPRequest(ctx, "", http.MethodPut, "", &web.HTTPReqOpt{
|
||||
resp, err = web.SendHTTPRequest(ctx, http.MethodPut, "", &web.HTTPReqOpt{
|
||||
OverrideURL: resp.Header.Get("Location"),
|
||||
Body: encryptedWithMAC,
|
||||
ContentType: web.ContentTypeOctetStream,
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
})
|
||||
web.CloseBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send upload request: %w", err)
|
||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
|
|
@ -360,13 +260,12 @@ func (cli *Client) uploadAttachmentTUS(
|
|||
uploadAttributes.Headers["Upload-Length"] = fmt.Sprintf("%d", len(encryptedWithMAC))
|
||||
uploadAttributes.Headers["Upload-Metadata"] = "filename " + base64.StdEncoding.EncodeToString([]byte(uploadAttributes.Key))
|
||||
|
||||
resp, err := web.SendHTTPRequest(ctx, "", http.MethodPost, "", &web.HTTPReqOpt{
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPost, "", &web.HTTPReqOpt{
|
||||
OverrideURL: uploadAttributes.SignedUploadLocation,
|
||||
Body: encryptedWithMAC,
|
||||
ContentType: web.ContentTypeOffsetOctetStream,
|
||||
Headers: uploadAttributes.Headers,
|
||||
})
|
||||
web.CloseBody(resp)
|
||||
// TODO actually support resuming on error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send upload request: %w", err)
|
||||
|
|
@ -381,17 +280,12 @@ func (cli *Client) uploadAttachmentTUS(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) UploadGroupAvatar(ctx context.Context, avatarBytes []byte, gid types.GroupIdentifier, groupMasterKey types.SerializedGroupMasterKey) (string, error) {
|
||||
func (cli *Client) UploadGroupAvatar(ctx context.Context, avatarBytes []byte, gid types.GroupIdentifier) (string, error) {
|
||||
log := zerolog.Ctx(ctx)
|
||||
if groupMasterKey == "" {
|
||||
var err error
|
||||
groupMasterKey, err = cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Could not get master key from group id")
|
||||
return "", err
|
||||
} else if groupMasterKey == "" {
|
||||
return "", fmt.Errorf("no master key found for group %s", gid)
|
||||
}
|
||||
groupMasterKey, err := cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Could not get master key from group id")
|
||||
return "", err
|
||||
}
|
||||
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyToBytes(groupMasterKey))
|
||||
if err != nil {
|
||||
|
|
@ -411,15 +305,14 @@ func (cli *Client) UploadGroupAvatar(ctx context.Context, avatarBytes []byte, gi
|
|||
}
|
||||
|
||||
// Get upload form from Signal server
|
||||
formPath := "/v2/groups/avatar/form"
|
||||
opts := &web.HTTPReqOpt{Username: &groupAuth.Username, Password: &groupAuth.Password, ContentType: web.ContentTypeProtobuf}
|
||||
resp, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, formPath, opts)
|
||||
formPath := "/v1/groups/avatar/form"
|
||||
opts := &web.HTTPReqOpt{Username: &groupAuth.Username, Password: &groupAuth.Password, ContentType: web.ContentTypeProtobuf, Host: web.StorageHostname}
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, formPath, opts)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error sending request fetching avatar upload form")
|
||||
return "", err
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
web.CloseBody(resp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error decoding response body fetching upload attributes")
|
||||
return "", err
|
||||
|
|
@ -445,11 +338,11 @@ func (cli *Client) UploadGroupAvatar(ctx context.Context, avatarBytes []byte, gi
|
|||
w.Close()
|
||||
|
||||
// Upload avatar to CDN
|
||||
resp, err = web.SendHTTPRequest(ctx, web.CDN1Hostname, http.MethodPost, "", &web.HTTPReqOpt{
|
||||
resp, err = web.SendHTTPRequest(ctx, http.MethodPost, "", &web.HTTPReqOpt{
|
||||
Body: requestBody.Bytes(),
|
||||
ContentType: web.ContentType(w.FormDataContentType()),
|
||||
Host: web.CDN1Hostname,
|
||||
})
|
||||
web.CloseBody(resp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error sending request uploading attachment")
|
||||
return "", err
|
||||
|
|
@ -478,56 +371,14 @@ func aesDecrypt(key, ciphertext []byte) ([]byte, error) {
|
|||
return nil, fmt.Errorf("ciphertext not multiple of AES blocksize (%d extra bytes)", len(ciphertext)%aes.BlockSize)
|
||||
}
|
||||
|
||||
iv := ciphertext[:IVLength]
|
||||
ciphertext = ciphertext[IVLength:]
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, ciphertext)
|
||||
return pkcs7.Unpad(ciphertext)
|
||||
}
|
||||
|
||||
func aesDecryptFile(key []byte, file *os.File, downloadedSize int64) (int64, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fileReader := io.LimitReader(file, downloadedSize)
|
||||
|
||||
if downloadedSize%aes.BlockSize != 0 {
|
||||
return 0, fmt.Errorf("ciphertext not multiple of AES blocksize (%d extra bytes)", downloadedSize%aes.BlockSize)
|
||||
}
|
||||
|
||||
iv := make([]byte, IVLength)
|
||||
n, err := fileReader.Read(iv)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to read IV from attachment file: %w", err)
|
||||
} else if n != IVLength {
|
||||
return 0, fmt.Errorf("unexpected IV length read from attachment file: %d", n)
|
||||
}
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
buf := make([]byte, 4096)
|
||||
var offset int64
|
||||
var pad byte
|
||||
for {
|
||||
n, err = fileReader.Read(buf)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return 0, fmt.Errorf("failed to read from attachment file: %w", err)
|
||||
}
|
||||
if n > 0 {
|
||||
mode.CryptBlocks(buf[:n], buf[:n])
|
||||
if _, err = file.WriteAt(buf[:n], offset); err != nil {
|
||||
return 0, fmt.Errorf("failed to write decrypted data to attachment file: %w", err)
|
||||
}
|
||||
offset += int64(n)
|
||||
pad = buf[n-1]
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pad := ciphertext[len(ciphertext)-1]
|
||||
if pad > aes.BlockSize {
|
||||
return 0, fmt.Errorf("pad value (%d) larger than AES blocksize (%d)", pad, aes.BlockSize)
|
||||
return nil, fmt.Errorf("pad value (%d) larger than AES blocksize (%d)", pad, aes.BlockSize)
|
||||
}
|
||||
return downloadedSize - int64(pad), nil
|
||||
return ciphertext[aes.BlockSize : len(ciphertext)-int(pad)], nil
|
||||
}
|
||||
|
||||
func appendMAC(key, body []byte) []byte {
|
||||
|
|
@ -542,11 +393,14 @@ func aesEncrypt(key, plaintext []byte) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
plaintext = pkcs7.Pad(plaintext, aes.BlockSize)
|
||||
pad := aes.BlockSize - len(plaintext)%aes.BlockSize
|
||||
plaintext = append(plaintext, bytes.Repeat([]byte{byte(pad)}, pad)...)
|
||||
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
iv := random.Bytes(16)
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(plaintext, plaintext)
|
||||
mode.CryptBlocks(ciphertext, plaintext)
|
||||
|
||||
return append(iv, plaintext...), nil
|
||||
return append(iv, ciphertext...), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ func (cli *Client) deriveTransferKeys() (aesKey, hmacKey [32]byte, err error) {
|
|||
}
|
||||
|
||||
func downloadTransferArchive(ctx context.Context, meta *TransferArchiveMetadata, writeTo io.Writer) error {
|
||||
resp, err := web.GetAttachment(ctx, getAttachmentPath(0, meta.Key), meta.CDN)
|
||||
resp, err := web.GetAttachment(ctx, getAttachmentPath(0, meta.Key), meta.CDN, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download transfer archive: %w", err)
|
||||
}
|
||||
|
|
@ -282,11 +282,7 @@ func (cli *Client) WaitForTransfer(ctx context.Context) (*TransferArchiveMetadat
|
|||
}
|
||||
reqDuration := time.Since(reqStart)
|
||||
if reqDuration < reqTimeout-10*time.Second {
|
||||
select {
|
||||
case <-time.After(15 * time.Second):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
time.Sleep(15 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -295,14 +291,21 @@ func (cli *Client) tryRequestTransferArchive(ctx context.Context, timeout time.D
|
|||
reqCtx, cancel := context.WithTimeout(ctx, timeout+15*time.Second)
|
||||
defer cancel()
|
||||
path := "/v1/devices/transfer_archive?timeout=" + strconv.Itoa(int(timeout.Seconds()))
|
||||
resp, err := cli.AuthedWS.SendRequest(reqCtx, http.MethodGet, path, nil, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
opts := &web.HTTPReqOpt{Username: &username, Password: &password}
|
||||
resp, err := web.SendHTTPRequest(reqCtx, http.MethodGet, path, opts)
|
||||
defer func() {
|
||||
if resp != nil && resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if resp.GetStatus() == http.StatusNoContent {
|
||||
} else if resp.StatusCode == http.StatusNoContent {
|
||||
return nil, nil
|
||||
} else if resp.GetStatus() != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code %d", resp.GetStatus())
|
||||
} else if err = json.Unmarshal(resp.Body, &respBody); err != nil {
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
} else if err = json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
} else {
|
||||
return respBody, nil
|
||||
|
|
|
|||
|
|
@ -18,21 +18,16 @@ package signalmeow
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exsync"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/events"
|
||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/store"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||
)
|
||||
|
||||
|
|
@ -40,16 +35,14 @@ type Client struct {
|
|||
Store *store.Device
|
||||
Log zerolog.Logger
|
||||
|
||||
senderCertificateWithE164 *libsignalgo.SenderCertificate
|
||||
senderCertificateNoE164 *libsignalgo.SenderCertificate
|
||||
senderCertificateCache sync.Mutex
|
||||
|
||||
sendCache *exsync.RingBuffer[sendCacheKey, *signalpb.Content]
|
||||
|
||||
GroupCache *GroupCache
|
||||
ProfileCache *ProfileCache
|
||||
LastContactRequestTime time.Time
|
||||
SyncContactsOnConnect bool
|
||||
SenderCertificateWithE164 *libsignalgo.SenderCertificate
|
||||
SenderCertificateNoE164 *libsignalgo.SenderCertificate
|
||||
GroupCredentials *GroupCredentials
|
||||
GroupCache *GroupCache
|
||||
ProfileCache *ProfileCache
|
||||
GroupCallCache *map[string]bool
|
||||
LastContactRequestTime time.Time
|
||||
SyncContactsOnConnect bool
|
||||
|
||||
encryptionLock sync.Mutex
|
||||
|
||||
|
|
@ -71,26 +64,6 @@ type Client struct {
|
|||
writeCallbackCounter chan time.Time
|
||||
}
|
||||
|
||||
// InMemorySendCacheSize specifies how large the cache for sent messages is, which is used to respond to retry receipts.
|
||||
// The cache is large because every group member will be listed separately.
|
||||
// 2k entries should hold at least 2 messages in max size groups.
|
||||
var InMemorySendCacheSize = 2048
|
||||
|
||||
func NewClient(device *store.Device, log zerolog.Logger, evtHandler func(events.SignalEvent) bool) *Client {
|
||||
return &Client{
|
||||
Store: device,
|
||||
Log: log,
|
||||
EventHandler: evtHandler,
|
||||
GroupCache: NewGroupCache(device.ACIServiceID()),
|
||||
ProfileCache: &ProfileCache{
|
||||
profiles: make(map[string]*types.Profile),
|
||||
errors: make(map[string]*error),
|
||||
lastFetched: make(map[string]time.Time),
|
||||
},
|
||||
sendCache: exsync.NewRingBuffer[sendCacheKey, *signalpb.Content](InMemorySendCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleEvent(evt events.SignalEvent) bool {
|
||||
return cli.EventHandler(evt)
|
||||
}
|
||||
|
|
@ -137,11 +110,3 @@ func (cli *Client) connectUnauthedWS(ctx context.Context) (chan web.SignalWebsoc
|
|||
func (cli *Client) IsLoggedIn() bool {
|
||||
return cli.Store != nil && cli.Store.IsDeviceLoggedIn()
|
||||
}
|
||||
|
||||
func (cli *Client) GetRemoteConfig(ctx context.Context) (json.RawMessage, error) {
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodGet, "/v2/config", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Body, web.DecodeWSResponseBody(ctx, nil, resp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import (
|
|||
)
|
||||
|
||||
func (cli *Client) StoreContactDetailsAsContact(ctx context.Context, contactDetails *signalpb.ContactDetails, avatar *[]byte) (*types.Recipient, error) {
|
||||
parsedUUID, err := ParseStringOrBinaryUUID(contactDetails.GetAci(), contactDetails.GetAciBinary())
|
||||
parsedUUID, err := uuid.Parse(contactDetails.GetAci())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||
)
|
||||
|
||||
func hmacSHA256(key, input []byte) []byte {
|
||||
|
|
@ -62,12 +63,18 @@ func (cli *Client) updateDeviceName(ctx context.Context, encryptedName []byte) e
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal device name update request: %w", err)
|
||||
}
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodPut, "/v1/accounts/name", reqData, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPut, "/v1/accounts/name", &web.HTTPReqOpt{
|
||||
Body: reqData,
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send device name update request: %w", err)
|
||||
}
|
||||
if resp.GetStatus() < 200 || resp.GetStatus() >= 300 {
|
||||
return fmt.Errorf("device name update request returned status %d", resp.GetStatus())
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("device name update request returned status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,17 +28,16 @@ type SignalEvent interface {
|
|||
isSignalEvent()
|
||||
}
|
||||
|
||||
func (*ChatEvent) isSignalEvent() {}
|
||||
func (*DecryptionError) isSignalEvent() {}
|
||||
func (*Receipt) isSignalEvent() {}
|
||||
func (*ReadSelf) isSignalEvent() {}
|
||||
func (*Call) isSignalEvent() {}
|
||||
func (*ContactList) isSignalEvent() {}
|
||||
func (*ACIFound) isSignalEvent() {}
|
||||
func (*DeleteForMe) isSignalEvent() {}
|
||||
func (*MessageRequestResponse) isSignalEvent() {}
|
||||
func (*QueueEmpty) isSignalEvent() {}
|
||||
func (*LoggedOut) isSignalEvent() {}
|
||||
func (*ChatEvent) isSignalEvent() {}
|
||||
func (*DecryptionError) isSignalEvent() {}
|
||||
func (*Receipt) isSignalEvent() {}
|
||||
func (*ReadSelf) isSignalEvent() {}
|
||||
func (*Call) isSignalEvent() {}
|
||||
func (*ContactList) isSignalEvent() {}
|
||||
func (*ACIFound) isSignalEvent() {}
|
||||
func (*DeleteForMe) isSignalEvent() {}
|
||||
func (*QueueEmpty) isSignalEvent() {}
|
||||
func (*LoggedOut) isSignalEvent() {}
|
||||
|
||||
type MessageInfo struct {
|
||||
Sender uuid.UUID
|
||||
|
|
@ -90,14 +89,6 @@ type DeleteForMe struct {
|
|||
*signalpb.SyncMessage_DeleteForMe
|
||||
}
|
||||
|
||||
type MessageRequestResponse struct {
|
||||
Timestamp uint64
|
||||
ThreadACI uuid.UUID
|
||||
GroupID *libsignalgo.GroupIdentifier
|
||||
Type signalpb.SyncMessage_MessageRequestResponse_Type
|
||||
Raw *signalpb.SyncMessage_MessageRequestResponse
|
||||
}
|
||||
|
||||
type QueueEmpty struct{}
|
||||
|
||||
type LoggedOut struct{ Error error }
|
||||
|
|
|
|||
|
|
@ -1,346 +0,0 @@
|
|||
// mautrix-signal - A Matrix-signal puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package signalmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||
)
|
||||
|
||||
type SendEndorsementCache struct {
|
||||
SendEndorsement libsignalgo.GroupSendEndorsement
|
||||
MemberEndorsements map[libsignalgo.ServiceID]libsignalgo.GroupSendEndorsement
|
||||
Expiration time.Time
|
||||
SecretParams *libsignalgo.GroupSecretParams
|
||||
}
|
||||
|
||||
func (sec *SendEndorsementCache) GetToken() (libsignalgo.GroupSendFullToken, error) {
|
||||
return sec.GetTokenWith(sec.SendEndorsement)
|
||||
}
|
||||
|
||||
func (sec *SendEndorsementCache) GetTokenWith(altToken libsignalgo.GroupSendEndorsement) (libsignalgo.GroupSendFullToken, error) {
|
||||
return altToken.ToFullToken(sec.SecretParams, sec.Expiration)
|
||||
}
|
||||
|
||||
type cachedGroup struct {
|
||||
*Group
|
||||
*SendEndorsementCache
|
||||
FetchedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type GroupCache struct {
|
||||
serviceID libsignalgo.ServiceID
|
||||
|
||||
credentials *GroupCredentials
|
||||
credentialsLock sync.RWMutex
|
||||
|
||||
data map[types.GroupIdentifier]*cachedGroup
|
||||
lock sync.RWMutex
|
||||
|
||||
activeCalls map[types.GroupIdentifier]string
|
||||
callsLock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewGroupCache(serviceID libsignalgo.ServiceID) *GroupCache {
|
||||
return &GroupCache{
|
||||
serviceID: serviceID,
|
||||
data: make(map[types.GroupIdentifier]*cachedGroup),
|
||||
activeCalls: make(map[types.GroupIdentifier]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *GroupCache) GetCredentials(
|
||||
ctx context.Context,
|
||||
fetch func(context.Context, time.Time) (*GroupCredentials, error),
|
||||
) (*GroupCredential, error) {
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
gc.credentialsLock.RLock()
|
||||
cred := gc.getCachedCredentials(today.Unix())
|
||||
gc.credentialsLock.RUnlock()
|
||||
if cred != nil {
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
gc.credentialsLock.Lock()
|
||||
defer gc.credentialsLock.Unlock()
|
||||
cred = gc.getCachedCredentials(today.Unix())
|
||||
if cred != nil {
|
||||
return cred, nil
|
||||
}
|
||||
creds, err := fetch(ctx, today)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gc.credentials = creds
|
||||
cred = gc.getCachedCredentials(today.Unix())
|
||||
if cred == nil {
|
||||
return nil, fmt.Errorf("no credentials for today after fetch")
|
||||
}
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
func (gc *GroupCache) getCachedCredentials(today int64) *GroupCredential {
|
||||
if gc.credentials == nil {
|
||||
return nil
|
||||
}
|
||||
for _, cred := range gc.credentials.Credentials {
|
||||
if cred.RedemptionTime == today {
|
||||
return &cred
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *GroupCache) UpdateActiveCall(id types.GroupIdentifier, callID string) bool {
|
||||
gc.callsLock.Lock()
|
||||
defer gc.callsLock.Unlock()
|
||||
currentCallID, ok := gc.activeCalls[id]
|
||||
if ok {
|
||||
// If we do, then this must be ending the call
|
||||
if currentCallID == callID {
|
||||
delete(gc.activeCalls, id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
gc.activeCalls[id] = callID
|
||||
return true
|
||||
}
|
||||
|
||||
func (gc *GroupCache) Get(id types.GroupIdentifier) (*Group, *SendEndorsementCache, bool) {
|
||||
gc.lock.RLock()
|
||||
defer gc.lock.RUnlock()
|
||||
c, ok := gc.data[id]
|
||||
if !ok || time.Until(c.Expiration) < 5*time.Minute {
|
||||
return nil, nil, false
|
||||
}
|
||||
return c.Group, c.SendEndorsementCache, true
|
||||
}
|
||||
|
||||
func (gc *GroupCache) Delete(id types.GroupIdentifier) {
|
||||
gc.lock.Lock()
|
||||
defer gc.lock.Unlock()
|
||||
delete(gc.data, id)
|
||||
}
|
||||
|
||||
func (gc *GroupCache) Put(data *Group, endorsementResponse libsignalgo.GroupSendEndorsementsResponse) error {
|
||||
gsp, err := masterKeyToBytes(data.GroupMasterKey).SecretParams()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get secret params: %w", err)
|
||||
}
|
||||
expiration, err := endorsementResponse.GetExpiration()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get endorsement expiration: %w", err)
|
||||
}
|
||||
endorsement, memberEndorsements, err := endorsementResponse.ReceiveWithServiceIDs(data.getMemberServiceIDs(), gc.serviceID, &gsp, prodServerPublicParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to receive endorsements: %w", err)
|
||||
}
|
||||
|
||||
gc.lock.Lock()
|
||||
defer gc.lock.Unlock()
|
||||
cached, exists := gc.data[data.GroupIdentifier]
|
||||
if exists && cached.Revision > data.Revision {
|
||||
return nil
|
||||
}
|
||||
gc.data[data.GroupIdentifier] = &cachedGroup{
|
||||
Group: data,
|
||||
FetchedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
||||
SendEndorsementCache: &SendEndorsementCache{
|
||||
Expiration: expiration,
|
||||
SendEndorsement: endorsement,
|
||||
MemberEndorsements: memberEndorsements,
|
||||
SecretParams: &gsp,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *GroupCache) ApplyUpdate(change *GroupChange, endorsementResponse libsignalgo.GroupSendEndorsementsResponse) error {
|
||||
mkBytes := masterKeyToBytes(change.GroupMasterKey)
|
||||
rawGroupID, err := mkBytes.GroupIdentifier()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get group identifier: %w", err)
|
||||
}
|
||||
gsp, err := mkBytes.SecretParams()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get secret params: %w", err)
|
||||
}
|
||||
id := types.GroupIdentifier(rawGroupID.String())
|
||||
|
||||
gc.lock.Lock()
|
||||
defer gc.lock.Unlock()
|
||||
|
||||
cached, exists := gc.data[id]
|
||||
if !exists || cached.Revision >= change.Revision {
|
||||
return nil
|
||||
} else if cached.Revision < change.Revision-1 {
|
||||
// We missed an update, evict
|
||||
delete(gc.data, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pending member adds, promotes and removes
|
||||
cached.PendingMembers = append(cached.PendingMembers, change.AddPendingMembers...)
|
||||
for _, promo := range change.PromotePendingMembers {
|
||||
cached.PendingMembers = slices.DeleteFunc(cached.PendingMembers, func(p *PendingMember) bool {
|
||||
return p.ServiceID.Type == libsignalgo.ServiceIDTypeACI && p.ServiceID.UUID == promo.ACI
|
||||
})
|
||||
cached.Members = append(cached.Members, &GroupMember{
|
||||
ACI: promo.ACI,
|
||||
ProfileKey: promo.ProfileKey,
|
||||
Role: GroupMember_DEFAULT,
|
||||
JoinedAtRevision: change.Revision,
|
||||
})
|
||||
}
|
||||
for _, promo := range change.PromotePendingPniAciMembers {
|
||||
cached.PendingMembers = slices.DeleteFunc(cached.PendingMembers, func(p *PendingMember) bool {
|
||||
return (p.ServiceID.Type == libsignalgo.ServiceIDTypePNI && p.ServiceID.UUID == promo.PNI) ||
|
||||
(p.ServiceID.Type == libsignalgo.ServiceIDTypeACI && p.ServiceID.UUID == promo.ACI)
|
||||
})
|
||||
cached.Members = append(cached.Members, &GroupMember{
|
||||
ACI: promo.ACI,
|
||||
ProfileKey: promo.ProfileKey,
|
||||
Role: GroupMember_DEFAULT,
|
||||
JoinedAtRevision: change.Revision,
|
||||
})
|
||||
}
|
||||
cached.PendingMembers = slices.DeleteFunc(cached.PendingMembers, func(p *PendingMember) bool {
|
||||
return slices.ContainsFunc(change.DeletePendingMembers, func(s *libsignalgo.ServiceID) bool {
|
||||
return s != nil && p.ServiceID == *s
|
||||
})
|
||||
})
|
||||
|
||||
// Requesting member adds, promotes and removes
|
||||
cached.RequestingMembers = append(cached.RequestingMembers, change.AddRequestingMembers...)
|
||||
for _, promo := range change.PromoteRequestingMembers {
|
||||
var profileKey libsignalgo.ProfileKey
|
||||
cached.RequestingMembers = slices.DeleteFunc(cached.RequestingMembers, func(r *RequestingMember) bool {
|
||||
if r.ACI == promo.ACI {
|
||||
profileKey = r.ProfileKey
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
cached.Members = append(cached.Members, &GroupMember{
|
||||
ACI: promo.ACI,
|
||||
ProfileKey: profileKey,
|
||||
Role: promo.Role,
|
||||
JoinedAtRevision: change.Revision,
|
||||
})
|
||||
}
|
||||
cached.RequestingMembers = slices.DeleteFunc(cached.RequestingMembers, func(r *RequestingMember) bool {
|
||||
return slices.ContainsFunc(change.DeleteRequestingMembers, func(u *uuid.UUID) bool {
|
||||
return u != nil && r.ACI == *u
|
||||
})
|
||||
})
|
||||
|
||||
// Direct member adds, removes and modifications
|
||||
for _, member := range change.AddMembers {
|
||||
cached.Members = append(cached.Members, &GroupMember{
|
||||
ACI: member.ACI,
|
||||
Role: member.Role,
|
||||
ProfileKey: member.ProfileKey,
|
||||
JoinedAtRevision: member.JoinedAtRevision,
|
||||
})
|
||||
}
|
||||
for _, rm := range change.ModifyMemberRoles {
|
||||
cached.findMemberOrEmpty(rm.ACI).Role = rm.Role
|
||||
}
|
||||
for _, pk := range change.ModifyMemberProfileKeys {
|
||||
cached.findMemberOrEmpty(pk.ACI).ProfileKey = pk.ProfileKey
|
||||
}
|
||||
cached.Members = slices.DeleteFunc(cached.Members, func(member *GroupMember) bool {
|
||||
return slices.ContainsFunc(change.DeleteMembers, func(u *uuid.UUID) bool {
|
||||
return u != nil && *u == member.ACI
|
||||
})
|
||||
})
|
||||
|
||||
// Banned members
|
||||
cached.BannedMembers = append(cached.BannedMembers, change.AddBannedMembers...)
|
||||
cached.BannedMembers = slices.DeleteFunc(cached.BannedMembers, func(b *BannedMember) bool {
|
||||
return slices.ContainsFunc(change.DeleteBannedMembers, func(s *libsignalgo.ServiceID) bool {
|
||||
return s != nil && b.ServiceID == *s
|
||||
})
|
||||
})
|
||||
|
||||
// Non-member modifications
|
||||
if change.ModifyInviteLinkPassword != nil {
|
||||
cached.InviteLinkPassword = change.ModifyInviteLinkPassword
|
||||
}
|
||||
if change.ModifyTitle != nil {
|
||||
cached.Title = *change.ModifyTitle
|
||||
}
|
||||
if change.ModifyDescription != nil {
|
||||
cached.Description = *change.ModifyDescription
|
||||
}
|
||||
if change.ModifyAvatar != nil {
|
||||
cached.AvatarPath = *change.ModifyAvatar
|
||||
}
|
||||
if change.ModifyAnnouncementsOnly != nil {
|
||||
cached.AnnouncementsOnly = *change.ModifyAnnouncementsOnly
|
||||
}
|
||||
if change.ModifyDisappearingMessagesDuration != nil {
|
||||
cached.DisappearingMessagesDuration = *change.ModifyDisappearingMessagesDuration
|
||||
}
|
||||
if change.ModifyAttributesAccess != nil {
|
||||
cached.AccessControl.Attributes = *change.ModifyAttributesAccess
|
||||
}
|
||||
if change.ModifyMemberAccess != nil {
|
||||
cached.AccessControl.Members = *change.ModifyMemberAccess
|
||||
}
|
||||
if change.ModifyAddFromInviteLinkAccess != nil {
|
||||
cached.AccessControl.AddFromInviteLink = *change.ModifyAddFromInviteLinkAccess
|
||||
}
|
||||
|
||||
cached.UpdatedAt = time.Now()
|
||||
cached.Revision = change.Revision
|
||||
endorsement, memberEndorsements, err := endorsementResponse.ReceiveWithServiceIDs(
|
||||
cached.getMemberServiceIDs(),
|
||||
gc.serviceID,
|
||||
&gsp,
|
||||
prodServerPublicParams,
|
||||
)
|
||||
if err != nil {
|
||||
delete(gc.data, id)
|
||||
return fmt.Errorf("failed to receive endorsements: %w", err)
|
||||
}
|
||||
expiration, err := endorsementResponse.GetExpiration()
|
||||
if err != nil {
|
||||
delete(gc.data, id)
|
||||
return fmt.Errorf("failed to get endorsement expiration: %w", err)
|
||||
}
|
||||
// TODO do these responses overwrite the entire thing?
|
||||
cached.SendEndorsementCache = &SendEndorsementCache{
|
||||
SendEndorsement: endorsement,
|
||||
MemberEndorsements: memberEndorsements,
|
||||
Expiration: expiration,
|
||||
SecretParams: &gsp,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exslices"
|
||||
"go.mau.fi/util/ptr"
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
|
@ -91,12 +90,6 @@ type Group struct {
|
|||
//PublicKey *libsignalgo.PublicKey
|
||||
}
|
||||
|
||||
func (group *Group) getMemberServiceIDs() []libsignalgo.ServiceID {
|
||||
return exslices.CastFunc(group.Members, func(from *GroupMember) libsignalgo.ServiceID {
|
||||
return libsignalgo.NewACIServiceID(from.ACI)
|
||||
})
|
||||
}
|
||||
|
||||
func (group *Group) GetInviteLink() (string, error) {
|
||||
if group.InviteLinkPassword == nil {
|
||||
return "", fmt.Errorf("no invite link password set")
|
||||
|
|
@ -106,8 +99,8 @@ func (group *Group) GetInviteLink() (string, error) {
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't decode invite link password")
|
||||
}
|
||||
inviteLinkContents := signalpb.GroupInviteLink_ContentsV1{
|
||||
ContentsV1: &signalpb.GroupInviteLink_GroupInviteLinkContentsV1{
|
||||
inviteLinkContents := signalpb.GroupInviteLink_V1Contents{
|
||||
V1Contents: &signalpb.GroupInviteLink_GroupInviteLinkContentsV1{
|
||||
GroupMasterKey: masterKeyBytes[:],
|
||||
InviteLinkPassword: inviteLinkPasswordBytes,
|
||||
},
|
||||
|
|
@ -121,15 +114,6 @@ func (group *Group) GetInviteLink() (string, error) {
|
|||
return "https://signal.group/#" + inviteLinkPath, nil
|
||||
}
|
||||
|
||||
func (group *Group) findMemberOrEmpty(aci uuid.UUID) *GroupMember {
|
||||
for _, member := range group.Members {
|
||||
if member.ACI == aci {
|
||||
return member
|
||||
}
|
||||
}
|
||||
return &GroupMember{}
|
||||
}
|
||||
|
||||
type GroupAccessControl struct {
|
||||
Members AccessControl
|
||||
AddFromInviteLink AccessControl
|
||||
|
|
@ -226,7 +210,8 @@ func (groupChange *GroupChange) isEmpty() bool {
|
|||
len(groupChange.PromoteRequestingMembers) == 0 &&
|
||||
groupChange.ModifyDescription == nil &&
|
||||
groupChange.ModifyAnnouncementsOnly == nil &&
|
||||
len(groupChange.AddBannedMembers) == 0
|
||||
len(groupChange.AddBannedMembers) == 0 &&
|
||||
len(groupChange.DeleteMembers) == 0
|
||||
}
|
||||
|
||||
func (groupChange *GroupChange) resolveConflict(group *Group) {
|
||||
|
|
@ -329,7 +314,8 @@ func (cli *Client) fetchNewGroupCreds(ctx context.Context, today time.Time) (*Gr
|
|||
Logger()
|
||||
sevenDaysOut := today.Add(7 * 24 * time.Hour)
|
||||
path := fmt.Sprintf("/v1/certificate/auth/group?redemptionStartSeconds=%d&redemptionEndSeconds=%d&pniAsServiceId=true", today.Unix(), sevenDaysOut.Unix())
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodGet, path, nil, nil)
|
||||
authRequest := web.CreateWSRequest(http.MethodGet, path, nil, nil, nil)
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, authRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SendRequest error: %w", err)
|
||||
}
|
||||
|
|
@ -343,22 +329,51 @@ func (cli *Client) fetchNewGroupCreds(ctx context.Context, today time.Time) (*Gr
|
|||
log.Err(err).Msg("json.Unmarshal error")
|
||||
return nil, err
|
||||
}
|
||||
// make sure pni matches device pni
|
||||
if creds.PNI != cli.Store.PNI {
|
||||
return nil, fmt.Errorf("mismatching PNI in group credentials: %s != %s", creds.PNI, cli.Store.PNI)
|
||||
err := fmt.Errorf("creds.PNI != d.PNI")
|
||||
log.Err(err).Msg("creds.PNI != d.PNI")
|
||||
return nil, err
|
||||
}
|
||||
return &creds, nil
|
||||
}
|
||||
|
||||
func (cli *Client) getCachedAuthorizationForToday(today time.Time) *GroupCredential {
|
||||
if cli.GroupCredentials == nil {
|
||||
// No cached credentials
|
||||
return nil
|
||||
}
|
||||
allCreds := cli.GroupCredentials
|
||||
// Get the credential for today
|
||||
for _, cred := range allCreds.Credentials {
|
||||
if cred.RedemptionTime == today.Unix() {
|
||||
return &cred
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) GetAuthorizationForToday(ctx context.Context, masterKey libsignalgo.GroupMasterKey) (*GroupAuth, error) {
|
||||
log := zerolog.Ctx(ctx).With().
|
||||
Str("action", "get authorization for today").
|
||||
Logger()
|
||||
// Timestamps for the start of today, and 7 days later
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
|
||||
todayCred, err := cli.GroupCache.GetCredentials(ctx, cli.fetchNewGroupCreds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get group credentials: %w", err)
|
||||
todayCred := cli.getCachedAuthorizationForToday(today)
|
||||
if todayCred == nil {
|
||||
creds, err := cli.fetchNewGroupCreds(ctx, today)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetchNewGroupCreds error: %w", err)
|
||||
}
|
||||
cli.GroupCredentials = creds
|
||||
todayCred = cli.getCachedAuthorizationForToday(today)
|
||||
}
|
||||
if todayCred == nil {
|
||||
return nil, fmt.Errorf("couldn't get credential for today")
|
||||
}
|
||||
|
||||
//TODO: cache cred after unmarshalling
|
||||
redemptionTime := uint64(todayCred.RedemptionTime)
|
||||
credential := todayCred.Credential
|
||||
authCredentialResponse, err := libsignalgo.NewAuthCredentialWithPniResponse(credential)
|
||||
|
|
@ -470,7 +485,7 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
|||
descriptionBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedGroup.Description)
|
||||
if err == nil {
|
||||
// treat a failure in obtaining the description as non-fatal
|
||||
decryptedGroup.Description = cleanupStringProperty(descriptionBlob.GetDescriptionText())
|
||||
decryptedGroup.Description = cleanupStringProperty(descriptionBlob.GetDescription())
|
||||
}
|
||||
|
||||
if encryptedGroup.DisappearingMessagesTimer != nil && len(encryptedGroup.DisappearingMessagesTimer) > 0 {
|
||||
|
|
@ -482,8 +497,8 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
|||
}
|
||||
|
||||
// These aren't encrypted
|
||||
decryptedGroup.AvatarPath = encryptedGroup.AvatarUrl
|
||||
decryptedGroup.Revision = encryptedGroup.Version
|
||||
decryptedGroup.AvatarPath = encryptedGroup.Avatar
|
||||
decryptedGroup.Revision = encryptedGroup.Revision
|
||||
|
||||
// Decrypt members
|
||||
for _, member := range encryptedGroup.Members {
|
||||
|
|
@ -497,7 +512,7 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
|||
decryptedGroup.Members = append(decryptedGroup.Members, decryptedMember)
|
||||
}
|
||||
|
||||
for _, pendingMember := range encryptedGroup.MembersPendingProfileKey {
|
||||
for _, pendingMember := range encryptedGroup.PendingMembers {
|
||||
if pendingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -509,7 +524,7 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
|||
decryptedGroup.PendingMembers = append(decryptedGroup.PendingMembers, decryptedPendingMember)
|
||||
}
|
||||
|
||||
for _, requestingMember := range encryptedGroup.MembersPendingAdminApproval {
|
||||
for _, requestingMember := range encryptedGroup.RequestingMembers {
|
||||
if requestingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -520,7 +535,7 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
|||
decryptedGroup.RequestingMembers = append(decryptedGroup.RequestingMembers, decryptedRequestingMember)
|
||||
}
|
||||
|
||||
for _, bannedMember := range encryptedGroup.MembersBanned {
|
||||
for _, bannedMember := range encryptedGroup.BannedMembers {
|
||||
if bannedMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -619,7 +634,7 @@ func (cli *Client) fetchGroupByID(ctx context.Context, gid types.GroupIdentifier
|
|||
return nil, fmt.Errorf("failed to get group master key: %w", err)
|
||||
}
|
||||
if groupMasterKey == "" {
|
||||
return nil, fmt.Errorf("%w for %s", ErrGroupMasterKeyNotFound, gid)
|
||||
return nil, fmt.Errorf("No group master key found for group identifier %s", gid)
|
||||
}
|
||||
return cli.fetchGroupWithMasterKey(ctx, groupMasterKey)
|
||||
}
|
||||
|
|
@ -634,9 +649,9 @@ func (cli *Client) fetchGroupWithMasterKey(ctx context.Context, groupMasterKey t
|
|||
Username: &groupAuth.Username,
|
||||
Password: &groupAuth.Password,
|
||||
ContentType: web.ContentTypeProtobuf,
|
||||
Host: web.StorageHostname,
|
||||
}
|
||||
response, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, "/v2/groups", opts)
|
||||
defer web.CloseBody(response)
|
||||
response, err := web.SendHTTPRequest(ctx, http.MethodGet, "/v2/groups", opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -661,10 +676,6 @@ func (cli *Client) parseGroupResponse(ctx context.Context, response *http.Respon
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt group: %w", err)
|
||||
}
|
||||
err = cli.GroupCache.Put(group, groupResponse.GroupSendEndorsementsResponse)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to cache group response")
|
||||
}
|
||||
|
||||
// Store the profile keys in case they're new
|
||||
for _, member := range group.Members {
|
||||
|
|
@ -685,11 +696,11 @@ func (cli *Client) parseGroupResponse(ctx context.Context, response *http.Respon
|
|||
func (cli *Client) DownloadGroupAvatar(ctx context.Context, avatarPath string, groupMasterKey types.SerializedGroupMasterKey) ([]byte, error) {
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
opts := &web.HTTPReqOpt{
|
||||
Host: web.CDN1Hostname,
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
}
|
||||
resp, err := web.SendHTTPRequest(ctx, web.CDN1Hostname, http.MethodGet, avatarPath, opts)
|
||||
defer web.CloseBody(resp)
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, avatarPath, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
|
@ -708,21 +719,23 @@ func (cli *Client) DownloadGroupAvatar(ctx context.Context, avatarPath string, g
|
|||
return decrypted, nil
|
||||
}
|
||||
|
||||
func (cli *Client) RetrieveGroupByID(ctx context.Context, gid types.GroupIdentifier, revision uint32) (*Group, *SendEndorsementCache, error) {
|
||||
cached, endorsement, ok := cli.GroupCache.Get(gid)
|
||||
if ok && cached.Revision >= revision {
|
||||
return cached, endorsement, nil
|
||||
func (cli *Client) RetrieveGroupByID(ctx context.Context, gid types.GroupIdentifier, revision uint32) (*Group, error) {
|
||||
cli.initGroupCache()
|
||||
|
||||
lastFetched, ok := cli.GroupCache.lastFetched[gid]
|
||||
if ok && time.Since(lastFetched) < 1*time.Hour {
|
||||
group, ok := cli.GroupCache.groups[gid]
|
||||
if ok && group.Revision >= revision {
|
||||
return group, nil
|
||||
}
|
||||
}
|
||||
group, err := cli.fetchGroupByID(ctx, gid)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
cached, endorsement, ok = cli.GroupCache.Get(gid)
|
||||
if !ok {
|
||||
zerolog.Ctx(ctx).Warn().Msg("Group not found in cache after fetching")
|
||||
return group, nil, nil
|
||||
}
|
||||
return cached, endorsement, nil
|
||||
cli.GroupCache.groups[gid] = group
|
||||
cli.GroupCache.lastFetched[gid] = time.Now()
|
||||
return group, nil
|
||||
}
|
||||
|
||||
// We should store the group master key in the group store as soon as we see it,
|
||||
|
|
@ -740,6 +753,40 @@ func (cli *Client) StoreMasterKey(ctx context.Context, groupMasterKey types.Seri
|
|||
return groupIdentifier, nil
|
||||
}
|
||||
|
||||
// We need to track active calls so we don't send too many IncomingSignalMessageCalls
|
||||
// Of course for group calls Signal doesn't tell us *anything* so we're mostly just inferring
|
||||
// So we just jam a new call ID in, and return true if we *think* this is a new incoming call
|
||||
func (cli *Client) UpdateActiveCalls(gid types.GroupIdentifier, callID string) (isActive bool) {
|
||||
cli.initGroupCache()
|
||||
// Check to see if we currently have an active call for this group
|
||||
currentCallID, ok := cli.GroupCache.activeCalls[gid]
|
||||
if ok {
|
||||
// If we do, then this must be ending the call
|
||||
if currentCallID == callID {
|
||||
delete(cli.GroupCache.activeCalls, gid)
|
||||
return false
|
||||
}
|
||||
}
|
||||
cli.GroupCache.activeCalls[gid] = callID
|
||||
return true
|
||||
}
|
||||
|
||||
func (cli *Client) initGroupCache() {
|
||||
if cli.GroupCache == nil {
|
||||
cli.GroupCache = &GroupCache{
|
||||
groups: make(map[types.GroupIdentifier]*Group),
|
||||
lastFetched: make(map[types.GroupIdentifier]time.Time),
|
||||
activeCalls: make(map[types.GroupIdentifier]string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GroupCache struct {
|
||||
groups map[types.GroupIdentifier]*Group
|
||||
lastFetched map[types.GroupIdentifier]time.Time
|
||||
activeCalls map[types.GroupIdentifier]string
|
||||
}
|
||||
|
||||
func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalpb.GroupContextV2) (*GroupChange, error) {
|
||||
masterKeyBytes := libsignalgo.GroupMasterKey(groupContext.MasterKey)
|
||||
groupMasterKey := masterKeyFromBytes(masterKeyBytes)
|
||||
|
|
@ -757,15 +804,6 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
log := zerolog.Ctx(ctx).With().Str("action", "decrypt group change").Logger()
|
||||
serverSignature := encryptedGroupChange.ServerSignature
|
||||
encryptedActionsBytes := encryptedGroupChange.Actions
|
||||
var success bool
|
||||
defer func() {
|
||||
if !success {
|
||||
rawGroupID, _ := masterKeyToBytes(groupMasterKey).GroupIdentifier()
|
||||
if rawGroupID != nil {
|
||||
cli.GroupCache.Delete(types.GroupIdentifier(rawGroupID.String()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
if verifySignature {
|
||||
|
|
@ -788,14 +826,14 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
return nil, err
|
||||
}
|
||||
|
||||
sourceServiceID, err := groupSecretParams.DecryptServiceID(libsignalgo.UUIDCiphertext(encryptedActions.SourceUserId))
|
||||
sourceServiceID, err := groupSecretParams.DecryptServiceID(libsignalgo.UUIDCiphertext(encryptedActions.SourceServiceId))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Couldn't decrypt source serviceID")
|
||||
return nil, err
|
||||
}
|
||||
decryptedGroupChange := &GroupChange{
|
||||
GroupMasterKey: groupMasterKey,
|
||||
Revision: encryptedActions.Version,
|
||||
Revision: encryptedActions.Revision,
|
||||
SourceServiceID: sourceServiceID,
|
||||
}
|
||||
|
||||
|
|
@ -815,7 +853,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
descriptionBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDescription.Description)
|
||||
if err == nil {
|
||||
// treat a failure in obtaining the description as non-fatal
|
||||
newDescription := cleanupStringProperty(descriptionBlob.GetDescriptionText())
|
||||
newDescription := cleanupStringProperty(descriptionBlob.GetDescription())
|
||||
decryptedGroupChange.ModifyDescription = &newDescription
|
||||
}
|
||||
}
|
||||
|
|
@ -890,7 +928,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
}
|
||||
}
|
||||
|
||||
for _, addPendingMember := range encryptedActions.AddMembersPendingProfileKey {
|
||||
for _, addPendingMember := range encryptedActions.AddPendingMembers {
|
||||
if addPendingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -903,7 +941,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
decryptedGroupChange.AddPendingMembers = append(decryptedGroupChange.AddPendingMembers, decryptedPendingMember)
|
||||
}
|
||||
|
||||
for _, deletePendingMember := range encryptedActions.DeleteMembersPendingProfileKey {
|
||||
for _, deletePendingMember := range encryptedActions.DeletePendingMembers {
|
||||
if deletePendingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -916,7 +954,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
decryptedGroupChange.DeletePendingMembers = append(decryptedGroupChange.DeletePendingMembers, &userID)
|
||||
}
|
||||
|
||||
for _, promotePendingMember := range encryptedActions.PromoteMembersPendingProfileKey {
|
||||
for _, promotePendingMember := range encryptedActions.PromotePendingMembers {
|
||||
if promotePendingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -935,7 +973,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
}
|
||||
}
|
||||
|
||||
for _, promotePendingPniAciMember := range encryptedActions.PromoteMembersPendingPniAciProfileKey {
|
||||
for _, promotePendingPniAciMember := range encryptedActions.PromotePendingPniAciMembers {
|
||||
// TODO: pretending this is a PendingMember should do for mautrix-signal, but we probably want to treat them separately at some point
|
||||
if promotePendingPniAciMember == nil {
|
||||
continue
|
||||
|
|
@ -965,7 +1003,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
}
|
||||
}
|
||||
|
||||
for _, addRequestingMember := range encryptedActions.AddMembersPendingAdminApproval {
|
||||
for _, addRequestingMember := range encryptedActions.AddRequestingMembers {
|
||||
if addRequestingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -981,7 +1019,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
}
|
||||
}
|
||||
|
||||
for _, deleteRequestingMember := range encryptedActions.DeleteMembersPendingAdminApproval {
|
||||
for _, deleteRequestingMember := range encryptedActions.DeleteRequestingMembers {
|
||||
if deleteRequestingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -994,7 +1032,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
decryptedGroupChange.DeleteRequestingMembers = append(decryptedGroupChange.DeleteRequestingMembers, &serviceID.UUID)
|
||||
}
|
||||
|
||||
for _, promoteRequestingMember := range encryptedActions.PromoteMembersPendingAdminApproval {
|
||||
for _, promoteRequestingMember := range encryptedActions.PromoteRequestingMembers {
|
||||
if promoteRequestingMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -1010,7 +1048,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
})
|
||||
}
|
||||
|
||||
for _, addBannedMember := range encryptedActions.AddMembersBanned {
|
||||
for _, addBannedMember := range encryptedActions.AddBannedMembers {
|
||||
if addBannedMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -1027,7 +1065,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
})
|
||||
}
|
||||
|
||||
for _, deleteBannedMember := range encryptedActions.DeleteMembersBanned {
|
||||
for _, deleteBannedMember := range encryptedActions.DeleteBannedMembers {
|
||||
if deleteBannedMember == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -1055,8 +1093,8 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
if encryptedActions.ModifyAnnouncementsOnly != nil {
|
||||
decryptedGroupChange.ModifyAnnouncementsOnly = &encryptedActions.ModifyAnnouncementsOnly.AnnouncementsOnly
|
||||
}
|
||||
if encryptedActions.ModifyDisappearingMessageTimer != nil && len(encryptedActions.ModifyDisappearingMessageTimer.Timer) > 0 {
|
||||
timerBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDisappearingMessageTimer.Timer)
|
||||
if encryptedActions.ModifyDisappearingMessagesTimer != nil && len(encryptedActions.ModifyDisappearingMessagesTimer.Timer) > 0 {
|
||||
timerBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDisappearingMessagesTimer.Timer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1068,12 +1106,6 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
|||
decryptedGroupChange.ModifyInviteLinkPassword = &inviteLinkPassword
|
||||
}
|
||||
|
||||
success = true
|
||||
err = cli.GroupCache.ApplyUpdate(decryptedGroupChange, nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to apply group change to cache")
|
||||
}
|
||||
|
||||
return decryptedGroupChange, nil
|
||||
}
|
||||
|
||||
|
|
@ -1127,11 +1159,11 @@ func decryptMember(ctx context.Context, member *signalpb.Member, groupSecretPara
|
|||
ACI: *aci,
|
||||
ProfileKey: *profileKey,
|
||||
Role: GroupMemberRole(member.Role),
|
||||
JoinedAtRevision: member.JoinedAtVersion,
|
||||
JoinedAtRevision: member.JoinedAtRevision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decryptPendingMember(ctx context.Context, pendingMember *signalpb.MemberPendingProfileKey, groupSecretParams libsignalgo.GroupSecretParams) (*PendingMember, error) {
|
||||
func decryptPendingMember(ctx context.Context, pendingMember *signalpb.PendingMember, groupSecretParams libsignalgo.GroupSecretParams) (*PendingMember, error) {
|
||||
log := zerolog.Ctx(ctx)
|
||||
encryptedUserID := libsignalgo.UUIDCiphertext(pendingMember.Member.UserId)
|
||||
userID, err := groupSecretParams.DecryptServiceID(encryptedUserID)
|
||||
|
|
@ -1154,7 +1186,7 @@ func decryptPendingMember(ctx context.Context, pendingMember *signalpb.MemberPen
|
|||
}, nil
|
||||
}
|
||||
|
||||
func decryptRequestingMember(ctx context.Context, requestingMember *signalpb.MemberPendingAdminApproval, groupSecretParams libsignalgo.GroupSecretParams) (*RequestingMember, error) {
|
||||
func decryptRequestingMember(ctx context.Context, requestingMember *signalpb.RequestingMember, groupSecretParams libsignalgo.GroupSecretParams) (*RequestingMember, error) {
|
||||
aci, profileKey, err := decryptPKeyAndIDorPresentation(ctx, requestingMember.UserId, requestingMember.ProfileKey, requestingMember.Presentation, groupSecretParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1175,7 +1207,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Could not get groupSecretParams from master key")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions := &signalpb.GroupChange_Actions{Version: decryptedGroupChange.Revision}
|
||||
groupChangeActions := &signalpb.GroupChange_Actions{Revision: decryptedGroupChange.Revision}
|
||||
if decryptedGroupChange.ModifyTitle != nil {
|
||||
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_Title{Title: *decryptedGroupChange.ModifyTitle}}
|
||||
encryptedTitle, err := encryptBlobIntoGroupProperty(groupSecretParams, &attributeBlob)
|
||||
|
|
@ -1186,7 +1218,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
groupChangeActions.ModifyTitle = &signalpb.GroupChange_Actions_ModifyTitleAction{Title: *encryptedTitle}
|
||||
}
|
||||
if decryptedGroupChange.ModifyDescription != nil {
|
||||
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_DescriptionText{DescriptionText: *decryptedGroupChange.ModifyDescription}}
|
||||
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_Description{Description: *decryptedGroupChange.ModifyDescription}}
|
||||
encryptedDescription, err := encryptBlobIntoGroupProperty(groupSecretParams, &attributeBlob)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Could not get encrypt description")
|
||||
|
|
@ -1208,7 +1240,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
JoinFromInviteLink: addMember.JoinFromInviteLink,
|
||||
})
|
||||
} else {
|
||||
groupChangeActions.AddMembersPendingProfileKey = append(groupChangeActions.AddMembersPendingProfileKey, &signalpb.GroupChange_Actions_AddMemberPendingProfileKeyAction{
|
||||
groupChangeActions.AddPendingMembers = append(groupChangeActions.AddPendingMembers, &signalpb.GroupChange_Actions_AddPendingMemberAction{
|
||||
Added: encryptedPendingMember,
|
||||
})
|
||||
}
|
||||
|
|
@ -1240,7 +1272,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Failed to encrypt pendingMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.AddMembersPendingProfileKey = append(groupChangeActions.AddMembersPendingProfileKey, &signalpb.GroupChange_Actions_AddMemberPendingProfileKeyAction{
|
||||
groupChangeActions.AddPendingMembers = append(groupChangeActions.AddPendingMembers, &signalpb.GroupChange_Actions_AddPendingMemberAction{
|
||||
Added: encryptedPendingMember,
|
||||
})
|
||||
}
|
||||
|
|
@ -1250,7 +1282,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Encrypt UserId error for deletePendingMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.DeleteMembersPendingProfileKey = append(groupChangeActions.DeleteMembersPendingProfileKey, &signalpb.GroupChange_Actions_DeleteMemberPendingProfileKeyAction{
|
||||
groupChangeActions.DeletePendingMembers = append(groupChangeActions.DeletePendingMembers, &signalpb.GroupChange_Actions_DeletePendingMemberAction{
|
||||
DeletedUserId: encryptedUserID[:],
|
||||
})
|
||||
}
|
||||
|
|
@ -1268,7 +1300,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("failed creating expiring profile key credential presentation for addMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.PromoteMembersPendingProfileKey = append(groupChangeActions.PromoteMembersPendingProfileKey, &signalpb.GroupChange_Actions_PromoteMemberPendingProfileKeyAction{
|
||||
groupChangeActions.PromotePendingMembers = append(groupChangeActions.PromotePendingMembers, &signalpb.GroupChange_Actions_PromotePendingMemberAction{
|
||||
Presentation: *presentation,
|
||||
})
|
||||
}
|
||||
|
|
@ -1286,8 +1318,8 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("failed creating expiring profile key credential presentation for addMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.AddMembersPendingAdminApproval = append(groupChangeActions.AddMembersPendingAdminApproval, &signalpb.GroupChange_Actions_AddMemberPendingAdminApprovalAction{
|
||||
Added: &signalpb.MemberPendingAdminApproval{
|
||||
groupChangeActions.AddRequestingMembers = append(groupChangeActions.AddRequestingMembers, &signalpb.GroupChange_Actions_AddRequestingMemberAction{
|
||||
Added: &signalpb.RequestingMember{
|
||||
Presentation: *presentation,
|
||||
},
|
||||
})
|
||||
|
|
@ -1298,7 +1330,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Encrypt UserId error for deleteRequestingMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.DeleteMembersPendingAdminApproval = append(groupChangeActions.DeleteMembersPendingAdminApproval, &signalpb.GroupChange_Actions_DeleteMemberPendingAdminApprovalAction{
|
||||
groupChangeActions.DeleteRequestingMembers = append(groupChangeActions.DeleteRequestingMembers, &signalpb.GroupChange_Actions_DeleteRequestingMemberAction{
|
||||
DeletedUserId: encryptedUserID[:],
|
||||
})
|
||||
}
|
||||
|
|
@ -1309,7 +1341,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
return nil, err
|
||||
}
|
||||
|
||||
groupChangeActions.PromoteMembersPendingAdminApproval = append(groupChangeActions.PromoteMembersPendingAdminApproval, &signalpb.GroupChange_Actions_PromoteMemberPendingAdminApprovalAction{
|
||||
groupChangeActions.PromoteRequestingMembers = append(groupChangeActions.PromoteRequestingMembers, &signalpb.GroupChange_Actions_PromoteRequestingMemberAction{
|
||||
UserId: encryptedUserID[:],
|
||||
Role: signalpb.Member_Role(promoteRequestingMember.Role),
|
||||
})
|
||||
|
|
@ -1320,8 +1352,8 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Encrypt UserId error for promoteRequestingMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.AddMembersBanned = append(groupChangeActions.AddMembersBanned, &signalpb.GroupChange_Actions_AddMemberBannedAction{
|
||||
Added: &signalpb.MemberBanned{
|
||||
groupChangeActions.AddBannedMembers = append(groupChangeActions.AddBannedMembers, &signalpb.GroupChange_Actions_AddBannedMemberAction{
|
||||
Added: &signalpb.BannedMember{
|
||||
UserId: encryptedUserID[:],
|
||||
Timestamp: addBannedMember.Timestamp,
|
||||
},
|
||||
|
|
@ -1333,7 +1365,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Encrypt UserId error for promoteRequestingMember")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.DeleteMembersBanned = append(groupChangeActions.DeleteMembersBanned, &signalpb.GroupChange_Actions_DeleteMemberBannedAction{
|
||||
groupChangeActions.DeleteBannedMembers = append(groupChangeActions.DeleteBannedMembers, &signalpb.GroupChange_Actions_DeleteBannedMemberAction{
|
||||
DeletedUserId: encryptedUserID[:],
|
||||
})
|
||||
}
|
||||
|
|
@ -1364,7 +1396,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
log.Err(err).Msg("Could not get encrypt Title")
|
||||
return nil, err
|
||||
}
|
||||
groupChangeActions.ModifyDisappearingMessageTimer = &signalpb.GroupChange_Actions_ModifyDisappearingMessageTimerAction{Timer: *encryptedTimer}
|
||||
groupChangeActions.ModifyDisappearingMessagesTimer = &signalpb.GroupChange_Actions_ModifyDisappearingMessagesTimerAction{Timer: *encryptedTimer}
|
||||
}
|
||||
if decryptedGroupChange.ModifyInviteLinkPassword != nil {
|
||||
inviteLinkPasswordBytes, err := inviteLinkPasswordToBytes(*decryptedGroupChange.ModifyInviteLinkPassword)
|
||||
|
|
@ -1379,7 +1411,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
|||
return cli.patchGroup(ctx, groupChangeActions, groupMasterKey, nil)
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMember(ctx context.Context, member *GroupMember, groupSecretParams *libsignalgo.GroupSecretParams) (*signalpb.Member, *signalpb.MemberPendingProfileKey, error) {
|
||||
func (cli *Client) encryptMember(ctx context.Context, member *GroupMember, groupSecretParams *libsignalgo.GroupSecretParams) (*signalpb.Member, *signalpb.PendingMember, error) {
|
||||
log := zerolog.Ctx(ctx)
|
||||
expiringProfileKeyCredential, err := cli.FetchExpiringProfileKeyCredentialById(ctx, member.ACI)
|
||||
if err != nil {
|
||||
|
|
@ -1407,7 +1439,7 @@ func (cli *Client) encryptMember(ctx context.Context, member *GroupMember, group
|
|||
return &encryptedMember, nil, nil
|
||||
}
|
||||
|
||||
func (cli *Client) encryptPendingMember(ctx context.Context, pendingMember *PendingMember, groupSecretParams *libsignalgo.GroupSecretParams) (*signalpb.MemberPendingProfileKey, error) {
|
||||
func (cli *Client) encryptPendingMember(ctx context.Context, pendingMember *PendingMember, groupSecretParams *libsignalgo.GroupSecretParams) (*signalpb.PendingMember, error) {
|
||||
log := zerolog.Ctx(ctx)
|
||||
encryptedUserID, err := groupSecretParams.EncryptServiceID(pendingMember.ServiceID)
|
||||
if err != nil {
|
||||
|
|
@ -1419,7 +1451,7 @@ func (cli *Client) encryptPendingMember(ctx context.Context, pendingMember *Pend
|
|||
log.Err(err).Msg("Encrypt AddedByUserId error for addPendingMember")
|
||||
return nil, err
|
||||
}
|
||||
encryptedPendingMember := signalpb.MemberPendingProfileKey{
|
||||
encryptedPendingMember := signalpb.PendingMember{
|
||||
AddedByUserId: encryptedAddedByUserID[:],
|
||||
Member: &signalpb.Member{
|
||||
UserId: encryptedUserID[:],
|
||||
|
|
@ -1472,9 +1504,9 @@ func (cli *Client) patchGroup(ctx context.Context, groupChange *signalpb.GroupCh
|
|||
Password: &groupAuth.Password,
|
||||
ContentType: web.ContentTypeProtobuf,
|
||||
Body: requestBody,
|
||||
Host: web.StorageHostname,
|
||||
}
|
||||
resp, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodPatch, path, opts)
|
||||
defer web.CloseBody(resp)
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPatch, path, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SendRequest error: %w", err)
|
||||
}
|
||||
|
|
@ -1513,21 +1545,17 @@ func (cli *Client) patchGroup(ctx context.Context, groupChange *signalpb.GroupCh
|
|||
return &changeResp, nil
|
||||
}
|
||||
|
||||
var ErrGroupMasterKeyNotFound = errors.New("group master key not found in store")
|
||||
|
||||
func (cli *Client) UpdateGroup(ctx context.Context, groupChange *GroupChange, gid types.GroupIdentifier) (uint32, error) {
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "UpdateGroup").Logger()
|
||||
groupMasterKey, err := cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get master key for group: %w", err)
|
||||
} else if groupMasterKey == "" {
|
||||
return 0, ErrGroupMasterKeyNotFound
|
||||
}
|
||||
groupChange.GroupMasterKey = groupMasterKey
|
||||
masterKeyBytes := masterKeyToBytes(groupMasterKey)
|
||||
var refetchedAddMemberCredentials bool
|
||||
var signedGroupChange *signalpb.GroupChangeResponse
|
||||
group, _, err := cli.RetrieveGroupByID(ctx, gid, 0)
|
||||
group, err := cli.RetrieveGroupByID(ctx, gid, 0)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to fetch group info to update: %w", err)
|
||||
}
|
||||
|
|
@ -1550,8 +1578,10 @@ func (cli *Client) UpdateGroup(ctx context.Context, groupChange *GroupChange, gi
|
|||
return 0, fmt.Errorf("failed to update group: %w", err)
|
||||
}
|
||||
} else if errors.Is(err, ConflictError) {
|
||||
cli.GroupCache.Delete(gid)
|
||||
group, _, err = cli.RetrieveGroupByID(ctx, gid, 0)
|
||||
delete(cli.GroupCache.groups, gid)
|
||||
delete(cli.GroupCache.lastFetched, gid)
|
||||
delete(cli.GroupCache.activeCalls, gid)
|
||||
group, err = cli.RetrieveGroupByID(ctx, gid, 0)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to fetch group after conflict: %w", err)
|
||||
}
|
||||
|
|
@ -1564,13 +1594,12 @@ func (cli *Client) UpdateGroup(ctx context.Context, groupChange *GroupChange, gi
|
|||
return 0, fmt.Errorf("unknown error encrypting and signing group change: %w", err)
|
||||
}
|
||||
}
|
||||
delete(cli.GroupCache.groups, gid)
|
||||
delete(cli.GroupCache.lastFetched, gid)
|
||||
delete(cli.GroupCache.activeCalls, gid)
|
||||
if signedGroupChange == nil {
|
||||
return 0, fmt.Errorf("no signed group change returned: %w", err)
|
||||
}
|
||||
err = cli.GroupCache.ApplyUpdate(groupChange, signedGroupChange.GroupSendEndorsementsResponse)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to apply group change to cache")
|
||||
}
|
||||
groupChangeBytes, err := proto.Marshal(signedGroupChange.GroupChange)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to marshal signed group change: %w", err)
|
||||
|
|
@ -1603,12 +1632,12 @@ func (cli *Client) EncryptGroup(ctx context.Context, decryptedGroup *Group, grou
|
|||
encryptedGroup := &signalpb.Group{
|
||||
PublicKey: groupPublicParams[:],
|
||||
Title: *encryptedTitle,
|
||||
AvatarUrl: decryptedGroup.AvatarPath,
|
||||
Avatar: decryptedGroup.AvatarPath,
|
||||
AnnouncementsOnly: decryptedGroup.AnnouncementsOnly,
|
||||
Version: 0,
|
||||
Revision: 0,
|
||||
}
|
||||
if decryptedGroup.Description != "" {
|
||||
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_DescriptionText{DescriptionText: decryptedGroup.Description}}
|
||||
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_Description{Description: decryptedGroup.Description}}
|
||||
encryptedDescription, err := encryptBlobIntoGroupProperty(groupSecretParams, &attributeBlob)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Could not get encrypt Description")
|
||||
|
|
@ -1634,7 +1663,7 @@ func (cli *Client) EncryptGroup(ctx context.Context, decryptedGroup *Group, grou
|
|||
if encryptedMember != nil {
|
||||
encryptedGroup.Members = append(encryptedGroup.Members, encryptedMember)
|
||||
} else {
|
||||
encryptedGroup.MembersPendingProfileKey = append(encryptedGroup.MembersPendingProfileKey, encryptedPendingMember)
|
||||
encryptedGroup.PendingMembers = append(encryptedGroup.PendingMembers, encryptedPendingMember)
|
||||
}
|
||||
}
|
||||
for _, pendingMember := range decryptedGroup.PendingMembers {
|
||||
|
|
@ -1643,7 +1672,7 @@ func (cli *Client) EncryptGroup(ctx context.Context, decryptedGroup *Group, grou
|
|||
log.Err(err).Msg("Failed to encrypt pendingMember")
|
||||
return nil, err
|
||||
}
|
||||
encryptedGroup.MembersPendingProfileKey = append(encryptedGroup.MembersPendingProfileKey, encryptedPendingMember)
|
||||
encryptedGroup.PendingMembers = append(encryptedGroup.PendingMembers, encryptedPendingMember)
|
||||
}
|
||||
return encryptedGroup, nil
|
||||
}
|
||||
|
|
@ -1666,7 +1695,7 @@ func PrepareGroupCreation(decryptedGroup *Group) (libsignalgo.GroupMasterKey, er
|
|||
return masterKeyBytes, nil
|
||||
}
|
||||
|
||||
func (cli *Client) createGroupOnServer(ctx context.Context, decryptedGroup *Group) (*Group, error) {
|
||||
func (cli *Client) createGroupOnServer(ctx context.Context, decryptedGroup *Group, avatarBytes []byte) (*Group, error) {
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "CreateGroupOnServer").Logger()
|
||||
masterKeyBytes, err := PrepareGroupCreation(decryptedGroup)
|
||||
if err != nil {
|
||||
|
|
@ -1681,6 +1710,14 @@ func (cli *Client) createGroupOnServer(ctx context.Context, decryptedGroup *Grou
|
|||
log.Err(err).Msg("DeriveGroupSecretParamsFromMasterKey error")
|
||||
return nil, err
|
||||
}
|
||||
if len(avatarBytes) > 0 {
|
||||
avatarPath, err := cli.UploadGroupAvatar(ctx, avatarBytes, decryptedGroup.GroupIdentifier)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to upload group avatar")
|
||||
return nil, err
|
||||
}
|
||||
decryptedGroup.AvatarPath = avatarPath
|
||||
}
|
||||
encryptedGroup, err := cli.EncryptGroup(ctx, decryptedGroup, groupSecretParams)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to encrypt group")
|
||||
|
|
@ -1702,9 +1739,9 @@ func (cli *Client) createGroupOnServer(ctx context.Context, decryptedGroup *Grou
|
|||
Password: &groupAuth.Password,
|
||||
ContentType: web.ContentTypeProtobuf,
|
||||
Body: requestBody,
|
||||
Host: web.StorageHostname,
|
||||
}
|
||||
resp, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodPut, path, opts)
|
||||
defer web.CloseBody(resp)
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPut, path, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SendRequest error: %w", err)
|
||||
}
|
||||
|
|
@ -1731,9 +1768,9 @@ func GenerateInviteLinkPassword() types.SerializedInviteLinkPassword {
|
|||
return InviteLinkPasswordFromBytes(random.Bytes(16))
|
||||
}
|
||||
|
||||
func (cli *Client) CreateGroup(ctx context.Context, decryptedGroup *Group) (*Group, error) {
|
||||
func (cli *Client) CreateGroup(ctx context.Context, decryptedGroup *Group, avatarBytes []byte) (*Group, error) {
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "CreateGroup").Logger()
|
||||
group, err := cli.createGroupOnServer(ctx, decryptedGroup)
|
||||
group, err := cli.createGroupOnServer(ctx, decryptedGroup, avatarBytes)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error creating group on server")
|
||||
return nil, err
|
||||
|
|
@ -1756,7 +1793,7 @@ func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdent
|
|||
return nil, err
|
||||
}
|
||||
if groupMasterKey == "" {
|
||||
return nil, ErrGroupMasterKeyNotFound
|
||||
return nil, fmt.Errorf("No group master key found for group identifier %s", gid)
|
||||
}
|
||||
masterKeyBytes := masterKeyToBytes(groupMasterKey)
|
||||
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyBytes)
|
||||
|
|
@ -1767,6 +1804,7 @@ func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdent
|
|||
Username: &groupAuth.Username,
|
||||
Password: &groupAuth.Password,
|
||||
ContentType: web.ContentTypeProtobuf,
|
||||
Host: web.StorageHostname,
|
||||
Headers: map[string]string{
|
||||
// TODO actually cache the data and provide real expiry timestamp
|
||||
"Cached-Send-Endorsements": "0",
|
||||
|
|
@ -1774,8 +1812,7 @@ func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdent
|
|||
}
|
||||
// highest known epoch seems to always be 5, but that may change in the future. includeLastState is always false
|
||||
path := fmt.Sprintf("/v2/groups/logs/%d?maxSupportedChangeEpoch=%d&includeFirstState=%t&includeLastState=false", fromRevision, 5, includeFirstState)
|
||||
response, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, path, opts)
|
||||
defer web.CloseBody(response)
|
||||
response, err := web.SendHTTPRequest(ctx, http.MethodGet, path, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -43,6 +43,25 @@ type GeneratedPreKeys struct {
|
|||
IdentityKey []uint8
|
||||
}
|
||||
|
||||
func (cli *Client) GenerateAndRegisterPreKeys(ctx context.Context, pks store.PreKeyStore) error {
|
||||
_, err := cli.GenerateAndSaveNextPreKeyBatch(ctx, pks, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate and save next prekey batch: %w", err)
|
||||
}
|
||||
_, err = cli.GenerateAndSaveNextKyberPreKeyBatch(ctx, pks, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate and save next kyber prekey batch: %w", err)
|
||||
}
|
||||
|
||||
// We need to upload all currently valid prekeys, not just the ones we just generated
|
||||
err = cli.RegisterAllPreKeys(ctx, pks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register prekey batches: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *Client) RegisterAllPreKeys(ctx context.Context, pks store.PreKeyStore) error {
|
||||
var identityKeyPair *libsignalgo.IdentityKeyPair
|
||||
var pni bool
|
||||
|
|
@ -78,11 +97,10 @@ func (cli *Client) RegisterAllPreKeys(ctx context.Context, pks store.PreKeyStore
|
|||
KyberPreKeys: kyberPreKeys,
|
||||
IdentityKey: identityKey,
|
||||
}
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Int("num_prekeys", len(preKeys)).
|
||||
Int("num_kyber_prekeys", len(kyberPreKeys)).
|
||||
Msg("Registering all prekeys")
|
||||
err = cli.RegisterPreKeys(ctx, &generatedPreKeys, pni)
|
||||
preKeyUsername := fmt.Sprintf("%s.%d", cli.Store.ACI, cli.Store.DeviceID)
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "register prekeys").Logger()
|
||||
log.Debug().Int("num_prekeys", len(preKeys)).Int("num_kyber_prekeys", len(kyberPreKeys)).Msg("Registering prekeys")
|
||||
err = RegisterPreKeys(ctx, &generatedPreKeys, pni, preKeyUsername, cli.Store.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register prekeys: %w", err)
|
||||
}
|
||||
|
|
@ -328,11 +346,11 @@ func KyberPreKeyToJSON(kyberPreKey *libsignalgo.KyberPreKeyRecord) (map[string]i
|
|||
|
||||
var errPrekeyUpload422 = errors.New("http 422 while registering prekeys")
|
||||
|
||||
func (cli *Client) RegisterPreKeys(ctx context.Context, generatedPreKeys *GeneratedPreKeys, pni bool) error {
|
||||
func RegisterPreKeys(ctx context.Context, generatedPreKeys *GeneratedPreKeys, pni bool, username string, password string) error {
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "register prekeys").Logger()
|
||||
// Convert generated prekeys to JSON
|
||||
preKeysJson := []map[string]any{}
|
||||
kyberPreKeysJson := []map[string]any{}
|
||||
preKeysJson := []map[string]interface{}{}
|
||||
kyberPreKeysJson := []map[string]interface{}{}
|
||||
for _, preKey := range generatedPreKeys.PreKeys {
|
||||
preKeyJson, err := PreKeyToJSON(preKey)
|
||||
if err != nil {
|
||||
|
|
@ -349,27 +367,32 @@ func (cli *Client) RegisterPreKeys(ctx context.Context, generatedPreKeys *Genera
|
|||
}
|
||||
|
||||
identityKey := generatedPreKeys.IdentityKey
|
||||
registerJSON := map[string]any{
|
||||
register_json := map[string]interface{}{
|
||||
"preKeys": preKeysJson,
|
||||
"pqPreKeys": kyberPreKeysJson,
|
||||
"identityKey": base64.StdEncoding.EncodeToString(identityKey),
|
||||
}
|
||||
|
||||
// Send request
|
||||
jsonBytes, err := json.Marshal(registerJSON)
|
||||
jsonBytes, err := json.Marshal(register_json)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error marshalling register JSON")
|
||||
return err
|
||||
}
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodPut, keysPath(pni), jsonBytes, nil)
|
||||
opts := &web.HTTPReqOpt{Body: jsonBytes, Username: &username, Password: &password}
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPut, keysPath(pni), opts)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error sending request")
|
||||
return err
|
||||
}
|
||||
if resp.GetStatus() == 422 {
|
||||
defer resp.Body.Close()
|
||||
// status code not 2xx
|
||||
if resp.StatusCode == 422 {
|
||||
return errPrekeyUpload422
|
||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("error registering prekeys: %v", resp.Status)
|
||||
}
|
||||
return web.DecodeWSResponseBody(ctx, nil, resp)
|
||||
return err
|
||||
}
|
||||
|
||||
type prekeyResponse struct {
|
||||
|
|
@ -404,40 +427,25 @@ func addBase64PaddingAndDecode(data string) ([]byte, error) {
|
|||
return base64.StdEncoding.DecodeString(data)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUnregisteredUser = errors.New("user is unregistered")
|
||||
ErrDevicesChanged = errors.New("device list changed while sending skdm")
|
||||
)
|
||||
|
||||
func (cli *Client) FetchAndProcessPreKey(ctx context.Context, theirServiceID libsignalgo.ServiceID, specificDeviceID int) error {
|
||||
if cli.Store.RecipientStore.IsUnregistered(ctx, theirServiceID) {
|
||||
return fmt.Errorf("%w (cached)", ErrUnregisteredUser)
|
||||
}
|
||||
localAddress, err := cli.Store.ACIServiceID().Address(uint(cli.Store.DeviceID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get own address: %w", err)
|
||||
}
|
||||
// Fetch prekey
|
||||
deviceIDPath := "/*"
|
||||
if specificDeviceID >= 0 {
|
||||
deviceIDPath = "/" + fmt.Sprint(specificDeviceID)
|
||||
}
|
||||
// TODO this should be done via the unauthed websocket if possible
|
||||
path := "/v2/keys/" + theirServiceID.String() + deviceIDPath + "?pq=true"
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodGet, path, nil, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, path, &web.HTTPReqOpt{Username: &username, Password: &password})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending request: %w", err)
|
||||
} else if resp.GetStatus() == 404 {
|
||||
cli.Store.RecipientStore.MarkUnregistered(ctx, theirServiceID, true)
|
||||
return fmt.Errorf("%w (404 while querying keys)", ErrUnregisteredUser)
|
||||
}
|
||||
var respData prekeyResponse
|
||||
err = web.DecodeWSResponseBody(ctx, &respData, resp)
|
||||
var prekeyResponse prekeyResponse
|
||||
err = web.DecodeHTTPResponseBody(ctx, &prekeyResponse, resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding response body: %w", err)
|
||||
}
|
||||
|
||||
rawIdentityKey, err := addBase64PaddingAndDecode(respData.IdentityKey)
|
||||
rawIdentityKey, err := addBase64PaddingAndDecode(prekeyResponse.IdentityKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding identity key: %w", err)
|
||||
}
|
||||
|
|
@ -450,7 +458,7 @@ func (cli *Client) FetchAndProcessPreKey(ctx context.Context, theirServiceID lib
|
|||
}
|
||||
|
||||
// Process each prekey in response (should only be one at the moment)
|
||||
for _, d := range respData.Devices {
|
||||
for _, d := range prekeyResponse.Devices {
|
||||
var publicKey *libsignalgo.PublicKey
|
||||
var preKeyID uint32
|
||||
if d.PreKey != nil {
|
||||
|
|
@ -522,7 +530,6 @@ func (cli *Client) FetchAndProcessPreKey(ctx context.Context, theirServiceID lib
|
|||
ctx,
|
||||
preKeyBundle,
|
||||
address,
|
||||
localAddress,
|
||||
cli.Store.ACISessionStore,
|
||||
cli.Store.ACIIdentityStore,
|
||||
)
|
||||
|
|
@ -548,18 +555,19 @@ func keysPath(pni bool) string {
|
|||
|
||||
func (cli *Client) GetMyKeyCounts(ctx context.Context, pni bool) (int, int, error) {
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "get my key counts").Logger()
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodGet, keysPath(pni), nil, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, keysPath(pni), &web.HTTPReqOpt{Username: &username, Password: &password})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error sending request")
|
||||
return 0, 0, err
|
||||
}
|
||||
var respData preKeyCountResponse
|
||||
err = web.DecodeWSResponseBody(ctx, &respData, resp)
|
||||
var preKeyCountResponse preKeyCountResponse
|
||||
err = web.DecodeHTTPResponseBody(ctx, &preKeyCountResponse, resp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Fetching prekey counts, error with response body")
|
||||
return 0, 0, err
|
||||
}
|
||||
return respData.Count, respData.PQCount, err
|
||||
return preKeyCountResponse.Count, preKeyCountResponse.PQCount, err
|
||||
}
|
||||
|
||||
func (cli *Client) CheckAndUploadNewPreKeys(ctx context.Context, pks store.PreKeyStore) error {
|
||||
|
|
@ -596,29 +604,23 @@ func (cli *Client) keyCheckLoop(ctx context.Context) {
|
|||
log := zerolog.Ctx(ctx).With().Str("action", "start key check loop").Logger()
|
||||
|
||||
// Do the initial check in 5-10 minutes after starting the loop
|
||||
windowStart := 0
|
||||
windowSize := 1
|
||||
firstRun := true
|
||||
window_start := 0
|
||||
window_size := 1
|
||||
for {
|
||||
randomMinutesInWindow := rand.IntN(windowSize) + windowStart
|
||||
checkTime := time.Duration(randomMinutesInWindow) * time.Minute
|
||||
if firstRun {
|
||||
checkTime = 0
|
||||
firstRun = false
|
||||
} else {
|
||||
log.Debug().Dur("check_time", checkTime).Msg("Waiting to check for new prekeys")
|
||||
}
|
||||
random_minutes_in_window := rand.Intn(window_size) + window_start
|
||||
check_time := time.Duration(random_minutes_in_window) * time.Minute
|
||||
log.Debug().Dur("check_time", check_time).Msg("Waiting to check for new prekeys")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(checkTime):
|
||||
case <-time.After(check_time):
|
||||
err := cli.CheckAndUploadNewPreKeys(ctx, cli.Store.ACIPreKeyStore)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error checking and uploading new prekeys for ACI identity")
|
||||
// Retry within half an hour
|
||||
windowStart = 5
|
||||
windowSize = 25
|
||||
window_start = 5
|
||||
window_size = 25
|
||||
continue
|
||||
}
|
||||
err = cli.CheckAndUploadNewPreKeys(ctx, cli.Store.PNIPreKeyStore)
|
||||
|
|
@ -634,13 +636,13 @@ func (cli *Client) keyCheckLoop(ctx context.Context) {
|
|||
}
|
||||
log.Err(err).Msg("Error checking and uploading new prekeys for PNI identity")
|
||||
// Retry within half an hour
|
||||
windowStart = 5
|
||||
windowSize = 25
|
||||
window_start = 5
|
||||
window_size = 25
|
||||
continue
|
||||
}
|
||||
// After a successful check, check again in 36 to 60 hours
|
||||
windowStart = 36 * 60
|
||||
windowSize = 24 * 60
|
||||
window_start = 36 * 60
|
||||
window_size = 24 * 60
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ package signalmeow
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exerrors"
|
||||
|
||||
|
|
@ -69,8 +66,6 @@ func (l FFILogger) Log(level libsignalgo.LogLevel, file string, line uint, messa
|
|||
|
||||
func (FFILogger) Flush() {}
|
||||
|
||||
func (FFILogger) Destroy() {}
|
||||
|
||||
// Ensure FFILogger implements the Logger interface
|
||||
var _ libsignalgo.Logger = FFILogger{}
|
||||
|
||||
|
|
@ -81,28 +76,3 @@ var prodServerPublicParams *libsignalgo.ServerPublicParams
|
|||
func init() {
|
||||
prodServerPublicParams = exerrors.Must(libsignalgo.DeserializeServerPublicParams(prodServerPublicParamsSlice))
|
||||
}
|
||||
|
||||
var ErrEmptyUUIDInput = errors.New("both input variables are empty")
|
||||
|
||||
func ParseStringOrBinaryServiceID(str string, bytes []byte) (libsignalgo.ServiceID, error) {
|
||||
if str != "" {
|
||||
return libsignalgo.ServiceIDFromString(str)
|
||||
}
|
||||
if bytes != nil {
|
||||
return libsignalgo.ServiceIDFromBytes(bytes)
|
||||
}
|
||||
return libsignalgo.EmptyServiceID, ErrEmptyUUIDInput
|
||||
}
|
||||
|
||||
func ParseStringOrBinaryUUID(str string, bytes []byte) (uuid.UUID, error) {
|
||||
if str != "" {
|
||||
return uuid.Parse(str)
|
||||
}
|
||||
if bytes != nil {
|
||||
if len(bytes) != 16 {
|
||||
return uuid.Nil, fmt.Errorf("invalid UUID length %d (expected 16)", len(bytes))
|
||||
}
|
||||
return uuid.UUID(bytes), nil
|
||||
}
|
||||
return uuid.Nil, ErrEmptyUUIDInput
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,14 @@ func (cli *Client) getCachedProfileByID(signalID uuid.UUID, refreshAfter time.Du
|
|||
}
|
||||
|
||||
func (cli *Client) RetrieveProfileByID(ctx context.Context, signalID uuid.UUID, refreshAfter time.Duration) (*types.Profile, error) {
|
||||
if cli.ProfileCache == nil {
|
||||
cli.ProfileCache = &ProfileCache{
|
||||
profiles: make(map[string]*types.Profile),
|
||||
errors: make(map[string]*error),
|
||||
lastFetched: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have a cached profile that is less than an hour old
|
||||
// or if we have a cached error that is less than an hour old
|
||||
profile, err := cli.getCachedProfileByID(signalID, refreshAfter)
|
||||
|
|
@ -210,18 +218,18 @@ func (cli *Client) fetchProfileWithRequestAndKey(ctx context.Context, signalID u
|
|||
path += "/" + string(credentialRequest)
|
||||
path += "?credentialType=expiringProfileKey"
|
||||
}
|
||||
headers := http.Header{}
|
||||
profileRequest := web.CreateWSRequest(http.MethodGet, path, nil, nil, nil)
|
||||
if useUnidentified {
|
||||
headers.Set("Unidentified-Access-Key", base64AccessKey)
|
||||
headers.Set("Accept-Language", "en-US")
|
||||
profileRequest.Headers = append(profileRequest.Headers, "unidentified-access-key:"+base64AccessKey)
|
||||
profileRequest.Headers = append(profileRequest.Headers, "accept-language:en-CA")
|
||||
}
|
||||
resp, err := cli.UnauthedWS.SendRequest(ctx, http.MethodGet, path, nil, headers)
|
||||
resp, err := cli.UnauthedWS.SendRequest(ctx, profileRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error sending request: %w", err)
|
||||
}
|
||||
var profile types.Profile
|
||||
profile.FetchedAt = time.Now()
|
||||
logEvt := log.Trace().Uint32("status_code", resp.GetStatus()).Str("resp_message", resp.GetMessage())
|
||||
logEvt := log.Trace().Uint32("status_code", resp.GetStatus())
|
||||
if logEvt.Enabled() {
|
||||
if json.Valid(resp.Body) {
|
||||
logEvt.RawJSON("response_data", resp.Body)
|
||||
|
|
@ -280,14 +288,15 @@ func (cli *Client) fetchProfileWithRequestAndKey(ctx context.Context, signalID u
|
|||
func (cli *Client) DownloadUserAvatar(ctx context.Context, avatarPath string, profileKey libsignalgo.ProfileKey) ([]byte, error) {
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
opts := &web.HTTPReqOpt{
|
||||
Host: web.CDN1Hostname,
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
}
|
||||
resp, err := web.SendHTTPRequest(ctx, web.CDN1Hostname, http.MethodGet, avatarPath, opts)
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, avatarPath, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer web.CloseBody(resp)
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("unexpected response status %d", resp.StatusCode)
|
||||
}
|
||||
|
|
|
|||
4
pkg/signalmeow/protobuf/ContactDiscovery.pb.go
generated
4
pkg/signalmeow/protobuf/ContactDiscovery.pb.go
generated
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: ContactDiscovery.proto
|
||||
|
||||
// Copyright 2021 Signal Messenger, LLC
|
||||
|
|
|
|||
4
pkg/signalmeow/protobuf/DeviceName.pb.go
generated
4
pkg/signalmeow/protobuf/DeviceName.pb.go
generated
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: DeviceName.proto
|
||||
|
||||
// Copyright 2018 Signal Messenger, LLC
|
||||
|
|
|
|||
1978
pkg/signalmeow/protobuf/Groups.pb.go
generated
1978
pkg/signalmeow/protobuf/Groups.pb.go
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,139 +1,92 @@
|
|||
/*
|
||||
* Copyright 2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
/**
|
||||
* Copyright (C) 2019 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package signal;
|
||||
|
||||
option java_package = "org.signal.storageservice.storage.protos.groups";
|
||||
option java_outer_classname = "GroupProtos";
|
||||
option java_package = "org.signal.storageservice.protos.groups";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message AvatarUploadAttributes {
|
||||
string key = 1;
|
||||
string key = 1;
|
||||
string credential = 2;
|
||||
string acl = 3;
|
||||
string algorithm = 4;
|
||||
string date = 5;
|
||||
string policy = 6;
|
||||
string signature = 7;
|
||||
string acl = 3;
|
||||
string algorithm = 4;
|
||||
string date = 5;
|
||||
string policy = 6;
|
||||
string signature = 7;
|
||||
}
|
||||
|
||||
// Stored data
|
||||
|
||||
message Member {
|
||||
enum Role {
|
||||
UNKNOWN = 0;
|
||||
DEFAULT = 1;
|
||||
UNKNOWN = 0;
|
||||
DEFAULT = 1;
|
||||
ADMINISTRATOR = 2;
|
||||
}
|
||||
|
||||
bytes userId = 1;
|
||||
Role role = 2;
|
||||
bytes profileKey = 3;
|
||||
bytes presentation = 4;
|
||||
uint32 joinedAtVersion = 5;
|
||||
bytes labelEmoji = 6; // decrypts to a UTF-8 string
|
||||
bytes labelString = 7; // decrypts to a UTF-8 string
|
||||
bytes userId = 1;
|
||||
Role role = 2;
|
||||
bytes profileKey = 3;
|
||||
bytes presentation = 4; // Only set when sending to server
|
||||
uint32 joinedAtRevision = 5;
|
||||
}
|
||||
|
||||
message MemberPendingProfileKey {
|
||||
Member member = 1;
|
||||
bytes addedByUserId = 2;
|
||||
uint64 timestamp = 3; // ms since epoch
|
||||
message PendingMember {
|
||||
Member member = 1;
|
||||
bytes addedByUserId = 2;
|
||||
uint64 timestamp = 3;
|
||||
}
|
||||
|
||||
message MemberPendingAdminApproval {
|
||||
bytes userId = 1;
|
||||
bytes profileKey = 2;
|
||||
bytes presentation = 3;
|
||||
uint64 timestamp = 4; // ms since epoch
|
||||
message RequestingMember {
|
||||
bytes userId = 1;
|
||||
bytes profileKey = 2;
|
||||
bytes presentation = 3; // Only set when sending to server
|
||||
uint64 timestamp = 4;
|
||||
}
|
||||
|
||||
message MemberBanned {
|
||||
bytes userId = 1;
|
||||
uint64 timestamp = 2; // ms since epoch
|
||||
message BannedMember {
|
||||
bytes userId = 1;
|
||||
uint64 timestamp = 2;
|
||||
}
|
||||
|
||||
message AccessControl {
|
||||
enum AccessRequired {
|
||||
UNKNOWN = 0;
|
||||
ANY = 1;
|
||||
MEMBER = 2;
|
||||
UNKNOWN = 0;
|
||||
ANY = 1;
|
||||
MEMBER = 2;
|
||||
ADMINISTRATOR = 3;
|
||||
UNSATISFIABLE = 4;
|
||||
}
|
||||
|
||||
AccessRequired attributes = 1;
|
||||
AccessRequired members = 2;
|
||||
AccessRequired attributes = 1;
|
||||
AccessRequired members = 2;
|
||||
AccessRequired addFromInviteLink = 3;
|
||||
AccessRequired memberLabel = 4;
|
||||
}
|
||||
|
||||
message Group {
|
||||
bytes publicKey = 1;
|
||||
bytes title = 2;
|
||||
bytes description = 11;
|
||||
// The URL for this group's avatar. The content at this URL can be
|
||||
// decrypted/deserialized into a `GroupAttributeBlob`.
|
||||
string avatarUrl = 3;
|
||||
bytes disappearingMessagesTimer = 4;
|
||||
AccessControl accessControl = 5;
|
||||
uint32 version = 6;
|
||||
repeated Member members = 7;
|
||||
repeated MemberPendingProfileKey membersPendingProfileKey = 8;
|
||||
repeated MemberPendingAdminApproval membersPendingAdminApproval = 9;
|
||||
bytes inviteLinkPassword = 10;
|
||||
bool announcements_only = 12;
|
||||
repeated MemberBanned members_banned = 13;
|
||||
bool terminated = 14;
|
||||
// next: 15
|
||||
bytes publicKey = 1;
|
||||
bytes title = 2;
|
||||
string avatar = 3;
|
||||
bytes disappearingMessagesTimer = 4;
|
||||
AccessControl accessControl = 5;
|
||||
uint32 revision = 6;
|
||||
repeated Member members = 7;
|
||||
repeated PendingMember pendingMembers = 8;
|
||||
repeated RequestingMember requestingMembers = 9;
|
||||
bytes inviteLinkPassword = 10;
|
||||
bytes description = 11;
|
||||
bool announcementsOnly = 12;
|
||||
repeated BannedMember bannedMembers = 13;
|
||||
}
|
||||
|
||||
message GroupAttributeBlob {
|
||||
oneof content {
|
||||
string title = 1;
|
||||
bytes avatar = 2;
|
||||
uint32 disappearingMessagesDuration = 3;
|
||||
string descriptionText = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message GroupInviteLink {
|
||||
message GroupInviteLinkContentsV1 {
|
||||
bytes groupMasterKey = 1;
|
||||
bytes inviteLinkPassword = 2;
|
||||
}
|
||||
|
||||
oneof contents {
|
||||
GroupInviteLinkContentsV1 contentsV1 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message GroupJoinInfo {
|
||||
bytes publicKey = 1;
|
||||
bytes title = 2;
|
||||
bytes description = 8;
|
||||
string avatar = 3;
|
||||
uint32 memberCount = 4;
|
||||
AccessControl.AccessRequired addFromInviteLink = 5;
|
||||
uint32 version = 6;
|
||||
bool pendingAdminApproval = 7;
|
||||
// bool pendingAdminApprovalFull = 9;
|
||||
// next: 10
|
||||
}
|
||||
|
||||
// Deltas
|
||||
|
||||
message GroupChange {
|
||||
|
||||
message Actions {
|
||||
|
||||
message AddMemberAction {
|
||||
Member added = 1;
|
||||
bool joinFromInviteLink = 2;
|
||||
Member added = 1;
|
||||
bool joinFromInviteLink = 2;
|
||||
}
|
||||
|
||||
message DeleteMemberAction {
|
||||
|
|
@ -141,61 +94,55 @@ message GroupChange {
|
|||
}
|
||||
|
||||
message ModifyMemberRoleAction {
|
||||
bytes userId = 1;
|
||||
Member.Role role = 2;
|
||||
}
|
||||
|
||||
message ModifyMemberLabelAction {
|
||||
bytes userId = 1;
|
||||
bytes labelEmoji = 2; // decrypts to a UTF-8 string
|
||||
bytes labelString = 3; // decrypts to a UTF-8 string
|
||||
bytes userId = 1;
|
||||
Member.Role role = 2;
|
||||
}
|
||||
|
||||
message ModifyMemberProfileKeyAction {
|
||||
bytes presentation = 1;
|
||||
bytes user_id = 2;
|
||||
bytes profile_key = 3;
|
||||
bytes presentation = 1; // Only set when sending to server
|
||||
bytes user_id = 2; // Only set when receiving from server
|
||||
bytes profile_key = 3; // Only set when receiving from server
|
||||
}
|
||||
|
||||
message AddMemberPendingProfileKeyAction {
|
||||
MemberPendingProfileKey added = 1;
|
||||
message AddPendingMemberAction {
|
||||
PendingMember added = 1;
|
||||
}
|
||||
|
||||
message DeleteMemberPendingProfileKeyAction {
|
||||
message DeletePendingMemberAction {
|
||||
bytes deletedUserId = 1;
|
||||
}
|
||||
|
||||
message PromoteMemberPendingProfileKeyAction {
|
||||
bytes presentation = 1;
|
||||
bytes user_id = 2;
|
||||
bytes profile_key = 3;
|
||||
message PromotePendingMemberAction {
|
||||
bytes presentation = 1; // Only set when sending to server
|
||||
bytes user_id = 2; // Only set when receiving from server
|
||||
bytes profile_key = 3; // Only set when receiving from server
|
||||
}
|
||||
|
||||
message PromoteMemberPendingPniAciProfileKeyAction {
|
||||
bytes presentation = 1;
|
||||
bytes user_id = 2;
|
||||
bytes pni = 3;
|
||||
bytes profile_key = 4;
|
||||
message PromotePendingPniAciMemberProfileKeyAction {
|
||||
bytes presentation = 1; // Only set when sending to server
|
||||
bytes userId = 2; // Only set when receiving from server
|
||||
bytes pni = 3; // Only set when receiving from server
|
||||
bytes profileKey = 4; // Only set when receiving from server
|
||||
}
|
||||
|
||||
message AddMemberPendingAdminApprovalAction {
|
||||
MemberPendingAdminApproval added = 1;
|
||||
message AddRequestingMemberAction {
|
||||
RequestingMember added = 1;
|
||||
}
|
||||
|
||||
message DeleteMemberPendingAdminApprovalAction {
|
||||
message DeleteRequestingMemberAction {
|
||||
bytes deletedUserId = 1;
|
||||
}
|
||||
|
||||
message PromoteMemberPendingAdminApprovalAction {
|
||||
bytes userId = 1;
|
||||
Member.Role role = 2;
|
||||
message PromoteRequestingMemberAction {
|
||||
bytes userId = 1;
|
||||
Member.Role role = 2;
|
||||
}
|
||||
|
||||
message AddMemberBannedAction {
|
||||
MemberBanned added = 1;
|
||||
message AddBannedMemberAction {
|
||||
BannedMember added = 1;
|
||||
}
|
||||
|
||||
message DeleteMemberBannedAction {
|
||||
message DeleteBannedMemberAction {
|
||||
bytes deletedUserId = 1;
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +158,7 @@ message GroupChange {
|
|||
string avatar = 1;
|
||||
}
|
||||
|
||||
message ModifyDisappearingMessageTimerAction {
|
||||
message ModifyDisappearingMessagesTimerAction {
|
||||
bytes timer = 1;
|
||||
}
|
||||
|
||||
|
|
@ -227,83 +174,97 @@ message GroupChange {
|
|||
AccessControl.AccessRequired addFromInviteLinkAccess = 1;
|
||||
}
|
||||
|
||||
message ModifyMemberLabelAccessControlAction {
|
||||
AccessControl.AccessRequired memberLabelAccess = 1;
|
||||
}
|
||||
|
||||
message ModifyInviteLinkPasswordAction {
|
||||
bytes inviteLinkPassword = 1;
|
||||
}
|
||||
|
||||
message ModifyAnnouncementsOnlyAction {
|
||||
bool announcements_only = 1;
|
||||
bool announcementsOnly = 1;
|
||||
}
|
||||
|
||||
message TerminateGroupAction {}
|
||||
|
||||
bytes sourceUserId = 1;
|
||||
// clients should not provide this value; the server will provide it in the response buffer to ensure the signature is binding to a particular group
|
||||
// if clients set it during a request the server will respond with 400.
|
||||
bytes group_id = 25;
|
||||
uint32 version = 2;
|
||||
|
||||
repeated AddMemberAction addMembers = 3;
|
||||
repeated DeleteMemberAction deleteMembers = 4;
|
||||
repeated ModifyMemberRoleAction modifyMemberRoles = 5;
|
||||
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6;
|
||||
repeated AddMemberPendingProfileKeyAction addMembersPendingProfileKey = 7;
|
||||
repeated DeleteMemberPendingProfileKeyAction deleteMembersPendingProfileKey = 8;
|
||||
repeated PromoteMemberPendingProfileKeyAction promoteMembersPendingProfileKey = 9;
|
||||
ModifyTitleAction modifyTitle = 10;
|
||||
ModifyAvatarAction modifyAvatar = 11;
|
||||
ModifyDisappearingMessageTimerAction modifyDisappearingMessageTimer = 12;
|
||||
ModifyAttributesAccessControlAction modifyAttributesAccess = 13;
|
||||
ModifyMembersAccessControlAction modifyMemberAccess = 14;
|
||||
ModifyAddFromInviteLinkAccessControlAction modifyAddFromInviteLinkAccess = 15; // change epoch = 1
|
||||
repeated AddMemberPendingAdminApprovalAction addMembersPendingAdminApproval = 16; // change epoch = 1
|
||||
repeated DeleteMemberPendingAdminApprovalAction deleteMembersPendingAdminApproval = 17; // change epoch = 1
|
||||
repeated PromoteMemberPendingAdminApprovalAction promoteMembersPendingAdminApproval = 18; // change epoch = 1
|
||||
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19; // change epoch = 1
|
||||
ModifyDescriptionAction modifyDescription = 20; // change epoch = 2
|
||||
ModifyAnnouncementsOnlyAction modify_announcements_only = 21; // change epoch = 3
|
||||
repeated AddMemberBannedAction add_members_banned = 22; // change epoch = 4
|
||||
repeated DeleteMemberBannedAction delete_members_banned = 23; // change epoch = 4
|
||||
repeated PromoteMemberPendingPniAciProfileKeyAction promote_members_pending_pni_aci_profile_key = 24; // change epoch = 5
|
||||
repeated ModifyMemberLabelAction modifyMemberLabels = 26; // change epoch = 6;
|
||||
ModifyMemberLabelAccessControlAction modifyMemberLabelAccess = 27; // change epoch = 6
|
||||
TerminateGroupAction terminate_group = 28; // change epoch = 7
|
||||
// next: 29
|
||||
bytes sourceServiceId = 1;
|
||||
bytes groupId = 25; // Only set when receiving from server
|
||||
uint32 revision = 2;
|
||||
repeated AddMemberAction addMembers = 3;
|
||||
repeated DeleteMemberAction deleteMembers = 4;
|
||||
repeated ModifyMemberRoleAction modifyMemberRoles = 5;
|
||||
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6;
|
||||
repeated AddPendingMemberAction addPendingMembers = 7;
|
||||
repeated DeletePendingMemberAction deletePendingMembers = 8;
|
||||
repeated PromotePendingMemberAction promotePendingMembers = 9;
|
||||
ModifyTitleAction modifyTitle = 10;
|
||||
ModifyAvatarAction modifyAvatar = 11;
|
||||
ModifyDisappearingMessagesTimerAction modifyDisappearingMessagesTimer = 12;
|
||||
ModifyAttributesAccessControlAction modifyAttributesAccess = 13;
|
||||
ModifyMembersAccessControlAction modifyMemberAccess = 14;
|
||||
ModifyAddFromInviteLinkAccessControlAction modifyAddFromInviteLinkAccess = 15;
|
||||
repeated AddRequestingMemberAction addRequestingMembers = 16;
|
||||
repeated DeleteRequestingMemberAction deleteRequestingMembers = 17;
|
||||
repeated PromoteRequestingMemberAction promoteRequestingMembers = 18;
|
||||
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19;
|
||||
ModifyDescriptionAction modifyDescription = 20;
|
||||
ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21;
|
||||
repeated AddBannedMemberAction addBannedMembers = 22;
|
||||
repeated DeleteBannedMemberAction deleteBannedMembers = 23;
|
||||
repeated PromotePendingPniAciMemberProfileKeyAction promotePendingPniAciMembers = 24;
|
||||
}
|
||||
|
||||
bytes actions = 1;
|
||||
bytes serverSignature = 2;
|
||||
uint32 changeEpoch = 3;
|
||||
bytes actions = 1;
|
||||
bytes serverSignature = 2;
|
||||
uint32 changeEpoch = 3;
|
||||
}
|
||||
|
||||
// External credentials
|
||||
|
||||
message ExternalGroupCredential {
|
||||
string token = 1;
|
||||
}
|
||||
|
||||
// API responses
|
||||
|
||||
message GroupResponse {
|
||||
Group group = 1;
|
||||
bytes group_send_endorsements_response = 2;
|
||||
Group group = 1;
|
||||
bytes groupSendEndorsementsResponse = 2;
|
||||
}
|
||||
|
||||
message GroupChanges {
|
||||
message GroupChangeState {
|
||||
GroupChange groupChange = 1;
|
||||
Group groupState = 2;
|
||||
Group groupState = 2;
|
||||
}
|
||||
|
||||
repeated GroupChangeState groupChanges = 1;
|
||||
bytes group_send_endorsements_response = 2;
|
||||
repeated GroupChangeState groupChanges = 1;
|
||||
bytes groupSendEndorsementsResponse = 2;
|
||||
}
|
||||
|
||||
message GroupChangeResponse {
|
||||
GroupChange group_change = 1;
|
||||
bytes group_send_endorsements_response = 2;
|
||||
GroupChange groupChange = 1;
|
||||
bytes groupSendEndorsementsResponse = 2;
|
||||
}
|
||||
|
||||
message GroupAttributeBlob {
|
||||
oneof content {
|
||||
string title = 1;
|
||||
bytes avatar = 2;
|
||||
uint32 disappearingMessagesDuration = 3;
|
||||
string description = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message GroupInviteLink {
|
||||
message GroupInviteLinkContentsV1 {
|
||||
bytes groupMasterKey = 1;
|
||||
bytes inviteLinkPassword = 2;
|
||||
}
|
||||
|
||||
oneof contents {
|
||||
GroupInviteLinkContentsV1 v1Contents = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message GroupJoinInfo {
|
||||
bytes publicKey = 1;
|
||||
bytes title = 2;
|
||||
string avatar = 3;
|
||||
uint32 memberCount = 4;
|
||||
AccessControl.AccessRequired addFromInviteLink = 5;
|
||||
uint32 revision = 6;
|
||||
bool pendingAdminApproval = 7;
|
||||
bytes description = 8;
|
||||
}
|
||||
|
||||
message GroupExternalCredential {
|
||||
string token = 1;
|
||||
}
|
||||
|
|
|
|||
19
pkg/signalmeow/protobuf/Provisioning.pb.go
generated
19
pkg/signalmeow/protobuf/Provisioning.pb.go
generated
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: Provisioning.proto
|
||||
|
||||
package signalpb
|
||||
|
|
@ -199,6 +199,7 @@ type ProvisionMessage struct {
|
|||
ProfileKey []byte `protobuf:"bytes,6,opt,name=profileKey" json:"profileKey,omitempty"`
|
||||
ReadReceipts *bool `protobuf:"varint,7,opt,name=readReceipts" json:"readReceipts,omitempty"`
|
||||
ProvisioningVersion *uint32 `protobuf:"varint,9,opt,name=provisioningVersion" json:"provisioningVersion,omitempty"`
|
||||
MasterKey []byte `protobuf:"bytes,13,opt,name=masterKey" json:"masterKey,omitempty"` // Deprecated, but required by linked devices
|
||||
EphemeralBackupKey []byte `protobuf:"bytes,14,opt,name=ephemeralBackupKey" json:"ephemeralBackupKey,omitempty"` // 32 bytes
|
||||
AccountEntropyPool *string `protobuf:"bytes,15,opt,name=accountEntropyPool" json:"accountEntropyPool,omitempty"`
|
||||
MediaRootBackupKey []byte `protobuf:"bytes,16,opt,name=mediaRootBackupKey" json:"mediaRootBackupKey,omitempty"` // 32-bytes
|
||||
|
|
@ -322,6 +323,13 @@ func (x *ProvisionMessage) GetProvisioningVersion() uint32 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (x *ProvisionMessage) GetMasterKey() []byte {
|
||||
if x != nil {
|
||||
return x.MasterKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProvisionMessage) GetEphemeralBackupKey() []byte {
|
||||
if x != nil {
|
||||
return x.EphemeralBackupKey
|
||||
|
|
@ -366,7 +374,7 @@ const file_Provisioning_proto_rawDesc = "" +
|
|||
"\aaddress\x18\x01 \x01(\tR\aaddress\"E\n" +
|
||||
"\x11ProvisionEnvelope\x12\x1c\n" +
|
||||
"\tpublicKey\x18\x01 \x01(\fR\tpublicKey\x12\x12\n" +
|
||||
"\x04body\x18\x02 \x01(\fR\x04body\"\xb4\x05\n" +
|
||||
"\x04body\x18\x02 \x01(\fR\x04body\"\xcc\x05\n" +
|
||||
"\x10ProvisionMessage\x122\n" +
|
||||
"\x14aciIdentityKeyPublic\x18\x01 \x01(\fR\x14aciIdentityKeyPublic\x124\n" +
|
||||
"\x15aciIdentityKeyPrivate\x18\x02 \x01(\fR\x15aciIdentityKeyPrivate\x122\n" +
|
||||
|
|
@ -382,12 +390,13 @@ const file_Provisioning_proto_rawDesc = "" +
|
|||
"profileKey\x18\x06 \x01(\fR\n" +
|
||||
"profileKey\x12\"\n" +
|
||||
"\freadReceipts\x18\a \x01(\bR\freadReceipts\x120\n" +
|
||||
"\x13provisioningVersion\x18\t \x01(\rR\x13provisioningVersion\x12.\n" +
|
||||
"\x13provisioningVersion\x18\t \x01(\rR\x13provisioningVersion\x12\x1c\n" +
|
||||
"\tmasterKey\x18\r \x01(\fR\tmasterKey\x12.\n" +
|
||||
"\x12ephemeralBackupKey\x18\x0e \x01(\fR\x12ephemeralBackupKey\x12.\n" +
|
||||
"\x12accountEntropyPool\x18\x0f \x01(\tR\x12accountEntropyPool\x12.\n" +
|
||||
"\x12mediaRootBackupKey\x18\x10 \x01(\fR\x12mediaRootBackupKey\x12\x1c\n" +
|
||||
"\taciBinary\x18\x11 \x01(\fR\taciBinary\x12\x1c\n" +
|
||||
"\tpniBinary\x18\x12 \x01(\fR\tpniBinaryJ\x04\b\r\x10\x0e*G\n" +
|
||||
"\tpniBinary\x18\x12 \x01(\fR\tpniBinary*G\n" +
|
||||
"\x13ProvisioningVersion\x12\v\n" +
|
||||
"\aINITIAL\x10\x00\x12\x12\n" +
|
||||
"\x0eTABLET_SUPPORT\x10\x01\x12\v\n" +
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ message ProvisionMessage {
|
|||
optional bytes profileKey = 6;
|
||||
optional bool readReceipts = 7;
|
||||
optional uint32 provisioningVersion = 9;
|
||||
reserved /*masterKey*/ 13; // Deprecated in favor of accountEntropyPool
|
||||
optional bytes masterKey = 13; // Deprecated, but required by linked devices
|
||||
optional bytes ephemeralBackupKey = 14; // 32 bytes
|
||||
optional string accountEntropyPool = 15;
|
||||
optional bytes mediaRootBackupKey = 16; // 32-bytes
|
||||
|
|
|
|||
2256
pkg/signalmeow/protobuf/SignalService.pb.go
generated
2256
pkg/signalmeow/protobuf/SignalService.pb.go
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,79 +13,23 @@ option java_outer_classname = "SignalServiceProtos";
|
|||
message Envelope {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* A double-ratchet message represents a "normal," "unsealed-sender" message
|
||||
* encrypted using the Double Ratchet within an established Signal session.
|
||||
* Double-ratchet messages include sender information in the plaintext
|
||||
* portion of the `Envelope`.
|
||||
*/
|
||||
DOUBLE_RATCHET = 1; // content => (version byte | SignalMessage{Content})
|
||||
|
||||
CIPHERTEXT = 1; // content => (version byte | SignalMessage{Content})
|
||||
reserved 2;
|
||||
reserved "KEY_EXCHANGE";
|
||||
|
||||
/**
|
||||
* A prekey message begins a new Signal session. The `content` of a prekey
|
||||
* message is a superset of a double-ratchet message's `content` and
|
||||
* contains the sender's identity public key and information identifying the
|
||||
* pre-keys used in the message's ciphertext. Like double-ratchet messages,
|
||||
* prekey messages contain sender information in the plaintext portion of
|
||||
* the `Envelope`.
|
||||
*/
|
||||
PREKEY_MESSAGE = 3; // content => (version byte | PreKeySignalMessage{Content})
|
||||
|
||||
/**
|
||||
* Server delivery receipts are generated by the server when
|
||||
* "unsealed-sender" messages are delivered to and acknowledged by the
|
||||
* destination device. Server delivery receipts identify the sender in the
|
||||
* plaintext portion of the `Envelope` and have no `content`. Note that
|
||||
* receipts for sealed-sender messages are generated by clients as
|
||||
* `UNIDENTIFIED_SENDER` messages.
|
||||
*
|
||||
* Note that, with server delivery receipts, the "client timestamp" on
|
||||
* the envelope refers to the timestamp of the original message (i.e. the
|
||||
* message the server just delivered) and not to the time of delivery. The
|
||||
* "server timestamp" refers to the time of delivery.
|
||||
*/
|
||||
SERVER_DELIVERY_RECEIPT = 5; // content => []
|
||||
|
||||
/**
|
||||
* An unidentified sender message represents a message with no sender
|
||||
* information in the plaintext portion of the `Envelope`. Unidentified
|
||||
* sender messages always contain an additional `subtype` in their
|
||||
* `content`. They may or may not be part of an existing Signal session
|
||||
* (i.e. an unidentified sender message may have a "prekey message"
|
||||
* subtype or may indicate an encryption error).
|
||||
*/
|
||||
UNIDENTIFIED_SENDER = 6; // content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
|
||||
|
||||
reserved 7;
|
||||
reserved "SENDERKEY_MESSAGE";
|
||||
|
||||
/**
|
||||
* A plaintext message is used solely to convey encryption error receipts
|
||||
* and never contains encrypted message content. Encryption error receipts
|
||||
* must be delivered in plaintext because, encryption/decryption of a prior
|
||||
* message failed and there is no reason to believe that
|
||||
* encryption/decryption of subsequent messages with the same key material
|
||||
* would succeed.
|
||||
*
|
||||
* Critically, plaintext messages never have "real" message content
|
||||
* generated by users. Plaintext messages include sender information.
|
||||
*/
|
||||
PLAINTEXT_CONTENT = 8; // content => (marker byte | Content)
|
||||
|
||||
// next: 9
|
||||
PREKEY_BUNDLE = 3; // content => (version byte | PreKeySignalMessage{Content})
|
||||
SERVER_DELIVERY_RECEIPT = 5; // legacyMessage => [] AND content => []
|
||||
UNIDENTIFIED_SENDER = 6; // legacyMessage => [] AND content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
|
||||
SENDERKEY_MESSAGE = 7; // legacyMessage => [] AND content => (version byte | SenderKeyMessage)
|
||||
PLAINTEXT_CONTENT = 8; // legacyMessage => [] AND content => (marker byte | Content)
|
||||
}
|
||||
|
||||
optional Type type = 1;
|
||||
reserved 2; // formerly optional string sourceE164 = 2;
|
||||
optional string sourceServiceId = 11;
|
||||
optional uint32 sourceDeviceId = 7;
|
||||
optional uint32 sourceDevice = 7;
|
||||
optional string destinationServiceId = 13;
|
||||
reserved 3; // formerly optional string relay = 3;
|
||||
optional uint64 clientTimestamp = 5;
|
||||
optional uint64 timestamp = 5;
|
||||
reserved 6; // formerly optional bytes legacyMessage = 6; // Contains an encrypted DataMessage; this field could have been set historically for type 1 or 3 messages; no longer in use
|
||||
optional bytes content = 8; // Contains an encrypted Content
|
||||
optional string serverGuid = 9;
|
||||
|
|
@ -96,28 +40,21 @@ message Envelope {
|
|||
optional bool story = 16; // indicates that the content is a story.
|
||||
optional bytes report_spam_token = 17; // token sent when reporting spam
|
||||
reserved 18; // internal server use
|
||||
optional bytes sourceServiceIdBinary = 19; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
optional bytes destinationServiceIdBinary = 20; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
optional bytes serverGuidBinary = 21; // 16-byte UUID
|
||||
optional bytes updatedPniBinary = 22; // 16-byte UUID
|
||||
// next: 22
|
||||
// next: 19
|
||||
}
|
||||
|
||||
message Content {
|
||||
oneof content {
|
||||
DataMessage dataMessage = 1;
|
||||
SyncMessage syncMessage = 2;
|
||||
CallMessage callMessage = 3;
|
||||
NullMessage nullMessage = 4;
|
||||
ReceiptMessage receiptMessage = 5;
|
||||
TypingMessage typingMessage = 6;
|
||||
bytes /* DecryptionErrorMessage */ decryptionErrorMessage = 8;
|
||||
StoryMessage storyMessage = 9;
|
||||
EditMessage editMessage = 11;
|
||||
}
|
||||
|
||||
optional DataMessage dataMessage = 1;
|
||||
optional SyncMessage syncMessage = 2;
|
||||
optional CallMessage callMessage = 3;
|
||||
optional NullMessage nullMessage = 4;
|
||||
optional ReceiptMessage receiptMessage = 5;
|
||||
optional TypingMessage typingMessage = 6;
|
||||
optional bytes /* SenderKeyDistributionMessage */ senderKeyDistributionMessage = 7;
|
||||
optional bytes /* DecryptionErrorMessage */ decryptionErrorMessage = 8;
|
||||
optional StoryMessage storyMessage = 9;
|
||||
optional PniSignatureMessage pniSignatureMessage = 10;
|
||||
optional EditMessage editMessage = 11;
|
||||
}
|
||||
|
||||
message CallMessage {
|
||||
|
|
@ -240,7 +177,6 @@ message DataMessage {
|
|||
enum Type {
|
||||
NORMAL = 0;
|
||||
GIFT_BADGE = 1;
|
||||
POLL = 2;
|
||||
}
|
||||
|
||||
message QuotedAttachment {
|
||||
|
|
@ -256,7 +192,6 @@ message DataMessage {
|
|||
repeated QuotedAttachment attachments = 4;
|
||||
repeated BodyRange bodyRanges = 6;
|
||||
optional Type type = 7;
|
||||
optional bytes authorAciBinary = 8; // 16-byte UUID
|
||||
}
|
||||
|
||||
message Contact {
|
||||
|
|
@ -341,7 +276,6 @@ message DataMessage {
|
|||
reserved /* targetAuthorE164 */ 3;
|
||||
optional string targetAuthorAci = 4;
|
||||
optional uint64 targetSentTimestamp = 5;
|
||||
optional bytes targetAuthorAciBinary = 6; // 16-byte UUID
|
||||
}
|
||||
|
||||
message Delete {
|
||||
|
|
@ -355,7 +289,6 @@ message DataMessage {
|
|||
message StoryContext {
|
||||
optional string authorAci = 1;
|
||||
optional uint64 sentTimestamp = 2;
|
||||
optional bytes authorAciBinary = 3; // 16-byte UUID
|
||||
}
|
||||
|
||||
enum ProtocolVersion {
|
||||
|
|
@ -369,50 +302,13 @@ message DataMessage {
|
|||
CDN_SELECTOR_ATTACHMENTS = 5;
|
||||
MENTIONS = 6;
|
||||
PAYMENTS = 7;
|
||||
POLLS = 8;
|
||||
CURRENT = 8;
|
||||
CURRENT = 7;
|
||||
}
|
||||
|
||||
message GiftBadge {
|
||||
optional bytes receiptCredentialPresentation = 1;
|
||||
}
|
||||
|
||||
message PollCreate {
|
||||
optional string question = 1;
|
||||
optional bool allowMultiple = 2;
|
||||
repeated string options = 3;
|
||||
}
|
||||
|
||||
message PollTerminate {
|
||||
optional uint64 targetSentTimestamp = 1;
|
||||
}
|
||||
|
||||
message PollVote {
|
||||
optional bytes targetAuthorAciBinary = 1;
|
||||
optional uint64 targetSentTimestamp = 2;
|
||||
repeated uint32 optionIndexes = 3;
|
||||
optional uint32 voteCount = 4;
|
||||
}
|
||||
|
||||
message PinMessage {
|
||||
optional bytes targetAuthorAciBinary = 1; // 16-byte UUID
|
||||
optional uint64 targetSentTimestamp = 2;
|
||||
oneof pinDuration {
|
||||
uint32 pinDurationSeconds = 3;
|
||||
bool pinDurationForever = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message UnpinMessage {
|
||||
optional bytes targetAuthorAciBinary = 1; // 16-byte UUID
|
||||
optional uint64 targetSentTimestamp = 2;
|
||||
}
|
||||
|
||||
message AdminDelete {
|
||||
optional bytes targetAuthorAciBinary = 1; // 16-byte UUID
|
||||
optional uint64 targetSentTimestamp = 2;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
reserved /*groupV1*/ 3;
|
||||
|
|
@ -435,13 +331,7 @@ message DataMessage {
|
|||
optional Payment payment = 20;
|
||||
optional StoryContext storyContext = 21;
|
||||
optional GiftBadge giftBadge = 22;
|
||||
optional PollCreate pollCreate = 24;
|
||||
optional PollTerminate pollTerminate = 25;
|
||||
optional PollVote pollVote = 26;
|
||||
optional PinMessage pinMessage = 27;
|
||||
optional UnpinMessage unpinMessage = 28;
|
||||
optional AdminDelete adminDelete = 29;
|
||||
// NEXT ID: 30
|
||||
// NEXT ID: 24
|
||||
}
|
||||
|
||||
message NullMessage {
|
||||
|
|
@ -500,12 +390,6 @@ message TextAttachment {
|
|||
}
|
||||
|
||||
message Gradient {
|
||||
// Color ordering:
|
||||
// 0 degrees: bottom-to-top
|
||||
// 90 degrees: left-to-right
|
||||
// 180 degrees: top-to-bottom
|
||||
// 270 degrees: right-to-left
|
||||
|
||||
optional uint32 startColor = 1; // deprecated: this field will be removed in a future release.
|
||||
optional uint32 endColor = 2; // deprecated: this field will be removed in a future release.
|
||||
optional uint32 angle = 3; // degrees
|
||||
|
|
@ -536,7 +420,6 @@ message Verified {
|
|||
optional bytes identityKey = 2;
|
||||
optional State state = 3;
|
||||
optional bytes nullMessage = 4;
|
||||
optional bytes destinationAciBinary = 6; // 16-byte UUID
|
||||
}
|
||||
|
||||
message SyncMessage {
|
||||
|
|
@ -547,7 +430,6 @@ message SyncMessage {
|
|||
optional bool unidentified = 2;
|
||||
reserved /*destinationPni */ 4;
|
||||
optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations
|
||||
optional bytes destinationServiceIdBinary = 6; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
}
|
||||
|
||||
message StoryMessageRecipient {
|
||||
|
|
@ -555,7 +437,6 @@ message SyncMessage {
|
|||
repeated string distributionListIds = 2;
|
||||
optional bool isAllowedToReply = 3;
|
||||
reserved /*destinationPni */ 4;
|
||||
optional bytes destinationServiceIdBinary = 5; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
}
|
||||
|
||||
optional string destinationE164 = 1;
|
||||
|
|
@ -569,8 +450,7 @@ message SyncMessage {
|
|||
repeated StoryMessageRecipient storyMessageRecipients = 9;
|
||||
optional EditMessage editMessage = 10;
|
||||
reserved /*destinationPni */ 11;
|
||||
optional bytes destinationServiceIdBinary = 12; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
// Next ID: 13
|
||||
// Next ID: 12
|
||||
}
|
||||
|
||||
message Contacts {
|
||||
|
|
@ -582,7 +462,6 @@ message SyncMessage {
|
|||
repeated string numbers = 1;
|
||||
repeated string acis = 3;
|
||||
repeated bytes groupIds = 2;
|
||||
repeated bytes acisBinary = 4; // 16-byte UUID
|
||||
}
|
||||
|
||||
message Request {
|
||||
|
|
@ -603,14 +482,12 @@ message SyncMessage {
|
|||
reserved /*senderE164*/ 1;
|
||||
optional string senderAci = 3;
|
||||
optional uint64 timestamp = 2;
|
||||
optional bytes senderAciBinary = 4; // 16-byte UUID
|
||||
}
|
||||
|
||||
message Viewed {
|
||||
reserved /*senderE164*/ 1;
|
||||
optional string senderAci = 3;
|
||||
optional uint64 timestamp = 2;
|
||||
optional bytes senderAciBinary = 4; // 16-byte UUID
|
||||
}
|
||||
|
||||
message Configuration {
|
||||
|
|
@ -618,7 +495,7 @@ message SyncMessage {
|
|||
optional bool unidentifiedDeliveryIndicators = 2;
|
||||
optional bool typingIndicators = 3;
|
||||
reserved /* linkPreviews */ 4;
|
||||
reserved /* provisioningVersion */ 5;
|
||||
optional uint32 provisioningVersion = 5;
|
||||
optional bool linkPreviews = 6;
|
||||
}
|
||||
|
||||
|
|
@ -637,7 +514,6 @@ message SyncMessage {
|
|||
reserved /*senderE164*/ 1;
|
||||
optional string senderAci = 3;
|
||||
optional uint64 timestamp = 2;
|
||||
optional bytes senderAciBinary = 4; // 16-byte UUID
|
||||
}
|
||||
|
||||
message FetchLatest {
|
||||
|
|
@ -653,7 +529,7 @@ message SyncMessage {
|
|||
|
||||
message Keys {
|
||||
reserved /* storageService */ 1;
|
||||
reserved /* master */ 2;
|
||||
optional bytes master = 2; // deprecated: this field will be removed in a future release.
|
||||
optional string accountEntropyPool = 3;
|
||||
optional bytes mediaRootBackupKey = 4;
|
||||
}
|
||||
|
|
@ -678,7 +554,6 @@ message SyncMessage {
|
|||
optional string threadAci = 2;
|
||||
optional bytes groupId = 3;
|
||||
optional Type type = 4;
|
||||
optional bytes threadAciBinary = 5; // 16-byte UUID
|
||||
}
|
||||
|
||||
message OutgoingPayment {
|
||||
|
|
@ -753,7 +628,7 @@ message SyncMessage {
|
|||
optional bytes rootKey = 1;
|
||||
optional bytes adminPasskey = 2;
|
||||
optional Type type = 3; // defaults to UPDATE
|
||||
reserved /*epoch*/ 4;
|
||||
optional bytes epoch = 4;
|
||||
}
|
||||
|
||||
message CallLogEvent {
|
||||
|
|
@ -850,40 +725,31 @@ message SyncMessage {
|
|||
}
|
||||
}
|
||||
|
||||
oneof content {
|
||||
Sent sent = 1;
|
||||
Contacts contacts = 2;
|
||||
Request request = 4;
|
||||
Blocked blocked = 6;
|
||||
Verified verified = 7;
|
||||
Configuration configuration = 9;
|
||||
ViewOnceOpen viewOnceOpen = 11;
|
||||
FetchLatest fetchLatest = 12;
|
||||
Keys keys = 13;
|
||||
MessageRequestResponse messageRequestResponse = 14;
|
||||
OutgoingPayment outgoingPayment = 15;
|
||||
PniChangeNumber pniChangeNumber = 18;
|
||||
CallEvent callEvent = 19;
|
||||
CallLinkUpdate callLinkUpdate = 20;
|
||||
CallLogEvent callLogEvent = 21;
|
||||
DeleteForMe deleteForMe = 22;
|
||||
DeviceNameChange deviceNameChange = 23;
|
||||
AttachmentBackfillRequest attachmentBackfillRequest = 24;
|
||||
AttachmentBackfillResponse attachmentBackfillResponse = 25;
|
||||
}
|
||||
|
||||
optional Sent sent = 1;
|
||||
optional Contacts contacts = 2;
|
||||
reserved /*groups*/ 3;
|
||||
|
||||
// Protobufs don't allow `repeated` fields to be inside of `oneof` so while
|
||||
// the fields below are mutually exclusive with the rest of the values above
|
||||
// we have to place them outside of `oneof`.
|
||||
optional Request request = 4;
|
||||
repeated Read read = 5;
|
||||
repeated StickerPackOperation stickerPackOperation = 10;
|
||||
repeated Viewed viewed = 16;
|
||||
|
||||
reserved /*pniIdentity*/ 17;
|
||||
|
||||
optional Blocked blocked = 6;
|
||||
optional Verified verified = 7;
|
||||
optional Configuration configuration = 9;
|
||||
optional bytes padding = 8;
|
||||
repeated StickerPackOperation stickerPackOperation = 10;
|
||||
optional ViewOnceOpen viewOnceOpen = 11;
|
||||
optional FetchLatest fetchLatest = 12;
|
||||
optional Keys keys = 13;
|
||||
optional MessageRequestResponse messageRequestResponse = 14;
|
||||
optional OutgoingPayment outgoingPayment = 15;
|
||||
repeated Viewed viewed = 16;
|
||||
reserved /*pniIdentity*/ 17;
|
||||
optional PniChangeNumber pniChangeNumber = 18;
|
||||
optional CallEvent callEvent = 19;
|
||||
optional CallLinkUpdate callLinkUpdate = 20;
|
||||
optional CallLogEvent callLogEvent = 21;
|
||||
optional DeleteForMe deleteForMe = 22;
|
||||
optional DeviceNameChange deviceNameChange = 23;
|
||||
optional AttachmentBackfillRequest attachmentBackfillRequest = 24;
|
||||
optional AttachmentBackfillResponse attachmentBackfillResponse = 25;
|
||||
}
|
||||
|
||||
message AttachmentPointer {
|
||||
|
|
@ -935,7 +801,6 @@ message ContactDetails {
|
|||
|
||||
optional string number = 1;
|
||||
optional string aci = 9;
|
||||
optional bytes aciBinary = 13; // 16-byte UUID
|
||||
optional string name = 2;
|
||||
optional Avatar avatar = 3;
|
||||
reserved /* color */ 4;
|
||||
|
|
@ -946,7 +811,7 @@ message ContactDetails {
|
|||
optional uint32 expireTimerVersion = 12;
|
||||
optional uint32 inboxPosition = 10;
|
||||
reserved /* archived */ 11;
|
||||
// NEXT ID: 14
|
||||
// NEXT ID: 13
|
||||
}
|
||||
|
||||
message PaymentAddress {
|
||||
|
|
@ -993,7 +858,6 @@ message BodyRange {
|
|||
oneof associatedValue {
|
||||
string mentionAci = 3;
|
||||
Style style = 4;
|
||||
bytes mentionAciBinary = 5; // 16-byte UUID
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1001,7 +865,6 @@ message AddressableMessage {
|
|||
oneof author {
|
||||
string authorServiceId = 1;
|
||||
string authorE164 = 2;
|
||||
bytes authorServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
}
|
||||
optional uint64 sentTimestamp = 3;
|
||||
}
|
||||
|
|
@ -1011,6 +874,5 @@ message ConversationIdentifier {
|
|||
string threadServiceId = 1;
|
||||
bytes threadGroupId = 2;
|
||||
string threadE164 = 3;
|
||||
bytes threadServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
pkg/signalmeow/protobuf/StickerResources.pb.go
generated
4
pkg/signalmeow/protobuf/StickerResources.pb.go
generated
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: StickerResources.proto
|
||||
|
||||
package signalpb
|
||||
|
|
|
|||
162
pkg/signalmeow/protobuf/StorageService.pb.go
generated
162
pkg/signalmeow/protobuf/StorageService.pb.go
generated
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: StorageService.proto
|
||||
|
||||
package signalpb
|
||||
|
|
@ -1086,9 +1086,7 @@ type ContactRecord struct {
|
|||
PniSignatureVerified bool `protobuf:"varint,21,opt,name=pniSignatureVerified,proto3" json:"pniSignatureVerified,omitempty"`
|
||||
Nickname *ContactRecord_Name `protobuf:"bytes,22,opt,name=nickname,proto3" json:"nickname,omitempty"`
|
||||
Note string `protobuf:"bytes,23,opt,name=note,proto3" json:"note,omitempty"`
|
||||
AvatarColor *AvatarColor `protobuf:"varint,24,opt,name=avatarColor,proto3,enum=signalservice.AvatarColor,oneof" json:"avatarColor,omitempty"`
|
||||
AciBinary []byte `protobuf:"bytes,25,opt,name=aciBinary,proto3" json:"aciBinary,omitempty"` // 16-byte UUID
|
||||
PniBinary []byte `protobuf:"bytes,26,opt,name=pniBinary,proto3" json:"pniBinary,omitempty"` // 16-byte UUID
|
||||
AvatarColor *AvatarColor `protobuf:"varint,24,opt,name=avatarColor,proto3,enum=signalservice.AvatarColor,oneof" json:"avatarColor,omitempty"` // Next ID: 25
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -1291,20 +1289,6 @@ func (x *ContactRecord) GetAvatarColor() AvatarColor {
|
|||
return AvatarColor_A100
|
||||
}
|
||||
|
||||
func (x *ContactRecord) GetAciBinary() []byte {
|
||||
if x != nil {
|
||||
return x.AciBinary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ContactRecord) GetPniBinary() []byte {
|
||||
if x != nil {
|
||||
return x.PniBinary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GroupV1Record struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
|
|
@ -1401,7 +1385,6 @@ type GroupV2Record struct {
|
|||
HideStory bool `protobuf:"varint,8,opt,name=hideStory,proto3" json:"hideStory,omitempty"`
|
||||
StorySendMode GroupV2Record_StorySendMode `protobuf:"varint,10,opt,name=storySendMode,proto3,enum=signalservice.GroupV2Record_StorySendMode" json:"storySendMode,omitempty"`
|
||||
AvatarColor *AvatarColor `protobuf:"varint,11,opt,name=avatarColor,proto3,enum=signalservice.AvatarColor,oneof" json:"avatarColor,omitempty"`
|
||||
VerifiedNameHash []byte `protobuf:"bytes,12,opt,name=verifiedNameHash,proto3" json:"verifiedNameHash,omitempty"` // SHA-256 of UTF-8 encoded decrypted group title that was last verified
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -1506,13 +1489,6 @@ func (x *GroupV2Record) GetAvatarColor() AvatarColor {
|
|||
return AvatarColor_A100
|
||||
}
|
||||
|
||||
func (x *GroupV2Record) GetVerifiedNameHash() []byte {
|
||||
if x != nil {
|
||||
return x.VerifiedNameHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Payments struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||
|
|
@ -1604,9 +1580,6 @@ type AccountRecord struct {
|
|||
AvatarColor *AvatarColor `protobuf:"varint,42,opt,name=avatarColor,proto3,enum=signalservice.AvatarColor,oneof" json:"avatarColor,omitempty"`
|
||||
BackupTierHistory *AccountRecord_BackupTierHistory `protobuf:"bytes,43,opt,name=backupTierHistory,proto3" json:"backupTierHistory,omitempty"`
|
||||
NotificationProfileManualOverride *AccountRecord_NotificationProfileManualOverride `protobuf:"bytes,44,opt,name=notificationProfileManualOverride,proto3" json:"notificationProfileManualOverride,omitempty"`
|
||||
NotificationProfileSyncDisabled bool `protobuf:"varint,45,opt,name=notificationProfileSyncDisabled,proto3" json:"notificationProfileSyncDisabled,omitempty"`
|
||||
AutomaticKeyVerificationDisabled bool `protobuf:"varint,46,opt,name=automaticKeyVerificationDisabled,proto3" json:"automaticKeyVerificationDisabled,omitempty"`
|
||||
HasSeenAdminDeleteEducationDialog bool `protobuf:"varint,47,opt,name=hasSeenAdminDeleteEducationDialog,proto3" json:"hasSeenAdminDeleteEducationDialog,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -1900,38 +1873,16 @@ func (x *AccountRecord) GetNotificationProfileManualOverride() *AccountRecord_No
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *AccountRecord) GetNotificationProfileSyncDisabled() bool {
|
||||
if x != nil {
|
||||
return x.NotificationProfileSyncDisabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *AccountRecord) GetAutomaticKeyVerificationDisabled() bool {
|
||||
if x != nil {
|
||||
return x.AutomaticKeyVerificationDisabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *AccountRecord) GetHasSeenAdminDeleteEducationDialog() bool {
|
||||
if x != nil {
|
||||
return x.HasSeenAdminDeleteEducationDialog
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type StoryDistributionListRecord struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Identifier []byte `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
RecipientServiceIds []string `protobuf:"bytes,3,rep,name=recipientServiceIds,proto3" json:"recipientServiceIds,omitempty"`
|
||||
DeletedAtTimestamp uint64 `protobuf:"varint,4,opt,name=deletedAtTimestamp,proto3" json:"deletedAtTimestamp,omitempty"`
|
||||
AllowsReplies bool `protobuf:"varint,5,opt,name=allowsReplies,proto3" json:"allowsReplies,omitempty"`
|
||||
IsBlockList bool `protobuf:"varint,6,opt,name=isBlockList,proto3" json:"isBlockList,omitempty"`
|
||||
RecipientServiceIdsBinary [][]byte `protobuf:"bytes,7,rep,name=recipientServiceIdsBinary,proto3" json:"recipientServiceIdsBinary,omitempty"` // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Identifier []byte `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
RecipientServiceIds []string `protobuf:"bytes,3,rep,name=recipientServiceIds,proto3" json:"recipientServiceIds,omitempty"`
|
||||
DeletedAtTimestamp uint64 `protobuf:"varint,4,opt,name=deletedAtTimestamp,proto3" json:"deletedAtTimestamp,omitempty"`
|
||||
AllowsReplies bool `protobuf:"varint,5,opt,name=allowsReplies,proto3" json:"allowsReplies,omitempty"`
|
||||
IsBlockList bool `protobuf:"varint,6,opt,name=isBlockList,proto3" json:"isBlockList,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StoryDistributionListRecord) Reset() {
|
||||
|
|
@ -2006,18 +1957,12 @@ func (x *StoryDistributionListRecord) GetIsBlockList() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (x *StoryDistributionListRecord) GetRecipientServiceIdsBinary() [][]byte {
|
||||
if x != nil {
|
||||
return x.RecipientServiceIdsBinary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CallLinkRecord struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RootKey []byte `protobuf:"bytes,1,opt,name=rootKey,proto3" json:"rootKey,omitempty"`
|
||||
AdminPasskey []byte `protobuf:"bytes,2,opt,name=adminPasskey,proto3" json:"adminPasskey,omitempty"`
|
||||
DeletedAtTimestampMs uint64 `protobuf:"varint,3,opt,name=deletedAtTimestampMs,proto3" json:"deletedAtTimestampMs,omitempty"`
|
||||
Epoch []byte `protobuf:"bytes,4,opt,name=epoch,proto3,oneof" json:"epoch,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -2073,6 +2018,13 @@ func (x *CallLinkRecord) GetDeletedAtTimestampMs() uint64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (x *CallLinkRecord) GetEpoch() []byte {
|
||||
if x != nil {
|
||||
return x.Epoch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Recipient struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Identifier:
|
||||
|
|
@ -2931,12 +2883,11 @@ func (*AccountRecord_NotificationProfileManualOverride_Enabled) isAccountRecord_
|
|||
}
|
||||
|
||||
type AccountRecord_PinnedConversation_Contact struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
||||
E164 string `protobuf:"bytes,2,opt,name=e164,proto3" json:"e164,omitempty"`
|
||||
ServiceIdBinary []byte `protobuf:"bytes,3,opt,name=serviceIdBinary,proto3" json:"serviceIdBinary,omitempty"` // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
||||
E164 string `protobuf:"bytes,2,opt,name=e164,proto3" json:"e164,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AccountRecord_PinnedConversation_Contact) Reset() {
|
||||
|
|
@ -2983,13 +2934,6 @@ func (x *AccountRecord_PinnedConversation_Contact) GetE164() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (x *AccountRecord_PinnedConversation_Contact) GetServiceIdBinary() []byte {
|
||||
if x != nil {
|
||||
return x.ServiceIdBinary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AccountRecord_NotificationProfileManualOverride_ManuallyEnabled struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
|
|
@ -3044,12 +2988,11 @@ func (x *AccountRecord_NotificationProfileManualOverride_ManuallyEnabled) GetEnd
|
|||
}
|
||||
|
||||
type Recipient_Contact struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
||||
E164 string `protobuf:"bytes,2,opt,name=e164,proto3" json:"e164,omitempty"`
|
||||
ServiceIdBinary []byte `protobuf:"bytes,3,opt,name=serviceIdBinary,proto3" json:"serviceIdBinary,omitempty"` // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
||||
E164 string `protobuf:"bytes,2,opt,name=e164,proto3" json:"e164,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Recipient_Contact) Reset() {
|
||||
|
|
@ -3096,13 +3039,6 @@ func (x *Recipient_Contact) GetE164() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (x *Recipient_Contact) GetServiceIdBinary() []byte {
|
||||
if x != nil {
|
||||
return x.ServiceIdBinary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_StorageService_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_StorageService_proto_rawDesc = "" +
|
||||
|
|
@ -3155,7 +3091,7 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"chatFolder\x18\b \x01(\v2\x1f.signalservice.ChatFolderRecordH\x00R\n" +
|
||||
"chatFolder\x12V\n" +
|
||||
"\x13notificationProfile\x18\t \x01(\v2\".signalservice.NotificationProfileH\x00R\x13notificationProfileB\b\n" +
|
||||
"\x06record\"\xd9\b\n" +
|
||||
"\x06record\"\x9d\b\n" +
|
||||
"\rContactRecord\x12\x10\n" +
|
||||
"\x03aci\x18\x01 \x01(\tR\x03aci\x12\x12\n" +
|
||||
"\x04e164\x18\x02 \x01(\tR\x04e164\x12\x10\n" +
|
||||
|
|
@ -3185,9 +3121,7 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"\x14pniSignatureVerified\x18\x15 \x01(\bR\x14pniSignatureVerified\x12=\n" +
|
||||
"\bnickname\x18\x16 \x01(\v2!.signalservice.ContactRecord.NameR\bnickname\x12\x12\n" +
|
||||
"\x04note\x18\x17 \x01(\tR\x04note\x12A\n" +
|
||||
"\vavatarColor\x18\x18 \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\x12\x1c\n" +
|
||||
"\taciBinary\x18\x19 \x01(\fR\taciBinary\x12\x1c\n" +
|
||||
"\tpniBinary\x18\x1a \x01(\fR\tpniBinary\x1a4\n" +
|
||||
"\vavatarColor\x18\x18 \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\x1a4\n" +
|
||||
"\x04Name\x12\x14\n" +
|
||||
"\x05given\x18\x01 \x01(\tR\x05given\x12\x16\n" +
|
||||
"\x06family\x18\x02 \x01(\tR\x06family\":\n" +
|
||||
|
|
@ -3203,7 +3137,7 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"\vwhitelisted\x18\x03 \x01(\bR\vwhitelisted\x12\x1a\n" +
|
||||
"\barchived\x18\x04 \x01(\bR\barchived\x12\"\n" +
|
||||
"\fmarkedUnread\x18\x05 \x01(\bR\fmarkedUnread\x120\n" +
|
||||
"\x13mutedUntilTimestamp\x18\x06 \x01(\x04R\x13mutedUntilTimestamp\"\xcd\x04\n" +
|
||||
"\x13mutedUntilTimestamp\x18\x06 \x01(\x04R\x13mutedUntilTimestamp\"\xa1\x04\n" +
|
||||
"\rGroupV2Record\x12\x1c\n" +
|
||||
"\tmasterKey\x18\x01 \x01(\fR\tmasterKey\x12\x18\n" +
|
||||
"\ablocked\x18\x02 \x01(\bR\ablocked\x12 \n" +
|
||||
|
|
@ -3215,8 +3149,7 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"\thideStory\x18\b \x01(\bR\thideStory\x12P\n" +
|
||||
"\rstorySendMode\x18\n" +
|
||||
" \x01(\x0e2*.signalservice.GroupV2Record.StorySendModeR\rstorySendMode\x12A\n" +
|
||||
"\vavatarColor\x18\v \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\x12*\n" +
|
||||
"\x10verifiedNameHash\x18\f \x01(\fR\x10verifiedNameHash\"7\n" +
|
||||
"\vavatarColor\x18\v \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\"7\n" +
|
||||
"\rStorySendMode\x12\v\n" +
|
||||
"\aDEFAULT\x10\x00\x12\f\n" +
|
||||
"\bDISABLED\x10\x01\x12\v\n" +
|
||||
|
|
@ -3225,7 +3158,7 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"\">\n" +
|
||||
"\bPayments\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12\x18\n" +
|
||||
"\aentropy\x18\x02 \x01(\fR\aentropy\"\x99\x1d\n" +
|
||||
"\aentropy\x18\x02 \x01(\fR\aentropy\"\x8b\x1b\n" +
|
||||
"\rAccountRecord\x12\x1e\n" +
|
||||
"\n" +
|
||||
"profileKey\x18\x01 \x01(\fR\n" +
|
||||
|
|
@ -3270,18 +3203,14 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"\x14backupSubscriberData\x18) \x01(\v2..signalservice.AccountRecord.IAPSubscriberDataR\x14backupSubscriberData\x12A\n" +
|
||||
"\vavatarColor\x18* \x01(\x0e2\x1a.signalservice.AvatarColorH\x02R\vavatarColor\x88\x01\x01\x12\\\n" +
|
||||
"\x11backupTierHistory\x18+ \x01(\v2..signalservice.AccountRecord.BackupTierHistoryR\x11backupTierHistory\x12\x8c\x01\n" +
|
||||
"!notificationProfileManualOverride\x18, \x01(\v2>.signalservice.AccountRecord.NotificationProfileManualOverrideR!notificationProfileManualOverride\x12H\n" +
|
||||
"\x1fnotificationProfileSyncDisabled\x18- \x01(\bR\x1fnotificationProfileSyncDisabled\x12J\n" +
|
||||
" automaticKeyVerificationDisabled\x18. \x01(\bR automaticKeyVerificationDisabled\x12L\n" +
|
||||
"!hasSeenAdminDeleteEducationDialog\x18/ \x01(\bR!hasSeenAdminDeleteEducationDialog\x1a\xb0\x02\n" +
|
||||
"!notificationProfileManualOverride\x18, \x01(\v2>.signalservice.AccountRecord.NotificationProfileManualOverrideR!notificationProfileManualOverride\x1a\x86\x02\n" +
|
||||
"\x12PinnedConversation\x12S\n" +
|
||||
"\acontact\x18\x01 \x01(\v27.signalservice.AccountRecord.PinnedConversation.ContactH\x00R\acontact\x12&\n" +
|
||||
"\rlegacyGroupId\x18\x03 \x01(\fH\x00R\rlegacyGroupId\x12(\n" +
|
||||
"\x0egroupMasterKey\x18\x04 \x01(\fH\x00R\x0egroupMasterKey\x1ae\n" +
|
||||
"\x0egroupMasterKey\x18\x04 \x01(\fH\x00R\x0egroupMasterKey\x1a;\n" +
|
||||
"\aContact\x12\x1c\n" +
|
||||
"\tserviceId\x18\x01 \x01(\tR\tserviceId\x12\x12\n" +
|
||||
"\x04e164\x18\x02 \x01(\tR\x04e164\x12(\n" +
|
||||
"\x0fserviceIdBinary\x18\x03 \x01(\fR\x0fserviceIdBinaryB\f\n" +
|
||||
"\x04e164\x18\x02 \x01(\tR\x04e164B\f\n" +
|
||||
"\n" +
|
||||
"identifier\x1a\xf8\x01\n" +
|
||||
"\fUsernameLink\x12\x18\n" +
|
||||
|
|
@ -3329,7 +3258,7 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"_hasBackupB\r\n" +
|
||||
"\v_backupTierB\x0e\n" +
|
||||
"\f_avatarColorJ\x04\b\t\x10\n" +
|
||||
"J\x04\b\x13\x10\x14J\x04\b\x1c\x10\x1dJ\x04\b\x1f\x10 J\x04\b$\x10%J\x04\b%\x10&J\x04\b&\x10'\"\xb9\x02\n" +
|
||||
"J\x04\b\x13\x10\x14J\x04\b\x1c\x10\x1dJ\x04\b\x1f\x10 J\x04\b$\x10%J\x04\b%\x10&J\x04\b&\x10'\"\xfb\x01\n" +
|
||||
"\x1bStoryDistributionListRecord\x12\x1e\n" +
|
||||
"\n" +
|
||||
"identifier\x18\x01 \x01(\fR\n" +
|
||||
|
|
@ -3338,20 +3267,20 @@ const file_StorageService_proto_rawDesc = "" +
|
|||
"\x13recipientServiceIds\x18\x03 \x03(\tR\x13recipientServiceIds\x12.\n" +
|
||||
"\x12deletedAtTimestamp\x18\x04 \x01(\x04R\x12deletedAtTimestamp\x12$\n" +
|
||||
"\rallowsReplies\x18\x05 \x01(\bR\rallowsReplies\x12 \n" +
|
||||
"\visBlockList\x18\x06 \x01(\bR\visBlockList\x12<\n" +
|
||||
"\x19recipientServiceIdsBinary\x18\a \x03(\fR\x19recipientServiceIdsBinary\"\x88\x01\n" +
|
||||
"\visBlockList\x18\x06 \x01(\bR\visBlockList\"\xa7\x01\n" +
|
||||
"\x0eCallLinkRecord\x12\x18\n" +
|
||||
"\arootKey\x18\x01 \x01(\fR\arootKey\x12\"\n" +
|
||||
"\fadminPasskey\x18\x02 \x01(\fR\fadminPasskey\x122\n" +
|
||||
"\x14deletedAtTimestampMs\x18\x03 \x01(\x04R\x14deletedAtTimestampMsJ\x04\b\x04\x10\x05\"\x90\x02\n" +
|
||||
"\x14deletedAtTimestampMs\x18\x03 \x01(\x04R\x14deletedAtTimestampMs\x12\x19\n" +
|
||||
"\x05epoch\x18\x04 \x01(\fH\x00R\x05epoch\x88\x01\x01B\b\n" +
|
||||
"\x06_epoch\"\xe6\x01\n" +
|
||||
"\tRecipient\x12<\n" +
|
||||
"\acontact\x18\x01 \x01(\v2 .signalservice.Recipient.ContactH\x00R\acontact\x12&\n" +
|
||||
"\rlegacyGroupId\x18\x02 \x01(\fH\x00R\rlegacyGroupId\x12(\n" +
|
||||
"\x0egroupMasterKey\x18\x03 \x01(\fH\x00R\x0egroupMasterKey\x1ae\n" +
|
||||
"\x0egroupMasterKey\x18\x03 \x01(\fH\x00R\x0egroupMasterKey\x1a;\n" +
|
||||
"\aContact\x12\x1c\n" +
|
||||
"\tserviceId\x18\x01 \x01(\tR\tserviceId\x12\x12\n" +
|
||||
"\x04e164\x18\x02 \x01(\tR\x04e164\x12(\n" +
|
||||
"\x0fserviceIdBinary\x18\x03 \x01(\fR\x0fserviceIdBinaryB\f\n" +
|
||||
"\x04e164\x18\x02 \x01(\tR\x04e164B\f\n" +
|
||||
"\n" +
|
||||
"identifier\"\xe8\x04\n" +
|
||||
"\x10ChatFolderRecord\x12\x1e\n" +
|
||||
|
|
@ -3539,6 +3468,7 @@ func file_StorageService_proto_init() {
|
|||
file_StorageService_proto_msgTypes[7].OneofWrappers = []any{}
|
||||
file_StorageService_proto_msgTypes[9].OneofWrappers = []any{}
|
||||
file_StorageService_proto_msgTypes[11].OneofWrappers = []any{}
|
||||
file_StorageService_proto_msgTypes[13].OneofWrappers = []any{}
|
||||
file_StorageService_proto_msgTypes[14].OneofWrappers = []any{
|
||||
(*Recipient_Contact_)(nil),
|
||||
(*Recipient_LegacyGroupId)(nil),
|
||||
|
|
|
|||
|
|
@ -140,9 +140,7 @@ message ContactRecord {
|
|||
Name nickname = 22;
|
||||
string note = 23;
|
||||
optional AvatarColor avatarColor = 24;
|
||||
bytes aciBinary = 25; // 16-byte UUID
|
||||
bytes pniBinary = 26; // 16-byte UUID
|
||||
// Next ID: 27
|
||||
// Next ID: 25
|
||||
}
|
||||
|
||||
message GroupV1Record {
|
||||
|
|
@ -172,7 +170,6 @@ message GroupV2Record {
|
|||
reserved /* storySendEnabled */ 9;
|
||||
StorySendMode storySendMode = 10;
|
||||
optional AvatarColor avatarColor = 11;
|
||||
bytes verifiedNameHash = 12; // SHA-256 of UTF-8 encoded decrypted group title that was last verified
|
||||
}
|
||||
|
||||
message Payments {
|
||||
|
|
@ -190,9 +187,8 @@ message AccountRecord {
|
|||
|
||||
message PinnedConversation {
|
||||
message Contact {
|
||||
string serviceId = 1;
|
||||
string e164 = 2;
|
||||
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
string serviceId = 1;
|
||||
string e164 = 2;
|
||||
}
|
||||
|
||||
oneof identifier {
|
||||
|
|
@ -295,33 +291,28 @@ message AccountRecord {
|
|||
optional AvatarColor avatarColor = 42;
|
||||
BackupTierHistory backupTierHistory = 43;
|
||||
NotificationProfileManualOverride notificationProfileManualOverride = 44;
|
||||
bool notificationProfileSyncDisabled = 45;
|
||||
bool automaticKeyVerificationDisabled = 46;
|
||||
bool hasSeenAdminDeleteEducationDialog = 47;
|
||||
}
|
||||
|
||||
message StoryDistributionListRecord {
|
||||
bytes identifier = 1;
|
||||
string name = 2;
|
||||
repeated string recipientServiceIds = 3;
|
||||
uint64 deletedAtTimestamp = 4;
|
||||
bool allowsReplies = 5;
|
||||
bool isBlockList = 6;
|
||||
repeated bytes recipientServiceIdsBinary = 7; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
bytes identifier = 1;
|
||||
string name = 2;
|
||||
repeated string recipientServiceIds = 3;
|
||||
uint64 deletedAtTimestamp = 4;
|
||||
bool allowsReplies = 5;
|
||||
bool isBlockList = 6;
|
||||
}
|
||||
|
||||
message CallLinkRecord {
|
||||
bytes rootKey = 1;
|
||||
bytes adminPasskey = 2;
|
||||
uint64 deletedAtTimestampMs = 3;
|
||||
reserved 4; // was epoch field, never used
|
||||
optional bytes epoch = 4;
|
||||
}
|
||||
|
||||
message Recipient {
|
||||
message Contact {
|
||||
string serviceId = 1;
|
||||
string e164 = 2;
|
||||
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||
string serviceId = 1;
|
||||
string e164 = 2;
|
||||
}
|
||||
|
||||
oneof identifier {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: UnidentifiedDelivery.proto
|
||||
|
||||
// Copyright 2018 Signal Messenger, LLC
|
||||
|
|
|
|||
4
pkg/signalmeow/protobuf/WebSocketResources.pb.go
generated
4
pkg/signalmeow/protobuf/WebSocketResources.pb.go
generated
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.34.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v3.21.12
|
||||
// source: WebSocketResources.proto
|
||||
|
||||
package signalpb
|
||||
|
|
|
|||
2767
pkg/signalmeow/protobuf/backuppb/Backup.pb.go
generated
2767
pkg/signalmeow/protobuf/backuppb/Backup.pb.go
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
package signal.backup;
|
||||
|
||||
option java_package = "org.signal.archive.proto";
|
||||
option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
|
||||
option swift_prefix = "BackupProto_";
|
||||
|
||||
message BackupInfo {
|
||||
|
|
@ -68,40 +68,6 @@ message AccountData {
|
|||
Color color = 3;
|
||||
}
|
||||
|
||||
enum SentMediaQuality {
|
||||
UNKNOWN_QUALITY = 0; // Interpret as "Standard"
|
||||
STANDARD = 1;
|
||||
HIGH = 2;
|
||||
}
|
||||
|
||||
message AutoDownloadSettings {
|
||||
enum AutoDownloadOption {
|
||||
UNKNOWN = 0; // Interpret as "Never"
|
||||
NEVER = 1;
|
||||
WIFI = 2;
|
||||
WIFI_AND_CELLULAR = 3;
|
||||
}
|
||||
|
||||
AutoDownloadOption images = 1;
|
||||
AutoDownloadOption audio = 2;
|
||||
AutoDownloadOption video = 3;
|
||||
AutoDownloadOption documents = 4;
|
||||
}
|
||||
|
||||
enum AppTheme {
|
||||
UNKNOWN_APP_THEME = 0; // Interpret as "System"
|
||||
SYSTEM = 1;
|
||||
LIGHT = 2;
|
||||
DARK = 3;
|
||||
}
|
||||
|
||||
enum CallsUseLessDataSetting {
|
||||
UNKNOWN_CALL_DATA_SETTING = 0; // Interpret as "Never"
|
||||
NEVER = 1;
|
||||
MOBILE_DATA_ONLY = 2;
|
||||
WIFI_AND_MOBILE_DATA = 3;
|
||||
}
|
||||
|
||||
message AccountSettings {
|
||||
bool readReceipts = 1;
|
||||
bool sealedSenderIndicators = 2;
|
||||
|
|
@ -125,17 +91,6 @@ message AccountData {
|
|||
bool optimizeOnDeviceStorage = 20;
|
||||
// See zkgroup for integer particular values. Unset if backups are not enabled.
|
||||
optional uint64 backupTier = 21;
|
||||
reserved /* showSealedSenderIndicators */ 22;
|
||||
SentMediaQuality defaultSentMediaQuality = 23;
|
||||
AutoDownloadSettings autoDownloadSettings = 24;
|
||||
reserved /* wifiAutoDownloadSettings */ 25;
|
||||
optional uint32 screenLockTimeoutMinutes = 26; // If unset, consider screen lock to be disabled.
|
||||
optional bool pinReminders = 27; // If unset, consider pin reminders to be enabled.
|
||||
AppTheme appTheme = 28; // If unset, treat the same as "Unknown" case
|
||||
CallsUseLessDataSetting callsUseLessDataSetting = 29; // If unset, treat the same as "Unknown" case
|
||||
bool allowSealedSenderFromAnyone = 30;
|
||||
bool allowAutomaticKeyVerification = 31;
|
||||
bool hasSeenAdminDeleteEducationDialog = 32;
|
||||
}
|
||||
|
||||
message SubscriberData {
|
||||
|
|
@ -156,18 +111,6 @@ message AccountData {
|
|||
}
|
||||
}
|
||||
|
||||
message AndroidSpecificSettings {
|
||||
enum NavigationBarSize {
|
||||
UNKNOWN_BAR_SIZE = 0; // Intepret as "Normal"
|
||||
NORMAL = 1;
|
||||
COMPACT = 2;
|
||||
}
|
||||
|
||||
bool useSystemEmoji = 1;
|
||||
bool screenshotSecurity = 2;
|
||||
NavigationBarSize navigationBarSize = 3; // If unset, treat the same as "Unknown" case
|
||||
}
|
||||
|
||||
bytes profileKey = 1;
|
||||
optional string username = 2;
|
||||
UsernameLink usernameLink = 3;
|
||||
|
|
@ -179,10 +122,6 @@ message AccountData {
|
|||
AccountSettings accountSettings = 9;
|
||||
IAPSubscriberData backupsSubscriberData = 10;
|
||||
string svrPin = 11;
|
||||
AndroidSpecificSettings androidSpecificSettings = 12;
|
||||
string bioText = 13;
|
||||
string bioEmoji = 14;
|
||||
optional bytes keyTransparencyData = 15;
|
||||
}
|
||||
|
||||
message Recipient {
|
||||
|
|
@ -271,7 +210,6 @@ message Contact {
|
|||
string systemFamilyName = 19;
|
||||
string systemNickname = 20;
|
||||
optional AvatarColor avatarColor = 21;
|
||||
optional bytes keyTransparencyData = 22;
|
||||
}
|
||||
|
||||
message Group {
|
||||
|
|
@ -308,7 +246,6 @@ message Group {
|
|||
bytes inviteLinkPassword = 10;
|
||||
bool announcements_only = 12;
|
||||
repeated MemberBanned members_banned = 13;
|
||||
bool terminated = 14;
|
||||
}
|
||||
|
||||
message GroupAttributeBlob {
|
||||
|
|
@ -333,8 +270,6 @@ message Group {
|
|||
reserved /*profileKey*/ 3; // This field is ignored in Backups, in favor of Contact frames for members
|
||||
reserved /*presentation*/ 4; // This field is deprecated in the context of static group state
|
||||
uint32 joinedAtVersion = 5;
|
||||
string labelEmoji = 6;
|
||||
string labelString = 7;
|
||||
}
|
||||
|
||||
message MemberPendingProfileKey {
|
||||
|
|
@ -367,7 +302,6 @@ message Group {
|
|||
AccessRequired attributes = 1;
|
||||
AccessRequired members = 2;
|
||||
AccessRequired addFromInviteLink = 3;
|
||||
AccessRequired memberLabel = 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -410,7 +344,7 @@ message CallLink {
|
|||
string name = 3;
|
||||
Restrictions restrictions = 4;
|
||||
uint64 expirationMs = 5;
|
||||
reserved /*epoch*/ 6;
|
||||
optional bytes epoch = 6; // May be absent/empty for older links
|
||||
}
|
||||
|
||||
message AdHocCall {
|
||||
|
|
@ -468,14 +402,6 @@ message ChatItem {
|
|||
message DirectionlessMessageDetails {
|
||||
}
|
||||
|
||||
message PinDetails {
|
||||
uint64 pinnedAtTimestamp = 1;
|
||||
oneof pinExpiry {
|
||||
uint64 pinExpiresAtTimestamp = 2; // timestamp when the pin should expire
|
||||
bool pinNeverExpires = 3;
|
||||
}
|
||||
}
|
||||
|
||||
uint64 chatId = 1; // conversation id
|
||||
uint64 authorId = 2; // recipient id
|
||||
uint64 dateSent = 3;
|
||||
|
|
@ -502,11 +428,7 @@ message ChatItem {
|
|||
GiftBadge giftBadge = 17;
|
||||
ViewOnceMessage viewOnceMessage = 18;
|
||||
DirectStoryReplyMessage directStoryReplyMessage = 19; // group story reply messages are not backed up
|
||||
Poll poll = 20;
|
||||
AdminDeletedMessage adminDeletedMessage = 22;
|
||||
}
|
||||
|
||||
PinDetails pinDetails = 21; // only set if message is pinned
|
||||
}
|
||||
|
||||
message SendStatus {
|
||||
|
|
@ -837,7 +759,6 @@ message Quote {
|
|||
NORMAL = 1;
|
||||
GIFT_BADGE = 2;
|
||||
VIEW_ONCE = 3;
|
||||
POLL = 4;
|
||||
}
|
||||
|
||||
message QuotedAttachment {
|
||||
|
|
@ -884,30 +805,6 @@ message Reaction {
|
|||
uint64 sortOrder = 4;
|
||||
}
|
||||
|
||||
message Poll {
|
||||
|
||||
message PollOption {
|
||||
|
||||
message PollVote {
|
||||
uint64 voterId = 1; // A direct reference to Recipient proto id. Must be self or contact.
|
||||
uint32 voteCount = 2; // Tracks how many times you voted.
|
||||
}
|
||||
|
||||
string option = 1; // Between 1-100 characters
|
||||
repeated PollVote votes = 2;
|
||||
}
|
||||
|
||||
string question = 1; // Between 1-100 characters
|
||||
bool allowMultiple = 2;
|
||||
repeated PollOption options = 3; // At least two
|
||||
bool hasEnded = 4;
|
||||
repeated Reaction reactions = 5;
|
||||
}
|
||||
|
||||
message AdminDeletedMessage {
|
||||
uint64 adminId = 1; // id of the admin that deleted the message
|
||||
}
|
||||
|
||||
message ChatUpdateMessage {
|
||||
// If unset, importers should ignore the update message without throwing an error.
|
||||
oneof update {
|
||||
|
|
@ -920,8 +817,6 @@ message ChatUpdateMessage {
|
|||
IndividualCall individualCall = 7;
|
||||
GroupCall groupCall = 8;
|
||||
LearnedProfileChatUpdate learnedProfileChange = 9;
|
||||
PollTerminateUpdate pollTerminate = 10;
|
||||
PinMessageUpdate pinMessage = 11;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1078,8 +973,6 @@ message GroupChangeChatUpdate {
|
|||
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
|
||||
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
|
||||
GroupExpirationTimerUpdate groupExpirationTimerUpdate = 34;
|
||||
GroupMemberLabelAccessLevelChangeUpdate groupMemberLabelAccessLevelChangeUpdate = 35;
|
||||
GroupTerminateChangeUpdate groupTerminateChangeUpdate = 36;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1131,15 +1024,6 @@ message GroupAttributesAccessLevelChangeUpdate {
|
|||
GroupV2AccessLevel accessLevel = 2;
|
||||
}
|
||||
|
||||
message GroupMemberLabelAccessLevelChangeUpdate {
|
||||
optional bytes updaterAci = 1;
|
||||
GroupV2AccessLevel accessLevel = 2;
|
||||
}
|
||||
|
||||
message GroupTerminateChangeUpdate {
|
||||
optional bytes updaterAci = 1;
|
||||
}
|
||||
|
||||
message GroupAnnouncementOnlyChangeUpdate {
|
||||
optional bytes updaterAci = 1;
|
||||
bool isAnnouncementOnly = 2;
|
||||
|
|
@ -1298,16 +1182,6 @@ message GroupExpirationTimerUpdate {
|
|||
optional bytes updaterAci = 2;
|
||||
}
|
||||
|
||||
message PollTerminateUpdate {
|
||||
uint64 targetSentTimestamp = 1;
|
||||
string question = 2; // Between 1-100 characters
|
||||
}
|
||||
|
||||
message PinMessageUpdate {
|
||||
uint64 targetSentTimestamp = 1;
|
||||
uint64 authorId = 2; // recipient id
|
||||
}
|
||||
|
||||
message StickerPack {
|
||||
bytes packId = 1;
|
||||
bytes packKey = 2;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ANDROID_GIT_REVISION=${1:-439760e7732585bfd078d92d93732c04cc31e29e}
|
||||
DESKTOP_GIT_REVISION=${1:-1b2a3e7b283c32c5654a39da12fc04139fd26dbd}
|
||||
ANDROID_GIT_REVISION=${1:-62fdf3d1aa9f637729ae67b55aadcc24f38f0117}
|
||||
DESKTOP_GIT_REVISION=${1:-203a1cc5e3f9c1533a58caff72e13aa6eaeeddc7}
|
||||
|
||||
update_proto() {
|
||||
case "$1" in
|
||||
Signal-Android)
|
||||
REPO="Signal-Android"
|
||||
prefix="lib/libsignal-service/src/main/protowire/"
|
||||
prefix="libsignal-service/src/main/protowire/"
|
||||
GIT_REVISION=$ANDROID_GIT_REVISION
|
||||
;;
|
||||
Signal-Android-Archive)
|
||||
Signal-Android-App)
|
||||
REPO="Signal-Android"
|
||||
prefix="lib/archive/src/main/protowire/"
|
||||
prefix="app/src/main/protowire/"
|
||||
GIT_REVISION=$ANDROID_GIT_REVISION
|
||||
;;
|
||||
Signal-Desktop)
|
||||
|
|
@ -34,10 +34,10 @@ update_proto Signal-Android StickerResources.proto
|
|||
update_proto Signal-Android WebSocketResources.proto
|
||||
update_proto Signal-Android StorageService.proto
|
||||
|
||||
update_proto Signal-Android-Archive Backup.proto
|
||||
update_proto Signal-Android-App Backup.proto
|
||||
mv Backup.proto backuppb/Backup.proto
|
||||
|
||||
update_proto Signal-Desktop DeviceName.proto
|
||||
# TODO these were moved to libsignal only
|
||||
#update_proto Signal-Desktop UnidentifiedDelivery.proto
|
||||
#update_proto Signal-Desktop ContactDiscovery.proto
|
||||
update_proto Signal-Desktop UnidentifiedDelivery.proto
|
||||
# Android has CDSI.proto too, but the types have more generic names (since android uses a different package name)
|
||||
update_proto Signal-Desktop ContactDiscovery.proto
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ package signalmeow
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
mrand "math/rand/v2"
|
||||
mrand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
|
@ -53,6 +54,7 @@ const (
|
|||
StateProvisioningError ProvisioningState = iota
|
||||
StateProvisioningURLReceived
|
||||
StateProvisioningDataReceived
|
||||
StateProvisioningPreKeysRegistered
|
||||
)
|
||||
|
||||
func (s ProvisioningState) String() string {
|
||||
|
|
@ -63,6 +65,8 @@ func (s ProvisioningState) String() string {
|
|||
return "StateProvisioningURLReceived"
|
||||
case StateProvisioningDataReceived:
|
||||
return "StateProvisioningDataReceived"
|
||||
case StateProvisioningPreKeysRegistered:
|
||||
return "StateProvisioningPreKeysRegistered"
|
||||
default:
|
||||
return fmt.Sprintf("ProvisioningState(%d)", s)
|
||||
}
|
||||
|
|
@ -124,8 +128,8 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
|||
username := *provisioningMessage.Number
|
||||
password := random.String(22)
|
||||
code := provisioningMessage.ProvisioningCode
|
||||
aciRegistrationID := mrand.IntN(16383) + 1
|
||||
pniRegistrationID := mrand.IntN(16383) + 1
|
||||
aciRegistrationID := mrand.Intn(16383) + 1
|
||||
pniRegistrationID := mrand.Intn(16383) + 1
|
||||
aciSignedPreKey := GenerateSignedPreKey(1, aciIdentityKeyPair)
|
||||
pniSignedPreKey := GenerateSignedPreKey(1, pniIdentityKeyPair)
|
||||
aciPQLastResortPreKey := GenerateKyberPreKeys(1, 1, aciIdentityKeyPair)[0]
|
||||
|
|
@ -165,19 +169,24 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
|||
DeviceID: deviceId,
|
||||
Number: *provisioningMessage.Number,
|
||||
Password: password,
|
||||
MasterKey: provisioningMessage.GetMasterKey(),
|
||||
AccountEntropyPool: libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()),
|
||||
EphemeralBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetEphemeralBackupKey()),
|
||||
MediaRootBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetMediaRootBackupKey()),
|
||||
}
|
||||
if provisioningMessage.GetAccountEntropyPool() != "" {
|
||||
data.MasterKey, err = libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()).DeriveSVRKey()
|
||||
var masterKey []byte
|
||||
masterKey, err = libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()).DeriveSVRKey()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
||||
} else {
|
||||
log.Debug().Msg("Derived master key from account entropy pool")
|
||||
}
|
||||
} else {
|
||||
log.Warn().Msg("No account entropy pool in provisioning message")
|
||||
if data.MasterKey == nil {
|
||||
data.MasterKey = masterKey
|
||||
} else if !hmac.Equal(data.MasterKey, masterKey) {
|
||||
log.Warn().Msg("Master key mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// Store the provisioning data
|
||||
|
|
@ -241,6 +250,28 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
|||
|
||||
// Return the provisioning data
|
||||
c <- ProvisioningResponse{State: StateProvisioningDataReceived, ProvisioningData: data}
|
||||
|
||||
// Generate, store, and register prekeys
|
||||
// TODO hacky client construction
|
||||
cli := &Client{Store: device}
|
||||
err = cli.GenerateAndRegisterPreKeys(ctx, device.ACIPreKeyStore)
|
||||
if err != nil {
|
||||
c <- ProvisioningResponse{
|
||||
State: StateProvisioningError,
|
||||
Err: fmt.Errorf("error generating and registering ACI prekeys: %w", err),
|
||||
}
|
||||
return
|
||||
}
|
||||
err = cli.GenerateAndRegisterPreKeys(ctx, device.PNIPreKeyStore)
|
||||
if err != nil {
|
||||
c <- ProvisioningResponse{
|
||||
State: StateProvisioningError,
|
||||
Err: fmt.Errorf("error generating and registering PNI prekeys: %w", err),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c <- ProvisioningResponse{State: StateProvisioningPreKeysRegistered}
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
|
@ -268,7 +299,7 @@ func startProvisioning(ctx context.Context, ws *websocket.Conn, provisioningCiph
|
|||
return "", fmt.Errorf("failed to unmarshal provisioning UUID: %w", err)
|
||||
}
|
||||
|
||||
linkCapabilities := []string{"backup4,backup5"}
|
||||
linkCapabilities := []string{"backup4", "backup5"}
|
||||
if !allowBackup {
|
||||
linkCapabilities = []string{}
|
||||
}
|
||||
|
|
@ -335,19 +366,36 @@ var signalCapabilities = map[string]any{
|
|||
var signalCapabilitiesBody = exerrors.Must(json.Marshal(signalCapabilities))
|
||||
|
||||
func (cli *Client) RegisterCapabilities(ctx context.Context) error {
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodPut, "/v1/devices/capabilities", signalCapabilitiesBody, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodPut, "/v1/devices/capabilities", &web.HTTPReqOpt{
|
||||
Body: signalCapabilitiesBody,
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
ContentType: web.ContentTypeJSON,
|
||||
})
|
||||
if resp != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
} else if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
return web.DecodeWSResponseBody(ctx, nil, resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) Unlink(ctx context.Context) error {
|
||||
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodDelete, fmt.Sprintf("/v1/devices/%d", cli.Store.DeviceID), nil, nil)
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
resp, err := web.SendHTTPRequest(ctx, http.MethodDelete, fmt.Sprintf("/v1/devices/%d", cli.Store.DeviceID), &web.HTTPReqOpt{
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
return web.DecodeWSResponseBody(ctx, nil, resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func confirmDevice(
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ package signalmeow
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||
)
|
||||
|
||||
|
|
@ -36,21 +36,30 @@ type ReqRegisterAPNs struct {
|
|||
}
|
||||
|
||||
func (cli *Client) registerPush(ctx context.Context, pushType string, data any) error {
|
||||
var resp *signalpb.WebSocketResponseMessage
|
||||
var err error
|
||||
username, password := cli.Store.BasicAuthCreds()
|
||||
req := &web.HTTPReqOpt{
|
||||
Username: &username,
|
||||
Password: &password,
|
||||
}
|
||||
var method string
|
||||
if data != nil {
|
||||
body, err := json.Marshal(data)
|
||||
method = http.MethodPut
|
||||
req.ContentType = web.ContentTypeJSON
|
||||
var err error
|
||||
req.Body, err = json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err = cli.AuthedWS.SendRequest(ctx, http.MethodPut, "/v1/accounts/"+pushType, body, nil)
|
||||
} else {
|
||||
resp, err = cli.AuthedWS.SendRequest(ctx, http.MethodDelete, "/v1/accounts/"+pushType, nil, nil)
|
||||
method = http.MethodDelete
|
||||
}
|
||||
resp, err := web.SendHTTPRequest(ctx, method, "/v1/accounts/"+pushType, req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if resp.StatusCode >= 300 || resp.StatusCode < 200 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
return web.DecodeWSResponseBody(ctx, nil, resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) RegisterFCM(ctx context.Context, token string) error {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/ptr"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
|
|
@ -250,39 +249,32 @@ func (cli *Client) StartReceiveLoops(ctx context.Context) (chan SignalConnection
|
|||
cli.loopWg.Add(1)
|
||||
go func() {
|
||||
defer cli.loopWg.Done()
|
||||
select {
|
||||
case <-loopCtx.Done():
|
||||
return
|
||||
case <-initialConnectChan:
|
||||
log.Info().Msg("Both websockets connected, sending contacts sync request")
|
||||
err = cli.RegisterCapabilities(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to register capabilities")
|
||||
} else {
|
||||
zerolog.Ctx(ctx).Debug().Msg("Successfully registered capabilities")
|
||||
}
|
||||
// Start loop to check for and upload more prekeys
|
||||
cli.loopWg.Add(1)
|
||||
go func() {
|
||||
defer cli.loopWg.Done()
|
||||
cli.keyCheckLoop(loopCtx)
|
||||
}()
|
||||
// TODO hacky
|
||||
if cli.SyncContactsOnConnect {
|
||||
cli.SendContactSyncRequest(loopCtx)
|
||||
}
|
||||
if cli.Store.MasterKey == nil {
|
||||
cli.SendStorageMasterKeyRequest(loopCtx)
|
||||
for {
|
||||
select {
|
||||
case <-loopCtx.Done():
|
||||
return
|
||||
case <-initialConnectChan:
|
||||
log.Info().Msg("Both websockets connected, sending contacts sync request")
|
||||
// TODO hacky
|
||||
if cli.SyncContactsOnConnect {
|
||||
cli.SendContactSyncRequest(loopCtx)
|
||||
}
|
||||
if cli.Store.MasterKey == nil {
|
||||
cli.SendStorageMasterKeyRequest(loopCtx)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return statusChan, nil
|
||||
}
|
||||
// Start loop to check for and upload more prekeys
|
||||
cli.loopWg.Add(1)
|
||||
go func() {
|
||||
defer cli.loopWg.Done()
|
||||
cli.keyCheckLoop(loopCtx)
|
||||
}()
|
||||
|
||||
func (cli *Client) ForceReconnect() {
|
||||
cli.AuthedWS.ForceReconnect()
|
||||
cli.UnauthedWS.ForceReconnect()
|
||||
return statusChan, nil
|
||||
}
|
||||
|
||||
func (cli *Client) StopReceiveLoops() error {
|
||||
|
|
@ -357,25 +349,21 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb.
|
|||
return nil, err
|
||||
}
|
||||
log = log.With().
|
||||
Uint64("envelope_timestamp", envelope.GetClientTimestamp()).
|
||||
Uint64("envelope_timestamp", envelope.GetTimestamp()).
|
||||
Uint64("server_timestamp", envelope.GetServerTimestamp()).
|
||||
Logger()
|
||||
ctx = log.WithContext(ctx)
|
||||
destinationServiceID, _ := ParseStringOrBinaryServiceID(envelope.GetDestinationServiceId(), envelope.GetDestinationServiceIdBinary())
|
||||
sourceServiceID, _ := ParseStringOrBinaryServiceID(envelope.GetSourceServiceId(), envelope.GetSourceServiceIdBinary())
|
||||
destinationServiceID, err := libsignalgo.ServiceIDFromString(envelope.GetDestinationServiceId())
|
||||
log.Debug().
|
||||
Str("destination_service_id", envelope.GetDestinationServiceId()).
|
||||
Str("source_service_id", envelope.GetSourceServiceId()).
|
||||
Hex("destination_service_id_bytes", envelope.GetDestinationServiceIdBinary()).
|
||||
Hex("source_service_id_bytes", envelope.GetSourceServiceIdBinary()).
|
||||
Uint32("source_device_id", envelope.GetSourceDeviceId()).
|
||||
Uint32("source_device_id", envelope.GetSourceDevice()).
|
||||
Object("parsed_destination_service_id", destinationServiceID).
|
||||
Object("parsed_source_service_id", sourceServiceID).
|
||||
Int32("envelope_type_id", int32(envelope.GetType())).
|
||||
Str("envelope_type", signalpb.Envelope_Type_name[int32(envelope.GetType())]).
|
||||
Msg("Received envelope")
|
||||
|
||||
result := cli.decryptEnvelope(ctx, envelope, sourceServiceID, destinationServiceID)
|
||||
result := cli.decryptEnvelope(ctx, envelope)
|
||||
|
||||
err = cli.handleDecryptedResult(ctx, result, envelope, destinationServiceID)
|
||||
if err != nil {
|
||||
|
|
@ -407,7 +395,7 @@ func (cli *Client) handleDecryptedResult(
|
|||
result DecryptionResult,
|
||||
envelope *signalpb.Envelope,
|
||||
destinationServiceID libsignalgo.ServiceID,
|
||||
) (retErr error) {
|
||||
) error {
|
||||
if errors.Is(result.Err, context.Canceled) {
|
||||
return result.Err
|
||||
} else if ctx.Err() != nil {
|
||||
|
|
@ -429,58 +417,37 @@ func (cli *Client) handleDecryptedResult(
|
|||
}()
|
||||
}
|
||||
|
||||
var theirServiceID libsignalgo.ServiceID
|
||||
var err error
|
||||
if result.SenderAddress == nil {
|
||||
log.Err(result.Err).
|
||||
Bool("urgent", envelope.GetUrgent()).
|
||||
Stringer("content_hint", result.ContentHint).
|
||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||
Uint64("client_ts", envelope.GetClientTimestamp()).
|
||||
Msg("No sender address received")
|
||||
return nil
|
||||
} else if theirServiceID, err = result.SenderAddress.NameServiceID(); err != nil {
|
||||
log.Warn().
|
||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||
Uint64("client_ts", envelope.GetClientTimestamp()).
|
||||
Msg("Failed to get sender name as service ID")
|
||||
return fmt.Errorf("failed to get sender name as service ID: %w", err)
|
||||
} else if theirServiceID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
log.Warn().
|
||||
Any("their_service_id", theirServiceID).
|
||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||
Uint64("client_ts", envelope.GetClientTimestamp()).
|
||||
Msg("Dropping message from non-ACI sender")
|
||||
return nil
|
||||
}
|
||||
cli.Store.RecipientStore.MarkUnregistered(ctx, theirServiceID, false)
|
||||
|
||||
handlerSuccess := true
|
||||
defer func() {
|
||||
if retErr == nil && !handlerSuccess {
|
||||
retErr = ErrHandlerFailed
|
||||
}
|
||||
}()
|
||||
// result.Err is set if there was an error during decryption and we
|
||||
// should notifiy the user that the message could not be decrypted
|
||||
if result.Err != nil {
|
||||
logEvt := log.Err(result.Err).
|
||||
Bool("urgent", envelope.GetUrgent()).
|
||||
Stringer("content_hint", result.ContentHint).
|
||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||
Uint64("client_ts", envelope.GetTimestamp())
|
||||
if result.SenderAddress == nil {
|
||||
logEvt.Msg("Decryption error with unknown sender")
|
||||
return nil
|
||||
}
|
||||
theirServiceID, err := result.SenderAddress.NameServiceID()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Name error handling decryption error")
|
||||
} else if theirServiceID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
log.Warn().Any("their_service_id", theirServiceID).Msg("Sender ServiceID is not an ACI")
|
||||
}
|
||||
if errors.Is(result.Err, EventAlreadyProcessed) {
|
||||
logEvt.Discard().Msg("")
|
||||
log.Debug().Err(result.Err).
|
||||
Bool("urgent", envelope.GetUrgent()).
|
||||
Stringer("content_hint", result.ContentHint).
|
||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||
Uint64("client_ts", envelope.GetClientTimestamp()).
|
||||
Uint64("client_ts", envelope.GetTimestamp()).
|
||||
Stringer("sender", theirServiceID).
|
||||
Msg("Ignoring already processed event")
|
||||
return nil
|
||||
}
|
||||
log.Err(result.Err).
|
||||
Bool("urgent", envelope.GetUrgent()).
|
||||
Stringer("content_hint", result.ContentHint).
|
||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||
Uint64("client_ts", envelope.GetClientTimestamp()).
|
||||
Stringer("sender", theirServiceID).
|
||||
Msg("Decryption error with known sender")
|
||||
logEvt.Stringer("sender", theirServiceID).Msg("Decryption error with known sender")
|
||||
// Only send decryption error event if the message was urgent,
|
||||
// to prevent spamming errors for typing notifications and whatnot
|
||||
if envelope.GetUrgent() &&
|
||||
|
|
@ -489,38 +456,27 @@ func (cli *Client) handleDecryptedResult(
|
|||
handlerSuccess = cli.handleEvent(&events.DecryptionError{
|
||||
Sender: theirServiceID.UUID,
|
||||
Err: result.Err,
|
||||
Timestamp: envelope.GetClientTimestamp(),
|
||||
Timestamp: envelope.GetTimestamp(),
|
||||
})
|
||||
}
|
||||
if result.Retriable {
|
||||
go func() {
|
||||
err := cli.sendRetryRequest(ctx, result, envelope.GetClientTimestamp())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to send retry request in background")
|
||||
}
|
||||
}()
|
||||
}
|
||||
if !handlerSuccess {
|
||||
return ErrHandlerFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rawContent := result.Content
|
||||
if rawContent == nil {
|
||||
content := result.Content
|
||||
if content == nil {
|
||||
log.Warn().Msg("Decrypted content is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
deviceID, _ := result.SenderAddress.DeviceID()
|
||||
log.Trace().
|
||||
Any("raw_data", rawContent).
|
||||
Stringer("sender", theirServiceID).
|
||||
Uint("sender_device", deviceID).
|
||||
Msg("Raw event data")
|
||||
name, _ := result.SenderAddress.Name()
|
||||
deviceId, _ := result.SenderAddress.DeviceID()
|
||||
log.Trace().Any("raw_data", content).Str("sender", name).Uint("sender_device", deviceId).Msg("Raw event data")
|
||||
newLog := log.With().
|
||||
Stringer("sender_name", theirServiceID).
|
||||
Uint("sender_device_id", deviceID).
|
||||
Str("sender_name", name).
|
||||
Uint("sender_device_id", deviceId).
|
||||
Str("destination_service_id", destinationServiceID.String()).
|
||||
Logger()
|
||||
log = &newLog
|
||||
|
|
@ -529,32 +485,12 @@ func (cli *Client) handleDecryptedResult(
|
|||
if result.CiphertextHash != nil {
|
||||
logEvt = logEvt.Hex("ciphertext_hash", result.CiphertextHash[:])
|
||||
}
|
||||
logEvt.Bool("unencrypted", result.Unencrypted).Msg("Decrypted message")
|
||||
|
||||
// Handle unencrypted types early and refuse any other unencrypted message
|
||||
if rawContent.GetDecryptionErrorMessage() != nil {
|
||||
handlerSuccess = true
|
||||
dem, err := libsignalgo.DeserializeDecryptionErrorMessage(rawContent.GetDecryptionErrorMessage())
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to unmarshal decryption error message")
|
||||
} else {
|
||||
go func() {
|
||||
err := cli.handleRetryRequest(ctx, result, dem)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to handle decryption error message in background")
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
} else if result.Unencrypted {
|
||||
log.Warn().Msg("Unexpected non-decryption-error content in unencrypted message")
|
||||
return nil
|
||||
}
|
||||
logEvt.Msg("Decrypted message")
|
||||
|
||||
// If there's a sender key distribution message, process it
|
||||
if rawContent.SenderKeyDistributionMessage != nil {
|
||||
if content.GetSenderKeyDistributionMessage() != nil {
|
||||
log.Debug().Msg("content includes sender key distribution message")
|
||||
skdm, err := libsignalgo.DeserializeSenderKeyDistributionMessage(rawContent.SenderKeyDistributionMessage)
|
||||
skdm, err := libsignalgo.DeserializeSenderKeyDistributionMessage(content.GetSenderKeyDistributionMessage())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("DeserializeSenderKeyDistributionMessage error")
|
||||
return err
|
||||
|
|
@ -571,120 +507,214 @@ func (cli *Client) handleDecryptedResult(
|
|||
}
|
||||
}
|
||||
|
||||
// If we're getting a message to our PNI, mark it as needing a PNI signature message on the next send
|
||||
theirServiceID, err := result.SenderAddress.NameServiceID()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Name error")
|
||||
return err
|
||||
} else if theirServiceID.Type != libsignalgo.ServiceIDTypeACI {
|
||||
log.Warn().Any("their_service_id", theirServiceID).Msg("Sender ServiceID is not an ACI")
|
||||
return nil
|
||||
}
|
||||
|
||||
if destinationServiceID == cli.Store.PNIServiceID() {
|
||||
_, err = cli.Store.RecipientStore.LoadAndUpdateRecipient(ctx, theirServiceID.UUID, uuid.Nil, func(recipient *types.Recipient) (changed bool, err error) {
|
||||
if recipient.Whitelisted == nil {
|
||||
log.Debug().Msg("Marking recipient as not whitelisted")
|
||||
recipient.Whitelisted = ptr.Ptr(false)
|
||||
changed = true
|
||||
}
|
||||
if !recipient.NeedsPNISignature {
|
||||
log.Debug().Msg("Marking recipient as needing PNI signature")
|
||||
recipient.NeedsPNISignature = true
|
||||
changed = true
|
||||
return true, nil
|
||||
}
|
||||
return
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to set needs_pni_signature flag after receiving message to PNI service ID")
|
||||
}
|
||||
}
|
||||
|
||||
// If we receive a PNI signature message (because we sent to a PNI earlier), process it
|
||||
if rawContent.PniSignatureMessage != nil {
|
||||
if content.GetPniSignatureMessage() != nil {
|
||||
log.Debug().Msg("Content includes PNI signature message")
|
||||
err = cli.handlePNISignatureMessage(ctx, theirServiceID, rawContent.PniSignatureMessage)
|
||||
err = cli.handlePNISignatureMessage(ctx, theirServiceID, content.GetPniSignatureMessage())
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Hex("pni_raw", rawContent.PniSignatureMessage.GetPni()).
|
||||
Hex("pni_raw", content.GetPniSignatureMessage().GetPni()).
|
||||
Stringer("aci", theirServiceID.UUID).
|
||||
Msg("Failed to verify ACI-PNI mapping")
|
||||
}
|
||||
}
|
||||
|
||||
isBlocked, err := cli.Store.RecipientStore.IsBlocked(ctx, theirServiceID.UUID)
|
||||
if err != nil {
|
||||
log.Err(err).Stringer("sender", theirServiceID).Msg("Failed to check if sender is blocked")
|
||||
// TODO: handle more sync messages
|
||||
if content.SyncMessage != nil {
|
||||
if content.SyncMessage.Keys != nil {
|
||||
aep := libsignalgo.AccountEntropyPool(content.SyncMessage.Keys.GetAccountEntropyPool())
|
||||
cli.Store.MasterKey = content.SyncMessage.Keys.GetMaster()
|
||||
if aep != "" {
|
||||
aepMasterKey, err := aep.DeriveSVRKey()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
||||
} else if cli.Store.MasterKey == nil {
|
||||
cli.Store.MasterKey = aepMasterKey
|
||||
log.Debug().Msg("Derived master key from account entropy pool (no master key in sync message)")
|
||||
} else if !bytes.Equal(aepMasterKey, cli.Store.MasterKey) {
|
||||
log.Warn().Msg("Derived master key doesn't match one in sync message")
|
||||
} else {
|
||||
log.Debug().Msg("Derived master key matches one in sync message")
|
||||
}
|
||||
} else {
|
||||
log.Debug().Msg("No account entropy pool in sync message")
|
||||
}
|
||||
err = cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to save device after receiving master key")
|
||||
} else {
|
||||
log.Info().Msg("Received master key")
|
||||
go cli.SyncStorage(ctx)
|
||||
}
|
||||
} else if content.SyncMessage.GetFetchLatest().GetType() == signalpb.SyncMessage_FetchLatest_STORAGE_MANIFEST {
|
||||
log.Debug().Msg("Received storage manifest fetch latest notice")
|
||||
go cli.SyncStorage(ctx)
|
||||
}
|
||||
syncSent := content.SyncMessage.GetSent()
|
||||
if syncSent.GetMessage() != nil || syncSent.GetEditMessage() != nil {
|
||||
destination := syncSent.DestinationServiceId
|
||||
var syncDestinationServiceID libsignalgo.ServiceID
|
||||
if destination != nil {
|
||||
syncDestinationServiceID, err = libsignalgo.ServiceIDFromString(*destination)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Sync message destination parse error")
|
||||
return err
|
||||
}
|
||||
if syncSent.GetDestinationE164() != "" {
|
||||
aci, pni := syncDestinationServiceID.ToACIAndPNI()
|
||||
_, err = cli.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, syncSent.GetDestinationE164())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update recipient E164 after receiving sync message")
|
||||
}
|
||||
}
|
||||
}
|
||||
if destination == nil && syncSent.GetMessage().GetGroupV2() == nil && syncSent.GetEditMessage().GetDataMessage().GetGroupV2() == nil {
|
||||
log.Warn().Msg("sync message sent destination is nil")
|
||||
} else if content.SyncMessage.Sent.Message != nil {
|
||||
// TODO handle expiration start ts, and maybe the sync message ts?
|
||||
cli.incomingDataMessage(ctx, content.SyncMessage.Sent.Message, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp())
|
||||
} else if content.SyncMessage.Sent.EditMessage != nil {
|
||||
cli.incomingEditMessage(ctx, content.SyncMessage.Sent.EditMessage, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp())
|
||||
}
|
||||
}
|
||||
if content.SyncMessage.Contacts != nil {
|
||||
log.Debug().Msg("Recieved sync message contacts")
|
||||
blob := content.SyncMessage.Contacts.Blob
|
||||
if blob != nil {
|
||||
contactsBytes, err := DownloadAttachmentWithPointer(ctx, blob, nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Contacts Sync DownloadAttachment error")
|
||||
}
|
||||
// unmarshall contacts
|
||||
contacts, avatars, err := unmarshalContactDetailsMessages(contactsBytes)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Contacts Sync unmarshalContactDetailsMessages error")
|
||||
}
|
||||
log.Debug().Int("contact_count", len(contacts)).Msg("Contacts Sync received contacts")
|
||||
convertedContacts := make([]*types.Recipient, 0, len(contacts))
|
||||
err = cli.Store.DoContactTxn(ctx, func(ctx context.Context) error {
|
||||
for i, signalContact := range contacts {
|
||||
if signalContact.Aci == nil || *signalContact.Aci == "" {
|
||||
// TODO lookup PNI via CDSI and store that when ACI is missing?
|
||||
log.Info().
|
||||
Any("contact", signalContact).
|
||||
Msg("Signal Contact UUID is nil, skipping")
|
||||
continue
|
||||
}
|
||||
contact, err := cli.StoreContactDetailsAsContact(ctx, signalContact, &avatars[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
convertedContacts = append(convertedContacts, contact)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error storing contacts")
|
||||
} else {
|
||||
handlerSuccess = cli.handleEvent(&events.ContactList{
|
||||
Contacts: convertedContacts,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if content.SyncMessage.Read != nil {
|
||||
handlerSuccess = cli.handleEvent(&events.ReadSelf{
|
||||
Timestamp: envelope.GetTimestamp(),
|
||||
Messages: content.SyncMessage.GetRead(),
|
||||
})
|
||||
}
|
||||
if content.SyncMessage.DeleteForMe != nil {
|
||||
handlerSuccess = cli.handleEvent(&events.DeleteForMe{
|
||||
Timestamp: envelope.GetTimestamp(),
|
||||
SyncMessage_DeleteForMe: content.SyncMessage.DeleteForMe,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var sendDeliveryReceipt bool
|
||||
var deliveryReceiptTS uint64
|
||||
switch content := rawContent.Content.(type) {
|
||||
case *signalpb.Content_SyncMessage:
|
||||
if theirServiceID == cli.Store.ACIServiceID() {
|
||||
handlerSuccess = cli.handleSyncMessage(ctx, content.SyncMessage, envelope)
|
||||
sendDeliveryReceipt := true
|
||||
if content.DataMessage != nil {
|
||||
handlerSuccess = cli.incomingDataMessage(ctx, content.DataMessage, theirServiceID.UUID, theirServiceID, envelope.GetServerTimestamp())
|
||||
} else if content.EditMessage != nil {
|
||||
handlerSuccess = cli.incomingEditMessage(ctx, content.EditMessage, theirServiceID.UUID, theirServiceID, envelope.GetServerTimestamp())
|
||||
} else {
|
||||
sendDeliveryReceipt = false
|
||||
}
|
||||
if sendDeliveryReceipt && handlerSuccess {
|
||||
err = cli.sendDeliveryReceipts(ctx, []uint64{content.DataMessage.GetTimestamp()}, theirServiceID.UUID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("sendDeliveryReceipts error")
|
||||
}
|
||||
return nil
|
||||
case *signalpb.Content_DataMessage:
|
||||
handlerSuccess, sendDeliveryReceipt = cli.incomingDataMessage(
|
||||
ctx, content.DataMessage, theirServiceID.UUID, theirServiceID, envelope.GetServerTimestamp(), isBlocked,
|
||||
)
|
||||
deliveryReceiptTS = content.DataMessage.GetTimestamp()
|
||||
case *signalpb.Content_EditMessage:
|
||||
handlerSuccess, sendDeliveryReceipt = cli.incomingEditMessage(
|
||||
ctx, content.EditMessage, theirServiceID.UUID, theirServiceID, envelope.GetServerTimestamp(), isBlocked,
|
||||
)
|
||||
deliveryReceiptTS = content.EditMessage.GetDataMessage().GetTimestamp()
|
||||
case *signalpb.Content_ReceiptMessage:
|
||||
if content.ReceiptMessage.GetType() == signalpb.ReceiptMessage_DELIVERY && theirServiceID == cli.Store.ACIServiceID() {
|
||||
}
|
||||
|
||||
if content.TypingMessage != nil {
|
||||
var groupID types.GroupIdentifier
|
||||
if content.TypingMessage.GetGroupId() != nil {
|
||||
gidBytes := content.TypingMessage.GetGroupId()
|
||||
groupID = types.GroupIdentifier(base64.StdEncoding.EncodeToString(gidBytes))
|
||||
}
|
||||
// No handler success check here, nobody cares if typing notifications are dropped
|
||||
cli.handleEvent(&events.ChatEvent{
|
||||
Info: events.MessageInfo{
|
||||
Sender: theirServiceID.UUID,
|
||||
ChatID: groupOrUserID(groupID, theirServiceID),
|
||||
ServerTimestamp: envelope.GetServerTimestamp(),
|
||||
},
|
||||
Event: content.TypingMessage,
|
||||
})
|
||||
}
|
||||
|
||||
// DM call message (group call is an opaque callMessage and a groupCallUpdate in a dataMessage)
|
||||
if content.CallMessage != nil && (content.CallMessage.Offer != nil || content.CallMessage.Hangup != nil) {
|
||||
handlerSuccess = cli.handleEvent(&events.Call{
|
||||
Info: events.MessageInfo{
|
||||
Sender: theirServiceID.UUID,
|
||||
ChatID: theirServiceID.String(),
|
||||
ServerTimestamp: envelope.GetServerTimestamp(),
|
||||
},
|
||||
// CallMessage doesn't have its own timestamp, use one from the envelope
|
||||
Timestamp: envelope.GetTimestamp(),
|
||||
IsRinging: content.CallMessage.Offer != nil,
|
||||
}) && handlerSuccess
|
||||
}
|
||||
|
||||
// Read and delivery receipts
|
||||
if content.ReceiptMessage != nil {
|
||||
if content.GetReceiptMessage().GetType() == signalpb.ReceiptMessage_DELIVERY && theirServiceID == cli.Store.ACIServiceID() {
|
||||
// Ignore delivery receipts from other own devices
|
||||
return nil
|
||||
}
|
||||
handlerSuccess = cli.handleEvent(&events.Receipt{
|
||||
Sender: theirServiceID.UUID,
|
||||
Content: content.ReceiptMessage,
|
||||
})
|
||||
case *signalpb.Content_TypingMessage:
|
||||
var groupID types.GroupIdentifier
|
||||
if content.TypingMessage.GetGroupId() != nil {
|
||||
gidBytes := content.TypingMessage.GetGroupId()
|
||||
groupID = types.GroupIdentifier(base64.StdEncoding.EncodeToString(gidBytes))
|
||||
}
|
||||
if !isBlocked || groupID != "" {
|
||||
// No handler success check here, nobody cares if typing notifications are dropped
|
||||
cli.handleEvent(&events.ChatEvent{
|
||||
Info: events.MessageInfo{
|
||||
Sender: theirServiceID.UUID,
|
||||
ChatID: groupOrUserID(groupID, theirServiceID),
|
||||
ServerTimestamp: envelope.GetServerTimestamp(),
|
||||
},
|
||||
Event: content.TypingMessage,
|
||||
})
|
||||
}
|
||||
case *signalpb.Content_CallMessage:
|
||||
if !isBlocked && (content.CallMessage.Offer != nil || content.CallMessage.Hangup != nil) {
|
||||
handlerSuccess = cli.handleEvent(&events.Call{
|
||||
Info: events.MessageInfo{
|
||||
Sender: theirServiceID.UUID,
|
||||
ChatID: theirServiceID.String(),
|
||||
ServerTimestamp: envelope.GetServerTimestamp(),
|
||||
},
|
||||
// CallMessage doesn't have its own timestamp, use one from the envelope
|
||||
Timestamp: envelope.GetClientTimestamp(),
|
||||
IsRinging: content.CallMessage.Offer != nil,
|
||||
})
|
||||
}
|
||||
case *signalpb.Content_DecryptionErrorMessage:
|
||||
// These should've been handled earlier
|
||||
log.Warn().Msg("Unexpected decryption error message content in decrypted message")
|
||||
case *signalpb.Content_NullMessage:
|
||||
// This is intentionally ignored
|
||||
case *signalpb.Content_StoryMessage:
|
||||
// This is also ignored for now
|
||||
default:
|
||||
if rawContent.PniSignatureMessage == nil && rawContent.SenderKeyDistributionMessage == nil {
|
||||
log.Warn().Type("content_type", content).Msg("Unrecognized message content type")
|
||||
}
|
||||
}) && handlerSuccess
|
||||
}
|
||||
|
||||
if sendDeliveryReceipt && handlerSuccess {
|
||||
err = cli.sendDeliveryReceipts(ctx, []uint64{deliveryReceiptTS}, theirServiceID.UUID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("sendDeliveryReceipts error")
|
||||
}
|
||||
if !handlerSuccess {
|
||||
return ErrHandlerFailed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -695,184 +725,6 @@ func groupOrUserID(groupID types.GroupIdentifier, userID libsignalgo.ServiceID)
|
|||
return string(groupID)
|
||||
}
|
||||
|
||||
func (cli *Client) handleSyncMessage(ctx context.Context, msg *signalpb.SyncMessage, envelope *signalpb.Envelope) (handlerSuccess bool) {
|
||||
// TODO: handle more sync messages
|
||||
handlerSuccess = true
|
||||
log := zerolog.Ctx(ctx)
|
||||
switch content := msg.Content.(type) {
|
||||
case *signalpb.SyncMessage_Keys_:
|
||||
aep := libsignalgo.AccountEntropyPool(content.Keys.GetAccountEntropyPool())
|
||||
if aep != "" {
|
||||
aepMasterKey, err := aep.DeriveSVRKey()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
||||
} else if cli.Store.MasterKey == nil {
|
||||
cli.Store.MasterKey = aepMasterKey
|
||||
log.Debug().Msg("Derived master key from account entropy pool (no master key in sync message)")
|
||||
} else if !bytes.Equal(aepMasterKey, cli.Store.MasterKey) {
|
||||
log.Warn().Msg("Derived master key doesn't match one in sync message")
|
||||
} else {
|
||||
log.Debug().Msg("Derived master key matches one in sync message")
|
||||
}
|
||||
} else {
|
||||
log.Debug().Msg("No account entropy pool in sync message")
|
||||
}
|
||||
err := cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to save device after receiving master key")
|
||||
} else {
|
||||
log.Info().Msg("Received master key")
|
||||
go cli.SyncStorage(ctx)
|
||||
}
|
||||
case *signalpb.SyncMessage_FetchLatest_:
|
||||
switch content.FetchLatest.GetType() {
|
||||
case signalpb.SyncMessage_FetchLatest_STORAGE_MANIFEST:
|
||||
log.Debug().Msg("Received storage manifest fetch latest notice")
|
||||
go cli.SyncStorage(ctx)
|
||||
default:
|
||||
log.Debug().
|
||||
Stringer("fetch_latest_type", content.FetchLatest.GetType()).
|
||||
Msg("Received unknown fetch latest notice")
|
||||
}
|
||||
case *signalpb.SyncMessage_Sent_:
|
||||
syncSent := content.Sent
|
||||
if syncSent.GetMessage() != nil || syncSent.GetEditMessage() != nil {
|
||||
syncDestinationServiceID, err := ParseStringOrBinaryServiceID(syncSent.GetDestinationServiceId(), syncSent.GetDestinationServiceIdBinary())
|
||||
if err != nil && !errors.Is(err, ErrEmptyUUIDInput) {
|
||||
log.Err(err).Msg("Sync message destination parse error")
|
||||
}
|
||||
if syncSent.GetDestinationE164() != "" && !syncDestinationServiceID.IsEmpty() {
|
||||
aci, pni := syncDestinationServiceID.ToACIAndPNI()
|
||||
_, err = cli.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, syncSent.GetDestinationE164())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update recipient E164 after receiving sync message")
|
||||
}
|
||||
}
|
||||
for _, unident := range syncSent.GetUnidentifiedStatus() {
|
||||
serviceID, err := ParseStringOrBinaryServiceID(unident.GetDestinationServiceId(), unident.GetDestinationServiceIdBinary())
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("destination_service_id", unident.GetDestinationServiceId()).
|
||||
Hex("destination_service_id_bytes", unident.GetDestinationServiceIdBinary()).
|
||||
Msg("Failed to parse destination service ID of unidentified send")
|
||||
continue
|
||||
}
|
||||
changed, err := cli.saveSyncPNIIdentityKey(ctx, serviceID, unident.GetDestinationPniIdentityKey())
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Stringer("destination_service_id", serviceID).
|
||||
Msg("Failed to save PNI identity key from sync message")
|
||||
} else if changed {
|
||||
log.Debug().
|
||||
Stringer("destination_service_id", serviceID).
|
||||
Msg("Saved new PNI identity key from sync message")
|
||||
}
|
||||
}
|
||||
|
||||
if syncDestinationServiceID.IsEmpty() && syncSent.GetMessage().GetGroupV2() == nil && syncSent.GetEditMessage().GetDataMessage().GetGroupV2() == nil {
|
||||
log.Warn().Msg("sync message sent destination is nil")
|
||||
} else if syncSent.Message != nil {
|
||||
// TODO handle expiration start ts, and maybe the sync message ts?
|
||||
cli.incomingDataMessage(ctx, syncSent.Message, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp(), false)
|
||||
} else if syncSent.EditMessage != nil {
|
||||
cli.incomingEditMessage(ctx, syncSent.EditMessage, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp(), false)
|
||||
}
|
||||
}
|
||||
case *signalpb.SyncMessage_Contacts_:
|
||||
log.Debug().Msg("Recieved sync message contacts")
|
||||
if content.Contacts.Blob != nil {
|
||||
// TODO roundtrip via disk to save memory
|
||||
contactsBytes, err := DownloadAttachmentWithPointer(ctx, content.Contacts.Blob, nil, nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Contacts Sync DownloadAttachment error")
|
||||
}
|
||||
// unmarshall contacts
|
||||
contacts, avatars, err := unmarshalContactDetailsMessages(contactsBytes)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Contacts Sync unmarshalContactDetailsMessages error")
|
||||
}
|
||||
log.Debug().Int("contact_count", len(contacts)).Msg("Contacts Sync received contacts")
|
||||
convertedContacts := make([]*types.Recipient, 0, len(contacts))
|
||||
err = cli.Store.DoContactTxn(ctx, func(ctx context.Context) error {
|
||||
for i, signalContact := range contacts {
|
||||
if (signalContact.Aci == nil || *signalContact.Aci == "") && len(signalContact.AciBinary) != 16 {
|
||||
// TODO lookup PNI via CDSI and store that when ACI is missing?
|
||||
log.Info().
|
||||
Any("contact", signalContact).
|
||||
Msg("Signal Contact UUID is nil, skipping")
|
||||
continue
|
||||
}
|
||||
contact, err := cli.StoreContactDetailsAsContact(ctx, signalContact, &avatars[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
convertedContacts = append(convertedContacts, contact)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error storing contacts")
|
||||
} else {
|
||||
handlerSuccess = cli.handleEvent(&events.ContactList{
|
||||
Contacts: convertedContacts,
|
||||
})
|
||||
}
|
||||
}
|
||||
case *signalpb.SyncMessage_DeleteForMe_:
|
||||
handlerSuccess = cli.handleEvent(&events.DeleteForMe{
|
||||
Timestamp: envelope.GetClientTimestamp(),
|
||||
SyncMessage_DeleteForMe: content.DeleteForMe,
|
||||
})
|
||||
case *signalpb.SyncMessage_MessageRequestResponse_:
|
||||
aciUUID, _ := ParseStringOrBinaryUUID(content.MessageRequestResponse.GetThreadAci(), content.MessageRequestResponse.GetThreadAciBinary())
|
||||
if aciUUID != uuid.Nil && content.MessageRequestResponse.GetType() == signalpb.SyncMessage_MessageRequestResponse_ACCEPT {
|
||||
_, err := cli.Store.RecipientStore.LoadAndUpdateRecipient(ctx, aciUUID, uuid.Nil, func(recipient *types.Recipient) (changed bool, err error) {
|
||||
changed = !ptr.Val(recipient.Whitelisted) || recipient.NeedsPNISignature
|
||||
recipient.Whitelisted = ptr.Ptr(true)
|
||||
recipient.NeedsPNISignature = false
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to clear needs_pni_signature flag after message request accept")
|
||||
}
|
||||
}
|
||||
var groupID *libsignalgo.GroupIdentifier
|
||||
if len(content.MessageRequestResponse.GroupId) == libsignalgo.GroupIdentifierLength {
|
||||
groupID = (*libsignalgo.GroupIdentifier)(content.MessageRequestResponse.GroupId)
|
||||
}
|
||||
handlerSuccess = cli.handleEvent(&events.MessageRequestResponse{
|
||||
Timestamp: envelope.GetClientTimestamp(),
|
||||
ThreadACI: aciUUID,
|
||||
GroupID: groupID,
|
||||
Type: content.MessageRequestResponse.GetType(),
|
||||
Raw: content.MessageRequestResponse,
|
||||
})
|
||||
default:
|
||||
if msg.Read != nil {
|
||||
handlerSuccess = cli.handleEvent(&events.ReadSelf{
|
||||
Timestamp: envelope.GetClientTimestamp(),
|
||||
Messages: msg.Read,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) saveSyncPNIIdentityKey(ctx context.Context, serviceID libsignalgo.ServiceID, identityKeyBytes []byte) (bool, error) {
|
||||
if identityKeyBytes == nil || serviceID.Type != libsignalgo.ServiceIDTypePNI {
|
||||
return false, nil
|
||||
}
|
||||
identityKey, err := libsignalgo.DeserializeIdentityKey(identityKeyBytes)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to deserialize PNI identity key: %w", err)
|
||||
}
|
||||
changed, err := cli.Store.IdentityKeyStore.SaveIdentityKey(ctx, serviceID, identityKey)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to save PNI identity key: %w", err)
|
||||
}
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func (cli *Client) handlePNISignatureMessage(ctx context.Context, sender libsignalgo.ServiceID, msg *signalpb.PniSignatureMessage) error {
|
||||
if sender.Type != libsignalgo.ServiceIDTypeACI {
|
||||
return fmt.Errorf("PNI signature message sender is not an ACI")
|
||||
|
|
@ -923,14 +775,7 @@ func (cli *Client) handlePNISignatureMessage(ctx context.Context, sender libsign
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) incomingEditMessage(
|
||||
ctx context.Context,
|
||||
editMessage *signalpb.EditMessage,
|
||||
messageSenderACI uuid.UUID,
|
||||
chatRecipient libsignalgo.ServiceID,
|
||||
serverTimestamp uint64,
|
||||
isBlocked bool,
|
||||
) (handlerSuccess, sendDeliveryReceipt bool) {
|
||||
func (cli *Client) incomingEditMessage(ctx context.Context, editMessage *signalpb.EditMessage, messageSenderACI uuid.UUID, chatRecipient libsignalgo.ServiceID, serverTimestamp uint64) bool {
|
||||
// If it's a group message, get the ID and invalidate cache if necessary
|
||||
var groupID types.GroupIdentifier
|
||||
var groupRevision uint32
|
||||
|
|
@ -942,12 +787,9 @@ func (cli *Client) incomingEditMessage(
|
|||
groupID, err = cli.StoreMasterKey(ctx, masterKey)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("StoreMasterKey error")
|
||||
return
|
||||
return false
|
||||
}
|
||||
groupRevision = editMessage.GetDataMessage().GetGroupV2().GetRevision()
|
||||
} else if isBlocked {
|
||||
zerolog.Ctx(ctx).Debug().Msg("Dropping direct message from blocked user")
|
||||
return true, false
|
||||
}
|
||||
return cli.handleEvent(&events.ChatEvent{
|
||||
Info: events.MessageInfo{
|
||||
|
|
@ -957,24 +799,17 @@ func (cli *Client) incomingEditMessage(
|
|||
ServerTimestamp: serverTimestamp,
|
||||
},
|
||||
Event: editMessage,
|
||||
}), true
|
||||
})
|
||||
}
|
||||
|
||||
func (cli *Client) incomingDataMessage(
|
||||
ctx context.Context,
|
||||
dataMessage *signalpb.DataMessage,
|
||||
messageSenderACI uuid.UUID,
|
||||
chatRecipient libsignalgo.ServiceID,
|
||||
serverTimestamp uint64,
|
||||
isBlocked bool,
|
||||
) (handlerSuccess, sendDeliveryReceipt bool) {
|
||||
func (cli *Client) incomingDataMessage(ctx context.Context, dataMessage *signalpb.DataMessage, messageSenderACI uuid.UUID, chatRecipient libsignalgo.ServiceID, serverTimestamp uint64) bool {
|
||||
// If there's a profile key, save it
|
||||
if dataMessage.ProfileKey != nil {
|
||||
profileKey := libsignalgo.ProfileKey(dataMessage.ProfileKey)
|
||||
err := cli.Store.RecipientStore.StoreProfileKey(ctx, messageSenderACI, profileKey)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("StoreProfileKey error")
|
||||
return
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -989,12 +824,9 @@ func (cli *Client) incomingDataMessage(
|
|||
groupID, err = cli.StoreMasterKey(ctx, masterKey)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("StoreMasterKey error")
|
||||
return
|
||||
return false
|
||||
}
|
||||
groupRevision = dataMessage.GetGroupV2().GetRevision()
|
||||
} else if isBlocked {
|
||||
zerolog.Ctx(ctx).Debug().Msg("Dropping direct message from blocked user")
|
||||
return true, false
|
||||
}
|
||||
|
||||
evtInfo := events.MessageInfo{
|
||||
|
|
@ -1005,17 +837,17 @@ func (cli *Client) incomingDataMessage(
|
|||
}
|
||||
// Hacky special case for group calls to cache the state
|
||||
if dataMessage.GroupCallUpdate != nil {
|
||||
isRinging := cli.GroupCache.UpdateActiveCall(groupID, dataMessage.GroupCallUpdate.GetEraId())
|
||||
isRinging := cli.UpdateActiveCalls(groupID, dataMessage.GroupCallUpdate.GetEraId())
|
||||
return cli.handleEvent(&events.Call{
|
||||
Info: evtInfo,
|
||||
Timestamp: dataMessage.GetTimestamp(),
|
||||
IsRinging: isRinging,
|
||||
}), true
|
||||
})
|
||||
} else {
|
||||
return cli.handleEvent(&events.ChatEvent{
|
||||
Info: evtInfo,
|
||||
Event: dataMessage,
|
||||
}), true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,21 +39,18 @@ type DecryptionResult struct {
|
|||
Content *signalpb.Content
|
||||
ContentHint signalpb.UnidentifiedSenderMessage_Message_ContentHint
|
||||
Err error
|
||||
GroupID *libsignalgo.GroupIdentifier
|
||||
Unencrypted bool
|
||||
|
||||
Retriable bool
|
||||
Ciphertext []byte
|
||||
CiphertextType libsignalgo.CiphertextMessageType
|
||||
}
|
||||
|
||||
func (cli *Client) decryptEnvelope(
|
||||
ctx context.Context,
|
||||
envelope *signalpb.Envelope,
|
||||
sourceServiceID, destinationServiceID libsignalgo.ServiceID,
|
||||
) DecryptionResult {
|
||||
if destinationServiceID.IsEmpty() {
|
||||
return DecryptionResult{Err: fmt.Errorf("envelope missing destination service ID")}
|
||||
log := zerolog.Ctx(ctx)
|
||||
|
||||
destinationServiceID, err := libsignalgo.ServiceIDFromString(envelope.GetDestinationServiceId())
|
||||
if err != nil {
|
||||
log.Err(err).Str("destination_service_id", envelope.GetDestinationServiceId()).Msg("Failed to parse destination service ID")
|
||||
return DecryptionResult{Err: fmt.Errorf("failed to parse destination service ID: %w", err)}
|
||||
}
|
||||
|
||||
switch *envelope.Type {
|
||||
|
|
@ -64,14 +61,17 @@ func (cli *Client) decryptEnvelope(
|
|||
}
|
||||
return result
|
||||
|
||||
case signalpb.Envelope_PREKEY_MESSAGE, signalpb.Envelope_DOUBLE_RATCHET:
|
||||
sender, err := sourceServiceID.Address(uint(envelope.GetSourceDeviceId()))
|
||||
case signalpb.Envelope_PREKEY_BUNDLE, signalpb.Envelope_CIPHERTEXT:
|
||||
sender, err := libsignalgo.NewUUIDAddressFromString(
|
||||
*envelope.SourceServiceId,
|
||||
uint(*envelope.SourceDevice),
|
||||
)
|
||||
if err != nil {
|
||||
return DecryptionResult{Err: fmt.Errorf("failed to wrap address: %v", err)}
|
||||
}
|
||||
var result *DecryptionResult
|
||||
var bundleType string
|
||||
if *envelope.Type == signalpb.Envelope_PREKEY_MESSAGE {
|
||||
if *envelope.Type == signalpb.Envelope_PREKEY_BUNDLE {
|
||||
result, err = cli.prekeyDecrypt(ctx, destinationServiceID, sender, envelope.Content, envelope.GetServerTimestamp())
|
||||
bundleType = "prekey bundle"
|
||||
} else {
|
||||
|
|
@ -79,34 +79,19 @@ func (cli *Client) decryptEnvelope(
|
|||
bundleType = "ciphertext"
|
||||
}
|
||||
if err != nil {
|
||||
return DecryptionResult{
|
||||
SenderAddress: sender,
|
||||
Err: fmt.Errorf("failed to decrypt %s envelope: %w", bundleType, err),
|
||||
Retriable: true, // TODO should these ever be not retriable?
|
||||
Ciphertext: envelope.Content,
|
||||
CiphertextType: libsignalgo.CiphertextMessageType(envelope.GetType()),
|
||||
}
|
||||
return DecryptionResult{Err: fmt.Errorf("failed to decrypt %s envelope: %w", bundleType, err), SenderAddress: sender}
|
||||
}
|
||||
return *result
|
||||
|
||||
case signalpb.Envelope_PLAINTEXT_CONTENT:
|
||||
addr, err := sourceServiceID.Address(uint(envelope.GetSourceDeviceId()))
|
||||
if err != nil {
|
||||
return DecryptionResult{Err: fmt.Errorf("failed to wrap address: %v", err)}
|
||||
}
|
||||
content, err := stripPadding(envelope.GetContent())
|
||||
if err != nil {
|
||||
return DecryptionResult{Err: fmt.Errorf("failed to strip padding: %w", err)}
|
||||
}
|
||||
return DecryptionResult{
|
||||
SenderAddress: addr,
|
||||
Content: &signalpb.Content{Content: &signalpb.Content_DecryptionErrorMessage{DecryptionErrorMessage: content}},
|
||||
Unencrypted: true,
|
||||
}
|
||||
return DecryptionResult{Err: fmt.Errorf("plaintext messages are not supported")}
|
||||
|
||||
case signalpb.Envelope_SERVER_DELIVERY_RECEIPT:
|
||||
return DecryptionResult{Err: fmt.Errorf("server delivery receipt envelopes are not yet supported")}
|
||||
|
||||
case signalpb.Envelope_SENDERKEY_MESSAGE:
|
||||
return DecryptionResult{Err: fmt.Errorf("senderkey message envelopes are not yet supported")}
|
||||
|
||||
case signalpb.Envelope_UNKNOWN:
|
||||
return DecryptionResult{Err: fmt.Errorf("unknown envelope type")}
|
||||
|
||||
|
|
@ -185,17 +170,12 @@ func (cli *Client) prekeyDecrypt(
|
|||
if is == nil {
|
||||
return nil, fmt.Errorf("no identity store found for %s", destination)
|
||||
}
|
||||
destinationAddress, err := destination.Address(uint(cli.Store.DeviceID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get own/destination address: %w", err)
|
||||
}
|
||||
|
||||
plaintext, ciphertextHash, err := cli.bufferedDecryptTxn(ctx, encryptedContent, serverTimestamp, func(ctx context.Context) ([]byte, error) {
|
||||
return libsignalgo.DecryptPreKey(
|
||||
ctx,
|
||||
preKeyMessage,
|
||||
sender,
|
||||
destinationAddress,
|
||||
ss,
|
||||
is,
|
||||
pks,
|
||||
|
|
@ -243,16 +223,11 @@ func (cli *Client) decryptCiphertextEnvelope(
|
|||
if identityStore == nil {
|
||||
return nil, fmt.Errorf("no identity store for destination service ID %s", destinationServiceID)
|
||||
}
|
||||
destinationAddress, err := destinationServiceID.Address(uint(cli.Store.DeviceID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get own address: %w", err)
|
||||
}
|
||||
plaintext, ciphertextHash, err := cli.bufferedDecryptTxn(ctx, ciphertext, serverTimestamp, func(ctx context.Context) ([]byte, error) {
|
||||
return libsignalgo.Decrypt(
|
||||
ctx,
|
||||
message,
|
||||
senderAddress,
|
||||
destinationAddress,
|
||||
sessionStore,
|
||||
identityStore,
|
||||
)
|
||||
|
|
@ -340,10 +315,6 @@ func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destin
|
|||
if err != nil {
|
||||
return result, fmt.Errorf("failed to get content hint: %w", err)
|
||||
}
|
||||
result.GroupID, err = usmc.GetGroupID()
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to get group ID: %w", err)
|
||||
}
|
||||
result.ContentHint = signalpb.UnidentifiedSenderMessage_Message_ContentHint(contentHint)
|
||||
senderUUID, err := senderCertificate.GetSenderUUID()
|
||||
if err != nil {
|
||||
|
|
@ -366,11 +337,8 @@ func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destin
|
|||
if err != nil {
|
||||
return result, fmt.Errorf("failed to get USMC contents: %w", err)
|
||||
}
|
||||
result.Ciphertext = usmcContents
|
||||
result.CiphertextType = messageType
|
||||
newLog := log.With().
|
||||
Stringer("sender_uuid", senderUUID).
|
||||
Stringer("group_id", result.GroupID).
|
||||
Uint32("sender_device_id", senderDeviceID).
|
||||
Str("sender_e164", senderE164).
|
||||
Uint8("sealed_sender_type", uint8(messageType)).
|
||||
|
|
@ -395,25 +363,15 @@ func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destin
|
|||
case libsignalgo.CiphertextMessageTypeWhisper:
|
||||
resultPtr, err = cli.decryptCiphertextEnvelope(ctx, destinationServiceID, senderAddress, usmcContents, envelope.GetServerTimestamp())
|
||||
case libsignalgo.CiphertextMessageTypePlaintext:
|
||||
usmcContents, err = stripPadding(usmcContents)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to strip padding: %w", err)
|
||||
}
|
||||
result.Unencrypted = true
|
||||
result.Content = &signalpb.Content{
|
||||
Content: &signalpb.Content_DecryptionErrorMessage{
|
||||
DecryptionErrorMessage: usmcContents,
|
||||
},
|
||||
}
|
||||
return result, err
|
||||
// TODO: handle plaintext (usually DecryptionErrorMessage) and retries
|
||||
// when implementing SenderKey groups
|
||||
return result, fmt.Errorf("unsupported plaintext sealed sender message")
|
||||
default:
|
||||
return result, fmt.Errorf("unsupported sealed sender message type %d", messageType)
|
||||
}
|
||||
if err != nil {
|
||||
result.Retriable = result.ContentHint == signalpb.UnidentifiedSenderMessage_Message_RESENDABLE
|
||||
return result, err
|
||||
}
|
||||
resultPtr.GroupID = result.GroupID
|
||||
return *resultPtr, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,214 +0,0 @@
|
|||
// mautrix-signal - A Matrix-signal puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package signalmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/random"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||
)
|
||||
|
||||
type sendCacheKey struct {
|
||||
recipient libsignalgo.ServiceID
|
||||
groupID types.GroupIdentifier
|
||||
timestamp uint64
|
||||
}
|
||||
|
||||
const RetryRespondMaxAge = 30 * 24 * time.Hour
|
||||
|
||||
func (cli *Client) sendRetryRequest(ctx context.Context, result DecryptionResult, originalTS uint64) error {
|
||||
serviceID, err := result.SenderAddress.NameServiceID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sender name as service ID: %w", err)
|
||||
}
|
||||
deviceID, err := result.SenderAddress.DeviceID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sender device ID: %w", err)
|
||||
}
|
||||
dem, err := libsignalgo.DecryptionErrorMessageForOriginalMessage(result.Ciphertext, result.CiphertextType, originalTS, deviceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create decryption error message: %w", err)
|
||||
}
|
||||
demBytes, err := dem.Serialize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize decryption error message: %w", err)
|
||||
}
|
||||
ptc, err := libsignalgo.PlaintextContentFromDecryptionErrorMessage(dem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create plaintext content from decryption error message: %w", err)
|
||||
}
|
||||
ctm, err := libsignalgo.NewCiphertextMessage(ptc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ciphertext message from plaintext content: %w", err)
|
||||
}
|
||||
_, err = cli.sendContent(ctx, serviceID, uint64(time.Now().UnixMilli()), &signalpb.Content{
|
||||
Content: &signalpb.Content_DecryptionErrorMessage{
|
||||
DecryptionErrorMessage: demBytes,
|
||||
},
|
||||
}, 0, true, result.GroupID, ctm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send decryption error message: %w", err)
|
||||
}
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("sender_service_id", serviceID).
|
||||
Uint("sender_device_id", deviceID).
|
||||
Stringer("group_id", result.GroupID).
|
||||
Msg("Sent retry receipt")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) handleRetryRequest(
|
||||
ctx context.Context,
|
||||
result DecryptionResult,
|
||||
dem *libsignalgo.DecryptionErrorMessage,
|
||||
) error {
|
||||
destDeviceID, err := dem.GetDeviceID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get device ID from decryption error message: %w", err)
|
||||
} else if int(destDeviceID) != cli.Store.DeviceID {
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Uint32("dest_device_id", destDeviceID).
|
||||
Msg("Ignoring decryption error message for another device")
|
||||
return nil
|
||||
}
|
||||
serviceID, err := result.SenderAddress.NameServiceID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sender name as service ID: %w", err)
|
||||
}
|
||||
deviceID, err := result.SenderAddress.DeviceID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sender device ID: %w", err)
|
||||
}
|
||||
ts, err := dem.GetTimestamp()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get timestamp: %w", err)
|
||||
}
|
||||
|
||||
cli.encryptionLock.Lock()
|
||||
defer cli.encryptionLock.Unlock()
|
||||
ctx = context.WithValue(ctx, contextKeyEncryptionLock, true)
|
||||
var didArchiveSession bool
|
||||
if ratchetKey, err := dem.GetRatchetKey(); err != nil {
|
||||
return fmt.Errorf("failed to get ratchet key: %w", err)
|
||||
} else if ratchetKey == nil {
|
||||
// No need to archive session if no ratchet key is provided, it was probably a sender key decryption error
|
||||
} else if session, err := cli.Store.ACISessionStore.LoadSession(ctx, result.SenderAddress); err != nil {
|
||||
return fmt.Errorf("failed to load session for sender: %w", err)
|
||||
} else if match, err := session.CurrentRatchetKeyMatches(ratchetKey); err != nil {
|
||||
return fmt.Errorf("failed to check ratchet key match: %w", err)
|
||||
} else if match {
|
||||
err = session.ArchiveCurrentState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to archive current session state: %w", err)
|
||||
}
|
||||
err = cli.Store.ACISessionStore.StoreSession(ctx, result.SenderAddress, session)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store archived session: %w", err)
|
||||
}
|
||||
didArchiveSession = true
|
||||
}
|
||||
var skdmBytes []byte
|
||||
groupID := types.BytesToGroupIdentifier(result.GroupID)
|
||||
if groupID != "" {
|
||||
ski, err := cli.Store.SenderKeyStore.GetSenderKeyInfo(ctx, groupID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sender key info for group %s: %w", groupID, err)
|
||||
}
|
||||
myAddress, err := cli.Store.ACIServiceID().Address(uint(cli.Store.DeviceID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get own address: %w", err)
|
||||
}
|
||||
if slices.Contains(ski.SharedWith[serviceID], int(deviceID)) {
|
||||
skdm, err := libsignalgo.NewSenderKeyDistributionMessage(ctx, myAddress, ski.DistributionID, cli.Store.SenderKeyStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sender key distribution message: %w", err)
|
||||
}
|
||||
skdmBytes, err = skdm.Serialize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize sender key distribution message: %w", err)
|
||||
}
|
||||
} else {
|
||||
zerolog.Ctx(ctx).Warn().
|
||||
Stringer("group_id", result.GroupID).
|
||||
Stringer("sender_service_id", serviceID).
|
||||
Stringer("distribution_id", ski.DistributionID).
|
||||
Uint("sender_device_id", deviceID).
|
||||
Ints("shared_with", ski.SharedWith[serviceID]).
|
||||
Msg("Sender key distribution list doesn't contain retry receipt sender")
|
||||
}
|
||||
}
|
||||
var retryContent *signalpb.Content
|
||||
var cacheHit bool
|
||||
if time.Since(time.UnixMilli(int64(ts))) < RetryRespondMaxAge {
|
||||
retryContent, cacheHit = cli.sendCache.Get(sendCacheKey{
|
||||
groupID: groupID,
|
||||
recipient: serviceID,
|
||||
timestamp: ts,
|
||||
})
|
||||
if !cacheHit {
|
||||
// TODO add support for external caches
|
||||
}
|
||||
}
|
||||
if retryContent == nil {
|
||||
retryContent = &signalpb.Content{}
|
||||
}
|
||||
retryContent.SenderKeyDistributionMessage = skdmBytes
|
||||
if !cacheHit && skdmBytes == nil {
|
||||
if !didArchiveSession {
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Uint64("msg_timestamp", ts).
|
||||
Stringer("sender_service_id", serviceID).
|
||||
Uint("sender_device_id", deviceID).
|
||||
Stringer("group_id", result.GroupID).
|
||||
Msg("Not responding to decryption error message")
|
||||
return nil
|
||||
}
|
||||
retryContent.Content = &signalpb.Content_NullMessage{
|
||||
NullMessage: &signalpb.NullMessage{
|
||||
Padding: random.Bytes(rand.IntN(511) + 1),
|
||||
},
|
||||
}
|
||||
}
|
||||
responseTimestamp := uint64(time.Now().UnixMilli())
|
||||
if cacheHit {
|
||||
responseTimestamp = ts
|
||||
}
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Uint32("dest_device_id", destDeviceID).
|
||||
Uint64("requested_msg_timestamp", ts).
|
||||
Stringer("sender_service_id", serviceID).
|
||||
Uint("sender_device_id", deviceID).
|
||||
Stringer("group_id", result.GroupID).
|
||||
Bool("did_archive_session", didArchiveSession).
|
||||
Bool("found_message_in_cache", cacheHit).
|
||||
Bool("including_skdm", skdmBytes != nil).
|
||||
Msg("Responding to decryption error message")
|
||||
_, err = cli.sendContent(ctx, serviceID, responseTimestamp, retryContent, 0, true, result.GroupID, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send response: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue