2023-06-30 11:05:33 +00:00
|
|
|
package libgm
|
2023-06-30 09:54:08 +00:00
|
|
|
|
|
|
|
import (
|
2023-07-10 22:20:50 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
2023-07-17 23:01:06 +00:00
|
|
|
"fmt"
|
2023-12-13 23:32:36 +00:00
|
|
|
"strings"
|
2023-07-01 09:51:13 +00:00
|
|
|
|
2023-07-17 23:01:06 +00:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
2023-12-13 23:32:36 +00:00
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/events"
|
2023-07-17 13:51:31 +00:00
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
2023-06-30 09:54:08 +00:00
|
|
|
)
|
|
|
|
|
2023-07-17 23:01:06 +00:00
|
|
|
type IncomingRPCMessage struct {
|
|
|
|
*gmproto.IncomingRPCMessage
|
|
|
|
|
2023-09-07 14:29:48 +00:00
|
|
|
IsOld bool
|
|
|
|
|
2023-07-17 23:01:06 +00:00
|
|
|
Pair *gmproto.RPCPairData
|
2024-02-22 20:37:49 +00:00
|
|
|
Gaia *gmproto.RPCGaiaData
|
2023-07-17 23:01:06 +00:00
|
|
|
|
|
|
|
Message *gmproto.RPCMessageData
|
|
|
|
DecryptedData []byte
|
|
|
|
DecryptedMessage proto.Message
|
|
|
|
}
|
|
|
|
|
2023-07-18 21:59:51 +00:00
|
|
|
var responseType = map[gmproto.ActionType]proto.Message{
|
|
|
|
gmproto.ActionType_IS_BUGLE_DEFAULT: &gmproto.IsBugleDefaultResponse{},
|
|
|
|
gmproto.ActionType_GET_UPDATES: &gmproto.UpdateEvents{},
|
|
|
|
gmproto.ActionType_LIST_CONVERSATIONS: &gmproto.ListConversationsResponse{},
|
|
|
|
gmproto.ActionType_NOTIFY_DITTO_ACTIVITY: &gmproto.NotifyDittoActivityResponse{},
|
|
|
|
gmproto.ActionType_GET_CONVERSATION_TYPE: &gmproto.GetConversationTypeResponse{},
|
|
|
|
gmproto.ActionType_GET_CONVERSATION: &gmproto.GetConversationResponse{},
|
|
|
|
gmproto.ActionType_LIST_MESSAGES: &gmproto.ListMessagesResponse{},
|
|
|
|
gmproto.ActionType_SEND_MESSAGE: &gmproto.SendMessageResponse{},
|
|
|
|
gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionResponse{},
|
|
|
|
gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageResponse{},
|
2023-09-04 22:18:01 +00:00
|
|
|
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetThumbnailResponse{},
|
|
|
|
gmproto.ActionType_GET_CONTACTS_THUMBNAIL: &gmproto.GetThumbnailResponse{},
|
2023-07-18 21:59:51 +00:00
|
|
|
gmproto.ActionType_LIST_CONTACTS: &gmproto.ListContactsResponse{},
|
|
|
|
gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsResponse{},
|
|
|
|
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationResponse{},
|
|
|
|
gmproto.ActionType_UPDATE_CONVERSATION: &gmproto.UpdateConversationResponse{},
|
|
|
|
}
|
|
|
|
|
2023-07-19 11:12:23 +00:00
|
|
|
func (c *Client) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*IncomingRPCMessage, error) {
|
2023-07-17 23:01:06 +00:00
|
|
|
msg := &IncomingRPCMessage{
|
|
|
|
IncomingRPCMessage: data,
|
|
|
|
}
|
|
|
|
switch data.BugleRoute {
|
|
|
|
case gmproto.BugleRoute_PairEvent:
|
|
|
|
msg.Pair = &gmproto.RPCPairData{}
|
|
|
|
err := proto.Unmarshal(data.GetMessageData(), msg.Pair)
|
|
|
|
if err != nil {
|
2023-07-31 16:44:13 +00:00
|
|
|
c.Logger.Trace().
|
|
|
|
Str("data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
|
|
|
Msg("Errored pair event content")
|
|
|
|
return nil, fmt.Errorf("failed to decode pair event: %w", err)
|
2023-07-17 23:01:06 +00:00
|
|
|
}
|
2024-02-22 20:37:49 +00:00
|
|
|
case gmproto.BugleRoute_GaiaEvent:
|
|
|
|
msg.Gaia = &gmproto.RPCGaiaData{}
|
|
|
|
err := proto.Unmarshal(data.GetMessageData(), msg.Gaia)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Trace().
|
|
|
|
Str("data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
|
|
|
Msg("Errored gaia event content")
|
|
|
|
return nil, fmt.Errorf("failed to decode gaia event: %w", err)
|
|
|
|
}
|
2023-07-17 23:01:06 +00:00
|
|
|
case gmproto.BugleRoute_DataEvent:
|
|
|
|
msg.Message = &gmproto.RPCMessageData{}
|
|
|
|
err := proto.Unmarshal(data.GetMessageData(), msg.Message)
|
|
|
|
if err != nil {
|
2023-07-31 16:44:13 +00:00
|
|
|
c.Logger.Trace().
|
|
|
|
Str("data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
|
|
|
Msg("Errored data event content")
|
|
|
|
return nil, fmt.Errorf("failed to decode data event: %w", err)
|
2023-07-17 23:01:06 +00:00
|
|
|
}
|
2023-07-18 21:59:51 +00:00
|
|
|
responseStruct, ok := responseType[msg.Message.GetAction()]
|
|
|
|
if ok {
|
|
|
|
msg.DecryptedMessage = responseStruct.ProtoReflect().New().Interface()
|
|
|
|
}
|
2023-07-17 23:01:06 +00:00
|
|
|
if msg.Message.EncryptedData != nil {
|
2023-07-19 11:12:23 +00:00
|
|
|
msg.DecryptedData, err = c.AuthData.RequestCrypto.Decrypt(msg.Message.EncryptedData)
|
2023-07-17 23:01:06 +00:00
|
|
|
if err != nil {
|
2023-07-31 16:44:13 +00:00
|
|
|
return nil, fmt.Errorf("failed to decrypt data event: %w", err)
|
2023-07-17 23:01:06 +00:00
|
|
|
}
|
2023-07-18 21:59:51 +00:00
|
|
|
if msg.DecryptedMessage != nil {
|
|
|
|
err = proto.Unmarshal(msg.DecryptedData, msg.DecryptedMessage)
|
|
|
|
if err != nil {
|
2023-07-31 16:44:13 +00:00
|
|
|
c.Logger.Trace().
|
|
|
|
Str("data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
|
|
|
|
Msg("Errored decrypted data event content")
|
|
|
|
return nil, fmt.Errorf("failed to decode decrypted data event: %w", err)
|
2023-07-18 21:59:51 +00:00
|
|
|
}
|
2023-07-17 23:01:06 +00:00
|
|
|
}
|
2023-12-13 23:32:36 +00:00
|
|
|
} else if msg.Message.EncryptedData2 != nil {
|
|
|
|
msg.DecryptedData, err = c.AuthData.RequestCrypto.Decrypt(msg.Message.EncryptedData2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to decrypt field 2 in data event: %w", err)
|
|
|
|
}
|
|
|
|
var ed2c gmproto.EncryptedData2Container
|
|
|
|
err = proto.Unmarshal(msg.DecryptedData, &ed2c)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Trace().
|
|
|
|
Str("data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
|
|
|
|
Msg("Errored decrypted data event content")
|
|
|
|
return nil, fmt.Errorf("failed to decode decrypted field 2 data event: %w", err)
|
|
|
|
}
|
|
|
|
// Hacky hack to have User.handleAccountChange do the right-ish thing on startup
|
|
|
|
if strings.ContainsRune(ed2c.GetAccountChange().GetAccount(), '@') {
|
|
|
|
c.triggerEvent(&events.AccountChange{
|
|
|
|
AccountChangeOrSomethingEvent: ed2c.GetAccountChange(),
|
|
|
|
IsFake: true,
|
|
|
|
})
|
|
|
|
}
|
2023-07-17 23:01:06 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown bugle route %d", data.BugleRoute)
|
|
|
|
}
|
|
|
|
return msg, nil
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:29:30 +00:00
|
|
|
func (c *Client) deduplicateHash(id string, hash [32]byte) bool {
|
2023-07-19 11:12:23 +00:00
|
|
|
const recentUpdatesLen = len(c.recentUpdates)
|
|
|
|
for i := c.recentUpdatesPtr + recentUpdatesLen - 1; i >= c.recentUpdatesPtr; i-- {
|
2023-07-19 19:29:30 +00:00
|
|
|
if c.recentUpdates[i%recentUpdatesLen].id == id {
|
|
|
|
if c.recentUpdates[i%recentUpdatesLen].hash == hash {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
2023-07-10 22:20:50 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-19 19:29:30 +00:00
|
|
|
c.recentUpdates[c.recentUpdatesPtr] = updateDedupItem{id: id, hash: hash}
|
2023-07-19 11:12:23 +00:00
|
|
|
c.recentUpdatesPtr = (c.recentUpdatesPtr + 1) % recentUpdatesLen
|
2023-07-10 22:20:50 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:29:30 +00:00
|
|
|
func (c *Client) logContent(res *IncomingRPCMessage, thingID string, contentHash []byte) {
|
2023-07-19 11:12:23 +00:00
|
|
|
if c.Logger.Trace().Enabled() && (res.DecryptedData != nil || res.DecryptedMessage != nil) {
|
2023-09-07 14:29:48 +00:00
|
|
|
evt := c.Logger.Trace().Bool("is_old", res.IsOld)
|
2023-07-17 23:01:06 +00:00
|
|
|
if res.DecryptedMessage != nil {
|
|
|
|
evt.Str("proto_name", string(res.DecryptedMessage.ProtoReflect().Descriptor().FullName()))
|
|
|
|
}
|
|
|
|
if res.DecryptedData != nil {
|
|
|
|
evt.Str("data", base64.StdEncoding.EncodeToString(res.DecryptedData))
|
2023-07-19 19:29:30 +00:00
|
|
|
if contentHash != nil {
|
|
|
|
evt.Str("thing_id", thingID)
|
|
|
|
evt.Hex("data_hash", contentHash)
|
|
|
|
}
|
2023-07-17 23:01:06 +00:00
|
|
|
} else {
|
|
|
|
evt.Str("data", "<null>")
|
|
|
|
}
|
|
|
|
evt.Msg("Got event")
|
2023-07-15 13:17:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:29:30 +00:00
|
|
|
func (c *Client) deduplicateUpdate(id string, msg *IncomingRPCMessage) bool {
|
2023-07-17 23:01:06 +00:00
|
|
|
if msg.DecryptedData != nil {
|
|
|
|
contentHash := sha256.Sum256(msg.DecryptedData)
|
2023-07-19 19:29:30 +00:00
|
|
|
if c.deduplicateHash(id, contentHash) {
|
2023-09-07 14:29:48 +00:00
|
|
|
c.Logger.Trace().
|
|
|
|
Str("thing_id", id).
|
|
|
|
Hex("data_hash", contentHash[:]).
|
|
|
|
Bool("is_old", msg.IsOld).
|
|
|
|
Msg("Ignoring duplicate update")
|
2023-07-10 22:20:50 +00:00
|
|
|
return true
|
|
|
|
}
|
2023-07-19 19:29:30 +00:00
|
|
|
c.logContent(msg, id, contentHash[:])
|
2023-07-10 22:20:50 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2023-07-09 11:16:52 +00:00
|
|
|
|
2023-07-19 11:12:23 +00:00
|
|
|
func (c *Client) HandleRPCMsg(rawMsg *gmproto.IncomingRPCMessage) {
|
|
|
|
msg, err := c.decryptInternalMessage(rawMsg)
|
2023-07-17 23:01:06 +00:00
|
|
|
if err != nil {
|
2023-07-19 11:12:23 +00:00
|
|
|
c.Logger.Err(err).Msg("Failed to decode incoming RPC message")
|
2023-07-09 11:16:52 +00:00
|
|
|
return
|
2023-07-01 09:51:13 +00:00
|
|
|
}
|
2023-07-09 11:16:52 +00:00
|
|
|
|
2023-07-19 11:12:23 +00:00
|
|
|
c.sessionHandler.queueMessageAck(msg.ResponseID)
|
|
|
|
if c.sessionHandler.receiveResponse(msg) {
|
2023-07-15 22:45:57 +00:00
|
|
|
return
|
|
|
|
}
|
2023-07-17 23:01:06 +00:00
|
|
|
switch msg.BugleRoute {
|
2023-07-17 13:51:31 +00:00
|
|
|
case gmproto.BugleRoute_PairEvent:
|
2023-08-10 12:40:43 +00:00
|
|
|
c.handlePairingEvent(msg)
|
2024-02-22 20:37:49 +00:00
|
|
|
case gmproto.BugleRoute_GaiaEvent:
|
|
|
|
c.handleGaiaPairingEvent(msg)
|
2023-07-17 13:51:31 +00:00
|
|
|
case gmproto.BugleRoute_DataEvent:
|
2023-07-19 11:12:23 +00:00
|
|
|
if c.skipCount > 0 {
|
|
|
|
c.skipCount--
|
2023-09-07 14:29:48 +00:00
|
|
|
msg.IsOld = true
|
2023-07-09 11:16:52 +00:00
|
|
|
}
|
2023-07-19 11:12:23 +00:00
|
|
|
c.handleUpdatesEvent(msg)
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-19 20:30:27 +00:00
|
|
|
|
2023-08-24 11:48:03 +00:00
|
|
|
type WrappedMessage struct {
|
|
|
|
*gmproto.Message
|
2023-09-07 14:29:48 +00:00
|
|
|
IsOld bool
|
|
|
|
Data []byte
|
2023-08-24 11:48:03 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 20:30:27 +00:00
|
|
|
func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) {
|
|
|
|
switch msg.Message.Action {
|
|
|
|
case gmproto.ActionType_GET_UPDATES:
|
|
|
|
data, ok := msg.DecryptedMessage.(*gmproto.UpdateEvents)
|
|
|
|
if !ok {
|
2023-09-07 14:29:48 +00:00
|
|
|
c.Logger.Error().
|
|
|
|
Type("data_type", msg.DecryptedMessage).
|
|
|
|
Bool("is_old", msg.IsOld).
|
|
|
|
Msg("Unexpected data type in GET_UPDATES event")
|
2023-07-19 20:30:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch evt := data.Event.(type) {
|
|
|
|
case *gmproto.UpdateEvents_UserAlertEvent:
|
|
|
|
c.logContent(msg, "", nil)
|
2023-09-07 14:29:48 +00:00
|
|
|
if msg.IsOld {
|
|
|
|
return
|
|
|
|
}
|
2023-07-19 20:30:27 +00:00
|
|
|
c.triggerEvent(evt.UserAlertEvent)
|
|
|
|
|
|
|
|
case *gmproto.UpdateEvents_SettingsEvent:
|
2023-08-30 16:35:02 +00:00
|
|
|
c.Logger.Debug().
|
|
|
|
Str("data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
|
2023-09-07 14:29:48 +00:00
|
|
|
Bool("is_old", msg.IsOld).
|
2023-08-30 16:35:02 +00:00
|
|
|
Msg("Got settings event")
|
2023-07-19 20:30:27 +00:00
|
|
|
c.triggerEvent(evt.SettingsEvent)
|
|
|
|
|
|
|
|
case *gmproto.UpdateEvents_ConversationEvent:
|
2023-08-25 15:29:27 +00:00
|
|
|
for _, part := range evt.ConversationEvent.GetData() {
|
|
|
|
if c.deduplicateUpdate(part.GetConversationID(), msg) {
|
|
|
|
return
|
2023-09-07 14:29:48 +00:00
|
|
|
} else if msg.IsOld {
|
|
|
|
c.Logger.Debug().Str("conv_id", part.ConversationID).Msg("Ignoring old conversation event")
|
|
|
|
continue
|
2023-08-25 15:29:27 +00:00
|
|
|
}
|
|
|
|
c.triggerEvent(part)
|
2023-07-19 20:30:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case *gmproto.UpdateEvents_MessageEvent:
|
2023-08-25 15:29:27 +00:00
|
|
|
for _, part := range evt.MessageEvent.GetData() {
|
|
|
|
if c.deduplicateUpdate(part.GetMessageID(), msg) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.triggerEvent(&WrappedMessage{
|
|
|
|
Message: part,
|
2023-09-07 14:29:48 +00:00
|
|
|
IsOld: msg.IsOld,
|
2023-08-25 15:29:27 +00:00
|
|
|
Data: msg.DecryptedData,
|
|
|
|
})
|
2023-07-19 20:30:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case *gmproto.UpdateEvents_TypingEvent:
|
|
|
|
c.logContent(msg, "", nil)
|
2023-09-07 14:29:48 +00:00
|
|
|
if msg.IsOld {
|
|
|
|
return
|
|
|
|
}
|
2023-07-19 20:30:27 +00:00
|
|
|
c.triggerEvent(evt.TypingEvent.GetData())
|
|
|
|
|
2023-12-13 23:18:08 +00:00
|
|
|
case *gmproto.UpdateEvents_AccountChange:
|
|
|
|
c.logContent(msg, "", nil)
|
2023-12-13 23:32:36 +00:00
|
|
|
c.triggerEvent(&events.AccountChange{
|
|
|
|
AccountChangeOrSomethingEvent: evt.AccountChange,
|
|
|
|
})
|
2023-12-13 23:18:08 +00:00
|
|
|
|
2023-07-19 20:30:27 +00:00
|
|
|
default:
|
2023-08-21 09:10:16 +00:00
|
|
|
c.Logger.Warn().
|
2024-02-22 20:37:49 +00:00
|
|
|
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
|
|
|
Str("decrypted_data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
|
2023-08-21 09:10:16 +00:00
|
|
|
Msg("Got unknown event type")
|
2023-07-19 20:30:27 +00:00
|
|
|
}
|
|
|
|
default:
|
2023-08-25 17:45:01 +00:00
|
|
|
c.Logger.Debug().
|
2024-02-22 20:37:49 +00:00
|
|
|
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
2023-08-25 17:45:01 +00:00
|
|
|
Str("request_id", msg.Message.SessionID).
|
|
|
|
Str("action_type", msg.Message.Action.String()).
|
2023-09-07 14:29:48 +00:00
|
|
|
Bool("is_old", msg.IsOld).
|
2023-08-25 17:45:01 +00:00
|
|
|
Msg("Got unexpected response")
|
2023-07-19 20:30:27 +00:00
|
|
|
}
|
|
|
|
}
|