diff --git a/bindings/group.go b/bindings/group.go index 343bbe7758240f28cfdb124907e0505a3b9cab4e..af9e5d204a941f945bb869aa975a8966699e4a78 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -104,14 +104,14 @@ func (g *GroupChat) LeaveGroup(groupIdBytes []byte) error { // Send sends the message to the specified group. Returns the round the messages // were sent on. -func (g *GroupChat) Send(groupIdBytes, message []byte) (int64, error) { +func (g *GroupChat) Send(groupIdBytes, message []byte) (*GroupSendReport, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { - return 0, errors.Errorf("Failed to unmarshal group ID: %+v", err) + return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err) } - round, err := g.m.Send(groupID, message) - return int64(round), err + round, timestamp, err := g.m.Send(groupID, message) + return &GroupSendReport{round, timestamp}, err } // GetGroups returns an IdList containing a list of group IDs that the user is a @@ -141,6 +141,10 @@ func (g *GroupChat) NumGroups() int { return g.m.NumGroups() } +//// +// NewGroupReport Structure +//// + // NewGroupReport is returned when creating a new group and contains the ID of // the group, a list of rounds that the group requests were sent on, and the // status of the send. @@ -170,6 +174,28 @@ func (ngr *NewGroupReport) GetStatus() int { return int(ngr.status) } +//// +// NewGroupReport Structure +//// + +// GroupSendReport is returned when sending a group message. It contains the +// round ID sent on and the timestamp of the send. +type GroupSendReport struct { + roundID id.Round + timestamp time.Time +} + +// GetRoundID returns the ID of the round that the send occurred on. +func (gsr *GroupSendReport) GetRoundID() int64 { + return int64(gsr.roundID) +} + +// GetTimestampMS returns the timestamp of the send in milliseconds. +func (gsr *GroupSendReport) GetTimestampMS() int64 { + ts := uint64(gsr.timestamp.UnixNano()) / uint64(time.Millisecond) + return int64(ts) +} + //// // Group Structure //// diff --git a/cmd/group.go b/cmd/group.go index ca457c65b84f68f87e784d3cec98bc956b32094c..b973fb5c7f2b7addcb12a26c2a59b7b4c1524c22 100644 --- a/cmd/group.go +++ b/cmd/group.go @@ -218,12 +218,13 @@ func sendGroup(groupIdString string, msg []byte, gm *groupChat.Manager) { jww.INFO.Printf("Sending to group %s message %q", groupID, msg) - rid, err := gm.Send(groupID, msg) + rid, timestamp, err := gm.Send(groupID, msg) if err != nil { jww.FATAL.Panicf("Sending message to group %s: %+v", groupID, err) } - jww.INFO.Printf("Sent to group %s on round %d", groupID, rid) + jww.INFO.Printf("Sent to group %s on round %d at %s", + groupID, rid, timestamp) fmt.Printf("Sent message %q to group.\n", msg) } diff --git a/groupChat/group.go b/groupChat/group.go index 5d67d19f1e52f80cf1628f2deece083b4d8f4568..8878a62cdf750823bd4c886fbfbfdca1ac81c13d 100644 --- a/groupChat/group.go +++ b/groupChat/group.go @@ -22,6 +22,7 @@ package groupChat import ( gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/xx_network/primitives/id" + "time" ) // GroupChat is used to send and receive cMix messages to/from multiple users. @@ -49,8 +50,9 @@ type GroupChat interface { LeaveGroup(groupID *id.ID) error // Send sends a message to all GroupChat members using Client.SendManyCMIX. - // The send fails if the message is too long. - Send(groupID *id.ID, message []byte) (id.Round, error) + // The send fails if the message is too long. Returns the ID of the round + // sent on and the timestamp of the message send. + Send(groupID *id.ID, message []byte) (id.Round, time.Time, error) // GetGroups returns a list of all registered GroupChat IDs. GetGroups() []*id.ID diff --git a/groupChat/send.go b/groupChat/send.go index ca25cfe604145224d12fd0651b7a0404375baee0..0ac72527b60e535b07fd553f4d7a2b386e578d32 100644 --- a/groupChat/send.go +++ b/groupChat/send.go @@ -36,36 +36,38 @@ const ( // Send sends a message to all group members using Client.SendManyCMIX. The // send fails if the message is too long. -func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, error) { +func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, time.Time, + error) { + timeNow := netTime.Now() // Create a cMix message for each group member - messages, err := m.createMessages(groupID, message) + messages, err := m.createMessages(groupID, message, timeNow) if err != nil { - return 0, errors.Errorf(newCmixMsgErr, err) + return 0, time.Time{}, errors.Errorf(newCmixMsgErr, err) } rid, _, err := m.net.SendManyCMIX(messages, params.GetDefaultCMIX()) if err != nil { - return 0, errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) + return 0, time.Time{}, + errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) } jww.DEBUG.Printf("Sent message to group %s.", groupID) - return rid, nil + return rid, timeNow, nil } // createMessages generates a list of cMix messages and a list of corresponding // recipient IDs. -func (m *Manager) createMessages(groupID *id.ID, msg []byte) ( - map[id.ID]format.Message, error) { - timeNow := netTime.Now() +func (m *Manager) createMessages(groupID *id.ID, msg []byte, + timestamp time.Time) (map[id.ID]format.Message, error) { g, exists := m.gs.Get(groupID) if !exists { return map[id.ID]format.Message{}, errors.Errorf(newNoGroupErr, groupID) } - return m.newMessages(g, msg, timeNow) + return m.newMessages(g, msg, timestamp) } // newMessages is a private function that allows the passing in of a timestamp diff --git a/groupChat/send_test.go b/groupChat/send_test.go index e9f794185429d91e544c8861cbb15660fa73ad56..395ffc3ee9e3b640b38e88e0b2e11331d9e8036d 100644 --- a/groupChat/send_test.go +++ b/groupChat/send_test.go @@ -29,7 +29,7 @@ func TestManager_Send(t *testing.T) { message := []byte("Group chat message.") sender := m.gs.GetUser().DeepCopy() - _, err := m.Send(g.ID, message) + _, _, err := m.Send(g.ID, message) if err != nil { t.Errorf("Send() returned an error: %+v", err) } @@ -109,7 +109,7 @@ func TestManager_Send_CmixMessageError(t *testing.T) { expectedErr := strings.SplitN(newCmixMsgErr, "%", 2)[0] // Send message - _, err := m.Send(g.ID, make([]byte, 400)) + _, _, err := m.Send(g.ID, make([]byte, 400)) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("Send() failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) @@ -124,7 +124,7 @@ func TestManager_Send_SendManyCMIXError(t *testing.T) { expectedErr := strings.SplitN(sendManyCmixErr, "%", 2)[0] // Send message - _, err := m.Send(g.ID, []byte("message")) + _, _, err := m.Send(g.ID, []byte("message")) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("Send() failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) @@ -144,7 +144,7 @@ func TestManager_createMessages(t *testing.T) { message := []byte("Test group message.") sender := m.gs.GetUser() - messages, err := m.createMessages(g.ID, message) + messages, err := m.createMessages(g.ID, message, netTime.Now()) if err != nil { t.Errorf("createMessages() returned an error: %+v", err) } @@ -204,7 +204,8 @@ func TestManager_createMessages_InvalidGroupIdError(t *testing.T) { m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t) // Read message and make sure the error is expected - _, err := m.createMessages(id.NewIdFromString("invalidID", id.Group, t), nil) + _, err := m.createMessages( + id.NewIdFromString("invalidID", id.Group, t), nil, time.Time{}) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("createMessages() did not return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err)