23
vendor/github.com/status-im/status-go/protocol/common/chat_entity.go
generated
vendored
Normal file
23
vendor/github.com/status-im/status-go/protocol/common/chat_entity.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
// ChatEntity is anything that is sendable in a chat.
|
||||
// Currently it encompass a Message and EmojiReaction.
|
||||
type ChatEntity interface {
|
||||
proto.Message
|
||||
|
||||
GetChatId() string
|
||||
GetMessageType() protobuf.MessageType
|
||||
GetSigPubKey() *ecdsa.PublicKey
|
||||
GetProtobuf() proto.Message
|
||||
WrapGroupMessage() bool
|
||||
|
||||
SetMessageType(messageType protobuf.MessageType)
|
||||
}
|
||||
133
vendor/github.com/status-im/status-go/protocol/common/crypto.go
generated
vendored
Normal file
133
vendor/github.com/status-im/status-go/protocol/common/crypto.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
const (
|
||||
nonceLength = 12
|
||||
defaultECHDSharedKeyLength = 16
|
||||
defaultECHDMACLength = 16
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidCiphertextLength = errors.New("invalid cyphertext length")
|
||||
|
||||
letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
numberRunes = []rune("0123456789")
|
||||
alphanumericRunes = append(numberRunes, letterRunes...)
|
||||
)
|
||||
|
||||
func HashPublicKey(pk *ecdsa.PublicKey) []byte {
|
||||
return Shake256(crypto.CompressPubkey(pk))
|
||||
}
|
||||
|
||||
func Decrypt(cyphertext []byte, key []byte) ([]byte, error) {
|
||||
if len(cyphertext) < nonceLength {
|
||||
return nil, ErrInvalidCiphertextLength
|
||||
}
|
||||
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := cyphertext[:nonceLength]
|
||||
return gcm.Open(nil, nonce, cyphertext[nonceLength:], nil)
|
||||
}
|
||||
|
||||
func Encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||
}
|
||||
|
||||
func Shake256(buf []byte) []byte {
|
||||
h := make([]byte, 64)
|
||||
sha3.ShakeSum256(h, buf)
|
||||
return h
|
||||
}
|
||||
|
||||
// IsPubKeyEqual checks that two public keys are equal
|
||||
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
|
||||
// the curve is always the same, just compare the points
|
||||
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
|
||||
}
|
||||
|
||||
func PubkeyToHex(key *ecdsa.PublicKey) string {
|
||||
return types.EncodeHex(crypto.FromECDSAPub(key))
|
||||
}
|
||||
|
||||
func PubkeyToHexBytes(key *ecdsa.PublicKey) types.HexBytes {
|
||||
return crypto.FromECDSAPub(key)
|
||||
}
|
||||
|
||||
func HexToPubkey(pk string) (*ecdsa.PublicKey, error) {
|
||||
bytes, err := types.DecodeHex(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypto.UnmarshalPubkey(bytes)
|
||||
}
|
||||
|
||||
func MakeECDHSharedKey(yourPrivateKey *ecdsa.PrivateKey, theirPubKey *ecdsa.PublicKey) ([]byte, error) {
|
||||
return ecies.ImportECDSA(yourPrivateKey).GenerateShared(
|
||||
ecies.ImportECDSAPublic(theirPubKey),
|
||||
defaultECHDSharedKeyLength,
|
||||
defaultECHDMACLength,
|
||||
)
|
||||
}
|
||||
|
||||
func randomString(choice []rune, n int) (string, error) {
|
||||
max := big.NewInt(int64(len(choice)))
|
||||
rr := rand.Reader
|
||||
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
pos, err := rand.Int(rr, max)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b[i] = choice[pos.Int64()]
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func RandomAlphabeticalString(n int) (string, error) {
|
||||
return randomString(letterRunes, n)
|
||||
}
|
||||
|
||||
func RandomAlphanumericString(n int) (string, error) {
|
||||
return randomString(alphanumericRunes, n)
|
||||
}
|
||||
5
vendor/github.com/status-im/status-go/protocol/common/errors.go
generated
vendored
Normal file
5
vendor/github.com/status-im/status-go/protocol/common/errors.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package common
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrRecordNotFound = errors.New("record not found")
|
||||
33
vendor/github.com/status-im/status-go/protocol/common/feature_flags.go
generated
vendored
Normal file
33
vendor/github.com/status-im/status-go/protocol/common/feature_flags.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
type FeatureFlags struct {
|
||||
// Datasync indicates whether direct messages should be sent exclusively
|
||||
// using datasync, breaking change for non-v1 clients. Public messages
|
||||
// are not impacted
|
||||
Datasync bool
|
||||
|
||||
// PushNotification indicates whether we should be enabling the push notification feature
|
||||
PushNotifications bool
|
||||
|
||||
// MailserverCycle indicates whether we should enable or not the mailserver cycle
|
||||
MailserverCycle bool
|
||||
|
||||
// DisableCheckingForBackup disables backup loop
|
||||
DisableCheckingForBackup bool
|
||||
|
||||
// DisableAutoMessageLoop disables auto message loop
|
||||
DisableAutoMessageLoop bool
|
||||
|
||||
// ResendRawMessagesDisabled indicates whether we should be disabling sending raw messages
|
||||
ResendRawMessagesDisabled bool
|
||||
|
||||
// StoreNodesDisabled indicates whether we should fetch messages from store nodes
|
||||
StoreNodesDisabled bool
|
||||
|
||||
// Peersyncing indicates whether we should advertise and sync messages with other peers
|
||||
Peersyncing bool
|
||||
|
||||
// AutoRequestHistoricMessages indicates whether we should automatically request
|
||||
// historic messages on getting online, connecting to store node, etc.
|
||||
AutoRequestHistoricMessages bool
|
||||
}
|
||||
28
vendor/github.com/status-im/status-go/protocol/common/media_server_image_id.go
generated
vendored
Normal file
28
vendor/github.com/status-im/status-go/protocol/common/media_server_image_id.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package common
|
||||
|
||||
type MediaServerImageIDPrefix string
|
||||
type MediaServerImageIDPostfix string
|
||||
type MediaServerImageID string
|
||||
|
||||
func CreateImageID(prefix MediaServerImageIDPrefix, postfix MediaServerImageIDPostfix) MediaServerImageID {
|
||||
return MediaServerImageID(string(prefix) + string(postfix))
|
||||
}
|
||||
|
||||
const (
|
||||
MediaServerIconPostfix MediaServerImageIDPostfix = "icon"
|
||||
MediaServerBannerPostfix MediaServerImageIDPostfix = "banner"
|
||||
)
|
||||
|
||||
const (
|
||||
MediaServerContactPrefix MediaServerImageIDPrefix = "contact-"
|
||||
MediaServerCommunityPrefix MediaServerImageIDPrefix = "community-"
|
||||
MediaServerChannelCommunityPrefix MediaServerImageIDPrefix = "community-channel-"
|
||||
)
|
||||
|
||||
const (
|
||||
MediaServerContactIcon = MediaServerImageID(string(MediaServerContactPrefix) + string(MediaServerIconPostfix))
|
||||
MediaServerCommunityIcon = MediaServerImageID(string(MediaServerCommunityPrefix) + string(MediaServerIconPostfix))
|
||||
MediaServerCommunityBanner = MediaServerImageID(string(MediaServerCommunityPrefix) + string(MediaServerBannerPostfix))
|
||||
MediaServerChannelCommunityIcon = MediaServerImageID(string(MediaServerChannelCommunityPrefix) + string(MediaServerIconPostfix))
|
||||
MediaServerChannelCommunityBanner = MediaServerImageID(string(MediaServerChannelCommunityPrefix) + string(MediaServerBannerPostfix))
|
||||
)
|
||||
771
vendor/github.com/status-im/status-go/protocol/common/message.go
generated
vendored
Normal file
771
vendor/github.com/status-im/status-go/protocol/common/message.go
generated
vendored
Normal file
@@ -0,0 +1,771 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/status-im/markdown"
|
||||
"github.com/status-im/markdown/ast"
|
||||
|
||||
accountJson "github.com/status-im/status-go/account/json"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/protocol/audio"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
// QuotedMessage contains the original text of the message replied to
|
||||
type QuotedMessage struct {
|
||||
ID string `json:"id"`
|
||||
ContentType int64 `json:"contentType"`
|
||||
// From is a public key of the author of the message.
|
||||
From string `json:"from"`
|
||||
Text string `json:"text"`
|
||||
ParsedText json.RawMessage `json:"parsedText,omitempty"`
|
||||
AlbumImages json.RawMessage `json:"albumImages,omitempty"`
|
||||
AlbumImagesCount int64 `json:"albumImagesCount"`
|
||||
// ImageLocalURL is the local url of the image
|
||||
ImageLocalURL string `json:"image,omitempty"`
|
||||
// AudioLocalURL is the local url of the audio
|
||||
AudioLocalURL string `json:"audio,omitempty"`
|
||||
|
||||
HasSticker bool `json:"sticker,omitempty"`
|
||||
// CommunityID is the id of the community advertised
|
||||
CommunityID string `json:"communityId,omitempty"`
|
||||
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
|
||||
DeletedForMe bool `json:"deletedForMe,omitempty"`
|
||||
|
||||
DiscordMessage *protobuf.DiscordMessage `json:"discordMessage,omitempty"`
|
||||
}
|
||||
|
||||
type CommandState int
|
||||
|
||||
const (
|
||||
CommandStateRequestAddressForTransaction CommandState = iota + 1
|
||||
CommandStateRequestAddressForTransactionDeclined
|
||||
CommandStateRequestAddressForTransactionAccepted
|
||||
CommandStateRequestTransaction
|
||||
CommandStateRequestTransactionDeclined
|
||||
CommandStateTransactionPending
|
||||
CommandStateTransactionSent
|
||||
)
|
||||
|
||||
type ContactRequestState int
|
||||
|
||||
const (
|
||||
ContactRequestStatePending ContactRequestState = iota + 1
|
||||
ContactRequestStateAccepted
|
||||
ContactRequestStateDismissed
|
||||
)
|
||||
|
||||
type ContactVerificationState int
|
||||
|
||||
const (
|
||||
ContactVerificationStatePending ContactVerificationState = iota + 1
|
||||
ContactVerificationStateAccepted
|
||||
ContactVerificationStateDeclined
|
||||
ContactVerificationStateTrusted
|
||||
ContactVerificationStateUntrustworthy
|
||||
ContactVerificationStateCanceled
|
||||
)
|
||||
|
||||
const EveryoneMentionTag = "0x00001"
|
||||
|
||||
type CommandParameters struct {
|
||||
// ID is the ID of the initial message
|
||||
ID string `json:"id"`
|
||||
// From is the address we are sending the command from
|
||||
From string `json:"from"`
|
||||
// Address is the address sent with the command
|
||||
Address string `json:"address"`
|
||||
// Contract is the contract address for ERC20 tokens
|
||||
Contract string `json:"contract"`
|
||||
// Value is the value as a string sent
|
||||
Value string `json:"value"`
|
||||
// TransactionHash is the hash of the transaction
|
||||
TransactionHash string `json:"transactionHash"`
|
||||
// CommandState is the state of the command
|
||||
CommandState CommandState `json:"commandState"`
|
||||
// The Signature of the pk-bytes+transaction-hash from the wallet
|
||||
// address originating
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
// GapParameters is the From and To indicating the missing period in chat history
|
||||
type GapParameters struct {
|
||||
From uint32 `json:"from,omitempty"`
|
||||
To uint32 `json:"to,omitempty"`
|
||||
}
|
||||
|
||||
func (c *CommandParameters) IsTokenTransfer() bool {
|
||||
return len(c.Contract) != 0
|
||||
}
|
||||
|
||||
const (
|
||||
OutgoingStatusSending = "sending"
|
||||
OutgoingStatusSent = "sent"
|
||||
OutgoingStatusDelivered = "delivered"
|
||||
)
|
||||
|
||||
type Messages []*Message
|
||||
|
||||
func (m Messages) GetClock(i int) uint64 {
|
||||
return m[i].Clock
|
||||
}
|
||||
|
||||
// Message represents a message record in the database,
|
||||
// more specifically in user_messages table.
|
||||
type Message struct {
|
||||
*protobuf.ChatMessage
|
||||
|
||||
// ID calculated as keccak256(compressedAuthorPubKey, data) where data is unencrypted payload.
|
||||
ID string `json:"id"`
|
||||
// WhisperTimestamp is a timestamp of a Whisper envelope.
|
||||
WhisperTimestamp uint64 `json:"whisperTimestamp"`
|
||||
// From is a public key of the author of the message.
|
||||
From string `json:"from"`
|
||||
// Random 3 words name
|
||||
Alias string `json:"alias"`
|
||||
// Identicon of the author
|
||||
Identicon string `json:"identicon"`
|
||||
// The chat id to be stored locally
|
||||
LocalChatID string `json:"localChatId"`
|
||||
// Seen set to true when user have read this message already
|
||||
Seen bool `json:"seen"`
|
||||
OutgoingStatus string `json:"outgoingStatus,omitempty"`
|
||||
|
||||
QuotedMessage *QuotedMessage `json:"quotedMessage"`
|
||||
|
||||
// CommandParameters is the parameters sent with the message
|
||||
CommandParameters *CommandParameters `json:"commandParameters"`
|
||||
|
||||
// GapParameters is the value from/to related to the gap
|
||||
GapParameters *GapParameters `json:"gapParameters,omitempty"`
|
||||
|
||||
// Computed fields
|
||||
// RTL is whether this is a right-to-left message (arabic/hebrew script etc)
|
||||
RTL bool `json:"rtl"`
|
||||
// ParsedText is the parsed markdown for displaying
|
||||
ParsedText []byte `json:"parsedText,omitempty"`
|
||||
// ParsedTextAst is the ast of the parsed text
|
||||
ParsedTextAst *ast.Node `json:"-"`
|
||||
// LineCount is the count of newlines in the message
|
||||
LineCount int `json:"lineCount"`
|
||||
// Base64Image is the converted base64 image
|
||||
Base64Image string `json:"image,omitempty"`
|
||||
// ImagePath is the path of the image to be sent
|
||||
ImagePath string `json:"imagePath,omitempty"`
|
||||
// Base64Audio is the converted base64 audio
|
||||
Base64Audio string `json:"audio,omitempty"`
|
||||
// AudioPath is the path of the audio to be sent
|
||||
AudioPath string `json:"audioPath,omitempty"`
|
||||
// ImageLocalURL is the local url of the image
|
||||
ImageLocalURL string `json:"imageLocalUrl,omitempty"`
|
||||
// AudioLocalURL is the local url of the audio
|
||||
AudioLocalURL string `json:"audioLocalUrl,omitempty"`
|
||||
// StickerLocalURL is the local url of the sticker
|
||||
StickerLocalURL string `json:"stickerLocalUrl,omitempty"`
|
||||
|
||||
// CommunityID is the id of the community to advertise
|
||||
CommunityID string `json:"communityId,omitempty"`
|
||||
|
||||
// Replace indicates that this is a replacement of a message
|
||||
// that has been updated
|
||||
Replace string `json:"replace,omitempty"`
|
||||
New bool `json:"new,omitempty"`
|
||||
|
||||
SigPubKey *ecdsa.PublicKey `json:"-"`
|
||||
|
||||
// Mentions is an array of mentions for a given message
|
||||
Mentions []string
|
||||
|
||||
// Mentioned is whether the user is mentioned in the message
|
||||
Mentioned bool `json:"mentioned"`
|
||||
|
||||
// Replied is whether the user is replied to in the message
|
||||
Replied bool `json:"replied"`
|
||||
|
||||
// Links is an array of links within given message
|
||||
Links []string
|
||||
LinkPreviews []LinkPreview `json:"linkPreviews"`
|
||||
StatusLinkPreviews []StatusLinkPreview `json:"statusLinkPreviews"`
|
||||
|
||||
// EditedAt indicates the clock value it was edited
|
||||
EditedAt uint64 `json:"editedAt"`
|
||||
|
||||
// Deleted indicates if a message was deleted
|
||||
Deleted bool `json:"deleted"`
|
||||
|
||||
DeletedBy string `json:"deletedBy,omitempty"`
|
||||
|
||||
DeletedForMe bool `json:"deletedForMe"`
|
||||
|
||||
// ContactRequestState is the state of the contact request message
|
||||
ContactRequestState ContactRequestState `json:"contactRequestState,omitempty"`
|
||||
|
||||
// ContactVerificationState is the state of the identity verification process
|
||||
ContactVerificationState ContactVerificationState `json:"contactVerificationState,omitempty"`
|
||||
|
||||
DiscordMessage *protobuf.DiscordMessage `json:"discordMessage,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Message) MarshalJSON() ([]byte, error) {
|
||||
type StickerAlias struct {
|
||||
Hash string `json:"hash"`
|
||||
Pack int32 `json:"pack"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
if m.ChatMessage == nil {
|
||||
m.ChatMessage = &protobuf.ChatMessage{}
|
||||
}
|
||||
|
||||
type MessageStructType struct {
|
||||
ID string `json:"id"`
|
||||
WhisperTimestamp uint64 `json:"whisperTimestamp"`
|
||||
From string `json:"from"`
|
||||
Alias string `json:"alias"`
|
||||
Identicon string `json:"identicon"`
|
||||
Seen bool `json:"seen"`
|
||||
OutgoingStatus string `json:"outgoingStatus,omitempty"`
|
||||
QuotedMessage *QuotedMessage `json:"quotedMessage"`
|
||||
RTL bool `json:"rtl"`
|
||||
ParsedText json.RawMessage `json:"parsedText,omitempty"`
|
||||
LineCount int `json:"lineCount"`
|
||||
Text string `json:"text"`
|
||||
ChatID string `json:"chatId"`
|
||||
LocalChatID string `json:"localChatId"`
|
||||
Clock uint64 `json:"clock"`
|
||||
Replace string `json:"replace"`
|
||||
ResponseTo string `json:"responseTo"`
|
||||
New bool `json:"new,omitempty"`
|
||||
EnsName string `json:"ensName"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Image string `json:"image,omitempty"`
|
||||
AlbumID string `json:"albumId,omitempty"`
|
||||
ImageWidth uint32 `json:"imageWidth,omitempty"`
|
||||
ImageHeight uint32 `json:"imageHeight,omitempty"`
|
||||
AlbumImagesCount uint32 `json:"albumImagesCount,omitempty"`
|
||||
Audio string `json:"audio,omitempty"`
|
||||
AudioDurationMs uint64 `json:"audioDurationMs,omitempty"`
|
||||
CommunityID string `json:"communityId,omitempty"`
|
||||
Sticker *StickerAlias `json:"sticker,omitempty"`
|
||||
CommandParameters *CommandParameters `json:"commandParameters,omitempty"`
|
||||
GapParameters *GapParameters `json:"gapParameters,omitempty"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
ContentType protobuf.ChatMessage_ContentType `json:"contentType"`
|
||||
MessageType protobuf.MessageType `json:"messageType"`
|
||||
Mentions []string `json:"mentions,omitempty"`
|
||||
Mentioned bool `json:"mentioned,omitempty"`
|
||||
Replied bool `json:"replied,omitempty"`
|
||||
Links []string `json:"links,omitempty"`
|
||||
LinkPreviews []LinkPreview `json:"linkPreviews,omitempty"`
|
||||
StatusLinkPreviews []StatusLinkPreview `json:"statusLinkPreviews,omitempty"`
|
||||
EditedAt uint64 `json:"editedAt,omitempty"`
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
DeletedBy string `json:"deletedBy,omitempty"`
|
||||
DeletedForMe bool `json:"deletedForMe,omitempty"`
|
||||
ContactRequestState ContactRequestState `json:"contactRequestState,omitempty"`
|
||||
ContactVerificationState ContactVerificationState `json:"contactVerificationState,omitempty"`
|
||||
DiscordMessage *protobuf.DiscordMessage `json:"discordMessage,omitempty"`
|
||||
BridgeMessage *protobuf.BridgeMessage `json:"bridgeMessage,omitempty"`
|
||||
}
|
||||
item := MessageStructType{
|
||||
ID: m.ID,
|
||||
WhisperTimestamp: m.WhisperTimestamp,
|
||||
From: m.From,
|
||||
Alias: m.Alias,
|
||||
Identicon: m.Identicon,
|
||||
Seen: m.Seen,
|
||||
OutgoingStatus: m.OutgoingStatus,
|
||||
QuotedMessage: m.QuotedMessage,
|
||||
RTL: m.RTL,
|
||||
ParsedText: m.ParsedText,
|
||||
LineCount: m.LineCount,
|
||||
Text: m.Text,
|
||||
Replace: m.Replace,
|
||||
ChatID: m.ChatId,
|
||||
LocalChatID: m.LocalChatID,
|
||||
Clock: m.Clock,
|
||||
ResponseTo: m.ResponseTo,
|
||||
New: m.New,
|
||||
EnsName: m.EnsName,
|
||||
DisplayName: m.DisplayName,
|
||||
Image: m.ImageLocalURL,
|
||||
Audio: m.AudioLocalURL,
|
||||
CommunityID: m.CommunityID,
|
||||
Timestamp: m.Timestamp,
|
||||
ContentType: m.ContentType,
|
||||
Mentions: m.Mentions,
|
||||
Mentioned: m.Mentioned,
|
||||
Replied: m.Replied,
|
||||
Links: m.Links,
|
||||
LinkPreviews: m.LinkPreviews,
|
||||
StatusLinkPreviews: m.StatusLinkPreviews,
|
||||
MessageType: m.MessageType,
|
||||
CommandParameters: m.CommandParameters,
|
||||
GapParameters: m.GapParameters,
|
||||
EditedAt: m.EditedAt,
|
||||
Deleted: m.Deleted,
|
||||
DeletedBy: m.DeletedBy,
|
||||
DeletedForMe: m.DeletedForMe,
|
||||
ContactRequestState: m.ContactRequestState,
|
||||
ContactVerificationState: m.ContactVerificationState,
|
||||
}
|
||||
|
||||
if sticker := m.GetSticker(); sticker != nil {
|
||||
item.Sticker = &StickerAlias{
|
||||
Pack: sticker.Pack,
|
||||
Hash: sticker.Hash,
|
||||
URL: m.StickerLocalURL,
|
||||
}
|
||||
}
|
||||
|
||||
if audio := m.GetAudio(); audio != nil {
|
||||
item.AudioDurationMs = audio.DurationMs
|
||||
}
|
||||
|
||||
if image := m.GetImage(); image != nil {
|
||||
item.AlbumID = image.AlbumId
|
||||
item.ImageWidth = image.Width
|
||||
item.ImageHeight = image.Height
|
||||
item.AlbumImagesCount = image.AlbumImagesCount
|
||||
}
|
||||
|
||||
if discordMessage := m.GetDiscordMessage(); discordMessage != nil {
|
||||
item.DiscordMessage = discordMessage
|
||||
}
|
||||
|
||||
if bridgeMessage := m.GetBridgeMessage(); bridgeMessage != nil {
|
||||
item.BridgeMessage = bridgeMessage
|
||||
}
|
||||
|
||||
if item.From != "" {
|
||||
ext, err := accountJson.ExtendStructWithPubKeyData(item.From, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(ext)
|
||||
}
|
||||
|
||||
return json.Marshal(item)
|
||||
}
|
||||
|
||||
func (m *Message) UnmarshalJSON(data []byte) error {
|
||||
type Alias Message
|
||||
aux := struct {
|
||||
*Alias
|
||||
ResponseTo string `json:"responseTo"`
|
||||
EnsName string `json:"ensName"`
|
||||
DisplayName string `json:"displayName"`
|
||||
ChatID string `json:"chatId"`
|
||||
Sticker *protobuf.StickerMessage `json:"sticker"`
|
||||
AudioDurationMs uint64 `json:"audioDurationMs"`
|
||||
ParsedText json.RawMessage `json:"parsedText"`
|
||||
ContentType protobuf.ChatMessage_ContentType `json:"contentType"`
|
||||
AlbumID string `json:"albumId"`
|
||||
ImageWidth uint32 `json:"imageWidth"`
|
||||
ImageHeight uint32 `json:"imageHeight"`
|
||||
AlbumImagesCount uint32 `json:"albumImagesCount"`
|
||||
From string `json:"from"`
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
DeletedForMe bool `json:"deletedForMe,omitempty"`
|
||||
}{
|
||||
Alias: (*Alias)(m),
|
||||
}
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
if aux.ContentType == protobuf.ChatMessage_STICKER {
|
||||
m.Payload = &protobuf.ChatMessage_Sticker{Sticker: aux.Sticker}
|
||||
}
|
||||
if aux.ContentType == protobuf.ChatMessage_AUDIO {
|
||||
m.Payload = &protobuf.ChatMessage_Audio{
|
||||
Audio: &protobuf.AudioMessage{DurationMs: aux.AudioDurationMs},
|
||||
}
|
||||
}
|
||||
|
||||
if aux.ContentType == protobuf.ChatMessage_IMAGE {
|
||||
m.Payload = &protobuf.ChatMessage_Image{
|
||||
Image: &protobuf.ImageMessage{
|
||||
AlbumId: aux.AlbumID,
|
||||
Width: aux.ImageWidth,
|
||||
Height: aux.ImageHeight,
|
||||
AlbumImagesCount: aux.AlbumImagesCount},
|
||||
}
|
||||
}
|
||||
|
||||
m.ResponseTo = aux.ResponseTo
|
||||
m.EnsName = aux.EnsName
|
||||
m.DisplayName = aux.DisplayName
|
||||
m.ChatId = aux.ChatID
|
||||
m.ContentType = aux.ContentType
|
||||
m.ParsedText = aux.ParsedText
|
||||
m.From = aux.From
|
||||
m.Deleted = aux.Deleted
|
||||
m.DeletedForMe = aux.DeletedForMe
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the first character is Hebrew or Arabic or the RTL character
|
||||
func isRTL(s string) bool {
|
||||
first, _ := utf8.DecodeRuneInString(s)
|
||||
return unicode.Is(unicode.Hebrew, first) ||
|
||||
unicode.Is(unicode.Arabic, first) ||
|
||||
// RTL character
|
||||
first == '\u200f'
|
||||
}
|
||||
|
||||
// parseImage check the message contains an image, and if so
|
||||
// it creates the a base64 encoded version of it.
|
||||
func (m *Message) parseImage() error {
|
||||
if m.ContentType != protobuf.ChatMessage_IMAGE {
|
||||
return nil
|
||||
}
|
||||
image := m.GetImage()
|
||||
if image == nil {
|
||||
return errors.New("image empty")
|
||||
}
|
||||
|
||||
payload := image.Payload
|
||||
|
||||
e64 := base64.StdEncoding
|
||||
|
||||
maxEncLen := e64.EncodedLen(len(payload))
|
||||
encBuf := make([]byte, maxEncLen)
|
||||
|
||||
e64.Encode(encBuf, payload)
|
||||
|
||||
mime, err := images.GetMimeType(image.Payload)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Base64Image = fmt.Sprintf("data:image/%s;base64,%s", mime, encBuf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseAudio check the message contains an audio, and if so
|
||||
// it creates a base64 encoded version of it.
|
||||
func (m *Message) parseAudio() error {
|
||||
if m.ContentType != protobuf.ChatMessage_AUDIO {
|
||||
return nil
|
||||
}
|
||||
audio := m.GetAudio()
|
||||
if audio == nil {
|
||||
return errors.New("audio empty")
|
||||
}
|
||||
|
||||
payload := audio.Payload
|
||||
|
||||
e64 := base64.StdEncoding
|
||||
|
||||
maxEncLen := e64.EncodedLen(len(payload))
|
||||
encBuf := make([]byte, maxEncLen)
|
||||
|
||||
e64.Encode(encBuf, payload)
|
||||
|
||||
mime, err := getAudioMessageMIME(audio)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Base64Audio = fmt.Sprintf("data:audio/%s;base64,%s", mime, encBuf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701
|
||||
type SimplifiedTextVisitor struct {
|
||||
text string
|
||||
canonicalNames map[string]string
|
||||
}
|
||||
|
||||
func (v *SimplifiedTextVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
// only on entering we fetch, otherwise we go on
|
||||
if !entering {
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case *ast.Mention:
|
||||
literal := string(n.Literal)
|
||||
canonicalName, ok := v.canonicalNames[literal]
|
||||
if ok {
|
||||
v.text += canonicalName
|
||||
} else {
|
||||
v.text += literal
|
||||
}
|
||||
case *ast.Link:
|
||||
destination := string(n.Destination)
|
||||
v.text += destination
|
||||
default:
|
||||
var literal string
|
||||
|
||||
leaf := node.AsLeaf()
|
||||
container := node.AsContainer()
|
||||
if leaf != nil {
|
||||
literal = string(leaf.Literal)
|
||||
} else if container != nil {
|
||||
literal = string(container.Literal)
|
||||
}
|
||||
v.text += literal
|
||||
}
|
||||
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
// implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701
|
||||
type MentionsAndLinksVisitor struct {
|
||||
identity string
|
||||
mentioned bool
|
||||
mentions []string
|
||||
links []string
|
||||
}
|
||||
|
||||
type LinksVisitor struct {
|
||||
Links []string
|
||||
}
|
||||
|
||||
func (v *MentionsAndLinksVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
// only on entering we fetch, otherwise we go on
|
||||
if !entering {
|
||||
return ast.GoToNext
|
||||
}
|
||||
switch n := node.(type) {
|
||||
case *ast.Mention:
|
||||
mention := string(n.Literal)
|
||||
if mention == v.identity || mention == EveryoneMentionTag {
|
||||
v.mentioned = true
|
||||
}
|
||||
v.mentions = append(v.mentions, mention)
|
||||
case *ast.Link:
|
||||
v.links = append(v.links, string(n.Destination))
|
||||
}
|
||||
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
func (v *LinksVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
|
||||
if !entering {
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case *ast.Link:
|
||||
v.Links = append(v.Links, string(n.Destination))
|
||||
}
|
||||
|
||||
return ast.GoToNext
|
||||
}
|
||||
|
||||
func runMentionsAndLinksVisitor(parsedText ast.Node, identity string) *MentionsAndLinksVisitor {
|
||||
visitor := &MentionsAndLinksVisitor{identity: identity}
|
||||
ast.Walk(parsedText, visitor)
|
||||
return visitor
|
||||
}
|
||||
|
||||
func RunLinksVisitor(parsedText ast.Node) *LinksVisitor {
|
||||
visitor := &LinksVisitor{}
|
||||
ast.Walk(parsedText, visitor)
|
||||
return visitor
|
||||
}
|
||||
|
||||
// PrepareContent return the parsed content of the message, the line-count and whether
|
||||
// is a right-to-left message
|
||||
func (m *Message) PrepareContent(identity string) error {
|
||||
var parsedText ast.Node
|
||||
switch m.ContentType {
|
||||
case protobuf.ChatMessage_DISCORD_MESSAGE:
|
||||
parsedText = markdown.Parse([]byte(m.GetDiscordMessage().Content), nil)
|
||||
default:
|
||||
parsedText = markdown.Parse([]byte(m.Text), nil)
|
||||
}
|
||||
|
||||
visitor := runMentionsAndLinksVisitor(parsedText, identity)
|
||||
m.Mentions = visitor.mentions
|
||||
m.Links = visitor.links
|
||||
// Leave it set if already set, as sometimes we might run this without
|
||||
// an identity
|
||||
if !m.Mentioned || identity != "" {
|
||||
m.Mentioned = visitor.mentioned
|
||||
}
|
||||
jsonParsedText, err := json.Marshal(parsedText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.ParsedTextAst = &parsedText
|
||||
m.ParsedText = jsonParsedText
|
||||
m.LineCount = strings.Count(m.Text, "\n")
|
||||
m.RTL = isRTL(m.Text)
|
||||
if err := m.parseImage(); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.parseAudio()
|
||||
}
|
||||
|
||||
// GetSimplifiedText returns a the text stripped of all the markdown and with mentions
|
||||
// replaced by canonical names
|
||||
func (m *Message) GetSimplifiedText(identity string, canonicalNames map[string]string) (string, error) {
|
||||
|
||||
if m.ContentType == protobuf.ChatMessage_AUDIO {
|
||||
return "Audio", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_STICKER {
|
||||
return "Sticker", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_IMAGE {
|
||||
return "Image", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_COMMUNITY {
|
||||
return "Community", nil
|
||||
}
|
||||
if m.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP {
|
||||
return "Group", nil
|
||||
}
|
||||
|
||||
if m.ParsedTextAst == nil {
|
||||
err := m.PrepareContent(identity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
visitor := &SimplifiedTextVisitor{canonicalNames: canonicalNames}
|
||||
ast.Walk(*m.ParsedTextAst, visitor)
|
||||
return visitor.text, nil
|
||||
}
|
||||
|
||||
func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) {
|
||||
switch i.Type {
|
||||
case protobuf.AudioMessage_AAC:
|
||||
return "aac", nil
|
||||
case protobuf.AudioMessage_AMR:
|
||||
return "amr", nil
|
||||
}
|
||||
|
||||
return "", errors.New("audio format not supported")
|
||||
}
|
||||
|
||||
// GetSigPubKey returns an ecdsa encoded public key
|
||||
// this function is required to implement the ChatEntity interface
|
||||
func (m *Message) GetSigPubKey() *ecdsa.PublicKey {
|
||||
return m.SigPubKey
|
||||
}
|
||||
|
||||
// GetProtoBuf returns the struct's embedded protobuf struct
|
||||
// this function is required to implement the ChatEntity interface
|
||||
func (m *Message) GetProtobuf() proto.Message {
|
||||
return m.ChatMessage
|
||||
}
|
||||
|
||||
// SetMessageType a setter for the MessageType field
|
||||
// this function is required to implement the ChatEntity interface
|
||||
func (m *Message) SetMessageType(messageType protobuf.MessageType) {
|
||||
m.MessageType = messageType
|
||||
}
|
||||
|
||||
// WrapGroupMessage indicates whether we should wrap this in membership information
|
||||
func (m *Message) WrapGroupMessage() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetPublicKey attempts to return or recreate the *ecdsa.PublicKey of the Message sender.
|
||||
// If the m.SigPubKey is set this will be returned
|
||||
// If the m.From is present the string is decoded and unmarshalled into a *ecdsa.PublicKey, the m.SigPubKey is set and returned
|
||||
// Else an error is thrown
|
||||
// This function differs from GetSigPubKey() as this function may return an error
|
||||
func (m *Message) GetSenderPubKey() (*ecdsa.PublicKey, error) {
|
||||
// TODO requires tests
|
||||
|
||||
if m.SigPubKey != nil {
|
||||
return m.SigPubKey, nil
|
||||
}
|
||||
|
||||
if len(m.From) > 0 {
|
||||
fromB, err := hex.DecodeString(m.From[2:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
senderPubKey, err := crypto.UnmarshalPubkey(fromB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.SigPubKey = senderPubKey
|
||||
return senderPubKey, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("no Message.SigPubKey or Message.From set unable to get public key")
|
||||
}
|
||||
|
||||
func (m *Message) LoadAudio() error {
|
||||
file, err := os.Open(m.AudioPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
payload, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
audioMessage := m.GetAudio()
|
||||
if audioMessage == nil {
|
||||
return errors.New("no audio has been passed")
|
||||
}
|
||||
audioMessage.Payload = payload
|
||||
audioMessage.Type = audio.Type(payload)
|
||||
m.Payload = &protobuf.ChatMessage_Audio{Audio: audioMessage}
|
||||
return os.Remove(m.AudioPath)
|
||||
}
|
||||
|
||||
func (m *Message) LoadImage() error {
|
||||
payload, err := images.OpenAndAdjustImage(images.CroppedImage{ImagePath: m.ImagePath}, false)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageMessage := m.GetImage()
|
||||
imageMessage.Payload = payload
|
||||
imageMessage.Format = images.GetProtobufImageFormat(payload)
|
||||
m.Payload = &protobuf.ChatMessage_Image{Image: imageMessage}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) SetAlbumIDAndImagesCount(albumID string, imagesCount uint32) error {
|
||||
imageMessage := m.GetImage()
|
||||
if imageMessage == nil {
|
||||
return errors.New("Image is empty")
|
||||
}
|
||||
imageMessage.AlbumId = albumID
|
||||
imageMessage.AlbumImagesCount = imagesCount
|
||||
m.Payload = &protobuf.ChatMessage_Image{Image: imageMessage}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMessage() *Message {
|
||||
return &Message{
|
||||
ChatMessage: &protobuf.ChatMessage{},
|
||||
}
|
||||
}
|
||||
498
vendor/github.com/status-im/status-go/protocol/common/message_linkpreview.go
generated
vendored
Normal file
498
vendor/github.com/status-im/status-go/protocol/common/message_linkpreview.go
generated
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
gethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type MakeMediaServerURLType func(msgID string, previewURL string, imageID MediaServerImageID) string
|
||||
type MakeMediaServerURLMessageWrapperType func(previewURL string, imageID MediaServerImageID) string
|
||||
|
||||
type LinkPreviewThumbnail struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
// Non-empty when the thumbnail is available via the media server, i.e. after
|
||||
// the chat message is sent.
|
||||
URL string `json:"url,omitempty"`
|
||||
// Non-empty when the thumbnail payload needs to be shared with the client,
|
||||
// but before it has been persisted.
|
||||
DataURI string `json:"dataUri,omitempty"`
|
||||
}
|
||||
|
||||
type LinkPreview struct {
|
||||
Type protobuf.UnfurledLink_LinkType `json:"type"`
|
||||
URL string `json:"url"`
|
||||
Hostname string `json:"hostname"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Thumbnail LinkPreviewThumbnail `json:"thumbnail,omitempty"`
|
||||
}
|
||||
|
||||
type StatusContactLinkPreview struct {
|
||||
// PublicKey is: "0x" + hex-encoded decompressed public key.
|
||||
// We keep it a string here for correct json marshalling.
|
||||
PublicKey string `json:"publicKey"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
Icon LinkPreviewThumbnail `json:"icon,omitempty"`
|
||||
}
|
||||
|
||||
type StatusCommunityLinkPreview struct {
|
||||
CommunityID string `json:"communityId"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
MembersCount uint32 `json:"membersCount"`
|
||||
Color string `json:"color"`
|
||||
Icon LinkPreviewThumbnail `json:"icon,omitempty"`
|
||||
Banner LinkPreviewThumbnail `json:"banner,omitempty"`
|
||||
}
|
||||
|
||||
type StatusCommunityChannelLinkPreview struct {
|
||||
ChannelUUID string `json:"channelUuid"`
|
||||
Emoji string `json:"emoji"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
Color string `json:"color"`
|
||||
Community *StatusCommunityLinkPreview `json:"community"`
|
||||
}
|
||||
|
||||
type StatusLinkPreview struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Contact *StatusContactLinkPreview `json:"contact,omitempty"`
|
||||
Community *StatusCommunityLinkPreview `json:"community,omitempty"`
|
||||
Channel *StatusCommunityChannelLinkPreview `json:"channel,omitempty"`
|
||||
}
|
||||
|
||||
func (thumbnail *LinkPreviewThumbnail) IsEmpty() bool {
|
||||
return thumbnail.Width == 0 &&
|
||||
thumbnail.Height == 0 &&
|
||||
thumbnail.URL == "" &&
|
||||
thumbnail.DataURI == ""
|
||||
}
|
||||
|
||||
func (thumbnail *LinkPreviewThumbnail) clear() {
|
||||
thumbnail.Width = 0
|
||||
thumbnail.Height = 0
|
||||
thumbnail.URL = ""
|
||||
thumbnail.DataURI = ""
|
||||
}
|
||||
|
||||
func (thumbnail *LinkPreviewThumbnail) validateForProto() error {
|
||||
if thumbnail.DataURI == "" {
|
||||
if thumbnail.Width == 0 && thumbnail.Height == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("dataUri is empty, but width/height are not zero")
|
||||
}
|
||||
|
||||
if thumbnail.Width == 0 || thumbnail.Height == 0 {
|
||||
return fmt.Errorf("dataUri is not empty, but width/heigth are zero")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (thumbnail *LinkPreviewThumbnail) convertToProto() (*protobuf.UnfurledLinkThumbnail, error) {
|
||||
var payload []byte
|
||||
var err error
|
||||
if thumbnail.DataURI != "" {
|
||||
payload, err = images.GetPayloadFromURI(thumbnail.DataURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get data URI payload, url='%s': %w", thumbnail.URL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &protobuf.UnfurledLinkThumbnail{
|
||||
Width: uint32(thumbnail.Width),
|
||||
Height: uint32(thumbnail.Height),
|
||||
Payload: payload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (thumbnail *LinkPreviewThumbnail) loadFromProto(
|
||||
input *protobuf.UnfurledLinkThumbnail,
|
||||
URL string,
|
||||
imageID MediaServerImageID,
|
||||
makeMediaServerURL MakeMediaServerURLMessageWrapperType) {
|
||||
|
||||
thumbnail.clear()
|
||||
thumbnail.Width = int(input.Width)
|
||||
thumbnail.Height = int(input.Height)
|
||||
|
||||
if len(input.Payload) > 0 {
|
||||
thumbnail.URL = makeMediaServerURL(URL, imageID)
|
||||
}
|
||||
}
|
||||
|
||||
func (preview *LinkPreview) validateForProto() error {
|
||||
switch preview.Type {
|
||||
case protobuf.UnfurledLink_IMAGE:
|
||||
if preview.URL == "" {
|
||||
return fmt.Errorf("empty url")
|
||||
}
|
||||
if err := preview.Thumbnail.validateForProto(); err != nil {
|
||||
return fmt.Errorf("thumbnail is not valid for proto: %w", err)
|
||||
}
|
||||
return nil
|
||||
default: // Validate as a link type by default.
|
||||
if preview.Title == "" {
|
||||
return fmt.Errorf("title is empty")
|
||||
}
|
||||
if preview.URL == "" {
|
||||
return fmt.Errorf("url is empty")
|
||||
}
|
||||
if err := preview.Thumbnail.validateForProto(); err != nil {
|
||||
return fmt.Errorf("thumbnail is not valid for proto: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (preview *StatusLinkPreview) validateForProto() error {
|
||||
if preview.URL == "" {
|
||||
return fmt.Errorf("url can't be empty")
|
||||
}
|
||||
|
||||
// At least and only one of Contact/Community/Channel should be present in the preview
|
||||
if preview.Contact != nil && preview.Community != nil {
|
||||
return fmt.Errorf("both contact and community are set at the same time")
|
||||
}
|
||||
if preview.Community != nil && preview.Channel != nil {
|
||||
return fmt.Errorf("both community and channel are set at the same time")
|
||||
}
|
||||
if preview.Channel != nil && preview.Contact != nil {
|
||||
return fmt.Errorf("both contact and channel are set at the same time")
|
||||
}
|
||||
if preview.Contact == nil && preview.Community == nil && preview.Channel == nil {
|
||||
return fmt.Errorf("none of contact/community/channel are set")
|
||||
}
|
||||
|
||||
if preview.Contact != nil {
|
||||
if preview.Contact.PublicKey == "" {
|
||||
return fmt.Errorf("contact publicKey is empty")
|
||||
}
|
||||
if err := preview.Contact.Icon.validateForProto(); err != nil {
|
||||
return fmt.Errorf("contact icon invalid: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if preview.Community != nil {
|
||||
return preview.Community.validateForProto()
|
||||
}
|
||||
|
||||
if preview.Channel != nil {
|
||||
if preview.Channel.ChannelUUID == "" {
|
||||
return fmt.Errorf("channelUuid is empty")
|
||||
}
|
||||
if preview.Channel.Community == nil {
|
||||
return fmt.Errorf("channel community is nil")
|
||||
}
|
||||
if err := preview.Channel.Community.validateForProto(); err != nil {
|
||||
return fmt.Errorf("channel community is not valid: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (preview *StatusCommunityLinkPreview) validateForProto() error {
|
||||
if preview == nil {
|
||||
return fmt.Errorf("community preview is empty")
|
||||
}
|
||||
if preview.CommunityID == "" {
|
||||
return fmt.Errorf("communityId is empty")
|
||||
}
|
||||
if err := preview.Icon.validateForProto(); err != nil {
|
||||
return fmt.Errorf("community icon is invalid: %w", err)
|
||||
}
|
||||
if err := preview.Banner.validateForProto(); err != nil {
|
||||
return fmt.Errorf("community banner is invalid: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (preview *StatusCommunityLinkPreview) convertToProto() (*protobuf.UnfurledStatusCommunityLink, error) {
|
||||
if preview == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
icon, err := preview.Icon.convertToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
banner, err := preview.Banner.convertToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
communityID, err := types.DecodeHex(preview.CommunityID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode community id: %w", err)
|
||||
}
|
||||
|
||||
community := &protobuf.UnfurledStatusCommunityLink{
|
||||
CommunityId: communityID,
|
||||
DisplayName: preview.DisplayName,
|
||||
Description: preview.Description,
|
||||
MembersCount: preview.MembersCount,
|
||||
Color: preview.Color,
|
||||
Icon: icon,
|
||||
Banner: banner,
|
||||
}
|
||||
|
||||
return community, nil
|
||||
}
|
||||
|
||||
func (preview *StatusCommunityLinkPreview) loadFromProto(c *protobuf.UnfurledStatusCommunityLink,
|
||||
URL string, thumbnailPrefix MediaServerImageIDPrefix,
|
||||
makeMediaServerURL MakeMediaServerURLMessageWrapperType) {
|
||||
|
||||
preview.CommunityID = types.EncodeHex(c.CommunityId)
|
||||
preview.DisplayName = c.DisplayName
|
||||
preview.Description = c.Description
|
||||
preview.MembersCount = c.MembersCount
|
||||
preview.Color = c.Color
|
||||
preview.Icon.clear()
|
||||
preview.Banner.clear()
|
||||
|
||||
if icon := c.GetIcon(); icon != nil {
|
||||
preview.Icon.loadFromProto(icon, URL, CreateImageID(thumbnailPrefix, MediaServerIconPostfix), makeMediaServerURL)
|
||||
}
|
||||
if banner := c.GetBanner(); banner != nil {
|
||||
preview.Banner.loadFromProto(banner, URL, CreateImageID(thumbnailPrefix, MediaServerBannerPostfix), makeMediaServerURL)
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertLinkPreviewsToProto expects previews to be correctly sent by the
|
||||
// client because we can't attempt to re-unfurl URLs at this point (it's
|
||||
// actually undesirable). We run a basic validation as an additional safety net.
|
||||
func (m *Message) ConvertLinkPreviewsToProto() ([]*protobuf.UnfurledLink, error) {
|
||||
if len(m.LinkPreviews) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
unfurledLinks := make([]*protobuf.UnfurledLink, 0, len(m.LinkPreviews))
|
||||
|
||||
for _, preview := range m.LinkPreviews {
|
||||
// Do not process subsequent previews because we do expect all previews to
|
||||
// be valid at this stage.
|
||||
if err := preview.validateForProto(); err != nil {
|
||||
return nil, fmt.Errorf("invalid link preview, url='%s': %w", preview.URL, err)
|
||||
}
|
||||
|
||||
var payload []byte
|
||||
var err error
|
||||
if preview.Thumbnail.DataURI != "" {
|
||||
payload, err = images.GetPayloadFromURI(preview.Thumbnail.DataURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get data URI payload, url='%s': %w", preview.URL, err)
|
||||
}
|
||||
}
|
||||
|
||||
ul := &protobuf.UnfurledLink{
|
||||
Type: preview.Type,
|
||||
Url: preview.URL,
|
||||
Title: preview.Title,
|
||||
Description: preview.Description,
|
||||
ThumbnailWidth: uint32(preview.Thumbnail.Width),
|
||||
ThumbnailHeight: uint32(preview.Thumbnail.Height),
|
||||
ThumbnailPayload: payload,
|
||||
}
|
||||
unfurledLinks = append(unfurledLinks, ul)
|
||||
}
|
||||
|
||||
return unfurledLinks, nil
|
||||
}
|
||||
|
||||
func (m *Message) ConvertFromProtoToLinkPreviews(makeMediaServerURL func(msgID string, previewURL string) string) []LinkPreview {
|
||||
var links []*protobuf.UnfurledLink
|
||||
|
||||
if links = m.GetUnfurledLinks(); links == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
previews := make([]LinkPreview, 0, len(links))
|
||||
for _, link := range links {
|
||||
parsedURL, err := url.Parse(link.Url)
|
||||
var hostname string
|
||||
// URL parsing in Go can fail with URLs that weren't correctly URL encoded.
|
||||
// This shouldn't happen in general, but if an error happens we just reuse
|
||||
// the full URL.
|
||||
if err != nil {
|
||||
hostname = link.Url
|
||||
} else {
|
||||
hostname = parsedURL.Hostname()
|
||||
}
|
||||
lp := LinkPreview{
|
||||
Description: link.Description,
|
||||
Hostname: hostname,
|
||||
Title: link.Title,
|
||||
Type: link.Type,
|
||||
URL: link.Url,
|
||||
}
|
||||
mediaURL := ""
|
||||
if len(link.ThumbnailPayload) > 0 {
|
||||
mediaURL = makeMediaServerURL(m.ID, link.Url)
|
||||
}
|
||||
if link.GetThumbnailPayload() != nil {
|
||||
lp.Thumbnail.Width = int(link.ThumbnailWidth)
|
||||
lp.Thumbnail.Height = int(link.ThumbnailHeight)
|
||||
lp.Thumbnail.URL = mediaURL
|
||||
}
|
||||
previews = append(previews, lp)
|
||||
}
|
||||
|
||||
return previews
|
||||
}
|
||||
|
||||
func (m *Message) ConvertStatusLinkPreviewsToProto() (*protobuf.UnfurledStatusLinks, error) {
|
||||
if len(m.StatusLinkPreviews) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
unfurledLinks := make([]*protobuf.UnfurledStatusLink, 0, len(m.StatusLinkPreviews))
|
||||
|
||||
for _, preview := range m.StatusLinkPreviews {
|
||||
// We expect all previews to be valid at this stage
|
||||
if err := preview.validateForProto(); err != nil {
|
||||
return nil, fmt.Errorf("invalid status link preview, url='%s': %w", preview.URL, err)
|
||||
}
|
||||
|
||||
ul := &protobuf.UnfurledStatusLink{
|
||||
Url: preview.URL,
|
||||
}
|
||||
|
||||
if preview.Contact != nil {
|
||||
decompressedPublicKey, err := types.DecodeHex(preview.Contact.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode contact public key: %w", err)
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(decompressedPublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal decompressed public key: %w", err)
|
||||
}
|
||||
|
||||
compressedPublicKey := crypto.CompressPubkey(publicKey)
|
||||
|
||||
icon, err := preview.Contact.Icon.convertToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ul.Payload = &protobuf.UnfurledStatusLink_Contact{
|
||||
Contact: &protobuf.UnfurledStatusContactLink{
|
||||
PublicKey: compressedPublicKey,
|
||||
DisplayName: preview.Contact.DisplayName,
|
||||
Description: preview.Contact.Description,
|
||||
Icon: icon,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if preview.Community != nil {
|
||||
communityPreview, err := preview.Community.convertToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ul.Payload = &protobuf.UnfurledStatusLink_Community{
|
||||
Community: communityPreview,
|
||||
}
|
||||
}
|
||||
|
||||
if preview.Channel != nil {
|
||||
communityPreview, err := preview.Channel.Community.convertToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ul.Payload = &protobuf.UnfurledStatusLink_Channel{
|
||||
Channel: &protobuf.UnfurledStatusChannelLink{
|
||||
ChannelUuid: preview.Channel.ChannelUUID,
|
||||
Emoji: preview.Channel.Emoji,
|
||||
DisplayName: preview.Channel.DisplayName,
|
||||
Description: preview.Channel.Description,
|
||||
Color: preview.Channel.Color,
|
||||
Community: communityPreview,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unfurledLinks = append(unfurledLinks, ul)
|
||||
}
|
||||
|
||||
return &protobuf.UnfurledStatusLinks{UnfurledStatusLinks: unfurledLinks}, nil
|
||||
}
|
||||
|
||||
func (m *Message) ConvertFromProtoToStatusLinkPreviews(makeMediaServerURL func(msgID string, previewURL string, imageID MediaServerImageID) string) []StatusLinkPreview {
|
||||
if m.GetUnfurledStatusLinks() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
links := m.UnfurledStatusLinks.GetUnfurledStatusLinks()
|
||||
|
||||
if links == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This wrapper adds the messageID to the callback
|
||||
makeMediaServerURLMessageWrapper := func(previewURL string, imageID MediaServerImageID) string {
|
||||
return makeMediaServerURL(m.ID, previewURL, imageID)
|
||||
}
|
||||
|
||||
previews := make([]StatusLinkPreview, 0, len(links))
|
||||
|
||||
for _, link := range links {
|
||||
lp := StatusLinkPreview{
|
||||
URL: link.Url,
|
||||
}
|
||||
|
||||
if c := link.GetContact(); c != nil {
|
||||
publicKey, err := crypto.DecompressPubkey(c.PublicKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
lp.Contact = &StatusContactLinkPreview{
|
||||
PublicKey: types.EncodeHex(gethcrypto.FromECDSAPub(publicKey)),
|
||||
DisplayName: c.DisplayName,
|
||||
Description: c.Description,
|
||||
}
|
||||
if icon := c.GetIcon(); icon != nil {
|
||||
lp.Contact.Icon.loadFromProto(icon, link.Url, MediaServerContactIcon, makeMediaServerURLMessageWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
if c := link.GetCommunity(); c != nil {
|
||||
lp.Community = new(StatusCommunityLinkPreview)
|
||||
lp.Community.loadFromProto(c, link.Url, MediaServerCommunityPrefix, makeMediaServerURLMessageWrapper)
|
||||
}
|
||||
|
||||
if c := link.GetChannel(); c != nil {
|
||||
lp.Channel = &StatusCommunityChannelLinkPreview{
|
||||
ChannelUUID: c.ChannelUuid,
|
||||
Emoji: c.Emoji,
|
||||
DisplayName: c.DisplayName,
|
||||
Description: c.Description,
|
||||
Color: c.Color,
|
||||
}
|
||||
if c.Community != nil {
|
||||
lp.Channel.Community = new(StatusCommunityLinkPreview)
|
||||
lp.Channel.Community.loadFromProto(c.Community, link.Url, MediaServerChannelCommunityPrefix, makeMediaServerURLMessageWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
previews = append(previews, lp)
|
||||
}
|
||||
|
||||
return previews
|
||||
}
|
||||
1420
vendor/github.com/status-im/status-go/protocol/common/message_sender.go
generated
vendored
Normal file
1420
vendor/github.com/status-im/status-go/protocol/common/message_sender.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
69
vendor/github.com/status-im/status-go/protocol/common/pin_message.go
generated
vendored
Normal file
69
vendor/github.com/status-im/status-go/protocol/common/pin_message.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type PinnedMessages []*PinnedMessage
|
||||
|
||||
func (m PinnedMessages) GetClock(i int) uint64 {
|
||||
return m[i].Message.Clock
|
||||
}
|
||||
|
||||
type PinMessage struct {
|
||||
*protobuf.PinMessage
|
||||
|
||||
// ID calculated as keccak256(compressedAuthorPubKey, data) where data is unencrypted payload.
|
||||
ID string `json:"id"`
|
||||
// MessageID string `json:"messageID"`
|
||||
// WhisperTimestamp is a timestamp of a Whisper envelope.
|
||||
WhisperTimestamp uint64 `json:"whisperTimestamp"`
|
||||
// From is a public key of the user who pinned the message.
|
||||
From string `json:"from"`
|
||||
// The chat id to be stored locally
|
||||
LocalChatID string `json:"localChatId"`
|
||||
SigPubKey *ecdsa.PublicKey `json:"-"`
|
||||
// Identicon of the author
|
||||
Identicon string `json:"identicon"`
|
||||
// Random 3 words name
|
||||
Alias string `json:"alias"`
|
||||
|
||||
Message *PinnedMessage `json:"pinnedMessage"`
|
||||
}
|
||||
|
||||
func NewPinMessage() *PinMessage {
|
||||
return &PinMessage{PinMessage: &protobuf.PinMessage{}}
|
||||
}
|
||||
|
||||
type PinnedMessage struct {
|
||||
Message *Message `json:"message"`
|
||||
PinnedAt uint64 `json:"pinnedAt"`
|
||||
PinnedBy string `json:"pinnedBy"`
|
||||
}
|
||||
|
||||
// WrapGroupMessage indicates whether we should wrap this in membership information
|
||||
func (m *PinMessage) WrapGroupMessage() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetMessageType a setter for the MessageType field
|
||||
// this function is required to implement the ChatEntity interface
|
||||
func (m *PinMessage) SetMessageType(messageType protobuf.MessageType) {
|
||||
m.MessageType = messageType
|
||||
}
|
||||
|
||||
// GetProtoBuf returns the struct's embedded protobuf struct
|
||||
// this function is required to implement the ChatEntity interface
|
||||
func (m *PinMessage) GetProtobuf() proto.Message {
|
||||
return m.PinMessage
|
||||
}
|
||||
|
||||
// GetSigPubKey returns an ecdsa encoded public key
|
||||
// this function is required to implement the ChatEntity interface
|
||||
func (m *PinMessage) GetSigPubKey() *ecdsa.PublicKey {
|
||||
return m.SigPubKey
|
||||
}
|
||||
41
vendor/github.com/status-im/status-go/protocol/common/raw_message.go
generated
vendored
Normal file
41
vendor/github.com/status-im/status-go/protocol/common/raw_message.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type CommKeyExMsgType uint8
|
||||
|
||||
const (
|
||||
KeyExMsgNone CommKeyExMsgType = 0
|
||||
KeyExMsgReuse CommKeyExMsgType = 1
|
||||
KeyExMsgRekey CommKeyExMsgType = 2
|
||||
)
|
||||
|
||||
// RawMessage represent a sent or received message, kept for being able
|
||||
// to re-send/propagate
|
||||
type RawMessage struct {
|
||||
ID string
|
||||
LocalChatID string
|
||||
LastSent uint64
|
||||
SendCount int
|
||||
Sent bool
|
||||
ResendAutomatically bool
|
||||
SkipEncryptionLayer bool // don't wrap message into ProtocolMessage
|
||||
SendPushNotification bool
|
||||
MessageType protobuf.ApplicationMetadataMessage_Type
|
||||
Payload []byte
|
||||
Sender *ecdsa.PrivateKey
|
||||
Recipients []*ecdsa.PublicKey
|
||||
SkipGroupMessageWrap bool
|
||||
SkipApplicationWrap bool
|
||||
SendOnPersonalTopic bool
|
||||
CommunityID []byte
|
||||
CommunityKeyExMsgType CommKeyExMsgType
|
||||
Ephemeral bool
|
||||
BeforeDispatch func(*RawMessage) error
|
||||
HashRatchetGroupID []byte
|
||||
PubsubTopic string
|
||||
}
|
||||
424
vendor/github.com/status-im/status-go/protocol/common/raw_messages_persistence.go
generated
vendored
Normal file
424
vendor/github.com/status-im/status-go/protocol/common/raw_messages_persistence.go
generated
vendored
Normal file
@@ -0,0 +1,424 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"database/sql"
|
||||
"encoding/gob"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type RawMessageConfirmation struct {
|
||||
// DataSyncID is the ID of the datasync message sent
|
||||
DataSyncID []byte
|
||||
// MessageID is the message id of the message
|
||||
MessageID []byte
|
||||
// PublicKey is the compressed receiver public key
|
||||
PublicKey []byte
|
||||
// ConfirmedAt is the unix timestamp in seconds of when the message was confirmed
|
||||
ConfirmedAt int64
|
||||
}
|
||||
|
||||
type RawMessagesPersistence struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewRawMessagesPersistence(db *sql.DB) *RawMessagesPersistence {
|
||||
return &RawMessagesPersistence{db: db}
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) SaveRawMessage(message *RawMessage) error {
|
||||
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
var pubKeys [][]byte
|
||||
for _, pk := range message.Recipients {
|
||||
pubKeys = append(pubKeys, crypto.CompressPubkey(pk))
|
||||
}
|
||||
// Encode recipients
|
||||
var encodedRecipients bytes.Buffer
|
||||
encoder := gob.NewEncoder(&encodedRecipients)
|
||||
|
||||
if err := encoder.Encode(pubKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the message is not sent, we check whether there's a record
|
||||
// in the database already and preserve the state
|
||||
if !message.Sent {
|
||||
oldMessage, err := db.rawMessageByID(tx, message.ID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
if oldMessage != nil {
|
||||
message.Sent = oldMessage.Sent
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO
|
||||
raw_messages
|
||||
(
|
||||
id,
|
||||
local_chat_id,
|
||||
last_sent,
|
||||
send_count,
|
||||
sent,
|
||||
message_type,
|
||||
resend_automatically,
|
||||
recipients,
|
||||
skip_encryption,
|
||||
send_push_notification,
|
||||
skip_group_message_wrap,
|
||||
send_on_personal_topic,
|
||||
payload
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
message.ID,
|
||||
message.LocalChatID,
|
||||
message.LastSent,
|
||||
message.SendCount,
|
||||
message.Sent,
|
||||
message.MessageType,
|
||||
message.ResendAutomatically,
|
||||
encodedRecipients.Bytes(),
|
||||
message.SkipEncryptionLayer,
|
||||
message.SendPushNotification,
|
||||
message.SkipGroupMessageWrap,
|
||||
message.SendOnPersonalTopic,
|
||||
message.Payload)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) RawMessageByID(id string) (*RawMessage, error) {
|
||||
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
return db.rawMessageByID(tx, id)
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) rawMessageByID(tx *sql.Tx, id string) (*RawMessage, error) {
|
||||
var rawPubKeys [][]byte
|
||||
var encodedRecipients []byte
|
||||
var skipGroupMessageWrap sql.NullBool
|
||||
var sendOnPersonalTopic sql.NullBool
|
||||
message := &RawMessage{}
|
||||
|
||||
err := tx.QueryRow(`
|
||||
SELECT
|
||||
id,
|
||||
local_chat_id,
|
||||
last_sent,
|
||||
send_count,
|
||||
sent,
|
||||
message_type,
|
||||
resend_automatically,
|
||||
recipients,
|
||||
skip_encryption,
|
||||
send_push_notification,
|
||||
skip_group_message_wrap,
|
||||
send_on_personal_topic,
|
||||
payload
|
||||
FROM
|
||||
raw_messages
|
||||
WHERE
|
||||
id = ?`,
|
||||
id,
|
||||
).Scan(
|
||||
&message.ID,
|
||||
&message.LocalChatID,
|
||||
&message.LastSent,
|
||||
&message.SendCount,
|
||||
&message.Sent,
|
||||
&message.MessageType,
|
||||
&message.ResendAutomatically,
|
||||
&encodedRecipients,
|
||||
&message.SkipEncryptionLayer,
|
||||
&message.SendPushNotification,
|
||||
&skipGroupMessageWrap,
|
||||
&sendOnPersonalTopic,
|
||||
&message.Payload,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rawPubKeys != nil {
|
||||
// Restore recipients
|
||||
decoder := gob.NewDecoder(bytes.NewBuffer(encodedRecipients))
|
||||
err = decoder.Decode(&rawPubKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pkBytes := range rawPubKeys {
|
||||
pubkey, err := crypto.UnmarshalPubkey(pkBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message.Recipients = append(message.Recipients, pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
if skipGroupMessageWrap.Valid {
|
||||
message.SkipGroupMessageWrap = skipGroupMessageWrap.Bool
|
||||
}
|
||||
|
||||
if sendOnPersonalTopic.Valid {
|
||||
message.SendOnPersonalTopic = sendOnPersonalTopic.Bool
|
||||
}
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) RawMessagesIDsByType(t protobuf.ApplicationMetadataMessage_Type) ([]string, error) {
|
||||
ids := []string{}
|
||||
|
||||
rows, err := db.db.Query(`
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
raw_messages
|
||||
WHERE
|
||||
message_type = ?`,
|
||||
t)
|
||||
if err != nil {
|
||||
return ids, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return ids, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// MarkAsConfirmed marks all the messages with dataSyncID as confirmed and returns
|
||||
// the messageIDs that can be considered confirmed.
|
||||
// If atLeastOne is set it will return messageid if at least once of the messages
|
||||
// sent has been confirmed
|
||||
func (db RawMessagesPersistence) MarkAsConfirmed(dataSyncID []byte, atLeastOne bool) (messageID types.HexBytes, err error) {
|
||||
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
confirmedAt := time.Now().Unix()
|
||||
_, err = tx.Exec(`UPDATE raw_message_confirmations SET confirmed_at = ? WHERE datasync_id = ? AND confirmed_at = 0`, confirmedAt, dataSyncID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Select any tuple that has a message_id with a datasync_id = ? and that has just been confirmed
|
||||
rows, err := tx.Query(`SELECT message_id,confirmed_at FROM raw_message_confirmations WHERE message_id = (SELECT message_id FROM raw_message_confirmations WHERE datasync_id = ? LIMIT 1)`, dataSyncID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
confirmedResult := true
|
||||
|
||||
for rows.Next() {
|
||||
var confirmedAt int64
|
||||
err = rows.Scan(&messageID, &confirmedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
confirmed := confirmedAt > 0
|
||||
|
||||
if atLeastOne && confirmed {
|
||||
// We return, as at least one was confirmed
|
||||
return
|
||||
}
|
||||
|
||||
confirmedResult = confirmedResult && confirmed
|
||||
}
|
||||
|
||||
if !confirmedResult {
|
||||
messageID = nil
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) InsertPendingConfirmation(confirmation *RawMessageConfirmation) error {
|
||||
|
||||
_, err := db.db.Exec(`INSERT INTO raw_message_confirmations
|
||||
(datasync_id, message_id, public_key)
|
||||
VALUES
|
||||
(?,?,?)`,
|
||||
confirmation.DataSyncID,
|
||||
confirmation.MessageID,
|
||||
confirmation.PublicKey,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) SaveHashRatchetMessage(groupID []byte, keyID []byte, m *types.Message) error {
|
||||
_, err := db.db.Exec(`INSERT INTO hash_ratchet_encrypted_messages(hash, sig, TTL, timestamp, topic, payload, dst, p2p, padding, group_id, key_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, m.Hash, m.Sig, m.TTL, m.Timestamp, types.TopicTypeToByteArray(m.Topic), m.Payload, m.Dst, m.P2P, m.Padding, groupID, keyID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) GetHashRatchetMessages(keyID []byte) ([]*types.Message, error) {
|
||||
var messages []*types.Message
|
||||
|
||||
rows, err := db.db.Query(`SELECT hash, sig, TTL, timestamp, topic, payload, dst, p2p, padding FROM hash_ratchet_encrypted_messages WHERE key_id = ?`, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var topic []byte
|
||||
message := &types.Message{}
|
||||
|
||||
err := rows.Scan(&message.Hash, &message.Sig, &message.TTL, &message.Timestamp, &topic, &message.Payload, &message.Dst, &message.P2P, &message.Padding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message.Topic = types.BytesToTopic(topic)
|
||||
messages = append(messages, message)
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (db RawMessagesPersistence) DeleteHashRatchetMessages(ids [][]byte) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
idsArgs := make([]interface{}, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
idsArgs = append(idsArgs, id)
|
||||
}
|
||||
inVector := strings.Repeat("?, ", len(ids)-1) + "?"
|
||||
|
||||
_, err := db.db.Exec("DELETE FROM hash_ratchet_encrypted_messages WHERE hash IN ("+inVector+")", idsArgs...) // nolint: gosec
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *RawMessagesPersistence) IsMessageAlreadyCompleted(hash []byte) (bool, error) {
|
||||
var alreadyCompleted int
|
||||
err := db.db.QueryRow("SELECT COUNT(*) FROM message_segments_completed WHERE hash = ?", hash).Scan(&alreadyCompleted)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return alreadyCompleted > 0, nil
|
||||
}
|
||||
|
||||
func (db *RawMessagesPersistence) SaveMessageSegment(segment *protobuf.SegmentMessage, sigPubKey *ecdsa.PublicKey, timestamp int64) error {
|
||||
sigPubKeyBlob := crypto.CompressPubkey(sigPubKey)
|
||||
|
||||
_, err := db.db.Exec("INSERT INTO message_segments (hash, segment_index, segments_count, sig_pub_key, payload, timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
segment.EntireMessageHash, segment.Index, segment.SegmentsCount, sigPubKeyBlob, segment.Payload, timestamp)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Get ordered message segments for given hash
|
||||
func (db *RawMessagesPersistence) GetMessageSegments(hash []byte, sigPubKey *ecdsa.PublicKey) ([]*protobuf.SegmentMessage, error) {
|
||||
sigPubKeyBlob := crypto.CompressPubkey(sigPubKey)
|
||||
|
||||
rows, err := db.db.Query("SELECT hash, segment_index, segments_count, payload FROM message_segments WHERE hash = ? AND sig_pub_key = ? ORDER BY segment_index", hash, sigPubKeyBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var segments []*protobuf.SegmentMessage
|
||||
for rows.Next() {
|
||||
var segment protobuf.SegmentMessage
|
||||
err := rows.Scan(&segment.EntireMessageHash, &segment.Index, &segment.SegmentsCount, &segment.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
segments = append(segments, &segment)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return segments, nil
|
||||
}
|
||||
|
||||
func (db *RawMessagesPersistence) RemoveMessageSegmentsOlderThan(timestamp int64) error {
|
||||
_, err := db.db.Exec("DELETE FROM message_segments WHERE timestamp < ?", timestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *RawMessagesPersistence) CompleteMessageSegments(hash []byte, sigPubKey *ecdsa.PublicKey, timestamp int64) error {
|
||||
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
sigPubKeyBlob := crypto.CompressPubkey(sigPubKey)
|
||||
|
||||
_, err = tx.Exec("DELETE FROM message_segments WHERE hash = ? AND sig_pub_key = ?", hash, sigPubKeyBlob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO message_segments_completed (hash, sig_pub_key, timestamp) VALUES (?,?,?)", hash, sigPubKeyBlob, timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *RawMessagesPersistence) RemoveMessageSegmentsCompletedOlderThan(timestamp int64) error {
|
||||
_, err := db.db.Exec("DELETE FROM message_segments_completed WHERE timestamp < ?", timestamp)
|
||||
return err
|
||||
}
|
||||
63
vendor/github.com/status-im/status-go/protocol/common/shard/shard.go
generated
vendored
Normal file
63
vendor/github.com/status-im/status-go/protocol/common/shard/shard.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package shard
|
||||
|
||||
import (
|
||||
wakuproto "github.com/waku-org/go-waku/waku/v2/protocol"
|
||||
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type Shard struct {
|
||||
Cluster uint16 `json:"cluster"`
|
||||
Index uint16 `json:"index"`
|
||||
}
|
||||
|
||||
func FromProtobuff(p *protobuf.Shard) *Shard {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Shard{
|
||||
Cluster: uint16(p.Cluster),
|
||||
Index: uint16(p.Index),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shard) Protobuffer() *protobuf.Shard {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &protobuf.Shard{
|
||||
Cluster: int32(s.Cluster),
|
||||
Index: int32(s.Index),
|
||||
}
|
||||
}
|
||||
func (s *Shard) PubsubTopic() string {
|
||||
if s != nil {
|
||||
return wakuproto.NewStaticShardingPubsubTopic(s.Cluster, s.Index).String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func DefaultNonProtectedPubsubTopic() string {
|
||||
return (&Shard{
|
||||
Cluster: MainStatusShardCluster,
|
||||
Index: NonProtectedShardIndex,
|
||||
}).PubsubTopic()
|
||||
}
|
||||
|
||||
const MainStatusShardCluster = 16
|
||||
const DefaultShardIndex = 32
|
||||
const NonProtectedShardIndex = 64
|
||||
const UndefinedShardValue = 0
|
||||
|
||||
func DefaultShardPubsubTopic() string {
|
||||
return wakuproto.NewStaticShardingPubsubTopic(MainStatusShardCluster, DefaultShardIndex).String()
|
||||
}
|
||||
|
||||
func DefaultShard() *Shard {
|
||||
return &Shard{
|
||||
Cluster: MainStatusShardCluster,
|
||||
Index: NonProtectedShardIndex,
|
||||
}
|
||||
}
|
||||
12
vendor/github.com/status-im/status-go/protocol/common/timesource.go
generated
vendored
Normal file
12
vendor/github.com/status-im/status-go/protocol/common/timesource.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package common
|
||||
|
||||
// TimeSource provides a unified way of getting the current time.
|
||||
// The intention is to always use a synchronized time source
|
||||
// between all components of the protocol.
|
||||
//
|
||||
// This is required by Whisper and Waku protocols
|
||||
// which rely on a fact that all peers
|
||||
// have a synchronized time source.
|
||||
type TimeSource interface {
|
||||
GetCurrentTime() uint64
|
||||
}
|
||||
Reference in New Issue
Block a user