Skip to content
Snippets Groups Projects
Select Git revision
  • 374093a51a9350bb7032a9730f1812b028f3ff27
  • release default protected
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • hotfix/TestHostPool_UpdateNdf_AddFilter
  • XX-4719/announcementChannels
  • xx-4717/logLevel
  • jonah/noob-channel
  • master protected
  • XX-4707/tagDiskJson
  • xx-4698/notification-retry
  • hotfix/notifylockup
  • syncNodes
  • hotfix/localCB
  • XX-4677/NewChanManagerMobile
  • XX-4689/DmSync
  • duplicatePrefix
  • XX-4601/HavenInvites
  • finalizedUICallbacks
  • XX-4673/AdminKeySync
  • debugNotifID
  • anne/test
  • v4.7.5
  • v4.7.4
  • v4.7.3
  • v4.7.2
  • v4.7.1
  • v4.6.3
  • v4.6.1
  • v4.5.0
  • v4.4.4
  • v4.3.11
  • v4.3.8
  • v4.3.7
  • v4.3.6
  • v4.3.5
  • v4.2.0
  • v4.3.0
  • v4.3.4
  • v4.3.3
  • v4.3.2
  • v4.3.1
41 results

makeGroup.go

Blame
  • user avatar
    Benjamin Wenger authored
    735f7dd2
    History
    makeGroup.go 6.84 KiB
    ///////////////////////////////////////////////////////////////////////////////
    // Copyright © 2020 xx network SEZC                                          //
    //                                                                           //
    // 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"
    	gs "gitlab.com/elixxir/client/groupChat/groupStore"
    	"gitlab.com/elixxir/client/interfaces/preimage"
    	"gitlab.com/elixxir/client/storage/edge"
    	"gitlab.com/elixxir/crypto/contact"
    	"gitlab.com/elixxir/crypto/fastRNG"
    	"gitlab.com/elixxir/crypto/group"
    	"gitlab.com/xx_network/primitives/id"
    	"gitlab.com/xx_network/primitives/netTime"
    	"strconv"
    )
    
    // Error messages.
    const (
    	maxInitMsgSizeErr = "new group request message length %d > %d maximum size"
    	getPrivKeyErr     = "failed to get private key from partner: %+v"
    	minMembersErr     = "length of membership list %d < %d minimum allowed"
    	maxMembersErr     = "length of membership list %d > %d maximum allowed"
    	getPartnerErr     = "failed to get partner %s: %+v"
    	makeMembershipErr = "failed to assemble group chat membership: %+v"
    	newIdPreimageErr  = "failed to create group ID preimage: %+v"
    	newKeyPreimageErr = "failed to create group key preimage: %+v"
    	addGroupErr       = "failed to save new group: %+v"
    )
    
    // MaxInitMessageSize is the maximum allowable length of the initial message
    // sent in a group request.
    const MaxInitMessageSize = 256
    
    // RequestStatus signals the status of the group requests on group creation.
    type RequestStatus int
    
    const (
    	NotSent     RequestStatus = iota // Error occurred before sending requests
    	AllFail                          // Sending of all requests failed
    	PartialSent                      // Sending of some request failed
    	AllSent                          // Sending of all request succeeded
    )
    
    // MakeGroup sends groupChat requests to all members over an authenticated
    // channel. The leader of a groupChat must have an authenticated channel with
    // each member of the groupChat to add them to the groupChat. It blocks until
    // all the groupChat requests are sent. Returns an error if at least one request
    // to a member fails to send.
    func (m Manager) MakeGroup(membership []*id.ID, name, msg []byte) (gs.Group,
    	[]id.Round, RequestStatus, error) {
    	// Return an error if the message is too long
    	if len(msg) > MaxInitMessageSize {
    		return gs.Group{}, nil, NotSent,
    			errors.Errorf(maxInitMsgSizeErr, len(msg), MaxInitMessageSize)
    	}
    
    	// Build membership and DH key list from list of IDs
    	mem, dkl, err := m.buildMembership(membership)
    	if err != nil {
    		return gs.Group{}, nil, NotSent, err
    	}
    
    	// Generate ID and key preimages
    	idPreimage, keyPreimage, err := getPreimages(m.rng)
    	if err != nil {
    		return gs.Group{}, nil, NotSent, err
    	}
    
    	// Create new group ID and key
    	groupID := group.NewID(idPreimage, mem)
    	groupKey := group.NewKey(keyPreimage, mem)
    
    	// Generate group creation timestamp stripped of the monotonic clock
    	created := netTime.Now().Round(0)
    
    	// Create new group and add to manager
    	g := gs.NewGroup(
    		name, groupID, groupKey, idPreimage, keyPreimage, msg, created, mem, dkl)
    	if err = m.gs.Add(g); err != nil {
    		return gs.Group{}, nil, NotSent, errors.Errorf(addGroupErr, err)
    	}
    
    	jww.DEBUG.Printf("Created new group %q with ID %s and %d members %s",
    		g.Name, g.ID, len(g.Members), g.Members)
    
    	// Send all group requests
    	roundIDs, status, err := m.sendRequests(g)
    
    	if err == nil {
    		edgeStore := m.store.GetEdge()
    		edgeStore.Add(edge.Preimage{
    			Data:   g.ID[:],
    			Type:   preimage.Group,
    			Source: g.ID[:],
    		}, m.store.GetUser().ReceptionID)
    	}
    
    	return g, roundIDs, status, err
    }
    
    // buildMembership retrieves the contact object for each member ID and creates a
    // new membership from them. The caller is set as the leader. For a member to be
    // added, the group leader must have an authenticated channel with the member.
    func (m Manager) buildMembership(members []*id.ID) (group.Membership,
    	gs.DhKeyList, error) {
    	// Return an error if the membership list has too few or too many members
    	if len(members) < group.MinParticipants {
    		return nil, nil,
    			errors.Errorf(minMembersErr, len(members), group.MinParticipants)
    	} else if len(members) > group.MaxParticipants {
    		return nil, nil,
    			errors.Errorf(maxMembersErr, len(members), group.MaxParticipants)
    	}
    
    	grp := m.store.E2e().GetGroup()
    	dkl := make(gs.DhKeyList, len(members))
    
    	// Lookup partner contact objects from their ID
    	contacts := make([]contact.Contact, len(members))
    	var err error
    	for i, uid := range members {
    		partner, err := m.store.E2e().GetPartner(uid)
    		if err != nil {
    			return nil, nil, errors.Errorf(getPartnerErr, uid, err)
    		}
    
    		contacts[i] = contact.Contact{
    			ID:       partner.GetPartnerID(),
    			DhPubKey: partner.GetPartnerOriginPublicKey(),
    		}
    
    		dkl.Add(partner.GetMyOriginPrivateKey(), group.Member{
    			ID:    partner.GetPartnerID(),
    			DhKey: partner.GetPartnerOriginPublicKey(),
    		}, grp)
    	}
    
    	// Create new Membership from contact list and client's own contact.
    	user := m.gs.GetUser()
    	leader := contact.Contact{ID: user.ID, DhPubKey: user.DhKey}
    	mem, err := group.NewMembership(leader, contacts...)
    	if err != nil {
    		return nil, nil, errors.Errorf(makeMembershipErr, err)
    	}
    
    	return mem, dkl, nil
    }
    
    // getPreimages generates and returns the group ID preimage and the group key
    // preimage. This function allows the stream to
    func getPreimages(streamGen *fastRNG.StreamGenerator) (group.IdPreimage,
    	group.KeyPreimage, error) {
    
    	// get new stream and defer its close
    	rng := streamGen.GetStream()
    	defer rng.Close()
    
    	idPreimage, err := group.NewIdPreimage(rng)
    	if err != nil {
    		return group.IdPreimage{}, group.KeyPreimage{},
    			errors.Errorf(newIdPreimageErr, err)
    	}
    
    	keyPreimage, err := group.NewKeyPreimage(rng)
    	if err != nil {
    		return group.IdPreimage{}, group.KeyPreimage{},
    			errors.Errorf(newKeyPreimageErr, err)
    	}
    
    	return idPreimage, keyPreimage, nil
    }
    
    // String prints the description of the status code. This functions satisfies
    // the fmt.Stringer interface.
    func (rs RequestStatus) String() string {
    	switch rs {
    	case NotSent:
    		return "NotSent"
    	case AllFail:
    		return "AllFail"
    	case PartialSent:
    		return "PartialSent"
    	case AllSent:
    		return "AllSent"
    	default:
    		return "INVALID STATUS"
    	}
    }
    
    // Message prints a full description of the status code.
    func (rs RequestStatus) Message() string {
    	switch rs {
    	case NotSent:
    		return "an error occurred before sending any group requests"
    	case AllFail:
    		return "all group requests failed to send"
    	case PartialSent:
    		return "some group requests failed to send"
    	case AllSent:
    		return "all groups requests successfully sent"
    	default:
    		return "INVALID STATUS " + strconv.Itoa(int(rs))
    	}
    }