diff --git a/bindings/single.go b/bindings/single.go index ca0e855cb2bdd53baa1da576ec198f2db5587976..a3bacbce28c6db129ae233ea0c684ad9b90a9817 100644 --- a/bindings/single.go +++ b/bindings/single.go @@ -166,7 +166,7 @@ type Stopper interface { // Parameters: // - callbackReport - the JSON marshalled bytes of the SingleUseCallbackReport // object, which can be passed into Cmix.WaitForRoundResult to see if the -// send succeeded. +// send operation succeeded. type SingleUseCallback interface { Callback(callbackReport []byte, err error) } @@ -177,7 +177,7 @@ type SingleUseCallback interface { // Parameters: // - callbackReport - the JSON marshalled bytes of the SingleUseResponseReport // object, which can be passed into Cmix.WaitForRoundResult to see if the -// send succeeded. +// send operation succeeded. type SingleUseResponse interface { Callback(responseReport []byte, err error) } diff --git a/bindings/ud.go b/bindings/ud.go index 217fb964b7f4b5d313f323fca5df46d95cf9d577..3a3f21a0d43a4905ba5645d40cd38f4e82ed4854 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -429,8 +429,15 @@ func LookupUD(e2eID int, udContact []byte, cb UdLookupCallback, // // Parameters: // - contactListJSON - the JSON marshalled bytes of []contact.Contact, or nil -// if an error occurs -// - err - any errors that occurred in the search +// if an error occurs. +// +// JSON Example: +// { +// "<xxc(2)F8dL9EC6gy+RMJuk3R+Au6eGExo02Wfio5cacjBcJRwDEgB7Ugdw/BAr6RkCABkWAFV1c2VybmFtZTA7c4LzV05sG+DMt+rFB0NIJg==xxc>", +// "<xxc(2)eMhAi/pYkW5jCmvKE5ZaTglQb+fTo1D8NxVitr5CCFADEgB7Ugdw/BAr6RoCABkWAFV1c2VybmFtZTE7fElAa7z3IcrYrrkwNjMS2w==xxc>", +// "<xxc(2)d7RJTu61Vy1lDThDMn8rYIiKSe1uXA/RCvvcIhq5Yg4DEgB7Ugdw/BAr6RsCABkWAFV1c2VybmFtZTI7N3XWrxIUpR29atpFMkcR6A==xxc>" +// } +// - err - any errors that occurred in the search. type UdSearchCallback interface { Callback(contactListJSON []byte, err error) } @@ -451,7 +458,7 @@ type UdSearchCallback interface { // Returns: // - []byte - the JSON marshalled bytes of the SingleUseSendReport object, // which can be passed into Cmix.WaitForRoundResult to see if the send -// succeeded. +// operation succeeded. func SearchUD(e2eID int, udContact []byte, cb UdSearchCallback, factListJSON, singleRequestParamsJSON []byte) ([]byte, error) { @@ -479,7 +486,20 @@ func SearchUD(e2eID int, udContact []byte, cb UdSearchCallback, } callback := func(contactList []contact.Contact, err error) { - contactListJSON, err2 := json.Marshal(contactList) + marshaledContactList := make([][]byte, 0) + // fixme: it may be wiser to change this callback interface + // to simply do the work below when parsing the response from UD. + // that would change ud/search.go in two places: + // - searchCallback + // - parseContacts + // I avoid doing that as it changes interfaces w/o approval + for i := range contactList { + con := contactList[i] + marshaledContactList = append( + marshaledContactList, con.Marshal()) + } + + contactListJSON, err2 := json.Marshal(marshaledContactList) if err2 != nil { jww.FATAL.Panicf( "Failed to marshal list of contact.Contact: %+v", err2) diff --git a/channels/emoji.go b/channels/emoji.go new file mode 100644 index 0000000000000000000000000000000000000000..d65844745e2c81ea549a962a81999fefc09e7214 --- /dev/null +++ b/channels/emoji.go @@ -0,0 +1,22 @@ +package channels + +import ( + "github.com/forPelevin/gomoji" + "github.com/pkg/errors" +) + +var InvalidReaction = errors.New("The reaction is not valid, " + + "it must be a single emoji") + +// ValidateReaction checks that the reaction only contains a single Emoji +func ValidateReaction(reaction string) error { + if len(gomoji.RemoveEmojis(reaction)) > 0 { + return InvalidReaction + } + + if len(gomoji.FindAll(reaction)) != 1 { + return InvalidReaction + } + + return nil +} diff --git a/channels/eventModel.go b/channels/eventModel.go index f86ec23e14ff8ca96574be95ddb65d3a5f03bc4c..93841ba39764e6fe9e608070f1fda363c781e0dc 100644 --- a/channels/eventModel.go +++ b/channels/eventModel.go @@ -2,6 +2,7 @@ package channels import ( "errors" + "github.com/golang/protobuf/proto" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/primitives/states" @@ -26,22 +27,23 @@ type EventModel interface { LeaveChannel(ChannelID *id.ID) ReceiveMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID, - SenderUsername string, Content []byte, + SenderUsername string, text string, timestamp time.Time, lease time.Duration, round rounds.Round) ReceiveReply(ChannelID *id.ID, MessageID cryptoChannel.MessageID, ReplyTo cryptoChannel.MessageID, SenderUsername string, - Content []byte, timestamp time.Time, lease time.Duration, + text string, timestamp time.Time, lease time.Duration, round rounds.Round) ReceiveReaction(ChannelID *id.ID, MessageID cryptoChannel.MessageID, ReactionTo cryptoChannel.MessageID, SenderUsername string, - Reaction []byte, timestamp time.Time, lease time.Duration, + Reaction string, timestamp time.Time, lease time.Duration, round rounds.Round) - IgnoreMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID) - UnIgnoreMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID) - PinMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID, end time.Time) - UnPinMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID) + //unimplemented + //IgnoreMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID) + //UnIgnoreMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID) + //PinMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID, end time.Time) + //UnPinMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID) } type MessageTypeReceiveMessage func(ChannelID *id.ID, @@ -63,8 +65,9 @@ func initEvents(model EventModel) *events { } //set up default message types - e.registered[Text] = e.model.ReceiveTextMessage - e.registered[AdminText] = e.model.ReceiveAdminTextMessage + e.registered[Text] = e.receiveTextMessage + e.registered[AdminText] = e.receiveTextMessage + e.registered[Reaction] = e.receiveReaction return e } @@ -127,3 +130,73 @@ func (e *events) triggerAdminEvent(chID *id.ID, cm *ChannelMessage, cm.Payload, round.Timestamps[states.QUEUED], time.Duration(cm.Lease), round) return } + +func (e *events) receiveTextMessage(ChannelID *id.ID, + MessageID cryptoChannel.MessageID, messageType MessageType, + SenderUsername string, Content []byte, timestamp time.Time, + lease time.Duration, round rounds.Round) { + txt := &CMIXChannelText{} + if err := proto.Unmarshal(Content, txt); err != nil { + jww.ERROR.Printf("Failed to text unmarshal message %s from %s on "+ + "channel %s, type %s, ts: %s, lease: %s, round: %d: %+v", + MessageID, SenderUsername, ChannelID, messageType, timestamp, lease, + round.ID, err) + return + } + + if txt.ReplyMessageID != nil { + if len(txt.ReplyMessageID) == cryptoChannel.MessageIDLen { + var replyTo cryptoChannel.MessageID + copy(replyTo[:], txt.ReplyMessageID) + e.model.ReceiveReply(ChannelID, MessageID, replyTo, SenderUsername, txt.Text, + timestamp, lease, round) + return + + } else { + jww.ERROR.Printf("Failed process reply to for message %s from %s on "+ + "channel %s, type %s, ts: %s, lease: %s, round: %d, returning "+ + "without reply", + MessageID, SenderUsername, ChannelID, messageType, timestamp, lease, + round.ID) + } + } + + e.model.ReceiveMessage(ChannelID, MessageID, SenderUsername, txt.Text, + timestamp, lease, round) +} + +func (e *events) receiveReaction(ChannelID *id.ID, + MessageID cryptoChannel.MessageID, messageType MessageType, + SenderUsername string, Content []byte, timestamp time.Time, + lease time.Duration, round rounds.Round) { + react := &CMIXChannelReaction{} + if err := proto.Unmarshal(Content, react); err != nil { + jww.ERROR.Printf("Failed to text unmarshal message %s from %s on "+ + "channel %s, type %s, ts: %s, lease: %s, round: %d: %+v", + MessageID, SenderUsername, ChannelID, messageType, timestamp, lease, + round.ID, err) + return + } + + //check that the reaction is a single emoji and ignore if it isn't + if err := ValidateReaction(react.Reaction); err != nil { + jww.ERROR.Printf("Failed process reaction %s from %s on channel "+ + "%s, type %s, ts: %s, lease: %s, round: %d, due to malformed "+ + "reaction (%s), ignoring reaction", + MessageID, SenderUsername, ChannelID, messageType, timestamp, lease, + round.ID, err) + } + + if react.ReactionMessageID != nil && len(react.ReactionMessageID) == cryptoChannel.MessageIDLen { + var reactTo cryptoChannel.MessageID + copy(reactTo[:], react.ReactionMessageID) + e.model.ReceiveReaction(ChannelID, MessageID, reactTo, SenderUsername, + react.Reaction, timestamp, lease, round) + } else { + jww.ERROR.Printf("Failed process reaction %s from %s on channel "+ + "%s, type %s, ts: %s, lease: %s, round: %d, reacting to "+ + "invalid message, ignoring reaction", + MessageID, SenderUsername, ChannelID, messageType, timestamp, lease, + round.ID) + } +} diff --git a/channels/interface.go b/channels/interface.go index 8ecc115e96c73d7e8087e692a677fcc9fdcfd56d..f3a62ca7a49cb8c37b43393d2acf8824ab021a1a 100644 --- a/channels/interface.go +++ b/channels/interface.go @@ -27,7 +27,7 @@ type Manager interface { SendReply(channelID *id.ID, msg string, replyTo cryptoChannel.MessageID, validUntil time.Duration, params cmix.CMIXParams) ( cryptoChannel.MessageID, id.Round, ephemeral.Id, error) - SendReaction(channelID *id.ID, msg []byte, + SendReaction(channelID *id.ID, reaction string, reactTo cryptoChannel.MessageID, validUntil time.Duration, params cmix.CMIXParams) ( cryptoChannel.MessageID, id.Round, ephemeral.Id, error) } diff --git a/channels/joinedChannel.go b/channels/joinedChannel.go index 0936b40eac894e6806d87ddc0faa77c230eafc42..0496298d6136b771a1cd53b2342ef180bafb4e2f 100644 --- a/channels/joinedChannel.go +++ b/channels/joinedChannel.go @@ -48,11 +48,14 @@ func (m *manager) storeUnsafe() error { // loadChannels loads all currently joined channels from disk and registers // them for message reception -func (m *manager) loadChannels() map[*id.ID]*joinedChannel { +func (m *manager) loadChannels() { obj, err := m.kv.Get(joinedChannelsKey, joinedChannelsVersion) - if err != nil { + if !m.kv.Exists(err) { + m.channels = make(map[*id.ID]*joinedChannel) + return + } else if err != nil { jww.FATAL.Panicf("Failed to load channels %+v", err) } @@ -72,7 +75,8 @@ func (m *manager) loadChannels() map[*id.ID]*joinedChannel { } chMap[chList[i]] = jc } - return chMap + + m.channels = chMap } //addChannel Adds a channel @@ -123,6 +127,22 @@ func (m *manager) addChannel(channel cryptoBroadcast.Channel) error { return nil } +func (m *manager) removeChannel(channelId *id.ID) error { + m.mux.Lock() + defer m.mux.Unlock() + + ch, exists := m.channels[channelId] + if !exists { + return ChannelDoesNotExistsErr + } + + ch.broadcast.Stop() + + delete(m.channels, channelId) + + return nil +} + //getChannel returns the given channel, if it exists func (m *manager) getChannel(channelId *id.ID) (*joinedChannel, error) { m.mux.RLock() diff --git a/channels/manager.go b/channels/manager.go index 43ffa87b6de5ca4d574edac9064b906fce828bc2..ebad0194c14b4bece68862b8a26746e33620459e 100644 --- a/channels/manager.go +++ b/channels/manager.go @@ -1,22 +1,14 @@ package channels import ( - "github.com/golang/protobuf/proto" "gitlab.com/elixxir/client/broadcast" - "gitlab.com/elixxir/client/cmix" "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/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" "sync" - "time" ) -const cmixChannelTextVerion = 0 - type manager struct { //List of all channels channels map[*id.ID]*joinedChannel @@ -30,195 +22,46 @@ type manager struct { //Events model events - broadcastMaker broadcast.NewBroadcastChannelFunc -} - -func NewManager() { - -} - -func (m *manager) JoinChannel(channel cryptoBroadcast.Channel) error { - return m.addChannel(channel) -} - -func (m *manager) SendGeneric(channelID *id.ID, msg []byte, validUntil time.Duration, - messageType MessageType, params cmix.CMIXParams) (cryptoChannel.MessageID, - id.Round, ephemeral.Id, error) { - - //find the channel - ch, err := m.getChannel(channelID) - if err != nil { - return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err - } - - var msgId cryptoChannel.MessageID - //Note: we are not checking check if message is too long before trying to - //find a round - - //Build the function pointer that will build the message - assemble := func(rid id.Round) ([]byte, error) { - - //Build the message - chMsg := &ChannelMessage{ - Lease: validUntil.Nanoseconds(), - RoundID: uint64(rid), - PayloadType: uint32(messageType), - Payload: msg, - } - - //Serialize the message - chMsgSerial, err := proto.Marshal(chMsg) - if err != nil { - return nil, err - } - - //Sign the message - messageSig, err := m.name.SignChannelMessage(chMsgSerial) - if err != nil { - return nil, err - } - - //Build the user message - validationSig, unameLease := m.name.GetChannelValidationSignature() - - usrMsg := &UserMessage{ - Message: chMsgSerial, - ValidationSignature: validationSig, - Signature: messageSig, - Username: m.name.GetUsername(), - ECCPublicKey: m.name.GetChannelPubkey(), - UsernameLease: unameLease.Unix(), - } - - //Serialize the user message - usrMsgSerial, err := proto.Marshal(usrMsg) - if err != nil { - return nil, err - } - - //Fill in any extra bits in the payload to ensure it is the right size - usrMsgSerialSized, err := broadcast.NewSizedBroadcast( - ch.broadcast.MaxAsymmetricPayloadSize(), usrMsgSerial) - if err != nil { - return nil, err - } - - msgId = cryptoChannel.MakeMessageID(usrMsgSerialSized) - - return usrMsgSerialSized, nil - } - //TODO: send the send message over to reception manually so it is added to - //the database early - rid, ephid, err := ch.broadcast.BroadcastWithAssembler(assemble, params) - return msgId, rid, ephid, err + // make the function used to create broadcasts be a pointer so it + // can be replaced in tests + broadcastMaker broadcast.NewBroadcastChannelFunc } -func (m *manager) SendAdminGeneric(privKey *rsa.PrivateKey, channelID *id.ID, - msg []byte, validUntil time.Duration, messageType MessageType, - params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, - error) { - - //find the channel - ch, err := m.getChannel(channelID) - if err != nil { - return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err - } - - var msgId cryptoChannel.MessageID - //Note: we are not checking check if message is too long before trying to - //find a round - - //Build the function pointer that will build the message - assemble := func(rid id.Round) ([]byte, error) { - - //Build the message - chMsg := &ChannelMessage{ - Lease: validUntil.Nanoseconds(), - RoundID: uint64(rid), - PayloadType: uint32(messageType), - Payload: msg, - } - - //Serialize the message - chMsgSerial, err := proto.Marshal(chMsg) - if err != nil { - return nil, err - } - - //check if the message is too long - if len(chMsgSerial) > broadcast.MaxSizedBroadcastPayloadSize(privKey.Size()) { - return nil, MessageTooLongErr - } - - //Fill in any extra bits in the payload to ensure it is the right size - chMsgSerialSized, err := broadcast.NewSizedBroadcast( - ch.broadcast.MaxAsymmetricPayloadSize(), chMsgSerial) - if err != nil { - return nil, err - } - - msgId = cryptoChannel.MakeMessageID(chMsgSerialSized) +func NewManager(kv *versioned.KV, client broadcast.Client, + rng *fastRNG.StreamGenerator, name NameService) Manager { - return chMsgSerialSized, nil - } - - //TODO: send the send message over to reception manually so it is added to - //the database early - rid, ephid, err := ch.broadcast.BroadcastAsymmetricWithAssembler(privKey, - assemble, params) - return msgId, rid, ephid, err -} + //prefix the kv with the username so multiple can be run + kv = kv.Prefix(name.GetUsername()) -func (m *manager) SendMessage(channelID *id.ID, msg string, - validUntil time.Duration, params cmix.CMIXParams) ( - cryptoChannel.MessageID, id.Round, ephemeral.Id, error) { - txt := &CMIXChannelText{ - Version: cmixChannelTextVerion, - Text: msg, - ReplyMessageID: nil, + m := manager{ + kv: kv, + client: client, + rng: rng, + name: name, + events: events{}, + broadcastMaker: broadcast.NewBroadcastChannel, } - txtMarshaled, err := proto.Marshal(txt) - if err != nil { - return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err - } + m.loadChannels() - return m.SendGeneric(channelID, txtMarshaled, validUntil, Text, params) + return &m } -func (m *manager) SendReply(channelID *id.ID, msg string, - replyTo cryptoChannel.MessageID, validUntil time.Duration, - params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, - error) { - txt := &CMIXChannelText{ - Version: cmixChannelTextVerion, - Text: msg, - ReplyMessageID: replyTo[:], - } - - txtMarshaled, err := proto.Marshal(txt) +func (m *manager) JoinChannel(channel cryptoBroadcast.Channel) error { + err := m.addChannel(channel) if err != nil { - return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + return err } - - return m.SendGeneric(channelID, txtMarshaled, validUntil, Text, params) + go m.events.model.JoinChannel(channel) + return nil } -func (m *manager) SendReaction(channelID *id.ID, msg string, - replyTo cryptoChannel.MessageID, validUntil time.Duration, - params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, - error) { - txt := &CMIXChannelText{ - Version: cmixChannelTextVerion, - Text: msg, - ReplyMessageID: replyTo[:], - } - - txtMarshaled, err := proto.Marshal(txt) +func (m *manager) LeaveChannel(channelId *id.ID) error { + err := m.removeChannel(channelId) if err != nil { - return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + return err } - - return m.SendGeneric(channelID, txtMarshaled, validUntil, Text, params) + go m.events.model.LeaveChannel(channelId) + return nil } diff --git a/channels/send.go b/channels/send.go index d72c017e84aa8688cf59f7a04353b0f96da67b06..86b34441aabd3e6a3d9a657cad2a177ae542b07d 100644 --- a/channels/send.go +++ b/channels/send.go @@ -1 +1,204 @@ package channels + +import ( + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix" + cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "google.golang.org/protobuf/proto" + "time" +) + +const ( + cmixChannelTextVersion = 0 + cmixChannelReactionVersion = 0 +) + +func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType, + msg []byte, validUntil time.Duration, params cmix.CMIXParams) ( + cryptoChannel.MessageID, id.Round, ephemeral.Id, error) { + + //find the channel + ch, err := m.getChannel(channelID) + if err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + var msgId cryptoChannel.MessageID + //Note: we are not checking check if message is too long before trying to + //find a round + + //Build the function pointer that will build the message + assemble := func(rid id.Round) ([]byte, error) { + + //Build the message + chMsg := &ChannelMessage{ + Lease: validUntil.Nanoseconds(), + RoundID: uint64(rid), + PayloadType: uint32(messageType), + Payload: msg, + } + + //Serialize the message + chMsgSerial, err := proto.Marshal(chMsg) + if err != nil { + return nil, err + } + + //Sign the message + messageSig, err := m.name.SignChannelMessage(chMsgSerial) + if err != nil { + return nil, err + } + + //Build the user message + validationSig, unameLease := m.name.GetChannelValidationSignature() + + usrMsg := &UserMessage{ + Message: chMsgSerial, + ValidationSignature: validationSig, + Signature: messageSig, + Username: m.name.GetUsername(), + ECCPublicKey: m.name.GetChannelPubkey(), + UsernameLease: unameLease.Unix(), + } + + //Serialize the user message + usrMsgSerial, err := proto.Marshal(usrMsg) + if err != nil { + return nil, err + } + + //Fill in any extra bits in the payload to ensure it is the right size + usrMsgSerialSized, err := broadcast.NewSizedBroadcast( + ch.broadcast.MaxAsymmetricPayloadSize(), usrMsgSerial) + if err != nil { + return nil, err + } + + msgId = cryptoChannel.MakeMessageID(usrMsgSerialSized) + + return usrMsgSerialSized, nil + } + + //TODO: send the send message over to reception manually so it is added to + //the database early + rid, ephid, err := ch.broadcast.BroadcastWithAssembler(assemble, params) + return msgId, rid, ephid, err +} + +func (m *manager) SendAdminGeneric(privKey *rsa.PrivateKey, channelID *id.ID, + msg []byte, validUntil time.Duration, messageType MessageType, + params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, + error) { + + //find the channel + ch, err := m.getChannel(channelID) + if err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + var msgId cryptoChannel.MessageID + //Note: we are not checking check if message is too long before trying to + //find a round + + //Build the function pointer that will build the message + assemble := func(rid id.Round) ([]byte, error) { + + //Build the message + chMsg := &ChannelMessage{ + Lease: validUntil.Nanoseconds(), + RoundID: uint64(rid), + PayloadType: uint32(messageType), + Payload: msg, + } + + //Serialize the message + chMsgSerial, err := proto.Marshal(chMsg) + if err != nil { + return nil, err + } + + //check if the message is too long + if len(chMsgSerial) > broadcast.MaxSizedBroadcastPayloadSize(privKey.Size()) { + return nil, MessageTooLongErr + } + + //Fill in any extra bits in the payload to ensure it is the right size + chMsgSerialSized, err := broadcast.NewSizedBroadcast( + ch.broadcast.MaxAsymmetricPayloadSize(), chMsgSerial) + if err != nil { + return nil, err + } + + msgId = cryptoChannel.MakeMessageID(chMsgSerialSized) + + return chMsgSerialSized, nil + } + + //TODO: send the send message over to reception manually so it is added to + //the database early + rid, ephid, err := ch.broadcast.BroadcastAsymmetricWithAssembler(privKey, + assemble, params) + return msgId, rid, ephid, err +} + +func (m *manager) SendMessage(channelID *id.ID, msg string, + validUntil time.Duration, params cmix.CMIXParams) ( + cryptoChannel.MessageID, id.Round, ephemeral.Id, error) { + txt := &CMIXChannelText{ + Version: cmixChannelTextVersion, + Text: msg, + ReplyMessageID: nil, + } + + txtMarshaled, err := proto.Marshal(txt) + if err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + return m.SendGeneric(channelID, Text, txtMarshaled, validUntil, params) +} + +func (m *manager) SendReply(channelID *id.ID, msg string, + replyTo cryptoChannel.MessageID, validUntil time.Duration, + params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, + error) { + txt := &CMIXChannelText{ + Version: cmixChannelTextVersion, + Text: msg, + ReplyMessageID: replyTo[:], + } + + txtMarshaled, err := proto.Marshal(txt) + if err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + return m.SendGeneric(channelID, Text, txtMarshaled, validUntil, params) +} + +func (m *manager) SendReaction(channelID *id.ID, reaction string, + replyTo cryptoChannel.MessageID, validUntil time.Duration, + params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, + error) { + + if err := ValidateReaction(reaction); err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + react := &CMIXChannelReaction{ + Version: cmixChannelReactionVersion, + Reaction: reaction, + ReactionMessageID: replyTo[:], + } + + reactMarshaled, err := proto.Marshal(react) + if err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + return m.SendGeneric(channelID, Reaction, reactMarshaled, validUntil, params) +} diff --git a/channels/text.pb.go b/channels/text.pb.go index ebada41edb208d4e5534f55a96831dedd407bd06..20dcb07ee9307c6d66aeaa3b5b5f118fc96d439b 100644 --- a/channels/text.pb.go +++ b/channels/text.pb.go @@ -96,7 +96,7 @@ type CMIXChannelReaction struct { unknownFields protoimpl.UnknownFields Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` - Reaction uint32 `protobuf:"varint,2,opt,name=reaction,proto3" json:"reaction,omitempty"` + Reaction string `protobuf:"bytes,2,opt,name=reaction,proto3" json:"reaction,omitempty"` ReactionMessageID []byte `protobuf:"bytes,3,opt,name=reactionMessageID,proto3" json:"reactionMessageID,omitempty"` } @@ -139,11 +139,11 @@ func (x *CMIXChannelReaction) GetVersion() uint32 { return 0 } -func (x *CMIXChannelReaction) GetReaction() uint32 { +func (x *CMIXChannelReaction) GetReaction() string { if x != nil { return x.Reaction } - return 0 + return "" } func (x *CMIXChannelReaction) GetReactionMessageID() []byte { @@ -167,7 +167,7 @@ var file_text_proto_rawDesc = []byte{ 0x43, 0x4d, 0x49, 0x58, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, - 0x08, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x08, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x11, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, diff --git a/channels/text.proto b/channels/text.proto index 4e1ad86791428f0d64e92b5bbb1d35d7b02567c4..c2d15310a23b0f293af9f3f2cad7e07332269290 100644 --- a/channels/text.proto +++ b/channels/text.proto @@ -18,6 +18,6 @@ message CMIXChannelText { message CMIXChannelReaction { uint32 version = 1; - uint32 reaction = 2; + string reaction = 2; bytes reactionMessageID = 3; } \ No newline at end of file diff --git a/go.mod b/go.mod index e75c992bdbd5521e90da2f5193ab230c75279661..0056221fb2d42b9da6cb80e93398ff5cf7e764a0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.17 require ( github.com/cloudflare/circl v1.2.0 + github.com/forPelevin/gomoji v1.1.6 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang/protobuf v1.5.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 @@ -47,6 +48,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.3.4 // indirect github.com/rs/cors v1.7.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/soheilhy/cmux v0.1.5 // indirect diff --git a/go.sum b/go.sum index 694614daf59cbc272114c5a9e4d535951e94677b..3be7615d55cf402389fc711faa2bde3ff66f280b 100644 --- a/go.sum +++ b/go.sum @@ -156,6 +156,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/forPelevin/gomoji v1.1.6 h1:mSIGhjyMiywuGFHR/6CLL/L6HwwDiQmYGdl1R9a/05w= +github.com/forPelevin/gomoji v1.1.6/go.mod h1:h31zCiwG8nIto/c9RmijODA1xgN2JSvwKfU7l65xeTk= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= @@ -525,6 +527,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= +github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=