Add system message for mutual contact state updates (#3519)

* feat: add mutual state update system message

* feat: send mutual state update on accepting CR

* feat: send mutual state update when removing a contact

* fix: don't send MutualStateUpdateMessage over wire

* fix: mutual state update message text fixed

* fix: new clock to ensure system message after CR and add chat to the response

* feat: add AC notification for contact removal

* feat: replace "sent" mutual state system message with "added"
This commit is contained in:
Mikhail Rogachev 2023-06-08 16:00:19 +04:00 committed by GitHub
parent 5b6f7226bb
commit 8589a525a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 524 additions and 160 deletions

View File

@ -27,6 +27,7 @@ const (
ActivityCenterNotificationTypeCommunityMembershipRequest
ActivityCenterNotificationTypeCommunityKicked
ActivityCenterNotificationTypeContactVerification
ActivityCenterNotificationTypeContactRemoved
)
type ActivityCenterMembershipStatus int

View File

@ -30,6 +30,14 @@ const (
ContactRequestStateDismissed
)
type MutualStateUpdateType int
const (
MutualStateUpdateTypeSent MutualStateUpdateType = iota + 1
MutualStateUpdateTypeAdded
MutualStateUpdateTypeRemoved
)
// ContactDeviceInfo is a struct containing information about a particular device owned by a contact
type ContactDeviceInfo struct {
// The installation id of the device

View File

@ -300,7 +300,11 @@ func ValidateReceivedChatMessage(message *protobuf.ChatMessage, whisperTimestamp
}
if message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP {
return errors.New("system message content type not allowed")
return errors.New("private group system message content type not allowed")
}
if message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE {
return errors.New("mutual state update system message content type not allowed")
}
if err := ValidateDisplayName(&message.DisplayName); err != nil {

View File

@ -75,7 +75,7 @@ func (s *MessengerActivityCenterMessageSuite) TestDeleteOneToOneChat() {
}
sendResponse, err := theirMessenger.SendContactRequest(context.Background(), r)
s.NoError(err)
s.Require().Len(sendResponse.Messages(), 1)
s.Require().Len(sendResponse.Messages(), 2)
response, err := WaitOnMessengerResponse(
s.m,
@ -84,7 +84,7 @@ func (s *MessengerActivityCenterMessageSuite) TestDeleteOneToOneChat() {
)
s.Require().NoError(err)
s.Require().Len(response.Chats(), 1)
s.Require().Len(response.Messages(), 1)
s.Require().Len(response.Messages(), 2)
s.Require().Len(response.ActivityCenterNotifications(), 1)
request := &requests.DeactivateChat{ID: response.Chats()[0].ID}

View File

@ -61,17 +61,36 @@ func (s *MessengerContactRequestSuite) newMessenger(shh types.Waku) *Messenger {
return messenger
}
func (s *MessengerContactRequestSuite) findFirstByContentType(messages []*common.Message, contentType protobuf.ChatMessage_ContentType) *common.Message {
for _, message := range messages {
if message.ContentType == contentType {
return message
}
}
return nil
}
func (s *MessengerContactRequestSuite) sendContactRequest(request *requests.SendContactRequest, messenger *Messenger) {
// Send contact request
resp, err := messenger.SendContactRequest(context.Background(), request)
s.Require().NoError(err)
s.Require().NotNil(resp)
// Check CR message
s.Require().Len(resp.Messages(), 1)
contactRequest := resp.Messages()[0]
// Check CR and mutual state update messages
s.Require().Len(resp.Messages(), 2)
contactRequest := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequest)
mutualStateUpdate := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(common.ContactRequestStatePending, contactRequest.ContactRequestState)
s.Require().Equal(request.Message, contactRequest.Text)
s.Require().NotNil(mutualStateUpdate.ID)
s.Require().Equal(mutualStateUpdate.From, messenger.myHexIdentity())
s.Require().Equal(mutualStateUpdate.ChatId, request.ID)
s.Require().Equal(mutualStateUpdate.Text, "You sent a contact request to @"+request.ID)
// Check pending notification
s.Require().Len(resp.ActivityCenterNotifications(), 1)
@ -98,6 +117,13 @@ func (s *MessengerContactRequestSuite) sendContactRequest(request *requests.Send
// Check contact's primary name matches notifiaction's name
s.Require().Equal(resp.ActivityCenterNotifications()[0].Name, contacts[0].PrimaryName())
// Make sure update message was saved properly
mutualStateUpdateFromDb, err := messenger.persistence.MessageByID(mutualStateUpdate.ID)
s.Require().NoError(err)
s.Require().Equal(mutualStateUpdateFromDb.From, messenger.myHexIdentity())
s.Require().Equal(mutualStateUpdateFromDb.ChatId, request.ID)
s.Require().Equal(mutualStateUpdateFromDb.Text, "You sent a contact request to @"+request.ID)
}
func (s *MessengerContactRequestSuite) receiveContactRequest(messageText string, theirMessenger *Messenger) *common.Message {
@ -105,7 +131,7 @@ func (s *MessengerContactRequestSuite) receiveContactRequest(messageText string,
resp, err := WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool {
return len(r.Contacts) == 1 && len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1
return len(r.Contacts) == 1 && len(r.Messages()) == 2 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
@ -114,11 +140,20 @@ func (s *MessengerContactRequestSuite) receiveContactRequest(messageText string,
s.Require().NoError(err)
s.Require().NotNil(resp)
// Check CR message
s.Require().Len(resp.Messages(), 1)
contactRequest := resp.Messages()[0]
// Check CR and mutual state update messages
s.Require().Len(resp.Messages(), 2)
contactRequest := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequest)
mutualStateUpdate := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(common.ContactRequestStatePending, contactRequest.ContactRequestState)
s.Require().Equal(messageText, contactRequest.Text)
s.Require().Equal(mutualStateUpdate.From, contactRequest.From)
s.Require().Equal(mutualStateUpdate.ChatId, contactRequest.From)
s.Require().Equal(mutualStateUpdate.Text, "@"+contactRequest.From+" sent you a contact request")
// Check activity center notification is of the right type
s.Require().Len(resp.ActivityCenterNotifications(), 1)
@ -132,7 +167,7 @@ func (s *MessengerContactRequestSuite) receiveContactRequest(messageText string,
notifications, err := theirMessenger.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
Cursor: "",
Limit: 10,
ActivityTypes: []ActivityCenterType{},
ActivityTypes: []ActivityCenterType{ActivityCenterNotificationTypeContactRequest},
ReadType: ActivityCenterQueryParamsReadUnread,
},
)
@ -163,11 +198,22 @@ func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *comm
resp, err := receiver.AcceptContactRequest(context.Background(), &requests.AcceptContactRequest{ID: types.Hex2Bytes(contactRequest.ID)})
s.Require().NoError(err)
// Make sure the message is updated
// Chack updated contact request message and mutual state update
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Equal(resp.Messages()[0].ID, contactRequest.ID)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
contactRequestMsg := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
mutualStateUpdate := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(contactRequestMsg.ID, contactRequest.ID)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequestMsg.ContactRequestState)
// Reversed for add contact update
s.Require().Equal(mutualStateUpdate.From, contactRequestMsg.ChatId)
s.Require().Equal(mutualStateUpdate.ChatId, contactRequestMsg.From)
s.Require().Equal(mutualStateUpdate.Text, "You added @"+contactRequestMsg.From+" as a contact")
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().Equal(resp.ActivityCenterNotifications()[0].ID.String(), contactRequest.ID)
@ -198,7 +244,7 @@ func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *comm
resp, err = WaitOnMessengerResponse(
sender,
func(r *MessengerResponse) bool {
return len(r.Contacts) == 1 && len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1
return len(r.Contacts) == 1 && len(r.Messages()) == 2 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
@ -211,14 +257,24 @@ func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *comm
s.Require().Equal(resp.ActivityCenterNotifications()[0].Read, true)
s.Require().Equal(resp.ActivityCenterNotifications()[0].Accepted, true)
s.Require().Equal(resp.ActivityCenterNotifications()[0].Dismissed, false)
s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message)
// Make sure the message is updated, sender s2de
s.Require().Len(resp.Messages(), 1)
s.Require().NotNil(resp.Messages()[0])
s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message)
s.Require().Equal(contactRequest.ID, resp.Messages()[0].ID)
s.Require().Equal(contactRequest.Text, resp.Messages()[0].Text)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
contactRequestMsg = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
mutualStateUpdate = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(contactRequest.ID, contactRequestMsg.ID)
s.Require().Equal(contactRequest.Text, contactRequestMsg.Text)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequestMsg.ContactRequestState)
s.Require().Equal(mutualStateUpdate.From, contactRequestMsg.ChatId)
s.Require().Equal(mutualStateUpdate.ChatId, contactRequestMsg.ChatId)
s.Require().Equal(mutualStateUpdate.Text, "@"+mutualStateUpdate.From+" added you as a contact")
// Make sure we consider them a mutual contact, sender side
mutualContacts = s.m.MutualContacts()
@ -294,7 +350,7 @@ func (s *MessengerContactRequestSuite) retractContactRequest(contactID string, t
resp, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool {
return len(r.Contacts) > 0
return len(r.Contacts) > 0 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
@ -307,11 +363,13 @@ func (s *MessengerContactRequestSuite) retractContactRequest(contactID string, t
s.Require().False(resp.Contacts[0].added())
s.Require().False(resp.Contacts[0].hasAddedUs())
// Check the contact state is correctly set
s.Require().Len(resp.Contacts, 1)
s.Require().Equal(ContactRequestStateNone, resp.Contacts[0].ContactRequestLocalState)
s.Require().Equal(ContactRequestStateNone, resp.Contacts[0].ContactRequestRemoteState)
// Check pending notification
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().Equal(ActivityCenterNotificationTypeContactRemoved, resp.ActivityCenterNotifications()[0].Type)
s.Require().Equal(resp.ActivityCenterNotifications()[0].Read, false)
}
func (s *MessengerContactRequestSuite) syncInstallationContactV2FromContact(contact *Contact) protobuf.SyncInstallationContactV2 {
@ -409,17 +467,40 @@ func (s *MessengerContactRequestSuite) TestReceiveAndAcceptContactRequestTwice()
// Resend contact request with higher clock value
resp, err := s.m.SendContactRequest(context.Background(), request)
s.Require().NoError(err)
s.Require().NotNil(resp)
// Check CR and mutual state update messages
s.Require().Len(resp.Messages(), 2)
contactRequest = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequest)
mutualStateUpdate := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequest.ContactRequestState)
s.Require().Equal(request.Message, contactRequest.Text)
s.Require().Equal(mutualStateUpdate.From, s.m.myHexIdentity())
s.Require().Equal(mutualStateUpdate.ChatId, request.ID)
// Wait for the message to reach its destination
resp, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool {
return len(r.Messages()) == 1 && r.Messages()[0].ID == resp.Messages()[0].ID
return len(r.Messages()) > 0
},
"no messages",
)
s.Require().NoError(err)
s.Require().Len(resp.Messages(), 1)
contactRequest = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequest)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequest.ContactRequestState)
s.Require().Equal(request.Message, contactRequest.Text)
// Nothing should have changed, on both sides
mutualContacts := s.m.MutualContacts()
s.Require().Len(mutualContacts, 1)
@ -450,9 +531,19 @@ func (s *MessengerContactRequestSuite) TestAcceptLatestContactRequestForContact(
// Make sure the message is updated
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Equal(resp.Messages()[0].ID, contactRequest.ID)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
contactRequestMsg := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
mutualStateUpdate := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(contactRequestMsg.ID, contactRequest.ID)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequestMsg.ContactRequestState)
s.Require().Equal(mutualStateUpdate.From, contactRequest.ChatId)
s.Require().Equal(mutualStateUpdate.ChatId, contactRequest.From)
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().Equal(resp.ActivityCenterNotifications()[0].ID.String(), contactRequest.ID)
@ -475,7 +566,7 @@ func (s *MessengerContactRequestSuite) TestAcceptLatestContactRequestForContact(
resp, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool {
return len(r.Contacts) == 1 && len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1
return len(r.Contacts) == 1 && len(r.Messages()) == 2 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
@ -484,9 +575,18 @@ func (s *MessengerContactRequestSuite) TestAcceptLatestContactRequestForContact(
// Make sure the message is updated, sender side
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Equal(messageText, resp.Messages()[0].Text)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
contactRequestMsg = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
mutualStateUpdate = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequestMsg.ContactRequestState)
s.Require().Equal(mutualStateUpdate.From, contactRequest.ChatId)
s.Require().Equal(mutualStateUpdate.ChatId, contactRequest.ChatId)
// Check activity center notification is of the right type
s.Require().Len(resp.ActivityCenterNotifications(), 1)
@ -952,11 +1052,20 @@ func (s *MessengerContactRequestSuite) TestBobSendsContactRequestAfterDecliningO
s.Require().NotNil(resp)
// Check CR message, it should be accepted
s.Require().Len(resp.Messages(), 1)
contactRequest = resp.Messages()[0]
s.Require().Len(resp.Messages(), 2)
contactRequest = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequest)
mutualStateUpdate := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE)
s.Require().NotNil(mutualStateUpdate)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequest.ContactRequestState)
s.Require().Equal(requestFromBob.Message, contactRequest.Text)
s.Require().Equal(mutualStateUpdate.From, contactRequest.From)
s.Require().Equal(mutualStateUpdate.ChatId, contactRequest.ChatId)
// Check pending notification
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().Equal(ActivityCenterNotificationTypeContactRequest, resp.ActivityCenterNotifications()[0].Type)
@ -979,7 +1088,7 @@ func (s *MessengerContactRequestSuite) TestBobSendsContactRequestAfterDecliningO
resp, err = WaitOnMessengerResponse(
alice,
func(r *MessengerResponse) bool {
return len(r.Contacts) == 1 && len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1
return len(r.Contacts) == 1 && len(r.Messages()) == 2 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
@ -988,9 +1097,15 @@ func (s *MessengerContactRequestSuite) TestBobSendsContactRequestAfterDecliningO
s.Require().NoError(err)
s.Require().NotNil(resp)
contactRequestMsg := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
// Check CR message, it should be accepted
s.Require().Len(resp.Messages(), 1)
contactRequest = resp.Messages()[0]
s.Require().Len(resp.Messages(), 2)
contactRequest = s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequest)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequest.ContactRequestState)
s.Require().Equal(requestFromBob.Message, contactRequest.Text)
@ -1127,10 +1242,14 @@ func (s *MessengerContactRequestSuite) TestBobRestoresIncomingContactRequestFrom
// Make sure the message is updated
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Len(resp.Messages(), 2)
contactRequestMsg := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
// NOTE: We don't restore CR message
// s.Require().Equal(resp.Messages()[0].ID, contactRequest.ID)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequestMsg.ContactRequestState)
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message)
@ -1201,10 +1320,14 @@ func (s *MessengerContactRequestSuite) TestAliceRestoresOutgoingContactRequestFr
// Make sure the message is updated
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Len(resp.Messages(), 2)
contactRequestMsg := s.findFirstByContentType(resp.Messages(), protobuf.ChatMessage_CONTACT_REQUEST)
s.Require().NotNil(contactRequestMsg)
// NOTE: We don't restore CR message
// s.Require().Equal(resp.Messages()[0].ID, contactRequest.ID)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Equal(common.ContactRequestStateAccepted, contactRequestMsg.ContactRequestState)
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message)

View File

@ -63,8 +63,7 @@ func (s *MessengerVerificationRequests) mutualContact(theirMessenger *Messenger)
s.Require().NoError(err)
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Equal(common.ContactRequestStatePending, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
// Make sure it's not returned as coming from us
contactRequests, _, err := s.m.PendingContactRequests("", 10)
@ -110,9 +109,7 @@ func (s *MessengerVerificationRequests) mutualContact(theirMessenger *Messenger)
// Make sure the message is updated
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Equal(resp.Messages()[0].ID, contactRequests[0].ID)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
s.Require().Len(resp.ActivityCenterNotifications(), 1)
s.Require().Equal(resp.ActivityCenterNotifications()[0].ID.String(), contactRequests[0].ID)
@ -135,7 +132,7 @@ func (s *MessengerVerificationRequests) mutualContact(theirMessenger *Messenger)
resp, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool {
return len(r.Contacts) == 1 && len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1
return len(r.Contacts) == 1 && len(r.Messages()) == 2 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
@ -148,9 +145,7 @@ func (s *MessengerVerificationRequests) mutualContact(theirMessenger *Messenger)
// Make sure the message is updated, sender s2de
s.Require().NotNil(resp)
s.Require().Len(resp.Messages(), 1)
s.Require().Equal(resp.Messages()[0].ID, contactRequests[0].ID)
s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState)
s.Require().Len(resp.Messages(), 2)
// Make sure we consider them a mutual contact, sender side
mutualContacts = s.m.MutualContacts()

View File

@ -4,11 +4,13 @@ import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"time"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"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/common"
"github.com/status-im/status-go/protocol/protobuf"
@ -16,6 +18,80 @@ import (
"github.com/status-im/status-go/protocol/transport"
)
func (m *Messenger) prepareMutualStateUpdateMessage(contactID string, updateType MutualStateUpdateType, clock uint64, timestamp uint64, outgoing bool) (*common.Message, error) {
var text string
var to string
var from string
if outgoing {
to = contactID
from = m.myHexIdentity()
switch updateType {
case MutualStateUpdateTypeSent:
text = "You sent a contact request to @" + to
case MutualStateUpdateTypeAdded:
text = "You added @" + to + " as a contact"
case MutualStateUpdateTypeRemoved:
text = "You removed @" + to + " as a contact"
default:
return nil, fmt.Errorf("unhandled outgoing MutualStateUpdateType = %d", updateType)
}
} else {
to = m.myHexIdentity()
from = contactID
switch updateType {
case MutualStateUpdateTypeSent:
text = "@" + from + " sent you a contact request"
case MutualStateUpdateTypeAdded:
text = "@" + from + " added you as a contact"
case MutualStateUpdateTypeRemoved:
text = "@" + from + " removed you as a contact"
default:
return nil, fmt.Errorf("unhandled incoming MutualStateUpdateType = %d", updateType)
}
}
message := &common.Message{
ChatMessage: protobuf.ChatMessage{
ChatId: contactID,
Text: text,
MessageType: protobuf.MessageType_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE,
Clock: clock,
Timestamp: timestamp,
},
From: from,
WhisperTimestamp: timestamp,
LocalChatID: contactID,
Seen: outgoing,
ID: types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s%s%d%d", from, to, updateType, clock)))),
}
return message, nil
}
func (m *Messenger) removeLastMutualStateUpdateMessage(response *MessengerResponse, contactID string) error {
messages, _, err := m.MessageByChatID(contactID, "", 5)
if err != nil {
return err
}
for _, message := range messages {
if message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE {
message.Deleted = true
message.DeletedBy = m.myHexIdentity()
err = m.persistence.SaveMessages([]*common.Message{message})
if err != nil {
return err
}
response.AddMessage(message)
response.AddRemovedMessage(&RemovedMessage{MessageID: message.ID, ChatID: contactID, DeletedBy: m.myHexIdentity()})
}
}
return nil
}
func (m *Messenger) acceptContactRequest(ctx context.Context, requestID string, syncing bool) (*MessengerResponse, error) {
contactRequest, err := m.persistence.MessageByID(requestID)
if err != nil {
@ -47,6 +123,26 @@ func (m *Messenger) acceptContactRequest(ctx context.Context, requestID string,
}
response.AddChat(chat)
// Remove last sent system message
err = m.removeLastMutualStateUpdateMessage(response, contactRequest.From)
if err != nil {
return nil, err
}
// System message for mutual state update
clock, timestamp := chat.NextClockAndTimestamp(m.getTimesource())
updateMessage, err := m.prepareMutualStateUpdateMessage(contactRequest.From, MutualStateUpdateTypeAdded, clock, timestamp, true)
if err != nil {
return nil, err
}
m.prepareMessage(updateMessage, m.httpServer)
err = m.persistence.SaveMessages([]*common.Message{updateMessage})
if err != nil {
return nil, err
}
response.AddMessage(updateMessage)
return response, nil
}
@ -235,6 +331,31 @@ func (m *Messenger) updateAcceptedContactRequest(response *MessengerResponse, co
response.AddMessage(contactRequest)
response.AddContact(contact)
// Remove last sent system message
err = m.removeLastMutualStateUpdateMessage(response, contact.ID)
if err != nil {
return nil, err
}
// System message for mutual state update
chat, clock, err := m.getOneToOneAndNextClock(contact)
if err != nil {
return nil, err
}
timestamp := m.getTimesource().GetCurrentTime()
updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeAdded, clock, timestamp, false)
if err != nil {
return nil, err
}
m.prepareMessage(updateMessage, m.httpServer)
err = m.persistence.SaveMessages([]*common.Message{updateMessage})
if err != nil {
return nil, err
}
response.AddMessage(updateMessage)
response.AddChat(chat)
return response, nil
}
@ -391,6 +512,7 @@ func (m *Messenger) addContact(ctx context.Context, pubKey, ensName, nickname, d
return nil, err
}
// Send contact request as a plain chat message
messageResponse, err := m.sendChatMessage(ctx, contactRequest)
if err != nil {
return nil, err
@ -401,6 +523,21 @@ func (m *Messenger) addContact(ctx context.Context, pubKey, ensName, nickname, d
return nil, err
}
// System message for mutual state update
clock, timestamp = chat.NextClockAndTimestamp(m.transport)
updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeSent, clock, timestamp, true)
if err != nil {
return nil, err
}
m.prepareMessage(updateMessage, m.httpServer)
err = m.persistence.SaveMessages([]*common.Message{updateMessage})
if err != nil {
return nil, err
}
response.AddMessage(updateMessage)
response.AddChat(chat)
notification := m.generateOutgoingContactRequestNotification(contact, contactRequest)
err = m.addActivityCenterNotification(response, notification)
if err != nil {
@ -495,7 +632,27 @@ func (m *Messenger) removeContact(ctx context.Context, response *MessengerRespon
return ErrContactNotFound
}
_, clock, err := m.getOneToOneAndNextClock(contact)
// System message for mutual state update
chat, clock, err := m.getOneToOneAndNextClock(contact)
if err != nil {
return err
}
timestamp := m.getTimesource().GetCurrentTime()
updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeRemoved, clock, timestamp, true)
if err != nil {
return err
}
m.prepareMessage(updateMessage, m.httpServer)
err = m.persistence.SaveMessages([]*common.Message{updateMessage})
if err != nil {
return err
}
response.AddMessage(updateMessage)
response.AddChat(chat)
// Next we retract a contact request
_, clock, err = m.getOneToOneAndNextClock(contact)
if err != nil {
return err
}
@ -656,7 +813,7 @@ func (m *Messenger) SetContactLocalNickname(request *requests.SetContactLocalNic
return response, nil
}
func (m *Messenger) blockContact(contactID string, isDesktopFunc bool) (*Contact, []*Chat, error) {
func (m *Messenger) blockContact(response *MessengerResponse, contactID string, isDesktopFunc bool) (*Contact, []*Chat, error) {
contact, err := m.BuildContact(&requests.BuildContact{PublicKey: contactID})
if err != nil {
return nil, nil, err
@ -702,13 +859,19 @@ func (m *Messenger) blockContact(contactID string, isDesktopFunc bool) (*Contact
return nil, nil, err
}
// We remove anything that's related to this contact request
err = m.persistence.HardDeleteChatContactRequestActivityCenterNotifications(contact.ID)
if err != nil {
return nil, nil, err
}
return contact, chats, nil
}
func (m *Messenger) BlockContact(contactID string) (*MessengerResponse, error) {
response := &MessengerResponse{}
contact, chats, err := m.blockContact(contactID, false)
contact, chats, err := m.blockContact(response, contactID, false)
if err != nil {
return nil, err
}
@ -732,7 +895,7 @@ func (m *Messenger) BlockContact(contactID string) (*MessengerResponse, error) {
func (m *Messenger) BlockContactDesktop(contactID string) (*MessengerResponse, error) {
response := &MessengerResponse{}
contact, chats, err := m.blockContact(contactID, true)
contact, chats, err := m.blockContact(response, contactID, true)
if err != nil {
return nil, err
}
@ -943,6 +1106,9 @@ func (m *Messenger) sendRetractContactRequest(contact *Contact) error {
MessageType: protobuf.ApplicationMetadataMessage_RETRACT_CONTACT_REQUEST,
ResendAutomatically: true,
})
if err != nil {
return err
}
return err
}

View File

@ -14,6 +14,8 @@ import (
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/google/uuid"
"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"
@ -341,6 +343,36 @@ func (m *Messenger) createIncomingContactRequestNotification(contact *Contact, m
return m.addActivityCenterNotification(messageState.Response, notification)
}
func (m *Messenger) createIncomingContactRequestEventAndNotification(contact *Contact, messageState *ReceivedMessageState, contactRequest *common.Message, createNewNotification bool) error {
var updateType MutualStateUpdateType
if contactRequest.ContactRequestState == common.ContactRequestStateAccepted {
updateType = MutualStateUpdateTypeAdded
} else {
updateType = MutualStateUpdateTypeSent
}
// System message for mutual state update
chat, clock, err := m.getOneToOneAndNextClock(contact)
if err != nil {
return err
}
timestamp := m.getTimesource().GetCurrentTime()
updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, updateType, clock, timestamp, false)
if err != nil {
return err
}
m.prepareMessage(updateMessage, m.httpServer)
err = m.persistence.SaveMessages([]*common.Message{updateMessage})
if err != nil {
return err
}
messageState.Response.AddMessage(updateMessage)
messageState.Response.AddChat(chat)
return m.createIncomingContactRequestNotification(contact, messageState, contactRequest, createNewNotification)
}
func (m *Messenger) handleCommandMessage(state *ReceivedMessageState, message *common.Message) error {
message.ID = state.CurrentMessageState.MessageID
message.From = state.CurrentMessageState.Contact.ID
@ -447,7 +479,7 @@ func (m *Messenger) syncContactRequestForInstallationContact(contact *Contact, s
return err
}
} else {
err = m.createIncomingContactRequestNotification(contact, state, contactRequest, true)
err = m.createIncomingContactRequestEventAndNotification(contact, state, contactRequest, true)
if err != nil {
return err
}
@ -918,7 +950,10 @@ func (m *Messenger) handleAcceptContactRequestMessage(state *ReceivedMessageStat
return err
}
} else {
err = m.createIncomingContactRequestNotification(contact, state, request, processingResponse.newContactRequestReceived)
err = m.createIncomingContactRequestEventAndNotification(contact, state, request, processingResponse.newContactRequestReceived)
if err != nil {
return err
}
}
if err != nil {
m.logger.Warn("could not create contact request notification", zap.Error(err))
@ -939,7 +974,7 @@ func (m *Messenger) HandleAcceptContactRequest(state *ReceivedMessageState, mess
return nil
}
func (m *Messenger) handleRetractContactRequest(contact *Contact, message protobuf.RetractContactRequest) error {
func (m *Messenger) handleRetractContactRequest(response *MessengerResponse, contact *Contact, message protobuf.RetractContactRequest) error {
if contact.ID == m.myHexIdentity() {
m.logger.Debug("retraction coming from us, ignoring")
return nil
@ -952,11 +987,40 @@ func (m *Messenger) handleRetractContactRequest(contact *Contact, message protob
return nil
}
// We remove anything that's related to this contact request
err := m.persistence.HardDeleteChatContactRequestActivityCenterNotifications(contact.ID)
// System message for mutual state update
chat, clock, err := m.getOneToOneAndNextClock(contact)
if err != nil {
return err
}
timestamp := m.getTimesource().GetCurrentTime()
updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeRemoved, clock, timestamp, false)
if err != nil {
return err
}
m.prepareMessage(updateMessage, m.httpServer)
err = m.persistence.SaveMessages([]*common.Message{updateMessage})
if err != nil {
return err
}
response.AddMessage(updateMessage)
response.AddChat(chat)
notification := &ActivityCenterNotification{
ID: types.FromHex(uuid.New().String()),
Type: ActivityCenterNotificationTypeContactRemoved,
Name: contact.PrimaryName(),
Author: contact.ID,
Timestamp: m.getTimesource().GetCurrentTime(),
ChatID: contact.ID,
Read: false,
}
err = m.addActivityCenterNotification(response, notification)
if err != nil {
m.logger.Warn("failed to create activity center notification", zap.Error(err))
return err
}
m.allContacts.Store(contact.ID, contact)
@ -965,7 +1029,7 @@ func (m *Messenger) handleRetractContactRequest(contact *Contact, message protob
func (m *Messenger) HandleRetractContactRequest(state *ReceivedMessageState, message protobuf.RetractContactRequest) error {
contact := state.CurrentMessageState.Contact
err := m.handleRetractContactRequest(contact, message)
err := m.handleRetractContactRequest(state.Response, contact, message)
if err != nil {
return err
}
@ -1020,7 +1084,7 @@ func (m *Messenger) HandleContactUpdate(state *ReceivedMessageState, message pro
return err
}
err = m.createIncomingContactRequestNotification(contact, state, contactRequest, true)
err = m.createIncomingContactRequestEventAndNotification(contact, state, contactRequest, true)
if err != nil {
return err
}
@ -1044,11 +1108,10 @@ func (m *Messenger) HandleContactUpdate(state *ReceivedMessageState, message pro
r := contact.ContactRequestReceived(message.ContactRequestClock)
if r.newContactRequestReceived {
err = m.createIncomingContactRequestNotification(contact, state, nil, true)
err = m.createIncomingContactRequestEventAndNotification(contact, state, nil, true)
if err != nil {
m.logger.Warn("could not create contact request notification", zap.Error(err))
return err
}
}
contact.LastUpdated = message.Clock
state.ModifiedContacts.Store(contact.ID, true)
@ -1975,11 +2038,10 @@ func (m *Messenger) handleChatMessage(state *ReceivedMessageState, forceSeen boo
receivedMessage.ContactRequestState = common.ContactRequestStatePending
}
err = m.createIncomingContactRequestNotification(contact, state, receivedMessage, true)
err = m.createIncomingContactRequestEventAndNotification(contact, state, receivedMessage, true)
if err != nil {
return err
}
}
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
@ -2003,7 +2065,7 @@ func (m *Messenger) handleChatMessage(state *ReceivedMessageState, forceSeen boo
state.AllContacts.Store(chatContact.ID, chatContact)
if sendNotification {
err = m.createIncomingContactRequestNotification(chatContact, state, receivedMessage, true)
err = m.createIncomingContactRequestEventAndNotification(chatContact, state, receivedMessage, true)
if err != nil {
return err
}

View File

@ -68,7 +68,8 @@ const (
ChatMessage_DISCORD_MESSAGE ChatMessage_ContentType = 12
ChatMessage_IDENTITY_VERIFICATION ChatMessage_ContentType = 13
// Only local
ChatMessage_SYSTEM_MESSAGE_PINNED_MESSAGE ChatMessage_ContentType = 14
ChatMessage_SYSTEM_MESSAGE_PINNED_MESSAGE ChatMessage_ContentType = 14
ChatMessage_SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE ChatMessage_ContentType = 15
)
var ChatMessage_ContentType_name = map[int32]string{
@ -87,6 +88,7 @@ var ChatMessage_ContentType_name = map[int32]string{
12: "DISCORD_MESSAGE",
13: "IDENTITY_VERIFICATION",
14: "SYSTEM_MESSAGE_PINNED_MESSAGE",
15: "SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE",
}
var ChatMessage_ContentType_value = map[string]int32{
@ -105,6 +107,7 @@ var ChatMessage_ContentType_value = map[string]int32{
"DISCORD_MESSAGE": 12,
"IDENTITY_VERIFICATION": 13,
"SYSTEM_MESSAGE_PINNED_MESSAGE": 14,
"SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE": 15,
}
func (x ChatMessage_ContentType) String() string {
@ -1188,94 +1191,95 @@ func init() {
}
var fileDescriptor_263952f55fd35689 = []byte{
// 1411 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x4f, 0x6f, 0xdb, 0xc6,
0x12, 0xb7, 0x64, 0xfd, 0xe3, 0xe8, 0x8f, 0xf9, 0x36, 0x4e, 0xc2, 0x04, 0x71, 0xe2, 0x08, 0x01,
0xe2, 0x87, 0xf7, 0xe0, 0x07, 0xe4, 0xa5, 0x45, 0x80, 0xa2, 0x28, 0x68, 0x89, 0xb1, 0xd9, 0x44,
0xb2, 0xba, 0xa2, 0x92, 0xba, 0x17, 0x62, 0x4d, 0xae, 0x2d, 0xc2, 0x14, 0xa9, 0x92, 0xcb, 0xb6,
0xea, 0xbd, 0x5f, 0xa9, 0x5f, 0xa0, 0x97, 0x1e, 0x7a, 0xed, 0xa1, 0xb7, 0x9e, 0xfa, 0x09, 0xfa,
0x01, 0x8a, 0xdd, 0xe5, 0x3f, 0xa9, 0xb1, 0x53, 0xe4, 0xc4, 0x9d, 0xe1, 0xcc, 0xec, 0xcc, 0x6f,
0x66, 0x67, 0x06, 0x90, 0x33, 0x27, 0xcc, 0x5e, 0xd0, 0x38, 0x26, 0x97, 0xf4, 0x70, 0x19, 0x85,
0x2c, 0x44, 0x2d, 0xf1, 0x39, 0x4f, 0x2e, 0xee, 0xb7, 0x69, 0x90, 0x2c, 0x62, 0xc9, 0xbe, 0xdf,
0x75, 0xc2, 0x80, 0x11, 0x87, 0x49, 0xb2, 0xff, 0x02, 0x7a, 0x53, 0xe6, 0x39, 0x57, 0x34, 0x1a,
0x49, 0x6d, 0x84, 0xa0, 0x36, 0x27, 0xf1, 0x5c, 0xab, 0xec, 0x57, 0x0e, 0x14, 0x2c, 0xce, 0x9c,
0xb7, 0x24, 0xce, 0x95, 0x56, 0xdd, 0xaf, 0x1c, 0xd4, 0xb1, 0x38, 0xf7, 0x7f, 0xae, 0x40, 0xc7,
0x5c, 0x90, 0x4b, 0x9a, 0x29, 0x6a, 0xd0, 0x5c, 0x92, 0x95, 0x1f, 0x12, 0x57, 0xe8, 0x76, 0x70,
0x46, 0xa2, 0xa7, 0x50, 0x63, 0xab, 0x25, 0x15, 0xea, 0xbd, 0x67, 0xb7, 0x0e, 0x33, 0xcf, 0x0e,
0x85, 0xbe, 0xb5, 0x5a, 0x52, 0x2c, 0x04, 0xd0, 0x3d, 0x68, 0x11, 0xff, 0x3c, 0x59, 0xd8, 0x9e,
0xab, 0x6d, 0x8b, 0xfb, 0x9b, 0x82, 0x36, 0x5d, 0xb4, 0x0b, 0xf5, 0x6f, 0x3d, 0x97, 0xcd, 0xb5,
0xda, 0x7e, 0xe5, 0xa0, 0x8b, 0x25, 0x81, 0xee, 0x40, 0x63, 0x4e, 0xbd, 0xcb, 0x39, 0xd3, 0xea,
0x82, 0x9d, 0x52, 0xe8, 0xbf, 0x80, 0x52, 0x43, 0xfc, 0x86, 0xd8, 0x76, 0xc2, 0x24, 0x60, 0x5a,
0x43, 0xc8, 0xa8, 0xd2, 0xa4, 0xf8, 0x31, 0xe0, 0xfc, 0xfe, 0x8f, 0x15, 0xe8, 0xe8, 0x89, 0xeb,
0x85, 0xef, 0x0f, 0xe5, 0xf9, 0x5a, 0x28, 0xfb, 0x45, 0x28, 0x65, 0x7d, 0x49, 0x94, 0xe2, 0x7a,
0x04, 0x6d, 0x37, 0x89, 0x08, 0xf3, 0xc2, 0xc0, 0x5e, 0xc4, 0x22, 0xb4, 0x1a, 0x86, 0x8c, 0x35,
0x8a, 0xfb, 0x1f, 0x81, 0x92, 0xeb, 0xa0, 0x3b, 0x80, 0x66, 0xe3, 0x57, 0xe3, 0xd3, 0xb7, 0x63,
0x5b, 0x9f, 0x0d, 0xcd, 0x53, 0xdb, 0x3a, 0x9b, 0x18, 0xea, 0x16, 0x6a, 0xc2, 0xb6, 0xae, 0x0f,
0xd4, 0x8a, 0x38, 0x8c, 0xb0, 0x5a, 0xed, 0xff, 0x50, 0x85, 0xb6, 0xe1, 0x7a, 0x2c, 0xf3, 0x7b,
0x17, 0xea, 0x8e, 0x1f, 0x3a, 0x57, 0xc2, 0xeb, 0x1a, 0x96, 0x04, 0xcf, 0x1e, 0xa3, 0xdf, 0x31,
0xe1, 0xb3, 0x82, 0xc5, 0x19, 0xdd, 0x85, 0xa6, 0xa8, 0x99, 0x1c, 0xe8, 0x06, 0x27, 0x4d, 0x17,
0xed, 0x01, 0xa4, 0x75, 0xc4, 0xff, 0xd5, 0xc4, 0x3f, 0x25, 0xe5, 0xc8, 0x34, 0x5c, 0x46, 0x24,
0x90, 0x78, 0x77, 0xb0, 0x24, 0xd0, 0x0b, 0xe8, 0x64, 0x4a, 0x02, 0x9d, 0x86, 0x40, 0xe7, 0x76,
0x81, 0x4e, 0xea, 0xa0, 0x80, 0xa4, 0xbd, 0x28, 0x08, 0x34, 0x84, 0x0e, 0x2f, 0x48, 0x1a, 0x30,
0xa9, 0xd9, 0x14, 0x9a, 0x8f, 0x0b, 0xcd, 0xc1, 0x9c, 0x64, 0xe1, 0x1d, 0x0e, 0xa4, 0xa4, 0xb4,
0xe2, 0x14, 0x44, 0xff, 0x97, 0x0a, 0x74, 0x87, 0xd4, 0xa7, 0x8c, 0xde, 0x8c, 0x44, 0x29, 0xea,
0xea, 0x0d, 0x51, 0x6f, 0x5f, 0x1b, 0x75, 0xed, 0xa6, 0xa8, 0xeb, 0xff, 0x38, 0xea, 0x3d, 0x00,
0x57, 0xb8, 0xeb, 0xda, 0xe7, 0x2b, 0x81, 0x96, 0x82, 0x95, 0x94, 0x73, 0xb4, 0xea, 0x9b, 0x80,
0x64, 0x34, 0x2f, 0xc3, 0x68, 0xf4, 0x9e, 0x90, 0xd6, 0x3d, 0xaf, 0x6e, 0x78, 0xde, 0xff, 0xb5,
0x0a, 0xbd, 0xa1, 0x17, 0x3b, 0x61, 0xe4, 0x66, 0x76, 0x7a, 0x50, 0xf5, 0xdc, 0xf4, 0x79, 0x57,
0x3d, 0x57, 0x94, 0x47, 0x56, 0xd2, 0x4a, 0x5a, 0xb0, 0x0f, 0x40, 0x61, 0xde, 0x82, 0xc6, 0x8c,
0x2c, 0x96, 0x19, 0x1c, 0x39, 0x03, 0x1d, 0xc0, 0x4e, 0x4e, 0xf0, 0xf2, 0xa3, 0x59, 0xa1, 0x6c,
0xb2, 0xf9, 0x43, 0x4a, 0xf3, 0x24, 0xd0, 0x51, 0x70, 0x46, 0xa2, 0x8f, 0xa1, 0x41, 0x12, 0x36,
0x0f, 0x23, 0x11, 0x7e, 0xfb, 0xd9, 0xc3, 0x02, 0xb6, 0x75, 0x7f, 0x75, 0x21, 0x85, 0x53, 0x69,
0xf4, 0x19, 0x28, 0x11, 0xbd, 0xa0, 0x11, 0x0d, 0x1c, 0x59, 0x2d, 0xed, 0x72, 0xb5, 0xac, 0xab,
0xe2, 0x4c, 0x10, 0x17, 0x3a, 0x68, 0x08, 0x6d, 0xc2, 0x18, 0x71, 0xe6, 0x0b, 0x1a, 0xb0, 0x58,
0x6b, 0xed, 0x6f, 0x1f, 0xb4, 0x9f, 0xf5, 0xaf, 0xbd, 0x3d, 0x17, 0xc5, 0x65, 0xb5, 0xfe, 0x1f,
0x15, 0xd8, 0x7d, 0x97, 0x9f, 0xef, 0x42, 0x37, 0x20, 0x8b, 0x1c, 0x5d, 0x7e, 0x46, 0x4f, 0xa0,
0xeb, 0x7a, 0xb1, 0x13, 0x79, 0x0b, 0x2f, 0x20, 0x2c, 0x8c, 0x52, 0x84, 0xd7, 0x99, 0xe8, 0x3e,
0xb4, 0x02, 0xcf, 0xb9, 0x12, 0xda, 0x12, 0xde, 0x9c, 0xe6, 0xf9, 0x21, 0xdf, 0x10, 0x46, 0xa2,
0x59, 0xe4, 0xa7, 0xc8, 0x16, 0x0c, 0x74, 0x08, 0x48, 0x12, 0xa2, 0xc9, 0x4d, 0xd2, 0x4e, 0xd6,
0x10, 0xb5, 0xfb, 0x8e, 0x3f, 0xfc, 0x26, 0x3f, 0x74, 0x88, 0xcf, 0x8d, 0x35, 0xe5, 0x4d, 0x19,
0xdd, 0x0f, 0xe1, 0xee, 0x35, 0xa0, 0x72, 0x27, 0xf2, 0x42, 0x4b, 0x23, 0x2e, 0xbd, 0x99, 0x07,
0xa0, 0x38, 0x73, 0x12, 0x04, 0xd4, 0x37, 0xf3, 0xba, 0xcc, 0x19, 0xbc, 0x30, 0x2e, 0x13, 0xcf,
0x77, 0xcd, 0xbc, 0xd1, 0xa7, 0x64, 0xff, 0xcf, 0x0a, 0x68, 0xd7, 0xe5, 0xe0, 0x6f, 0xe8, 0xae,
0xb9, 0xb0, 0x59, 0xfc, 0x48, 0x85, 0xed, 0x24, 0xf2, 0xd3, 0x0b, 0xf8, 0x91, 0x47, 0x7a, 0xe1,
0xf9, 0x74, 0x5c, 0xc2, 0x34, 0xa3, 0x79, 0x56, 0xf8, 0x79, 0xea, 0x7d, 0x4f, 0x8f, 0x56, 0x8c,
0xc6, 0x02, 0xd7, 0x1a, 0x5e, 0x67, 0xa2, 0x7d, 0x28, 0x77, 0x9e, 0xf4, 0xed, 0x96, 0x59, 0xe5,
0xe1, 0xd1, 0x5c, 0x1f, 0x1e, 0x65, 0x9c, 0x5b, 0x1b, 0x38, 0xff, 0x56, 0x81, 0xce, 0x2c, 0xb8,
0x48, 0x22, 0x9f, 0xba, 0xaf, 0xbd, 0xe0, 0x2a, 0x73, 0xbe, 0x52, 0x38, 0xbf, 0x0b, 0x75, 0xe6,
0x31, 0x3f, 0xab, 0x25, 0x49, 0x70, 0x87, 0x5c, 0xca, 0xeb, 0x66, 0xc9, 0x67, 0x49, 0x1a, 0x6c,
0x99, 0x85, 0xfe, 0x03, 0xff, 0x62, 0xf3, 0x64, 0x71, 0x1e, 0x10, 0xcf, 0xb7, 0x33, 0xd7, 0x64,
0x27, 0x53, 0xf3, 0x1f, 0x93, 0x7c, 0x56, 0xef, 0x14, 0xc2, 0x72, 0xe2, 0xca, 0xd1, 0xda, 0xcb,
0xd9, 0x6f, 0xc5, 0xe8, 0xfd, 0x37, 0x14, 0xca, 0x76, 0x3a, 0x84, 0xe5, 0x80, 0x2d, 0x0c, 0x9c,
0x08, 0x76, 0xff, 0xf7, 0x16, 0xb4, 0x4b, 0x7d, 0xfc, 0x9a, 0x4e, 0xb6, 0xd6, 0x73, 0xaa, 0xe2,
0x4f, 0xa9, 0xe7, 0x64, 0x43, 0x6c, 0xbb, 0x34, 0xc4, 0x1e, 0x41, 0x3b, 0xa2, 0xf1, 0x32, 0x0c,
0x62, 0x6a, 0xb3, 0x30, 0x4d, 0x28, 0x64, 0x2c, 0x2b, 0xe4, 0xfb, 0x04, 0x0d, 0x62, 0x5b, 0x3c,
0xa1, 0xb4, 0xff, 0xd0, 0x20, 0x16, 0xd9, 0x2e, 0x8d, 0x82, 0xc6, 0xda, 0x28, 0xd8, 0xec, 0xea,
0xcd, 0x0f, 0x9e, 0x65, 0xad, 0x0f, 0x99, 0x65, 0xe8, 0x39, 0x34, 0x63, 0xb9, 0x91, 0x69, 0x8a,
0x68, 0x6f, 0x5a, 0x61, 0x60, 0x7d, 0x55, 0x3b, 0xd9, 0xc2, 0x99, 0x28, 0x3a, 0x84, 0xba, 0x58,
0x75, 0x34, 0x10, 0x3a, 0x77, 0x36, 0x76, 0xac, 0x42, 0x43, 0x8a, 0x71, 0x79, 0xc2, 0x17, 0x0e,
0xad, 0xbd, 0x29, 0x5f, 0x5e, 0x64, 0xb8, 0xbc, 0x10, 0x43, 0x0f, 0x41, 0x71, 0xc2, 0xc5, 0x22,
0x09, 0x3c, 0xb6, 0xd2, 0x3a, 0xbc, 0x76, 0x4e, 0xb6, 0x70, 0xc1, 0x42, 0x03, 0xd8, 0x71, 0xe5,
0xa3, 0xcd, 0xd6, 0x50, 0xcd, 0xd9, 0xf4, 0x7e, 0xfd, 0x55, 0x9f, 0x6c, 0xe1, 0x9e, 0xbb, 0x3e,
0x99, 0xf2, 0x31, 0xdb, 0x2d, 0x8f, 0xd9, 0xc7, 0xd0, 0x71, 0xbd, 0x78, 0xe9, 0x93, 0x95, 0x4c,
0x64, 0x2f, 0xad, 0x70, 0xc9, 0x13, 0xc9, 0x5c, 0xc2, 0x7e, 0xba, 0xd6, 0xda, 0x11, 0xfd, 0x3a,
0xa1, 0x31, 0xb3, 0x97, 0x51, 0xb8, 0x24, 0x97, 0x84, 0x8f, 0xd8, 0x98, 0x11, 0x46, 0xb5, 0x1d,
0xe1, 0xce, 0xd3, 0x52, 0x36, 0xa4, 0x06, 0x96, 0x0a, 0x93, 0x5c, 0x7e, 0xca, 0xc5, 0xf1, 0x9e,
0x73, 0xd3, 0x6f, 0xf4, 0x29, 0xf4, 0x92, 0xf4, 0xb5, 0xda, 0xbe, 0x17, 0x5c, 0xc5, 0x9a, 0x2a,
0x06, 0x49, 0x09, 0xc8, 0xf2, 0x6b, 0xc6, 0xdd, 0xa4, 0x44, 0xc5, 0xfd, 0x9f, 0xaa, 0xd0, 0x1e,
0xac, 0xf5, 0x8c, 0xdd, 0x6c, 0xe5, 0x1b, 0x9c, 0x8e, 0x2d, 0x63, 0x6c, 0x65, 0x4b, 0x5f, 0x0f,
0xc0, 0x32, 0xbe, 0xb4, 0xec, 0xc9, 0x6b, 0xdd, 0x1c, 0xab, 0x15, 0xd4, 0x86, 0xe6, 0xd4, 0x32,
0x07, 0xaf, 0x0c, 0xac, 0x56, 0x11, 0x40, 0x63, 0x6a, 0xe9, 0xd6, 0x6c, 0xaa, 0x6e, 0x23, 0x05,
0xea, 0xc6, 0xe8, 0xf4, 0x73, 0x53, 0xad, 0xa1, 0xbb, 0x70, 0xcb, 0xc2, 0xfa, 0x78, 0xaa, 0x0f,
0x2c, 0xf3, 0x94, 0x5b, 0x1c, 0x8d, 0xf4, 0xf1, 0x50, 0xad, 0xa3, 0x03, 0x78, 0x32, 0x3d, 0x9b,
0x5a, 0xc6, 0xc8, 0x1e, 0x19, 0xd3, 0xa9, 0x7e, 0x6c, 0xe4, 0xb7, 0x4d, 0xb0, 0xf9, 0x46, 0xb7,
0x0c, 0xfb, 0x18, 0x9f, 0xce, 0x26, 0x6a, 0x83, 0x5b, 0x33, 0x47, 0xfa, 0xb1, 0xa1, 0x36, 0xf9,
0x51, 0xac, 0xa1, 0x6a, 0x0b, 0x75, 0x41, 0xe1, 0xc6, 0x66, 0x63, 0xd3, 0x3a, 0x53, 0x15, 0xbe,
0xa8, 0x6e, 0x98, 0x3b, 0xd6, 0x27, 0x2a, 0xa0, 0x5b, 0xb0, 0xc3, 0xed, 0xea, 0x03, 0xcb, 0xc6,
0xc6, 0x17, 0x33, 0x63, 0x6a, 0xa9, 0x6d, 0xce, 0x1c, 0x9a, 0xd3, 0xc1, 0x29, 0x1e, 0x66, 0xd2,
0x6a, 0x07, 0xdd, 0x83, 0xdb, 0xe6, 0xd0, 0x18, 0x5b, 0xa6, 0x75, 0x66, 0xbf, 0x31, 0xb0, 0xf9,
0xd2, 0x1c, 0xe8, 0xdc, 0x67, 0xb5, 0x8b, 0x1e, 0xc3, 0xde, 0x86, 0xf1, 0x89, 0x39, 0x1e, 0x1b,
0x85, 0x76, 0xef, 0x48, 0xc9, 0x3b, 0xed, 0x51, 0xf7, 0xab, 0xf6, 0xe1, 0xff, 0x3e, 0xc9, 0xb0,
0x3f, 0x6f, 0x88, 0xd3, 0xff, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x91, 0x0d, 0xf3, 0x6d, 0x19,
0x0d, 0x00, 0x00,
// 1430 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x5f, 0x73, 0xdb, 0x44,
0x10, 0x8f, 0x1d, 0xff, 0x89, 0x56, 0xb6, 0x23, 0xae, 0x69, 0xab, 0x76, 0x9a, 0x36, 0xd5, 0x74,
0x68, 0x18, 0x98, 0x30, 0x53, 0x0a, 0xd3, 0x19, 0x86, 0x61, 0x14, 0x5b, 0x4d, 0x44, 0x6b, 0xc7,
0x9c, 0xe5, 0x96, 0xf0, 0xa2, 0x51, 0xa4, 0x4b, 0xac, 0x89, 0x2c, 0x19, 0xe9, 0x04, 0x98, 0x77,
0x66, 0xf8, 0x44, 0x7c, 0x06, 0x1e, 0x78, 0xe5, 0x81, 0x2f, 0xc0, 0x0b, 0xaf, 0x7c, 0x00, 0xe6,
0xee, 0xf4, 0xcf, 0xa6, 0x49, 0x99, 0x3e, 0xe9, 0x76, 0xb5, 0xbb, 0xb7, 0xfb, 0xdb, 0xbd, 0xdd,
0x05, 0xe4, 0xce, 0x1c, 0x6a, 0xcf, 0x49, 0x92, 0x38, 0x17, 0xe4, 0x60, 0x11, 0x47, 0x34, 0x42,
0x5b, 0xfc, 0x73, 0x96, 0x9e, 0xdf, 0x95, 0x49, 0x98, 0xce, 0x13, 0xc1, 0xbe, 0xdb, 0x75, 0xa3,
0x90, 0x3a, 0x2e, 0x15, 0xa4, 0xf6, 0x0c, 0x7a, 0x13, 0xea, 0xbb, 0x97, 0x24, 0x1e, 0x0a, 0x6d,
0x84, 0xa0, 0x31, 0x73, 0x92, 0x99, 0x5a, 0xdb, 0xab, 0xed, 0x4b, 0x98, 0x9f, 0x19, 0x6f, 0xe1,
0xb8, 0x97, 0x6a, 0x7d, 0xaf, 0xb6, 0xdf, 0xc4, 0xfc, 0xac, 0xfd, 0x56, 0x83, 0x8e, 0x39, 0x77,
0x2e, 0x48, 0xae, 0xa8, 0x42, 0x7b, 0xe1, 0x2c, 0x83, 0xc8, 0xf1, 0xb8, 0x6e, 0x07, 0xe7, 0x24,
0x7a, 0x0c, 0x0d, 0xba, 0x5c, 0x10, 0xae, 0xde, 0x7b, 0x72, 0xe3, 0x20, 0xf7, 0xec, 0x80, 0xeb,
0x5b, 0xcb, 0x05, 0xc1, 0x5c, 0x00, 0xdd, 0x81, 0x2d, 0x27, 0x38, 0x4b, 0xe7, 0xb6, 0xef, 0xa9,
0x9b, 0xfc, 0xfe, 0x36, 0xa7, 0x4d, 0x0f, 0xed, 0x40, 0xf3, 0x07, 0xdf, 0xa3, 0x33, 0xb5, 0xb1,
0x57, 0xdb, 0xef, 0x62, 0x41, 0xa0, 0x5b, 0xd0, 0x9a, 0x11, 0xff, 0x62, 0x46, 0xd5, 0x26, 0x67,
0x67, 0x14, 0xfa, 0x08, 0x50, 0x66, 0x88, 0xdd, 0x90, 0xd8, 0x6e, 0x94, 0x86, 0x54, 0x6d, 0x71,
0x19, 0x45, 0x98, 0xe4, 0x3f, 0xfa, 0x8c, 0xaf, 0xfd, 0x5a, 0x83, 0x8e, 0x9e, 0x7a, 0x7e, 0xf4,
0xf6, 0x50, 0x9e, 0xae, 0x84, 0xb2, 0x57, 0x86, 0x52, 0xd5, 0x17, 0x44, 0x25, 0xae, 0x07, 0x20,
0x7b, 0x69, 0xec, 0x50, 0x3f, 0x0a, 0xed, 0x79, 0xc2, 0x43, 0x6b, 0x60, 0xc8, 0x59, 0xc3, 0x44,
0xfb, 0x14, 0xa4, 0x42, 0x07, 0xdd, 0x02, 0x34, 0x1d, 0xbd, 0x18, 0x9d, 0xbc, 0x1e, 0xd9, 0xfa,
0x74, 0x60, 0x9e, 0xd8, 0xd6, 0xe9, 0xd8, 0x50, 0x36, 0x50, 0x1b, 0x36, 0x75, 0xbd, 0xaf, 0xd4,
0xf8, 0x61, 0x88, 0x95, 0xba, 0xf6, 0x73, 0x1d, 0x64, 0xc3, 0xf3, 0x69, 0xee, 0xf7, 0x0e, 0x34,
0xdd, 0x20, 0x72, 0x2f, 0xb9, 0xd7, 0x0d, 0x2c, 0x08, 0x96, 0x3d, 0x4a, 0x7e, 0xa4, 0xdc, 0x67,
0x09, 0xf3, 0x33, 0xba, 0x0d, 0x6d, 0x5e, 0x33, 0x05, 0xd0, 0x2d, 0x46, 0x9a, 0x1e, 0xda, 0x05,
0xc8, 0xea, 0x88, 0xfd, 0x6b, 0xf0, 0x7f, 0x52, 0xc6, 0x11, 0x69, 0xb8, 0x88, 0x9d, 0x50, 0xe0,
0xdd, 0xc1, 0x82, 0x40, 0xcf, 0xa0, 0x93, 0x2b, 0x71, 0x74, 0x5a, 0x1c, 0x9d, 0x9b, 0x25, 0x3a,
0x99, 0x83, 0x1c, 0x12, 0x79, 0x5e, 0x12, 0x68, 0x00, 0x1d, 0x56, 0x90, 0x24, 0xa4, 0x42, 0xb3,
0xcd, 0x35, 0x1f, 0x96, 0x9a, 0xfd, 0x99, 0x93, 0x87, 0x77, 0xd0, 0x17, 0x92, 0xc2, 0x8a, 0x5b,
0x12, 0xda, 0xef, 0x35, 0xe8, 0x0e, 0x48, 0x40, 0x28, 0xb9, 0x1e, 0x89, 0x4a, 0xd4, 0xf5, 0x6b,
0xa2, 0xde, 0xbc, 0x32, 0xea, 0xc6, 0x75, 0x51, 0x37, 0xff, 0x77, 0xd4, 0xbb, 0x00, 0x1e, 0x77,
0xd7, 0xb3, 0xcf, 0x96, 0x1c, 0x2d, 0x09, 0x4b, 0x19, 0xe7, 0x70, 0xa9, 0x99, 0x80, 0x44, 0x34,
0xcf, 0xa3, 0x78, 0xf8, 0x96, 0x90, 0x56, 0x3d, 0xaf, 0xaf, 0x79, 0xae, 0xfd, 0x51, 0x87, 0xde,
0xc0, 0x4f, 0xdc, 0x28, 0xf6, 0x72, 0x3b, 0x3d, 0xa8, 0xfb, 0x5e, 0xf6, 0xbc, 0xeb, 0xbe, 0xc7,
0xcb, 0x23, 0x2f, 0x69, 0x29, 0x2b, 0xd8, 0x7b, 0x20, 0x51, 0x7f, 0x4e, 0x12, 0xea, 0xcc, 0x17,
0x39, 0x1c, 0x05, 0x03, 0xed, 0xc3, 0x76, 0x41, 0xb0, 0xf2, 0x23, 0x79, 0xa1, 0xac, 0xb3, 0xd9,
0x43, 0xca, 0xf2, 0xc4, 0xd1, 0x91, 0x70, 0x4e, 0xa2, 0xcf, 0xa0, 0xe5, 0xa4, 0x74, 0x16, 0xc5,
0x3c, 0x7c, 0xf9, 0xc9, 0xfd, 0x12, 0xb6, 0x55, 0x7f, 0x75, 0x2e, 0x85, 0x33, 0x69, 0xf4, 0x25,
0x48, 0x31, 0x39, 0x27, 0x31, 0x09, 0x5d, 0x51, 0x2d, 0x72, 0xb5, 0x5a, 0x56, 0x55, 0x71, 0x2e,
0x88, 0x4b, 0x1d, 0x34, 0x00, 0xd9, 0xa1, 0xd4, 0x71, 0x67, 0x73, 0x12, 0xd2, 0x44, 0xdd, 0xda,
0xdb, 0xdc, 0x97, 0x9f, 0x68, 0x57, 0xde, 0x5e, 0x88, 0xe2, 0xaa, 0x9a, 0xf6, 0x57, 0x0d, 0x76,
0xde, 0xe4, 0xe7, 0x9b, 0xd0, 0x0d, 0x9d, 0x79, 0x81, 0x2e, 0x3b, 0xa3, 0x47, 0xd0, 0xf5, 0xfc,
0xc4, 0x8d, 0xfd, 0xb9, 0x1f, 0x3a, 0x34, 0x8a, 0x33, 0x84, 0x57, 0x99, 0xe8, 0x2e, 0x6c, 0x85,
0xbe, 0x7b, 0xc9, 0xb5, 0x05, 0xbc, 0x05, 0xcd, 0xf2, 0xe3, 0x7c, 0xef, 0x50, 0x27, 0x9e, 0xc6,
0x41, 0x86, 0x6c, 0xc9, 0x40, 0x07, 0x80, 0x04, 0xc1, 0x9b, 0xdc, 0x38, 0xeb, 0x64, 0x2d, 0x5e,
0xbb, 0x6f, 0xf8, 0xc3, 0x6e, 0x0a, 0x22, 0xd7, 0x09, 0x98, 0xb1, 0xb6, 0xb8, 0x29, 0xa7, 0xb5,
0x08, 0x6e, 0x5f, 0x01, 0x2a, 0x73, 0xa2, 0x28, 0xb4, 0x2c, 0xe2, 0xca, 0x9b, 0xb9, 0x07, 0x92,
0x3b, 0x73, 0xc2, 0x90, 0x04, 0x66, 0x51, 0x97, 0x05, 0x83, 0x15, 0xc6, 0x45, 0xea, 0x07, 0x9e,
0x59, 0x34, 0xfa, 0x8c, 0xd4, 0xfe, 0xa9, 0x81, 0x7a, 0x55, 0x0e, 0xfe, 0x83, 0xee, 0x8a, 0x0b,
0xeb, 0xc5, 0x8f, 0x14, 0xd8, 0x4c, 0xe3, 0x20, 0xbb, 0x80, 0x1d, 0x59, 0xa4, 0xe7, 0x7e, 0x40,
0x46, 0x15, 0x4c, 0x73, 0x9a, 0x65, 0x85, 0x9d, 0x27, 0xfe, 0x4f, 0xe4, 0x70, 0x49, 0x49, 0xc2,
0x71, 0x6d, 0xe0, 0x55, 0x26, 0xda, 0x83, 0x6a, 0xe7, 0xc9, 0xde, 0x6e, 0x95, 0x55, 0x1d, 0x1e,
0xed, 0xd5, 0xe1, 0x51, 0xc5, 0x79, 0x6b, 0x0d, 0xe7, 0x3f, 0x6b, 0xd0, 0x99, 0x86, 0xe7, 0x69,
0x1c, 0x10, 0xef, 0xa5, 0x1f, 0x5e, 0xe6, 0xce, 0xd7, 0x4a, 0xe7, 0x77, 0xa0, 0x49, 0x7d, 0x1a,
0xe4, 0xb5, 0x24, 0x08, 0xe6, 0x90, 0x47, 0x58, 0xdd, 0x2c, 0xd8, 0x2c, 0xc9, 0x82, 0xad, 0xb2,
0xd0, 0x87, 0xf0, 0x1e, 0x9d, 0xa5, 0xf3, 0xb3, 0xd0, 0xf1, 0x03, 0x3b, 0x77, 0x4d, 0x74, 0x32,
0xa5, 0xf8, 0x31, 0x2e, 0x66, 0xf5, 0x76, 0x29, 0x2c, 0x26, 0xae, 0x18, 0xad, 0xbd, 0x82, 0xfd,
0x9a, 0x8f, 0xde, 0x0f, 0xa0, 0x54, 0xb6, 0xb3, 0x21, 0x2c, 0x06, 0x6c, 0x69, 0xe0, 0x98, 0xb3,
0xb5, 0x5f, 0x24, 0x90, 0x2b, 0x7d, 0xfc, 0x8a, 0x4e, 0xb6, 0xd2, 0x73, 0xea, 0xfc, 0x4f, 0xa5,
0xe7, 0xe4, 0x43, 0x6c, 0xb3, 0x32, 0xc4, 0x1e, 0x80, 0x1c, 0x93, 0x64, 0x11, 0x85, 0x09, 0xb1,
0x69, 0x94, 0x25, 0x14, 0x72, 0x96, 0x15, 0xb1, 0x7d, 0x82, 0x84, 0x89, 0xcd, 0x9f, 0x50, 0xd6,
0x7f, 0x48, 0x98, 0xf0, 0x6c, 0x57, 0x46, 0x41, 0x6b, 0x65, 0x14, 0xac, 0x77, 0xf5, 0xf6, 0x3b,
0xcf, 0xb2, 0xad, 0x77, 0x99, 0x65, 0xe8, 0x29, 0xb4, 0x13, 0xb1, 0x91, 0xa9, 0x12, 0x6f, 0x6f,
0x6a, 0x69, 0x60, 0x75, 0x55, 0x3b, 0xde, 0xc0, 0xb9, 0x28, 0x3a, 0x80, 0x26, 0x5f, 0x75, 0x54,
0xe0, 0x3a, 0xb7, 0xd6, 0x76, 0xac, 0x52, 0x43, 0x88, 0x31, 0x79, 0x87, 0x2d, 0x1c, 0xaa, 0xbc,
0x2e, 0x5f, 0x5d, 0x64, 0x98, 0x3c, 0x17, 0x43, 0xf7, 0x41, 0x72, 0xa3, 0xf9, 0x3c, 0x0d, 0x7d,
0xba, 0x54, 0x3b, 0xac, 0x76, 0x8e, 0x37, 0x70, 0xc9, 0x42, 0x7d, 0xd8, 0xf6, 0xc4, 0xa3, 0xcd,
0xd7, 0x50, 0xd5, 0x5d, 0xf7, 0x7e, 0xf5, 0x55, 0x1f, 0x6f, 0xe0, 0x9e, 0xb7, 0x3a, 0x99, 0x8a,
0x31, 0xdb, 0xad, 0x8e, 0xd9, 0x87, 0xd0, 0xf1, 0xfc, 0x64, 0x11, 0x38, 0x4b, 0x91, 0xc8, 0x5e,
0x56, 0xe1, 0x82, 0xc7, 0x93, 0xb9, 0x80, 0xbd, 0x6c, 0xad, 0xb5, 0x63, 0xf2, 0x5d, 0x4a, 0x12,
0x6a, 0x2f, 0xe2, 0x68, 0xe1, 0x5c, 0x38, 0x6c, 0xc4, 0x26, 0xd4, 0xa1, 0x44, 0xdd, 0xe6, 0xee,
0x3c, 0xae, 0x64, 0x43, 0x68, 0x60, 0xa1, 0x30, 0x2e, 0xe4, 0x27, 0x4c, 0x1c, 0xef, 0xba, 0xd7,
0xfd, 0x46, 0x5f, 0x40, 0x2f, 0xcd, 0x5e, 0xab, 0x1d, 0xf8, 0xe1, 0x65, 0xa2, 0x2a, 0x7c, 0x90,
0x54, 0x80, 0xac, 0xbe, 0x66, 0xdc, 0x4d, 0x2b, 0x54, 0xa2, 0xfd, 0x5d, 0x07, 0xb9, 0xbf, 0xd2,
0x33, 0x76, 0xf2, 0x95, 0xaf, 0x7f, 0x32, 0xb2, 0x8c, 0x91, 0x95, 0x2f, 0x7d, 0x3d, 0x00, 0xcb,
0xf8, 0xc6, 0xb2, 0xc7, 0x2f, 0x75, 0x73, 0xa4, 0xd4, 0x90, 0x0c, 0xed, 0x89, 0x65, 0xf6, 0x5f,
0x18, 0x58, 0xa9, 0x23, 0x80, 0xd6, 0xc4, 0xd2, 0xad, 0xe9, 0x44, 0xd9, 0x44, 0x12, 0x34, 0x8d,
0xe1, 0xc9, 0x57, 0xa6, 0xd2, 0x40, 0xb7, 0xe1, 0x86, 0x85, 0xf5, 0xd1, 0x44, 0xef, 0x5b, 0xe6,
0x09, 0xb3, 0x38, 0x1c, 0xea, 0xa3, 0x81, 0xd2, 0x44, 0xfb, 0xf0, 0x68, 0x72, 0x3a, 0xb1, 0x8c,
0xa1, 0x3d, 0x34, 0x26, 0x13, 0xfd, 0xc8, 0x28, 0x6e, 0x1b, 0x63, 0xf3, 0x95, 0x6e, 0x19, 0xf6,
0x11, 0x3e, 0x99, 0x8e, 0x95, 0x16, 0xb3, 0x66, 0x0e, 0xf5, 0x23, 0x43, 0x69, 0xb3, 0x23, 0x5f,
0x43, 0x95, 0x2d, 0xd4, 0x05, 0x89, 0x19, 0x9b, 0x8e, 0x4c, 0xeb, 0x54, 0x91, 0xd8, 0xa2, 0xba,
0x66, 0xee, 0x48, 0x1f, 0x2b, 0x80, 0x6e, 0xc0, 0x36, 0xb3, 0xab, 0xf7, 0x2d, 0x1b, 0x1b, 0x5f,
0x4f, 0x8d, 0x89, 0xa5, 0xc8, 0x8c, 0x39, 0x30, 0x27, 0xfd, 0x13, 0x3c, 0xc8, 0xa5, 0x95, 0x0e,
0xba, 0x03, 0x37, 0xcd, 0x81, 0x31, 0xb2, 0x4c, 0xeb, 0xd4, 0x7e, 0x65, 0x60, 0xf3, 0xb9, 0xd9,
0xd7, 0x99, 0xcf, 0x4a, 0x17, 0x3d, 0x84, 0xdd, 0x35, 0xe3, 0x63, 0x73, 0x34, 0x32, 0x4a, 0xed,
0x1e, 0x7a, 0x1f, 0xb4, 0x35, 0x91, 0xe1, 0xd4, 0x9a, 0xea, 0x2f, 0x6d, 0x06, 0x8a, 0x61, 0x4f,
0xc7, 0x03, 0xdd, 0x32, 0x94, 0xed, 0x43, 0xa9, 0xe8, 0xc8, 0x87, 0xdd, 0x6f, 0xe5, 0x83, 0x8f,
0x3f, 0xcf, 0x73, 0x74, 0xd6, 0xe2, 0xa7, 0x4f, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x18, 0x57,
0x25, 0x82, 0x41, 0x0d, 0x00, 0x00,
}

View File

@ -180,5 +180,6 @@ message ChatMessage {
IDENTITY_VERIFICATION = 13;
// Only local
SYSTEM_MESSAGE_PINNED_MESSAGE = 14;
SYSTEM_MESSAGE_MUTUAL_STATE_UPDATE = 15;
}
}