mirror of
https://github.com/mautrix/signal.git
synced 2026-05-14 21:26:54 -04:00
243 lines
7.2 KiB
Go
243 lines
7.2 KiB
Go
// 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 signalid
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/google/uuid"
|
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
|
)
|
|
|
|
type directMediaType byte
|
|
|
|
const (
|
|
directMediaTypeAttachment directMediaType = 0
|
|
directMediaTypeGroupAvatar directMediaType = 1
|
|
directMediaTypeProfileAvatar directMediaType = 2
|
|
directMediaTypePlaintextDigestAttachment directMediaType = 3
|
|
)
|
|
|
|
type DirectMediaInfo interface {
|
|
AsMediaID() (networkid.MediaID, error)
|
|
}
|
|
|
|
var (
|
|
_ DirectMediaInfo = (*DirectMediaAttachment)(nil)
|
|
_ DirectMediaInfo = (*DirectMediaGroupAvatar)(nil)
|
|
_ DirectMediaInfo = (*DirectMediaProfileAvatar)(nil)
|
|
)
|
|
|
|
type DirectMediaAttachment struct {
|
|
CDNID uint64
|
|
CDNKey string
|
|
CDNNumber uint32
|
|
Key []byte
|
|
PlaintextDigest bool
|
|
Digest []byte
|
|
Size uint32
|
|
}
|
|
|
|
func (m DirectMediaAttachment) AsMediaID() (mediaID networkid.MediaID, err error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
attType := directMediaTypeAttachment
|
|
if m.PlaintextDigest {
|
|
attType = directMediaTypePlaintextDigestAttachment
|
|
}
|
|
|
|
if err = binary.Write(buf, binary.BigEndian, attType); err != nil {
|
|
return
|
|
} else if err = writeUvarint(buf, m.CDNID); err != nil {
|
|
return
|
|
} else if err = writeByteSlice(buf, []byte(m.CDNKey)); err != nil {
|
|
return
|
|
} else if err = writeUvarint(buf, uint64(m.CDNNumber)); err != nil {
|
|
return
|
|
} else if err = writeByteSlice(buf, m.Key); err != nil {
|
|
return
|
|
} else if err = writeByteSlice(buf, m.Digest); err != nil {
|
|
return
|
|
} else if err = writeUvarint(buf, uint64(m.Size)); err != nil {
|
|
return
|
|
}
|
|
|
|
return networkid.MediaID(buf.Bytes()), nil
|
|
}
|
|
|
|
type DirectMediaGroupAvatar struct {
|
|
UserID uuid.UUID
|
|
GroupID [32]byte
|
|
GroupAvatarPath string
|
|
}
|
|
|
|
func (m DirectMediaGroupAvatar) AsMediaID() (mediaID networkid.MediaID, err error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
if err = binary.Write(buf, binary.BigEndian, directMediaTypeGroupAvatar); err != nil {
|
|
return
|
|
} else if err = binary.Write(buf, binary.BigEndian, m.UserID); err != nil {
|
|
return
|
|
} else if err = binary.Write(buf, binary.BigEndian, m.GroupID); err != nil {
|
|
return
|
|
} else if err = writeByteSlice(buf, []byte(m.GroupAvatarPath)); err != nil {
|
|
return
|
|
}
|
|
|
|
return networkid.MediaID(buf.Bytes()), nil
|
|
}
|
|
|
|
type DirectMediaProfileAvatar struct {
|
|
UserID uuid.UUID
|
|
ContactID uuid.UUID
|
|
ProfileAvatarPath string
|
|
}
|
|
|
|
func (m DirectMediaProfileAvatar) AsMediaID() (mediaID networkid.MediaID, err error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
if err = binary.Write(buf, binary.BigEndian, directMediaTypeProfileAvatar); err != nil {
|
|
return
|
|
} else if err = binary.Write(buf, binary.BigEndian, m.UserID); err != nil {
|
|
return
|
|
} else if err = binary.Write(buf, binary.BigEndian, m.ContactID); err != nil {
|
|
return
|
|
} else if err = writeByteSlice(buf, []byte(m.ProfileAvatarPath)); err != nil {
|
|
return
|
|
}
|
|
|
|
return networkid.MediaID(buf.Bytes()), nil
|
|
}
|
|
|
|
func ParseDirectMediaInfo(mediaID networkid.MediaID) (_ DirectMediaInfo, err error) {
|
|
mediaIDLen := len(mediaID)
|
|
if mediaIDLen == 0 {
|
|
return nil, fmt.Errorf("empty media ID")
|
|
}
|
|
|
|
buf := bytes.NewReader(mediaID)
|
|
|
|
// type byte
|
|
var mediaType directMediaType
|
|
if err := binary.Read(buf, binary.BigEndian, &mediaType); err != nil {
|
|
return nil, fmt.Errorf("failed to read media type: %w", err)
|
|
}
|
|
|
|
switch mediaType {
|
|
case directMediaTypeAttachment, directMediaTypePlaintextDigestAttachment:
|
|
var info DirectMediaAttachment
|
|
info.PlaintextDigest = mediaType == directMediaTypePlaintextDigestAttachment
|
|
|
|
if info.CDNID, err = binary.ReadUvarint(buf); err != nil {
|
|
return info, fmt.Errorf("failed to read cdn id: %w", err)
|
|
}
|
|
if cdnKey, err := readByteSlice(buf, mediaIDLen); err != nil {
|
|
return info, fmt.Errorf("failed to read cdn key: %w", err)
|
|
} else {
|
|
info.CDNKey = string(cdnKey)
|
|
}
|
|
if cdnNumber, err := binary.ReadUvarint(buf); err != nil {
|
|
return info, fmt.Errorf("failed to read cdn number: %w", err)
|
|
} else {
|
|
info.CDNNumber = uint32(cdnNumber)
|
|
}
|
|
if info.Key, err = readByteSlice(buf, mediaIDLen); err != nil {
|
|
return info, fmt.Errorf("failed to read key: %w", err)
|
|
} else if info.Digest, err = readByteSlice(buf, mediaIDLen); err != nil {
|
|
return info, fmt.Errorf("failed to read digest: %w", err)
|
|
}
|
|
if size, err := binary.ReadUvarint(buf); err != nil {
|
|
return info, fmt.Errorf("failed to read cdn id: %w", err)
|
|
} else {
|
|
info.Size = uint32(size)
|
|
}
|
|
|
|
return &info, nil
|
|
case directMediaTypeGroupAvatar:
|
|
var info DirectMediaGroupAvatar
|
|
|
|
if err = binary.Read(buf, binary.BigEndian, &info.UserID); err != nil {
|
|
return info, fmt.Errorf("failed to read user id: %w", err)
|
|
} else if err = binary.Read(buf, binary.BigEndian, &info.GroupID); err != nil {
|
|
return info, fmt.Errorf("failed to read group id: %w", err)
|
|
}
|
|
if groupAvatarPath, err := readByteSlice(buf, mediaIDLen); err != nil {
|
|
return info, fmt.Errorf("failed to read group avatar path: %w", err)
|
|
} else {
|
|
info.GroupAvatarPath = string(groupAvatarPath)
|
|
}
|
|
|
|
return &info, nil
|
|
case directMediaTypeProfileAvatar:
|
|
var info DirectMediaProfileAvatar
|
|
|
|
if err = binary.Read(buf, binary.BigEndian, &info.UserID); err != nil {
|
|
return info, fmt.Errorf("failed to read user id: %w", err)
|
|
} else if err = binary.Read(buf, binary.BigEndian, &info.ContactID); err != nil {
|
|
return info, fmt.Errorf("failed to read contact id: %w", err)
|
|
}
|
|
if profileAvatarPath, err := readByteSlice(buf, mediaIDLen); err != nil {
|
|
return info, fmt.Errorf("failed to read profile avatar path: %w", err)
|
|
} else {
|
|
info.ProfileAvatarPath = string(profileAvatarPath)
|
|
}
|
|
return &info, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid direct media type %d", mediaType)
|
|
}
|
|
|
|
func HashMediaID(mediaID networkid.MediaID) [32]byte {
|
|
return sha256.Sum256(mediaID)
|
|
}
|
|
|
|
func writeUvarint(w io.Writer, i uint64) error {
|
|
_, err := w.Write(binary.AppendUvarint(nil, i))
|
|
return err
|
|
}
|
|
|
|
func writeByteSlice(w io.Writer, b []byte) error {
|
|
if err := writeUvarint(w, uint64(len(b))); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(b)
|
|
return err
|
|
}
|
|
|
|
type byteReader interface {
|
|
io.ByteReader
|
|
io.Reader
|
|
}
|
|
|
|
func readByteSlice(r byteReader, maxLength int) ([]byte, error) {
|
|
length, err := binary.ReadUvarint(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading uvarint failed: %w", err)
|
|
} else if int(length) > maxLength {
|
|
return nil, fmt.Errorf("byte slice size larger than expected: %d > %d", length, maxLength)
|
|
} else if length == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
buf := make([]byte, length)
|
|
_, err = io.ReadFull(r, buf)
|
|
return buf, err
|
|
}
|