status-im/status-go

View on GitHub
protocol/messenger_pin_messages.go

Summary

Maintainability
A
0 mins
Test Coverage
D
69%
package protocol

import (
    "bytes"
    "context"
    "crypto/ecdsa"
    "crypto/sha256"
    "encoding/binary"
    "errors"
    "fmt"

    gethcommon "github.com/ethereum/go-ethereum/common"
    "github.com/status-im/status-go/eth-node/crypto"
    "github.com/status-im/status-go/protocol/common"
    "github.com/status-im/status-go/protocol/protobuf"
)

// SendPinMessage sends the PinMessage to the corresponding chat
func (m *Messenger) SendPinMessage(ctx context.Context, message *common.PinMessage) (*MessengerResponse, error) {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    return m.sendPinMessage(ctx, message)
}

func (m *Messenger) sendPinMessage(ctx context.Context, message *common.PinMessage) (*MessengerResponse, error) {
    var response MessengerResponse

    // A valid added chat is required.
    chat, ok := m.allChats.Load(message.ChatId)
    if !ok {
        return nil, errors.New("chat not found")
    }

    err := m.handleStandaloneChatIdentity(chat)
    if err != nil {
        return nil, err
    }

    err = extendPinMessageFromChat(message, chat, &m.identity.PublicKey, m.getTimesource())
    if err != nil {
        return nil, err
    }

    message.ID, err = generatePinMessageID(&m.identity.PublicKey, message, chat)
    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.encodeChatEntity(chat, message)
    if err != nil {
        return nil, err
    }

    rawMessage := common.RawMessage{
        LocalChatID:          chat.ID,
        Payload:              encodedMessage,
        MessageType:          protobuf.ApplicationMetadataMessage_PIN_MESSAGE,
        SkipGroupMessageWrap: true,
        ResendType:           chat.DefaultResendType(),
    }
    _, err = m.dispatchMessage(ctx, rawMessage)
    if err != nil {
        return nil, err
    }

    err = m.persistence.SavePinMessages([]*common.PinMessage{message})
    if err != nil {
        return nil, err
    }

    if message.Pinned {
        id, err := generatePinMessageNotificationID(&m.identity.PublicKey, message, chat)
        if err != nil {
            return nil, err
        }
        chatMessage := &common.Message{
            ChatMessage: &protobuf.ChatMessage{
                Clock:       message.Clock,
                Timestamp:   m.getTimesource().GetCurrentTime(),
                ChatId:      chat.ID,
                MessageType: message.MessageType,
                ResponseTo:  message.MessageId,
                ContentType: protobuf.ChatMessage_SYSTEM_MESSAGE_PINNED_MESSAGE,
            },
            WhisperTimestamp: m.getTimesource().GetCurrentTime(),
            ID:               id,
            LocalChatID:      chat.ID,
            From:             m.myHexIdentity(),
        }

        msg := []*common.Message{chatMessage}
        err = m.persistence.SaveMessages(msg)
        if err != nil {
            return nil, err
        }

        msg, err = m.pullMessagesAndResponsesFromDB(msg)
        if err != nil {
            return nil, err
        }

        response.SetMessages(msg)
        err = m.prepareMessages(response.messages)
        if err != nil {
            return nil, err
        }
    }

    response.AddPinMessage(message)
    response.AddChat(chat)
    return &response, m.saveChat(chat)
}

func (m *Messenger) PinnedMessageByChatID(chatID, cursor string, limit int) ([]*common.PinnedMessage, string, error) {
    pinnedMsgs, cursor, err := m.persistence.PinnedMessageByChatID(chatID, cursor, limit)

    if err != nil {
        return nil, "", err
    }

    if m.httpServer != nil {
        for idx := range pinnedMsgs {
            msg := pinnedMsgs[idx].Message
            err = m.prepareMessage(msg, m.httpServer)
            if err != nil {
                return nil, "", err
            }
            pinnedMsgs[idx].Message = msg
        }
    }
    return pinnedMsgs, cursor, nil
}

func (m *Messenger) SavePinMessages(messages []*common.PinMessage) error {
    return m.persistence.SavePinMessages(messages)
}

func generatePinMessageID(pubKey *ecdsa.PublicKey, pm *common.PinMessage, chat *Chat) (string, error) {
    data, err := pinMessageBaseID(pubKey, pm, chat)
    if err != nil {
        return "", err
    }

    id := sha256.Sum256(data)
    idString := fmt.Sprintf("%x", id)

    return idString, nil
}

func pinMessageBaseID(pubKey *ecdsa.PublicKey, pm *common.PinMessage, chat *Chat) ([]byte, error) {
    data := gethcommon.FromHex(pm.MessageId)

    switch {
    case chat.ChatType == ChatTypeOneToOne:
        ourPubKey := crypto.FromECDSAPub(pubKey)
        tmpPubKey, err := chat.PublicKey()
        if err != nil {
            return nil, err
        }
        theirPubKey := crypto.FromECDSAPub(tmpPubKey)

        if bytes.Compare(ourPubKey, theirPubKey) < 0 {
            data = append(data, ourPubKey...)   // our key
            data = append(data, theirPubKey...) // their key
        } else {
            data = append(data, theirPubKey...) // their key
            data = append(data, ourPubKey...)   // our key
        }
    default:
        data = append(data, []byte(chat.ID)...)
    }

    return data, nil
}

func generatePinMessageNotificationID(pubKey *ecdsa.PublicKey, pm *common.PinMessage, chat *Chat) (string, error) {
    data, err := pinMessageBaseID(pubKey, pm, chat)
    if err != nil {
        return "", err
    }

    clockBytes := make([]byte, 8)
    binary.LittleEndian.PutUint64(clockBytes, pm.Clock)
    data = append(data, clockBytes...)

    id := sha256.Sum256(data)
    idString := fmt.Sprintf("%x", id)

    return idString, nil
}