diff --git a/bindings/group.go b/bindings/group.go index e47cd9fc91c9631a2504bc88f68fec3eb2bfb07d..7e92f0ddbf31ac5dcc1fca582f9b8b77b6a10875 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -9,6 +9,7 @@ package bindings import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gc "gitlab.com/elixxir/client/groupChat" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/crypto/group" @@ -198,6 +199,10 @@ func (gsr *GroupSendReport) GetTimestampNano() int64 { // GetTimestampMS returns the timestamp of the send in milliseconds. func (gsr *GroupSendReport) GetTimestampMS() int64 { ts := uint64(gsr.timestamp.UnixNano()) / uint64(time.Millisecond) + + // TODO: remove the print below once debugging is done. + jww.DEBUG.Printf("Sent group message timestamp: %s", gsr.timestamp) + jww.DEBUG.Printf("Sent group message timestamp MS: %d", ts) return int64(ts) } @@ -226,6 +231,19 @@ func (g *Group) GetInitMessage() []byte { return g.g.InitMessage } +// GetCreatedNano returns the time the group was created in nanoseconds. This is +// also the time the group requests were sent. +func (g *Group) GetCreatedNano() int64 { + return g.g.Created.UnixNano() +} + +// GetCreatedMS returns the time the group was created in milliseconds. This is +// also the time the group requests were sent. +func (g *Group) GetCreatedMS() int64 { + ts := uint64(g.g.Created.UnixNano()) / uint64(time.Millisecond) + return int64(ts) +} + // GetMembership returns a list of contacts, one for each member in the group. // The list is in order; the first contact is the leader/creator of the group. // All subsequent members are ordered by their ID. @@ -329,6 +347,7 @@ func (gmr *GroupMessageReceive) GetTimestampNano() int64 { // GetTimestampMS returns the message timestamp in milliseconds. func (gmr *GroupMessageReceive) GetTimestampMS() int64 { + ts := uint64(gmr.Timestamp.UnixNano()) / uint64(time.Millisecond) return int64(ts) } diff --git a/groupChat/gcMessages.pb.go b/groupChat/gcMessages.pb.go index 37b23167e412866c8012fd9f8c95bd99adab88b3..d31c3001125f286af67fb1eacf6986b193513e7c 100644 --- a/groupChat/gcMessages.pb.go +++ b/groupChat/gcMessages.pb.go @@ -1,117 +1,201 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.3 // source: groupChat/gcMessages.proto package groupChat import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) // Request to join the group sent from leader to all members. type Request struct { - Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - IdPreimage []byte `protobuf:"bytes,2,opt,name=idPreimage,proto3" json:"idPreimage,omitempty"` - KeyPreimage []byte `protobuf:"bytes,3,opt,name=keyPreimage,proto3" json:"keyPreimage,omitempty"` - Members []byte `protobuf:"bytes,4,opt,name=members,proto3" json:"members,omitempty"` - Message []byte `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Request) Reset() { *m = Request{} } -func (m *Request) String() string { return proto.CompactTextString(m) } -func (*Request) ProtoMessage() {} -func (*Request) Descriptor() ([]byte, []int) { - return fileDescriptor_49d0b7a6ffb7e279, []int{0} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + IdPreimage []byte `protobuf:"bytes,2,opt,name=idPreimage,proto3" json:"idPreimage,omitempty"` + KeyPreimage []byte `protobuf:"bytes,3,opt,name=keyPreimage,proto3" json:"keyPreimage,omitempty"` + Members []byte `protobuf:"bytes,4,opt,name=members,proto3" json:"members,omitempty"` + Message []byte `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Created int64 `protobuf:"varint,6,opt,name=created,proto3" json:"created,omitempty"` } -func (m *Request) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Request.Unmarshal(m, b) -} -func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Request.Marshal(b, m, deterministic) -} -func (m *Request) XXX_Merge(src proto.Message) { - xxx_messageInfo_Request.Merge(m, src) +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_groupChat_gcMessages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Request) XXX_Size() int { - return xxx_messageInfo_Request.Size(m) + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Request) XXX_DiscardUnknown() { - xxx_messageInfo_Request.DiscardUnknown(m) + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_groupChat_gcMessages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Request proto.InternalMessageInfo +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_groupChat_gcMessages_proto_rawDescGZIP(), []int{0} +} -func (m *Request) GetName() []byte { - if m != nil { - return m.Name +func (x *Request) GetName() []byte { + if x != nil { + return x.Name } return nil } -func (m *Request) GetIdPreimage() []byte { - if m != nil { - return m.IdPreimage +func (x *Request) GetIdPreimage() []byte { + if x != nil { + return x.IdPreimage } return nil } -func (m *Request) GetKeyPreimage() []byte { - if m != nil { - return m.KeyPreimage +func (x *Request) GetKeyPreimage() []byte { + if x != nil { + return x.KeyPreimage } return nil } -func (m *Request) GetMembers() []byte { - if m != nil { - return m.Members +func (x *Request) GetMembers() []byte { + if x != nil { + return x.Members } return nil } -func (m *Request) GetMessage() []byte { - if m != nil { - return m.Message +func (x *Request) GetMessage() []byte { + if x != nil { + return x.Message } return nil } -func init() { - proto.RegisterType((*Request)(nil), "gcRequestMessages.Request") +func (x *Request) GetCreated() int64 { + if x != nil { + return x.Created + } + return 0 +} + +var File_groupChat_gcMessages_proto protoreflect.FileDescriptor + +var file_groupChat_gcMessages_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x68, 0x61, 0x74, 0x2f, 0x67, 0x63, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x67, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, + 0xad, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x64, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x6b, 0x65, 0x79, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6b, 0x65, 0x79, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x25, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6c, + 0x69, 0x78, 0x78, 0x69, 0x72, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x43, 0x68, 0x61, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } -func init() { - proto.RegisterFile("groupChat/gcMessages.proto", fileDescriptor_49d0b7a6ffb7e279) +var ( + file_groupChat_gcMessages_proto_rawDescOnce sync.Once + file_groupChat_gcMessages_proto_rawDescData = file_groupChat_gcMessages_proto_rawDesc +) + +func file_groupChat_gcMessages_proto_rawDescGZIP() []byte { + file_groupChat_gcMessages_proto_rawDescOnce.Do(func() { + file_groupChat_gcMessages_proto_rawDescData = protoimpl.X.CompressGZIP(file_groupChat_gcMessages_proto_rawDescData) + }) + return file_groupChat_gcMessages_proto_rawDescData } -var fileDescriptor_49d0b7a6ffb7e279 = []byte{ - // 186 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0x2f, 0xca, 0x2f, - 0x2d, 0x70, 0xce, 0x48, 0x2c, 0xd1, 0x4f, 0x4f, 0xf6, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, - 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4c, 0x4f, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, - 0x2e, 0x81, 0x49, 0x28, 0x4d, 0x66, 0xe4, 0x62, 0x87, 0x8a, 0x09, 0x09, 0x71, 0xb1, 0xe4, 0x25, - 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04, 0x81, 0xd9, 0x42, 0x72, 0x5c, 0x5c, 0x99, - 0x29, 0x01, 0x45, 0xa9, 0x99, 0xb9, 0x89, 0xe9, 0xa9, 0x12, 0x4c, 0x60, 0x19, 0x24, 0x11, 0x21, - 0x05, 0x2e, 0xee, 0xec, 0xd4, 0x4a, 0xb8, 0x02, 0x66, 0xb0, 0x02, 0x64, 0x21, 0x21, 0x09, 0x2e, - 0xf6, 0xdc, 0xd4, 0xdc, 0xa4, 0xd4, 0xa2, 0x62, 0x09, 0x16, 0xb0, 0x2c, 0x8c, 0x0b, 0x91, 0x01, - 0xbb, 0x43, 0x82, 0x15, 0x26, 0x03, 0xe6, 0x3a, 0xa9, 0x46, 0x29, 0xa7, 0x67, 0x96, 0xe4, 0x24, - 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xa7, 0xe6, 0x64, 0x56, 0x54, 0x64, 0x16, 0xe9, 0x27, 0xe7, - 0x64, 0xa6, 0xe6, 0x95, 0xe8, 0xc3, 0x3d, 0x98, 0xc4, 0x06, 0xf6, 0x96, 0x31, 0x20, 0x00, 0x00, - 0xff, 0xff, 0x6e, 0x63, 0x77, 0xd1, 0xf4, 0x00, 0x00, 0x00, +var file_groupChat_gcMessages_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_groupChat_gcMessages_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: gcRequestMessages.Request +} +var file_groupChat_gcMessages_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_groupChat_gcMessages_proto_init() } +func file_groupChat_gcMessages_proto_init() { + if File_groupChat_gcMessages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_groupChat_gcMessages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_groupChat_gcMessages_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_groupChat_gcMessages_proto_goTypes, + DependencyIndexes: file_groupChat_gcMessages_proto_depIdxs, + MessageInfos: file_groupChat_gcMessages_proto_msgTypes, + }.Build() + File_groupChat_gcMessages_proto = out.File + file_groupChat_gcMessages_proto_rawDesc = nil + file_groupChat_gcMessages_proto_goTypes = nil + file_groupChat_gcMessages_proto_depIdxs = nil } diff --git a/groupChat/gcMessages.proto b/groupChat/gcMessages.proto index 7ce1f3b40c5f34f61367dcec2e477dd8040faae5..cc417505c71d9158a7e5e82fcc71ad25c8e3170f 100644 --- a/groupChat/gcMessages.proto +++ b/groupChat/gcMessages.proto @@ -17,4 +17,5 @@ message Request { bytes keyPreimage = 3; bytes members = 4; bytes message = 5; + int64 created = 6; } \ No newline at end of file diff --git a/groupChat/groupStore/dhKeyList.go b/groupChat/groupStore/dhKeyList.go index f6359f3400327ca7639ac23618413e9e836d081d..38b7b56bf3d88c6bed432e29b5a580c270a0285a 100644 --- a/groupChat/groupStore/dhKeyList.go +++ b/groupChat/groupStore/dhKeyList.go @@ -35,6 +35,7 @@ func GenerateDhKeyList(userID *id.ID, privKey *cyclic.Int, dkl := make(DhKeyList, len(members)-1) for _, m := range members { + // Skip the group.member for the current user if !userID.Cmp(m.ID) { dkl.Add(privKey, m, grp) } @@ -87,7 +88,8 @@ func DeserializeDhKeyList(data []byte) (DhKeyList, error) { buff := bytes.NewBuffer(data) dkl := make(DhKeyList) - for n := buff.Next(id.ArrIDLen); len(n) == id.ArrIDLen; n = buff.Next(id.ArrIDLen) { + const idLen = id.ArrIDLen + for n := buff.Next(idLen); len(n) == idLen; n = buff.Next(idLen) { // Read and unmarshal ID uid, err := id.Unmarshal(n) if err != nil { diff --git a/groupChat/groupStore/dhKeyList_test.go b/groupChat/groupStore/dhKeyList_test.go index c8a633d441cf57c8775d384d817e985e3de59ea5..e6b7a9e6965703d5ae3b7c9a5272597a173ac355 100644 --- a/groupChat/groupStore/dhKeyList_test.go +++ b/groupChat/groupStore/dhKeyList_test.go @@ -1,27 +1,51 @@ package groupStore import ( + "gitlab.com/xx_network/primitives/id" "math/rand" "reflect" "strings" "testing" ) -// // Unit test of GenerateDhKeyList. -// func TestGenerateDhKeyList(t *testing.T) { -// prng := rand.New(rand.NewSource(42)) -// grp := getGroup() -// userID := id.NewIdFromString("userID", id.User, t) -// privKey := grp.NewInt(42) -// pubKey := grp.ExpG(privKey, grp.NewInt(1)) -// members := createMembership(prng, 10, t) -// members[2].ID = userID -// members[2].DhKey = pubKey -// -// dkl := GenerateDhKeyList(userID, privKey, members, grp) -// -// t.Log(dkl) -// } +// Unit test of GenerateDhKeyList. +func TestGenerateDhKeyList(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + grp := getGroup() + userID := id.NewIdFromString("userID", id.User, t) + privKey := grp.NewInt(42) + members := createMembership(prng, 10, t) + + // Set one of the members as the sender (the current user) + senderIndex := 2 + members[senderIndex].ID = userID + members[senderIndex].DhKey = grp.ExpG(privKey, grp.NewInt(1)) + + dkl := GenerateDhKeyList(userID, privKey, members, grp) + + for i, m := range members { + dhKey, exists := dkl[*m.ID] + if i == senderIndex { + // Make sure the sender is not in the list + if exists { + t.Errorf("Found DH key for sender (member #%d with ID %s) in "+ + "DH key list.", i, m.ID) + } + continue + } else if !exists { + // Ensure a DH key exists in the list for this member + t.Errorf("No DH key for member #%d with ID %s in DH key list.", + i, m.ID) + } + + // Make sure that the DH key is correct + if dhKey.Cmp(m.DhKey) == -2 { + t.Errorf("DH key in list for member #%d with ID %s incorrect."+ + "\nexpected: %s\nreceived: %s", + i, m.ID, m.DhKey.Text(10), dhKey.Text(10)) + } + } +} // Unit test of DhKeyList.DeepCopy. func TestDhKeyList_DeepCopy(t *testing.T) { @@ -73,7 +97,18 @@ func TestDeserializeDhKeyList_DhKeyBinaryDecodeError(t *testing.T) { // Unit test of DhKeyList.GoString. func TestDhKeyList_GoString(t *testing.T) { grp := createTestGroup(rand.New(rand.NewSource(42)), t) - expected := "{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn...}" + expected := "{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043..." + + " in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD" + + ": 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX" + + "2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sW" + + "k5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn...," + + " wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP:" + + " 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 258826" + + "0662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGW" + + "nhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKw" + + "VuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU5" + + "0joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HA" + + "HUn...}" if grp.DhKeys.GoString() != expected { t.Errorf("GoString failed to return the expected string."+ "\nexpected: %s\nreceived: %s", expected, grp.DhKeys.GoString()) diff --git a/groupChat/groupStore/group.go b/groupChat/groupStore/group.go index 927201addc7feff88fea8a86f85fe46400ebf2ba..9216bc43e582a595585e8c4cd8f05b68e846f980 100644 --- a/groupChat/groupStore/group.go +++ b/groupChat/groupStore/group.go @@ -18,6 +18,7 @@ import ( "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/netTime" "strings" + "time" ) // Storage values. @@ -41,9 +42,10 @@ type Group struct { Name []byte // Name of the group set by the user ID *id.ID // Group ID Key group.Key // Group key - IdPreimage group.IdPreimage // 256-bit value from CRNG - KeyPreimage group.KeyPreimage // 256-bit value from CRNG + IdPreimage group.IdPreimage // 256-bit randomly generated value + KeyPreimage group.KeyPreimage // 256-bit randomly generated value InitMessage []byte // The original invite message + Created time.Time // Timestamp of when the group was created Members group.Membership // Sorted list of members in group DhKeys DhKeyList // List of shared DH keys } @@ -51,7 +53,8 @@ type Group struct { // NewGroup creates a new Group from copies of the given data. func NewGroup(name []byte, groupID *id.ID, groupKey group.Key, idPreimage group.IdPreimage, keyPreimage group.KeyPreimage, - initMessage []byte, members group.Membership, dhKeys DhKeyList) Group { + initMessage []byte, created time.Time, members group.Membership, + dhKeys DhKeyList) Group { g := Group{ Name: make([]byte, len(name)), ID: groupID.DeepCopy(), @@ -59,6 +62,7 @@ func NewGroup(name []byte, groupID *id.ID, groupKey group.Key, IdPreimage: idPreimage, KeyPreimage: keyPreimage, InitMessage: make([]byte, len(initMessage)), + Created: created.Round(0), Members: members.DeepCopy(), DhKeys: dhKeys, } @@ -78,6 +82,7 @@ func (g Group) DeepCopy() Group { IdPreimage: g.IdPreimage, KeyPreimage: g.KeyPreimage, InitMessage: make([]byte, len(g.InitMessage)), + Created: g.Created, Members: g.Members.DeepCopy(), DhKeys: make(map[id.ID]*cyclic.Int, len(g.Members)-1), } @@ -118,7 +123,12 @@ func removeGroup(groupID *id.ID, kv *versioned.KV) error { return kv.Delete(groupStoreKey(groupID), groupStoreVersion) } -// Serialize serializes the Group and returns the byte slice. +// Serialize serializes the Group and returns the byte slice. The serialized +// data follows the following format. +// +----------+----------+----------+----------+------------+-------------+-----------------+-------------+---------+-------------+----------+----------+ +// | Name len | Name | ID | Key | IdPreimage | KeyPreimage | InitMessage len | InitMessage | Created | Members len | Members | DhKeys | +// | 8 bytes | variable | 33 bytes | 32 bytes | 32 bytes | 32 bytes | 8 bytes | variable | 8 bytes | 8 bytes | variable | variable | +// +----------+----------+----------+----------+------------+-------------+-----------------+-------------+---------+-------------+----------+----------+ func (g Group) Serialize() []byte { buff := bytes.NewBuffer(nil) @@ -146,6 +156,11 @@ func (g Group) Serialize() []byte { buff.Write(b) buff.Write(g.InitMessage) + // Write created timestamp as Unix nanoseconds + b = make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(g.Created.UnixNano())) + buff.Write(b) + // Write length of group membership and group membership b = make([]byte, 8) memberBytes := g.Members.Serialize() @@ -191,6 +206,14 @@ func DeserializeGroup(data []byte) (Group, error) { g.InitMessage = buff.Next(int(initMessageLength)) } + // Get created timestamp + createdNano := int64(binary.LittleEndian.Uint64(buff.Next(8))) + if createdNano == (time.Time{}).UnixNano() { + g.Created = time.Time{} + } else { + g.Created = time.Unix(0, createdNano) + } + // Get member list membersLength := binary.LittleEndian.Uint64(buff.Next(8)) g.Members, err = group.DeserializeMembership(buff.Next(int(membersLength))) @@ -221,7 +244,7 @@ func (g Group) GoString() string { idString = g.ID.String() } - str := make([]string, 8) + str := make([]string, 9) str[0] = "Name:" + fmt.Sprintf("%q", g.Name) str[1] = "ID:" + idString @@ -229,8 +252,9 @@ func (g Group) GoString() string { str[3] = "IdPreimage:" + g.IdPreimage.String() str[4] = "KeyPreimage:" + g.KeyPreimage.String() str[5] = "InitMessage:" + fmt.Sprintf("%q", g.InitMessage) - str[6] = "Members:" + g.Members.String() - str[7] = "DhKeys:" + g.DhKeys.GoString() + str[6] = "Created:" + g.Created.String() + str[7] = "Members:" + g.Members.String() + str[8] = "DhKeys:" + g.DhKeys.GoString() return "{" + strings.Join(str, ", ") + "}" } diff --git a/groupChat/groupStore/group_test.go b/groupChat/groupStore/group_test.go index 30bada2d76d58cdd11a8e2fde8388c7ac9df843c..2da1e2868991259142ec74cef049f9e9acd1035f 100644 --- a/groupChat/groupStore/group_test.go +++ b/groupChat/groupStore/group_test.go @@ -32,6 +32,7 @@ func TestNewGroup(t *testing.T) { IdPreimage: newIdPreimage(groupIdPreimage), KeyPreimage: newKeyPreimage(groupKeyPreimage), InitMessage: []byte(initMessage), + Created: created, Members: membership, DhKeys: dkl, } @@ -43,6 +44,7 @@ func TestNewGroup(t *testing.T) { newIdPreimage(groupIdPreimage), newKeyPreimage(groupKeyPreimage), []byte(initMessage), + expectedGroup.Created, membership, dkl, ) @@ -261,7 +263,38 @@ func Test_groupStoreKey(t *testing.T) { // Unit test of Group.GoString. func TestGroup_GoString(t *testing.T) { grp := createTestGroup(rand.New(rand.NewSource(42)), t) - expected := "{Name:\"groupName\", ID:XMCYoCcs5+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE, Key:a2V5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, IdPreimage:aWRQcmVpbWFnZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, KeyPreimage:a2V5UHJlaW1hZ2UAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, InitMessage:\"initMessage\", Members:{Leader: {U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID, 3534334367... in GRP: 6SsQ/HAHUn...}, Participants: 0: {Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD, 5274380952... in GRP: 6SsQ/HAHUn...}, 1: {QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD, 1628829379... in GRP: 6SsQ/HAHUn...}, 2: {invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD, 4157513341... in GRP: 6SsQ/HAHUn...}, 3: {o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD, 6317064433... in GRP: 6SsQ/HAHUn...}, 4: {wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D, 5785305945... in GRP: 6SsQ/HAHUn...}, 5: {15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD, 2010156224... in GRP: 6SsQ/HAHUn...}, 6: {3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD, 2643318057... in GRP: 6SsQ/HAHUn...}, 7: {55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D, 6482807720... in GRP: 6SsQ/HAHUn...}, 8: {9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD, 6603068123... in GRP: 6SsQ/HAHUn...}}, DhKeys:{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn...}}" + grp.Created = grp.Created.UTC() + expected := "{Name:\"groupName\", " + + "ID:XMCYoCcs5+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE, " + + "Key:a2V5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + + "IdPreimage:aWRQcmVpbWFnZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + + "KeyPreimage:a2V5UHJlaW1hZ2UAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + + "InitMessage:\"initMessage\", " + + "Created:" + grp.Created.String() + ", " + + "Members:{" + + "Leader: {U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID, 3534334367... in GRP: 6SsQ/HAHUn...}, " + + "Participants: " + + "0: {Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD, 5274380952... in GRP: 6SsQ/HAHUn...}, " + + "1: {QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD, 1628829379... in GRP: 6SsQ/HAHUn...}, " + + "2: {invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD, 4157513341... in GRP: 6SsQ/HAHUn...}, " + + "3: {o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD, 6317064433... in GRP: 6SsQ/HAHUn...}, " + + "4: {wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D, 5785305945... in GRP: 6SsQ/HAHUn...}, " + + "5: {15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD, 2010156224... in GRP: 6SsQ/HAHUn...}, " + + "6: {3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD, 2643318057... in GRP: 6SsQ/HAHUn...}, " + + "7: {55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D, 6482807720... in GRP: 6SsQ/HAHUn...}, " + + "8: {9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD, 6603068123... in GRP: 6SsQ/HAHUn...}" + + "}, " + + "DhKeys:{" + + "Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., " + + "QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., " + + "invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., " + + "o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., " + + "wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., " + + "15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., " + + "3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., " + + "55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., " + + "9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn..." + + "}}" if grp.GoString() != expected { t.Errorf("GoString failed to return the expected string."+ @@ -279,6 +312,7 @@ func TestGroup_GoString_NilGroup(t *testing.T) { "IdPreimage:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + "KeyPreimage:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + "InitMessage:\"\", " + + "Created:0001-01-01 00:00:00 +0000 UTC, " + "Members:{<nil>}, " + "DhKeys:{}" + "}" diff --git a/groupChat/groupStore/utils_test.go b/groupChat/groupStore/utils_test.go index 9a6460800498b11fdbcb8a4b9b5aa7d4e391b2f3..8aff7c8400183aa61b352d1e4f44f4056f591de5 100644 --- a/groupChat/groupStore/utils_test.go +++ b/groupChat/groupStore/utils_test.go @@ -15,6 +15,7 @@ import ( "gitlab.com/xx_network/primitives/id" "math/rand" "testing" + "time" ) const ( @@ -26,6 +27,8 @@ const ( initMessage = "initMessage" ) +var created = time.Date(1955, 11, 5, 12, 1, 0, 0, time.Local) + // createTestGroup generates a new group for testing. func createTestGroup(rng *rand.Rand, t *testing.T) Group { members := createMembership(rng, 10, t) @@ -37,6 +40,7 @@ func createTestGroup(rng *rand.Rand, t *testing.T) Group { newIdPreimage(groupIdPreimage), newKeyPreimage(groupKeyPreimage), []byte(initMessage), + created, members, dkl, ) diff --git a/groupChat/internalFormat_test.go b/groupChat/internalFormat_test.go index 2fc5a0ebb10f93dfff2f01bb54515ebff823f105..7c2d415ee68e041842d127293ccdf97b2506d1cf 100644 --- a/groupChat/internalFormat_test.go +++ b/groupChat/internalFormat_test.go @@ -190,7 +190,9 @@ func TestInternalMsg_String(t *testing.T) { payload = append(payload, 0, 1, 2) im.SetPayload(payload) - expected := `{timestamp:` + im.GetTimestamp().String() + `, senderID:dGVzdCBzZW5kZXIgSUQAAAAAAAAAAAAAAAAAAAAAAAAD, size:26, payload:"Sample payload message.\x00\x01\x02"}` + expected := "{timestamp:" + im.GetTimestamp().String() + + ", senderID:dGVzdCBzZW5kZXIgSUQAAAAAAAAAAAAAAAAAAAAAAAAD, " + + "size:26, payload:\"Sample payload message.\\x00\\x01\\x02\"}" if im.String() != expected { t.Errorf("String() failed to return the expected value."+ diff --git a/groupChat/makeGroup.go b/groupChat/makeGroup.go index a8f7773f9dc57feef95a5316fa193b7b83de799d..7cfe7dfd8326b1f221b9ef4a9bf16c8096879c3a 100644 --- a/groupChat/makeGroup.go +++ b/groupChat/makeGroup.go @@ -17,6 +17,7 @@ import ( "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/group" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" "strconv" ) @@ -76,15 +77,18 @@ func (m Manager) MakeGroup(membership []*id.ID, name, msg []byte) (gs.Group, 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, mem, dkl) - if err := m.gs.Add(g); err != nil { + 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 members %s", - g.Name, g.ID, g.Members) + 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) diff --git a/groupChat/manager.go b/groupChat/manager.go index 7fffb109d44dadc435383d65424ac1b55b0f41b3..4b0b3066772f7f7e72e38ef531c32f7d44b3f797 100644 --- a/groupChat/manager.go +++ b/groupChat/manager.go @@ -138,7 +138,7 @@ func (m Manager) JoinGroup(g gs.Group) error { Source: g.ID[:], }, m.store.GetUser().ReceptionID) - jww.DEBUG.Printf("Joined group %s.", g.ID) + jww.DEBUG.Printf("Joined group %q with ID %s.", g.Name, g.ID) return nil } @@ -156,7 +156,7 @@ func (m Manager) LeaveGroup(groupID *id.ID) error { Source: groupID[:], }, m.store.GetUser().ReceptionID) - jww.DEBUG.Printf("Left group %s.", groupID) + jww.DEBUG.Printf("Left group with ID %s.", groupID) return err } diff --git a/groupChat/manager_test.go b/groupChat/manager_test.go index ccb12a194b8c47c3561f3ec07a4b869b5e667571..b3367230a80faaf3b2f752e627414914d8c8fbfe 100644 --- a/groupChat/manager_test.go +++ b/groupChat/manager_test.go @@ -127,7 +127,10 @@ func Test_newManager_LoadError(t *testing.T) { } } -// +// FIXME: the test storage.Session used for each manager currently uses the same +// user. To fix this test, they need to use different users, which requires +// modifying +// storage.InitTestingSession. // func TestManager_StartProcesses(t *testing.T) { // jww.SetLogThreshold(jww.LevelTrace) // jww.SetStdoutThreshold(jww.LevelTrace) diff --git a/groupChat/publicFormat_test.go b/groupChat/publicFormat_test.go index 750a8057f41433bc53d5e8c211bf6e73e10f2a57..ceb1ba2ec32ea0f6112b33a53218af557c0c2706 100644 --- a/groupChat/publicFormat_test.go +++ b/groupChat/publicFormat_test.go @@ -141,7 +141,9 @@ func Test_publicMsg_String(t *testing.T) { payload = append(payload, 0, 1, 2) pm.SetPayload(payload) - expected := `{salt:U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=, payload:"Sample payload message.\x00\x01\x02\x00\x00\x00\x00\x00\x00"}` + expected := "{salt:U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=, " + + "payload:\"Sample payload message." + + "\\x00\\x01\\x02\\x00\\x00\\x00\\x00\\x00\\x00\"}" if pm.String() != expected { t.Errorf("String() failed to return the expected value."+ diff --git a/groupChat/receive.go b/groupChat/receive.go index a708a83becd4d1d33fd8dddf459d0562852e5fd1..0cf5ca65f7a90a2648e1e5d30fe2a26772eb8007 100644 --- a/groupChat/receive.go +++ b/groupChat/receive.go @@ -25,7 +25,7 @@ const ( unmarshalInternalMsgErr = "failed to unmarshal group internal message: %+v" unmarshalSenderIdErr = "failed to unmarshal sender ID: %+v" unmarshalPublicMsgErr = "failed to unmarshal group cMix message contents: %+v" - findGroupKeyFpErr = "failed to find group with key fingerprint matching %s" + findGroupKeyFpErr = "no group with key fingerprint %s" genCryptKeyMacErr = "failed to generate encryption key for group " + "cMix message because MAC verification failed (epoch %d could be off)" ) @@ -45,13 +45,23 @@ func (m Manager) receive(rawMsgs chan message.Receive, stop *stoppable.Single) { jww.DEBUG.Print("Group message reception received cMix message.") // Attempt to read the message - g, msgID, timestamp, senderID, msg, err := m.readMessage(receiveMsg) + g, msgID, timestamp, senderID, msg, noFpMatch, err := + m.readMessage(receiveMsg) if err != nil { - jww.WARN.Printf("Group message reception failed to read cMix "+ - "message: %+v", err) + if noFpMatch { + jww.DEBUG.Printf("Received message not for group chat: %+v", + err) + } else { + jww.WARN.Printf("Group message reception failed to read "+ + "cMix message: %+v", err) + } continue } + jww.DEBUG.Printf("Received group message with ID %s from sender "+ + "%s in group %s with ID %s at %s.", msgID, senderID, g.Name, + g.ID, timestamp) + // If the message was read correctly, send it to the callback go m.receiveFunc(MessageReceive{ GroupID: g.ID, @@ -72,31 +82,32 @@ func (m Manager) receive(rawMsgs chan message.Receive, stop *stoppable.Single) { // of a group message. The encrypted group message data is unmarshalled from a // cMix message in the message.Receive and then decrypted and the MAC is // verified. The group is found by finding the group with a matching key -// fingerprint. +// fingerprint. Returns true if the key fingerprint cannot be found; in this +// case no warning or error should be printed. func (m *Manager) readMessage(msg message.Receive) (gs.Group, group.MessageID, - time.Time, *id.ID, []byte, error) { + time.Time, *id.ID, []byte, bool, error) { // Unmarshal payload into cMix message cMixMsg := format.Unmarshal(msg.Payload) // Unmarshal cMix message contents to get public message format - publicMsg, err := unmarshalPublicMsg(cMixMsg.GetContents()) + pubMsg, err := unmarshalPublicMsg(cMixMsg.GetContents()) if err != nil { - return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, + return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, false, errors.Errorf(unmarshalPublicMsgErr, err) } // Get the group from storage via key fingerprint lookup - g, exists := m.gs.GetByKeyFp(cMixMsg.GetKeyFP(), publicMsg.GetSalt()) + g, exists := m.gs.GetByKeyFp(cMixMsg.GetKeyFP(), pubMsg.GetSalt()) if !exists { - return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, + return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, true, errors.Errorf(findGroupKeyFpErr, cMixMsg.GetKeyFP()) } // Decrypt the payload and return the messages timestamp, sender ID, and // message contents messageID, timestamp, senderID, contents, err := m.decryptMessage( - g, cMixMsg, publicMsg, msg.RoundTimestamp) - return g, messageID, timestamp, senderID, contents, err + g, cMixMsg, pubMsg, msg.RoundTimestamp) + return g, messageID, timestamp, senderID, contents, false, err } // decryptMessage decrypts the group message payload and returns its message ID, @@ -116,23 +127,22 @@ func (m *Manager) decryptMessage(g gs.Group, cMixMsg format.Message, publicMsg.GetPayload()) // Unmarshal internal message - internalMsg, err := unmarshalInternalMsg(decryptedPayload) + intlMsg, err := unmarshalInternalMsg(decryptedPayload) if err != nil { return group.MessageID{}, time.Time{}, nil, nil, errors.Errorf(unmarshalInternalMsgErr, err) } // Unmarshal sender ID - senderID, err := internalMsg.GetSenderID() + senderID, err := intlMsg.GetSenderID() if err != nil { return group.MessageID{}, time.Time{}, nil, nil, errors.Errorf(unmarshalSenderIdErr, err) } - messageID := group.NewMessageID(g.ID, internalMsg.Marshal()) + messageID := group.NewMessageID(g.ID, intlMsg.Marshal()) - return messageID, internalMsg.GetTimestamp(), senderID, - internalMsg.GetPayload(), nil + return messageID, intlMsg.GetTimestamp(), senderID, intlMsg.GetPayload(), nil } // getCryptKey generates the decryption key for a group internal message. The diff --git a/groupChat/receiveRequest.go b/groupChat/receiveRequest.go index 76018cc4ae50ece4128510a0221013f9845b411d..844b86249e593bdd5532bda0ed480058804e5b61 100644 --- a/groupChat/receiveRequest.go +++ b/groupChat/receiveRequest.go @@ -15,6 +15,7 @@ import ( "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/crypto/group" + "time" ) // Error message. @@ -37,7 +38,7 @@ func (m Manager) receiveRequest(rawMsgs chan message.Receive, stop.ToStopped() return case sendMsg := <-rawMsgs: - jww.DEBUG.Print("Group message request received send message.") + jww.DEBUG.Print("Group message request received message.") // Generate the group from the request message g, err := m.readRequest(sendMsg) @@ -50,6 +51,9 @@ func (m Manager) receiveRequest(rawMsgs chan message.Receive, // Call request callback with the new group if it does not already // exist if _, exists := m.GetGroup(g.ID); !exists { + jww.DEBUG.Printf("Received group request from sender %s for "+ + "group %s with ID %s.", sendMsg.Sender, g.Name, g.ID) + go m.requestFunc(g) } } @@ -72,7 +76,7 @@ func (m *Manager) readRequest(msg message.Receive) (gs.Group, error) { } // Deserialize membership list - membership, err := group.DeserializeMembership(request.Members) + membership, err := group.DeserializeMembership(request.GetMembers()) if err != nil { return gs.Group{}, errors.Errorf(deserializeMembershipErr, err) } @@ -98,15 +102,18 @@ func (m *Manager) readRequest(msg message.Receive) (gs.Group, error) { // Copy preimages var idPreimage group.IdPreimage - copy(idPreimage[:], request.IdPreimage) + copy(idPreimage[:], request.GetIdPreimage()) var keyPreimage group.KeyPreimage - copy(keyPreimage[:], request.KeyPreimage) + copy(keyPreimage[:], request.GetKeyPreimage()) // Create group ID and key groupID := group.NewID(idPreimage, membership) groupKey := group.NewKey(keyPreimage, membership) + // Convert created timestamp from nanoseconds to time.Time + created := time.Unix(0, request.GetCreated()) + // Return the new group - return gs.NewGroup(request.Name, groupID, groupKey, idPreimage, keyPreimage, - request.Message, membership, dkl), nil + return gs.NewGroup(request.GetName(), groupID, groupKey, idPreimage, + keyPreimage, request.GetMessage(), created, membership, dkl), nil } diff --git a/groupChat/receiveRequest_test.go b/groupChat/receiveRequest_test.go index 6925853a325948b005c7c38e11f2a28621ab2b11..eda6c9e50315ba284d83ee8cc80a6b9d6bc51f17 100644 --- a/groupChat/receiveRequest_test.go +++ b/groupChat/receiveRequest_test.go @@ -11,54 +11,66 @@ import ( "github.com/golang/protobuf/proto" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/stoppable" "math/rand" + "reflect" "strings" "testing" "time" ) -// // Tests that the correct group is received from the request. -// func TestManager_receiveRequest(t *testing.T) { -// prng := rand.New(rand.NewSource(42)) -// requestChan := make(chan gs.Group) -// requestFunc := func(g gs.Group) { requestChan <- g } -// m, _ := newTestManagerWithStore(prng, 10, 0, requestFunc, nil, t) -// g := newTestGroupWithUser(m.store.E2e().GetGroup(), -// m.store.GetUser().ReceptionID, m.store.GetUser().E2eDhPublicKey, -// m.store.GetUser().E2eDhPrivateKey, prng, t) -// -// requestMarshaled, err := proto.Marshal(&Request{ -// Name: g.Name, -// IdPreimage: g.IdPreimage.Bytes(), -// KeyPreimage: g.KeyPreimage.Bytes(), -// Members: g.Members.Serialize(), -// Message: g.InitMessage, -// }) -// if err != nil { -// t.Errorf("Failed to marshal proto message: %+v", err) -// } -// -// msg := message.Receive{ -// Payload: requestMarshaled, -// MessageType: message.GroupCreationRequest, -// } -// -// rawMessages := make(chan message.Receive) -// quit := make(chan struct{}) -// go m.receiveRequest(rawMessages, quit) -// rawMessages <- msg -// -// select { -// case receivedGrp := <-requestChan: -// if !reflect.DeepEqual(g, receivedGrp) { -// t.Errorf("receiveRequest() failed to return the expected group."+ -// "\nexpected: %#v\nreceived: %#v", g, receivedGrp) -// } -// case <-time.NewTimer(5 * time.Millisecond).C: -// t.Error("Timed out while waiting for callback.") -// } -// } +// Tests that the correct group is received from the request. +func TestManager_receiveRequest(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + requestChan := make(chan gs.Group) + requestFunc := func(g gs.Group) { requestChan <- g } + m, _ := newTestManagerWithStore(prng, 10, 0, requestFunc, nil, t) + g := newTestGroupWithUser(m.store.E2e().GetGroup(), + m.store.GetUser().ReceptionID, m.store.GetUser().E2eDhPublicKey, + m.store.GetUser().E2eDhPrivateKey, prng, t) + + requestMarshaled, err := proto.Marshal(&Request{ + Name: g.Name, + IdPreimage: g.IdPreimage.Bytes(), + KeyPreimage: g.KeyPreimage.Bytes(), + Members: g.Members.Serialize(), + Message: g.InitMessage, + Created: g.Created.UnixNano(), + }) + if err != nil { + t.Errorf("Failed to marshal proto message: %+v", err) + } + + msg := message.Receive{ + Sender: g.Members[0].ID, + Payload: requestMarshaled, + MessageType: message.GroupCreationRequest, + } + + _ = m.store.E2e().AddPartner( + g.Members[0].ID, + g.Members[0].DhKey, + m.store.E2e().GetGroup().NewInt(2), + params.GetDefaultE2ESessionParams(), + params.GetDefaultE2ESessionParams(), + ) + + rawMessages := make(chan message.Receive) + quit := stoppable.NewSingle("groupReceiveRequestTestStoppable") + go m.receiveRequest(rawMessages, quit) + rawMessages <- msg + + select { + case receivedGrp := <-requestChan: + if !reflect.DeepEqual(g, receivedGrp) { + t.Errorf("receiveRequest() failed to return the expected group."+ + "\nexpected: %#v\nreceived: %#v", g, receivedGrp) + } + case <-time.NewTimer(5 * time.Millisecond).C: + t.Error("Timed out while waiting for callback.") + } +} // Tests that the callback is not called when the group already exists in the // manager. @@ -147,43 +159,44 @@ func TestManager_receiveRequest_SendMessageTypeError(t *testing.T) { } } -// // Unit test of readRequest. -// func TestManager_readRequest(t *testing.T) { -// m, g := newTestManager(rand.New(rand.NewSource(42)), t) -// _ = m.store.E2e().AddPartner( -// g.Members[0].ID, -// g.Members[0].DhKey, -// m.store.E2e().GetGroup().NewInt(43), -// params.GetDefaultE2ESessionParams(), -// params.GetDefaultE2ESessionParams(), -// ) -// -// requestMarshaled, err := proto.Marshal(&Request{ -// Name: g.Name, -// IdPreimage: g.IdPreimage.Bytes(), -// KeyPreimage: g.KeyPreimage.Bytes(), -// Members: g.Members.Serialize(), -// Message: g.InitMessage, -// }) -// if err != nil { -// t.Errorf("Failed to marshal proto message: %+v", err) -// } -// -// msg := message.Receive{ -// Payload: requestMarshaled, -// MessageType: message.GroupCreationRequest, -// } -// -// newGrp, err := m.readRequest(msg) -// if err != nil { -// t.Errorf("readRequest() returned an error: %+v", err) -// } -// -// if !reflect.DeepEqual(g, newGrp) { -// t.Errorf("readRequest() returned the wrong group."+ -// "\nexpected: %#v\nreceived: %#v", g, newGrp) -// } -// } +// Unit test of readRequest. +func TestManager_readRequest(t *testing.T) { + m, g := newTestManager(rand.New(rand.NewSource(42)), t) + _ = m.store.E2e().AddPartner( + g.Members[0].ID, + g.Members[0].DhKey, + m.store.E2e().GetGroup().NewInt(2), + params.GetDefaultE2ESessionParams(), + params.GetDefaultE2ESessionParams(), + ) + + requestMarshaled, err := proto.Marshal(&Request{ + Name: g.Name, + IdPreimage: g.IdPreimage.Bytes(), + KeyPreimage: g.KeyPreimage.Bytes(), + Members: g.Members.Serialize(), + Message: g.InitMessage, + Created: g.Created.UnixNano(), + }) + if err != nil { + t.Errorf("Failed to marshal proto message: %+v", err) + } + + msg := message.Receive{ + Payload: requestMarshaled, + MessageType: message.GroupCreationRequest, + } + + newGrp, err := m.readRequest(msg) + if err != nil { + t.Errorf("readRequest() returned an error: %+v", err) + } + + if !reflect.DeepEqual(g, newGrp) { + t.Errorf("readRequest() returned the wrong group."+ + "\nexpected: %#v\nreceived: %#v", g, newGrp) + } +} // Error path: an error is returned if the message type is incorrect. func TestManager_readRequest_MessageTypeError(t *testing.T) { diff --git a/groupChat/receive_test.go b/groupChat/receive_test.go index 125e34815ee64282df2a546e1a3e001c575ea266..1592f19f2185852de918a76ba17f5dd767f62018 100644 --- a/groupChat/receive_test.go +++ b/groupChat/receive_test.go @@ -165,11 +165,16 @@ func TestManager_readMessage(t *testing.T) { } m.gs.SetUser(expectedGrp.Members[4], t) - g, messageID, timestamp, senderID, contents, err := m.readMessage(receiveMsg) + g, messageID, timestamp, senderID, contents, noFpMatch, err := + m.readMessage(receiveMsg) if err != nil { t.Errorf("readMessage() returned an error: %+v", err) } + if noFpMatch { + t.Error("Fingerprint did not match when it should have.") + } + if !reflect.DeepEqual(expectedGrp, g) { t.Errorf("readMessage() returned incorrect group."+ "\nexpected: %#v\nreceived: %#v", expectedGrp, g) @@ -226,7 +231,7 @@ func TestManager_readMessage_FindGroupKpError(t *testing.T) { expectedErr := strings.SplitN(findGroupKeyFpErr, "%", 2)[0] m.gs.SetUser(g.Members[4], t) - _, _, _, _, _, err = m.readMessage(receiveMsg) + _, _, _, _, _, _, err = m.readMessage(receiveMsg) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("readMessage() failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) diff --git a/groupChat/send.go b/groupChat/send.go index 29941ce8e71fcc1cea9ec900b01be88d4bea0cc0..916410d1759add008948cd8d748c0bcf47cd9add 100644 --- a/groupChat/send.go +++ b/groupChat/send.go @@ -38,7 +38,8 @@ const ( // send fails if the message is too long. func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, time.Time, error) { - timeNow := netTime.Now() + // Get the current time stripped of the monotonic clock + timeNow := netTime.Now().Round(0) // Create a cMix message for each group member messages, err := m.createMessages(groupID, message, timeNow) @@ -55,7 +56,8 @@ func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, time.Time, errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) } - jww.DEBUG.Printf("Sent message to group %s.", groupID) + jww.DEBUG.Printf("Sent message to %d members in group %s at %s.", + len(messages), groupID, timeNow) return rid, timeNow, nil } @@ -75,8 +77,8 @@ func (m *Manager) createMessages(groupID *id.ID, msg []byte, // newMessages is a private function that allows the passing in of a timestamp // and streamGen instead of a fastRNG.StreamGenerator for easier testing. -func (m *Manager) newMessages(g gs.Group, msg []byte, - timestamp time.Time) (map[id.ID]format.Message, error) { +func (m *Manager) newMessages(g gs.Group, msg []byte, timestamp time.Time) ( + map[id.ID]format.Message, error) { // Create list of cMix messages messages := make(map[id.ID]format.Message) @@ -131,15 +133,15 @@ func (m *Manager) newCmixMsg(g gs.Group, msg []byte, timestamp time.Time, // Create three message layers cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - publicMsg, internalMsg, err := newMessageParts(cmixMsg.ContentsSize()) + pubMsg, intlMsg, err := newMessageParts(cmixMsg.ContentsSize()) if err != nil { return cmixMsg, err } // Return an error if the message is too large to fit in the payload - if internalMsg.GetPayloadMaxSize() < len(msg) { - return cmixMsg, errors.Errorf(messageLenErr, len(msg), - internalMsg.GetPayloadMaxSize()) + if intlMsg.GetPayloadMaxSize() < len(msg) { + return cmixMsg, errors.Errorf( + messageLenErr, len(msg), intlMsg.GetPayloadMaxSize()) } // Generate 256-bit salt @@ -158,13 +160,13 @@ func (m *Manager) newCmixMsg(g gs.Group, msg []byte, timestamp time.Time, } // Generate internal message - payload := setInternalPayload(internalMsg, timestamp, m.gs.GetUser().ID, msg) + payload := setInternalPayload(intlMsg, timestamp, m.gs.GetUser().ID, msg) // Encrypt internal message encryptedPayload := group.Encrypt(key, keyFp, payload) // Generate public message - publicPayload := setPublicPayload(publicMsg, salt, encryptedPayload) + publicPayload := setPublicPayload(pubMsg, salt, encryptedPayload) // Generate MAC mac := group.NewMAC(key, encryptedPayload, g.DhKeys[*mem.ID]) @@ -180,17 +182,17 @@ func (m *Manager) newCmixMsg(g gs.Group, msg []byte, timestamp time.Time, // 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) { - publicMsg, err := newPublicMsg(payloadSize) + pubMsg, err := newPublicMsg(payloadSize) if err != nil { - return publicMsg, internalMsg{}, errors.Errorf(newPublicMsgErr, err) + return pubMsg, internalMsg{}, errors.Errorf(newPublicMsgErr, err) } - internalMsg, err := newInternalMsg(publicMsg.GetPayloadSize()) + intlMsg, err := newInternalMsg(pubMsg.GetPayloadSize()) if err != nil { - return publicMsg, internalMsg, errors.Errorf(newInternalMsgErr, err) + return pubMsg, intlMsg, errors.Errorf(newInternalMsgErr, err) } - return publicMsg, internalMsg, nil + return pubMsg, intlMsg, nil } // newSalt generates a new salt of the specified size. diff --git a/groupChat/sendRequests.go b/groupChat/sendRequests.go index cd7913fcf9488d1a5927d4171f7677e99cf245c6..edcecc77f1b5516a9ed75d9481738210374b88e4 100644 --- a/groupChat/sendRequests.go +++ b/groupChat/sendRequests.go @@ -50,6 +50,7 @@ func (m Manager) sendRequests(g gs.Group) ([]id.Round, RequestStatus, error) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), }) if err != nil { return nil, NotSent, errors.Errorf(protoMarshalErr, err) @@ -101,6 +102,9 @@ func (m Manager) sendRequests(g gs.Group) ([]id.Round, RequestStatus, error) { strings.Join(errs, "\n")) } + jww.DEBUG.Printf("Sent group request to %d members in group %q with ID %s.", + len(g.Members), g.Name, g.ID) + // If all sends succeeded, return a list of roundIDs return roundIdMap2List(roundIDs), AllSent, nil } diff --git a/groupChat/sendRequests_test.go b/groupChat/sendRequests_test.go index aa9339e5ee298b445c87023dd8c19ab1e0b7afdd..9c22c13fed92f6b986a6f71db63d6f9aea3a230a 100644 --- a/groupChat/sendRequests_test.go +++ b/groupChat/sendRequests_test.go @@ -30,6 +30,7 @@ func TestManager_ResendRequest(t *testing.T) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), } _, status, err := m.ResendRequest(g.ID) @@ -108,6 +109,7 @@ func TestManager_sendRequests(t *testing.T) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), } _, status, err := m.sendRequests(g) diff --git a/groupChat/utils_test.go b/groupChat/utils_test.go index 554a1d063ebce44b81fad9e323c640de14fbff09..069772587c34f8b8597b8b56951b383c0c7fe752 100644 --- a/groupChat/utils_test.go +++ b/groupChat/utils_test.go @@ -33,6 +33,7 @@ import ( "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/ndf" + "gitlab.com/xx_network/primitives/netTime" "math/rand" "sync" "testing" @@ -156,7 +157,7 @@ func newTestGroup(grp *cyclic.Group, privKey *cyclic.Int, rng *rand.Rand, groupKey := group.NewKey(keyPreimage, membership) return gs.NewGroup(name, groupID, groupKey, idPreimage, keyPreimage, msg, - membership, dkl) + netTime.Now(), membership, dkl) } // newTestGroup generates a new group with random values for testing. @@ -190,7 +191,7 @@ func newTestGroupWithUser(grp *cyclic.Group, uid *id.ID, pubKey, groupKey := group.NewKey(keyPreimage, membership) return gs.NewGroup(name, groupID, groupKey, idPreimage, keyPreimage, msg, - membership, dkl) + netTime.Now().Round(0), membership, dkl) } // randCycInt returns a random cyclic int.