215 lines
6.4 KiB
Go
215 lines
6.4 KiB
Go
package libgm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"golang.org/x/exp/slices"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
|
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
|
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
|
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
|
)
|
|
|
|
type Response struct {
|
|
client *Client
|
|
ResponseId string
|
|
RoutingOpCode int64
|
|
Data *binary.EncodedResponse // base64 encoded (decode -> protomessage)
|
|
|
|
StartExecute string
|
|
FinishExecute string
|
|
DevicePair *DevicePair
|
|
}
|
|
|
|
type SessionHandler struct {
|
|
client *Client
|
|
requests map[string]map[int64]*ResponseChan
|
|
|
|
ackMap []string
|
|
ackTicker *time.Ticker
|
|
|
|
sessionId string
|
|
|
|
responseTimeout time.Duration
|
|
}
|
|
|
|
func (s *SessionHandler) SetResponseTimeout(milliSeconds int) {
|
|
s.responseTimeout = time.Duration(milliSeconds) * time.Millisecond
|
|
}
|
|
|
|
func (s *SessionHandler) ResetSessionId() {
|
|
s.sessionId = util.RandomUUIDv4()
|
|
}
|
|
|
|
func (c *Client) createAndSendRequest(instructionId int64, ttl int64, newSession bool, encryptedProtoMessage protoreflect.Message) (string, error) {
|
|
requestId := util.RandomUUIDv4()
|
|
instruction, ok := c.instructions.GetInstruction(instructionId)
|
|
if !ok {
|
|
return "", fmt.Errorf("failed to get instruction: %v does not exist", instructionId)
|
|
}
|
|
|
|
if newSession {
|
|
requestId = c.sessionHandler.sessionId
|
|
}
|
|
|
|
var encryptedData []byte
|
|
var encryptErr error
|
|
if encryptedProtoMessage != nil {
|
|
encryptedData, encryptErr = c.EncryptPayloadData(encryptedProtoMessage)
|
|
if encryptErr != nil {
|
|
return "", fmt.Errorf("failed to encrypt payload data for opcode: %v", instructionId)
|
|
}
|
|
c.Logger.Info().Any("encryptedData", encryptedData).Msg("Sending request with encrypted data")
|
|
}
|
|
|
|
encodedData := payload.NewEncodedPayload(requestId, instruction.Opcode, encryptedData, c.sessionHandler.sessionId)
|
|
encodedStr, encodeErr := crypto.EncodeProtoB64(encodedData)
|
|
if encodeErr != nil {
|
|
panic(fmt.Errorf("Failed to encode data: %w", encodeErr))
|
|
}
|
|
messageData := payload.NewMessageData(requestId, encodedStr, instruction.RoutingOpCode, instruction.MsgType)
|
|
authMessage := payload.NewAuthData(requestId, c.rpcKey, &binary.Date{Year: 2023, Seq1: 6, Seq2: 8, Seq3: 4, Seq4: 6})
|
|
sendMessage := payload.NewSendMessage(c.devicePair.Mobile, messageData, authMessage, ttl)
|
|
|
|
sentRequestId, reqErr := c.sessionHandler.completeSendMessage(encodedData.RequestId, instruction.Opcode, sendMessage)
|
|
if reqErr != nil {
|
|
return "", fmt.Errorf("failed to send message request for opcode: %v", instructionId)
|
|
}
|
|
return sentRequestId, nil
|
|
}
|
|
|
|
func (s *SessionHandler) completeSendMessage(requestId string, opCode int64, msg *binary.SendMessage) (string, error) {
|
|
jsonData, err := s.toJSON(msg.ProtoReflect())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
//s.client.Logger.Debug().Any("payload", string(jsonData)).Msg("Sending message request")
|
|
s.addRequestToChannel(requestId, opCode)
|
|
_, reqErr := s.client.rpc.sendMessageRequest(util.SEND_MESSAGE, jsonData)
|
|
if reqErr != nil {
|
|
return "", reqErr
|
|
}
|
|
return requestId, nil
|
|
}
|
|
|
|
func (s *SessionHandler) toJSON(message protoreflect.Message) ([]byte, error) {
|
|
interfaceArr, err := pblite.Serialize(message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
jsonData, jsonErr := json.Marshal(interfaceArr)
|
|
if jsonErr != nil {
|
|
return nil, jsonErr
|
|
}
|
|
return jsonData, nil
|
|
}
|
|
|
|
func (s *SessionHandler) addResponseAck(responseId string) {
|
|
hasResponseId := slices.Contains(s.ackMap, responseId)
|
|
if !hasResponseId {
|
|
s.ackMap = append(s.ackMap, responseId)
|
|
}
|
|
}
|
|
|
|
func (s *SessionHandler) startAckInterval() {
|
|
if s.ackTicker != nil {
|
|
s.ackTicker.Stop()
|
|
}
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
s.ackTicker = ticker
|
|
go func() {
|
|
for range ticker.C {
|
|
s.sendAckRequest()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (s *SessionHandler) sendAckRequest() {
|
|
if len(s.ackMap) <= 0 {
|
|
return
|
|
}
|
|
reqId := util.RandomUUIDv4()
|
|
ackMessagePayload := &binary.AckMessagePayload{
|
|
AuthData: &binary.AuthMessage{
|
|
RequestId: reqId,
|
|
RpcKey: s.client.rpcKey,
|
|
Date: &binary.Date{Year: 2023, Seq1: 6, Seq2: 8, Seq3: 4, Seq4: 6},
|
|
},
|
|
EmptyArr: &binary.EmptyArr{},
|
|
NoClue: nil,
|
|
}
|
|
dataArray, err := pblite.Serialize(ackMessagePayload.ProtoReflect())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ackMessages := make([][]interface{}, 0)
|
|
for _, reqId := range s.ackMap {
|
|
ackMessageData := &binary.AckMessageData{RequestId: reqId, Device: s.client.devicePair.Browser}
|
|
ackMessageDataArr, err := pblite.Serialize(ackMessageData.ProtoReflect())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ackMessages = append(ackMessages, ackMessageDataArr)
|
|
s.ackMap = util.RemoveFromSlice(s.ackMap, reqId)
|
|
}
|
|
dataArray = append(dataArray, ackMessages)
|
|
jsonData, jsonErr := json.Marshal(dataArray)
|
|
if jsonErr != nil {
|
|
panic(err)
|
|
}
|
|
_, err = s.client.rpc.sendMessageRequest(util.ACK_MESSAGES, jsonData)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
s.client.Logger.Debug().Msg("[ACK] Sent Request")
|
|
}
|
|
|
|
func (s *SessionHandler) NewResponse(response *binary.RPCResponse) (*Response, error) {
|
|
//s.client.Logger.Debug().Any("rpcResponse", response).Msg("Raw rpc response")
|
|
decodedData, err := crypto.DecodeEncodedResponse(response.Data.EncodedData)
|
|
if err != nil {
|
|
panic(err)
|
|
return nil, err
|
|
}
|
|
return &Response{
|
|
client: s.client,
|
|
ResponseId: response.Data.RequestId,
|
|
RoutingOpCode: response.Data.RoutingOpCode,
|
|
StartExecute: response.Data.Ts1,
|
|
FinishExecute: response.Data.Ts2,
|
|
DevicePair: &DevicePair{
|
|
Mobile: response.Data.Mobile,
|
|
Browser: response.Data.Browser,
|
|
},
|
|
Data: decodedData,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Response) decryptData() (proto.Message, error) {
|
|
if r.Data.EncryptedData != nil {
|
|
instruction, ok := r.client.instructions.GetInstruction(r.Data.Opcode)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to decrypt data for unknown opcode: %v", r.Data.Opcode)
|
|
}
|
|
decryptedBytes, errDecrypt := instruction.cryptor.Decrypt(r.Data.EncryptedData)
|
|
if errDecrypt != nil {
|
|
return nil, errDecrypt
|
|
}
|
|
//os.WriteFile("opcode_"+strconv.Itoa(int(instruction.Opcode))+".bin", decryptedBytes, os.ModePerm)
|
|
|
|
protoMessageData := instruction.DecryptedProtoMessage.ProtoReflect().Type().New().Interface()
|
|
decodeProtoErr := binary.DecodeProtoMessage(decryptedBytes, protoMessageData)
|
|
if decodeProtoErr != nil {
|
|
return nil, decodeProtoErr
|
|
}
|
|
|
|
return protoMessageData, nil
|
|
}
|
|
return nil, fmt.Errorf("no encrypted data to decrypt for requestId: %s", r.Data.RequestId)
|
|
}
|