Skip to content
Snippets Groups Projects
Select Git revision
  • d1d07c4b23221e07b7e89c53d3311d2f9e60f335
  • release default
  • master protected
  • feature/xx-4717/logLevel
  • XX-4626/SingleUsePackage
  • josh/DmPackage
  • xx-4437/no-registration
  • feature/project/DM
  • project/channels
  • feature/ctidh
  • Jakub/rootless-CI
  • jono/wasmDemo
  • feature/XX-4108/updateProtoc
  • feature/hotfix/unsafe_send_to_self
  • Anne/OldSessionTesting
  • hotfix/groupChat
  • josh/groupCreationScript
  • feature/XX-3797/restore
  • feature/XX-3789/DeleteIndividualRequests
  • dev
  • feature/debugSendCMIX
21 results

update.sh

Blame
  • send.go 7.61 KiB
    ////////////////////////////////////////////////////////////////////////////////
    // Copyright © 2022 xx foundation                                             //
    //                                                                            //
    // Use of this source code is governed by a license that can be found in the  //
    // LICENSE file.                                                              //
    ////////////////////////////////////////////////////////////////////////////////
    
    package groupChat
    
    import (
    	"github.com/pkg/errors"
    	jww "github.com/spf13/jwalterweatherman"
    	"gitlab.com/elixxir/client/cmix"
    	"gitlab.com/elixxir/client/cmix/message"
    	"gitlab.com/elixxir/client/cmix/rounds"
    	gs "gitlab.com/elixxir/client/groupChat/groupStore"
    	"gitlab.com/elixxir/crypto/cyclic"
    	"gitlab.com/elixxir/crypto/group"
    	"gitlab.com/elixxir/primitives/format"
    	"gitlab.com/xx_network/primitives/id"
    	"gitlab.com/xx_network/primitives/netTime"
    	"io"
    	"time"
    )
    
    // Error messages.
    const (
    
    	// manager.Send
    	newNoGroupErr   = "no group found with ID %s"
    	newCmixMsgErr   = "failed to generate cMix messages for group chat %q (%s): %+v"
    	sendManyCmixErr = "failed to send group chat message from member %s to group %q (%s): %+v"
    
    	// newCmixMsg
    	messageLenErr = "message length %d is greater than maximum payload size %d"
    	newKeyErr     = "failed to generate key for encrypting group payload"
    
    	// newMessageParts
    	newPublicMsgErr   = "failed to create new public group message for cMix message: %+v"
    	newInternalMsgErr = "failed to create new internal group message for cMix message: %+v"
    
    	// newSalt
    	saltReadErr       = "failed to generate salt for group message: %+v"
    	saltReadLengthErr = "length of generated salt %d != %d required"
    )
    
    // Send sends a message to all group members using Cmix.SendMany.
    // The send fails if the message is too long.
    func (m *manager) Send(groupID *id.ID, tag string, message []byte) (
    	rounds.Round, time.Time, group.MessageID, error) {
    
    	if tag == "" {
    		tag = defaultServiceTag
    	}
    
    	// Get the relevant group
    	g, exists := m.GetGroup(groupID)
    	if !exists {
    		return rounds.Round{}, time.Time{}, group.MessageID{},
    			errors.Errorf(newNoGroupErr, groupID)
    	}
    
    	// Get the current time stripped of the monotonic clock
    	timeNow := netTime.Now().Round(0)
    
    	// Create a cMix message for each group member
    	groupMessages, err := m.newMessages(g, tag, message, timeNow)
    	if err != nil {
    		return rounds.Round{}, time.Time{}, group.MessageID{},
    			errors.Errorf(newCmixMsgErr, g.Name, g.ID, err)
    	}
    
    	// Obtain message ID
    	msgId, err := getGroupMessageId(
    		m.getE2eGroup(), groupID, m.getReceptionIdentity().ID, timeNow, message)
    	if err != nil {
    		return rounds.Round{}, time.Time{}, group.MessageID{}, err
    	}
    
    	// Send all the groupMessages
    	param := cmix.GetDefaultCMIXParams()
    	param.DebugTag = "group.Message"
    	rid, _, err := m.getCMix().SendMany(groupMessages, param)
    	if err != nil {
    		return rounds.Round{}, time.Time{}, group.MessageID{},
    			errors.Errorf(sendManyCmixErr, m.getReceptionIdentity().ID, g.Name, g.ID, err)
    	}
    
    	jww.DEBUG.Printf("[GC] Sent message to %d members in group %s at %s.",
    		len(groupMessages), groupID, timeNow)
    	return rid, timeNow, msgId, nil
    }
    
    // newMessages builds a list of messages, one for each group chat member.
    func (m *manager) newMessages(g gs.Group, tag string, msg []byte,
    	timestamp time.Time) ([]cmix.TargetedCmixMessage, error) {
    
    	// Create list of cMix messages
    	messages := make([]cmix.TargetedCmixMessage, 0, len(g.Members))
    	rng := m.getRng().GetStream()
    	defer rng.Close()
    
    	// Create cMix messages in parallel
    	for _, member := range g.Members {
    		// Do not send to the sender
    		if m.getReceptionIdentity().ID.Cmp(member.ID) {
    			continue
    		}
    
    		// Add cMix message to list
    		cMixMsg, err := newCmixMsg(g, tag, msg, timestamp, member, rng,
    			m.getReceptionIdentity().ID, m.getCMix().GetMaxMessageLength())
    		if err != nil {
    			return nil, err
    		}
    		messages = append(messages, cMixMsg)
    	}
    
    	return messages, nil
    }
    
    // newCmixMsg generates a new cmix.TargetedCmixMessage for the given group
    // member
    func newCmixMsg(g gs.Group, tag string, msg []byte, timestamp time.Time,
    	mem group.Member, rng io.Reader, senderId *id.ID, maxCmixMessageSize int) (
    	cmix.TargetedCmixMessage, error) {
    
    	// Initialize targeted message
    	cmixMsg := cmix.TargetedCmixMessage{
    		Recipient: mem.ID,
    		Service: message.Service{
    			Identifier: g.ID[:],
    			Tag:        makeServiceTag(tag),
    			Metadata:   g.ID[:],
    		},
    	}
    
    	// Create three message layers
    	pubMsg, intlMsg, err := newMessageParts(maxCmixMessageSize)
    	if err != nil {
    		return cmixMsg, err
    	}
    
    	// Return an error if the message is too large to fit in the payload
    	if intlMsg.GetPayloadMaxSize() < len(msg) {
    		return cmixMsg, errors.Errorf(
    			messageLenErr, len(msg), intlMsg.GetPayloadMaxSize())
    	}
    
    	// Generate 256-bit salt
    	salt, err := newSalt(rng)
    	if err != nil {
    		return cmixMsg, err
    	}
    
    	// Generate key fingerprint
    	cmixMsg.Fingerprint = group.NewKeyFingerprint(g.Key, salt, mem.ID)
    
    	// Generate key
    	key, err := group.NewKdfKey(g.Key, group.ComputeEpoch(timestamp), salt)
    	if err != nil {
    		return cmixMsg, errors.WithMessage(err, newKeyErr)
    	}
    
    	// Generate internal message
    	payload := setInternalPayload(intlMsg, timestamp, senderId, msg)
    
    	// Encrypt internal message
    	encryptedPayload := group.Encrypt(key, cmixMsg.Fingerprint, payload)
    
    	// Generate public message
    	cmixMsg.Payload = setPublicPayload(pubMsg, salt, encryptedPayload)
    
    	// Generate MAC
    	cmixMsg.Mac = group.NewMAC(key, encryptedPayload, g.DhKeys[*mem.ID])
    
    	return cmixMsg, nil
    }
    
    // getGroupMessageId builds the group message ID.
    func getGroupMessageId(grp *cyclic.Group, groupId, senderId *id.ID,
    	timestamp time.Time, msg []byte) (group.MessageID, error) {
    	cmixMsg := format.NewMessage(grp.GetP().ByteLen())
    	_, intlMsg, err := newMessageParts(cmixMsg.ContentsSize())
    	if err != nil {
    		return group.MessageID{}, errors.WithMessage(err,
    			"Failed to make message parts for message ID")
    	}
    	return group.NewMessageID(groupId,
    		setInternalPayload(intlMsg, timestamp, senderId, msg)), nil
    }
    
    // newMessageParts generates a public payload message and the internal payload
    // message. An error is returned if the messages cannot fit in the payloadSize.
    func newMessageParts(payloadSize int) (publicMsg, internalMsg, error) {
    	pubMsg, err := newPublicMsg(payloadSize)
    	if err != nil {
    		return pubMsg, internalMsg{}, errors.Errorf(newPublicMsgErr, err)
    	}
    
    	intlMsg, err := newInternalMsg(pubMsg.GetPayloadSize())
    	if err != nil {
    		return pubMsg, intlMsg, errors.Errorf(newInternalMsgErr, err)
    	}
    
    	return pubMsg, intlMsg, nil
    }
    
    // newSalt generates a new salt of the specified size.
    func newSalt(rng io.Reader) ([group.SaltLen]byte, error) {
    	var salt [group.SaltLen]byte
    	n, err := rng.Read(salt[:])
    	if err != nil {
    		return salt, errors.Errorf(saltReadErr, err)
    	} else if n != group.SaltLen {
    		return salt, errors.Errorf(saltReadLengthErr, group.SaltLen, n)
    	}
    
    	return salt, nil
    }
    
    // setInternalPayload sets the timestamp, sender ID, and message of the
    // internalMsg and returns the marshal bytes.
    func setInternalPayload(internalMsg internalMsg, timestamp time.Time,
    	sender *id.ID, msg []byte) []byte {
    	// Set timestamp, sender ID, and message to the internalMsg
    	internalMsg.SetTimestamp(timestamp)
    	internalMsg.SetSenderID(sender)
    	internalMsg.SetPayload(msg)
    
    	// Return the payload marshaled
    	return internalMsg.Marshal()
    }
    
    // setPublicPayload sets the salt and encrypted payload of the publicMsg and
    // returns the marshal bytes.
    func setPublicPayload(publicMsg publicMsg, salt [group.SaltLen]byte,
    	encryptedPayload []byte) []byte {
    	// Set salt and payload
    	publicMsg.SetSalt(salt)
    	publicMsg.SetPayload(encryptedPayload)
    
    	return publicMsg.Marshal()
    }