diff --git a/bindings/broadcast.go b/bindings/broadcast.go index 9dc521d03a88ecb523c7f2aaf9f911e144f10aa4..adcae331543392ab5d8023d3b9c1c026beacaba7 100644 --- a/bindings/broadcast.go +++ b/bindings/broadcast.go @@ -44,9 +44,11 @@ type ChannelDef struct { // BroadcastMessage is the bindings representation of a broadcast message. // -// Example JSON: -// {"RoundID":42, +// BroadcastMessage Example JSON: +// { +// "RoundID":42, // "EphID":[0,0,0,0,0,0,24,61], +// "RoundURL":"https://dashboard.xx.network/rounds/25?xxmessenger=true", // "Payload":"SGVsbG8sIGJyb2FkY2FzdCBmcmllbmRzIQ==" // } type BroadcastMessage struct { @@ -57,13 +59,16 @@ type BroadcastMessage struct { // BroadcastReport is the bindings representation of the info on how a broadcast // message was sent // -// Example JSON: -// {"RoundID":42, -// "EphID":[0,0,0,0,0,0,24,61] +// BroadcastReport Example JSON: +// { +// "Rounds": [25, 26, 29], +// "EphID":[0,0,0,0,0,0,24,61], +// "RoundURL":"https://dashboard.xx.network/rounds/25?xxmessenger=true" // } type BroadcastReport struct { RoundsList - EphID ephemeral.Id + RoundURL string + EphID ephemeral.Id } // BroadcastListener is the public function type bindings can use to listen for @@ -131,6 +136,7 @@ func (c *Channel) Listen(l BroadcastListener, method int) error { l.Callback(json.Marshal(&BroadcastMessage{ BroadcastReport: BroadcastReport{ RoundsList: makeRoundsList(round.ID), + RoundURL: getRoundURL(round.ID), EphID: receptionID.EphId, }, Payload: payload, @@ -152,6 +158,7 @@ func (c *Channel) Broadcast(payload []byte) ([]byte, error) { return nil, err } return json.Marshal(BroadcastReport{ + RoundURL: getRoundURL(rid.ID), RoundsList: makeRoundsList(rid.ID), EphID: eid, }) @@ -174,6 +181,7 @@ func (c *Channel) BroadcastAsymmetric(payload, pk []byte) ([]byte, error) { } return json.Marshal(BroadcastReport{ RoundsList: makeRoundsList(rid.ID), + RoundURL: getRoundURL(rid.ID), EphID: eid, }) } diff --git a/bindings/channels.go b/bindings/channels.go new file mode 100644 index 0000000000000000000000000000000000000000..872512c34148c12cea6b73f61ff18104dac92b63 --- /dev/null +++ b/bindings/channels.go @@ -0,0 +1,665 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 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" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/channels" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/xxdk" + cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" + cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "sync" + "time" +) + +//////////////////////////////////////////////////////////////////////////////// +// Singleton Tracker // +//////////////////////////////////////////////////////////////////////////////// + +// channelManagerTrackerSingleton is used to track ChannelsManager objects +// so that they can be referenced by ID back over the bindings. +var channelManagerTrackerSingleton = &channelManagerTracker{ + tracked: make(map[int]*ChannelsManager), + count: 0, +} + +// channelManagerTracker is a singleton used to keep track of extant +// ChannelsManager objects, preventing race conditions created by passing it +// over the bindings. +type channelManagerTracker struct { + tracked map[int]*ChannelsManager + count int + mux sync.RWMutex +} + +// make create a ChannelsManager from an [channels.Manager], assigns it a unique +// ID, and adds it to the channelManagerTracker. +func (cmt *channelManagerTracker) make(c channels.Manager) *ChannelsManager { + cmt.mux.Lock() + defer cmt.mux.Unlock() + + chID := cmt.count + cmt.count++ + + cmt.tracked[chID] = &ChannelsManager{ + api: c, + id: chID, + } + + return cmt.tracked[chID] +} + +// get an ChannelsManager from the channelManagerTracker given its ID. +func (cmt *channelManagerTracker) get(id int) (*ChannelsManager, error) { + cmt.mux.RLock() + defer cmt.mux.RUnlock() + + c, exist := cmt.tracked[id] + if !exist { + return nil, errors.Errorf( + "Cannot get ChannelsManager for ID %d, does not exist", id) + } + + return c, nil +} + +// delete removes a ChannelsManager from the channelManagerTracker. +func (cmt *channelManagerTracker) delete(id int) { + cmt.mux.Lock() + defer cmt.mux.Unlock() + + delete(cmt.tracked, id) +} + +//////////////////////////////////////////////////////////////////////////////// +// Basic Channel API // +//////////////////////////////////////////////////////////////////////////////// + +// ChannelsManager is a bindings-layer struct that wraps a [channels.Manager] +// interface. +type ChannelsManager struct { + api channels.Manager + id int +} + +// GetID returns the channelManagerTracker ID for the ChannelsManager object. +func (cm *ChannelsManager) GetID() int { + return cm.id +} + +// NewChannelsManager constructs a ChannelsManager. +// FIXME: This is a work in progress and should not be used an event model is +// implemented in the style of the bindings layer's AuthCallbacks. Remove this +// note when that has been done. +// +// Parameters: +// - e2eID - The tracked e2e object ID. This can be retrieved using +// [E2e.GetID]. +// - udID - The tracked UD object ID. This can be retrieved using +// [UserDiscovery.GetID]. +func NewChannelsManager(e2eID, udID int) (*ChannelsManager, error) { + // Get user from singleton + user, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + udMan, err := udTrackerSingleton.get(udID) + if err != nil { + return nil, err + } + + nameService, err := udMan.api.StartChannelNameService() + if err != nil { + return nil, err + } + + // Construct new channels manager + // TODO: Implement a bindings layer event model, pass that in as a parameter + // or the function and pass that into here. + m := channels.NewManager(user.api.GetStorage().GetKV(), user.api.GetCmix(), + user.api.GetRng(), nameService, nil) + + // Add channel to singleton and return + return channelManagerTrackerSingleton.make(m), nil +} + +// JoinChannel joins the given channel. It will fail if the channel has already +// been joined. +// +// Parameters: +// - channelJson - A JSON encoded [ChannelDef]. +func (cm *ChannelsManager) JoinChannel(channelJson []byte) error { + // Unmarshal channel definition + def := ChannelDef{} + err := json.Unmarshal(channelJson, &def) + if err != nil { + return err + } + + // Construct ID using the embedded cryptographic information + channelId, err := cryptoBroadcast.NewChannelID(def.Name, def.Description, + def.Salt, def.PubKey) + if err != nil { + return err + } + + // Construct public key into object + rsaPubKey, err := rsa.LoadPublicKeyFromPem(def.PubKey) + if err != nil { + return err + } + + // Construct cryptographic channel object + channel := &cryptoBroadcast.Channel{ + ReceptionID: channelId, + Name: def.Name, + Description: def.Description, + Salt: def.Salt, + RsaPubKey: rsaPubKey, + } + + // Join the channel using the API + return cm.api.JoinChannel(channel) +} + +// GetChannels returns the IDs of all channels that have been joined. +// +// Returns: +// - []byte - A JSON marshalled list of IDs. +// +// JSON Example: +// { +// U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", +// "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" +// } +func (cm *ChannelsManager) GetChannels() ([]byte, error) { + channelIds := cm.api.GetChannels() + return json.Marshal(channelIds) +} + +// GetChannelId returns the ID of the channel given the channel's cryptographic +// information. +// +// Parameters: +// - channelJson - A JSON encoded [ChannelDef]. This may be retrieved from +// [Channel.Get], for example. +// +// Returns: +// - []byte - A JSON encoded channel ID ([id.ID]). +// +// JSON Example: +// "dGVzdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD" +func (cm *ChannelsManager) GetChannelId(channelJson []byte) ([]byte, error) { + def := ChannelDef{} + err := json.Unmarshal(channelJson, &def) + if err != nil { + return nil, err + } + + channelId, err := cryptoBroadcast.NewChannelID(def.Name, def.Description, + def.Salt, def.PubKey) + if err != nil { + return nil, err + } + + return json.Marshal(channelId) +} + +// GetChannel returns the underlying cryptographic structure for a given +// channel. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +// +// Returns: +// - []byte - A JSON marshalled ChannelDef. +func (cm *ChannelsManager) GetChannel(marshalledChanId []byte) ([]byte, error) { + // Unmarshal ID + chanId, err := id.Unmarshal(marshalledChanId) + if err != nil { + return nil, err + } + + // Retrieve channel from manager + def, err := cm.api.GetChannel(chanId) + if err != nil { + return nil, err + } + + // Marshal channel + return json.Marshal(&ChannelDef{ + Name: def.Name, + Description: def.Description, + Salt: def.Salt, + PubKey: rsa.CreatePublicKeyPem(def.RsaPubKey), + }) +} + +// LeaveChannel leaves the given channel. It will return an error if the +// channel was not previously joined. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +func (cm *ChannelsManager) LeaveChannel(marshalledChanId []byte) error { + // Unmarshal channel ID + channelId, err := id.Unmarshal(marshalledChanId) + if err != nil { + return err + } + + // Leave the channel + return cm.api.LeaveChannel(channelId) +} + +// ReplayChannel replays all messages from the channel within the network's +// memory (~3 weeks) over the event model. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +func (cm *ChannelsManager) ReplayChannel(marshalledChanId []byte) error { + + // Unmarshal channel ID + chanId, err := id.Unmarshal(marshalledChanId) + if err != nil { + return err + } + + // Replay channel + return cm.api.ReplayChannel(chanId) +} + +//////////////////////////////////////////////////////////////////////////////// +// Channel Sending Methods & Reports // +//////////////////////////////////////////////////////////////////////////////// + +// ChannelSendReport is the bindings' representation of the return values of +// ChannelsManager's Send operations. +// +// JSON Example: +// { +// "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", +// "Rounds":[1,5,9], +// "EphId": 0 +// } +type ChannelSendReport struct { + MessageId []byte + RoundsList + EphId int64 +} + +// SendGeneric is used to send a raw message over a channel. In general, it +// should be wrapped in a function that defines the wire protocol. If the final +// message, before being sent over the wire, is too long, this will return an +// error. Due to the underlying encoding using compression, it isn't possible to +// define the largest payload that can be sent, but it will always be possible +// to send a payload of 802 bytes at minimum. The meaning of validUntil depends +// on the use case. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +// - messageType - The message type of the message. This will be a valid +// [channels.MessageType]. +// - message - The contents of the message. This need not be of data type +// string, as the message could be a specified format that the channel may +// recognize. +// - leaseTimeMS - The lease of the message. This will be how long the message +// is valid until, in milliseconds. As per the channels.Manager +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here. +// - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, +// and GetDefaultCMixParams will be used internally. +// +// Returns: +// - []byte - A JSON marshalled ChannelSendReport. +func (cm *ChannelsManager) SendGeneric(marshalledChanId []byte, + messageType int, message []byte, leaseTimeMS int64, + cmixParamsJSON []byte) ([]byte, error) { + + // Unmarshal channel ID and parameters + chanId, params, err := parseChannelsParameters( + marshalledChanId, cmixParamsJSON) + if err != nil { + return nil, err + } + + // Send message + chanMsgId, rndId, ephId, err := cm.api.SendGeneric(chanId, + channels.MessageType(messageType), message, + time.Duration(leaseTimeMS), params.CMIX) + if err != nil { + return nil, err + } + + // Construct send report + return constructChannelSendReport(chanMsgId, rndId, ephId) +} + +// SendAdminGeneric is used to send a raw message over a channel encrypted with +// admin keys, identifying it as sent by the admin. In general, it should be +// wrapped in a function that defines the wire protocol. If the final message, +// before being sent over the wire, is too long, this will return an error. The +// message must be at most 510 bytes long. +// +// Parameters: +// - adminPrivateKey - The PEM-encoded admin RSA private key. +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +// - messageType - The message type of the message. This will be a valid +// [channels.MessageType]. +// - message - The contents of the message. The message should be at most 510 +// bytes. This need not be of data type string, as the message could be a +// specified format that the channel may recognize. +// - leaseTimeMS - The lease of the message. This will be how long the message +// is valid until, in milliseconds. As per the channels.Manager +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here. +// - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, +// and GetDefaultCMixParams will be used internally. +// +// Returns: +// - []byte - A JSON marshalled ChannelSendReport. +func (cm *ChannelsManager) SendAdminGeneric(adminPrivateKey, + marshalledChanId []byte, + messageType int, message []byte, leaseTimeMS int64, + cmixParamsJSON []byte) ([]byte, error) { + + // Load private key from file + rsaPrivKey, err := rsa.LoadPrivateKeyFromPem(adminPrivateKey) + if err != nil { + return nil, err + } + + // Unmarshal channel ID and parameters + chanId, params, err := parseChannelsParameters( + marshalledChanId, cmixParamsJSON) + if err != nil { + return nil, err + } + + // Send admin message + chanMsgId, rndId, ephId, err := cm.api.SendAdminGeneric(rsaPrivKey, + chanId, channels.MessageType(messageType), message, + time.Duration(leaseTimeMS), params.CMIX) + + // Construct send report + return constructChannelSendReport(chanMsgId, rndId, ephId) +} + +// SendMessage is used to send a formatted message over a channel. +// Due to the underlying encoding using compression, it isn't possible to define +// the largest payload that can be sent, but it will always be possible to send +// a payload of 798 bytes at minimum. +// +// The message will auto delete validUntil after the round it is sent in, +// lasting forever if [channels.ValidForever] is used. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +// - message - The contents of the message. The message should be at most 510 +// bytes. This is expected to be Unicode, and thus a string data type is +// expected +// - leaseTimeMS - The lease of the message. This will be how long the message +// is valid until, in milliseconds. As per the channels.Manager +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here. +// - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be +// empty, and GetDefaultCMixParams will be used internally. +// +// Returns: +// - []byte - A JSON marshalled ChannelSendReport +func (cm *ChannelsManager) SendMessage(marshalledChanId []byte, + message string, leaseTimeMS int64, cmixParamsJSON []byte) ([]byte, error) { + + // Unmarshal channel ID and parameters + chanId, params, err := parseChannelsParameters( + marshalledChanId, cmixParamsJSON) + if err != nil { + return nil, err + } + + // Send message + chanMsgId, rndId, ephId, err := cm.api.SendMessage(chanId, message, + time.Duration(leaseTimeMS), params.CMIX) + if err != nil { + return nil, err + } + + // Construct send report + return constructChannelSendReport(chanMsgId, rndId, ephId) +} + +// SendReply is used to send a formatted message over a channel. +// Due to the underlying encoding using compression, it isn't possible to define +// the largest payload that can be sent, but it will always be possible to send +// a payload of 766 bytes at minimum. +// +// If the message ID the reply is sent to does not exist, then the other side +// will post the message as a normal message and not a reply. +// The message will auto delete validUntil after the round it is sent in, +// lasting forever if ValidForever is used. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +// - message - The contents of the message. The message should be at most 510 +// bytes. This is expected to be Unicode, and thus a string data type is +// expected. +// - messageToReactTo - The marshalled [channel.MessageID] of the message you +// wish to reply to. This may be found in the ChannelSendReport if replying +// to your own. Alternatively, if reacting to another user's message, you may +// retrieve it via the ChannelMessageReceptionCallback registered using +// RegisterReceiveHandler. +// - leaseTimeMS - The lease of the message. This will be how long the message +// is valid until, in milliseconds. As per the channels.Manager +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here. +// - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, +// and GetDefaultCMixParams will be used internally. +// +// Returns: +// - []byte - A JSON marshalled ChannelSendReport +func (cm *ChannelsManager) SendReply(marshalledChanId []byte, + message string, messageToReactTo []byte, leaseTimeMS int64, + cmixParamsJSON []byte) ([]byte, error) { + + // Unmarshal channel ID and parameters + chanId, params, err := parseChannelsParameters( + marshalledChanId, cmixParamsJSON) + if err != nil { + return nil, err + } + + // Unmarshal message ID + msgId := cryptoChannel.MessageID{} + copy(msgId[:], messageToReactTo) + + // Send Reply + chanMsgId, rndId, ephId, err := cm.api.SendReply(chanId, message, + msgId, time.Duration(leaseTimeMS), params.CMIX) + if err != nil { + return nil, err + } + + // Construct send report + return constructChannelSendReport(chanMsgId, rndId, ephId) +} + +// SendReaction is used to send a reaction to a message over a channel. +// The reaction must be a single emoji with no other characters, and will +// be rejected otherwise. +// Users will drop the reaction if they do not recognize the reactTo message. +// +// Parameters: +// - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be +// retrieved using ChannelsManager.GetChannelId. +// - reaction - The user's reaction. This should be a single emoji with no +// other characters. As such, a Unicode string is expected. +// - messageToReactTo - The marshalled [channel.MessageID] of the message you +// wish to reply to. This may be found in the ChannelSendReport if replying +// to your own. Alternatively, if reacting to another user's message, you may +// retrieve it via the ChannelMessageReceptionCallback registered using +// RegisterReceiveHandler. +// - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, +// and GetDefaultCMixParams will be used internally. +// +// Returns: +// - []byte - A JSON marshalled ChannelSendReport. +func (cm *ChannelsManager) SendReaction(marshalledChanId []byte, + reaction string, messageToReactTo []byte, + cmixParamsJSON []byte) ([]byte, error) { + + // Unmarshal channel ID and parameters + chanId, params, err := parseChannelsParameters( + marshalledChanId, cmixParamsJSON) + if err != nil { + return nil, err + } + + // Unmarshal message ID + msgId := cryptoChannel.MessageID{} + copy(msgId[:], messageToReactTo) + + // Send reaction + chanMsgId, rndId, ephId, err := cm.api.SendReaction(chanId, + reaction, msgId, params.CMIX) + if err != nil { + return nil, err + } + + // Construct send report + return constructChannelSendReport(chanMsgId, rndId, ephId) +} + +// parseChannelsParameters is a helper function for the Send functions. It +// parses the channel ID and the passed in parameters into their respective +// objects. These objects are passed into the API via the internal send +// functions. +func parseChannelsParameters(marshalledChanId, cmixParamsJSON []byte) ( + *id.ID, xxdk.CMIXParams, error) { + // Unmarshal channel ID + chanId, err := id.Unmarshal(marshalledChanId) + if err != nil { + return nil, xxdk.CMIXParams{}, err + } + + // Unmarshal cmix params + params, err := parseCMixParams(cmixParamsJSON) + if err != nil { + return nil, xxdk.CMIXParams{}, err + } + + return chanId, params, nil +} + +// constructChannelSendReport is a helper function which returns a JSON +// marshalled ChannelSendReport. +func constructChannelSendReport(channelMessageId cryptoChannel.MessageID, + roundId id.Round, ephId ephemeral.Id) ([]byte, error) { + // Construct send report + chanSendReport := ChannelSendReport{ + MessageId: channelMessageId.Bytes(), + RoundsList: makeRoundsList(roundId), + EphId: ephId.Int64(), + } + + // Marshal send report + return json.Marshal(chanSendReport) +} + +//////////////////////////////////////////////////////////////////////////////// +// Channel Receiving Logic and Callback Registration // +//////////////////////////////////////////////////////////////////////////////// + +// ReceivedChannelMessageReport is a report structure returned via the +// ChannelMessageReceptionCallback. This report gives the context for the +// channel the message was sent to and the message itself. This is returned via +// the callback as JSON marshalled bytes. +// +// JSON Example: +// { +// "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", +// "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", +// "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", +// "MessageType": 42, +// "SenderUsername": "hunter2", +// "Content": "YmFuX2JhZFVTZXI=", +// "Timestamp": 1662502150335283000, +// "Lease": 25, +// "Rounds": [ 1, 4, 9], +// } +type ReceivedChannelMessageReport struct { + ChannelId []byte + MessageId []byte + MessageType int + SenderUsername string + Content []byte + Timestamp int64 + Lease int64 + RoundsList +} + +// ChannelMessageReceptionCallback is the callback that returns the context for +// a channel message via the Callback. +type ChannelMessageReceptionCallback interface { + Callback(receivedChannelMessageReport []byte, err error) +} + +// RegisterReceiveHandler is used to register handlers for non-default message +// types. They can be processed by modules. It is important that such modules +// sync up with the event model implementation. +// +// There can only be one handler per [channels.MessageType], and this will +// return an error on any re-registration. +// +// Parameters: +// - messageType - represents the [channels.MessageType] which will have a +// registered listener. +// - listenerCb - the callback which will be executed when a channel message +// of messageType is received. +func (cm *ChannelsManager) RegisterReceiveHandler(messageType int, + listenerCb ChannelMessageReceptionCallback) error { + + // Wrap callback around backend interface + cb := channels.MessageTypeReceiveMessage( + func(channelID *id.ID, messageID cryptoChannel.MessageID, + messageType channels.MessageType, senderUsername string, + content []byte, timestamp time.Time, lease time.Duration, + round rounds.Round) { + + rcm := ReceivedChannelMessageReport{ + ChannelId: channelID.Marshal(), + MessageId: messageID.Bytes(), + MessageType: int(messageType), + SenderUsername: senderUsername, + Content: content, + Timestamp: timestamp.UnixNano(), + Lease: int64(lease), + RoundsList: makeRoundsList(round.ID), + } + + listenerCb.Callback(json.Marshal(rcm)) + }) + + // Register handler + return cm.api.RegisterReceiveHandler(channels.MessageType(messageType), cb) +} diff --git a/bindings/cmix.go b/bindings/cmix.go index 422740df88391fcdd91e54848520d38dfeb28a8d..5b5683f8aedb3e0e57f087de749c5e090823f0c0 100644 --- a/bindings/cmix.go +++ b/bindings/cmix.go @@ -60,10 +60,6 @@ func NewCmix(ndfJSON, storageDir string, password []byte, registrationCode strin // subprocesses to perform network operations. func LoadCmix(storageDir string, password []byte, cmixParamsJSON []byte) (*Cmix, error) { - if len(cmixParamsJSON) == 0 { - jww.WARN.Printf("cMix params not specified, using defaults...") - cmixParamsJSON = GetDefaultCMixParams() - } params, err := parseCMixParams(cmixParamsJSON) if err != nil { diff --git a/bindings/connect.go b/bindings/connect.go index faaffc1956afc88ef17762cf52bd22fc09ae737e..df3d4ae46c7f7c0a016ecd75fb576e0e48c98e87 100644 --- a/bindings/connect.go +++ b/bindings/connect.go @@ -94,6 +94,7 @@ func (c *Connection) SendE2E(mt int, payload []byte) ([]byte, error) { sr := E2ESendReport{ RoundsList: makeRoundsList(sendReport.RoundList...), + RoundURL: getRoundURL(sendReport.RoundList[0]), MessageID: sendReport.MessageId.Marshal(), Timestamp: sendReport.SentTime.UnixNano(), KeyResidue: sendReport.KeyResidue.Marshal(), diff --git a/bindings/delivery.go b/bindings/delivery.go index 92ccdd759425226f206bac0f7f280fefdbd70f60..b420a58fc7d80a04609bc55dd100f58399a7d5ea 100644 --- a/bindings/delivery.go +++ b/bindings/delivery.go @@ -9,6 +9,7 @@ package bindings import ( "encoding/json" + "fmt" "time" "github.com/pkg/errors" @@ -17,9 +18,33 @@ import ( "gitlab.com/xx_network/primitives/id" ) +// dashboardBaseURL is the base of the xx network's round dashboard URL. +// This should be used by any type of send report's GetRoundURL method. +var dashboardBaseURL = "https://dashboard.xx.network" + +// SetDashboardURL is a function which modifies the base dashboard URL +// that is returned as part of any send report. Internally, this is defaulted +// to "https://dashboard.xx.network". This should only be called if the user +// explicitly wants to modify the dashboard URL. This function is not +// thread-safe, and as such should only be called on setup. +// +// Parameters: +// - newURL - A valid URL that will be used for round look up on any send +// report. +func SetDashboardURL(newURL string) { + dashboardBaseURL = newURL +} + +// getRoundURL is a helper function which returns the specific round +// within any type of send report, if they have a round in their RoundsList. +// This helper function is messenger specific. +func getRoundURL(round id.Round) string { + return fmt.Sprintf("%s/rounds/%d?xxmessenger=true", dashboardBaseURL, round) +} + // RoundsList contains a list of round IDs. // -// Example marshalled roundList object: +// JSON Example: // [1001,1003,1006] type RoundsList struct { Rounds []uint64 diff --git a/bindings/e2eHandler.go b/bindings/e2eHandler.go index 8f53305e39b40a281b7bc9780d1dae091a2c9989..4b63407520f8e1aa756f2e749e4b0971fc72fdd2 100644 --- a/bindings/e2eHandler.go +++ b/bindings/e2eHandler.go @@ -23,15 +23,17 @@ import ( // E2ESendReport is the bindings' representation of the return values of // SendE2E. // -// Example E2ESendReport: -//{ -//"Rounds": [ 1, 4, 9], -//"MessageID": "iM34yCIr4Je8ZIzL9iAAG1UWAeDiHybxMTioMAaezvs=", -//"Timestamp": 1661532254302612000, -//"KeyResidue": "9q2/A69EAuFM1hFAT7Bzy5uGOQ4T6bPFF72h5PlgCWE=" -//} +// E2ESendReport Example JSON: +// { +// "Rounds": [ 1, 4, 9], +// "RoundURL":"https://dashboard.xx.network/rounds/25?xxmessenger=true", +// "MessageID": "iM34yCIr4Je8ZIzL9iAAG1UWAeDiHybxMTioMAaezvs=", +// "Timestamp": 1661532254302612000, +// "KeyResidue": "9q2/A69EAuFM1hFAT7Bzy5uGOQ4T6bPFF72h5PlgCWE=" +// } type E2ESendReport struct { RoundsList + RoundURL string MessageID []byte Timestamp int64 KeyResidue []byte @@ -45,6 +47,19 @@ func (e *E2e) GetReceptionID() []byte { return e.api.GetE2E().GetReceptionID().Marshal() } +// DeleteContact removes a partner from E2e's storage. +// +// Parameters: +// - partnerID - the marshalled bytes of id.ID. +func (e *E2e) DeleteContact(partnerID []byte) error { + partner, err := id.Unmarshal(partnerID) + if err != nil { + return err + } + + return e.api.DeleteContact(partner) +} + // GetAllPartnerIDs returns a list of all partner IDs that the user has an E2E // relationship with. // @@ -147,6 +162,7 @@ func (e *E2e) SendE2E(messageType int, recipientId, payload, result := E2ESendReport{ RoundsList: makeRoundsList(sendReport.RoundList...), + RoundURL: getRoundURL(sendReport.RoundList[0]), MessageID: sendReport.MessageId.Marshal(), Timestamp: sendReport.SentTime.UnixNano(), KeyResidue: sendReport.KeyResidue.Marshal(), diff --git a/bindings/group.go b/bindings/group.go index 6b2041bdb548f3d3f9ccb5b6758e1be45c28faca..cd40636424304646f286bedd107b446ac343e16a 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -157,6 +157,7 @@ func (g *GroupChat) MakeGroup( report := GroupReport{ Id: grp.ID.Bytes(), RoundsList: makeRoundsList(roundIDs...), + RoundURL: getRoundURL(roundIDs[0]), Status: int(status), } @@ -199,6 +200,7 @@ func (g *GroupChat) ResendRequest(groupId []byte) ([]byte, error) { report := &GroupReport{ Id: grp.ID.Bytes(), RoundsList: makeRoundsList(rnds...), + RoundURL: getRoundURL(rnds[0]), Status: int(status), } @@ -269,6 +271,7 @@ func (g *GroupChat) Send(groupId, message []byte, tag string) ([]byte, error) { // Construct send report sendReport := &GroupSendReport{ + RoundURL: getRoundURL(round.ID), RoundsList: makeRoundsList(round.ID), Timestamp: timestamp.UnixNano(), MessageID: msgID.Bytes(), @@ -407,6 +410,7 @@ type GroupRequest interface { } // GroupChatProcessor manages the handling of received group chat messages. +// The decryptedMessage field will be a JSON marshalled GroupChatMessage. type GroupChatProcessor interface { Process(decryptedMessage, msg, receptionId []byte, ephemeralId, roundId int64, err error) @@ -419,13 +423,53 @@ type groupChatProcessor struct { bindingsCb GroupChatProcessor } +// GroupChatMessage is the bindings layer representation of the +// [groupChat.MessageReceive]. +// +// GroupChatMessage Example JSON: +// { +// "GroupId": "AAAAAAAJlasAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE", +// "SenderId": "AAAAAAAAB8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", +// "MessageId": "Zm9ydHkgZml2ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", +// "Payload": "Zm9ydHkgZml2ZQ==", +// "Timestamp": 1663009269474079000 +// } +type GroupChatMessage struct { + // GroupId is the ID of the group that this message was sent on. + GroupId []byte + + // SenderId is the ID of the sender of this message. + SenderId []byte + + // MessageId is the ID of this group message. + MessageId []byte + + // Payload is the content of the message. + Payload []byte + + // Timestamp is the time this message was sent on. + Timestamp int64 +} + +// convertMessageReceive is a helper function which converts a +// [groupChat.MessageReceive] to the bindings-layer representation GroupChatMessage. +func convertMessageReceive(decryptedMsg gc.MessageReceive) GroupChatMessage { + return GroupChatMessage{ + GroupId: decryptedMsg.GroupID.Bytes(), + SenderId: decryptedMsg.SenderID.Bytes(), + MessageId: decryptedMsg.ID.Bytes(), + Payload: decryptedMsg.Payload, + Timestamp: decryptedMsg.Timestamp.UnixNano(), + } +} + // 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) + decryptedMessage, err = json.Marshal(convertMessageReceive(decryptedMsg)) message = msg.Marshal() receptionId = receptionID.Source.Marshal() ephemeralId = receptionID.EphId.Int64() @@ -451,16 +495,34 @@ func (gcp *groupChatProcessor) String() string { // 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. +// +// Example GroupReport JSON: +// { +// "Id": "AAAAAAAAAM0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE", +// "Rounds": [25, 64], +// "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true", +// "Status": 1 +// } type GroupReport struct { Id []byte RoundsList - Status int + RoundURL string + Status int } // GroupSendReport is returned when sending a group message. It contains the // round ID sent on and the timestamp of the send operation. +// +// Example GroupSendReport JSON: +// { +// "Rounds": [25, 64], +// "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true", +// "Timestamp": 1662577352813112000, +// "MessageID": "69ug6FA50UT2q6MWH3hne9PkHQ+H9DnEDsBhc0m0Aww=" +// } type GroupSendReport struct { RoundsList + RoundURL string Timestamp int64 MessageID []byte } diff --git a/bindings/json_test.go b/bindings/json_test.go deleted file mode 100644 index 4dd6eeadcfe5f789aa59ef1f2a1ba7b372e2b709..0000000000000000000000000000000000000000 --- a/bindings/json_test.go +++ /dev/null @@ -1,40 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -package bindings - -import ( - "encoding/json" - "gitlab.com/elixxir/crypto/e2e" - "math/rand" - "testing" - "time" -) - -func TestName(t *testing.T) { - rl := []uint64{1, 4, 9} - prng := rand.New(rand.NewSource(42)) - rfp := make([]byte, 32) - prng.Read(rfp) - mid := e2e.NewMessageID(rfp, prng.Uint64()) - - randData := make([]byte, 32) - prng.Read(randData) - k := e2e.Key{} - copy(k[:], randData) - kr := e2e.NewKeyResidue(k) - - report := E2ESendReport{ - RoundsList: RoundsList{rl}, - MessageID: mid.Marshal(), - Timestamp: time.Now().UnixNano(), - KeyResidue: kr.Marshal(), - } - - marshal, _ := json.Marshal(report) - t.Logf("%s", marshal) -} diff --git a/bindings/listener.go b/bindings/listener.go index cc2bba18222e19a6f4bbf0f3f57948e7e9899872..8eaf0faff2e58bb188a9451f624d2eae1926bd9c 100644 --- a/bindings/listener.go +++ b/bindings/listener.go @@ -62,6 +62,7 @@ type Message struct { Encrypted bool RoundId int + RoundURL string } // Hear is called to receive a message in the UI. @@ -76,6 +77,7 @@ func (l listener) Hear(item receive.Message) { Timestamp: item.Timestamp.UnixNano(), Encrypted: item.Encrypted, RoundId: int(item.Round.ID), + RoundURL: getRoundURL(item.Round.ID), } result, err := json.Marshal(&m) if err != nil { diff --git a/bindings/params.go b/bindings/params.go index 79ad6201ee957e0c34c584d29dc6afe095b76ae8..f44c71edd2a13c0ae59a928f276619c43a2c1a2e 100644 --- a/bindings/params.go +++ b/bindings/params.go @@ -77,27 +77,42 @@ func GetDefaultE2eFileTransferParams() []byte { return data } +// parseE2eFileTransferParams is a helper function which parses a JSON +// marshalled [e2eFileTransfer.Params]. func parseE2eFileTransferParams(data []byte) (e2eFileTransfer.Params, error) { p := &e2eFileTransfer.Params{} return *p, p.UnmarshalJSON(data) } +// parseSingleUseParams is a helper function which parses a JSON marshalled +// [single.RequestParams]. func parseSingleUseParams(data []byte) (single.RequestParams, error) { p := &single.RequestParams{} return *p, p.UnmarshalJSON(data) } +// parseFileTransferParams is a helper function which parses a JSON marshalled +// [fileTransfer.Params]. func parseFileTransferParams(data []byte) (fileTransfer.Params, error) { p := &fileTransfer.Params{} return *p, p.UnmarshalJSON(data) } +// parseCMixParams is a helper function which parses a JSON marshalled +// [xxdk.CMIXParams]. func parseCMixParams(data []byte) (xxdk.CMIXParams, error) { + if len(data) == 0 { + jww.WARN.Printf("cMix params not specified, using defaults...") + data = GetDefaultCMixParams() + } + p := &xxdk.CMIXParams{} err := p.Unmarshal(data) return *p, err } +// parseE2EParams is a helper function which parses a JSON marshalled +// [xxdk.E2EParams]. func parseE2EParams(data []byte) (xxdk.E2EParams, error) { p := &xxdk.E2EParams{} err := p.Unmarshal(data) diff --git a/bindings/single.go b/bindings/single.go index 71c4420148dcf7049a4b4fdc911afd990b810838..d6625cd71d8fe027fa2a64ca1b146d30e6f6eef0 100644 --- a/bindings/single.go +++ b/bindings/single.go @@ -64,6 +64,7 @@ func TransmitSingleUse(e2eID int, recipient []byte, tag string, payload, EphID: eid.EphId.Int64(), ReceptionID: eid.Source, RoundsList: makeRoundsList(rids...), + RoundURL: getRoundURL(rids[0]), } return json.Marshal(sr) } @@ -99,14 +100,16 @@ func Listen(e2eID int, tag string, cb SingleUseCallback) (Stopper, error) { // SingleUseSendReport is the bindings-layer struct used to represent // information returned by single.TransmitRequest. // -// JSON example: +// SingleUseSendReport JSON example: // { // "Rounds":[1,5,9], +// "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true", // "EphID":1655533, // "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"} // } type SingleUseSendReport struct { RoundsList + RoundURL string ReceptionID *id.ID EphID int64 } @@ -115,9 +118,10 @@ type SingleUseSendReport struct { // information passed to the single.Response callback interface in response to // single.TransmitRequest. // -// JSON example: +// SingleUseResponseReport JSON example: // { // "Rounds":[1,5,9], +// "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true", // "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==", // "EphID":1655533, // "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}, @@ -125,6 +129,7 @@ type SingleUseSendReport struct { // } type SingleUseResponseReport struct { RoundsList + RoundURL string Payload []byte ReceptionID *id.ID EphID int64 @@ -134,16 +139,18 @@ type SingleUseResponseReport struct { // SingleUseCallbackReport is the bindings-layer struct used to represent // single -use messages received by a callback passed into single.Listen. // -// JSON example: -// { -// "Rounds":[1,5,9], -// "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==", -// "Partner":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", -// "EphID":1655533, -// "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"} -// } +// SingleUseCallbackReport JSON example: +// { +// "Rounds":[1,5,9], +// "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true", +// "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==", +// "Partner":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", +// "EphID":1655533, +// "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"} +// } type SingleUseCallbackReport struct { RoundsList + RoundURL string Payload []byte Partner *id.ID EphID int64 @@ -208,6 +215,7 @@ func (sl singleUseListener) Callback( scr := SingleUseCallbackReport{ Payload: req.GetPayload(), RoundsList: makeRoundsList(rids...), + RoundURL: getRoundURL(rids[0]), Partner: req.GetPartner(), EphID: eid.EphId.Int64(), ReceptionID: eid.Source, @@ -246,6 +254,7 @@ func (sr singleUseResponse) Callback(payload []byte, } sendReport := SingleUseResponseReport{ RoundsList: makeRoundsList(rids...), + RoundURL: getRoundURL(rids[0]), ReceptionID: receptionID.Source, EphID: receptionID.EphId.Int64(), Payload: payload, diff --git a/bindings/ud.go b/bindings/ud.go index a49031ad7fd9752a99315c6606c04706cbb1b800..d167e145daf1e110bf0728cf13bc28c54287538a 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -414,6 +414,7 @@ func LookupUD(e2eID int, udContact []byte, cb UdLookupCallback, EphID: eid.EphId.Int64(), ReceptionID: eid.Source, RoundsList: makeRoundsList(rids...), + RoundURL: getRoundURL(rids[0]), } return json.Marshal(sr) @@ -517,6 +518,7 @@ func SearchUD(e2eID int, udContact []byte, cb UdSearchCallback, EphID: eid.EphId.Int64(), ReceptionID: eid.Source, RoundsList: makeRoundsList(rids...), + RoundURL: getRoundURL(rids[0]), } return json.Marshal(sr) diff --git a/cmd/callbacks.go b/cmd/callbacks.go index 79ea1931d349b3c056c29ce33a6dfdbddc60cb7e..058c79dd0430b81243a4077f167694b37a909c3d 100644 --- a/cmd/callbacks.go +++ b/cmd/callbacks.go @@ -27,6 +27,7 @@ import ( type authCallbacks struct { autoConfirm bool confCh chan *id.ID + reqCh chan *id.ID params xxdk.E2EParams } @@ -34,6 +35,7 @@ func makeAuthCallbacks(autoConfirm bool, params xxdk.E2EParams) *authCallbacks { return &authCallbacks{ autoConfirm: autoConfirm, confCh: make(chan *id.ID, 10), + reqCh: make(chan *id.ID, 10), params: params, } } @@ -44,7 +46,7 @@ func (a *authCallbacks) Request(requestor contact.Contact, msg := fmt.Sprintf("Authentication channel request from: %s\n", requestor.ID) jww.INFO.Printf(msg) - fmt.Printf(msg) + fmt.Print(msg) if a.autoConfirm { jww.INFO.Printf("Channel Request: %s", requestor.ID) @@ -55,8 +57,9 @@ func (a *authCallbacks) Request(requestor contact.Contact, } a.confCh <- requestor.ID + } else { + a.reqCh <- requestor.ID } - } func (a *authCallbacks) Confirm(requestor contact.Contact, @@ -72,7 +75,7 @@ func (a *authCallbacks) Reset(requestor contact.Contact, msg := fmt.Sprintf("Authentication channel reset from: %s\n", requestor.ID) jww.INFO.Printf(msg) - fmt.Printf(msg) + fmt.Print(msg) } func registerMessageListener(user *xxdk.E2e) chan receive.Message { diff --git a/cmd/root.go b/cmd/root.go index af4fa2ac28c5c716b82b7b7fa42e3f0127370040..81d197451d72494298a87613c71ec24a44c86d5f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,11 +14,12 @@ import ( "encoding/hex" "encoding/json" "fmt" - cryptoE2e "gitlab.com/elixxir/crypto/e2e" "io/ioutil" "log" "os" + cryptoE2e "gitlab.com/elixxir/crypto/e2e" + "github.com/pkg/profile" "strconv" @@ -76,8 +77,13 @@ var rootCmd = &cobra.Command{ cmixParams, e2eParams := initParams() - authCbs := makeAuthCallbacks( - viper.GetBool(unsafeChannelCreationFlag), e2eParams) + autoConfirm := viper.GetBool(unsafeChannelCreationFlag) + acceptChannels := viper.GetBool(acceptChannelFlag) + if acceptChannels { + autoConfirm = false + } + + authCbs := makeAuthCallbacks(autoConfirm, e2eParams) user := initE2e(cmixParams, e2eParams, authCbs) jww.INFO.Printf("Client Initialized...") @@ -96,12 +102,14 @@ var rootCmd = &cobra.Command{ recipientContact = readContact(destFile) recipientID = recipientContact.ID } else if destId == "0" || sendId == destId { - jww.INFO.Printf("Sending message to self") + jww.INFO.Printf("Sending message to self, " + + "this will timeout unless authrequest is sent") recipientID = receptionIdentity.ID recipientContact = receptionIdentity.GetContact() } else { recipientID = parseRecipient(destId) - jww.INFO.Printf("destId: %v\nrecipientId: %v", destId, recipientID) + jww.INFO.Printf("destId: %v\nrecipientId: %v", destId, + recipientID) } isPrecanPartner := isPrecanID(recipientID) @@ -151,11 +159,42 @@ var rootCmd = &cobra.Command{ // Send Messages msgBody := viper.GetString(messageFlag) + hasMsgs := true + if msgBody == "" { + hasMsgs = false + } time.Sleep(10 * time.Second) // Accept auth request for this recipient + authSecs := viper.GetUint(authTimeoutFlag) authConfirmed := false - if viper.GetBool(acceptChannelFlag) { + jww.INFO.Printf("Preexisting E2e partners: %+v", user.GetE2E().GetAllPartnerIDs()) + if user.GetE2E().HasAuthenticatedChannel(recipientID) { + jww.INFO.Printf("Authenticated channel already in "+ + "place for %s", recipientID) + authConfirmed = true + } else { + jww.INFO.Printf("No authenticated channel in "+ + "place for %s", recipientID) + } + + if acceptChannels && !authConfirmed { + for reqDone := false; !reqDone; { + select { + case reqID := <-authCbs.reqCh: + if recipientID.Cmp(reqID) { + reqDone = true + } else { + fmt.Printf( + "unexpected request:"+ + " %s", reqID) + } + case <-time.After(time.Duration(authSecs) * + time.Second): + fmt.Print("timed out on auth request") + reqDone = true + } + } // Verify that the confirmation message makes it to the // original sender if viper.GetBool(verifySendFlag) { @@ -171,16 +210,6 @@ var rootCmd = &cobra.Command{ authConfirmed = true } - jww.INFO.Printf("Preexisting E2e partners: %+v", user.GetE2E().GetAllPartnerIDs()) - if user.GetE2E().HasAuthenticatedChannel(recipientID) { - jww.INFO.Printf("Authenticated channel already in "+ - "place for %s", recipientID) - authConfirmed = true - } else { - jww.INFO.Printf("No authenticated channel in "+ - "place for %s", recipientID) - } - // Send unsafe messages or not? unsafe := viper.GetBool(unsafeFlag) sendAuthReq := viper.GetBool(sendAuthRequestFlag) @@ -200,7 +229,7 @@ var rootCmd = &cobra.Command{ authConfirmed = false } - if !unsafe && !authConfirmed { + if !unsafe && !authConfirmed && hasMsgs { // Signal for authConfirm callback in a separate thread go func() { for { @@ -216,16 +245,15 @@ var rootCmd = &cobra.Command{ scnt := uint(0) // Wait until authConfirmed - waitSecs := viper.GetUint(authTimeoutFlag) - for !authConfirmed && scnt < waitSecs { + for !authConfirmed && scnt < authSecs { time.Sleep(1 * time.Second) scnt++ } - if scnt == waitSecs { + if scnt == authSecs { jww.FATAL.Panicf("Could not confirm "+ "authentication channel for %s, "+ "waited %d seconds.", recipientID, - waitSecs) + authSecs) } jww.INFO.Printf("Authentication channel confirmation"+ " took %d seconds", scnt) @@ -278,6 +306,13 @@ var rootCmd = &cobra.Command{ wg := &sync.WaitGroup{} sendCnt := int(viper.GetUint(sendCountFlag)) + if !hasMsgs && sendCnt != 0 { + msg := "No message to send, please set your message" + + "or set sendCount to 0 to suppress this warning" + jww.WARN.Printf(msg) + fmt.Print(msg) + sendCnt = 0 + } wg.Add(sendCnt) go func() { sendDelay := time.Duration(viper.GetUint(sendDelayFlag)) @@ -556,7 +591,7 @@ func addAuthenticatedChannel(user *xxdk.E2e, recipientID *id.ID, msg := fmt.Sprintf("Adding authenticated channel for: %s\n", recipientID) jww.INFO.Printf(msg) - fmt.Printf(msg) + fmt.Print(msg) recipientContact := recipient diff --git a/cmix/gateway/hostPool.go b/cmix/gateway/hostPool.go index 49875776c2ff735520e7662eea9a14cdb3a44811..c37dbdf2fdcda4b8f9f8fac7f9339640809e40fe 100644 --- a/cmix/gateway/hostPool.go +++ b/cmix/gateway/hostPool.go @@ -247,7 +247,7 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, } } else { jww.WARN.Printf( - "Building new HostPool because no HostList stored: %+v", err) + "Building new HostPool because no HostList stored: %s", err.Error()) } // Build the initial HostPool and return diff --git a/cmix/identity/receptionID/store.go b/cmix/identity/receptionID/store.go index 37dfb8bba2212773c6c2de62fd3604ba3d307c1e..f5b96e7eeec37ea3623a2e5f24e3b902e5d75fda 100644 --- a/cmix/identity/receptionID/store.go +++ b/cmix/identity/receptionID/store.go @@ -61,7 +61,7 @@ func NewOrLoadStore(kv *versioned.KV) *Store { s, err := loadStore(kv) if err != nil { jww.WARN.Printf( - "ReceptionID store not found, creating a new one: %+v", err) + "ReceptionID store not found, creating a new one: %s", err.Error()) s = &Store{ active: []*registration{}, diff --git a/e2e/parse/partition/store.go b/e2e/parse/partition/store.go index 5ca9007a85a78f28b4566b2ec0be710f4305b0bc..713fc291a9bb530994c3598b26e595eea52a6b03 100644 --- a/e2e/parse/partition/store.go +++ b/e2e/parse/partition/store.go @@ -171,7 +171,7 @@ func (s *Store) loadActivePartitions() { defer s.mux.Unlock() obj, err := s.kv.Get(activePartitions, activePartitionVersion) if err != nil { - jww.DEBUG.Printf("Could not load active partitions: %+v", err) + jww.DEBUG.Printf("Could not load active partitions: %s", err.Error()) return } diff --git a/go.mod b/go.mod index e7ed636f1c67bc8a3fcd2c35d7dbc2f909fe6aaf..c2d7de066f1f9f4f8c9a4fb0410672fe24bd4f7f 100644 --- a/go.mod +++ b/go.mod @@ -14,12 +14,12 @@ require ( github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f - gitlab.com/elixxir/comms v0.0.4-0.20220907184530-d8eec143a1e8 - gitlab.com/elixxir/crypto v0.0.7-0.20220902165412-5c5e3e990e84 - gitlab.com/elixxir/ekv v0.2.1-0.20220901224437-ab4cbf94bf8b + gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd + gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af + gitlab.com/elixxir/ekv v0.2.1 gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6 - gitlab.com/xx_network/comms v0.0.4-0.20220902164216-e3272eb0efac - gitlab.com/xx_network/crypto v0.0.5-0.20220902182733-69aad094b487 + gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3 + gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287 gitlab.com/xx_network/primitives v0.0.4-0.20220809193445-9fc0a5209548 go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa @@ -29,7 +29,7 @@ require ( ) require ( - git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220829220442-4f51c27ab822 // indirect + git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220908170150-ef04339ffe65 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/badoux/checkmail v1.2.1 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -43,7 +43,6 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/compress v1.11.7 // indirect github.com/klauspost/cpuid/v2 v2.1.0 // indirect - github.com/ktr0731/grpc-web-go-client v0.2.8 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/go.sum b/go.sum index d1e34be2430ef1d0d936bd10bb5366651164bdc2..1a159e20639d8254167e431e61db4d30ec922634 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220829220442-4f51c27ab822 h1:s2Lxh7KcUbACkGKI2SqaGTeaKdO6GCtfU6/F12Q3Hp4= -git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220829220442-4f51c27ab822/go.mod h1:GrZ4Fy3YfaNe7RLnai+H+jE+fwqFA90tVmYOpKK90Yg= +git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220908170150-ef04339ffe65 h1:ksB3ZiMeFplqlaCjMDqKegbGzDZdS5pU0Z5GgFeBdZ0= +git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220908170150-ef04339ffe65/go.mod h1:uFKw2wmgtlYMdiIm08dM0Vj4XvX9ZKVCj71c8O7SAPo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -629,14 +629,14 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40= gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/comms v0.0.4-0.20220907184530-d8eec143a1e8 h1:xbJBQdMdB+mMsKIVXqx7eAWzZmulA3KdRhlbyAk0NIc= -gitlab.com/elixxir/comms v0.0.4-0.20220907184530-d8eec143a1e8/go.mod h1:xE9NKMAxzvTXmyJ5BMjBZFWMPfvQpw3rB7be2VYvU8E= +gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd h1:2nHE7EoptSTBFjCxMeAveKT6urbguCwgg8Jx7XYEVe4= +gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd/go.mod h1:AO6XkMhaHJW8eXlgL5m3UUcJqsSP8F5Wm1GX+wyq/rw= 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.20220902165412-5c5e3e990e84 h1:sQCoZ+w09X/8LjQo9m7bg51IoX4AXSrXBtU8eblM/II= -gitlab.com/elixxir/crypto v0.0.7-0.20220902165412-5c5e3e990e84/go.mod h1:Sfb4ceEoWLLYNT8V6isZa5k87NfUecM8i34Z+hDYTgg= -gitlab.com/elixxir/ekv v0.2.1-0.20220901224437-ab4cbf94bf8b h1:0cN/WHbFaonJEN+odqyjtSrkuEbBnEnWfggT91svPGc= -gitlab.com/elixxir/ekv v0.2.1-0.20220901224437-ab4cbf94bf8b/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU= +gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af h1:L1eOTS6m8dlCheAFOf/S3C+IcORd2R8f5qdyVRVelWA= +gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok= +gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is= +gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU= 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= @@ -646,13 +646,12 @@ gitlab.com/elixxir/primitives v0.0.3-0.20220810173935-592f34a88326/go.mod h1:9Bb gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6 h1:/cxxZBP5jTPDpC3zgOx9vV1ojmJyG8pYtkl3IbcewNQ= gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6/go.mod h1:9Bb2+u+CDSwsEU5Droo6saDAXuBDvLRjexpBhPAYxhA= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= -gitlab.com/xx_network/comms v0.0.4-0.20220902164216-e3272eb0efac h1:LitOitWXN/4VNXe4dfR7Kp0reqoYmduVaExrmT1pylg= -gitlab.com/xx_network/comms v0.0.4-0.20220902164216-e3272eb0efac/go.mod h1:S5p4aZTz1rpN27E36U+nCQbqw6ZqQfnJNeFS54DnnJ0= +gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3 h1:7mReTvEUVoI5Qpltcmbodc/j6rdPPHDIvenY4ZmWP7o= +gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3/go.mod h1:E2QKOKyPKLRjLUwMxgZpTKueEsHDEqshfqOHJ54ttxU= gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE= gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= -gitlab.com/xx_network/crypto v0.0.5-0.20220729193517-1e5e96f39f6e/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= -gitlab.com/xx_network/crypto v0.0.5-0.20220902182733-69aad094b487 h1:CsnSSVw2o4PXFKo4OFbivjoLA0RE/Ktx9hxvtQRGNjw= -gitlab.com/xx_network/crypto v0.0.5-0.20220902182733-69aad094b487/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287 h1:Jd71F8f/8rieWybMqkxpKKZVVyGkeCNZWZcviGGnQ9A= +gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= 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= diff --git a/ud/channelIDTracking.go b/ud/channelIDTracking.go index 65d216190e5e6126c39e9bbcb178467360877cce..25a870ee2995a9c6385568a0a04615fc989c3ce1 100644 --- a/ud/channelIDTracking.go +++ b/ud/channelIDTracking.go @@ -25,7 +25,6 @@ const ( graceDuration = time.Hour ) -var startChannelNameServiceOnce sync.Once var ErrChannelLeaseSignature = errors.New("failure to validate lease signature") // loadRegistrationDisk loads a registrationDisk from the kv @@ -355,24 +354,26 @@ func (c *clientIDTracker) requestChannelLease() (int64, []byte, error) { // StartChannelNameService creates a new clientIDTracker // and returns a reference to it's type as the NameService interface. -// However it's scheduler thread isn't started until it's Start +// However, it's scheduler thread isn't started until it's Start // method is called. -func (m *Manager) StartChannelNameService() channels.NameService { - udPubKeyBytes := m.user.GetCmix().GetInstance().GetPartialNdf().Get().UDB.DhPubKey - var service channels.NameService - username, err := m.store.GetUsername() - if err != nil { - jww.FATAL.Panic(err) - } - startChannelNameServiceOnce.Do(func() { - service = newclientIDTracker( +func (m *Manager) StartChannelNameService() (channels.NameService, error) { + + if m.nameService == nil { + udPubKeyBytes := m.user.GetCmix().GetInstance().GetPartialNdf().Get().UDB.DhPubKey + username, err := m.store.GetUsername() + if err != nil { + return nil, err + } + m.nameService = newclientIDTracker( m.comms, m.ud.host, username, m.getKv(), m.user.GetReceptionIdentity(), - ed25519.PublicKey(udPubKeyBytes), + udPubKeyBytes, m.getRng()) - }) - return service + + } + + return m.nameService, nil } diff --git a/ud/manager.go b/ud/manager.go index 89edc765fa7820e761dd824b56dd4fb85cfdec4a..fd57a886b7046c09cfb33bf6a58adc0d752730e5 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -44,6 +44,10 @@ type Manager struct { // ud is the tracker for the contact information of the specified UD server. // This information is specified in Manager's constructors (NewOrLoad and NewManagerFromBackup). ud *userDiscovery + + // nameService adheres to the channels.NameService interface. This is + // implemented using the clientIDTracker. + nameService *clientIDTracker } // NewOrLoad loads an existing Manager from storage or creates a