Skip to content
Snippets Groups Projects
Commit 0bb71608 authored by Josh Brooks's avatar Josh Brooks
Browse files

Merge branch 'release' of gitlab.com:elixxir/client into feature/DeleteContact

parents d5411ecd dee47036
Branches
Tags
1 merge request!23Release
......@@ -85,25 +85,33 @@ tag:
- git tag $(release/client.linux64 version | grep "Elixxir Client v"| cut -d ' ' -f3) -f
- git push origin_tags -f --tags
bindings:
bindings-ios:
stage: build
except:
- tags
tags:
- ios
script:
- export PATH="/usr/local/opt/go@1.13/bin:$PATH"
- go get -u golang.org/x/mobile/cmd/gomobile
- go get -u golang.org/x/mobile/bind
- rm -rf $HOME/go/src/gitlab.com/elixxir/client/
- mkdir -p $HOME/go/src/gitlab.com/elixxir/client/
- cp -r * $HOME/go/src/gitlab.com/elixxir/client/
- GO111MODULE=on gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings
- GO111MODULE=on gomobile bind -target ios gitlab.com/elixxir/client/bindings
- gomobile bind -target ios gitlab.com/elixxir/client/bindings
- zip -r iOS.zip Bindings.framework
artifacts:
paths:
- iOS.zip
bindings-android:
stage: build
except:
- tags
tags:
- android
script:
- export ANDROID_HOME=/android-sdk
- export PATH=$PATH:/android-sdk/platform-tools/:/usr/local/go/bin
- go get -u golang.org/x/mobile/cmd/gomobile
- gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings
artifacts:
paths:
- bindings.aar
- bindings-sources.jar
......
......@@ -125,3 +125,17 @@ func (c *Client) MakePrecannedContact(precannedID uint) contact.Contact {
Facts: make([]fact.Fact, 0),
}
}
// GetRelationshipFingerprint returns a unique 15 character fingerprint for an
// E2E relationship. An error is returned if no relationship with the partner
// is found.
func (c *Client) GetRelationshipFingerprint(partner *id.ID) (string, error) {
m, err := c.storage.E2e().GetPartner(partner)
if err != nil {
return "", errors.Errorf("could not get partner %s: %+v", partner, err)
} else if m == nil {
return "", errors.Errorf("manager for partner %s is nil.", partner)
}
return m.GetRelationshipFingerprint(), nil
}
......@@ -8,10 +8,6 @@
package api
import (
"gitlab.com/xx_network/comms/connect"
"gitlab.com/xx_network/primitives/id"
"time"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/auth"
......@@ -28,12 +24,17 @@ import (
"gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/primitives/version"
"gitlab.com/xx_network/comms/connect"
"gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/crypto/large"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/primitives/id"
"gitlab.com/xx_network/primitives/ndf"
"time"
)
const followerStoppableName = "client"
type Client struct {
//generic RNG for client
rng *fastRNG.StreamGenerator
......@@ -181,7 +182,7 @@ func OpenClient(storageDir string, password []byte, parameters params.Network) (
rng: rngStreamGen,
comms: nil,
network: nil,
runner: stoppable.NewMulti("client"),
runner: stoppable.NewMulti(followerStoppableName),
status: newStatusTracker(),
parameters: parameters,
}
......@@ -350,6 +351,7 @@ func (c *Client) initPermissioning(def *ndf.NetworkDefinition) error {
}
// ----- Client Functions -----
// StartNetworkFollower kicks off the tracking of the network. It starts
// long running network client threads and returns an object for checking
// state and stopping those threads.
......@@ -380,7 +382,7 @@ func (c *Client) initPermissioning(def *ndf.NetworkDefinition) error {
// Responds to confirmations of successful rekey operations
// - Auth Callback (/auth/callback.go)
// Handles both auth confirm and requests
func (c *Client) StartNetworkFollower() (<-chan interfaces.ClientError, error) {
func (c *Client) StartNetworkFollower(timeout time.Duration) (<-chan interfaces.ClientError, error) {
u := c.GetUser()
jww.INFO.Printf("StartNetworkFollower() \n\tTransmisstionID: %s "+
"\n\tReceptionID: %s", u.TransmissionID, u.ReceptionID)
......@@ -399,12 +401,21 @@ func (c *Client) StartNetworkFollower() (<-chan interfaces.ClientError, error) {
}
}
err := c.status.toStarting()
// Wait for any threads from the previous follower to close and then create
// a new stoppable
err := stoppable.WaitForStopped(c.runner, timeout)
if err != nil {
return nil, err
} else {
c.runner = stoppable.NewMulti(followerStoppableName)
}
err = c.status.toStarting()
if err != nil {
return nil, errors.WithMessage(err, "Failed to Start the Network Follower")
}
stopAuth := c.auth.StartProcessies()
stopAuth := c.auth.StartProcesses()
c.runner.Add(stopAuth)
stopFollow, err := c.network.Follow(cer)
......@@ -431,13 +442,12 @@ func (c *Client) StartNetworkFollower() (<-chan interfaces.ClientError, error) {
// fails to stop it.
// if the network follower is running and this fails, the client object will
// most likely be in an unrecoverable state and need to be trashed.
func (c *Client) StopNetworkFollower(timeout time.Duration) error {
func (c *Client) StopNetworkFollower() error {
err := c.status.toStopping()
if err != nil {
return errors.WithMessage(err, "Failed to Stop the Network Follower")
}
err = c.runner.Close(timeout)
c.runner = stoppable.NewMulti("client")
err = c.runner.Close()
err2 := c.status.toStopped()
if err2 != nil {
if err == nil {
......
......@@ -12,7 +12,6 @@ import (
jww "github.com/spf13/jwalterweatherman"
pb "gitlab.com/elixxir/comms/mixmessages"
"gitlab.com/elixxir/comms/network"
ds "gitlab.com/elixxir/comms/network/dataStructures"
"gitlab.com/elixxir/primitives/states"
"gitlab.com/xx_network/comms/connect"
......@@ -131,7 +130,7 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration,
// Find out what happened to old (historical) rounds if any are needed
if len(historicalRequest.Rounds) > 0 {
go c.getHistoricalRounds(historicalRequest, networkInstance, sendResults, commsInterface)
go c.getHistoricalRounds(historicalRequest, sendResults, commsInterface)
}
// Determine the results of all rounds requested
......@@ -180,7 +179,7 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration,
// Helper function which asynchronously pings a random gateway until
// it gets information on it's requested historical rounds
func (c *Client) getHistoricalRounds(msg *pb.HistoricalRounds,
instance *network.Instance, sendResults chan ds.EventReturn, comms historicalRoundsComm) {
sendResults chan ds.EventReturn, comms historicalRoundsComm) {
var resp *pb.HistoricalRoundsResponse
......@@ -189,7 +188,7 @@ func (c *Client) getHistoricalRounds(msg *pb.HistoricalRounds,
// Find a gateway to request about the roundRequests
result, err := c.GetNetworkInterface().GetSender().SendToAny(func(host *connect.Host) (interface{}, error) {
return comms.RequestHistoricalRounds(host, msg)
})
}, nil)
// If an error, retry with (potentially) a different gw host.
// If no error from received gateway request, exit loop
......
......@@ -27,7 +27,7 @@ func (c *Client) SendE2E(m message.Send, param params.E2E) ([]id.Round,
e2e.MessageID, error) {
jww.INFO.Printf("SendE2E(%s, %d. %v)", m.Recipient,
m.MessageType, m.Payload)
return c.network.SendE2E(m, param)
return c.network.SendE2E(m, param, nil)
}
// SendUnsafe sends an unencrypted payload to the provided recipient
......@@ -52,6 +52,14 @@ func (c *Client) SendCMIX(msg format.Message, recipientID *id.ID,
return c.network.SendCMIX(msg, recipientID, param)
}
// SendManyCMIX sends many "raw" CMIX message payloads to each of the
// provided recipients. Used for group chat functionality. Returns the
// round ID of the round the payload was sent or an error if it fails.
func (c *Client) SendManyCMIX(messages map[id.ID]format.Message,
params params.CMIX) (id.Round, []ephemeral.Id, error) {
return c.network.SendManyCMIX(messages, params)
}
// NewCMIXMessage Creates a new cMix message with the right properties
// for the current cMix network.
// FIXME: this is weird and shouldn't be necessary, but it is.
......
......@@ -93,7 +93,7 @@ func (t *testNetworkManagerGeneric) Follow(report interfaces.ClientErrorReport)
func (t *testNetworkManagerGeneric) CheckGarbledMessages() {
return
}
func (t *testNetworkManagerGeneric) SendE2E(m message.Send, p params.E2E) (
func (t *testNetworkManagerGeneric) SendE2E(message.Send, params.E2E, *stoppable.Single) (
[]id.Round, cE2e.MessageID, error) {
rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
return rounds, cE2e.MessageID{}, nil
......@@ -105,6 +105,9 @@ func (t *testNetworkManagerGeneric) SendUnsafe(m message.Send, p params.Unsafe)
func (t *testNetworkManagerGeneric) SendCMIX(message format.Message, rid *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error) {
return id.Round(0), ephemeral.Id{}, nil
}
func (t *testNetworkManagerGeneric) SendManyCMIX(messages map[id.ID]format.Message, p params.CMIX) (id.Round, []ephemeral.Id, error) {
return 0, []ephemeral.Id{}, nil
}
func (t *testNetworkManagerGeneric) GetInstance() *network.Instance {
return t.instance
}
......
......@@ -23,20 +23,21 @@ import (
"strings"
)
func (m *Manager) StartProcessies() stoppable.Stoppable {
func (m *Manager) StartProcesses() stoppable.Stoppable {
stop := stoppable.NewSingle("Auth")
go func() {
for {
select {
case <-stop.Quit():
stop.ToStopped()
return
case msg := <-m.rawMessages:
m.processAuthMessage(msg)
}
}
}()
return stop
}
......@@ -132,7 +133,7 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
// confirmation in case there are state issues.
// do not store
if _, err := m.storage.E2e().GetPartner(partnerID); err == nil {
jww.WARN.Printf("Recieved Auth request for %s, "+
jww.WARN.Printf("Received Auth request for %s, "+
"channel already exists. Ignoring", partnerID)
//exit
return
......@@ -140,8 +141,8 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
//check if the relationship already exists,
rType, sr2, _, err := m.storage.Auth().GetRequest(partnerID)
if err != nil && !strings.Contains(err.Error(), auth.NoRequest) {
// if another error is recieved, print it and exit
jww.WARN.Printf("Recieved new Auth request for %s, "+
// if another error is received, print it and exit
jww.WARN.Printf("Received new Auth request for %s, "+
"internal lookup produced bad result: %+v",
partnerID, err)
return
......
......@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/xx_network/primitives/id"
)
// Create an insecure e2e relationship with a precanned user
......@@ -123,3 +124,15 @@ func (c *Client) VerifyOwnership(receivedMarshaled, verifiedMarshaled []byte) (b
return c.api.VerifyOwnership(received, verified), nil
}
// GetRelationshipFingerprint returns a unique 15 character fingerprint for an
// E2E relationship. An error is returned if no relationship with the partner
// is found.
func (c *Client) GetRelationshipFingerprint(partnerID []byte) (string, error) {
partner, err := id.Unmarshal(partnerID)
if err != nil {
return "", err
}
return c.api.GetRelationshipFingerprint(partner)
}
......@@ -198,8 +198,9 @@ func UnmarshalSendReport(b []byte) (*SendReport, error) {
// Responds to sent rekeys and executes them
// - KeyExchange Confirm (/keyExchange/confirm.go)
// Responds to confirmations of successful rekey operations
func (c *Client) StartNetworkFollower(clientError ClientError) error {
errChan, err := c.api.StartNetworkFollower()
func (c *Client) StartNetworkFollower(clientError ClientError, timeoutMS int) error {
timeout := time.Duration(timeoutMS) * time.Millisecond
errChan, err := c.api.StartNetworkFollower(timeout)
if err != nil {
return errors.New(fmt.Sprintf("Failed to start the "+
"network follower: %+v", err))
......@@ -218,9 +219,8 @@ func (c *Client) StartNetworkFollower(clientError ClientError) error {
// fails to stop it.
// if the network follower is running and this fails, the client object will
// most likely be in an unrecoverable state and need to be trashed.
func (c *Client) StopNetworkFollower(timeoutMS int) error {
timeout := time.Duration(timeoutMS) * time.Millisecond
if err := c.api.StopNetworkFollower(timeout); err != nil {
func (c *Client) StopNetworkFollower() error {
if err := c.api.StopNetworkFollower(); err != nil {
return errors.New(fmt.Sprintf("Failed to stop the "+
"network follower: %+v", err))
}
......
///////////////////////////////////////////////////////////////////////////////
// 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 (
"github.com/pkg/errors"
gc "gitlab.com/elixxir/client/groupChat"
gs "gitlab.com/elixxir/client/groupChat/groupStore"
"gitlab.com/elixxir/crypto/group"
"gitlab.com/xx_network/primitives/id"
)
// GroupChat object contains the group chat manager.
type GroupChat struct {
m *gc.Manager
}
// GroupRequestFunc contains a function callback that is called when a group
// request is received.
type GroupRequestFunc interface {
GroupRequestCallback(g Group)
}
// GroupReceiveFunc contains a function callback that is called when a group
// message is received.
type GroupReceiveFunc interface {
GroupReceiveCallback(msg GroupMessageReceive)
}
// NewGroupManager creates a new group chat manager.
func NewGroupManager(client *Client, requestFunc GroupRequestFunc,
receiveFunc GroupReceiveFunc) (GroupChat, error) {
requestCallback := func(g gs.Group) {
requestFunc.GroupRequestCallback(Group{g})
}
receiveCallback := func(msg gc.MessageReceive) {
receiveFunc.GroupReceiveCallback(GroupMessageReceive{msg})
}
// Create a new group chat manager
m, err := gc.NewManager(&client.api, requestCallback, receiveCallback)
if err != nil {
return GroupChat{}, err
}
// Start group request and message retrieval workers
client.api.AddService(m.StartProcesses)
return GroupChat{m}, nil
}
// MakeGroup creates a new group and sends a group request to all members in the
// group. The ID of the new group, the rounds the requests were sent on, and the
// status of the send are contained in NewGroupReport.
func (g GroupChat) MakeGroup(membership IdList, name, message []byte) (NewGroupReport, error) {
grp, rounds, status, err := g.m.MakeGroup(membership.list, name, message)
return NewGroupReport{Group{grp}, rounds, status}, err
}
// ResendRequest resends a group request to all members in the group. The rounds
// they were sent on and the status of the send are contained in NewGroupReport.
func (g GroupChat) ResendRequest(groupIdBytes []byte) (NewGroupReport, error) {
groupID, err := id.Unmarshal(groupIdBytes)
if err != nil {
return NewGroupReport{},
errors.Errorf("Failed to unmarshal group ID: %+v", err)
}
rounds, status, err := g.m.ResendRequest(groupID)
return NewGroupReport{Group{}, rounds, status}, nil
}
// JoinGroup allows a user to join a group when they receive a request. The
// caller must pass in the serialized bytes of a Group.
func (g GroupChat) JoinGroup(serializedGroupData []byte) error {
grp, err := gs.DeserializeGroup(serializedGroupData)
if err != nil {
return err
}
return g.m.JoinGroup(grp)
}
// LeaveGroup deletes a group so a user no longer has access.
func (g GroupChat) LeaveGroup(groupIdBytes []byte) error {
groupID, err := id.Unmarshal(groupIdBytes)
if err != nil {
return errors.Errorf("Failed to unmarshal group ID: %+v", err)
}
return g.m.LeaveGroup(groupID)
}
// 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) {
groupID, err := id.Unmarshal(groupIdBytes)
if err != nil {
return 0, errors.Errorf("Failed to unmarshal group ID: %+v", err)
}
round, err := g.m.Send(groupID, message)
return int64(round), err
}
// GetGroups returns an IdList containing a list of group IDs that the user is a
// part of.
func (g GroupChat) GetGroups() IdList {
return IdList{g.m.GetGroups()}
}
// GetGroup returns the group with the group ID. If no group exists, then the
// error "failed to find group" is returned.
func (g GroupChat) GetGroup(groupIdBytes []byte) (Group, error) {
groupID, err := id.Unmarshal(groupIdBytes)
if err != nil {
return Group{}, errors.Errorf("Failed to unmarshal group ID: %+v", err)
}
grp, exists := g.m.GetGroup(groupID)
if !exists {
return Group{}, errors.New("failed to find group")
}
return Group{grp}, nil
}
// NumGroups returns the number of groups the user is a part of.
func (g GroupChat) NumGroups() int {
return g.m.NumGroups()
}
// 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.
type NewGroupReport struct {
group Group
rounds []id.Round
status gc.RequestStatus
}
// GetGroup returns the Group.
func (ngr NewGroupReport) GetGroup() Group {
return ngr.group
}
// GetRoundList returns the RoundList containing a list of rounds requests were
// sent on.
func (ngr NewGroupReport) GetRoundList() RoundList {
return RoundList{ngr.rounds}
}
// GetStatus returns the status of the requests sent when creating a new group.
// status = 0 an error occurred before any requests could be sent
// 1 all requests failed to send
// 2 some request failed and some succeeded
// 3, all requests sent successfully
func (ngr NewGroupReport) GetStatus() int {
return int(ngr.status)
}
////
// Group Structure
////
// Group structure contains the identifying and membership information of a
// group chat.
type Group struct {
g gs.Group
}
// 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()
}
// 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.
func (g Group) GetMembership() GroupMembership {
return GroupMembership{g.g.Members}
}
// Serialize serializes the Group.
func (g Group) Serialize() []byte {
return g.g.Serialize()
}
////
// Membership Structure
////
// GroupMembership structure contains a list of members that are part of a
// group. The first member is the group leader.
type GroupMembership struct {
m group.Membership
}
// Len returns the number of members in the group membership.
func (gm GroupMembership) Len() int {
return gm.Len()
}
// Get returns the member at the index. The member at index 0 is always the
// group leader. An error is returned if the index is out of range.
func (gm GroupMembership) Get(i int) (GroupMember, error) {
if i < 0 || i > gm.Len() {
return GroupMember{}, errors.Errorf("ID list index must be between %d "+
"and the last element %d.", 0, gm.Len())
}
return GroupMember{gm.m[i]}, nil
}
////
// Member Structure
////
// GroupMember represents a member in the group membership list.
type GroupMember struct {
group.Member
}
// GetID returns the 33-byte user ID of the member.
func (gm GroupMember) GetID() []byte {
return gm.ID.Bytes()
}
// GetDhKey returns the byte representation of the public Diffie–Hellman key of
// the member.
func (gm GroupMember) GetDhKey() []byte {
return gm.DhKey.Bytes()
}
////
// Message Receive Structure
////
// GroupMessageReceive contains a group message, its ID, and its data that a
// user receives.
type GroupMessageReceive struct {
gc.MessageReceive
}
// GetGroupID returns the 33-byte group ID.
func (gmr GroupMessageReceive) GetGroupID() []byte {
return gmr.GroupID.Bytes()
}
// GetMessageID returns the message ID.
func (gmr GroupMessageReceive) GetMessageID() []byte {
return gmr.ID.Bytes()
}
// GetPayload returns the message payload.
func (gmr GroupMessageReceive) GetPayload() []byte {
return gmr.Payload
}
// GetSenderID returns the 33-byte user ID of the sender.
func (gmr GroupMessageReceive) GetSenderID() []byte {
return gmr.SenderID.Bytes()
}
// GetRecipientID returns the 33-byte user ID of the recipient.
func (gmr GroupMessageReceive) GetRecipientID() []byte {
return gmr.RecipientID.Bytes()
}
// GetEphemeralID returns the ephemeral ID of the recipient.
func (gmr GroupMessageReceive) GetEphemeralID() int64 {
return gmr.EphemeralID.Int64()
}
// GetTimestampNano returns the message timestamp in nanoseconds.
func (gmr GroupMessageReceive) GetTimestampNano() int64 {
return gmr.Timestamp.UnixNano()
}
// GetRoundID returns the ID of the round the message was sent on.
func (gmr GroupMessageReceive) GetRoundID() int64 {
return int64(gmr.RoundID)
}
// GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
// message was sent on.
func (gmr GroupMessageReceive) GetRoundTimestampNano() int64 {
return gmr.RoundTimestamp.UnixNano()
}
......@@ -8,7 +8,7 @@
package bindings
import (
"errors"
"github.com/pkg/errors"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/primitives/fact"
"gitlab.com/xx_network/primitives/id"
......@@ -115,3 +115,41 @@ func (fl *FactList) Add(factData string, factType int) error {
func (fl *FactList) Stringify() (string, error) {
return fl.c.Facts.Stringify(), nil
}
/* ID list */
// IdList contains a list of IDs.
type IdList struct {
list []*id.ID
}
// MakeIdList creates a new empty IdList.
func MakeIdList() IdList {
return IdList{[]*id.ID{}}
}
// Len returns the number of IDs in the list.
func (idl IdList) Len() int {
return len(idl.list)
}
// Add appends the ID bytes to the end of the list.
func (idl IdList) Add(idBytes []byte) error {
newID, err := id.Unmarshal(idBytes)
if err != nil {
return err
}
idl.list = append(idl.list, newID)
return nil
}
// Get returns the ID at the index. An error is returned if the index is out of
// range.
func (idl IdList) Get(i int) ([]byte, error) {
if i < 0 || i > len(idl.list) {
return nil, errors.Errorf("ID list index must be between %d and the "+
"last element %d.", 0, len(idl.list))
}
return idl.list[i].Bytes(), nil
}
......@@ -17,33 +17,51 @@ type Message struct {
r message.Receive
}
//Returns the id of the message
// GetID returns the id of the message
func (m *Message) GetID() []byte {
return m.r.ID[:]
}
// Returns the message's sender ID, if available
// GetSender returns the message's sender ID, if available
func (m *Message) GetSender() []byte {
return m.r.Sender.Bytes()
}
// Returns the message's payload/contents
// GetPayload returns the message's payload/contents
func (m *Message) GetPayload() []byte {
return m.r.Payload
}
// Returns the message's type
// GetMessageType returns the message's type
func (m *Message) GetMessageType() int {
return int(m.r.MessageType)
}
// Returns the message's timestamp in ms
// GetTimestampMS returns the message's timestamp in milliseconds
func (m *Message) GetTimestampMS() int64 {
ts := m.r.Timestamp.UnixNano()
ts = (ts + 999999) / 1000000
return ts
}
// GetTimestampNano returns the message's timestamp in nanoseconds
func (m *Message) GetTimestampNano() int64 {
return m.r.Timestamp.UnixNano()
}
// GetRoundTimestampMS returns the message's round timestamp in milliseconds
func (m *Message) GetRoundTimestampMS() int64 {
ts := m.r.RoundTimestamp.UnixNano()
ts = (ts + 999999) / 1000000
return ts
}
// GetRoundTimestampNano returns the message's round timestamp in nanoseconds
func (m *Message) GetRoundTimestampNano() int64 {
return m.r.RoundTimestamp.UnixNano()
}
// GetRoundId returns the message's round ID
func (m *Message) GetRoundId() int64 {
return int64(m.r.RoundId)
}
......@@ -57,6 +57,53 @@ func (c *Client) SendCmix(recipient, contents []byte, parameters string) (int, e
return int(rid), nil
}
// SendManyCMIX sends many "raw" CMIX message payloads to each of the
// provided recipients. Used for group chat functionality. Returns the
// round ID of the round the payload was sent or an error if it fails.
// This will return an error if:
// - any recipient ID is invalid
// - any of the the message contents are too long for the message structure
// - the message cannot be sent
// This will return the round the message was sent on if it is successfully sent
// This can be used to register a round event to learn about message delivery.
// on failure a round id of -1 is returned
// fixme: cannot use a slice of slices over bindings. Will need to modify this function once
// a proper input format has been specified
//func (c *Client) SendManyCMIX(recipients, contents [][]byte, parameters string) (int, error) {
//
// p, err := params.GetCMIXParameters(parameters)
// if err != nil {
// return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
// err))
// }
//
// // Build messages
// messages := make(map[id.ID]format.Message, len(contents))
// for i := 0; i < len(contents); i++ {
// msg, err := c.api.NewCMIXMessage(contents[i])
// if err != nil {
// return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
// err))
// }
//
// u, err := id.Unmarshal(recipients[i])
// if err != nil {
// return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
// err))
// }
//
// messages[*u] = msg
// }
//
// rid, _, err := c.api.SendManyCMIX(messages, p)
// if err != nil {
// return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
// err))
// }
// return int(rid), nil
//}
// SendUnsafe sends an unencrypted payload to the provided recipient
// with the provided msgType. Returns the list of rounds in which parts
// of the message were sent or an error if it fails.
......
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
// The group subcommand allows creation and sending messages to groups
package cmd
import (
"bufio"
"fmt"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"gitlab.com/elixxir/client/api"
"gitlab.com/elixxir/client/groupChat"
"gitlab.com/elixxir/client/groupChat/groupStore"
"gitlab.com/xx_network/primitives/id"
"os"
"time"
)
// groupCmd represents the base command when called without any subcommands
var groupCmd = &cobra.Command{
Use: "group",
Short: "Group commands for cMix client",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
client := initClient()
// Print user's reception ID
user := client.GetUser()
jww.INFO.Printf("User: %s", user.ReceptionID)
_, _ = initClientCallbacks(client)
_, err := client.StartNetworkFollower(5 * time.Second)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
// Initialize the group chat manager
groupManager, recChan, reqChan := initGroupManager(client)
// Wait until connected or crash on timeout
connected := make(chan bool, 10)
client.GetHealth().AddChannel(connected)
waitUntilConnected(connected)
// After connection, make sure we have registered with at least 85% of
// the nodes
for numReg, total := 1, 100; numReg < (total*3)/4; {
time.Sleep(1 * time.Second)
numReg, total, err = client.GetNodeRegistrationStatus()
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
jww.INFO.Printf("Registering with nodes (%d/%d)...", numReg, total)
}
// Get group message and name
msgBody := []byte(viper.GetString("message"))
name := []byte(viper.GetString("name"))
timeout := viper.GetDuration("receiveTimeout")
if viper.IsSet("create") {
filePath := viper.GetString("create")
createGroup(name, msgBody, filePath, groupManager)
}
if viper.IsSet("resend") {
groupIdString := viper.GetString("resend")
resendRequests(groupIdString, groupManager)
}
if viper.GetBool("join") {
joinGroup(reqChan, timeout, groupManager)
}
if viper.IsSet("leave") {
groupIdString := viper.GetString("leave")
leaveGroup(groupIdString, groupManager)
}
if viper.IsSet("sendMessage") {
groupIdString := viper.GetString("sendMessage")
sendGroup(groupIdString, msgBody, groupManager)
}
if viper.IsSet("wait") {
numMessages := viper.GetUint("wait")
messageWait(numMessages, timeout, recChan)
}
if viper.GetBool("list") {
listGroups(groupManager)
}
if viper.IsSet("show") {
groupIdString := viper.GetString("show")
showGroup(groupIdString, groupManager)
}
},
}
// initGroupManager creates a new group chat manager and starts the process
// service.
func initGroupManager(client *api.Client) (*groupChat.Manager,
chan groupChat.MessageReceive, chan groupStore.Group) {
recChan := make(chan groupChat.MessageReceive, 10)
receiveCb := func(msg groupChat.MessageReceive) {
recChan <- msg
}
reqChan := make(chan groupStore.Group, 10)
requestCb := func(g groupStore.Group) {
reqChan <- g
}
jww.INFO.Print("Creating new group manager.")
manager, err := groupChat.NewManager(client, requestCb, receiveCb)
if err != nil {
jww.FATAL.Panicf("Failed to initialize group chat manager: %+v", err)
}
// Start group request and message receiver
client.AddService(manager.StartProcesses)
return manager, recChan, reqChan
}
// createGroup creates a new group with the provided name and sends out requests
// to the list of user IDs found at the given file path.
func createGroup(name, msg []byte, filePath string, gm *groupChat.Manager) {
userIdStrings := ReadLines(filePath)
userIDs := make([]*id.ID, 0, len(userIdStrings))
for _, userIdStr := range userIdStrings {
userID, _ := parseRecipient(userIdStr)
userIDs = append(userIDs, userID)
}
grp, rids, status, err := gm.MakeGroup(userIDs, name, msg)
if err != nil {
jww.FATAL.Panicf("Failed to create new group: %+v", err)
}
// Integration grabs the group ID from this line
jww.INFO.Printf("NewGroupID: b64:%s", grp.ID)
jww.INFO.Printf("Created Group: Requests:%s on rounds %#v, %v", status, rids, grp)
fmt.Printf("Created new group with name %q and message %q\n", grp.Name,
grp.InitMessage)
}
// resendRequests resends group requests for the group ID.
func resendRequests(groupIdString string, gm *groupChat.Manager) {
groupID, _ := parseRecipient(groupIdString)
rids, status, err := gm.ResendRequest(groupID)
if err != nil {
jww.FATAL.Panicf("Failed to resend requests to group %s: %+v",
groupID, err)
}
jww.INFO.Printf("Resending requests to group %s: %v, %s", groupID, rids, status)
fmt.Println("Resending group requests to group.")
}
// joinGroup joins a group when a request is received on the group request
// channel.
func joinGroup(reqChan chan groupStore.Group, timeout time.Duration, gm *groupChat.Manager) {
jww.INFO.Print("Waiting for group request to be received.")
fmt.Println("Waiting for group request to be received.")
select {
case grp := <-reqChan:
err := gm.JoinGroup(grp)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
jww.INFO.Printf("Joined group: %s", grp.ID)
fmt.Printf("Joined group with name %q and message %q\n",
grp.Name, grp.InitMessage)
case <-time.NewTimer(timeout).C:
jww.INFO.Printf("Timed out after %s waiting for group request.", timeout)
fmt.Println("Timed out waiting for group request.")
return
}
}
// leaveGroup leaves the group.
func leaveGroup(groupIdString string, gm *groupChat.Manager) {
groupID, _ := parseRecipient(groupIdString)
jww.INFO.Printf("Leaving group %s.", groupID)
err := gm.LeaveGroup(groupID)
if err != nil {
jww.FATAL.Panicf("Failed to leave group %s: %+v", groupID, err)
}
jww.INFO.Printf("Left group: %s", groupID)
fmt.Println("Left group.")
}
// sendGroup send the message to the group.
func sendGroup(groupIdString string, msg []byte, gm *groupChat.Manager) {
groupID, _ := parseRecipient(groupIdString)
jww.INFO.Printf("Sending to group %s message %q", groupID, msg)
rid, 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)
fmt.Printf("Sent message %q to group.\n", msg)
}
// messageWait waits for the given number of messages to be received on the
// groupChat.MessageReceive channel.
func messageWait(numMessages uint, timeout time.Duration, recChan chan groupChat.MessageReceive) {
jww.INFO.Printf("Waiting for %d group message(s) to be received.", numMessages)
fmt.Printf("Waiting for %d group message(s) to be received.\n", numMessages)
for i := uint(0); i < numMessages; {
select {
case msg := <-recChan:
i++
jww.INFO.Printf("Received group message %d/%d: %s", i, numMessages, msg)
fmt.Printf("Received group message: %q\n", msg.Payload)
case <-time.NewTimer(timeout).C:
jww.INFO.Printf("Timed out after %s waiting for group message.", timeout)
fmt.Printf("Timed out waiting for %d group message(s).\n", numMessages)
return
}
}
}
// listGroups prints a list of all groups.
func listGroups(gm *groupChat.Manager) {
for i, gid := range gm.GetGroups() {
jww.INFO.Printf("Group %d: %s", i, gid)
}
fmt.Printf("Printed list of %d groups.\n", gm.NumGroups())
}
// showGroup prints all the information of the group.
func showGroup(groupIdString string, gm *groupChat.Manager) {
groupID, _ := parseRecipient(groupIdString)
grp, ok := gm.GetGroup(groupID)
if !ok {
jww.FATAL.Printf("Could not find group: %s", groupID)
}
jww.INFO.Printf("Show group %#v", grp)
fmt.Printf("Got group with name %q and message %q\n", grp.Name, grp.InitMessage)
}
// ReadLines returns each line in a file as a string.
func ReadLines(fileName string) []string {
file, err := os.Open(fileName)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
defer file.Close()
var res []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
res = append(res, scanner.Text())
}
if err := scanner.Err(); err != nil {
jww.FATAL.Panicf(err.Error())
}
return res
}
func init() {
groupCmd.Flags().String("create", "",
"Create a group with from the list of contact file paths.")
err := viper.BindPFlag("create", groupCmd.Flags().Lookup("create"))
checkBindErr(err, "create")
groupCmd.Flags().String("name", "Group Name",
"The name of the new group to create.")
err = viper.BindPFlag("name", groupCmd.Flags().Lookup("name"))
checkBindErr(err, "name")
groupCmd.Flags().String("resend", "",
"Resend invites for all users in this group ID.")
err = viper.BindPFlag("resend", groupCmd.Flags().Lookup("resend"))
checkBindErr(err, "resend")
groupCmd.Flags().Bool("join", false,
"Waits for group request joins the group.")
err = viper.BindPFlag("join", groupCmd.Flags().Lookup("join"))
checkBindErr(err, "join")
groupCmd.Flags().String("leave", "",
"Leave this group ID.")
err = viper.BindPFlag("leave", groupCmd.Flags().Lookup("leave"))
checkBindErr(err, "leave")
groupCmd.Flags().String("sendMessage", "",
"Send message to this group ID.")
err = viper.BindPFlag("sendMessage", groupCmd.Flags().Lookup("sendMessage"))
checkBindErr(err, "sendMessage")
groupCmd.Flags().Uint("wait", 0,
"Waits for number of messages to be received.")
err = viper.BindPFlag("wait", groupCmd.Flags().Lookup("wait"))
checkBindErr(err, "wait")
groupCmd.Flags().Duration("receiveTimeout", time.Minute,
"Amount of time to wait for a group request or message before timing out.")
err = viper.BindPFlag("receiveTimeout", groupCmd.Flags().Lookup("receiveTimeout"))
checkBindErr(err, "receiveTimeout")
groupCmd.Flags().Bool("list", false,
"Prints list all groups to which this client belongs.")
err = viper.BindPFlag("list", groupCmd.Flags().Lookup("list"))
checkBindErr(err, "list")
groupCmd.Flags().String("show", "",
"Prints the members of this group ID.")
err = viper.BindPFlag("show", groupCmd.Flags().Lookup("show"))
checkBindErr(err, "show")
rootCmd.AddCommand(groupCmd)
}
func checkBindErr(err error, key string) {
if err != nil {
jww.ERROR.Printf("viper.BindPFlag failed for %s: %+v", key, err)
}
}
......@@ -80,42 +80,20 @@ var rootCmd = &cobra.Command{
recipientContact = user.GetContact()
}
// Set up reception handler
swboard := client.GetSwitchboard()
recvCh := make(chan message.Receive, 10000)
listenerID := swboard.RegisterChannel("DefaultCLIReceiver",
switchboard.AnyUser(), message.Text, recvCh)
jww.INFO.Printf("Message ListenerID: %v", listenerID)
confCh, recvCh := initClientCallbacks(client)
// Set up auth request handler, which simply prints the
// user id of the requester.
authMgr := client.GetAuthRegistrar()
authMgr.AddGeneralRequestCallback(printChanRequest)
// If unsafe channels, add auto-acceptor
// The following block is used to check if the request from
// a channel authorization is from the recipient we intend in
// this run.
authConfirmed := false
authMgr.AddGeneralConfirmCallback(func(
partner contact.Contact) {
jww.INFO.Printf("Channel Confirmed: %s",
partner.ID)
authConfirmed = recipientID.Cmp(partner.ID)
})
if viper.GetBool("unsafe-channel-creation") {
authMgr.AddGeneralRequestCallback(func(
requestor contact.Contact, message string) {
jww.INFO.Printf("Channel Request: %s",
requestor.ID)
_, err := client.ConfirmAuthenticatedChannel(
requestor)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
authConfirmed = recipientID.Cmp(
requestor.ID)
})
go func() {
for {
requestor := <-confCh
authConfirmed = recipientID.Cmp(requestor)
}
}()
_, err := client.StartNetworkFollower()
_, err := client.StartNetworkFollower(5 * time.Second)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
......@@ -270,7 +248,7 @@ var rootCmd = &cobra.Command{
}
fmt.Printf("Received %d\n", receiveCnt)
err = client.StopNetworkFollower(5 * time.Second)
err = client.StopNetworkFollower()
if err != nil {
jww.WARN.Printf(
"Failed to cleanly close threads: %+v\n",
......@@ -283,6 +261,44 @@ var rootCmd = &cobra.Command{
},
}
func initClientCallbacks(client *api.Client) (chan *id.ID,
chan message.Receive) {
// Set up reception handler
swboard := client.GetSwitchboard()
recvCh := make(chan message.Receive, 10000)
listenerID := swboard.RegisterChannel("DefaultCLIReceiver",
switchboard.AnyUser(), message.Text, recvCh)
jww.INFO.Printf("Message ListenerID: %v", listenerID)
// Set up auth request handler, which simply prints the
// user id of the requester.
authMgr := client.GetAuthRegistrar()
authMgr.AddGeneralRequestCallback(printChanRequest)
// If unsafe channels, add auto-acceptor
authConfirmed := make(chan *id.ID, 10)
authMgr.AddGeneralConfirmCallback(func(
partner contact.Contact) {
jww.INFO.Printf("Channel Confirmed: %s",
partner.ID)
authConfirmed <- partner.ID
})
if viper.GetBool("unsafe-channel-creation") {
authMgr.AddGeneralRequestCallback(func(
requestor contact.Contact, message string) {
jww.INFO.Printf("Channel Request: %s",
requestor.ID)
_, err := client.ConfirmAuthenticatedChannel(
requestor)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
authConfirmed <- requestor.ID
})
}
return authConfirmed, recvCh
}
// Helper function which prints the round resuls
func printRoundResults(allRoundsSucceeded, timedOut bool,
rounds map[id.Round]api.RoundResult, roundIDs []id.Round, msg message.Send) {
......@@ -352,7 +368,6 @@ func createClient() *api.Client {
err = api.NewClient(string(ndfJSON), storeDir,
[]byte(pass), regCode)
}
}
if err != nil {
......
......@@ -62,7 +62,7 @@ var singleCmd = &cobra.Command{
})
}
_, err := client.StartNetworkFollower()
_, err := client.StartNetworkFollower(5 * time.Second)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
......
......@@ -62,7 +62,7 @@ var udCmd = &cobra.Command{
})
}
_, err := client.StartNetworkFollower()
_, err := client.StartNetworkFollower(50 * time.Millisecond)
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
......@@ -176,7 +176,7 @@ var udCmd = &cobra.Command{
}
if len(facts) == 0 {
err = client.StopNetworkFollower(10 * time.Second)
err = client.StopNetworkFollower()
if err != nil {
jww.WARN.Print(err)
}
......@@ -196,7 +196,7 @@ var udCmd = &cobra.Command{
jww.FATAL.Panicf("%+v", err)
}
time.Sleep(91 * time.Second)
err = client.StopNetworkFollower(90 * time.Second)
err = client.StopNetworkFollower()
if err != nil {
jww.WARN.Print(err)
}
......
......@@ -17,13 +17,13 @@ require (
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/viper v1.7.1
gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228
gitlab.com/elixxir/comms v0.0.4-0.20210603164716-b39b297bbb57
gitlab.com/elixxir/crypto v0.0.7-0.20210603164430-a52be8b1a3e8
gitlab.com/elixxir/comms v0.0.4-0.20210607222512-0f2e89b475b4
gitlab.com/elixxir/crypto v0.0.7-0.20210607221512-0a9bff216f7c
gitlab.com/elixxir/ekv v0.1.5
gitlab.com/elixxir/primitives v0.0.3-0.20210603164310-4bd6e45e65e1
gitlab.com/elixxir/primitives v0.0.3-0.20210607210820-afd1b028b558
gitlab.com/xx_network/comms v0.0.4-0.20210603164237-d0c36076d7f0
gitlab.com/xx_network/crypto v0.0.5-0.20210603164136-743cb9b0a967
gitlab.com/xx_network/primitives v0.0.4-0.20210603164056-0abf3f914f25
gitlab.com/xx_network/primitives v0.0.4-0.20210607221158-361a2cbc5529
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
......
......@@ -247,20 +247,20 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0=
github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA=
gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k=
gitlab.com/elixxir/comms v0.0.4-0.20210603164716-b39b297bbb57 h1:7brO8ImMildCKPL1EXYgWPGz4GEIKBRSZmTGeGD33n8=
gitlab.com/elixxir/comms v0.0.4-0.20210603164716-b39b297bbb57/go.mod h1:s+aHLLk2bJOajrTb237G3J3gRUFkFWMddnaGOgt5Deg=
gitlab.com/elixxir/comms v0.0.4-0.20210607222512-0f2e89b475b4 h1:9h/Dkvu8t4/Q2LY81qoxjbO4GJtVanjWV3B9UMuZgDA=
gitlab.com/elixxir/comms v0.0.4-0.20210607222512-0f2e89b475b4/go.mod h1:Ox1NgdvFRy4/AfWAIrKZR9W6O+PAYeNOZviGbhqH+eo=
gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
gitlab.com/elixxir/crypto v0.0.7-0.20210603164430-a52be8b1a3e8 h1:3d4I+PxwyhvLwj6jInDO43kCGcSiybhrREiYH9jsFmc=
gitlab.com/elixxir/crypto v0.0.7-0.20210603164430-a52be8b1a3e8/go.mod h1:WacVVYlNPYbeDR1gBRJHtAx6T5P6ZZZ0tTHW5GTdi5Q=
gitlab.com/elixxir/crypto v0.0.7-0.20210607221512-0a9bff216f7c h1:G4IE4xEnoapQoZsMK1XRAB5QdOr2z+IyxZ6JOIXXuQ8=
gitlab.com/elixxir/crypto v0.0.7-0.20210607221512-0a9bff216f7c/go.mod h1:HAW5sLSUHKgylen7C4YoVNr0kp5g21/eKJw6sCuBRFs=
gitlab.com/elixxir/ekv v0.1.5 h1:R8M1PA5zRU1HVnTyrtwybdABh7gUJSCvt1JZwUSeTzk=
gitlab.com/elixxir/ekv v0.1.5/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4=
gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8=
gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc=
gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE=
gitlab.com/elixxir/primitives v0.0.3-0.20210603164310-4bd6e45e65e1 h1:RZFD4TbJCBO36WYGVH7gakUKbumFsAUsuEqR8ryYF/A=
gitlab.com/elixxir/primitives v0.0.3-0.20210603164310-4bd6e45e65e1/go.mod h1:1EFCSsERjE5RagCjys/70sqkAZC5PimEkWHcljh4bwQ=
gitlab.com/elixxir/primitives v0.0.3-0.20210607210820-afd1b028b558 h1:J8FllvIDv5RkON+Bg61NxNr78cYLOLifRrg/ugm5mW8=
gitlab.com/elixxir/primitives v0.0.3-0.20210607210820-afd1b028b558/go.mod h1:1EFCSsERjE5RagCjys/70sqkAZC5PimEkWHcljh4bwQ=
gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
gitlab.com/xx_network/comms v0.0.4-0.20210603164237-d0c36076d7f0 h1:+/U+6Ra5pqDhIHCqMniESovsakooCFTv/omlpfvffU8=
gitlab.com/xx_network/comms v0.0.4-0.20210603164237-d0c36076d7f0/go.mod h1:cpogFfWweZFzldGnRmgI9ilW2IFyeINDXpa1sAP950U=
......@@ -271,8 +271,9 @@ gitlab.com/xx_network/crypto v0.0.5-0.20210603164136-743cb9b0a967/go.mod h1:qOOP
gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA=
gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug=
gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
gitlab.com/xx_network/primitives v0.0.4-0.20210603164056-0abf3f914f25 h1:SH3pO8rAj59B6LLFDYWNrgj5yUncT7/TBXrSyORbQRQ=
gitlab.com/xx_network/primitives v0.0.4-0.20210603164056-0abf3f914f25/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
gitlab.com/xx_network/primitives v0.0.4-0.20210607221158-361a2cbc5529 h1:zC1z2Pcxy+fQc3ZzRxz2lOorj1LqBms3xohIVmThPb0=
gitlab.com/xx_network/primitives v0.0.4-0.20210607221158-361a2cbc5529/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5 h1:FY+4Rh1Q2rgLyv10aKJjhWApuKRCR/054XhreudfAvw=
gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment