diff --git a/channels/emoji.go b/channels/emoji.go index 8d0a20a44c5c1eaa9422168fc7013302ce50d3f9..4823f6fcab737f3b0b7f9e044fcdc69b69d1c67f 100644 --- a/channels/emoji.go +++ b/channels/emoji.go @@ -1,14 +1,15 @@ package channels import ( + "bufio" + "bytes" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "regexp" ) -// found at https://www.regextester.com/106421 -const findEmoji = `(\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-` + - `\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff])` +//based on emojis found at https://unicode.org/emoji/charts/full-emoji-list.html +const findEmoji = `[\xA9\xAE\x{2000}-\x{3300}\x{1F000}-\x{1FBFF}]` var InvalidReaction = errors.New( "The reaction is not valid, it must be a single emoji") @@ -35,8 +36,10 @@ func ValidateReaction(reaction string) error { return InvalidReaction } - // make sure it is only one emoji - if !compiledRegex.Match([]byte(reaction)) { + reader := bufio.NewReader(bytes.NewReader([]byte(reaction))) + + // make sure it has emojis + if !compiledRegex.MatchReader(reader) { return InvalidReaction } diff --git a/channels/emoji_test.go b/channels/emoji_test.go index 2486a1c960d66f8b162b3073d26cc184a5b3cb87..f8b9f43c355574887a135fa1c6c22021c4372b19 100644 --- a/channels/emoji_test.go +++ b/channels/emoji_test.go @@ -1,26 +1,24 @@ package channels import ( - "fmt" - "regexp" "testing" ) func TestValidateReaction(t *testing.T) { - r := "ðŸ†" - reg, _ := regexp.Compile(findEmoji) + testReactions := []string{"ðŸ†", "😂", "â¤", "🤣", "ðŸ‘", "ðŸ˜", "ðŸ™", "😘", "🥰", "ðŸ˜", + "😊", "☺", "A", "b", "AA", "1", "ðŸ†ðŸ†", "ðŸ†A", "ðŸ‘ðŸ‘ðŸ‘", "ðŸ‘😘A", "O"} + expected := []error{ + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + InvalidReaction, InvalidReaction, InvalidReaction, InvalidReaction, + InvalidReaction, InvalidReaction, InvalidReaction, InvalidReaction, + InvalidReaction} - fmt.Println(reg.Match([]byte("ðŸ†"))) - fmt.Println(reg.Match([]byte("😋"))) - fmt.Println(reg.Match([]byte("A"))) - fmt.Println(reg.Match([]byte("R"))) - fmt.Println(reg.Match([]byte("#⃣"))) - fmt.Println(reg.Match([]byte("#ï¸âƒ£"))) - fmt.Println(reg.Match([]byte("ðŸ’ðŸ½â€â™€"))) - - err := ValidateReaction(r) - if err != nil { - t.Errorf("Got error: %+v", err) + for i, r := range testReactions { + err := ValidateReaction(r) + if err != expected[i] { + t.Errorf("Got incorrect response for `%s` (%d): "+ + "`%s` vs `%s`", r, i, err, expected[i]) + } } } diff --git a/channels/errors.go b/channels/errors.go index 3fd5dac363b82893946de5e5dd751c29245a3afe..008b64fccb849f556b0b86aa03b3e2a6f87fd39c 100644 --- a/channels/errors.go +++ b/channels/errors.go @@ -9,4 +9,6 @@ var ( "the channel cannot be found") MessageTooLongErr = errors.New( "the passed message is too long") + WrongPrivateKey = errors.New( + "the passed private key does not match the channel") ) diff --git a/channels/eventModel_test.go b/channels/eventModel_test.go index 9379c19459aa81a2232503a7644342ea202dcc0b..c329ce022453e59a667c6c5f91d43bf8de1ab358 100644 --- a/channels/eventModel_test.go +++ b/channels/eventModel_test.go @@ -683,27 +683,25 @@ func TestEvents_receiveReaction_InvalidReactionMessageID(t *testing.T) { } } -// todo: enable once ValidateReaction works -/* -func TestEvents_receiveReaction_InvalidReactionContent(t *testing.T){ +func TestEvents_receiveReaction_InvalidReactionContent(t *testing.T) { me := &MockEvent{} e := initEvents(me) //craft the input for the event chID := &id.ID{} - chID[0]=1 + chID[0] = 1 replyMsgId := []byte("blarg") textPayload := &CMIXChannelReaction{ - Version: 0, - Reaction: "Im not a reaction", + Version: 0, + Reaction: "I'm not a reaction", ReactionMessageID: replyMsgId[:], } textMarshaled, err := proto.Marshal(textPayload) - if err!=nil{ + if err != nil { t.Fatalf("failed to marshael the message proto: %+v", err) } @@ -712,42 +710,41 @@ func TestEvents_receiveReaction_InvalidReactionContent(t *testing.T){ senderUsername := "Alice" ts := time.Now() - lease := 69*time.Minute + lease := 69 * time.Minute r := rounds.Round{ID: 420, Timestamps: make(map[states.Round]time.Time)} - r.Timestamps[states.QUEUED]=time.Now() - + r.Timestamps[states.QUEUED] = time.Now() //call the handler e.receiveReaction(chID, msgID, 0, senderUsername, textMarshaled, ts, lease, r) //check the results on the model - if me.eventReceive.channelID!=nil{ + if me.eventReceive.channelID != nil { t.Errorf("Channel ID did propogated correctly when the reaction " + "is bad") } - if me.eventReceive.messageID.Equals(msgID){ + if me.eventReceive.messageID.Equals(msgID) { t.Errorf("Message ID propogated correctly when the reaction is " + "bad") } - if !me.eventReceive.reactionTo.Equals(cryptoChannel.MessageID{}){ + if !me.eventReceive.reactionTo.Equals(cryptoChannel.MessageID{}) { t.Errorf("Reaction ID propogated correctly when the reaction " + "is bad") } - if me.eventReceive.senderUsername!=""{ + if me.eventReceive.senderUsername != "" { t.Errorf("SenderID propogated correctly when the reaction " + "is bad") } - if me.eventReceive.lease!=0{ + if me.eventReceive.lease != 0 { t.Errorf("Message lease propogated correctly when the " + "reaction is bad") } -}*/ +} func getFuncName(i interface{}) string { return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() diff --git a/channels/interface.go b/channels/interface.go index ee5f385ae5ad1e187fb58e1325d3ecddea94037a..3d60c7a7aefb14f497e1fe9ff87969dc3c7b92cd 100644 --- a/channels/interface.go +++ b/channels/interface.go @@ -30,7 +30,7 @@ type Manager interface { // If the final message, before being sent over the wire, is too long, this will // return an error. The message must be at most 510 bytes long. SendAdminGeneric(privKey *rsa.PrivateKey, channelID *id.ID, - msg []byte, validUntil time.Duration, messageType MessageType, + messageType MessageType, msg []byte, validUntil time.Duration, params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, error) diff --git a/channels/send.go b/channels/send.go index 2523822e3166adf73cc84be8e4e8707f367fc8bc..558023c99def46f369bdb971bb74dadaed64a9c0 100644 --- a/channels/send.go +++ b/channels/send.go @@ -102,7 +102,7 @@ func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType, // If the final message, before being sent over the wire, is too long, this will // return an error. The message must be at most 510 bytes long. func (m *manager) SendAdminGeneric(privKey *rsa.PrivateKey, channelID *id.ID, - msg []byte, validUntil time.Duration, messageType MessageType, + messageType MessageType, msg []byte, validUntil time.Duration, params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, error) { @@ -112,6 +112,11 @@ func (m *manager) SendAdminGeneric(privKey *rsa.PrivateKey, channelID *id.ID, return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err } + //verify the private key is correct + if ch.broadcast.Get().RsaPubKey.N.Cmp(privKey.GetPublic().N) != 0 { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, WrongPrivateKey + } + var msgId cryptoChannel.MessageID //Note: we are not checking check if message is too long before trying to //find a round diff --git a/channels/send_test.go b/channels/send_test.go index cf0fe39927e0c77c0b5d8a7809471133bd3ddd22..d6eb9df91dd3299c4322482a71739748d67baf73 100644 --- a/channels/send_test.go +++ b/channels/send_test.go @@ -1,52 +1,75 @@ package channels import ( + "bytes" "crypto/ed25519" + "gitlab.com/xx_network/crypto/csprng" "testing" "time" - "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/multicastRSA" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/elixxir/client/broadcast" "gitlab.com/elixxir/client/cmix" - "gitlab.com/elixxir/client/cmix/message" - "gitlab.com/elixxir/client/cmix/rounds" - "gitlab.com/elixxir/client/storage/versioned" cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" - cryptoChannel "gitlab.com/elixxir/crypto/channel" - "gitlab.com/elixxir/crypto/fastRNG" - "gitlab.com/elixxir/ekv" ) -type mockBroadcastChannel struct{} +type mockBroadcastChannel struct { + hasRun bool + + payload []byte + params cmix.CMIXParams + + pk multicastRSA.PrivateKey + + crypto *cryptoBroadcast.Channel +} func (m *mockBroadcastChannel) MaxPayloadSize() int { - return 12345 + return 1024 } func (m *mockBroadcastChannel) MaxAsymmetricPayloadSize() int { - return 123 + return 512 } func (m *mockBroadcastChannel) Get() *cryptoBroadcast.Channel { - return &cryptoBroadcast.Channel{} + return m.crypto } func (m *mockBroadcastChannel) Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) { + + m.hasRun = true + + m.payload = payload + m.params = cMixParams + return id.Round(123), ephemeral.Id{}, nil } func (m *mockBroadcastChannel) BroadcastWithAssembler(assembler broadcast.Assembler, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) { - return id.Round(123), ephemeral.Id{}, nil + m.hasRun = true + + var err error + + m.payload, err = assembler(42) + m.params = cMixParams + + return id.Round(123), ephemeral.Id{}, err } func (m *mockBroadcastChannel) BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) { + m.hasRun = true + + m.payload = payload + m.params = cMixParams + + m.pk = pk return id.Round(123), ephemeral.Id{}, nil } @@ -54,48 +77,25 @@ func (m *mockBroadcastChannel) BroadcastAsymmetricWithAssembler( pk multicastRSA.PrivateKey, assembler broadcast.Assembler, cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) { - return id.Round(123), ephemeral.Id{}, nil -} - -func (m *mockBroadcastChannel) RegisterListener(listenerCb broadcast.ListenerFunc, method broadcast.Method) error { - return nil -} - -func (m *mockBroadcastChannel) Stop() { -} - -type mockBroadcastClient struct{} - -func (m *mockBroadcastClient) GetMaxMessageLength() int { - return 123 -} - -func (m *mockBroadcastClient) SendWithAssembler(recipient *id.ID, assembler cmix.MessageAssembler, - cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) { + m.hasRun = true - ephemeralId := ephemeral.Id{} + var err error - return id.Round(567), ephemeralId, nil + m.payload, err = assembler(42) + m.params = cMixParams -} + m.pk = pk -func (m *mockBroadcastClient) IsHealthy() bool { - return true + return id.Round(123), ephemeral.Id{}, err } -func (m *mockBroadcastClient) AddIdentity(id *id.ID, validUntil time.Time, persistent bool) { - +func (m *mockBroadcastChannel) RegisterListener(listenerCb broadcast.ListenerFunc, method broadcast.Method) error { + return nil } -func (m *mockBroadcastClient) AddService(clientID *id.ID, newService message.Service, - response message.Processor) { - +func (m *mockBroadcastChannel) Stop() { } -func (m *mockBroadcastClient) DeleteClientService(clientID *id.ID) {} - -func (m *mockBroadcastClient) RemoveIdentity(id *id.ID) {} - type mockNameService struct { validChMsg bool } @@ -105,15 +105,15 @@ func (m *mockNameService) GetUsername() string { } func (m *mockNameService) GetChannelValidationSignature() (signature []byte, lease time.Time) { - return nil, time.Now() + return []byte("fake validation sig"), time.Now() } func (m *mockNameService) GetChannelPubkey() ed25519.PublicKey { - return nil + return []byte("fake pubkey") } func (m *mockNameService) SignChannelMessage(message []byte) (signature []byte, err error) { - return nil, nil + return []byte("fake sig"), nil } func (m *mockNameService) ValidateChannelMessage(username string, lease time.Time, @@ -121,54 +121,29 @@ func (m *mockNameService) ValidateChannelMessage(username string, lease time.Tim return m.validChMsg } -type mockEventModel struct{} - -func (m *mockEventModel) JoinChannel(channel *cryptoBroadcast.Channel) {} - -func (m *mockEventModel) LeaveChannel(channelID *id.ID) {} - -func (m *mockEventModel) ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID, - senderUsername string, text string, - timestamp time.Time, lease time.Duration, round rounds.Round) { -} - -func (m *mockEventModel) ReceiveReply(ChannelID *id.ID, messageID cryptoChannel.MessageID, - replyTo cryptoChannel.MessageID, SenderUsername string, - text string, timestamp time.Time, lease time.Duration, - round rounds.Round) { - -} - -func (m *mockEventModel) ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID, - reactionTo cryptoChannel.MessageID, senderUsername string, - reaction string, timestamp time.Time, lease time.Duration, - round rounds.Round) { - -} - func TestSendGeneric(t *testing.T) { - kv := versioned.NewKV(ekv.MakeMemstore()) - client := new(mockBroadcastClient) - rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) nameService := new(mockNameService) nameService.validChMsg = true - model := new(mockEventModel) - mm := NewManager(kv, client, rngGen, nameService, model) - m := mm.(*manager) + m := &manager{ + channels: make(map[id.ID]*joinedChannel), + name: nameService, + } channelID := new(id.ID) - messageType := MessageType(Text) + messageType := Text msg := []byte("hello world") validUntil := time.Hour params := new(cmix.CMIXParams) + mbc := &mockBroadcastChannel{} + m.channels[*channelID] = &joinedChannel{ - broadcast: &mockBroadcastChannel{}, + broadcast: mbc, } - messageId, roundId, ephemeralId, err := mm.SendGeneric( + messageId, roundId, ephemeralId, err := m.SendGeneric( channelID, messageType, msg, @@ -180,4 +155,89 @@ func TestSendGeneric(t *testing.T) { } t.Logf("messageId %v, roundId %v, ephemeralId %v", messageId, roundId, ephemeralId) + //verify the message was handled correctly + + //Unsize the broadcast + unsized, err := broadcast.DecodeSizedBroadcast(mbc.payload) + if err != nil { + t.Fatalf("Failed to decode the sized broadcast: %s", err) + } + + //decode the user message + umi, err := unmarshalUserMessageInternal(unsized) + if err != nil { + t.Fatalf("Failed to decode the user message: %s", err) + } + + // do checks of the data + if !umi.GetMessageID().Equals(messageId) { + t.Errorf("The message IDs do not match. %s vs %s ", + umi.messageID, messageId) + } + + if !bytes.Equal(umi.GetChannelMessage().Payload, msg) { + t.Errorf("The payload does not match. %s vs %s ", + umi.GetChannelMessage().Payload, msg) + } +} + +func TestAdminGeneric(t *testing.T) { + + nameService := new(mockNameService) + nameService.validChMsg = true + + m := &manager{ + channels: make(map[id.ID]*joinedChannel), + name: nameService, + } + + messageType := Text + msg := []byte("hello world") + validUntil := time.Hour + + rng := &csprng.SystemRNG{} + ch, priv, err := cryptoBroadcast.NewChannel("test", "test", rng) + if err != nil { + t.Fatalf("Failed to generate channel: %+v", err) + } + + mbc := &mockBroadcastChannel{crypto: ch} + + m.channels[*ch.ReceptionID] = &joinedChannel{ + broadcast: mbc, + } + + messageId, roundId, ephemeralId, err := m.SendAdminGeneric(priv, + ch.ReceptionID, messageType, msg, validUntil, cmix.GetDefaultCMIXParams()) + if err != nil { + t.Fatalf("Failed to SendAdminGeneric: %v", err) + } + t.Logf("messageId %v, roundId %v, ephemeralId %v", messageId, roundId, ephemeralId) + + //verify the message was handled correctly + + //Unsize the broadcast + unsized, err := broadcast.DecodeSizedBroadcast(mbc.payload) + if err != nil { + t.Fatalf("Failed to decode the sized broadcast: %s", err) + } + + //decode the channel message + + umi, err := unmarshalUserMessageInternal(unsized) + if err != nil { + t.Fatalf("Failed to decode the user message: %s", err) + } + + // do checks of the data + if !umi.GetMessageID().Equals(messageId) { + t.Errorf("The message IDs do not match. %s vs %s ", + umi.messageID, messageId) + } + + if !bytes.Equal(umi.GetChannelMessage().Payload, msg) { + t.Errorf("The payload does not match. %s vs %s ", + umi.GetChannelMessage().Payload, msg) + } + * / }