mirror of
https://github.com/mautrix/signal.git
synced 2026-05-14 21:26: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
|
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
|
||||||
<!-- All items below are mandatory. Issues not following the rules may be closed without comment. -->
|
ignored or closed immediately.
|
||||||
|
-->
|
||||||
* [ ] 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: ``
|
|
||||||
|
|
|
||||||
16
.github/workflows/go.yml
vendored
16
.github/workflows/go.yml
vendored
|
|
@ -11,14 +11,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.25", "1.26"]
|
go-version: ["1.24", "1.25"]
|
||||||
name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
name: Lint ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
@ -40,14 +40,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.25", "1.26"]
|
go-version: ["1.24", "1.25"]
|
||||||
name: Test ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
name: Test ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
|
||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
lock-stale:
|
lock-stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v6
|
- uses: dessant/lock-threads@v5
|
||||||
id: lock
|
id: lock
|
||||||
with:
|
with:
|
||||||
issue-inactive-days: 90
|
issue-inactive-days: 90
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ repos:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
|
||||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||||
rev: v1.0.0-rc.4
|
rev: v1.0.0-rc.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-imports
|
- id: go-imports
|
||||||
exclude: "pb\\.go$"
|
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 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 PNI signature not being sent when replying to message requests.
|
||||||
* Fixed unnecessary repeating error notices when Signal is down.
|
* 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
|
# v25.10
|
||||||
|
|
||||||
|
|
|
||||||
22
Dockerfile
22
Dockerfile
|
|
@ -1,17 +1,18 @@
|
||||||
# -- Build libsignal (with Rust) --
|
# -- Build libsignal (with Rust) --
|
||||||
FROM rust:1-alpine AS rust-builder
|
FROM rust:1-alpine as rust-builder
|
||||||
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev protobuf-dev
|
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
# Copy all files needed for Rust build, and no Go files
|
# Copy all files needed for Rust build, and no Go files
|
||||||
COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/.
|
COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/.
|
||||||
COPY build-rust.sh .
|
COPY build-rust.sh .
|
||||||
|
|
||||||
|
ARG DBG=0
|
||||||
RUN ./build-rust.sh
|
RUN ./build-rust.sh
|
||||||
|
|
||||||
# -- Build mautrix-signal (with Go) --
|
# -- Build mautrix-signal (with Go) --
|
||||||
FROM golang:1-alpine3.23 AS go-builder
|
FROM golang:1-alpine3.22 AS go-builder
|
||||||
RUN apk add --no-cache git ca-certificates build-base olm-dev zlib-dev
|
RUN apk add --no-cache git ca-certificates build-base olm-dev
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
# Copy all files needed for Go build, and no Rust files
|
# Copy all files needed for Go build, and no Rust files
|
||||||
|
|
@ -25,14 +26,20 @@ COPY pkg/connector/. pkg/connector/.
|
||||||
COPY cmd/. cmd/.
|
COPY cmd/. cmd/.
|
||||||
COPY .git .git
|
COPY .git .git
|
||||||
|
|
||||||
|
ARG DBG=0
|
||||||
ENV LIBRARY_PATH=.
|
ENV LIBRARY_PATH=.
|
||||||
COPY --from=rust-builder /build/pkg/libsignalgo/libsignal/target/*/libsignal_ffi.a ./
|
COPY --from=rust-builder /build/pkg/libsignalgo/libsignal/target/*/libsignal_ffi.a ./
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
|
if [ "$DBG" = 1 ]; then
|
||||||
|
go install github.com/go-delve/delve/cmd/dlv@latest
|
||||||
|
else
|
||||||
|
touch /go/bin/dlv
|
||||||
|
fi
|
||||||
EOF
|
EOF
|
||||||
RUN ./build-go.sh
|
RUN ./build-go.sh
|
||||||
|
|
||||||
# -- Run mautrix-signal --
|
# -- Run mautrix-signal --
|
||||||
FROM alpine:3.23
|
FROM alpine:3.22
|
||||||
|
|
||||||
ENV UID=1337 \
|
ENV UID=1337 \
|
||||||
GID=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/mautrix-signal /usr/bin/mautrix-signal
|
||||||
COPY --from=go-builder /build/docker-run.sh /docker-run.sh
|
COPY --from=go-builder /build/docker-run.sh /docker-run.sh
|
||||||
|
COPY --from=go-builder /go/bin/dlv /usr/bin/dlv
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
|
ARG DBG
|
||||||
|
ARG DBGWAIT=0
|
||||||
|
ENV DBG=${DBG} DBGWAIT=${DBGWAIT}
|
||||||
|
RUN echo "Debug mode: DBG=${DBG} DBGWAIT=${DBGWAIT}"
|
||||||
CMD ["/docker-run.sh"]
|
CMD ["/docker-run.sh"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ARG DOCKER_HUB="docker.io"
|
ARG DOCKER_HUB="docker.io"
|
||||||
|
|
||||||
FROM ${DOCKER_HUB}/alpine:3.23
|
FROM ${DOCKER_HUB}/alpine:3.22
|
||||||
|
|
||||||
ENV UID=1337 \
|
ENV UID=1337 \
|
||||||
GID=1337
|
GID=1337
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
* [x] Text
|
* [x] Text
|
||||||
* [x] Formatting
|
* [x] Formatting
|
||||||
* [x] Mentions
|
* [x] Mentions
|
||||||
* [x] Polls
|
|
||||||
* [x] Media
|
* [x] Media
|
||||||
* [x] Images
|
* [x] Images
|
||||||
* [x] Audio files
|
* [x] Audio files
|
||||||
|
|
@ -35,7 +34,6 @@
|
||||||
* [x] Text
|
* [x] Text
|
||||||
* [x] Formatting
|
* [x] Formatting
|
||||||
* [x] Mentions
|
* [x] Mentions
|
||||||
* [x] Polls
|
|
||||||
* [ ] Media
|
* [ ] Media
|
||||||
* [x] Images
|
* [x] Images
|
||||||
* [x] Voice notes
|
* [x] Voice notes
|
||||||
|
|
@ -67,8 +65,8 @@
|
||||||
* [ ] Delivery receipts (there's no good way to bridge these)
|
* [ ] Delivery receipts (there's no good way to bridge these)
|
||||||
* [x] Disappearing messages
|
* [x] Disappearing messages
|
||||||
* Misc
|
* Misc
|
||||||
* [x] Automatic portal creation
|
* [ ] Automatic portal creation
|
||||||
* [x] After login
|
* [ ] After login
|
||||||
* [x] When receiving message
|
* [x] When receiving message
|
||||||
* [x] Linking as secondary device
|
* [x] Linking as secondary device
|
||||||
* [ ] Registering as primary device
|
* [ ] Registering as primary device
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,9 @@
|
||||||
#!/bin/sh
|
#!/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
|
#!/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
|
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
|
#!/bin/sh
|
||||||
set -e
|
|
||||||
./build-rust.sh
|
./build-rust.sh
|
||||||
cp -f pkg/libsignalgo/libsignal/target/release/libsignal_ffi.a .
|
cp -f pkg/libsignalgo/libsignal/target/release/libsignal_ffi.a .
|
||||||
LIBRARY_PATH=.:$LIBRARY_PATH ./build-go.sh
|
LIBRARY_PATH=.:$LIBRARY_PATH ./build-go.sh
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"
|
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/connector"
|
"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.
|
// Information to find out exactly which commit the bridge was built from.
|
||||||
|
|
@ -37,14 +34,13 @@ var m = mxmain.BridgeMain{
|
||||||
Name: "mautrix-signal",
|
Name: "mautrix-signal",
|
||||||
URL: "https://github.com/mautrix/signal",
|
URL: "https://github.com/mautrix/signal",
|
||||||
Description: "A Matrix-Signal puppeting bridge.",
|
Description: "A Matrix-Signal puppeting bridge.",
|
||||||
Version: "26.04",
|
Version: "25.10",
|
||||||
SemCalVer: true,
|
SemCalVer: true,
|
||||||
|
|
||||||
Connector: &connector.SignalConnector{},
|
Connector: &connector.SignalConnector{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
web.UserAgent = fmt.Sprintf("mautrix-signal/%s %s", m.Version, web.BaseUserAgent)
|
|
||||||
m.PostStart = func() {
|
m.PostStart = func() {
|
||||||
if m.Matrix.Provisioning != nil {
|
if m.Matrix.Provisioning != nil {
|
||||||
m.Matrix.Provisioning.Router.HandleFunc("GET /v2/resolve_identifier/{phonenum}", legacyProvResolveIdentifier)
|
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
|
module go.mau.fi/mautrix-signal
|
||||||
|
|
||||||
go 1.25.0
|
go 1.24.0
|
||||||
|
|
||||||
toolchain go1.26.2
|
toolchain go1.25.3
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coder/websocket v1.8.14
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-pointer v0.0.1
|
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/stretchr/testify v1.11.1
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25
|
go.mau.fi/util v0.9.2
|
||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.43.0
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b
|
||||||
golang.org/x/net v0.53.0
|
golang.org/x/net v0.46.0
|
||||||
golang.org/x/sync v0.20.0
|
google.golang.org/protobuf v1.36.10
|
||||||
google.golang.org/protobuf v1.36.11
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
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 (
|
require (
|
||||||
filippo.io/edwards25519 v1.2.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/kr/pretty v0.3.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-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.44 // indirect
|
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect
|
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // 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/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // 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
|
go.mau.fi/zeroconfig v0.2.0 // indirect
|
||||||
golang.org/x/mod v0.35.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.36.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
maunium.net/go/mauflag v1.0.0 // 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.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
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 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
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 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
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.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
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/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg=
|
||||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
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 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
|
||||||
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
|
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.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM=
|
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 h1:QTvNkZ5ylY0PGgA+Lih+GdboMLY/G9SEGLMEGVjTVA4=
|
||||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
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/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 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
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.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
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=
|
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.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
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.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.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
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/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 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
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.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.7.13/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.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM=
|
||||||
go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs=
|
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 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||||
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA=
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
|
||||||
golang.org/x/sys v0.6.0/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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
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=
|
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 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
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.25.3-0.20251027130059-9f2669025f28 h1:BG0IPWqlVR2mxAQM8jhOhHa168si5iXKeTv11nCGj58=
|
||||||
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/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI=
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
||||||
if dm == nil {
|
if dm == nil {
|
||||||
continue
|
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))
|
convertedReactions := make([]*bridgev2.BackfillReaction, 0, len(reactions))
|
||||||
for _, reaction := range reactions {
|
for _, reaction := range reactions {
|
||||||
reactionSenderACI, err := getRecipientACI(reaction.AuthorId)
|
reactionSenderACI, err := getRecipientACI(reaction.AuthorId)
|
||||||
|
|
@ -187,7 +187,7 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
||||||
CompleteCallback: func() {
|
CompleteCallback: func() {
|
||||||
// When reaching the last backwards backfill batch, delete the chat from the backup store.
|
// 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 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)
|
err := s.Client.Store.BackupStore.DeleteBackupChat(ctx, chat.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete chat from backup store")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete chat from backup store")
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func capID() string {
|
func capID() string {
|
||||||
base := "fi.mau.signal.capabilities.2026_05_12"
|
base := "fi.mau.signal.capabilities.2025_10_27"
|
||||||
if ffmpeg.Supported() {
|
if ffmpeg.Supported() {
|
||||||
return base + "+ffmpeg"
|
return base + "+ffmpeg"
|
||||||
}
|
}
|
||||||
|
|
@ -111,8 +111,7 @@ var signalCaps = &event.RoomFeatures{
|
||||||
},
|
},
|
||||||
event.CapMsgSticker: {
|
event.CapMsgSticker: {
|
||||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||||
// Signal clients will only render static webp, so apng is preferred
|
"image/webp": event.CapLevelFullySupported,
|
||||||
"image/webp": event.CapLevelPartialSupport,
|
|
||||||
"image/png": event.CapLevelFullySupported,
|
"image/png": event.CapLevelFullySupported,
|
||||||
"image/apng": event.CapLevelFullySupported,
|
"image/apng": event.CapLevelFullySupported,
|
||||||
"image/gif": supportedIfFFmpeg(),
|
"image/gif": supportedIfFFmpeg(),
|
||||||
|
|
@ -139,10 +138,10 @@ var signalCaps = &event.RoomFeatures{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
State: event.StateFeatureMap{
|
State: event.StateFeatureMap{
|
||||||
event.StateRoomName.Type: {Level: event.CapLevelFullySupported},
|
event.StateRoomName.Type: event.CapLevelFullySupported,
|
||||||
event.StateRoomAvatar.Type: {Level: event.CapLevelFullySupported},
|
event.StateRoomAvatar.Type: event.CapLevelFullySupported,
|
||||||
event.StateTopic.Type: {Level: event.CapLevelFullySupported},
|
event.StateTopic.Type: event.CapLevelFullySupported,
|
||||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
event.StateBeeperDisappearingTimer.Type: event.CapLevelFullySupported,
|
||||||
},
|
},
|
||||||
MemberActions: event.MemberFeatureMap{
|
MemberActions: event.MemberFeatureMap{
|
||||||
event.MemberActionInvite: event.CapLevelFullySupported,
|
event.MemberActionInvite: event.CapLevelFullySupported,
|
||||||
|
|
@ -170,12 +169,6 @@ var signalCaps = &event.RoomFeatures{
|
||||||
CustomEmojiReactions: false,
|
CustomEmojiReactions: false,
|
||||||
ReadReceipts: true,
|
ReadReceipts: true,
|
||||||
TypingNotifications: true,
|
TypingNotifications: true,
|
||||||
|
|
||||||
DeleteChat: true,
|
|
||||||
MessageRequest: &event.MessageRequestFeatures{
|
|
||||||
AcceptWithMessage: event.CapLevelPartialSupport,
|
|
||||||
AcceptWithButton: event.CapLevelFullySupported,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var signalDisappearingCap = &event.DisappearingTimerCapability{
|
var signalDisappearingCap = &event.DisappearingTimerCapability{
|
||||||
|
|
@ -190,7 +183,7 @@ func init() {
|
||||||
signalCapsDM.ID = capID() + "+dm"
|
signalCapsDM.ID = capID() + "+dm"
|
||||||
signalCapsDM.MemberActions = nil
|
signalCapsDM.MemberActions = nil
|
||||||
signalCapsDM.State = event.StateFeatureMap{
|
signalCapsDM.State = event.StateFeatureMap{
|
||||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
event.StateBeeperDisappearingTimer.Type: event.CapLevelFullySupported,
|
||||||
}
|
}
|
||||||
signalCapsNoteToSelf = ptr.Clone(signalCapsDM)
|
signalCapsNoteToSelf = ptr.Clone(signalCapsDM)
|
||||||
signalCapsNoteToSelf.EditMaxAge = nil
|
signalCapsNoteToSelf.EditMaxAge = nil
|
||||||
|
|
@ -212,7 +205,6 @@ var signalGeneralCaps = &bridgev2.NetworkGeneralCapabilities{
|
||||||
AggressiveUpdateInfo: true,
|
AggressiveUpdateInfo: true,
|
||||||
ImplicitReadReceipts: true,
|
ImplicitReadReceipts: true,
|
||||||
Provisioning: bridgev2.ProvisioningCapabilities{
|
Provisioning: bridgev2.ProvisioningCapabilities{
|
||||||
ImagePackImport: true,
|
|
||||||
ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{
|
ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{
|
||||||
CreateDM: true,
|
CreateDM: true,
|
||||||
LookupPhone: true,
|
LookupPhone: true,
|
||||||
|
|
@ -237,5 +229,5 @@ func (s *SignalConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) GetBridgeInfoVersion() (info, capabilities int) {
|
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)
|
e164String := fmt.Sprintf("+%d", e164Number)
|
||||||
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
||||||
return nil, fmt.Errorf("error looking up number in local contact list: %w", err)
|
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
|
aci = recipient.ACI
|
||||||
pni = recipient.PNI
|
pni = recipient.PNI
|
||||||
} else if resp, err := s.Client.LookupPhone(ctx, e164Number); err != nil {
|
} 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")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to save recipient entry after looking up phone")
|
||||||
}
|
}
|
||||||
aci, pni = recipient.ACI, recipient.PNI
|
aci, pni = recipient.ACI, recipient.PNI
|
||||||
if aci != uuid.Nil {
|
|
||||||
s.Client.Store.RecipientStore.MarkUnregistered(ctx, libsignalgo.NewACIServiceID(aci), false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
aci, pni = serviceID.ToACIAndPNI()
|
aci, pni = serviceID.ToACIAndPNI()
|
||||||
|
|
@ -326,13 +323,13 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
||||||
}
|
}
|
||||||
var avatarBytes []byte
|
var avatarBytes []byte
|
||||||
var avatarMXC id.ContentURIString
|
var avatarMXC id.ContentURIString
|
||||||
if params.Avatar != nil && params.Avatar.URL != "" {
|
if params.Avatar != nil {
|
||||||
avatarMXC = params.Avatar.URL
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to download avatar: %w", err)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to upload avatar: %w", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create group: %w", err)
|
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 {
|
func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *types.Recipient, backupChat *store.BackupChat) *bridgev2.CreateChatResponse {
|
||||||
namePtr := bridgev2.DefaultChatName
|
name := ""
|
||||||
topic := PrivateChatTopic
|
topic := PrivateChatTopic
|
||||||
selfUser := s.makeEventSender(s.Client.Store.ACI)
|
selfUser := s.makeEventSender(s.Client.Store.ACI)
|
||||||
members := &bridgev2.ChatMemberList{
|
members := &bridgev2.ChatMemberList{
|
||||||
|
|
@ -441,7 +438,7 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
var serviceID libsignalgo.ServiceID
|
var serviceID libsignalgo.ServiceID
|
||||||
var avatar *bridgev2.Avatar
|
var avatar *bridgev2.Avatar
|
||||||
if recipient.ACI == uuid.Nil {
|
if recipient.ACI == uuid.Nil {
|
||||||
namePtr = ptr.Ptr(s.Main.Config.FormatDisplayname(recipient))
|
name = s.Main.Config.FormatDisplayname(recipient)
|
||||||
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
||||||
} else {
|
} else {
|
||||||
if backupChat == nil {
|
if backupChat == nil {
|
||||||
|
|
@ -453,7 +450,7 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
}
|
}
|
||||||
members.OtherUserID = signalid.MakeUserID(recipient.ACI)
|
members.OtherUserID = signalid.MakeUserID(recipient.ACI)
|
||||||
if recipient.ACI == s.Client.Store.ACI {
|
if recipient.ACI == s.Client.Store.ACI {
|
||||||
namePtr = ptr.Ptr(NoteToSelfName)
|
name = NoteToSelfName
|
||||||
avatar = &bridgev2.Avatar{
|
avatar = &bridgev2.Avatar{
|
||||||
ID: networkid.AvatarID(s.Main.Config.NoteToSelfAvatar),
|
ID: networkid.AvatarID(s.Main.Config.NoteToSelfAvatar),
|
||||||
Remove: len(s.Main.Config.NoteToSelfAvatar) == 0,
|
Remove: len(s.Main.Config.NoteToSelfAvatar) == 0,
|
||||||
|
|
@ -474,14 +471,14 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
return &bridgev2.CreateChatResponse{
|
return &bridgev2.CreateChatResponse{
|
||||||
PortalKey: s.makeDMPortalKey(serviceID),
|
PortalKey: s.makeDMPortalKey(serviceID),
|
||||||
PortalInfo: &bridgev2.ChatInfo{
|
PortalInfo: &bridgev2.ChatInfo{
|
||||||
Name: namePtr,
|
Name: &name,
|
||||||
Avatar: avatar,
|
Avatar: avatar,
|
||||||
Topic: &topic,
|
Topic: &topic,
|
||||||
Members: members,
|
Members: members,
|
||||||
Type: ptr.Ptr(database.RoomTypeDM),
|
Type: ptr.Ptr(database.RoomTypeDM),
|
||||||
|
|
||||||
MessageRequest: ptr.Ptr(recipient.ACI != uuid.Nil && recipient.ProbablyMessageRequest()),
|
|
||||||
CanBackfill: backupChat != nil,
|
CanBackfill: backupChat != nil,
|
||||||
|
|
||||||
ExtraUpdates: updatePortalSyncMeta,
|
ExtraUpdates: updatePortalSyncMeta,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,19 +32,10 @@ import (
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SignalClient) stopChatSync() {
|
func (s *SignalClient) syncChats(ctx context.Context) {
|
||||||
if cancel := s.cancelChatSync.Swap(nil); cancel != nil {
|
|
||||||
(*cancel)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc) {
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if s.UserLogin.Metadata.(*signalid.UserLoginMetadata).ChatsSynced {
|
if s.UserLogin.Metadata.(*signalid.UserLoginMetadata).ChatsSynced {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Client.Store.EphemeralBackupKey != nil {
|
if s.Client.Store.EphemeralBackupKey != nil {
|
||||||
zerolog.Ctx(ctx).Info().Msg("Fetching transfer archive before syncing chats")
|
zerolog.Ctx(ctx).Info().Msg("Fetching transfer archive before syncing chats")
|
||||||
meta, err := s.Client.WaitForTransfer(ctx)
|
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")
|
zerolog.Ctx(ctx).Info().Int("chat_count", len(chats)).Msg("Fetched chats to sync from database")
|
||||||
for _, chat := range chats {
|
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)
|
recipient, err := s.Client.Store.BackupStore.GetBackupRecipient(ctx, chat.RecipientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get recipient for chat")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to get recipient for chat")
|
||||||
continue
|
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{
|
resyncEvt := &simplevent.ChatResync{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package connector
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
@ -27,7 +26,6 @@ import (
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
"maunium.net/go/mautrix/bridgev2/status"
|
"maunium.net/go/mautrix/bridgev2/status"
|
||||||
"maunium.net/go/mautrix/event"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
||||||
|
|
@ -41,13 +39,11 @@ type SignalClient struct {
|
||||||
Ghost *bridgev2.Ghost
|
Ghost *bridgev2.Ghost
|
||||||
|
|
||||||
queueEmptyWaiter *exsync.Event
|
queueEmptyWaiter *exsync.Event
|
||||||
cancelChatSync atomic.Pointer[context.CancelFunc]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ bridgev2.NetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.NetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.BackgroundSyncingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.BackgroundSyncingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.StickerImportingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var pushCfg = &bridgev2.PushConfig{
|
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) {
|
func (s *SignalClient) LogoutRemote(ctx context.Context) {
|
||||||
if s.Client == nil {
|
if s.Client == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.stopChatSync()
|
err := s.Client.StopReceiveLoops()
|
||||||
err := s.Client.Unlink(ctx)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to unlink device")
|
|
||||||
}
|
|
||||||
err = s.Client.StopReceiveLoops()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to stop receive loops for logout")
|
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)
|
err = s.Main.Store.DeleteDevice(context.TODO(), &s.Client.Store.DeviceData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete device from store")
|
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:
|
case signalmeow.SignalConnectionEventLoggedOut:
|
||||||
s.stopChatSync()
|
|
||||||
s.UserLogin.Log.Debug().Msg("Sending BadCredentials BridgeState")
|
s.UserLogin.Log.Debug().Msg("Sending BadCredentials BridgeState")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: "You have been logged out of Signal, please reconnect"})
|
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 {
|
if s.Client == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.stopChatSync()
|
|
||||||
err := s.Client.StopReceiveLoops()
|
err := s.Client.StopReceiveLoops()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.UserLogin.Log.Err(err).Msg("Failed to stop receive loops")
|
s.UserLogin.Log.Err(err).Msg("Failed to stop receive loops")
|
||||||
|
|
@ -296,20 +281,28 @@ func (s *SignalClient) Disconnect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) postLoginConnect() {
|
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)
|
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) {
|
func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, doSync bool) {
|
||||||
if ctx.Err() != nil {
|
err := s.Client.RegisterCapabilities(ctx)
|
||||||
zerolog.Ctx(ctx).Debug().
|
if err != nil {
|
||||||
Int("retry_count", retryCount).
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to register capabilities")
|
||||||
AnErr("ctx_err", ctx.Err()).
|
} else {
|
||||||
Msg("Context is canceled, not trying to connect")
|
zerolog.Ctx(ctx).Debug().Msg("Successfully registered capabilities")
|
||||||
return
|
|
||||||
}
|
|
||||||
if retryCount == 0 {
|
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting})
|
|
||||||
}
|
}
|
||||||
ch, err := s.Client.StartReceiveLoops(ctx)
|
ch, err := s.Client.StartReceiveLoops(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -320,39 +313,12 @@ func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, noLoginSy
|
||||||
retryInSeconds = 150
|
retryInSeconds = 150
|
||||||
}
|
}
|
||||||
zerolog.Ctx(ctx).Debug().Int("retry_in_seconds", retryInSeconds).Msg("Sleeping and retrying connection")
|
zerolog.Ctx(ctx).Debug().Int("retry_in_seconds", retryInSeconds).Msg("Sleeping and retrying connection")
|
||||||
select {
|
time.Sleep(time.Duration(retryInSeconds) * time.Second)
|
||||||
case <-time.After(time.Duration(retryInSeconds) * time.Second):
|
s.tryConnect(ctx, retryCount+1, doSync)
|
||||||
case <-ctx.Done():
|
} else {
|
||||||
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)
|
go s.bridgeStateLoop(ch)
|
||||||
if noLoginSync {
|
if doSync {
|
||||||
go s.syncChats(syncCtx, cancel)
|
go s.syncChats(ctx)
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
NoteToSelfAvatar id.ContentURIString `yaml:"note_to_self_avatar"`
|
||||||
LocationFormat string `yaml:"location_format"`
|
LocationFormat string `yaml:"location_format"`
|
||||||
DisappearViewOnce bool `yaml:"disappear_view_once"`
|
DisappearViewOnce bool `yaml:"disappear_view_once"`
|
||||||
ExtEvPolls bool `yaml:"extev_polls"`
|
|
||||||
|
|
||||||
displaynameTemplate *template.Template `yaml:"-"`
|
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, "note_to_self_avatar")
|
||||||
helper.Copy(up.Str, "location_format")
|
helper.Copy(up.Str, "location_format")
|
||||||
helper.Copy(up.Bool, "disappear_view_once")
|
helper.Copy(up.Bool, "disappear_view_once")
|
||||||
helper.Copy(up.Bool, "extev_polls")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,15 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mau.fi/util/dbutil"
|
"go.mau.fi/util/dbutil"
|
||||||
"go.mau.fi/util/exhttp"
|
|
||||||
"go.mau.fi/util/exsync"
|
"go.mau.fi/util/exsync"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/bridgev2/commands"
|
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/msgconv"
|
"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"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/store"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/store"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SignalConnector struct {
|
type SignalConnector struct {
|
||||||
|
|
@ -67,8 +63,6 @@ func (s *SignalConnector) Init(bridge *bridgev2.Bridge) {
|
||||||
s.MsgConv = msgconv.NewMessageConverter(bridge)
|
s.MsgConv = msgconv.NewMessageConverter(bridge)
|
||||||
s.MsgConv.LocationFormat = s.Config.LocationFormat
|
s.MsgConv.LocationFormat = s.Config.LocationFormat
|
||||||
s.MsgConv.DisappearViewOnce = s.Config.DisappearViewOnce
|
s.MsgConv.DisappearViewOnce = s.Config.DisappearViewOnce
|
||||||
s.MsgConv.ExtEvPolls = s.Config.ExtEvPolls
|
|
||||||
bridge.Commands.(*commands.Processor).AddHandlers(CmdDiscardSenderKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) SetMaxFileSize(maxSize int64) {
|
func (s *SignalConnector) SetMaxFileSize(maxSize int64) {
|
||||||
|
|
@ -76,7 +70,6 @@ func (s *SignalConnector) SetMaxFileSize(maxSize int64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) Start(ctx context.Context) error {
|
func (s *SignalConnector) Start(ctx context.Context) error {
|
||||||
s.ResetHTTPTransport()
|
|
||||||
err := s.Store.Upgrade(ctx)
|
err := s.Store.Upgrade(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bridgev2.DBUpgradeError{Err: err, Section: "signalmeow"}
|
return bridgev2.DBUpgradeError{Err: err, Section: "signalmeow"}
|
||||||
|
|
@ -84,26 +77,6 @@ func (s *SignalConnector) Start(ctx context.Context) error {
|
||||||
return nil
|
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 {
|
func (s *SignalConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error {
|
||||||
aci, err := uuid.Parse(string(login.ID))
|
aci, err := uuid.Parse(string(login.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -120,13 +93,13 @@ func (s *SignalConnector) LoadUserLogin(ctx context.Context, login *bridgev2.Use
|
||||||
queueEmptyWaiter: exsync.NewEvent(),
|
queueEmptyWaiter: exsync.NewEvent(),
|
||||||
}
|
}
|
||||||
if device != nil {
|
if device != nil {
|
||||||
sc.Client = signalmeow.NewClient(
|
sc.Client = &signalmeow.Client{
|
||||||
device,
|
Store: device,
|
||||||
sc.UserLogin.Log.With().Str("component", "signalmeow").Logger(),
|
Log: sc.UserLogin.Log.With().Str("component", "signalmeow").Logger(),
|
||||||
sc.handleSignalEvent,
|
EventHandler: sc.handleSignalEvent,
|
||||||
)
|
|
||||||
sc.Client.SyncContactsOnConnect = s.Config.SyncContactsOnStartup &&
|
SyncContactsOnConnect: s.Config.SyncContactsOnStartup,
|
||||||
time.Since(login.Metadata.(*signalid.UserLoginMetadata).LastContactSync.Time) > 3*24*time.Hour
|
}
|
||||||
}
|
}
|
||||||
login.Client = sc
|
login.Client = sc
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"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)
|
return nil, fmt.Errorf("failed to parse direct media id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawDataResp []byte
|
|
||||||
switch info := info.(type) {
|
switch info := info.(type) {
|
||||||
case *signalid.DirectMediaAttachment:
|
case *signalid.DirectMediaAttachment:
|
||||||
log.Info().
|
log.Info().
|
||||||
|
|
@ -42,15 +41,18 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
||||||
Uint32("size", info.Size).
|
Uint32("size", info.Size).
|
||||||
Msg("Direct downloading attachment")
|
Msg("Direct downloading attachment")
|
||||||
|
|
||||||
return &mediaproxy.GetMediaResponseFile{
|
return &mediaproxy.GetMediaResponseCallback{
|
||||||
Callback: func(w *os.File) (*mediaproxy.FileMeta, error) {
|
Callback: func(w io.Writer) (int64, error) {
|
||||||
_, err := signalmeow.DownloadAttachment(
|
data, err := signalmeow.DownloadAttachment(
|
||||||
ctx, info.CDNID, info.CDNKey, info.CDNNumber, info.Key, info.Digest, info.PlaintextDigest, info.Size, w,
|
ctx, info.CDNID, info.CDNKey, info.CDNNumber, info.Key, info.Digest, info.PlaintextDigest, info.Size,
|
||||||
)
|
)
|
||||||
if err != nil {
|
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
|
}, nil
|
||||||
case *signalid.DirectMediaGroupAvatar:
|
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)
|
return nil, fmt.Errorf("failed to to get group master key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rawDataResp, err = client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
return &mediaproxy.GetMediaResponseCallback{
|
||||||
|
Callback: func(w io.Writer) (int64, error) {
|
||||||
|
data, err := client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Direct download failed")
|
log.Err(err).Msg("Direct download failed")
|
||||||
return nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(data)
|
||||||
|
return int64(len(data)), err
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
case *signalid.DirectMediaProfileAvatar:
|
case *signalid.DirectMediaProfileAvatar:
|
||||||
log.Info().
|
log.Info().
|
||||||
Stringer("user_id", info.UserID).
|
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")
|
return nil, fmt.Errorf("profile key not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawDataResp, err = client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
return &mediaproxy.GetMediaResponseCallback{
|
||||||
|
Callback: func(w io.Writer) (int64, error) {
|
||||||
|
data, err := client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Direct download failed")
|
log.Err(err).Msg("Direct download failed")
|
||||||
return nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
case *signalid.DirectMediaSticker:
|
|
||||||
log.Info().
|
|
||||||
Hex("pack_id", info.PackID).
|
|
||||||
Uint32("sticker_id", info.StickerID).
|
|
||||||
Msg("Direct downloading sticker")
|
|
||||||
|
|
||||||
rawDataResp, err = signalmeow.DownloadStickerPackItem(ctx, info.PackID, info.PackKey, info.StickerID)
|
_, err = w.Write(data)
|
||||||
if err != nil {
|
return int64(len(data)), err
|
||||||
log.Err(err).Msg("Direct download failed")
|
},
|
||||||
return nil, err
|
}, nil
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("no downloader for direct media type: %T", info)
|
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'
|
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?
|
# Should view-once messages disappear shortly after sending a read receipt on Matrix?
|
||||||
disappear_view_once: false
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve group by id: %w", err)
|
return nil, fmt.Errorf("failed to retrieve group by id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -53,9 +52,6 @@ var (
|
||||||
_ bridgev2.RoomTopicHandlingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.RoomTopicHandlingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.ChatViewingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.ChatViewingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.DisappearTimerChangingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.DisappearTimerChangingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.DeleteChatHandlingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
_ bridgev2.PollHandlingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
_ bridgev2.MessageRequestAcceptingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.PortalID, content *signalpb.Content) error {
|
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("failed_to_send_to_count", len(result.FailedToSendTo)).
|
||||||
Int("successfully_sent_to_count", len(result.SuccessfullySentTo)).
|
Int("successfully_sent_to_count", len(result.SuccessfullySentTo)).
|
||||||
Logger()
|
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 {
|
if len(result.SuccessfullySentTo) == 0 && len(result.FailedToSendTo) == 0 {
|
||||||
log.Debug().Msg("No successes or failures - Probably sent to myself")
|
log.Debug().Msg("No successes or failures - Probably sent to myself")
|
||||||
} else if len(result.SuccessfullySentTo) == 0 {
|
} else if len(result.SuccessfullySentTo) == 0 {
|
||||||
log.Error().Msg("Failed to send event to all members of Signal group")
|
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")
|
return errors.New("failed to send to any members of Signal group")
|
||||||
|
|
||||||
} else if len(result.SuccessfullySentTo) < totalRecipients {
|
} 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 {
|
} else {
|
||||||
log.Debug().Msg("Sent event to all members of Signal group")
|
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) {
|
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(
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
msgID := signalid.MakeMessageID(s.Client.Store.ACI, ts)
|
||||||
msg.AddPendingToIgnore(networkid.TransactionID(msgID))
|
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 {
|
if err != nil {
|
||||||
return nil, bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
return nil, bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +126,9 @@ func (s *SignalClient) doSendMessage(
|
||||||
ID: msgID,
|
ID: msgID,
|
||||||
SenderID: signalid.MakeUserID(s.Client.Store.ACI),
|
SenderID: signalid.MakeUserID(s.Client.Store.ACI),
|
||||||
Timestamp: time.UnixMilli(int64(ts)),
|
Timestamp: time.UnixMilli(int64(ts)),
|
||||||
Metadata: meta,
|
Metadata: &signalid.MessageMetadata{
|
||||||
|
ContainsAttachments: len(converted.Attachments) > 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return &bridgev2.MatrixMessageResponse{
|
return &bridgev2.MatrixMessageResponse{
|
||||||
DB: dbMsg,
|
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)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
err = s.sendMessage(ctx, msg.Portal.ID, &signalpb.Content{EditMessage: &signalpb.EditMessage{
|
||||||
converted.Timestamp = &ts
|
|
||||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapEditMessage(&signalpb.EditMessage{
|
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
DataMessage: converted,
|
DataMessage: converted,
|
||||||
}))
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
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)
|
return nil, fmt.Errorf("failed to parse target message ID: %w", err)
|
||||||
}
|
}
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
wrappedContent := &signalpb.Content{
|
||||||
|
DataMessage: &signalpb.DataMessage{
|
||||||
Timestamp: proto.Uint64(ts),
|
Timestamp: proto.Uint64(ts),
|
||||||
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
||||||
Reaction: &signalpb.DataMessage_Reaction{
|
Reaction: &signalpb.DataMessage_Reaction{
|
||||||
Emoji: proto.String(msg.PreHandleResp.Emoji),
|
Emoji: proto.String(msg.PreHandleResp.Emoji),
|
||||||
Remove: proto.Bool(false),
|
Remove: proto.Bool(false),
|
||||||
TargetAuthorAciBinary: targetAuthorACI[:],
|
TargetAuthorAci: proto.String(targetAuthorACI.String()),
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
},
|
},
|
||||||
}))
|
},
|
||||||
|
}
|
||||||
|
err = s.sendMessage(ctx, msg.Portal.ID, wrappedContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
return fmt.Errorf("failed to parse target message ID: %w", err)
|
||||||
}
|
}
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
wrappedContent := &signalpb.Content{
|
||||||
|
DataMessage: &signalpb.DataMessage{
|
||||||
Timestamp: proto.Uint64(ts),
|
Timestamp: proto.Uint64(ts),
|
||||||
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
RequiredProtocolVersion: proto.Uint32(uint32(signalpb.DataMessage_REACTIONS)),
|
||||||
Reaction: &signalpb.DataMessage_Reaction{
|
Reaction: &signalpb.DataMessage_Reaction{
|
||||||
Emoji: proto.String(msg.TargetReaction.Emoji),
|
Emoji: proto.String(msg.TargetReaction.Emoji),
|
||||||
Remove: proto.Bool(true),
|
Remove: proto.Bool(true),
|
||||||
TargetAuthorAciBinary: targetAuthorACI[:],
|
TargetAuthorAci: proto.String(targetAuthorACI.String()),
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
},
|
},
|
||||||
}))
|
},
|
||||||
|
}
|
||||||
|
err = s.sendMessage(ctx, msg.Portal.ID, wrappedContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -246,12 +234,15 @@ func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridg
|
||||||
return fmt.Errorf("cannot delete other people's messages")
|
return fmt.Errorf("cannot delete other people's messages")
|
||||||
}
|
}
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
wrappedContent := &signalpb.Content{
|
||||||
|
DataMessage: &signalpb.DataMessage{
|
||||||
Timestamp: proto.Uint64(ts),
|
Timestamp: proto.Uint64(ts),
|
||||||
Delete: &signalpb.DataMessage_Delete{
|
Delete: &signalpb.DataMessage_Delete{
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
},
|
},
|
||||||
}))
|
},
|
||||||
|
}
|
||||||
|
err = s.sendMessage(ctx, msg.Portal.ID, wrappedContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if !userID.IsEmpty() && userID.Type == libsignalgo.ServiceIDTypeACI {
|
||||||
|
typingMessage := signalmeow.TypingMessage(typing.IsTyping)
|
||||||
result := s.Client.SendMessage(ctx, userID, typingMessage)
|
result := s.Client.SendMessage(ctx, userID, typingMessage)
|
||||||
if !result.WasSuccessful {
|
if !result.WasSuccessful {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
} else if groupID != "" {
|
|
||||||
_, err = s.Client.SendGroupMessage(ctx, groupID, typingMessage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
return false, fmt.Errorf("failed to download avatar: %w", err)
|
||||||
}
|
}
|
||||||
avatarHash = sha256.Sum256(data)
|
avatarHash = sha256.Sum256(data)
|
||||||
avatarPath, err = s.Client.UploadGroupAvatar(ctx, data, groupID, "")
|
avatarPath, err = s.Client.UploadGroupAvatar(ctx, data, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to reupload avatar: %w", err)
|
return false, fmt.Errorf("failed to reupload avatar: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -397,24 +385,22 @@ func (s *SignalClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) {
|
func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (bool, error) {
|
||||||
if msg.Type.IsSelf && msg.OrigSender != nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var targetIntent bridgev2.MatrixAPI
|
var targetIntent bridgev2.MatrixAPI
|
||||||
var targetSignalID libsignalgo.ServiceID
|
var targetSignalID libsignalgo.ServiceID
|
||||||
var err error
|
var err error
|
||||||
if msg.Portal.RoomType == database.RoomTypeDM {
|
if msg.Portal.RoomType == database.RoomTypeDM {
|
||||||
|
//TODO: this probably needs to revert some changes and clean up the portal on leaves
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case bridgev2.Invite:
|
case bridgev2.Invite:
|
||||||
return nil, fmt.Errorf("cannot invite additional user to dm")
|
return false, fmt.Errorf("cannot invite additional user to dm")
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetSignalID, err = signalid.ParseGhostOrUserLoginID(msg.Target)
|
targetSignalID, err = signalid.ParseGhostOrUserLoginID(msg.Target)
|
||||||
if err != nil {
|
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) {
|
switch target := msg.Target.(type) {
|
||||||
case *bridgev2.Ghost:
|
case *bridgev2.Ghost:
|
||||||
|
|
@ -424,12 +410,12 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
if targetIntent == nil {
|
if targetIntent == nil {
|
||||||
ghost, err := s.Main.Bridge.GetGhostByID(ctx, networkid.UserID(target.ID))
|
ghost, err := s.Main.Bridge.GetGhostByID(ctx, networkid.UserID(target.ID))
|
||||||
if err != nil {
|
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
|
targetIntent = ghost.Intent
|
||||||
}
|
}
|
||||||
default:
|
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().
|
log := zerolog.Ctx(ctx).With().
|
||||||
Str("From Membership", string(msg.Type.From)).
|
Str("From Membership", string(msg.Type.From)).
|
||||||
|
|
@ -449,7 +435,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case bridgev2.AcceptInvite:
|
case bridgev2.AcceptInvite:
|
||||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
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{{
|
gc.PromotePendingMembers = []*signalmeow.PromotePendingMember{{
|
||||||
ACI: targetSignalID.UUID,
|
ACI: targetSignalID.UUID,
|
||||||
|
|
@ -458,7 +444,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
gc.DeletePendingMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
gc.DeletePendingMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
||||||
case bridgev2.Leave, bridgev2.Kick:
|
case bridgev2.Leave, bridgev2.Kick:
|
||||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
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}
|
gc.DeleteMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||||
case bridgev2.Invite:
|
case bridgev2.Invite:
|
||||||
|
|
@ -494,7 +480,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
// }}
|
// }}
|
||||||
case bridgev2.AcceptKnock:
|
case bridgev2.AcceptKnock:
|
||||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
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{{
|
gc.PromoteRequestingMembers = []*signalmeow.RoleMember{{
|
||||||
ACI: targetSignalID.UUID,
|
ACI: targetSignalID.UUID,
|
||||||
|
|
@ -502,7 +488,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
}}
|
}}
|
||||||
case bridgev2.RetractKnock, bridgev2.RejectKnock:
|
case bridgev2.RetractKnock, bridgev2.RejectKnock:
|
||||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
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}
|
gc.DeleteRequestingMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||||
case bridgev2.BanKnocked, bridgev2.BanInvited, bridgev2.BanJoined, bridgev2.BanLeft:
|
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 {
|
switch msg.Type {
|
||||||
case bridgev2.BanJoined:
|
case bridgev2.BanJoined:
|
||||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
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}
|
gc.DeleteMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||||
case bridgev2.BanInvited:
|
case bridgev2.BanInvited:
|
||||||
gc.DeletePendingMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
gc.DeletePendingMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
||||||
case bridgev2.BanKnocked:
|
case bridgev2.BanKnocked:
|
||||||
if targetSignalID.Type != libsignalgo.ServiceIDTypeACI {
|
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}
|
gc.DeleteRequestingMembers = []*uuid.UUID{&targetSignalID.UUID}
|
||||||
}
|
}
|
||||||
case bridgev2.Unban:
|
case bridgev2.Unban:
|
||||||
gc.DeleteBannedMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
gc.DeleteBannedMembers = []*libsignalgo.ServiceID{&targetSignalID}
|
||||||
default:
|
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)
|
_, groupID, err := signalid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil || groupID == "" {
|
if err != nil || groupID == "" {
|
||||||
return nil, err
|
return false, err
|
||||||
}
|
}
|
||||||
gc.Revision = msg.Portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
gc.Revision = msg.Portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
||||||
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return false, err
|
||||||
}
|
}
|
||||||
if msg.Type == bridgev2.Invite && targetSignalID.Type != libsignalgo.ServiceIDTypePNI {
|
if msg.Type == bridgev2.Invite && targetSignalID.Type != libsignalgo.ServiceIDTypePNI {
|
||||||
err = targetIntent.EnsureJoined(ctx, msg.Portal.MXID)
|
err = targetIntent.EnsureJoined(ctx, msg.Portal.MXID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg.Portal.Metadata.(*signalid.PortalMetadata).Revision = revision
|
msg.Portal.Metadata.(*signalid.PortalMetadata).Revision = revision
|
||||||
return nil, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func plToRole(pl int) signalmeow.GroupMemberRole {
|
func plToRole(pl int) signalmeow.GroupMemberRole {
|
||||||
|
|
@ -679,11 +666,13 @@ func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *b
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||||
res := s.Client.SendMessage(ctx, userID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
res := s.Client.SendMessage(ctx, userID, &signalpb.Content{
|
||||||
|
DataMessage: &signalpb.DataMessage{
|
||||||
Timestamp: ptr.Ptr(ts),
|
Timestamp: ptr.Ptr(ts),
|
||||||
Flags: ptr.Ptr(uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE)),
|
Flags: ptr.Ptr(uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE)),
|
||||||
ExpireTimer: ptr.Ptr(uint32(msg.Content.Timer.Seconds())),
|
ExpireTimer: ptr.Ptr(uint32(msg.Content.Timer.Seconds())),
|
||||||
}))
|
},
|
||||||
|
})
|
||||||
if !res.WasSuccessful {
|
if !res.WasSuccessful {
|
||||||
return false, res.Error
|
return false, res.Error
|
||||||
}
|
}
|
||||||
|
|
@ -691,222 +680,3 @@ func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *b
|
||||||
return true, nil
|
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/google/uuid"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exzerolog"
|
"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"
|
||||||
"maunium.net/go/mautrix/bridgev2/database"
|
"maunium.net/go/mautrix/bridgev2/database"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
|
|
@ -37,7 +35,6 @@ import (
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/events"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/events"
|
||||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||||
|
|
@ -55,8 +52,6 @@ func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
|
||||||
return s.handleSignalReadSelf(evt)
|
return s.handleSignalReadSelf(evt)
|
||||||
case *events.DeleteForMe:
|
case *events.DeleteForMe:
|
||||||
return s.handleSignalDeleteForMe(evt)
|
return s.handleSignalDeleteForMe(evt)
|
||||||
case *events.MessageRequestResponse:
|
|
||||||
return s.handleSignalMessageRequestResponse(evt)
|
|
||||||
case *events.Call:
|
case *events.Call:
|
||||||
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapCallEvent(evt)).Success
|
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapCallEvent(evt)).Success
|
||||||
case *events.ContactList:
|
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() {
|
if userID, _, _ := signalid.ParsePortalID(portal.ID); !userID.IsEmpty() {
|
||||||
content.MsgType = event.MsgText
|
content.MsgType = event.MsgText
|
||||||
}
|
}
|
||||||
content.BeeperActionMessage = &event.BeeperActionMessage{
|
|
||||||
Type: event.BeeperActionMessageCall,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
content.Body = "Call ended"
|
content.Body = "Call ended"
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +167,7 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
||||||
case *signalpb.DataMessage:
|
case *signalpb.DataMessage:
|
||||||
switch {
|
switch {
|
||||||
case innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
|
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.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
|
||||||
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
||||||
return bridgev2.RemoteEventMessage
|
return bridgev2.RemoteEventMessage
|
||||||
|
|
@ -184,7 +176,7 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
||||||
return bridgev2.RemoteEventReactionRemove
|
return bridgev2.RemoteEventReactionRemove
|
||||||
}
|
}
|
||||||
return bridgev2.RemoteEventReaction
|
return bridgev2.RemoteEventReaction
|
||||||
case innerEvt.Delete != nil, innerEvt.AdminDelete != nil:
|
case innerEvt.Delete != nil:
|
||||||
return bridgev2.RemoteEventMessageRemove
|
return bridgev2.RemoteEventMessageRemove
|
||||||
case innerEvt.GetGroupV2().GetGroupChange() != nil:
|
case innerEvt.GetGroupV2().GetGroupChange() != nil:
|
||||||
return bridgev2.RemoteEventChatInfoChange
|
return bridgev2.RemoteEventChatInfoChange
|
||||||
|
|
@ -293,21 +285,16 @@ func (evt *Bv2ChatEvent) GetTimestamp() time.Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Bv2ChatEvent) GetTargetMessage() networkid.MessageID {
|
func (evt *Bv2ChatEvent) GetTargetMessage() networkid.MessageID {
|
||||||
var targetAuthorACI uuid.UUID
|
var targetAuthorACI string
|
||||||
var targetSentTS uint64
|
var targetSentTS uint64
|
||||||
switch innerEvt := evt.Event.(type) {
|
switch innerEvt := evt.Event.(type) {
|
||||||
case *signalpb.DataMessage:
|
case *signalpb.DataMessage:
|
||||||
switch {
|
switch {
|
||||||
case innerEvt.Reaction != nil:
|
case innerEvt.Reaction != nil:
|
||||||
targetAuthorACI, _ = signalmeow.ParseStringOrBinaryUUID(innerEvt.Reaction.GetTargetAuthorAci(), innerEvt.Reaction.GetTargetAuthorAciBinary())
|
targetAuthorACI = innerEvt.Reaction.GetTargetAuthorAci()
|
||||||
targetSentTS = innerEvt.Reaction.GetTargetSentTimestamp()
|
targetSentTS = innerEvt.Reaction.GetTargetSentTimestamp()
|
||||||
case innerEvt.Delete != nil:
|
case innerEvt.Delete != nil:
|
||||||
targetSentTS = innerEvt.Delete.GetTargetSentTimestamp()
|
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:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -316,10 +303,11 @@ func (evt *Bv2ChatEvent) GetTargetMessage() networkid.MessageID {
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if targetAuthorACI == uuid.Nil {
|
targetAuthorUUID := evt.Info.Sender
|
||||||
targetAuthorACI = 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) {
|
func (evt *Bv2ChatEvent) GetReactionEmoji() (string, networkid.EmojiID) {
|
||||||
|
|
@ -339,7 +327,7 @@ func (evt *Bv2ChatEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Po
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("ConvertMessage() called for non-DataMessage event")
|
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 != "" {
|
if converted.Disappear.Type != "" {
|
||||||
evtTS := evt.GetTimestamp()
|
evtTS := evt.GetTimestamp()
|
||||||
if !dataMsg.GetIsViewOnce() {
|
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")
|
return nil, fmt.Errorf("ConvertEdit() called for non-EditMessage event")
|
||||||
}
|
}
|
||||||
// TODO tell converter about existing parts to avoid reupload?
|
// 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?
|
// TODO can anything other than the text be edited?
|
||||||
editPart := converted.Parts[len(converted.Parts)-1].ToEditPart(existing[len(existing)-1])
|
editPart := converted.Parts[len(converted.Parts)-1].ToEditPart(existing[len(existing)-1])
|
||||||
editPart.Part.EditCount++
|
editPart.Part.EditCount++
|
||||||
|
|
@ -426,7 +414,7 @@ func (b *Bv2Receipt) GetReadUpTo() time.Time {
|
||||||
return 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 {
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
|
|
@ -472,7 +460,7 @@ func (s *SignalClient) handleSignalReceipt(evt *events.Receipt) bool {
|
||||||
Stringer("sender_id", evt.Sender).
|
Stringer("sender_id", evt.Sender).
|
||||||
Stringer("receipt_type", evt.Content.GetType()).
|
Stringer("receipt_type", evt.Content.GetType()).
|
||||||
Logger()
|
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) {
|
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))
|
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().
|
log := s.UserLogin.Log.With().
|
||||||
Str("action", "handle signal read self").
|
Str("action", "handle signal read self").
|
||||||
Logger()
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -504,13 +492,6 @@ func (s *SignalClient) conversationIDToPortalKey(ctx context.Context, cid *signa
|
||||||
return networkid.PortalKey{}, false
|
return networkid.PortalKey{}, false
|
||||||
}
|
}
|
||||||
return s.makeDMPortalKey(serviceID), true
|
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:
|
case *signalpb.ConversationIdentifier_ThreadGroupId:
|
||||||
if len(ident.ThreadGroupId) != libsignalgo.GroupIdentifierLength {
|
if len(ident.ThreadGroupId) != libsignalgo.GroupIdentifierLength {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
|
@ -549,22 +530,6 @@ func (s *SignalClient) addressableMessageToID(ctx context.Context, portalKey net
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return signalid.MakeMessageID(serviceID.UUID, am.GetSentTimestamp())
|
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:
|
case *signalpb.AddressableMessage_AuthorE164:
|
||||||
log.Warn().
|
log.Warn().
|
||||||
Object("portal_key", portalKey).
|
Object("portal_key", portalKey).
|
||||||
|
|
@ -655,45 +620,13 @@ func (s *SignalClient) handleSignalDeleteForMe(evt *events.DeleteForMe) bool {
|
||||||
return true
|
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) {
|
func (s *SignalClient) handleSignalACIFound(evt *events.ACIFound) {
|
||||||
log := s.UserLogin.Log.With().
|
log := s.UserLogin.Log.With().
|
||||||
Str("action", "handle aci found").
|
Str("action", "handle aci found").
|
||||||
Stringer("aci", evt.ACI).
|
Stringer("aci", evt.ACI).
|
||||||
Stringer("pni", evt.PNI).
|
Stringer("pni", evt.PNI).
|
||||||
Logger()
|
Logger()
|
||||||
ctx := log.WithContext(s.Main.Bridge.BackgroundCtx)
|
ctx := log.WithContext(context.TODO())
|
||||||
pniPortalKey := s.makeDMPortalKey(evt.PNI)
|
pniPortalKey := s.makeDMPortalKey(evt.PNI)
|
||||||
aciPortalKey := s.makeDMPortalKey(evt.ACI)
|
aciPortalKey := s.makeDMPortalKey(evt.ACI)
|
||||||
result, portal, err := s.Main.Bridge.ReIDPortal(ctx, pniPortalKey, aciPortalKey)
|
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) {
|
func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
|
||||||
log := s.UserLogin.Log.With().Str("action", "handle contact list").Logger()
|
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 {
|
for _, contact := range evt.Contacts {
|
||||||
if contact.ACI == uuid.Nil {
|
if contact.ACI == uuid.Nil {
|
||||||
continue
|
continue
|
||||||
|
|
@ -741,33 +674,6 @@ func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
|
||||||
if contact.ACI == s.Client.Store.ACI {
|
if contact.ACI == s.Client.Store.ACI {
|
||||||
s.updateRemoteProfile(ctx, true)
|
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
|
cancelChan context.CancelFunc
|
||||||
ProvChan chan signalmeow.ProvisioningResponse
|
ProvChan chan signalmeow.ProvisioningResponse
|
||||||
newQRCount int
|
newQRCount int
|
||||||
|
|
||||||
|
ProvData *store.DeviceData
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ bridgev2.LoginProcessDisplayAndWait = (*QRLogin)(nil)
|
var _ bridgev2.LoginProcessDisplayAndWait = (*QRLogin)(nil)
|
||||||
|
|
@ -75,7 +77,7 @@ func (qr *QRLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
Str("action", "login").
|
Str("action", "login").
|
||||||
Stringer("user_id", qr.User.MXID).
|
Stringer("user_id", qr.User.MXID).
|
||||||
Logger()
|
Logger()
|
||||||
provCtx, cancel := context.WithCancel(log.WithContext(qr.Main.Bridge.BackgroundCtx))
|
provCtx, cancel := context.WithCancel(log.WithContext(context.Background()))
|
||||||
qr.cancelChan = cancel
|
qr.cancelChan = cancel
|
||||||
// Don't use the start context here: the channel will outlive the start request.
|
// Don't use the start context here: the channel will outlive the start request.
|
||||||
qr.ProvChan = signalmeow.PerformProvisioning(
|
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")
|
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 {
|
select {
|
||||||
case resp := <-qr.ProvChan:
|
case resp := <-qr.ProvChan:
|
||||||
if resp.Err != nil {
|
if resp.Err != nil {
|
||||||
|
|
@ -122,7 +132,15 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
qr.cancelChan()
|
qr.cancelChan()
|
||||||
return nil, fmt.Errorf("no signal account ID received")
|
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
|
// 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.
|
// 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()
|
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{
|
ul, err := qr.User.NewLogin(ctx, &database.UserLogin{
|
||||||
ID: signalid.MakeUserLoginID(provData.ACI),
|
ID: newLoginID,
|
||||||
RemoteName: provData.Number,
|
RemoteName: qr.ProvData.Number,
|
||||||
RemoteProfile: status.RemoteProfile{
|
RemoteProfile: status.RemoteProfile{
|
||||||
Phone: provData.Number,
|
Phone: qr.ProvData.Number,
|
||||||
},
|
},
|
||||||
Metadata: &signalid.UserLoginMetadata{},
|
Metadata: &signalid.UserLoginMetadata{},
|
||||||
}, &bridgev2.NewLoginParams{
|
}, &bridgev2.NewLoginParams{
|
||||||
|
|
@ -159,7 +190,7 @@ func (qr *QRLogin) loginComplete(ctx context.Context, provData *store.DeviceData
|
||||||
return &bridgev2.LoginStep{
|
return &bridgev2.LoginStep{
|
||||||
Type: bridgev2.LoginStepTypeComplete,
|
Type: bridgev2.LoginStepTypeComplete,
|
||||||
StepID: LoginStepComplete,
|
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{
|
CompleteParams: &bridgev2.LoginCompleteParams{
|
||||||
UserLoginID: ul.ID,
|
UserLoginID: ul.ID,
|
||||||
UserLogin: ul,
|
UserLogin: ul,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ package libsignalgo
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,22 +42,6 @@ func BytesToBuffer(data []byte) C.SignalBorrowedBuffer {
|
||||||
return buf
|
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 {
|
func EmptyBorrowedBuffer() C.SignalBorrowedBuffer {
|
||||||
return C.SignalBorrowedBuffer{}
|
return C.SignalBorrowedBuffer{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package libsignalgo
|
package libsignalgo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -lsignal_ffi -ldl -lm -lz -lstdc++
|
#cgo LDFLAGS: -lsignal_ffi -ldl -lm -lz
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
|
||||||
|
|
@ -39,17 +39,3 @@ func CopySignalOwnedBufferToBytes(buffer C.SignalOwnedBuffer) (b []byte) {
|
||||||
C.signal_free_buffer(buffer.base, buffer.length)
|
C.signal_free_buffer(buffer.base, buffer.length)
|
||||||
return
|
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 "C"
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DecryptionErrorMessage struct {
|
type DecryptionErrorMessage struct {
|
||||||
|
|
@ -48,7 +49,7 @@ func DeserializeDecryptionErrorMessage(messageBytes []byte) (*DecryptionErrorMes
|
||||||
return wrapDecryptionErrorMessage(dem.raw), nil
|
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
|
var dem C.SignalMutPointerDecryptionErrorMessage
|
||||||
signalFfiError := C.signal_decryption_error_message_for_original_message(
|
signalFfiError := C.signal_decryption_error_message_for_original_message(
|
||||||
&dem,
|
&dem,
|
||||||
|
|
@ -111,14 +112,14 @@ func (dem *DecryptionErrorMessage) Serialize() ([]byte, error) {
|
||||||
return CopySignalOwnedBufferToBytes(serialized), nil
|
return CopySignalOwnedBufferToBytes(serialized), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dem *DecryptionErrorMessage) GetTimestamp() (uint64, error) {
|
func (dem *DecryptionErrorMessage) GetTimestamp() (time.Time, error) {
|
||||||
var ts C.uint64_t
|
var ts C.uint64_t
|
||||||
signalFfiError := C.signal_decryption_error_message_get_timestamp(&ts, dem.constPtr())
|
signalFfiError := C.signal_decryption_error_message_get_timestamp(&ts, dem.constPtr())
|
||||||
runtime.KeepAlive(dem)
|
runtime.KeepAlive(dem)
|
||||||
if signalFfiError != nil {
|
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) {
|
func (dem *DecryptionErrorMessage) GetDeviceID() (uint32, error) {
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,6 @@ import (
|
||||||
|
|
||||||
type ErrorCode int
|
type ErrorCode int
|
||||||
|
|
||||||
func (e ErrorCode) Error() string {
|
|
||||||
return fmt.Sprintf("libsignalgo.ErrorCode(%d)", int(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrorCodeUnknownError ErrorCode = 1
|
ErrorCodeUnknownError ErrorCode = 1
|
||||||
ErrorCodeInvalidState ErrorCode = 2
|
ErrorCodeInvalidState ErrorCode = 2
|
||||||
|
|
@ -38,7 +34,6 @@ const (
|
||||||
ErrorCodeInvalidArgument ErrorCode = 5
|
ErrorCodeInvalidArgument ErrorCode = 5
|
||||||
ErrorCodeInvalidType ErrorCode = 6
|
ErrorCodeInvalidType ErrorCode = 6
|
||||||
ErrorCodeInvalidUtf8String ErrorCode = 7
|
ErrorCodeInvalidUtf8String ErrorCode = 7
|
||||||
ErrorCodeCancelled ErrorCode = 8
|
|
||||||
ErrorCodeProtobufError ErrorCode = 10
|
ErrorCodeProtobufError ErrorCode = 10
|
||||||
ErrorCodeLegacyCiphertextVersion ErrorCode = 21
|
ErrorCodeLegacyCiphertextVersion ErrorCode = 21
|
||||||
ErrorCodeUnknownCiphertextVersion ErrorCode = 22
|
ErrorCodeUnknownCiphertextVersion ErrorCode = 22
|
||||||
|
|
@ -56,61 +51,9 @@ const (
|
||||||
ErrorCodeInvalidRegistrationId ErrorCode = 81
|
ErrorCodeInvalidRegistrationId ErrorCode = 81
|
||||||
ErrorCodeInvalidSession ErrorCode = 82
|
ErrorCodeInvalidSession ErrorCode = 82
|
||||||
ErrorCodeInvalidSenderKeySession ErrorCode = 83
|
ErrorCodeInvalidSenderKeySession ErrorCode = 83
|
||||||
ErrorCodeInvalidProtocolAddress ErrorCode = 84
|
|
||||||
ErrorCodeDuplicatedMessage ErrorCode = 90
|
ErrorCodeDuplicatedMessage ErrorCode = 90
|
||||||
ErrorCodeCallbackError ErrorCode = 100
|
ErrorCodeCallbackError ErrorCode = 100
|
||||||
ErrorCodeVerificationFailure ErrorCode = 110
|
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SignalError struct {
|
type SignalError struct {
|
||||||
|
|
@ -122,10 +65,6 @@ func (e *SignalError) Error() string {
|
||||||
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
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 {
|
func (ctx *CallbackContext) wrapError(signalError *C.SignalFfiError) error {
|
||||||
if signalError == nil {
|
if signalError == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func GroupEncrypt(ctx context.Context, ptext []byte, sender *Address, distributi
|
||||||
signalFfiError := C.signal_group_encrypt_message(
|
signalFfiError := C.signal_group_encrypt_message(
|
||||||
&ciphertextMessage,
|
&ciphertextMessage,
|
||||||
sender.constPtr(),
|
sender.constPtr(),
|
||||||
*(*C.SignalUuid)(unsafe.Pointer(&distributionID)),
|
(*[C.SignalUUID_LEN]C.uchar)(unsafe.Pointer(&distributionID)),
|
||||||
BytesToBuffer(ptext),
|
BytesToBuffer(ptext),
|
||||||
callbackCtx.wrapSenderKeyStore(store))
|
callbackCtx.wrapSenderKeyStore(store))
|
||||||
runtime.KeepAlive(ptext)
|
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) {
|
func GenerateGroupSecretParamsWithRandomness(randomness Randomness) (GroupSecretParams, error) {
|
||||||
var params [C.SignalGROUP_SECRET_PARAMS_LEN]C.uchar
|
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)))
|
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) {
|
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 {
|
type IdentityKeyPair struct {
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,14 @@ package libsignalgo
|
||||||
/*
|
/*
|
||||||
#include "./libsignal-ffi.h"
|
#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_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_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, SignalMutPointerPublicKey *public_keyp, SignalMutPointerProtocolAddress address);
|
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, bool *out, SignalMutPointerProtocolAddress address, SignalMutPointerPublicKey public_key, uint32_t direction);
|
extern int signal_is_trusted_identity_callback(void *store_ctx, const_address *address, const_public_key *public_key, unsigned int direction);
|
||||||
extern void signal_destroy_identity_key_store_callback(void *store_ctx);
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -49,29 +51,22 @@ type IdentityKeyStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_get_identity_key_pair_callback
|
//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 {
|
return wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
||||||
key, err := store.GetIdentityKeyPair(ctx)
|
key, err := store.GetIdentityKeyPair(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if key == nil {
|
if key == nil {
|
||||||
keyp.first.raw = nil
|
*keyp = nil
|
||||||
keyp.second.raw = nil
|
} else {
|
||||||
return nil
|
clone, err := key.privateKey.Clone()
|
||||||
}
|
|
||||||
privClone, err := key.privateKey.Clone()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pubClone, err := key.publicKey.Clone()
|
clone.CancelFinalizer()
|
||||||
if err != nil {
|
*keyp = clone.ptr
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
privClone.CancelFinalizer()
|
|
||||||
pubClone.CancelFinalizer()
|
|
||||||
keyp.first.raw = privClone.ptr
|
|
||||||
keyp.second.raw = pubClone.ptr
|
|
||||||
return err
|
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
|
//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 {
|
func signal_save_identity_key_callback(storeCtx unsafe.Pointer, address *C.const_address, publicKey *C.const_public_key) C.int {
|
||||||
return wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
return wrapStoreCallbackCustomReturn(storeCtx, func(store IdentityKeyStore, ctx context.Context) (int, error) {
|
||||||
publicKeyStruct := PublicKey{ptr: publicKey.raw}
|
publicKeyStruct := PublicKey{ptr: (*C.SignalPublicKey)(unsafe.Pointer(publicKey))}
|
||||||
cloned, err := publicKeyStruct.Clone()
|
cloned, err := publicKeyStruct.Clone()
|
||||||
if err != nil {
|
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()
|
theirServiceID, err := addr.NameServiceID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return -1, err
|
||||||
}
|
}
|
||||||
replaced, err := store.SaveIdentityKey(
|
replaced, err := store.SaveIdentityKey(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
@ -106,21 +101,20 @@ func signal_save_identity_key_callback(storeCtx unsafe.Pointer, out *C.uint8_t,
|
||||||
&IdentityKey{cloned},
|
&IdentityKey{cloned},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return -1, err
|
||||||
}
|
}
|
||||||
if replaced {
|
if replaced {
|
||||||
*out = 1
|
return 1, nil
|
||||||
} else {
|
} else {
|
||||||
*out = 0
|
return 0, nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_get_identity_key_callback
|
//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 {
|
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()
|
theirServiceID, err := addr.NameServiceID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
key, err := store.GetIdentityKey(ctx, theirServiceID)
|
||||||
if err == nil && key != nil {
|
if err == nil && key != nil {
|
||||||
key.publicKey.CancelFinalizer()
|
key.publicKey.CancelFinalizer()
|
||||||
public_keyp.raw = key.publicKey.ptr
|
*public_keyp = key.publicKey.ptr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_is_trusted_identity_callback
|
//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 {
|
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 wrapStoreCallback(storeCtx, func(store IdentityKeyStore, ctx context.Context) error {
|
return wrapStoreCallbackCustomReturn(storeCtx, func(store IdentityKeyStore, ctx context.Context) (int, error) {
|
||||||
addr := &Address{ptr: address.raw}
|
addr := &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}
|
||||||
theirServiceID, err := addr.NameServiceID()
|
theirServiceID, err := addr.NameServiceID()
|
||||||
if err != nil {
|
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 {
|
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 {
|
func (ctx *CallbackContext) wrapIdentityKeyStore(store IdentityKeyStore) C.SignalConstPointerFfiIdentityKeyStoreStruct {
|
||||||
return C.SignalConstPointerFfiIdentityKeyStoreStruct{&C.SignalIdentityKeyStore{
|
return C.SignalConstPointerFfiIdentityKeyStoreStruct{&C.SignalIdentityKeyStore{
|
||||||
ctx: wrapStore(ctx, store),
|
ctx: wrapStore(ctx, store),
|
||||||
get_local_identity_key_pair: C.SignalFfiIdentityKeyStoreGetLocalIdentityKeyPair(C.signal_get_identity_key_pair_callback),
|
get_identity_key_pair: C.SignalGetIdentityKeyPair(C.signal_get_identity_key_pair_callback),
|
||||||
get_local_registration_id: C.SignalFfiIdentityKeyStoreGetLocalRegistrationId(C.signal_get_local_registration_id_callback),
|
get_local_registration_id: C.SignalGetLocalRegistrationId(C.signal_get_local_registration_id_callback),
|
||||||
get_identity_key: C.SignalFfiIdentityKeyStoreGetIdentityKey(C.signal_get_identity_key_callback),
|
save_identity: C.SignalSaveIdentityKey(C.signal_save_identity_key_callback),
|
||||||
save_identity_key: C.SignalFfiIdentityKeyStoreSaveIdentityKey(C.signal_save_identity_key_callback),
|
get_identity: C.SignalGetIdentityKey(C.signal_get_identity_key_callback),
|
||||||
is_trusted_identity: C.SignalFfiIdentityKeyStoreIsTrustedIdentity(C.signal_is_trusted_identity_callback),
|
is_trusted_identity: C.SignalIsTrustedIdentity(C.signal_is_trusted_identity_callback),
|
||||||
destroy: C.SignalFfiIdentityKeyStoreDestroy(C.signal_destroy_identity_key_store_callback),
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,11 @@ package libsignalgo
|
||||||
/*
|
/*
|
||||||
#include "./libsignal-ffi.h"
|
#include "./libsignal-ffi.h"
|
||||||
|
|
||||||
extern int signal_load_kyber_pre_key_callback(void *store_ctx, SignalMutPointerKyberPreKeyRecord *recordp, uint32_t id);
|
typedef const SignalKyberPreKeyRecord const_kyber_pre_key_record;
|
||||||
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 int signal_load_kyber_pre_key_callback(void *store_ctx, SignalKyberPreKeyRecord **recordp, uint32_t id);
|
||||||
extern void signal_destroy_kyber_pre_key_store_callback(void *store_ctx);
|
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 "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -38,21 +39,21 @@ type KyberPreKeyStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_load_kyber_pre_key_callback
|
//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 {
|
return wrapStoreCallback(storeCtx, func(store KyberPreKeyStore, ctx context.Context) error {
|
||||||
key, err := store.LoadKyberPreKey(ctx, uint32(id))
|
key, err := store.LoadKyberPreKey(ctx, uint32(id))
|
||||||
if err == nil && key != nil {
|
if err == nil && key != nil {
|
||||||
key.CancelFinalizer()
|
key.CancelFinalizer()
|
||||||
keyp.raw = key.ptr
|
*keyp = key.ptr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_store_kyber_pre_key_callback
|
//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 {
|
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()
|
cloned, err := record.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
//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 {
|
return wrapStoreCallback(storeCtx, func(store KyberPreKeyStore, ctx context.Context) error {
|
||||||
// TODO use ecPrekeyID and baseKey?
|
err := store.MarkKyberPreKeyUsed(ctx, uint32(id))
|
||||||
return 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 {
|
func (ctx *CallbackContext) wrapKyberPreKeyStore(store KyberPreKeyStore) C.SignalConstPointerFfiKyberPreKeyStoreStruct {
|
||||||
return C.SignalConstPointerFfiKyberPreKeyStoreStruct{&C.SignalKyberPreKeyStore{
|
return C.SignalConstPointerFfiKyberPreKeyStoreStruct{&C.SignalKyberPreKeyStore{
|
||||||
ctx: wrapStore(ctx, store),
|
ctx: wrapStore(ctx, store),
|
||||||
load_kyber_pre_key: C.SignalFfiKyberPreKeyStoreLoadKyberPreKey(C.signal_load_kyber_pre_key_callback),
|
load_kyber_pre_key: C.SignalLoadKyberPreKey(C.signal_load_kyber_pre_key_callback),
|
||||||
store_kyber_pre_key: C.SignalFfiKyberPreKeyStoreStoreKyberPreKey(C.signal_store_kyber_pre_key_callback),
|
store_kyber_pre_key: C.SignalStoreKyberPreKey(C.signal_store_kyber_pre_key_callback),
|
||||||
mark_kyber_pre_key_used: C.SignalFfiKyberPreKeyStoreMarkKyberPreKeyUsed(C.signal_mark_kyber_pre_key_used_callback),
|
mark_kyber_pre_key_used: C.SignalMarkKyberPreKeyUsed(C.signal_mark_kyber_pre_key_used_callback),
|
||||||
destroy: C.SignalFfiKyberPreKeyStoreDestroy(C.signal_destroy_kyber_pre_key_store_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 SignalBackupId_LEN 16
|
||||||
|
|
||||||
#define SignalCallLinkSecretParams_ROOT_KEY_MAX_BYTES_FOR_SHO 16
|
|
||||||
|
|
||||||
#define SignalNUM_AUTH_CRED_ATTRIBUTES 3
|
#define SignalNUM_AUTH_CRED_ATTRIBUTES 3
|
||||||
|
|
||||||
#define SignalNUM_PROFILE_KEY_CRED_ATTRIBUTES 4
|
#define SignalNUM_PROFILE_KEY_CRED_ATTRIBUTES 4
|
||||||
|
|
@ -235,7 +233,6 @@ typedef enum {
|
||||||
SignalErrorCodeChatServiceInactive = 149,
|
SignalErrorCodeChatServiceInactive = 149,
|
||||||
SignalErrorCodeRequestTimedOut = 150,
|
SignalErrorCodeRequestTimedOut = 150,
|
||||||
SignalErrorCodeRateLimitChallenge = 151,
|
SignalErrorCodeRateLimitChallenge = 151,
|
||||||
SignalErrorCodePossibleCaptiveNetwork = 152,
|
|
||||||
SignalErrorCodeSvrDataMissing = 160,
|
SignalErrorCodeSvrDataMissing = 160,
|
||||||
SignalErrorCodeSvrRestoreFailed = 161,
|
SignalErrorCodeSvrRestoreFailed = 161,
|
||||||
SignalErrorCodeSvrRotationMachineTooManySteps = 162,
|
SignalErrorCodeSvrRotationMachineTooManySteps = 162,
|
||||||
|
|
@ -258,10 +255,6 @@ typedef enum {
|
||||||
SignalErrorCodeRegistrationLock = 201,
|
SignalErrorCodeRegistrationLock = 201,
|
||||||
SignalErrorCodeKeyTransparencyError = 210,
|
SignalErrorCodeKeyTransparencyError = 210,
|
||||||
SignalErrorCodeKeyTransparencyVerificationFailed = 211,
|
SignalErrorCodeKeyTransparencyVerificationFailed = 211,
|
||||||
SignalErrorCodeRequestUnauthorized = 220,
|
|
||||||
SignalErrorCodeMismatchedDevices = 221,
|
|
||||||
SignalErrorCodeServiceIdNotFound = 222,
|
|
||||||
SignalErrorCodeUploadTooLarge = 223,
|
|
||||||
} SignalErrorCode;
|
} SignalErrorCode;
|
||||||
|
|
||||||
enum SignalSvr2CredentialsResult {
|
enum SignalSvr2CredentialsResult {
|
||||||
|
|
@ -346,8 +339,6 @@ typedef struct SignalPrivateKey SignalPrivateKey;
|
||||||
*/
|
*/
|
||||||
typedef struct SignalProtocolAddress SignalProtocolAddress;
|
typedef struct SignalProtocolAddress SignalProtocolAddress;
|
||||||
|
|
||||||
typedef struct SignalProvisioningChatConnection SignalProvisioningChatConnection;
|
|
||||||
|
|
||||||
typedef struct SignalPublicKey SignalPublicKey;
|
typedef struct SignalPublicKey SignalPublicKey;
|
||||||
|
|
||||||
typedef struct SignalRegisterAccountRequest SignalRegisterAccountRequest;
|
typedef struct SignalRegisterAccountRequest SignalRegisterAccountRequest;
|
||||||
|
|
@ -512,59 +503,15 @@ typedef struct {
|
||||||
const SignalAuthenticatedChatConnection *raw;
|
const SignalAuthenticatedChatConnection *raw;
|
||||||
} SignalConstPointerAuthenticatedChatConnection;
|
} 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 SignalConnectionInfo SignalChatConnectionInfo;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SignalChatConnectionInfo *raw;
|
SignalChatConnectionInfo *raw;
|
||||||
} SignalMutPointerChatConnectionInfo;
|
} SignalMutPointerChatConnectionInfo;
|
||||||
|
|
||||||
typedef struct {
|
typedef void (*SignalReceivedIncomingMessage)(void *ctx, SignalOwnedBuffer envelope, uint64_t timestamp_millis, SignalServerMessageAck *cleanup);
|
||||||
SignalServerMessageAck *raw;
|
|
||||||
} SignalMutPointerServerMessageAck;
|
|
||||||
|
|
||||||
typedef int (*SignalFfiChatListenerReceivedIncomingMessage)(void *ctx, SignalOwnedBuffer envelope, uint64_t timestamp, SignalMutPointerServerMessageAck ack);
|
typedef void (*SignalReceivedQueueEmpty)(void *ctx);
|
||||||
|
|
||||||
typedef int (*SignalFfiChatListenerReceivedQueueEmpty)(void *ctx);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of a array allocated on the Rust heap for use in C code.
|
* 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 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 {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiChatListenerReceivedIncomingMessage received_incoming_message;
|
SignalReceivedIncomingMessage received_incoming_message;
|
||||||
SignalFfiChatListenerReceivedQueueEmpty received_queue_empty;
|
SignalReceivedQueueEmpty received_queue_empty;
|
||||||
SignalFfiChatListenerReceivedAlerts received_alerts;
|
SignalReceivedAlerts received_alerts;
|
||||||
SignalFfiChatListenerConnectionInterrupted connection_interrupted;
|
SignalConnectionInterrupted connection_interrupted;
|
||||||
SignalFfiChatListenerDestroy destroy;
|
SignalDestroyChatListener destroy;
|
||||||
} SignalFfiChatListenerStruct;
|
} SignalFfiChatListenerStruct;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalFfiChatListenerStruct *raw;
|
const SignalFfiChatListenerStruct *raw;
|
||||||
} SignalConstPointerFfiChatListenerStruct;
|
} 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 {
|
typedef struct {
|
||||||
uint16_t status;
|
uint16_t status;
|
||||||
const char *message;
|
const char *message;
|
||||||
|
|
@ -636,27 +615,6 @@ typedef struct {
|
||||||
*/
|
*/
|
||||||
typedef uint8_t SignalServiceIdFixedWidthBinaryBytes[17];
|
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 {
|
typedef struct {
|
||||||
SignalPrivateKey *raw;
|
SignalPrivateKey *raw;
|
||||||
} SignalMutPointerPrivateKey;
|
} SignalMutPointerPrivateKey;
|
||||||
|
|
@ -768,6 +726,10 @@ typedef struct {
|
||||||
const SignalPlaintextContent *raw;
|
const SignalPlaintextContent *raw;
|
||||||
} SignalConstPointerPlaintextContent;
|
} SignalConstPointerPlaintextContent;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const SignalCiphertextMessage *raw;
|
||||||
|
} SignalConstPointerCiphertextMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SignalConnectionInfo *raw;
|
SignalConnectionInfo *raw;
|
||||||
} SignalMutPointerConnectionInfo;
|
} SignalMutPointerConnectionInfo;
|
||||||
|
|
@ -792,52 +754,49 @@ typedef struct {
|
||||||
SignalSessionRecord *raw;
|
SignalSessionRecord *raw;
|
||||||
} SignalMutPointerSessionRecord;
|
} 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 {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiSessionStoreLoadSession load_session;
|
SignalLoadSession load_session;
|
||||||
SignalFfiSessionStoreStoreSession store_session;
|
SignalStoreSession store_session;
|
||||||
SignalFfiSessionStoreDestroy destroy;
|
|
||||||
} SignalSessionStore;
|
} SignalSessionStore;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalSessionStore *raw;
|
const SignalSessionStore *raw;
|
||||||
} SignalConstPointerFfiSessionStoreStruct;
|
} 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 {
|
typedef struct {
|
||||||
SignalPublicKey *raw;
|
SignalPublicKey *raw;
|
||||||
} SignalMutPointerPublicKey;
|
} SignalMutPointerPublicKey;
|
||||||
|
|
||||||
typedef struct {
|
typedef int (*SignalGetIdentityKey)(void *store_ctx, SignalMutPointerPublicKey *public_keyp, SignalConstPointerProtocolAddress address);
|
||||||
SignalMutPointerPrivateKey first;
|
|
||||||
SignalMutPointerPublicKey second;
|
|
||||||
} SignalPairOfMutPointerPrivateKeyMutPointerPublicKey;
|
|
||||||
|
|
||||||
typedef int (*SignalFfiIdentityKeyStoreGetLocalIdentityKeyPair)(void *ctx, SignalPairOfMutPointerPrivateKeyMutPointerPublicKey *out);
|
typedef int (*SignalIsTrustedIdentity)(void *store_ctx, SignalConstPointerProtocolAddress address, SignalConstPointerPublicKey public_key, unsigned int direction);
|
||||||
|
|
||||||
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 struct {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiIdentityKeyStoreGetLocalIdentityKeyPair get_local_identity_key_pair;
|
SignalGetIdentityKeyPair get_identity_key_pair;
|
||||||
SignalFfiIdentityKeyStoreGetLocalRegistrationId get_local_registration_id;
|
SignalGetLocalRegistrationId get_local_registration_id;
|
||||||
SignalFfiIdentityKeyStoreGetIdentityKey get_identity_key;
|
SignalSaveIdentityKey save_identity;
|
||||||
SignalFfiIdentityKeyStoreSaveIdentityKey save_identity_key;
|
SignalGetIdentityKey get_identity;
|
||||||
SignalFfiIdentityKeyStoreIsTrustedIdentity is_trusted_identity;
|
SignalIsTrustedIdentity is_trusted_identity;
|
||||||
SignalFfiIdentityKeyStoreDestroy destroy;
|
|
||||||
} SignalIdentityKeyStore;
|
} SignalIdentityKeyStore;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -852,20 +811,21 @@ typedef struct {
|
||||||
SignalPreKeyRecord *raw;
|
SignalPreKeyRecord *raw;
|
||||||
} SignalMutPointerPreKeyRecord;
|
} 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 {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiPreKeyStoreLoadPreKey load_pre_key;
|
SignalLoadPreKey load_pre_key;
|
||||||
SignalFfiPreKeyStoreStorePreKey store_pre_key;
|
SignalStorePreKey store_pre_key;
|
||||||
SignalFfiPreKeyStoreRemovePreKey remove_pre_key;
|
SignalRemovePreKey remove_pre_key;
|
||||||
SignalFfiPreKeyStoreDestroy destroy;
|
|
||||||
} SignalPreKeyStore;
|
} SignalPreKeyStore;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -876,17 +836,18 @@ typedef struct {
|
||||||
SignalSignedPreKeyRecord *raw;
|
SignalSignedPreKeyRecord *raw;
|
||||||
} SignalMutPointerSignedPreKeyRecord;
|
} 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 {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiSignedPreKeyStoreLoadSignedPreKey load_signed_pre_key;
|
SignalLoadSignedPreKey load_signed_pre_key;
|
||||||
SignalFfiSignedPreKeyStoreStoreSignedPreKey store_signed_pre_key;
|
SignalStoreSignedPreKey store_signed_pre_key;
|
||||||
SignalFfiSignedPreKeyStoreDestroy destroy;
|
|
||||||
} SignalSignedPreKeyStore;
|
} SignalSignedPreKeyStore;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -897,20 +858,21 @@ typedef struct {
|
||||||
SignalKyberPreKeyRecord *raw;
|
SignalKyberPreKeyRecord *raw;
|
||||||
} SignalMutPointerKyberPreKeyRecord;
|
} 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 {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiKyberPreKeyStoreLoadKyberPreKey load_kyber_pre_key;
|
SignalLoadKyberPreKey load_kyber_pre_key;
|
||||||
SignalFfiKyberPreKeyStoreStoreKyberPreKey store_kyber_pre_key;
|
SignalStoreKyberPreKey store_kyber_pre_key;
|
||||||
SignalFfiKyberPreKeyStoreMarkKyberPreKeyUsed mark_kyber_pre_key_used;
|
SignalMarkKyberPreKeyUsed mark_kyber_pre_key_used;
|
||||||
SignalFfiKyberPreKeyStoreDestroy destroy;
|
|
||||||
} SignalKyberPreKeyStore;
|
} SignalKyberPreKeyStore;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -935,45 +897,11 @@ typedef struct {
|
||||||
uint32_t second;
|
uint32_t second;
|
||||||
} SignalPairOfc_charu32;
|
} 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 {
|
typedef struct {
|
||||||
const char *first;
|
const char *first;
|
||||||
SignalOwnedBuffer second;
|
SignalOwnedBuffer second;
|
||||||
} SignalPairOfc_charOwnedBufferOfc_uchar;
|
} SignalPairOfc_charOwnedBufferOfc_uchar;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
SignalPairOfc_charOwnedBufferOfc_uchar first;
|
|
||||||
int64_t second;
|
|
||||||
} SignalPairOfPairOfc_charOwnedBufferOfc_uchari64;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *first;
|
const char *first;
|
||||||
bool second;
|
bool second;
|
||||||
|
|
@ -987,10 +915,6 @@ typedef struct {
|
||||||
const SignalFingerprint *raw;
|
const SignalFingerprint *raw;
|
||||||
} SignalConstPointerFingerprint;
|
} SignalConstPointerFingerprint;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const SignalPublicKey *raw;
|
|
||||||
} SignalConstPointerPublicKey;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/**
|
/**
|
||||||
* The badge ID.
|
* The badge ID.
|
||||||
|
|
@ -1017,47 +941,22 @@ typedef struct {
|
||||||
size_t length;
|
size_t length;
|
||||||
} SignalOwnedBufferOfFfiRegisterResponseBadge;
|
} 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 {
|
typedef struct {
|
||||||
SignalSenderKeyRecord *raw;
|
SignalSenderKeyRecord *raw;
|
||||||
} SignalMutPointerSenderKeyRecord;
|
} 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 {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiSenderKeyStoreLoadSenderKey load_sender_key;
|
SignalLoadSenderKey load_sender_key;
|
||||||
SignalFfiSenderKeyStoreStoreSenderKey store_sender_key;
|
SignalStoreSenderKey store_sender_key;
|
||||||
SignalFfiSenderKeyStoreDestroy destroy;
|
|
||||||
} SignalSenderKeyStore;
|
} SignalSenderKeyStore;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -1102,23 +1001,15 @@ typedef struct {
|
||||||
SignalIncrementalMac *raw;
|
SignalIncrementalMac *raw;
|
||||||
} SignalMutPointerIncrementalMac;
|
} 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 (*SignalLogFlushCallback)(void *ctx);
|
||||||
|
|
||||||
typedef void (*SignalFfiLoggerDestroy)(void *ctx);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiLoggerLog log;
|
SignalLogCallback log;
|
||||||
SignalFfiLoggerFlush flush;
|
SignalLogFlushCallback flush;
|
||||||
SignalFfiLoggerDestroy destroy;
|
} SignalFfiLogger;
|
||||||
} SignalFfiLoggerStruct;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
SignalOwnedBuffer first;
|
|
||||||
SignalOwnedBuffer second;
|
|
||||||
} SignalPairOfOwnedBufferOfc_ucharOwnedBufferOfc_uchar;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A C callback used to report the results of Rust futures.
|
* A C callback used to report the results of Rust futures.
|
||||||
|
|
@ -1130,10 +1021,10 @@ typedef struct {
|
||||||
* completed once.
|
* completed once.
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
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;
|
const void *context;
|
||||||
SignalCancellationId cancellation_id;
|
SignalCancellationId cancellation_id;
|
||||||
} SignalCPromisePairOfOwnedBufferOfc_ucharOwnedBufferOfc_uchar;
|
} SignalCPromiseOwnedBufferOfc_uchar;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalUnauthenticatedChatConnection *raw;
|
const SignalUnauthenticatedChatConnection *raw;
|
||||||
|
|
@ -1171,10 +1062,6 @@ typedef struct {
|
||||||
SignalKyberSecretKey *raw;
|
SignalKyberSecretKey *raw;
|
||||||
} SignalMutPointerKyberSecretKey;
|
} SignalMutPointerKyberSecretKey;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const SignalKyberPreKeyRecord *raw;
|
|
||||||
} SignalConstPointerKyberPreKeyRecord;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalKyberPublicKey *raw;
|
const SignalKyberPublicKey *raw;
|
||||||
} SignalConstPointerKyberPublicKey;
|
} SignalConstPointerKyberPublicKey;
|
||||||
|
|
@ -1203,17 +1090,14 @@ typedef struct {
|
||||||
const SignalMessageBackupValidationOutcome *raw;
|
const SignalMessageBackupValidationOutcome *raw;
|
||||||
} SignalConstPointerMessageBackupValidationOutcome;
|
} 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 int (*SignalSkip)(void *ctx, uint64_t amount);
|
||||||
|
|
||||||
typedef void (*SignalFfiInputStreamDestroy)(void *ctx);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *ctx;
|
void *ctx;
|
||||||
SignalFfiInputStreamRead read;
|
SignalRead read;
|
||||||
SignalFfiInputStreamSkip skip;
|
SignalSkip skip;
|
||||||
SignalFfiInputStreamDestroy destroy;
|
|
||||||
} SignalInputStream;
|
} SignalInputStream;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -1245,12 +1129,12 @@ typedef struct {
|
||||||
} SignalMutPointerPlaintextContent;
|
} SignalMutPointerPlaintextContent;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalPreKeyBundle *raw;
|
SignalPreKeyBundle *raw;
|
||||||
} SignalConstPointerPreKeyBundle;
|
} SignalMutPointerPreKeyBundle;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalPreKeyRecord *raw;
|
const SignalPreKeyBundle *raw;
|
||||||
} SignalConstPointerPreKeyRecord;
|
} SignalConstPointerPreKeyBundle;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SignalPreKeySignalMessage *raw;
|
SignalPreKeySignalMessage *raw;
|
||||||
|
|
@ -1260,49 +1144,6 @@ typedef struct {
|
||||||
const SignalSenderKeyDistributionMessage *raw;
|
const SignalSenderKeyDistributionMessage *raw;
|
||||||
} SignalConstPointerSenderKeyDistributionMessage;
|
} 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 {
|
typedef struct {
|
||||||
SignalRegisterAccountRequest *raw;
|
SignalRegisterAccountRequest *raw;
|
||||||
} SignalMutPointerRegisterAccountRequest;
|
} SignalMutPointerRegisterAccountRequest;
|
||||||
|
|
@ -1326,10 +1167,7 @@ typedef struct {
|
||||||
const SignalRegisterAccountResponse *raw;
|
const SignalRegisterAccountResponse *raw;
|
||||||
} SignalConstPointerRegisterAccountResponse;
|
} SignalConstPointerRegisterAccountResponse;
|
||||||
|
|
||||||
typedef struct {
|
typedef uint8_t SignalOptionalUuid[17];
|
||||||
bool present;
|
|
||||||
uint8_t bytes[16];
|
|
||||||
} SignalOptionalUuid;
|
|
||||||
|
|
||||||
typedef SignalAccountAttributes SignalRegistrationAccountAttributes;
|
typedef SignalAccountAttributes SignalRegistrationAccountAttributes;
|
||||||
|
|
||||||
|
|
@ -1444,10 +1282,6 @@ typedef struct {
|
||||||
size_t length;
|
size_t length;
|
||||||
} SignalBorrowedSliceOfConstPointerProtocolAddress;
|
} SignalBorrowedSliceOfConstPointerProtocolAddress;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const SignalSessionRecord *raw;
|
|
||||||
} SignalConstPointerSessionRecord;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalConstPointerSessionRecord *base;
|
const SignalConstPointerSessionRecord *base;
|
||||||
size_t length;
|
size_t length;
|
||||||
|
|
@ -1527,8 +1361,8 @@ typedef struct {
|
||||||
} SignalConstPointerSenderKeyMessage;
|
} SignalConstPointerSenderKeyMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalSenderKeyRecord *raw;
|
SignalServerMessageAck *raw;
|
||||||
} SignalConstPointerSenderKeyRecord;
|
} SignalMutPointerServerMessageAck;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const SignalServerMessageAck *raw;
|
const SignalServerMessageAck *raw;
|
||||||
|
|
@ -1546,10 +1380,6 @@ typedef struct {
|
||||||
const SignalSgxClientState *raw;
|
const SignalSgxClientState *raw;
|
||||||
} SignalConstPointerSgxClientState;
|
} SignalConstPointerSgxClientState;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const SignalSignedPreKeyRecord *raw;
|
|
||||||
} SignalConstPointerSignedPreKeyRecord;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SignalTokioAsyncContext *raw;
|
SignalTokioAsyncContext *raw;
|
||||||
} SignalMutPointerTokioAsyncContext;
|
} SignalMutPointerTokioAsyncContext;
|
||||||
|
|
@ -1573,26 +1403,6 @@ typedef struct {
|
||||||
SignalCancellationId cancellation_id;
|
SignalCancellationId cancellation_id;
|
||||||
} SignalCPromiseMutPointerUnauthenticatedChatConnection;
|
} 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.
|
* A C callback used to report the results of Rust futures.
|
||||||
*
|
*
|
||||||
|
|
@ -1608,42 +1418,6 @@ typedef struct {
|
||||||
SignalCancellationId cancellation_id;
|
SignalCancellationId cancellation_id;
|
||||||
} SignalCPromiseOptionalUuid;
|
} 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 {
|
typedef struct {
|
||||||
SignalValidatingMac *raw;
|
SignalValidatingMac *raw;
|
||||||
} SignalMutPointerValidatingMac;
|
} SignalMutPointerValidatingMac;
|
||||||
|
|
@ -1658,8 +1432,6 @@ typedef uint8_t SignalRandomnessBytes[SignalRANDOMNESS_LEN];
|
||||||
|
|
||||||
typedef uint8_t SignalUnidentifiedAccessKey[SignalACCESS_KEY_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_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);
|
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_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_info(SignalMutPointerChatConnectionInfo *out, SignalConstPointerAuthenticatedChatConnection chat);
|
||||||
|
|
||||||
SignalFfiError *signal_authenticated_chat_connection_init_listener(SignalConstPointerAuthenticatedChatConnection chat, SignalConstPointerFfiChatListenerStruct listener);
|
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(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_check_valid_contents(SignalBorrowedBuffer params_bytes);
|
||||||
|
|
||||||
SignalFfiError *signal_backup_auth_credential_get_backup_id(uint8_t (*out)[16], SignalBorrowedBuffer credential_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_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);
|
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_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);
|
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_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);
|
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_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_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);
|
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_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);
|
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_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_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_list_of_strings(SignalOwnedBufferOfCStringPtr buffer);
|
||||||
|
|
||||||
void signal_free_lookup_response_entry_list(SignalOwnedBufferOfFfiCdsiLookupResponseEntry 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);
|
void signal_free_string(const char *buf);
|
||||||
|
|
||||||
SignalFfiError *signal_generic_server_public_params_check_valid_contents(SignalBorrowedBuffer params_bytes);
|
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_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);
|
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);
|
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_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_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_key_transparency_username_hash_search_key(SignalOwnedBuffer *out, SignalBorrowedBuffer hash);
|
||||||
|
|
||||||
SignalFfiError *signal_kyber_key_pair_clone(SignalMutPointerKyberKeyPair *new_obj, SignalConstPointerKyberKeyPair obj);
|
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_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);
|
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_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_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_deserialize(SignalMutPointerPublicKey *out, SignalBorrowedBuffer data);
|
||||||
|
|
||||||
SignalFfiError *signal_publickey_destroy(SignalMutPointerPublicKey p);
|
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_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);
|
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_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_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_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);
|
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_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_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);
|
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_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_connect(SignalCPromiseMutPointerUnauthenticatedChatConnection *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerConnectionManager connection_manager, SignalBorrowedBytestringArray languages);
|
||||||
|
|
||||||
SignalFfiError *signal_unauthenticated_chat_connection_destroy(SignalMutPointerUnauthenticatedChatConnection p);
|
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_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_info(SignalMutPointerChatConnectionInfo *out, SignalConstPointerUnauthenticatedChatConnection chat);
|
||||||
|
|
||||||
SignalFfiError *signal_unauthenticated_chat_connection_init_listener(SignalConstPointerUnauthenticatedChatConnection chat, SignalConstPointerFfiChatListenerStruct listener);
|
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_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(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_deserialize(SignalMutPointerUnidentifiedSenderMessageContent *out, SignalBorrowedBuffer data);
|
||||||
|
|
||||||
SignalFfiError *signal_unidentified_sender_message_content_destroy(SignalMutPointerUnidentifiedSenderMessageContent p);
|
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_callback(void *ctx, SignalLogLevel level, char *file, uint32_t line, char *message);
|
||||||
extern void signal_log_flush_callback(void *ctx);
|
extern void signal_log_flush_callback(void *ctx);
|
||||||
extern void signal_log_destroy_callback(void *ctx);
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -41,11 +40,6 @@ func signal_log_flush_callback(ctx unsafe.Pointer) {
|
||||||
ffiLogger.Flush()
|
ffiLogger.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_log_destroy_callback
|
|
||||||
func signal_log_destroy_callback(ctx unsafe.Pointer) {
|
|
||||||
ffiLogger.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogLevel int
|
type LogLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -59,14 +53,12 @@ const (
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Log(level LogLevel, file string, line uint, message string)
|
Log(level LogLevel, file string, line uint, message string)
|
||||||
Flush()
|
Flush()
|
||||||
Destroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLogger(level LogLevel, logger Logger) {
|
func InitLogger(level LogLevel, logger Logger) {
|
||||||
ffiLogger = logger
|
ffiLogger = logger
|
||||||
C.signal_init_logger(C.SignalLogLevel(level), C.SignalFfiLoggerStruct{
|
C.signal_init_logger(C.SignalLogLevel(level), C.SignalFfiLogger{
|
||||||
log: C.SignalFfiLoggerLog(C.signal_log_callback),
|
log: C.SignalLogCallback(C.signal_log_callback),
|
||||||
flush: C.SignalFfiLoggerFlush(C.signal_log_flush_callback),
|
flush: C.SignalLogFlushCallback(C.signal_log_flush_callback),
|
||||||
destroy: C.SignalFfiLoggerDestroy(C.signal_log_destroy_callback),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"time"
|
"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 ciphertextMessage C.SignalMutPointerCiphertextMessage
|
||||||
var now C.uint64_t = C.uint64_t(time.Now().Unix())
|
var now C.uint64_t = C.uint64_t(time.Now().Unix())
|
||||||
callbackCtx := NewCallbackContext(ctx)
|
callbackCtx := NewCallbackContext(ctx)
|
||||||
|
|
@ -36,7 +36,6 @@ func Encrypt(ctx context.Context, plaintext []byte, forAddress, localAddress *Ad
|
||||||
&ciphertextMessage,
|
&ciphertextMessage,
|
||||||
BytesToBuffer(plaintext),
|
BytesToBuffer(plaintext),
|
||||||
forAddress.constPtr(),
|
forAddress.constPtr(),
|
||||||
localAddress.constPtr(),
|
|
||||||
callbackCtx.wrapSessionStore(sessionStore),
|
callbackCtx.wrapSessionStore(sessionStore),
|
||||||
callbackCtx.wrapIdentityKeyStore(identityKeyStore),
|
callbackCtx.wrapIdentityKeyStore(identityKeyStore),
|
||||||
now,
|
now,
|
||||||
|
|
@ -49,7 +48,7 @@ func Encrypt(ctx context.Context, plaintext []byte, forAddress, localAddress *Ad
|
||||||
return wrapCiphertextMessage(ciphertextMessage.raw), nil
|
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)
|
callbackCtx := NewCallbackContext(ctx)
|
||||||
defer callbackCtx.Unref()
|
defer callbackCtx.Unref()
|
||||||
var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||||
|
|
@ -57,7 +56,6 @@ func Decrypt(ctx context.Context, message *Message, fromAddress, localAddress *A
|
||||||
&decrypted,
|
&decrypted,
|
||||||
message.constPtr(),
|
message.constPtr(),
|
||||||
fromAddress.constPtr(),
|
fromAddress.constPtr(),
|
||||||
localAddress.constPtr(),
|
|
||||||
callbackCtx.wrapSessionStore(sessionStore),
|
callbackCtx.wrapSessionStore(sessionStore),
|
||||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"runtime"
|
"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)
|
callbackCtx := NewCallbackContext(ctx)
|
||||||
defer callbackCtx.Unref()
|
defer callbackCtx.Unref()
|
||||||
var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
||||||
|
|
@ -34,7 +34,6 @@ func DecryptPreKey(ctx context.Context, preKeyMessage *PreKeyMessage, fromAddres
|
||||||
&decrypted,
|
&decrypted,
|
||||||
preKeyMessage.constPtr(),
|
preKeyMessage.constPtr(),
|
||||||
fromAddress.constPtr(),
|
fromAddress.constPtr(),
|
||||||
localAddress.constPtr(),
|
|
||||||
callbackCtx.wrapSessionStore(sessionStore),
|
callbackCtx.wrapSessionStore(sessionStore),
|
||||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||||
callbackCtx.wrapPreKeyStore(preKeyStore),
|
callbackCtx.wrapPreKeyStore(preKeyStore),
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,13 @@ import (
|
||||||
"time"
|
"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)
|
callbackCtx := NewCallbackContext(ctx)
|
||||||
defer callbackCtx.Unref()
|
defer callbackCtx.Unref()
|
||||||
var now C.uint64_t = C.uint64_t(time.Now().Unix())
|
var now C.uint64_t = C.uint64_t(time.Now().Unix())
|
||||||
signalFfiError := C.signal_process_prekey_bundle(
|
signalFfiError := C.signal_process_prekey_bundle(
|
||||||
bundle.constPtr(),
|
bundle.constPtr(),
|
||||||
forAddress.constPtr(),
|
forAddress.constPtr(),
|
||||||
localAddress.constPtr(),
|
|
||||||
callbackCtx.wrapSessionStore(sessionStore),
|
callbackCtx.wrapSessionStore(sessionStore),
|
||||||
callbackCtx.wrapIdentityKeyStore(identityStore),
|
callbackCtx.wrapIdentityKeyStore(identityStore),
|
||||||
now,
|
now,
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,11 @@ package libsignalgo
|
||||||
/*
|
/*
|
||||||
#include "./libsignal-ffi.h"
|
#include "./libsignal-ffi.h"
|
||||||
|
|
||||||
extern int signal_load_pre_key_callback(void *store_ctx, SignalMutPointerPreKeyRecord *recordp, uint32_t id);
|
typedef const SignalPreKeyRecord const_pre_key_record;
|
||||||
extern int signal_store_pre_key_callback(void *store_ctx, uint32_t id, SignalMutPointerPreKeyRecord 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 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 "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -38,21 +39,21 @@ type PreKeyStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_load_pre_key_callback
|
//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 {
|
return wrapStoreCallback(storeCtx, func(store PreKeyStore, ctx context.Context) error {
|
||||||
key, err := store.LoadPreKey(ctx, uint32(id))
|
key, err := store.LoadPreKey(ctx, uint32(id))
|
||||||
if err == nil && key != nil {
|
if err == nil && key != nil {
|
||||||
key.CancelFinalizer()
|
key.CancelFinalizer()
|
||||||
keyp.raw = key.ptr
|
*keyp = key.ptr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_store_pre_key_callback
|
//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 {
|
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()
|
cloned, err := record.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (ctx *CallbackContext) wrapPreKeyStore(store PreKeyStore) C.SignalConstPointerFfiPreKeyStoreStruct {
|
||||||
return C.SignalConstPointerFfiPreKeyStoreStruct{&C.SignalPreKeyStore{
|
return C.SignalConstPointerFfiPreKeyStoreStruct{&C.SignalPreKeyStore{
|
||||||
ctx: wrapStore(ctx, store),
|
ctx: wrapStore(ctx, store),
|
||||||
load_pre_key: C.SignalFfiPreKeyStoreLoadPreKey(C.signal_load_pre_key_callback),
|
load_pre_key: C.SignalLoadPreKey(C.signal_load_pre_key_callback),
|
||||||
store_pre_key: C.SignalFfiPreKeyStoreStorePreKey(C.signal_store_pre_key_callback),
|
store_pre_key: C.SignalStorePreKey(C.signal_store_pre_key_callback),
|
||||||
remove_pre_key: C.SignalFfiPreKeyStoreRemovePreKey(C.signal_remove_pre_key_callback),
|
remove_pre_key: C.SignalRemovePreKey(C.signal_remove_pre_key_callback),
|
||||||
destroy: C.SignalFfiPreKeyStoreDestroy(C.signal_destroy_pre_key_store_callback),
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ package libsignalgo
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
@ -55,6 +54,10 @@ func (pk *ProfileKey) IsEmpty() bool {
|
||||||
return pk == nil || *pk == blankProfileKey
|
return pk == nil || *pk == blankProfileKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ak *AccessKey) String() string {
|
||||||
|
return string(ak[:])
|
||||||
|
}
|
||||||
|
|
||||||
func (pv *ProfileKeyVersion) String() string {
|
func (pv *ProfileKeyVersion) String() string {
|
||||||
return string(pv[:])
|
return string(pv[:])
|
||||||
}
|
}
|
||||||
|
|
@ -66,23 +69,6 @@ func (pk *ProfileKey) Slice() []byte {
|
||||||
return pk[:]
|
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) {
|
func (pk *ProfileKey) GetCommitment(u uuid.UUID) (*ProfileKeyCommitment, error) {
|
||||||
c_result := [C.SignalPROFILE_KEY_COMMITMENT_LEN]C.uchar{}
|
c_result := [C.SignalPROFILE_KEY_COMMITMENT_LEN]C.uchar{}
|
||||||
c_profileKey := (*[C.SignalPROFILE_KEY_LEN]C.uchar)(unsafe.Pointer(pk))
|
c_profileKey := (*[C.SignalPROFILE_KEY_LEN]C.uchar)(unsafe.Pointer(pk))
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ type PublicKey struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapPublicKey(ptr *C.SignalPublicKey) *PublicKey {
|
func wrapPublicKey(ptr *C.SignalPublicKey) *PublicKey {
|
||||||
if ptr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
publicKey := &PublicKey{ptr: ptr}
|
publicKey := &PublicKey{ptr: ptr}
|
||||||
runtime.SetFinalizer(publicKey, (*PublicKey).Destroy)
|
runtime.SetFinalizer(publicKey, (*PublicKey).Destroy)
|
||||||
return publicKey
|
return publicKey
|
||||||
|
|
@ -84,15 +81,15 @@ func (k *PublicKey) CancelFinalizer() {
|
||||||
runtime.SetFinalizer(k, nil)
|
runtime.SetFinalizer(k, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *PublicKey) Equal(other *PublicKey) (bool, error) {
|
func (k *PublicKey) Compare(other *PublicKey) (int, error) {
|
||||||
var comparison C.bool
|
var comparison C.int
|
||||||
signalFfiError := C.signal_publickey_equals(&comparison, k.constPtr(), other.constPtr())
|
signalFfiError := C.signal_publickey_compare(&comparison, k.constPtr(), other.constPtr())
|
||||||
runtime.KeepAlive(k)
|
runtime.KeepAlive(k)
|
||||||
runtime.KeepAlive(other)
|
runtime.KeepAlive(other)
|
||||||
if signalFfiError != nil {
|
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) {
|
func (k *PublicKey) Bytes() ([]byte, error) {
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ package libsignalgo
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
@ -44,17 +42,8 @@ func NewSealedSenderAddress(e164 string, uuid uuid.UUID, deviceID uint32) *Seale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SealedSenderEncryptPlaintext(
|
func SealedSenderEncryptPlaintext(ctx context.Context, message []byte, contentHint UnidentifiedSenderMessageContentHint, forAddress *Address, fromSenderCert *SenderCertificate, sessionStore SessionStore, identityStore IdentityKeyStore) ([]byte, error) {
|
||||||
ctx context.Context,
|
ciphertextMessage, err := Encrypt(ctx, message, forAddress, sessionStore, identityStore)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +52,7 @@ func SealedSenderEncryptPlaintext(
|
||||||
ciphertextMessage,
|
ciphertextMessage,
|
||||||
fromSenderCert,
|
fromSenderCert,
|
||||||
contentHint,
|
contentHint,
|
||||||
groupID,
|
nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -89,51 +78,8 @@ func SealedSenderEncrypt(ctx context.Context, usmc *UnidentifiedSenderMessageCon
|
||||||
return CopySignalOwnedBufferToBytes(encrypted), nil
|
return CopySignalOwnedBufferToBytes(encrypted), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionAddressTuple struct {
|
func SealedSenderMultiRecipientEncrypt(messageContent *UnidentifiedSenderMessageContent, forRecipients []*Address, identityStore IdentityKeyStore, sessionStore SessionStore, ctx *CallbackContext) ([]byte, error) {
|
||||||
ServiceID ServiceID
|
panic("not implemented")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SealedSenderResult struct {
|
type SealedSenderResult struct {
|
||||||
|
|
@ -180,22 +126,18 @@ func wrapUnidentifiedSenderMessageContent(ptr *C.SignalUnidentifiedSenderMessage
|
||||||
return messageContent
|
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 usmc C.SignalMutPointerUnidentifiedSenderMessageContent
|
||||||
var groupIDBytes []byte
|
|
||||||
if groupID != nil {
|
|
||||||
groupIDBytes = groupID[:]
|
|
||||||
}
|
|
||||||
signalFfiError := C.signal_unidentified_sender_message_content_new(
|
signalFfiError := C.signal_unidentified_sender_message_content_new(
|
||||||
&usmc,
|
&usmc,
|
||||||
message.constPtr(),
|
message.constPtr(),
|
||||||
senderCertificate.constPtr(),
|
senderCertificate.constPtr(),
|
||||||
C.uint32_t(contentHint),
|
C.uint32_t(contentHint),
|
||||||
BytesToBuffer(groupIDBytes),
|
BytesToBuffer(groupID),
|
||||||
)
|
)
|
||||||
runtime.KeepAlive(message)
|
runtime.KeepAlive(message)
|
||||||
runtime.KeepAlive(senderCertificate)
|
runtime.KeepAlive(senderCertificate)
|
||||||
runtime.KeepAlive(groupIDBytes)
|
runtime.KeepAlive(groupID)
|
||||||
if signalFfiError != nil {
|
if signalFfiError != nil {
|
||||||
return nil, wrapError(signalFfiError)
|
return nil, wrapError(signalFfiError)
|
||||||
}
|
}
|
||||||
|
|
@ -267,21 +209,18 @@ func (usmc *UnidentifiedSenderMessageContent) GetContents() ([]byte, error) {
|
||||||
return CopySignalOwnedBufferToBytes(contents), nil
|
return CopySignalOwnedBufferToBytes(contents), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (usmc *UnidentifiedSenderMessageContent) GetGroupID() (*GroupIdentifier, error) {
|
//func (usmc *UnidentifiedSenderMessageContent) GetGroupID() ([]byte, error) {
|
||||||
var contents C.SignalOwnedBuffer = C.SignalOwnedBuffer{}
|
// var groupID *C.uchar
|
||||||
signalFfiError := C.signal_unidentified_sender_message_content_get_group_id_or_empty(&contents, usmc.constPtr())
|
// var length C.ulong
|
||||||
runtime.KeepAlive(usmc)
|
// signalFfiError := C.signal_unidentified_sender_message_content_get_group_id(&groupID, &length, usmc.ptr)
|
||||||
if signalFfiError != nil {
|
// if signalFfiError != nil {
|
||||||
return nil, wrapError(signalFfiError)
|
// return nil, wrapError(signalFfiError)
|
||||||
}
|
// }
|
||||||
bytes := CopySignalOwnedBufferToBytes(contents)
|
// if groupID == nil {
|
||||||
if len(bytes) == 0 {
|
// return nil, nil
|
||||||
return nil, nil
|
// }
|
||||||
} else if len(bytes) != GroupIdentifierLength {
|
// return CopyBufferToBytes(groupID, length), nil
|
||||||
return nil, fmt.Errorf("unexpected group ID length: %d", len(bytes))
|
//}
|
||||||
}
|
|
||||||
return (*GroupIdentifier)(bytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (usmc *UnidentifiedSenderMessageContent) GetSenderCertificate() (*SenderCertificate, error) {
|
func (usmc *UnidentifiedSenderMessageContent) GetSenderCertificate() (*SenderCertificate, error) {
|
||||||
var senderCertificate C.SignalMutPointerSenderCertificate
|
var senderCertificate C.SignalMutPointerSenderCertificate
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ func NewSenderKeyDistributionMessage(ctx context.Context, sender *Address, distr
|
||||||
signalFfiError := C.signal_sender_key_distribution_message_create(
|
signalFfiError := C.signal_sender_key_distribution_message_create(
|
||||||
&skdm,
|
&skdm,
|
||||||
sender.constPtr(),
|
sender.constPtr(),
|
||||||
*(*C.SignalUuid)(unsafe.Pointer(&distributionID)),
|
(*[C.SignalUUID_LEN]C.uchar)(unsafe.Pointer(&distributionID)),
|
||||||
callbackCtx.wrapSenderKeyStore(store),
|
callbackCtx.wrapSenderKeyStore(store),
|
||||||
)
|
)
|
||||||
runtime.KeepAlive(sender)
|
runtime.KeepAlive(sender)
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,13 @@ package libsignalgo
|
||||||
/*
|
/*
|
||||||
#include "./libsignal-ffi.h"
|
#include "./libsignal-ffi.h"
|
||||||
|
|
||||||
extern int signal_load_sender_key_callback(void *store_ctx, SignalMutPointerSenderKeyRecord *out, SignalMutPointerProtocolAddress sender, SignalUuid distribution_id);
|
typedef const SignalProtocolAddress const_address;
|
||||||
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 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 "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -38,40 +42,36 @@ type SenderKeyStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_load_sender_key_callback
|
//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 {
|
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 {
|
if err == nil && record != nil {
|
||||||
record.CancelFinalizer()
|
record.CancelFinalizer()
|
||||||
recordp.raw = record.ptr
|
*recordp = record.ptr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_store_sender_key_callback
|
//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 {
|
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()
|
cloned, err := record.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (ctx *CallbackContext) wrapSenderKeyStore(store SenderKeyStore) C.SignalConstPointerFfiSenderKeyStoreStruct {
|
||||||
return C.SignalConstPointerFfiSenderKeyStoreStruct{&C.SignalSenderKeyStore{
|
return C.SignalConstPointerFfiSenderKeyStoreStruct{&C.SignalSenderKeyStore{
|
||||||
ctx: wrapStore(ctx, store),
|
ctx: wrapStore(ctx, store),
|
||||||
load_sender_key: C.SignalFfiSenderKeyStoreLoadSenderKey(C.signal_load_sender_key_callback),
|
load_sender_key: C.SignalLoadSenderKey(C.signal_load_sender_key_callback),
|
||||||
store_sender_key: C.SignalFfiSenderKeyStoreStoreSenderKey(C.signal_store_sender_key_callback),
|
store_sender_key: C.SignalStoreSenderKey(C.signal_store_sender_key_callback),
|
||||||
destroy: C.SignalFfiSenderKeyStoreDestroy(C.signal_destroy_sender_key_store_callback),
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if C.SignalUUID_LEN != 16 {
|
||||||
|
panic("libsignal-ffi uuid type size mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ServiceIDType byte
|
type ServiceIDType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -87,9 +93,6 @@ func (s ServiceID) IsEmpty() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ServiceID) Address(deviceID uint) (*Address, error) {
|
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)
|
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)
|
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) {
|
func (s ServiceID) MarshalZerologObject(e *zerolog.Event) {
|
||||||
e.Stringer("type", s.Type)
|
e.Stringer("type", s.Type)
|
||||||
e.Stringer("uuid", s.UUID)
|
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 {
|
func ServiceIDFromCFixedBytes(serviceID *C.SignalServiceIdFixedWidthBinaryBytes) ServiceID {
|
||||||
var id ServiceID
|
var id ServiceID
|
||||||
fixedBytes := (*ServiceIDFixedBytes)(unsafe.Pointer(serviceID))
|
fixedBytes := (*ServiceIDFixedBytes)(unsafe.Pointer(serviceID))
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"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()
|
ctx := context.TODO()
|
||||||
|
|
||||||
bobPreKey, err := libsignalgo.GeneratePrivateKey()
|
bobPreKey, err := libsignalgo.GeneratePrivateKey()
|
||||||
|
|
@ -86,7 +86,7 @@ func initializeSessions(t *testing.T, aliceStore, bobStore *InMemorySignalProtoc
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Alice processes the bundle
|
// 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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
record, err := aliceStore.LoadSession(ctx, bobAddress)
|
record, err := aliceStore.LoadSession(ctx, bobAddress)
|
||||||
|
|
@ -132,11 +132,11 @@ func TestSessionCipher(t *testing.T) {
|
||||||
aliceStore := NewInMemorySignalProtocolStore()
|
aliceStore := NewInMemorySignalProtocolStore()
|
||||||
bobStore := NewInMemorySignalProtocolStore()
|
bobStore := NewInMemorySignalProtocolStore()
|
||||||
|
|
||||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||||
|
|
||||||
alicePlaintext := []byte{8, 6, 7, 5, 3, 0, 9}
|
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)
|
assert.NoError(t, err)
|
||||||
aliceCiphertextMessageType, err := aliceCiphertext.MessageType()
|
aliceCiphertextMessageType, err := aliceCiphertext.MessageType()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -147,13 +147,13 @@ func TestSessionCipher(t *testing.T) {
|
||||||
bobCiphertext, err := libsignalgo.DeserializePreKeyMessage(aliceCiphertextSerialized)
|
bobCiphertext, err := libsignalgo.DeserializePreKeyMessage(aliceCiphertextSerialized)
|
||||||
assert.NoError(t, err)
|
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.NoError(t, err)
|
||||||
assert.Equal(t, alicePlaintext, bobPlaintext)
|
assert.Equal(t, alicePlaintext, bobPlaintext)
|
||||||
|
|
||||||
bobPlaintext2 := []byte{23}
|
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)
|
assert.NoError(t, err)
|
||||||
bobCiphertext2MessageType, err := bobCiphertext2.MessageType()
|
bobCiphertext2MessageType, err := bobCiphertext2.MessageType()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -163,7 +163,7 @@ func TestSessionCipher(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
aliceCiphertext2, err := libsignalgo.DeserializeMessage(bobCiphertext2Serialized)
|
aliceCiphertext2, err := libsignalgo.DeserializeMessage(bobCiphertext2Serialized)
|
||||||
assert.NoError(t, err)
|
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.NoError(t, err)
|
||||||
assert.Equal(t, bobPlaintext2, alicePlaintext2)
|
assert.Equal(t, bobPlaintext2, alicePlaintext2)
|
||||||
}
|
}
|
||||||
|
|
@ -183,11 +183,11 @@ func TestSessionCipherWithBadStore(t *testing.T) {
|
||||||
aliceStore := NewInMemorySignalProtocolStore()
|
aliceStore := NewInMemorySignalProtocolStore()
|
||||||
bobStore := &BadInMemorySignalProtocolStore{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}
|
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)
|
assert.NoError(t, err)
|
||||||
aliceCiphertextMessageType, err := aliceCiphertext.MessageType()
|
aliceCiphertextMessageType, err := aliceCiphertext.MessageType()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -198,7 +198,7 @@ func TestSessionCipherWithBadStore(t *testing.T) {
|
||||||
bobCiphertext, err := libsignalgo.DeserializePreKeyMessage(aliceCiphertextSerialized)
|
bobCiphertext, err := libsignalgo.DeserializePreKeyMessage(aliceCiphertextSerialized)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
t.Skip("This test is broken") // TODO fix
|
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)
|
require.Error(t, err)
|
||||||
assert.Equal(t, "Test error", err.Error())
|
assert.Equal(t, "Test error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +216,7 @@ func TestSealedSenderEncrypt_Repeated(t *testing.T) {
|
||||||
aliceStore := NewInMemorySignalProtocolStore()
|
aliceStore := NewInMemorySignalProtocolStore()
|
||||||
bobStore := NewInMemorySignalProtocolStore()
|
bobStore := NewInMemorySignalProtocolStore()
|
||||||
|
|
||||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||||
|
|
||||||
trustRoot, err := libsignalgo.GenerateIdentityKeyPair()
|
trustRoot, err := libsignalgo.GenerateIdentityKeyPair()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -241,7 +241,7 @@ func TestSealedSenderEncrypt_Repeated(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
message := []byte(fmt.Sprintf("%04d vision", 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)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, ciphertext)
|
assert.NotNil(t, ciphertext)
|
||||||
}
|
}
|
||||||
|
|
@ -252,18 +252,15 @@ func TestArchiveSession(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
setupLogging()
|
setupLogging()
|
||||||
|
|
||||||
aliceACI := uuid.New()
|
|
||||||
bobACI := uuid.New()
|
bobACI := uuid.New()
|
||||||
|
|
||||||
aliceAddress, err := libsignalgo.NewACIServiceID(aliceACI).Address(1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
bobAddress, err := libsignalgo.NewACIServiceID(bobACI).Address(1)
|
bobAddress, err := libsignalgo.NewACIServiceID(bobACI).Address(1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
aliceStore := NewInMemorySignalProtocolStore()
|
aliceStore := NewInMemorySignalProtocolStore()
|
||||||
bobStore := NewInMemorySignalProtocolStore()
|
bobStore := NewInMemorySignalProtocolStore()
|
||||||
|
|
||||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||||
|
|
||||||
session, err := aliceStore.LoadSession(ctx, bobAddress)
|
session, err := aliceStore.LoadSession(ctx, bobAddress)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -318,7 +315,7 @@ func TestSealedSenderGroupCipher(t *testing.T) {
|
||||||
|
|
||||||
bobStore := NewInMemorySignalProtocolStore()
|
bobStore := NewInMemorySignalProtocolStore()
|
||||||
|
|
||||||
initializeSessions(t, aliceStore, bobStore, bobAddress, aliceAddress)
|
initializeSessions(t, aliceStore, bobStore, bobAddress)
|
||||||
|
|
||||||
trustRoot, err := libsignalgo.GenerateIdentityKeyPair()
|
trustRoot, err := libsignalgo.GenerateIdentityKeyPair()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,6 @@ func (sr *SessionRecord) ArchiveCurrentState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *SessionRecord) CurrentRatchetKeyMatches(key *PublicKey) (bool, error) {
|
func (sr *SessionRecord) CurrentRatchetKeyMatches(key *PublicKey) (bool, error) {
|
||||||
if sr == nil || key == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
var result C.bool
|
var result C.bool
|
||||||
signalFfiError := C.signal_session_record_current_ratchet_key_matches(
|
signalFfiError := C.signal_session_record_current_ratchet_key_matches(
|
||||||
&result,
|
&result,
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ package libsignalgo
|
||||||
/*
|
/*
|
||||||
#include "./libsignal-ffi.h"
|
#include "./libsignal-ffi.h"
|
||||||
|
|
||||||
extern int signal_load_session_callback(void *store_ctx, SignalMutPointerSessionRecord *recordp, SignalMutPointerProtocolAddress address);
|
typedef const SignalSessionRecord const_session_record;
|
||||||
extern int signal_store_session_callback(void *store_ctx, SignalMutPointerProtocolAddress address, SignalMutPointerSessionRecord record);
|
typedef const SignalProtocolAddress const_address;
|
||||||
extern void signal_destroy_session_store_callback(void *store_ctx);
|
|
||||||
|
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 "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -36,39 +38,33 @@ type SessionStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_load_session_callback
|
//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 {
|
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 {
|
if err == nil && record != nil {
|
||||||
record.CancelFinalizer()
|
record.CancelFinalizer()
|
||||||
recordp.raw = record.ptr
|
*recordp = record.ptr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_store_session_callback
|
//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 {
|
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()
|
cloned, err := record.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (ctx *CallbackContext) wrapSessionStore(store SessionStore) C.SignalConstPointerFfiSessionStoreStruct {
|
||||||
return C.SignalConstPointerFfiSessionStoreStruct{&C.SignalSessionStore{
|
return C.SignalConstPointerFfiSessionStoreStruct{&C.SignalSessionStore{
|
||||||
ctx: wrapStore(ctx, store),
|
ctx: wrapStore(ctx, store),
|
||||||
load_session: C.SignalFfiSessionStoreLoadSession(C.signal_load_session_callback),
|
load_session: C.SignalLoadSession(C.signal_load_session_callback),
|
||||||
store_session: C.SignalFfiSessionStoreStoreSession(C.signal_store_session_callback),
|
store_session: C.SignalStoreSession(C.signal_store_session_callback),
|
||||||
destroy: C.SignalFfiSessionStoreDestroy(C.signal_destroy_session_store_callback),
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,6 @@ func (FFILogger) Log(level libsignalgo.LogLevel, file string, line uint, message
|
||||||
|
|
||||||
func (FFILogger) Flush() {}
|
func (FFILogger) Flush() {}
|
||||||
|
|
||||||
func (FFILogger) Destroy() {}
|
|
||||||
|
|
||||||
var loggingSetup = false
|
var loggingSetup = false
|
||||||
|
|
||||||
func setupLogging() {
|
func setupLogging() {
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,10 @@ package libsignalgo
|
||||||
/*
|
/*
|
||||||
#include "./libsignal-ffi.h"
|
#include "./libsignal-ffi.h"
|
||||||
|
|
||||||
extern int signal_load_signed_pre_key_callback(void *store_ctx, SignalMutPointerSignedPreKeyRecord *recordp, uint32_t id);
|
typedef const SignalSignedPreKeyRecord const_signed_pre_key_record;
|
||||||
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);
|
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 "C"
|
||||||
import (
|
import (
|
||||||
|
|
@ -36,21 +37,21 @@ type SignedPreKeyStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_load_signed_pre_key_callback
|
//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 {
|
return wrapStoreCallback(storeCtx, func(store SignedPreKeyStore, ctx context.Context) error {
|
||||||
key, err := store.LoadSignedPreKey(ctx, uint32(id))
|
key, err := store.LoadSignedPreKey(ctx, uint32(id))
|
||||||
if err == nil && key != nil {
|
if err == nil && key != nil {
|
||||||
key.CancelFinalizer()
|
key.CancelFinalizer()
|
||||||
keyp.raw = key.ptr
|
*keyp = key.ptr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export signal_store_signed_pre_key_callback
|
//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 {
|
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()
|
cloned, err := record.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (ctx *CallbackContext) wrapSignedPreKeyStore(store SignedPreKeyStore) C.SignalConstPointerFfiSignedPreKeyStoreStruct {
|
||||||
return C.SignalConstPointerFfiSignedPreKeyStoreStruct{&C.SignalSignedPreKeyStore{
|
return C.SignalConstPointerFfiSignedPreKeyStoreStruct{&C.SignalSignedPreKeyStore{
|
||||||
ctx: wrapStore(ctx, store),
|
ctx: wrapStore(ctx, store),
|
||||||
load_signed_pre_key: C.SignalFfiSignedPreKeyStoreLoadSignedPreKey(C.signal_load_signed_pre_key_callback),
|
load_signed_pre_key: C.SignalLoadSignedPreKey(C.signal_load_signed_pre_key_callback),
|
||||||
store_signed_pre_key: C.SignalFfiSignedPreKeyStoreStoreSignedPreKey(C.signal_store_signed_pre_key_callback),
|
store_signed_pre_key: C.SignalStoreSignedPreKey(C.signal_store_signed_pre_key_callback),
|
||||||
destroy: C.SignalFfiSignedPreKeyStoreDestroy(C.signal_destroy_signed_pre_key_store_callback),
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd /data
|
cd /data
|
||||||
export RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER=""
|
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
|
cd libsignal
|
||||||
cargo build -p libsignal-ffi --release
|
cargo build -p libsignal-ffi --release
|
||||||
cbindgen --profile release rust/bridge/ffi -o libsignal-ffi.h
|
cbindgen --profile release rust/bridge/ffi -o libsignal-ffi.h
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
package libsignalgo
|
package libsignalgo
|
||||||
|
|
||||||
const Version = "v0.93.2"
|
const Version = "v0.84.0"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ func (mc *MessageConverter) ToSignal(
|
||||||
portal *bridgev2.Portal,
|
portal *bridgev2.Portal,
|
||||||
evt *event.Event,
|
evt *event.Event,
|
||||||
content *event.MessageEventContent,
|
content *event.MessageEventContent,
|
||||||
|
timestamp uint64,
|
||||||
relaybotFormatted bool,
|
relaybotFormatted bool,
|
||||||
replyTo *database.Message,
|
replyTo *database.Message,
|
||||||
) (*signalpb.DataMessage, error) {
|
) (*signalpb.DataMessage, error) {
|
||||||
|
|
@ -54,6 +55,7 @@ func (mc *MessageConverter) ToSignal(
|
||||||
}
|
}
|
||||||
|
|
||||||
dm := &signalpb.DataMessage{
|
dm := &signalpb.DataMessage{
|
||||||
|
Timestamp: ×tamp,
|
||||||
Preview: mc.convertURLPreviewToSignal(ctx, content),
|
Preview: mc.convertURLPreviewToSignal(ctx, content),
|
||||||
}
|
}
|
||||||
if replyTo != nil {
|
if replyTo != nil {
|
||||||
|
|
@ -61,7 +63,7 @@ func (mc *MessageConverter) ToSignal(
|
||||||
if err == nil {
|
if err == nil {
|
||||||
dm.Quote = &signalpb.DataMessage_Quote{
|
dm.Quote = &signalpb.DataMessage_Quote{
|
||||||
Id: proto.Uint64(messageID),
|
Id: proto.Uint64(messageID),
|
||||||
AuthorAciBinary: authorACI[:],
|
AuthorAci: proto.String(authorACI.String()),
|
||||||
Type: signalpb.DataMessage_Quote_NORMAL.Enum(),
|
Type: signalpb.DataMessage_Quote_NORMAL.Enum(),
|
||||||
}
|
}
|
||||||
if replyTo.Metadata.(*signalid.MessageMetadata).ContainsAttachments {
|
if replyTo.Metadata.(*signalid.MessageMetadata).ContainsAttachments {
|
||||||
|
|
@ -110,9 +112,6 @@ func (mc *MessageConverter) ToSignal(
|
||||||
return nil, fmt.Errorf("failed to convert sticker: %w", err)
|
return nil, fmt.Errorf("failed to convert sticker: %w", err)
|
||||||
}
|
}
|
||||||
att.Flags = proto.Uint32(uint32(signalpb.AttachmentPointer_BORDERLESS))
|
att.Flags = proto.Uint32(uint32(signalpb.AttachmentPointer_BORDERLESS))
|
||||||
|
|
||||||
dm.Sticker = ParseStickerMeta(content.Info.BridgedSticker)
|
|
||||||
if dm.Sticker == nil {
|
|
||||||
var emoji *string
|
var emoji *string
|
||||||
// TODO check for single grapheme cluster?
|
// TODO check for single grapheme cluster?
|
||||||
if len([]rune(content.Body)) == 1 {
|
if len([]rune(content.Body)) == 1 {
|
||||||
|
|
@ -124,10 +123,10 @@ func (mc *MessageConverter) ToSignal(
|
||||||
PackId: make([]byte, 16),
|
PackId: make([]byte, 16),
|
||||||
PackKey: make([]byte, 32),
|
PackKey: make([]byte, 32),
|
||||||
StickerId: proto.Uint32(0),
|
StickerId: proto.Uint32(0),
|
||||||
|
|
||||||
|
Data: att,
|
||||||
Emoji: emoji,
|
Emoji: emoji,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
dm.Sticker.Data = att
|
|
||||||
case event.MsgLocation:
|
case event.MsgLocation:
|
||||||
lat, lon, err := parseGeoURI(content.GeoURI)
|
lat, lon, err := parseGeoURI(content.GeoURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -81,16 +81,6 @@ func BackupToDataMessage(ci *backuppb.ChatItem, attMap AttachmentMap) (*signalpb
|
||||||
Emoji: ti.StickerMessage.Sticker.Emoji,
|
Emoji: ti.StickerMessage.Sticker.Emoji,
|
||||||
Data: backupToSignalAttachment(ti.StickerMessage.Sticker.Data, 0, uuid.New(), attMap),
|
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:
|
case *backuppb.ChatItem_RemoteDeletedMessage:
|
||||||
// TODO handle some other way? (also disappeared view-once messages)
|
// TODO handle some other way? (also disappeared view-once messages)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -248,7 +238,11 @@ func backupToSignalBodyRange(from *backuppb.BodyRange) *signalpb.BodyRange {
|
||||||
out.Length = &from.Length
|
out.Length = &from.Length
|
||||||
switch av := from.AssociatedValue.(type) {
|
switch av := from.AssociatedValue.(type) {
|
||||||
case *backuppb.BodyRange_MentionAci:
|
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_:
|
case *backuppb.BodyRange_Style_:
|
||||||
out.AssociatedValue = &signalpb.BodyRange_Style_{Style: signalpb.BodyRange_Style(av.Style)}
|
out.AssociatedValue = &signalpb.BodyRange_Style_{Style: signalpb.BodyRange_Style(av.Style)}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -54,7 +51,7 @@ func calculateLength(dm *signalpb.DataMessage) int {
|
||||||
if dm.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0 {
|
if dm.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if dm.Sticker != nil || dm.PollVote != nil || dm.PollCreate != nil || dm.PollTerminate != nil {
|
if dm.Sticker != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
length := len(dm.Attachments) + len(dm.Contact)
|
length := len(dm.Attachments) + len(dm.Contact)
|
||||||
|
|
@ -78,13 +75,11 @@ func CanConvertSignal(dm *signalpb.DataMessage) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ViewOnceDisappearTimer = 5 * time.Minute
|
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(
|
func (mc *MessageConverter) ToMatrix(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
client *signalmeow.Client,
|
client *signalmeow.Client,
|
||||||
portal *bridgev2.Portal,
|
portal *bridgev2.Portal,
|
||||||
sender uuid.UUID,
|
|
||||||
intent bridgev2.MatrixAPI,
|
intent bridgev2.MatrixAPI,
|
||||||
dm *signalpb.DataMessage,
|
dm *signalpb.DataMessage,
|
||||||
attMap AttachmentMap,
|
attMap AttachmentMap,
|
||||||
|
|
@ -113,20 +108,8 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
// Don't allow any other parts in a sticker message
|
// Don't allow any other parts in a sticker message
|
||||||
return cm
|
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() {
|
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))
|
cm.Parts = append(cm.Parts, mc.convertAttachmentToMatrix(ctx, i, att, attMap))
|
||||||
} else {
|
} else {
|
||||||
longBody, err := mc.downloadSignalLongText(ctx, att, attMap)
|
longBody, err := mc.downloadSignalLongText(ctx, att, attMap)
|
||||||
|
|
@ -177,12 +160,9 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dm.Quote != nil {
|
if dm.Quote != nil {
|
||||||
authorACI, err := signalmeow.ParseStringOrBinaryUUID(dm.Quote.GetAuthorAci(), dm.Quote.GetAuthorAciBinary())
|
authorACI, err := uuid.Parse(dm.Quote.GetAuthorAci())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).
|
zerolog.Ctx(ctx).Err(err).Str("author_aci", dm.Quote.GetAuthorAci()).Msg("Failed to parse quote author ACI")
|
||||||
Str("author_aci", dm.Quote.GetAuthorAci()).
|
|
||||||
Hex("author_aci_binary", dm.Quote.GetAuthorAciBinary()).
|
|
||||||
Msg("Failed to parse quote author ACI")
|
|
||||||
} else {
|
} else {
|
||||||
cm.ReplyTo = &networkid.MessageOptionalPartID{
|
cm.ReplyTo = &networkid.MessageOptionalPartID{
|
||||||
MessageID: signalid.MakeMessageID(authorACI, dm.Quote.GetId()),
|
MessageID: signalid.MakeMessageID(authorACI, dm.Quote.GetId()),
|
||||||
|
|
@ -343,7 +323,7 @@ func (mc *MessageConverter) convertContactToVCard(ctx context.Context, contact *
|
||||||
card.Add(vcard.FieldTelephone, &field)
|
card.Add(vcard.FieldTelephone, &field)
|
||||||
}
|
}
|
||||||
if contact.GetAvatar().GetAvatar() != nil {
|
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 {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to download contact avatar")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to download contact avatar")
|
||||||
} else {
|
} 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
|
// Signal stickers are 512x512, so tell Matrix clients to render them as 256x256
|
||||||
// https://github.com/signalapp/Signal-Desktop/blob/v7.77.0-beta.1/ts/components/conversation/Message.dom.tsx#L135
|
|
||||||
if converted.Content.Info.Width == 512 && converted.Content.Info.Height == 512 {
|
if converted.Content.Info.Width == 512 && converted.Content.Info.Height == 512 {
|
||||||
converted.Content.Info.Width = 200
|
converted.Content.Info.Width = 256
|
||||||
converted.Content.Info.Height = 200
|
converted.Content.Info.Height = 256
|
||||||
}
|
}
|
||||||
converted.Content.Body = sticker.GetEmoji()
|
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.Type = event.EventSticker
|
||||||
converted.Content.MsgType = ""
|
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
|
return converted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MessageConverter) downloadSignalLongText(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*string, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -508,9 +491,7 @@ func checkIfAttachmentExists(att *signalpb.AttachmentPointer, attMap AttachmentM
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MessageConverter) downloadAttachment(
|
func (mc *MessageConverter) downloadAttachment(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) ([]byte, error) {
|
||||||
ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap, into *os.File,
|
|
||||||
) ([]byte, error) {
|
|
||||||
if err := checkIfAttachmentExists(att, attMap); err != nil {
|
if err := checkIfAttachmentExists(att, attMap); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -521,19 +502,19 @@ func (mc *MessageConverter) downloadAttachment(
|
||||||
plaintextHash = target.GetPlaintextHash()
|
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) {
|
func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*bridgev2.ConvertedMessagePart, error) {
|
||||||
|
fileName := att.GetFileName()
|
||||||
content := &event.MessageEventContent{
|
content := &event.MessageEventContent{
|
||||||
Body: att.GetFileName(),
|
|
||||||
Info: &event.FileInfo{
|
Info: &event.FileInfo{
|
||||||
MimeType: att.GetContentType(),
|
|
||||||
Width: int(att.GetWidth()),
|
Width: int(att.GetWidth()),
|
||||||
Height: int(att.GetHeight()),
|
Height: int(att.GetHeight()),
|
||||||
Size: int(att.GetSize()),
|
Size: int(att.GetSize()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
mimeType := att.GetContentType()
|
||||||
if err := checkIfAttachmentExists(att, attMap); err != nil {
|
if err := checkIfAttachmentExists(att, attMap); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if mc.DirectMedia {
|
} 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)
|
content.URL, err = mc.Bridge.Matrix.GenerateContentURI(ctx, mediaID)
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -569,7 +568,7 @@ func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalp
|
||||||
content.Info.Blurhash = att.GetBlurHash()
|
content.Info.Blurhash = att.GetBlurHash()
|
||||||
content.Info.AnoaBlurhash = att.GetBlurHash()
|
content.Info.AnoaBlurhash = att.GetBlurHash()
|
||||||
}
|
}
|
||||||
switch strings.Split(content.Info.MimeType, "/")[0] {
|
switch strings.Split(mimeType, "/")[0] {
|
||||||
case "image":
|
case "image":
|
||||||
content.MsgType = event.MsgImage
|
content.MsgType = event.MsgImage
|
||||||
case "video":
|
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 == "" {
|
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{
|
return &bridgev2.ConvertedMessagePart{
|
||||||
Type: event.EventMessage,
|
Type: event.EventMessage,
|
||||||
|
|
@ -600,186 +601,3 @@ func (mc *MessageConverter) reuploadAttachment(ctx context.Context, att *signalp
|
||||||
Extra: extra,
|
Extra: extra,
|
||||||
}, nil
|
}, 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
|
LocationFormat string
|
||||||
DisappearViewOnce bool
|
DisappearViewOnce bool
|
||||||
DirectMedia bool
|
DirectMedia bool
|
||||||
ExtEvPolls bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessageConverter(br *bridgev2.Bridge) *MessageConverter {
|
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 {
|
GetUUIDFromMXID: func(ctx context.Context, userID id.UserID) uuid.UUID {
|
||||||
parsed, ok := br.Matrix.ParseGhostMXID(userID)
|
parsed, ok := br.Matrix.ParseGhostMXID(userID)
|
||||||
if ok {
|
if ok {
|
||||||
u, _ := signalid.ParseUserID(parsed)
|
u, _ := uuid.Parse(string(parsed))
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
user, _ := br.GetExistingUserByMXID(ctx, userID)
|
user, _ := br.GetExistingUserByMXID(ctx, userID)
|
||||||
|
|
@ -86,7 +85,7 @@ func NewMessageConverter(br *bridgev2.Bridge) *MessageConverter {
|
||||||
if user != nil {
|
if user != nil {
|
||||||
preferredLogin, _, _ := getPortal(ctx).FindPreferredLogin(ctx, user, true)
|
preferredLogin, _, _ := getPortal(ctx).FindPreferredLogin(ctx, user, true)
|
||||||
if preferredLogin != nil {
|
if preferredLogin != nil {
|
||||||
u, _ := signalid.ParseUserLoginID(preferredLogin.ID)
|
u, _ := uuid.Parse(string(preferredLogin.ID))
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
@ -86,27 +85,15 @@ func Parse(ctx context.Context, message string, ranges []*signalpb.BodyRange, pa
|
||||||
Start: int(*r.Start),
|
Start: int(*r.Start),
|
||||||
Length: int(*r.Length),
|
Length: int(*r.Length),
|
||||||
}.TruncateEnd(maxLength)
|
}.TruncateEnd(maxLength)
|
||||||
var mentionACI uuid.UUID
|
|
||||||
switch rv := r.GetAssociatedValue().(type) {
|
switch rv := r.GetAssociatedValue().(type) {
|
||||||
case *signalpb.BodyRange_Style_:
|
case *signalpb.BodyRange_Style_:
|
||||||
br.Value = Style(rv.Style)
|
br.Value = Style(rv.Style)
|
||||||
case *signalpb.BodyRange_MentionAci:
|
case *signalpb.BodyRange_MentionAci:
|
||||||
var err error
|
parsed, err := uuid.Parse(rv.MentionAci)
|
||||||
mentionACI, err = uuid.Parse(rv.MentionAci)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case *signalpb.BodyRange_MentionAciBinary:
|
userInfo := params.GetUserInfo(ctx, parsed)
|
||||||
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)
|
|
||||||
if userInfo.MXID == "" {
|
if userInfo.MXID == "" {
|
||||||
continue
|
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,
|
// Maybe use NewUTF16String and do index replacements for the plaintext body too,
|
||||||
// or just replace the plaintext body by parsing the generated HTML.
|
// or just replace the plaintext body by parsing the generated HTML.
|
||||||
content.Body = strings.Replace(content.Body, "\uFFFC", userInfo.Name, 1)
|
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)
|
lrt.Add(br)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ func (m Mention) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Mention) Proto() signalpb.BodyRangeAssociatedValue {
|
func (m Mention) Proto() signalpb.BodyRangeAssociatedValue {
|
||||||
return &signalpb.BodyRange_MentionAciBinary{
|
return &signalpb.BodyRange_MentionAci{
|
||||||
MentionAciBinary: m.UUID[:],
|
MentionAci: m.UUID.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,10 @@ type PortalMetadata struct {
|
||||||
|
|
||||||
type MessageMetadata struct {
|
type MessageMetadata struct {
|
||||||
ContainsAttachments bool `json:"contains_attachments,omitempty"`
|
ContainsAttachments bool `json:"contains_attachments,omitempty"`
|
||||||
MatrixPollOptionIDs []string `json:"matrix_poll_option_ids,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserLoginMetadata struct {
|
type UserLoginMetadata struct {
|
||||||
ChatsSynced bool `json:"chats_synced,omitempty"`
|
ChatsSynced bool `json:"chats_synced,omitempty"`
|
||||||
LastContactSync jsontime.UnixMilli `json:"last_contact_sync,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GhostMetadata struct {
|
type GhostMetadata struct {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ const (
|
||||||
directMediaTypeGroupAvatar directMediaType = 1
|
directMediaTypeGroupAvatar directMediaType = 1
|
||||||
directMediaTypeProfileAvatar directMediaType = 2
|
directMediaTypeProfileAvatar directMediaType = 2
|
||||||
directMediaTypePlaintextDigestAttachment directMediaType = 3
|
directMediaTypePlaintextDigestAttachment directMediaType = 3
|
||||||
directMediaTypeSticker directMediaType = 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DirectMediaInfo interface {
|
type DirectMediaInfo interface {
|
||||||
|
|
@ -45,7 +44,6 @@ var (
|
||||||
_ DirectMediaInfo = (*DirectMediaAttachment)(nil)
|
_ DirectMediaInfo = (*DirectMediaAttachment)(nil)
|
||||||
_ DirectMediaInfo = (*DirectMediaGroupAvatar)(nil)
|
_ DirectMediaInfo = (*DirectMediaGroupAvatar)(nil)
|
||||||
_ DirectMediaInfo = (*DirectMediaProfileAvatar)(nil)
|
_ DirectMediaInfo = (*DirectMediaProfileAvatar)(nil)
|
||||||
_ DirectMediaInfo = (*DirectMediaSticker)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DirectMediaAttachment struct {
|
type DirectMediaAttachment struct {
|
||||||
|
|
@ -129,30 +127,6 @@ func (m DirectMediaProfileAvatar) AsMediaID() (mediaID networkid.MediaID, err er
|
||||||
return networkid.MediaID(buf.Bytes()), nil
|
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) {
|
func ParseDirectMediaInfo(mediaID networkid.MediaID) (_ DirectMediaInfo, err error) {
|
||||||
mediaIDLen := len(mediaID)
|
mediaIDLen := len(mediaID)
|
||||||
if mediaIDLen == 0 {
|
if mediaIDLen == 0 {
|
||||||
|
|
@ -226,15 +200,6 @@ func ParseDirectMediaInfo(mediaID networkid.MediaID) (_ DirectMediaInfo, err err
|
||||||
info.ProfileAvatarPath = string(profileAvatarPath)
|
info.ProfileAvatarPath = string(profileAvatarPath)
|
||||||
}
|
}
|
||||||
return &info, nil
|
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)
|
return nil, fmt.Errorf("invalid direct media type %d", mediaType)
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,8 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/fallocate"
|
|
||||||
"go.mau.fi/util/pkcs7"
|
|
||||||
"go.mau.fi/util/random"
|
"go.mau.fi/util/random"
|
||||||
"google.golang.org/protobuf/proto"
|
"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 ErrInvalidDigestForAttachment = errors.New("invalid digest for attachment")
|
||||||
var ErrAttachmentNotFound = errors.New("attachment not found on server")
|
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()
|
digest := a.GetDigest()
|
||||||
plaintextDigest := false
|
plaintextDigest := false
|
||||||
if digest == nil && plaintextHash != nil {
|
if digest == nil && plaintextHash != nil {
|
||||||
digest = plaintextHash
|
digest = plaintextHash
|
||||||
plaintextDigest = true
|
plaintextDigest = true
|
||||||
}
|
}
|
||||||
return DownloadAttachment(
|
return DownloadAttachment(ctx, a.GetCdnId(), a.GetCdnKey(), a.GetCdnNumber(), a.Key, digest, plaintextDigest, a.GetSize())
|
||||||
ctx, a.GetCdnId(), a.GetCdnKey(), a.GetCdnNumber(), a.Key, digest, plaintextDigest, a.GetSize(), into,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DownloadAttachment(
|
func DownloadAttachment(ctx context.Context, cdnID uint64, cdnKey string, cdnNumber uint32, key, digest []byte, plaintextDigest bool, size uint32) ([]byte, error) {
|
||||||
ctx context.Context,
|
path := getAttachmentPath(cdnID, cdnKey)
|
||||||
cdnID uint64,
|
resp, err := web.GetAttachment(ctx, path, cdnNumber, nil)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func() {
|
bodyReader := resp.Body
|
||||||
_ = resp.Body.Close()
|
defer bodyReader.Close()
|
||||||
}()
|
|
||||||
|
|
||||||
var body []byte
|
body, err := io.ReadAll(bodyReader)
|
||||||
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)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -125,27 +94,12 @@ func DownloadAttachment(
|
||||||
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
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)
|
return decryptAttachment(body, key, digest, plaintextDigest, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MACLength = 32
|
const MACLength = 32
|
||||||
const IVLength = 16
|
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) {
|
func decryptAttachment(body, key, digest []byte, plaintextDigest bool, size uint32) ([]byte, error) {
|
||||||
if !plaintextDigest {
|
if !plaintextDigest {
|
||||||
hash := sha256.Sum256(body)
|
hash := sha256.Sum256(body)
|
||||||
|
|
@ -153,7 +107,12 @@ func decryptAttachment(body, key, digest []byte, plaintextDigest bool, size uint
|
||||||
return nil, ErrInvalidDigestForAttachment
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -170,59 +129,6 @@ func decryptAttachment(body, key, digest []byte, plaintextDigest bool, size uint
|
||||||
return decrypted, nil
|
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 {
|
type attachmentV4UploadAttributes struct {
|
||||||
Cdn uint32 `json:"cdn"`
|
Cdn uint32 `json:"cdn"`
|
||||||
Key string `json:"key"`
|
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) {
|
func (cli *Client) UploadAttachment(ctx context.Context, body []byte) (*signalpb.AttachmentPointer, error) {
|
||||||
log := zerolog.Ctx(ctx).With().Str("func", "upload attachment").Logger()
|
log := zerolog.Ctx(ctx).With().Str("func", "upload attachment").Logger()
|
||||||
keys := random.Bytes(64) // combined AES and MAC keys
|
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)
|
body = extend(body, paddedLen)
|
||||||
|
|
||||||
encryptedWithMAC, err := macAndAESEncrypt(keys, body)
|
encrypted, err := aesEncrypt(keys[:32], body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
encryptedWithMAC := appendMAC(keys[32:], encrypted)
|
||||||
|
|
||||||
// Get upload attributes from Signal server
|
// Get upload attributes from Signal server
|
||||||
attributesPath := "/v4/attachments/form/upload"
|
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 {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to request upload attributes")
|
log.Err(err).Msg("Failed to request upload attributes")
|
||||||
return nil, fmt.Errorf("failed to request upload attributes: %w", err)
|
return nil, fmt.Errorf("failed to request upload attributes: %w", err)
|
||||||
}
|
}
|
||||||
var uploadAttributes attachmentV4UploadAttributes
|
var uploadAttributes attachmentV4UploadAttributes
|
||||||
err = web.DecodeWSResponseBody(ctx, &uploadAttributes, resp)
|
err = web.DecodeHTTPResponseBody(ctx, &uploadAttributes, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to decode upload attributes")
|
log.Err(err).Msg("Failed to decode upload attributes")
|
||||||
return nil, fmt.Errorf("failed to decode upload attributes: %w", err)
|
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)
|
err = cli.uploadAttachmentTUS(ctx, uploadAttributes, encryptedWithMAC)
|
||||||
} else {
|
} else {
|
||||||
log.Trace().Msg("Using legacy upload")
|
log.Trace().Msg("Using legacy upload")
|
||||||
err = cli.uploadAttachmentLegacy(ctx, uploadAttributes, encryptedWithMAC)
|
err = cli.uploadAttachmentLegacy(ctx, uploadAttributes, encryptedWithMAC, username, password)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to upload attachment")
|
log.Err(err).Msg("Failed to upload attachment")
|
||||||
|
|
@ -317,17 +218,17 @@ func (cli *Client) uploadAttachmentLegacy(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
uploadAttributes attachmentV4UploadAttributes,
|
uploadAttributes attachmentV4UploadAttributes,
|
||||||
encryptedWithMAC []byte,
|
encryptedWithMAC []byte,
|
||||||
|
username string,
|
||||||
|
password string,
|
||||||
) error {
|
) error {
|
||||||
username, password := cli.Store.BasicAuthCreds()
|
|
||||||
// Allocate attachment on CDN
|
// 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,
|
OverrideURL: uploadAttributes.SignedUploadLocation,
|
||||||
ContentType: web.ContentTypeOctetStream,
|
ContentType: web.ContentTypeOctetStream,
|
||||||
Headers: uploadAttributes.Headers,
|
Headers: uploadAttributes.Headers,
|
||||||
Username: &username,
|
Username: &username,
|
||||||
Password: &password,
|
Password: &password,
|
||||||
})
|
})
|
||||||
web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send allocate request: %w", err)
|
return fmt.Errorf("failed to send allocate request: %w", err)
|
||||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
|
@ -335,14 +236,13 @@ func (cli *Client) uploadAttachmentLegacy(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload attachment to CDN
|
// 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"),
|
OverrideURL: resp.Header.Get("Location"),
|
||||||
Body: encryptedWithMAC,
|
Body: encryptedWithMAC,
|
||||||
ContentType: web.ContentTypeOctetStream,
|
ContentType: web.ContentTypeOctetStream,
|
||||||
Username: &username,
|
Username: &username,
|
||||||
Password: &password,
|
Password: &password,
|
||||||
})
|
})
|
||||||
web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send upload request: %w", err)
|
return fmt.Errorf("failed to send upload request: %w", err)
|
||||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
} 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-Length"] = fmt.Sprintf("%d", len(encryptedWithMAC))
|
||||||
uploadAttributes.Headers["Upload-Metadata"] = "filename " + base64.StdEncoding.EncodeToString([]byte(uploadAttributes.Key))
|
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,
|
OverrideURL: uploadAttributes.SignedUploadLocation,
|
||||||
Body: encryptedWithMAC,
|
Body: encryptedWithMAC,
|
||||||
ContentType: web.ContentTypeOffsetOctetStream,
|
ContentType: web.ContentTypeOffsetOctetStream,
|
||||||
Headers: uploadAttributes.Headers,
|
Headers: uploadAttributes.Headers,
|
||||||
})
|
})
|
||||||
web.CloseBody(resp)
|
|
||||||
// TODO actually support resuming on error
|
// TODO actually support resuming on error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send upload request: %w", err)
|
return fmt.Errorf("failed to send upload request: %w", err)
|
||||||
|
|
@ -381,17 +280,12 @@ func (cli *Client) uploadAttachmentTUS(
|
||||||
return nil
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
if groupMasterKey == "" {
|
groupMasterKey, err := cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
||||||
var err error
|
|
||||||
groupMasterKey, err = cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Could not get master key from group id")
|
log.Err(err).Msg("Could not get master key from group id")
|
||||||
return "", err
|
return "", err
|
||||||
} else if groupMasterKey == "" {
|
|
||||||
return "", fmt.Errorf("no master key found for group %s", gid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyToBytes(groupMasterKey))
|
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyToBytes(groupMasterKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -411,15 +305,14 @@ func (cli *Client) UploadGroupAvatar(ctx context.Context, avatarBytes []byte, gi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get upload form from Signal server
|
// Get upload form from Signal server
|
||||||
formPath := "/v2/groups/avatar/form"
|
formPath := "/v1/groups/avatar/form"
|
||||||
opts := &web.HTTPReqOpt{Username: &groupAuth.Username, Password: &groupAuth.Password, ContentType: web.ContentTypeProtobuf}
|
opts := &web.HTTPReqOpt{Username: &groupAuth.Username, Password: &groupAuth.Password, ContentType: web.ContentTypeProtobuf, Host: web.StorageHostname}
|
||||||
resp, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, formPath, opts)
|
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, formPath, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error sending request fetching avatar upload form")
|
log.Err(err).Msg("Error sending request fetching avatar upload form")
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error decoding response body fetching upload attributes")
|
log.Err(err).Msg("Error decoding response body fetching upload attributes")
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -445,11 +338,11 @@ func (cli *Client) UploadGroupAvatar(ctx context.Context, avatarBytes []byte, gi
|
||||||
w.Close()
|
w.Close()
|
||||||
|
|
||||||
// Upload avatar to CDN
|
// 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(),
|
Body: requestBody.Bytes(),
|
||||||
ContentType: web.ContentType(w.FormDataContentType()),
|
ContentType: web.ContentType(w.FormDataContentType()),
|
||||||
|
Host: web.CDN1Hostname,
|
||||||
})
|
})
|
||||||
web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error sending request uploading attachment")
|
log.Err(err).Msg("Error sending request uploading attachment")
|
||||||
return "", err
|
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)
|
return nil, fmt.Errorf("ciphertext not multiple of AES blocksize (%d extra bytes)", len(ciphertext)%aes.BlockSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := ciphertext[:IVLength]
|
iv := ciphertext[:aes.BlockSize]
|
||||||
ciphertext = ciphertext[IVLength:]
|
|
||||||
mode := cipher.NewCBCDecrypter(block, iv)
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
mode.CryptBlocks(ciphertext, ciphertext)
|
mode.CryptBlocks(ciphertext, ciphertext)
|
||||||
return pkcs7.Unpad(ciphertext)
|
pad := ciphertext[len(ciphertext)-1]
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pad > aes.BlockSize {
|
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 {
|
func appendMAC(key, body []byte) []byte {
|
||||||
|
|
@ -542,11 +393,14 @@ func aesEncrypt(key, plaintext []byte) ([]byte, error) {
|
||||||
return nil, err
|
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)
|
iv := random.Bytes(16)
|
||||||
|
|
||||||
mode := cipher.NewCBCEncrypter(block, iv)
|
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 {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download transfer archive: %w", err)
|
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)
|
reqDuration := time.Since(reqStart)
|
||||||
if reqDuration < reqTimeout-10*time.Second {
|
if reqDuration < reqTimeout-10*time.Second {
|
||||||
select {
|
time.Sleep(15 * time.Second)
|
||||||
case <-time.After(15 * time.Second):
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,14 +291,21 @@ func (cli *Client) tryRequestTransferArchive(ctx context.Context, timeout time.D
|
||||||
reqCtx, cancel := context.WithTimeout(ctx, timeout+15*time.Second)
|
reqCtx, cancel := context.WithTimeout(ctx, timeout+15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
path := "/v1/devices/transfer_archive?timeout=" + strconv.Itoa(int(timeout.Seconds()))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if resp.GetStatus() == http.StatusNoContent {
|
} else if resp.StatusCode == http.StatusNoContent {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else if resp.GetStatus() != http.StatusOK {
|
} else if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("unexpected status code %d", resp.GetStatus())
|
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||||
} else if err = json.Unmarshal(resp.Body, &respBody); err != nil {
|
} else if err = json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
} else {
|
} else {
|
||||||
return respBody, nil
|
return respBody, nil
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,16 @@ package signalmeow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exsync"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/events"
|
"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/store"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,14 +35,12 @@ type Client struct {
|
||||||
Store *store.Device
|
Store *store.Device
|
||||||
Log zerolog.Logger
|
Log zerolog.Logger
|
||||||
|
|
||||||
senderCertificateWithE164 *libsignalgo.SenderCertificate
|
SenderCertificateWithE164 *libsignalgo.SenderCertificate
|
||||||
senderCertificateNoE164 *libsignalgo.SenderCertificate
|
SenderCertificateNoE164 *libsignalgo.SenderCertificate
|
||||||
senderCertificateCache sync.Mutex
|
GroupCredentials *GroupCredentials
|
||||||
|
|
||||||
sendCache *exsync.RingBuffer[sendCacheKey, *signalpb.Content]
|
|
||||||
|
|
||||||
GroupCache *GroupCache
|
GroupCache *GroupCache
|
||||||
ProfileCache *ProfileCache
|
ProfileCache *ProfileCache
|
||||||
|
GroupCallCache *map[string]bool
|
||||||
LastContactRequestTime time.Time
|
LastContactRequestTime time.Time
|
||||||
SyncContactsOnConnect bool
|
SyncContactsOnConnect bool
|
||||||
|
|
||||||
|
|
@ -71,26 +64,6 @@ type Client struct {
|
||||||
writeCallbackCounter chan time.Time
|
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 {
|
func (cli *Client) handleEvent(evt events.SignalEvent) bool {
|
||||||
return cli.EventHandler(evt)
|
return cli.EventHandler(evt)
|
||||||
}
|
}
|
||||||
|
|
@ -137,11 +110,3 @@ func (cli *Client) connectUnauthedWS(ctx context.Context) (chan web.SignalWebsoc
|
||||||
func (cli *Client) IsLoggedIn() bool {
|
func (cli *Client) IsLoggedIn() bool {
|
||||||
return cli.Store != nil && cli.Store.IsDeviceLoggedIn()
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import (
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
||||||
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
func hmacSHA256(key, input []byte) []byte {
|
func hmacSHA256(key, input []byte) []byte {
|
||||||
|
|
@ -62,12 +63,18 @@ func (cli *Client) updateDeviceName(ctx context.Context, encryptedName []byte) e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal device name update request: %w", err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send device name update request: %w", err)
|
return fmt.Errorf("failed to send device name update request: %w", err)
|
||||||
}
|
}
|
||||||
if resp.GetStatus() < 200 || resp.GetStatus() >= 300 {
|
defer resp.Body.Close()
|
||||||
return fmt.Errorf("device name update request returned status %d", resp.GetStatus())
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("device name update request returned status %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ func (*Call) isSignalEvent() {}
|
||||||
func (*ContactList) isSignalEvent() {}
|
func (*ContactList) isSignalEvent() {}
|
||||||
func (*ACIFound) isSignalEvent() {}
|
func (*ACIFound) isSignalEvent() {}
|
||||||
func (*DeleteForMe) isSignalEvent() {}
|
func (*DeleteForMe) isSignalEvent() {}
|
||||||
func (*MessageRequestResponse) isSignalEvent() {}
|
|
||||||
func (*QueueEmpty) isSignalEvent() {}
|
func (*QueueEmpty) isSignalEvent() {}
|
||||||
func (*LoggedOut) isSignalEvent() {}
|
func (*LoggedOut) isSignalEvent() {}
|
||||||
|
|
||||||
|
|
@ -90,14 +89,6 @@ type DeleteForMe struct {
|
||||||
*signalpb.SyncMessage_DeleteForMe
|
*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 QueueEmpty struct{}
|
||||||
|
|
||||||
type LoggedOut struct{ Error error }
|
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/google/uuid"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exslices"
|
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/util/random"
|
"go.mau.fi/util/random"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
@ -91,12 +90,6 @@ type Group struct {
|
||||||
//PublicKey *libsignalgo.PublicKey
|
//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) {
|
func (group *Group) GetInviteLink() (string, error) {
|
||||||
if group.InviteLinkPassword == nil {
|
if group.InviteLinkPassword == nil {
|
||||||
return "", fmt.Errorf("no invite link password set")
|
return "", fmt.Errorf("no invite link password set")
|
||||||
|
|
@ -106,8 +99,8 @@ func (group *Group) GetInviteLink() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("couldn't decode invite link password")
|
return "", fmt.Errorf("couldn't decode invite link password")
|
||||||
}
|
}
|
||||||
inviteLinkContents := signalpb.GroupInviteLink_ContentsV1{
|
inviteLinkContents := signalpb.GroupInviteLink_V1Contents{
|
||||||
ContentsV1: &signalpb.GroupInviteLink_GroupInviteLinkContentsV1{
|
V1Contents: &signalpb.GroupInviteLink_GroupInviteLinkContentsV1{
|
||||||
GroupMasterKey: masterKeyBytes[:],
|
GroupMasterKey: masterKeyBytes[:],
|
||||||
InviteLinkPassword: inviteLinkPasswordBytes,
|
InviteLinkPassword: inviteLinkPasswordBytes,
|
||||||
},
|
},
|
||||||
|
|
@ -121,15 +114,6 @@ func (group *Group) GetInviteLink() (string, error) {
|
||||||
return "https://signal.group/#" + inviteLinkPath, nil
|
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 {
|
type GroupAccessControl struct {
|
||||||
Members AccessControl
|
Members AccessControl
|
||||||
AddFromInviteLink AccessControl
|
AddFromInviteLink AccessControl
|
||||||
|
|
@ -226,7 +210,8 @@ func (groupChange *GroupChange) isEmpty() bool {
|
||||||
len(groupChange.PromoteRequestingMembers) == 0 &&
|
len(groupChange.PromoteRequestingMembers) == 0 &&
|
||||||
groupChange.ModifyDescription == nil &&
|
groupChange.ModifyDescription == nil &&
|
||||||
groupChange.ModifyAnnouncementsOnly == nil &&
|
groupChange.ModifyAnnouncementsOnly == nil &&
|
||||||
len(groupChange.AddBannedMembers) == 0
|
len(groupChange.AddBannedMembers) == 0 &&
|
||||||
|
len(groupChange.DeleteMembers) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (groupChange *GroupChange) resolveConflict(group *Group) {
|
func (groupChange *GroupChange) resolveConflict(group *Group) {
|
||||||
|
|
@ -329,7 +314,8 @@ func (cli *Client) fetchNewGroupCreds(ctx context.Context, today time.Time) (*Gr
|
||||||
Logger()
|
Logger()
|
||||||
sevenDaysOut := today.Add(7 * 24 * time.Hour)
|
sevenDaysOut := today.Add(7 * 24 * time.Hour)
|
||||||
path := fmt.Sprintf("/v1/certificate/auth/group?redemptionStartSeconds=%d&redemptionEndSeconds=%d&pniAsServiceId=true", today.Unix(), sevenDaysOut.Unix())
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SendRequest error: %w", err)
|
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")
|
log.Err(err).Msg("json.Unmarshal error")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// make sure pni matches device pni
|
||||||
if creds.PNI != cli.Store.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
|
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) {
|
func (cli *Client) GetAuthorizationForToday(ctx context.Context, masterKey libsignalgo.GroupMasterKey) (*GroupAuth, error) {
|
||||||
log := zerolog.Ctx(ctx).With().
|
log := zerolog.Ctx(ctx).With().
|
||||||
Str("action", "get authorization for today").
|
Str("action", "get authorization for today").
|
||||||
Logger()
|
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)
|
todayCred := cli.getCachedAuthorizationForToday(today)
|
||||||
|
if todayCred == nil {
|
||||||
|
creds, err := cli.fetchNewGroupCreds(ctx, today)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get group credentials: %w", err)
|
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)
|
redemptionTime := uint64(todayCred.RedemptionTime)
|
||||||
credential := todayCred.Credential
|
credential := todayCred.Credential
|
||||||
authCredentialResponse, err := libsignalgo.NewAuthCredentialWithPniResponse(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)
|
descriptionBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedGroup.Description)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// treat a failure in obtaining the description as non-fatal
|
// 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 {
|
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
|
// These aren't encrypted
|
||||||
decryptedGroup.AvatarPath = encryptedGroup.AvatarUrl
|
decryptedGroup.AvatarPath = encryptedGroup.Avatar
|
||||||
decryptedGroup.Revision = encryptedGroup.Version
|
decryptedGroup.Revision = encryptedGroup.Revision
|
||||||
|
|
||||||
// Decrypt members
|
// Decrypt members
|
||||||
for _, member := range encryptedGroup.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)
|
decryptedGroup.Members = append(decryptedGroup.Members, decryptedMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pendingMember := range encryptedGroup.MembersPendingProfileKey {
|
for _, pendingMember := range encryptedGroup.PendingMembers {
|
||||||
if pendingMember == nil {
|
if pendingMember == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -509,7 +524,7 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
||||||
decryptedGroup.PendingMembers = append(decryptedGroup.PendingMembers, decryptedPendingMember)
|
decryptedGroup.PendingMembers = append(decryptedGroup.PendingMembers, decryptedPendingMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, requestingMember := range encryptedGroup.MembersPendingAdminApproval {
|
for _, requestingMember := range encryptedGroup.RequestingMembers {
|
||||||
if requestingMember == nil {
|
if requestingMember == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +535,7 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
||||||
decryptedGroup.RequestingMembers = append(decryptedGroup.RequestingMembers, decryptedRequestingMember)
|
decryptedGroup.RequestingMembers = append(decryptedGroup.RequestingMembers, decryptedRequestingMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bannedMember := range encryptedGroup.MembersBanned {
|
for _, bannedMember := range encryptedGroup.BannedMembers {
|
||||||
if bannedMember == nil {
|
if bannedMember == nil {
|
||||||
continue
|
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)
|
return nil, fmt.Errorf("failed to get group master key: %w", err)
|
||||||
}
|
}
|
||||||
if groupMasterKey == "" {
|
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)
|
return cli.fetchGroupWithMasterKey(ctx, groupMasterKey)
|
||||||
}
|
}
|
||||||
|
|
@ -634,9 +649,9 @@ func (cli *Client) fetchGroupWithMasterKey(ctx context.Context, groupMasterKey t
|
||||||
Username: &groupAuth.Username,
|
Username: &groupAuth.Username,
|
||||||
Password: &groupAuth.Password,
|
Password: &groupAuth.Password,
|
||||||
ContentType: web.ContentTypeProtobuf,
|
ContentType: web.ContentTypeProtobuf,
|
||||||
|
Host: web.StorageHostname,
|
||||||
}
|
}
|
||||||
response, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, "/v2/groups", opts)
|
response, err := web.SendHTTPRequest(ctx, http.MethodGet, "/v2/groups", opts)
|
||||||
defer web.CloseBody(response)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -661,10 +676,6 @@ func (cli *Client) parseGroupResponse(ctx context.Context, response *http.Respon
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decrypt group: %w", err)
|
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
|
// Store the profile keys in case they're new
|
||||||
for _, member := range group.Members {
|
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) {
|
func (cli *Client) DownloadGroupAvatar(ctx context.Context, avatarPath string, groupMasterKey types.SerializedGroupMasterKey) ([]byte, error) {
|
||||||
username, password := cli.Store.BasicAuthCreds()
|
username, password := cli.Store.BasicAuthCreds()
|
||||||
opts := &web.HTTPReqOpt{
|
opts := &web.HTTPReqOpt{
|
||||||
|
Host: web.CDN1Hostname,
|
||||||
Username: &username,
|
Username: &username,
|
||||||
Password: &password,
|
Password: &password,
|
||||||
}
|
}
|
||||||
resp, err := web.SendHTTPRequest(ctx, web.CDN1Hostname, http.MethodGet, avatarPath, opts)
|
resp, err := web.SendHTTPRequest(ctx, http.MethodGet, avatarPath, opts)
|
||||||
defer web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
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
|
return decrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) RetrieveGroupByID(ctx context.Context, gid types.GroupIdentifier, revision uint32) (*Group, *SendEndorsementCache, error) {
|
func (cli *Client) RetrieveGroupByID(ctx context.Context, gid types.GroupIdentifier, revision uint32) (*Group, error) {
|
||||||
cached, endorsement, ok := cli.GroupCache.Get(gid)
|
cli.initGroupCache()
|
||||||
if ok && cached.Revision >= revision {
|
|
||||||
return cached, endorsement, nil
|
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)
|
group, err := cli.fetchGroupByID(ctx, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cached, endorsement, ok = cli.GroupCache.Get(gid)
|
cli.GroupCache.groups[gid] = group
|
||||||
if !ok {
|
cli.GroupCache.lastFetched[gid] = time.Now()
|
||||||
zerolog.Ctx(ctx).Warn().Msg("Group not found in cache after fetching")
|
return group, nil
|
||||||
return group, nil, nil
|
|
||||||
}
|
|
||||||
return cached, endorsement, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should store the group master key in the group store as soon as we see it,
|
// 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
|
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) {
|
func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalpb.GroupContextV2) (*GroupChange, error) {
|
||||||
masterKeyBytes := libsignalgo.GroupMasterKey(groupContext.MasterKey)
|
masterKeyBytes := libsignalgo.GroupMasterKey(groupContext.MasterKey)
|
||||||
groupMasterKey := masterKeyFromBytes(masterKeyBytes)
|
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()
|
log := zerolog.Ctx(ctx).With().Str("action", "decrypt group change").Logger()
|
||||||
serverSignature := encryptedGroupChange.ServerSignature
|
serverSignature := encryptedGroupChange.ServerSignature
|
||||||
encryptedActionsBytes := encryptedGroupChange.Actions
|
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
|
var err error
|
||||||
if verifySignature {
|
if verifySignature {
|
||||||
|
|
@ -788,14 +826,14 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceServiceID, err := groupSecretParams.DecryptServiceID(libsignalgo.UUIDCiphertext(encryptedActions.SourceUserId))
|
sourceServiceID, err := groupSecretParams.DecryptServiceID(libsignalgo.UUIDCiphertext(encryptedActions.SourceServiceId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Couldn't decrypt source serviceID")
|
log.Err(err).Msg("Couldn't decrypt source serviceID")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
decryptedGroupChange := &GroupChange{
|
decryptedGroupChange := &GroupChange{
|
||||||
GroupMasterKey: groupMasterKey,
|
GroupMasterKey: groupMasterKey,
|
||||||
Revision: encryptedActions.Version,
|
Revision: encryptedActions.Revision,
|
||||||
SourceServiceID: sourceServiceID,
|
SourceServiceID: sourceServiceID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -815,7 +853,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
descriptionBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDescription.Description)
|
descriptionBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDescription.Description)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// treat a failure in obtaining the description as non-fatal
|
// treat a failure in obtaining the description as non-fatal
|
||||||
newDescription := cleanupStringProperty(descriptionBlob.GetDescriptionText())
|
newDescription := cleanupStringProperty(descriptionBlob.GetDescription())
|
||||||
decryptedGroupChange.ModifyDescription = &newDescription
|
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 {
|
if addPendingMember == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -903,7 +941,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
decryptedGroupChange.AddPendingMembers = append(decryptedGroupChange.AddPendingMembers, decryptedPendingMember)
|
decryptedGroupChange.AddPendingMembers = append(decryptedGroupChange.AddPendingMembers, decryptedPendingMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, deletePendingMember := range encryptedActions.DeleteMembersPendingProfileKey {
|
for _, deletePendingMember := range encryptedActions.DeletePendingMembers {
|
||||||
if deletePendingMember == nil {
|
if deletePendingMember == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -916,7 +954,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
decryptedGroupChange.DeletePendingMembers = append(decryptedGroupChange.DeletePendingMembers, &userID)
|
decryptedGroupChange.DeletePendingMembers = append(decryptedGroupChange.DeletePendingMembers, &userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, promotePendingMember := range encryptedActions.PromoteMembersPendingProfileKey {
|
for _, promotePendingMember := range encryptedActions.PromotePendingMembers {
|
||||||
if promotePendingMember == nil {
|
if promotePendingMember == nil {
|
||||||
continue
|
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
|
// 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 {
|
if promotePendingPniAciMember == nil {
|
||||||
continue
|
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 {
|
if addRequestingMember == nil {
|
||||||
continue
|
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 {
|
if deleteRequestingMember == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -994,7 +1032,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
decryptedGroupChange.DeleteRequestingMembers = append(decryptedGroupChange.DeleteRequestingMembers, &serviceID.UUID)
|
decryptedGroupChange.DeleteRequestingMembers = append(decryptedGroupChange.DeleteRequestingMembers, &serviceID.UUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, promoteRequestingMember := range encryptedActions.PromoteMembersPendingAdminApproval {
|
for _, promoteRequestingMember := range encryptedActions.PromoteRequestingMembers {
|
||||||
if promoteRequestingMember == nil {
|
if promoteRequestingMember == nil {
|
||||||
continue
|
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 {
|
if addBannedMember == nil {
|
||||||
continue
|
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 {
|
if deleteBannedMember == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -1055,8 +1093,8 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
if encryptedActions.ModifyAnnouncementsOnly != nil {
|
if encryptedActions.ModifyAnnouncementsOnly != nil {
|
||||||
decryptedGroupChange.ModifyAnnouncementsOnly = &encryptedActions.ModifyAnnouncementsOnly.AnnouncementsOnly
|
decryptedGroupChange.ModifyAnnouncementsOnly = &encryptedActions.ModifyAnnouncementsOnly.AnnouncementsOnly
|
||||||
}
|
}
|
||||||
if encryptedActions.ModifyDisappearingMessageTimer != nil && len(encryptedActions.ModifyDisappearingMessageTimer.Timer) > 0 {
|
if encryptedActions.ModifyDisappearingMessagesTimer != nil && len(encryptedActions.ModifyDisappearingMessagesTimer.Timer) > 0 {
|
||||||
timerBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDisappearingMessageTimer.Timer)
|
timerBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDisappearingMessagesTimer.Timer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1068,12 +1106,6 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
decryptedGroupChange.ModifyInviteLinkPassword = &inviteLinkPassword
|
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
|
return decryptedGroupChange, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1127,11 +1159,11 @@ func decryptMember(ctx context.Context, member *signalpb.Member, groupSecretPara
|
||||||
ACI: *aci,
|
ACI: *aci,
|
||||||
ProfileKey: *profileKey,
|
ProfileKey: *profileKey,
|
||||||
Role: GroupMemberRole(member.Role),
|
Role: GroupMemberRole(member.Role),
|
||||||
JoinedAtRevision: member.JoinedAtVersion,
|
JoinedAtRevision: member.JoinedAtRevision,
|
||||||
}, nil
|
}, 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)
|
log := zerolog.Ctx(ctx)
|
||||||
encryptedUserID := libsignalgo.UUIDCiphertext(pendingMember.Member.UserId)
|
encryptedUserID := libsignalgo.UUIDCiphertext(pendingMember.Member.UserId)
|
||||||
userID, err := groupSecretParams.DecryptServiceID(encryptedUserID)
|
userID, err := groupSecretParams.DecryptServiceID(encryptedUserID)
|
||||||
|
|
@ -1154,7 +1186,7 @@ func decryptPendingMember(ctx context.Context, pendingMember *signalpb.MemberPen
|
||||||
}, nil
|
}, 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)
|
aci, profileKey, err := decryptPKeyAndIDorPresentation(ctx, requestingMember.UserId, requestingMember.ProfileKey, requestingMember.Presentation, groupSecretParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
log.Err(err).Msg("Could not get groupSecretParams from master key")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions := &signalpb.GroupChange_Actions{Version: decryptedGroupChange.Revision}
|
groupChangeActions := &signalpb.GroupChange_Actions{Revision: decryptedGroupChange.Revision}
|
||||||
if decryptedGroupChange.ModifyTitle != nil {
|
if decryptedGroupChange.ModifyTitle != nil {
|
||||||
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_Title{Title: *decryptedGroupChange.ModifyTitle}}
|
attributeBlob := signalpb.GroupAttributeBlob{Content: &signalpb.GroupAttributeBlob_Title{Title: *decryptedGroupChange.ModifyTitle}}
|
||||||
encryptedTitle, err := encryptBlobIntoGroupProperty(groupSecretParams, &attributeBlob)
|
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}
|
groupChangeActions.ModifyTitle = &signalpb.GroupChange_Actions_ModifyTitleAction{Title: *encryptedTitle}
|
||||||
}
|
}
|
||||||
if decryptedGroupChange.ModifyDescription != nil {
|
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)
|
encryptedDescription, err := encryptBlobIntoGroupProperty(groupSecretParams, &attributeBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Could not get encrypt description")
|
log.Err(err).Msg("Could not get encrypt description")
|
||||||
|
|
@ -1208,7 +1240,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
JoinFromInviteLink: addMember.JoinFromInviteLink,
|
JoinFromInviteLink: addMember.JoinFromInviteLink,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
groupChangeActions.AddMembersPendingProfileKey = append(groupChangeActions.AddMembersPendingProfileKey, &signalpb.GroupChange_Actions_AddMemberPendingProfileKeyAction{
|
groupChangeActions.AddPendingMembers = append(groupChangeActions.AddPendingMembers, &signalpb.GroupChange_Actions_AddPendingMemberAction{
|
||||||
Added: encryptedPendingMember,
|
Added: encryptedPendingMember,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1240,7 +1272,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
log.Err(err).Msg("Failed to encrypt pendingMember")
|
log.Err(err).Msg("Failed to encrypt pendingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.AddMembersPendingProfileKey = append(groupChangeActions.AddMembersPendingProfileKey, &signalpb.GroupChange_Actions_AddMemberPendingProfileKeyAction{
|
groupChangeActions.AddPendingMembers = append(groupChangeActions.AddPendingMembers, &signalpb.GroupChange_Actions_AddPendingMemberAction{
|
||||||
Added: encryptedPendingMember,
|
Added: encryptedPendingMember,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1250,7 +1282,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
log.Err(err).Msg("Encrypt UserId error for deletePendingMember")
|
log.Err(err).Msg("Encrypt UserId error for deletePendingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.DeleteMembersPendingProfileKey = append(groupChangeActions.DeleteMembersPendingProfileKey, &signalpb.GroupChange_Actions_DeleteMemberPendingProfileKeyAction{
|
groupChangeActions.DeletePendingMembers = append(groupChangeActions.DeletePendingMembers, &signalpb.GroupChange_Actions_DeletePendingMemberAction{
|
||||||
DeletedUserId: encryptedUserID[:],
|
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")
|
log.Err(err).Msg("failed creating expiring profile key credential presentation for addMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.PromoteMembersPendingProfileKey = append(groupChangeActions.PromoteMembersPendingProfileKey, &signalpb.GroupChange_Actions_PromoteMemberPendingProfileKeyAction{
|
groupChangeActions.PromotePendingMembers = append(groupChangeActions.PromotePendingMembers, &signalpb.GroupChange_Actions_PromotePendingMemberAction{
|
||||||
Presentation: *presentation,
|
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")
|
log.Err(err).Msg("failed creating expiring profile key credential presentation for addMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.AddMembersPendingAdminApproval = append(groupChangeActions.AddMembersPendingAdminApproval, &signalpb.GroupChange_Actions_AddMemberPendingAdminApprovalAction{
|
groupChangeActions.AddRequestingMembers = append(groupChangeActions.AddRequestingMembers, &signalpb.GroupChange_Actions_AddRequestingMemberAction{
|
||||||
Added: &signalpb.MemberPendingAdminApproval{
|
Added: &signalpb.RequestingMember{
|
||||||
Presentation: *presentation,
|
Presentation: *presentation,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -1298,7 +1330,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
log.Err(err).Msg("Encrypt UserId error for deleteRequestingMember")
|
log.Err(err).Msg("Encrypt UserId error for deleteRequestingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.DeleteMembersPendingAdminApproval = append(groupChangeActions.DeleteMembersPendingAdminApproval, &signalpb.GroupChange_Actions_DeleteMemberPendingAdminApprovalAction{
|
groupChangeActions.DeleteRequestingMembers = append(groupChangeActions.DeleteRequestingMembers, &signalpb.GroupChange_Actions_DeleteRequestingMemberAction{
|
||||||
DeletedUserId: encryptedUserID[:],
|
DeletedUserId: encryptedUserID[:],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1309,7 +1341,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
groupChangeActions.PromoteMembersPendingAdminApproval = append(groupChangeActions.PromoteMembersPendingAdminApproval, &signalpb.GroupChange_Actions_PromoteMemberPendingAdminApprovalAction{
|
groupChangeActions.PromoteRequestingMembers = append(groupChangeActions.PromoteRequestingMembers, &signalpb.GroupChange_Actions_PromoteRequestingMemberAction{
|
||||||
UserId: encryptedUserID[:],
|
UserId: encryptedUserID[:],
|
||||||
Role: signalpb.Member_Role(promoteRequestingMember.Role),
|
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")
|
log.Err(err).Msg("Encrypt UserId error for promoteRequestingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.AddMembersBanned = append(groupChangeActions.AddMembersBanned, &signalpb.GroupChange_Actions_AddMemberBannedAction{
|
groupChangeActions.AddBannedMembers = append(groupChangeActions.AddBannedMembers, &signalpb.GroupChange_Actions_AddBannedMemberAction{
|
||||||
Added: &signalpb.MemberBanned{
|
Added: &signalpb.BannedMember{
|
||||||
UserId: encryptedUserID[:],
|
UserId: encryptedUserID[:],
|
||||||
Timestamp: addBannedMember.Timestamp,
|
Timestamp: addBannedMember.Timestamp,
|
||||||
},
|
},
|
||||||
|
|
@ -1333,7 +1365,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
log.Err(err).Msg("Encrypt UserId error for promoteRequestingMember")
|
log.Err(err).Msg("Encrypt UserId error for promoteRequestingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.DeleteMembersBanned = append(groupChangeActions.DeleteMembersBanned, &signalpb.GroupChange_Actions_DeleteMemberBannedAction{
|
groupChangeActions.DeleteBannedMembers = append(groupChangeActions.DeleteBannedMembers, &signalpb.GroupChange_Actions_DeleteBannedMemberAction{
|
||||||
DeletedUserId: encryptedUserID[:],
|
DeletedUserId: encryptedUserID[:],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1364,7 +1396,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
log.Err(err).Msg("Could not get encrypt Title")
|
log.Err(err).Msg("Could not get encrypt Title")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
groupChangeActions.ModifyDisappearingMessageTimer = &signalpb.GroupChange_Actions_ModifyDisappearingMessageTimerAction{Timer: *encryptedTimer}
|
groupChangeActions.ModifyDisappearingMessagesTimer = &signalpb.GroupChange_Actions_ModifyDisappearingMessagesTimerAction{Timer: *encryptedTimer}
|
||||||
}
|
}
|
||||||
if decryptedGroupChange.ModifyInviteLinkPassword != nil {
|
if decryptedGroupChange.ModifyInviteLinkPassword != nil {
|
||||||
inviteLinkPasswordBytes, err := inviteLinkPasswordToBytes(*decryptedGroupChange.ModifyInviteLinkPassword)
|
inviteLinkPasswordBytes, err := inviteLinkPasswordToBytes(*decryptedGroupChange.ModifyInviteLinkPassword)
|
||||||
|
|
@ -1379,7 +1411,7 @@ func (cli *Client) EncryptAndSignGroupChange(ctx context.Context, decryptedGroup
|
||||||
return cli.patchGroup(ctx, groupChangeActions, groupMasterKey, nil)
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
expiringProfileKeyCredential, err := cli.FetchExpiringProfileKeyCredentialById(ctx, member.ACI)
|
expiringProfileKeyCredential, err := cli.FetchExpiringProfileKeyCredentialById(ctx, member.ACI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1407,7 +1439,7 @@ func (cli *Client) encryptMember(ctx context.Context, member *GroupMember, group
|
||||||
return &encryptedMember, nil, nil
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
encryptedUserID, err := groupSecretParams.EncryptServiceID(pendingMember.ServiceID)
|
encryptedUserID, err := groupSecretParams.EncryptServiceID(pendingMember.ServiceID)
|
||||||
if err != nil {
|
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")
|
log.Err(err).Msg("Encrypt AddedByUserId error for addPendingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptedPendingMember := signalpb.MemberPendingProfileKey{
|
encryptedPendingMember := signalpb.PendingMember{
|
||||||
AddedByUserId: encryptedAddedByUserID[:],
|
AddedByUserId: encryptedAddedByUserID[:],
|
||||||
Member: &signalpb.Member{
|
Member: &signalpb.Member{
|
||||||
UserId: encryptedUserID[:],
|
UserId: encryptedUserID[:],
|
||||||
|
|
@ -1472,9 +1504,9 @@ func (cli *Client) patchGroup(ctx context.Context, groupChange *signalpb.GroupCh
|
||||||
Password: &groupAuth.Password,
|
Password: &groupAuth.Password,
|
||||||
ContentType: web.ContentTypeProtobuf,
|
ContentType: web.ContentTypeProtobuf,
|
||||||
Body: requestBody,
|
Body: requestBody,
|
||||||
|
Host: web.StorageHostname,
|
||||||
}
|
}
|
||||||
resp, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodPatch, path, opts)
|
resp, err := web.SendHTTPRequest(ctx, http.MethodPatch, path, opts)
|
||||||
defer web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SendRequest error: %w", err)
|
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
|
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) {
|
func (cli *Client) UpdateGroup(ctx context.Context, groupChange *GroupChange, gid types.GroupIdentifier) (uint32, error) {
|
||||||
log := zerolog.Ctx(ctx).With().Str("action", "UpdateGroup").Logger()
|
log := zerolog.Ctx(ctx).With().Str("action", "UpdateGroup").Logger()
|
||||||
groupMasterKey, err := cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
groupMasterKey, err := cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to get master key for group: %w", err)
|
return 0, fmt.Errorf("failed to get master key for group: %w", err)
|
||||||
} else if groupMasterKey == "" {
|
|
||||||
return 0, ErrGroupMasterKeyNotFound
|
|
||||||
}
|
}
|
||||||
groupChange.GroupMasterKey = groupMasterKey
|
groupChange.GroupMasterKey = groupMasterKey
|
||||||
masterKeyBytes := masterKeyToBytes(groupMasterKey)
|
masterKeyBytes := masterKeyToBytes(groupMasterKey)
|
||||||
var refetchedAddMemberCredentials bool
|
var refetchedAddMemberCredentials bool
|
||||||
var signedGroupChange *signalpb.GroupChangeResponse
|
var signedGroupChange *signalpb.GroupChangeResponse
|
||||||
group, _, err := cli.RetrieveGroupByID(ctx, gid, 0)
|
group, err := cli.RetrieveGroupByID(ctx, gid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to fetch group info to update: %w", err)
|
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)
|
return 0, fmt.Errorf("failed to update group: %w", err)
|
||||||
}
|
}
|
||||||
} else if errors.Is(err, ConflictError) {
|
} else if errors.Is(err, ConflictError) {
|
||||||
cli.GroupCache.Delete(gid)
|
delete(cli.GroupCache.groups, gid)
|
||||||
group, _, err = cli.RetrieveGroupByID(ctx, gid, 0)
|
delete(cli.GroupCache.lastFetched, gid)
|
||||||
|
delete(cli.GroupCache.activeCalls, gid)
|
||||||
|
group, err = cli.RetrieveGroupByID(ctx, gid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to fetch group after conflict: %w", err)
|
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)
|
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 {
|
if signedGroupChange == nil {
|
||||||
return 0, fmt.Errorf("no signed group change returned: %w", err)
|
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)
|
groupChangeBytes, err := proto.Marshal(signedGroupChange.GroupChange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to marshal signed group change: %w", err)
|
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{
|
encryptedGroup := &signalpb.Group{
|
||||||
PublicKey: groupPublicParams[:],
|
PublicKey: groupPublicParams[:],
|
||||||
Title: *encryptedTitle,
|
Title: *encryptedTitle,
|
||||||
AvatarUrl: decryptedGroup.AvatarPath,
|
Avatar: decryptedGroup.AvatarPath,
|
||||||
AnnouncementsOnly: decryptedGroup.AnnouncementsOnly,
|
AnnouncementsOnly: decryptedGroup.AnnouncementsOnly,
|
||||||
Version: 0,
|
Revision: 0,
|
||||||
}
|
}
|
||||||
if decryptedGroup.Description != "" {
|
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)
|
encryptedDescription, err := encryptBlobIntoGroupProperty(groupSecretParams, &attributeBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Could not get encrypt Description")
|
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 {
|
if encryptedMember != nil {
|
||||||
encryptedGroup.Members = append(encryptedGroup.Members, encryptedMember)
|
encryptedGroup.Members = append(encryptedGroup.Members, encryptedMember)
|
||||||
} else {
|
} else {
|
||||||
encryptedGroup.MembersPendingProfileKey = append(encryptedGroup.MembersPendingProfileKey, encryptedPendingMember)
|
encryptedGroup.PendingMembers = append(encryptedGroup.PendingMembers, encryptedPendingMember)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, pendingMember := range decryptedGroup.PendingMembers {
|
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")
|
log.Err(err).Msg("Failed to encrypt pendingMember")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptedGroup.MembersPendingProfileKey = append(encryptedGroup.MembersPendingProfileKey, encryptedPendingMember)
|
encryptedGroup.PendingMembers = append(encryptedGroup.PendingMembers, encryptedPendingMember)
|
||||||
}
|
}
|
||||||
return encryptedGroup, nil
|
return encryptedGroup, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1666,7 +1695,7 @@ func PrepareGroupCreation(decryptedGroup *Group) (libsignalgo.GroupMasterKey, er
|
||||||
return masterKeyBytes, nil
|
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()
|
log := zerolog.Ctx(ctx).With().Str("action", "CreateGroupOnServer").Logger()
|
||||||
masterKeyBytes, err := PrepareGroupCreation(decryptedGroup)
|
masterKeyBytes, err := PrepareGroupCreation(decryptedGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1681,6 +1710,14 @@ func (cli *Client) createGroupOnServer(ctx context.Context, decryptedGroup *Grou
|
||||||
log.Err(err).Msg("DeriveGroupSecretParamsFromMasterKey error")
|
log.Err(err).Msg("DeriveGroupSecretParamsFromMasterKey error")
|
||||||
return nil, err
|
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)
|
encryptedGroup, err := cli.EncryptGroup(ctx, decryptedGroup, groupSecretParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to encrypt group")
|
log.Err(err).Msg("Failed to encrypt group")
|
||||||
|
|
@ -1702,9 +1739,9 @@ func (cli *Client) createGroupOnServer(ctx context.Context, decryptedGroup *Grou
|
||||||
Password: &groupAuth.Password,
|
Password: &groupAuth.Password,
|
||||||
ContentType: web.ContentTypeProtobuf,
|
ContentType: web.ContentTypeProtobuf,
|
||||||
Body: requestBody,
|
Body: requestBody,
|
||||||
|
Host: web.StorageHostname,
|
||||||
}
|
}
|
||||||
resp, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodPut, path, opts)
|
resp, err := web.SendHTTPRequest(ctx, http.MethodPut, path, opts)
|
||||||
defer web.CloseBody(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SendRequest error: %w", err)
|
return nil, fmt.Errorf("SendRequest error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1731,9 +1768,9 @@ func GenerateInviteLinkPassword() types.SerializedInviteLinkPassword {
|
||||||
return InviteLinkPasswordFromBytes(random.Bytes(16))
|
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()
|
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 {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error creating group on server")
|
log.Err(err).Msg("Error creating group on server")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1756,7 +1793,7 @@ func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdent
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if groupMasterKey == "" {
|
if groupMasterKey == "" {
|
||||||
return nil, ErrGroupMasterKeyNotFound
|
return nil, fmt.Errorf("No group master key found for group identifier %s", gid)
|
||||||
}
|
}
|
||||||
masterKeyBytes := masterKeyToBytes(groupMasterKey)
|
masterKeyBytes := masterKeyToBytes(groupMasterKey)
|
||||||
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyBytes)
|
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyBytes)
|
||||||
|
|
@ -1767,6 +1804,7 @@ func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdent
|
||||||
Username: &groupAuth.Username,
|
Username: &groupAuth.Username,
|
||||||
Password: &groupAuth.Password,
|
Password: &groupAuth.Password,
|
||||||
ContentType: web.ContentTypeProtobuf,
|
ContentType: web.ContentTypeProtobuf,
|
||||||
|
Host: web.StorageHostname,
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
// TODO actually cache the data and provide real expiry timestamp
|
// TODO actually cache the data and provide real expiry timestamp
|
||||||
"Cached-Send-Endorsements": "0",
|
"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
|
// 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)
|
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)
|
response, err := web.SendHTTPRequest(ctx, http.MethodGet, path, opts)
|
||||||
defer web.CloseBody(response)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand/v2"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -43,6 +43,25 @@ type GeneratedPreKeys struct {
|
||||||
IdentityKey []uint8
|
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 {
|
func (cli *Client) RegisterAllPreKeys(ctx context.Context, pks store.PreKeyStore) error {
|
||||||
var identityKeyPair *libsignalgo.IdentityKeyPair
|
var identityKeyPair *libsignalgo.IdentityKeyPair
|
||||||
var pni bool
|
var pni bool
|
||||||
|
|
@ -78,11 +97,10 @@ func (cli *Client) RegisterAllPreKeys(ctx context.Context, pks store.PreKeyStore
|
||||||
KyberPreKeys: kyberPreKeys,
|
KyberPreKeys: kyberPreKeys,
|
||||||
IdentityKey: identityKey,
|
IdentityKey: identityKey,
|
||||||
}
|
}
|
||||||
zerolog.Ctx(ctx).Debug().
|
preKeyUsername := fmt.Sprintf("%s.%d", cli.Store.ACI, cli.Store.DeviceID)
|
||||||
Int("num_prekeys", len(preKeys)).
|
log := zerolog.Ctx(ctx).With().Str("action", "register prekeys").Logger()
|
||||||
Int("num_kyber_prekeys", len(kyberPreKeys)).
|
log.Debug().Int("num_prekeys", len(preKeys)).Int("num_kyber_prekeys", len(kyberPreKeys)).Msg("Registering prekeys")
|
||||||
Msg("Registering all prekeys")
|
err = RegisterPreKeys(ctx, &generatedPreKeys, pni, preKeyUsername, cli.Store.Password)
|
||||||
err = cli.RegisterPreKeys(ctx, &generatedPreKeys, pni)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to register prekeys: %w", err)
|
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")
|
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()
|
log := zerolog.Ctx(ctx).With().Str("action", "register prekeys").Logger()
|
||||||
// Convert generated prekeys to JSON
|
// Convert generated prekeys to JSON
|
||||||
preKeysJson := []map[string]any{}
|
preKeysJson := []map[string]interface{}{}
|
||||||
kyberPreKeysJson := []map[string]any{}
|
kyberPreKeysJson := []map[string]interface{}{}
|
||||||
for _, preKey := range generatedPreKeys.PreKeys {
|
for _, preKey := range generatedPreKeys.PreKeys {
|
||||||
preKeyJson, err := PreKeyToJSON(preKey)
|
preKeyJson, err := PreKeyToJSON(preKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -349,27 +367,32 @@ func (cli *Client) RegisterPreKeys(ctx context.Context, generatedPreKeys *Genera
|
||||||
}
|
}
|
||||||
|
|
||||||
identityKey := generatedPreKeys.IdentityKey
|
identityKey := generatedPreKeys.IdentityKey
|
||||||
registerJSON := map[string]any{
|
register_json := map[string]interface{}{
|
||||||
"preKeys": preKeysJson,
|
"preKeys": preKeysJson,
|
||||||
"pqPreKeys": kyberPreKeysJson,
|
"pqPreKeys": kyberPreKeysJson,
|
||||||
"identityKey": base64.StdEncoding.EncodeToString(identityKey),
|
"identityKey": base64.StdEncoding.EncodeToString(identityKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
jsonBytes, err := json.Marshal(registerJSON)
|
jsonBytes, err := json.Marshal(register_json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error marshalling register JSON")
|
log.Err(err).Msg("Error marshalling register JSON")
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error sending request")
|
log.Err(err).Msg("Error sending request")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.GetStatus() == 422 {
|
defer resp.Body.Close()
|
||||||
|
// status code not 2xx
|
||||||
|
if resp.StatusCode == 422 {
|
||||||
return errPrekeyUpload422
|
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 {
|
type prekeyResponse struct {
|
||||||
|
|
@ -404,40 +427,25 @@ func addBase64PaddingAndDecode(data string) ([]byte, error) {
|
||||||
return base64.StdEncoding.DecodeString(data)
|
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 {
|
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
|
// Fetch prekey
|
||||||
deviceIDPath := "/*"
|
deviceIDPath := "/*"
|
||||||
if specificDeviceID >= 0 {
|
if specificDeviceID >= 0 {
|
||||||
deviceIDPath = "/" + fmt.Sprint(specificDeviceID)
|
deviceIDPath = "/" + fmt.Sprint(specificDeviceID)
|
||||||
}
|
}
|
||||||
// TODO this should be done via the unauthed websocket if possible
|
|
||||||
path := "/v2/keys/" + theirServiceID.String() + deviceIDPath + "?pq=true"
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("error sending request: %w", err)
|
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
|
var prekeyResponse prekeyResponse
|
||||||
err = web.DecodeWSResponseBody(ctx, &respData, resp)
|
err = web.DecodeHTTPResponseBody(ctx, &prekeyResponse, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error decoding response body: %w", err)
|
return fmt.Errorf("error decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rawIdentityKey, err := addBase64PaddingAndDecode(respData.IdentityKey)
|
rawIdentityKey, err := addBase64PaddingAndDecode(prekeyResponse.IdentityKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error decoding identity key: %w", err)
|
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)
|
// 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 publicKey *libsignalgo.PublicKey
|
||||||
var preKeyID uint32
|
var preKeyID uint32
|
||||||
if d.PreKey != nil {
|
if d.PreKey != nil {
|
||||||
|
|
@ -522,7 +530,6 @@ func (cli *Client) FetchAndProcessPreKey(ctx context.Context, theirServiceID lib
|
||||||
ctx,
|
ctx,
|
||||||
preKeyBundle,
|
preKeyBundle,
|
||||||
address,
|
address,
|
||||||
localAddress,
|
|
||||||
cli.Store.ACISessionStore,
|
cli.Store.ACISessionStore,
|
||||||
cli.Store.ACIIdentityStore,
|
cli.Store.ACIIdentityStore,
|
||||||
)
|
)
|
||||||
|
|
@ -548,18 +555,19 @@ func keysPath(pni bool) string {
|
||||||
|
|
||||||
func (cli *Client) GetMyKeyCounts(ctx context.Context, pni bool) (int, int, error) {
|
func (cli *Client) GetMyKeyCounts(ctx context.Context, pni bool) (int, int, error) {
|
||||||
log := zerolog.Ctx(ctx).With().Str("action", "get my key counts").Logger()
|
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 {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error sending request")
|
log.Err(err).Msg("Error sending request")
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
var respData preKeyCountResponse
|
var preKeyCountResponse preKeyCountResponse
|
||||||
err = web.DecodeWSResponseBody(ctx, &respData, resp)
|
err = web.DecodeHTTPResponseBody(ctx, &preKeyCountResponse, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Fetching prekey counts, error with response body")
|
log.Err(err).Msg("Fetching prekey counts, error with response body")
|
||||||
return 0, 0, err
|
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 {
|
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()
|
log := zerolog.Ctx(ctx).With().Str("action", "start key check loop").Logger()
|
||||||
|
|
||||||
// Do the initial check in 5-10 minutes after starting the loop
|
// Do the initial check in 5-10 minutes after starting the loop
|
||||||
windowStart := 0
|
window_start := 0
|
||||||
windowSize := 1
|
window_size := 1
|
||||||
firstRun := true
|
|
||||||
for {
|
for {
|
||||||
randomMinutesInWindow := rand.IntN(windowSize) + windowStart
|
random_minutes_in_window := rand.Intn(window_size) + window_start
|
||||||
checkTime := time.Duration(randomMinutesInWindow) * time.Minute
|
check_time := time.Duration(random_minutes_in_window) * time.Minute
|
||||||
if firstRun {
|
log.Debug().Dur("check_time", check_time).Msg("Waiting to check for new prekeys")
|
||||||
checkTime = 0
|
|
||||||
firstRun = false
|
|
||||||
} else {
|
|
||||||
log.Debug().Dur("check_time", checkTime).Msg("Waiting to check for new prekeys")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(checkTime):
|
case <-time.After(check_time):
|
||||||
err := cli.CheckAndUploadNewPreKeys(ctx, cli.Store.ACIPreKeyStore)
|
err := cli.CheckAndUploadNewPreKeys(ctx, cli.Store.ACIPreKeyStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error checking and uploading new prekeys for ACI identity")
|
log.Err(err).Msg("Error checking and uploading new prekeys for ACI identity")
|
||||||
// Retry within half an hour
|
// Retry within half an hour
|
||||||
windowStart = 5
|
window_start = 5
|
||||||
windowSize = 25
|
window_size = 25
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = cli.CheckAndUploadNewPreKeys(ctx, cli.Store.PNIPreKeyStore)
|
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")
|
log.Err(err).Msg("Error checking and uploading new prekeys for PNI identity")
|
||||||
// Retry within half an hour
|
// Retry within half an hour
|
||||||
windowStart = 5
|
window_start = 5
|
||||||
windowSize = 25
|
window_size = 25
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// After a successful check, check again in 36 to 60 hours
|
// After a successful check, check again in 36 to 60 hours
|
||||||
windowStart = 36 * 60
|
window_start = 36 * 60
|
||||||
windowSize = 24 * 60
|
window_size = 24 * 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,7 @@ package signalmeow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exerrors"
|
"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) Flush() {}
|
||||||
|
|
||||||
func (FFILogger) Destroy() {}
|
|
||||||
|
|
||||||
// Ensure FFILogger implements the Logger interface
|
// Ensure FFILogger implements the Logger interface
|
||||||
var _ libsignalgo.Logger = FFILogger{}
|
var _ libsignalgo.Logger = FFILogger{}
|
||||||
|
|
||||||
|
|
@ -81,28 +76,3 @@ var prodServerPublicParams *libsignalgo.ServerPublicParams
|
||||||
func init() {
|
func init() {
|
||||||
prodServerPublicParams = exerrors.Must(libsignalgo.DeserializeServerPublicParams(prodServerPublicParamsSlice))
|
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) {
|
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
|
// 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
|
// or if we have a cached error that is less than an hour old
|
||||||
profile, err := cli.getCachedProfileByID(signalID, refreshAfter)
|
profile, err := cli.getCachedProfileByID(signalID, refreshAfter)
|
||||||
|
|
@ -210,18 +218,18 @@ func (cli *Client) fetchProfileWithRequestAndKey(ctx context.Context, signalID u
|
||||||
path += "/" + string(credentialRequest)
|
path += "/" + string(credentialRequest)
|
||||||
path += "?credentialType=expiringProfileKey"
|
path += "?credentialType=expiringProfileKey"
|
||||||
}
|
}
|
||||||
headers := http.Header{}
|
profileRequest := web.CreateWSRequest(http.MethodGet, path, nil, nil, nil)
|
||||||
if useUnidentified {
|
if useUnidentified {
|
||||||
headers.Set("Unidentified-Access-Key", base64AccessKey)
|
profileRequest.Headers = append(profileRequest.Headers, "unidentified-access-key:"+base64AccessKey)
|
||||||
headers.Set("Accept-Language", "en-US")
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error sending request: %w", err)
|
return nil, fmt.Errorf("error sending request: %w", err)
|
||||||
}
|
}
|
||||||
var profile types.Profile
|
var profile types.Profile
|
||||||
profile.FetchedAt = time.Now()
|
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 logEvt.Enabled() {
|
||||||
if json.Valid(resp.Body) {
|
if json.Valid(resp.Body) {
|
||||||
logEvt.RawJSON("response_data", 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) {
|
func (cli *Client) DownloadUserAvatar(ctx context.Context, avatarPath string, profileKey libsignalgo.ProfileKey) ([]byte, error) {
|
||||||
username, password := cli.Store.BasicAuthCreds()
|
username, password := cli.Store.BasicAuthCreds()
|
||||||
opts := &web.HTTPReqOpt{
|
opts := &web.HTTPReqOpt{
|
||||||
|
Host: web.CDN1Hostname,
|
||||||
Username: &username,
|
Username: &username,
|
||||||
Password: &password,
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
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 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
return nil, fmt.Errorf("unexpected response status %d", resp.StatusCode)
|
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.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: ContactDiscovery.proto
|
// source: ContactDiscovery.proto
|
||||||
|
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// 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.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: DeviceName.proto
|
// source: DeviceName.proto
|
||||||
|
|
||||||
// Copyright 2018 Signal Messenger, LLC
|
// Copyright 2018 Signal Messenger, LLC
|
||||||
|
|
|
||||||
1926
pkg/signalmeow/protobuf/Groups.pb.go
generated
1926
pkg/signalmeow/protobuf/Groups.pb.go
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,11 @@
|
||||||
/*
|
/**
|
||||||
* Copyright 2020 Signal Messenger, LLC
|
* Copyright (C) 2019 Open Whisper Systems
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
*
|
||||||
|
* Licensed according to the LICENSE file in this repository.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package signal;
|
option java_package = "org.signal.storageservice.protos.groups";
|
||||||
|
|
||||||
option java_package = "org.signal.storageservice.storage.protos.groups";
|
|
||||||
option java_outer_classname = "GroupProtos";
|
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
message AvatarUploadAttributes {
|
message AvatarUploadAttributes {
|
||||||
|
|
@ -21,8 +18,6 @@ message AvatarUploadAttributes {
|
||||||
string signature = 7;
|
string signature = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stored data
|
|
||||||
|
|
||||||
message Member {
|
message Member {
|
||||||
enum Role {
|
enum Role {
|
||||||
UNKNOWN = 0;
|
UNKNOWN = 0;
|
||||||
|
|
@ -33,28 +28,26 @@ message Member {
|
||||||
bytes userId = 1;
|
bytes userId = 1;
|
||||||
Role role = 2;
|
Role role = 2;
|
||||||
bytes profileKey = 3;
|
bytes profileKey = 3;
|
||||||
bytes presentation = 4;
|
bytes presentation = 4; // Only set when sending to server
|
||||||
uint32 joinedAtVersion = 5;
|
uint32 joinedAtRevision = 5;
|
||||||
bytes labelEmoji = 6; // decrypts to a UTF-8 string
|
|
||||||
bytes labelString = 7; // decrypts to a UTF-8 string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message MemberPendingProfileKey {
|
message PendingMember {
|
||||||
Member member = 1;
|
Member member = 1;
|
||||||
bytes addedByUserId = 2;
|
bytes addedByUserId = 2;
|
||||||
uint64 timestamp = 3; // ms since epoch
|
uint64 timestamp = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MemberPendingAdminApproval {
|
message RequestingMember {
|
||||||
bytes userId = 1;
|
bytes userId = 1;
|
||||||
bytes profileKey = 2;
|
bytes profileKey = 2;
|
||||||
bytes presentation = 3;
|
bytes presentation = 3; // Only set when sending to server
|
||||||
uint64 timestamp = 4; // ms since epoch
|
uint64 timestamp = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MemberBanned {
|
message BannedMember {
|
||||||
bytes userId = 1;
|
bytes userId = 1;
|
||||||
uint64 timestamp = 2; // ms since epoch
|
uint64 timestamp = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AccessControl {
|
message AccessControl {
|
||||||
|
|
@ -69,64 +62,24 @@ message AccessControl {
|
||||||
AccessRequired attributes = 1;
|
AccessRequired attributes = 1;
|
||||||
AccessRequired members = 2;
|
AccessRequired members = 2;
|
||||||
AccessRequired addFromInviteLink = 3;
|
AccessRequired addFromInviteLink = 3;
|
||||||
AccessRequired memberLabel = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Group {
|
message Group {
|
||||||
bytes publicKey = 1;
|
bytes publicKey = 1;
|
||||||
bytes title = 2;
|
bytes title = 2;
|
||||||
bytes description = 11;
|
string avatar = 3;
|
||||||
// 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;
|
bytes disappearingMessagesTimer = 4;
|
||||||
AccessControl accessControl = 5;
|
AccessControl accessControl = 5;
|
||||||
uint32 version = 6;
|
uint32 revision = 6;
|
||||||
repeated Member members = 7;
|
repeated Member members = 7;
|
||||||
repeated MemberPendingProfileKey membersPendingProfileKey = 8;
|
repeated PendingMember pendingMembers = 8;
|
||||||
repeated MemberPendingAdminApproval membersPendingAdminApproval = 9;
|
repeated RequestingMember requestingMembers = 9;
|
||||||
bytes inviteLinkPassword = 10;
|
bytes inviteLinkPassword = 10;
|
||||||
bool announcements_only = 12;
|
bytes description = 11;
|
||||||
repeated MemberBanned members_banned = 13;
|
bool announcementsOnly = 12;
|
||||||
bool terminated = 14;
|
repeated BannedMember bannedMembers = 13;
|
||||||
// next: 15
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 GroupChange {
|
||||||
|
|
||||||
message Actions {
|
message Actions {
|
||||||
|
|
@ -145,57 +98,51 @@ message GroupChange {
|
||||||
Member.Role role = 2;
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
message ModifyMemberProfileKeyAction {
|
message ModifyMemberProfileKeyAction {
|
||||||
bytes presentation = 1;
|
bytes presentation = 1; // Only set when sending to server
|
||||||
bytes user_id = 2;
|
bytes user_id = 2; // Only set when receiving from server
|
||||||
bytes profile_key = 3;
|
bytes profile_key = 3; // Only set when receiving from server
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddMemberPendingProfileKeyAction {
|
message AddPendingMemberAction {
|
||||||
MemberPendingProfileKey added = 1;
|
PendingMember added = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeleteMemberPendingProfileKeyAction {
|
message DeletePendingMemberAction {
|
||||||
bytes deletedUserId = 1;
|
bytes deletedUserId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PromoteMemberPendingProfileKeyAction {
|
message PromotePendingMemberAction {
|
||||||
bytes presentation = 1;
|
bytes presentation = 1; // Only set when sending to server
|
||||||
bytes user_id = 2;
|
bytes user_id = 2; // Only set when receiving from server
|
||||||
bytes profile_key = 3;
|
bytes profile_key = 3; // Only set when receiving from server
|
||||||
}
|
}
|
||||||
|
|
||||||
message PromoteMemberPendingPniAciProfileKeyAction {
|
message PromotePendingPniAciMemberProfileKeyAction {
|
||||||
bytes presentation = 1;
|
bytes presentation = 1; // Only set when sending to server
|
||||||
bytes user_id = 2;
|
bytes userId = 2; // Only set when receiving from server
|
||||||
bytes pni = 3;
|
bytes pni = 3; // Only set when receiving from server
|
||||||
bytes profile_key = 4;
|
bytes profileKey = 4; // Only set when receiving from server
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddMemberPendingAdminApprovalAction {
|
message AddRequestingMemberAction {
|
||||||
MemberPendingAdminApproval added = 1;
|
RequestingMember added = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeleteMemberPendingAdminApprovalAction {
|
message DeleteRequestingMemberAction {
|
||||||
bytes deletedUserId = 1;
|
bytes deletedUserId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PromoteMemberPendingAdminApprovalAction {
|
message PromoteRequestingMemberAction {
|
||||||
bytes userId = 1;
|
bytes userId = 1;
|
||||||
Member.Role role = 2;
|
Member.Role role = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddMemberBannedAction {
|
message AddBannedMemberAction {
|
||||||
MemberBanned added = 1;
|
BannedMember added = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeleteMemberBannedAction {
|
message DeleteBannedMemberAction {
|
||||||
bytes deletedUserId = 1;
|
bytes deletedUserId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +158,7 @@ message GroupChange {
|
||||||
string avatar = 1;
|
string avatar = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ModifyDisappearingMessageTimerAction {
|
message ModifyDisappearingMessagesTimerAction {
|
||||||
bytes timer = 1;
|
bytes timer = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,52 +174,39 @@ message GroupChange {
|
||||||
AccessControl.AccessRequired addFromInviteLinkAccess = 1;
|
AccessControl.AccessRequired addFromInviteLinkAccess = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ModifyMemberLabelAccessControlAction {
|
|
||||||
AccessControl.AccessRequired memberLabelAccess = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ModifyInviteLinkPasswordAction {
|
message ModifyInviteLinkPasswordAction {
|
||||||
bytes inviteLinkPassword = 1;
|
bytes inviteLinkPassword = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ModifyAnnouncementsOnlyAction {
|
message ModifyAnnouncementsOnlyAction {
|
||||||
bool announcements_only = 1;
|
bool announcementsOnly = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TerminateGroupAction {}
|
bytes sourceServiceId = 1;
|
||||||
|
bytes groupId = 25; // Only set when receiving from server
|
||||||
bytes sourceUserId = 1;
|
uint32 revision = 2;
|
||||||
// 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 AddMemberAction addMembers = 3;
|
||||||
repeated DeleteMemberAction deleteMembers = 4;
|
repeated DeleteMemberAction deleteMembers = 4;
|
||||||
repeated ModifyMemberRoleAction modifyMemberRoles = 5;
|
repeated ModifyMemberRoleAction modifyMemberRoles = 5;
|
||||||
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6;
|
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6;
|
||||||
repeated AddMemberPendingProfileKeyAction addMembersPendingProfileKey = 7;
|
repeated AddPendingMemberAction addPendingMembers = 7;
|
||||||
repeated DeleteMemberPendingProfileKeyAction deleteMembersPendingProfileKey = 8;
|
repeated DeletePendingMemberAction deletePendingMembers = 8;
|
||||||
repeated PromoteMemberPendingProfileKeyAction promoteMembersPendingProfileKey = 9;
|
repeated PromotePendingMemberAction promotePendingMembers = 9;
|
||||||
ModifyTitleAction modifyTitle = 10;
|
ModifyTitleAction modifyTitle = 10;
|
||||||
ModifyAvatarAction modifyAvatar = 11;
|
ModifyAvatarAction modifyAvatar = 11;
|
||||||
ModifyDisappearingMessageTimerAction modifyDisappearingMessageTimer = 12;
|
ModifyDisappearingMessagesTimerAction modifyDisappearingMessagesTimer = 12;
|
||||||
ModifyAttributesAccessControlAction modifyAttributesAccess = 13;
|
ModifyAttributesAccessControlAction modifyAttributesAccess = 13;
|
||||||
ModifyMembersAccessControlAction modifyMemberAccess = 14;
|
ModifyMembersAccessControlAction modifyMemberAccess = 14;
|
||||||
ModifyAddFromInviteLinkAccessControlAction modifyAddFromInviteLinkAccess = 15; // change epoch = 1
|
ModifyAddFromInviteLinkAccessControlAction modifyAddFromInviteLinkAccess = 15;
|
||||||
repeated AddMemberPendingAdminApprovalAction addMembersPendingAdminApproval = 16; // change epoch = 1
|
repeated AddRequestingMemberAction addRequestingMembers = 16;
|
||||||
repeated DeleteMemberPendingAdminApprovalAction deleteMembersPendingAdminApproval = 17; // change epoch = 1
|
repeated DeleteRequestingMemberAction deleteRequestingMembers = 17;
|
||||||
repeated PromoteMemberPendingAdminApprovalAction promoteMembersPendingAdminApproval = 18; // change epoch = 1
|
repeated PromoteRequestingMemberAction promoteRequestingMembers = 18;
|
||||||
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19; // change epoch = 1
|
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19;
|
||||||
ModifyDescriptionAction modifyDescription = 20; // change epoch = 2
|
ModifyDescriptionAction modifyDescription = 20;
|
||||||
ModifyAnnouncementsOnlyAction modify_announcements_only = 21; // change epoch = 3
|
ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21;
|
||||||
repeated AddMemberBannedAction add_members_banned = 22; // change epoch = 4
|
repeated AddBannedMemberAction addBannedMembers = 22;
|
||||||
repeated DeleteMemberBannedAction delete_members_banned = 23; // change epoch = 4
|
repeated DeleteBannedMemberAction deleteBannedMembers = 23;
|
||||||
repeated PromoteMemberPendingPniAciProfileKeyAction promote_members_pending_pni_aci_profile_key = 24; // change epoch = 5
|
repeated PromotePendingPniAciMemberProfileKeyAction promotePendingPniAciMembers = 24;
|
||||||
repeated ModifyMemberLabelAction modifyMemberLabels = 26; // change epoch = 6;
|
|
||||||
ModifyMemberLabelAccessControlAction modifyMemberLabelAccess = 27; // change epoch = 6
|
|
||||||
TerminateGroupAction terminate_group = 28; // change epoch = 7
|
|
||||||
// next: 29
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes actions = 1;
|
bytes actions = 1;
|
||||||
|
|
@ -280,17 +214,9 @@ message GroupChange {
|
||||||
uint32 changeEpoch = 3;
|
uint32 changeEpoch = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// External credentials
|
|
||||||
|
|
||||||
message ExternalGroupCredential {
|
|
||||||
string token = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API responses
|
|
||||||
|
|
||||||
message GroupResponse {
|
message GroupResponse {
|
||||||
Group group = 1;
|
Group group = 1;
|
||||||
bytes group_send_endorsements_response = 2;
|
bytes groupSendEndorsementsResponse = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupChanges {
|
message GroupChanges {
|
||||||
|
|
@ -300,10 +226,45 @@ message GroupChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated GroupChangeState groupChanges = 1;
|
repeated GroupChangeState groupChanges = 1;
|
||||||
bytes group_send_endorsements_response = 2;
|
bytes groupSendEndorsementsResponse = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupChangeResponse {
|
message GroupChangeResponse {
|
||||||
GroupChange group_change = 1;
|
GroupChange groupChange = 1;
|
||||||
bytes group_send_endorsements_response = 2;
|
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.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: Provisioning.proto
|
// source: Provisioning.proto
|
||||||
|
|
||||||
package signalpb
|
package signalpb
|
||||||
|
|
@ -199,6 +199,7 @@ type ProvisionMessage struct {
|
||||||
ProfileKey []byte `protobuf:"bytes,6,opt,name=profileKey" json:"profileKey,omitempty"`
|
ProfileKey []byte `protobuf:"bytes,6,opt,name=profileKey" json:"profileKey,omitempty"`
|
||||||
ReadReceipts *bool `protobuf:"varint,7,opt,name=readReceipts" json:"readReceipts,omitempty"`
|
ReadReceipts *bool `protobuf:"varint,7,opt,name=readReceipts" json:"readReceipts,omitempty"`
|
||||||
ProvisioningVersion *uint32 `protobuf:"varint,9,opt,name=provisioningVersion" json:"provisioningVersion,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
|
EphemeralBackupKey []byte `protobuf:"bytes,14,opt,name=ephemeralBackupKey" json:"ephemeralBackupKey,omitempty"` // 32 bytes
|
||||||
AccountEntropyPool *string `protobuf:"bytes,15,opt,name=accountEntropyPool" json:"accountEntropyPool,omitempty"`
|
AccountEntropyPool *string `protobuf:"bytes,15,opt,name=accountEntropyPool" json:"accountEntropyPool,omitempty"`
|
||||||
MediaRootBackupKey []byte `protobuf:"bytes,16,opt,name=mediaRootBackupKey" json:"mediaRootBackupKey,omitempty"` // 32-bytes
|
MediaRootBackupKey []byte `protobuf:"bytes,16,opt,name=mediaRootBackupKey" json:"mediaRootBackupKey,omitempty"` // 32-bytes
|
||||||
|
|
@ -322,6 +323,13 @@ func (x *ProvisionMessage) GetProvisioningVersion() uint32 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProvisionMessage) GetMasterKey() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.MasterKey
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ProvisionMessage) GetEphemeralBackupKey() []byte {
|
func (x *ProvisionMessage) GetEphemeralBackupKey() []byte {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.EphemeralBackupKey
|
return x.EphemeralBackupKey
|
||||||
|
|
@ -366,7 +374,7 @@ const file_Provisioning_proto_rawDesc = "" +
|
||||||
"\aaddress\x18\x01 \x01(\tR\aaddress\"E\n" +
|
"\aaddress\x18\x01 \x01(\tR\aaddress\"E\n" +
|
||||||
"\x11ProvisionEnvelope\x12\x1c\n" +
|
"\x11ProvisionEnvelope\x12\x1c\n" +
|
||||||
"\tpublicKey\x18\x01 \x01(\fR\tpublicKey\x12\x12\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" +
|
"\x10ProvisionMessage\x122\n" +
|
||||||
"\x14aciIdentityKeyPublic\x18\x01 \x01(\fR\x14aciIdentityKeyPublic\x124\n" +
|
"\x14aciIdentityKeyPublic\x18\x01 \x01(\fR\x14aciIdentityKeyPublic\x124\n" +
|
||||||
"\x15aciIdentityKeyPrivate\x18\x02 \x01(\fR\x15aciIdentityKeyPrivate\x122\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\x18\x06 \x01(\fR\n" +
|
||||||
"profileKey\x12\"\n" +
|
"profileKey\x12\"\n" +
|
||||||
"\freadReceipts\x18\a \x01(\bR\freadReceipts\x120\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" +
|
"\x12ephemeralBackupKey\x18\x0e \x01(\fR\x12ephemeralBackupKey\x12.\n" +
|
||||||
"\x12accountEntropyPool\x18\x0f \x01(\tR\x12accountEntropyPool\x12.\n" +
|
"\x12accountEntropyPool\x18\x0f \x01(\tR\x12accountEntropyPool\x12.\n" +
|
||||||
"\x12mediaRootBackupKey\x18\x10 \x01(\fR\x12mediaRootBackupKey\x12\x1c\n" +
|
"\x12mediaRootBackupKey\x18\x10 \x01(\fR\x12mediaRootBackupKey\x12\x1c\n" +
|
||||||
"\taciBinary\x18\x11 \x01(\fR\taciBinary\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" +
|
"\x13ProvisioningVersion\x12\v\n" +
|
||||||
"\aINITIAL\x10\x00\x12\x12\n" +
|
"\aINITIAL\x10\x00\x12\x12\n" +
|
||||||
"\x0eTABLET_SUPPORT\x10\x01\x12\v\n" +
|
"\x0eTABLET_SUPPORT\x10\x01\x12\v\n" +
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ message ProvisionMessage {
|
||||||
optional bytes profileKey = 6;
|
optional bytes profileKey = 6;
|
||||||
optional bool readReceipts = 7;
|
optional bool readReceipts = 7;
|
||||||
optional uint32 provisioningVersion = 9;
|
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 bytes ephemeralBackupKey = 14; // 32 bytes
|
||||||
optional string accountEntropyPool = 15;
|
optional string accountEntropyPool = 15;
|
||||||
optional bytes mediaRootBackupKey = 16; // 32-bytes
|
optional bytes mediaRootBackupKey = 16; // 32-bytes
|
||||||
|
|
|
||||||
2040
pkg/signalmeow/protobuf/SignalService.pb.go
generated
2040
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 {
|
message Envelope {
|
||||||
enum Type {
|
enum Type {
|
||||||
UNKNOWN = 0;
|
UNKNOWN = 0;
|
||||||
|
CIPHERTEXT = 1; // content => (version byte | SignalMessage{Content})
|
||||||
/**
|
|
||||||
* 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})
|
|
||||||
|
|
||||||
reserved 2;
|
reserved 2;
|
||||||
reserved "KEY_EXCHANGE";
|
reserved "KEY_EXCHANGE";
|
||||||
|
PREKEY_BUNDLE = 3; // content => (version byte | PreKeySignalMessage{Content})
|
||||||
/**
|
SERVER_DELIVERY_RECEIPT = 5; // legacyMessage => [] AND content => []
|
||||||
* A prekey message begins a new Signal session. The `content` of a prekey
|
UNIDENTIFIED_SENDER = 6; // legacyMessage => [] AND content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
|
||||||
* message is a superset of a double-ratchet message's `content` and
|
SENDERKEY_MESSAGE = 7; // legacyMessage => [] AND content => (version byte | SenderKeyMessage)
|
||||||
* contains the sender's identity public key and information identifying the
|
PLAINTEXT_CONTENT = 8; // legacyMessage => [] AND content => (marker byte | Content)
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
reserved 2; // formerly optional string sourceE164 = 2;
|
reserved 2; // formerly optional string sourceE164 = 2;
|
||||||
optional string sourceServiceId = 11;
|
optional string sourceServiceId = 11;
|
||||||
optional uint32 sourceDeviceId = 7;
|
optional uint32 sourceDevice = 7;
|
||||||
optional string destinationServiceId = 13;
|
optional string destinationServiceId = 13;
|
||||||
reserved 3; // formerly optional string relay = 3;
|
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
|
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 bytes content = 8; // Contains an encrypted Content
|
||||||
optional string serverGuid = 9;
|
optional string serverGuid = 9;
|
||||||
|
|
@ -96,28 +40,21 @@ message Envelope {
|
||||||
optional bool story = 16; // indicates that the content is a story.
|
optional bool story = 16; // indicates that the content is a story.
|
||||||
optional bytes report_spam_token = 17; // token sent when reporting spam
|
optional bytes report_spam_token = 17; // token sent when reporting spam
|
||||||
reserved 18; // internal server use
|
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)
|
// next: 19
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Content {
|
message Content {
|
||||||
oneof content {
|
optional DataMessage dataMessage = 1;
|
||||||
DataMessage dataMessage = 1;
|
optional SyncMessage syncMessage = 2;
|
||||||
SyncMessage syncMessage = 2;
|
optional CallMessage callMessage = 3;
|
||||||
CallMessage callMessage = 3;
|
optional NullMessage nullMessage = 4;
|
||||||
NullMessage nullMessage = 4;
|
optional ReceiptMessage receiptMessage = 5;
|
||||||
ReceiptMessage receiptMessage = 5;
|
optional TypingMessage typingMessage = 6;
|
||||||
TypingMessage typingMessage = 6;
|
|
||||||
bytes /* DecryptionErrorMessage */ decryptionErrorMessage = 8;
|
|
||||||
StoryMessage storyMessage = 9;
|
|
||||||
EditMessage editMessage = 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
optional bytes /* SenderKeyDistributionMessage */ senderKeyDistributionMessage = 7;
|
optional bytes /* SenderKeyDistributionMessage */ senderKeyDistributionMessage = 7;
|
||||||
|
optional bytes /* DecryptionErrorMessage */ decryptionErrorMessage = 8;
|
||||||
|
optional StoryMessage storyMessage = 9;
|
||||||
optional PniSignatureMessage pniSignatureMessage = 10;
|
optional PniSignatureMessage pniSignatureMessage = 10;
|
||||||
|
optional EditMessage editMessage = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CallMessage {
|
message CallMessage {
|
||||||
|
|
@ -240,7 +177,6 @@ message DataMessage {
|
||||||
enum Type {
|
enum Type {
|
||||||
NORMAL = 0;
|
NORMAL = 0;
|
||||||
GIFT_BADGE = 1;
|
GIFT_BADGE = 1;
|
||||||
POLL = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message QuotedAttachment {
|
message QuotedAttachment {
|
||||||
|
|
@ -256,7 +192,6 @@ message DataMessage {
|
||||||
repeated QuotedAttachment attachments = 4;
|
repeated QuotedAttachment attachments = 4;
|
||||||
repeated BodyRange bodyRanges = 6;
|
repeated BodyRange bodyRanges = 6;
|
||||||
optional Type type = 7;
|
optional Type type = 7;
|
||||||
optional bytes authorAciBinary = 8; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Contact {
|
message Contact {
|
||||||
|
|
@ -341,7 +276,6 @@ message DataMessage {
|
||||||
reserved /* targetAuthorE164 */ 3;
|
reserved /* targetAuthorE164 */ 3;
|
||||||
optional string targetAuthorAci = 4;
|
optional string targetAuthorAci = 4;
|
||||||
optional uint64 targetSentTimestamp = 5;
|
optional uint64 targetSentTimestamp = 5;
|
||||||
optional bytes targetAuthorAciBinary = 6; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Delete {
|
message Delete {
|
||||||
|
|
@ -355,7 +289,6 @@ message DataMessage {
|
||||||
message StoryContext {
|
message StoryContext {
|
||||||
optional string authorAci = 1;
|
optional string authorAci = 1;
|
||||||
optional uint64 sentTimestamp = 2;
|
optional uint64 sentTimestamp = 2;
|
||||||
optional bytes authorAciBinary = 3; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProtocolVersion {
|
enum ProtocolVersion {
|
||||||
|
|
@ -369,50 +302,13 @@ message DataMessage {
|
||||||
CDN_SELECTOR_ATTACHMENTS = 5;
|
CDN_SELECTOR_ATTACHMENTS = 5;
|
||||||
MENTIONS = 6;
|
MENTIONS = 6;
|
||||||
PAYMENTS = 7;
|
PAYMENTS = 7;
|
||||||
POLLS = 8;
|
CURRENT = 7;
|
||||||
CURRENT = 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GiftBadge {
|
message GiftBadge {
|
||||||
optional bytes receiptCredentialPresentation = 1;
|
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;
|
optional string body = 1;
|
||||||
repeated AttachmentPointer attachments = 2;
|
repeated AttachmentPointer attachments = 2;
|
||||||
reserved /*groupV1*/ 3;
|
reserved /*groupV1*/ 3;
|
||||||
|
|
@ -435,13 +331,7 @@ message DataMessage {
|
||||||
optional Payment payment = 20;
|
optional Payment payment = 20;
|
||||||
optional StoryContext storyContext = 21;
|
optional StoryContext storyContext = 21;
|
||||||
optional GiftBadge giftBadge = 22;
|
optional GiftBadge giftBadge = 22;
|
||||||
optional PollCreate pollCreate = 24;
|
// NEXT ID: 24
|
||||||
optional PollTerminate pollTerminate = 25;
|
|
||||||
optional PollVote pollVote = 26;
|
|
||||||
optional PinMessage pinMessage = 27;
|
|
||||||
optional UnpinMessage unpinMessage = 28;
|
|
||||||
optional AdminDelete adminDelete = 29;
|
|
||||||
// NEXT ID: 30
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message NullMessage {
|
message NullMessage {
|
||||||
|
|
@ -500,12 +390,6 @@ message TextAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Gradient {
|
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 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 endColor = 2; // deprecated: this field will be removed in a future release.
|
||||||
optional uint32 angle = 3; // degrees
|
optional uint32 angle = 3; // degrees
|
||||||
|
|
@ -536,7 +420,6 @@ message Verified {
|
||||||
optional bytes identityKey = 2;
|
optional bytes identityKey = 2;
|
||||||
optional State state = 3;
|
optional State state = 3;
|
||||||
optional bytes nullMessage = 4;
|
optional bytes nullMessage = 4;
|
||||||
optional bytes destinationAciBinary = 6; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message SyncMessage {
|
message SyncMessage {
|
||||||
|
|
@ -547,7 +430,6 @@ message SyncMessage {
|
||||||
optional bool unidentified = 2;
|
optional bool unidentified = 2;
|
||||||
reserved /*destinationPni */ 4;
|
reserved /*destinationPni */ 4;
|
||||||
optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations
|
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 {
|
message StoryMessageRecipient {
|
||||||
|
|
@ -555,7 +437,6 @@ message SyncMessage {
|
||||||
repeated string distributionListIds = 2;
|
repeated string distributionListIds = 2;
|
||||||
optional bool isAllowedToReply = 3;
|
optional bool isAllowedToReply = 3;
|
||||||
reserved /*destinationPni */ 4;
|
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;
|
optional string destinationE164 = 1;
|
||||||
|
|
@ -569,8 +450,7 @@ message SyncMessage {
|
||||||
repeated StoryMessageRecipient storyMessageRecipients = 9;
|
repeated StoryMessageRecipient storyMessageRecipients = 9;
|
||||||
optional EditMessage editMessage = 10;
|
optional EditMessage editMessage = 10;
|
||||||
reserved /*destinationPni */ 11;
|
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: 12
|
||||||
// Next ID: 13
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Contacts {
|
message Contacts {
|
||||||
|
|
@ -582,7 +462,6 @@ message SyncMessage {
|
||||||
repeated string numbers = 1;
|
repeated string numbers = 1;
|
||||||
repeated string acis = 3;
|
repeated string acis = 3;
|
||||||
repeated bytes groupIds = 2;
|
repeated bytes groupIds = 2;
|
||||||
repeated bytes acisBinary = 4; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Request {
|
message Request {
|
||||||
|
|
@ -603,14 +482,12 @@ message SyncMessage {
|
||||||
reserved /*senderE164*/ 1;
|
reserved /*senderE164*/ 1;
|
||||||
optional string senderAci = 3;
|
optional string senderAci = 3;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
optional bytes senderAciBinary = 4; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Viewed {
|
message Viewed {
|
||||||
reserved /*senderE164*/ 1;
|
reserved /*senderE164*/ 1;
|
||||||
optional string senderAci = 3;
|
optional string senderAci = 3;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
optional bytes senderAciBinary = 4; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Configuration {
|
message Configuration {
|
||||||
|
|
@ -618,7 +495,7 @@ message SyncMessage {
|
||||||
optional bool unidentifiedDeliveryIndicators = 2;
|
optional bool unidentifiedDeliveryIndicators = 2;
|
||||||
optional bool typingIndicators = 3;
|
optional bool typingIndicators = 3;
|
||||||
reserved /* linkPreviews */ 4;
|
reserved /* linkPreviews */ 4;
|
||||||
reserved /* provisioningVersion */ 5;
|
optional uint32 provisioningVersion = 5;
|
||||||
optional bool linkPreviews = 6;
|
optional bool linkPreviews = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -637,7 +514,6 @@ message SyncMessage {
|
||||||
reserved /*senderE164*/ 1;
|
reserved /*senderE164*/ 1;
|
||||||
optional string senderAci = 3;
|
optional string senderAci = 3;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
optional bytes senderAciBinary = 4; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message FetchLatest {
|
message FetchLatest {
|
||||||
|
|
@ -653,7 +529,7 @@ message SyncMessage {
|
||||||
|
|
||||||
message Keys {
|
message Keys {
|
||||||
reserved /* storageService */ 1;
|
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 string accountEntropyPool = 3;
|
||||||
optional bytes mediaRootBackupKey = 4;
|
optional bytes mediaRootBackupKey = 4;
|
||||||
}
|
}
|
||||||
|
|
@ -678,7 +554,6 @@ message SyncMessage {
|
||||||
optional string threadAci = 2;
|
optional string threadAci = 2;
|
||||||
optional bytes groupId = 3;
|
optional bytes groupId = 3;
|
||||||
optional Type type = 4;
|
optional Type type = 4;
|
||||||
optional bytes threadAciBinary = 5; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message OutgoingPayment {
|
message OutgoingPayment {
|
||||||
|
|
@ -753,7 +628,7 @@ message SyncMessage {
|
||||||
optional bytes rootKey = 1;
|
optional bytes rootKey = 1;
|
||||||
optional bytes adminPasskey = 2;
|
optional bytes adminPasskey = 2;
|
||||||
optional Type type = 3; // defaults to UPDATE
|
optional Type type = 3; // defaults to UPDATE
|
||||||
reserved /*epoch*/ 4;
|
optional bytes epoch = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CallLogEvent {
|
message CallLogEvent {
|
||||||
|
|
@ -850,40 +725,31 @@ message SyncMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oneof content {
|
optional Sent sent = 1;
|
||||||
Sent sent = 1;
|
optional Contacts contacts = 2;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
reserved /*groups*/ 3;
|
reserved /*groups*/ 3;
|
||||||
|
optional Request request = 4;
|
||||||
// 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`.
|
|
||||||
repeated Read read = 5;
|
repeated Read read = 5;
|
||||||
repeated StickerPackOperation stickerPackOperation = 10;
|
optional Blocked blocked = 6;
|
||||||
repeated Viewed viewed = 16;
|
optional Verified verified = 7;
|
||||||
|
optional Configuration configuration = 9;
|
||||||
reserved /*pniIdentity*/ 17;
|
|
||||||
|
|
||||||
optional bytes padding = 8;
|
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 {
|
message AttachmentPointer {
|
||||||
|
|
@ -935,7 +801,6 @@ message ContactDetails {
|
||||||
|
|
||||||
optional string number = 1;
|
optional string number = 1;
|
||||||
optional string aci = 9;
|
optional string aci = 9;
|
||||||
optional bytes aciBinary = 13; // 16-byte UUID
|
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
optional Avatar avatar = 3;
|
optional Avatar avatar = 3;
|
||||||
reserved /* color */ 4;
|
reserved /* color */ 4;
|
||||||
|
|
@ -946,7 +811,7 @@ message ContactDetails {
|
||||||
optional uint32 expireTimerVersion = 12;
|
optional uint32 expireTimerVersion = 12;
|
||||||
optional uint32 inboxPosition = 10;
|
optional uint32 inboxPosition = 10;
|
||||||
reserved /* archived */ 11;
|
reserved /* archived */ 11;
|
||||||
// NEXT ID: 14
|
// NEXT ID: 13
|
||||||
}
|
}
|
||||||
|
|
||||||
message PaymentAddress {
|
message PaymentAddress {
|
||||||
|
|
@ -993,7 +858,6 @@ message BodyRange {
|
||||||
oneof associatedValue {
|
oneof associatedValue {
|
||||||
string mentionAci = 3;
|
string mentionAci = 3;
|
||||||
Style style = 4;
|
Style style = 4;
|
||||||
bytes mentionAciBinary = 5; // 16-byte UUID
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1001,7 +865,6 @@ message AddressableMessage {
|
||||||
oneof author {
|
oneof author {
|
||||||
string authorServiceId = 1;
|
string authorServiceId = 1;
|
||||||
string authorE164 = 2;
|
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;
|
optional uint64 sentTimestamp = 3;
|
||||||
}
|
}
|
||||||
|
|
@ -1011,6 +874,5 @@ message ConversationIdentifier {
|
||||||
string threadServiceId = 1;
|
string threadServiceId = 1;
|
||||||
bytes threadGroupId = 2;
|
bytes threadGroupId = 2;
|
||||||
string threadE164 = 3;
|
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.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: StickerResources.proto
|
// source: StickerResources.proto
|
||||||
|
|
||||||
package signalpb
|
package signalpb
|
||||||
|
|
|
||||||
124
pkg/signalmeow/protobuf/StorageService.pb.go
generated
124
pkg/signalmeow/protobuf/StorageService.pb.go
generated
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: StorageService.proto
|
// source: StorageService.proto
|
||||||
|
|
||||||
package signalpb
|
package signalpb
|
||||||
|
|
@ -1086,9 +1086,7 @@ type ContactRecord struct {
|
||||||
PniSignatureVerified bool `protobuf:"varint,21,opt,name=pniSignatureVerified,proto3" json:"pniSignatureVerified,omitempty"`
|
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"`
|
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"`
|
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"`
|
AvatarColor *AvatarColor `protobuf:"varint,24,opt,name=avatarColor,proto3,enum=signalservice.AvatarColor,oneof" json:"avatarColor,omitempty"` // Next ID: 25
|
||||||
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
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -1291,20 +1289,6 @@ func (x *ContactRecord) GetAvatarColor() AvatarColor {
|
||||||
return AvatarColor_A100
|
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 {
|
type GroupV1Record struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
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"`
|
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"`
|
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"`
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -1506,13 +1489,6 @@ func (x *GroupV2Record) GetAvatarColor() AvatarColor {
|
||||||
return AvatarColor_A100
|
return AvatarColor_A100
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GroupV2Record) GetVerifiedNameHash() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.VerifiedNameHash
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Payments struct {
|
type Payments struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
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"`
|
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"`
|
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"`
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -1900,27 +1873,6 @@ func (x *AccountRecord) GetNotificationProfileManualOverride() *AccountRecord_No
|
||||||
return nil
|
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 {
|
type StoryDistributionListRecord struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Identifier []byte `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
Identifier []byte `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||||
|
|
@ -1929,7 +1881,6 @@ type StoryDistributionListRecord struct {
|
||||||
DeletedAtTimestamp uint64 `protobuf:"varint,4,opt,name=deletedAtTimestamp,proto3" json:"deletedAtTimestamp,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"`
|
AllowsReplies bool `protobuf:"varint,5,opt,name=allowsReplies,proto3" json:"allowsReplies,omitempty"`
|
||||||
IsBlockList bool `protobuf:"varint,6,opt,name=isBlockList,proto3" json:"isBlockList,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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -2006,18 +1957,12 @@ func (x *StoryDistributionListRecord) GetIsBlockList() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StoryDistributionListRecord) GetRecipientServiceIdsBinary() [][]byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.RecipientServiceIdsBinary
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallLinkRecord struct {
|
type CallLinkRecord struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
RootKey []byte `protobuf:"bytes,1,opt,name=rootKey,proto3" json:"rootKey,omitempty"`
|
RootKey []byte `protobuf:"bytes,1,opt,name=rootKey,proto3" json:"rootKey,omitempty"`
|
||||||
AdminPasskey []byte `protobuf:"bytes,2,opt,name=adminPasskey,proto3" json:"adminPasskey,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"`
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -2073,6 +2018,13 @@ func (x *CallLinkRecord) GetDeletedAtTimestampMs() uint64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *CallLinkRecord) GetEpoch() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Epoch
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Recipient struct {
|
type Recipient struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// Types that are valid to be assigned to Identifier:
|
// Types that are valid to be assigned to Identifier:
|
||||||
|
|
@ -2934,7 +2886,6 @@ type AccountRecord_PinnedConversation_Contact struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
||||||
E164 string `protobuf:"bytes,2,opt,name=e164,proto3" json:"e164,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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -2983,13 +2934,6 @@ func (x *AccountRecord_PinnedConversation_Contact) GetE164() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AccountRecord_PinnedConversation_Contact) GetServiceIdBinary() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.ServiceIdBinary
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountRecord_NotificationProfileManualOverride_ManuallyEnabled struct {
|
type AccountRecord_NotificationProfileManualOverride_ManuallyEnabled struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
|
@ -3047,7 +2991,6 @@ type Recipient_Contact struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"`
|
||||||
E164 string `protobuf:"bytes,2,opt,name=e164,proto3" json:"e164,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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -3096,13 +3039,6 @@ func (x *Recipient_Contact) GetE164() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Recipient_Contact) GetServiceIdBinary() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.ServiceIdBinary
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_StorageService_proto protoreflect.FileDescriptor
|
var File_StorageService_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_StorageService_proto_rawDesc = "" +
|
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\x18\b \x01(\v2\x1f.signalservice.ChatFolderRecordH\x00R\n" +
|
||||||
"chatFolder\x12V\n" +
|
"chatFolder\x12V\n" +
|
||||||
"\x13notificationProfile\x18\t \x01(\v2\".signalservice.NotificationProfileH\x00R\x13notificationProfileB\b\n" +
|
"\x13notificationProfile\x18\t \x01(\v2\".signalservice.NotificationProfileH\x00R\x13notificationProfileB\b\n" +
|
||||||
"\x06record\"\xd9\b\n" +
|
"\x06record\"\x9d\b\n" +
|
||||||
"\rContactRecord\x12\x10\n" +
|
"\rContactRecord\x12\x10\n" +
|
||||||
"\x03aci\x18\x01 \x01(\tR\x03aci\x12\x12\n" +
|
"\x03aci\x18\x01 \x01(\tR\x03aci\x12\x12\n" +
|
||||||
"\x04e164\x18\x02 \x01(\tR\x04e164\x12\x10\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" +
|
"\x14pniSignatureVerified\x18\x15 \x01(\bR\x14pniSignatureVerified\x12=\n" +
|
||||||
"\bnickname\x18\x16 \x01(\v2!.signalservice.ContactRecord.NameR\bnickname\x12\x12\n" +
|
"\bnickname\x18\x16 \x01(\v2!.signalservice.ContactRecord.NameR\bnickname\x12\x12\n" +
|
||||||
"\x04note\x18\x17 \x01(\tR\x04note\x12A\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" +
|
"\vavatarColor\x18\x18 \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\x1a4\n" +
|
||||||
"\taciBinary\x18\x19 \x01(\fR\taciBinary\x12\x1c\n" +
|
|
||||||
"\tpniBinary\x18\x1a \x01(\fR\tpniBinary\x1a4\n" +
|
|
||||||
"\x04Name\x12\x14\n" +
|
"\x04Name\x12\x14\n" +
|
||||||
"\x05given\x18\x01 \x01(\tR\x05given\x12\x16\n" +
|
"\x05given\x18\x01 \x01(\tR\x05given\x12\x16\n" +
|
||||||
"\x06family\x18\x02 \x01(\tR\x06family\":\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" +
|
"\vwhitelisted\x18\x03 \x01(\bR\vwhitelisted\x12\x1a\n" +
|
||||||
"\barchived\x18\x04 \x01(\bR\barchived\x12\"\n" +
|
"\barchived\x18\x04 \x01(\bR\barchived\x12\"\n" +
|
||||||
"\fmarkedUnread\x18\x05 \x01(\bR\fmarkedUnread\x120\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" +
|
"\rGroupV2Record\x12\x1c\n" +
|
||||||
"\tmasterKey\x18\x01 \x01(\fR\tmasterKey\x12\x18\n" +
|
"\tmasterKey\x18\x01 \x01(\fR\tmasterKey\x12\x18\n" +
|
||||||
"\ablocked\x18\x02 \x01(\bR\ablocked\x12 \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" +
|
"\thideStory\x18\b \x01(\bR\thideStory\x12P\n" +
|
||||||
"\rstorySendMode\x18\n" +
|
"\rstorySendMode\x18\n" +
|
||||||
" \x01(\x0e2*.signalservice.GroupV2Record.StorySendModeR\rstorySendMode\x12A\n" +
|
" \x01(\x0e2*.signalservice.GroupV2Record.StorySendModeR\rstorySendMode\x12A\n" +
|
||||||
"\vavatarColor\x18\v \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\x12*\n" +
|
"\vavatarColor\x18\v \x01(\x0e2\x1a.signalservice.AvatarColorH\x00R\vavatarColor\x88\x01\x01\"7\n" +
|
||||||
"\x10verifiedNameHash\x18\f \x01(\fR\x10verifiedNameHash\"7\n" +
|
|
||||||
"\rStorySendMode\x12\v\n" +
|
"\rStorySendMode\x12\v\n" +
|
||||||
"\aDEFAULT\x10\x00\x12\f\n" +
|
"\aDEFAULT\x10\x00\x12\f\n" +
|
||||||
"\bDISABLED\x10\x01\x12\v\n" +
|
"\bDISABLED\x10\x01\x12\v\n" +
|
||||||
|
|
@ -3225,7 +3158,7 @@ const file_StorageService_proto_rawDesc = "" +
|
||||||
"\">\n" +
|
"\">\n" +
|
||||||
"\bPayments\x12\x18\n" +
|
"\bPayments\x12\x18\n" +
|
||||||
"\aenabled\x18\x01 \x01(\bR\aenabled\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" +
|
"\rAccountRecord\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"profileKey\x18\x01 \x01(\fR\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" +
|
"\x14backupSubscriberData\x18) \x01(\v2..signalservice.AccountRecord.IAPSubscriberDataR\x14backupSubscriberData\x12A\n" +
|
||||||
"\vavatarColor\x18* \x01(\x0e2\x1a.signalservice.AvatarColorH\x02R\vavatarColor\x88\x01\x01\x12\\\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" +
|
"\x11backupTierHistory\x18+ \x01(\v2..signalservice.AccountRecord.BackupTierHistoryR\x11backupTierHistory\x12\x8c\x01\n" +
|
||||||
"!notificationProfileManualOverride\x18, \x01(\v2>.signalservice.AccountRecord.NotificationProfileManualOverrideR!notificationProfileManualOverride\x12H\n" +
|
"!notificationProfileManualOverride\x18, \x01(\v2>.signalservice.AccountRecord.NotificationProfileManualOverrideR!notificationProfileManualOverride\x1a\x86\x02\n" +
|
||||||
"\x1fnotificationProfileSyncDisabled\x18- \x01(\bR\x1fnotificationProfileSyncDisabled\x12J\n" +
|
|
||||||
" automaticKeyVerificationDisabled\x18. \x01(\bR automaticKeyVerificationDisabled\x12L\n" +
|
|
||||||
"!hasSeenAdminDeleteEducationDialog\x18/ \x01(\bR!hasSeenAdminDeleteEducationDialog\x1a\xb0\x02\n" +
|
|
||||||
"\x12PinnedConversation\x12S\n" +
|
"\x12PinnedConversation\x12S\n" +
|
||||||
"\acontact\x18\x01 \x01(\v27.signalservice.AccountRecord.PinnedConversation.ContactH\x00R\acontact\x12&\n" +
|
"\acontact\x18\x01 \x01(\v27.signalservice.AccountRecord.PinnedConversation.ContactH\x00R\acontact\x12&\n" +
|
||||||
"\rlegacyGroupId\x18\x03 \x01(\fH\x00R\rlegacyGroupId\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" +
|
"\aContact\x12\x1c\n" +
|
||||||
"\tserviceId\x18\x01 \x01(\tR\tserviceId\x12\x12\n" +
|
"\tserviceId\x18\x01 \x01(\tR\tserviceId\x12\x12\n" +
|
||||||
"\x04e164\x18\x02 \x01(\tR\x04e164\x12(\n" +
|
"\x04e164\x18\x02 \x01(\tR\x04e164B\f\n" +
|
||||||
"\x0fserviceIdBinary\x18\x03 \x01(\fR\x0fserviceIdBinaryB\f\n" +
|
|
||||||
"\n" +
|
"\n" +
|
||||||
"identifier\x1a\xf8\x01\n" +
|
"identifier\x1a\xf8\x01\n" +
|
||||||
"\fUsernameLink\x12\x18\n" +
|
"\fUsernameLink\x12\x18\n" +
|
||||||
|
|
@ -3329,7 +3258,7 @@ const file_StorageService_proto_rawDesc = "" +
|
||||||
"_hasBackupB\r\n" +
|
"_hasBackupB\r\n" +
|
||||||
"\v_backupTierB\x0e\n" +
|
"\v_backupTierB\x0e\n" +
|
||||||
"\f_avatarColorJ\x04\b\t\x10\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" +
|
"\x1bStoryDistributionListRecord\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"identifier\x18\x01 \x01(\fR\n" +
|
"identifier\x18\x01 \x01(\fR\n" +
|
||||||
|
|
@ -3338,20 +3267,20 @@ const file_StorageService_proto_rawDesc = "" +
|
||||||
"\x13recipientServiceIds\x18\x03 \x03(\tR\x13recipientServiceIds\x12.\n" +
|
"\x13recipientServiceIds\x18\x03 \x03(\tR\x13recipientServiceIds\x12.\n" +
|
||||||
"\x12deletedAtTimestamp\x18\x04 \x01(\x04R\x12deletedAtTimestamp\x12$\n" +
|
"\x12deletedAtTimestamp\x18\x04 \x01(\x04R\x12deletedAtTimestamp\x12$\n" +
|
||||||
"\rallowsReplies\x18\x05 \x01(\bR\rallowsReplies\x12 \n" +
|
"\rallowsReplies\x18\x05 \x01(\bR\rallowsReplies\x12 \n" +
|
||||||
"\visBlockList\x18\x06 \x01(\bR\visBlockList\x12<\n" +
|
"\visBlockList\x18\x06 \x01(\bR\visBlockList\"\xa7\x01\n" +
|
||||||
"\x19recipientServiceIdsBinary\x18\a \x03(\fR\x19recipientServiceIdsBinary\"\x88\x01\n" +
|
|
||||||
"\x0eCallLinkRecord\x12\x18\n" +
|
"\x0eCallLinkRecord\x12\x18\n" +
|
||||||
"\arootKey\x18\x01 \x01(\fR\arootKey\x12\"\n" +
|
"\arootKey\x18\x01 \x01(\fR\arootKey\x12\"\n" +
|
||||||
"\fadminPasskey\x18\x02 \x01(\fR\fadminPasskey\x122\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" +
|
"\tRecipient\x12<\n" +
|
||||||
"\acontact\x18\x01 \x01(\v2 .signalservice.Recipient.ContactH\x00R\acontact\x12&\n" +
|
"\acontact\x18\x01 \x01(\v2 .signalservice.Recipient.ContactH\x00R\acontact\x12&\n" +
|
||||||
"\rlegacyGroupId\x18\x02 \x01(\fH\x00R\rlegacyGroupId\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" +
|
"\aContact\x12\x1c\n" +
|
||||||
"\tserviceId\x18\x01 \x01(\tR\tserviceId\x12\x12\n" +
|
"\tserviceId\x18\x01 \x01(\tR\tserviceId\x12\x12\n" +
|
||||||
"\x04e164\x18\x02 \x01(\tR\x04e164\x12(\n" +
|
"\x04e164\x18\x02 \x01(\tR\x04e164B\f\n" +
|
||||||
"\x0fserviceIdBinary\x18\x03 \x01(\fR\x0fserviceIdBinaryB\f\n" +
|
|
||||||
"\n" +
|
"\n" +
|
||||||
"identifier\"\xe8\x04\n" +
|
"identifier\"\xe8\x04\n" +
|
||||||
"\x10ChatFolderRecord\x12\x1e\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[7].OneofWrappers = []any{}
|
||||||
file_StorageService_proto_msgTypes[9].OneofWrappers = []any{}
|
file_StorageService_proto_msgTypes[9].OneofWrappers = []any{}
|
||||||
file_StorageService_proto_msgTypes[11].OneofWrappers = []any{}
|
file_StorageService_proto_msgTypes[11].OneofWrappers = []any{}
|
||||||
|
file_StorageService_proto_msgTypes[13].OneofWrappers = []any{}
|
||||||
file_StorageService_proto_msgTypes[14].OneofWrappers = []any{
|
file_StorageService_proto_msgTypes[14].OneofWrappers = []any{
|
||||||
(*Recipient_Contact_)(nil),
|
(*Recipient_Contact_)(nil),
|
||||||
(*Recipient_LegacyGroupId)(nil),
|
(*Recipient_LegacyGroupId)(nil),
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,7 @@ message ContactRecord {
|
||||||
Name nickname = 22;
|
Name nickname = 22;
|
||||||
string note = 23;
|
string note = 23;
|
||||||
optional AvatarColor avatarColor = 24;
|
optional AvatarColor avatarColor = 24;
|
||||||
bytes aciBinary = 25; // 16-byte UUID
|
// Next ID: 25
|
||||||
bytes pniBinary = 26; // 16-byte UUID
|
|
||||||
// Next ID: 27
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupV1Record {
|
message GroupV1Record {
|
||||||
|
|
@ -172,7 +170,6 @@ message GroupV2Record {
|
||||||
reserved /* storySendEnabled */ 9;
|
reserved /* storySendEnabled */ 9;
|
||||||
StorySendMode storySendMode = 10;
|
StorySendMode storySendMode = 10;
|
||||||
optional AvatarColor avatarColor = 11;
|
optional AvatarColor avatarColor = 11;
|
||||||
bytes verifiedNameHash = 12; // SHA-256 of UTF-8 encoded decrypted group title that was last verified
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Payments {
|
message Payments {
|
||||||
|
|
@ -192,7 +189,6 @@ message AccountRecord {
|
||||||
message Contact {
|
message Contact {
|
||||||
string serviceId = 1;
|
string serviceId = 1;
|
||||||
string e164 = 2;
|
string e164 = 2;
|
||||||
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oneof identifier {
|
oneof identifier {
|
||||||
|
|
@ -295,9 +291,6 @@ message AccountRecord {
|
||||||
optional AvatarColor avatarColor = 42;
|
optional AvatarColor avatarColor = 42;
|
||||||
BackupTierHistory backupTierHistory = 43;
|
BackupTierHistory backupTierHistory = 43;
|
||||||
NotificationProfileManualOverride notificationProfileManualOverride = 44;
|
NotificationProfileManualOverride notificationProfileManualOverride = 44;
|
||||||
bool notificationProfileSyncDisabled = 45;
|
|
||||||
bool automaticKeyVerificationDisabled = 46;
|
|
||||||
bool hasSeenAdminDeleteEducationDialog = 47;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message StoryDistributionListRecord {
|
message StoryDistributionListRecord {
|
||||||
|
|
@ -307,21 +300,19 @@ message StoryDistributionListRecord {
|
||||||
uint64 deletedAtTimestamp = 4;
|
uint64 deletedAtTimestamp = 4;
|
||||||
bool allowsReplies = 5;
|
bool allowsReplies = 5;
|
||||||
bool isBlockList = 6;
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message CallLinkRecord {
|
message CallLinkRecord {
|
||||||
bytes rootKey = 1;
|
bytes rootKey = 1;
|
||||||
bytes adminPasskey = 2;
|
bytes adminPasskey = 2;
|
||||||
uint64 deletedAtTimestampMs = 3;
|
uint64 deletedAtTimestampMs = 3;
|
||||||
reserved 4; // was epoch field, never used
|
optional bytes epoch = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Recipient {
|
message Recipient {
|
||||||
message Contact {
|
message Contact {
|
||||||
string serviceId = 1;
|
string serviceId = 1;
|
||||||
string e164 = 2;
|
string e164 = 2;
|
||||||
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oneof identifier {
|
oneof identifier {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: UnidentifiedDelivery.proto
|
// source: UnidentifiedDelivery.proto
|
||||||
|
|
||||||
// Copyright 2018 Signal Messenger, LLC
|
// 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.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v3.21.12
|
||||||
// source: WebSocketResources.proto
|
// source: WebSocketResources.proto
|
||||||
|
|
||||||
package signalpb
|
package signalpb
|
||||||
|
|
|
||||||
2695
pkg/signalmeow/protobuf/backuppb/Backup.pb.go
generated
2695
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;
|
package signal.backup;
|
||||||
|
|
||||||
option java_package = "org.signal.archive.proto";
|
option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
|
||||||
option swift_prefix = "BackupProto_";
|
option swift_prefix = "BackupProto_";
|
||||||
|
|
||||||
message BackupInfo {
|
message BackupInfo {
|
||||||
|
|
@ -68,40 +68,6 @@ message AccountData {
|
||||||
Color color = 3;
|
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 {
|
message AccountSettings {
|
||||||
bool readReceipts = 1;
|
bool readReceipts = 1;
|
||||||
bool sealedSenderIndicators = 2;
|
bool sealedSenderIndicators = 2;
|
||||||
|
|
@ -125,17 +91,6 @@ message AccountData {
|
||||||
bool optimizeOnDeviceStorage = 20;
|
bool optimizeOnDeviceStorage = 20;
|
||||||
// See zkgroup for integer particular values. Unset if backups are not enabled.
|
// See zkgroup for integer particular values. Unset if backups are not enabled.
|
||||||
optional uint64 backupTier = 21;
|
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 {
|
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;
|
bytes profileKey = 1;
|
||||||
optional string username = 2;
|
optional string username = 2;
|
||||||
UsernameLink usernameLink = 3;
|
UsernameLink usernameLink = 3;
|
||||||
|
|
@ -179,10 +122,6 @@ message AccountData {
|
||||||
AccountSettings accountSettings = 9;
|
AccountSettings accountSettings = 9;
|
||||||
IAPSubscriberData backupsSubscriberData = 10;
|
IAPSubscriberData backupsSubscriberData = 10;
|
||||||
string svrPin = 11;
|
string svrPin = 11;
|
||||||
AndroidSpecificSettings androidSpecificSettings = 12;
|
|
||||||
string bioText = 13;
|
|
||||||
string bioEmoji = 14;
|
|
||||||
optional bytes keyTransparencyData = 15;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Recipient {
|
message Recipient {
|
||||||
|
|
@ -271,7 +210,6 @@ message Contact {
|
||||||
string systemFamilyName = 19;
|
string systemFamilyName = 19;
|
||||||
string systemNickname = 20;
|
string systemNickname = 20;
|
||||||
optional AvatarColor avatarColor = 21;
|
optional AvatarColor avatarColor = 21;
|
||||||
optional bytes keyTransparencyData = 22;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Group {
|
message Group {
|
||||||
|
|
@ -308,7 +246,6 @@ message Group {
|
||||||
bytes inviteLinkPassword = 10;
|
bytes inviteLinkPassword = 10;
|
||||||
bool announcements_only = 12;
|
bool announcements_only = 12;
|
||||||
repeated MemberBanned members_banned = 13;
|
repeated MemberBanned members_banned = 13;
|
||||||
bool terminated = 14;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupAttributeBlob {
|
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 /*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
|
reserved /*presentation*/ 4; // This field is deprecated in the context of static group state
|
||||||
uint32 joinedAtVersion = 5;
|
uint32 joinedAtVersion = 5;
|
||||||
string labelEmoji = 6;
|
|
||||||
string labelString = 7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message MemberPendingProfileKey {
|
message MemberPendingProfileKey {
|
||||||
|
|
@ -367,7 +302,6 @@ message Group {
|
||||||
AccessRequired attributes = 1;
|
AccessRequired attributes = 1;
|
||||||
AccessRequired members = 2;
|
AccessRequired members = 2;
|
||||||
AccessRequired addFromInviteLink = 3;
|
AccessRequired addFromInviteLink = 3;
|
||||||
AccessRequired memberLabel = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,7 +344,7 @@ message CallLink {
|
||||||
string name = 3;
|
string name = 3;
|
||||||
Restrictions restrictions = 4;
|
Restrictions restrictions = 4;
|
||||||
uint64 expirationMs = 5;
|
uint64 expirationMs = 5;
|
||||||
reserved /*epoch*/ 6;
|
optional bytes epoch = 6; // May be absent/empty for older links
|
||||||
}
|
}
|
||||||
|
|
||||||
message AdHocCall {
|
message AdHocCall {
|
||||||
|
|
@ -468,14 +402,6 @@ message ChatItem {
|
||||||
message DirectionlessMessageDetails {
|
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 chatId = 1; // conversation id
|
||||||
uint64 authorId = 2; // recipient id
|
uint64 authorId = 2; // recipient id
|
||||||
uint64 dateSent = 3;
|
uint64 dateSent = 3;
|
||||||
|
|
@ -502,11 +428,7 @@ message ChatItem {
|
||||||
GiftBadge giftBadge = 17;
|
GiftBadge giftBadge = 17;
|
||||||
ViewOnceMessage viewOnceMessage = 18;
|
ViewOnceMessage viewOnceMessage = 18;
|
||||||
DirectStoryReplyMessage directStoryReplyMessage = 19; // group story reply messages are not backed up
|
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 {
|
message SendStatus {
|
||||||
|
|
@ -837,7 +759,6 @@ message Quote {
|
||||||
NORMAL = 1;
|
NORMAL = 1;
|
||||||
GIFT_BADGE = 2;
|
GIFT_BADGE = 2;
|
||||||
VIEW_ONCE = 3;
|
VIEW_ONCE = 3;
|
||||||
POLL = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message QuotedAttachment {
|
message QuotedAttachment {
|
||||||
|
|
@ -884,30 +805,6 @@ message Reaction {
|
||||||
uint64 sortOrder = 4;
|
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 {
|
message ChatUpdateMessage {
|
||||||
// If unset, importers should ignore the update message without throwing an error.
|
// If unset, importers should ignore the update message without throwing an error.
|
||||||
oneof update {
|
oneof update {
|
||||||
|
|
@ -920,8 +817,6 @@ message ChatUpdateMessage {
|
||||||
IndividualCall individualCall = 7;
|
IndividualCall individualCall = 7;
|
||||||
GroupCall groupCall = 8;
|
GroupCall groupCall = 8;
|
||||||
LearnedProfileChatUpdate learnedProfileChange = 9;
|
LearnedProfileChatUpdate learnedProfileChange = 9;
|
||||||
PollTerminateUpdate pollTerminate = 10;
|
|
||||||
PinMessageUpdate pinMessage = 11;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1078,8 +973,6 @@ message GroupChangeChatUpdate {
|
||||||
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
|
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
|
||||||
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
|
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
|
||||||
GroupExpirationTimerUpdate groupExpirationTimerUpdate = 34;
|
GroupExpirationTimerUpdate groupExpirationTimerUpdate = 34;
|
||||||
GroupMemberLabelAccessLevelChangeUpdate groupMemberLabelAccessLevelChangeUpdate = 35;
|
|
||||||
GroupTerminateChangeUpdate groupTerminateChangeUpdate = 36;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1131,15 +1024,6 @@ message GroupAttributesAccessLevelChangeUpdate {
|
||||||
GroupV2AccessLevel accessLevel = 2;
|
GroupV2AccessLevel accessLevel = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupMemberLabelAccessLevelChangeUpdate {
|
|
||||||
optional bytes updaterAci = 1;
|
|
||||||
GroupV2AccessLevel accessLevel = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GroupTerminateChangeUpdate {
|
|
||||||
optional bytes updaterAci = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GroupAnnouncementOnlyChangeUpdate {
|
message GroupAnnouncementOnlyChangeUpdate {
|
||||||
optional bytes updaterAci = 1;
|
optional bytes updaterAci = 1;
|
||||||
bool isAnnouncementOnly = 2;
|
bool isAnnouncementOnly = 2;
|
||||||
|
|
@ -1298,16 +1182,6 @@ message GroupExpirationTimerUpdate {
|
||||||
optional bytes updaterAci = 2;
|
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 {
|
message StickerPack {
|
||||||
bytes packId = 1;
|
bytes packId = 1;
|
||||||
bytes packKey = 2;
|
bytes packKey = 2;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ANDROID_GIT_REVISION=${1:-439760e7732585bfd078d92d93732c04cc31e29e}
|
ANDROID_GIT_REVISION=${1:-62fdf3d1aa9f637729ae67b55aadcc24f38f0117}
|
||||||
DESKTOP_GIT_REVISION=${1:-1b2a3e7b283c32c5654a39da12fc04139fd26dbd}
|
DESKTOP_GIT_REVISION=${1:-203a1cc5e3f9c1533a58caff72e13aa6eaeeddc7}
|
||||||
|
|
||||||
update_proto() {
|
update_proto() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
Signal-Android)
|
Signal-Android)
|
||||||
REPO="Signal-Android"
|
REPO="Signal-Android"
|
||||||
prefix="lib/libsignal-service/src/main/protowire/"
|
prefix="libsignal-service/src/main/protowire/"
|
||||||
GIT_REVISION=$ANDROID_GIT_REVISION
|
GIT_REVISION=$ANDROID_GIT_REVISION
|
||||||
;;
|
;;
|
||||||
Signal-Android-Archive)
|
Signal-Android-App)
|
||||||
REPO="Signal-Android"
|
REPO="Signal-Android"
|
||||||
prefix="lib/archive/src/main/protowire/"
|
prefix="app/src/main/protowire/"
|
||||||
GIT_REVISION=$ANDROID_GIT_REVISION
|
GIT_REVISION=$ANDROID_GIT_REVISION
|
||||||
;;
|
;;
|
||||||
Signal-Desktop)
|
Signal-Desktop)
|
||||||
|
|
@ -34,10 +34,10 @@ update_proto Signal-Android StickerResources.proto
|
||||||
update_proto Signal-Android WebSocketResources.proto
|
update_proto Signal-Android WebSocketResources.proto
|
||||||
update_proto Signal-Android StorageService.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
|
mv Backup.proto backuppb/Backup.proto
|
||||||
|
|
||||||
update_proto Signal-Desktop DeviceName.proto
|
update_proto Signal-Desktop DeviceName.proto
|
||||||
# TODO these were moved to libsignal only
|
update_proto Signal-Desktop UnidentifiedDelivery.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
|
update_proto Signal-Desktop ContactDiscovery.proto
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,11 @@ package signalmeow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
mrand "math/rand/v2"
|
mrand "math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -53,6 +54,7 @@ const (
|
||||||
StateProvisioningError ProvisioningState = iota
|
StateProvisioningError ProvisioningState = iota
|
||||||
StateProvisioningURLReceived
|
StateProvisioningURLReceived
|
||||||
StateProvisioningDataReceived
|
StateProvisioningDataReceived
|
||||||
|
StateProvisioningPreKeysRegistered
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s ProvisioningState) String() string {
|
func (s ProvisioningState) String() string {
|
||||||
|
|
@ -63,6 +65,8 @@ func (s ProvisioningState) String() string {
|
||||||
return "StateProvisioningURLReceived"
|
return "StateProvisioningURLReceived"
|
||||||
case StateProvisioningDataReceived:
|
case StateProvisioningDataReceived:
|
||||||
return "StateProvisioningDataReceived"
|
return "StateProvisioningDataReceived"
|
||||||
|
case StateProvisioningPreKeysRegistered:
|
||||||
|
return "StateProvisioningPreKeysRegistered"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("ProvisioningState(%d)", s)
|
return fmt.Sprintf("ProvisioningState(%d)", s)
|
||||||
}
|
}
|
||||||
|
|
@ -124,8 +128,8 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
||||||
username := *provisioningMessage.Number
|
username := *provisioningMessage.Number
|
||||||
password := random.String(22)
|
password := random.String(22)
|
||||||
code := provisioningMessage.ProvisioningCode
|
code := provisioningMessage.ProvisioningCode
|
||||||
aciRegistrationID := mrand.IntN(16383) + 1
|
aciRegistrationID := mrand.Intn(16383) + 1
|
||||||
pniRegistrationID := mrand.IntN(16383) + 1
|
pniRegistrationID := mrand.Intn(16383) + 1
|
||||||
aciSignedPreKey := GenerateSignedPreKey(1, aciIdentityKeyPair)
|
aciSignedPreKey := GenerateSignedPreKey(1, aciIdentityKeyPair)
|
||||||
pniSignedPreKey := GenerateSignedPreKey(1, pniIdentityKeyPair)
|
pniSignedPreKey := GenerateSignedPreKey(1, pniIdentityKeyPair)
|
||||||
aciPQLastResortPreKey := GenerateKyberPreKeys(1, 1, aciIdentityKeyPair)[0]
|
aciPQLastResortPreKey := GenerateKyberPreKeys(1, 1, aciIdentityKeyPair)[0]
|
||||||
|
|
@ -165,19 +169,24 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
||||||
DeviceID: deviceId,
|
DeviceID: deviceId,
|
||||||
Number: *provisioningMessage.Number,
|
Number: *provisioningMessage.Number,
|
||||||
Password: password,
|
Password: password,
|
||||||
|
MasterKey: provisioningMessage.GetMasterKey(),
|
||||||
AccountEntropyPool: libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()),
|
AccountEntropyPool: libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()),
|
||||||
EphemeralBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetEphemeralBackupKey()),
|
EphemeralBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetEphemeralBackupKey()),
|
||||||
MediaRootBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetMediaRootBackupKey()),
|
MediaRootBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetMediaRootBackupKey()),
|
||||||
}
|
}
|
||||||
if provisioningMessage.GetAccountEntropyPool() != "" {
|
if provisioningMessage.GetAccountEntropyPool() != "" {
|
||||||
data.MasterKey, err = libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()).DeriveSVRKey()
|
var masterKey []byte
|
||||||
|
masterKey, err = libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()).DeriveSVRKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Msg("Derived master key from account entropy pool")
|
log.Debug().Msg("Derived master key from account entropy pool")
|
||||||
}
|
}
|
||||||
} else {
|
if data.MasterKey == nil {
|
||||||
log.Warn().Msg("No account entropy pool in provisioning message")
|
data.MasterKey = masterKey
|
||||||
|
} else if !hmac.Equal(data.MasterKey, masterKey) {
|
||||||
|
log.Warn().Msg("Master key mismatch")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the provisioning data
|
// Store the provisioning data
|
||||||
|
|
@ -241,6 +250,28 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
||||||
|
|
||||||
// Return the provisioning data
|
// Return the provisioning data
|
||||||
c <- ProvisioningResponse{State: StateProvisioningDataReceived, ProvisioningData: 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
|
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)
|
return "", fmt.Errorf("failed to unmarshal provisioning UUID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
linkCapabilities := []string{"backup4,backup5"}
|
linkCapabilities := []string{"backup4", "backup5"}
|
||||||
if !allowBackup {
|
if !allowBackup {
|
||||||
linkCapabilities = []string{}
|
linkCapabilities = []string{}
|
||||||
}
|
}
|
||||||
|
|
@ -335,19 +366,36 @@ var signalCapabilities = map[string]any{
|
||||||
var signalCapabilitiesBody = exerrors.Must(json.Marshal(signalCapabilities))
|
var signalCapabilitiesBody = exerrors.Must(json.Marshal(signalCapabilities))
|
||||||
|
|
||||||
func (cli *Client) RegisterCapabilities(ctx context.Context) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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(
|
func confirmDevice(
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ package signalmeow
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
"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 {
|
func (cli *Client) registerPush(ctx context.Context, pushType string, data any) error {
|
||||||
var resp *signalpb.WebSocketResponseMessage
|
username, password := cli.Store.BasicAuthCreds()
|
||||||
var err error
|
req := &web.HTTPReqOpt{
|
||||||
|
Username: &username,
|
||||||
|
Password: &password,
|
||||||
|
}
|
||||||
|
var method string
|
||||||
if data != nil {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err = cli.AuthedWS.SendRequest(ctx, http.MethodPut, "/v1/accounts/"+pushType, body, nil)
|
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (cli *Client) RegisterFCM(ctx context.Context, token string) error {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/ptr"
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
|
|
@ -250,23 +249,12 @@ func (cli *Client) StartReceiveLoops(ctx context.Context) (chan SignalConnection
|
||||||
cli.loopWg.Add(1)
|
cli.loopWg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer cli.loopWg.Done()
|
defer cli.loopWg.Done()
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case <-loopCtx.Done():
|
case <-loopCtx.Done():
|
||||||
return
|
return
|
||||||
case <-initialConnectChan:
|
case <-initialConnectChan:
|
||||||
log.Info().Msg("Both websockets connected, sending contacts sync request")
|
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
|
// TODO hacky
|
||||||
if cli.SyncContactsOnConnect {
|
if cli.SyncContactsOnConnect {
|
||||||
cli.SendContactSyncRequest(loopCtx)
|
cli.SendContactSyncRequest(loopCtx)
|
||||||
|
|
@ -274,15 +262,19 @@ func (cli *Client) StartReceiveLoops(ctx context.Context) (chan SignalConnection
|
||||||
if cli.Store.MasterKey == nil {
|
if cli.Store.MasterKey == nil {
|
||||||
cli.SendStorageMasterKeyRequest(loopCtx)
|
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() {
|
return statusChan, nil
|
||||||
cli.AuthedWS.ForceReconnect()
|
|
||||||
cli.UnauthedWS.ForceReconnect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) StopReceiveLoops() error {
|
func (cli *Client) StopReceiveLoops() error {
|
||||||
|
|
@ -357,25 +349,21 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log = log.With().
|
log = log.With().
|
||||||
Uint64("envelope_timestamp", envelope.GetClientTimestamp()).
|
Uint64("envelope_timestamp", envelope.GetTimestamp()).
|
||||||
Uint64("server_timestamp", envelope.GetServerTimestamp()).
|
Uint64("server_timestamp", envelope.GetServerTimestamp()).
|
||||||
Logger()
|
Logger()
|
||||||
ctx = log.WithContext(ctx)
|
ctx = log.WithContext(ctx)
|
||||||
destinationServiceID, _ := ParseStringOrBinaryServiceID(envelope.GetDestinationServiceId(), envelope.GetDestinationServiceIdBinary())
|
destinationServiceID, err := libsignalgo.ServiceIDFromString(envelope.GetDestinationServiceId())
|
||||||
sourceServiceID, _ := ParseStringOrBinaryServiceID(envelope.GetSourceServiceId(), envelope.GetSourceServiceIdBinary())
|
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("destination_service_id", envelope.GetDestinationServiceId()).
|
Str("destination_service_id", envelope.GetDestinationServiceId()).
|
||||||
Str("source_service_id", envelope.GetSourceServiceId()).
|
Str("source_service_id", envelope.GetSourceServiceId()).
|
||||||
Hex("destination_service_id_bytes", envelope.GetDestinationServiceIdBinary()).
|
Uint32("source_device_id", envelope.GetSourceDevice()).
|
||||||
Hex("source_service_id_bytes", envelope.GetSourceServiceIdBinary()).
|
|
||||||
Uint32("source_device_id", envelope.GetSourceDeviceId()).
|
|
||||||
Object("parsed_destination_service_id", destinationServiceID).
|
Object("parsed_destination_service_id", destinationServiceID).
|
||||||
Object("parsed_source_service_id", sourceServiceID).
|
|
||||||
Int32("envelope_type_id", int32(envelope.GetType())).
|
Int32("envelope_type_id", int32(envelope.GetType())).
|
||||||
Str("envelope_type", signalpb.Envelope_Type_name[int32(envelope.GetType())]).
|
Str("envelope_type", signalpb.Envelope_Type_name[int32(envelope.GetType())]).
|
||||||
Msg("Received envelope")
|
Msg("Received envelope")
|
||||||
|
|
||||||
result := cli.decryptEnvelope(ctx, envelope, sourceServiceID, destinationServiceID)
|
result := cli.decryptEnvelope(ctx, envelope)
|
||||||
|
|
||||||
err = cli.handleDecryptedResult(ctx, result, envelope, destinationServiceID)
|
err = cli.handleDecryptedResult(ctx, result, envelope, destinationServiceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -407,7 +395,7 @@ func (cli *Client) handleDecryptedResult(
|
||||||
result DecryptionResult,
|
result DecryptionResult,
|
||||||
envelope *signalpb.Envelope,
|
envelope *signalpb.Envelope,
|
||||||
destinationServiceID libsignalgo.ServiceID,
|
destinationServiceID libsignalgo.ServiceID,
|
||||||
) (retErr error) {
|
) error {
|
||||||
if errors.Is(result.Err, context.Canceled) {
|
if errors.Is(result.Err, context.Canceled) {
|
||||||
return result.Err
|
return result.Err
|
||||||
} else if ctx.Err() != nil {
|
} 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
|
handlerSuccess := true
|
||||||
defer func() {
|
|
||||||
if retErr == nil && !handlerSuccess {
|
|
||||||
retErr = ErrHandlerFailed
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// result.Err is set if there was an error during decryption and we
|
// result.Err is set if there was an error during decryption and we
|
||||||
// should notifiy the user that the message could not be decrypted
|
// should notifiy the user that the message could not be decrypted
|
||||||
if result.Err != nil {
|
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) {
|
if errors.Is(result.Err, EventAlreadyProcessed) {
|
||||||
|
logEvt.Discard().Msg("")
|
||||||
log.Debug().Err(result.Err).
|
log.Debug().Err(result.Err).
|
||||||
Bool("urgent", envelope.GetUrgent()).
|
Bool("urgent", envelope.GetUrgent()).
|
||||||
Stringer("content_hint", result.ContentHint).
|
Stringer("content_hint", result.ContentHint).
|
||||||
Uint64("server_ts", envelope.GetServerTimestamp()).
|
Uint64("server_ts", envelope.GetServerTimestamp()).
|
||||||
Uint64("client_ts", envelope.GetClientTimestamp()).
|
Uint64("client_ts", envelope.GetTimestamp()).
|
||||||
Stringer("sender", theirServiceID).
|
Stringer("sender", theirServiceID).
|
||||||
Msg("Ignoring already processed event")
|
Msg("Ignoring already processed event")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Err(result.Err).
|
logEvt.Stringer("sender", theirServiceID).Msg("Decryption error with known sender")
|
||||||
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")
|
|
||||||
// Only send decryption error event if the message was urgent,
|
// Only send decryption error event if the message was urgent,
|
||||||
// to prevent spamming errors for typing notifications and whatnot
|
// to prevent spamming errors for typing notifications and whatnot
|
||||||
if envelope.GetUrgent() &&
|
if envelope.GetUrgent() &&
|
||||||
|
|
@ -489,38 +456,27 @@ func (cli *Client) handleDecryptedResult(
|
||||||
handlerSuccess = cli.handleEvent(&events.DecryptionError{
|
handlerSuccess = cli.handleEvent(&events.DecryptionError{
|
||||||
Sender: theirServiceID.UUID,
|
Sender: theirServiceID.UUID,
|
||||||
Err: result.Err,
|
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 {
|
if !handlerSuccess {
|
||||||
return ErrHandlerFailed
|
return ErrHandlerFailed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rawContent := result.Content
|
content := result.Content
|
||||||
if rawContent == nil {
|
if content == nil {
|
||||||
log.Warn().Msg("Decrypted content is nil")
|
log.Warn().Msg("Decrypted content is nil")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceID, _ := result.SenderAddress.DeviceID()
|
name, _ := result.SenderAddress.Name()
|
||||||
log.Trace().
|
deviceId, _ := result.SenderAddress.DeviceID()
|
||||||
Any("raw_data", rawContent).
|
log.Trace().Any("raw_data", content).Str("sender", name).Uint("sender_device", deviceId).Msg("Raw event data")
|
||||||
Stringer("sender", theirServiceID).
|
|
||||||
Uint("sender_device", deviceID).
|
|
||||||
Msg("Raw event data")
|
|
||||||
newLog := log.With().
|
newLog := log.With().
|
||||||
Stringer("sender_name", theirServiceID).
|
Str("sender_name", name).
|
||||||
Uint("sender_device_id", deviceID).
|
Uint("sender_device_id", deviceId).
|
||||||
Str("destination_service_id", destinationServiceID.String()).
|
Str("destination_service_id", destinationServiceID.String()).
|
||||||
Logger()
|
Logger()
|
||||||
log = &newLog
|
log = &newLog
|
||||||
|
|
@ -529,32 +485,12 @@ func (cli *Client) handleDecryptedResult(
|
||||||
if result.CiphertextHash != nil {
|
if result.CiphertextHash != nil {
|
||||||
logEvt = logEvt.Hex("ciphertext_hash", result.CiphertextHash[:])
|
logEvt = logEvt.Hex("ciphertext_hash", result.CiphertextHash[:])
|
||||||
}
|
}
|
||||||
logEvt.Bool("unencrypted", result.Unencrypted).Msg("Decrypted message")
|
logEvt.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's a sender key distribution message, process it
|
// 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")
|
log.Debug().Msg("content includes sender key distribution message")
|
||||||
skdm, err := libsignalgo.DeserializeSenderKeyDistributionMessage(rawContent.SenderKeyDistributionMessage)
|
skdm, err := libsignalgo.DeserializeSenderKeyDistributionMessage(content.GetSenderKeyDistributionMessage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("DeserializeSenderKeyDistributionMessage error")
|
log.Err(err).Msg("DeserializeSenderKeyDistributionMessage error")
|
||||||
return err
|
return err
|
||||||
|
|
@ -571,137 +507,45 @@ 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() {
|
if destinationServiceID == cli.Store.PNIServiceID() {
|
||||||
_, err = cli.Store.RecipientStore.LoadAndUpdateRecipient(ctx, theirServiceID.UUID, uuid.Nil, func(recipient *types.Recipient) (changed bool, err error) {
|
_, 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 {
|
if !recipient.NeedsPNISignature {
|
||||||
log.Debug().Msg("Marking recipient as needing PNI signature")
|
log.Debug().Msg("Marking recipient as needing PNI signature")
|
||||||
recipient.NeedsPNISignature = true
|
recipient.NeedsPNISignature = true
|
||||||
changed = true
|
return true, nil
|
||||||
}
|
}
|
||||||
return
|
return false, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to set needs_pni_signature flag after receiving message to PNI service ID")
|
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 content.GetPniSignatureMessage() != nil {
|
||||||
if rawContent.PniSignatureMessage != nil {
|
|
||||||
log.Debug().Msg("Content includes PNI signature message")
|
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 {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Hex("pni_raw", rawContent.PniSignatureMessage.GetPni()).
|
Hex("pni_raw", content.GetPniSignatureMessage().GetPni()).
|
||||||
Stringer("aci", theirServiceID.UUID).
|
Stringer("aci", theirServiceID.UUID).
|
||||||
Msg("Failed to verify ACI-PNI mapping")
|
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sendDeliveryReceipt && handlerSuccess {
|
|
||||||
err = cli.sendDeliveryReceipts(ctx, []uint64{deliveryReceiptTS}, theirServiceID.UUID)
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("sendDeliveryReceipts error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupOrUserID(groupID types.GroupIdentifier, userID libsignalgo.ServiceID) string {
|
|
||||||
if groupID == "" {
|
|
||||||
return userID.String()
|
|
||||||
}
|
|
||||||
return string(groupID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *Client) handleSyncMessage(ctx context.Context, msg *signalpb.SyncMessage, envelope *signalpb.Envelope) (handlerSuccess bool) {
|
|
||||||
// TODO: handle more sync messages
|
// TODO: handle more sync messages
|
||||||
handlerSuccess = true
|
if content.SyncMessage != nil {
|
||||||
log := zerolog.Ctx(ctx)
|
if content.SyncMessage.Keys != nil {
|
||||||
switch content := msg.Content.(type) {
|
aep := libsignalgo.AccountEntropyPool(content.SyncMessage.Keys.GetAccountEntropyPool())
|
||||||
case *signalpb.SyncMessage_Keys_:
|
cli.Store.MasterKey = content.SyncMessage.Keys.GetMaster()
|
||||||
aep := libsignalgo.AccountEntropyPool(content.Keys.GetAccountEntropyPool())
|
|
||||||
if aep != "" {
|
if aep != "" {
|
||||||
aepMasterKey, err := aep.DeriveSVRKey()
|
aepMasterKey, err := aep.DeriveSVRKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -717,72 +561,49 @@ func (cli *Client) handleSyncMessage(ctx context.Context, msg *signalpb.SyncMess
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Msg("No account entropy pool in sync message")
|
log.Debug().Msg("No account entropy pool in sync message")
|
||||||
}
|
}
|
||||||
err := cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData)
|
err = cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to save device after receiving master key")
|
log.Err(err).Msg("Failed to save device after receiving master key")
|
||||||
} else {
|
} else {
|
||||||
log.Info().Msg("Received master key")
|
log.Info().Msg("Received master key")
|
||||||
go cli.SyncStorage(ctx)
|
go cli.SyncStorage(ctx)
|
||||||
}
|
}
|
||||||
case *signalpb.SyncMessage_FetchLatest_:
|
} else if content.SyncMessage.GetFetchLatest().GetType() == signalpb.SyncMessage_FetchLatest_STORAGE_MANIFEST {
|
||||||
switch content.FetchLatest.GetType() {
|
|
||||||
case signalpb.SyncMessage_FetchLatest_STORAGE_MANIFEST:
|
|
||||||
log.Debug().Msg("Received storage manifest fetch latest notice")
|
log.Debug().Msg("Received storage manifest fetch latest notice")
|
||||||
go cli.SyncStorage(ctx)
|
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.SyncMessage.GetSent()
|
||||||
syncSent := content.Sent
|
|
||||||
if syncSent.GetMessage() != nil || syncSent.GetEditMessage() != nil {
|
if syncSent.GetMessage() != nil || syncSent.GetEditMessage() != nil {
|
||||||
syncDestinationServiceID, err := ParseStringOrBinaryServiceID(syncSent.GetDestinationServiceId(), syncSent.GetDestinationServiceIdBinary())
|
destination := syncSent.DestinationServiceId
|
||||||
if err != nil && !errors.Is(err, ErrEmptyUUIDInput) {
|
var syncDestinationServiceID libsignalgo.ServiceID
|
||||||
|
if destination != nil {
|
||||||
|
syncDestinationServiceID, err = libsignalgo.ServiceIDFromString(*destination)
|
||||||
|
if err != nil {
|
||||||
log.Err(err).Msg("Sync message destination parse error")
|
log.Err(err).Msg("Sync message destination parse error")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if syncSent.GetDestinationE164() != "" && !syncDestinationServiceID.IsEmpty() {
|
if syncSent.GetDestinationE164() != "" {
|
||||||
aci, pni := syncDestinationServiceID.ToACIAndPNI()
|
aci, pni := syncDestinationServiceID.ToACIAndPNI()
|
||||||
_, err = cli.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, syncSent.GetDestinationE164())
|
_, err = cli.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, syncSent.GetDestinationE164())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to update recipient E164 after receiving sync message")
|
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 destination == nil && syncSent.GetMessage().GetGroupV2() == nil && syncSent.GetEditMessage().GetDataMessage().GetGroupV2() == nil {
|
||||||
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")
|
log.Warn().Msg("sync message sent destination is nil")
|
||||||
} else if syncSent.Message != nil {
|
} else if content.SyncMessage.Sent.Message != nil {
|
||||||
// TODO handle expiration start ts, and maybe the sync message ts?
|
// TODO handle expiration start ts, and maybe the sync message ts?
|
||||||
cli.incomingDataMessage(ctx, syncSent.Message, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp(), false)
|
cli.incomingDataMessage(ctx, content.SyncMessage.Sent.Message, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp())
|
||||||
} else if syncSent.EditMessage != nil {
|
} else if content.SyncMessage.Sent.EditMessage != nil {
|
||||||
cli.incomingEditMessage(ctx, syncSent.EditMessage, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp(), false)
|
cli.incomingEditMessage(ctx, content.SyncMessage.Sent.EditMessage, cli.Store.ACI, syncDestinationServiceID, envelope.GetServerTimestamp())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *signalpb.SyncMessage_Contacts_:
|
if content.SyncMessage.Contacts != nil {
|
||||||
log.Debug().Msg("Recieved sync message contacts")
|
log.Debug().Msg("Recieved sync message contacts")
|
||||||
if content.Contacts.Blob != nil {
|
blob := content.SyncMessage.Contacts.Blob
|
||||||
// TODO roundtrip via disk to save memory
|
if blob != nil {
|
||||||
contactsBytes, err := DownloadAttachmentWithPointer(ctx, content.Contacts.Blob, nil, nil)
|
contactsBytes, err := DownloadAttachmentWithPointer(ctx, blob, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Contacts Sync DownloadAttachment error")
|
log.Err(err).Msg("Contacts Sync DownloadAttachment error")
|
||||||
}
|
}
|
||||||
|
|
@ -795,7 +616,7 @@ func (cli *Client) handleSyncMessage(ctx context.Context, msg *signalpb.SyncMess
|
||||||
convertedContacts := make([]*types.Recipient, 0, len(contacts))
|
convertedContacts := make([]*types.Recipient, 0, len(contacts))
|
||||||
err = cli.Store.DoContactTxn(ctx, func(ctx context.Context) error {
|
err = cli.Store.DoContactTxn(ctx, func(ctx context.Context) error {
|
||||||
for i, signalContact := range contacts {
|
for i, signalContact := range contacts {
|
||||||
if (signalContact.Aci == nil || *signalContact.Aci == "") && len(signalContact.AciBinary) != 16 {
|
if signalContact.Aci == nil || *signalContact.Aci == "" {
|
||||||
// TODO lookup PNI via CDSI and store that when ACI is missing?
|
// TODO lookup PNI via CDSI and store that when ACI is missing?
|
||||||
log.Info().
|
log.Info().
|
||||||
Any("contact", signalContact).
|
Any("contact", signalContact).
|
||||||
|
|
@ -818,59 +639,90 @@ func (cli *Client) handleSyncMessage(ctx context.Context, msg *signalpb.SyncMess
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
if content.SyncMessage.Read != nil {
|
||||||
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{
|
handlerSuccess = cli.handleEvent(&events.ReadSelf{
|
||||||
Timestamp: envelope.GetClientTimestamp(),
|
Timestamp: envelope.GetTimestamp(),
|
||||||
Messages: msg.Read,
|
Messages: content.SyncMessage.GetRead(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
if content.SyncMessage.DeleteForMe != nil {
|
||||||
return
|
handlerSuccess = cli.handleEvent(&events.DeleteForMe{
|
||||||
|
Timestamp: envelope.GetTimestamp(),
|
||||||
|
SyncMessage_DeleteForMe: content.SyncMessage.DeleteForMe,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to deserialize PNI identity key: %w", err)
|
log.Err(err).Msg("sendDeliveryReceipts error")
|
||||||
}
|
}
|
||||||
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
|
|
||||||
|
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,
|
||||||
|
}) && handlerSuccess
|
||||||
|
}
|
||||||
|
if !handlerSuccess {
|
||||||
|
return ErrHandlerFailed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupOrUserID(groupID types.GroupIdentifier, userID libsignalgo.ServiceID) string {
|
||||||
|
if groupID == "" {
|
||||||
|
return userID.String()
|
||||||
|
}
|
||||||
|
return string(groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) handlePNISignatureMessage(ctx context.Context, sender libsignalgo.ServiceID, msg *signalpb.PniSignatureMessage) error {
|
func (cli *Client) handlePNISignatureMessage(ctx context.Context, sender libsignalgo.ServiceID, msg *signalpb.PniSignatureMessage) error {
|
||||||
|
|
@ -923,14 +775,7 @@ func (cli *Client) handlePNISignatureMessage(ctx context.Context, sender libsign
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) incomingEditMessage(
|
func (cli *Client) incomingEditMessage(ctx context.Context, editMessage *signalpb.EditMessage, messageSenderACI uuid.UUID, chatRecipient libsignalgo.ServiceID, serverTimestamp uint64) bool {
|
||||||
ctx context.Context,
|
|
||||||
editMessage *signalpb.EditMessage,
|
|
||||||
messageSenderACI uuid.UUID,
|
|
||||||
chatRecipient libsignalgo.ServiceID,
|
|
||||||
serverTimestamp uint64,
|
|
||||||
isBlocked bool,
|
|
||||||
) (handlerSuccess, sendDeliveryReceipt bool) {
|
|
||||||
// If it's a group message, get the ID and invalidate cache if necessary
|
// If it's a group message, get the ID and invalidate cache if necessary
|
||||||
var groupID types.GroupIdentifier
|
var groupID types.GroupIdentifier
|
||||||
var groupRevision uint32
|
var groupRevision uint32
|
||||||
|
|
@ -942,12 +787,9 @@ func (cli *Client) incomingEditMessage(
|
||||||
groupID, err = cli.StoreMasterKey(ctx, masterKey)
|
groupID, err = cli.StoreMasterKey(ctx, masterKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("StoreMasterKey error")
|
zerolog.Ctx(ctx).Err(err).Msg("StoreMasterKey error")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
groupRevision = editMessage.GetDataMessage().GetGroupV2().GetRevision()
|
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{
|
return cli.handleEvent(&events.ChatEvent{
|
||||||
Info: events.MessageInfo{
|
Info: events.MessageInfo{
|
||||||
|
|
@ -957,24 +799,17 @@ func (cli *Client) incomingEditMessage(
|
||||||
ServerTimestamp: serverTimestamp,
|
ServerTimestamp: serverTimestamp,
|
||||||
},
|
},
|
||||||
Event: editMessage,
|
Event: editMessage,
|
||||||
}), true
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) incomingDataMessage(
|
func (cli *Client) incomingDataMessage(ctx context.Context, dataMessage *signalpb.DataMessage, messageSenderACI uuid.UUID, chatRecipient libsignalgo.ServiceID, serverTimestamp uint64) bool {
|
||||||
ctx context.Context,
|
|
||||||
dataMessage *signalpb.DataMessage,
|
|
||||||
messageSenderACI uuid.UUID,
|
|
||||||
chatRecipient libsignalgo.ServiceID,
|
|
||||||
serverTimestamp uint64,
|
|
||||||
isBlocked bool,
|
|
||||||
) (handlerSuccess, sendDeliveryReceipt bool) {
|
|
||||||
// If there's a profile key, save it
|
// If there's a profile key, save it
|
||||||
if dataMessage.ProfileKey != nil {
|
if dataMessage.ProfileKey != nil {
|
||||||
profileKey := libsignalgo.ProfileKey(dataMessage.ProfileKey)
|
profileKey := libsignalgo.ProfileKey(dataMessage.ProfileKey)
|
||||||
err := cli.Store.RecipientStore.StoreProfileKey(ctx, messageSenderACI, profileKey)
|
err := cli.Store.RecipientStore.StoreProfileKey(ctx, messageSenderACI, profileKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("StoreProfileKey error")
|
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)
|
groupID, err = cli.StoreMasterKey(ctx, masterKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("StoreMasterKey error")
|
zerolog.Ctx(ctx).Err(err).Msg("StoreMasterKey error")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
groupRevision = dataMessage.GetGroupV2().GetRevision()
|
groupRevision = dataMessage.GetGroupV2().GetRevision()
|
||||||
} else if isBlocked {
|
|
||||||
zerolog.Ctx(ctx).Debug().Msg("Dropping direct message from blocked user")
|
|
||||||
return true, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evtInfo := events.MessageInfo{
|
evtInfo := events.MessageInfo{
|
||||||
|
|
@ -1005,17 +837,17 @@ func (cli *Client) incomingDataMessage(
|
||||||
}
|
}
|
||||||
// Hacky special case for group calls to cache the state
|
// Hacky special case for group calls to cache the state
|
||||||
if dataMessage.GroupCallUpdate != nil {
|
if dataMessage.GroupCallUpdate != nil {
|
||||||
isRinging := cli.GroupCache.UpdateActiveCall(groupID, dataMessage.GroupCallUpdate.GetEraId())
|
isRinging := cli.UpdateActiveCalls(groupID, dataMessage.GroupCallUpdate.GetEraId())
|
||||||
return cli.handleEvent(&events.Call{
|
return cli.handleEvent(&events.Call{
|
||||||
Info: evtInfo,
|
Info: evtInfo,
|
||||||
Timestamp: dataMessage.GetTimestamp(),
|
Timestamp: dataMessage.GetTimestamp(),
|
||||||
IsRinging: isRinging,
|
IsRinging: isRinging,
|
||||||
}), true
|
})
|
||||||
} else {
|
} else {
|
||||||
return cli.handleEvent(&events.ChatEvent{
|
return cli.handleEvent(&events.ChatEvent{
|
||||||
Info: evtInfo,
|
Info: evtInfo,
|
||||||
Event: dataMessage,
|
Event: dataMessage,
|
||||||
}), true
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,21 +39,18 @@ type DecryptionResult struct {
|
||||||
Content *signalpb.Content
|
Content *signalpb.Content
|
||||||
ContentHint signalpb.UnidentifiedSenderMessage_Message_ContentHint
|
ContentHint signalpb.UnidentifiedSenderMessage_Message_ContentHint
|
||||||
Err error
|
Err error
|
||||||
GroupID *libsignalgo.GroupIdentifier
|
|
||||||
Unencrypted bool
|
|
||||||
|
|
||||||
Retriable bool
|
|
||||||
Ciphertext []byte
|
|
||||||
CiphertextType libsignalgo.CiphertextMessageType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) decryptEnvelope(
|
func (cli *Client) decryptEnvelope(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
envelope *signalpb.Envelope,
|
envelope *signalpb.Envelope,
|
||||||
sourceServiceID, destinationServiceID libsignalgo.ServiceID,
|
|
||||||
) DecryptionResult {
|
) DecryptionResult {
|
||||||
if destinationServiceID.IsEmpty() {
|
log := zerolog.Ctx(ctx)
|
||||||
return DecryptionResult{Err: fmt.Errorf("envelope missing destination service ID")}
|
|
||||||
|
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 {
|
switch *envelope.Type {
|
||||||
|
|
@ -64,14 +61,17 @@ func (cli *Client) decryptEnvelope(
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
case signalpb.Envelope_PREKEY_MESSAGE, signalpb.Envelope_DOUBLE_RATCHET:
|
case signalpb.Envelope_PREKEY_BUNDLE, signalpb.Envelope_CIPHERTEXT:
|
||||||
sender, err := sourceServiceID.Address(uint(envelope.GetSourceDeviceId()))
|
sender, err := libsignalgo.NewUUIDAddressFromString(
|
||||||
|
*envelope.SourceServiceId,
|
||||||
|
uint(*envelope.SourceDevice),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecryptionResult{Err: fmt.Errorf("failed to wrap address: %v", err)}
|
return DecryptionResult{Err: fmt.Errorf("failed to wrap address: %v", err)}
|
||||||
}
|
}
|
||||||
var result *DecryptionResult
|
var result *DecryptionResult
|
||||||
var bundleType string
|
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())
|
result, err = cli.prekeyDecrypt(ctx, destinationServiceID, sender, envelope.Content, envelope.GetServerTimestamp())
|
||||||
bundleType = "prekey bundle"
|
bundleType = "prekey bundle"
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -79,34 +79,19 @@ func (cli *Client) decryptEnvelope(
|
||||||
bundleType = "ciphertext"
|
bundleType = "ciphertext"
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecryptionResult{
|
return DecryptionResult{Err: fmt.Errorf("failed to decrypt %s envelope: %w", bundleType, err), SenderAddress: sender}
|
||||||
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 *result
|
return *result
|
||||||
|
|
||||||
case signalpb.Envelope_PLAINTEXT_CONTENT:
|
case signalpb.Envelope_PLAINTEXT_CONTENT:
|
||||||
addr, err := sourceServiceID.Address(uint(envelope.GetSourceDeviceId()))
|
return DecryptionResult{Err: fmt.Errorf("plaintext messages are not supported")}
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
case signalpb.Envelope_SERVER_DELIVERY_RECEIPT:
|
case signalpb.Envelope_SERVER_DELIVERY_RECEIPT:
|
||||||
return DecryptionResult{Err: fmt.Errorf("server delivery receipt envelopes are not yet supported")}
|
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:
|
case signalpb.Envelope_UNKNOWN:
|
||||||
return DecryptionResult{Err: fmt.Errorf("unknown envelope type")}
|
return DecryptionResult{Err: fmt.Errorf("unknown envelope type")}
|
||||||
|
|
||||||
|
|
@ -185,17 +170,12 @@ func (cli *Client) prekeyDecrypt(
|
||||||
if is == nil {
|
if is == nil {
|
||||||
return nil, fmt.Errorf("no identity store found for %s", destination)
|
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) {
|
plaintext, ciphertextHash, err := cli.bufferedDecryptTxn(ctx, encryptedContent, serverTimestamp, func(ctx context.Context) ([]byte, error) {
|
||||||
return libsignalgo.DecryptPreKey(
|
return libsignalgo.DecryptPreKey(
|
||||||
ctx,
|
ctx,
|
||||||
preKeyMessage,
|
preKeyMessage,
|
||||||
sender,
|
sender,
|
||||||
destinationAddress,
|
|
||||||
ss,
|
ss,
|
||||||
is,
|
is,
|
||||||
pks,
|
pks,
|
||||||
|
|
@ -243,16 +223,11 @@ func (cli *Client) decryptCiphertextEnvelope(
|
||||||
if identityStore == nil {
|
if identityStore == nil {
|
||||||
return nil, fmt.Errorf("no identity store for destination service ID %s", destinationServiceID)
|
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) {
|
plaintext, ciphertextHash, err := cli.bufferedDecryptTxn(ctx, ciphertext, serverTimestamp, func(ctx context.Context) ([]byte, error) {
|
||||||
return libsignalgo.Decrypt(
|
return libsignalgo.Decrypt(
|
||||||
ctx,
|
ctx,
|
||||||
message,
|
message,
|
||||||
senderAddress,
|
senderAddress,
|
||||||
destinationAddress,
|
|
||||||
sessionStore,
|
sessionStore,
|
||||||
identityStore,
|
identityStore,
|
||||||
)
|
)
|
||||||
|
|
@ -340,10 +315,6 @@ func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destin
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("failed to get content hint: %w", err)
|
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)
|
result.ContentHint = signalpb.UnidentifiedSenderMessage_Message_ContentHint(contentHint)
|
||||||
senderUUID, err := senderCertificate.GetSenderUUID()
|
senderUUID, err := senderCertificate.GetSenderUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -366,11 +337,8 @@ func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destin
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("failed to get USMC contents: %w", err)
|
return result, fmt.Errorf("failed to get USMC contents: %w", err)
|
||||||
}
|
}
|
||||||
result.Ciphertext = usmcContents
|
|
||||||
result.CiphertextType = messageType
|
|
||||||
newLog := log.With().
|
newLog := log.With().
|
||||||
Stringer("sender_uuid", senderUUID).
|
Stringer("sender_uuid", senderUUID).
|
||||||
Stringer("group_id", result.GroupID).
|
|
||||||
Uint32("sender_device_id", senderDeviceID).
|
Uint32("sender_device_id", senderDeviceID).
|
||||||
Str("sender_e164", senderE164).
|
Str("sender_e164", senderE164).
|
||||||
Uint8("sealed_sender_type", uint8(messageType)).
|
Uint8("sealed_sender_type", uint8(messageType)).
|
||||||
|
|
@ -395,25 +363,15 @@ func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destin
|
||||||
case libsignalgo.CiphertextMessageTypeWhisper:
|
case libsignalgo.CiphertextMessageTypeWhisper:
|
||||||
resultPtr, err = cli.decryptCiphertextEnvelope(ctx, destinationServiceID, senderAddress, usmcContents, envelope.GetServerTimestamp())
|
resultPtr, err = cli.decryptCiphertextEnvelope(ctx, destinationServiceID, senderAddress, usmcContents, envelope.GetServerTimestamp())
|
||||||
case libsignalgo.CiphertextMessageTypePlaintext:
|
case libsignalgo.CiphertextMessageTypePlaintext:
|
||||||
usmcContents, err = stripPadding(usmcContents)
|
// TODO: handle plaintext (usually DecryptionErrorMessage) and retries
|
||||||
if err != nil {
|
// when implementing SenderKey groups
|
||||||
err = fmt.Errorf("failed to strip padding: %w", err)
|
return result, fmt.Errorf("unsupported plaintext sealed sender message")
|
||||||
}
|
|
||||||
result.Unencrypted = true
|
|
||||||
result.Content = &signalpb.Content{
|
|
||||||
Content: &signalpb.Content_DecryptionErrorMessage{
|
|
||||||
DecryptionErrorMessage: usmcContents,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
default:
|
default:
|
||||||
return result, fmt.Errorf("unsupported sealed sender message type %d", messageType)
|
return result, fmt.Errorf("unsupported sealed sender message type %d", messageType)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Retriable = result.ContentHint == signalpb.UnidentifiedSenderMessage_Message_RESENDABLE
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
resultPtr.GroupID = result.GroupID
|
|
||||||
return *resultPtr, nil
|
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