Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
client
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Deploy
Releases
Package registry
Container Registry
Model registry
Operate
Terraform modules
Analyze
Contributor analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
elixxir
client
Commits
d6d35ae7
Commit
d6d35ae7
authored
2 years ago
by
Josh Brooks
Browse files
Options
Downloads
Patches
Plain Diff
Implemnt group chat for bindings
parent
805b64c6
No related branches found
No related tags found
2 merge requests
!510
Release
,
!315
Hotfix/bindings updates
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
bindings/e2eHandler.go
+2
-1
2 additions, 1 deletion
bindings/e2eHandler.go
bindings/group.go
+429
-0
429 additions, 0 deletions
bindings/group.go
bindings/identity.go
+14
-0
14 additions, 0 deletions
bindings/identity.go
groupChat/wrapper.go
+68
-0
68 additions, 0 deletions
groupChat/wrapper.go
with
513 additions
and
1 deletion
bindings/e2eHandler.go
+
2
−
1
View file @
d6d35ae7
...
...
@@ -206,7 +206,8 @@ type messageProcessor struct {
bindingsCbs
Processor
}
// convertAuthCallbacks turns an auth.Callbacks into an AuthCallbacks
// convertProcessor turns the input of a message.Processor to the
// binding-layer primitives equivalents within the Processor.Process.
func
convertProcessor
(
msg
format
.
Message
,
receptionID
receptionID
.
EphemeralIdentity
,
round
rounds
.
Round
)
(
message
[]
byte
,
receptionId
[]
byte
,
ephemeralId
int64
,
roundId
int64
)
{
...
...
This diff is collapsed.
Click to expand it.
bindings/group.go
+
429
−
0
View file @
d6d35ae7
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package
bindings
import
(
"encoding/json"
"fmt"
"github.com/pkg/errors"
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds"
gc
"gitlab.com/elixxir/client/groupChat"
gs
"gitlab.com/elixxir/client/groupChat/groupStore"
"gitlab.com/elixxir/crypto/group"
"gitlab.com/elixxir/primitives/format"
"gitlab.com/xx_network/primitives/id"
"sync"
"time"
)
////////////////////////////////////////////////////////////////////////////////
// Group Chat Singleton Tracker //
////////////////////////////////////////////////////////////////////////////////
// groupChatTrackerSingleton is used to track GroupChat objects so that they can be
// referenced by ID back over the bindings.
var
groupChatTrackerSingleton
=
&
groupChatTracker
{
tracked
:
make
(
map
[
int
]
*
GroupChat
),
count
:
0
,
}
// groupChatTracker is a singleton used to keep track of extant GroupChat objects,
// preventing race conditions created by passing it over the bindings.
type
groupChatTracker
struct
{
tracked
map
[
int
]
*
GroupChat
count
int
mux
sync
.
RWMutex
}
// make create a GroupChat from a groupChat.Wrapper, assigns it a unique ID, and
// adds it to the udTracker.
func
(
ut
*
groupChatTracker
)
make
(
gcInt
gc
.
GroupChat
)
*
GroupChat
{
ut
.
mux
.
Lock
()
defer
ut
.
mux
.
Unlock
()
id
:=
ut
.
count
ut
.
count
++
ut
.
tracked
[
id
]
=
&
GroupChat
{
m
:
gc
.
NewWrapper
(
gcInt
),
id
:
id
,
}
return
ut
.
tracked
[
id
]
}
// get an GroupChat from the groupChatTracker given its ID.
func
(
ut
*
groupChatTracker
)
get
(
id
int
)
(
*
GroupChat
,
error
)
{
ut
.
mux
.
RLock
()
defer
ut
.
mux
.
RUnlock
()
c
,
exist
:=
ut
.
tracked
[
id
]
if
!
exist
{
return
nil
,
errors
.
Errorf
(
"Cannot get UserDiscovery for ID %d, does not exist"
,
id
)
}
return
c
,
nil
}
// delete removes a GroupChat from the groupChatTracker.
func
(
ut
*
groupChatTracker
)
delete
(
id
int
)
{
ut
.
mux
.
Lock
()
defer
ut
.
mux
.
Unlock
()
delete
(
ut
.
tracked
,
id
)
}
////////////////////////////////////////////////////////////////////////////////
// Group Chat //
////////////////////////////////////////////////////////////////////////////////
// GroupChat is a binding-layer group chat manager.
type
GroupChat
struct
{
m
*
gc
.
Wrapper
id
int
}
// GroupRequest is a bindings-layer interface that handles a group reception.
//
// Parameters:
// - payload - a byte serialized representation of a group.
type
GroupRequest
interface
{
Callback
(
payload
[]
byte
)
}
// NewManager creates a bindings-layer group chat manager.
//
// Parameters:
// - e2eID - e2e object ID in the tracker.
// - requestFunc - a callback to handle group chat requests.
// - processor - the group chat message processor.
func
NewManager
(
e2eID
int
,
requestFunc
GroupRequest
,
processor
GroupChatProcessor
)
(
*
GroupChat
,
error
)
{
// Get user from singleton
user
,
err
:=
e2eTrackerSingleton
.
get
(
e2eID
)
if
err
!=
nil
{
return
nil
,
err
}
// Construct a wrapper for the request callback
requestCb
:=
func
(
g
gs
.
Group
)
{
//fixme: review this to see if should be json marshaled.
// At the moment, groupStore.DhKeyList is an unsupported
// type, it would need a MarshalJson method
requestFunc
.
Callback
(
g
.
Serialize
())
}
// Construct a group chat manager
gcInt
,
err
:=
gc
.
NewManager
(
user
.
api
,
requestCb
,
&
groupChatProcessor
{
bindingsCb
:
processor
})
if
err
!=
nil
{
return
nil
,
err
}
// Construct wrapper
return
groupChatTrackerSingleton
.
make
(
gcInt
),
nil
}
// GetID returns the groupChatTracker ID for the GroupChat object.
func
(
g
*
GroupChat
)
GetID
()
int
{
return
g
.
id
}
// MakeGroup creates a new Group and sends a group request to all members in the
// group.
//
// Parameters:
// - membership - members the user wants in the group.
// - message - the initial message sent to all members in the group. This is an
// optional parameter and may be nil.
// - tag - the name of the group decided by the creator. This is an optional parameter
// and may be nil. If nil the group will be assigned the default name.
//
// Returns:
// - []byte - a JSON-marshalled GroupReport.
func
(
g
*
GroupChat
)
MakeGroup
(
membership
IdList
,
message
,
name
[]
byte
)
(
[]
byte
,
error
)
{
// Construct membership list into a list of []*id.Id
members
,
err
:=
deserializeIdList
(
membership
.
Ids
)
if
err
!=
nil
{
return
nil
,
err
}
// Construct group
grp
,
rounds
,
status
,
err
:=
g
.
m
.
MakeGroup
(
members
,
name
,
message
)
errStr
:=
""
if
err
!=
nil
{
errStr
=
err
.
Error
()
}
// Construct the group report
report
:=
GroupReport
{
Id
:
grp
.
ID
.
Bytes
(),
Rounds
:
makeRoundsList
(
rounds
),
Status
:
int
(
status
),
Err
:
errStr
,
}
// Marshal the report
return
json
.
Marshal
(
report
)
}
// ResendRequest resends a group request to all members in the group.
//
// Parameters:
// - groupId - a byte representation of a group. This can be found in the data
// returned by GroupChat.MakeGroup.
//
// Returns:
// - []byte - a JSON-marshalled GroupReport.
func
(
g
*
GroupChat
)
ResendRequest
(
groupId
[]
byte
)
([]
byte
,
error
)
{
// Unmarshal the group ID
groupID
,
err
:=
id
.
Unmarshal
(
groupId
)
if
err
!=
nil
{
return
nil
,
errors
.
Errorf
(
"Failed to unmarshal group ID: %+v"
,
err
)
}
// Retrieve group from manager
grp
,
exists
:=
g
.
m
.
GetGroup
(
groupID
)
if
!
exists
{
return
nil
,
errors
.
Errorf
(
"Failed to find group %s"
,
groupID
)
}
// Resent request
rnds
,
status
,
err
:=
g
.
m
.
ResendRequest
(
groupID
)
errStr
:=
""
if
err
!=
nil
{
errStr
=
err
.
Error
()
}
// Construct the group report
report
:=
&
GroupReport
{
Id
:
grp
.
ID
.
Bytes
(),
Rounds
:
makeRoundsList
(
rnds
),
Status
:
int
(
status
),
Err
:
errStr
,
}
// Marshal the report
return
json
.
Marshal
(
report
)
}
// JoinGroup allows a user to join a group when a request is received.
//
// Parameters:
// - group - a serialized Group. This is received by the GroupRequest.Callback.
func
(
g
*
GroupChat
)
JoinGroup
(
group
[]
byte
)
error
{
grp
,
err
:=
gs
.
DeserializeGroup
(
group
)
if
err
!=
nil
{
return
err
}
return
g
.
m
.
JoinGroup
(
grp
)
}
// LeaveGroup deletes a group so a user no longer has access.
//
// Parameters:
// - groupId - the byte data representing a group ID.
// This can be pulled from a marshalled GroupReport.
func
(
g
*
GroupChat
)
LeaveGroup
(
groupId
[]
byte
)
error
{
grpId
,
err
:=
id
.
Unmarshal
(
groupId
)
if
err
!=
nil
{
return
errors
.
Errorf
(
"Failed to unmarshal group ID: %+v"
,
err
)
}
return
g
.
m
.
LeaveGroup
(
grpId
)
}
// Send is the bindings-level function for sending to a group.
//
// Parameters:
// - groupId - the byte data representing a group ID.
// This can be pulled from a marshalled GroupReport.
// - message - the message that the user wishes to send to the group.
// - tag - the tag associated with the message. This tag may be empty.
//
// Returns:
// - []byte - a JSON marshalled GroupSendReport.
func
(
g
*
GroupChat
)
Send
(
groupId
,
message
[]
byte
,
tag
string
)
([]
byte
,
error
)
{
groupID
,
err
:=
id
.
Unmarshal
(
groupId
)
if
err
!=
nil
{
return
nil
,
errors
.
Errorf
(
"Failed to unmarshal group ID: %+v"
,
err
)
}
round
,
timestamp
,
msgID
,
err
:=
g
.
m
.
Send
(
groupID
,
message
,
tag
)
errStr
:=
""
if
err
!=
nil
{
errStr
=
err
.
Error
()
}
sendReport
:=
&
GroupSendReport
{
RoundID
:
round
,
Timestamp
:
timestamp
,
MessageID
:
msgID
,
Err
:
errStr
,
}
return
json
.
Marshal
(
sendReport
)
}
// GetGroups returns an IdList containing a list of group IDs that the user is a member of.
func
(
g
*
GroupChat
)
GetGroups
()
IdList
{
return
makeIdList
(
g
.
m
.
GetGroups
())
}
// GetGroup returns the group with the group ID. If no group exists, then the
// error "failed to find group" is returned.
//
// Parameters:
// - groupId - the byte data representing a group ID.
// This can be pulled from a marshalled GroupReport.
// Returns:
// - Group - the bindings-layer representation of a Group.
func
(
g
*
GroupChat
)
GetGroup
(
groupId
[]
byte
)
(
*
Group
,
error
)
{
groupID
,
err
:=
id
.
Unmarshal
(
groupId
)
if
err
!=
nil
{
return
nil
,
errors
.
Errorf
(
"Failed to unmarshal group ID: %+v"
,
err
)
}
grp
,
exists
:=
g
.
m
.
GetGroup
(
groupID
)
if
!
exists
{
return
nil
,
errors
.
New
(
"failed to find group"
)
}
return
&
Group
{
g
:
grp
},
nil
}
// NumGroups returns the number of groups the user is a part of.
func
(
g
*
GroupChat
)
NumGroups
()
int
{
return
g
.
m
.
NumGroups
()
}
////////////////////////////////////////////////////////////////////////////////
// Group Structure
////////////////////////////////////////////////////////////////////////////////
// Group structure contains the identifying and membership information of a
// group chat.
type
Group
struct
{
g
gs
.
Group
id
int
}
// GetName returns the name set by the user for the group.
func
(
g
*
Group
)
GetName
()
[]
byte
{
return
g
.
g
.
Name
}
// GetID return the 33-byte unique group ID.
func
(
g
*
Group
)
GetID
()
[]
byte
{
return
g
.
g
.
ID
.
Bytes
()
}
// GetInitMessage returns initial message sent with the group request.
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 retrieves a list of group members. The list is in order;
// the first contact is the leader/creator of the group.
// All subsequent members are ordered by their ID.
//
// Returns:
// - []byte - a JSON marshalled version of the member list.
func
(
g
*
Group
)
GetMembership
()
([]
byte
,
error
)
{
return
json
.
Marshal
(
g
.
g
.
Members
)
}
// Serialize serializes the Group.
func
(
g
*
Group
)
Serialize
()
[]
byte
{
return
g
.
g
.
Serialize
()
}
//////////////////////////////////////////////////////////////////////////////////
// Group Chat Processor
//////////////////////////////////////////////////////////////////////////////////
// GroupChatProcessor manages the handling of received group chat messages.
type
GroupChatProcessor
interface
{
Process
(
decryptedMessage
,
msg
,
receptionId
[]
byte
,
ephemeralId
,
roundId
int64
,
err
error
)
fmt
.
Stringer
}
// groupChatProcessor implements GroupChatProcessor as a way of obtaining a
// groupChat.Processor over the bindings.
type
groupChatProcessor
struct
{
bindingsCb
GroupChatProcessor
}
// convertProcessor turns the input of a groupChat.Processor to the
// binding-layer primitives equivalents within the GroupChatProcessor.Process.
func
convertGroupChatProcessor
(
decryptedMsg
gc
.
MessageReceive
,
msg
format
.
Message
,
receptionID
receptionID
.
EphemeralIdentity
,
round
rounds
.
Round
)
(
decryptedMessage
,
message
,
receptionId
[]
byte
,
ephemeralId
,
roundId
int64
,
err
error
)
{
decryptedMessage
,
err
=
json
.
Marshal
(
decryptedMsg
)
message
=
msg
.
Marshal
()
receptionId
=
receptionID
.
Source
.
Marshal
()
ephemeralId
=
receptionID
.
EphId
.
Int64
()
roundId
=
int64
(
round
.
ID
)
return
}
// Process handles incoming group chat messages.
func
(
gcp
*
groupChatProcessor
)
Process
(
decryptedMsg
gc
.
MessageReceive
,
msg
format
.
Message
,
receptionID
receptionID
.
EphemeralIdentity
,
round
rounds
.
Round
)
{
gcp
.
bindingsCb
.
Process
(
convertGroupChatProcessor
(
decryptedMsg
,
msg
,
receptionID
,
round
))
}
// String prints a name for debugging.
func
(
gcp
*
groupChatProcessor
)
String
()
string
{
return
gcp
.
bindingsCb
.
String
()
}
/////////////////////////////////////////////////////////////////////////////////
// Report Structures
////////////////////////////////////////////////////////////////////////////////
// GroupReport 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 operation.
type
GroupReport
struct
{
Id
[]
byte
Rounds
RoundsList
Status
int
Err
string
}
// GroupSendReport is returned when sending a group message. It contains the
// round ID sent on and the timestamp of the send operation.
type
GroupSendReport
struct
{
RoundID
id
.
Round
Timestamp
time
.
Time
MessageID
group
.
MessageID
Err
string
}
This diff is collapsed.
Click to expand it.
bindings/identity.go
+
14
−
0
View file @
d6d35ae7
...
...
@@ -210,3 +210,17 @@ func makeIdList(ids []*id.ID) IdList {
}
return
IdList
{
Ids
:
convertedIds
}
}
// deserializeIdList is a helper function which creates a list of id.ID's
// given an IdList. It deserializes each element of the IdList using id.Unmarshal.
func
deserializeIdList
(
ids
IdList
)
([]
*
id
.
ID
,
error
)
{
convertedIds
:=
make
([]
*
id
.
ID
,
len
(
ids
.
Ids
))
for
i
,
serializedId
:=
range
ids
.
Ids
{
deserializedId
,
err
:=
id
.
Unmarshal
(
serializedId
)
if
err
!=
nil
{
return
nil
,
err
}
convertedIds
[
i
]
=
deserializedId
}
return
convertedIds
,
nil
}
This diff is collapsed.
Click to expand it.
groupChat/wrapper.go
0 → 100644
+
68
−
0
View file @
d6d35ae7
////////////////////////////////////////////////////////////////////////////////
// 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
(
gs
"gitlab.com/elixxir/client/groupChat/groupStore"
"gitlab.com/elixxir/crypto/group"
"gitlab.com/xx_network/primitives/id"
"time"
)
// Wrapper handles the sending and receiving of group chat using E2E
// messages to inform the recipient of incoming group chat messages.
type
Wrapper
struct
{
gc
GroupChat
}
// NewWrapper constructs a wrapper around the GroupChat interface.
func
NewWrapper
(
manager
GroupChat
)
*
Wrapper
{
return
&
Wrapper
{
gc
:
manager
}
}
// MakeGroup calls GroupChat.MakeGroup.
func
(
w
*
Wrapper
)
MakeGroup
(
membership
[]
*
id
.
ID
,
name
,
message
[]
byte
)
(
gs
.
Group
,
[]
id
.
Round
,
RequestStatus
,
error
)
{
return
w
.
gc
.
MakeGroup
(
membership
,
name
,
message
)
}
// GetGroup calls GroupChat.GetGroup.
func
(
w
*
Wrapper
)
GetGroup
(
groupID
*
id
.
ID
)
(
gs
.
Group
,
bool
)
{
return
w
.
gc
.
GetGroup
(
groupID
)
}
// ResendRequest calls GroupChat.ResendRequest.
func
(
w
*
Wrapper
)
ResendRequest
(
groupID
*
id
.
ID
)
([]
id
.
Round
,
RequestStatus
,
error
)
{
return
w
.
gc
.
ResendRequest
(
groupID
)
}
// JoinGroup calls GroupChat.JoinGroup.
func
(
w
*
Wrapper
)
JoinGroup
(
grp
gs
.
Group
)
error
{
return
w
.
gc
.
JoinGroup
(
grp
)
}
// LeaveGroup calls GroupChat.LeaveGroup.
func
(
w
*
Wrapper
)
LeaveGroup
(
groupID
*
id
.
ID
)
error
{
return
w
.
gc
.
LeaveGroup
(
groupID
)
}
// Send calls GroupChat.Send.
func
(
w
*
Wrapper
)
Send
(
groupID
*
id
.
ID
,
message
[]
byte
,
tag
string
)
(
id
.
Round
,
time
.
Time
,
group
.
MessageID
,
error
)
{
return
w
.
gc
.
Send
(
groupID
,
tag
,
message
)
}
// GetGroups calls GroupChat.GetGroups.
func
(
w
*
Wrapper
)
GetGroups
()
[]
*
id
.
ID
{
return
w
.
gc
.
GetGroups
()
}
// NumGroups calls GroupChat.NumGroups.
func
(
w
*
Wrapper
)
NumGroups
()
int
{
return
w
.
gc
.
NumGroups
()
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment