Skip to content
Snippets Groups Projects
Commit 9ef7d0cc authored by Jono Wenger's avatar Jono Wenger
Browse files

Merge branch 'XX-4702/pingReply' into 'project/HavenBeta'

XX-4702 / Differentiate ping and reply

See merge request !665
parents 87202292 0b4a85a6
Branches
Tags
2 merge requests!665XX-4702 / Differentiate ping and reply,!617Project/haven beta
...@@ -1158,22 +1158,26 @@ func ValidForever() int { ...@@ -1158,22 +1158,26 @@ func ValidForever() int {
// to the user should be tracked while all actions should not be. // to the user should be tracked while all actions should not be.
// - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, // - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty,
// and [GetDefaultCMixParams] will be used internally. // and [GetDefaultCMixParams] will be used internally.
// - pingsJSON - JSON of a slice of public keys of users that should receive // - pingsMapJSON - JSON of a map of slices of [ed25519.PublicKey] of users
// mobile notifications for the message. // that should receive mobile notifications for the message. Each slice keys
// // on a [channels.PingType] that describes the type of notification it is.
// Example pingsJSON:
// //
// [ // Example pingsMapJSON:
// "FgJMvgSsY4rrKkS/jSe+vFOJOs5qSSyOUSW7UtF9/KU=", // {
// "fPqcHtrJ398PAC35QyWXEU9PHzz8Z4BKQTCxSvpSygw=", // "usrMention": [
// "JnjCgh7g/+hNiI9VPKW01aRSxGOFmNulNCymy3ImXAo=" // "CLdKxbe8D2WVOpx1mT63TZ5CP/nesmxHLT5DUUalpe0=",
// "S2c6NXjNqgR11SCOaiQUughWaLpWBKNufPt6cbTVHMA="
// ],
// "usrReply": [
// "aaMzSeA6Cu2Aix2MlOwzrAI+NnpKshzvZRT02PZPVec="
// ] // ]
// }
// //
// Returns: // Returns:
// - []byte - JSON of [ChannelSendReport]. // - []byte - JSON of [ChannelSendReport].
func (cm *ChannelsManager) SendGeneric(channelIdBytes []byte, messageType int, func (cm *ChannelsManager) SendGeneric(channelIdBytes []byte, messageType int,
message []byte, validUntilMS int64, tracked bool, cmixParamsJSON []byte, message []byte, validUntilMS int64, tracked bool, cmixParamsJSON []byte,
pingsJSON []byte) ([]byte, error) { pingsMapJSON []byte) ([]byte, error) {
// Unmarshal channel ID and parameters // Unmarshal channel ID and parameters
channelID, params, err := channelID, params, err :=
...@@ -1190,14 +1194,14 @@ func (cm *ChannelsManager) SendGeneric(channelIdBytes []byte, messageType int, ...@@ -1190,14 +1194,14 @@ func (cm *ChannelsManager) SendGeneric(channelIdBytes []byte, messageType int,
lease = channels.ValidForever lease = channels.ValidForever
} }
pings, err := unmarshalPingsJson(pingsJSON) pingsMap, err := unmarshalPingsMapJson(pingsMapJSON)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Send message // Send message
messageID, rnd, ephID, err := cm.api.SendGeneric( messageID, rnd, ephID, err := cm.api.SendGeneric(
channelID, msgType, message, lease, tracked, params.CMIX, pings) channelID, msgType, message, lease, tracked, params.CMIX, pingsMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -2026,8 +2030,21 @@ func (cm *ChannelsManager) SetMobileNotificationsLevel( ...@@ -2026,8 +2030,21 @@ func (cm *ChannelsManager) SetMobileNotificationsLevel(
// Example return: // Example return:
// //
// [ // [
// {"channel":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD","type":1}, // {
// {"channel":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD","type":2} // "channel": "jOgZopfYj4zrE/AHtKmkf+QEWnfUKv9KfIy/+Bsg0PkD",
// "type": 1,
// "pingType": "usrMention"
// },
// {
// "channel": "GKmfN/LKXQYM6++TC6DeZYqoxvSUPkh5UAHWODqh9zkD",
// "type": 2,
// "pingType": "usrReply"
// },
// {
// "channel": "M+28xtj0coHrhDHfojGNcyb2c4maO7ZuheB6egS0Pc4D",
// "type": 1,
// "pingType": ""
// }
// ] // ]
func GetChannelNotificationReportsForMe(notificationFilterJSON []byte, func GetChannelNotificationReportsForMe(notificationFilterJSON []byte,
notificationDataCSV string) ([]byte, error) { notificationDataCSV string) ([]byte, error) {
...@@ -3410,3 +3427,11 @@ func unmarshalPingsJson(b []byte) ([]ed25519.PublicKey, error) { ...@@ -3410,3 +3427,11 @@ func unmarshalPingsJson(b []byte) ([]ed25519.PublicKey, error) {
} }
return pings, nil return pings, nil
} }
func unmarshalPingsMapJson(b []byte) (map[channels.PingType][]ed25519.PublicKey, error) {
var pingsMap map[channels.PingType][]ed25519.PublicKey
if b != nil && len(b) > 0 {
return pingsMap, json.Unmarshal(b, &pingsMap)
}
return pingsMap, nil
}
...@@ -122,7 +122,7 @@ type Manager interface { ...@@ -122,7 +122,7 @@ type Manager interface {
// ID (i.e., always returns 0) cannot be tracked, or it will cause errors. // ID (i.e., always returns 0) cannot be tracked, or it will cause errors.
SendGeneric(channelID *id.ID, messageType MessageType, msg []byte, SendGeneric(channelID *id.ID, messageType MessageType, msg []byte,
validUntil time.Duration, tracked bool, params cmix.CMIXParams, validUntil time.Duration, tracked bool, params cmix.CMIXParams,
pings []ed25519.PublicKey) ( pingsMap map[PingType][]ed25519.PublicKey) (
message.ID, rounds.Round, ephemeral.Id, error) message.ID, rounds.Round, ephemeral.Id, error)
// SendMessage is used to send a formatted message over a channel. // SendMessage is used to send a formatted message over a channel.
......
...@@ -93,7 +93,8 @@ func (n *notifications) addChannel(channelID *id.ID) { ...@@ -93,7 +93,8 @@ func (n *notifications) addChannel(channelID *id.ID) {
err := n.nm.Set( err := n.nm.Set(
channelID, notificationGroup, NotifyNone.Marshal(), clientNotif.Mute) channelID, notificationGroup, NotifyNone.Marshal(), clientNotif.Mute)
if err != nil { if err != nil {
jww.WARN.Printf("[CH] Failed to add channel (%s) to notifications manager: %+v", channelID, err) jww.WARN.Printf("[CH] Failed to add channel (%s) to notifications "+
"manager: %+v", channelID, err)
} }
} }
...@@ -103,7 +104,8 @@ func (n *notifications) addChannel(channelID *id.ID) { ...@@ -103,7 +104,8 @@ func (n *notifications) addChannel(channelID *id.ID) {
func (n *notifications) removeChannel(channelID *id.ID) { func (n *notifications) removeChannel(channelID *id.ID) {
err := n.nm.Delete(channelID) err := n.nm.Delete(channelID)
if err != nil { if err != nil {
jww.WARN.Printf("[CH] Failed to remove channel (%s) from notifications manager: %+v", channelID, err) jww.WARN.Printf("[CH] Failed to remove channel (%s) from notifications "+
"manager: %+v", channelID, err)
} }
} }
...@@ -226,7 +228,8 @@ func (n *notifications) processesNotificationUpdates(group clientNotif.Group, ...@@ -226,7 +228,8 @@ func (n *notifications) processesNotificationUpdates(group clientNotif.Group,
changed := make([]NotificationState, 0, len(created)+len(edits)) changed := make([]NotificationState, 0, len(created)+len(edits))
nfs := make([]NotificationFilter, 0, len(group)) nfs := make([]NotificationFilter, 0, len(group))
tags := makeUserPingTags(n.pubKey) tags := makeUserPingTags(map[PingType][]ed25519.PublicKey{
ReplyPing: {n.pubKey}, MentionPing: {n.pubKey}})
for chanID, notif := range group { for chanID, notif := range group {
channelID := chanID.DeepCopy() channelID := chanID.DeepCopy()
...@@ -303,6 +306,10 @@ type NotificationReport struct { ...@@ -303,6 +306,10 @@ type NotificationReport struct {
// Type is the MessageType of the message that the notification belongs to. // Type is the MessageType of the message that the notification belongs to.
Type MessageType `json:"type"` Type MessageType `json:"type"`
// PingType describes the type of ping. If it is empty, then it is a generic
// ping.
PingType PingType `json:"pingType,omitempty"`
} }
// GetNotificationReportsForMe checks the notification data against the filter // GetNotificationReportsForMe checks the notification data against the filter
...@@ -331,10 +338,12 @@ func GetNotificationReportsForMe(nfs []NotificationFilter, ...@@ -331,10 +338,12 @@ func GetNotificationReportsForMe(nfs []NotificationFilter,
if found { if found {
messageType := UnmarshalMessageType(b) messageType := UnmarshalMessageType(b)
if nf.match(matchedTags, messageType) { match, pt := nf.match(matchedTags, messageType)
if match {
nr = append(nr, NotificationReport{ nr = append(nr, NotificationReport{
Channel: nf.ChannelID, Channel: nf.ChannelID,
Type: messageType, Type: messageType,
PingType: pt,
}) })
} }
} }
...@@ -394,7 +403,9 @@ type AllowLists struct { ...@@ -394,7 +403,9 @@ type AllowLists struct {
// match determines if the message with the given tags and message type are // match determines if the message with the given tags and message type are
// allowed through the filter. // allowed through the filter.
func (nf NotificationFilter) match( func (nf NotificationFilter) match(
matchedTags map[string]struct{}, mt MessageType) bool { matchedTags map[string]struct{}, mt MessageType) (bool, PingType) {
pt := GenericPing
// Check if any filter tags match the matched tags // Check if any filter tags match the matched tags
for _, tag := range nf.Tags { for _, tag := range nf.Tags {
...@@ -402,18 +413,30 @@ func (nf NotificationFilter) match( ...@@ -402,18 +413,30 @@ func (nf NotificationFilter) match(
// with tags list // with tags list
if _, exists := matchedTags[tag]; exists { if _, exists := matchedTags[tag]; exists {
if _, exists = nf.AllowWithTags[mt]; exists { if _, exists = nf.AllowWithTags[mt]; exists {
return true currentPT, err := pingTypeFromTag(tag)
if err != nil {
jww.WARN.Printf(
"[CH] Failed to get ping type for tag %q: %+v", tag, err)
} }
return false
pt = getRankingPingType(pt, currentPT)
} else {
return false, ""
}
} }
} }
if pt != GenericPing {
return true, pt
}
// If no tag matches, then check if the message type is in the allowed // If no tag matches, then check if the message type is in the allowed
// without tags list // without tags list
if _, exists := nf.AllowWithoutTags[mt]; exists { if _, exists := nf.AllowWithoutTags[mt]; exists {
return true return true, pt
} }
return false return false, ""
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -438,7 +461,7 @@ var notificationLevelAllowLists = map[notificationSourceType]map[NotificationLev ...@@ -438,7 +461,7 @@ var notificationLevelAllowLists = map[notificationSourceType]map[NotificationLev
AllowWithoutTags: map[MessageType]struct{}{}, AllowWithoutTags: map[MessageType]struct{}{},
}, },
NotifyAll: { NotifyAll: {
AllowWithTags: map[MessageType]struct{}{}, AllowWithTags: map[MessageType]struct{}{Text: {}},
AllowWithoutTags: map[MessageType]struct{}{Text: {}}, AllowWithoutTags: map[MessageType]struct{}{Text: {}},
}, },
}, },
...@@ -448,7 +471,7 @@ var notificationLevelAllowLists = map[notificationSourceType]map[NotificationLev ...@@ -448,7 +471,7 @@ var notificationLevelAllowLists = map[notificationSourceType]map[NotificationLev
AllowWithoutTags: map[MessageType]struct{}{Pinned: {}}, AllowWithoutTags: map[MessageType]struct{}{Pinned: {}},
}, },
NotifyAll: { NotifyAll: {
AllowWithTags: map[MessageType]struct{}{}, AllowWithTags: map[MessageType]struct{}{AdminText: {}, Pinned: {}},
AllowWithoutTags: map[MessageType]struct{}{AdminText: {}, Pinned: {}}, AllowWithoutTags: map[MessageType]struct{}{AdminText: {}, Pinned: {}},
}, },
}, },
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"bytes" "bytes"
"crypto/ed25519" "crypto/ed25519"
"encoding/json" "encoding/json"
"fmt"
"math/rand" "math/rand"
"reflect" "reflect"
"sort" "sort"
...@@ -320,17 +321,20 @@ func Test_notifications_processesNotificationUpdates(t *testing.T) { ...@@ -320,17 +321,20 @@ func Test_notifications_processesNotificationUpdates(t *testing.T) {
}) })
if level != NotifyNone { if level != NotifyNone {
tags := makeUserPingTags(map[PingType][]ed25519.PublicKey{
ReplyPing: {n.pubKey}, MentionPing: {n.pubKey}})
sort.Strings(tags)
ex = append(ex, ex = append(ex,
NotificationFilter{ NotificationFilter{
Identifier: ch.broadcast.AsymmetricIdentifier(), Identifier: ch.broadcast.AsymmetricIdentifier(),
ChannelID: channelID, ChannelID: channelID,
Tags: makeUserPingTags(n.pubKey), Tags: tags,
AllowLists: notificationLevelAllowLists[asymmetric][level], AllowLists: notificationLevelAllowLists[asymmetric][level],
}, },
NotificationFilter{ NotificationFilter{
Identifier: ch.broadcast.SymmetricIdentifier(), Identifier: ch.broadcast.SymmetricIdentifier(),
ChannelID: channelID, ChannelID: channelID,
Tags: makeUserPingTags(n.pubKey), Tags: tags,
AllowLists: notificationLevelAllowLists[symmetric][level], AllowLists: notificationLevelAllowLists[symmetric][level],
}) })
} }
...@@ -359,6 +363,10 @@ func Test_notifications_processesNotificationUpdates(t *testing.T) { ...@@ -359,6 +363,10 @@ func Test_notifications_processesNotificationUpdates(t *testing.T) {
return bytes.Compare(nf[i].Identifier, nf[j].Identifier) == -1 return bytes.Compare(nf[i].Identifier, nf[j].Identifier) == -1
}) })
for i := range nf {
sort.Strings(nf[i].Tags)
}
if !reflect.DeepEqual(ex, nf) { if !reflect.DeepEqual(ex, nf) {
t.Errorf("Unexpected filter list."+ t.Errorf("Unexpected filter list."+
"\nexpected: %+v\nreceived: %+v", ex, nf) "\nexpected: %+v\nreceived: %+v", ex, nf)
...@@ -386,6 +394,7 @@ func TestGetNotificationReportsForMe(t *testing.T) { ...@@ -386,6 +394,7 @@ func TestGetNotificationReportsForMe(t *testing.T) {
types := []MessageType{Text, AdminText, Reaction, Delete, Pinned, Mute, types := []MessageType{Text, AdminText, Reaction, Delete, Pinned, Mute,
AdminReplay, FileTransfer} AdminReplay, FileTransfer}
levels := []NotificationLevel{NotifyPing, NotifyAll} levels := []NotificationLevel{NotifyPing, NotifyAll}
pingTypes := []PingType{ReplyPing, MentionPing}
var expected []NotificationReport var expected []NotificationReport
var notifData []*primNotif.Data var notifData []*primNotif.Data
...@@ -400,7 +409,8 @@ func TestGetNotificationReportsForMe(t *testing.T) { ...@@ -400,7 +409,8 @@ func TestGetNotificationReportsForMe(t *testing.T) {
identifier := append(chanID.Marshal(), []byte("identifier")...) identifier := append(chanID.Marshal(), []byte("identifier")...)
tags := make([]string, 1+rng.Intn(4)) tags := make([]string, 1+rng.Intn(4))
for j := range tags { for j := range tags {
tags[j] = makeUserPingTag(makeEd25519PubKey(rng, t)) tags[j] = makeUserPingTag(makeEd25519PubKey(rng, t),
pingTypes[rng.Intn(len(pingTypes))])
} }
mtByte := mt.Marshal() mtByte := mt.Marshal()
...@@ -417,8 +427,13 @@ func TestGetNotificationReportsForMe(t *testing.T) { ...@@ -417,8 +427,13 @@ func TestGetNotificationReportsForMe(t *testing.T) {
if includeChannel { if includeChannel {
var filterTags []string var filterTags []string
var pt PingType
if includeTags { if includeTags {
filterTags = []string{tags[rng.Intn(len(tags))]} filterTags = []string{tags[rng.Intn(len(tags))]}
pt, err = pingTypeFromTag(filterTags[0])
if err != nil {
t.Errorf("Failed to get Ping Type: %+v", err)
}
} }
nfs = append(nfs, NotificationFilter{ nfs = append(nfs, NotificationFilter{
Identifier: identifier, Identifier: identifier,
...@@ -438,6 +453,7 @@ func TestGetNotificationReportsForMe(t *testing.T) { ...@@ -438,6 +453,7 @@ func TestGetNotificationReportsForMe(t *testing.T) {
expected = append(expected, NotificationReport{ expected = append(expected, NotificationReport{
Channel: chanID, Channel: chanID,
Type: mt, Type: mt,
PingType: pt,
}) })
} }
} }
...@@ -447,6 +463,9 @@ func TestGetNotificationReportsForMe(t *testing.T) { ...@@ -447,6 +463,9 @@ func TestGetNotificationReportsForMe(t *testing.T) {
nrs := GetNotificationReportsForMe(nfs, notifData) nrs := GetNotificationReportsForMe(nfs, notifData)
data, _ := json.MarshalIndent(nrs, "// ", " ")
fmt.Printf("// %s\n", data)
if !reflect.DeepEqual(nrs, expected) { if !reflect.DeepEqual(nrs, expected) {
t.Errorf("NotificationReport list does not match expected."+ t.Errorf("NotificationReport list does not match expected."+
"\nexpected: %+v\nreceived: %+v", expected, nrs) "\nexpected: %+v\nreceived: %+v", expected, nrs)
...@@ -520,7 +539,8 @@ func TestNotificationFilter_JSON(t *testing.T) { ...@@ -520,7 +539,8 @@ func TestNotificationFilter_JSON(t *testing.T) {
nf := NotificationFilter{ nf := NotificationFilter{
Identifier: append(chanID.Marshal(), []byte("Identifier")...), Identifier: append(chanID.Marshal(), []byte("Identifier")...),
ChannelID: chanID, ChannelID: chanID,
Tags: makeUserPingTags(makeEd25519PubKey(rng, t)), Tags: makeUserPingTags(map[PingType][]ed25519.PublicKey{
MentionPing: {makeEd25519PubKey(rng, t)}}),
AllowLists: notificationLevelAllowLists[symmetric][NotifyPing], AllowLists: notificationLevelAllowLists[symmetric][NotifyPing],
} }
...@@ -553,7 +573,8 @@ func TestNotificationFilter_Slice_JSON(t *testing.T) { ...@@ -553,7 +573,8 @@ func TestNotificationFilter_Slice_JSON(t *testing.T) {
nfs[i] = NotificationFilter{ nfs[i] = NotificationFilter{
Identifier: append(chanID.Marshal(), []byte("Identifier")...), Identifier: append(chanID.Marshal(), []byte("Identifier")...),
ChannelID: chanID, ChannelID: chanID,
Tags: makeUserPingTags(makeEd25519PubKey(rng, t)), Tags: makeUserPingTags(map[PingType][]ed25519.PublicKey{
MentionPing: {makeEd25519PubKey(rng, t)}}),
AllowLists: notificationLevelAllowLists[sourceTypes[i%len(sourceTypes)]][levels[i%len(levels)]], AllowLists: notificationLevelAllowLists[sourceTypes[i%len(sourceTypes)]][levels[i%len(levels)]],
} }
} }
......
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
package channels
import (
"crypto/ed25519"
"encoding/hex"
"strings"
"github.com/pkg/errors"
)
// PingType describes a user ping. It is used to describe more information about
// the ping to the user.
type PingType string
const (
// GenericPing indicates a generic notification that is not a reply or
// mention.
GenericPing PingType = ""
// ReplyPing indicates that someone replied to this user's message.
ReplyPing PingType = "usrReply"
// MentionPing indicates that someone mentioned (tagged) the user in a
// message.
MentionPing PingType = "usrMention"
)
// getRankingPingType returns the ping that ranks higher.
func getRankingPingType(a, b PingType) PingType {
if pingHierarchy[a] > pingHierarchy[b] {
return a
}
return b
}
// pingHierarchy describes the ranking of ping types. If a user receives a ping
// on multiple tags, the highest ranking ping tag is reported to the user.
var pingHierarchy = map[PingType]uint32{
GenericPing: 0,
MentionPing: 100,
ReplyPing: 200,
}
// delimiter between the public key and ping type in a ping tag.
const pingTagDelim = "-"
// makeUserPingTags creates a list of tags to include in a cmix.Service from
// channel pings.
func makeUserPingTags(pings map[PingType][]ed25519.PublicKey) []string {
if pings == nil || len(pings) == 0 {
return nil
}
var tags []string
for pt, users := range pings {
s := make([]string, len(users))
for i := range users {
s[i] = makeUserPingTag(users[i], pt)
}
tags = append(tags, s...)
}
return tags
}
// makeUserPingTag creates a tag from a user's public key and ping type to be
// used in a tag list in a cmix.Service.
func makeUserPingTag(user ed25519.PublicKey, pt PingType) string {
return hex.EncodeToString(user) + pingTagDelim + string(pt)
}
// pingTypeFromTag gets the PingType from the ping tag.
func pingTypeFromTag(tag string) (PingType, error) {
s := strings.SplitN(tag, pingTagDelim, 2)
if len(s) < 2 {
return "", errors.New("invalid ping tag")
}
return PingType(s[1]), nil
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
package channels
import (
"crypto/ed25519"
"math/rand"
"reflect"
"sort"
"testing"
)
// Tests that all combinations of defined PingType passed into
// getRankingPingType result in the expected type.
func Test_getRankingPingType(t *testing.T) {
tests := []struct{ a, b, expected PingType }{
{GenericPing, ReplyPing, ReplyPing},
{GenericPing, MentionPing, MentionPing},
{ReplyPing, GenericPing, ReplyPing},
{ReplyPing, MentionPing, ReplyPing},
{MentionPing, GenericPing, MentionPing},
{MentionPing, ReplyPing, ReplyPing},
}
for i, tt := range tests {
pt := getRankingPingType(tt.a, tt.b)
if tt.expected != pt {
t.Errorf("Unexpected PingType when comparing %q with %q (%d)."+
"\nexpected: %q\nreceived: %q", tt.a, tt.b, i, tt.expected, pt)
}
}
}
// Unit test of makeUserPingTags.
func Test_makeUserPingTags(t *testing.T) {
prng := rand.New(rand.NewSource(579498))
types := []PingType{ReplyPing, MentionPing}
for i := 0; i < 6; i++ {
pings := map[PingType][]ed25519.PublicKey{
ReplyPing: {}, MentionPing: {},
}
expected := make([]string, i)
for j := 0; j < i; j++ {
pt := types[prng.Intn(len(types))]
user, _, _ := ed25519.GenerateKey(prng)
pings[pt] = append(pings[pt], user)
expected[j] = makeUserPingTag(user, pt)
}
if i == 0 {
expected = nil
}
tags := makeUserPingTags(pings)
sort.Strings(expected)
sort.Strings(tags)
if !reflect.DeepEqual(expected, tags) {
t.Errorf("Unexpected tags (%d).\nexpected: %#v\nreceived: %#v",
i, expected, tags)
}
}
}
// Consistency test of makeUserPingTag.
func TestManager_makeUserPingTag_Consistency(t *testing.T) {
prng := rand.New(rand.NewSource(579498))
expected := []string{
"32b15a6bd6db85bf239a7fe6aeb84072c2646b96188c9c4f7dec6d35928436c2-usrReply",
"88ab667cdc60e041849a924ed7b6e60fa2de62eca4b67336c86e7620a19d270f-usrReply",
"ddab884d040d9182950aca0eab2899977caf62eef098d0973f99aa1b4c97f541-usrMention",
"76341ec7e5da6fe34b80021c4e0f6362d3a1a37091126f8cb74ec8ee5bb375fb-usrMention",
"d0f05d30b6d62460788ac7a7bde3b0de8065c6b1673ba3a724af90e87f1a152e-usrMention",
"69a8a814d254be6d3652c5f013d063803c6542a94c0dcd52cb9d9f68bcf14041-usrMention",
"215a17025a25d892baa595d6677046fe74d625505ad65a64d367975c94477831-usrMention",
"9b6400094a53921ff041c024265748df635843cb6a50e8a4d6f68f7c52f9e766-usrMention",
"456e99e886cf889b6b9ce63ecece752267b199032a2236239aaedf9d519a491b-usrReply",
"2cf8839b549c5b360fb5434de944918d5680e203e173889fd48dd4c91099762e-usrMention",
"a0a48cffe820a8f7e213b6bb99b99c1270fc8126bbde165b756449895f6f6603-usrReply",
"d1086f945874607d89d9cc59c6e940217bd69c8d71dc45a0f3728cb6146ae154-usrReply",
"db60467fc127405470c73cbc4734d1098d43e85c61904a114d95466cfb30781c-usrReply",
"7bd7b36cf1393fdf8576df1182e5b668bb3f021aa4a1b562d885711153a30395-usrReply",
"61a25b1fbb5dd9490d43b64d6899ae3f1fac2c2e7bf8cade5fbbcced897f10fe-usrReply",
"689c8cc76cb15c457f6666457b360701d4f1a6312f6f1a1f1c910353a5d0ce10-usrMention",
"64c7b1b343f38077d30cb9b99a0ed2844974d984fbd9630999a4a59069a964a7-usrReply",
"f56ee72f86ee39d52887a08927606bce8a9d21726a82792655aee098705c48a9-usrReply",
"008da28a9f8f70dafe533283acabbed77cb92bf5058f044bb50e1167ce6f05b4-usrMention",
"70a659261ad311cf6111639ab6be9677f5afc0ab9e13546b3afc64597ccaabfd-usrReply",
}
types := []PingType{ReplyPing, MentionPing}
for i, exp := range expected {
pubKey, _, _ := ed25519.GenerateKey(prng)
tag := makeUserPingTag(pubKey, types[prng.Intn(len(types))])
if exp != tag {
t.Errorf("Unexpected tag for key %X (%d)."+
"\nexpected: %s\nreceived: %s", pubKey, i, exp, tag)
}
}
}
// Unit test of pingTypeFromTag.
func Test_pingTypeFromTag(t *testing.T) {
prng := rand.New(rand.NewSource(65881))
types := []PingType{ReplyPing, MentionPing}
for i := 0; i < 10; i++ {
pubKey, _, _ := ed25519.GenerateKey(prng)
expected := types[prng.Intn(len(types))]
tag := makeUserPingTag(pubKey, expected)
pt, err := pingTypeFromTag(tag)
if err != nil {
t.Errorf("Failed to get type (%d): %+v", i, err)
}
if expected != pt {
t.Errorf("Unexpected PingType (%d)."+
"\nexpected: %s\nreceived: %s", i, expected, pt)
}
}
}
// Error path: Tests that pingTypeFromTag returns an error for an invalid tag.
func Test_typeFromPingTag_InvalidTagError(t *testing.T) {
_, err := pingTypeFromTag(
"64c7b1b343f38077d30cb9b99a0ed2844974d984fbd9630999a4a59069a964a7")
if err == nil {
t.Errorf("Did not get error for invalid tag.")
}
}
...@@ -13,20 +13,21 @@ import ( ...@@ -13,20 +13,21 @@ import (
"crypto/hmac" "crypto/hmac"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"golang.org/x/crypto/blake2b"
"google.golang.org/protobuf/proto"
"gitlab.com/elixxir/client/v4/cmix" "gitlab.com/elixxir/client/v4/cmix"
"gitlab.com/elixxir/client/v4/cmix/rounds" "gitlab.com/elixxir/client/v4/cmix/rounds"
"gitlab.com/elixxir/client/v4/emoji" "gitlab.com/elixxir/client/v4/emoji"
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
"gitlab.com/elixxir/crypto/message" "gitlab.com/elixxir/crypto/message"
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
"gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/id/ephemeral"
"gitlab.com/xx_network/primitives/netTime" "gitlab.com/xx_network/primitives/netTime"
"golang.org/x/crypto/blake2b"
"google.golang.org/protobuf/proto"
) )
const ( const (
...@@ -109,7 +110,7 @@ func timeNow() string { return netTime.Now().Format("15:04:05.9999999") } ...@@ -109,7 +110,7 @@ func timeNow() string { return netTime.Now().Format("15:04:05.9999999") }
// for this message. They must be in the channel and have notifications enabled // for this message. They must be in the channel and have notifications enabled
func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType, func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType,
msg []byte, validUntil time.Duration, tracked bool, params cmix.CMIXParams, msg []byte, validUntil time.Duration, tracked bool, params cmix.CMIXParams,
pings []ed25519.PublicKey) ( pingsMap map[PingType][]ed25519.PublicKey) (
message.ID, rounds.Round, ephemeral.Id, error) { message.ID, rounds.Round, ephemeral.Id, error) {
if hmac.Equal(channelID.Bytes(), emptyChannelID.Bytes()) { if hmac.Equal(channelID.Bytes(), emptyChannelID.Bytes()) {
...@@ -242,7 +243,7 @@ func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType, ...@@ -242,7 +243,7 @@ func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType,
} }
log += fmt.Sprintf("Broadcasting message at %s. ", timeNow()) log += fmt.Sprintf("Broadcasting message at %s. ", timeNow())
tags := makeUserPingTags(pings...) tags := makeUserPingTags(pingsMap)
mt := messageType.Marshal() mt := messageType.Marshal()
r, ephID, err := ch.broadcast.BroadcastWithAssembler(assemble, tags, r, ephID, err := ch.broadcast.BroadcastWithAssembler(assemble, tags,
[2]byte{mt[0], mt[1]}, params) [2]byte{mt[0], mt[1]}, params)
...@@ -301,8 +302,10 @@ func (m *manager) SendMessage(channelID *id.ID, msg string, ...@@ -301,8 +302,10 @@ func (m *manager) SendMessage(channelID *id.ID, msg string,
return message.ID{}, rounds.Round{}, ephemeral.Id{}, err return message.ID{}, rounds.Round{}, ephemeral.Id{}, err
} }
pingMap := map[PingType][]ed25519.PublicKey{MentionPing: pings}
return m.SendGeneric( return m.SendGeneric(
channelID, Text, txtMarshaled, validUntil, true, params, pings) channelID, Text, txtMarshaled, validUntil, true, params, pingMap)
} }
// SendReply is used to send a formatted message over a channel. // SendReply is used to send a formatted message over a channel.
...@@ -339,8 +342,20 @@ func (m *manager) SendReply(channelID *id.ID, msg string, ...@@ -339,8 +342,20 @@ func (m *manager) SendReply(channelID *id.ID, msg string,
return message.ID{}, rounds.Round{}, ephemeral.Id{}, err return message.ID{}, rounds.Round{}, ephemeral.Id{}, err
} }
// Get the public key of the sender
mm, err := m.events.model.GetMessage(replyTo)
if err != nil {
return message.ID{}, rounds.Round{}, ephemeral.Id{}, errors.Wrapf(err,
"failed getting message %s from event model", replyTo)
}
pingMap := map[PingType][]ed25519.PublicKey{
ReplyPing: {mm.PubKey},
MentionPing: pings,
}
return m.SendGeneric( return m.SendGeneric(
channelID, Text, txtMarshaled, validUntil, true, params, pings) channelID, Text, txtMarshaled, validUntil, true, params, pingMap)
} }
// SendReaction is used to send a reaction to a message over a channel. The // SendReaction is used to send a reaction to a message over a channel. The
...@@ -471,9 +486,11 @@ func (m *manager) SendInvite(channelID *id.ID, msg string, ...@@ -471,9 +486,11 @@ func (m *manager) SendInvite(channelID *id.ID, msg string,
return message.ID{}, rounds.Round{}, ephemeral.Id{}, err return message.ID{}, rounds.Round{}, ephemeral.Id{}, err
} }
pingMap := map[PingType][]ed25519.PublicKey{MentionPing: pings}
// Send invitation // Send invitation
return m.SendGeneric(channelID, Invitation, invitationMarshalled, return m.SendGeneric(channelID, Invitation, invitationMarshalled,
validUntil, true, params, pings) validUntil, true, params, pingMap)
} }
// replayAdminMessage is used to rebroadcast an admin message asa a norma user. // replayAdminMessage is used to rebroadcast an admin message asa a norma user.
...@@ -791,21 +808,5 @@ func makeChaDebugTag( ...@@ -791,21 +808,5 @@ func makeChaDebugTag(
h.Write(id) h.Write(id)
tripCode := base64.RawStdEncoding.EncodeToString(h.Sum(nil))[:12] tripCode := base64.RawStdEncoding.EncodeToString(h.Sum(nil))[:12]
return fmt.Sprintf("%s-%s", baseTag, tripCode) return baseTag + "-" + tripCode
}
func makeUserPingTags(users ...ed25519.PublicKey) []string {
if users == nil || len(users) == 0 {
return nil
}
s := make([]string, len(users))
for i := 0; i < len(s); i++ {
s[i] = makeUserPingTag(users[i])
}
return s
}
func makeUserPingTag(user ed25519.PublicKey) string {
return fmt.Sprintf("%x-usrping", user)
} }
...@@ -14,27 +14,25 @@ import ( ...@@ -14,27 +14,25 @@ import (
"testing" "testing"
"time" "time"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.com/elixxir/client/v4/collective"
"github.com/golang/protobuf/proto" "gitlab.com/elixxir/client/v4/broadcast"
"gitlab.com/elixxir/client/v4/cmix"
"gitlab.com/elixxir/client/v4/cmix/identity/receptionID" "gitlab.com/elixxir/client/v4/cmix/identity/receptionID"
"gitlab.com/elixxir/client/v4/cmix/rounds" "gitlab.com/elixxir/client/v4/cmix/rounds"
"gitlab.com/elixxir/client/v4/collective"
"gitlab.com/elixxir/client/v4/collective/versioned" "gitlab.com/elixxir/client/v4/collective/versioned"
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
cryptoChannel "gitlab.com/elixxir/crypto/channel" cryptoChannel "gitlab.com/elixxir/crypto/channel"
"gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/crypto/message" "gitlab.com/elixxir/crypto/message"
"gitlab.com/elixxir/crypto/rsa" "gitlab.com/elixxir/crypto/rsa"
"gitlab.com/elixxir/ekv" "gitlab.com/elixxir/ekv"
"gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/primitives/netTime"
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
"gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/id/ephemeral"
"gitlab.com/xx_network/primitives/netTime"
"gitlab.com/elixxir/client/v4/broadcast"
"gitlab.com/elixxir/client/v4/cmix"
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
) )
func Test_manager_SendGeneric(t *testing.T) { func Test_manager_SendGeneric(t *testing.T) {
...@@ -289,7 +287,7 @@ func Test_manager_SendReply(t *testing.T) { ...@@ -289,7 +287,7 @@ func Test_manager_SendReply(t *testing.T) {
channels: make(map[id.ID]*joinedChannel), channels: make(map[id.ID]*joinedChannel),
local: kv, local: kv,
rng: crng, rng: crng,
events: initEvents(&mockEventModel{}, 512, kv, crng), events: initEvents(&MockEvent{}, 512, kv, crng),
nicknameManager: &nicknameManager{byChannel: make(map[id.ID]string), remote: nil}, nicknameManager: &nicknameManager{byChannel: make(map[id.ID]string), remote: nil},
st: loadSendTracker(&mockBroadcastClient{}, kv, func(*id.ID, st: loadSendTracker(&mockBroadcastClient{}, kv, func(*id.ID,
*userMessageInternal, []byte, time.Time, *userMessageInternal, []byte, time.Time,
...@@ -816,6 +814,7 @@ func Test_manager_MuteUser(t *testing.T) { ...@@ -816,6 +814,7 @@ func Test_manager_MuteUser(t *testing.T) {
} }
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Mock Interfaces // // Mock Interfaces //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
......
...@@ -582,21 +582,19 @@ func (m *mockChannelsManager) GetChannels() []*id.ID { pani ...@@ -582,21 +582,19 @@ func (m *mockChannelsManager) GetChannels() []*id.ID { pani
func (m *mockChannelsManager) GetChannel(*id.ID) (*cryptoBroadcast.Channel, error) { func (m *mockChannelsManager) GetChannel(*id.ID) (*cryptoBroadcast.Channel, error) {
panic("implement me") panic("implement me")
} }
func (m *mockChannelsManager) SendSilent(channelID *id.ID, validUntil time.Duration, params cmix.CMIXParams) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) { func (m *mockChannelsManager) SendSilent(*id.ID, time.Duration, cmix.CMIXParams) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
panic("implement me") panic("implement me")
} }
func (m *mockChannelsManager) SendInvite(*id.ID, string, *cryptoBroadcast.Channel, string, time.Duration, cmix.CMIXParams, []ed25519.PublicKey) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
func (m *mockChannelsManager) SendInvite(channelID *id.ID, msg string, inviteTo *cryptoBroadcast.Channel, host string, validUntil time.Duration, params cmix.CMIXParams, pings []ed25519.PublicKey) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
panic("implement me") panic("implement me")
} }
func (m *mockChannelsManager) GetNotificationStatus(*id.ID) (clientNotif.NotificationState, error) {
func (m *mockChannelsManager) GetNotificationStatus(channelID *id.ID) (clientNotif.NotificationState, error) {
panic("implement me") panic("implement me")
} }
func (m *mockChannelsManager) SendGeneric(channelID *id.ID, func (m *mockChannelsManager) SendGeneric(channelID *id.ID,
messageType channels.MessageType, msg []byte, validUntil time.Duration, messageType channels.MessageType, msg []byte, validUntil time.Duration,
_ bool, _ cmix.CMIXParams, _ []ed25519.PublicKey) ( _ bool, _ cmix.CMIXParams, _ map[channels.PingType][]ed25519.PublicKey) (
cryptoMessage.ID, rounds.Round, ephemeral.Id, error) { cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
msgID := cryptoMessage.DeriveChannelMessageID(channelID, 0, msg) msgID := cryptoMessage.DeriveChannelMessageID(channelID, 0, msg)
...@@ -645,11 +643,10 @@ func (m *mockChannelsManager) GetNickname(*id.ID) (nickname string, exists bool) ...@@ -645,11 +643,10 @@ func (m *mockChannelsManager) GetNickname(*id.ID) (nickname string, exists bool)
func (m *mockChannelsManager) Muted(*id.ID) bool { panic("implement me") } func (m *mockChannelsManager) Muted(*id.ID) bool { panic("implement me") }
func (m *mockChannelsManager) GetMutedUsers(*id.ID) []ed25519.PublicKey { panic("implement me") } func (m *mockChannelsManager) GetMutedUsers(*id.ID) []ed25519.PublicKey { panic("implement me") }
func (m *mockChannelsManager) GetNotificationLevel(channelID *id.ID) (channels.NotificationLevel, error) { func (m *mockChannelsManager) GetNotificationLevel(*id.ID) (channels.NotificationLevel, error) {
panic("implement me") panic("implement me")
} }
func (m *mockChannelsManager) SetMobileNotificationsLevel(channelID *id.ID, level channels.NotificationLevel, func (m *mockChannelsManager) SetMobileNotificationsLevel(*id.ID, channels.NotificationLevel, clientNotif.NotificationState) error {
status clientNotif.NotificationState) error {
panic("implement me") panic("implement me")
} }
func (m *mockChannelsManager) IsChannelAdmin(*id.ID) bool { panic("implement me") } func (m *mockChannelsManager) IsChannelAdmin(*id.ID) bool { panic("implement me") }
......
...@@ -343,8 +343,11 @@ func (w *Wrapper) Send(channelID *id.ID, fileLink []byte, fileName, ...@@ -343,8 +343,11 @@ func (w *Wrapper) Send(channelID *id.ID, fileLink []byte, fileName,
errors.Wrap(err, "error JSON marshalling file info") errors.Wrap(err, "error JSON marshalling file info")
} }
pingMap := map[channels.PingType][]ed25519.PublicKey{
channels.MentionPing: pings}
return w.ch.SendGeneric(channelID, channels.FileTransfer, return w.ch.SendGeneric(channelID, channels.FileTransfer,
fileInfo, validUntil, true, params.CMIX, pings) fileInfo, validUntil, true, params.CMIX, pingMap)
} }
// RegisterSentProgressCallback registers the callback to the given file // RegisterSentProgressCallback registers the callback to the given file
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment