From 6ffe67deec65237fce752da7adc1f9a2745d8921 Mon Sep 17 00:00:00 2001 From: Samuel Hawksby-Robinson Date: Sat, 25 Jul 2020 15:16:00 +0100 Subject: [PATCH] Added ChatEntity interface and made required changes for its use --- protocol/chat_entity.go | 13 +++++ protocol/emoji_reaction.go | 30 ++++++------ protocol/message.go | 6 +++ protocol/message_handler.go | 66 +++++++++++++------------- protocol/messenger_test.go | 2 +- protocol/protobuf/emoji_reaction.proto | 10 ++++ 6 files changed, 79 insertions(+), 48 deletions(-) create mode 100644 protocol/chat_entity.go diff --git a/protocol/chat_entity.go b/protocol/chat_entity.go new file mode 100644 index 000000000..5c2f4e6cf --- /dev/null +++ b/protocol/chat_entity.go @@ -0,0 +1,13 @@ +package protocol + +import ( + "crypto/ecdsa" + + "github.com/status-im/status-go/protocol/protobuf" +) + +type ChatEntity interface { + GetChatId() string + GetMessageType() protobuf.MessageType + GetSigPubKey() *ecdsa.PublicKey +} diff --git a/protocol/emoji_reaction.go b/protocol/emoji_reaction.go index d0af4b420..befbcd4c5 100644 --- a/protocol/emoji_reaction.go +++ b/protocol/emoji_reaction.go @@ -1,29 +1,31 @@ package protocol -import "github.com/status-im/status-go/protocol/protobuf" +import ( + "crypto/ecdsa" + + "github.com/status-im/status-go/protocol/protobuf" +) // EmojiReaction represents an emoji reaction from a user in the application layer, used for persistence, querying and // signaling type EmojiReaction struct { + protobuf.EmojiReaction + // ID calculated as keccak256(compressedAuthorPubKey, data) where data is unencrypted payload. ID string - // Clock Lamport timestamp of the chat message - Clock uint64 - - // MessageID the ID of the target message that the user wishes to react to - MessageID string - - // ChatID the ID of the chat the message belongs to, for query efficiency the ChatID is stored in the db even though the - // target message also stores the ChatID - ChatID string - - // EmojiID the ID of the emoji the user wishes to react with - EmojiID protobuf.EmojiReaction_Type - // From is a public key of the author of the emoji reaction. From string // Retracted represents whether the user has chosen to remove a previously given reaction Retracted bool + + // SigPubKey is the ecdsa encoded public key of the emoji reaction author + SigPubKey *ecdsa.PublicKey `json:"-"` +} + +// GetSigPubKey returns an ecdsa encoded public key +// this function is also required to implement the ChatEntity interface +func (e EmojiReaction) GetSigPubKey() *ecdsa.PublicKey { + return e.SigPubKey } diff --git a/protocol/message.go b/protocol/message.go index ea78950d0..9df57d6ac 100644 --- a/protocol/message.go +++ b/protocol/message.go @@ -332,3 +332,9 @@ func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) { return "", errors.New("audio format not supported") } + +// GetSigPubKey returns an ecdsa encoded public key +// this function is also required to implement the ChatEntity interface +func (m Message) GetSigPubKey() *ecdsa.PublicKey { + return m.SigPubKey +} diff --git a/protocol/message_handler.go b/protocol/message_handler.go index c9c51c88a..f1de98e9c 100644 --- a/protocol/message_handler.go +++ b/protocol/message_handler.go @@ -126,7 +126,7 @@ func (m *MessageHandler) handleCommandMessage(state *ReceivedMessageState, messa if err := message.PrepareContent(); err != nil { return fmt.Errorf("failed to prepare content: %v", err) } - chat, err := m.matchMessage(message, state.AllChats, state.Timesource) + chat, err := m.matchChatEntity(message, state.AllChats, state.Timesource) if err != nil { return err } @@ -312,9 +312,9 @@ func (m *MessageHandler) HandleChatMessage(state *ReceivedMessageState) error { if err != nil { return fmt.Errorf("failed to prepare message content: %v", err) } - chat, err := m.matchMessage(receivedMessage, state.AllChats, state.Timesource) + chat, err := m.matchChatEntity(receivedMessage, state.AllChats, state.Timesource) if err != nil { - return err // matchMessage returns a descriptive error message + return err // matchChatEntity returns a descriptive error message } // If deleted-at is greater, ignore message @@ -359,9 +359,7 @@ func (m *MessageHandler) HandleChatMessage(state *ReceivedMessageState) error { } // Add to response - if receivedMessage != nil { - state.Response.Messages = append(state.Response.Messages, receivedMessage) - } + state.Response.Messages = append(state.Response.Messages, receivedMessage) return nil } @@ -567,26 +565,26 @@ func (m *MessageHandler) HandleDeclineRequestTransaction(messageState *ReceivedM return m.handleCommandMessage(messageState, oldMessage) } -func (m *MessageHandler) matchMessage(message *Message, chats map[string]*Chat, timesource TimeSource) (*Chat, error) { - if message.SigPubKey == nil { +func (m *MessageHandler) matchChatEntity(chatEntity ChatEntity, chats map[string]*Chat, timesource TimeSource) (*Chat, error) { + if chatEntity.GetSigPubKey() == nil { m.logger.Error("public key can't be empty") - return nil, errors.New("received a message with empty public key") + return nil, errors.New("received a chatEntity with empty public key") } switch { - case message.MessageType == protobuf.MessageType_PUBLIC_GROUP: + case chatEntity.GetMessageType() == protobuf.MessageType_PUBLIC_GROUP: // For public messages, all outgoing and incoming messages have the same chatID // equal to a public chat name. - chatID := message.ChatId + chatID := chatEntity.GetChatId() chat := chats[chatID] if chat == nil { - return nil, errors.New("received a public message from non-existing chat") + return nil, errors.New("received a public chatEntity from non-existing chat") } return chat, nil - case message.MessageType == protobuf.MessageType_ONE_TO_ONE && common.IsPubKeyEqual(message.SigPubKey, &m.identity.PublicKey): + case chatEntity.GetMessageType() == protobuf.MessageType_ONE_TO_ONE && common.IsPubKeyEqual(message.SigPubKey, &m.identity.PublicKey): // It's a private message coming from us so we rely on Message.ChatID // If chat does not exist, it should be created to support multidevice synchronization. - chatID := message.ChatId + chatID := chatEntity.GetChatId() chat := chats[chatID] if chat == nil { if len(chatID) != PubKeyStringLength { @@ -606,27 +604,27 @@ func (m *MessageHandler) matchMessage(message *Message, chats map[string]*Chat, chat = &newChat } return chat, nil - case message.MessageType == protobuf.MessageType_ONE_TO_ONE: - // It's an incoming private message. ChatID is calculated from the signature. + case chatEntity.GetMessageType() == protobuf.MessageType_ONE_TO_ONE: + // It's an incoming private chatEntity. ChatID is calculated from the signature. // If a chat does not exist, a new one is created and saved. - chatID := contactIDFromPublicKey(message.SigPubKey) + chatID := contactIDFromPublicKey(chatEntity.GetSigPubKey()) chat := chats[chatID] if chat == nil { // TODO: this should be a three-word name used in the mobile client - newChat := CreateOneToOneChat(chatID[:8], message.SigPubKey, timesource) + newChat := CreateOneToOneChat(chatID[:8], chatEntity.GetSigPubKey(), timesource) chat = &newChat } return chat, nil - case message.MessageType == protobuf.MessageType_PRIVATE_GROUP: - // In the case of a group message, ChatID is the same for all messages belonging to a group. + case chatEntity.GetMessageType() == protobuf.MessageType_PRIVATE_GROUP: + // In the case of a group chatEntity, ChatID is the same for all messages belonging to a group. // It needs to be verified if the signature public key belongs to the chat. - chatID := message.ChatId + chatID := chatEntity.GetChatId() chat := chats[chatID] if chat == nil { - return nil, errors.New("received group chat message for non-existing chat") + return nil, errors.New("received group chat chatEntity for non-existing chat") } - theirKeyHex := contactIDFromPublicKey(message.SigPubKey) + theirKeyHex := contactIDFromPublicKey(chatEntity.GetSigPubKey()) myKeyHex := contactIDFromPublicKey(&m.identity.PublicKey) var theyJoined bool var iJoined bool @@ -670,21 +668,23 @@ func (m *MessageHandler) messageExists(messageID string, existingMessagesMap map return false, nil } -func (m *MessageHandler) HandleEmojiReaction(state *ReceivedMessageState, message protobuf.EmojiReaction) error { +func (m *MessageHandler) HandleEmojiReaction(state *ReceivedMessageState, pbEmojiR protobuf.EmojiReaction) error { logger := m.logger.With(zap.String("site", "HandleEmojiReaction")) - // TODO change this to chat id directly from the protobuf once it is updated - contact := state.CurrentMessageState.Contact - chat, ok := state.AllChats[contact.ID] - if !ok { - chat = OneToOneFromPublicKey(state.CurrentMessageState.PublicKey, state.Timesource) - // We don't want to show the chat to the user - chat.Active = false + emojiReaction := &EmojiReaction{ + EmojiReaction: pbEmojiR, + ID: state.CurrentMessageState.MessageID, + From: state.CurrentMessageState.Contact.ID, + SigPubKey: state.CurrentMessageState.PublicKey, + } + chat, err := m.matchChatEntity(emojiReaction, state.AllChats, state.Timesource) + if err != nil { + return err // matchChatEntity returns a descriptive error message } logger.Info("Handling emoji reaction") - if chat.LastClockValue < message.Clock { - chat.LastClockValue = message.Clock + if chat.LastClockValue < pbEmojiR.Clock { + chat.LastClockValue = pbEmojiR.Clock } state.ModifiedChats[chat.ID] = true diff --git a/protocol/messenger_test.go b/protocol/messenger_test.go index c82d0522b..511039645 100644 --- a/protocol/messenger_test.go +++ b/protocol/messenger_test.go @@ -2254,7 +2254,7 @@ func (s *MessageHandlerSuite) TestRun() { s.Empty(message.LocalChatID) message.ID = strconv.Itoa(idx) // manually set the ID because messages does not go through messageProcessor - chat, err := s.messageHandler.matchMessage(&message, chatsMap, &testTimeSource{}) + chat, err := s.messageHandler.matchChatEntity(&message, chatsMap, &testTimeSource{}) if tc.Error { s.Require().Error(err) } else { diff --git a/protocol/protobuf/emoji_reaction.proto b/protocol/protobuf/emoji_reaction.proto index d9ad30a2b..101dfc1ac 100644 --- a/protocol/protobuf/emoji_reaction.proto +++ b/protocol/protobuf/emoji_reaction.proto @@ -5,10 +5,20 @@ package protobuf; import "enums.proto"; message EmojiReaction { + // clock Lamport timestamp of the chat message uint64 clock = 1; + + // chat_id the ID of the chat the message belongs to, for query efficiency the chat_id is stored in the db even though the + // target message also stores the chat_id string chat_id = 2; + + // message_id the ID of the target message that the user wishes to react to string message_id = 3; + + // message_type is (somewhat confusingly) the ID of the type of chat the message belongs to MessageType message_type = 4; + + // type the ID of the emoji the user wishes to react with Type type = 5; enum Type {